Skip to content

Discussion on the what the public API should be #4

@ZegCricket

Description

@ZegCricket

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?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions