diff --git a/src/packages/harp-device/src/harp/device/core/__init__.py b/src/packages/harp-device/src/harp/device/core/__init__.py index 7e3dee2..a0b4ab3 100644 --- a/src/packages/harp-device/src/harp/device/core/__init__.py +++ b/src/packages/harp-device/src/harp/device/core/__init__.py @@ -1,34 +1,37 @@ +# This file was automatically generated and should not be edited directly. +# To make changes, edit the device metadata and regenerate the interface. + """The core register set every Harp device carries, and its address space.""" -from ._register_map import REGISTER_MAP -from ._registers import ( - AssemblyVersion, - ClockConfiguration, - ClockConfigurationFlags, - ClockConfigurationPayload, - CoreVersionHigh, - CoreVersionLow, - DeviceName, - DeviceNamePayload, - EnableFlag, - FirmwareVersionHigh, - FirmwareVersionLow, - HardwareVersionHigh, - HardwareVersionLow, - OperationControl, - OperationControlPayload, - OperationMode, - ResetDevice, - ResetDevicePayload, - ResetFlags, - SerialNumber, - TimestampMicroseconds, - TimestampSeconds, - WhoAmI, +import enum +from typing import Any, ClassVar + +import numpy as np +from harp.protocol import ( + AnonymousPayload, + BitMask, + BoolConverter, + Field, + GroupMask, + PayloadType, + RegisterBase, + RegisterU16, + RegisterU32, + RegisterU8, + StringConverter, + StructPayload, ) + __all__ = [ - "REGISTER_MAP", + "ResetFlags", + "ClockConfigurationFlags", + "OperationMode", + "EnableFlag", + "OperationControlPayload", + "ResetDevicePayload", + "DeviceNamePayload", + "ClockConfigurationPayload", "WhoAmI", "HardwareVersionHigh", "HardwareVersionLow", @@ -40,16 +43,236 @@ "TimestampSeconds", "TimestampMicroseconds", "OperationControl", - "OperationControlPayload", - "OperationMode", "ResetDevice", - "ResetDevicePayload", - "ResetFlags", "DeviceName", - "DeviceNamePayload", - "EnableFlag", - "ClockConfiguration", - "ClockConfigurationFlags", - "ClockConfigurationPayload", "SerialNumber", + "ClockConfiguration", + "REGISTER_MAP", ] + + +class ResetFlags(enum.IntFlag): + """Specifies the behavior of the non-volatile registers when resetting the device.""" + + RESTORE_DEFAULT = 0x1 + """The device will boot with all the registers reset to their default factory values.""" + + RESTORE_EEPROM = 0x2 + """The device will boot and restore all the registers to the values stored in non-volatile memory.""" + + SAVE = 0x4 + """The device will boot and save all the current register values to non-volatile memory.""" + + RESTORE_NAME = 0x8 + """The device will boot with the default device name.""" + + UPDATE_FIRMWARE = 0x20 + """The device will enter firmware update mode.""" + + BOOT_FROM_DEFAULT = 0x40 + """Specifies that the device has booted from default factory values.""" + + BOOT_FROM_EEPROM = 0x80 + """Specifies that the device has booted from non-volatile values stored in EEPROM.""" + + +class ClockConfigurationFlags(enum.IntFlag): + """Specifies configuration flags for the device synchronization clock.""" + + CLOCK_REPEATER = 0x1 + """The device will repeat the clock synchronization signal to the clock output connector, if available.""" + + CLOCK_GENERATOR = 0x2 + """The device resets and generates the clock synchronization signal on the clock output connector, if available.""" + + REPEATER_CAPABILITY = 0x8 + """Specifies the device has the capability to repeat the clock synchronization signal to the clock output connector.""" + + GENERATOR_CAPABILITY = 0x10 + """Specifies the device has the capability to generate the clock synchronization signal to the clock output connector.""" + + CLOCK_UNLOCK = 0x40 + """The device will unlock the timestamp register counter and will accept commands to set new timestamp values.""" + + CLOCK_LOCK = 0x80 + """The device will lock the timestamp register counter and will not accept commands to set new timestamp values.""" + + +class OperationMode(enum.IntEnum): + """Specifies the operation mode of the device.""" + + STANDBY = 0 + """Disable all event reporting on the device.""" + + ACTIVE = 1 + """Event detection is enabled. Only enabled events are reported by the device.""" + + SPEED = 3 + """The device enters speed mode.""" + + +class EnableFlag(enum.IntEnum): + """Specifies whether a specific register flag is enabled or disabled.""" + + DISABLED = 0 + """Specifies that the flag is disabled.""" + + ENABLED = 1 + """Specifies that the flag is enabled.""" + + +class OperationControlPayload(StructPayload[np.uint8]): + """Represents the payload of the OperationControl register.""" + + operation_mode: OperationMode = GroupMask(enum=OperationMode, mask=0x3) + """Specifies the operation mode of the device.""" + + dump_registers: bool = Field(BoolConverter(), mask=0x8) + """Specifies whether the device should report the content of all registers on initialization.""" + + mute_replies: bool = Field(BoolConverter(), mask=0x10) + """Specifies whether the replies to all commands will be muted, i.e. not sent by the device.""" + + visual_indicators: EnableFlag = GroupMask(enum=EnableFlag, mask=0x20) + """Specifies the state of all visual indicators on the device.""" + + operation_led: EnableFlag = GroupMask(enum=EnableFlag, mask=0x40) + """Specifies whether the device state LED should report the operation mode of the device.""" + + heartbeat: EnableFlag = GroupMask(enum=EnableFlag, mask=0x80) + """Specifies whether the device should report the content of the seconds register each second.""" + + +class ResetDevicePayload(AnonymousPayload[np.uint8]): + """Represents the payload of the ResetDevice register.""" + + __value__: ResetFlags = BitMask(enum=ResetFlags) + + +class DeviceNamePayload(AnonymousPayload[np.uint8]): + """Represents the payload of the DeviceName register.""" + + __value__: str = Field(StringConverter(25)) + + +class ClockConfigurationPayload(AnonymousPayload[np.uint8]): + """Represents the payload of the ClockConfiguration register.""" + + __value__: ClockConfigurationFlags = BitMask(enum=ClockConfigurationFlags) + + +class WhoAmI(RegisterU16): + """Specifies the identity class of the device.""" + + address: ClassVar[int] = 0 + + +class HardwareVersionHigh(RegisterU8): + """Specifies the major hardware version of the device.""" + + address: ClassVar[int] = 1 + + +class HardwareVersionLow(RegisterU8): + """Specifies the minor hardware version of the device.""" + + address: ClassVar[int] = 2 + + +class AssemblyVersion(RegisterU8): + """Specifies the version of the assembled components in the device.""" + + address: ClassVar[int] = 3 + + +class CoreVersionHigh(RegisterU8): + """Specifies the major version of the Harp core implemented by the device.""" + + address: ClassVar[int] = 4 + + +class CoreVersionLow(RegisterU8): + """Specifies the minor version of the Harp core implemented by the device.""" + + address: ClassVar[int] = 5 + + +class FirmwareVersionHigh(RegisterU8): + """Specifies the major version of the Harp core implemented by the device.""" + + address: ClassVar[int] = 6 + + +class FirmwareVersionLow(RegisterU8): + """Specifies the minor version of the Harp core implemented by the device.""" + + address: ClassVar[int] = 7 + + +class TimestampSeconds(RegisterU32): + """Stores the integral part of the system timestamp, in seconds.""" + + address: ClassVar[int] = 8 + + +class TimestampMicroseconds(RegisterU16): + """Stores the fractional part of the system timestamp, in microseconds.""" + + address: ClassVar[int] = 9 + + +class OperationControl(RegisterBase[OperationControlPayload]): + """Stores the configuration mode of the device.""" + + address: ClassVar[int] = 10 + payload_type: ClassVar[PayloadType] = PayloadType.U8 + payload_class = OperationControlPayload + + +class ResetDevice(RegisterBase[ResetFlags]): + """Resets the device and saves non-volatile registers.""" + + address: ClassVar[int] = 11 + payload_type: ClassVar[PayloadType] = PayloadType.U8 + payload_class = ResetDevicePayload + + +class DeviceName(RegisterBase[str]): + """Stores the user-specified device name.""" + + address: ClassVar[int] = 12 + payload_type: ClassVar[PayloadType] = PayloadType.U8 + payload_class = DeviceNamePayload + + +class SerialNumber(RegisterU16): + """Specifies the unique serial number of the device.""" + + address: ClassVar[int] = 13 + + +class ClockConfiguration(RegisterBase[ClockConfigurationFlags]): + """Specifies the configuration for the device synchronization clock.""" + + address: ClassVar[int] = 14 + payload_type: ClassVar[PayloadType] = PayloadType.U8 + payload_class = ClockConfigurationPayload + + +REGISTER_MAP: dict[int, type[RegisterBase[Any]]] = { + 0: WhoAmI, + 1: HardwareVersionHigh, + 2: HardwareVersionLow, + 3: AssemblyVersion, + 4: CoreVersionHigh, + 5: CoreVersionLow, + 6: FirmwareVersionHigh, + 7: FirmwareVersionLow, + 8: TimestampSeconds, + 9: TimestampMicroseconds, + 10: OperationControl, + 11: ResetDevice, + 12: DeviceName, + 13: SerialNumber, + 14: ClockConfiguration, +} diff --git a/src/packages/harp-device/src/harp/device/core/_register_map.py b/src/packages/harp-device/src/harp/device/core/_register_map.py deleted file mode 100644 index 560bd83..0000000 --- a/src/packages/harp-device/src/harp/device/core/_register_map.py +++ /dev/null @@ -1,48 +0,0 @@ -"""Address to register-class map for the core Harp registers. - -Downstream device packages spread this into their own map:: - - from harp.device.core import REGISTER_MAP as _CORE_REGISTER_MAP - - REGISTER_MAP = {**_CORE_REGISTER_MAP, 32: DigitalInputState, ...} -""" - -from typing import Any - -from harp.protocol import RegisterBase - -from ._registers import ( - AssemblyVersion, - ClockConfiguration, - CoreVersionHigh, - CoreVersionLow, - DeviceName, - FirmwareVersionHigh, - FirmwareVersionLow, - HardwareVersionHigh, - HardwareVersionLow, - OperationControl, - ResetDevice, - SerialNumber, - TimestampMicroseconds, - TimestampSeconds, - WhoAmI, -) - -REGISTER_MAP: dict[int, type[RegisterBase[Any]]] = { - 0: WhoAmI, - 1: HardwareVersionHigh, - 2: HardwareVersionLow, - 3: AssemblyVersion, - 4: CoreVersionHigh, - 5: CoreVersionLow, - 6: FirmwareVersionHigh, - 7: FirmwareVersionLow, - 8: TimestampSeconds, - 9: TimestampMicroseconds, - 10: OperationControl, - 11: ResetDevice, - 12: DeviceName, - 13: SerialNumber, - 14: ClockConfiguration, -} diff --git a/src/packages/harp-device/src/harp/device/core/_registers.py b/src/packages/harp-device/src/harp/device/core/_registers.py deleted file mode 100644 index b489da6..0000000 --- a/src/packages/harp-device/src/harp/device/core/_registers.py +++ /dev/null @@ -1,210 +0,0 @@ -# This file was automatically generated and should not be edited directly. -# To make changes, edit the device metadata and regenerate the interface. - -import enum -from typing import ClassVar - -import numpy as np -from harp.protocol import ( - AnonymousPayload, - BitMask, - BoolConverter, - Field, - GroupMask, - PayloadType, - RegisterBase, - RegisterU16, - RegisterU32, - RegisterU8, - StringConverter, - StructPayload, -) - - -class ResetFlags(enum.IntFlag): - """Specifies the behavior of the non-volatile registers when resetting the device.""" - - RESTORE_DEFAULT = 0x1 - """The device will boot with all the registers reset to their default factory values.""" - RESTORE_EEPROM = 0x2 - """The device will boot and restore all the registers to the values stored in non-volatile memory.""" - SAVE = 0x4 - """The device will boot and save all the current register values to non-volatile memory.""" - RESTORE_NAME = 0x8 - """The device will boot with the default device name.""" - UPDATE_FIRMWARE = 0x20 - """The device will enter firmware update mode.""" - BOOT_FROM_DEFAULT = 0x40 - """Specifies that the device has booted from default factory values.""" - BOOT_FROM_EEPROM = 0x80 - """Specifies that the device has booted from non-volatile values stored in EEPROM.""" - - -class ClockConfigurationFlags(enum.IntFlag): - """Specifies configuration flags for the device synchronization clock.""" - - CLOCK_REPEATER = 0x1 - """The device will repeat the clock synchronization signal to the clock output connector, if available.""" - CLOCK_GENERATOR = 0x2 - """The device resets and generates the clock synchronization signal on the clock output connector, if available.""" - REPEATER_CAPABILITY = 0x8 - """Specifies the device has the capability to repeat the clock synchronization signal to the clock output connector.""" - GENERATOR_CAPABILITY = 0x10 - """Specifies the device has the capability to generate the clock synchronization signal to the clock output connector.""" - CLOCK_UNLOCK = 0x40 - """The device will unlock the timestamp register counter and will accept commands to set new timestamp values.""" - CLOCK_LOCK = 0x80 - """The device will lock the timestamp register counter and will not accept commands to set new timestamp values.""" - - -class OperationMode(enum.IntEnum): - """Specifies the operation mode of the device.""" - - STANDBY = 0 - """Disable all event reporting on the device.""" - ACTIVE = 1 - """Event detection is enabled. Only enabled events are reported by the device.""" - SPEED = 3 - """The device enters speed mode.""" - - -class EnableFlag(enum.IntEnum): - """Specifies whether a specific register flag is enabled or disabled.""" - - DISABLED = 0 - """Specifies that the flag is disabled.""" - ENABLED = 1 - """Specifies that the flag is enabled.""" - - -class OperationControlPayload(StructPayload[np.uint8]): - """Represents the payload of the OperationControl register.""" - - operation_mode: OperationMode = GroupMask(enum=OperationMode, mask=0x3) - """Specifies the operation mode of the device.""" - dump_registers: bool = Field(BoolConverter(), mask=0x8) - """Specifies whether the device should report the content of all registers on initialization.""" - mute_replies: bool = Field(BoolConverter(), mask=0x10) - """Specifies whether the replies to all commands will be muted, i.e. not sent by the device.""" - visual_indicators: EnableFlag = GroupMask(enum=EnableFlag, mask=0x20) - """Specifies the state of all visual indicators on the device.""" - operation_led: EnableFlag = GroupMask(enum=EnableFlag, mask=0x40) - """Specifies whether the device state LED should report the operation mode of the device.""" - heartbeat: EnableFlag = GroupMask(enum=EnableFlag, mask=0x80) - """Specifies whether the device should report the content of the seconds register each second.""" - - -class ResetDevicePayload(AnonymousPayload[np.uint8]): - """Represents the payload of the ResetDevice register.""" - - __value__: ResetFlags = BitMask(enum=ResetFlags) - - -class DeviceNamePayload(AnonymousPayload[np.uint8]): - """Represents the payload of the DeviceName register.""" - - __value__: str = Field(StringConverter(25)) - - -class ClockConfigurationPayload(AnonymousPayload[np.uint8]): - """Represents the payload of the ClockConfiguration register.""" - - __value__: ClockConfigurationFlags = BitMask(enum=ClockConfigurationFlags) - - -class WhoAmI(RegisterU16): - """Specifies the identity class of the device.""" - - address: ClassVar[int] = 0 - - -class HardwareVersionHigh(RegisterU8): - """Specifies the major hardware version of the device.""" - - address: ClassVar[int] = 1 - - -class HardwareVersionLow(RegisterU8): - """Specifies the minor hardware version of the device.""" - - address: ClassVar[int] = 2 - - -class AssemblyVersion(RegisterU8): - """Specifies the version of the assembled components in the device.""" - - address: ClassVar[int] = 3 - - -class CoreVersionHigh(RegisterU8): - """Specifies the major version of the Harp core implemented by the device.""" - - address: ClassVar[int] = 4 - - -class CoreVersionLow(RegisterU8): - """Specifies the minor version of the Harp core implemented by the device.""" - - address: ClassVar[int] = 5 - - -class FirmwareVersionHigh(RegisterU8): - """Specifies the major version of the Harp core implemented by the device.""" - - address: ClassVar[int] = 6 - - -class FirmwareVersionLow(RegisterU8): - """Specifies the minor version of the Harp core implemented by the device.""" - - address: ClassVar[int] = 7 - - -class TimestampSeconds(RegisterU32): - """Stores the integral part of the system timestamp, in seconds.""" - - address: ClassVar[int] = 8 - - -class TimestampMicroseconds(RegisterU16): - """Stores the fractional part of the system timestamp, in microseconds.""" - - address: ClassVar[int] = 9 - - -class OperationControl(RegisterBase[OperationControlPayload]): - """Stores the configuration mode of the device.""" - - address: ClassVar[int] = 10 - payload_type: ClassVar[PayloadType] = PayloadType.U8 - payload_class = OperationControlPayload - - -class ResetDevice(RegisterBase[ResetFlags]): - """Resets the device and saves non-volatile registers.""" - - address: ClassVar[int] = 11 - payload_type: ClassVar[PayloadType] = PayloadType.U8 - payload_class = ResetDevicePayload - - -class DeviceName(RegisterBase[str]): - """Stores the user-specified device name.""" - - address: ClassVar[int] = 12 - payload_type: ClassVar[PayloadType] = PayloadType.U8 - payload_class = DeviceNamePayload - - -class SerialNumber(RegisterU16): - """Specifies the unique serial number of the device.""" - - address: ClassVar[int] = 13 - - -class ClockConfiguration(RegisterBase[ClockConfigurationFlags]): - """Specifies the configuration for the device synchronization clock.""" - - address: ClassVar[int] = 14 - payload_type: ClassVar[PayloadType] = PayloadType.U8 - payload_class = ClockConfigurationPayload diff --git a/src/packages/harp-device/src/harp/device/schema/_model.py b/src/packages/harp-device/src/harp/device/schema/_model.py index 14d6271..5e6ac67 100644 --- a/src/packages/harp-device/src/harp/device/schema/_model.py +++ b/src/packages/harp-device/src/harp/device/schema/_model.py @@ -211,6 +211,9 @@ class Register(BaseModel): class Registers(BaseModel): """A bare register collection, a header-less ``device.yml`` fragment.""" + description: Optional[str] = Field( + None, description="A summary description of the register interface." + ) registers: Dict[str, Register] = Field( ..., description="The collection of registers implementing the device function." ) diff --git a/src/packages/harp-device/src/harp/device/schema/_module.py b/src/packages/harp-device/src/harp/device/schema/_module.py index 259136f..5a7170c 100644 --- a/src/packages/harp-device/src/harp/device/schema/_module.py +++ b/src/packages/harp-device/src/harp/device/schema/_module.py @@ -84,7 +84,8 @@ def create_device_module( name, so :class:`~harp.data.DatasetReader` matches files by it; * ``__name__``, the same name, falling back to ``"Device"`` so the module is never anonymous. This names the module rather than the device, and is not part of what - a device module promises. + a device module promises; + * ``__doc__``, the optional ``description`` of the schema. Because the names come from the schema at runtime they don't autocomplete, and each resolves as ``Any`` rather than its own type. A generated device package is @@ -112,7 +113,7 @@ def create_device_module( for declaration in contents.values(): declaration.__module__ = module_name - module = DeviceModule(module_name, f"Harp registers for {module_name}, from a schema.") + module = DeviceModule(module_name, device.description) vars(module).update( contents, DEVICE_NAME=device_name, diff --git a/tests/assets/common.yml b/tests/assets/core.yml similarity index 56% rename from tests/assets/common.yml rename to tests/assets/core.yml index f32a34f..2610747 100644 --- a/tests/assets/common.yml +++ b/tests/assets/core.yml @@ -1,4 +1,6 @@ -# yaml-language-server: $schema=registers.json +# yaml-language-server: $schema=https://harp-tech.org/draft-02/schema/registers.json +description: The core register set every Harp device carries, and its address space. +device: Tests registers: WhoAmI: address: 0 @@ -44,14 +46,14 @@ registers: address: 8 type: U32 access: [Read, Write, Event] - description: Stores the integral part of the system timestamp, in seconds. volatile: true + description: Stores the integral part of the system timestamp, in seconds. TimestampMicroseconds: address: 9 type: U16 access: Read - description: Stores the fractional part of the system timestamp, in microseconds. volatile: true + description: Stores the fractional part of the system timestamp, in microseconds. OperationControl: address: 10 type: U8 @@ -59,29 +61,29 @@ registers: description: Stores the configuration mode of the device. payloadSpec: OperationMode: - description: Specifies the operation mode of the device. - maskType: OperationMode mask: 0x3 + maskType: OperationMode + description: Specifies the operation mode of the device. DumpRegisters: - description: Specifies whether the device should report the content of all registers on initialization. - interfaceType: bool mask: 0x8 - MuteReplies: - description: Specifies whether the replies to all commands will be muted, i.e. not sent by the device. interfaceType: bool + description: Specifies whether the device should report the content of all registers on initialization. + MuteReplies: mask: 0x10 + interfaceType: bool + description: Specifies whether the replies to all commands will be muted, i.e. not sent by the device. VisualIndicators: - description: Specifies the state of all visual indicators on the device. - maskType: LedState mask: 0x20 + maskType: EnableFlag + description: Specifies the state of all visual indicators on the device. OperationLed: - description: Specifies whether the device state LED should report the operation mode of the device. - maskType: LedState mask: 0x40 - Heartbeat: - description: Specifies whether the device should report the content of the seconds register each second. maskType: EnableFlag + description: Specifies whether the device state LED should report the operation mode of the device. + Heartbeat: mask: 0x80 + maskType: EnableFlag + description: Specifies whether the device should report the content of the seconds register each second. ResetDevice: address: 11 type: U8 @@ -93,6 +95,7 @@ registers: type: U8 length: 25 access: Write + interfaceType: string description: Stores the user-specified device name. SerialNumber: address: 13 @@ -105,41 +108,77 @@ registers: access: Write maskType: ClockConfigurationFlags description: Specifies the configuration for the device synchronization clock. -groupMasks: - OperationMode: - description: Specifies the operation mode of the device. - values: - Standby: {value: 0, description: Disable all event reporting on the device.} - Active: {value: 1, description: Event detection is enabled. Only enabled events are reported by the device.} - Speed: {value: 3, description: The device enters speed mode.} - EnableFlag: - description: Specifies whether a specific register flag is enabled or disabled. - values: - Disabled: {value: 0, description: Specifies that the flag is disabled.} - Enabled: {value: 1, description: Specifies that the flag is enabled.} - LedState: - description: Specifies the state of an LED on the device. - values: - Off: {value: 0, description: Specifies that the LED is off.} - On: {value: 1, description: Specifies that the LED is on.} bitMasks: ResetFlags: description: Specifies the behavior of the non-volatile registers when resetting the device. bits: - None: {value: 0, description: All reset flags are cleared.} - RestoreDefault: {value: 0x1, description: The device will boot with all the registers reset to their default factory values.} - RestoreEeprom: {value: 0x2, description: The device will boot and restore all the registers to the values stored in non-volatile memory.} - Save: {value: 0x4, description: The device will boot and save all the current register values to non-volatile memory.} - RestoreName: {value: 0x8, description: The device will boot with the default device name.} - BootFromDefault: {value: 0x40, description: Specifies that the device has booted from default factory values.} - BootFromEeprom: {value: 0x80, description: Specifies that the device has booted from non-volatile values stored in EEPROM.} + None: + value: 0x0 + description: All reset flags are cleared. + RestoreDefault: + value: 0x1 + description: The device will boot with all the registers reset to their default factory values. + RestoreEeprom: + value: 0x2 + description: The device will boot and restore all the registers to the values stored in non-volatile memory. + Save: + value: 0x4 + description: The device will boot and save all the current register values to non-volatile memory. + RestoreName: + value: 0x8 + description: The device will boot with the default device name. + UpdateFirmware: + value: 0x20 + description: The device will enter firmware update mode. + BootFromDefault: + value: 0x40 + description: Specifies that the device has booted from default factory values. + BootFromEeprom: + value: 0x80 + description: Specifies that the device has booted from non-volatile values stored in EEPROM. ClockConfigurationFlags: description: Specifies configuration flags for the device synchronization clock. bits: - None: {value: 0, description: All clock configuration flags are cleared.} - ClockRepeater: {value: 0x1, description: "The device will repeat the clock synchronization signal to the clock output connector, if available."} - ClockGenerator: {value: 0x2, description: "The device resets and generates the clock synchronization signal on the clock output connector, if available."} - RepeaterCapability: {value: 0x8, description: Specifies the device has the capability to repeat the clock synchronization signal to the clock output connector.} - GeneratorCapability: {value: 0x10, description: Specifies the device has the capability to generate the clock synchronization signal to the clock output connector.} - ClockUnlock: {value: 0x40, description: The device will unlock the timestamp register counter and will accept commands to set new timestamp values.} - ClockLock: {value: 0x80, description: The device will lock the timestamp register counter and will not accept commands to set new timestamp values.} \ No newline at end of file + None: + value: 0x0 + description: All clock configuration flags are cleared. + ClockRepeater: + value: 0x1 + description: The device will repeat the clock synchronization signal to the clock output connector, if available. + ClockGenerator: + value: 0x2 + description: The device resets and generates the clock synchronization signal on the clock output connector, if available. + RepeaterCapability: + value: 0x8 + description: Specifies the device has the capability to repeat the clock synchronization signal to the clock output connector. + GeneratorCapability: + value: 0x10 + description: Specifies the device has the capability to generate the clock synchronization signal to the clock output connector. + ClockUnlock: + value: 0x40 + description: The device will unlock the timestamp register counter and will accept commands to set new timestamp values. + ClockLock: + value: 0x80 + description: The device will lock the timestamp register counter and will not accept commands to set new timestamp values. +groupMasks: + OperationMode: + description: Specifies the operation mode of the device. + values: + Standby: + value: 0x0 + description: Disable all event reporting on the device. + Active: + value: 0x1 + description: Event detection is enabled. Only enabled events are reported by the device. + Speed: + value: 0x3 + description: The device enters speed mode. + EnableFlag: + description: Specifies whether a specific register flag is enabled or disabled. + values: + Disabled: + value: 0x0 + description: Specifies that the flag is disabled. + Enabled: + value: 0x1 + description: Specifies that the flag is enabled. diff --git a/tests/conftest.py b/tests/conftest.py index ed31099..9a9f45b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -22,6 +22,6 @@ def device_yml() -> str: @pytest.fixture(scope="session") -def common_yml() -> str: - """The Harp common (core) register set ``common.yml`` as text.""" - return (ASSETS / "common.yml").read_text() +def core_yml() -> str: + """The generators core metadata ``core.yml`` as text.""" + return (ASSETS / "core.yml").read_text() diff --git a/tests/device/expected_core.py b/tests/device/expected_core.py deleted file mode 100644 index 39970f4..0000000 --- a/tests/device/expected_core.py +++ /dev/null @@ -1,276 +0,0 @@ -# This file was automatically generated and should not be edited directly. -# To make changes, edit the device metadata and regenerate the interface. - -import enum -from typing import Any, ClassVar - -import numpy as np -from harp.protocol import ( - AnonymousPayload, - BitMask, - BoolConverter, - Field, - GroupMask, - PayloadType, - RegisterBase, - RegisterU16, - RegisterU32, - RegisterU8, - StringConverter, - StructPayload, -) - - -__all__ = [ - "ResetFlags", - "ClockConfigurationFlags", - "OperationMode", - "EnableFlag", - "OperationControlPayload", - "ResetDevicePayload", - "DeviceNamePayload", - "ClockConfigurationPayload", - "WhoAmI", - "HardwareVersionHigh", - "HardwareVersionLow", - "AssemblyVersion", - "CoreVersionHigh", - "CoreVersionLow", - "FirmwareVersionHigh", - "FirmwareVersionLow", - "TimestampSeconds", - "TimestampMicroseconds", - "OperationControl", - "ResetDevice", - "DeviceName", - "SerialNumber", - "ClockConfiguration", - "REGISTER_MAP", -] - - -class ResetFlags(enum.IntFlag): - """Specifies the behavior of the non-volatile registers when resetting the device.""" - - RESTORE_DEFAULT = 0x1 - """The device will boot with all the registers reset to their default factory values.""" - - RESTORE_EEPROM = 0x2 - """The device will boot and restore all the registers to the values stored in non-volatile memory.""" - - SAVE = 0x4 - """The device will boot and save all the current register values to non-volatile memory.""" - - RESTORE_NAME = 0x8 - """The device will boot with the default device name.""" - - UPDATE_FIRMWARE = 0x20 - """The device will enter firmware update mode.""" - - BOOT_FROM_DEFAULT = 0x40 - """Specifies that the device has booted from default factory values.""" - - BOOT_FROM_EEPROM = 0x80 - """Specifies that the device has booted from non-volatile values stored in EEPROM.""" - - -class ClockConfigurationFlags(enum.IntFlag): - """Specifies configuration flags for the device synchronization clock.""" - - CLOCK_REPEATER = 0x1 - """The device will repeat the clock synchronization signal to the clock output connector, if available.""" - - CLOCK_GENERATOR = 0x2 - """The device resets and generates the clock synchronization signal on the clock output connector, if available.""" - - REPEATER_CAPABILITY = 0x8 - """Specifies the device has the capability to repeat the clock synchronization signal to the clock output connector.""" - - GENERATOR_CAPABILITY = 0x10 - """Specifies the device has the capability to generate the clock synchronization signal to the clock output connector.""" - - CLOCK_UNLOCK = 0x40 - """The device will unlock the timestamp register counter and will accept commands to set new timestamp values.""" - - CLOCK_LOCK = 0x80 - """The device will lock the timestamp register counter and will not accept commands to set new timestamp values.""" - - -class OperationMode(enum.IntEnum): - """Specifies the operation mode of the device.""" - - STANDBY = 0 - """Disable all event reporting on the device.""" - - ACTIVE = 1 - """Event detection is enabled. Only enabled events are reported by the device.""" - - SPEED = 3 - """The device enters speed mode.""" - - -class EnableFlag(enum.IntEnum): - """Specifies whether a specific register flag is enabled or disabled.""" - - DISABLED = 0 - """Specifies that the flag is disabled.""" - - ENABLED = 1 - """Specifies that the flag is enabled.""" - - -class OperationControlPayload(StructPayload[np.uint8]): - """Represents the payload of the OperationControl register.""" - - operation_mode: OperationMode = GroupMask(enum=OperationMode, mask=0x3) - """Specifies the operation mode of the device.""" - - dump_registers: bool = Field(BoolConverter(), mask=0x8) - """Specifies whether the device should report the content of all registers on initialization.""" - - mute_replies: bool = Field(BoolConverter(), mask=0x10) - """Specifies whether the replies to all commands will be muted, i.e. not sent by the device.""" - - visual_indicators: EnableFlag = GroupMask(enum=EnableFlag, mask=0x20) - """Specifies the state of all visual indicators on the device.""" - - operation_led: EnableFlag = GroupMask(enum=EnableFlag, mask=0x40) - """Specifies whether the device state LED should report the operation mode of the device.""" - - heartbeat: EnableFlag = GroupMask(enum=EnableFlag, mask=0x80) - """Specifies whether the device should report the content of the seconds register each second.""" - - -class ResetDevicePayload(AnonymousPayload[np.uint8]): - """Represents the payload of the ResetDevice register.""" - - __value__: ResetFlags = BitMask(enum=ResetFlags) - - -class DeviceNamePayload(AnonymousPayload[np.uint8]): - """Represents the payload of the DeviceName register.""" - - __value__: str = Field(StringConverter(25)) - - -class ClockConfigurationPayload(AnonymousPayload[np.uint8]): - """Represents the payload of the ClockConfiguration register.""" - - __value__: ClockConfigurationFlags = BitMask(enum=ClockConfigurationFlags) - - -class WhoAmI(RegisterU16): - """Specifies the identity class of the device.""" - - address: ClassVar[int] = 0 - - -class HardwareVersionHigh(RegisterU8): - """Specifies the major hardware version of the device.""" - - address: ClassVar[int] = 1 - - -class HardwareVersionLow(RegisterU8): - """Specifies the minor hardware version of the device.""" - - address: ClassVar[int] = 2 - - -class AssemblyVersion(RegisterU8): - """Specifies the version of the assembled components in the device.""" - - address: ClassVar[int] = 3 - - -class CoreVersionHigh(RegisterU8): - """Specifies the major version of the Harp core implemented by the device.""" - - address: ClassVar[int] = 4 - - -class CoreVersionLow(RegisterU8): - """Specifies the minor version of the Harp core implemented by the device.""" - - address: ClassVar[int] = 5 - - -class FirmwareVersionHigh(RegisterU8): - """Specifies the major version of the Harp core implemented by the device.""" - - address: ClassVar[int] = 6 - - -class FirmwareVersionLow(RegisterU8): - """Specifies the minor version of the Harp core implemented by the device.""" - - address: ClassVar[int] = 7 - - -class TimestampSeconds(RegisterU32): - """Stores the integral part of the system timestamp, in seconds.""" - - address: ClassVar[int] = 8 - - -class TimestampMicroseconds(RegisterU16): - """Stores the fractional part of the system timestamp, in microseconds.""" - - address: ClassVar[int] = 9 - - -class OperationControl(RegisterBase[OperationControlPayload]): - """Stores the configuration mode of the device.""" - - address: ClassVar[int] = 10 - payload_type: ClassVar[PayloadType] = PayloadType.U8 - payload_class = OperationControlPayload - - -class ResetDevice(RegisterBase[ResetFlags]): - """Resets the device and saves non-volatile registers.""" - - address: ClassVar[int] = 11 - payload_type: ClassVar[PayloadType] = PayloadType.U8 - payload_class = ResetDevicePayload - - -class DeviceName(RegisterBase[str]): - """Stores the user-specified device name.""" - - address: ClassVar[int] = 12 - payload_type: ClassVar[PayloadType] = PayloadType.U8 - payload_class = DeviceNamePayload - - -class SerialNumber(RegisterU16): - """Specifies the unique serial number of the device.""" - - address: ClassVar[int] = 13 - - -class ClockConfiguration(RegisterBase[ClockConfigurationFlags]): - """Specifies the configuration for the device synchronization clock.""" - - address: ClassVar[int] = 14 - payload_type: ClassVar[PayloadType] = PayloadType.U8 - payload_class = ClockConfigurationPayload - - -REGISTER_MAP: dict[int, type[RegisterBase[Any]]] = { - 0: WhoAmI, - 1: HardwareVersionHigh, - 2: HardwareVersionLow, - 3: AssemblyVersion, - 4: CoreVersionHigh, - 5: CoreVersionLow, - 6: FirmwareVersionHigh, - 7: FirmwareVersionLow, - 8: TimestampSeconds, - 9: TimestampMicroseconds, - 10: OperationControl, - 11: ResetDevice, - 12: DeviceName, - 13: SerialNumber, - 14: ClockConfiguration, -} diff --git a/tests/device/test_create_device_module.py b/tests/device/test_create_device_module.py index ae8c39f..ccab57c 100644 --- a/tests/device/test_create_device_module.py +++ b/tests/device/test_create_device_module.py @@ -5,7 +5,12 @@ import pytest from harp.device.core import REGISTER_MAP as CORE_REGISTER_MAP from harp.device.core import WhoAmI -from harp.device.schema import DeviceModule, DeviceModuleLike, create_device_module +from harp.device.schema import ( + DeviceModule, + DeviceModuleLike, + create_device_module, + parse_device_schema, +) from harp.protocol import RegisterBase from . import expected_device @@ -48,6 +53,20 @@ def test_whoami_from_schema(): assert mod.WHO_AM_I == 1216 +def test_docstring_from_schema(core_yml): + # The docstring is the schema description rather than composed from the module name, + # so a runtime module reads the same as the generated package for the same metadata. + doc = create_device_module(core_yml).__doc__ + assert doc == parse_device_schema(core_yml).description + assert doc + + +def test_docstring_absent_when_undeclared(test_module): + # device.yml declares no description, and a generated package carries no module + # docstring either, so both paths agree instead of one composing a name. + assert test_module.__doc__ is None + + def test_registers_are_reachable_by_name(test_module): assert test_module.AnalogData.address == 33 assert test_module.EncoderMode.address == 103 diff --git a/tests/device/test_emit.py b/tests/device/test_emit.py index eebab9a..cc1f588 100644 --- a/tests/device/test_emit.py +++ b/tests/device/test_emit.py @@ -13,7 +13,7 @@ create_registers, ) -from . import expected_core, expected_device +from . import expected_device from .converters import DataConverter CONVERTERS = {"DataConverter": DataConverter()} @@ -137,17 +137,19 @@ def test_enum_names_match_generator_for_every_enum(device_registers): # --------------------------------------------------------------------------- -# Core reference output, from protocol common.yml +# Core registers: the emitter and the generated package, from the same core.yml # --------------------------------------------------------------------------- def _core_expected(): - return {cls.__name__: cls for cls in expected_core.REGISTER_MAP.values()} + # harp.device.core is the generated Python interface, so it is the reference for + # what the emitter should build from the same core registers. + return {cls.__name__: cls for cls in core.REGISTER_MAP.values()} @pytest.mark.parametrize("name", sorted(_core_expected())) -def test_core_register_structural(name, common_yml): - emitted = create_registers(common_yml)[name] +def test_core_register_structural(name, core_yml): + emitted = create_registers(core_yml)[name] expected = _core_expected()[name] assert emitted.address == expected.address assert emitted.payload_type == expected.payload_type @@ -155,10 +157,6 @@ def test_core_register_structural(name, common_yml): emitted.payload_class.payload_dtype.itemsize == expected.payload_class.payload_dtype.itemsize ) - if name == "DeviceName": - # Generator enriches DeviceName to interfaceType: string, while the - # common.yml of the protocol does not, so only the layout size matches here. - return assert _layout(emitted.payload_class.payload_dtype) == _layout( expected.payload_class.payload_dtype ) diff --git a/tests/device/test_schema.py b/tests/device/test_schema.py index ab74d8d..bfda793 100644 --- a/tests/device/test_schema.py +++ b/tests/device/test_schema.py @@ -10,6 +10,7 @@ def test_parse_full_device(device_yml): assert isinstance(m, DeviceModel) assert m.device == "Tests" assert m.whoAmI is None # this application-device metadata omits whoAmI + assert m.description is None # and omits a top-level description assert "AnalogData" in m.registers ad = m.registers["AnalogData"] assert ad.type is PayloadType.Float @@ -58,22 +59,36 @@ def test_parse_bytes_decodes_as_utf8_regardless_of_locale(): ) -def test_parse_common_registers(common_yml): - c = parse_device_schema(common_yml) - assert c.device is None +def test_reserved_word_mask_keys_stay_strings(): + # Off, On, Yes and No are booleans in YAML 1.1, so a 1.1 parser keys these values by + # True and False. + schema = ( + "registers:\n" + " Indicators: {address: 32, type: U8, access: Write, maskType: LedState}\n" + "groupMasks:\n" + " LedState:\n" + " values:\n" + " Off: 0\n" + " On: 1\n" + ) + m = parse_device_schema(schema) + assert {k: int(v) for k, v in m.groupMasks["LedState"].values.items()} == {"Off": 0, "On": 1} + + +def test_parse_core_registers(core_yml): + c = parse_device_schema(core_yml) assert "WhoAmI" in c.registers - # Off/On group-mask keys must stay strings (YAML 1.1 would coerce to bool). - assert {k: int(v) for k, v in c.groupMasks["LedState"].values.items()} == {"Off": 0, "On": 1} + assert c.description # the core metadata declares a top-level description # 'None' bit name stays a string, not YAML null. assert "None" in c.bitMasks["ResetFlags"].bits -def test_bool_values_preserved(common_yml): - c = parse_device_schema(common_yml) +def test_bool_values_preserved(core_yml): + c = parse_device_schema(core_yml) assert c.registers["TimestampSeconds"].volatile is True -def test_access_list_and_scalar(common_yml): - c = parse_device_schema(common_yml) +def test_access_list_and_scalar(core_yml): + c = parse_device_schema(core_yml) # TimestampSeconds has a list access [Read, Write, Event]; WhoAmI a scalar. assert isinstance(c.registers["TimestampSeconds"].access, list)