On the Harp SRM from yesterday (23/01/2026), two concepts for the public API were presented and discussed, although a decision on what concept to follow is yet to be made.
First concept
The first concept consists on classes that represent a HarpMessage from a specific register. This includes the message itself as well as the correct payload for that register (especially registers with payloadSpec).
Class Example
class WhoAmI(HarpMessage[int]):
ADDRESS: int = 0
PAYLOAD_TYPE: PayloadType = PayloadType.U16
def __init__(
self,
payload: Optional[int] = None,
message_type: MessageType = MessageType.READ,
# This can be removed since we can infer this through the timestamp parameter
# payload_type: Literal[
# PayloadType.U16, PayloadType.TIMESTAMPED_U16
#] = PayloadType.U16,
*,
timestamp: Optional[float] = None,
port: int = 255,
):
super().__init__(
message_type, self.ADDRESS, self.PAYLOAD_TYPE, payload, timestamp, port
)
Usage example
from harp.serial import Device
from harp.protocol import WhoAmI
with Device("/dev/ttyUSB0") as device:
# Always returns HarpMessage
reply_msg = device.send(WhoAmI())
print(reply_msg) # This prints "Type: READ \r\n Length....." (__str__ method from HarpMessage)
print(reply_msg.payload) # This prints "1236"
# or alternatively, get the value itself
reply_value = device.read_who_am_i()
print(reply_value) # This prints "1236"
Second concept
The second concept consists on creating an instance of a Spec (or another name) that has the register's address, payload_type and a "decode" and "encode" methods.
Spec example
@dataclass(frozen=True)
class Spec(Generic[T]):
addr: int
payload_type: PayloadType
decode: Callable[[Any], T] # from payload (int|float|list) -> T
encode: Callable[[T], Any] # from T -> payload (int|float|list
The creation of a Spec for the WhoAmI register is as follows:
def _id(x):
return x
COMMON_SPECS = {
CommonRegisters.WHO_AM_I: Spec(
addr=0,
payload_type=PayloadType.U16,
decode=int,
encode=_id,
),
...
}
Usage example
from harp.serial import Device
from harp.protocol import WhoAmI
with Device("/dev/ttyUSB0") as device:
# There's a read_reg and write_reg that return the HarpMessage
reply_msg = device.read_reg(CommonRegisters.WHO_AM_I)
print(reply_msg) # This prints "Type: READ \r\n Length....." (__str__ method from HarpMessage)
print(reply_msg.payload) # This prints "1236"
# or alternatively, get the value itself
# internally, this method will call "decode" over the corresponding Spec
reply_value = device.read_who_am_i()
print(reply_value) # This prints "1236"
Things to consider:
-
Try to eventually unify with harp-python, with focus on vectorization on load of .bin files
-
When developing a new device, when there's still no specific device package, we can simply use the send method to send a custom HarpMessage for a register of that new device.
-
Ease of conversion from and to payloadSpec
Any further suggestions? Which approach seems better so that we can direct our efforts to that one?
On the Harp SRM from yesterday (23/01/2026), two concepts for the public API were presented and discussed, although a decision on what concept to follow is yet to be made.
First concept
The first concept consists on classes that represent a
HarpMessagefrom a specific register. This includes the message itself as well as the correct payload for that register (especially registers with payloadSpec).Class Example
Usage example
Second concept
The second concept consists on creating an instance of a Spec (or another name) that has the register's address, payload_type and a "decode" and "encode" methods.
Spec example
The creation of a Spec for the WhoAmI register is as follows:
Usage example
Things to consider:
Try to eventually unify with harp-python, with focus on vectorization on load of .bin files
When developing a new device, when there's still no specific device package, we can simply use the
sendmethod to send a customHarpMessagefor a register of that new device.Ease of conversion
fromandtopayloadSpecAny further suggestions? Which approach seems better so that we can direct our efforts to that one?