From be0c7a063212f2b0c0efdfda79a0d89264c38865 Mon Sep 17 00:00:00 2001 From: Joshua Vasquez Date: Wed, 13 Apr 2022 17:02:02 -0700 Subject: [PATCH 001/159] can now pip instal in editable mode --- README.md | 16 +++++++++++++--- pyproject.toml | 4 ++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 4b18b2d..11c2463 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,20 @@ - - # pyharp Harp implementation of the Harp protocol. -## Edit the code +## Install with Pip +From this directory, create a *setup.py* file with +```` +poetry build +```` +and then install in editable mode with +```` +pip install -e . +``` + +Note that for the above to work, a fairly recent version of pip (>= 21.3) is required. + +## Install with Poetry Each Python user has is own very dear IDE for editing. Here, we are leaving instructions on how to edit this code using pyCharm, Anaconda and Poetry. diff --git a/pyproject.toml b/pyproject.toml index 62037dc..ca8d302 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,8 +16,8 @@ mypy = "^0.782" black = "^19.10b0" [build-system] -requires = ["poetry>=0.12"] -build-backend = "poetry.masonry.api" +requires = ["poetry-core>=1.0.8"] +build-backend = "poetry.core.masonry.api" [tool.poetry.scripts] pyharp = "pyharp.main:main" From ef9936e2fd46e8c06ae907e0614ae424442fd784 Mon Sep 17 00:00:00 2001 From: Joshua Vasquez Date: Wed, 13 Apr 2022 17:02:48 -0700 Subject: [PATCH 002/159] updating readme --- README.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/README.md b/README.md index 11c2463..7b5a181 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,7 @@ Harp implementation of the Harp protocol. ## Install with Pip -From this directory, create a *setup.py* file with -```` -poetry build -```` -and then install in editable mode with +From this directory, install in editable mode with ```` pip install -e . ``` From 69e11f0b1f6850eb998a40b6a59f3a5f677dcdb1 Mon Sep 17 00:00:00 2001 From: Joshua Vasquez Date: Wed, 13 Apr 2022 17:03:44 -0700 Subject: [PATCH 003/159] fixing readme bug --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7b5a181..cea031f 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Harp implementation of the Harp protocol. From this directory, install in editable mode with ```` pip install -e . -``` +```` Note that for the above to work, a fairly recent version of pip (>= 21.3) is required. From 4fff7bab10d0d4284ab2b5ba656f72bf0f0cdfdd Mon Sep 17 00:00:00 2001 From: Joshua Vasquez Date: Wed, 13 Apr 2022 17:32:34 -0700 Subject: [PATCH 004/159] adding udev rules --- 10-ftdi.rules | 3 +++ README.md | 14 ++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 10-ftdi.rules diff --git a/10-ftdi.rules b/10-ftdi.rules new file mode 100644 index 0000000..b960fec --- /dev/null +++ b/10-ftdi.rules @@ -0,0 +1,3 @@ +# UDEV rules for an ftdi RS232 Serial (Uart) IC +SUBSYSTEMS=="usb", ENV{.LOCAL_ifNum}="$attr{bInterfaceNumber}" +SUBSYSTEMS=="usb", KERNEL=="ttyUSB*", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", MODE="0666", SYMLINK+="ftdi-uart_%E{.LOCAL_ifNum}" diff --git a/README.md b/README.md index cea031f..1a38b41 100644 --- a/README.md +++ b/README.md @@ -75,3 +75,17 @@ Device info: * Firmware version: 1.0 * Device user name: IBL_rig_0 ``` + +## For Linux Only + +### UDEV rules +either copy `10-ftdi.rules` into your /etc/udev/rules.d folder or symlink it with +```` +sudo ln -s /full/path/to/pyharp/10-ftdi.rules /etc/udev/rules.d/10-ftdi.rules + +```` + +Reload udev rules with: +```` +sudo udevadm control --reload-rules +```` From d974ee403ccf3d311318a78a6a813cfe9fce1fa2 Mon Sep 17 00:00:00 2001 From: Joshua Vasquez Date: Wed, 13 Apr 2022 18:20:27 -0700 Subject: [PATCH 005/159] device_names is now a default dict; examples now also work on linux --- 10-ftdi.rules | 2 +- examples/check_device_id.py | 11 +++- examples/get_info.py | 10 +++- examples/write_and_read_from_registers.py | 10 +++- pyharp/device.py | 2 +- pyharp/device_names.py | 67 +++++++++-------------- 6 files changed, 53 insertions(+), 49 deletions(-) mode change 100644 => 100755 examples/check_device_id.py mode change 100644 => 100755 examples/get_info.py mode change 100644 => 100755 examples/write_and_read_from_registers.py diff --git a/10-ftdi.rules b/10-ftdi.rules index b960fec..91f9aff 100644 --- a/10-ftdi.rules +++ b/10-ftdi.rules @@ -1,3 +1,3 @@ # UDEV rules for an ftdi RS232 Serial (Uart) IC SUBSYSTEMS=="usb", ENV{.LOCAL_ifNum}="$attr{bInterfaceNumber}" -SUBSYSTEMS=="usb", KERNEL=="ttyUSB*", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", MODE="0666", SYMLINK+="ftdi-uart_%E{.LOCAL_ifNum}" +SUBSYSTEMS=="usb", KERNEL=="ttyUSB*", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", MODE="0666", SYMLINK+="harp_device_%E{.LOCAL_ifNum}" diff --git a/examples/check_device_id.py b/examples/check_device_id.py old mode 100644 new mode 100755 index b118b0e..237e20e --- a/examples/check_device_id.py +++ b/examples/check_device_id.py @@ -1,7 +1,10 @@ +#!/usr/bin/env python3 from pyharp.device import Device from pyharp.messages import HarpMessage from pyharp.messages import MessageType +from pyharp.device_names import device_names from struct import * +import os # ON THIS EXAMPLE @@ -11,7 +14,11 @@ # Open the device -device = Device("COM95") # Open serial connection +# Open serial connection +if os.name == "posix": # check for Linux. + device = Device("/dev/ttyUSB0") +else: # assume Windows. + device = Device("COM95") # Get some of the device's parameters device_id = device.WHO_AM_I # Get device's ID @@ -19,7 +26,7 @@ device_user_name = device.DEVICE_NAME # Get device's user name # Check if we are dealing with the correct device -if device_id == 2080: +if device_id in device_names: print("Correct device was found!") print(f"Device's ID: {device_id}") print(f"Device's name: {device_id_description}") diff --git a/examples/get_info.py b/examples/get_info.py old mode 100644 new mode 100755 index fca840c..56be774 --- a/examples/get_info.py +++ b/examples/get_info.py @@ -1,7 +1,9 @@ +#!/usr/bin/env python3 from pyharp.device import Device from pyharp.messages import HarpMessage from pyharp.messages import MessageType from struct import * +import os # ON THIS EXAMPLE @@ -11,7 +13,11 @@ # Open the device and print the info on screen -device = Device("COM95", "ibl.bin") # Open serial connection and save communication to a file +# Open serial connection and save communication to a file +if os.name == 'posix': # check for Linux. + device = Device("/dev/harp_device_00", "ibl.bin") +else: # assume Windows. + device = Device("COM95", "ibl.bin") device.info() # Display device's info on screen # Get some of the device's parameters @@ -29,4 +35,4 @@ device_assembly = device.ASSEMBLY_VERSION # Get device's assembly version # Close connection -device.disconnect() \ No newline at end of file +device.disconnect() diff --git a/examples/write_and_read_from_registers.py b/examples/write_and_read_from_registers.py old mode 100644 new mode 100755 index ccde127..ceb6d52 --- a/examples/write_and_read_from_registers.py +++ b/examples/write_and_read_from_registers.py @@ -1,7 +1,9 @@ +#!/usr/bin/env python3 from pyharp.device import Device from pyharp.messages import HarpMessage from pyharp.messages import MessageType from struct import * +import os # ON THIS EXAMPLE @@ -12,7 +14,11 @@ # Open the device and print the info on screen -device = Device("COM95", "ibl.bin") # Open serial connection and save communication to a file +# Open serial connection and save communication to a file +if os.name == 'posix': # check for Linux. + device = Device("/dev/harp_device_00", "ibl.bin") +else: # assume Windows. + device = Device("COM95", "ibl.bin") # Read current analog sensor's higher threshold (ANA_SENSOR_TH0_HIGH) at address 42 analog_threshold_h = device.send(HarpMessage.ReadU16(42).frame).payload_as_int() @@ -34,4 +40,4 @@ print(f"Analog sensor's values: {analog_sensor}") # Close connection -device.disconnect() \ No newline at end of file +device.disconnect() diff --git a/pyharp/device.py b/pyharp/device.py index 5e0e64d..25ecf31 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -4,7 +4,7 @@ from pyharp.messages import HarpMessage, ReplyHarpMessage from pyharp.messages import CommonRegisters -from pyharp import device_names +from pyharp.device_names import device_names class Device: diff --git a/pyharp/device_names.py b/pyharp/device_names.py index e5d8cbd..d7b7557 100644 --- a/pyharp/device_names.py +++ b/pyharp/device_names.py @@ -1,41 +1,26 @@ -def get(value: int) -> str: - if value == 1024: - return "Poke" - elif value == 1040: - return "MultiPwm" - elif value == 1056: - return "Wear" - elif value == 1072: - return "VoltsDrive" - elif value == 1088: - return "LedController" - elif value == 1104: - return "Synchronizer" - elif value == 1121: - return "SimpleAnalogGenerator" - elif value == 1136: - return "Archimedes" - elif value == 1152: - return "ClockSynchronizer" - elif value == 1168: - return "Camera" - elif value == 1184: - return "PyControl" - elif value == 1200: - return "FlyPad" - elif value == 1216: - return "Behavior" - elif value == 1232: - return "LoadCells" - elif value == 1248: - return "AudioSwitch" - elif value == 1264: - return "Rgb" - elif value == 1200: - return "FlyPad" - elif value == 2064: - return "FP3002" - elif value == 2080: - return "IblBehavior" - else: - return "NotSpecified" +from collections import defaultdict + + +current_device_names = \ + {1024: 'Poke', + 1040: 'MultiPwm', + 1056: 'Wear', + 1072: 'VoltsDrive', + 1088: 'LedController', + 1104: 'Synchronizer', + 1121: 'SimpleAnalogGenerator', + 1136: 'Archimedes', + 1152: 'ClockSynchronizer', + 1168: 'Camera', + 1184: 'PyControl', + 1200: 'FlyPad', + 1216: 'Behavior', + 1232: 'LoadCells', + 1248: 'AudioSwitch', + 1264: 'Rgb', + 1200: 'FlyPad', + 2064: 'FP3002', + 2080: 'IblBehavior'} +device_names = defaultdict(lambda: 'NotSpecified') +device_names.update(current_device_names) + From fbdacbd4d12c19feb653815b46ca318d3932e1a0 Mon Sep 17 00:00:00 2001 From: Joshua Vasquez Date: Fri, 15 Apr 2022 14:52:15 -0700 Subject: [PATCH 006/159] fixing pdf link; tweaking serial port to use ftdi rule --- examples/check_device_id.py | 2 +- pyharp/device.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/check_device_id.py b/examples/check_device_id.py index 237e20e..9568921 100755 --- a/examples/check_device_id.py +++ b/examples/check_device_id.py @@ -16,7 +16,7 @@ # Open the device # Open serial connection if os.name == "posix": # check for Linux. - device = Device("/dev/ttyUSB0") + device = Device("/dev/harp_device_00") else: # assume Windows. device = Device("COM95") diff --git a/pyharp/device.py b/pyharp/device.py index 25ecf31..b145f9c 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -9,7 +9,7 @@ class Device: """ - https://github.com/harp-tech/protocol/blob/master/Device%201.0%201.3%2020190207.pdf + https://github.com/harp-tech/protocol/blob/master/Device%201.1%201.0%2020220402.pdf """ _ser: serial.Serial From 33d8cc896d7462658fce5cf74f422d61476912d0 Mon Sep 17 00:00:00 2001 From: Joshua Vasquez Date: Fri, 15 Apr 2022 14:56:29 -0700 Subject: [PATCH 007/159] Update README.md --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 1a38b41..b506d9b 100644 --- a/README.md +++ b/README.md @@ -76,16 +76,16 @@ Device info: * Device user name: IBL_rig_0 ``` -## For Linux Only +## for Linux -### UDEV rules -either copy `10-ftdi.rules` into your /etc/udev/rules.d folder or symlink it with -```` -sudo ln -s /full/path/to/pyharp/10-ftdi.rules /etc/udev/rules.d/10-ftdi.rules +### Install UDEV Rules +Install by either copying `10-harp.rules` over to your `/etc/udev/rules.d` folder or by symlinking it with: +```` +sudo ln -s /absolute/path/to/vibratome-controller/10-harp.rules /etc/udev/rules.d/10-harp.rules ```` -Reload udev rules with: +Then reload udev rules with ```` sudo udevadm control --reload-rules ```` From 0191b237bafb2fe32bdff1e58777b3eb9059ab59 Mon Sep 17 00:00:00 2001 From: Joshua Vasquez Date: Mon, 18 Apr 2022 13:41:57 -0700 Subject: [PATCH 008/159] PEP 563 return type hints --- pyharp/messages.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/pyharp/messages.py b/pyharp/messages.py index 383ae2e..866e3f9 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -1,3 +1,4 @@ +from __future__ import annotations # for type hints (PEP 563) # from abc import ABC, abstractmethod from typing import Union, Tuple, Optional @@ -81,39 +82,39 @@ def message_type(self) -> int: return self._frame[0] @staticmethod - def ReadU8(address: int) -> "ReadU8HarpMessage": + def ReadU8(address: int) -> ReadU8HarpMessage: return ReadU8HarpMessage(address) @staticmethod - def ReadS8(address: int) -> "ReadS8HarpMessage": + def ReadS8(address: int) -> ReadS8HarpMessage: return ReadS8HarpMessage(address) @staticmethod - def ReadS16(address: int) -> "ReadS16HarpMessage": + def ReadS16(address: int) -> ReadS16HarpMessage: return ReadS16HarpMessage(address) @staticmethod - def ReadU16(address: int) -> "ReadU16HarpMessage": + def ReadU16(address: int) -> ReadU16HarpMessage: return ReadU16HarpMessage(address) @staticmethod - def WriteU8(address: int, value: int) -> "WriteU8HarpMessage": + def WriteU8(address: int, value: int) -> WriteU8HarpMessage: return WriteU8HarpMessage(address, value) @staticmethod - def WriteS8(address: int, value: int) -> "WriteS8HarpMessage": + def WriteS8(address: int, value: int) -> WriteS8HarpMessage: return WriteS8HarpMessage(address, value) @staticmethod - def WriteS16(address: int, value: int) -> "WriteS16HarpMessage": + def WriteS16(address: int, value: int) -> WriteS16HarpMessage: return WriteS16HarpMessage(address, value) @staticmethod - def WriteU16(address: int, value: int) -> "WriteU16HarpMessage": + def WriteU16(address: int, value: int) -> WriteU16HarpMessage: return WriteU16HarpMessage(address, value) @staticmethod - def parse(frame: bytearray) -> "ReplyHarpMessage": + def parse(frame: bytearray) -> ReplyHarpMessage: return ReplyHarpMessage(frame) From 6a73a9932c0969e2915ac993205ff2a0d312ab2f Mon Sep 17 00:00:00 2001 From: Joshua Vasquez Date: Mon, 18 Apr 2022 18:41:27 -0700 Subject: [PATCH 009/159] grand messages.py refactor --- 10-ftdi.rules | 3 - examples/write_and_read_from_registers.py | 13 +- pyharp/messages.py | 190 ++++++++-------------- 3 files changed, 78 insertions(+), 128 deletions(-) delete mode 100644 10-ftdi.rules diff --git a/10-ftdi.rules b/10-ftdi.rules deleted file mode 100644 index 91f9aff..0000000 --- a/10-ftdi.rules +++ /dev/null @@ -1,3 +0,0 @@ -# UDEV rules for an ftdi RS232 Serial (Uart) IC -SUBSYSTEMS=="usb", ENV{.LOCAL_ifNum}="$attr{bInterfaceNumber}" -SUBSYSTEMS=="usb", KERNEL=="ttyUSB*", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", MODE="0666", SYMLINK+="harp_device_%E{.LOCAL_ifNum}" diff --git a/examples/write_and_read_from_registers.py b/examples/write_and_read_from_registers.py index ceb6d52..6f6f029 100755 --- a/examples/write_and_read_from_registers.py +++ b/examples/write_and_read_from_registers.py @@ -21,8 +21,17 @@ device = Device("COM95", "ibl.bin") # Read current analog sensor's higher threshold (ANA_SENSOR_TH0_HIGH) at address 42 -analog_threshold_h = device.send(HarpMessage.ReadU16(42).frame).payload_as_int() -print(f"Analog sensor's higher threshold: {analog_threshold_h}") +#analog_threshold_h = device.send(HarpMessage.ReadU16(42).frame).payload_as_int() +#print(f"Analog sensor's higher threshold: {analog_threshold_h}") + + +data_stream = device.send(HarpMessage.ReadU8(33).frame) # returns a ReplyHarpMessage +#data_stream = device.send(HarpMessage.ReadS16(33).frame).payload_as_int_array() +print(f"Data Stream payload type: {data_stream.payload_type}") +print(f"Data Stream message type: {data_stream.message_type}") +print(f"Data Stream timestamp: {data_stream.timestamp}") +print(f"Data Stream num bytes: {data_stream.length}") +print(f"Data Stream: {data_stream.payload}") # Increase current analog sensor's higher threshold by one unit device.send(HarpMessage.WriteU16(42, analog_threshold_h+1).frame) diff --git a/pyharp/messages.py b/pyharp/messages.py index 866e3f9..3b09d2d 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -1,9 +1,10 @@ from __future__ import annotations # for type hints (PEP 563) +from enum import Enum # from abc import ABC, abstractmethod from typing import Union, Tuple, Optional -class MessageType: +class MessageType(Enum): READ: int = 1 WRITE: int = 2 EVENT: int = 3 @@ -11,7 +12,7 @@ class MessageType: WRITE_ERROR: int = 10 -class PayloadType: +class PayloadType(Enum): isUnsigned: int = 0x00 isSigned: int = 0x80 isFloat: int = 0x40 @@ -37,8 +38,17 @@ class PayloadType: TimestampedS64 = hasTimestamp | S64 TimestampedFloat = hasTimestamp | Float - ALL_UNSIGNED = [U8, U16, U32, TimestampedU8, TimestampedU16] - ALL_SIGNED = [S8, S16, S32, TimestampedS8, TimestampedS16] + +ALL_UNSIGNED = [PayloadType.U8, + PayloadType.U16, + PayloadType.U32, + PayloadType.TimestampedU8, + PayloadType.TimestampedU16] +ALL_SIGNED = [PayloadType.S8, + PayloadType.S16, + PayloadType.S32, + PayloadType.TimestampedS8, + PayloadType.TimestampedS16] class CommonRegisters: @@ -61,6 +71,10 @@ class CommonRegisters: class HarpMessage: + """ + https://github.com/harp-tech/protocol/blob/master/Binary%20Protocol%201.0%201.1%2020180223.pdf + """ + DEFAULT_PORT: int = 255 _frame: bytearray @@ -79,7 +93,27 @@ def frame(self) -> bytearray: @property def message_type(self) -> int: - return self._frame[0] + return MessageType(self._frame[0]) + + @property + def length(self) -> int: + return self._frame[1] + + @property + def address(self) -> int: + return self._frame[2] + + @property + def port(self) -> int: + return self._frame[3] + + @property + def payload_type(self) -> int: + return PayloadType(self._frame[4]) + + @property + def checksum(self) -> int: + return self._frame[-1] @staticmethod def ReadU8(address: int) -> ReadU8HarpMessage: @@ -118,6 +152,7 @@ def parse(frame: bytearray) -> ReplyHarpMessage: return ReplyHarpMessage(frame) +# A Response Message from a harp device. class ReplyHarpMessage(HarpMessage): PAYLOAD_START_ADDRESS: int PAYLOAD_LAST_ADDRESS: int @@ -141,16 +176,10 @@ def __init__( self._frame = frame - self._message_type = frame[0] - self._length = frame[1] - self._address = frame[2] - self._port = frame[3] - self._payload_type = frame[4] - # TOOO: add timestamp here - self._payload = frame[ - 11:-1 - ] # retrieve all content from 11 (where payload starts) until the checksum (not inclusive) - self._checksum = frame[-1] # last index is the checksum + self._timestamp = int.from_bytes(frame[5:9], byteorder="little", signed=False) + \ + int.from_bytes(frame[9:11], byteorder="little", signed=False)*32e-6 + # retrieve all content from 11 (where payload starts) until the checksum (not inclusive) + self._payload = frame[11:-1] # print(f"Type: {self.message_type}") # print(f"Length: {self.length}") @@ -161,61 +190,43 @@ def __init__( # print(f"Checksum: {self.checksum}") # print(f"Frame: {self.frame}") - @property - def frame(self) -> bytearray: - return self._frame - - @property - def message_type(self) -> int: - return self._message_type - - @property - def length(self) -> int: - return self._length - - @property - def address(self) -> int: - return self._address - - @property - def port(self) -> int: - return self._port - - @property - def payload_type(self) -> int: - return self._payload_type - @property def payload(self) -> bytes: return self._payload + @property + def timestamp(self) -> float: + return self._timestamp + def payload_as_int(self) -> int: value: int = 0 - if self.payload_type in PayloadType.ALL_UNSIGNED: + if self.payload_type in ALL_UNSIGNED: value = int.from_bytes(self.payload, byteorder="little", signed=False) - elif self.payload_type in PayloadType.ALL_SIGNED: + elif self.payload_type in ALL_SIGNED: value = int.from_bytes(self.payload, byteorder="little", signed=True) return value - def payload_as_int_array(self): - pass # TODO: implement this + def payload_as_int_array(self) -> list: + datatype_bytes = 0x0F & self.payload_type.value # number of bytes per chunk: 1, 2, 4, or 8. + # TODO: is len(self.payload) == self.length? + signed = True if self.payload_type in ALL_UNSIGNED else False + # Break the payload into chunks of datatype size in bytes + byte_chunks = [self.payload[i: i+datatype_bytes] for i in range(0, len(self.payload), datatype_bytes)] + return [int.from_bytes(chunk, byteorder="little", signed=signed) for chunk in byte_chunks] def payload_as_string(self) -> str: return self.payload.decode("utf-8") - @property - def checksum(self) -> int: - return self._checksum - +# A Read Request Message sent to a harp device. class ReadHarpMessage(HarpMessage): - MESSAGE_TYPE: int = MessageType.READ - _length: int - _address: int - _payload_type: int + MESSAGE_TYPE: int = MessageType.READ.value + _length: int # length of this message minus checksum. + _address: int # address to read from # address to read from + _payload_type: int # p _checksum: int - def __init__(self, payload_type: int, address: int): + def __init__(self, payload_type: PayloadType, address: int): self._frame = bytearray() self._frame.append(self.MESSAGE_TYPE) @@ -225,42 +236,9 @@ def __init__(self, payload_type: int, address: int): self._frame.append(address) self._frame.append(self.DEFAULT_PORT) - self._frame.append(payload_type) + self._frame.append(payload_type.value) self._frame.append(self.calculate_checksum()) - # def calculate_checksum(self) -> int: - # return ( - # self.message_type - # + self.length - # + self.address - # + self.port - # + self.payload_type - # ) & 255 - - @property - def message_type(self) -> int: - return self._frame[0] - - @property - def length(self) -> int: - return self._frame[1] - - @property - def address(self) -> int: - return self._frame[2] - - @property - def port(self) -> int: - return self._frame[3] - - @property - def payload_type(self) -> int: - return self._frame[4] - - @property - def checksum(self) -> int: - return self._frame[5] - class ReadU8HarpMessage(ReadHarpMessage): def __init__(self, address: int): @@ -284,7 +262,7 @@ def __init__(self, address: int): class WriteHarpMessage(HarpMessage): BASE_LENGTH: int = 5 - MESSAGE_TYPE: int = MessageType.WRITE + MESSAGE_TYPE: int = MessageType.WRITE.value _length: int _address: int _payload_type: int @@ -292,7 +270,7 @@ class WriteHarpMessage(HarpMessage): _checksum: int def __init__( - self, payload_type: int, payload: bytes, address: int, offset: int = 0 + self, payload_type: PayloadType, payload: bytes, address: int, offset: int = 0 ): """ @@ -309,47 +287,13 @@ def __init__( self._frame.append(address) self._frame.append(HarpMessage.DEFAULT_PORT) - self._frame.append(payload_type) + self._frame.append(payload_type.value) for i in payload: self._frame.append(i) self._frame.append(self.calculate_checksum()) - # def calculate_checksum(self) -> int: - # return ( - # self.message_type - # + self.length - # + self.address - # + self.port - # + self.payload_type - # + self.payload - # ) & 255 - - @property - def message_type(self) -> int: - return self._frame[0] - - @property - def length(self) -> int: - return self._frame[1] - - @property - def address(self) -> int: - return self._frame[2] - - @property - def port(self) -> int: - return self._frame[3] - - @property - def payload_type(self) -> int: - return self._frame[4] - - @property - def checksum(self) -> int: - return self._frame[-1] - class WriteU8HarpMessage(WriteHarpMessage): def __init__(self, address: int, value: int): From 4108f6b76e63111ba2fa97817cfefd3f07b2a95c Mon Sep 17 00:00:00 2001 From: Joshua Vasquez Date: Mon, 18 Apr 2022 18:58:27 -0700 Subject: [PATCH 010/159] more message.py refactor --- examples/write_and_read_from_registers.py | 4 +-- pyharp/messages.py | 44 ++++++----------------- 2 files changed, 13 insertions(+), 35 deletions(-) diff --git a/examples/write_and_read_from_registers.py b/examples/write_and_read_from_registers.py index 6f6f029..ae7ba96 100755 --- a/examples/write_and_read_from_registers.py +++ b/examples/write_and_read_from_registers.py @@ -27,8 +27,8 @@ data_stream = device.send(HarpMessage.ReadU8(33).frame) # returns a ReplyHarpMessage #data_stream = device.send(HarpMessage.ReadS16(33).frame).payload_as_int_array() -print(f"Data Stream payload type: {data_stream.payload_type}") -print(f"Data Stream message type: {data_stream.message_type}") +print(f"Data Stream payload type: {data_stream.payload_type.name}") +print(f"Data Stream message type: {data_stream.message_type.name}") print(f"Data Stream timestamp: {data_stream.timestamp}") print(f"Data Stream num bytes: {data_stream.length}") print(f"Data Stream: {data_stream.payload}") diff --git a/pyharp/messages.py b/pyharp/messages.py index 3b09d2d..434a134 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -67,9 +67,6 @@ class CommonRegisters: DEVICE_NAME = 0x0C -T = Union[int, bytearray] - - class HarpMessage: """ https://github.com/harp-tech/protocol/blob/master/Binary%20Protocol%201.0%201.1%2020180223.pdf @@ -154,38 +151,27 @@ def parse(frame: bytearray) -> ReplyHarpMessage: # A Response Message from a harp device. class ReplyHarpMessage(HarpMessage): - PAYLOAD_START_ADDRESS: int - PAYLOAD_LAST_ADDRESS: int - _message_type: int - _length: int - _address: int - _payload_type: int - _payload: bytes - _checksum: int + def __init__( self, frame: bytearray, ): """ - :param payload_type: - :param payload: - :param address: - :param offset: how many bytes more besides the length corresponding to U8 (for example, for U16 it would be offset=1) + :param frame: the serialized message frame. """ self._frame = frame - self._timestamp = int.from_bytes(frame[5:9], byteorder="little", signed=False) + \ int.from_bytes(frame[9:11], byteorder="little", signed=False)*32e-6 # retrieve all content from 11 (where payload starts) until the checksum (not inclusive) self._payload = frame[11:-1] - # print(f"Type: {self.message_type}") + # print(f"Type: {self.message_type.name}") # print(f"Length: {self.length}") # print(f"Address: {self.address}") # print(f"Port: {self.port}") - # print(f"Payload Type: {self.payload_type}") + # print(f"Payload Type: {self.payload_type.name}") # print(f"Payload: {self.payload}") # print(f"Checksum: {self.checksum}") # print(f"Frame: {self.frame}") @@ -207,7 +193,8 @@ def payload_as_int(self) -> int: return value def payload_as_int_array(self) -> list: - datatype_bytes = 0x0F & self.payload_type.value # number of bytes per chunk: 1, 2, 4, or 8. + # Number of bytes per chunk. Get this from the bit field structure. + datatype_bytes = 0x0F & self.payload_type.value # TODO: is len(self.payload) == self.length? signed = True if self.payload_type in ALL_UNSIGNED else False # Break the payload into chunks of datatype size in bytes @@ -220,20 +207,16 @@ def payload_as_string(self) -> str: # A Read Request Message sent to a harp device. class ReadHarpMessage(HarpMessage): - MESSAGE_TYPE: int = MessageType.READ.value - _length: int # length of this message minus checksum. - _address: int # address to read from # address to read from - _payload_type: int # p - _checksum: int + MESSAGE_TYPE: int = MessageType.READ + def __init__(self, payload_type: PayloadType, address: int): self._frame = bytearray() - self._frame.append(self.MESSAGE_TYPE) + self._frame.append(self.MESSAGE_TYPE.value) length: int = 4 self._frame.append(length) - self._frame.append(address) self._frame.append(self.DEFAULT_PORT) self._frame.append(payload_type.value) @@ -262,12 +245,7 @@ def __init__(self, address: int): class WriteHarpMessage(HarpMessage): BASE_LENGTH: int = 5 - MESSAGE_TYPE: int = MessageType.WRITE.value - _length: int - _address: int - _payload_type: int - _payload: int - _checksum: int + MESSAGE_TYPE: int = MessageType.WRITE def __init__( self, payload_type: PayloadType, payload: bytes, address: int, offset: int = 0 @@ -281,7 +259,7 @@ def __init__( """ self._frame = bytearray() - self._frame.append(self.MESSAGE_TYPE) + self._frame.append(self.MESSAGE_TYPE.value) self._frame.append(self.BASE_LENGTH + offset) From e3457bb148824ea869a09d68b899e67beeafa3cd Mon Sep 17 00:00:00 2001 From: Joshua Vasquez Date: Tue, 19 Apr 2022 16:10:16 -0700 Subject: [PATCH 011/159] adding harp rules --- 10-harp.rules | 3 ++ examples/get_info.py | 2 +- examples/write_and_read_from_registers.py | 32 ++++++------- pyharp/device.py | 56 ++++++++++++++++++++--- 4 files changed, 70 insertions(+), 23 deletions(-) create mode 100644 10-harp.rules diff --git a/10-harp.rules b/10-harp.rules new file mode 100644 index 0000000..c5114ef --- /dev/null +++ b/10-harp.rules @@ -0,0 +1,3 @@ +# UDEV rules for a Harp Device (actually an ftdi RS232 Serial [Uart] IC) +SUBSYSTEMS=="usb", ENV{.LOCAL_ifNum}="$attr{bInterfaceNumber}" +SUBSYSTEMS=="usb", KERNEL=="ttyUSB*", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", MODE="0666", SYMLINK+="harp_device_%E{.LOCAL_ifNum}" diff --git a/examples/get_info.py b/examples/get_info.py index 56be774..44e4f18 100755 --- a/examples/get_info.py +++ b/examples/get_info.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -from pyharp.device import Device +from pyharp.device import Device, DeviceMode from pyharp.messages import HarpMessage from pyharp.messages import MessageType from struct import * diff --git a/examples/write_and_read_from_registers.py b/examples/write_and_read_from_registers.py index ae7ba96..3634353 100755 --- a/examples/write_and_read_from_registers.py +++ b/examples/write_and_read_from_registers.py @@ -31,22 +31,22 @@ print(f"Data Stream message type: {data_stream.message_type.name}") print(f"Data Stream timestamp: {data_stream.timestamp}") print(f"Data Stream num bytes: {data_stream.length}") -print(f"Data Stream: {data_stream.payload}") - -# Increase current analog sensor's higher threshold by one unit -device.send(HarpMessage.WriteU16(42, analog_threshold_h+1).frame) - -# Check if the register was well written -analog_threshold_h = device.send(HarpMessage.ReadU16(42).frame).payload_as_int() -print(f"Analog sensor's higher threshold: {analog_threshold_h}") - -# Read 10 samples of the analog sensor and display the values -# The value is at register STREAM[0], address 33 -analog_sensor = [] -for x in range(10): - value = device.send(HarpMessage.ReadS16(33).frame).payload_as_int() - analog_sensor.append(value & 0xffff) -print(f"Analog sensor's values: {analog_sensor}") +print(f"Data Stream payload: {data_stream.payload}") + +## Increase current analog sensor's higher threshold by one unit +#device.send(HarpMessage.WriteU16(42, analog_threshold_h+1).frame) +# +## Check if the register was well written +#analog_threshold_h = device.send(HarpMessage.ReadU16(42).frame).payload_as_int() +#print(f"Analog sensor's higher threshold: {analog_threshold_h}") +# +## Read 10 samples of the analog sensor and display the values +## The value is at register STREAM[0], address 33 +#analog_sensor = [] +#for x in range(10): +# value = device.send(HarpMessage.ReadS16(33).frame).payload_as_int() +# analog_sensor.append(value & 0xffff) +#print(f"Analog sensor's values: {analog_sensor}") # Close connection device.disconnect() diff --git a/pyharp/device.py b/pyharp/device.py index b145f9c..1c608ba 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -5,6 +5,14 @@ from pyharp.messages import HarpMessage, ReplyHarpMessage from pyharp.messages import CommonRegisters from pyharp.device_names import device_names +from enum import Enum + + +class DeviceMode(Enum): + Standby = 0 + Active = 1 + Reserved = 2 + Speed = 3 class Device: @@ -53,16 +61,13 @@ def load(self) -> None: def info(self) -> None: print("Device info:") - #print(f"* Who am I (ID): {self.WHO_AM_I}") - #print(f"* Who am I (Device): {self.WHO_AM_I_DEVICE}") print(f"* Who am I: ({self.WHO_AM_I}) {self.WHO_AM_I_DEVICE}") print(f"* HW version: {self.HW_VERSION_H}.{self.HW_VERSION_L}") print(f"* Assembly version: {self.ASSEMBLY_VERSION}") print(f"* HARP version: {self.HARP_VERSION_H}.{self.HARP_VERSION_L}") - print( - f"* Firmware version: {self.FIRMWARE_VERSION_H}.{self.FIRMWARE_VERSION_L}" - ) + print(f"* Firmware version: {self.FIRMWARE_VERSION_H}.{self.FIRMWARE_VERSION_L}") print(f"* Device user name: {self.DEVICE_NAME}") + print(f"* Mode: {self.read_device_mode().name}") def read(self): pass @@ -158,7 +163,7 @@ def read_fw_l_version(self) -> int: reply: ReplyHarpMessage = self.send( HarpMessage.ReadU8(address).frame, dump=False - ) +) return reply.payload_as_int() @@ -172,6 +177,45 @@ def read_device_name(self) -> str: return reply.payload_as_string() + def read_device_mode(self) -> int: + address = CommonRegisters.OPERATION_CTRL + reply = self.send(HarpMessage.ReadU8(address).frame) + return DeviceMode(reply.payload_as_int() & 0x03) + +# TODO: Not sure if we want to implement these. Delete if no. +# def set_mode(self, mode: DeviceMode): +# address = CommonRegisters.OPERATION_CTRL +# # Read register first. +# reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_int() +# reg_value &= ~0x03 # mask off old mode. +# reg_value |= mode.value +# reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) +# +# def enable_alive_en(self): +# """Enable ALIVE_EN such that the device sends an event each second.""" +# address = CommonRegisters.OPERATION_CTRL +# # Read register first. +# reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_int() +# reg_value |= (1 << 7) +# reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) +# +# def enable_status_led(self): +# """enable the device's status led if one exists.""" +# address = CommonRegisters.OPERATION_CTRL +# # Read register first. +# reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_int() +# reg_value |= (1 << 6) +# reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) + + def disable_alive_en(self): + """disable ALIVE_EN such that the device does not send an event each second.""" + address = CommonRegisters.OPERATION_CTRL + # Read register first. + reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_int() + reg_value &= ~(1 << 7) # mask off old mode. + reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) + + def send(self, message_bytes: bytearray, dump: bool = True) -> ReplyHarpMessage: self._ser.write(message_bytes) From 455de819097220bce69ec44459df44ae28af5bf3 Mon Sep 17 00:00:00 2001 From: Joshua Vasquez Date: Tue, 19 Apr 2022 16:14:54 -0700 Subject: [PATCH 012/159] adding simple behavior io stuff --- examples/behavior_device_driver_test.py | 41 +++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100755 examples/behavior_device_driver_test.py diff --git a/examples/behavior_device_driver_test.py b/examples/behavior_device_driver_test.py new file mode 100755 index 0000000..410584e --- /dev/null +++ b/examples/behavior_device_driver_test.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +from pyharp.drivers.behavior import Behavior +from pyharp.messages import HarpMessage +from pyharp.messages import MessageType +from struct import * +import os + + +# Open the device and print the info on screen +# Open serial connection and save communication to a file +device = None +if os.name == 'posix': # check for Linux. + device = Behavior("/dev/harp_device_00", "ibl.bin") +else: # assume Windows. + device = Behavior("COM95", "ibl.bin") + +print(f"digital outputs: {device.all_output_states:016b}") +print(f"setting digital outputs") +device.all_output_states = 0x0000 +device.set_outputs(0x0000) +print(f"digital outputs: {device.all_output_states:016b}") + +device.D0 = 1 +print(f"D0: {device.D0}") +device.D0 = 0 +print(f"D0: {device.D0}") + +device.D1 = 1 +print(f"D1: {device.D1}") +device.D1 = 0 +print(f"D1: {device.D1}") + +#import time +#while True: +# print(f"PORT0 IN State: {device.port0_i0}") +# print(f"PORT0 IO State: {device.port0_io0}") +# print(f"PORT0 OUT State: {device.port0_o0}") +# print(f"all port io states: {device.all_port_io_states}") +# print(f"all port output states: {device.all_port_output_states}") +# print() +# time.sleep(0.1) From 4e5e58fe33dc6c85006bfc2ca68c190e8ffa8666 Mon Sep 17 00:00:00 2001 From: Joshua Vasquez Date: Tue, 19 Apr 2022 16:16:22 -0700 Subject: [PATCH 013/159] adding prelim behavior driver --- pyharp/drivers/__init__.py | 0 pyharp/drivers/behavior.py | 313 +++++++++++++++++++++++++++++++++++++ 2 files changed, 313 insertions(+) create mode 100644 pyharp/drivers/__init__.py create mode 100644 pyharp/drivers/behavior.py diff --git a/pyharp/drivers/__init__.py b/pyharp/drivers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pyharp/drivers/behavior.py b/pyharp/drivers/behavior.py new file mode 100644 index 0000000..1a0f6e2 --- /dev/null +++ b/pyharp/drivers/behavior.py @@ -0,0 +1,313 @@ +"""Behavior Device Driver.""" + +from pyharp.messages import HarpMessage, ReplyHarpMessage +from pyharp.device_names import device_names +from pyharp.device import Device +import serial +from serial.serialutil import SerialException + + +# Type, Base Address, "Description." +REGISTERS = \ +{ # RJ45 "PORT" (0, 1, 2) Digital Inputs + "PORT_DIS" : ("U8", 32, "Reflects the state of DI digital lines of each Port."), + + # Manipulate any of the boards digital outputs. + "OUTPUTS_SET": ("U16", 34, "Set the corresponding output."), + "OUTPUTS_CLR": ("U16", 35, "Clear the corresponding output."), + "OUTPUTS_TOGGLE": ("U16", 36, "Toggle the corresponding output."), + "OUTPUTS_OUT": ("U16", 37, "Control corresponding output."), + + # RJ45 "PORT" (0, 1, 2) Digital IOs + "PORT_DIOS_SET": ("U8", 38, "Set the corresponding DIO."), + "PORT_DIOS_CLEAR": ("U8", 39, "Clear the corresponding DIO."), + "PORT_DIOS_TOGGLE": ("U8", 40, "Toggle the corresponding DIO."), + "PORT_DIOS_OUT": ("U8", 41, "Control the corresponding DIO."), + "PORT_DIOS_CONF": ("U8", 42, "Set the DIOs direction (1 is output)."), + "PORT_DIOS_IN": ("U8", 43, "State of the DIOs."), +} + + + +class Behavior: + """Driver for BehaviorDevice.""" + + # On Linux, the symlink to the first detected harp device. + # Name set in udev rules and will increment with subsequent devices. + DEVICE_NAME = "Behavior" + DEFAULT_PORT_NAME = "/dev/harp_device_00" + ID = 1216 + + # TODO: put this in a base class? + READ_MSG_LOOKUP = \ + { + "U8": HarpMessage.ReadU8, + "U16": HarpMessage.ReadU16, + "S16": HarpMessage.ReadS16, + } + + WRITE_MSG_LOOKUP = \ + { + "U8": HarpMessage.WriteU8, + "U16": HarpMessage.WriteU16, + "S16": HarpMessage.WriteS16, + } + + + def __init__(self, port_name=None, output_filename=None): + """Class constructor. Connect to a device.""" + + self.device = None + + try: + if port_name is None: + self.device = Device(self.__class__.DEFAULT_PORT_NAME, output_filename) + else: + self.device = Device(port_name, output_filename) + except (FileNotFoundError, SerialException): + print("Error: Failed to connect to Behavior Device. Is it plugged in?") + raise + + if self.device.WHO_AM_I != self.__class__.ID: + raise IOError("Error: Did not connect to Harp Behavior Device.") + + + # TODO: put this in a base class? + def get_reg_info(self, reg_name: str) -> str: + """get info for this device's particular reg.""" + try: + return REGISTERS[reg_name][2] + except KeyError: + raise KeyError(f"reg: {reg_name} is not a register in " + "{self.__class__.name} Device's register map.") + + +# Board inputs, outputs, and some settings configured as @properties. + # INPUTS + @property + def all_port_input_states(self): + """return the state of all PORT digital inputs.""" + reg_type, reg_index, _ = REGISTERS["PORT_DIS"] + read_message_type = self.__class__.READ_MSG_LOOKUP[reg_type] + return self.device.send(read_message_type(reg_index).frame).payload_as_int() + + @property + def port0_i0(self): + """return the state of port0 digital in 0.""" + return self.all_port_input_states & 0x01 + + @property + def port1_i0(self): + """return the state of port0 digital in 0.""" + return (self.all_port_input_states >> 1) & 0x01 + + @property + def port2_i0(self): + """return the state of port2 digital in 0.""" + return (self.all_port_input_states >> 2) & 0x01 + + # IOs + def set_port_io_states(self, bitmask : int): + """set the state of all PORT digital ios. (1 is output.)""" + reg_type, reg_index, _ = REGISTERS["PORT_DIOS_CONF"] + write_message_type = self.__class__.WRITE_MSG_LOOKUP[reg_type] + self.device.send(write_message_type(reg_index, bitmask).frame) + + @property #FIXME: this doesn't seem to work + def all_port_io_states(self): + """return the state of all PORT digital ios.""" + reg_type, reg_index, _ = REGISTERS["PORT_DIOS_IN"] + read_message_type = self.__class__.READ_MSG_LOOKUP[reg_type] + return self.device.send(read_message_type(reg_index).frame).payload_as_int() + + @property + def port0_io0(self): + """read the digital io state.""" + return self.all_port_io_states & 0x01 + + @port0_io0.setter + def port0_io0(self, value: int): + """write port0 digital io state.""" + pass + + @property + def port1_io0(self): + """read the digital io state.""" + return (self.all_port_io_states >> 1) & 0x01 + + @port0_io0.setter + def port1_io0(self, value: int): + """write port0 digital io state.""" + self.set_outputs(value&0x01) + + @property + def port2_io0(self): + """read the digital io state.""" + return (self.all_port_io_states >> 2) & 0x01 + + @port0_io0.setter + def port2_io0(self, value: int): + """write port0 digital io state.""" + pass + + + # OUTPUTS + @property + def all_output_states(self): + """return the state of all PORT digital inputs.""" + reg_type, reg_index, _ = REGISTERS["OUTPUTS_OUT"] + read_message_type = self.__class__.READ_MSG_LOOKUP[reg_type] + return self.device.send(read_message_type(reg_index).frame).payload_as_int() + + @all_output_states.setter + def all_output_states(self, bitmask : int): + """set the state of all PORT digital inputs.""" + reg_type, reg_index, _ = REGISTERS["OUTPUTS_OUT"] + write_message_type = self.__class__.WRITE_MSG_LOOKUP[reg_type] + return self.device.send(write_message_type(reg_index, bitmask).frame) + + def set_outputs(self, bitmask : int): + """set digital outputs to logic 1 according to bitmask.""" + reg_type, reg_index, _ = REGISTERS["OUTPUTS_SET"] + write_message_type = self.__class__.WRITE_MSG_LOOKUP[reg_type] + return self.device.send(write_message_type(reg_index, bitmask).frame) + + def clear_outputs(self, bitmask : int): + """clear digital outputs (specified with logic 1) according to bitmask.""" + reg_type, reg_index, _ = REGISTERS["OUTPUTS_CLR"] + write_message_type = self.__class__.WRITE_MSG_LOOKUP[reg_type] + return self.device.send(write_message_type(reg_index, bitmask).frame) + + @property + def D0(self): + """read the digital output D0 state.""" + return (self.all_output_states >> 10) & 0x01 + + @D0.setter + def D0(self, value): + """set the digital output D0 state.""" + if value: + self.set_outputs(1 << 10) + else: + self.clear_outputs(1 << 10) + + @property + def D1(self): + """read the digital output D1 state.""" + return (self.all_output_states >> 11) & 0x01 + + @D1.setter + def D1(self, value): + """set the digital output D1 state.""" + if value: + self.set_outputs(1 << 11) + else: + self.clear_outputs(1 << 11) + + @property + def D2(self): + """read the digital output D2 state.""" + return (self.all_output_states >> 12) & 0x01 + + @D2.setter + def D2(self, value): + """set the digital output D2 state.""" + if value: + self.set_outputs(1 << 12) + else: + self.clear_outputs(1 << 12) + + @property + def D3(self): + """read the digital output D3 state.""" + return (self.all_output_states >> 10) & 0x01 + + @D3.setter + def D3(self, value): + """set the digital output D3 state.""" + if value: + self.set_outputs(1 << 13) + else: + self.clear_outputs(1 << 13) + + @property + def port0_D0(self): + return self.all_output_states & 0x01 + + @port0_D0.setter + def port0_D0(self, value): + if value: + self.set_outputs(1) + else: + self.clear_outputs(1) + + @property + def port1_D0(self): + return (self.all_output_states >> 1) & 0x01 + + @port1_D0.setter + def port1_D0(self, value): + if value: + self.set_outputs(1 << 1) + else: + self.clear_outputs(1 << 1) + + @property + def port2_D0(self): + return (self.all_output_states >> 2) & 0x01 + + @port2_D0.setter + def port2_D0(self, value): + if value: + self.set_outputs(1 << 2) + else: + self.clear_outputs(1 << 2) + + + @property + def port0_12V(self): + return (self.all_output_states >> 3) & 0x01 + + @port0_12V.setter + def port0_12V(self, value): + if value: + self.set_outputs(1 << 3) + else: + self.clear_outputs(1 << 3) + + @property + def port1_12V(self): + return (self.all_output_states >> 4) & 0x01 + + @port1_12V.setter + def port1_12V(self, value): + if value: + self.set_outputs(1 << 4) + else: + self.clear_outputs(1 << 4) + + @property + def port2_12V(self): + return (self.all_output_states >> 5) & 0x01 + + @port2_12V.setter + def port2_12V(self, value): + if value: + self.set_outputs(1 << 5) + else: + self.clear_outputs(1 << 5) + + + def __enter__(self): + """Setup for the 'with' statement""" + return self + + + def __exit__(self, *args): + """Cleanup for the 'with' statement""" + self.device.disconnect() + + + def __del__(self): + """Cleanup when Device gets garbage collected.""" + self.device.disconnect() From 0e47767cf9ee0c23485091d946e378d35c2f5f36 Mon Sep 17 00:00:00 2001 From: Joshua Vasquez Date: Wed, 20 Apr 2022 16:19:44 -0700 Subject: [PATCH 014/159] adding a string representation --- pyharp/messages.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pyharp/messages.py b/pyharp/messages.py index 434a134..cab9351 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -167,6 +167,16 @@ def __init__( # retrieve all content from 11 (where payload starts) until the checksum (not inclusive) self._payload = frame[11:-1] + def __str__(self): + return f"Type: {self.message_type.name}\r\n" + \ + f"Length: {self.length}\r\n" + \ + f"Address: {self.address}\r\n" + \ + f"Port: {self.port}\r\n" + \ + f"Timestampe: {self.timestamp:6f}\r\n" + \ + f"Payload Type: {self.payload_type.name}\r\n" + \ + f"Payload: {self.payload}\r\n" + \ + f"Checksum: {self.checksum}\r\n" + \ + f"Raw Frame: {self.frame}\r\n" # print(f"Type: {self.message_type.name}") # print(f"Length: {self.length}") # print(f"Address: {self.address}") From 43629c0b19aaf9eb8ccbc7095533c96769137687 Mon Sep 17 00:00:00 2001 From: Joshua Vasquez Date: Wed, 20 Apr 2022 17:55:12 -0700 Subject: [PATCH 015/159] added __str__ and active mode script --- examples/behavior_device_driver_test.py | 32 ++-- examples/wait_for_events.py | 25 +++ examples/write_and_read_from_registers.py | 11 ++ pyharp/device.py | 53 ++++--- pyharp/drivers/behavior.py | 177 +++++++++++++++------- pyharp/messages.py | 52 +++++-- 6 files changed, 249 insertions(+), 101 deletions(-) create mode 100755 examples/wait_for_events.py diff --git a/examples/behavior_device_driver_test.py b/examples/behavior_device_driver_test.py index 410584e..d9cfab7 100755 --- a/examples/behavior_device_driver_test.py +++ b/examples/behavior_device_driver_test.py @@ -14,21 +14,33 @@ else: # assume Windows. device = Behavior("COM95", "ibl.bin") +print(f"digital inputs: {device.all_input_states:03b}") print(f"digital outputs: {device.all_output_states:016b}") print(f"setting digital outputs") -device.all_output_states = 0x0000 -device.set_outputs(0x0000) +#device.all_output_states = 0x0000 # Set the whole port directly. +#device.set_outputs(0xFFFF) # Set the values set to logic 1 only. +#device.clear_outputs(0xFFFF)# Clear values set to logic 1 only. print(f"digital outputs: {device.all_output_states:016b}") +device.set_io_configuration(0b111) -device.D0 = 1 -print(f"D0: {device.D0}") -device.D0 = 0 -print(f"D0: {device.D0}") +# TODO: FIXME. IOs are not working +#device.set_io_configuration(0b111) # This is getting ignored? +#device.set_io_outputs(0b000) +#device.all_io_states = 0b000 +#print(f"digital ios: {device.all_io_states:03b}") + +#device.D0 = 0 +#print(f"D0: {device.D0}") +#device.D0 = 1 +#print(f"D0: {device.D0}") +# +#device.D1 = 0 +#print(f"D1: {device.D1}") +#device.D1 = 1 +#print(f"D1: {device.D1}") +# +#print(f"DI2: {device.DI2}") -device.D1 = 1 -print(f"D1: {device.D1}") -device.D1 = 0 -print(f"D1: {device.D1}") #import time #while True: diff --git a/examples/wait_for_events.py b/examples/wait_for_events.py new file mode 100755 index 0000000..44c2b30 --- /dev/null +++ b/examples/wait_for_events.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +from pyharp.drivers.behavior import Behavior +from pyharp.messages import HarpMessage +from pyharp.messages import MessageType +from struct import * +import os + +from pyharp.device import DeviceMode + + +# Open the device and print the info on screen +# Open serial connection and save communication to a file +device = None +if os.name == 'posix': # check for Linux. + device = Behavior("/dev/harp_device_00", "ibl.bin") +else: # assume Windows. + device = Behavior("COM95", "ibl.bin") + +print("Setting mode to active.") +device.device.set_mode(DeviceMode.Active) +import time +while True: + event_response = device.device._read() # read any incoming events. + if event_response is not None: + print(event_response) diff --git a/examples/write_and_read_from_registers.py b/examples/write_and_read_from_registers.py index 3634353..4430b00 100755 --- a/examples/write_and_read_from_registers.py +++ b/examples/write_and_read_from_registers.py @@ -25,6 +25,9 @@ #print(f"Analog sensor's higher threshold: {analog_threshold_h}") +import time + +print(f"System time: {time.perf_counter():.6f}") data_stream = device.send(HarpMessage.ReadU8(33).frame) # returns a ReplyHarpMessage #data_stream = device.send(HarpMessage.ReadS16(33).frame).payload_as_int_array() print(f"Data Stream payload type: {data_stream.payload_type.name}") @@ -33,6 +36,14 @@ print(f"Data Stream num bytes: {data_stream.length}") print(f"Data Stream payload: {data_stream.payload}") +print(f"System time: {time.perf_counter():.6f}") +event_reg_response = device.send(HarpMessage.ReadU8(77).frame) # returns a ReplyHarpMessage +print(f"EVNT_ENABLE payload type: {event_reg_response.payload_type.name}") +print(f"EVNT_ENABLE message type: {event_reg_response.message_type.name}") +print(f"EVNT_ENABLE timestamp: {event_reg_response.timestamp}") +print(f"EVNT_ENABLE num bytes: {event_reg_response.length}") +print(f"EVNT_ENABLE payload: {event_reg_response.payload[0]:08b}") + ## Increase current analog sensor's higher threshold by one unit #device.send(HarpMessage.WriteU16(42, analog_threshold_h+1).frame) # diff --git a/pyharp/device.py b/pyharp/device.py index 1c608ba..4b71f78 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -1,5 +1,5 @@ import serial -from typing import Optional +from typing import Optional, Union from pathlib import Path from pyharp.messages import HarpMessage, ReplyHarpMessage @@ -177,19 +177,22 @@ def read_device_name(self) -> str: return reply.payload_as_string() - def read_device_mode(self) -> int: + def read_device_mode(self) -> DeviceMode: address = CommonRegisters.OPERATION_CTRL reply = self.send(HarpMessage.ReadU8(address).frame) + print(reply) return DeviceMode(reply.payload_as_int() & 0x03) # TODO: Not sure if we want to implement these. Delete if no. -# def set_mode(self, mode: DeviceMode): -# address = CommonRegisters.OPERATION_CTRL -# # Read register first. -# reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_int() -# reg_value &= ~0x03 # mask off old mode. -# reg_value |= mode.value -# reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) + def set_mode(self, mode: DeviceMode) -> ReplyHarpMessage: + """Change the device's OPMODE. Reply can be ignored.""" + address = CommonRegisters.OPERATION_CTRL + # Read register first. + reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_int() + reg_value &= ~0x03 # mask off old mode. + reg_value |= mode.value + reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) + return reply # # def enable_alive_en(self): # """Enable ALIVE_EN such that the device sends an event each second.""" @@ -217,27 +220,35 @@ def disable_alive_en(self): def send(self, message_bytes: bytearray, dump: bool = True) -> ReplyHarpMessage: - + """Send a harp message; return the device's reply.""" self._ser.write(message_bytes) # TODO: handle case where read is None - - message_type = self._ser.read(1)[0] # byte array with only one byte - message_length = self._ser.read(1)[0] - message_content = self._ser.read(message_length) - - frame = bytearray() - frame.append(message_type) - frame.append(message_length) - frame += message_content - - reply: ReplyHarpMessage = HarpMessage.parse(frame) + reply: ReplyHarpMessage = self._read() if dump: self._dump_reply(reply.frame) return reply + + def _read(self) -> Union[ReplyHarpMessage, None]: + """Read an incoming serial message.""" + try: + message_type = self._ser.read(1)[0] # byte array with only one byte + message_length = self._ser.read(1)[0] + message_content = self._ser.read(message_length) + + frame = bytearray() + frame.append(message_type) + frame.append(message_length) + frame += message_content + + return HarpMessage.parse(frame) + except IndexError: + return None + + def _dump_reply(self, reply: bytes): assert self._dump_file_path is not None with self._dump_file_path.open(mode="ab") as f: diff --git a/pyharp/drivers/behavior.py b/pyharp/drivers/behavior.py index 1a0f6e2..77081d3 100644 --- a/pyharp/drivers/behavior.py +++ b/pyharp/drivers/behavior.py @@ -5,14 +5,16 @@ from pyharp.device import Device import serial from serial.serialutil import SerialException +from enum import Enum +# These definitions are from app_regs.h in the firmware. # Type, Base Address, "Description." REGISTERS = \ { # RJ45 "PORT" (0, 1, 2) Digital Inputs "PORT_DIS" : ("U8", 32, "Reflects the state of DI digital lines of each Port."), - # Manipulate any of the boards digital outputs. + # Manipulate any of the board's digital outputs. "OUTPUTS_SET": ("U16", 34, "Set the corresponding output."), "OUTPUTS_CLR": ("U16", 35, "Clear the corresponding output."), "OUTPUTS_TOGGLE": ("U16", 36, "Toggle the corresponding output."), @@ -25,9 +27,48 @@ "PORT_DIOS_OUT": ("U8", 41, "Control the corresponding DIO."), "PORT_DIOS_CONF": ("U8", 42, "Set the DIOs direction (1 is output)."), "PORT_DIOS_IN": ("U8", 43, "State of the DIOs."), + + "EVNT_ENABLE": ("U8", 77, "Enable events within the bitfields."), } +# Register Bitfields +class PORT_DIS(Enum): + DI0 = 0 + DI1 = 1 + DI2 = 2 + +class OUTPUTS_OUT(Enum): + PORT0_DO = 0 + PORT0_D1 = 1 + PORT0_D2 = 2 + + PORT0_12V = 3 + PORT1_12V = 4 + PORT2_12V = 5 + + B_LED0 = 6 + B_LED1 = 7 + B_RGB0 = 8 + B_RGB1 = 9 + + DO0 = 10 + DO1 = 11 + DO2 = 12 + DO3 = 13 + +class PORT_DIOS_IN(Enum): + DIO0 = 0 + DIO1 = 0 + DIO2 = 0 + +class EVNT_ENABLE(Enum): + PORT_DIS = 0 + PORT_DIOS_IN = 1 + DATA = 2 + CAM0 = 3 + CAM1 = 4 + class Behavior: """Driver for BehaviorDevice.""" @@ -72,7 +113,6 @@ def __init__(self, port_name=None, output_filename=None): raise IOError("Error: Did not connect to Harp Behavior Device.") - # TODO: put this in a base class? def get_reg_info(self, reg_name: str) -> str: """get info for this device's particular reg.""" try: @@ -85,70 +125,95 @@ def get_reg_info(self, reg_name: str) -> str: # Board inputs, outputs, and some settings configured as @properties. # INPUTS @property - def all_port_input_states(self): + def all_input_states(self): """return the state of all PORT digital inputs.""" reg_type, reg_index, _ = REGISTERS["PORT_DIS"] read_message_type = self.__class__.READ_MSG_LOOKUP[reg_type] return self.device.send(read_message_type(reg_index).frame).payload_as_int() @property - def port0_i0(self): - """return the state of port0 digital in 0.""" + def DI0(self): + """return the state of port0 digital input 0.""" return self.all_port_input_states & 0x01 @property - def port1_i0(self): - """return the state of port0 digital in 0.""" - return (self.all_port_input_states >> 1) & 0x01 - - @property - def port2_i0(self): - """return the state of port2 digital in 0.""" - return (self.all_port_input_states >> 2) & 0x01 - - # IOs - def set_port_io_states(self, bitmask : int): - """set the state of all PORT digital ios. (1 is output.)""" - reg_type, reg_index, _ = REGISTERS["PORT_DIOS_CONF"] - write_message_type = self.__class__.WRITE_MSG_LOOKUP[reg_type] - self.device.send(write_message_type(reg_index, bitmask).frame) - - @property #FIXME: this doesn't seem to work - def all_port_io_states(self): - """return the state of all PORT digital ios.""" - reg_type, reg_index, _ = REGISTERS["PORT_DIOS_IN"] - read_message_type = self.__class__.READ_MSG_LOOKUP[reg_type] - return self.device.send(read_message_type(reg_index).frame).payload_as_int() - - @property - def port0_io0(self): - """read the digital io state.""" - return self.all_port_io_states & 0x01 - - @port0_io0.setter - def port0_io0(self, value: int): - """write port0 digital io state.""" - pass - - @property - def port1_io0(self): - """read the digital io state.""" - return (self.all_port_io_states >> 1) & 0x01 - - @port0_io0.setter - def port1_io0(self, value: int): - """write port0 digital io state.""" - self.set_outputs(value&0x01) + def DI1(self): + """return the state of port1 digital input 0.""" + offset = PORT_DIS.DI1.value + return (self.all_port_input_states >> offset) & 0x01 @property - def port2_io0(self): - """read the digital io state.""" - return (self.all_port_io_states >> 2) & 0x01 - - @port0_io0.setter - def port2_io0(self, value: int): - """write port0 digital io state.""" - pass + def DI2(self): + """return the state of port2 digital input 0.""" + offset = PORT_DIS.DI2.value + return (self.all_input_states >> offset) & 0x01 + +# These do not work currently. Perhaps something needs to be cleared (MIMIC?) +# before they will configure properly. +# # IOs +# def set_io_configuration(self, bitmask : int): +# """set the state of all PORT digital ios. (1 is output.)""" +# reg_type, reg_index, _ = REGISTERS["PORT_DIOS_CONF"] +# write_message_type = self.__class__.WRITE_MSG_LOOKUP[reg_type] +# self.device.send(write_message_type(reg_index, bitmask).frame) +# +# @property +# def all_io_states(self): +# """return the state of all PORT digital ios.""" +# reg_type, reg_index, _ = REGISTERS["PORT_DIOS_IN"] +# read_message_type = self.__class__.READ_MSG_LOOKUP[reg_type] +# return self.device.send(read_message_type(reg_index).frame).payload_as_int() +# +# @all_io_states.setter +# def all_io_states(self, bitmask : int): +# """set the state of all PORT digital input/outputs.""" +# # Setting the state of the "DIO" pins, requires writing to the +# # _IN register, which is different from the OUTPUT +# reg_type, reg_index, _ = REGISTERS["PORT_DIOS_IN"] +# write_message_type = self.__class__.WRITE_MSG_LOOKUP[reg_type] +# return self.device.send(write_message_type(reg_index, bitmask).frame) +# +# def set_io_outputs(self, bitmask : int): +# """set digital input/outputs to logic 1 according to bitmask.""" +# reg_type, reg_index, _ = REGISTERS["PORT_DIOS_SET"] +# write_message_type = self.__class__.WRITE_MSG_LOOKUP[reg_type] +# return self.device.send(write_message_type(reg_index, bitmask).frame) +# +# def clear_io_outputs(self, bitmask : int): +# """clear digital input/outputs (specified with logic 1) according to bitmask.""" +# reg_type, reg_index, _ = REGISTERS["PORT_DIOS_CLEAR"] +# write_message_type = self.__class__.WRITE_MSG_LOOKUP[reg_type] +# return self.device.send(write_message_type(reg_index, bitmask).frame) +# +# @property +# def port0_io0(self): +# """read the digital io state.""" +# return self.all_port_io_states & 0x01 +# +# @port0_io0.setter +# def port0_io0(self, value: int): +# """write port0 digital io state.""" +# pass +# +# @property +# def port1_io0(self): +# """read the digital io state.""" +# return (self.all_port_io_states >> 1) & 0x01 +# +# @port0_io0.setter +# def port1_io0(self, value: int): +# """write port0 digital io state.""" +# self.set_outputs(value&0x01) +# +# @property +# def port2_io0(self): +# """read the digital io state.""" +# return (self.all_port_io_states >> 2) & 0x01 +# +# @port0_io0.setter +# def port2_io0(self, value: int): +# """write port0 digital io state.""" +# pass # OUTPUTS @@ -298,6 +363,8 @@ def port2_12V(self, value): self.clear_outputs(1 << 5) + + def __enter__(self): """Setup for the 'with' statement""" return self diff --git a/pyharp/messages.py b/pyharp/messages.py index cab9351..d1035b2 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -89,7 +89,7 @@ def frame(self) -> bytearray: return self._frame @property - def message_type(self) -> int: + def message_type(self) -> MessageType: return MessageType(self._frame[0]) @property @@ -162,29 +162,51 @@ def __init__( """ self._frame = frame - self._timestamp = int.from_bytes(frame[5:9], byteorder="little", signed=False) + \ - int.from_bytes(frame[9:11], byteorder="little", signed=False)*32e-6 # retrieve all content from 11 (where payload starts) until the checksum (not inclusive) self._payload = frame[11:-1] + # Assign timestamp after _payload since @properties all rely on self._payload. + self._timestamp = int.from_bytes(frame[5:9], byteorder="little", signed=False) + \ + int.from_bytes(frame[9:11], byteorder="little", signed=False)*32e-6 + # Timestamp is junk if it's not present. + if self.payload_type.value & PayloadType.hasTimestamp.value: + self._timestamp = None + + + def __repr__(self): + """Print debug representation of a reply message.""" + print(self.__str__()) + print(f"Raw Frame: {self.frame}") + + def __str__(self): + """Print friendly representation of a reply message.""" + payload_str = "" + format_str = "" + if self.payload_type.value & 0x01: + format_str = '08b' + elif self.payload_type.value & 0x02: + format_str = '016b' + elif self.payload_type.value & 0x04: + if self.message_type.value & PayloadType.isFloat.value: + format_str = '.6f' + else: + format_str = '032b' + elif self.payload_type.value & 0x08: + format_str = '064b' + + for item in self.payload: + payload_str += f"{item:{format_str}} " + return f"Type: {self.message_type.name}\r\n" + \ f"Length: {self.length}\r\n" + \ f"Address: {self.address}\r\n" + \ f"Port: {self.port}\r\n" + \ - f"Timestampe: {self.timestamp:6f}\r\n" + \ + f"Timestamp: {self.timestamp}\r\n" + \ f"Payload Type: {self.payload_type.name}\r\n" + \ - f"Payload: {self.payload}\r\n" + \ - f"Checksum: {self.checksum}\r\n" + \ - f"Raw Frame: {self.frame}\r\n" - # print(f"Type: {self.message_type.name}") - # print(f"Length: {self.length}") - # print(f"Address: {self.address}") - # print(f"Port: {self.port}") - # print(f"Payload Type: {self.payload_type.name}") - # print(f"Payload: {self.payload}") - # print(f"Checksum: {self.checksum}") - # print(f"Frame: {self.frame}") + f"Payload Length: {len(self.payload)}\r\n" + \ + f"Payload: {payload_str}\r\n" + \ + f"Checksum: {self.checksum}" @property def payload(self) -> bytes: From edfc3618647cf5bbe0a912dcfaaa7685d36146da Mon Sep 17 00:00:00 2001 From: Joshua Vasquez Date: Wed, 20 Apr 2022 18:09:29 -0700 Subject: [PATCH 016/159] printing DI event changes works --- examples/wait_for_events.py | 2 +- pyharp/messages.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/wait_for_events.py b/examples/wait_for_events.py index 44c2b30..291cecf 100755 --- a/examples/wait_for_events.py +++ b/examples/wait_for_events.py @@ -21,5 +21,5 @@ import time while True: event_response = device.device._read() # read any incoming events. - if event_response is not None: + if event_response is not None and event_response.address != 44: print(event_response) diff --git a/pyharp/messages.py b/pyharp/messages.py index d1035b2..da81795 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -169,7 +169,7 @@ def __init__( self._timestamp = int.from_bytes(frame[5:9], byteorder="little", signed=False) + \ int.from_bytes(frame[9:11], byteorder="little", signed=False)*32e-6 # Timestamp is junk if it's not present. - if self.payload_type.value & PayloadType.hasTimestamp.value: + if not (self.payload_type.value & PayloadType.hasTimestamp.value): self._timestamp = None From d5d835a185fe903a5b9c0426fb84a32e2b077a49 Mon Sep 17 00:00:00 2001 From: Joshua Vasquez Date: Thu, 21 Apr 2022 12:08:11 -0700 Subject: [PATCH 017/159] updating how payload is parsed --- examples/wait_for_events.py | 2 +- pyharp/device.py | 44 ++++++++++++++----------- pyharp/messages.py | 64 +++++++++++++------------------------ 3 files changed, 49 insertions(+), 61 deletions(-) diff --git a/examples/wait_for_events.py b/examples/wait_for_events.py index 291cecf..5248f00 100755 --- a/examples/wait_for_events.py +++ b/examples/wait_for_events.py @@ -18,8 +18,8 @@ print("Setting mode to active.") device.device.set_mode(DeviceMode.Active) -import time while True: event_response = device.device._read() # read any incoming events. if event_response is not None and event_response.address != 44: + print() print(event_response) diff --git a/pyharp/device.py b/pyharp/device.py index 4b71f78..5d3c7f3 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -193,29 +193,37 @@ def set_mode(self, mode: DeviceMode) -> ReplyHarpMessage: reg_value |= mode.value reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) return reply -# -# def enable_alive_en(self): -# """Enable ALIVE_EN such that the device sends an event each second.""" -# address = CommonRegisters.OPERATION_CTRL -# # Read register first. -# reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_int() -# reg_value |= (1 << 7) -# reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) -# -# def enable_status_led(self): -# """enable the device's status led if one exists.""" -# address = CommonRegisters.OPERATION_CTRL -# # Read register first. -# reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_int() -# reg_value |= (1 << 6) -# reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) + + def enable_status_led(self): + """enable the device's status led if one exists.""" + address = CommonRegisters.OPERATION_CTRL + # Read register first. + reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_int() + reg_value |= (1 << 6) + reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) + + def enable_status_led(self): + """enable the device's status led if one exists.""" + address = CommonRegisters.OPERATION_CTRL + # Read register first. + reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_int() + reg_value &= ~(1 << 6) + reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) + + def enable_alive_en(self): + """Enable ALIVE_EN such that the device sends an event each second.""" + address = CommonRegisters.OPERATION_CTRL + # Read register first. + reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_int() + reg_value |= (1 << 7) + reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) def disable_alive_en(self): """disable ALIVE_EN such that the device does not send an event each second.""" address = CommonRegisters.OPERATION_CTRL # Read register first. - reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_int() - reg_value &= ~(1 << 7) # mask off old mode. + reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_fixed_int() + reg_value != (1<< 7 & 0xFF) reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) diff --git a/pyharp/messages.py b/pyharp/messages.py index da81795..2531fb2 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -39,18 +39,6 @@ class PayloadType(Enum): TimestampedFloat = hasTimestamp | Float -ALL_UNSIGNED = [PayloadType.U8, - PayloadType.U16, - PayloadType.U32, - PayloadType.TimestampedU8, - PayloadType.TimestampedU16] -ALL_SIGNED = [PayloadType.S8, - PayloadType.S16, - PayloadType.S32, - PayloadType.TimestampedS8, - PayloadType.TimestampedS16] - - class CommonRegisters: WHO_AM_I = 0x00 HW_VERSION_H = 0x01 @@ -163,7 +151,8 @@ def __init__( self._frame = frame # retrieve all content from 11 (where payload starts) until the checksum (not inclusive) - self._payload = frame[11:-1] + self._raw_payload = frame[11:-1] + self._payload = self._parse_payload(self._raw_payload) # payload formatted as list[payload type] # Assign timestamp after _payload since @properties all rely on self._payload. self._timestamp = int.from_bytes(frame[5:9], byteorder="little", signed=False) + \ @@ -173,6 +162,16 @@ def __init__( self._timestamp = None + def _parse_payload(self, raw_payload) -> list[int]: + """return the payload as a list of ints after parsing it from the raw payload.""" + is_signed = True if (self.payload_type.value & 0x80) else False + bytes_per_word = self.payload_type.value & 0x07 + payload_len = len(raw_payload) + + word_chunks = [raw_payload[i:i+bytes_per_word] for i in range(0, payload_len, bytes_per_word)] + return [int.from_bytes(chunk, byteorder="little", signed=is_signed) for chunk in word_chunks] + + def __repr__(self): """Print debug representation of a reply message.""" print(self.__str__()) @@ -183,17 +182,11 @@ def __str__(self): """Print friendly representation of a reply message.""" payload_str = "" format_str = "" - if self.payload_type.value & 0x01: - format_str = '08b' - elif self.payload_type.value & 0x02: - format_str = '016b' - elif self.payload_type.value & 0x04: - if self.message_type.value & PayloadType.isFloat.value: - format_str = '.6f' - else: - format_str = '032b' - elif self.payload_type.value & 0x08: - format_str = '064b' + if self.payload_type == PayloadType.Float: + format_str = '.6f' + else: + bytes_per_word = self.payload_type.value & 0x07 + format_str = f'0{bytes_per_word}b' for item in self.payload: payload_str += f"{item:{format_str}} " @@ -205,11 +198,12 @@ def __str__(self): f"Timestamp: {self.timestamp}\r\n" + \ f"Payload Type: {self.payload_type.name}\r\n" + \ f"Payload Length: {len(self.payload)}\r\n" + \ - f"Payload: {payload_str}\r\n" + \ + f"Payload: {self.payload}\r\n" + \ f"Checksum: {self.checksum}" @property - def payload(self) -> bytes: + def payload(self) -> Union[int, list[int]]: + """return the payload formatted as the appropriate type.""" return self._payload @property @@ -217,24 +211,10 @@ def timestamp(self) -> float: return self._timestamp def payload_as_int(self) -> int: - value: int = 0 - if self.payload_type in ALL_UNSIGNED: - value = int.from_bytes(self.payload, byteorder="little", signed=False) - elif self.payload_type in ALL_SIGNED: - value = int.from_bytes(self.payload, byteorder="little", signed=True) - return value - - def payload_as_int_array(self) -> list: - # Number of bytes per chunk. Get this from the bit field structure. - datatype_bytes = 0x0F & self.payload_type.value - # TODO: is len(self.payload) == self.length? - signed = True if self.payload_type in ALL_UNSIGNED else False - # Break the payload into chunks of datatype size in bytes - byte_chunks = [self.payload[i: i+datatype_bytes] for i in range(0, len(self.payload), datatype_bytes)] - return [int.from_bytes(chunk, byteorder="little", signed=signed) for chunk in byte_chunks] + return self.payload[0] def payload_as_string(self) -> str: - return self.payload.decode("utf-8") + return self._raw_payload.decode("utf-8") # A Read Request Message sent to a harp device. From 3a122a3dd11b5ec35a6ccb3b33b4f26c03e5c49f Mon Sep 17 00:00:00 2001 From: Joshua Vasquez Date: Thu, 21 Apr 2022 17:17:57 -0700 Subject: [PATCH 018/159] adding enabling of events --- examples/wait_for_events.py | 6 +++-- pyharp/device.py | 10 ++++---- pyharp/drivers/behavior.py | 46 +++++++++++++++++++++++++++++-------- pyharp/messages.py | 2 +- 4 files changed, 46 insertions(+), 18 deletions(-) diff --git a/examples/wait_for_events.py b/examples/wait_for_events.py index 5248f00..8f1b9f8 100755 --- a/examples/wait_for_events.py +++ b/examples/wait_for_events.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -from pyharp.drivers.behavior import Behavior +from pyharp.drivers.behavior import Behavior, Events from pyharp.messages import HarpMessage from pyharp.messages import MessageType from struct import * @@ -18,8 +18,10 @@ print("Setting mode to active.") device.device.set_mode(DeviceMode.Active) +device.disable_all_events() +device.enable_events(Events.port_digital_inputs) while True: event_response = device.device._read() # read any incoming events. - if event_response is not None and event_response.address != 44: + if event_response is not None:# and event_response.address != 44: print() print(event_response) diff --git a/pyharp/device.py b/pyharp/device.py index 5d3c7f3..8eeb366 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -32,10 +32,6 @@ class Device: HARP_VERSION_L: int FIRMWARE_VERSION_H: int FIRMWARE_VERSION_L: int - # TIMESTAMP_SECOND = 0x08 - # TIMESTAMP_MICRO = 0x09 - # OPERATION_CTRL = 0x0A - # RESET_DEV = 0x0B DEVICE_NAME: str def __init__(self, serial_port: str, dump_file_path: Optional[str] = None): @@ -222,8 +218,8 @@ def disable_alive_en(self): """disable ALIVE_EN such that the device does not send an event each second.""" address = CommonRegisters.OPERATION_CTRL # Read register first. - reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_fixed_int() - reg_value != (1<< 7 & 0xFF) + reg_value = self.send(HarpMessage.ReadU8(address).frame).payload[0] + reg_value &= ((1<< 7) ^ 0xFF) # bitwise ~ operator substitute for Python ints. reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) @@ -232,6 +228,8 @@ def send(self, message_bytes: bytearray, dump: bool = True) -> ReplyHarpMessage: self._ser.write(message_bytes) # TODO: handle case where read is None + # FIXME: waiting for a message reply like this + # breaks if events are also being broadcasted (i.e: in ActiveMode). reply: ReplyHarpMessage = self._read() if dump: diff --git a/pyharp/drivers/behavior.py b/pyharp/drivers/behavior.py index 77081d3..989f89a 100644 --- a/pyharp/drivers/behavior.py +++ b/pyharp/drivers/behavior.py @@ -28,6 +28,8 @@ "PORT_DIOS_CONF": ("U8", 42, "Set the DIOs direction (1 is output)."), "PORT_DIOS_IN": ("U8", 43, "State of the DIOs."), + "ADD_REG_DATA": ("S16", 44, "Voltage at ADC input and decoder (poke2) value."), + "EVNT_ENABLE": ("U8", 77, "Enable events within the bitfields."), } @@ -62,16 +64,18 @@ class PORT_DIOS_IN(Enum): DIO1 = 0 DIO2 = 0 -class EVNT_ENABLE(Enum): - PORT_DIS = 0 - PORT_DIOS_IN = 1 - DATA = 2 - CAM0 = 3 - CAM1 = 4 + +# reader-friendly events for enabling/disabling. +class Events(Enum): + port_digital_inputs = 0 # PORT_DIS + port_digital_ios = 1 # PORT_DIOS_IN + analog_input = 2 # DATA + cam0 = 3 # CAM0 + cam1 = 3 # CAM1 class Behavior: - """Driver for BehaviorDevice.""" + """Driver for Behavior Device.""" # On Linux, the symlink to the first detected harp device. # Name set in udev rules and will increment with subsequent devices. @@ -122,6 +126,28 @@ def get_reg_info(self, reg_name: str) -> str: "{self.__class__.name} Device's register map.") + def disable_all_events(self) -> ReplyHarpMessage: + """Disable the publishing of all events from Behavior device.""" + event_reg_bitmask = (((1 << Events.port_digital_inputs.value) | \ + (1 << Events.port_digital_ios.value) | \ + (1 << Events.analog_input.value) | \ + (1 << Events.cam0.value) | \ + (1 << Events.cam1.value) ) ^ 0xFF) + reg_type, reg_index, _ = REGISTERS["EVNT_ENABLE"] + write_message_type = self.__class__.WRITE_MSG_LOOKUP[reg_type] + return self.device.send(write_message_type(reg_index, event_reg_bitmask).frame) + + + def enable_events(self, *events: Events) -> ReplyHarpMessage: + """enable any events passed in as arguments.""" + event_reg_bitmask = 0x00 + for event in events: + event_reg_bitmask |= (1 << event.value) + reg_type, reg_index, _ = REGISTERS["EVNT_ENABLE"] + write_message_type = self.__class__.WRITE_MSG_LOOKUP[reg_type] + return self.device.send(write_message_type(reg_index, event_reg_bitmask).frame) + + # Board inputs, outputs, and some settings configured as @properties. # INPUTS @property @@ -372,9 +398,11 @@ def __enter__(self): def __exit__(self, *args): """Cleanup for the 'with' statement""" - self.device.disconnect() + if self.device is not None: + self.device.disconnect() def __del__(self): """Cleanup when Device gets garbage collected.""" - self.device.disconnect() + if self.device is not None: + self.device.disconnect() diff --git a/pyharp/messages.py b/pyharp/messages.py index 2531fb2..e458758 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -166,7 +166,7 @@ def _parse_payload(self, raw_payload) -> list[int]: """return the payload as a list of ints after parsing it from the raw payload.""" is_signed = True if (self.payload_type.value & 0x80) else False bytes_per_word = self.payload_type.value & 0x07 - payload_len = len(raw_payload) + payload_len = len(raw_payload) # payload length in bytes. word_chunks = [raw_payload[i:i+bytes_per_word] for i in range(0, payload_len, bytes_per_word)] return [int.from_bytes(chunk, byteorder="little", signed=is_signed) for chunk in word_chunks] From 2fc5c5866a51a4d29b19bde5339ea910672b8fed Mon Sep 17 00:00:00 2001 From: Joshua Vasquez Date: Fri, 22 Apr 2022 12:55:00 -0700 Subject: [PATCH 019/159] adding inWaiting to reads --- examples/wait_for_events.py | 1 + pyharp/device.py | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/examples/wait_for_events.py b/examples/wait_for_events.py index 8f1b9f8..18c8d29 100755 --- a/examples/wait_for_events.py +++ b/examples/wait_for_events.py @@ -17,6 +17,7 @@ device = Behavior("COM95", "ibl.bin") print("Setting mode to active.") +# Mode will remain active for up to 3 seconds after CTS pin is brought low. device.device.set_mode(DeviceMode.Active) device.disable_all_events() device.enable_events(Events.port_digital_inputs) diff --git a/pyharp/device.py b/pyharp/device.py index 8eeb366..2bae568 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -239,7 +239,11 @@ def send(self, message_bytes: bytearray, dump: bool = True) -> ReplyHarpMessage: def _read(self) -> Union[ReplyHarpMessage, None]: - """Read an incoming serial message.""" + """(Blocking) Read an incoming serial message.""" + # block until we get at least one byte. + while True: + if self._ser.inWaiting(): + break try: message_type = self._ser.read(1)[0] # byte array with only one byte message_length = self._ser.read(1)[0] From 3f7dfba5e12ef84842351f9408c5eaa6f1256a6b Mon Sep 17 00:00:00 2001 From: Joshua Vasquez Date: Thu, 28 Sep 2023 14:24:13 -0700 Subject: [PATCH 020/159] read dumped registers --- examples/get_info.py | 6 +++++- pyharp/device.py | 51 ++++++++++++++++++++++++++++++++++++++------ pyharp/messages.py | 3 +-- 3 files changed, 50 insertions(+), 10 deletions(-) diff --git a/examples/get_info.py b/examples/get_info.py index 44e4f18..ebb202c 100755 --- a/examples/get_info.py +++ b/examples/get_info.py @@ -15,7 +15,9 @@ # Open the device and print the info on screen # Open serial connection and save communication to a file if os.name == 'posix': # check for Linux. - device = Device("/dev/harp_device_00", "ibl.bin") + #device = Device("/dev/harp_device_00", "ibl.bin") + #device = Device("/dev/ttyACM0") + device = Device("/dev/ttyUSB0") else: # assume Windows. device = Device("COM95", "ibl.bin") device.info() # Display device's info on screen @@ -34,5 +36,7 @@ device_harp_l = device.HARP_VERSION_L # Get device's harp core version device_assembly = device.ASSEMBLY_VERSION # Get device's assembly version +print(device.dump_registers()) + # Close connection device.disconnect() diff --git a/pyharp/device.py b/pyharp/device.py index 2bae568..9278273 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -3,9 +3,10 @@ from pathlib import Path from pyharp.messages import HarpMessage, ReplyHarpMessage -from pyharp.messages import CommonRegisters +from pyharp.messages import CommonRegisters, MessageType from pyharp.device_names import device_names from enum import Enum +from time import perf_counter class DeviceMode(Enum): @@ -34,6 +35,8 @@ class Device: FIRMWARE_VERSION_L: int DEVICE_NAME: str + TIMEOUT_S = 1.0 + def __init__(self, serial_port: str, dump_file_path: Optional[str] = None): self._serial_port = serial_port if dump_file_path is None: @@ -72,7 +75,7 @@ def connect(self) -> None: self._ser = serial.Serial( self._serial_port, # "/dev/tty.usbserial-A106C8O9" baudrate=1000000, - timeout=1, + timeout=self.__class__.TIMEOUT_S, parity=serial.PARITY_NONE, stopbits=1, bytesize=8, @@ -88,7 +91,7 @@ def read_who_am_i(self) -> int: reply: ReplyHarpMessage = self.send( HarpMessage.ReadU16(address).frame, dump=False ) - + #print(str(reply)) return reply.payload_as_int() def read_who_am_i_device(self) -> str: @@ -97,6 +100,7 @@ def read_who_am_i_device(self) -> str: reply: ReplyHarpMessage = self.send( HarpMessage.ReadU16(address).frame, dump=False ) + #print(str(reply)) return device_names.get(reply.payload_as_int()) @@ -106,6 +110,7 @@ def read_hw_version_h(self) -> int: reply: ReplyHarpMessage = self.send( HarpMessage.ReadU8(address).frame, dump=False ) + #print(str(reply)) return reply.payload_as_int() @@ -115,6 +120,7 @@ def read_hw_version_l(self) -> int: reply: ReplyHarpMessage = self.send( HarpMessage.ReadU8(address).frame, dump=False ) + #print(str(reply)) return reply.payload_as_int() @@ -124,6 +130,7 @@ def read_assembly_version(self) -> int: reply: ReplyHarpMessage = self.send( HarpMessage.ReadU8(address).frame, dump=False ) + #print(str(reply)) return reply.payload_as_int() @@ -133,6 +140,7 @@ def read_harp_h_version(self) -> int: reply: ReplyHarpMessage = self.send( HarpMessage.ReadU8(address).frame, dump=False ) + #print(str(reply)) return reply.payload_as_int() @@ -142,6 +150,7 @@ def read_harp_l_version(self) -> int: reply: ReplyHarpMessage = self.send( HarpMessage.ReadU8(address).frame, dump=False ) + #print(str(reply)) return reply.payload_as_int() @@ -151,6 +160,7 @@ def read_fw_h_version(self) -> int: reply: ReplyHarpMessage = self.send( HarpMessage.ReadU8(address).frame, dump=False ) + #print(str(reply)) return reply.payload_as_int() @@ -160,6 +170,7 @@ def read_fw_l_version(self) -> int: reply: ReplyHarpMessage = self.send( HarpMessage.ReadU8(address).frame, dump=False ) + #print(str(reply)) return reply.payload_as_int() @@ -170,15 +181,32 @@ def read_device_name(self) -> str: reply: ReplyHarpMessage = self.send( HarpMessage.ReadU8(address).frame, dump=False ) + #print(str(reply)) return reply.payload_as_string() def read_device_mode(self) -> DeviceMode: address = CommonRegisters.OPERATION_CTRL reply = self.send(HarpMessage.ReadU8(address).frame) - print(reply) return DeviceMode(reply.payload_as_int() & 0x03) + def dump_registers(self) -> list: + """Assert the DUMP bit to dump the values of all core and app registers + as Harp Read Reply Messages. + """ + address = CommonRegisters.OPERATION_CTRL + reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_int() + reg_value |= 0x08 # Assert DUMP bit + self._ser.write(HarpMessage.WriteU8(address, reg_value).frame) + replies = [] + while True: + msg = self._read() + if msg is not None: + replies.append(msg) + else: + break + return replies + # TODO: Not sure if we want to implement these. Delete if no. def set_mode(self, mode: DeviceMode) -> ReplyHarpMessage: """Change the device's OPMODE. Reply can be ignored.""" @@ -225,6 +253,7 @@ def disable_alive_en(self): def send(self, message_bytes: bytearray, dump: bool = True) -> ReplyHarpMessage: """Send a harp message; return the device's reply.""" + #print(f"Sending: {repr(message_bytes)}") self._ser.write(message_bytes) # TODO: handle case where read is None @@ -232,7 +261,7 @@ def send(self, message_bytes: bytearray, dump: bool = True) -> ReplyHarpMessage: # breaks if events are also being broadcasted (i.e: in ActiveMode). reply: ReplyHarpMessage = self._read() - if dump: + if dump and self._dump_file_path is not None: self._dump_reply(reply.frame) return reply @@ -240,21 +269,29 @@ def send(self, message_bytes: bytearray, dump: bool = True) -> ReplyHarpMessage: def _read(self) -> Union[ReplyHarpMessage, None]: """(Blocking) Read an incoming serial message.""" - # block until we get at least one byte. + # block for up to TIMEOUT until we get at least one byte. + read_start = perf_counter() while True: if self._ser.inWaiting(): break + if perf_counter() - read_start >= self.__class__.TIMEOUT_S: + break try: message_type = self._ser.read(1)[0] # byte array with only one byte message_length = self._ser.read(1)[0] message_content = self._ser.read(message_length) + #print(f"Read back:") + #print(f" type: {MessageType(message_type).name}") + #print(f" length : {repr(message_length)}") + #print(f" payload: {list(message_content)}") frame = bytearray() frame.append(message_type) frame.append(message_length) frame += message_content + msg = HarpMessage.parse(frame) - return HarpMessage.parse(frame) + return msg except IndexError: return None diff --git a/pyharp/messages.py b/pyharp/messages.py index e458758..71d0199 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -174,8 +174,7 @@ def _parse_payload(self, raw_payload) -> list[int]: def __repr__(self): """Print debug representation of a reply message.""" - print(self.__str__()) - print(f"Raw Frame: {self.frame}") + return self.__str__() + f"\r\nRaw Frame: {self.frame}" def __str__(self): From 401bd36a77c687698e38d4faae36a3801d6b70ce Mon Sep 17 00:00:00 2001 From: Joshua Vasquez Date: Thu, 21 Dec 2023 13:57:34 -0800 Subject: [PATCH 021/159] expose more core features --- examples/get_info.py | 8 +++--- examples/wait_for_events.py | 13 +++++----- pyharp/device.py | 8 +++--- pyharp/messages.py | 51 ++++++++++++++++++++++++++++++++++--- 4 files changed, 64 insertions(+), 16 deletions(-) diff --git a/examples/get_info.py b/examples/get_info.py index ebb202c..f2d8e5e 100755 --- a/examples/get_info.py +++ b/examples/get_info.py @@ -16,8 +16,8 @@ # Open serial connection and save communication to a file if os.name == 'posix': # check for Linux. #device = Device("/dev/harp_device_00", "ibl.bin") - #device = Device("/dev/ttyACM0") - device = Device("/dev/ttyUSB0") + device = Device("/dev/ttyACM0") + #device = Device("/dev/ttyUSB0") else: # assume Windows. device = Device("COM95", "ibl.bin") device.info() # Display device's info on screen @@ -36,7 +36,9 @@ device_harp_l = device.HARP_VERSION_L # Get device's harp core version device_assembly = device.ASSEMBLY_VERSION # Get device's assembly version -print(device.dump_registers()) +reg_dump = device.dump_registers() +for i in range(11): + print(reg_dump[i]) # Close connection device.disconnect() diff --git a/examples/wait_for_events.py b/examples/wait_for_events.py index 18c8d29..845e7c4 100755 --- a/examples/wait_for_events.py +++ b/examples/wait_for_events.py @@ -5,24 +5,25 @@ from struct import * import os -from pyharp.device import DeviceMode +from pyharp.device import Device, DeviceMode # Open the device and print the info on screen # Open serial connection and save communication to a file device = None if os.name == 'posix': # check for Linux. - device = Behavior("/dev/harp_device_00", "ibl.bin") + #device = Behavior("/dev/harp_device_00", "ibl.bin") + device = Device("/dev/ttyACM0",) else: # assume Windows. device = Behavior("COM95", "ibl.bin") print("Setting mode to active.") # Mode will remain active for up to 3 seconds after CTS pin is brought low. -device.device.set_mode(DeviceMode.Active) -device.disable_all_events() -device.enable_events(Events.port_digital_inputs) +device.set_mode(DeviceMode.Active) +#device.disable_all_events() +#device.enable_events(Events.port_digital_inputs) while True: - event_response = device.device._read() # read any incoming events. + event_response = device._read() # read any incoming events. if event_response is not None:# and event_response.address != 44: print() print(event_response) diff --git a/pyharp/device.py b/pyharp/device.py index 9278273..166ce2f 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -223,15 +223,15 @@ def enable_status_led(self): address = CommonRegisters.OPERATION_CTRL # Read register first. reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_int() - reg_value |= (1 << 6) + reg_value |= (1 << 5) reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) - def enable_status_led(self): - """enable the device's status led if one exists.""" + def disable_status_led(self): + """disable the device's status led if one exists.""" address = CommonRegisters.OPERATION_CTRL # Read register first. reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_int() - reg_value &= ~(1 << 6) + reg_value &= ~(1 << 5) reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) def enable_alive_en(self): diff --git a/pyharp/messages.py b/pyharp/messages.py index 71d0199..7566e79 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -2,6 +2,7 @@ from enum import Enum # from abc import ABC, abstractmethod from typing import Union, Tuple, Optional +import struct class MessageType(Enum): @@ -93,7 +94,7 @@ def port(self) -> int: return self._frame[3] @property - def payload_type(self) -> int: + def payload_type(self) -> PayloadType: return PayloadType(self._frame[4]) @property @@ -116,6 +117,16 @@ def ReadS16(address: int) -> ReadS16HarpMessage: def ReadU16(address: int) -> ReadU16HarpMessage: return ReadU16HarpMessage(address) + # TODO: ReadS16 + + @staticmethod + def ReadU32(address: int) -> ReadU32HarpMessage: + return ReadU32HarpMessage(address) + + @staticmethod + def ReadFloat(address: int) -> ReadFloatHarpMessage: + return ReadFloatHarpMessage(address) + @staticmethod def WriteU8(address: int, value: int) -> WriteU8HarpMessage: return WriteU8HarpMessage(address, value) @@ -132,6 +143,10 @@ def WriteS16(address: int, value: int) -> WriteS16HarpMessage: def WriteU16(address: int, value: int) -> WriteU16HarpMessage: return WriteU16HarpMessage(address, value) + @staticmethod + def WriteFloat(address: int, value: int) -> WriteFloatHarpMessage: + return WriteFloatHarpMessage(address, value) + @staticmethod def parse(frame: bytearray) -> ReplyHarpMessage: return ReplyHarpMessage(frame) @@ -165,11 +180,15 @@ def __init__( def _parse_payload(self, raw_payload) -> list[int]: """return the payload as a list of ints after parsing it from the raw payload.""" is_signed = True if (self.payload_type.value & 0x80) else False + is_float = True if (self.payload_type.value & 0x40) else False bytes_per_word = self.payload_type.value & 0x07 payload_len = len(raw_payload) # payload length in bytes. word_chunks = [raw_payload[i:i+bytes_per_word] for i in range(0, payload_len, bytes_per_word)] - return [int.from_bytes(chunk, byteorder="little", signed=is_signed) for chunk in word_chunks] + if not is_float: + return [int.from_bytes(chunk, byteorder="little", signed=is_signed) for chunk in word_chunks] + else: # handle float case. + return [struct.unpack(' int: def payload_as_string(self) -> str: return self._raw_payload.decode("utf-8") + def payload_as_float(self) -> float: + return self.payload[0] # already parsed. + # A Read Request Message sent to a harp device. class ReadHarpMessage(HarpMessage): @@ -253,6 +275,15 @@ class ReadS16HarpMessage(ReadHarpMessage): def __init__(self, address: int): super().__init__(PayloadType.S16, address) +class ReadU32HarpMessage(ReadHarpMessage): + def __init__(self, address: int): + super().__init__(PayloadType.U32, address) + + +class ReadFloatHarpMessage(ReadHarpMessage): + def __init__(self, address: int): + super().__init__(PayloadType.Float, address) + class WriteHarpMessage(HarpMessage): BASE_LENGTH: int = 5 @@ -327,3 +358,17 @@ def __init__(self, address: int, value: int): @property def payload(self) -> int: return int.from_bytes(self._frame[5:7], byteorder="little", signed=True) + + +class WriteFloatHarpMessage(WriteHarpMessage): + def __init__(self, address: int, value: float): + super().__init__( + PayloadType.Float, + struct.pack(' float: + return struct.unpack(' Date: Thu, 21 Dec 2023 14:53:21 -0800 Subject: [PATCH 022/159] add WriteU32 and WriteS32 --- examples/get_info.py | 4 ++-- examples/wait_for_events.py | 3 ++- pyharp/messages.py | 30 ++++++++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/examples/get_info.py b/examples/get_info.py index f2d8e5e..d7b7a47 100755 --- a/examples/get_info.py +++ b/examples/get_info.py @@ -16,8 +16,8 @@ # Open serial connection and save communication to a file if os.name == 'posix': # check for Linux. #device = Device("/dev/harp_device_00", "ibl.bin") - device = Device("/dev/ttyACM0") - #device = Device("/dev/ttyUSB0") + #device = Device("/dev/ttyACM0") + device = Device("/dev/ttyUSB0") else: # assume Windows. device = Device("COM95", "ibl.bin") device.info() # Display device's info on screen diff --git a/examples/wait_for_events.py b/examples/wait_for_events.py index 845e7c4..b846d37 100755 --- a/examples/wait_for_events.py +++ b/examples/wait_for_events.py @@ -13,7 +13,8 @@ device = None if os.name == 'posix': # check for Linux. #device = Behavior("/dev/harp_device_00", "ibl.bin") - device = Device("/dev/ttyACM0",) + #device = Device("/dev/ttyACM0",) + device = Device("/dev/ttyUSB0",) else: # assume Windows. device = Behavior("COM95", "ibl.bin") diff --git a/pyharp/messages.py b/pyharp/messages.py index 7566e79..b7bd57d 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -147,6 +147,14 @@ def WriteU16(address: int, value: int) -> WriteU16HarpMessage: def WriteFloat(address: int, value: int) -> WriteFloatHarpMessage: return WriteFloatHarpMessage(address, value) + @staticmethod + def WriteU32(address: int, value: int) -> WriteU32HarpMessage: + return WriteU32HarpMessage(address, value) + + @staticmethod + def WriteS32(address: int, value: int) -> WriteS32HarpMessage: + return WriteS32HarpMessage(address, value) + @staticmethod def parse(frame: bytearray) -> ReplyHarpMessage: return ReplyHarpMessage(frame) @@ -372,3 +380,25 @@ def __init__(self, address: int, value: float): @property def payload(self) -> float: return struct.unpack(' int: + return int.from_bytes(self._frame[5:9], byteorder="little", signed=False) + + +class WriteS32HarpMessage(WriteHarpMessage): + def __init__(self, address: int, value: int): + super().__init__( + PayloadType.S32, value.to_bytes(4, byteorder="little", signed=False), address, offset=3 + ) + + @property + def payload(self) -> int: + return int.from_bytes(self._frame[5:9], byteorder="little", signed=False) From fb5447628e542f2ec7ff693efe8aed6b5406005a Mon Sep 17 00:00:00 2001 From: Joshua Vasquez Date: Thu, 21 Dec 2023 14:55:01 -0800 Subject: [PATCH 023/159] fix signed error in WriteS32 --- pyharp/messages.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyharp/messages.py b/pyharp/messages.py index b7bd57d..3d34af4 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -396,9 +396,9 @@ def payload(self) -> int: class WriteS32HarpMessage(WriteHarpMessage): def __init__(self, address: int, value: int): super().__init__( - PayloadType.S32, value.to_bytes(4, byteorder="little", signed=False), address, offset=3 + PayloadType.S32, value.to_bytes(4, byteorder="little", signed=True), address, offset=3 ) @property def payload(self) -> int: - return int.from_bytes(self._frame[5:9], byteorder="little", signed=False) + return int.from_bytes(self._frame[5:9], byteorder="little", signed=True) From e7900d41f8bc1a9f6a246fbf9768456466308479 Mon Sep 17 00:00:00 2001 From: Joshua Vasquez Date: Fri, 12 Jan 2024 14:57:43 -0800 Subject: [PATCH 024/159] add debug level logging. --- pyharp/device.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pyharp/device.py b/pyharp/device.py index 166ce2f..547639f 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -1,4 +1,5 @@ import serial +import logging from typing import Optional, Union from pathlib import Path @@ -38,6 +39,7 @@ class Device: TIMEOUT_S = 1.0 def __init__(self, serial_port: str, dump_file_path: Optional[str] = None): + self.log = logging.getLogger(f"{__name__}.{self.__class__.__name__}") self._serial_port = serial_port if dump_file_path is None: self._dump_file_path = None @@ -280,6 +282,9 @@ def _read(self) -> Union[ReplyHarpMessage, None]: message_type = self._ser.read(1)[0] # byte array with only one byte message_length = self._ser.read(1)[0] message_content = self._ser.read(message_length) + self.log.debug(f"reply (type): {message_type}") + self.log.debug(f"reply (length): {message_length}") + self.log.debug(f"reply (payload): {message_content}") #print(f"Read back:") #print(f" type: {MessageType(message_type).name}") #print(f" length : {repr(message_length)}") From 1b1e833e2ee750471c94623f28fc499a6134c314 Mon Sep 17 00:00:00 2001 From: "jessy.liao" Date: Fri, 17 May 2024 14:54:57 -0700 Subject: [PATCH 025/159] Added ReadS32 and refactored WriteS32 to handle array inputs --- pyharp/messages.py | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/pyharp/messages.py b/pyharp/messages.py index 3d34af4..e86747a 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -1,7 +1,7 @@ from __future__ import annotations # for type hints (PEP 563) from enum import Enum # from abc import ABC, abstractmethod -from typing import Union, Tuple, Optional +from typing import Union, Tuple, Optional, List import struct @@ -123,6 +123,10 @@ def ReadU16(address: int) -> ReadU16HarpMessage: def ReadU32(address: int) -> ReadU32HarpMessage: return ReadU32HarpMessage(address) + @staticmethod + def ReadS32(address: int) -> ReadS32HarpMessage: + return ReadS32HarpMessage(address) + @staticmethod def ReadFloat(address: int) -> ReadFloatHarpMessage: return ReadFloatHarpMessage(address) @@ -288,6 +292,11 @@ def __init__(self, address: int): super().__init__(PayloadType.U32, address) +class ReadS32HarpMessage(ReadHarpMessage): + def __init__(self, address: int): + super().__init__(PayloadType.S32, address) + + class ReadFloatHarpMessage(ReadHarpMessage): def __init__(self, address: int): super().__init__(PayloadType.Float, address) @@ -317,8 +326,12 @@ def __init__( self._frame.append(HarpMessage.DEFAULT_PORT) self._frame.append(payload_type.value) - for i in payload: - self._frame.append(i) + # Handle payloads that are bytes or bytearray (bytearray = multi-motor instructions) + if isinstance(payload, bytearray): + self._frame += payload + else: + for i in payload: + self._frame.append(i) self._frame.append(self.calculate_checksum()) @@ -394,11 +407,19 @@ def payload(self) -> int: class WriteS32HarpMessage(WriteHarpMessage): - def __init__(self, address: int, value: int): + def __init__(self, address: int, value: int | List[int]): + if isinstance(value, list): + payload = bytearray() + for val in value: + payload += val.to_bytes(4, byteorder="little", signed=True) + offset = 15 + else: + payload = value.to_bytes(4, byteorder="little", signed=True) + offset = 3 super().__init__( - PayloadType.S32, value.to_bytes(4, byteorder="little", signed=True), address, offset=3 + PayloadType.S32, payload, address, offset=offset ) @property - def payload(self) -> int: + def payload(self) -> int | List[int]: return int.from_bytes(self._frame[5:9], byteorder="little", signed=True) From 368e346dd66c17516974a339a0cf3f4807c02874 Mon Sep 17 00:00:00 2001 From: Patrick Latimer Date: Wed, 30 Oct 2024 16:23:22 -0700 Subject: [PATCH 026/159] Add threaded serial port --- pyharp/device.py | 171 ++++++++++++++++++++++++------------------ pyharp/harp_serial.py | 84 +++++++++++++++++++++ pyharp/messages.py | 95 ++++++++++++++--------- pyproject.toml | 1 + tests/test_device.py | 27 ++++++- 5 files changed, 269 insertions(+), 109 deletions(-) create mode 100644 pyharp/harp_serial.py diff --git a/pyharp/device.py b/pyharp/device.py index 547639f..4496859 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -1,9 +1,18 @@ import serial import logging +import threading +import queue from typing import Optional, Union from pathlib import Path -from pyharp.messages import HarpMessage, ReplyHarpMessage +from pyharp.harp_serial import HarpSerial +from pyharp.messages import ( + HarpMessage, + ReadHarpMessage, + ReplyHarpMessage, + # ResetDevOffsets, + Register, +) from pyharp.messages import CommonRegisters, MessageType from pyharp.device_names import device_names from enum import Enum @@ -22,7 +31,8 @@ class Device: https://github.com/harp-tech/protocol/blob/master/Device%201.1%201.0%2020220402.pdf """ - _ser: serial.Serial + # _ser: serial.Serial + _ser: HarpSerial _dump_file_path: Path WHO_AM_I: int @@ -38,13 +48,19 @@ class Device: TIMEOUT_S = 1.0 - def __init__(self, serial_port: str, dump_file_path: Optional[str] = None): + def __init__( + self, + serial_port: str, + dump_file_path: Optional[str] = None, + read_timeout_s=1, + ): self.log = logging.getLogger(f"{__name__}.{self.__class__.__name__}") self._serial_port = serial_port if dump_file_path is None: self._dump_file_path = None else: self._dump_file_path = Path() / dump_file_path + self.read_timeout_s = read_timeout_s self.connect() self.load() @@ -74,7 +90,8 @@ def read(self): pass def connect(self) -> None: - self._ser = serial.Serial( + self._ser = HarpSerial( + # self._ser = serial.Serial( self._serial_port, # "/dev/tty.usbserial-A106C8O9" baudrate=1000000, timeout=self.__class__.TIMEOUT_S, @@ -93,7 +110,7 @@ def read_who_am_i(self) -> int: reply: ReplyHarpMessage = self.send( HarpMessage.ReadU16(address).frame, dump=False ) - #print(str(reply)) + # print(str(reply)) return reply.payload_as_int() def read_who_am_i_device(self) -> str: @@ -102,77 +119,63 @@ def read_who_am_i_device(self) -> str: reply: ReplyHarpMessage = self.send( HarpMessage.ReadU16(address).frame, dump=False ) - #print(str(reply)) + # print(str(reply)) return device_names.get(reply.payload_as_int()) def read_hw_version_h(self) -> int: address = CommonRegisters.HW_VERSION_H - reply: ReplyHarpMessage = self.send( - HarpMessage.ReadU8(address).frame, dump=False - ) - #print(str(reply)) + reply: ReplyHarpMessage = self.send(HarpMessage.ReadU8(address).frame, dump=False) + # print(str(reply)) return reply.payload_as_int() def read_hw_version_l(self) -> int: address = CommonRegisters.HW_VERSION_L - reply: ReplyHarpMessage = self.send( - HarpMessage.ReadU8(address).frame, dump=False - ) - #print(str(reply)) + reply: ReplyHarpMessage = self.send(HarpMessage.ReadU8(address).frame, dump=False) + # print(str(reply)) return reply.payload_as_int() def read_assembly_version(self) -> int: address = CommonRegisters.ASSEMBLY_VERSION - reply: ReplyHarpMessage = self.send( - HarpMessage.ReadU8(address).frame, dump=False - ) - #print(str(reply)) + reply: ReplyHarpMessage = self.send(HarpMessage.ReadU8(address).frame, dump=False) + # print(str(reply)) return reply.payload_as_int() def read_harp_h_version(self) -> int: address = CommonRegisters.HARP_VERSION_H - reply: ReplyHarpMessage = self.send( - HarpMessage.ReadU8(address).frame, dump=False - ) - #print(str(reply)) + reply: ReplyHarpMessage = self.send(HarpMessage.ReadU8(address).frame, dump=False) + # print(str(reply)) return reply.payload_as_int() def read_harp_l_version(self) -> int: address = CommonRegisters.HARP_VERSION_L - reply: ReplyHarpMessage = self.send( - HarpMessage.ReadU8(address).frame, dump=False - ) - #print(str(reply)) + reply: ReplyHarpMessage = self.send(HarpMessage.ReadU8(address).frame, dump=False) + # print(str(reply)) return reply.payload_as_int() def read_fw_h_version(self) -> int: address = CommonRegisters.FIRMWARE_VERSION_H - reply: ReplyHarpMessage = self.send( - HarpMessage.ReadU8(address).frame, dump=False - ) - #print(str(reply)) + reply: ReplyHarpMessage = self.send(HarpMessage.ReadU8(address).frame, dump=False) + # print(str(reply)) return reply.payload_as_int() def read_fw_l_version(self) -> int: address = CommonRegisters.FIRMWARE_VERSION_L - reply: ReplyHarpMessage = self.send( - HarpMessage.ReadU8(address).frame, dump=False -) - #print(str(reply)) + reply: ReplyHarpMessage = self.send(HarpMessage.ReadU8(address).frame, dump=False) + # print(str(reply)) return reply.payload_as_int() @@ -180,10 +183,8 @@ def read_device_name(self) -> str: address = CommonRegisters.DEVICE_NAME # reply: Optional[bytes] = self.send(HarpMessage.ReadU8(address).frame, 13 + 24) - reply: ReplyHarpMessage = self.send( - HarpMessage.ReadU8(address).frame, dump=False - ) - #print(str(reply)) + reply: ReplyHarpMessage = self.send(HarpMessage.ReadU8(address).frame, dump=False) + # print(str(reply)) return reply.payload_as_string() @@ -209,13 +210,13 @@ def dump_registers(self) -> list: break return replies -# TODO: Not sure if we want to implement these. Delete if no. + # TODO: Not sure if we want to implement these. Delete if no. def set_mode(self, mode: DeviceMode) -> ReplyHarpMessage: """Change the device's OPMODE. Reply can be ignored.""" address = CommonRegisters.OPERATION_CTRL # Read register first. reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_int() - reg_value &= ~0x03 # mask off old mode. + reg_value &= ~0x03 # mask off old mode. reg_value |= mode.value reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) return reply @@ -225,7 +226,7 @@ def enable_status_led(self): address = CommonRegisters.OPERATION_CTRL # Read register first. reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_int() - reg_value |= (1 << 5) + reg_value |= 1 << 5 reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) def disable_status_led(self): @@ -241,7 +242,7 @@ def enable_alive_en(self): address = CommonRegisters.OPERATION_CTRL # Read register first. reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_int() - reg_value |= (1 << 7) + reg_value |= 1 << 7 reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) def disable_alive_en(self): @@ -249,18 +250,22 @@ def disable_alive_en(self): address = CommonRegisters.OPERATION_CTRL # Read register first. reg_value = self.send(HarpMessage.ReadU8(address).frame).payload[0] - reg_value &= ((1<< 7) ^ 0xFF) # bitwise ~ operator substitute for Python ints. + reg_value &= (1 << 7) ^ 0xFF # bitwise ~ operator substitute for Python ints. reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) + def reset_device(self): + address = CommonRegisters.RESET_DEV + # reset_value = 0xFF & (1< ReplyHarpMessage: """Send a harp message; return the device's reply.""" - #print(f"Sending: {repr(message_bytes)}") + # print(f"Sending: {repr(message_bytes)}") self._ser.write(message_bytes) # TODO: handle case where read is None - # FIXME: waiting for a message reply like this - # breaks if events are also being broadcasted (i.e: in ActiveMode). reply: ReplyHarpMessage = self._read() if dump and self._dump_file_path is not None: @@ -268,40 +273,60 @@ def send(self, message_bytes: bytearray, dump: bool = True) -> ReplyHarpMessage: return reply + # def _read(self) -> Union[ReplyHarpMessage, None]: + # """(Blocking) Read an incoming serial message.""" + # # block for up to TIMEOUT until we get at least one byte. + # read_start = perf_counter() + # while True: + # if self._ser.inWaiting(): + # break + # if perf_counter() - read_start >= self.__class__.TIMEOUT_S: + # break + # try: + # message_type = self._ser.read(1)[0] # byte array with only one byte + # message_length = self._ser.read(1)[0] + # message_content = self._ser.read(message_length) + # self.log.debug(f"reply (type): {message_type}") + # self.log.debug(f"reply (length): {message_length}") + # self.log.debug(f"reply (payload): {message_content}") + # # print(f"Read back:") + # # print(f" type: {MessageType(message_type).name}") + # # print(f" length : {repr(message_length)}") + # # print(f" payload: {list(message_content)}") + + # frame = bytearray() + # frame.append(message_type) + # frame.append(message_length) + # frame += message_content + # msg = HarpMessage.parse(frame) + + # return msg + # except IndexError: + # return None def _read(self) -> Union[ReplyHarpMessage, None]: """(Blocking) Read an incoming serial message.""" - # block for up to TIMEOUT until we get at least one byte. - read_start = perf_counter() - while True: - if self._ser.inWaiting(): - break - if perf_counter() - read_start >= self.__class__.TIMEOUT_S: - break try: - message_type = self._ser.read(1)[0] # byte array with only one byte - message_length = self._ser.read(1)[0] - message_content = self._ser.read(message_length) - self.log.debug(f"reply (type): {message_type}") - self.log.debug(f"reply (length): {message_length}") - self.log.debug(f"reply (payload): {message_content}") - #print(f"Read back:") - #print(f" type: {MessageType(message_type).name}") - #print(f" length : {repr(message_length)}") - #print(f" payload: {list(message_content)}") - - frame = bytearray() - frame.append(message_type) - frame.append(message_length) - frame += message_content - msg = HarpMessage.parse(frame) - - return msg - except IndexError: + return self._ser.msg_q.get(block=True, timeout=self.read_timeout_s) + except queue.Empty: return None - def _dump_reply(self, reply: bytes): assert self._dump_file_path is not None with self._dump_file_path.open(mode="ab") as f: f.write(reply) + + # def read_register(self, register_name: str): + # register: Register = CommonRegisters[register_name] + # ReadHarpMessage(register.type, register.address) + + # def write_register(self, register_name: str, value): + + def get_events(self) -> list[ReplyHarpMessage]: + msgs = [] + while True: + try: + msgs.append(self._ser.event_q.get(timeout=False)) + except queue.Empty: + break + return msgs diff --git a/pyharp/harp_serial.py b/pyharp/harp_serial.py new file mode 100644 index 0000000..85a0bb8 --- /dev/null +++ b/pyharp/harp_serial.py @@ -0,0 +1,84 @@ +from typing import Union +from functools import partial +import logging +import queue +import threading +import serial +import serial.threaded + +from pyharp.messages import HarpMessage, MessageType + + +class HarpSerialProtocol(serial.threaded.Protocol): + _read_q: queue.Queue + + def __init__(self, _read_q: queue.Queue, *args, **kwargs): + self._read_q = _read_q + super().__init__(*args, **kwargs) + + def connection_made(self, transport: serial.threaded.ReaderThread) -> None: + print(f"Connected to {transport.serial.port}") + return super().connection_made(transport) + + def data_received(self, data: bytes) -> None: + for byte in data: + self._read_q.put(byte) + return super().data_received(data) + + def connection_lost(self, exc: Union[BaseException, None]) -> None: + print(f"Lost connection!") + return super().connection_lost(exc) + + +class HarpSerial: + + msg_q: queue.Queue + event_q: queue.Queue + + def __init__(self, serial_port: str, **kwargs): + self._ser = serial.Serial(serial_port, **kwargs) + + self.log = logging.getLogger(f"{__name__}.{self.__class__.__name__}") + + self._read_q = queue.Queue() + self.msg_q = queue.Queue() + self.event_q = queue.Queue() + + self._reader = serial.threaded.ReaderThread( + self._ser, + partial(HarpSerialProtocol, self._read_q), + ) + self._reader.start() + transport, protocol = self._reader.connect() + + self._parse_thread = threading.Thread( + target=self.parse_harp_msgs_threaded, + daemon=True, + ) + self._parse_thread.start() + + def close(self): + self._reader.close() + + def write(self, data): + self._reader.write(data) + + def parse_harp_msgs_threaded(self): + while True: + message_type = self._read_q.get(1) # byte array with only one byte + message_length = self._read_q.get(1) + message_content = bytes([self._read_q.get() for _ in range(message_length)]) + self.log.debug(f"reply (type): {message_type}") + self.log.debug(f"reply (length): {message_length}") + self.log.debug(f"reply (payload): {message_content}") + + frame = bytearray() + frame.append(message_type) + frame.append(message_length) + frame += message_content + msg = HarpMessage.parse(frame) + + if msg.message_type == MessageType.EVENT: + self.event_q.put(msg) + else: + self.msg_q.put(msg) diff --git a/pyharp/messages.py b/pyharp/messages.py index e86747a..2a91a25 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -1,7 +1,9 @@ -from __future__ import annotations # for type hints (PEP 563) +from __future__ import annotations # for type hints (PEP 563) from enum import Enum + # from abc import ABC, abstractmethod -from typing import Union, Tuple, Optional, List +from typing import Union, Tuple, Optional, List, Any +from dataclasses import dataclass import struct @@ -56,6 +58,16 @@ class CommonRegisters: DEVICE_NAME = 0x0C +@dataclass +class Register: + name: str + address: int + type: PayloadType + access: Optional[str] = None + description: Optional[str] = None + reset_value: Optional[Any] = None + + class HarpMessage: """ https://github.com/harp-tech/protocol/blob/master/Binary%20Protocol%201.0%201.1%2020180223.pdf @@ -167,9 +179,9 @@ def parse(frame: bytearray) -> ReplyHarpMessage: # A Response Message from a harp device. class ReplyHarpMessage(HarpMessage): - def __init__( - self, frame: bytearray, + self, + frame: bytearray, ): """ @@ -179,57 +191,66 @@ def __init__( self._frame = frame # retrieve all content from 11 (where payload starts) until the checksum (not inclusive) self._raw_payload = frame[11:-1] - self._payload = self._parse_payload(self._raw_payload) # payload formatted as list[payload type] + self._payload = self._parse_payload( + self._raw_payload + ) # payload formatted as list[payload type] # Assign timestamp after _payload since @properties all rely on self._payload. - self._timestamp = int.from_bytes(frame[5:9], byteorder="little", signed=False) + \ - int.from_bytes(frame[9:11], byteorder="little", signed=False)*32e-6 + self._timestamp = ( + int.from_bytes(frame[5:9], byteorder="little", signed=False) + + int.from_bytes(frame[9:11], byteorder="little", signed=False) * 32e-6 + ) # Timestamp is junk if it's not present. if not (self.payload_type.value & PayloadType.hasTimestamp.value): self._timestamp = None - def _parse_payload(self, raw_payload) -> list[int]: """return the payload as a list of ints after parsing it from the raw payload.""" is_signed = True if (self.payload_type.value & 0x80) else False is_float = True if (self.payload_type.value & 0x40) else False bytes_per_word = self.payload_type.value & 0x07 - payload_len = len(raw_payload) # payload length in bytes. + payload_len = len(raw_payload) # payload length in bytes. - word_chunks = [raw_payload[i:i+bytes_per_word] for i in range(0, payload_len, bytes_per_word)] + word_chunks = [ + raw_payload[i : i + bytes_per_word] + for i in range(0, payload_len, bytes_per_word) + ] if not is_float: - return [int.from_bytes(chunk, byteorder="little", signed=is_signed) for chunk in word_chunks] - else: # handle float case. - return [struct.unpack(' Union[int, list[int]]: @@ -254,7 +275,6 @@ def payload_as_float(self) -> float: class ReadHarpMessage(HarpMessage): MESSAGE_TYPE: int = MessageType.READ - def __init__(self, payload_type: PayloadType, address: int): self._frame = bytearray() @@ -287,6 +307,7 @@ class ReadS16HarpMessage(ReadHarpMessage): def __init__(self, address: int): super().__init__(PayloadType.S16, address) + class ReadU32HarpMessage(ReadHarpMessage): def __init__(self, address: int): super().__init__(PayloadType.U32, address) @@ -359,7 +380,10 @@ def payload(self) -> int: class WriteU16HarpMessage(WriteHarpMessage): def __init__(self, address: int, value: int): super().__init__( - PayloadType.U16, value.to_bytes(2, byteorder="little", signed=False), address, offset=1 + PayloadType.U16, + value.to_bytes(2, byteorder="little", signed=False), + address, + offset=1, ) @property @@ -385,20 +409,25 @@ class WriteFloatHarpMessage(WriteHarpMessage): def __init__(self, address: int, value: float): super().__init__( PayloadType.Float, - struct.pack(' float: - return struct.unpack(' int | List[int]: diff --git a/pyproject.toml b/pyproject.toml index ca8d302..9d3b6a2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,6 +5,7 @@ description = "Library for data acquisition and control of devices implementing authors = ["filcarv "] license = "MIT" readme = 'README.md' +python-versions = '>3.10' [tool.poetry.dependencies] python = "^3.4" diff --git a/tests/test_device.py b/tests/test_device.py index da16859..196a07c 100644 --- a/tests/test_device.py +++ b/tests/test_device.py @@ -1,6 +1,7 @@ import serial - +import time from typing import Optional + from pyharp.messages import HarpMessage, ReplyHarpMessage from pyharp.device import Device @@ -47,7 +48,7 @@ def test_U8() -> None: # write 65 on register 38 write_message = HarpMessage.WriteU8(register, write_value) - reply : ReplyHarpMessage = device.send(write_message.frame) + reply: ReplyHarpMessage = device.send(write_message.frame) assert reply is not None # read register 38 @@ -84,3 +85,25 @@ def test_U8() -> None: # assert not ser.is_open # # # assert data[0] == '\t' + + +def test_device_events(device: Device) -> None: + + event_q = device._ser.event_q + + while True: + print(device._ser.event_q.qsize()) + if not event_q.empty(): + try: + msg: ReplyHarpMessage = event_q.get() + print(msg) + except Exception: + pass + time.sleep(0.3) + + +if __name__ == "__main__": + # open serial connection and load info + device = Device("COM4", "dump.txt") + # assert device._dump_file_path.exists() + test_device_events(device) From 2381e8ff1d0050cb7cd8636f9b12605b661623b1 Mon Sep 17 00:00:00 2001 From: Patrick Latimer <110747402+patricklatimer@users.noreply.github.com> Date: Wed, 12 Feb 2025 14:56:44 -0800 Subject: [PATCH 027/159] clean up formatting changes --- pyharp/device.py | 103 ++++++++++++++++++--------------------------- pyharp/messages.py | 95 +++++++++++++++-------------------------- 2 files changed, 74 insertions(+), 124 deletions(-) diff --git a/pyharp/device.py b/pyharp/device.py index 4496859..0a754cb 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -31,7 +31,6 @@ class Device: https://github.com/harp-tech/protocol/blob/master/Device%201.1%201.0%2020220402.pdf """ - # _ser: serial.Serial _ser: HarpSerial _dump_file_path: Path @@ -91,7 +90,6 @@ def read(self): def connect(self) -> None: self._ser = HarpSerial( - # self._ser = serial.Serial( self._serial_port, # "/dev/tty.usbserial-A106C8O9" baudrate=1000000, timeout=self.__class__.TIMEOUT_S, @@ -110,7 +108,7 @@ def read_who_am_i(self) -> int: reply: ReplyHarpMessage = self.send( HarpMessage.ReadU16(address).frame, dump=False ) - # print(str(reply)) + #print(str(reply)) return reply.payload_as_int() def read_who_am_i_device(self) -> str: @@ -119,63 +117,77 @@ def read_who_am_i_device(self) -> str: reply: ReplyHarpMessage = self.send( HarpMessage.ReadU16(address).frame, dump=False ) - # print(str(reply)) + #print(str(reply)) return device_names.get(reply.payload_as_int()) def read_hw_version_h(self) -> int: address = CommonRegisters.HW_VERSION_H - reply: ReplyHarpMessage = self.send(HarpMessage.ReadU8(address).frame, dump=False) - # print(str(reply)) + reply: ReplyHarpMessage = self.send( + HarpMessage.ReadU8(address).frame, dump=False + ) + #print(str(reply)) return reply.payload_as_int() def read_hw_version_l(self) -> int: address = CommonRegisters.HW_VERSION_L - reply: ReplyHarpMessage = self.send(HarpMessage.ReadU8(address).frame, dump=False) - # print(str(reply)) + reply: ReplyHarpMessage = self.send( + HarpMessage.ReadU8(address).frame, dump=False + ) + #print(str(reply)) return reply.payload_as_int() def read_assembly_version(self) -> int: address = CommonRegisters.ASSEMBLY_VERSION - reply: ReplyHarpMessage = self.send(HarpMessage.ReadU8(address).frame, dump=False) - # print(str(reply)) + reply: ReplyHarpMessage = self.send( + HarpMessage.ReadU8(address).frame, dump=False + ) + #print(str(reply)) return reply.payload_as_int() def read_harp_h_version(self) -> int: address = CommonRegisters.HARP_VERSION_H - reply: ReplyHarpMessage = self.send(HarpMessage.ReadU8(address).frame, dump=False) - # print(str(reply)) + reply: ReplyHarpMessage = self.send( + HarpMessage.ReadU8(address).frame, dump=False + ) + #print(str(reply)) return reply.payload_as_int() def read_harp_l_version(self) -> int: address = CommonRegisters.HARP_VERSION_L - reply: ReplyHarpMessage = self.send(HarpMessage.ReadU8(address).frame, dump=False) - # print(str(reply)) + reply: ReplyHarpMessage = self.send( + HarpMessage.ReadU8(address).frame, dump=False + ) + #print(str(reply)) return reply.payload_as_int() def read_fw_h_version(self) -> int: address = CommonRegisters.FIRMWARE_VERSION_H - reply: ReplyHarpMessage = self.send(HarpMessage.ReadU8(address).frame, dump=False) - # print(str(reply)) + reply: ReplyHarpMessage = self.send( + HarpMessage.ReadU8(address).frame, dump=False + ) + #print(str(reply)) return reply.payload_as_int() def read_fw_l_version(self) -> int: address = CommonRegisters.FIRMWARE_VERSION_L - reply: ReplyHarpMessage = self.send(HarpMessage.ReadU8(address).frame, dump=False) - # print(str(reply)) + reply: ReplyHarpMessage = self.send( + HarpMessage.ReadU8(address).frame, dump=False +) + #print(str(reply)) return reply.payload_as_int() @@ -183,8 +195,10 @@ def read_device_name(self) -> str: address = CommonRegisters.DEVICE_NAME # reply: Optional[bytes] = self.send(HarpMessage.ReadU8(address).frame, 13 + 24) - reply: ReplyHarpMessage = self.send(HarpMessage.ReadU8(address).frame, dump=False) - # print(str(reply)) + reply: ReplyHarpMessage = self.send( + HarpMessage.ReadU8(address).frame, dump=False + ) + #print(str(reply)) return reply.payload_as_string() @@ -210,13 +224,13 @@ def dump_registers(self) -> list: break return replies - # TODO: Not sure if we want to implement these. Delete if no. +# TODO: Not sure if we want to implement these. Delete if no. def set_mode(self, mode: DeviceMode) -> ReplyHarpMessage: """Change the device's OPMODE. Reply can be ignored.""" address = CommonRegisters.OPERATION_CTRL # Read register first. reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_int() - reg_value &= ~0x03 # mask off old mode. + reg_value &= ~0x03 # mask off old mode. reg_value |= mode.value reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) return reply @@ -226,7 +240,7 @@ def enable_status_led(self): address = CommonRegisters.OPERATION_CTRL # Read register first. reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_int() - reg_value |= 1 << 5 + reg_value |= (1 << 5) reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) def disable_status_led(self): @@ -242,7 +256,7 @@ def enable_alive_en(self): address = CommonRegisters.OPERATION_CTRL # Read register first. reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_int() - reg_value |= 1 << 7 + reg_value |= (1 << 7) reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) def disable_alive_en(self): @@ -250,7 +264,7 @@ def disable_alive_en(self): address = CommonRegisters.OPERATION_CTRL # Read register first. reg_value = self.send(HarpMessage.ReadU8(address).frame).payload[0] - reg_value &= (1 << 7) ^ 0xFF # bitwise ~ operator substitute for Python ints. + reg_value &= ((1<< 7) ^ 0xFF) # bitwise ~ operator substitute for Python ints. reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) def reset_device(self): @@ -262,7 +276,7 @@ def reset_device(self): def send(self, message_bytes: bytearray, dump: bool = True) -> ReplyHarpMessage: """Send a harp message; return the device's reply.""" - # print(f"Sending: {repr(message_bytes)}") + #print(f"Sending: {repr(message_bytes)}") self._ser.write(message_bytes) # TODO: handle case where read is None @@ -273,36 +287,6 @@ def send(self, message_bytes: bytearray, dump: bool = True) -> ReplyHarpMessage: return reply - # def _read(self) -> Union[ReplyHarpMessage, None]: - # """(Blocking) Read an incoming serial message.""" - # # block for up to TIMEOUT until we get at least one byte. - # read_start = perf_counter() - # while True: - # if self._ser.inWaiting(): - # break - # if perf_counter() - read_start >= self.__class__.TIMEOUT_S: - # break - # try: - # message_type = self._ser.read(1)[0] # byte array with only one byte - # message_length = self._ser.read(1)[0] - # message_content = self._ser.read(message_length) - # self.log.debug(f"reply (type): {message_type}") - # self.log.debug(f"reply (length): {message_length}") - # self.log.debug(f"reply (payload): {message_content}") - # # print(f"Read back:") - # # print(f" type: {MessageType(message_type).name}") - # # print(f" length : {repr(message_length)}") - # # print(f" payload: {list(message_content)}") - - # frame = bytearray() - # frame.append(message_type) - # frame.append(message_length) - # frame += message_content - # msg = HarpMessage.parse(frame) - - # return msg - # except IndexError: - # return None def _read(self) -> Union[ReplyHarpMessage, None]: """(Blocking) Read an incoming serial message.""" @@ -310,18 +294,11 @@ def _read(self) -> Union[ReplyHarpMessage, None]: return self._ser.msg_q.get(block=True, timeout=self.read_timeout_s) except queue.Empty: return None - def _dump_reply(self, reply: bytes): assert self._dump_file_path is not None with self._dump_file_path.open(mode="ab") as f: f.write(reply) - # def read_register(self, register_name: str): - # register: Register = CommonRegisters[register_name] - # ReadHarpMessage(register.type, register.address) - - # def write_register(self, register_name: str, value): - def get_events(self) -> list[ReplyHarpMessage]: msgs = [] while True: diff --git a/pyharp/messages.py b/pyharp/messages.py index 2a91a25..e86747a 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -1,9 +1,7 @@ -from __future__ import annotations # for type hints (PEP 563) +from __future__ import annotations # for type hints (PEP 563) from enum import Enum - # from abc import ABC, abstractmethod -from typing import Union, Tuple, Optional, List, Any -from dataclasses import dataclass +from typing import Union, Tuple, Optional, List import struct @@ -58,16 +56,6 @@ class CommonRegisters: DEVICE_NAME = 0x0C -@dataclass -class Register: - name: str - address: int - type: PayloadType - access: Optional[str] = None - description: Optional[str] = None - reset_value: Optional[Any] = None - - class HarpMessage: """ https://github.com/harp-tech/protocol/blob/master/Binary%20Protocol%201.0%201.1%2020180223.pdf @@ -179,9 +167,9 @@ def parse(frame: bytearray) -> ReplyHarpMessage: # A Response Message from a harp device. class ReplyHarpMessage(HarpMessage): + def __init__( - self, - frame: bytearray, + self, frame: bytearray, ): """ @@ -191,66 +179,57 @@ def __init__( self._frame = frame # retrieve all content from 11 (where payload starts) until the checksum (not inclusive) self._raw_payload = frame[11:-1] - self._payload = self._parse_payload( - self._raw_payload - ) # payload formatted as list[payload type] + self._payload = self._parse_payload(self._raw_payload) # payload formatted as list[payload type] # Assign timestamp after _payload since @properties all rely on self._payload. - self._timestamp = ( - int.from_bytes(frame[5:9], byteorder="little", signed=False) - + int.from_bytes(frame[9:11], byteorder="little", signed=False) * 32e-6 - ) + self._timestamp = int.from_bytes(frame[5:9], byteorder="little", signed=False) + \ + int.from_bytes(frame[9:11], byteorder="little", signed=False)*32e-6 # Timestamp is junk if it's not present. if not (self.payload_type.value & PayloadType.hasTimestamp.value): self._timestamp = None + def _parse_payload(self, raw_payload) -> list[int]: """return the payload as a list of ints after parsing it from the raw payload.""" is_signed = True if (self.payload_type.value & 0x80) else False is_float = True if (self.payload_type.value & 0x40) else False bytes_per_word = self.payload_type.value & 0x07 - payload_len = len(raw_payload) # payload length in bytes. + payload_len = len(raw_payload) # payload length in bytes. - word_chunks = [ - raw_payload[i : i + bytes_per_word] - for i in range(0, payload_len, bytes_per_word) - ] + word_chunks = [raw_payload[i:i+bytes_per_word] for i in range(0, payload_len, bytes_per_word)] if not is_float: - return [ - int.from_bytes(chunk, byteorder="little", signed=is_signed) - for chunk in word_chunks - ] - else: # handle float case. - return [struct.unpack(" Union[int, list[int]]: @@ -275,6 +254,7 @@ def payload_as_float(self) -> float: class ReadHarpMessage(HarpMessage): MESSAGE_TYPE: int = MessageType.READ + def __init__(self, payload_type: PayloadType, address: int): self._frame = bytearray() @@ -307,7 +287,6 @@ class ReadS16HarpMessage(ReadHarpMessage): def __init__(self, address: int): super().__init__(PayloadType.S16, address) - class ReadU32HarpMessage(ReadHarpMessage): def __init__(self, address: int): super().__init__(PayloadType.U32, address) @@ -380,10 +359,7 @@ def payload(self) -> int: class WriteU16HarpMessage(WriteHarpMessage): def __init__(self, address: int, value: int): super().__init__( - PayloadType.U16, - value.to_bytes(2, byteorder="little", signed=False), - address, - offset=1, + PayloadType.U16, value.to_bytes(2, byteorder="little", signed=False), address, offset=1 ) @property @@ -409,25 +385,20 @@ class WriteFloatHarpMessage(WriteHarpMessage): def __init__(self, address: int, value: float): super().__init__( PayloadType.Float, - struct.pack( - " float: - return struct.unpack(" int | List[int]: From 9e305456fd539d4e317edd5af0fab023f1ccae50 Mon Sep 17 00:00:00 2001 From: Patrick Latimer Date: Wed, 12 Feb 2025 15:53:35 -0800 Subject: [PATCH 028/159] add event_count method and update test --- pyharp/device.py | 15 +++++++-------- pyharp/harp_serial.py | 4 ++-- tests/test_device.py | 20 +++----------------- 3 files changed, 12 insertions(+), 27 deletions(-) diff --git a/pyharp/device.py b/pyharp/device.py index 0a754cb..0f4368e 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -6,13 +6,7 @@ from pathlib import Path from pyharp.harp_serial import HarpSerial -from pyharp.messages import ( - HarpMessage, - ReadHarpMessage, - ReplyHarpMessage, - # ResetDevOffsets, - Register, -) +from pyharp.messages import HarpMessage, ReplyHarpMessage from pyharp.messages import CommonRegisters, MessageType from pyharp.device_names import device_names from enum import Enum @@ -287,19 +281,20 @@ def send(self, message_bytes: bytearray, dump: bool = True) -> ReplyHarpMessage: return reply - def _read(self) -> Union[ReplyHarpMessage, None]: """(Blocking) Read an incoming serial message.""" try: return self._ser.msg_q.get(block=True, timeout=self.read_timeout_s) except queue.Empty: return None + def _dump_reply(self, reply: bytes): assert self._dump_file_path is not None with self._dump_file_path.open(mode="ab") as f: f.write(reply) def get_events(self) -> list[ReplyHarpMessage]: + """Get all events from the event queue.""" msgs = [] while True: try: @@ -307,3 +302,7 @@ def get_events(self) -> list[ReplyHarpMessage]: except queue.Empty: break return msgs + + def event_count(self) -> int: + """Get the number of events in the event queue.""" + return self._ser.event_q.qsize() \ No newline at end of file diff --git a/pyharp/harp_serial.py b/pyharp/harp_serial.py index 85a0bb8..3b9d4a2 100644 --- a/pyharp/harp_serial.py +++ b/pyharp/harp_serial.py @@ -17,7 +17,7 @@ def __init__(self, _read_q: queue.Queue, *args, **kwargs): super().__init__(*args, **kwargs) def connection_made(self, transport: serial.threaded.ReaderThread) -> None: - print(f"Connected to {transport.serial.port}") + # print(f"Connected to {transport.serial.port}") return super().connection_made(transport) def data_received(self, data: bytes) -> None: @@ -26,7 +26,7 @@ def data_received(self, data: bytes) -> None: return super().data_received(data) def connection_lost(self, exc: Union[BaseException, None]) -> None: - print(f"Lost connection!") + # print(f"Lost connection!") return super().connection_lost(exc) diff --git a/tests/test_device.py b/tests/test_device.py index 196a07c..9501663 100644 --- a/tests/test_device.py +++ b/tests/test_device.py @@ -1,7 +1,6 @@ import serial import time from typing import Optional - from pyharp.messages import HarpMessage, ReplyHarpMessage from pyharp.device import Device @@ -89,21 +88,8 @@ def test_U8() -> None: def test_device_events(device: Device) -> None: - event_q = device._ser.event_q - while True: - print(device._ser.event_q.qsize()) - if not event_q.empty(): - try: - msg: ReplyHarpMessage = event_q.get() - print(msg) - except Exception: - pass + print(device.event_count()) + for msg in device.get_events(): + print(msg) time.sleep(0.3) - - -if __name__ == "__main__": - # open serial connection and load info - device = Device("COM4", "dump.txt") - # assert device._dump_file_path.exists() - test_device_events(device) From 0d993bb8d522eafd976e1aca0cd85cc9eec1b734 Mon Sep 17 00:00:00 2001 From: Patrick Latimer Date: Wed, 12 Feb 2025 15:54:56 -0800 Subject: [PATCH 029/159] remove unused import --- pyharp/device.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyharp/device.py b/pyharp/device.py index 0f4368e..b1c8a3b 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -1,6 +1,5 @@ import serial import logging -import threading import queue from typing import Optional, Union from pathlib import Path From 583a2709d5dab14d09bc86f54b3439e6e7854695 Mon Sep 17 00:00:00 2001 From: Patrick Latimer Date: Wed, 12 Feb 2025 15:57:43 -0800 Subject: [PATCH 030/159] undo formatting change --- tests/test_device.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/test_device.py b/tests/test_device.py index 9501663..5a5eab9 100644 --- a/tests/test_device.py +++ b/tests/test_device.py @@ -47,7 +47,7 @@ def test_U8() -> None: # write 65 on register 38 write_message = HarpMessage.WriteU8(register, write_value) - reply: ReplyHarpMessage = device.send(write_message.frame) + reply : ReplyHarpMessage = device.send(write_message.frame) assert reply is not None # read register 38 @@ -87,7 +87,6 @@ def test_U8() -> None: def test_device_events(device: Device) -> None: - while True: print(device.event_count()) for msg in device.get_events(): From 556e51263c35b7d6d91e5d944ba82415fd580de1 Mon Sep 17 00:00:00 2001 From: Patrick Latimer Date: Wed, 12 Feb 2025 15:59:17 -0800 Subject: [PATCH 031/159] remove python-versions pin --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 9d3b6a2..ca8d302 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,6 @@ description = "Library for data acquisition and control of devices implementing authors = ["filcarv "] license = "MIT" readme = 'README.md' -python-versions = '>3.10' [tool.poetry.dependencies] python = "^3.4" From efdefc5d9387b1662a7c6c8e03fba137a2408c2a Mon Sep 17 00:00:00 2001 From: Patrick Latimer Date: Wed, 12 Feb 2025 16:53:54 -0800 Subject: [PATCH 032/159] add future import for list annotations --- pyharp/device.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyharp/device.py b/pyharp/device.py index b1c8a3b..7bfdf76 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -3,6 +3,7 @@ import queue from typing import Optional, Union from pathlib import Path +from __future__ import annotations # enable subscriptable type hints for lists. from pyharp.harp_serial import HarpSerial from pyharp.messages import HarpMessage, ReplyHarpMessage From 0a15ab28fc5fd02c871646de37aa1b3b4a4727f4 Mon Sep 17 00:00:00 2001 From: Patrick Latimer Date: Wed, 12 Feb 2025 16:57:42 -0800 Subject: [PATCH 033/159] fix future import --- pyharp/device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyharp/device.py b/pyharp/device.py index 7bfdf76..b4c6f6d 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -1,9 +1,9 @@ +from __future__ import annotations # enable subscriptable type hints for lists. import serial import logging import queue from typing import Optional, Union from pathlib import Path -from __future__ import annotations # enable subscriptable type hints for lists. from pyharp.harp_serial import HarpSerial from pyharp.messages import HarpMessage, ReplyHarpMessage From c68f87b542d8109bceafa7ad7f61813ae8da5fd9 Mon Sep 17 00:00:00 2001 From: Sonya Vasquez Date: Thu, 20 Feb 2025 16:09:49 -0800 Subject: [PATCH 034/159] update wait-for-events example --- examples/get_info.py | 5 +++-- examples/wait_for_events.py | 7 ++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/examples/get_info.py b/examples/get_info.py index d7b7a47..eec8cef 100755 --- a/examples/get_info.py +++ b/examples/get_info.py @@ -37,8 +37,9 @@ device_assembly = device.ASSEMBLY_VERSION # Get device's assembly version reg_dump = device.dump_registers() -for i in range(11): - print(reg_dump[i]) +for reg_reply in reg_dump: + print(reg_reply) + print() # Close connection device.disconnect() diff --git a/examples/wait_for_events.py b/examples/wait_for_events.py index b846d37..99fbe15 100755 --- a/examples/wait_for_events.py +++ b/examples/wait_for_events.py @@ -24,7 +24,8 @@ #device.disable_all_events() #device.enable_events(Events.port_digital_inputs) while True: - event_response = device._read() # read any incoming events. - if event_response is not None:# and event_response.address != 44: + if not device.event_count(): + pass + for msg in device.get_events(): + print(msg) print() - print(event_response) From de151be77eab4a279e5b0e408e8c57fc4e6ff843 Mon Sep 17 00:00:00 2001 From: Sonya Vasquez Date: Fri, 21 Feb 2025 10:50:47 -0800 Subject: [PATCH 035/159] remove extraneous call --- examples/wait_for_events.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/wait_for_events.py b/examples/wait_for_events.py index 99fbe15..584836e 100755 --- a/examples/wait_for_events.py +++ b/examples/wait_for_events.py @@ -24,8 +24,6 @@ #device.disable_all_events() #device.enable_events(Events.port_digital_inputs) while True: - if not device.event_count(): - pass for msg in device.get_events(): print(msg) print() From e86eb296f31f83f652922ef6e47d3d60f7d5e1bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 19 Mar 2025 11:43:51 +0000 Subject: [PATCH 036/159] Convert project to uv --- pyproject.toml | 28 ++++++------- uv.lock | 111 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+), 15 deletions(-) create mode 100644 uv.lock diff --git a/pyproject.toml b/pyproject.toml index ca8d302..325a289 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,23 +1,21 @@ -[tool.poetry] +[project] name = "pyharp" version = "0.1.0" description = "Library for data acquisition and control of devices implementing the Harp protocol." -authors = ["filcarv "] +authors = [{ name= "Filipe Carvalho", email="filipe@open-ephys.org"}] license = "MIT" readme = 'README.md' +requires-python = ">=3.11" +dependencies = [ + "pyserial>=3.5", +] -[tool.poetry.dependencies] -python = "^3.4" -pyserial = "^3.4" - -[tool.poetry.dev-dependencies] -pytest = "^5.2" -mypy = "^0.782" -black = "^19.10b0" +[dependency-groups] +dev = [ + "pytest>=8.3.5", + "ruff>=0.11.0", +] [build-system] -requires = ["poetry-core>=1.0.8"] -build-backend = "poetry.core.masonry.api" - -[tool.poetry.scripts] -pyharp = "pyharp.main:main" +requires = ["hatchling"] +build-backend = "hatchling.build" diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..9b482ec --- /dev/null +++ b/uv.lock @@ -0,0 +1,111 @@ +version = 1 +revision = 1 +requires-python = ">=3.11" + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, +] + +[[package]] +name = "packaging" +version = "24.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, +] + +[[package]] +name = "pyharp" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "pyserial" }, +] + +[package.dev-dependencies] +dev = [ + { name = "pytest" }, + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [{ name = "pyserial", specifier = ">=3.5" }] + +[package.metadata.requires-dev] +dev = [ + { name = "pytest", specifier = ">=8.3.5" }, + { name = "ruff", specifier = ">=0.11.0" }, +] + +[[package]] +name = "pyserial" +version = "3.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1e/7d/ae3f0a63f41e4d2f6cb66a5b57197850f919f59e558159a4dd3a818f5082/pyserial-3.5.tar.gz", hash = "sha256:3c77e014170dfffbd816e6ffc205e9842efb10be9f58ec16d3e8675b4925cddb", size = 159125 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/bc/587a445451b253b285629263eb51c2d8e9bcea4fc97826266d186f96f558/pyserial-3.5-py2.py3-none-any.whl", hash = "sha256:c4451db6ba391ca6ca299fb3ec7bae67a5c55dde170964c7a14ceefec02f2cf0", size = 90585 }, +] + +[[package]] +name = "pytest" +version = "8.3.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 }, +] + +[[package]] +name = "ruff" +version = "0.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/77/2b/7ca27e854d92df5e681e6527dc0f9254c9dc06c8408317893cf96c851cdd/ruff-0.11.0.tar.gz", hash = "sha256:e55c620690a4a7ee6f1cccb256ec2157dc597d109400ae75bbf944fc9d6462e2", size = 3799407 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/40/3d0340a9e5edc77d37852c0cd98c5985a5a8081fc3befaeb2ae90aaafd2b/ruff-0.11.0-py3-none-linux_armv6l.whl", hash = "sha256:dc67e32bc3b29557513eb7eeabb23efdb25753684b913bebb8a0c62495095acb", size = 10098158 }, + { url = "https://files.pythonhosted.org/packages/ec/a9/d8f5abb3b87b973b007649ac7bf63665a05b2ae2b2af39217b09f52abbbf/ruff-0.11.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:38c23fd9bdec4eb437b4c1e3595905a0a8edfccd63a790f818b28c78fe345639", size = 10879071 }, + { url = "https://files.pythonhosted.org/packages/ab/62/aaa198614c6211677913ec480415c5e6509586d7b796356cec73a2f8a3e6/ruff-0.11.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7c8661b0be91a38bd56db593e9331beaf9064a79028adee2d5f392674bbc5e88", size = 10247944 }, + { url = "https://files.pythonhosted.org/packages/9f/52/59e0a9f2cf1ce5e6cbe336b6dd0144725c8ea3b97cac60688f4e7880bf13/ruff-0.11.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b6c0e8d3d2db7e9f6efd884f44b8dc542d5b6b590fc4bb334fdbc624d93a29a2", size = 10421725 }, + { url = "https://files.pythonhosted.org/packages/a6/c3/dcd71acc6dff72ce66d13f4be5bca1dbed4db678dff2f0f6f307b04e5c02/ruff-0.11.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c3156d3f4b42e57247275a0a7e15a851c165a4fc89c5e8fa30ea6da4f7407b8", size = 9954435 }, + { url = "https://files.pythonhosted.org/packages/a6/9a/342d336c7c52dbd136dee97d4c7797e66c3f92df804f8f3b30da59b92e9c/ruff-0.11.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:490b1e147c1260545f6d041c4092483e3f6d8eba81dc2875eaebcf9140b53905", size = 11492664 }, + { url = "https://files.pythonhosted.org/packages/84/35/6e7defd2d7ca95cc385ac1bd9f7f2e4a61b9cc35d60a263aebc8e590c462/ruff-0.11.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1bc09a7419e09662983b1312f6fa5dab829d6ab5d11f18c3760be7ca521c9329", size = 12207856 }, + { url = "https://files.pythonhosted.org/packages/22/78/da669c8731bacf40001c880ada6d31bcfb81f89cc996230c3b80d319993e/ruff-0.11.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bcfa478daf61ac8002214eb2ca5f3e9365048506a9d52b11bea3ecea822bb844", size = 11645156 }, + { url = "https://files.pythonhosted.org/packages/ee/47/e27d17d83530a208f4a9ab2e94f758574a04c51e492aa58f91a3ed7cbbcb/ruff-0.11.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6fbb2aed66fe742a6a3a0075ed467a459b7cedc5ae01008340075909d819df1e", size = 13884167 }, + { url = "https://files.pythonhosted.org/packages/9f/5e/42ffbb0a5d4b07bbc642b7d58357b4e19a0f4774275ca6ca7d1f7b5452cd/ruff-0.11.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:92c0c1ff014351c0b0cdfdb1e35fa83b780f1e065667167bb9502d47ca41e6db", size = 11348311 }, + { url = "https://files.pythonhosted.org/packages/c8/51/dc3ce0c5ce1a586727a3444a32f98b83ba99599bb1ebca29d9302886e87f/ruff-0.11.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e4fd5ff5de5f83e0458a138e8a869c7c5e907541aec32b707f57cf9a5e124445", size = 10305039 }, + { url = "https://files.pythonhosted.org/packages/60/e0/475f0c2f26280f46f2d6d1df1ba96b3399e0234cf368cc4c88e6ad10dcd9/ruff-0.11.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:96bc89a5c5fd21a04939773f9e0e276308be0935de06845110f43fd5c2e4ead7", size = 9937939 }, + { url = "https://files.pythonhosted.org/packages/e2/d3/3e61b7fd3e9cdd1e5b8c7ac188bec12975c824e51c5cd3d64caf81b0331e/ruff-0.11.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a9352b9d767889ec5df1483f94870564e8102d4d7e99da52ebf564b882cdc2c7", size = 10923259 }, + { url = "https://files.pythonhosted.org/packages/30/32/cd74149ebb40b62ddd14bd2d1842149aeb7f74191fb0f49bd45c76909ff2/ruff-0.11.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:049a191969a10897fe052ef9cc7491b3ef6de79acd7790af7d7897b7a9bfbcb6", size = 11406212 }, + { url = "https://files.pythonhosted.org/packages/00/ef/033022a6b104be32e899b00de704d7c6d1723a54d4c9e09d147368f14b62/ruff-0.11.0-py3-none-win32.whl", hash = "sha256:3191e9116b6b5bbe187447656f0c8526f0d36b6fd89ad78ccaad6bdc2fad7df2", size = 10310905 }, + { url = "https://files.pythonhosted.org/packages/ed/8a/163f2e78c37757d035bd56cd60c8d96312904ca4a6deeab8442d7b3cbf89/ruff-0.11.0-py3-none-win_amd64.whl", hash = "sha256:c58bfa00e740ca0a6c43d41fb004cd22d165302f360aaa56f7126d544db31a21", size = 11411730 }, + { url = "https://files.pythonhosted.org/packages/4e/f7/096f6efabe69b49d7ca61052fc70289c05d8d35735c137ef5ba5ef423662/ruff-0.11.0-py3-none-win_arm64.whl", hash = "sha256:868364fc23f5aa122b00c6f794211e85f7e78f5dffdf7c590ab90b8c4e69b657", size = 10538956 }, +] From 0163e17c920a254f7407c0dd7a9ac1a39f486654 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 19 Mar 2025 13:31:58 +0000 Subject: [PATCH 037/159] Define docstyle convention --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 325a289..ee807f4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,3 +19,6 @@ dev = [ [build-system] requires = ["hatchling"] build-backend = "hatchling.build" + +[tool.ruff.lint.pydocstyle] +convention = "numpy" \ No newline at end of file From 15ac05e663ef19d6cd0f694c93215189f18efd9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Grilo?= Date: Wed, 19 Mar 2025 15:07:49 +0000 Subject: [PATCH 038/159] Update default vscode settings --- .vscode/extensions.json | 6 ++++++ .vscode/settings.json | 9 +++++++++ 2 files changed, 15 insertions(+) create mode 100644 .vscode/extensions.json create mode 100644 .vscode/settings.json diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..4e265ea --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,6 @@ +{ + "recommendations": [ + "ms-python.python", + "charliermarsh.ruff" + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..dec7265 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "editor.defaultFormatter": "charliermarsh.ruff", + "editor.formatOnSave": true, + "editor.formatOnPaste": true, + "ruff.organizeImports": true, + "editor.codeActionsOnSave": { + "source.organizeImports": "explicit" + } +} \ No newline at end of file From efceffd3451fbfd62ed0f782009e5a7b41de6ffc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Grilo?= Date: Wed, 19 Mar 2025 16:50:27 +0000 Subject: [PATCH 039/159] Add docs to the project --- docs/index.md | 4 + mkdocs.yml | 16 ++ pyproject.toml | 5 +- uv.lock | 490 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 514 insertions(+), 1 deletion(-) create mode 100644 docs/index.md create mode 100644 mkdocs.yml diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..c0f9b06 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,4 @@ +# pyharp + +!!! Warning + Work in Progress! \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..e4b6a6e --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,16 @@ +site_name: pyharp + +plugins: + - mkdocstrings: + handlers: + python: + options: + docstring_style: numpy + +markdown_extensions: + - admonition + - pymdownx.details + - pymdownx.superfences + +theme: + name: material diff --git a/pyproject.toml b/pyproject.toml index ee807f4..14f8aa5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,9 @@ dependencies = [ [dependency-groups] dev = [ + "mkdocs>=1.6.1", + "mkdocs-material>=9.6.9", + "mkdocstrings-python>=1.16.6", "pytest>=8.3.5", "ruff>=0.11.0", ] @@ -21,4 +24,4 @@ requires = ["hatchling"] build-backend = "hatchling.build" [tool.ruff.lint.pydocstyle] -convention = "numpy" \ No newline at end of file +convention = "numpy" diff --git a/uv.lock b/uv.lock index 9b482ec..984b6c6 100644 --- a/uv.lock +++ b/uv.lock @@ -2,6 +2,97 @@ version = 1 revision = 1 requires-python = ">=3.11" +[[package]] +name = "babel" +version = "2.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537 }, +] + +[[package]] +name = "backrefs" +version = "5.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/46/caba1eb32fa5784428ab401a5487f73db4104590ecd939ed9daaf18b47e0/backrefs-5.8.tar.gz", hash = "sha256:2cab642a205ce966af3dd4b38ee36009b31fa9502a35fd61d59ccc116e40a6bd", size = 6773994 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/cb/d019ab87fe70e0fe3946196d50d6a4428623dc0c38a6669c8cae0320fbf3/backrefs-5.8-py310-none-any.whl", hash = "sha256:c67f6638a34a5b8730812f5101376f9d41dc38c43f1fdc35cb54700f6ed4465d", size = 380337 }, + { url = "https://files.pythonhosted.org/packages/a9/86/abd17f50ee21b2248075cb6924c6e7f9d23b4925ca64ec660e869c2633f1/backrefs-5.8-py311-none-any.whl", hash = "sha256:2e1c15e4af0e12e45c8701bd5da0902d326b2e200cafcd25e49d9f06d44bb61b", size = 392142 }, + { url = "https://files.pythonhosted.org/packages/b3/04/7b415bd75c8ab3268cc138c76fa648c19495fcc7d155508a0e62f3f82308/backrefs-5.8-py312-none-any.whl", hash = "sha256:bbef7169a33811080d67cdf1538c8289f76f0942ff971222a16034da88a73486", size = 398021 }, + { url = "https://files.pythonhosted.org/packages/04/b8/60dcfb90eb03a06e883a92abbc2ab95c71f0d8c9dd0af76ab1d5ce0b1402/backrefs-5.8-py313-none-any.whl", hash = "sha256:e3a63b073867dbefd0536425f43db618578528e3896fb77be7141328642a1585", size = 399915 }, + { url = "https://files.pythonhosted.org/packages/0c/37/fb6973edeb700f6e3d6ff222400602ab1830446c25c7b4676d8de93e65b8/backrefs-5.8-py39-none-any.whl", hash = "sha256:a66851e4533fb5b371aa0628e1fee1af05135616b86140c9d787a2ffdf4b8fdc", size = 380336 }, +] + +[[package]] +name = "certifi" +version = "2025.1.31" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/80/41ef5d5a7935d2d3a773e3eaebf0a9350542f2cab4eac59a7a4741fbbbbe/charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125", size = 194995 }, + { url = "https://files.pythonhosted.org/packages/7a/28/0b9fefa7b8b080ec492110af6d88aa3dea91c464b17d53474b6e9ba5d2c5/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1", size = 139471 }, + { url = "https://files.pythonhosted.org/packages/71/64/d24ab1a997efb06402e3fc07317e94da358e2585165930d9d59ad45fcae2/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3", size = 149831 }, + { url = "https://files.pythonhosted.org/packages/37/ed/be39e5258e198655240db5e19e0b11379163ad7070962d6b0c87ed2c4d39/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd", size = 142335 }, + { url = "https://files.pythonhosted.org/packages/88/83/489e9504711fa05d8dde1574996408026bdbdbd938f23be67deebb5eca92/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00", size = 143862 }, + { url = "https://files.pythonhosted.org/packages/c6/c7/32da20821cf387b759ad24627a9aca289d2822de929b8a41b6241767b461/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12", size = 145673 }, + { url = "https://files.pythonhosted.org/packages/68/85/f4288e96039abdd5aeb5c546fa20a37b50da71b5cf01e75e87f16cd43304/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77", size = 140211 }, + { url = "https://files.pythonhosted.org/packages/28/a3/a42e70d03cbdabc18997baf4f0227c73591a08041c149e710045c281f97b/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146", size = 148039 }, + { url = "https://files.pythonhosted.org/packages/85/e4/65699e8ab3014ecbe6f5c71d1a55d810fb716bbfd74f6283d5c2aa87febf/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd", size = 151939 }, + { url = "https://files.pythonhosted.org/packages/b1/82/8e9fe624cc5374193de6860aba3ea8070f584c8565ee77c168ec13274bd2/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6", size = 149075 }, + { url = "https://files.pythonhosted.org/packages/3d/7b/82865ba54c765560c8433f65e8acb9217cb839a9e32b42af4aa8e945870f/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8", size = 144340 }, + { url = "https://files.pythonhosted.org/packages/b5/b6/9674a4b7d4d99a0d2df9b215da766ee682718f88055751e1e5e753c82db0/charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b", size = 95205 }, + { url = "https://files.pythonhosted.org/packages/1e/ab/45b180e175de4402dcf7547e4fb617283bae54ce35c27930a6f35b6bef15/charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76", size = 102441 }, + { url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105 }, + { url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404 }, + { url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423 }, + { url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184 }, + { url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268 }, + { url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601 }, + { url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098 }, + { url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520 }, + { url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852 }, + { url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488 }, + { url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192 }, + { url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550 }, + { url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785 }, + { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698 }, + { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162 }, + { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263 }, + { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966 }, + { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992 }, + { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162 }, + { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972 }, + { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095 }, + { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668 }, + { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073 }, + { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732 }, + { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391 }, + { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702 }, + { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 }, +] + +[[package]] +name = "click" +version = "8.1.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 }, +] + [[package]] name = "colorama" version = "0.4.6" @@ -11,6 +102,39 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, ] +[[package]] +name = "ghp-import" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d9/29/d40217cbe2f6b1359e00c6c307bb3fc876ba74068cbab3dde77f03ca0dc4/ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343", size = 10943 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034 }, +] + +[[package]] +name = "griffe" +version = "1.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6a/ba/1ebe51a22c491a3fc94b44ef9c46a5b5472540e24a5c3f251cebbab7214b/griffe-1.6.1.tar.gz", hash = "sha256:ff0acf706b2680f8c721412623091c891e752b2c61b7037618f7b77d06732cf5", size = 393112 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/d3/a760d1062e44587230aa65573c70edaad4ee8a0e60e193a3172b304d24d8/griffe-1.6.1-py3-none-any.whl", hash = "sha256:b0131670db16834f82383bcf4f788778853c9bf4dc7a1a2b708bb0808ca56a98", size = 128615 }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, +] + [[package]] name = "iniconfig" version = "2.0.0" @@ -20,6 +144,198 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, ] +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 }, +] + +[[package]] +name = "markdown" +version = "3.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/28/3af612670f82f4c056911fbbbb42760255801b3068c48de792d354ff4472/markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2", size = 357086 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/08/83871f3c50fc983b88547c196d11cf8c3340e37c32d2e9d6152abe2c61f7/Markdown-3.7-py3-none-any.whl", hash = "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803", size = 106349 }, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353 }, + { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392 }, + { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984 }, + { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120 }, + { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032 }, + { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057 }, + { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359 }, + { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306 }, + { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094 }, + { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521 }, + { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348 }, + { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149 }, + { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118 }, + { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993 }, + { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178 }, + { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319 }, + { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352 }, + { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097 }, + { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601 }, + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, +] + +[[package]] +name = "mergedeep" +version = "1.3.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/41/580bb4006e3ed0361b8151a01d324fb03f420815446c7def45d02f74c270/mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8", size = 4661 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307", size = 6354 }, +] + +[[package]] +name = "mkdocs" +version = "1.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "ghp-import" }, + { name = "jinja2" }, + { name = "markdown" }, + { name = "markupsafe" }, + { name = "mergedeep" }, + { name = "mkdocs-get-deps" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "pyyaml" }, + { name = "pyyaml-env-tag" }, + { name = "watchdog" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bc/c6/bbd4f061bd16b378247f12953ffcb04786a618ce5e904b8c5a01a0309061/mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2", size = 3889159 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e", size = 3864451 }, +] + +[[package]] +name = "mkdocs-autorefs" +version = "1.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown" }, + { name = "markupsafe" }, + { name = "mkdocs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c2/44/140469d87379c02f1e1870315f3143718036a983dd0416650827b8883192/mkdocs_autorefs-1.4.1.tar.gz", hash = "sha256:4b5b6235a4becb2b10425c2fa191737e415b37aa3418919db33e5d774c9db079", size = 4131355 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/29/1125f7b11db63e8e32bcfa0752a4eea30abff3ebd0796f808e14571ddaa2/mkdocs_autorefs-1.4.1-py3-none-any.whl", hash = "sha256:9793c5ac06a6ebbe52ec0f8439256e66187badf4b5334b5fde0b128ec134df4f", size = 5782047 }, +] + +[[package]] +name = "mkdocs-get-deps" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mergedeep" }, + { name = "platformdirs" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/f5/ed29cd50067784976f25ed0ed6fcd3c2ce9eb90650aa3b2796ddf7b6870b/mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c", size = 10239 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134", size = 9521 }, +] + +[[package]] +name = "mkdocs-material" +version = "9.6.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "babel" }, + { name = "backrefs" }, + { name = "colorama" }, + { name = "jinja2" }, + { name = "markdown" }, + { name = "mkdocs" }, + { name = "mkdocs-material-extensions" }, + { name = "paginate" }, + { name = "pygments" }, + { name = "pymdown-extensions" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/11/cb/6dd3b6a7925429c0229738098ee874dbf7fa02db55558adb2c5bf86077b2/mkdocs_material-9.6.9.tar.gz", hash = "sha256:a4872139715a1f27b2aa3f3dc31a9794b7bbf36333c0ba4607cf04786c94f89c", size = 3948083 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/7c/ea5a671b2ff5d0e3f3108a7f7d75b541d683e4969aaead2a8f3e59e0fc27/mkdocs_material-9.6.9-py3-none-any.whl", hash = "sha256:6e61b7fb623ce2aa4622056592b155a9eea56ff3487d0835075360be45a4c8d1", size = 8697935 }, +] + +[[package]] +name = "mkdocs-material-extensions" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/79/9b/9b4c96d6593b2a541e1cb8b34899a6d021d208bb357042823d4d2cabdbe7/mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443", size = 11847 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31", size = 8728 }, +] + +[[package]] +name = "mkdocstrings" +version = "0.29.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jinja2" }, + { name = "markdown" }, + { name = "markupsafe" }, + { name = "mkdocs" }, + { name = "mkdocs-autorefs" }, + { name = "pymdown-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8e/4d/a9484dc5d926295bdf308f1f6c4f07fcc99735b970591edc414d401fcc91/mkdocstrings-0.29.0.tar.gz", hash = "sha256:3657be1384543ce0ee82112c3e521bbf48e41303aa0c229b9ffcccba057d922e", size = 1212185 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/47/eb876dfd84e48f31ff60897d161b309cf6a04ca270155b0662aae562b3fb/mkdocstrings-0.29.0-py3-none-any.whl", hash = "sha256:8ea98358d2006f60befa940fdebbbc88a26b37ecbcded10be726ba359284f73d", size = 1630824 }, +] + +[[package]] +name = "mkdocstrings-python" +version = "1.16.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "griffe" }, + { name = "mkdocs-autorefs" }, + { name = "mkdocstrings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8e/e7/0691e34e807a8f5c28f0988fcfeeb584f0b569ce433bf341944f14bdb3ff/mkdocstrings_python-1.16.6.tar.gz", hash = "sha256:cefe0f0e17ab4a4611f01b0a2af75e4298664e0ff54feb83c91a485bfed82dc9", size = 201565 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/42/ed682687ef5f248e104f82806d5d9893f6dd81d8cb4561692e190ba1a252/mkdocstrings_python-1.16.6-py3-none-any.whl", hash = "sha256:de877dd71f69878c973c4897a39683b7b6961bee7b058879095b69681488453f", size = 123207 }, +] + [[package]] name = "packaging" version = "24.2" @@ -29,6 +345,33 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, ] +[[package]] +name = "paginate" +version = "0.5.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/46/68dde5b6bc00c1296ec6466ab27dddede6aec9af1b99090e1107091b3b84/paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945", size = 19252 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591", size = 13746 }, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 }, +] + +[[package]] +name = "platformdirs" +version = "4.3.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/fc/128cc9cb8f03208bdbf93d3aa862e16d376844a14f9a0ce5cf4507372de4/platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", size = 21302 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439 }, +] + [[package]] name = "pluggy" version = "1.5.0" @@ -38,6 +381,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, ] +[[package]] +name = "pygments" +version = "2.19.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 }, +] + [[package]] name = "pyharp" version = "0.1.0" @@ -48,6 +400,9 @@ dependencies = [ [package.dev-dependencies] dev = [ + { name = "mkdocs" }, + { name = "mkdocs-material" }, + { name = "mkdocstrings-python" }, { name = "pytest" }, { name = "ruff" }, ] @@ -57,10 +412,26 @@ requires-dist = [{ name = "pyserial", specifier = ">=3.5" }] [package.metadata.requires-dev] dev = [ + { name = "mkdocs", specifier = ">=1.6.1" }, + { name = "mkdocs-material", specifier = ">=9.6.9" }, + { name = "mkdocstrings-python", specifier = ">=1.16.6" }, { name = "pytest", specifier = ">=8.3.5" }, { name = "ruff", specifier = ">=0.11.0" }, ] +[[package]] +name = "pymdown-extensions" +version = "10.14.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7c/44/e6de2fdc880ad0ec7547ca2e087212be815efbc9a425a8d5ba9ede602cbb/pymdown_extensions-10.14.3.tar.gz", hash = "sha256:41e576ce3f5d650be59e900e4ceff231e0aed2a88cf30acaee41e02f063a061b", size = 846846 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/f5/b9e2a42aa8f9e34d52d66de87941ecd236570c7ed2e87775ed23bbe4e224/pymdown_extensions-10.14.3-py3-none-any.whl", hash = "sha256:05e0bee73d64b9c71a4ae17c72abc2f700e8bc8403755a00580b49a4e9f189e9", size = 264467 }, +] + [[package]] name = "pyserial" version = "3.5" @@ -85,6 +456,80 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 }, ] +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612 }, + { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040 }, + { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829 }, + { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167 }, + { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952 }, + { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301 }, + { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638 }, + { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850 }, + { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980 }, + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, +] + +[[package]] +name = "pyyaml-env-tag" +version = "0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fb/8e/da1c6c58f751b70f8ceb1eb25bc25d524e8f14fe16edcce3f4e3ba08629c/pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb", size = 5631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/66/bbb1dd374f5c870f59c5bb1db0e18cbe7fa739415a24cbd95b2d1f5ae0c4/pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069", size = 3911 }, +] + +[[package]] +name = "requests" +version = "2.32.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, +] + [[package]] name = "ruff" version = "0.11.0" @@ -109,3 +554,48 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ed/8a/163f2e78c37757d035bd56cd60c8d96312904ca4a6deeab8442d7b3cbf89/ruff-0.11.0-py3-none-win_amd64.whl", hash = "sha256:c58bfa00e740ca0a6c43d41fb004cd22d165302f360aaa56f7126d544db31a21", size = 11411730 }, { url = "https://files.pythonhosted.org/packages/4e/f7/096f6efabe69b49d7ca61052fc70289c05d8d35735c137ef5ba5ef423662/ruff-0.11.0-py3-none-win_arm64.whl", hash = "sha256:868364fc23f5aa122b00c6f794211e85f7e78f5dffdf7c590ab90b8c4e69b657", size = 10538956 }, ] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, +] + +[[package]] +name = "urllib3" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369 }, +] + +[[package]] +name = "watchdog" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/24/d9be5cd6642a6aa68352ded4b4b10fb0d7889cb7f45814fb92cecd35f101/watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c", size = 96393 }, + { url = "https://files.pythonhosted.org/packages/63/7a/6013b0d8dbc56adca7fdd4f0beed381c59f6752341b12fa0886fa7afc78b/watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2", size = 88392 }, + { url = "https://files.pythonhosted.org/packages/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c", size = 89019 }, + { url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471 }, + { url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449 }, + { url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054 }, + { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480 }, + { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451 }, + { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057 }, + { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079 }, + { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078 }, + { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076 }, + { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077 }, + { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078 }, + { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077 }, + { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078 }, + { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065 }, + { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070 }, + { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067 }, +] From 627b1dbede50f0c2ed543cf357fabfe78b962d4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Grilo?= Date: Wed, 19 Mar 2025 16:50:52 +0000 Subject: [PATCH 040/159] Delete poetry.lock --- poetry.lock | 415 ---------------------------------------------------- 1 file changed, 415 deletions(-) delete mode 100644 poetry.lock diff --git a/poetry.lock b/poetry.lock deleted file mode 100644 index 6e3d0a4..0000000 --- a/poetry.lock +++ /dev/null @@ -1,415 +0,0 @@ -[[package]] -category = "main" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -name = "appdirs" -optional = false -python-versions = "*" -version = "1.4.4" - -[[package]] -category = "dev" -description = "Atomic file writes." -marker = "sys_platform == \"win32\"" -name = "atomicwrites" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.4.0" - -[[package]] -category = "main" -description = "Classes Without Boilerplate" -name = "attrs" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "19.3.0" - -[package.extras] -azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"] -dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"] -docs = ["sphinx", "zope.interface"] -tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] - -[[package]] -category = "main" -description = "The uncompromising code formatter." -name = "black" -optional = false -python-versions = ">=3.6" -version = "19.10b0" - -[package.dependencies] -appdirs = "*" -attrs = ">=18.1.0" -click = ">=6.5" -pathspec = ">=0.6,<1" -regex = "*" -toml = ">=0.9.4" -typed-ast = ">=1.4.0" - -[package.extras] -d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] - -[[package]] -category = "main" -description = "Composable command line interface toolkit" -name = "click" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "7.1.2" - -[[package]] -category = "dev" -description = "Cross-platform colored terminal text." -marker = "sys_platform == \"win32\"" -name = "colorama" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "0.4.3" - -[[package]] -category = "dev" -description = "Read metadata from Python packages" -marker = "python_version < \"3.8\"" -name = "importlib-metadata" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" -version = "1.7.0" - -[package.dependencies] -zipp = ">=0.5" - -[package.extras] -docs = ["sphinx", "rst.linker"] -testing = ["packaging", "pep517", "importlib-resources (>=1.3)"] - -[[package]] -category = "dev" -description = "More routines for operating on iterables, beyond itertools" -name = "more-itertools" -optional = false -python-versions = ">=3.5" -version = "8.4.0" - -[[package]] -category = "dev" -description = "Optional static typing for Python" -name = "mypy" -optional = false -python-versions = ">=3.5" -version = "0.782" - -[package.dependencies] -mypy-extensions = ">=0.4.3,<0.5.0" -typed-ast = ">=1.4.0,<1.5.0" -typing-extensions = ">=3.7.4" - -[package.extras] -dmypy = ["psutil (>=4.0)"] - -[[package]] -category = "dev" -description = "Experimental type system extensions for programs checked with the mypy typechecker." -name = "mypy-extensions" -optional = false -python-versions = "*" -version = "0.4.3" - -[[package]] -category = "dev" -description = "Core utilities for Python packages" -name = "packaging" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "20.4" - -[package.dependencies] -pyparsing = ">=2.0.2" -six = "*" - -[[package]] -category = "main" -description = "Utility library for gitignore style pattern matching of file paths." -name = "pathspec" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "0.8.0" - -[[package]] -category = "dev" -description = "plugin and hook calling mechanisms for python" -name = "pluggy" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "0.13.1" - -[package.dependencies] -[package.dependencies.importlib-metadata] -python = "<3.8" -version = ">=0.12" - -[package.extras] -dev = ["pre-commit", "tox"] - -[[package]] -category = "dev" -description = "library with cross-python path, ini-parsing, io, code, log facilities" -name = "py" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.9.0" - -[[package]] -category = "dev" -description = "Python parsing module" -name = "pyparsing" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -version = "2.4.7" - -[[package]] -category = "main" -description = "Python Serial Port Extension" -name = "pyserial" -optional = false -python-versions = "*" -version = "3.4" - -[[package]] -category = "dev" -description = "pytest: simple powerful testing with Python" -name = "pytest" -optional = false -python-versions = ">=3.5" -version = "5.4.3" - -[package.dependencies] -atomicwrites = ">=1.0" -attrs = ">=17.4.0" -colorama = "*" -more-itertools = ">=4.0.0" -packaging = "*" -pluggy = ">=0.12,<1.0" -py = ">=1.5.0" -wcwidth = "*" - -[package.dependencies.importlib-metadata] -python = "<3.8" -version = ">=0.12" - -[package.extras] -checkqa-mypy = ["mypy (v0.761)"] -testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] - -[[package]] -category = "main" -description = "Alternative regular expression module, to replace re." -name = "regex" -optional = false -python-versions = "*" -version = "2020.7.14" - -[[package]] -category = "dev" -description = "Python 2 and 3 compatibility utilities" -name = "six" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -version = "1.15.0" - -[[package]] -category = "main" -description = "Python Library for Tom's Obvious, Minimal Language" -name = "toml" -optional = false -python-versions = "*" -version = "0.10.1" - -[[package]] -category = "main" -description = "a fork of Python 2 and 3 ast modules with type comment support" -name = "typed-ast" -optional = false -python-versions = "*" -version = "1.4.1" - -[[package]] -category = "dev" -description = "Backported and Experimental Type Hints for Python 3.5+" -name = "typing-extensions" -optional = false -python-versions = "*" -version = "3.7.4.2" - -[[package]] -category = "dev" -description = "Measures the displayed width of unicode strings in a terminal" -name = "wcwidth" -optional = false -python-versions = "*" -version = "0.2.5" - -[[package]] -category = "dev" -description = "Backport of pathlib-compatible object wrapper for zip files" -marker = "python_version < \"3.8\"" -name = "zipp" -optional = false -python-versions = ">=3.6" -version = "3.1.0" - -[package.extras] -docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] -testing = ["jaraco.itertools", "func-timeout"] - -[metadata] -content-hash = "e591cefbf7f181c5a3dbb815804404256412aa148b25f12528bbad5dfb31e6e7" -python-versions = "^3.7" - -[metadata.files] -appdirs = [ - {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, - {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, -] -atomicwrites = [ - {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, - {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, -] -attrs = [ - {file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"}, - {file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"}, -] -black = [ - {file = "black-19.10b0-py36-none-any.whl", hash = "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b"}, - {file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"}, -] -click = [ - {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, - {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, -] -colorama = [ - {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"}, - {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"}, -] -importlib-metadata = [ - {file = "importlib_metadata-1.7.0-py2.py3-none-any.whl", hash = "sha256:dc15b2969b4ce36305c51eebe62d418ac7791e9a157911d58bfb1f9ccd8e2070"}, - {file = "importlib_metadata-1.7.0.tar.gz", hash = "sha256:90bb658cdbbf6d1735b6341ce708fc7024a3e14e99ffdc5783edea9f9b077f83"}, -] -more-itertools = [ - {file = "more-itertools-8.4.0.tar.gz", hash = "sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5"}, - {file = "more_itertools-8.4.0-py3-none-any.whl", hash = "sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2"}, -] -mypy = [ - {file = "mypy-0.782-cp35-cp35m-macosx_10_6_x86_64.whl", hash = "sha256:2c6cde8aa3426c1682d35190b59b71f661237d74b053822ea3d748e2c9578a7c"}, - {file = "mypy-0.782-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9c7a9a7ceb2871ba4bac1cf7217a7dd9ccd44c27c2950edbc6dc08530f32ad4e"}, - {file = "mypy-0.782-cp35-cp35m-win_amd64.whl", hash = "sha256:c05b9e4fb1d8a41d41dec8786c94f3b95d3c5f528298d769eb8e73d293abc48d"}, - {file = "mypy-0.782-cp36-cp36m-macosx_10_6_x86_64.whl", hash = "sha256:6731603dfe0ce4352c555c6284c6db0dc935b685e9ce2e4cf220abe1e14386fd"}, - {file = "mypy-0.782-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:f05644db6779387ccdb468cc47a44b4356fc2ffa9287135d05b70a98dc83b89a"}, - {file = "mypy-0.782-cp36-cp36m-win_amd64.whl", hash = "sha256:b7fbfabdbcc78c4f6fc4712544b9b0d6bf171069c6e0e3cb82440dd10ced3406"}, - {file = "mypy-0.782-cp37-cp37m-macosx_10_6_x86_64.whl", hash = "sha256:3fdda71c067d3ddfb21da4b80e2686b71e9e5c72cca65fa216d207a358827f86"}, - {file = "mypy-0.782-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d7df6eddb6054d21ca4d3c6249cae5578cb4602951fd2b6ee2f5510ffb098707"}, - {file = "mypy-0.782-cp37-cp37m-win_amd64.whl", hash = "sha256:a4a2cbcfc4cbf45cd126f531dedda8485671545b43107ded25ce952aac6fb308"}, - {file = "mypy-0.782-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6bb93479caa6619d21d6e7160c552c1193f6952f0668cdda2f851156e85186fc"}, - {file = "mypy-0.782-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:81c7908b94239c4010e16642c9102bfc958ab14e36048fa77d0be3289dda76ea"}, - {file = "mypy-0.782-cp38-cp38-win_amd64.whl", hash = "sha256:5dd13ff1f2a97f94540fd37a49e5d255950ebcdf446fb597463a40d0df3fac8b"}, - {file = "mypy-0.782-py3-none-any.whl", hash = "sha256:e0b61738ab504e656d1fe4ff0c0601387a5489ca122d55390ade31f9ca0e252d"}, - {file = "mypy-0.782.tar.gz", hash = "sha256:eff7d4a85e9eea55afa34888dfeaccde99e7520b51f867ac28a48492c0b1130c"}, -] -mypy-extensions = [ - {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, - {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, -] -packaging = [ - {file = "packaging-20.4-py2.py3-none-any.whl", hash = "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"}, - {file = "packaging-20.4.tar.gz", hash = "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8"}, -] -pathspec = [ - {file = "pathspec-0.8.0-py2.py3-none-any.whl", hash = "sha256:7d91249d21749788d07a2d0f94147accd8f845507400749ea19c1ec9054a12b0"}, - {file = "pathspec-0.8.0.tar.gz", hash = "sha256:da45173eb3a6f2a5a487efba21f050af2b41948be6ab52b6a1e3ff22bb8b7061"}, -] -pluggy = [ - {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, - {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, -] -py = [ - {file = "py-1.9.0-py2.py3-none-any.whl", hash = "sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2"}, - {file = "py-1.9.0.tar.gz", hash = "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342"}, -] -pyparsing = [ - {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, - {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, -] -pyserial = [ - {file = "pyserial-3.4-py2.py3-none-any.whl", hash = "sha256:e0770fadba80c31013896c7e6ef703f72e7834965954a78e71a3049488d4d7d8"}, - {file = "pyserial-3.4.tar.gz", hash = "sha256:6e2d401fdee0eab996cf734e67773a0143b932772ca8b42451440cfed942c627"}, -] -pytest = [ - {file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"}, - {file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"}, -] -regex = [ - {file = "regex-2020.7.14-cp27-cp27m-win32.whl", hash = "sha256:e46d13f38cfcbb79bfdb2964b0fe12561fe633caf964a77a5f8d4e45fe5d2ef7"}, - {file = "regex-2020.7.14-cp27-cp27m-win_amd64.whl", hash = "sha256:6961548bba529cac7c07af2fd4d527c5b91bb8fe18995fed6044ac22b3d14644"}, - {file = "regex-2020.7.14-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:c50a724d136ec10d920661f1442e4a8b010a4fe5aebd65e0c2241ea41dbe93dc"}, - {file = "regex-2020.7.14-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8a51f2c6d1f884e98846a0a9021ff6861bdb98457879f412fdc2b42d14494067"}, - {file = "regex-2020.7.14-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:9c568495e35599625f7b999774e29e8d6b01a6fb684d77dee1f56d41b11b40cd"}, - {file = "regex-2020.7.14-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:51178c738d559a2d1071ce0b0f56e57eb315bcf8f7d4cf127674b533e3101f88"}, - {file = "regex-2020.7.14-cp36-cp36m-win32.whl", hash = "sha256:9eddaafb3c48e0900690c1727fba226c4804b8e6127ea409689c3bb492d06de4"}, - {file = "regex-2020.7.14-cp36-cp36m-win_amd64.whl", hash = "sha256:14a53646369157baa0499513f96091eb70382eb50b2c82393d17d7ec81b7b85f"}, - {file = "regex-2020.7.14-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:1269fef3167bb52631ad4fa7dd27bf635d5a0790b8e6222065d42e91bede4162"}, - {file = "regex-2020.7.14-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d0a5095d52b90ff38592bbdc2644f17c6d495762edf47d876049cfd2968fbccf"}, - {file = "regex-2020.7.14-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:4c037fd14c5f4e308b8370b447b469ca10e69427966527edcab07f52d88388f7"}, - {file = "regex-2020.7.14-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:bc3d98f621898b4a9bc7fecc00513eec8f40b5b83913d74ccb445f037d58cd89"}, - {file = "regex-2020.7.14-cp37-cp37m-win32.whl", hash = "sha256:46bac5ca10fb748d6c55843a931855e2727a7a22584f302dd9bb1506e69f83f6"}, - {file = "regex-2020.7.14-cp37-cp37m-win_amd64.whl", hash = "sha256:0dc64ee3f33cd7899f79a8d788abfbec168410be356ed9bd30bbd3f0a23a7204"}, - {file = "regex-2020.7.14-cp38-cp38-manylinux1_i686.whl", hash = "sha256:5ea81ea3dbd6767873c611687141ec7b06ed8bab43f68fad5b7be184a920dc99"}, - {file = "regex-2020.7.14-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:bbb332d45b32df41200380fff14712cb6093b61bd142272a10b16778c418e98e"}, - {file = "regex-2020.7.14-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:c11d6033115dc4887c456565303f540c44197f4fc1a2bfb192224a301534888e"}, - {file = "regex-2020.7.14-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:75aaa27aa521a182824d89e5ab0a1d16ca207318a6b65042b046053cfc8ed07a"}, - {file = "regex-2020.7.14-cp38-cp38-win32.whl", hash = "sha256:d6cff2276e502b86a25fd10c2a96973fdb45c7a977dca2138d661417f3728341"}, - {file = "regex-2020.7.14-cp38-cp38-win_amd64.whl", hash = "sha256:7a2dd66d2d4df34fa82c9dc85657c5e019b87932019947faece7983f2089a840"}, - {file = "regex-2020.7.14.tar.gz", hash = "sha256:3a3af27a8d23143c49a3420efe5b3f8cf1a48c6fc8bc6856b03f638abc1833bb"}, -] -six = [ - {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, - {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, -] -toml = [ - {file = "toml-0.10.1-py2.py3-none-any.whl", hash = "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"}, - {file = "toml-0.10.1.tar.gz", hash = "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f"}, -] -typed-ast = [ - {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"}, - {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"}, - {file = "typed_ast-1.4.1-cp35-cp35m-win32.whl", hash = "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919"}, - {file = "typed_ast-1.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01"}, - {file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"}, - {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"}, - {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"}, - {file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"}, - {file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"}, - {file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"}, - {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"}, - {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"}, - {file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"}, - {file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"}, - {file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"}, - {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"}, - {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"}, - {file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"}, - {file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"}, - {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"}, - {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"}, -] -typing-extensions = [ - {file = "typing_extensions-3.7.4.2-py2-none-any.whl", hash = "sha256:f8d2bd89d25bc39dabe7d23df520442fa1d8969b82544370e03d88b5a591c392"}, - {file = "typing_extensions-3.7.4.2-py3-none-any.whl", hash = "sha256:6e95524d8a547a91e08f404ae485bbb71962de46967e1b71a0cb89af24e761c5"}, - {file = "typing_extensions-3.7.4.2.tar.gz", hash = "sha256:79ee589a3caca649a9bfd2a8de4709837400dfa00b6cc81962a1e6a1815969ae"}, -] -wcwidth = [ - {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, - {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, -] -zipp = [ - {file = "zipp-3.1.0-py3-none-any.whl", hash = "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b"}, - {file = "zipp-3.1.0.tar.gz", hash = "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96"}, -] From 23a2c08425a2e43d85d62e6b960a387b200394a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Grilo?= Date: Wed, 19 Mar 2025 16:51:22 +0000 Subject: [PATCH 041/159] Update .gitignore and README --- .gitignore | 180 +++++++++++++++++++++++++++++++++++++++++++++++++++-- README.md | 3 + 2 files changed, 177 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index f60ee92..1800114 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,174 @@ -.idea/ -pyharp.egg-info/ -.python-version -__pycache__ -tests/.pytest_cache -**/*.bin \ No newline at end of file +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +#uv.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/latest/usage/project/#working-with-version-control +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc \ No newline at end of file diff --git a/README.md b/README.md index b506d9b..9191782 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,9 @@ Harp implementation of the Harp protocol. +> [!CAUTION] +> The README is currently outdated! + ## Install with Pip From this directory, install in editable mode with ```` From b121ab125f02272c2e92c33dab7961c9fe5e6900 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Grilo?= Date: Mon, 24 Mar 2025 11:07:26 +0000 Subject: [PATCH 042/159] Chore: remove shebangs from example scripts --- examples/behavior_device_driver_test.py | 48 +++++++++++------------ examples/check_device_id.py | 18 ++++----- examples/get_info.py | 36 ++++++++--------- examples/wait_for_events.py | 25 ++++++------ examples/write_and_read_from_registers.py | 36 ++++++++--------- 5 files changed, 78 insertions(+), 85 deletions(-) diff --git a/examples/behavior_device_driver_test.py b/examples/behavior_device_driver_test.py index d9cfab7..9971ad6 100755 --- a/examples/behavior_device_driver_test.py +++ b/examples/behavior_device_driver_test.py @@ -1,49 +1,47 @@ -#!/usr/bin/env python3 -from pyharp.drivers.behavior import Behavior -from pyharp.messages import HarpMessage -from pyharp.messages import MessageType -from struct import * import os +from struct import * +from pyharp.drivers.behavior import Behavior +from pyharp.messages import HarpMessage, MessageType # Open the device and print the info on screen # Open serial connection and save communication to a file device = None -if os.name == 'posix': # check for Linux. +if os.name == "posix": # check for Linux. device = Behavior("/dev/harp_device_00", "ibl.bin") -else: # assume Windows. +else: # assume Windows. device = Behavior("COM95", "ibl.bin") print(f"digital inputs: {device.all_input_states:03b}") print(f"digital outputs: {device.all_output_states:016b}") print(f"setting digital outputs") -#device.all_output_states = 0x0000 # Set the whole port directly. -#device.set_outputs(0xFFFF) # Set the values set to logic 1 only. -#device.clear_outputs(0xFFFF)# Clear values set to logic 1 only. +# device.all_output_states = 0x0000 # Set the whole port directly. +# device.set_outputs(0xFFFF) # Set the values set to logic 1 only. +# device.clear_outputs(0xFFFF)# Clear values set to logic 1 only. print(f"digital outputs: {device.all_output_states:016b}") device.set_io_configuration(0b111) # TODO: FIXME. IOs are not working -#device.set_io_configuration(0b111) # This is getting ignored? -#device.set_io_outputs(0b000) -#device.all_io_states = 0b000 -#print(f"digital ios: {device.all_io_states:03b}") +# device.set_io_configuration(0b111) # This is getting ignored? +# device.set_io_outputs(0b000) +# device.all_io_states = 0b000 +# print(f"digital ios: {device.all_io_states:03b}") -#device.D0 = 0 -#print(f"D0: {device.D0}") -#device.D0 = 1 -#print(f"D0: {device.D0}") +# device.D0 = 0 +# print(f"D0: {device.D0}") +# device.D0 = 1 +# print(f"D0: {device.D0}") # -#device.D1 = 0 -#print(f"D1: {device.D1}") -#device.D1 = 1 -#print(f"D1: {device.D1}") +# device.D1 = 0 +# print(f"D1: {device.D1}") +# device.D1 = 1 +# print(f"D1: {device.D1}") # -#print(f"DI2: {device.DI2}") +# print(f"DI2: {device.DI2}") -#import time -#while True: +# import time +# while True: # print(f"PORT0 IN State: {device.port0_i0}") # print(f"PORT0 IO State: {device.port0_io0}") # print(f"PORT0 OUT State: {device.port0_o0}") diff --git a/examples/check_device_id.py b/examples/check_device_id.py index 9568921..7d94eb3 100755 --- a/examples/check_device_id.py +++ b/examples/check_device_id.py @@ -1,11 +1,9 @@ -#!/usr/bin/env python3 -from pyharp.device import Device -from pyharp.messages import HarpMessage -from pyharp.messages import MessageType -from pyharp.device_names import device_names -from struct import * import os +from struct import * +from pyharp.device import Device +from pyharp.device_names import device_names +from pyharp.messages import HarpMessage, MessageType # ON THIS EXAMPLE # @@ -15,15 +13,15 @@ # Open the device # Open serial connection -if os.name == "posix": # check for Linux. +if os.name == "posix": # check for Linux. device = Device("/dev/harp_device_00") -else: # assume Windows. +else: # assume Windows. device = Device("COM95") # Get some of the device's parameters -device_id = device.WHO_AM_I # Get device's ID +device_id = device.WHO_AM_I # Get device's ID device_id_description = device.WHO_AM_I_DEVICE # Get device's user name -device_user_name = device.DEVICE_NAME # Get device's user name +device_user_name = device.DEVICE_NAME # Get device's user name # Check if we are dealing with the correct device if device_id in device_names: diff --git a/examples/get_info.py b/examples/get_info.py index eec8cef..e0f2ff6 100755 --- a/examples/get_info.py +++ b/examples/get_info.py @@ -1,10 +1,8 @@ -#!/usr/bin/env python3 -from pyharp.device import Device, DeviceMode -from pyharp.messages import HarpMessage -from pyharp.messages import MessageType -from struct import * import os +from struct import * +from pyharp.device import Device, DeviceMode +from pyharp.messages import HarpMessage, MessageType # ON THIS EXAMPLE # @@ -14,27 +12,27 @@ # Open the device and print the info on screen # Open serial connection and save communication to a file -if os.name == 'posix': # check for Linux. - #device = Device("/dev/harp_device_00", "ibl.bin") - #device = Device("/dev/ttyACM0") +if os.name == "posix": # check for Linux. + # device = Device("/dev/harp_device_00", "ibl.bin") + # device = Device("/dev/ttyACM0") device = Device("/dev/ttyUSB0") -else: # assume Windows. +else: # assume Windows. device = Device("COM95", "ibl.bin") -device.info() # Display device's info on screen +device.info() # Display device's info on screen # Get some of the device's parameters -device_id = device.WHO_AM_I # Get device's ID +device_id = device.WHO_AM_I # Get device's ID device_id_description = device.WHO_AM_I_DEVICE # Get device's user name -device_user_name = device.DEVICE_NAME # Get device's user name +device_user_name = device.DEVICE_NAME # Get device's user name # Get versions -device_fw_h = device.FIRMWARE_VERSION_H # Get device's firmware version -device_fw_l = device.FIRMWARE_VERSION_L # Get device's firmware version -device_hw_h = device.HW_VERSION_H # Get device's hardware version -device_hw_l = device.HW_VERSION_L # Get device's hardware version -device_harp_h = device.HARP_VERSION_H # Get device's harp core version -device_harp_l = device.HARP_VERSION_L # Get device's harp core version -device_assembly = device.ASSEMBLY_VERSION # Get device's assembly version +device_fw_h = device.FIRMWARE_VERSION_H # Get device's firmware version +device_fw_l = device.FIRMWARE_VERSION_L # Get device's firmware version +device_hw_h = device.HW_VERSION_H # Get device's hardware version +device_hw_l = device.HW_VERSION_L # Get device's hardware version +device_harp_h = device.HARP_VERSION_H # Get device's harp core version +device_harp_l = device.HARP_VERSION_L # Get device's harp core version +device_assembly = device.ASSEMBLY_VERSION # Get device's assembly version reg_dump = device.dump_registers() for reg_reply in reg_dump: diff --git a/examples/wait_for_events.py b/examples/wait_for_events.py index 584836e..27f693e 100755 --- a/examples/wait_for_events.py +++ b/examples/wait_for_events.py @@ -1,28 +1,27 @@ -#!/usr/bin/env python3 -from pyharp.drivers.behavior import Behavior, Events -from pyharp.messages import HarpMessage -from pyharp.messages import MessageType -from struct import * import os +from struct import * from pyharp.device import Device, DeviceMode - +from pyharp.drivers.behavior import Behavior, Events +from pyharp.messages import HarpMessage, MessageType # Open the device and print the info on screen # Open serial connection and save communication to a file device = None -if os.name == 'posix': # check for Linux. - #device = Behavior("/dev/harp_device_00", "ibl.bin") - #device = Device("/dev/ttyACM0",) - device = Device("/dev/ttyUSB0",) -else: # assume Windows. +if os.name == "posix": # check for Linux. + # device = Behavior("/dev/harp_device_00", "ibl.bin") + # device = Device("/dev/ttyACM0",) + device = Device( + "/dev/ttyUSB0", + ) +else: # assume Windows. device = Behavior("COM95", "ibl.bin") print("Setting mode to active.") # Mode will remain active for up to 3 seconds after CTS pin is brought low. device.set_mode(DeviceMode.Active) -#device.disable_all_events() -#device.enable_events(Events.port_digital_inputs) +# device.disable_all_events() +# device.enable_events(Events.port_digital_inputs) while True: for msg in device.get_events(): print(msg) diff --git a/examples/write_and_read_from_registers.py b/examples/write_and_read_from_registers.py index 4430b00..d02aab0 100755 --- a/examples/write_and_read_from_registers.py +++ b/examples/write_and_read_from_registers.py @@ -1,10 +1,8 @@ -#!/usr/bin/env python3 -from pyharp.device import Device -from pyharp.messages import HarpMessage -from pyharp.messages import MessageType -from struct import * import os +from struct import * +from pyharp.device import Device +from pyharp.messages import HarpMessage, MessageType # ON THIS EXAMPLE # @@ -15,21 +13,21 @@ # Open the device and print the info on screen # Open serial connection and save communication to a file -if os.name == 'posix': # check for Linux. +if os.name == "posix": # check for Linux. device = Device("/dev/harp_device_00", "ibl.bin") -else: # assume Windows. +else: # assume Windows. device = Device("COM95", "ibl.bin") # Read current analog sensor's higher threshold (ANA_SENSOR_TH0_HIGH) at address 42 -#analog_threshold_h = device.send(HarpMessage.ReadU16(42).frame).payload_as_int() -#print(f"Analog sensor's higher threshold: {analog_threshold_h}") +# analog_threshold_h = device.send(HarpMessage.ReadU16(42).frame).payload_as_int() +# print(f"Analog sensor's higher threshold: {analog_threshold_h}") import time print(f"System time: {time.perf_counter():.6f}") -data_stream = device.send(HarpMessage.ReadU8(33).frame) # returns a ReplyHarpMessage -#data_stream = device.send(HarpMessage.ReadS16(33).frame).payload_as_int_array() +data_stream = device.send(HarpMessage.ReadU8(33).frame) # returns a ReplyHarpMessage +# data_stream = device.send(HarpMessage.ReadS16(33).frame).payload_as_int_array() print(f"Data Stream payload type: {data_stream.payload_type.name}") print(f"Data Stream message type: {data_stream.message_type.name}") print(f"Data Stream timestamp: {data_stream.timestamp}") @@ -37,7 +35,9 @@ print(f"Data Stream payload: {data_stream.payload}") print(f"System time: {time.perf_counter():.6f}") -event_reg_response = device.send(HarpMessage.ReadU8(77).frame) # returns a ReplyHarpMessage +event_reg_response = device.send( + HarpMessage.ReadU8(77).frame +) # returns a ReplyHarpMessage print(f"EVNT_ENABLE payload type: {event_reg_response.payload_type.name}") print(f"EVNT_ENABLE message type: {event_reg_response.message_type.name}") print(f"EVNT_ENABLE timestamp: {event_reg_response.timestamp}") @@ -45,19 +45,19 @@ print(f"EVNT_ENABLE payload: {event_reg_response.payload[0]:08b}") ## Increase current analog sensor's higher threshold by one unit -#device.send(HarpMessage.WriteU16(42, analog_threshold_h+1).frame) +# device.send(HarpMessage.WriteU16(42, analog_threshold_h+1).frame) # ## Check if the register was well written -#analog_threshold_h = device.send(HarpMessage.ReadU16(42).frame).payload_as_int() -#print(f"Analog sensor's higher threshold: {analog_threshold_h}") +# analog_threshold_h = device.send(HarpMessage.ReadU16(42).frame).payload_as_int() +# print(f"Analog sensor's higher threshold: {analog_threshold_h}") # ## Read 10 samples of the analog sensor and display the values ## The value is at register STREAM[0], address 33 -#analog_sensor = [] -#for x in range(10): +# analog_sensor = [] +# for x in range(10): # value = device.send(HarpMessage.ReadS16(33).frame).payload_as_int() # analog_sensor.append(value & 0xffff) -#print(f"Analog sensor's values: {analog_sensor}") +# print(f"Analog sensor's values: {analog_sensor}") # Close connection device.disconnect() From 3f1b5f88ea5c6d8ccd4046f4b696fd262ec368e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Tue, 25 Mar 2025 10:40:00 +0000 Subject: [PATCH 043/159] Cleanup unused code --- examples/behavior_device_driver_test.py | 2 -- examples/check_device_id.py | 2 -- examples/get_info.py | 4 +--- examples/wait_for_events.py | 4 +--- examples/write_and_read_from_registers.py | 3 +-- pyharp/main.py | 9 --------- tests/test_device.py | 5 ++--- tests/test_messages.py | 2 +- 8 files changed, 6 insertions(+), 25 deletions(-) delete mode 100644 pyharp/main.py diff --git a/examples/behavior_device_driver_test.py b/examples/behavior_device_driver_test.py index 9971ad6..24c1978 100755 --- a/examples/behavior_device_driver_test.py +++ b/examples/behavior_device_driver_test.py @@ -1,8 +1,6 @@ import os -from struct import * from pyharp.drivers.behavior import Behavior -from pyharp.messages import HarpMessage, MessageType # Open the device and print the info on screen # Open serial connection and save communication to a file diff --git a/examples/check_device_id.py b/examples/check_device_id.py index 7d94eb3..7c6bd57 100755 --- a/examples/check_device_id.py +++ b/examples/check_device_id.py @@ -1,9 +1,7 @@ import os -from struct import * from pyharp.device import Device from pyharp.device_names import device_names -from pyharp.messages import HarpMessage, MessageType # ON THIS EXAMPLE # diff --git a/examples/get_info.py b/examples/get_info.py index e0f2ff6..8a33174 100755 --- a/examples/get_info.py +++ b/examples/get_info.py @@ -1,8 +1,6 @@ import os -from struct import * -from pyharp.device import Device, DeviceMode -from pyharp.messages import HarpMessage, MessageType +from pyharp.device import Device # ON THIS EXAMPLE # diff --git a/examples/wait_for_events.py b/examples/wait_for_events.py index 27f693e..6cdb615 100755 --- a/examples/wait_for_events.py +++ b/examples/wait_for_events.py @@ -1,9 +1,7 @@ import os -from struct import * from pyharp.device import Device, DeviceMode -from pyharp.drivers.behavior import Behavior, Events -from pyharp.messages import HarpMessage, MessageType +from pyharp.drivers.behavior import Behavior # Open the device and print the info on screen # Open serial connection and save communication to a file diff --git a/examples/write_and_read_from_registers.py b/examples/write_and_read_from_registers.py index d02aab0..e17ba1d 100755 --- a/examples/write_and_read_from_registers.py +++ b/examples/write_and_read_from_registers.py @@ -1,8 +1,7 @@ import os -from struct import * from pyharp.device import Device -from pyharp.messages import HarpMessage, MessageType +from pyharp.messages import HarpMessage # ON THIS EXAMPLE # diff --git a/pyharp/main.py b/pyharp/main.py deleted file mode 100644 index 7197906..0000000 --- a/pyharp/main.py +++ /dev/null @@ -1,9 +0,0 @@ -import serial - - -def main() -> None: - print("hello world") - - -if __name__ == "__main__": - main() diff --git a/tests/test_device.py b/tests/test_device.py index 5a5eab9..cc5dff2 100644 --- a/tests/test_device.py +++ b/tests/test_device.py @@ -1,8 +1,7 @@ -import serial import time -from typing import Optional -from pyharp.messages import HarpMessage, ReplyHarpMessage + from pyharp.device import Device +from pyharp.messages import HarpMessage, ReplyHarpMessage DEFAULT_ADDRESS = 42 diff --git a/tests/test_messages.py b/tests/test_messages.py index 1100ae9..452bfed 100644 --- a/tests/test_messages.py +++ b/tests/test_messages.py @@ -1,5 +1,5 @@ from pyharp.messages import HarpMessage -from pyharp.messages import MessageType +from pyharp.messages import HarpMessage, MessageType from pyharp.messages import CommonRegisters DEFAULT_ADDRESS = 42 From 2e4f2169bafccf5085349b8f0ea0c1df845839a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Tue, 25 Mar 2025 10:41:51 +0000 Subject: [PATCH 044/159] Remove ReadXXHarpMessage classes --- pyharp/messages.py | 64 ++++++++++------------------------------------ 1 file changed, 14 insertions(+), 50 deletions(-) diff --git a/pyharp/messages.py b/pyharp/messages.py index e86747a..cdf8ff1 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -102,34 +102,32 @@ def checksum(self) -> int: return self._frame[-1] @staticmethod - def ReadU8(address: int) -> ReadU8HarpMessage: - return ReadU8HarpMessage(address) + def ReadU8(address: int) -> ReadHarpMessage: + return ReadHarpMessage(payload_type=PayloadType.U8, address=address) @staticmethod - def ReadS8(address: int) -> ReadS8HarpMessage: - return ReadS8HarpMessage(address) + def ReadS8(address: int) -> ReadHarpMessage: + return ReadHarpMessage(payload_type=PayloadType.S8, address=address) @staticmethod - def ReadS16(address: int) -> ReadS16HarpMessage: - return ReadS16HarpMessage(address) + def ReadS16(address: int) -> ReadHarpMessage: + return ReadHarpMessage(payload_type=PayloadType.S16, address=address) @staticmethod - def ReadU16(address: int) -> ReadU16HarpMessage: - return ReadU16HarpMessage(address) - - # TODO: ReadS16 + def ReadU16(address: int) -> ReadHarpMessage: + return ReadHarpMessage(payload_type=PayloadType.U16, address=address) @staticmethod - def ReadU32(address: int) -> ReadU32HarpMessage: - return ReadU32HarpMessage(address) + def ReadU32(address: int) -> ReadHarpMessage: + return ReadHarpMessage(payload_type=PayloadType.U32, address=address) @staticmethod - def ReadS32(address: int) -> ReadS32HarpMessage: - return ReadS32HarpMessage(address) + def ReadS32(address: int) -> ReadHarpMessage: + return ReadHarpMessage(payload_type=PayloadType.S32, address=address) @staticmethod - def ReadFloat(address: int) -> ReadFloatHarpMessage: - return ReadFloatHarpMessage(address) + def ReadFloat(address: int) -> ReadHarpMessage: + return ReadHarpMessage(payload_type=PayloadType.Float, address=address) @staticmethod def WriteU8(address: int, value: int) -> WriteU8HarpMessage: @@ -268,40 +266,6 @@ def __init__(self, payload_type: PayloadType, address: int): self._frame.append(self.calculate_checksum()) -class ReadU8HarpMessage(ReadHarpMessage): - def __init__(self, address: int): - super().__init__(PayloadType.U8, address) - - -class ReadS8HarpMessage(ReadHarpMessage): - def __init__(self, address: int): - super().__init__(PayloadType.S8, address) - - -class ReadU16HarpMessage(ReadHarpMessage): - def __init__(self, address: int): - super().__init__(PayloadType.U16, address) - - -class ReadS16HarpMessage(ReadHarpMessage): - def __init__(self, address: int): - super().__init__(PayloadType.S16, address) - -class ReadU32HarpMessage(ReadHarpMessage): - def __init__(self, address: int): - super().__init__(PayloadType.U32, address) - - -class ReadS32HarpMessage(ReadHarpMessage): - def __init__(self, address: int): - super().__init__(PayloadType.S32, address) - - -class ReadFloatHarpMessage(ReadHarpMessage): - def __init__(self, address: int): - super().__init__(PayloadType.Float, address) - - class WriteHarpMessage(HarpMessage): BASE_LENGTH: int = 5 MESSAGE_TYPE: int = MessageType.WRITE From 00217d4d2016448a42df7dd88d3f050b469158d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Tue, 25 Mar 2025 10:44:09 +0000 Subject: [PATCH 045/159] Move base classes --- pyharp/base.py | 52 +++++++++++++++++++++++++++++++++++++ pyharp/device.py | 15 ++++++----- pyharp/messages.py | 58 +++--------------------------------------- tests/test_messages.py | 3 +-- 4 files changed, 65 insertions(+), 63 deletions(-) create mode 100644 pyharp/base.py diff --git a/pyharp/base.py b/pyharp/base.py new file mode 100644 index 0000000..70daabd --- /dev/null +++ b/pyharp/base.py @@ -0,0 +1,52 @@ +from enum import Enum + + +class MessageType(Enum): + READ: int = 1 + WRITE: int = 2 + EVENT: int = 3 + READ_ERROR: int = 9 + WRITE_ERROR: int = 10 + + +class PayloadType(Enum): + isUnsigned: int = 0x00 + isSigned: int = 0x80 + isFloat: int = 0x40 + hasTimestamp: int = 0x10 + + U8 = isUnsigned | 1 # 1 + S8 = isSigned | 1 # 129 + U16 = isUnsigned | 2 # 2 + S16 = isSigned | 2 # 130 + U32 = isUnsigned | 4 + S32 = isSigned | 4 + U64 = isUnsigned | 8 + S64 = isSigned | 8 + Float = isFloat | 4 + Timestamp = hasTimestamp + TimestampedU8 = hasTimestamp | U8 + TimestampedS8 = hasTimestamp | S8 + TimestampedU16 = hasTimestamp | U16 + TimestampedS16 = hasTimestamp | S16 + TimestampedU32 = hasTimestamp | U32 + TimestampedS32 = hasTimestamp | S32 + TimestampedU64 = hasTimestamp | U64 + TimestampedS64 = hasTimestamp | S64 + TimestampedFloat = hasTimestamp | Float + + +class CommonRegisters: + WHO_AM_I = 0x00 + HW_VERSION_H = 0x01 + HW_VERSION_L = 0x02 + ASSEMBLY_VERSION = 0x03 + HARP_VERSION_H = 0x04 + HARP_VERSION_L = 0x05 + FIRMWARE_VERSION_H = 0x06 + FIRMWARE_VERSION_L = 0x07 + TIMESTAMP_SECOND = 0x08 + TIMESTAMP_MICRO = 0x09 + OPERATION_CTRL = 0x0A + RESET_DEV = 0x0B + DEVICE_NAME = 0x0C diff --git a/pyharp/device.py b/pyharp/device.py index b4c6f6d..fa06a26 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -1,16 +1,17 @@ -from __future__ import annotations # enable subscriptable type hints for lists. -import serial +from __future__ import annotations # enable subscriptable type hints for lists. + import logging import queue -from typing import Optional, Union +from enum import Enum from pathlib import Path +from typing import Optional, Union +import serial + +from pyharp.base import CommonRegisters +from pyharp.device_names import device_names from pyharp.harp_serial import HarpSerial from pyharp.messages import HarpMessage, ReplyHarpMessage -from pyharp.messages import CommonRegisters, MessageType -from pyharp.device_names import device_names -from enum import Enum -from time import perf_counter class DeviceMode(Enum): diff --git a/pyharp/messages.py b/pyharp/messages.py index cdf8ff1..75ea243 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -1,59 +1,9 @@ -from __future__ import annotations # for type hints (PEP 563) -from enum import Enum -# from abc import ABC, abstractmethod -from typing import Union, Tuple, Optional, List -import struct +from __future__ import annotations # for type hints (PEP 563) +import struct +from typing import List, Union -class MessageType(Enum): - READ: int = 1 - WRITE: int = 2 - EVENT: int = 3 - READ_ERROR: int = 9 - WRITE_ERROR: int = 10 - - -class PayloadType(Enum): - isUnsigned: int = 0x00 - isSigned: int = 0x80 - isFloat: int = 0x40 - hasTimestamp: int = 0x10 - - U8 = isUnsigned | 1 # 1 - S8 = isSigned | 1 # 129 - U16 = isUnsigned | 2 # 2 - S16 = isSigned | 2 # 130 - U32 = isUnsigned | 4 - S32 = isSigned | 4 - U64 = isUnsigned | 8 - S64 = isSigned | 8 - Float = isFloat | 4 - Timestamp = hasTimestamp - TimestampedU8 = hasTimestamp | U8 - TimestampedS8 = hasTimestamp | S8 - TimestampedU16 = hasTimestamp | U16 - TimestampedS16 = hasTimestamp | S16 - TimestampedU32 = hasTimestamp | U32 - TimestampedS32 = hasTimestamp | S32 - TimestampedU64 = hasTimestamp | U64 - TimestampedS64 = hasTimestamp | S64 - TimestampedFloat = hasTimestamp | Float - - -class CommonRegisters: - WHO_AM_I = 0x00 - HW_VERSION_H = 0x01 - HW_VERSION_L = 0x02 - ASSEMBLY_VERSION = 0x03 - HARP_VERSION_H = 0x04 - HARP_VERSION_L = 0x05 - FIRMWARE_VERSION_H = 0x06 - FIRMWARE_VERSION_L = 0x07 - TIMESTAMP_SECOND = 0x08 - TIMESTAMP_MICRO = 0x09 - OPERATION_CTRL = 0x0A - RESET_DEV = 0x0B - DEVICE_NAME = 0x0C +from pyharp.base import MessageType, PayloadType class HarpMessage: diff --git a/tests/test_messages.py b/tests/test_messages.py index 452bfed..a5e1ae3 100644 --- a/tests/test_messages.py +++ b/tests/test_messages.py @@ -1,6 +1,5 @@ -from pyharp.messages import HarpMessage +from pyharp.base import CommonRegisters from pyharp.messages import HarpMessage, MessageType -from pyharp.messages import CommonRegisters DEFAULT_ADDRESS = 42 From 2716ce81209c864ee110c9aa24bf0124cb3bf93a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Tue, 25 Mar 2025 10:46:48 +0000 Subject: [PATCH 046/159] Formatting --- pyharp/messages.py | 61 ++++++++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/pyharp/messages.py b/pyharp/messages.py index 75ea243..9e07b94 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -114,10 +114,9 @@ def parse(frame: bytearray) -> ReplyHarpMessage: # A Response Message from a harp device. class ReplyHarpMessage(HarpMessage): - - def __init__( - self, frame: bytearray, + self, + frame: bytearray, ): """ @@ -127,57 +126,66 @@ def __init__( self._frame = frame # retrieve all content from 11 (where payload starts) until the checksum (not inclusive) self._raw_payload = frame[11:-1] - self._payload = self._parse_payload(self._raw_payload) # payload formatted as list[payload type] + self._payload = self._parse_payload( + self._raw_payload + ) # payload formatted as list[payload type] # Assign timestamp after _payload since @properties all rely on self._payload. - self._timestamp = int.from_bytes(frame[5:9], byteorder="little", signed=False) + \ - int.from_bytes(frame[9:11], byteorder="little", signed=False)*32e-6 + self._timestamp = ( + int.from_bytes(frame[5:9], byteorder="little", signed=False) + + int.from_bytes(frame[9:11], byteorder="little", signed=False) * 32e-6 + ) # Timestamp is junk if it's not present. if not (self.payload_type.value & PayloadType.hasTimestamp.value): self._timestamp = None - def _parse_payload(self, raw_payload) -> list[int]: """return the payload as a list of ints after parsing it from the raw payload.""" is_signed = True if (self.payload_type.value & 0x80) else False is_float = True if (self.payload_type.value & 0x40) else False bytes_per_word = self.payload_type.value & 0x07 - payload_len = len(raw_payload) # payload length in bytes. + payload_len = len(raw_payload) # payload length in bytes. - word_chunks = [raw_payload[i:i+bytes_per_word] for i in range(0, payload_len, bytes_per_word)] + word_chunks = [ + raw_payload[i : i + bytes_per_word] + for i in range(0, payload_len, bytes_per_word) + ] if not is_float: - return [int.from_bytes(chunk, byteorder="little", signed=is_signed) for chunk in word_chunks] - else: # handle float case. - return [struct.unpack(' Union[int, list[int]]: @@ -202,7 +210,6 @@ def payload_as_float(self) -> float: class ReadHarpMessage(HarpMessage): MESSAGE_TYPE: int = MessageType.READ - def __init__(self, payload_type: PayloadType, address: int): self._frame = bytearray() @@ -237,7 +244,7 @@ def __init__( self._frame.append(self.BASE_LENGTH + offset) self._frame.append(address) - self._frame.append(HarpMessage.DEFAULT_PORT) + self._frame.append(self.DEFAULT_PORT) self._frame.append(payload_type.value) # Handle payloads that are bytes or bytearray (bytearray = multi-motor instructions) From 0f57b08724fbac03bcc1b7b3e48148efcf673e07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Tue, 25 Mar 2025 10:47:00 +0000 Subject: [PATCH 047/159] Update protocol link --- pyharp/messages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyharp/messages.py b/pyharp/messages.py index 9e07b94..d8f683b 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -8,7 +8,7 @@ class HarpMessage: """ - https://github.com/harp-tech/protocol/blob/master/Binary%20Protocol%201.0%201.1%2020180223.pdf + https://github.com/harp-tech/protocol/blob/main/BinaryProtocol-8bit.md """ DEFAULT_PORT: int = 255 From 9b86a4508e43ad0d9e0c607e19028d3f9246a51a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Tue, 25 Mar 2025 11:30:54 +0000 Subject: [PATCH 048/159] Convert usage of ReadXX classes to device related methods --- examples/behavior_device_driver_test.py | 4 +- examples/write_and_read_from_registers.py | 11 +-- pyharp/device.py | 107 +++++++++++----------- pyharp/drivers/behavior.py | 27 +++--- tests/test_device.py | 6 +- tests/test_messages.py | 16 ++-- 6 files changed, 89 insertions(+), 82 deletions(-) diff --git a/examples/behavior_device_driver_test.py b/examples/behavior_device_driver_test.py index 24c1978..dd6a3a4 100755 --- a/examples/behavior_device_driver_test.py +++ b/examples/behavior_device_driver_test.py @@ -6,7 +6,7 @@ # Open serial connection and save communication to a file device = None if os.name == "posix": # check for Linux. - device = Behavior("/dev/harp_device_00", "ibl.bin") + device = Behavior("/dev/ttyUSB0", "ibl.bin") else: # assume Windows. device = Behavior("COM95", "ibl.bin") @@ -17,7 +17,7 @@ # device.set_outputs(0xFFFF) # Set the values set to logic 1 only. # device.clear_outputs(0xFFFF)# Clear values set to logic 1 only. print(f"digital outputs: {device.all_output_states:016b}") -device.set_io_configuration(0b111) +# device.set_io_configuration(0b111) # TODO: FIXME. IOs are not working # device.set_io_configuration(0b111) # This is getting ignored? diff --git a/examples/write_and_read_from_registers.py b/examples/write_and_read_from_registers.py index e17ba1d..91d7c7e 100755 --- a/examples/write_and_read_from_registers.py +++ b/examples/write_and_read_from_registers.py @@ -1,7 +1,6 @@ import os from pyharp.device import Device -from pyharp.messages import HarpMessage # ON THIS EXAMPLE # @@ -13,7 +12,7 @@ # Open the device and print the info on screen # Open serial connection and save communication to a file if os.name == "posix": # check for Linux. - device = Device("/dev/harp_device_00", "ibl.bin") + device = Device("/dev/ttyUSB0", "ibl.bin") else: # assume Windows. device = Device("COM95", "ibl.bin") @@ -25,8 +24,8 @@ import time print(f"System time: {time.perf_counter():.6f}") -data_stream = device.send(HarpMessage.ReadU8(33).frame) # returns a ReplyHarpMessage -# data_stream = device.send(HarpMessage.ReadS16(33).frame).payload_as_int_array() +data_stream = device.read_u8(33) + print(f"Data Stream payload type: {data_stream.payload_type.name}") print(f"Data Stream message type: {data_stream.message_type.name}") print(f"Data Stream timestamp: {data_stream.timestamp}") @@ -34,9 +33,7 @@ print(f"Data Stream payload: {data_stream.payload}") print(f"System time: {time.perf_counter():.6f}") -event_reg_response = device.send( - HarpMessage.ReadU8(77).frame -) # returns a ReplyHarpMessage +event_reg_response = device.read_u8(77) # returns a ReplyHarpMessage print(f"EVNT_ENABLE payload type: {event_reg_response.payload_type.name}") print(f"EVNT_ENABLE message type: {event_reg_response.message_type.name}") print(f"EVNT_ENABLE timestamp: {event_reg_response.timestamp}") diff --git a/pyharp/device.py b/pyharp/device.py index fa06a26..f782086 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -8,10 +8,10 @@ import serial -from pyharp.base import CommonRegisters +from pyharp.base import CommonRegisters, PayloadType from pyharp.device_names import device_names from pyharp.harp_serial import HarpSerial -from pyharp.messages import HarpMessage, ReplyHarpMessage +from pyharp.messages import HarpMessage, ReadHarpMessage, ReplyHarpMessage class DeviceMode(Enum): @@ -100,106 +100,76 @@ def disconnect(self) -> None: def read_who_am_i(self) -> int: address = CommonRegisters.WHO_AM_I - reply: ReplyHarpMessage = self.send( - HarpMessage.ReadU16(address).frame, dump=False - ) - #print(str(reply)) + reply: ReplyHarpMessage = self.read_u16(address, dump=False) + return reply.payload_as_int() def read_who_am_i_device(self) -> str: address = CommonRegisters.WHO_AM_I - reply: ReplyHarpMessage = self.send( - HarpMessage.ReadU16(address).frame, dump=False - ) - #print(str(reply)) + reply: ReplyHarpMessage = self.read_u16(address, dump=False) return device_names.get(reply.payload_as_int()) def read_hw_version_h(self) -> int: address = CommonRegisters.HW_VERSION_H - reply: ReplyHarpMessage = self.send( - HarpMessage.ReadU8(address).frame, dump=False - ) - #print(str(reply)) + reply: ReplyHarpMessage = self.read_u8(address, dump=False) return reply.payload_as_int() def read_hw_version_l(self) -> int: address = CommonRegisters.HW_VERSION_L - reply: ReplyHarpMessage = self.send( - HarpMessage.ReadU8(address).frame, dump=False - ) - #print(str(reply)) + reply: ReplyHarpMessage = self.read_u8(address, dump=False) return reply.payload_as_int() def read_assembly_version(self) -> int: address = CommonRegisters.ASSEMBLY_VERSION - reply: ReplyHarpMessage = self.send( - HarpMessage.ReadU8(address).frame, dump=False - ) - #print(str(reply)) + reply: ReplyHarpMessage = self.read_u8(address, dump=False) return reply.payload_as_int() def read_harp_h_version(self) -> int: address = CommonRegisters.HARP_VERSION_H - reply: ReplyHarpMessage = self.send( - HarpMessage.ReadU8(address).frame, dump=False - ) - #print(str(reply)) + reply: ReplyHarpMessage = self.read_u8(address, dump=False) return reply.payload_as_int() def read_harp_l_version(self) -> int: address = CommonRegisters.HARP_VERSION_L - reply: ReplyHarpMessage = self.send( - HarpMessage.ReadU8(address).frame, dump=False - ) - #print(str(reply)) + reply: ReplyHarpMessage = self.read_u8(address, dump=False) return reply.payload_as_int() def read_fw_h_version(self) -> int: address = CommonRegisters.FIRMWARE_VERSION_H - reply: ReplyHarpMessage = self.send( - HarpMessage.ReadU8(address).frame, dump=False - ) - #print(str(reply)) + reply: ReplyHarpMessage = self.read_u8(address, dump=False) return reply.payload_as_int() def read_fw_l_version(self) -> int: address = CommonRegisters.FIRMWARE_VERSION_L - reply: ReplyHarpMessage = self.send( - HarpMessage.ReadU8(address).frame, dump=False -) - #print(str(reply)) + reply: ReplyHarpMessage = self.read_u8(address, dump=False) return reply.payload_as_int() def read_device_name(self) -> str: address = CommonRegisters.DEVICE_NAME - # reply: Optional[bytes] = self.send(HarpMessage.ReadU8(address).frame, 13 + 24) - reply: ReplyHarpMessage = self.send( - HarpMessage.ReadU8(address).frame, dump=False - ) - #print(str(reply)) + reply: ReplyHarpMessage = self.read_u8(address, dump=False) return reply.payload_as_string() def read_device_mode(self) -> DeviceMode: address = CommonRegisters.OPERATION_CTRL - reply = self.send(HarpMessage.ReadU8(address).frame) + reply = self.read_u8(address) return DeviceMode(reply.payload_as_int() & 0x03) def dump_registers(self) -> list: @@ -207,7 +177,7 @@ def dump_registers(self) -> list: as Harp Read Reply Messages. """ address = CommonRegisters.OPERATION_CTRL - reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_int() + reg_value = self.read_u8(address).payload_as_int() reg_value |= 0x08 # Assert DUMP bit self._ser.write(HarpMessage.WriteU8(address, reg_value).frame) replies = [] @@ -224,7 +194,7 @@ def set_mode(self, mode: DeviceMode) -> ReplyHarpMessage: """Change the device's OPMODE. Reply can be ignored.""" address = CommonRegisters.OPERATION_CTRL # Read register first. - reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_int() + reg_value = self.read_u8(address).payload_as_int() reg_value &= ~0x03 # mask off old mode. reg_value |= mode.value reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) @@ -234,7 +204,7 @@ def enable_status_led(self): """enable the device's status led if one exists.""" address = CommonRegisters.OPERATION_CTRL # Read register first. - reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_int() + reg_value = self.read_u8(address).payload_as_int() reg_value |= (1 << 5) reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) @@ -242,7 +212,7 @@ def disable_status_led(self): """disable the device's status led if one exists.""" address = CommonRegisters.OPERATION_CTRL # Read register first. - reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_int() + reg_value = self.read_u8(address).payload_as_int() reg_value &= ~(1 << 5) reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) @@ -250,7 +220,7 @@ def enable_alive_en(self): """Enable ALIVE_EN such that the device sends an event each second.""" address = CommonRegisters.OPERATION_CTRL # Read register first. - reg_value = self.send(HarpMessage.ReadU8(address).frame).payload_as_int() + reg_value = self.read_u8(address).payload_as_int() reg_value |= (1 << 7) reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) @@ -258,7 +228,7 @@ def disable_alive_en(self): """disable ALIVE_EN such that the device does not send an event each second.""" address = CommonRegisters.OPERATION_CTRL # Read register first. - reg_value = self.send(HarpMessage.ReadU8(address).frame).payload[0] + reg_value = self.read_u8(address).payload[0] reg_value &= ((1<< 7) ^ 0xFF) # bitwise ~ operator substitute for Python ints. reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) @@ -306,4 +276,39 @@ def get_events(self) -> list[ReplyHarpMessage]: def event_count(self) -> int: """Get the number of events in the event queue.""" - return self._ser.event_q.qsize() \ No newline at end of file + return self._ser.event_q.qsize() + + def read_u8(self, address: int, dump: bool = True) -> ReplyHarpMessage: + return self.send( + ReadHarpMessage(payload_type=PayloadType.U8, address=address).frame, dump + ) + + def read_s8(self, address: int, dump: bool = True) -> ReplyHarpMessage: + return self.send( + ReadHarpMessage(payload_type=PayloadType.S8, address=address).frame, dump + ) + + def read_u16(self, address: int, dump: bool = True) -> ReplyHarpMessage: + return self.send( + ReadHarpMessage(payload_type=PayloadType.U16, address=address).frame, dump + ) + + def read_s16(self, address: int, dump: bool = True) -> ReplyHarpMessage: + return self.send( + ReadHarpMessage(payload_type=PayloadType.S16, address=address).frame, dump + ) + + def read_u32(self, address: int, dump: bool = True) -> ReplyHarpMessage: + return self.send( + ReadHarpMessage(payload_type=PayloadType.U32, address=address).frame, dump + ) + + def read_s32(self, address: int, dump: bool = True) -> ReplyHarpMessage: + return self.send( + ReadHarpMessage(payload_type=PayloadType.S32, address=address).frame, dump + ) + + def read_float(self, address: int, dump: bool = True) -> ReplyHarpMessage: + return self.send( + ReadHarpMessage(payload_type=PayloadType.FLOAT, address=address).frame, dump + ) diff --git a/pyharp/drivers/behavior.py b/pyharp/drivers/behavior.py index 989f89a..c36dd07 100644 --- a/pyharp/drivers/behavior.py +++ b/pyharp/drivers/behavior.py @@ -1,12 +1,14 @@ """Behavior Device Driver.""" -from pyharp.messages import HarpMessage, ReplyHarpMessage -from pyharp.device_names import device_names -from pyharp.device import Device +from enum import Enum + import serial from serial.serialutil import SerialException -from enum import Enum +from pyharp.base import PayloadType +from pyharp.device import Device +from pyharp.device_names import device_names +from pyharp.messages import HarpMessage, ReadHarpMessage, ReplyHarpMessage # These definitions are from app_regs.h in the firmware. # Type, Base Address, "Description." @@ -84,11 +86,10 @@ class Behavior: ID = 1216 # TODO: put this in a base class? - READ_MSG_LOOKUP = \ - { - "U8": HarpMessage.ReadU8, - "U16": HarpMessage.ReadU16, - "S16": HarpMessage.ReadS16, + READ_MSG_LOOKUP = { + "U8": PayloadType.U8, + "U16": PayloadType.U16, + "S16": PayloadType.S16, } WRITE_MSG_LOOKUP = \ @@ -155,7 +156,9 @@ def all_input_states(self): """return the state of all PORT digital inputs.""" reg_type, reg_index, _ = REGISTERS["PORT_DIS"] read_message_type = self.__class__.READ_MSG_LOOKUP[reg_type] - return self.device.send(read_message_type(reg_index).frame).payload_as_int() + return self.device.send( + ReadHarpMessage(read_message_type, reg_index).frame + ).payload_as_int() @property def DI0(self): @@ -248,7 +251,9 @@ def all_output_states(self): """return the state of all PORT digital inputs.""" reg_type, reg_index, _ = REGISTERS["OUTPUTS_OUT"] read_message_type = self.__class__.READ_MSG_LOOKUP[reg_type] - return self.device.send(read_message_type(reg_index).frame).payload_as_int() + return self.device.send( + ReadHarpMessage(read_message_type, reg_index).frame + ).payload_as_int() @all_output_states.setter def all_output_states(self, bitmask : int): diff --git a/tests/test_device.py b/tests/test_device.py index cc5dff2..31306ae 100644 --- a/tests/test_device.py +++ b/tests/test_device.py @@ -23,8 +23,7 @@ def test_read_U8() -> None: register: int = 38 read_size: int = 35 # TODO: automatically calculate this! - read_message = HarpMessage.ReadU8(register) - reply: ReplyHarpMessage = device.send(read_message.frame) + reply: ReplyHarpMessage = device.read_u8(register) assert reply is not None # assert reply.payload_as_int() == write_value @@ -50,8 +49,7 @@ def test_U8() -> None: assert reply is not None # read register 38 - read_message = HarpMessage.ReadU8(register) - reply = device.send(read_message.frame) + reply = device.read_u8(register) assert reply is not None assert reply.payload_as_int() == write_value diff --git a/tests/test_messages.py b/tests/test_messages.py index a5e1ae3..13fc637 100644 --- a/tests/test_messages.py +++ b/tests/test_messages.py @@ -1,11 +1,11 @@ -from pyharp.base import CommonRegisters -from pyharp.messages import HarpMessage, MessageType +from pyharp.base import CommonRegisters, PayloadType +from pyharp.messages import HarpMessage, MessageType, ReadHarpMessage DEFAULT_ADDRESS = 42 def test_create_read_U8() -> None: - message = HarpMessage.ReadU8(DEFAULT_ADDRESS) + message = ReadHarpMessage(payload_type=PayloadType.U8, address=DEFAULT_ADDRESS) assert message.message_type == MessageType.READ assert message.checksum == 47 # 1 + 4 + 42 + 255 + 1 - 256 @@ -13,7 +13,7 @@ def test_create_read_U8() -> None: def test_create_read_S8() -> None: - message = HarpMessage.ReadS8(DEFAULT_ADDRESS) + message = ReadHarpMessage(payload_type=PayloadType.S8, address=DEFAULT_ADDRESS) assert message.message_type == MessageType.READ assert message.checksum == 175 # 1 + 4 + 42 + 255 + 129 - 256 @@ -21,7 +21,7 @@ def test_create_read_S8() -> None: def test_create_read_U16() -> None: - message = HarpMessage.ReadU16(DEFAULT_ADDRESS) + message = ReadHarpMessage(payload_type=PayloadType.U16, address=DEFAULT_ADDRESS) assert message.message_type == MessageType.READ assert message.checksum == 48 # 1 + 4 + 42 + 255 + 2 - 256 @@ -29,7 +29,7 @@ def test_create_read_U16() -> None: def test_create_read_S16() -> None: - message = HarpMessage.ReadS16(DEFAULT_ADDRESS) + message = ReadHarpMessage(payload_type=PayloadType.S16, address=DEFAULT_ADDRESS) assert message.message_type == MessageType.READ assert message.checksum == 176 # 1 + 4 + 42 + 255 + 130 - 256 @@ -79,6 +79,8 @@ def test_create_write_S16() -> None: def test_read_who_am_i() -> None: - message = HarpMessage.ReadU16(CommonRegisters.WHO_AM_I) + message = ReadHarpMessage( + payload_type=PayloadType.U16, address=CommonRegisters.WHO_AM_I + ) assert str(message.frame) == str(bytearray(b"\x01\x04\x00\xff\x02\x06")) From bcfcf3c4cf7241a91f33d0f2de5a602b89679925 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Tue, 25 Mar 2025 11:31:08 +0000 Subject: [PATCH 049/159] Remove ReadXX methods from HarpMessage --- pyharp/messages.py | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/pyharp/messages.py b/pyharp/messages.py index d8f683b..d1ba6c6 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -51,34 +51,6 @@ def payload_type(self) -> PayloadType: def checksum(self) -> int: return self._frame[-1] - @staticmethod - def ReadU8(address: int) -> ReadHarpMessage: - return ReadHarpMessage(payload_type=PayloadType.U8, address=address) - - @staticmethod - def ReadS8(address: int) -> ReadHarpMessage: - return ReadHarpMessage(payload_type=PayloadType.S8, address=address) - - @staticmethod - def ReadS16(address: int) -> ReadHarpMessage: - return ReadHarpMessage(payload_type=PayloadType.S16, address=address) - - @staticmethod - def ReadU16(address: int) -> ReadHarpMessage: - return ReadHarpMessage(payload_type=PayloadType.U16, address=address) - - @staticmethod - def ReadU32(address: int) -> ReadHarpMessage: - return ReadHarpMessage(payload_type=PayloadType.U32, address=address) - - @staticmethod - def ReadS32(address: int) -> ReadHarpMessage: - return ReadHarpMessage(payload_type=PayloadType.S32, address=address) - - @staticmethod - def ReadFloat(address: int) -> ReadHarpMessage: - return ReadHarpMessage(payload_type=PayloadType.Float, address=address) - @staticmethod def WriteU8(address: int, value: int) -> WriteU8HarpMessage: return WriteU8HarpMessage(address, value) From f4745038e41db3743dab5ea73be024fa21f2e267 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Tue, 25 Mar 2025 16:06:52 +0000 Subject: [PATCH 050/159] Extract unrelated Enum members --- pyharp/base.py | 48 +++++++++++++++++++++++----------------------- pyharp/messages.py | 2 +- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/pyharp/base.py b/pyharp/base.py index 70daabd..271a903 100644 --- a/pyharp/base.py +++ b/pyharp/base.py @@ -1,5 +1,10 @@ from enum import Enum +# TODO: Find a way to really hide this from the user +_isUnsigned: int = 0x00 +_isSigned: int = 0x80 +_isFloat: int = 0x40 +_hasTimestamp: int = 0x10 class MessageType(Enum): READ: int = 1 @@ -10,30 +15,25 @@ class MessageType(Enum): class PayloadType(Enum): - isUnsigned: int = 0x00 - isSigned: int = 0x80 - isFloat: int = 0x40 - hasTimestamp: int = 0x10 - - U8 = isUnsigned | 1 # 1 - S8 = isSigned | 1 # 129 - U16 = isUnsigned | 2 # 2 - S16 = isSigned | 2 # 130 - U32 = isUnsigned | 4 - S32 = isSigned | 4 - U64 = isUnsigned | 8 - S64 = isSigned | 8 - Float = isFloat | 4 - Timestamp = hasTimestamp - TimestampedU8 = hasTimestamp | U8 - TimestampedS8 = hasTimestamp | S8 - TimestampedU16 = hasTimestamp | U16 - TimestampedS16 = hasTimestamp | S16 - TimestampedU32 = hasTimestamp | U32 - TimestampedS32 = hasTimestamp | S32 - TimestampedU64 = hasTimestamp | U64 - TimestampedS64 = hasTimestamp | S64 - TimestampedFloat = hasTimestamp | Float + U8 = _isUnsigned | 1 # 1 + S8 = _isSigned | 1 # 129 + U16 = _isUnsigned | 2 # 2 + S16 = _isSigned | 2 # 130 + U32 = _isUnsigned | 4 + S32 = _isSigned | 4 + U64 = _isUnsigned | 8 + S64 = _isSigned | 8 + Float = _isFloat | 4 + Timestamp = _hasTimestamp + TimestampedU8 = _hasTimestamp | U8 + TimestampedS8 = _hasTimestamp | S8 + TimestampedU16 = _hasTimestamp | U16 + TimestampedS16 = _hasTimestamp | S16 + TimestampedU32 = _hasTimestamp | U32 + TimestampedS32 = _hasTimestamp | S32 + TimestampedU64 = _hasTimestamp | U64 + TimestampedS64 = _hasTimestamp | S64 + TimestampedFloat = _hasTimestamp | Float class CommonRegisters: diff --git a/pyharp/messages.py b/pyharp/messages.py index d1ba6c6..30aac30 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -108,7 +108,7 @@ def __init__( + int.from_bytes(frame[9:11], byteorder="little", signed=False) * 32e-6 ) # Timestamp is junk if it's not present. - if not (self.payload_type.value & PayloadType.hasTimestamp.value): + if not (self.payload_type.value & PayloadType.Timestamp.value): self._timestamp = None def _parse_payload(self, raw_payload) -> list[int]: From ab0a7da1a5c13d9f530bd62b05f6a35f0cf8f0ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Tue, 25 Mar 2025 16:08:45 +0000 Subject: [PATCH 051/159] Add read_S64 and read_U64 methods to Device --- pyharp/device.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pyharp/device.py b/pyharp/device.py index f782086..8711fdc 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -308,6 +308,16 @@ def read_s32(self, address: int, dump: bool = True) -> ReplyHarpMessage: ReadHarpMessage(payload_type=PayloadType.S32, address=address).frame, dump ) + def read_u64(self, address: int, dump: bool = True) -> ReplyHarpMessage: + return self.send( + ReadHarpMessage(payload_type=PayloadType.U64, address=address).frame, dump + ) + + def read_s64(self, address: int, dump: bool = True) -> ReplyHarpMessage: + return self.send( + ReadHarpMessage(payload_type=PayloadType.S64, address=address).frame, dump + ) + def read_float(self, address: int, dump: bool = True) -> ReplyHarpMessage: return self.send( ReadHarpMessage(payload_type=PayloadType.FLOAT, address=address).frame, dump From 0859612c113e436d6e25da3d7f5038666dde4ff4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Tue, 25 Mar 2025 16:12:56 +0000 Subject: [PATCH 052/159] Create write_XXX methods on Device --- pyharp/device.py | 104 ++++++++++++++++-- pyharp/drivers/behavior.py | 113 +++++++++---------- pyharp/messages.py | 219 +++++++++++++++---------------------- tests/test_device.py | 9 +- tests/test_messages.py | 18 +-- 5 files changed, 242 insertions(+), 221 deletions(-) diff --git a/pyharp/device.py b/pyharp/device.py index 8711fdc..f4f160a 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -4,14 +4,14 @@ import queue from enum import Enum from pathlib import Path -from typing import Optional, Union +from typing import List, Optional, Union import serial from pyharp.base import CommonRegisters, PayloadType from pyharp.device_names import device_names from pyharp.harp_serial import HarpSerial -from pyharp.messages import HarpMessage, ReadHarpMessage, ReplyHarpMessage +from pyharp.messages import ReadHarpMessage, ReplyHarpMessage, WriteHarpMessage class DeviceMode(Enum): @@ -179,7 +179,7 @@ def dump_registers(self) -> list: address = CommonRegisters.OPERATION_CTRL reg_value = self.read_u8(address).payload_as_int() reg_value |= 0x08 # Assert DUMP bit - self._ser.write(HarpMessage.WriteU8(address, reg_value).frame) + self._ser.write(WriteHarpMessage(PayloadType.U8, address, reg_value).frame) replies = [] while True: msg = self._read() @@ -197,7 +197,7 @@ def set_mode(self, mode: DeviceMode) -> ReplyHarpMessage: reg_value = self.read_u8(address).payload_as_int() reg_value &= ~0x03 # mask off old mode. reg_value |= mode.value - reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) + reply = self.send(WriteHarpMessage(PayloadType.U8, address, reg_value).frame) return reply def enable_status_led(self): @@ -206,7 +206,7 @@ def enable_status_led(self): # Read register first. reg_value = self.read_u8(address).payload_as_int() reg_value |= (1 << 5) - reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) + reply = self.send(WriteHarpMessage(PayloadType.U8, address, reg_value).frame) def disable_status_led(self): """disable the device's status led if one exists.""" @@ -214,7 +214,7 @@ def disable_status_led(self): # Read register first. reg_value = self.read_u8(address).payload_as_int() reg_value &= ~(1 << 5) - reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) + reply = self.send(WriteHarpMessage(PayloadType.U8, address, reg_value).frame) def enable_alive_en(self): """Enable ALIVE_EN such that the device sends an event each second.""" @@ -222,22 +222,21 @@ def enable_alive_en(self): # Read register first. reg_value = self.read_u8(address).payload_as_int() reg_value |= (1 << 7) - reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) + reply = self.send(WriteHarpMessage(PayloadType.U8, address, reg_value).frame) def disable_alive_en(self): """disable ALIVE_EN such that the device does not send an event each second.""" address = CommonRegisters.OPERATION_CTRL # Read register first. reg_value = self.read_u8(address).payload[0] - reg_value &= ((1<< 7) ^ 0xFF) # bitwise ~ operator substitute for Python ints. - reply = self.send(HarpMessage.WriteU8(address, reg_value).frame) + reg_value &= (1 << 7) ^ 0xFF # bitwise ~ operator substitute for Python ints. + reply = self.send(WriteHarpMessage(PayloadType.U8, address, reg_value).frame) def reset_device(self): address = CommonRegisters.RESET_DEV # reset_value = 0xFF & (1< ReplyHarpMessage: """Send a harp message; return the device's reply.""" @@ -320,5 +319,86 @@ def read_s64(self, address: int, dump: bool = True) -> ReplyHarpMessage: def read_float(self, address: int, dump: bool = True) -> ReplyHarpMessage: return self.send( - ReadHarpMessage(payload_type=PayloadType.FLOAT, address=address).frame, dump + ReadHarpMessage(payload_type=PayloadType.Float, address=address).frame, dump ) + + def write_u8(self, address: int, value: int | List[int]) -> ReplyHarpMessage: + return self.send( + WriteHarpMessage( + payload_type=PayloadType.U8, + address=address, + value=value, + ).frame + ) + + def write_s8(self, address: int, value: int | List[int]) -> ReplyHarpMessage: + return self.send( + WriteHarpMessage( + payload_type=PayloadType.S8, + address=address, + value=value, + ).frame + ) + + def write_u16(self, address: int, value: int | List[int]) -> ReplyHarpMessage: + return self.send( + WriteHarpMessage( + payload_type=PayloadType.U16, + address=address, + value=value, + ).frame + ) + + def write_s16(self, address: int, value: int | List[int]) -> ReplyHarpMessage: + return self.send( + WriteHarpMessage( + payload_type=PayloadType.S16, + address=address, + value=value, + ).frame + ) + + def write_u32(self, address: int, value: int | List[int]) -> ReplyHarpMessage: + return self.send( + WriteHarpMessage( + payload_type=PayloadType.U32, + address=address, + value=value, + ).frame + ) + + def write_s32(self, address: int, value: int | List[int]) -> ReplyHarpMessage: + return self.send( + WriteHarpMessage( + payload_type=PayloadType.S32, + address=address, + value=value, + ).frame + ) + + def write_u64(self, address: int, value: int | List[int]) -> ReplyHarpMessage: + return self.send( + WriteHarpMessage( + payload_type=PayloadType.U64, + address=address, + value=value, + ).frame + ) + + def write_s64(self, address: int, value: int | List[int]) -> ReplyHarpMessage: + return self.send( + WriteHarpMessage( + payload_type=PayloadType.S64, + address=address, + value=value, + ).frame + ) + + def write_float(self, address: int, value: float | List[float]) -> ReplyHarpMessage: + return self.send( + WriteHarpMessage( + payload_type=PayloadType.Float, + address=address, + value=value, + ).frame + ) \ No newline at end of file diff --git a/pyharp/drivers/behavior.py b/pyharp/drivers/behavior.py index c36dd07..2f4b333 100644 --- a/pyharp/drivers/behavior.py +++ b/pyharp/drivers/behavior.py @@ -2,37 +2,38 @@ from enum import Enum -import serial from serial.serialutil import SerialException from pyharp.base import PayloadType from pyharp.device import Device -from pyharp.device_names import device_names -from pyharp.messages import HarpMessage, ReadHarpMessage, ReplyHarpMessage +from pyharp.messages import ReadHarpMessage, ReplyHarpMessage, WriteHarpMessage # These definitions are from app_regs.h in the firmware. # Type, Base Address, "Description." -REGISTERS = \ -{ # RJ45 "PORT" (0, 1, 2) Digital Inputs - "PORT_DIS" : ("U8", 32, "Reflects the state of DI digital lines of each Port."), - +REGISTERS = { # RJ45 "PORT" (0, 1, 2) Digital Inputs + "PORT_DIS": ( + PayloadType.U8, + 32, + "Reflects the state of DI digital lines of each Port.", + ), # Manipulate any of the board's digital outputs. - "OUTPUTS_SET": ("U16", 34, "Set the corresponding output."), - "OUTPUTS_CLR": ("U16", 35, "Clear the corresponding output."), - "OUTPUTS_TOGGLE": ("U16", 36, "Toggle the corresponding output."), - "OUTPUTS_OUT": ("U16", 37, "Control corresponding output."), - + "OUTPUTS_SET": (PayloadType.U16, 34, "Set the corresponding output."), + "OUTPUTS_CLR": (PayloadType.U16, 35, "Clear the corresponding output."), + "OUTPUTS_TOGGLE": (PayloadType.U16, 36, "Toggle the corresponding output."), + "OUTPUTS_OUT": (PayloadType.U16, 37, "Control corresponding output."), # RJ45 "PORT" (0, 1, 2) Digital IOs - "PORT_DIOS_SET": ("U8", 38, "Set the corresponding DIO."), - "PORT_DIOS_CLEAR": ("U8", 39, "Clear the corresponding DIO."), - "PORT_DIOS_TOGGLE": ("U8", 40, "Toggle the corresponding DIO."), - "PORT_DIOS_OUT": ("U8", 41, "Control the corresponding DIO."), - "PORT_DIOS_CONF": ("U8", 42, "Set the DIOs direction (1 is output)."), - "PORT_DIOS_IN": ("U8", 43, "State of the DIOs."), - - "ADD_REG_DATA": ("S16", 44, "Voltage at ADC input and decoder (poke2) value."), - - "EVNT_ENABLE": ("U8", 77, "Enable events within the bitfields."), + "PORT_DIOS_SET": (PayloadType.U8, 38, "Set the corresponding DIO."), + "PORT_DIOS_CLEAR": (PayloadType.U8, 39, "Clear the corresponding DIO."), + "PORT_DIOS_TOGGLE": (PayloadType.U8, 40, "Toggle the corresponding DIO."), + "PORT_DIOS_OUT": (PayloadType.U8, 41, "Control the corresponding DIO."), + "PORT_DIOS_CONF": (PayloadType.U8, 42, "Set the DIOs direction (1 is output)."), + "PORT_DIOS_IN": (PayloadType.U8, 43, "State of the DIOs."), + "ADD_REG_DATA": ( + PayloadType.S16, + 44, + "Voltage at ADC input and decoder (poke2) value.", + ), + "EVNT_ENABLE": (PayloadType.U8, 77, "Enable events within the bitfields."), } @@ -42,6 +43,7 @@ class PORT_DIS(Enum): DI1 = 1 DI2 = 2 + class OUTPUTS_OUT(Enum): PORT0_DO = 0 PORT0_D1 = 1 @@ -61,6 +63,7 @@ class OUTPUTS_OUT(Enum): DO2 = 12 DO3 = 13 + class PORT_DIOS_IN(Enum): DIO0 = 0 DIO1 = 0 @@ -69,11 +72,11 @@ class PORT_DIOS_IN(Enum): # reader-friendly events for enabling/disabling. class Events(Enum): - port_digital_inputs = 0 # PORT_DIS - port_digital_ios = 1 # PORT_DIOS_IN - analog_input = 2 # DATA - cam0 = 3 # CAM0 - cam1 = 3 # CAM1 + port_digital_inputs = 0 # PORT_DIS + port_digital_ios = 1 # PORT_DIOS_IN + analog_input = 2 # DATA + cam0 = 3 # CAM0 + cam1 = 3 # CAM1 class Behavior: @@ -85,21 +88,6 @@ class Behavior: DEFAULT_PORT_NAME = "/dev/harp_device_00" ID = 1216 - # TODO: put this in a base class? - READ_MSG_LOOKUP = { - "U8": PayloadType.U8, - "U16": PayloadType.U16, - "S16": PayloadType.S16, - } - - WRITE_MSG_LOOKUP = \ - { - "U8": HarpMessage.WriteU8, - "U16": HarpMessage.WriteU16, - "S16": HarpMessage.WriteS16, - } - - def __init__(self, port_name=None, output_filename=None): """Class constructor. Connect to a device.""" @@ -135,8 +123,9 @@ def disable_all_events(self) -> ReplyHarpMessage: (1 << Events.cam0.value) | \ (1 << Events.cam1.value) ) ^ 0xFF) reg_type, reg_index, _ = REGISTERS["EVNT_ENABLE"] - write_message_type = self.__class__.WRITE_MSG_LOOKUP[reg_type] - return self.device.send(write_message_type(reg_index, event_reg_bitmask).frame) + return self.device.send( + WriteHarpMessage(reg_type, reg_index, event_reg_bitmask).frame + ) def enable_events(self, *events: Events) -> ReplyHarpMessage: @@ -145,8 +134,9 @@ def enable_events(self, *events: Events) -> ReplyHarpMessage: for event in events: event_reg_bitmask |= (1 << event.value) reg_type, reg_index, _ = REGISTERS["EVNT_ENABLE"] - write_message_type = self.__class__.WRITE_MSG_LOOKUP[reg_type] - return self.device.send(write_message_type(reg_index, event_reg_bitmask).frame) + return self.device.send( + WriteHarpMessage(reg_type, reg_index, event_reg_bitmask).frame + ) # Board inputs, outputs, and some settings configured as @properties. @@ -155,9 +145,8 @@ def enable_events(self, *events: Events) -> ReplyHarpMessage: def all_input_states(self): """return the state of all PORT digital inputs.""" reg_type, reg_index, _ = REGISTERS["PORT_DIS"] - read_message_type = self.__class__.READ_MSG_LOOKUP[reg_type] return self.device.send( - ReadHarpMessage(read_message_type, reg_index).frame + ReadHarpMessage(reg_type, reg_index).frame ).payload_as_int() @property @@ -183,8 +172,8 @@ def DI2(self): # def set_io_configuration(self, bitmask : int): # """set the state of all PORT digital ios. (1 is output.)""" # reg_type, reg_index, _ = REGISTERS["PORT_DIOS_CONF"] -# write_message_type = self.__class__.WRITE_MSG_LOOKUP[reg_type] -# self.device.send(write_message_type(reg_index, bitmask).frame) + + # self.device.send(WriteHarpMessage(reg_type, reg_index, bitmask).frame) # # @property # def all_io_states(self): @@ -199,20 +188,20 @@ def DI2(self): # # Setting the state of the "DIO" pins, requires writing to the # # _IN register, which is different from the OUTPUT # reg_type, reg_index, _ = REGISTERS["PORT_DIOS_IN"] -# write_message_type = self.__class__.WRITE_MSG_LOOKUP[reg_type] -# return self.device.send(write_message_type(reg_index, bitmask).frame) + + # return self.device.send(WriteHarpMessage(reg_type, reg_index, bitmask).frame) # # def set_io_outputs(self, bitmask : int): # """set digital input/outputs to logic 1 according to bitmask.""" # reg_type, reg_index, _ = REGISTERS["PORT_DIOS_SET"] -# write_message_type = self.__class__.WRITE_MSG_LOOKUP[reg_type] -# return self.device.send(write_message_type(reg_index, bitmask).frame) + + # return self.device.send(WriteHarpMessage(reg_type, reg_index, bitmask).frame) # # def clear_io_outputs(self, bitmask : int): # """clear digital input/outputs (specified with logic 1) according to bitmask.""" # reg_type, reg_index, _ = REGISTERS["PORT_DIOS_CLEAR"] -# write_message_type = self.__class__.WRITE_MSG_LOOKUP[reg_type] -# return self.device.send(write_message_type(reg_index, bitmask).frame) + + # return self.device.send(WriteHarpMessage(reg_type, reg_index, bitmask).frame) # # @property # def port0_io0(self): @@ -250,29 +239,25 @@ def DI2(self): def all_output_states(self): """return the state of all PORT digital inputs.""" reg_type, reg_index, _ = REGISTERS["OUTPUTS_OUT"] - read_message_type = self.__class__.READ_MSG_LOOKUP[reg_type] return self.device.send( - ReadHarpMessage(read_message_type, reg_index).frame + ReadHarpMessage(reg_type, reg_index).frame ).payload_as_int() @all_output_states.setter def all_output_states(self, bitmask : int): """set the state of all PORT digital inputs.""" reg_type, reg_index, _ = REGISTERS["OUTPUTS_OUT"] - write_message_type = self.__class__.WRITE_MSG_LOOKUP[reg_type] - return self.device.send(write_message_type(reg_index, bitmask).frame) + return self.device.send(WriteHarpMessage(reg_type, reg_index, bitmask).frame) def set_outputs(self, bitmask : int): """set digital outputs to logic 1 according to bitmask.""" reg_type, reg_index, _ = REGISTERS["OUTPUTS_SET"] - write_message_type = self.__class__.WRITE_MSG_LOOKUP[reg_type] - return self.device.send(write_message_type(reg_index, bitmask).frame) + return self.device.send(WriteHarpMessage(reg_type, reg_index, bitmask).frame) def clear_outputs(self, bitmask : int): """clear digital outputs (specified with logic 1) according to bitmask.""" reg_type, reg_index, _ = REGISTERS["OUTPUTS_CLR"] - write_message_type = self.__class__.WRITE_MSG_LOOKUP[reg_type] - return self.device.send(write_message_type(reg_index, bitmask).frame) + return self.device.send(WriteHarpMessage(reg_type, reg_index, bitmask).frame) @property def D0(self): diff --git a/pyharp/messages.py b/pyharp/messages.py index 30aac30..13b7d01 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -51,34 +51,6 @@ def payload_type(self) -> PayloadType: def checksum(self) -> int: return self._frame[-1] - @staticmethod - def WriteU8(address: int, value: int) -> WriteU8HarpMessage: - return WriteU8HarpMessage(address, value) - - @staticmethod - def WriteS8(address: int, value: int) -> WriteS8HarpMessage: - return WriteS8HarpMessage(address, value) - - @staticmethod - def WriteS16(address: int, value: int) -> WriteS16HarpMessage: - return WriteS16HarpMessage(address, value) - - @staticmethod - def WriteU16(address: int, value: int) -> WriteU16HarpMessage: - return WriteU16HarpMessage(address, value) - - @staticmethod - def WriteFloat(address: int, value: int) -> WriteFloatHarpMessage: - return WriteFloatHarpMessage(address, value) - - @staticmethod - def WriteU32(address: int, value: int) -> WriteU32HarpMessage: - return WriteU32HarpMessage(address, value) - - @staticmethod - def WriteS32(address: int, value: int) -> WriteS32HarpMessage: - return WriteS32HarpMessage(address, value) - @staticmethod def parse(frame: bytearray) -> ReplyHarpMessage: return ReplyHarpMessage(frame) @@ -86,6 +58,10 @@ def parse(frame: bytearray) -> ReplyHarpMessage: # A Response Message from a harp device. class ReplyHarpMessage(HarpMessage): + """ + A Response Message from a harp device. + """ + def __init__( self, frame: bytearray, @@ -197,122 +173,103 @@ def __init__(self, payload_type: PayloadType, address: int): class WriteHarpMessage(HarpMessage): BASE_LENGTH: int = 5 + BASE_LENGTH: int = 4 MESSAGE_TYPE: int = MessageType.WRITE + # Define payload type properties + _PAYLOAD_CONFIG = { + # payload_type: (byte_size, signed, is_float) + PayloadType.U8: (1, False, False), + PayloadType.S8: (1, True, False), + PayloadType.U16: (2, False, False), + PayloadType.S16: (2, True, False), + PayloadType.U32: (4, False, False), + PayloadType.S32: (4, True, False), + PayloadType.U64: (8, False, False), + PayloadType.S64: (8, True, False), + PayloadType.Float: (4, False, True), + } + def __init__( - self, payload_type: PayloadType, payload: bytes, address: int, offset: int = 0 + self, + payload_type: PayloadType, + address: int, + value: int | float | List[int] | List[float] = None, ): """ - - :param payload_type: - :param payload: - :param address: - :param offset: how many bytes more besides the length corresponding to U8 (for example, for U16 it would be offset=1) + Create a WriteHarpMessage to send to a device. + + Parameters + ---------- + payload_type : PayloadType + Type of payload (U8, S8, U16, etc.) + address : int + Register address to write to + value : int, float, List[int], or List[float], optional + Value(s) to write - can be a single value or list of values + + Notes + ----- + The message frame is constructed according to the HARP binary protocol. + The length is calculated as BASE_LENGTH + payload size in bytes. """ + self._frame = bytearray() - self._frame.append(self.MESSAGE_TYPE.value) + # Get configuration for this payload type + byte_size, signed, is_float = self._PAYLOAD_CONFIG.get( + payload_type, (1, False, False) + ) + + # Convert value to payload bytes + payload = bytearray() + values = value if isinstance(value, list) else [value] - self._frame.append(self.BASE_LENGTH + offset) + for val in values: + if is_float: + payload += struct.pack(" int: - return self.frame[5] - - -class WriteS8HarpMessage(WriteHarpMessage): - def __init__(self, address: int, value: int): - super().__init__( - PayloadType.S8, value.to_bytes(1, byteorder="little", signed=True), address - ) - - @property - def payload(self) -> int: - return int.from_bytes([self.frame[5]], byteorder="little", signed=True) - - -class WriteU16HarpMessage(WriteHarpMessage): - def __init__(self, address: int, value: int): - super().__init__( - PayloadType.U16, value.to_bytes(2, byteorder="little", signed=False), address, offset=1 - ) - - @property - def payload(self) -> int: - return int.from_bytes(self._frame[5:7], byteorder="little", signed=False) - - -class WriteS16HarpMessage(WriteHarpMessage): - def __init__(self, address: int, value: int): - super().__init__( - PayloadType.S16, - value.to_bytes(2, byteorder="little", signed=True), - address, - offset=1, - ) - @property - def payload(self) -> int: - return int.from_bytes(self._frame[5:7], byteorder="little", signed=True) - - -class WriteFloatHarpMessage(WriteHarpMessage): - def __init__(self, address: int, value: float): - super().__init__( - PayloadType.Float, - struct.pack(' float: - return struct.unpack(' int: - return int.from_bytes(self._frame[5:9], byteorder="little", signed=False) - - -class WriteS32HarpMessage(WriteHarpMessage): - def __init__(self, address: int, value: int | List[int]): - if isinstance(value, list): - payload = bytearray() - for val in value: - payload += val.to_bytes(4, byteorder="little", signed=True) - offset = 15 - else: - payload = value.to_bytes(4, byteorder="little", signed=True) - offset = 3 - super().__init__( - PayloadType.S32, payload, address, offset=offset - ) - - @property - def payload(self) -> int | List[int]: - return int.from_bytes(self._frame[5:9], byteorder="little", signed=True) + def payload(self) -> Union[int, list[int]]: + match self.payload_type: + case PayloadType.U8: + return self.frame[5] + case PayloadType.S8: + return int.from_bytes([self.frame[5]], byteorder="little", signed=True) + case PayloadType.U16: + return int.from_bytes( + self._frame[5:7], byteorder="little", signed=False + ) + case PayloadType.S16: + return int.from_bytes(self._frame[5:7], byteorder="little", signed=True) + case PayloadType.Float: + return struct.unpack(" None: # open serial connection and load info - device = Device("/dev/tty.usbserial-A106C8O9") + device = Device("/dev/ttyUSB0", "dump.bin") assert device._ser.is_open device.info() device.disconnect() @@ -17,7 +17,7 @@ def test_create_device() -> None: def test_read_U8() -> None: # open serial connection and load info - device = Device("/dev/tty.usbserial-A106C8O9", "dump.bin") + device = Device("/dev/ttyUSB0", "dump.bin") # read register 38 register: int = 38 @@ -34,7 +34,7 @@ def test_read_U8() -> None: def test_U8() -> None: # open serial connection and load info - device = Device("/dev/tty.usbserial-A106C8O9", "dump.txt") + device = Device("/dev/ttyUSB0", "dump.bin") assert device._dump_file_path.exists() register: int = 38 @@ -44,8 +44,7 @@ def test_U8() -> None: # assert reply[11] == 0 # what is the default register value?! # write 65 on register 38 - write_message = HarpMessage.WriteU8(register, write_value) - reply : ReplyHarpMessage = device.send(write_message.frame) + reply: ReplyHarpMessage = device.write_u8(register, write_value) assert reply is not None # read register 38 diff --git a/tests/test_messages.py b/tests/test_messages.py index 13fc637..93223b4 100644 --- a/tests/test_messages.py +++ b/tests/test_messages.py @@ -1,5 +1,5 @@ from pyharp.base import CommonRegisters, PayloadType -from pyharp.messages import HarpMessage, MessageType, ReadHarpMessage +from pyharp.messages import MessageType, ReadHarpMessage, WriteHarpMessage DEFAULT_ADDRESS = 42 @@ -38,9 +38,9 @@ def test_create_read_S16() -> None: def test_create_write_U8() -> None: value: int = 23 - message = HarpMessage.WriteU8(DEFAULT_ADDRESS, value) + message = WriteHarpMessage(PayloadType.U8, DEFAULT_ADDRESS, value) - assert message.message_type == MessageType.Write + assert message.message_type == MessageType.WRITE assert message.payload == value assert message.checksum == 72 # 2 + 5 + 42 + 255 + 1 + 23 - 256 print(message.frame) @@ -48,9 +48,9 @@ def test_create_write_U8() -> None: def test_create_write_S8() -> None: value: int = -3 # corresponds to signed int 253 (0xFD) - message = HarpMessage.WriteS8(DEFAULT_ADDRESS, value) + message = WriteHarpMessage(PayloadType.S8, DEFAULT_ADDRESS, value) - assert message.message_type == MessageType.Write + assert message.message_type == MessageType.WRITE assert message.payload == value assert message.checksum == 174 # (2 + 5 + 42 + 255 + 129 + 253) & 255 print(message.frame) @@ -58,9 +58,9 @@ def test_create_write_S8() -> None: def test_create_write_U16() -> None: value: int = 1024 # 4 0 (2 x bytes) - message = HarpMessage.WriteU16(DEFAULT_ADDRESS, value) + message = WriteHarpMessage(PayloadType.U16, DEFAULT_ADDRESS, value) - assert message.message_type == MessageType.Write + assert message.message_type == MessageType.WRITE assert message.length == 6 assert message.payload == value assert message.checksum == 55 # (2 + 6 + 42 + 255 + 2 + 4 + 0) & 255 @@ -69,9 +69,9 @@ def test_create_write_U16() -> None: def test_create_write_S16() -> None: value: int = -4837 # 27 237 (2 x bytes), corresponds to signed int 7149 - message = HarpMessage.WriteS16(DEFAULT_ADDRESS, value) + message = WriteHarpMessage(PayloadType.S16, DEFAULT_ADDRESS, value) - assert message.message_type == MessageType.Write + assert message.message_type == MessageType.WRITE assert message.length == 6 assert message.payload == value assert message.checksum == 187 # (2 + 6 + 42 + 255 + 130 + 27 + 237) & 255 From 472e1a5362b3ce1298957311d99854804d8d93b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 2 Apr 2025 09:30:58 +0100 Subject: [PATCH 053/159] Add dump parameter to Device's write methods --- pyharp/device.py | 65 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 46 insertions(+), 19 deletions(-) diff --git a/pyharp/device.py b/pyharp/device.py index f4f160a..b022114 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -322,83 +322,110 @@ def read_float(self, address: int, dump: bool = True) -> ReplyHarpMessage: ReadHarpMessage(payload_type=PayloadType.Float, address=address).frame, dump ) - def write_u8(self, address: int, value: int | List[int]) -> ReplyHarpMessage: + def write_u8( + self, address: int, value: int | List[int], dump: bool = True + ) -> ReplyHarpMessage: return self.send( WriteHarpMessage( payload_type=PayloadType.U8, address=address, value=value, - ).frame + ).frame, + dump=dump, ) - def write_s8(self, address: int, value: int | List[int]) -> ReplyHarpMessage: + def write_s8( + self, address: int, value: int | List[int], dump: bool = True + ) -> ReplyHarpMessage: return self.send( WriteHarpMessage( payload_type=PayloadType.S8, address=address, value=value, - ).frame + ).frame, + dump=dump, ) - def write_u16(self, address: int, value: int | List[int]) -> ReplyHarpMessage: + def write_u16( + self, address: int, value: int | List[int], dump: bool = True + ) -> ReplyHarpMessage: return self.send( WriteHarpMessage( payload_type=PayloadType.U16, address=address, value=value, - ).frame + ).frame, + dump=dump, ) - def write_s16(self, address: int, value: int | List[int]) -> ReplyHarpMessage: + def write_s16( + self, address: int, value: int | List[int], dump: bool = True + ) -> ReplyHarpMessage: return self.send( WriteHarpMessage( payload_type=PayloadType.S16, address=address, value=value, - ).frame + ).frame, + dump=dump, ) - def write_u32(self, address: int, value: int | List[int]) -> ReplyHarpMessage: + def write_u32( + self, address: int, value: int | List[int], dump: bool = True + ) -> ReplyHarpMessage: return self.send( WriteHarpMessage( payload_type=PayloadType.U32, address=address, value=value, - ).frame + ).frame, + dump=dump, ) - def write_s32(self, address: int, value: int | List[int]) -> ReplyHarpMessage: + def write_s32( + self, address: int, value: int | List[int], dump: bool = True + ) -> ReplyHarpMessage: return self.send( WriteHarpMessage( payload_type=PayloadType.S32, address=address, value=value, - ).frame + ).frame, + dump=dump, ) - def write_u64(self, address: int, value: int | List[int]) -> ReplyHarpMessage: + def write_u64( + self, address: int, value: int | List[int], dump: bool = True + ) -> ReplyHarpMessage: return self.send( WriteHarpMessage( payload_type=PayloadType.U64, address=address, value=value, - ).frame + ).frame, + dump=dump, ) - def write_s64(self, address: int, value: int | List[int]) -> ReplyHarpMessage: + def write_s64( + self, address: int, value: int | List[int], dump: bool = True + ) -> ReplyHarpMessage: return self.send( WriteHarpMessage( payload_type=PayloadType.S64, address=address, value=value, - ).frame + ).frame, + dump=dump, ) - def write_float(self, address: int, value: float | List[float]) -> ReplyHarpMessage: + def write_float( + self, address: int, value: float | List[float], dump: bool = True + ) -> ReplyHarpMessage: return self.send( WriteHarpMessage( payload_type=PayloadType.Float, address=address, value=value, - ).frame - ) \ No newline at end of file + ).frame, + dump=dump, + ) From 904040dd710749e5a1be47da258fe2e4be444e77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 2 Apr 2025 10:49:20 +0100 Subject: [PATCH 054/159] Change default name and changed visibility of control registers methods --- examples/check_device_id.py | 4 +- examples/get_info.py | 2 +- pyharp/device.py | 160 ++++++++++++++++++------------------ 3 files changed, 81 insertions(+), 85 deletions(-) diff --git a/examples/check_device_id.py b/examples/check_device_id.py index 7c6bd57..7a64c52 100755 --- a/examples/check_device_id.py +++ b/examples/check_device_id.py @@ -18,14 +18,14 @@ # Get some of the device's parameters device_id = device.WHO_AM_I # Get device's ID -device_id_description = device.WHO_AM_I_DEVICE # Get device's user name +device_id_description = device.DEFAULT_DEVICE_NAME # Get device's user name device_user_name = device.DEVICE_NAME # Get device's user name # Check if we are dealing with the correct device if device_id in device_names: print("Correct device was found!") print(f"Device's ID: {device_id}") - print(f"Device's name: {device_id_description}") + print(f"Device's default name: {device_id_description}") print(f"Device's user name: {device_user_name}") else: print("Device not correct or is not a Harp device!") diff --git a/examples/get_info.py b/examples/get_info.py index 8a33174..113e548 100755 --- a/examples/get_info.py +++ b/examples/get_info.py @@ -20,7 +20,7 @@ # Get some of the device's parameters device_id = device.WHO_AM_I # Get device's ID -device_id_description = device.WHO_AM_I_DEVICE # Get device's user name +device_id_description = device.DEFAULT_DEVICE_NAME # Get device's user name device_user_name = device.DEVICE_NAME # Get device's user name # Get versions diff --git a/pyharp/device.py b/pyharp/device.py index b022114..1250295 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -30,7 +30,7 @@ class Device: _dump_file_path: Path WHO_AM_I: int - WHO_AM_I_DEVICE: str + DEFAULT_DEVICE_NAME: str HW_VERSION_H: int HW_VERSION_L: int ASSEMBLY_VERSION: int @@ -59,20 +59,20 @@ def __init__( self.load() def load(self) -> None: - self.WHO_AM_I = self.read_who_am_i() - self.WHO_AM_I_DEVICE = self.read_who_am_i_device() - self.HW_VERSION_H = self.read_hw_version_h() - self.HW_VERSION_L = self.read_hw_version_l() - self.ASSEMBLY_VERSION = self.read_assembly_version() - self.HARP_VERSION_H = self.read_harp_h_version() - self.HARP_VERSION_L = self.read_harp_l_version() - self.FIRMWARE_VERSION_H = self.read_fw_h_version() - self.FIRMWARE_VERSION_L = self.read_fw_l_version() - self.DEVICE_NAME = self.read_device_name() + self.WHO_AM_I = self._read_who_am_i() + self.DEFAULT_DEVICE_NAME = self._read_default_device_name() + self.HW_VERSION_H = self._read_hw_version_h() + self.HW_VERSION_L = self._read_hw_version_l() + self.ASSEMBLY_VERSION = self._read_assembly_version() + self.HARP_VERSION_H = self._read_harp_h_version() + self.HARP_VERSION_L = self._read_harp_l_version() + self.FIRMWARE_VERSION_H = self._read_fw_h_version() + self.FIRMWARE_VERSION_L = self._read_fw_l_version() + self.DEVICE_NAME = self._read_device_name() def info(self) -> None: print("Device info:") - print(f"* Who am I: ({self.WHO_AM_I}) {self.WHO_AM_I_DEVICE}") + print(f"* Who am I: ({self.WHO_AM_I}) {self.DEFAULT_DEVICE_NAME}") print(f"* HW version: {self.HW_VERSION_H}.{self.HW_VERSION_L}") print(f"* Assembly version: {self.ASSEMBLY_VERSION}") print(f"* HARP version: {self.HARP_VERSION_H}.{self.HARP_VERSION_L}") @@ -97,76 +97,6 @@ def connect(self) -> None: def disconnect(self) -> None: self._ser.close() - def read_who_am_i(self) -> int: - address = CommonRegisters.WHO_AM_I - - reply: ReplyHarpMessage = self.read_u16(address, dump=False) - - return reply.payload_as_int() - - def read_who_am_i_device(self) -> str: - address = CommonRegisters.WHO_AM_I - - reply: ReplyHarpMessage = self.read_u16(address, dump=False) - - return device_names.get(reply.payload_as_int()) - - def read_hw_version_h(self) -> int: - address = CommonRegisters.HW_VERSION_H - - reply: ReplyHarpMessage = self.read_u8(address, dump=False) - - return reply.payload_as_int() - - def read_hw_version_l(self) -> int: - address = CommonRegisters.HW_VERSION_L - - reply: ReplyHarpMessage = self.read_u8(address, dump=False) - - return reply.payload_as_int() - - def read_assembly_version(self) -> int: - address = CommonRegisters.ASSEMBLY_VERSION - - reply: ReplyHarpMessage = self.read_u8(address, dump=False) - - return reply.payload_as_int() - - def read_harp_h_version(self) -> int: - address = CommonRegisters.HARP_VERSION_H - - reply: ReplyHarpMessage = self.read_u8(address, dump=False) - - return reply.payload_as_int() - - def read_harp_l_version(self) -> int: - address = CommonRegisters.HARP_VERSION_L - - reply: ReplyHarpMessage = self.read_u8(address, dump=False) - - return reply.payload_as_int() - - def read_fw_h_version(self) -> int: - address = CommonRegisters.FIRMWARE_VERSION_H - - reply: ReplyHarpMessage = self.read_u8(address, dump=False) - - return reply.payload_as_int() - - def read_fw_l_version(self) -> int: - address = CommonRegisters.FIRMWARE_VERSION_L - - reply: ReplyHarpMessage = self.read_u8(address, dump=False) - - return reply.payload_as_int() - - def read_device_name(self) -> str: - address = CommonRegisters.DEVICE_NAME - - reply: ReplyHarpMessage = self.read_u8(address, dump=False) - - return reply.payload_as_string() - def read_device_mode(self) -> DeviceMode: address = CommonRegisters.OPERATION_CTRL reply = self.read_u8(address) @@ -429,3 +359,69 @@ def write_float( ).frame, dump=dump, ) + + def _read_who_am_i(self) -> int: + address = CommonRegisters.WHO_AM_I + + reply: ReplyHarpMessage = self.read_u16(address, dump=False) + + return reply.payload_as_int() + + def _read_default_device_name(self) -> str: + return device_names.get(self.WHO_AM_I, "Unknown device") + + def _read_hw_version_h(self) -> int: + address = CommonRegisters.HW_VERSION_H + + reply: ReplyHarpMessage = self.read_u8(address, dump=False) + + return reply.payload_as_int() + + def _read_hw_version_l(self) -> int: + address = CommonRegisters.HW_VERSION_L + + reply: ReplyHarpMessage = self.read_u8(address, dump=False) + + return reply.payload_as_int() + + def _read_assembly_version(self) -> int: + address = CommonRegisters.ASSEMBLY_VERSION + + reply: ReplyHarpMessage = self.read_u8(address, dump=False) + + return reply.payload_as_int() + + def _read_harp_h_version(self) -> int: + address = CommonRegisters.HARP_VERSION_H + + reply: ReplyHarpMessage = self.read_u8(address, dump=False) + + return reply.payload_as_int() + + def _read_harp_l_version(self) -> int: + address = CommonRegisters.HARP_VERSION_L + + reply: ReplyHarpMessage = self.read_u8(address, dump=False) + + return reply.payload_as_int() + + def _read_fw_h_version(self) -> int: + address = CommonRegisters.FIRMWARE_VERSION_H + + reply: ReplyHarpMessage = self.read_u8(address, dump=False) + + return reply.payload_as_int() + + def _read_fw_l_version(self) -> int: + address = CommonRegisters.FIRMWARE_VERSION_L + + reply: ReplyHarpMessage = self.read_u8(address, dump=False) + + return reply.payload_as_int() + + def _read_device_name(self) -> str: + address = CommonRegisters.DEVICE_NAME + + reply: ReplyHarpMessage = self.read_u8(address, dump=False) + + return reply.payload_as_string() From 4af5567a62270a70aca420963383477798440850 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 2 Apr 2025 10:53:02 +0100 Subject: [PATCH 055/159] Refactor timeout assignment and correct payload string formatting --- pyharp/device.py | 2 +- pyharp/messages.py | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/pyharp/device.py b/pyharp/device.py index 1250295..905e9c1 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -87,7 +87,7 @@ def connect(self) -> None: self._ser = HarpSerial( self._serial_port, # "/dev/tty.usbserial-A106C8O9" baudrate=1000000, - timeout=self.__class__.TIMEOUT_S, + timeout=self.TIMEOUT_S, parity=serial.PARITY_NONE, stopbits=1, bytesize=8, diff --git a/pyharp/messages.py b/pyharp/messages.py index 13b7d01..2c8e780 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -120,8 +120,7 @@ def __str__(self): bytes_per_word = self.payload_type.value & 0x07 format_str = f"0{bytes_per_word}b" - for item in self.payload: - payload_str += f"{item:{format_str}} " + payload_str = "".join(f"{item:{format_str}} " for item in self.payload) return ( f"Type: {self.message_type.name}\r\n" @@ -131,7 +130,7 @@ def __str__(self): + f"Timestamp: {self.timestamp}\r\n" + f"Payload Type: {self.payload_type.name}\r\n" + f"Payload Length: {len(self.payload)}\r\n" - + f"Payload: {self.payload}\r\n" + + f"Payload: {payload_str}\r\n" + f"Checksum: {self.checksum}" ) @@ -245,7 +244,7 @@ def __init__( def payload(self) -> Union[int, list[int]]: match self.payload_type: case PayloadType.U8: - return self.frame[5] + return self._frame[5] case PayloadType.S8: return int.from_bytes([self.frame[5]], byteorder="little", signed=True) case PayloadType.U16: @@ -262,7 +261,6 @@ def payload(self) -> Union[int, list[int]]: ) case PayloadType.S32: return int.from_bytes(self._frame[5:9], byteorder="little", signed=True) - # TODO: missing validation for U64 and S64 case PayloadType.U64: return int.from_bytes( self._frame[5:13], byteorder="little", signed=False From a2b8cc092b2bf77ffc41474ab0c780d07a95a9e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 2 Apr 2025 13:53:22 +0100 Subject: [PATCH 056/159] Update current_device_names --- pyharp/device_names.py | 68 ++++++++++++++++++++++++++++-------------- 1 file changed, 46 insertions(+), 22 deletions(-) diff --git a/pyharp/device_names.py b/pyharp/device_names.py index d7b7557..c166ff0 100644 --- a/pyharp/device_names.py +++ b/pyharp/device_names.py @@ -1,26 +1,50 @@ from collections import defaultdict +# This file contains the device names for the current version of the pyHarp library. +# These names were extracted from https://github.com/harp-tech/protocol/blob/main/whoami.yml +# commit used: https://github.com/harp-tech/protocol/commit/3e2a228 -current_device_names = \ - {1024: 'Poke', - 1040: 'MultiPwm', - 1056: 'Wear', - 1072: 'VoltsDrive', - 1088: 'LedController', - 1104: 'Synchronizer', - 1121: 'SimpleAnalogGenerator', - 1136: 'Archimedes', - 1152: 'ClockSynchronizer', - 1168: 'Camera', - 1184: 'PyControl', - 1200: 'FlyPad', - 1216: 'Behavior', - 1232: 'LoadCells', - 1248: 'AudioSwitch', - 1264: 'Rgb', - 1200: 'FlyPad', - 2064: 'FP3002', - 2080: 'IblBehavior'} -device_names = defaultdict(lambda: 'NotSpecified') -device_names.update(current_device_names) +current_device_names = { + 256: "USBHub", + 1024: "Poke", + 1040: "MultiPwmGenerator", + 1056: "Wear", + 1058: "WearBaseStationGen2", + 1072: "Driver12Volts", + 1088: "LedController", + 1104: "Synchronizer", + 1106: "InputExpander", + 1108: "OutputExpander", + 1121: "SimpleAnalogGenerator", + 1130: "StepperDriver", + 1136: "Archimedes", + 1140: "Olfactometer", + 1152: "ClockSynchronizer", + 1154: "TimestampGeneratorGen1", + 1158: "TimestampGeneratorGen3", + 1168: "CameraController", + 1170: "CameraControllerGen2", + 1184: "PyControlAdapter", + 1200: "FlyPad", + 1216: "Behavior", + 1224: "VestibularH1", + 1225: "VestibularH2", + 1232: "LoadCells", + 1236: "AnalogInput", + 1248: "RgbArray", + 1280: "SoundCard", + 1296: "SyringePump", + 1400: "LicketySplit", + 1401: "SniffDetector", + 1402: "Treadmill", + 1403: "cuTTLefish", + 1404: "WhiteRabbit", + 1405: "EnvironmentSensor", + 2064: "NeurophotometricsFP3002", + 2080: "Ibl_behavior_control", + 2094: "RfidReader", + 2110: "Pluma", +} +device_names = defaultdict(lambda: "NotSpecified") +device_names.update(current_device_names) From 45ee96543469788837ab72c939fea952e73004bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 2 Apr 2025 13:55:13 +0100 Subject: [PATCH 057/159] Add support for device's serial number --- examples/get_info.py | 1 + pyharp/base.py | 1 + pyharp/device.py | 14 ++++++++++++++ 3 files changed, 16 insertions(+) diff --git a/examples/get_info.py b/examples/get_info.py index 113e548..05ed9f9 100755 --- a/examples/get_info.py +++ b/examples/get_info.py @@ -31,6 +31,7 @@ device_harp_h = device.HARP_VERSION_H # Get device's harp core version device_harp_l = device.HARP_VERSION_L # Get device's harp core version device_assembly = device.ASSEMBLY_VERSION # Get device's assembly version +device_serial_number = device.SERIAL_NUMBER # Get device's serial number reg_dump = device.dump_registers() for reg_reply in reg_dump: diff --git a/pyharp/base.py b/pyharp/base.py index 271a903..af4219f 100644 --- a/pyharp/base.py +++ b/pyharp/base.py @@ -50,3 +50,4 @@ class CommonRegisters: OPERATION_CTRL = 0x0A RESET_DEV = 0x0B DEVICE_NAME = 0x0C + SERIAL_NUMBER = 0x0D diff --git a/pyharp/device.py b/pyharp/device.py index 905e9c1..1718394 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -39,6 +39,7 @@ class Device: FIRMWARE_VERSION_H: int FIRMWARE_VERSION_L: int DEVICE_NAME: str + SERIAL_NUMBER: int TIMEOUT_S = 1.0 @@ -69,6 +70,7 @@ def load(self) -> None: self.FIRMWARE_VERSION_H = self._read_fw_h_version() self.FIRMWARE_VERSION_L = self._read_fw_l_version() self.DEVICE_NAME = self._read_device_name() + self.SERIAL_NUMBER = self._read_serial_number() def info(self) -> None: print("Device info:") @@ -78,6 +80,7 @@ def info(self) -> None: print(f"* HARP version: {self.HARP_VERSION_H}.{self.HARP_VERSION_L}") print(f"* Firmware version: {self.FIRMWARE_VERSION_H}.{self.FIRMWARE_VERSION_L}") print(f"* Device user name: {self.DEVICE_NAME}") + print(f"* Serial number: {self.SERIAL_NUMBER}") print(f"* Mode: {self.read_device_mode().name}") def read(self): @@ -425,3 +428,14 @@ def _read_device_name(self) -> str: reply: ReplyHarpMessage = self.read_u8(address, dump=False) return reply.payload_as_string() + + def _read_serial_number(self) -> int: + address = CommonRegisters.SERIAL_NUMBER + + reply: ReplyHarpMessage = self.read_u8(address, dump=False) + + if reply.has_error(): + return 0 + + return reply.payload_as_int() + From 20c2c59449b602a8bde6d88b0638a640a55a975f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 2 Apr 2025 13:58:52 +0100 Subject: [PATCH 058/159] Remove unused method --- pyharp/device.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/pyharp/device.py b/pyharp/device.py index 1718394..57b7191 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -83,9 +83,6 @@ def info(self) -> None: print(f"* Serial number: {self.SERIAL_NUMBER}") print(f"* Mode: {self.read_device_mode().name}") - def read(self): - pass - def connect(self) -> None: self._ser = HarpSerial( self._serial_port, # "/dev/tty.usbserial-A106C8O9" From 1aecf2a3fe25a2796e58a500bba35861c2c35563 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 2 Apr 2025 16:12:12 +0100 Subject: [PATCH 059/159] Update README --- README.md | 96 ++++++++++++++++++++----------------------------------- 1 file changed, 35 insertions(+), 61 deletions(-) diff --git a/README.md b/README.md index 9191782..335330f 100644 --- a/README.md +++ b/README.md @@ -1,82 +1,56 @@ # pyharp -Harp implementation of the Harp protocol. +Python implementation of the Harp protocol for hardware control and data acquisition. -> [!CAUTION] -> The README is currently outdated! - -## Install with Pip -From this directory, install in editable mode with -```` -pip install -e . -```` - -Note that for the above to work, a fairly recent version of pip (>= 21.3) is required. - -## Install with Poetry - -Each Python user has is own very dear IDE for editing. Here, we are leaving instructions on how to edit this code using pyCharm, Anaconda and Poetry. - -The instructions are for beginner. Most of the users can just skip them. - -This was tested on a Windows machine, but should be similar to other systems. +## Installation +```bash +uv add pyharp +# or +pip install pyharp +``` -### 1. Install PyCHarm -**PyCharm** can be download from [here](https://www.jetbrains.com/pycharm/download/). The Community version is enough. -Download and install it. +## Quick Start -### 2. Install Anaconda +```python +from pyharp.device import Device -**Anaconda** can be found [here](https://www.anaconda.com/products/individual). -Download the version according to your computer and install it. -- Unselect **Add Anaconda to the system PATH environment variable** -- Select ** Register Anaconda as the system Pyhton** +# Connect to a device +device = Device("/dev/ttyUSB0") -It's suggested to reboot your computer at this point +# Get device information +device.info() -### 3. Install Poetry +# define register_address +register_address = 32 -Open the **Command Prompt** and execute the next command: -``` -curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python -``` +# Read from register +value = device.read_u8(register_address) -### 4. Install pyharp +# Write to register +device.write_u8(register_address, value) -Open **Anaconda**, navigate to the repository folder and execute the next commands: -``` -poetry install -poetry env info +# Disconnect when done +device.disconnect() ``` -The second comand will reply with a **Path:**. -Select and copy this path. +or using the `with` statement: -### 5. Using PyCharm to edit the code +```python +from pyharp.device import Device -1. Open **PyCharm** :) -2. Go to File -> Open, select the repository folder, and click **OK** -3. Go to File -> Settings -> Project:pyharp -> Project Interpreter -3.1 Click in the gear in front of the Project Interpreter: and select **Add...** -3.2 On Virtualenv Environment, chose Existing environment -3.3 Select **python.exe** on the folder Scripts under the path copied from the _poetry env info_ command -3.4 Click **OK** and **OK** +with Device("/dev/ttyUSB0") as device: + # Get device information + device.info() -You are ready to go! + # define register_address + register_address = 32 -### 6. Test the code + # Read from register + value = device.read_u8(register_address) -Under **PyCharm**, Open one of the examples from the folder _examples_ (the _get_info.py_ is generic, so it's a good option) and update the COMx to your COM number. -Right-click on top of the file and chose option _Run 'get_info.py_. You should read something like this in the console: -``` -Device info: -* Who am I: (2080) IblBehavior -* HW version: 1.0 -* Assembly version: 0 -* HARP version: 1.6 -* Firmware version: 1.0 -* Device user name: IBL_rig_0 + # Write to register + device.write_u8(register_address, value) ``` ## for Linux @@ -85,7 +59,7 @@ Device info: Install by either copying `10-harp.rules` over to your `/etc/udev/rules.d` folder or by symlinking it with: ```` -sudo ln -s /absolute/path/to/vibratome-controller/10-harp.rules /etc/udev/rules.d/10-harp.rules +sudo ln -s /absolute/path/to/10-harp.rules /etc/udev/rules.d/10-harp.rules ```` Then reload udev rules with From b9819aa54a61de2dc50d01c022437bbc8e8dac48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 2 Apr 2025 16:13:45 +0100 Subject: [PATCH 060/159] Update documentation dependencies --- pyproject.toml | 3 ++ uv.lock | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 14f8aa5..b124dea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,6 +13,9 @@ dependencies = [ [dependency-groups] dev = [ "mkdocs>=1.6.1", + "mkdocs-codeinclude-plugin>=0.2.1", + "mkdocs-git-authors-plugin>=0.9.4", + "mkdocs-git-committers-plugin-2>=2.5.0", "mkdocs-material>=9.6.9", "mkdocstrings-python>=1.16.6", "pytest>=8.3.5", diff --git a/uv.lock b/uv.lock index 984b6c6..046433d 100644 --- a/uv.lock +++ b/uv.lock @@ -114,6 +114,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034 }, ] +[[package]] +name = "gitdb" +version = "4.0.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "smmap" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/94/63b0fc47eb32792c7ba1fe1b694daec9a63620db1e313033d18140c2320a/gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571", size = 394684 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf", size = 62794 }, +] + +[[package]] +name = "gitpython" +version = "3.1.44" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "gitdb" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/89/37df0b71473153574a5cdef8f242de422a0f5d26d7a9e231e6f169b4ad14/gitpython-3.1.44.tar.gz", hash = "sha256:c87e30b26253bf5418b01b0660f818967f3c503193838337fe5e573331249269", size = 214196 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/9a/4114a9057db2f1462d5c8f8390ab7383925fe1ac012eaa42402ad65c2963/GitPython-3.1.44-py3-none-any.whl", hash = "sha256:9e0e10cda9bed1ee64bc9a6de50e7e38a9c9943241cd7f585f6df3ed28011110", size = 207599 }, +] + [[package]] name = "griffe" version = "1.6.1" @@ -260,6 +284,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f8/29/1125f7b11db63e8e32bcfa0752a4eea30abff3ebd0796f808e14571ddaa2/mkdocs_autorefs-1.4.1-py3-none-any.whl", hash = "sha256:9793c5ac06a6ebbe52ec0f8439256e66187badf4b5334b5fde0b128ec134df4f", size = 5782047 }, ] +[[package]] +name = "mkdocs-codeinclude-plugin" +version = "0.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mkdocs" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1b/b5/f72df157abc7f85e33ffa417464e9dd535ef5fda7654eda41190047a53b6/mkdocs-codeinclude-plugin-0.2.1.tar.gz", hash = "sha256:305387f67a885f0e36ec1cf977324fe1fe50d31301147194b63631d0864601b1", size = 8140 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/7b/60573ebf2a22b144eeaf3b29db9a6d4d342d68273f716ea2723d1ad723ba/mkdocs_codeinclude_plugin-0.2.1-py3-none-any.whl", hash = "sha256:172a917c9b257fa62850b669336151f85d3cd40312b2b52520cbcceab557ea6c", size = 8093 }, +] + [[package]] name = "mkdocs-get-deps" version = "0.2.0" @@ -274,6 +311,32 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134", size = 9521 }, ] +[[package]] +name = "mkdocs-git-authors-plugin" +version = "0.9.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mkdocs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/87/9a/063c4a3688e4669eb2054e4bf6e9cc582f6c1d85674e3f5b836ceff97c3b/mkdocs_git_authors_plugin-0.9.4.tar.gz", hash = "sha256:f5cfaf93d08981ce25591bbaf642051ed168c3886bb96ecd2dca53f0ef1973b8", size = 21914 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/ac/2b5bae4047276fda2bdd14a6d4af59288fb4d5de54151ae4e6ba17611ceb/mkdocs_git_authors_plugin-0.9.4-py3-none-any.whl", hash = "sha256:84b9b56c703841189c64d8ff6947034fe0a9c14a0a8f1f6255edfcfe3a56825f", size = 20752 }, +] + +[[package]] +name = "mkdocs-git-committers-plugin-2" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "gitpython" }, + { name = "mkdocs" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b4/8a/4ca4fb7d17f66fa709b49744c597204ad03fb3b011c76919564843426f11/mkdocs_git_committers_plugin_2-2.5.0.tar.gz", hash = "sha256:a01f17369e79ca28651681cddf212770e646e6191954bad884ca3067316aae60", size = 15183 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/f5/768590251839a148c188d64779b809bde0e78a306295c18fc29d7fc71ce1/mkdocs_git_committers_plugin_2-2.5.0-py3-none-any.whl", hash = "sha256:1778becf98ccdc5fac809ac7b62cf01d3c67d6e8432723dffbb823307d1193c4", size = 11788 }, +] + [[package]] name = "mkdocs-material" version = "9.6.9" @@ -401,6 +464,9 @@ dependencies = [ [package.dev-dependencies] dev = [ { name = "mkdocs" }, + { name = "mkdocs-codeinclude-plugin" }, + { name = "mkdocs-git-authors-plugin" }, + { name = "mkdocs-git-committers-plugin-2" }, { name = "mkdocs-material" }, { name = "mkdocstrings-python" }, { name = "pytest" }, @@ -413,6 +479,9 @@ requires-dist = [{ name = "pyserial", specifier = ">=3.5" }] [package.metadata.requires-dev] dev = [ { name = "mkdocs", specifier = ">=1.6.1" }, + { name = "mkdocs-codeinclude-plugin", specifier = ">=0.2.1" }, + { name = "mkdocs-git-authors-plugin", specifier = ">=0.9.4" }, + { name = "mkdocs-git-committers-plugin-2", specifier = ">=2.5.0" }, { name = "mkdocs-material", specifier = ">=9.6.9" }, { name = "mkdocstrings-python", specifier = ">=1.16.6" }, { name = "pytest", specifier = ">=8.3.5" }, @@ -564,6 +633,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, ] +[[package]] +name = "smmap" +version = "5.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/44/cd/a040c4b3119bbe532e5b0732286f805445375489fceaec1f48306068ee3b/smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5", size = 22329 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/be/d09147ad1ec7934636ad912901c5fd7667e1c858e19d355237db0d0cd5e4/smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e", size = 24303 }, +] + [[package]] name = "urllib3" version = "2.3.0" From 2ca3e4f3bb81d1593f1996b6e482b36b7dc9e59f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 2 Apr 2025 16:15:41 +0100 Subject: [PATCH 061/159] Update mkdocs configuration --- mkdocs.yml | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/mkdocs.yml b/mkdocs.yml index e4b6a6e..e474f18 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,16 +1,61 @@ site_name: pyharp +repo_url: "https://github.com/fchampalimaud/pyharp" +repo_name: "pyharp" plugins: + - search + - autorefs + - codeinclude - mkdocstrings: handlers: python: options: docstring_style: numpy + show_root_heading: true + show_submodules: true + show_source: false + - git-committers: + repository: fchampalimaud/pyharp + branch: main + - git-authors + markdown_extensions: + - abbr + - attr_list - admonition - pymdownx.details - pymdownx.superfences + - pymdownx.emoji: + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg + - toc: + permalink: "#" theme: name: material + icon: + repo: fontawesome/brands/github + features: + - content.tooltips + - toc.follow + palette: + - media: "(prefers-color-scheme)" + toggle: + icon: material/brightness-auto + name: Switch to light mode + - media: "(prefers-color-scheme: light)" + scheme: default + primary: blue + accent: blue + toggle: + icon: material/weather-sunny + name: Switch to dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + primary: red + accent: red + toggle: + icon: material/weather-night + name: Switch to system preference + From fb78b31e8146eb7b231811f9486b8cbf3ca49f63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 2 Apr 2025 16:16:55 +0100 Subject: [PATCH 062/159] Move examples to documentation folder --- .../examples/code}/check_device_id.py | 2 +- {examples => docs/examples/code}/get_info.py | 0 .../examples/code}/wait_for_events.py | 0 .../code}/write_and_read_from_registers.py | 0 docs/examples/examples.md | 42 ++++++++++++++++ examples/behavior_device_driver_test.py | 49 ------------------- 6 files changed, 43 insertions(+), 50 deletions(-) rename {examples => docs/examples/code}/check_device_id.py (95%) rename {examples => docs/examples/code}/get_info.py (100%) rename {examples => docs/examples/code}/wait_for_events.py (100%) rename {examples => docs/examples/code}/write_and_read_from_registers.py (100%) create mode 100644 docs/examples/examples.md delete mode 100755 examples/behavior_device_driver_test.py diff --git a/examples/check_device_id.py b/docs/examples/code/check_device_id.py similarity index 95% rename from examples/check_device_id.py rename to docs/examples/code/check_device_id.py index 7a64c52..04297a6 100755 --- a/examples/check_device_id.py +++ b/docs/examples/code/check_device_id.py @@ -12,7 +12,7 @@ # Open the device # Open serial connection if os.name == "posix": # check for Linux. - device = Device("/dev/harp_device_00") + device = Device("/dev/ttyUSB0") else: # assume Windows. device = Device("COM95") diff --git a/examples/get_info.py b/docs/examples/code/get_info.py similarity index 100% rename from examples/get_info.py rename to docs/examples/code/get_info.py diff --git a/examples/wait_for_events.py b/docs/examples/code/wait_for_events.py similarity index 100% rename from examples/wait_for_events.py rename to docs/examples/code/wait_for_events.py diff --git a/examples/write_and_read_from_registers.py b/docs/examples/code/write_and_read_from_registers.py similarity index 100% rename from examples/write_and_read_from_registers.py rename to docs/examples/code/write_and_read_from_registers.py diff --git a/docs/examples/examples.md b/docs/examples/examples.md new file mode 100644 index 0000000..1de83a0 --- /dev/null +++ b/docs/examples/examples.md @@ -0,0 +1,42 @@ +# Examples + +## Getting Device Info + +This example demonstrates connecting to a Harp device and reading its information: + + +```python +[](./code/get_info.py) +``` + + + +## Check Device Id + +This example demonstrates connecting to a Harp device and checking its ID: + + +```python +[](./code/check_device_id.py) +``` + + +## Wait for Events + +This example demonstrates connecting to a Harp device and waiting for events: + + +```python +[](./code/wait_for_events.py) +``` + + +## Write and Read from registers + +This example demonstrates connecting to a Harp device and writing and reading from registers: + + +```python +[](./code/write_and_read_from_registers.py) +``` + \ No newline at end of file diff --git a/examples/behavior_device_driver_test.py b/examples/behavior_device_driver_test.py deleted file mode 100755 index dd6a3a4..0000000 --- a/examples/behavior_device_driver_test.py +++ /dev/null @@ -1,49 +0,0 @@ -import os - -from pyharp.drivers.behavior import Behavior - -# Open the device and print the info on screen -# Open serial connection and save communication to a file -device = None -if os.name == "posix": # check for Linux. - device = Behavior("/dev/ttyUSB0", "ibl.bin") -else: # assume Windows. - device = Behavior("COM95", "ibl.bin") - -print(f"digital inputs: {device.all_input_states:03b}") -print(f"digital outputs: {device.all_output_states:016b}") -print(f"setting digital outputs") -# device.all_output_states = 0x0000 # Set the whole port directly. -# device.set_outputs(0xFFFF) # Set the values set to logic 1 only. -# device.clear_outputs(0xFFFF)# Clear values set to logic 1 only. -print(f"digital outputs: {device.all_output_states:016b}") -# device.set_io_configuration(0b111) - -# TODO: FIXME. IOs are not working -# device.set_io_configuration(0b111) # This is getting ignored? -# device.set_io_outputs(0b000) -# device.all_io_states = 0b000 -# print(f"digital ios: {device.all_io_states:03b}") - -# device.D0 = 0 -# print(f"D0: {device.D0}") -# device.D0 = 1 -# print(f"D0: {device.D0}") -# -# device.D1 = 0 -# print(f"D1: {device.D1}") -# device.D1 = 1 -# print(f"D1: {device.D1}") -# -# print(f"DI2: {device.DI2}") - - -# import time -# while True: -# print(f"PORT0 IN State: {device.port0_i0}") -# print(f"PORT0 IO State: {device.port0_io0}") -# print(f"PORT0 OUT State: {device.port0_o0}") -# print(f"all port io states: {device.all_port_io_states}") -# print(f"all port output states: {device.all_port_output_states}") -# print() -# time.sleep(0.1) From 4dd42e8d1613df4553534d4cf1c0fa8427ca4d20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 2 Apr 2025 16:17:30 +0100 Subject: [PATCH 063/159] Update documentation --- docs/api/device.md | 1 + docs/api/messages.md | 4 ++++ docs/index.md | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 docs/api/device.md create mode 100644 docs/api/messages.md diff --git a/docs/api/device.md b/docs/api/device.md new file mode 100644 index 0000000..9e949dc --- /dev/null +++ b/docs/api/device.md @@ -0,0 +1 @@ +::: pyharp.device.Device \ No newline at end of file diff --git a/docs/api/messages.md b/docs/api/messages.md new file mode 100644 index 0000000..17f4233 --- /dev/null +++ b/docs/api/messages.md @@ -0,0 +1,4 @@ +::: pyharp.messages.HarpMessage +::: pyharp.messages.ReplyHarpMessage +::: pyharp.messages.ReadHarpMessage +::: pyharp.messages.WriteHarpMessage \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index c0f9b06..f48b45f 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,4 +1,4 @@ -# pyharp +# Introduction !!! Warning Work in Progress! \ No newline at end of file From 8cd50ad717642b302e9037e2ba991de14f0966cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 2 Apr 2025 16:19:12 +0100 Subject: [PATCH 064/159] Add context manager support to Device class --- pyharp/device.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/pyharp/device.py b/pyharp/device.py index 57b7191..0ea24d4 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -436,3 +436,30 @@ def _read_serial_number(self) -> int: return reply.payload_as_int() + def __enter__(self): + """Support for using Device with 'with' statement. + + Returns + ------- + Device + The Device instance + """ + # Connection is already established in __init__ + # but we could add additional setup if needed + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + """Cleanup resources when exiting the 'with' block. + + Parameters + ---------- + exc_type : Exception type or None + Type of the exception that caused the context to be exited + exc_val : Exception or None + Exception instance that caused the context to be exited + exc_tb : traceback or None + Traceback if an exception occurred + """ + self.disconnect() + # Return False to propagate exceptions that occurred in the with block + return False \ No newline at end of file From 13924db15e1eee430faca1f3179bd068d940a2f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Grilo?= Date: Fri, 4 Apr 2025 17:58:33 +0100 Subject: [PATCH 065/159] Chore: add pytest configs to vscode workspace settings --- .vscode/settings.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index dec7265..fcf64d1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,5 +5,10 @@ "ruff.organizeImports": true, "editor.codeActionsOnSave": { "source.organizeImports": "explicit" - } + }, + "python.testing.pytestArgs": [ + "tests" + ], + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true } \ No newline at end of file From 413f36c24c9f36e389f2792e91463860fcc49a05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Grilo?= Date: Fri, 4 Apr 2025 18:02:48 +0100 Subject: [PATCH 066/159] Docs: separate examples in different pages --- docs/examples/check_device_id.md | 9 ++++ docs/examples/examples.md | 42 ------------------- docs/examples/get_info.md | 9 ++++ docs/examples/index.md | 10 +++++ docs/examples/wait_for_events.md | 9 ++++ .../examples/write_and_read_from_registers.md | 9 ++++ 6 files changed, 46 insertions(+), 42 deletions(-) create mode 100644 docs/examples/check_device_id.md delete mode 100644 docs/examples/examples.md create mode 100644 docs/examples/get_info.md create mode 100644 docs/examples/index.md create mode 100644 docs/examples/wait_for_events.md create mode 100644 docs/examples/write_and_read_from_registers.md diff --git a/docs/examples/check_device_id.md b/docs/examples/check_device_id.md new file mode 100644 index 0000000..c9946ec --- /dev/null +++ b/docs/examples/check_device_id.md @@ -0,0 +1,9 @@ +# Check Device Id + +This example demonstrates connecting to a Harp device and checking its ID: + + +```python +[](./code/check_device_id.py) +``` + \ No newline at end of file diff --git a/docs/examples/examples.md b/docs/examples/examples.md deleted file mode 100644 index 1de83a0..0000000 --- a/docs/examples/examples.md +++ /dev/null @@ -1,42 +0,0 @@ -# Examples - -## Getting Device Info - -This example demonstrates connecting to a Harp device and reading its information: - - -```python -[](./code/get_info.py) -``` - - - -## Check Device Id - -This example demonstrates connecting to a Harp device and checking its ID: - - -```python -[](./code/check_device_id.py) -``` - - -## Wait for Events - -This example demonstrates connecting to a Harp device and waiting for events: - - -```python -[](./code/wait_for_events.py) -``` - - -## Write and Read from registers - -This example demonstrates connecting to a Harp device and writing and reading from registers: - - -```python -[](./code/write_and_read_from_registers.py) -``` - \ No newline at end of file diff --git a/docs/examples/get_info.md b/docs/examples/get_info.md new file mode 100644 index 0000000..96d03e9 --- /dev/null +++ b/docs/examples/get_info.md @@ -0,0 +1,9 @@ +# Getting Device Info + +This example demonstrates connecting to a Harp device and reading its information: + + +```python +[](./code/get_info.py) +``` + diff --git a/docs/examples/index.md b/docs/examples/index.md new file mode 100644 index 0000000..6c4ac5b --- /dev/null +++ b/docs/examples/index.md @@ -0,0 +1,10 @@ +# Examples + +This section contains some examples to help you get started with `pyharp`. + +Here's the complete list of available examples: + +- [Getting Device Info](get_info.md) - connect to a Harp device and read its information. +- [Check Device ID](check_device.md) - connect to a Harp device and check its ID. +- [Wait for Events](wait_for_events.md) - connect to a Harp device and wait for events. +- [Write and Read from Registers](write_and_read_from_registers.md) - connect to a Harp device and read from registers. \ No newline at end of file diff --git a/docs/examples/wait_for_events.md b/docs/examples/wait_for_events.md new file mode 100644 index 0000000..a40c86a --- /dev/null +++ b/docs/examples/wait_for_events.md @@ -0,0 +1,9 @@ +# Wait for Events + +This example demonstrates connecting to a Harp device and waiting for events: + + +```python +[](./code/wait_for_events.py) +``` + \ No newline at end of file diff --git a/docs/examples/write_and_read_from_registers.md b/docs/examples/write_and_read_from_registers.md new file mode 100644 index 0000000..55f9e64 --- /dev/null +++ b/docs/examples/write_and_read_from_registers.md @@ -0,0 +1,9 @@ +# Write and Read from registers + +This example demonstrates connecting to a Harp device and writing and reading from registers: + + +```python +[](./code/write_and_read_from_registers.py) +``` + \ No newline at end of file From f0266ccc9de17f2f4ccaa7b2ab1303fc4fa499f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Grilo?= Date: Fri, 4 Apr 2025 18:05:23 +0100 Subject: [PATCH 067/159] Docs: modify documentation organizational, functional and visual aspects --- docs/assets/favicon.png | Bin 0 -> 14859 bytes docs/assets/logo.svg | 71 +++++++++++++++++++++++++++++++++++++ docs/stylesheets/extra.css | 9 +++++ mkdocs.yml | 65 +++++++++++++++++++++++---------- 4 files changed, 126 insertions(+), 19 deletions(-) create mode 100644 docs/assets/favicon.png create mode 100644 docs/assets/logo.svg create mode 100644 docs/stylesheets/extra.css diff --git a/docs/assets/favicon.png b/docs/assets/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..49c6faef9130e5bdf4a397eb92d94e0e8a3c85d0 GIT binary patch literal 14859 zcmeHuS5%W-_h%FptjLE*FDe}=0xu{Xr5LJGrK9vNAOu1c3raT#p-68cP3aIqY?RPq zf*1)9gwR3@kc1wX2fw-b-_2SxYp&jl@GO$&l)cZczrD}vdnWql&+wc9fk5ZMcXiA_ zpyM+~AC^iFvtxAfk0%u z{C$F59zJ%H2?+AYT~*@&frLO{9c_!Scgqvu@9abJD2H3~5t4hyRh(HS@^qNBl}@r< z%8uY*yL2zcG3FF!rp0kR?Z7ttY)b$0g%RzZ^Y_27JQ2}$|2Wdt$09DCh+yfxtfP=q zTE))P3K~TW?Tl;wCyNDK(elAGWMpY4?0Y;bt`f?Qt8l8+2uW!}0GkATzl&u89`JOftlVv}^!9BKJ1qCeSUd^4teaHX&=uZBjay)At|q*h zRUr-@KCD1Jy|6jbT@CqoE(t>j$BgoGPP6J?%?vux@<{tPx$t z3)^d<4zfq=cSI@cupW=KTKgWTiI*CrU4^II+Yg*{Yd2lhywSkBQ8OyN!nbfDJa2S^ zZeCAl#bDFsG_4<4cQqz=Iyjx!ZrQ9WSSLQbmqW;b+5Bqf1)Gfo3 zy}?4I?J+VuiEqmRhlCvpmC^DDr73n%wHpiflfOzxl?=!@-nx>jw0;7Pvc=s1hi2vK z9!fv)AUUY$mAiEOP#xCu>uZlV{056s-y%SeY959(t)vT8)j3%0>}!!e#}VOC_uD_24O!w(#&~C^heaz4# z!=TnYvZtz06}9AY-DgTu3MYOT{>d$>$97845O>-|O#2k=9GGIzH)-?1A|Rtt*m}jT zX7}qAZH?Q_PgVlwx~4DhocL{5{-RR+a;D_S*o-YKAb&03UH{mAbBu>O&Z~BM!fQ{e zzs5HJP0T56DKA4Rw^yWwwUOqrC9!(kq)EnC@rKnVStThASgEyMKqY%HSp?P#5&_N2je0AmFV$c`Uz>sI*WEVYSj%MS(hiPk2sh)hY}*_|NO z+poc5Iuz~e2=Bq+`qf2i*na4Nn%b9v{eL7lwp=ram{GiyNveRN{fQlcwbN^tvW?EB z8*F7r`Z)2cm**{SPwzkby3Y*D=R{V#PEMzFDuzsBd~GJ5VqId3>ia$*ciNxjB`>W; z=@K$mGrWpCTO7E>MiJ(R-zVR;M@(K)cfYxvsshqDcB&CowQT~N zPM47$19JS?;{mdkyF*F&$In?+A}E&Nvc2tgCUAY1q#Lk54tG45pb5or$qf(I8cd7` z5^(>->!os^(HMn1HI2OzZb*3V^uTQ*xH!JC*vxIEYG+#Qcx|POQ>JuH^a#{SahL&$ zMLjh17ld1WaC%IoysA_EvB~M;8#>tpL)xuurZCv#hu6Vm{3woyA#Bh~qQq$Jf6nfw z|F-nWcMpcvk=UK@UNG&Wh=*8XUJKwPtLwcV{c=F|T1MJM1`6E5-x}@q(rjGST;)|i z879AUK11@d6k(HSG|n#g0SE7Z62&Msu8Ot!(=+5Xh7)ZANr5FQ_V0#RM3U_;gib^BTLD;lCecRL9_yzuzJyD`MT?9up&pIkO!b&F6z2b!Z#u+t$RiN&8P~ z`XF2~EQ8I!ny{sh3(|hR1_oU%BBV&;pAH{r(GPHBtm=>JzRkjAqsYVF5sSDiqE|!J zq~A-`$YwWTHd3-%`QaOPdbAu=U*S`kPl|!$#MeqQbtzg3 z>n_z1esEX81V`MEk%|P}v_oWfFyD`J=6l%1hAvX(X5d**FmSBg=;74w862_A&eqlH z@8$NC!8WwZiWzszBsH!Oe~+C@xx0uG*{E;z;{Zm!w`lEj&#>InWqn=GxbSi0>_nqI zu5>{iw1}w<3$q1C5nka(o;2AJ-p3ocYruQ*R?f?@o|O$^b6fpgTU*Af*81Z= ztd6Xc{Gkr;;6zovpd`N!e|trGxvvAPsuC z^7-v@d~*BJP3@G6GW_X&?}t)}_f11}C#mDUZQE*0gzuY61|Ad007bGJ)(4dITieEK z{GVpmi5aLSTT!f>|8A?p`dZ@xKmD(+pi$TGKY@99x>7&&eR%D^DT_4(C0&50n2@Lm zU@Of0m0mV7H@Z2WUHwtxC|ocnXJcO_FfXuhf)A#+ssOt!lY6Sre-7}GhYk6rT4~oZ zcM!gx46q7%iC}m0=Fr6ccgOttFo}+!!VBXX4-N5rwsrAhy=;D=fM%M}USVO9Q51Rn zQV9eAvF`t)!SM@_!H_mIr*_I?;#K&G#@Q;?%>9DDKdZ>8{?Juk zsyizR!i4nAAMcDWod4$5{{fRh`?A0f0u^2nb<39F9W=LQ2~tcnU+HH}Zu1EpdsKEE zUj~Gh0RYyPJcTo?FN^X>`9)%z@w}YgATicG?j<$)ZZT4spBD)rbc&e>4L8GR6y8(q zvUW=O)COKJfa}cO#vSts`lq8+gyJxF*}J8AlJ&3lV%6*`00VR*cu_y6aU~M0@OPWy zp`8xct&TSuXS7q?>)`^qP$Sx_FMw^V=-r>dsve$#gNnHw%02_ZD$?nGYePhg)-G7H0Y6eHw4XnzI`pHuUkzsNTv~z7CZF^^p zp!tTFbj2%B-}EuV+${|YTpspLXjkX{kDrP*0smbl898=&$;_$r(R&n3NHD)fX2P?c z_iTSmDVle6y)E9C_YVoKb`S%E)lrI_p^xQNab@g-A+XlUfyv&En>xlkGhQ3Ng1g#C zAkd%6e21Koh~l4gWYNLxxJb`cr3Tqq(uP0`wHkG9{#YcCNQf{zds1b8!$S5-*Sol3 zC~LDG&lLWuj*m82-q0@STanRln7%2u&lxZ(1kZ5atwF~~nX=B!@w#@o^cnKL9VySe zp1aysF1~Y4_DNyx1x4~jFy@yyF`o-WNbo8{Oa{2KW-Yat?Uu9!RG00)=}ARN;(lHq zwFv)ImwVw!3zIQ4rTMV8_>iL;_I248d?H*ui*{CF@7`fYieS>F3x$P-8S9dg1rLkn zN$orW59{xRcVCin*Ejv0;cGrA<)9%3?crKy2IF(c<>SllnpGszZ&5-=o;rJ0He`4` zNK@C${9p$ZD~c5(+XkZ~TJ}W?l;=}{m;T00HJg(ToATKvzcxAV%6&YpOeu!j<}FsnS-s<;56NcMmk<&>C?#%K>U06CXy2$!3m}59}6U zEH!vo+eyB$0hH}^nJOAm9PD9mpa(V;OrH1DCYfWMGHI2-W@_u}ecD_FrJDWr_6QwA z{Y(5LRcf%x2glXOrtwKJ^P2mZF(Z3utwTqBC2`+deFQ8YV@XR9r5BcVbv64nP{Z>A z)`Re=i}YKhM2oOC>RhB_Pu!#7=NG_~%*I*)%t?^T=MtEP%}h=PW=X99rMC1Eo3QS% z9@K-O2K9_3_B1aCxCdnXw5yxv8MdFd!Td0xPjPHiK(A+`I%6E?w@Oa6&g4we$$=A5 zkV|ui*0{#^(%Pb)gwkgs3>9cEsXkd{w5$@Vnln%!;&AW%rll-q6t^I@MctebB44$u zU#;?rT=9c3(o2y%NG6tgSr$&JZM*VJV)6`NUCP+XH+F6o9DGE@0& z+|$e0f$@vV$jd2ZDZT$_dP!p{vD3MrhlfE^?W{t5hgw$6bPH@-9@qnfN7tno#} z!N!$DRQ(w|vncOAHDC$GT681Oi4@TL*q9~Cp07G`WCLJQo|ekTil}Rz^lrY$+ZtxaR*pl z0T07p#wP6h7EpUwrR^n^Z}Sl1f7EBu9cuHbupu66n}&{iwxV9B1>?`+tvv&s+dr!! z2Kne>1U75P^7cB1UC`dmj7?Qxo513MKG+3*x>(nrYyl%?8z6$!@`UkC6QV*MQeV>^-7dU7sr{ z!F=Xj?ev`TWd=jmvzu$uCMNvX`4{l~B^(`V7ds_Su0Su(0sux^Q#ilj%ZSQ}H)}8S z>3eZ0h>abGf}#Grc#e7(w{|1kwPYf0)o9mks;R_hb}n%}Z)bnV=@O}#PhxZgYnSuc zag=ttj%%5jeFkRtkQ#MR&4Imh%1jD!9YWA=yVy0rh`to{4NM?v@Q?n@r^Hh~dGb5~#Lf#Y(;zqcH zteVa4%&J>e8|n|X(lzBir^$%b2NZ?Z?ZB~Vr!9XKI^nF@>{TrGgVUR;+R3K=NY4Ji zKYc>f%Z!#8`Y_SbpdrM7mZH_>O6l$AhwHI&e^_aeS>5lcMi9h?b*E3VD7M&dbm<;G z1)(2DU=z6l81B$-50cjNW~r(u&sC4Rbi^et zx!W<|e8+4!LSxQYfaJ-g+|V*N`-z*BT9u(%XRunA*?0?Tn1&j5iHbiz$24}U7x`HF z_qsCUM#=jO8E^bjy8hZGQae32@}ZyOymCo=(o4dAZOlKTkxe@-rr5W?F55)gAMP{8 z=HKcpXYcHJQ&lo`FkI&uR79tTL4YSd6kW~rbMV5LQtt%KxL|T8ae@ENET9-GFZ^E- zu`&Zl2?~#G%mLB1t?EC>M~WkT{QV+(k7*a2#e5R%W!DuvCI!_`7|(unD}l zh9CM2_&p`8>9U!IPCIMVIy@U!Z#e`4vsTzBN-w~Z`6zvG01_nqZ`lqofNoy(5`m4N z9x>Nn{79y>#`3l4ZFgil=c88IVDb145bw}GGIZY*O_+3ww3;+^MMPuV?M}5B;&%Z- zEry)4?(|!N2%leid6BE5T8W(DjY9mIa4_0D0Gl(%^H<0fi$W>0E_`0Z?k?tLkj%8%up zcGFqP2=zV@0YLLP(if-np{uJhHn|0c+Nycv@!fFD#rtZ*d7d#6OUH*7mim zS&f{glXPUKXigu~i+SlCclD&CY~%#zkZ+Z0e)~**0#&4`D`d+-*)Tn`o-@Vb2b^$(sr9TA zf>@S_CDq0S#QZWWQg>8n`3Vbs0YT{%Q=fA9g9I@4f}D`(TjKRwJVmg?0R1`k~&=mwAoGJ&G)Q=mJ?T*}avA*K@VM{DKkMrlMmL|)z zdGy$y0B5es>bnsgu`yEO<0t`SQTC;vIKC1I+4_Ac?6n{729ik>lQRu5iNt#`p})1@ z8om`mlf}|YZAL_q*Z37 zPDc9BlH8{Oe`STsS>O**a!fT*eGJhY)ze;;ik!Od7kCWnY--a|saCr0fl1*%ze^y;^{clnAvncQ*2VR7gKkG45=_yw%YA zDm4sJI$$$SJ(xVVaDK42nx2aCCw@W{_>>6E=S+Mio!O}?QC2Jwp5Q$NtEvs3?0>zw_>9J~2`=kTkjdJuyS7oAP z5N?P)?l?YA%~W#I==ghA>uAl0CSm{97i>2SA$kE?!$E#LD0!%k!iIu}@7t!|j7Ry! zLcPb-9&$k9$Nq6Dy^UL3;O83K==gI+@AvtLADA)79Y7%5Y5@@E)$KGUzwQBn*Tf$K zM$?PTT(4^gt@M0AI;@7)-9RszDC=+`W`fcEn~->3tt;z$&mJm}Wi$_CYk*U)zIkxr zJ7Ig<@MwW_}ZsFs!syyiumHUmjsi%Q`UV%IE0Fu-8B$9S2$zi;V{n_pt ztg-fm^K|z)*-rv)fCRp#S91m60g#HJ$CmsknN0_0uZoVXL>RFI z{Q+-SjB_K4j&0)R7VjVFM!AIG{V`*=I5uAbGAt~Cya8p3-TWhY^I%oDte8IqWk>J59LL9Xy&$4j zDSb7Cct0lkrL>ggLS@=#4)oudxItet5yf|fa8_u>}# z-vEHpC;OM@v|_K-yNy`urM_~7TeGI^(*Z~PZI^l1CXVlSMh)wPv{x`KN$;cUAs6xr zjN`x*%L{6(yzLjY56+Hx;GJ%`mJ=FrKIpX&b10fqU?iY?4{&91;JPND#-c}gCFgH5 zxh~p7m$Cp&x6+DnI3iw$^2#}5aY|lQ9jafFwVcYtPy@(#$=P`odgocG#kMtJB|)nB zL0R!yVlf2d8L%4qN!n@BYD-HFEl++Fz85)x9*j}=fnlHd>|aJ)IC=g_SdO{6EL7)O z+33ij2zd=aGzi3Mi>juP%4arZkbJ|HC&8u0IW%$Y3+Lj|*17?nA5+ITd{`3%uq>hB zl!s;!1&Gsyud`5-?Nzc#fSYfQoVX5;5{2qtYkPvksJo773EoL#LcEbeT!Y@Z(^g(y zz5Nm>!swb-irIBW8}u!EgVRe|K%Xy=V3j?YbcO>h4SEFghp$M2O;d@uyXcS!0nKu@ zJFzz^V^-8*N_rSiar{HCXBvX)CHZE=nAfFu08&4vKe3sDH*GYbhubh_>(gc zhfveMt4{%uFoetroX$|AT$RNn5UdE)8-%x@)~R;ZAuWTSzfz?_F2JD#{bQd{h6# zA5i1*^nF;h8Bz}T{%w1eM_5sSVGb8kDm%AwdlkxIZ#2BJ+4qA+x9JbzhM;D znL0ZCDWv10fZHf;5!D;==H{irlb9pqPtCf zcNRVLfYfG>&_}K`L)zkAcuz@npL>Zozk5<0P>pAi3AO27cW5b|qR8u&w5q`V-IP?K zd|KcHvhHs9ihsIE0`WMg6N4$se-J-WG5M z$(B}wuLT+u{&xjT7oA`yQoxR^0Pj0eJ&3n{vx@dU)s{0irM(nYP|oOPh%T*0q)OXx zLVFC4z{Q5Lk*x~b&DdrqmyQ~g?wP`-?4_d;RlNa4f7wT>BHq6(Z1#CIF2Zrq6qP~F zn;xVdcCE<6BR>k7q%l29<7`?IR_8&MpqHhonDWy*i@-5{1{mr;lRn15YT7S30x3h= z#fag>M2vlAs7zXgXjqgY-L%>rC@AnS4g`P>hvzt4=L@gh?-{TtaBURN&b~3mLaO6| z%3CK~CjR+}mwo-g&8``pJ|ZzLw9N7bhE|7oB`v?&j1jugSgjOuD0pr*a;6);4Mv*Z3;vfQ_Lo2Ti9krWt03L6y)d8704fTGSnKHjD zUII+Ed3UMR15Dm1I^Y3(L}_GD58caUQj+*lUc5mhBDa4svwF^^Y*!V&s!VQihiYxa zSKsCJmWa^__j*=O_-XcLWwtU)vW44U<;Mi8{AjS5*Z47+Y@?8{L4J_!kP-Ooc$1Kb zxmD!>v1~GHSs9o?_Y={KaL@bzFP4JJG~W7J-balZUlxM!dzL>lFK2Qv;ERa=YBg)V zi3)v>N;u-cydg4j$QKC~JD@Cdm1D zz8kJVShLFkmObtOR^^YGXkvUuFj{^1qT?}%vMa5Xf6~^c@#*26w?|6)l6v-Zkn^hq#Z|*;OQ3Ic&FR465<5z9 z+{q}7>CTMD&~Nb;jQ(HHnKXP9JHOmZ1O{*=43cCs6_*Xc$3U@}H;S!$L;x0F)(n_D zKX)+o7vRp?nb+G`Yz5j|zh%~uZN1$!Q;v}BuWAo`R}QD|3O*NNeu?I+>LyEGaIsStA z1I=n%4e`O&Lk?GZpR#S}ewfw0VfwE9I7ll(E@~XJ+74+HNC5^t#S}E%?zXfB$`DK_ z8BtB>l&6M}_Jt(aBTM}J0KMVwI{wXjsBwYBrZ7iYBcj*d`VE$=#66L5c^*ek(OzMYs{%PmekOHg4-*MFmOsEfYwt z`}bkxAjPwnTmdZUp#$_EyCfoNrOKaN_y0*{o6%yrI12(Lac}KxO9#-}k`d2uXkSod zxqktqm99{8{fuU1zO}_D2N0VI74v~&%SY_OR&)T>ZN{1cl5t+|t#Y0t8m^ZLv^WP% z*vyQ^P&eLLvYDEuF&%rgQI56CqD~&u0y#-mDnm~Sqv4mWA_9?h|7cu{<6{A;ZiYSW zPl9QgN+Whzf)r}|t3c2(U&||bLX}e$`hQN3%|tsou1ZFP4`udVIdSV^?>uD-NDyfp zFee^l+2BPI=qrHUL0;6e3)cWO+i2gY`ykS!3}|IhxJ0A^;lk4-`EFu=+L7Z-UpYy(Z!yfGj)7MgrwzVwQF> zx0~Pw&5}nY@LrfB9F7<|g7JCO=g`0q^!8|83;-_93vXE`DDNd*e#ECWe8e@~`+Od? z1;DmM{C87}*RFYcgd9ZBGA96e79G(I86;07kjIlwGjR4fq|@QCg=L zz)dw`&E4Zo49}}eX&uGf17w7qs*Kq+)Id>dGHEL7-aBd8qp69dF0X;1uM1*6|LPcT z`{C*V20%rR>p{cHfa?DMlKrZZmUE{7xjoj@pmK=3xAKD@u0K$+rKoOhj1!|;^7ri7 z_kp5xYUNgA+88-M7pqkw*vX~to$5oXU@nv!N#6@I< zO*XB+xYscYu6o!407Ot$P}1{)iI9LI!dbUnS_&ucN1qp^QVGRRdLM*aMtT5>59Deo zh=iaX@QWqg)pi-EDf>>M>-=1mYASt4f$~a#2~^SrxTzTcBDMaxe^(iG18Uk3NS^ze z;Ia`kKdKRfiZ9+e#|+SNA7BD!*Zz=s!q=3vpV!bO$|uQiAMpJpO`w_7elgMFsPhcE z)__=*o(su)ARO_@5U)>pJ%<|vVz8Ft@$e1+%#5JrzY*7E1G7LsJZ6*grQUD2qY zGh%uH-f2Cqp>9Rf3XXHexgbJ(!N|G-SZu7m)1_uDHT%hJTKOr0IC-)7qwMnbF8)7& z4Co%UDZxyDXbET*iZ=>?DM~}}3slt^FYpw5?Z(MHwads;pyFi~DtxSdG1&YQsg;t$ zgRBxT3Pl(hZs6u(sKcybicpNF!d56lM)?Qfza4M;u8>@*oho7D*F?qRspw_i7o700 zwx#BU8z7!*RTi>ojpAW?$m@XgaVE_%5nv(d4S`(}{?Mqid|M*q`(X7K9PlK%x!LA( z>)xlAXU}^0!)V6f$wrYSQ4dn0xr2r^PJ)M*nQScM03VOosNCc)82fY!dJ&}9z~93N zxVziU9$5RRcEDuh_svy9oSw1++*N(Y71cc-+T8W207zLsr#yR-S}L$qX(H+O)9?%) z?XL2xY#z)bsCFOLoV_dnKm1v`M3rc-bhz*xBu5D4o*#3%rKvj@qT+(G-|C~8o9{Ym zl(l~1Ph|kPt$QfnLha=Go#vU%z?_@Zk4MdoIIL(yXFscT)HTLB(;9yeqUK(^F~f(l z4q*HE^;^dZr5VQWtgVa(e8*3(9-K*cT)?q;k0;7A+}rydApH#M7fVXU5zGEZ*5j0V z%rFqcE45sUql~YUs!|VwfxIK3*+1vQI;kLIW11zK-wPKE;p-4Pw=vZc55cX7BBYfk zN~Fn<&c}1#Oc_72OV$FIKwPij^S*Cr6a1ROV4%vck@JVS>?^A|?(FPABsl&qkMj3; z3cT^QJEWK1|CJGKvvLe<2b59jN`Oj#9B`2YapZ#EufXB9vSg1hzy+C*dXg>-D5UD( ztmEbzMf}g501CqYvvKi^(sMC>8npa1NUNS)hg5$Mh@W&!El24v^j6=qQE8(@OZcY#}L&ceVo wHM#!)9OXIw!}-6KH*>ze4)-gb`rU!^lu@Bjb+ literal 0 HcmV?d00001 diff --git a/docs/assets/logo.svg b/docs/assets/logo.svg new file mode 100644 index 0000000..acb0503 --- /dev/null +++ b/docs/assets/logo.svg @@ -0,0 +1,71 @@ + + + +image/svg+xml + + + + + + + + + + \ No newline at end of file diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css new file mode 100644 index 0000000..5829909 --- /dev/null +++ b/docs/stylesheets/extra.css @@ -0,0 +1,9 @@ +:root > * { + --md-primary-fg-color: #009DE1; + --md-primary-fg-color--light: #009DE1; + --md-primary-fg-color--dark: #009DE1; +} + +.md-nav__title { + display: none; +} \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index e474f18..46dace4 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,6 +1,6 @@ site_name: pyharp repo_url: "https://github.com/fchampalimaud/pyharp" -repo_name: "pyharp" +# repo_name: "pyharp" plugins: - search @@ -29,6 +29,13 @@ markdown_extensions: - pymdownx.emoji: emoji_index: !!python/name:material.extensions.emoji.twemoji emoji_generator: !!python/name:material.extensions.emoji.to_svg + - pymdownx.highlight: + anchor_linenums: true + line_spans: __span + pygments_lang_class: true + - pymdownx.inlinehilite + - pymdownx.snippets + - pymdownx.superfences - toc: permalink: "#" @@ -36,26 +43,46 @@ theme: name: material icon: repo: fontawesome/brands/github + logo: assets/logo.svg + favicon: assets/favicon.png features: - content.tooltips - toc.follow + - content.code.copy + # - navigation.sections + - navigation.indexes + - navigation.expand palette: - - media: "(prefers-color-scheme)" - toggle: - icon: material/brightness-auto - name: Switch to light mode - - media: "(prefers-color-scheme: light)" - scheme: default - primary: blue - accent: blue - toggle: - icon: material/weather-sunny - name: Switch to dark mode - - media: "(prefers-color-scheme: dark)" - scheme: slate - primary: red - accent: red - toggle: - icon: material/weather-night - name: Switch to system preference + - media: "(prefers-color-scheme)" + toggle: + icon: material/brightness-auto + name: Switch to light mode + - media: "(prefers-color-scheme: light)" + scheme: default + primary: custom + accent: light-blue + toggle: + icon: material/weather-sunny + name: Switch to dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + primary: custom + accent: light-blue + toggle: + icon: material/weather-night + name: Switch to system preference + +nav: + - Home: index.md + - Examples: + - examples/index.md + - Getting Device Info: examples/get_info.md + - Check Device ID: examples/check_device_id.md + - Wait for Events: examples/wait_for_events.md + - Write and Read from Registers: examples/write_and_read_from_registers.md + - API: + - Device: api/device.md + - Messages: api/messages.md +extra_css: +- stylesheets/extra.css \ No newline at end of file From 53b41a4f84e56f9f1f27e1cf8091602f05027fc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Grilo?= Date: Fri, 4 Apr 2025 18:05:47 +0100 Subject: [PATCH 068/159] Docs: modify home page --- docs/index.md | 70 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 67 insertions(+), 3 deletions(-) diff --git a/docs/index.md b/docs/index.md index f48b45f..335330f 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,4 +1,68 @@ -# Introduction +# pyharp -!!! Warning - Work in Progress! \ No newline at end of file +Python implementation of the Harp protocol for hardware control and data acquisition. + +## Installation + +```bash +uv add pyharp +# or +pip install pyharp +``` + +## Quick Start + +```python +from pyharp.device import Device + +# Connect to a device +device = Device("/dev/ttyUSB0") + +# Get device information +device.info() + +# define register_address +register_address = 32 + +# Read from register +value = device.read_u8(register_address) + +# Write to register +device.write_u8(register_address, value) + +# Disconnect when done +device.disconnect() +``` + +or using the `with` statement: + +```python +from pyharp.device import Device + +with Device("/dev/ttyUSB0") as device: + # Get device information + device.info() + + # define register_address + register_address = 32 + + # Read from register + value = device.read_u8(register_address) + + # Write to register + device.write_u8(register_address, value) +``` + +## for Linux + +### Install UDEV Rules + +Install by either copying `10-harp.rules` over to your `/etc/udev/rules.d` folder or by symlinking it with: +```` +sudo ln -s /absolute/path/to/10-harp.rules /etc/udev/rules.d/10-harp.rules +```` + +Then reload udev rules with +```` +sudo udevadm control --reload-rules +```` From b80d79bb6590843ecf99330067959d38f219e879 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Grilo?= Date: Fri, 4 Apr 2025 18:07:27 +0100 Subject: [PATCH 069/159] Test: comment incorrect tests to fix in the future --- tests/test_device.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/tests/test_device.py b/tests/test_device.py index a5a8e6f..00d9d3b 100644 --- a/tests/test_device.py +++ b/tests/test_device.py @@ -5,14 +5,14 @@ DEFAULT_ADDRESS = 42 - -def test_create_device() -> None: - # open serial connection and load info - device = Device("/dev/ttyUSB0", "dump.bin") - assert device._ser.is_open - device.info() - device.disconnect() - assert not device._ser.is_open +# FIXME +# def test_create_device() -> None: +# # open serial connection and load info +# device = Device("COM74", "dump.bin") +# assert device._ser.is_open +# device.info() +# device.disconnect() +# assert not device._ser.is_open def test_read_U8() -> None: @@ -82,9 +82,10 @@ def test_U8() -> None: # # assert data[0] == '\t' -def test_device_events(device: Device) -> None: - while True: - print(device.event_count()) - for msg in device.get_events(): - print(msg) - time.sleep(0.3) +# FIXME +# def test_device_events(device: Device) -> None: +# while True: +# print(device.event_count()) +# for msg in device.get_events(): +# print(msg) +# time.sleep(0.3) From d87c1ae8b2bacb432784a8be7862eaef27141651 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Grilo?= Date: Fri, 4 Apr 2025 18:14:34 +0100 Subject: [PATCH 070/159] Docs: add documentation to HarpMessage and Device classes --- pyharp/device.py | 549 ++++++++++++++++++++++++++++++++++++++++++--- pyharp/messages.py | 104 ++++++++- 2 files changed, 620 insertions(+), 33 deletions(-) diff --git a/pyharp/device.py b/pyharp/device.py index 0ea24d4..f572781 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -23,12 +23,34 @@ class DeviceMode(Enum): class Device: """ - https://github.com/harp-tech/protocol/blob/master/Device%201.1%201.0%2020220402.pdf + The `Device` class provides the interface for interacting with Harp devices. This implementation of the Harp device was based on the official documentation available on the [harp-tech website](https://harp-tech.org/protocol/Device.html). + + Attributes + ---------- + WHO_AM_I : int + the device ID number. A list of devices can be found [here](https://github.com/harp-tech/protocol/blob/main/whoami.md) + DEFAULT_DEVICE_NAME : str + the device name, i.e. "Behavior". This name is derived by cross-referencing the `WHO_AM_I` identifier with the corresponding device name in the `device_names` dictionary + HW_VERSION_H : int + the major hardware version + HW_VERSION_L : int + the minor hardware version + ASSEMBLY_VERSION : int + the version of the assembled components + HARP_VERSION_H : int + the major Harp core version + HARP_VERSION_L : int + the minor Harp core version + FIRMWARE_VERSION_H : int + the major firmware version + FIRMWARE_VERSION_L : int + the minor firmware version + DEVICE_NAME : str + the device name stored in the Harp device + SERIAL_NUMBER : int, optional + the serial number of the device """ - _ser: HarpSerial - _dump_file_path: Path - WHO_AM_I: int DEFAULT_DEVICE_NAME: str HW_VERSION_H: int @@ -49,6 +71,16 @@ def __init__( dump_file_path: Optional[str] = None, read_timeout_s=1, ): + """ + Parameters + ---------- + serial_port : str + the serial port used to establish the connection with the Harp device. It must be denoted as `ttyUSBx` in Linux and `COMx` in Windows, where `x` is the number of the serial port + dump_file_path: str, optional + the binary file to which all Harp messages will be written + read_timeout_s: float, optional + _TODO_ + """ self.log = logging.getLogger(f"{__name__}.{self.__class__.__name__}") self._serial_port = serial_port if dump_file_path is None: @@ -60,6 +92,9 @@ def __init__( self.load() def load(self) -> None: + """ + Loads the data stored in the device's common registers. + """ self.WHO_AM_I = self._read_who_am_i() self.DEFAULT_DEVICE_NAME = self._read_default_device_name() self.HW_VERSION_H = self._read_hw_version_h() @@ -73,6 +108,9 @@ def load(self) -> None: self.SERIAL_NUMBER = self._read_serial_number() def info(self) -> None: + """ + Prints the device information. + """ print("Device info:") print(f"* Who am I: ({self.WHO_AM_I}) {self.DEFAULT_DEVICE_NAME}") print(f"* HW version: {self.HW_VERSION_H}.{self.HW_VERSION_L}") @@ -84,6 +122,9 @@ def info(self) -> None: print(f"* Mode: {self.read_device_mode().name}") def connect(self) -> None: + """ + Connects to the Harp device. + """ self._ser = HarpSerial( self._serial_port, # "/dev/tty.usbserial-A106C8O9" baudrate=1000000, @@ -95,16 +136,33 @@ def connect(self) -> None: ) def disconnect(self) -> None: + """ + Disconnects from the Harp device. + """ self._ser.close() - def read_device_mode(self) -> DeviceMode: + def _read_device_mode(self) -> DeviceMode: + """ + Reads the current operation mode of the Harp device. + + Returns + ------- + DeviceMode + the current device mode + """ address = CommonRegisters.OPERATION_CTRL reply = self.read_u8(address) return DeviceMode(reply.payload_as_int() & 0x03) def dump_registers(self) -> list: - """Assert the DUMP bit to dump the values of all core and app registers - as Harp Read Reply Messages. + """ + Asserts the DUMP bit to dump the values of all core and app registers + as Harp Read Reply Messages. More information on the DUMP bit can be found [here](https://harp-tech.org/protocol/Device.html#r_operation_ctrl-u16--operation-mode-configuration). + + Returns + ------- + list + the list containing the reply Harp messages for all the device's registers """ address = CommonRegisters.OPERATION_CTRL reg_value = self.read_u8(address).payload_as_int() @@ -119,58 +177,114 @@ def dump_registers(self) -> list: break return replies -# TODO: Not sure if we want to implement these. Delete if no. def set_mode(self, mode: DeviceMode) -> ReplyHarpMessage: - """Change the device's OPMODE. Reply can be ignored.""" + """ + Sets the operation mode of the device. + + Parameters + ---------- + mode : DeviceMode + the new device mode value + + Returns + ------- + ReplyHarpMessage + the reply to the Harp message + """ address = CommonRegisters.OPERATION_CTRL - # Read register first. + + # Read register first reg_value = self.read_u8(address).payload_as_int() reg_value &= ~0x03 # mask off old mode. reg_value |= mode.value reply = self.send(WriteHarpMessage(PayloadType.U8, address, reg_value).frame) return reply - def enable_status_led(self): - """enable the device's status led if one exists.""" + def enable_status_led(self) -> ReplyHarpMessage: + """ + Enables the device's status led. + + Returns + ------- + ReplyHarpMessage + the reply to the Harp message + """ address = CommonRegisters.OPERATION_CTRL - # Read register first. + + # Read register first reg_value = self.read_u8(address).payload_as_int() reg_value |= (1 << 5) reply = self.send(WriteHarpMessage(PayloadType.U8, address, reg_value).frame) - def disable_status_led(self): - """disable the device's status led if one exists.""" + def disable_status_led(self) -> ReplyHarpMessage: + """ + Disables the device's status led. + + Returns + ------- + ReplyHarpMessage + the reply to the Harp message + """ address = CommonRegisters.OPERATION_CTRL - # Read register first. + + # Read register first reg_value = self.read_u8(address).payload_as_int() reg_value &= ~(1 << 5) reply = self.send(WriteHarpMessage(PayloadType.U8, address, reg_value).frame) - def enable_alive_en(self): - """Enable ALIVE_EN such that the device sends an event each second.""" + def enable_alive_en(self) -> ReplyHarpMessage: + """ + Enables the ALIVE_EN bit so that the device sends an event each second. More information on the ALIVE_EN bit can be found [here](https://harp-tech.org/protocol/Device.html#r_operation_ctrl-u16--operation-mode-configuration). + + Returns + ------- + ReplyHarpMessage + the reply to the Harp message + """ address = CommonRegisters.OPERATION_CTRL - # Read register first. + + # Read register first reg_value = self.read_u8(address).payload_as_int() reg_value |= (1 << 7) reply = self.send(WriteHarpMessage(PayloadType.U8, address, reg_value).frame) - def disable_alive_en(self): - """disable ALIVE_EN such that the device does not send an event each second.""" + def disable_alive_en(self) -> ReplyHarpMessage: + """ + Disables the ALIVE_EN bit so that the device does not send an event each second. More information on the ALIVE_EN bit can be found [here](https://harp-tech.org/protocol/Device.html#r_operation_ctrl-u16--operation-mode-configuration). + + Returns + ------- + ReplyHarpMessage + the reply to the Harp message + """ address = CommonRegisters.OPERATION_CTRL - # Read register first. + + # Read register first reg_value = self.read_u8(address).payload[0] reg_value &= (1 << 7) ^ 0xFF # bitwise ~ operator substitute for Python ints. reply = self.send(WriteHarpMessage(PayloadType.U8, address, reg_value).frame) def reset_device(self): address = CommonRegisters.RESET_DEV - # reset_value = 0xFF & (1< ReplyHarpMessage: - """Send a harp message; return the device's reply.""" - #print(f"Sending: {repr(message_bytes)}") + """ + Sends a Harp message. + + Parameters + ---------- + message_bytes : bytearray + the bytearray containing the message to be sent to the device + dump : bool, optional + indicates whether the reply message should be dumped or not + + Returns + ------- + ReplyHarpMessage + the reply to the Harp message + """ self._ser.write(message_bytes) # TODO: handle case where read is None @@ -182,19 +296,37 @@ def send(self, message_bytes: bytearray, dump: bool = True) -> ReplyHarpMessage: return reply def _read(self) -> Union[ReplyHarpMessage, None]: - """(Blocking) Read an incoming serial message.""" + """ + Reads an incoming serial message in a blocking way. + + Returns + ------- + Union[ReplyHarpMessage, None] + the incoming Harp message in case it exists + """ try: return self._ser.msg_q.get(block=True, timeout=self.read_timeout_s) except queue.Empty: return None - + def _dump_reply(self, reply: bytes): + """ + Dumps the reply to a Harp message in the dump file in case it exists. + """ + # TODO: try to handle a None _dump_file_path in a different way assert self._dump_file_path is not None with self._dump_file_path.open(mode="ab") as f: f.write(reply) def get_events(self) -> list[ReplyHarpMessage]: - """Get all events from the event queue.""" + """ + Gets all events from the event queue. + + Returns + ------- + list + the list containing every Harp event message that were on the queue + """ msgs = [] while True: try: @@ -204,50 +336,192 @@ def get_events(self) -> list[ReplyHarpMessage]: return msgs def event_count(self) -> int: - """Get the number of events in the event queue.""" + """ + Gets the number of events in the event queue. + + Returns + ------- + int + the number of events in the event queue + """ return self._ser.event_q.qsize() def read_u8(self, address: int, dump: bool = True) -> ReplyHarpMessage: + """ + Reads the value of a register of type U8. + + Parameters + ---------- + address : int + the register to be read + dump : bool, optional + indicates whether the reply message should be dumped or not + + Returns + ------- + ReplyHarpMessage + the reply to the Harp message that will contain the value read from the register + """ return self.send( ReadHarpMessage(payload_type=PayloadType.U8, address=address).frame, dump ) def read_s8(self, address: int, dump: bool = True) -> ReplyHarpMessage: + """ + Reads the value of a register of type S8. + + Parameters + ---------- + address : int + the register to be read + dump : bool, optional + indicates whether the reply message should be dumped or not + + Returns + ------- + ReplyHarpMessage + the reply to the Harp message that will contain the value read from the register + """ return self.send( ReadHarpMessage(payload_type=PayloadType.S8, address=address).frame, dump ) def read_u16(self, address: int, dump: bool = True) -> ReplyHarpMessage: + """ + Reads the value of a register of type U16. + + Parameters + ---------- + address : int + the register to be read + dump : bool, optional + indicates whether the reply message should be dumped or not + + Returns + ------- + ReplyHarpMessage + the reply to the Harp message that will contain the value read from the register + """ return self.send( ReadHarpMessage(payload_type=PayloadType.U16, address=address).frame, dump ) def read_s16(self, address: int, dump: bool = True) -> ReplyHarpMessage: + """ + Reads the value of a register of type S16. + + Parameters + ---------- + address : int + the register to be read + dump : bool, optional + indicates whether the reply message should be dumped or not + + Returns + ------- + ReplyHarpMessage + the reply to the Harp message that will contain the value read from the register + """ return self.send( ReadHarpMessage(payload_type=PayloadType.S16, address=address).frame, dump ) def read_u32(self, address: int, dump: bool = True) -> ReplyHarpMessage: + """ + Reads the value of a register of type U32. + + Parameters + ---------- + address : int + the register to be read + dump : bool, optional + indicates whether the reply message should be dumped or not + + Returns + ------- + ReplyHarpMessage + the reply to the Harp message that will contain the value read from the register + """ return self.send( ReadHarpMessage(payload_type=PayloadType.U32, address=address).frame, dump ) def read_s32(self, address: int, dump: bool = True) -> ReplyHarpMessage: + """ + Reads the value of a register of type S32. + + Parameters + ---------- + address : int + the register to be read + dump : bool, optional + indicates whether the reply message should be dumped or not + + Returns + ------- + ReplyHarpMessage + the reply to the Harp message that will contain the value read from the register + """ return self.send( ReadHarpMessage(payload_type=PayloadType.S32, address=address).frame, dump ) def read_u64(self, address: int, dump: bool = True) -> ReplyHarpMessage: + """ + Reads the value of a register of type U64. + + Parameters + ---------- + address : int + the register to be read + dump : bool, optional + indicates whether the reply message should be dumped or not + + Returns + ------- + ReplyHarpMessage + the reply to the Harp message that will contain the value read from the register + """ return self.send( ReadHarpMessage(payload_type=PayloadType.U64, address=address).frame, dump ) def read_s64(self, address: int, dump: bool = True) -> ReplyHarpMessage: + """ + Reads the value of a register of type S64. + + Parameters + ---------- + address : int + the register to be read + dump : bool, optional + indicates whether the reply message should be dumped or not + + Returns + ------- + ReplyHarpMessage + the reply to the Harp message that will contain the value read from the register + """ return self.send( ReadHarpMessage(payload_type=PayloadType.S64, address=address).frame, dump ) def read_float(self, address: int, dump: bool = True) -> ReplyHarpMessage: + """ + Reads the value of a register of type Float. + + Parameters + ---------- + address : int + the register to be read + dump : bool, optional + indicates whether the reply message should be dumped or not + + Returns + ------- + ReplyHarpMessage + the reply to the Harp message that will contain the value read from the register + """ return self.send( ReadHarpMessage(payload_type=PayloadType.Float, address=address).frame, dump ) @@ -255,6 +529,23 @@ def read_float(self, address: int, dump: bool = True) -> ReplyHarpMessage: def write_u8( self, address: int, value: int | List[int], dump: bool = True ) -> ReplyHarpMessage: + """ + Writes the value of a register of type U8. + + Parameters + ---------- + address : int + the register to be written on + value: int | List[int] + the value to be written to the register + dump : bool, optional + indicates whether the reply message should be dumped or not + + Returns + ------- + ReplyHarpMessage + the reply to the Harp message + """ return self.send( WriteHarpMessage( payload_type=PayloadType.U8, @@ -267,6 +558,23 @@ def write_u8( def write_s8( self, address: int, value: int | List[int], dump: bool = True ) -> ReplyHarpMessage: + """ + Writes the value of a register of type S8. + + Parameters + ---------- + address : int + the register to be written on + value: int | List[int] + the value to be written to the register + dump : bool, optional + indicates whether the reply message should be dumped or not + + Returns + ------- + ReplyHarpMessage + the reply to the Harp message + """ return self.send( WriteHarpMessage( payload_type=PayloadType.S8, @@ -279,6 +587,23 @@ def write_s8( def write_u16( self, address: int, value: int | List[int], dump: bool = True ) -> ReplyHarpMessage: + """ + Writes the value of a register of type U16. + + Parameters + ---------- + address : int + the register to be written on + value: int | List[int] + the value to be written to the register + dump : bool, optional + indicates whether the reply message should be dumped or not + + Returns + ------- + ReplyHarpMessage + the reply to the Harp message + """ return self.send( WriteHarpMessage( payload_type=PayloadType.U16, @@ -291,6 +616,23 @@ def write_u16( def write_s16( self, address: int, value: int | List[int], dump: bool = True ) -> ReplyHarpMessage: + """ + Writes the value of a register of type S16. + + Parameters + ---------- + address : int + the register to be written on + value: int | List[int] + the value to be written to the register + dump : bool, optional + indicates whether the reply message should be dumped or not + + Returns + ------- + ReplyHarpMessage + the reply to the Harp message + """ return self.send( WriteHarpMessage( payload_type=PayloadType.S16, @@ -303,6 +645,23 @@ def write_s16( def write_u32( self, address: int, value: int | List[int], dump: bool = True ) -> ReplyHarpMessage: + """ + Writes the value of a register of type U32. + + Parameters + ---------- + address : int + the register to be written on + value: int | List[int] + the value to be written to the register + dump : bool, optional + indicates whether the reply message should be dumped or not + + Returns + ------- + ReplyHarpMessage + the reply to the Harp message + """ return self.send( WriteHarpMessage( payload_type=PayloadType.U32, @@ -315,6 +674,23 @@ def write_u32( def write_s32( self, address: int, value: int | List[int], dump: bool = True ) -> ReplyHarpMessage: + """ + Writes the value of a register of type S32. + + Parameters + ---------- + address : int + the register to be written on + value: int | List[int] + the value to be written to the register + dump : bool, optional + indicates whether the reply message should be dumped or not + + Returns + ------- + ReplyHarpMessage + the reply to the Harp message + """ return self.send( WriteHarpMessage( payload_type=PayloadType.S32, @@ -327,6 +703,23 @@ def write_s32( def write_u64( self, address: int, value: int | List[int], dump: bool = True ) -> ReplyHarpMessage: + """ + Writes the value of a register of type U64. + + Parameters + ---------- + address : int + the register to be written on + value: int | List[int] + the value to be written to the register + dump : bool, optional + indicates whether the reply message should be dumped or not + + Returns + ------- + ReplyHarpMessage + the reply to the Harp message + """ return self.send( WriteHarpMessage( payload_type=PayloadType.U64, @@ -339,6 +732,23 @@ def write_u64( def write_s64( self, address: int, value: int | List[int], dump: bool = True ) -> ReplyHarpMessage: + """ + Writes the value of a register of type S64. + + Parameters + ---------- + address : int + the register to be written on + value: int | List[int] + the value to be written to the register + dump : bool, optional + indicates whether the reply message should be dumped or not + + Returns + ------- + ReplyHarpMessage + the reply to the Harp message + """ return self.send( WriteHarpMessage( payload_type=PayloadType.S64, @@ -351,6 +761,23 @@ def write_s64( def write_float( self, address: int, value: float | List[float], dump: bool = True ) -> ReplyHarpMessage: + """ + Writes the value of a register of type Float. + + Parameters + ---------- + address : int + the register to be written on + value: int | List[int] + the value to be written to the register + dump : bool, optional + indicates whether the reply message should be dumped or not + + Returns + ------- + ReplyHarpMessage + the reply to the Harp message + """ return self.send( WriteHarpMessage( payload_type=PayloadType.Float, @@ -361,6 +788,14 @@ def write_float( ) def _read_who_am_i(self) -> int: + """ + Reads the value stored in the `WHO_AM_I` register. + + Returns + ------- + int + the value of the `WHO_AM_I` register. + """ address = CommonRegisters.WHO_AM_I reply: ReplyHarpMessage = self.read_u16(address, dump=False) @@ -368,9 +803,25 @@ def _read_who_am_i(self) -> int: return reply.payload_as_int() def _read_default_device_name(self) -> str: + """ + Returns the `DEFAULT_DEVICE_NAME` by cross-referencing the `WHO_AM_I` with the corresponding device name in the `device_names` dictionary. + + Returns + ------- + str + the default device name. + """ return device_names.get(self.WHO_AM_I, "Unknown device") def _read_hw_version_h(self) -> int: + """ + Reads the value stored in the `HW_VERSION_H` register. + + Returns + ------- + int + the value of the `HW_VERSION_H` register. + """ address = CommonRegisters.HW_VERSION_H reply: ReplyHarpMessage = self.read_u8(address, dump=False) @@ -378,6 +829,14 @@ def _read_hw_version_h(self) -> int: return reply.payload_as_int() def _read_hw_version_l(self) -> int: + """ + Reads the value stored in the `HW_VERSION_L` register. + + Returns + ------- + int + the value of the `HW_VERSION_L` register. + """ address = CommonRegisters.HW_VERSION_L reply: ReplyHarpMessage = self.read_u8(address, dump=False) @@ -385,6 +844,14 @@ def _read_hw_version_l(self) -> int: return reply.payload_as_int() def _read_assembly_version(self) -> int: + """ + Reads the value stored in the `ASSEMBLY_VERSION` register. + + Returns + ------- + int + the value of the `ASSEMBLY_VERSION` register. + """ address = CommonRegisters.ASSEMBLY_VERSION reply: ReplyHarpMessage = self.read_u8(address, dump=False) @@ -420,6 +887,14 @@ def _read_fw_l_version(self) -> int: return reply.payload_as_int() def _read_device_name(self) -> str: + """ + Reads the value stored in the `DEVICE_NAME` register. + + Returns + ------- + int + the value of the `DEVICE_NAME` register. + """ address = CommonRegisters.DEVICE_NAME reply: ReplyHarpMessage = self.read_u8(address, dump=False) @@ -427,6 +902,14 @@ def _read_device_name(self) -> str: return reply.payload_as_string() def _read_serial_number(self) -> int: + """ + Reads the value stored in the `SERIAL_NUMBER` register. + + Returns + ------- + int + the value of the `SERIAL_NUMBER` register. + """ address = CommonRegisters.SERIAL_NUMBER reply: ReplyHarpMessage = self.read_u8(address, dump=False) @@ -437,7 +920,8 @@ def _read_serial_number(self) -> int: return reply.payload_as_int() def __enter__(self): - """Support for using Device with 'with' statement. + """ + Support for using Device with 'with' statement. Returns ------- @@ -449,7 +933,8 @@ def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): - """Cleanup resources when exiting the 'with' block. + """ + Cleanup resources when exiting the 'with' block. Parameters ---------- @@ -462,4 +947,4 @@ def __exit__(self, exc_type, exc_val, exc_tb): """ self.disconnect() # Return False to propagate exceptions that occurred in the with block - return False \ No newline at end of file + return False diff --git a/pyharp/messages.py b/pyharp/messages.py index 2c8e780..2b5aa46 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -8,7 +8,32 @@ class HarpMessage: """ - https://github.com/harp-tech/protocol/blob/main/BinaryProtocol-8bit.md + The `HarpMessage` class implements the Harp message as described in the [protocol](https://harp-tech.org/protocol/BinaryProtocol-8bit.html). + + Attributes + ---------- + frame : bytearray + the bytearray containing the whole Harp message + message_type : MessageType + the message type + length : int + the length parameter of the Harp message + """ + Calculates the checksum of the Harp message. + + Returns + ------- + int + the value of the checksum + """ + address : int + the address of the register to which the Harp message refers to + port : int + indicates the origin or destination of the Harp message in case the device is a hub of Harp devices. The value 255 points to the device itself (default value). + payload_type : PayloadType + the payload type + checksum : int + the sum of all bytes contained in the Harp message """ DEFAULT_PORT: int = 255 @@ -18,6 +43,14 @@ def __init__(self): self._frame = bytearray() def calculate_checksum(self) -> int: + """ + Calculates the checksum of the Harp message. + + Returns + ------- + int + the value of the checksum + """ checksum: int = 0 for i in self.frame: checksum += i @@ -25,34 +58,103 @@ def calculate_checksum(self) -> int: @property def frame(self) -> bytearray: + """ + The bytearray containing the whole Harp message. + + Returns + ------- + bytearray + the bytearray containing the whole Harp message + """ return self._frame @property def message_type(self) -> MessageType: + """ + The message type. + + Returns + ------- + MessageType + the message type + """ return MessageType(self._frame[0]) @property def length(self) -> int: + """ + The length parameter of the Harp message. + + Returns + ------- + int + the length parameter of the Harp message + """ return self._frame[1] @property def address(self) -> int: + """ + The address of the register to which the Harp message refers to. + + Returns + ------- + int + the address of the register to which the Harp message refers to + """ return self._frame[2] @property def port(self) -> int: + """ + Indicates the origin or destination of the Harp message in case the device is a hub of Harp devices. The value 255 points to the device itself (default value). + + Returns + ------- + int + the port value + """ return self._frame[3] @property def payload_type(self) -> PayloadType: + """ + The payload type. + + Returns + ------- + PayloadType + the payload type + """ return PayloadType(self._frame[4]) @property def checksum(self) -> int: + """ + The sum of all bytes contained in the Harp message. + + Returns + ------- + int + the sum of all bytes contained in the Harp message + """ return self._frame[-1] @staticmethod def parse(frame: bytearray) -> ReplyHarpMessage: + """ + Parses a bytearray to a (reply) Harp message. + + Parameters + ---------- + frame : bytearray + the bytearray will be parsed into a (reply) Harp message + + Returns + ------- + ReplyHarpMessage + the Harp message object parsed from the original bytearray + """ return ReplyHarpMessage(frame) From 56b9df37bc1a3b5c143f5e54b0e6ff0d03cf3ee8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Grilo?= Date: Fri, 4 Apr 2025 18:18:17 +0100 Subject: [PATCH 071/159] Refactor: change base.py enums to IntEnums --- pyharp/base.py | 113 +++++++++++++++++++++++++++++++++++++++++---- pyharp/messages.py | 26 ++++------- 2 files changed, 112 insertions(+), 27 deletions(-) diff --git a/pyharp/base.py b/pyharp/base.py index af4219f..f367820 100644 --- a/pyharp/base.py +++ b/pyharp/base.py @@ -1,12 +1,30 @@ -from enum import Enum +from enum import IntEnum -# TODO: Find a way to really hide this from the user +# Bit masks for the PayloadType _isUnsigned: int = 0x00 _isSigned: int = 0x80 _isFloat: int = 0x40 _hasTimestamp: int = 0x10 -class MessageType(Enum): + +class MessageType(IntEnum): + """ + An enumeration of the allowed message types of a Harp message. More information on the MessageType byte of a Harp message can be found [here](https://harp-tech.org/protocol/BinaryProtocol-8bit.html#messagetype-1-byte). + + Attributes + ---------- + READ : int + the value that corresponds to a Read Harp message (1) + WRITE : int + the value that corresponds to a Write Harp message (2) + EVENT : int + the value that corresponds to an Event Harp message (3). Messages of this type are only meant to be send by the device + READ_ERROR : int + the value that corresponds to a Read Error Harp message (9). Messages of this type are only meant to be send by the device + WRITE_ERROR : int + the value that corresponds to a Write Error Harp message (10). Messages of this type are only meant to be send by the device + """ + READ: int = 1 WRITE: int = 2 EVENT: int = 3 @@ -14,17 +32,59 @@ class MessageType(Enum): WRITE_ERROR: int = 10 -class PayloadType(Enum): - U8 = _isUnsigned | 1 # 1 - S8 = _isSigned | 1 # 129 - U16 = _isUnsigned | 2 # 2 - S16 = _isSigned | 2 # 130 +class PayloadType(IntEnum): + """ + An enumeration of the allowed payload types of a Harp message. More information on the PayloadType byte of a Harp message can be found [here](https://harp-tech.org/protocol/BinaryProtocol-8bit.html#payloadtype-1-byte). + + Attributes + ---------- + U8 : PayloadType + the value that corresponds to a message of type U8 + S8 : PayloadType + the value that corresponds to a message of type S8 + U16 : PayloadType + the value that corresponds to a message of type U16 + S16 : PayloadType + the value that corresponds to a message of type S16 + U32 : PayloadType + the value that corresponds to a message of type U32 + S32 : PayloadType + the value that corresponds to a message of type S32 + U64 : PayloadType + the value that corresponds to a message of type U64 + S64 : PayloadType + the value that corresponds to a message of type S64 + Float : PayloadType + the value that corresponds to a message of type Float + TimestampedU8 : PayloadType + the value that corresponds to a message of type TimestampedU8 + TimestampedS8 : PayloadType + the value that corresponds to a message of type TimestampedS8 + TimestampedU16 : PayloadType + the value that corresponds to a message of type TimestampedU16 + TimestampedS16 : PayloadType + the value that corresponds to a message of type TimestampedS16 + TimestampedU32 : PayloadType + the value that corresponds to a message of type TimestampedU32 + TimestampedS32 : PayloadType + the value that corresponds to a message of type TimestampedS32 + TimestampedU64 : PayloadType + the value that corresponds to a message of type TimestampedU64 + TimestampedS64 : PayloadType + the value that corresponds to a message of type TimestampedS64 + TimestampedFloat : PayloadType + the value that corresponds to a message of type TimestampedFloat + """ + + U8 = _isUnsigned | 1 + S8 = _isSigned | 1 + U16 = _isUnsigned | 2 + S16 = _isSigned | 2 U32 = _isUnsigned | 4 S32 = _isSigned | 4 U64 = _isUnsigned | 8 S64 = _isSigned | 8 Float = _isFloat | 4 - Timestamp = _hasTimestamp TimestampedU8 = _hasTimestamp | U8 TimestampedS8 = _hasTimestamp | S8 TimestampedU16 = _hasTimestamp | U16 @@ -36,7 +96,40 @@ class PayloadType(Enum): TimestampedFloat = _hasTimestamp | Float -class CommonRegisters: +class CommonRegisters(IntEnum): + """ + An enumeration with the registers that are common to every Harp device. More information on the common registers can be found [here](https://harp-tech.org/protocol/Device.html#table---list-of-available-common-registers). + + WHO_AM_I : int + the number of the `WHO_AM_I` register + HW_VERSION_H : int + the number of the `HW_VERSION_H` register + HW_VERSION_L : int + the number of the `HW_VERSION_L` register + ASSEMBLY_VERSION : int + the number of the `ASSEMBLY_VERSION` register + HARP_VERSION_H : int + the number of the `HARP_VERSION_H` register + HARP_VERSION_L : int + the number of the `HARP_VERSION_L` register + FIRMWARE_VERSION_H : int + the number of the `FIRMWARE_VERSION_H` register + FIRMWARE_VERSION_L : int + the number of the `FIRMWARE_VERSION_L` register + TIMESTAMP_SECOND : int + the number of the `TIMESTAMP_SECOND` register + TIMESTAMP_MICRO : int + the number of the `TIMESTAMP_MICRO` register + OPERATION_CTRL : int + the number of the `OPERATION_CTRL` register + RESET_DEV : int + the number of the `RESET_DEV` register + DEVICE_NAME : int + the number of the `DEVICE_NAME` register + SERIAL_NUMBER : int + the number of the `SERIAL_NUMBER` register + """ + WHO_AM_I = 0x00 HW_VERSION_H = 0x01 HW_VERSION_L = 0x02 diff --git a/pyharp/messages.py b/pyharp/messages.py index 2b5aa46..6f651bf 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -18,14 +18,6 @@ class HarpMessage: the message type length : int the length parameter of the Harp message - """ - Calculates the checksum of the Harp message. - - Returns - ------- - int - the value of the checksum - """ address : int the address of the register to which the Harp message refers to port : int @@ -186,14 +178,14 @@ def __init__( + int.from_bytes(frame[9:11], byteorder="little", signed=False) * 32e-6 ) # Timestamp is junk if it's not present. - if not (self.payload_type.value & PayloadType.Timestamp.value): + if not (self.payload_type & PayloadType.Timestamp): self._timestamp = None def _parse_payload(self, raw_payload) -> list[int]: """return the payload as a list of ints after parsing it from the raw payload.""" - is_signed = True if (self.payload_type.value & 0x80) else False - is_float = True if (self.payload_type.value & 0x40) else False - bytes_per_word = self.payload_type.value & 0x07 + is_signed = True if (self.payload_type & 0x80) else False + is_float = True if (self.payload_type & 0x40) else False + bytes_per_word = self.payload_type & 0x07 payload_len = len(raw_payload) # payload length in bytes. word_chunks = [ @@ -219,7 +211,7 @@ def __str__(self): if self.payload_type in [PayloadType.Float, PayloadType.TimestampedFloat]: format_str = ".6f" else: - bytes_per_word = self.payload_type.value & 0x07 + bytes_per_word = self.payload_type & 0x07 format_str = f"0{bytes_per_word}b" payload_str = "".join(f"{item:{format_str}} " for item in self.payload) @@ -262,13 +254,13 @@ class ReadHarpMessage(HarpMessage): def __init__(self, payload_type: PayloadType, address: int): self._frame = bytearray() - self._frame.append(self.MESSAGE_TYPE.value) + self._frame.append(self.MESSAGE_TYPE) length: int = 4 self._frame.append(length) self._frame.append(address) self._frame.append(self.DEFAULT_PORT) - self._frame.append(payload_type.value) + self._frame.append(payload_type) self._frame.append(self.calculate_checksum()) @@ -333,12 +325,12 @@ def __init__( payload += val.to_bytes(byte_size, byteorder="little", signed=signed) # Build the frame - self._frame.append(self.MESSAGE_TYPE.value) + self._frame.append(self.MESSAGE_TYPE) # Length is BASE_LENGTH + payload size self._frame.append(self.BASE_LENGTH + len(payload)) self._frame.append(address) self._frame.append(self.DEFAULT_PORT) - self._frame.append(payload_type.value) + self._frame.append(payload_type) self._frame += payload self._frame.append(self.calculate_checksum()) From c67742931b768b446ff369025af05a4190cf8806 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Grilo?= Date: Fri, 4 Apr 2025 18:20:54 +0100 Subject: [PATCH 072/159] Refactor: rename functions to follow a consistent naming pattern --- pyharp/device.py | 48 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/pyharp/device.py b/pyharp/device.py index f572781..4f849e6 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -100,10 +100,10 @@ def load(self) -> None: self.HW_VERSION_H = self._read_hw_version_h() self.HW_VERSION_L = self._read_hw_version_l() self.ASSEMBLY_VERSION = self._read_assembly_version() - self.HARP_VERSION_H = self._read_harp_h_version() - self.HARP_VERSION_L = self._read_harp_l_version() - self.FIRMWARE_VERSION_H = self._read_fw_h_version() - self.FIRMWARE_VERSION_L = self._read_fw_l_version() + self.HARP_VERSION_H = self._read_harp_version_h() + self.HARP_VERSION_L = self._read_harp_version_l() + self.FIRMWARE_VERSION_H = self._read_fw_version_h() + self.FIRMWARE_VERSION_L = self._read_fw_version_l() self.DEVICE_NAME = self._read_device_name() self.SERIAL_NUMBER = self._read_serial_number() @@ -858,28 +858,60 @@ def _read_assembly_version(self) -> int: return reply.payload_as_int() - def _read_harp_h_version(self) -> int: + def _read_harp_version_h(self) -> int: + """ + Reads the value stored in the `HARP_VERSION_H` register. + + Returns + ------- + int + the value of the `HARP_VERSION_H` register. + """ address = CommonRegisters.HARP_VERSION_H reply: ReplyHarpMessage = self.read_u8(address, dump=False) return reply.payload_as_int() - def _read_harp_l_version(self) -> int: + def _read_harp_version_l(self) -> int: + """ + Reads the value stored in the `HARP_VERSION_L` register. + + Returns + ------- + int + the value of the `HARP_VERSION_L` register. + """ address = CommonRegisters.HARP_VERSION_L reply: ReplyHarpMessage = self.read_u8(address, dump=False) return reply.payload_as_int() - def _read_fw_h_version(self) -> int: + def _read_fw_version_h(self) -> int: + """ + Reads the value stored in the `FW_VERSION_H` register. + + Returns + ------- + int + the value of the `FW_VERSION_H` register. + """ address = CommonRegisters.FIRMWARE_VERSION_H reply: ReplyHarpMessage = self.read_u8(address, dump=False) return reply.payload_as_int() - def _read_fw_l_version(self) -> int: + def _read_fw_version_l(self) -> int: + """ + Reads the value stored in the `FW_VERSION_L` register. + + Returns + ------- + int + the value of the `FW_VERSION_L` register. + """ address = CommonRegisters.FIRMWARE_VERSION_L reply: ReplyHarpMessage = self.read_u8(address, dump=False) From 74dd5c4828051233c028d6f565c30254775a0a7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Grilo?= Date: Fri, 4 Apr 2025 18:21:33 +0100 Subject: [PATCH 073/159] Refactor: move DeviceMode to base.py --- pyharp/base.py | 7 +++++++ pyharp/device.py | 10 +--------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/pyharp/base.py b/pyharp/base.py index f367820..388ef5b 100644 --- a/pyharp/base.py +++ b/pyharp/base.py @@ -144,3 +144,10 @@ class CommonRegisters(IntEnum): RESET_DEV = 0x0B DEVICE_NAME = 0x0C SERIAL_NUMBER = 0x0D + + +class DeviceMode(IntEnum): + Standby = 0 + Active = 1 + Reserved = 2 + Speed = 3 diff --git a/pyharp/device.py b/pyharp/device.py index 4f849e6..6205a5a 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -2,25 +2,17 @@ import logging import queue -from enum import Enum from pathlib import Path from typing import List, Optional, Union import serial -from pyharp.base import CommonRegisters, PayloadType +from pyharp.base import CommonRegisters, DeviceMode, PayloadType from pyharp.device_names import device_names from pyharp.harp_serial import HarpSerial from pyharp.messages import ReadHarpMessage, ReplyHarpMessage, WriteHarpMessage -class DeviceMode(Enum): - Standby = 0 - Active = 1 - Reserved = 2 - Speed = 3 - - class Device: """ The `Device` class provides the interface for interacting with Harp devices. This implementation of the Harp device was based on the official documentation available on the [harp-tech website](https://harp-tech.org/protocol/Device.html). From df43bdef69a00cac09daf825de4e9ddbc56d95ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Grilo?= Date: Fri, 4 Apr 2025 18:23:04 +0100 Subject: [PATCH 074/159] Refactor: make some class members private --- pyharp/device.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/pyharp/device.py b/pyharp/device.py index 6205a5a..8576559 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -55,7 +55,11 @@ class Device: DEVICE_NAME: str SERIAL_NUMBER: int - TIMEOUT_S = 1.0 + _ser: HarpSerial + _dump_file_path: Path + _read_timeout_s: float + + _TIMEOUT_S: float = 1.0 def __init__( self, @@ -79,7 +83,9 @@ def __init__( self._dump_file_path = None else: self._dump_file_path = Path() / dump_file_path - self.read_timeout_s = read_timeout_s + self._read_timeout_s = read_timeout_s + + # Connect to the Harp device and load the data stored in the device's common registers self.connect() self.load() @@ -120,7 +126,7 @@ def connect(self) -> None: self._ser = HarpSerial( self._serial_port, # "/dev/tty.usbserial-A106C8O9" baudrate=1000000, - timeout=self.TIMEOUT_S, + timeout=self._TIMEOUT_S, parity=serial.PARITY_NONE, stopbits=1, bytesize=8, @@ -297,7 +303,7 @@ def _read(self) -> Union[ReplyHarpMessage, None]: the incoming Harp message in case it exists """ try: - return self._ser.msg_q.get(block=True, timeout=self.read_timeout_s) + return self._ser.msg_q.get(block=True, timeout=self._read_timeout_s) except queue.Empty: return None From 27dc1c864877692676961fb051b85d25e7a0c42b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Grilo?= Date: Fri, 4 Apr 2025 18:25:00 +0100 Subject: [PATCH 075/159] Refactor: modify way of sending write harp messages --- pyharp/device.py | 54 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 40 insertions(+), 14 deletions(-) diff --git a/pyharp/device.py b/pyharp/device.py index 8576559..0ef3c51 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -164,8 +164,11 @@ def dump_registers(self) -> list: """ address = CommonRegisters.OPERATION_CTRL reg_value = self.read_u8(address).payload_as_int() - reg_value |= 0x08 # Assert DUMP bit - self._ser.write(WriteHarpMessage(PayloadType.U8, address, reg_value).frame) + # Assert DUMP bit + reg_value |= 0x08 + self.write_u8(address, reg_value) + + # Receive the contents of all registers as Harp Read Reply Messages replies = [] while True: msg = self._read() @@ -193,9 +196,14 @@ def set_mode(self, mode: DeviceMode) -> ReplyHarpMessage: # Read register first reg_value = self.read_u8(address).payload_as_int() - reg_value &= ~0x03 # mask off old mode. - reg_value |= mode.value - reply = self.send(WriteHarpMessage(PayloadType.U8, address, reg_value).frame) + + # Clear old operation mode + reg_value &= ~0x03 + + # Set new operation mode + reg_value |= mode + reply = self.write_u8(address, reg_value) + return reply def enable_status_led(self) -> ReplyHarpMessage: @@ -211,8 +219,10 @@ def enable_status_led(self) -> ReplyHarpMessage: # Read register first reg_value = self.read_u8(address).payload_as_int() - reg_value |= (1 << 5) - reply = self.send(WriteHarpMessage(PayloadType.U8, address, reg_value).frame) + reg_value |= 1 << 5 + reply = self.write_u8(address, reg_value) + + return reply def disable_status_led(self) -> ReplyHarpMessage: """ @@ -228,7 +238,9 @@ def disable_status_led(self) -> ReplyHarpMessage: # Read register first reg_value = self.read_u8(address).payload_as_int() reg_value &= ~(1 << 5) - reply = self.send(WriteHarpMessage(PayloadType.U8, address, reg_value).frame) + reply = self.write_u8(address, reg_value) + + return reply def enable_alive_en(self) -> ReplyHarpMessage: """ @@ -243,8 +255,10 @@ def enable_alive_en(self) -> ReplyHarpMessage: # Read register first reg_value = self.read_u8(address).payload_as_int() - reg_value |= (1 << 7) - reply = self.send(WriteHarpMessage(PayloadType.U8, address, reg_value).frame) + reg_value |= 1 << 7 + reply = self.write_u8(address, reg_value) + + return reply def disable_alive_en(self) -> ReplyHarpMessage: """ @@ -259,13 +273,25 @@ def disable_alive_en(self) -> ReplyHarpMessage: # Read register first reg_value = self.read_u8(address).payload[0] - reg_value &= (1 << 7) ^ 0xFF # bitwise ~ operator substitute for Python ints. - reply = self.send(WriteHarpMessage(PayloadType.U8, address, reg_value).frame) + reg_value &= ~(1 << 7) + reply = self.write_u8(address, reg_value) - def reset_device(self): + return reply + + def reset_device(self) -> ReplyHarpMessage: + """ + Resets the device and reboots with all the registers with the default values. Beware that the EEPROM will be erased. More information on the reset device register can be found [here](https://harp-tech.org/protocol/Device.html#r_reset_dev-u8--reset-device-and-save-non-volatile-registers). + + Returns + ------- + ReplyHarpMessage + the reply to the Harp message + """ address = CommonRegisters.RESET_DEV reset_value = 0x01 - self._ser.write(WriteHarpMessage(PayloadType.U8, address, reset_value).frame) + reply = self.write_u8(address, reset_value) + + return reply def send(self, message_bytes: bytearray, dump: bool = True) -> ReplyHarpMessage: """ From 355e90c3cb36469ca96623e99d86a20ece75c816 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Grilo?= Date: Fri, 4 Apr 2025 18:25:29 +0100 Subject: [PATCH 076/159] Refactor: misc changes --- pyharp/device.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pyharp/device.py b/pyharp/device.py index 0ef3c51..b07db19 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -65,7 +65,7 @@ def __init__( self, serial_port: str, dump_file_path: Optional[str] = None, - read_timeout_s=1, + read_timeout_s: float = 1, ): """ Parameters @@ -114,10 +114,12 @@ def info(self) -> None: print(f"* HW version: {self.HW_VERSION_H}.{self.HW_VERSION_L}") print(f"* Assembly version: {self.ASSEMBLY_VERSION}") print(f"* HARP version: {self.HARP_VERSION_H}.{self.HARP_VERSION_L}") - print(f"* Firmware version: {self.FIRMWARE_VERSION_H}.{self.FIRMWARE_VERSION_L}") + print( + f"* Firmware version: {self.FIRMWARE_VERSION_H}.{self.FIRMWARE_VERSION_L}" + ) print(f"* Device user name: {self.DEVICE_NAME}") print(f"* Serial number: {self.SERIAL_NUMBER}") - print(f"* Mode: {self.read_device_mode().name}") + print(f"* Mode: {self._read_device_mode().name}") def connect(self) -> None: """ From 2adbdc39da82596e330788c7df927ba49a1ac621 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Grilo?= Date: Sun, 6 Apr 2025 14:05:16 +0100 Subject: [PATCH 077/159] Feature: add HarpMessage.create static method --- pyharp/messages.py | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/pyharp/messages.py b/pyharp/messages.py index 6f651bf..b3c905a 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -149,8 +149,41 @@ def parse(frame: bytearray) -> ReplyHarpMessage: """ return ReplyHarpMessage(frame) + @staticmethod + def create( + message_type: MessageType, + address: int, + payload_type: PayloadType, + value: int | list[int] | float | list[float] = None, + ) -> HarpMessage: + """ + Creates a Harp message. + + Parameters + ---------- + message_type : MessageType + the message type. It can only be of type READ or WRITE + address : int + the address of the register that the message will interact with + payload_type : PayloadType + the payload type + value: int | list[int] | float | list[float], optional + the payload of the message. If message_type == MessageType.WRITE, the value cannot be None + """ + if message_type == MessageType.READ: + return ReadHarpMessage(payload_type, address) + elif message_type == MessageType.WRITE and value is not None: + return WriteHarpMessage(payload_type, address, value) + elif message_type != MessageType.READ and message_type != MessageType.WRITE: + raise Exception( + "The only valid message types are MessageType.READ and MessageType.Write!" + ) + else: + raise Exception( + "The value cannot be None is message type is equal to MessageType.WRITE!" + ) + -# A Response Message from a harp device. class ReplyHarpMessage(HarpMessage): """ A Response Message from a harp device. From 152a28de38eca40314fef33db14184d0fd0c52e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Grilo?= Date: Sun, 6 Apr 2025 14:10:30 +0100 Subject: [PATCH 078/159] Docs: add comments to the remaining code --- pyharp/base.py | 2 + pyharp/device.py | 2 +- pyharp/harp_serial.py | 80 ++++++++++++++++++++++--- pyharp/messages.py | 136 ++++++++++++++++++++++++++++++++++++------ 4 files changed, 194 insertions(+), 26 deletions(-) diff --git a/pyharp/base.py b/pyharp/base.py index 388ef5b..6962a0e 100644 --- a/pyharp/base.py +++ b/pyharp/base.py @@ -100,6 +100,8 @@ class CommonRegisters(IntEnum): """ An enumeration with the registers that are common to every Harp device. More information on the common registers can be found [here](https://harp-tech.org/protocol/Device.html#table---list-of-available-common-registers). + Attributes + ---------- WHO_AM_I : int the number of the `WHO_AM_I` register HW_VERSION_H : int diff --git a/pyharp/device.py b/pyharp/device.py index b07db19..4e4f8d2 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -71,7 +71,7 @@ def __init__( Parameters ---------- serial_port : str - the serial port used to establish the connection with the Harp device. It must be denoted as `ttyUSBx` in Linux and `COMx` in Windows, where `x` is the number of the serial port + the serial port used to establish the connection with the Harp device. It must be denoted as `/dev/ttyUSBx` in Linux and `COMx` in Windows, where `x` is the number of the serial port dump_file_path: str, optional the binary file to which all Harp messages will be written read_timeout_s: float, optional diff --git a/pyharp/harp_serial.py b/pyharp/harp_serial.py index 3b9d4a2..bed20b3 100644 --- a/pyharp/harp_serial.py +++ b/pyharp/harp_serial.py @@ -1,8 +1,9 @@ -from typing import Union -from functools import partial import logging import queue import threading +from functools import partial +from typing import Union + import serial import serial.threaded @@ -10,40 +11,91 @@ class HarpSerialProtocol(serial.threaded.Protocol): + """ + The `HarpSerialProtocol` class deals with the data received from the serial communication. + """ + _read_q: queue.Queue - def __init__(self, _read_q: queue.Queue, *args, **kwargs): - self._read_q = _read_q + def __init__(self, read_q: queue.Queue, *args, **kwargs): + """ + Parameters + ---------- + read_q : queue.Queue + the queue to where the data received will be put + """ + self._read_q = read_q super().__init__(*args, **kwargs) def connection_made(self, transport: serial.threaded.ReaderThread) -> None: - # print(f"Connected to {transport.serial.port}") + """ + _TODO_ + + Parameters + ---------- + transport : serial.threaded.ReaderThread + _TODO_ + """ return super().connection_made(transport) def data_received(self, data: bytes) -> None: + """ + Receives data from the serial commmunication. + + Parameters + ---------- + data : bytes + the data received from the serial communication + """ for byte in data: self._read_q.put(byte) return super().data_received(data) def connection_lost(self, exc: Union[BaseException, None]) -> None: - # print(f"Lost connection!") + """ + _TODO_ + + Parameters + ---------- + exc : exc: Union[BaseException, None] + _TODO_ + """ return super().connection_lost(exc) class HarpSerial: + """ + The `HarpSerial` deals with the received Harp messages and separates the events from the remaining messages. + + Attributes + ---------- + msg_q : queue.Queue + the queue containing the Harp messages that are not of the type `MessageType.EVENT` + event_q : queue.Queue + the queue containing the Harp messages of `MessageType.EVENT` + """ msg_q: queue.Queue event_q: queue.Queue def __init__(self, serial_port: str, **kwargs): + """ + Parameters + ---------- + serial_port : str + the serial port used to establish the connection with the Harp device. It must be denoted as `/dev/ttyUSBx` in Linux and `COMx` in Windows, where `x` is the number of the serial port + """ + # Connect to the Harp device self._ser = serial.Serial(serial_port, **kwargs) self.log = logging.getLogger(f"{__name__}.{self.__class__.__name__}") + # Initialize the message queues self._read_q = queue.Queue() self.msg_q = queue.Queue() self.event_q = queue.Queue() + # Start the thread with the `HarpSerialProtocol` self._reader = serial.threaded.ReaderThread( self._ser, partial(HarpSerialProtocol, self._read_q), @@ -51,6 +103,7 @@ def __init__(self, serial_port: str, **kwargs): self._reader.start() transport, protocol = self._reader.connect() + # Start the thread that parses and separates the events from the remaining messages self._parse_thread = threading.Thread( target=self.parse_harp_msgs_threaded, daemon=True, @@ -58,26 +111,39 @@ def __init__(self, serial_port: str, **kwargs): self._parse_thread.start() def close(self): + """ + Closes the serial port. + """ self._reader.close() def write(self, data): + """ + Writes data to the Harp device. + """ self._reader.write(data) def parse_harp_msgs_threaded(self): + """ + Parses the Harp messages and separates the events from the remaining messages. + """ while True: - message_type = self._read_q.get(1) # byte array with only one byte + # Gets the Harp message bytes based on the length byte of the message + message_type = self._read_q.get(1) message_length = self._read_q.get(1) message_content = bytes([self._read_q.get() for _ in range(message_length)]) self.log.debug(f"reply (type): {message_type}") self.log.debug(f"reply (length): {message_length}") self.log.debug(f"reply (payload): {message_content}") + # Reconstructs the message into a bytearray frame = bytearray() frame.append(message_type) frame.append(message_length) frame += message_content + # Parses the bytearray into a ReplyHarpMessage object msg = HarpMessage.parse(frame) + # Puts the parsed Harp message into the correct queue if msg.message_type == MessageType.EVENT: self.event_q.put(msg) else: diff --git a/pyharp/messages.py b/pyharp/messages.py index b3c905a..690e987 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -186,7 +186,14 @@ def create( class ReplyHarpMessage(HarpMessage): """ - A Response Message from a harp device. + A response message from a Harp device. + + Attributes + ---------- + payload : Union[int, list[int]] + the message payload formatted as the appropriate type + timestamp : float + the Harp timestamp at which the message was sent """ def __init__( @@ -194,32 +201,48 @@ def __init__( frame: bytearray, ): """ - - :param frame: the serialized message frame. + Parameters + ---------- + frame : bytearray + the Harp message in bytearray format """ self._frame = frame - # retrieve all content from 11 (where payload starts) until the checksum (not inclusive) + # Retrieve all content from 11 (where payload starts) until the checksum (not inclusive) self._raw_payload = frame[11:-1] - self._payload = self._parse_payload( - self._raw_payload - ) # payload formatted as list[payload type] + + # Format payload as list[PayloadType] + self._payload = self._parse_payload(self._raw_payload) # Assign timestamp after _payload since @properties all rely on self._payload. self._timestamp = ( int.from_bytes(frame[5:9], byteorder="little", signed=False) + int.from_bytes(frame[9:11], byteorder="little", signed=False) * 32e-6 ) + # Timestamp is junk if it's not present. if not (self.payload_type & PayloadType.Timestamp): self._timestamp = None - def _parse_payload(self, raw_payload) -> list[int]: - """return the payload as a list of ints after parsing it from the raw payload.""" + # TODO: handle the case of the payload being of type float + def _parse_payload(self, raw_payload: bytearray) -> list[int]: + """ + Returns the payload as a list of ints after parsing it from the raw payload. + + Parameters + ---------- + raw_payload : bytearray + the bytearray containing the raw payload + + Returns + ------- + list[int] + the list of ints containing the payload + """ is_signed = True if (self.payload_type & 0x80) else False is_float = True if (self.payload_type & 0x40) else False bytes_per_word = self.payload_type & 0x07 - payload_len = len(raw_payload) # payload length in bytes. + payload_len = len(raw_payload) word_chunks = [ raw_payload[i : i + bytes_per_word] @@ -230,15 +253,29 @@ def _parse_payload(self, raw_payload) -> list[int]: int.from_bytes(chunk, byteorder="little", signed=is_signed) for chunk in word_chunks ] - else: # handle float case. + else: # TODO: handle float case return [struct.unpack(" str: + """ + Prints debug representation of the reply message. + + Returns + ------- + str + the debug representation of the reply message + """ return self.__str__() + f"\r\nRaw Frame: {self.frame}" - def __str__(self): - """Print friendly representation of a reply message.""" + def __str__(self) -> str: + """ + Prints friendly representation of the reply message. + + Returns + ------- + str + the representation of the reply message + """ payload_str = "" format_str = "" if self.payload_type in [PayloadType.Float, PayloadType.TimestampedFloat]: @@ -261,27 +298,72 @@ def __str__(self): + f"Checksum: {self.checksum}" ) + # TODO: handle float case @property def payload(self) -> Union[int, list[int]]: - """return the payload formatted as the appropriate type.""" + """ + The message payload formatted as the appropriate type. + + Returns + ------- + Union[int, list[int]] + the message payload formatted as the appropriate type + """ return self._payload @property def timestamp(self) -> float: + """ + The Harp timestamp at which the message was sent. + + Returns + ------- + float + the Harp timestamp at which the message was sent + """ return self._timestamp + # TODO: does this function makes sense since self.payload() already exists? def payload_as_int(self) -> int: + """ + Returns the payload as an int. + + Returns + ------- + int + the payload parsed as an int + """ return self.payload[0] def payload_as_string(self) -> str: + """ + Returns the payload as a str. + + Returns + ------- + str + the payload parsed as a str + """ return self._raw_payload.decode("utf-8") + # TODO: handle float case and/or delete functional altogether def payload_as_float(self) -> float: - return self.payload[0] # already parsed. + """ + Returns the payload as a float. + + Returns + ------- + float + the payload parsed as a float + """ + return self.payload[0] -# A Read Request Message sent to a harp device. class ReadHarpMessage(HarpMessage): + """ + A read Harp message sent to a Harp device. + """ + MESSAGE_TYPE: int = MessageType.READ def __init__(self, payload_type: PayloadType, address: int): @@ -298,6 +380,15 @@ def __init__(self, payload_type: PayloadType, address: int): class WriteHarpMessage(HarpMessage): + """ + A write Harp message sent to a Harp device. + + Attributes + ---------- + payload : Union[int, list[int]] + the payload sent in the write Harp message + """ + BASE_LENGTH: int = 5 BASE_LENGTH: int = 4 MESSAGE_TYPE: int = MessageType.WRITE @@ -367,8 +458,17 @@ def __init__( self._frame += payload self._frame.append(self.calculate_checksum()) + # TODO: handle float and array cases @property def payload(self) -> Union[int, list[int]]: + """ + The payload sent in the write Harp message. + + Returns + ------- + Union[int, list[int]] + the payload sent in the write Harp message + """ match self.payload_type: case PayloadType.U8: return self._frame[5] From 09e0dd6a948e1801ac6f356b2c148dfa35ac6734 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Grilo?= Date: Sun, 6 Apr 2025 14:13:31 +0100 Subject: [PATCH 079/159] Feature: add classes in base.py to root pyharp namespace --- pyharp/__init__.py | 2 ++ pyharp/device.py | 2 +- pyharp/messages.py | 2 +- tests/test_device.py | 1 + tests/test_messages.py | 2 +- 5 files changed, 6 insertions(+), 3 deletions(-) diff --git a/pyharp/__init__.py b/pyharp/__init__.py index 3dc1f76..fc569a5 100644 --- a/pyharp/__init__.py +++ b/pyharp/__init__.py @@ -1 +1,3 @@ +from pyharp.base import CommonRegisters, DeviceMode, MessageType, PayloadType + __version__ = "0.1.0" diff --git a/pyharp/device.py b/pyharp/device.py index 4e4f8d2..b7fa03d 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -7,7 +7,7 @@ import serial -from pyharp.base import CommonRegisters, DeviceMode, PayloadType +from pyharp import CommonRegisters, DeviceMode, MessageType, PayloadType from pyharp.device_names import device_names from pyharp.harp_serial import HarpSerial from pyharp.messages import ReadHarpMessage, ReplyHarpMessage, WriteHarpMessage diff --git a/pyharp/messages.py b/pyharp/messages.py index 690e987..526948d 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -3,7 +3,7 @@ import struct from typing import List, Union -from pyharp.base import MessageType, PayloadType +from pyharp import MessageType, PayloadType class HarpMessage: diff --git a/tests/test_device.py b/tests/test_device.py index 00d9d3b..798866e 100644 --- a/tests/test_device.py +++ b/tests/test_device.py @@ -1,5 +1,6 @@ import time +from pyharp import MessageType, PayloadType from pyharp.device import Device from pyharp.messages import HarpMessage, ReplyHarpMessage diff --git a/tests/test_messages.py b/tests/test_messages.py index 93223b4..2be8ce0 100644 --- a/tests/test_messages.py +++ b/tests/test_messages.py @@ -1,4 +1,4 @@ -from pyharp.base import CommonRegisters, PayloadType +from pyharp import CommonRegisters, PayloadType from pyharp.messages import MessageType, ReadHarpMessage, WriteHarpMessage DEFAULT_ADDRESS = 42 From c7fea61b3a7d97a231cee86d7efecae994a5c518 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Grilo?= Date: Sun, 6 Apr 2025 14:20:39 +0100 Subject: [PATCH 080/159] Feature: remove read_xx and write_xx Device methods --- README.md | 12 +- docs/index.md | 12 +- pyharp/device.py | 565 ++++++++----------------------------------- tests/test_device.py | 10 +- 4 files changed, 122 insertions(+), 477 deletions(-) diff --git a/README.md b/README.md index 335330f..d1485ef 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,9 @@ pip install pyharp ## Quick Start ```python +from pyharp import MessageType, PayloadType from pyharp.device import Device +from pyharp.messages import HarpMessage # Connect to a device device = Device("/dev/ttyUSB0") @@ -25,10 +27,10 @@ device.info() register_address = 32 # Read from register -value = device.read_u8(register_address) +value = device.send(HarpMessage.create(MessageType.READ, register_address, PayloadType.U8).frame) # Write to register -device.write_u8(register_address, value) +device.send(HarpMessage.create(MessageType.WRITE, register_address, PayloadType.U8, value).frame) # Disconnect when done device.disconnect() @@ -37,7 +39,9 @@ device.disconnect() or using the `with` statement: ```python +from pyharp import MessageType, PayloadType from pyharp.device import Device +from pyharp.messages import HarpMessage with Device("/dev/ttyUSB0") as device: # Get device information @@ -47,10 +51,10 @@ with Device("/dev/ttyUSB0") as device: register_address = 32 # Read from register - value = device.read_u8(register_address) + value = device.send(HarpMessage.create(MessageType.READ, register_address, PayloadType.U8).frame) # Write to register - device.write_u8(register_address, value) + device.send(HarpMessage.create(MessageType.WRITE, register_address, PayloadType.U8, value).frame) ``` ## for Linux diff --git a/docs/index.md b/docs/index.md index 335330f..d1485ef 100644 --- a/docs/index.md +++ b/docs/index.md @@ -13,7 +13,9 @@ pip install pyharp ## Quick Start ```python +from pyharp import MessageType, PayloadType from pyharp.device import Device +from pyharp.messages import HarpMessage # Connect to a device device = Device("/dev/ttyUSB0") @@ -25,10 +27,10 @@ device.info() register_address = 32 # Read from register -value = device.read_u8(register_address) +value = device.send(HarpMessage.create(MessageType.READ, register_address, PayloadType.U8).frame) # Write to register -device.write_u8(register_address, value) +device.send(HarpMessage.create(MessageType.WRITE, register_address, PayloadType.U8, value).frame) # Disconnect when done device.disconnect() @@ -37,7 +39,9 @@ device.disconnect() or using the `with` statement: ```python +from pyharp import MessageType, PayloadType from pyharp.device import Device +from pyharp.messages import HarpMessage with Device("/dev/ttyUSB0") as device: # Get device information @@ -47,10 +51,10 @@ with Device("/dev/ttyUSB0") as device: register_address = 32 # Read from register - value = device.read_u8(register_address) + value = device.send(HarpMessage.create(MessageType.READ, register_address, PayloadType.U8).frame) # Write to register - device.write_u8(register_address, value) + device.send(HarpMessage.create(MessageType.WRITE, register_address, PayloadType.U8, value).frame) ``` ## for Linux diff --git a/pyharp/device.py b/pyharp/device.py index b7fa03d..27212fd 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -3,14 +3,14 @@ import logging import queue from pathlib import Path -from typing import List, Optional, Union +from typing import Optional, Union import serial from pyharp import CommonRegisters, DeviceMode, MessageType, PayloadType from pyharp.device_names import device_names from pyharp.harp_serial import HarpSerial -from pyharp.messages import ReadHarpMessage, ReplyHarpMessage, WriteHarpMessage +from pyharp.messages import HarpMessage, ReplyHarpMessage class Device: @@ -151,7 +151,9 @@ def _read_device_mode(self) -> DeviceMode: the current device mode """ address = CommonRegisters.OPERATION_CTRL - reply = self.read_u8(address) + reply = self.send( + HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame + ) return DeviceMode(reply.payload_as_int() & 0x03) def dump_registers(self) -> list: @@ -165,10 +167,16 @@ def dump_registers(self) -> list: the list containing the reply Harp messages for all the device's registers """ address = CommonRegisters.OPERATION_CTRL - reg_value = self.read_u8(address).payload_as_int() + reg_value = self.send( + HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame + ).payload_as_int() # Assert DUMP bit reg_value |= 0x08 - self.write_u8(address, reg_value) + self.send( + HarpMessage.create( + MessageType.WRITE, address, PayloadType.U8, reg_value + ).frame + ) # Receive the contents of all registers as Harp Read Reply Messages replies = [] @@ -197,14 +205,20 @@ def set_mode(self, mode: DeviceMode) -> ReplyHarpMessage: address = CommonRegisters.OPERATION_CTRL # Read register first - reg_value = self.read_u8(address).payload_as_int() + reg_value = self.send( + HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame + ).payload_as_int() # Clear old operation mode reg_value &= ~0x03 # Set new operation mode reg_value |= mode - reply = self.write_u8(address, reg_value) + reply = self.send( + HarpMessage.create( + MessageType.WRITE, address, PayloadType.U8, reg_value + ).frame + ) return reply @@ -220,9 +234,15 @@ def enable_status_led(self) -> ReplyHarpMessage: address = CommonRegisters.OPERATION_CTRL # Read register first - reg_value = self.read_u8(address).payload_as_int() + reg_value = self.send( + HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame + ).payload_as_int() reg_value |= 1 << 5 - reply = self.write_u8(address, reg_value) + reply = self.send( + HarpMessage.create( + MessageType.WRITE, address, PayloadType.U8, reg_value + ).frame + ) return reply @@ -238,9 +258,15 @@ def disable_status_led(self) -> ReplyHarpMessage: address = CommonRegisters.OPERATION_CTRL # Read register first - reg_value = self.read_u8(address).payload_as_int() + reg_value = self.send( + HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame + ).payload_as_int() reg_value &= ~(1 << 5) - reply = self.write_u8(address, reg_value) + reply = self.send( + HarpMessage.create( + MessageType.WRITE, address, PayloadType.U8, reg_value + ).frame + ) return reply @@ -256,9 +282,15 @@ def enable_alive_en(self) -> ReplyHarpMessage: address = CommonRegisters.OPERATION_CTRL # Read register first - reg_value = self.read_u8(address).payload_as_int() + reg_value = self.send( + HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame + ).payload_as_int() reg_value |= 1 << 7 - reply = self.write_u8(address, reg_value) + reply = self.send( + HarpMessage.create( + MessageType.WRITE, address, PayloadType.U8, reg_value + ).frame + ) return reply @@ -274,9 +306,15 @@ def disable_alive_en(self) -> ReplyHarpMessage: address = CommonRegisters.OPERATION_CTRL # Read register first - reg_value = self.read_u8(address).payload[0] + reg_value = self.send( + HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame + ).payload[0] reg_value &= ~(1 << 7) - reply = self.write_u8(address, reg_value) + reply = self.send( + HarpMessage.create( + MessageType.WRITE, address, PayloadType.U8, reg_value + ).frame + ) return reply @@ -291,7 +329,11 @@ def reset_device(self) -> ReplyHarpMessage: """ address = CommonRegisters.RESET_DEV reset_value = 0x01 - reply = self.write_u8(address, reset_value) + reply = self.send( + HarpMessage.create( + MessageType.WRITE, address, PayloadType.U8, reset_value + ).frame + ) return reply @@ -372,447 +414,6 @@ def event_count(self) -> int: """ return self._ser.event_q.qsize() - def read_u8(self, address: int, dump: bool = True) -> ReplyHarpMessage: - """ - Reads the value of a register of type U8. - - Parameters - ---------- - address : int - the register to be read - dump : bool, optional - indicates whether the reply message should be dumped or not - - Returns - ------- - ReplyHarpMessage - the reply to the Harp message that will contain the value read from the register - """ - return self.send( - ReadHarpMessage(payload_type=PayloadType.U8, address=address).frame, dump - ) - - def read_s8(self, address: int, dump: bool = True) -> ReplyHarpMessage: - """ - Reads the value of a register of type S8. - - Parameters - ---------- - address : int - the register to be read - dump : bool, optional - indicates whether the reply message should be dumped or not - - Returns - ------- - ReplyHarpMessage - the reply to the Harp message that will contain the value read from the register - """ - return self.send( - ReadHarpMessage(payload_type=PayloadType.S8, address=address).frame, dump - ) - - def read_u16(self, address: int, dump: bool = True) -> ReplyHarpMessage: - """ - Reads the value of a register of type U16. - - Parameters - ---------- - address : int - the register to be read - dump : bool, optional - indicates whether the reply message should be dumped or not - - Returns - ------- - ReplyHarpMessage - the reply to the Harp message that will contain the value read from the register - """ - return self.send( - ReadHarpMessage(payload_type=PayloadType.U16, address=address).frame, dump - ) - - def read_s16(self, address: int, dump: bool = True) -> ReplyHarpMessage: - """ - Reads the value of a register of type S16. - - Parameters - ---------- - address : int - the register to be read - dump : bool, optional - indicates whether the reply message should be dumped or not - - Returns - ------- - ReplyHarpMessage - the reply to the Harp message that will contain the value read from the register - """ - return self.send( - ReadHarpMessage(payload_type=PayloadType.S16, address=address).frame, dump - ) - - def read_u32(self, address: int, dump: bool = True) -> ReplyHarpMessage: - """ - Reads the value of a register of type U32. - - Parameters - ---------- - address : int - the register to be read - dump : bool, optional - indicates whether the reply message should be dumped or not - - Returns - ------- - ReplyHarpMessage - the reply to the Harp message that will contain the value read from the register - """ - return self.send( - ReadHarpMessage(payload_type=PayloadType.U32, address=address).frame, dump - ) - - def read_s32(self, address: int, dump: bool = True) -> ReplyHarpMessage: - """ - Reads the value of a register of type S32. - - Parameters - ---------- - address : int - the register to be read - dump : bool, optional - indicates whether the reply message should be dumped or not - - Returns - ------- - ReplyHarpMessage - the reply to the Harp message that will contain the value read from the register - """ - return self.send( - ReadHarpMessage(payload_type=PayloadType.S32, address=address).frame, dump - ) - - def read_u64(self, address: int, dump: bool = True) -> ReplyHarpMessage: - """ - Reads the value of a register of type U64. - - Parameters - ---------- - address : int - the register to be read - dump : bool, optional - indicates whether the reply message should be dumped or not - - Returns - ------- - ReplyHarpMessage - the reply to the Harp message that will contain the value read from the register - """ - return self.send( - ReadHarpMessage(payload_type=PayloadType.U64, address=address).frame, dump - ) - - def read_s64(self, address: int, dump: bool = True) -> ReplyHarpMessage: - """ - Reads the value of a register of type S64. - - Parameters - ---------- - address : int - the register to be read - dump : bool, optional - indicates whether the reply message should be dumped or not - - Returns - ------- - ReplyHarpMessage - the reply to the Harp message that will contain the value read from the register - """ - return self.send( - ReadHarpMessage(payload_type=PayloadType.S64, address=address).frame, dump - ) - - def read_float(self, address: int, dump: bool = True) -> ReplyHarpMessage: - """ - Reads the value of a register of type Float. - - Parameters - ---------- - address : int - the register to be read - dump : bool, optional - indicates whether the reply message should be dumped or not - - Returns - ------- - ReplyHarpMessage - the reply to the Harp message that will contain the value read from the register - """ - return self.send( - ReadHarpMessage(payload_type=PayloadType.Float, address=address).frame, dump - ) - - def write_u8( - self, address: int, value: int | List[int], dump: bool = True - ) -> ReplyHarpMessage: - """ - Writes the value of a register of type U8. - - Parameters - ---------- - address : int - the register to be written on - value: int | List[int] - the value to be written to the register - dump : bool, optional - indicates whether the reply message should be dumped or not - - Returns - ------- - ReplyHarpMessage - the reply to the Harp message - """ - return self.send( - WriteHarpMessage( - payload_type=PayloadType.U8, - address=address, - value=value, - ).frame, - dump=dump, - ) - - def write_s8( - self, address: int, value: int | List[int], dump: bool = True - ) -> ReplyHarpMessage: - """ - Writes the value of a register of type S8. - - Parameters - ---------- - address : int - the register to be written on - value: int | List[int] - the value to be written to the register - dump : bool, optional - indicates whether the reply message should be dumped or not - - Returns - ------- - ReplyHarpMessage - the reply to the Harp message - """ - return self.send( - WriteHarpMessage( - payload_type=PayloadType.S8, - address=address, - value=value, - ).frame, - dump=dump, - ) - - def write_u16( - self, address: int, value: int | List[int], dump: bool = True - ) -> ReplyHarpMessage: - """ - Writes the value of a register of type U16. - - Parameters - ---------- - address : int - the register to be written on - value: int | List[int] - the value to be written to the register - dump : bool, optional - indicates whether the reply message should be dumped or not - - Returns - ------- - ReplyHarpMessage - the reply to the Harp message - """ - return self.send( - WriteHarpMessage( - payload_type=PayloadType.U16, - address=address, - value=value, - ).frame, - dump=dump, - ) - - def write_s16( - self, address: int, value: int | List[int], dump: bool = True - ) -> ReplyHarpMessage: - """ - Writes the value of a register of type S16. - - Parameters - ---------- - address : int - the register to be written on - value: int | List[int] - the value to be written to the register - dump : bool, optional - indicates whether the reply message should be dumped or not - - Returns - ------- - ReplyHarpMessage - the reply to the Harp message - """ - return self.send( - WriteHarpMessage( - payload_type=PayloadType.S16, - address=address, - value=value, - ).frame, - dump=dump, - ) - - def write_u32( - self, address: int, value: int | List[int], dump: bool = True - ) -> ReplyHarpMessage: - """ - Writes the value of a register of type U32. - - Parameters - ---------- - address : int - the register to be written on - value: int | List[int] - the value to be written to the register - dump : bool, optional - indicates whether the reply message should be dumped or not - - Returns - ------- - ReplyHarpMessage - the reply to the Harp message - """ - return self.send( - WriteHarpMessage( - payload_type=PayloadType.U32, - address=address, - value=value, - ).frame, - dump=dump, - ) - - def write_s32( - self, address: int, value: int | List[int], dump: bool = True - ) -> ReplyHarpMessage: - """ - Writes the value of a register of type S32. - - Parameters - ---------- - address : int - the register to be written on - value: int | List[int] - the value to be written to the register - dump : bool, optional - indicates whether the reply message should be dumped or not - - Returns - ------- - ReplyHarpMessage - the reply to the Harp message - """ - return self.send( - WriteHarpMessage( - payload_type=PayloadType.S32, - address=address, - value=value, - ).frame, - dump=dump, - ) - - def write_u64( - self, address: int, value: int | List[int], dump: bool = True - ) -> ReplyHarpMessage: - """ - Writes the value of a register of type U64. - - Parameters - ---------- - address : int - the register to be written on - value: int | List[int] - the value to be written to the register - dump : bool, optional - indicates whether the reply message should be dumped or not - - Returns - ------- - ReplyHarpMessage - the reply to the Harp message - """ - return self.send( - WriteHarpMessage( - payload_type=PayloadType.U64, - address=address, - value=value, - ).frame, - dump=dump, - ) - - def write_s64( - self, address: int, value: int | List[int], dump: bool = True - ) -> ReplyHarpMessage: - """ - Writes the value of a register of type S64. - - Parameters - ---------- - address : int - the register to be written on - value: int | List[int] - the value to be written to the register - dump : bool, optional - indicates whether the reply message should be dumped or not - - Returns - ------- - ReplyHarpMessage - the reply to the Harp message - """ - return self.send( - WriteHarpMessage( - payload_type=PayloadType.S64, - address=address, - value=value, - ).frame, - dump=dump, - ) - - def write_float( - self, address: int, value: float | List[float], dump: bool = True - ) -> ReplyHarpMessage: - """ - Writes the value of a register of type Float. - - Parameters - ---------- - address : int - the register to be written on - value: int | List[int] - the value to be written to the register - dump : bool, optional - indicates whether the reply message should be dumped or not - - Returns - ------- - ReplyHarpMessage - the reply to the Harp message - """ - return self.send( - WriteHarpMessage( - payload_type=PayloadType.Float, - address=address, - value=value, - ).frame, - dump=dump, - ) - def _read_who_am_i(self) -> int: """ Reads the value stored in the `WHO_AM_I` register. @@ -824,7 +425,10 @@ def _read_who_am_i(self) -> int: """ address = CommonRegisters.WHO_AM_I - reply: ReplyHarpMessage = self.read_u16(address, dump=False) + reply: ReplyHarpMessage = self.send( + HarpMessage.create(MessageType.READ, address, PayloadType.U16).frame, + dump=False, + ) return reply.payload_as_int() @@ -850,7 +454,10 @@ def _read_hw_version_h(self) -> int: """ address = CommonRegisters.HW_VERSION_H - reply: ReplyHarpMessage = self.read_u8(address, dump=False) + reply: ReplyHarpMessage = self.send( + HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame, + dump=False, + ) return reply.payload_as_int() @@ -865,7 +472,10 @@ def _read_hw_version_l(self) -> int: """ address = CommonRegisters.HW_VERSION_L - reply: ReplyHarpMessage = self.read_u8(address, dump=False) + reply: ReplyHarpMessage = self.send( + HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame, + dump=False, + ) return reply.payload_as_int() @@ -880,7 +490,10 @@ def _read_assembly_version(self) -> int: """ address = CommonRegisters.ASSEMBLY_VERSION - reply: ReplyHarpMessage = self.read_u8(address, dump=False) + reply: ReplyHarpMessage = self.send( + HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame, + dump=False, + ) return reply.payload_as_int() @@ -895,7 +508,10 @@ def _read_harp_version_h(self) -> int: """ address = CommonRegisters.HARP_VERSION_H - reply: ReplyHarpMessage = self.read_u8(address, dump=False) + reply: ReplyHarpMessage = self.send( + HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame, + dump=False, + ) return reply.payload_as_int() @@ -910,7 +526,10 @@ def _read_harp_version_l(self) -> int: """ address = CommonRegisters.HARP_VERSION_L - reply: ReplyHarpMessage = self.read_u8(address, dump=False) + reply: ReplyHarpMessage = self.send( + HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame, + dump=False, + ) return reply.payload_as_int() @@ -925,7 +544,10 @@ def _read_fw_version_h(self) -> int: """ address = CommonRegisters.FIRMWARE_VERSION_H - reply: ReplyHarpMessage = self.read_u8(address, dump=False) + reply: ReplyHarpMessage = self.send( + HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame, + dump=False, + ) return reply.payload_as_int() @@ -940,7 +562,10 @@ def _read_fw_version_l(self) -> int: """ address = CommonRegisters.FIRMWARE_VERSION_L - reply: ReplyHarpMessage = self.read_u8(address, dump=False) + reply: ReplyHarpMessage = self.send( + HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame, + dump=False, + ) return reply.payload_as_int() @@ -955,7 +580,10 @@ def _read_device_name(self) -> str: """ address = CommonRegisters.DEVICE_NAME - reply: ReplyHarpMessage = self.read_u8(address, dump=False) + reply: ReplyHarpMessage = self.send( + HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame, + dump=False, + ) return reply.payload_as_string() @@ -970,7 +598,10 @@ def _read_serial_number(self) -> int: """ address = CommonRegisters.SERIAL_NUMBER - reply: ReplyHarpMessage = self.read_u8(address, dump=False) + reply: ReplyHarpMessage = self.send( + HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame, + dump=False, + ) if reply.has_error(): return 0 diff --git a/tests/test_device.py b/tests/test_device.py index 798866e..3a79b1b 100644 --- a/tests/test_device.py +++ b/tests/test_device.py @@ -24,7 +24,9 @@ def test_read_U8() -> None: register: int = 38 read_size: int = 35 # TODO: automatically calculate this! - reply: ReplyHarpMessage = device.read_u8(register) + reply: ReplyHarpMessage = device.send( + HarpMessage.create(MessageType.READ, register, PayloadType.U8).frame + ) assert reply is not None # assert reply.payload_as_int() == write_value @@ -45,7 +47,11 @@ def test_U8() -> None: # assert reply[11] == 0 # what is the default register value?! # write 65 on register 38 - reply: ReplyHarpMessage = device.write_u8(register, write_value) + reply: ReplyHarpMessage = device.send( + HarpMessage.create( + MessageType.WRITE, register, PayloadType.U8, write_value + ).frame + ) assert reply is not None # read register 38 From 8f52e90d6aadf9b7c678c275d58f7c3e85801af5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Grilo?= Date: Sun, 6 Apr 2025 14:25:47 +0100 Subject: [PATCH 081/159] Docs: reorganize examples section --- docs/examples/check_device_id.md | 9 --- docs/examples/code/check_device_id.py | 34 ----------- docs/examples/code/get_info.py | 42 ------------- docs/examples/code/wait_for_events.py | 26 -------- .../code/write_and_read_from_registers.py | 59 ------------------- docs/examples/get_info.md | 9 --- docs/examples/get_info/get_info.md | 12 ++++ docs/examples/get_info/get_info.py | 20 +++++++ docs/examples/index.md | 4 +- .../read_and_write_from_registers.md | 17 ++++++ .../read_and_write_from_registers.py | 33 +++++++++++ docs/examples/wait_for_events.md | 9 --- .../wait_for_events/wait_for_events.md | 12 ++++ .../wait_for_events/wait_for_events.py | 19 ++++++ .../examples/write_and_read_from_registers.md | 9 --- mkdocs.yml | 8 +-- 16 files changed, 119 insertions(+), 203 deletions(-) delete mode 100644 docs/examples/check_device_id.md delete mode 100755 docs/examples/code/check_device_id.py delete mode 100755 docs/examples/code/get_info.py delete mode 100755 docs/examples/code/wait_for_events.py delete mode 100755 docs/examples/code/write_and_read_from_registers.py delete mode 100644 docs/examples/get_info.md create mode 100644 docs/examples/get_info/get_info.md create mode 100755 docs/examples/get_info/get_info.py create mode 100644 docs/examples/read_and_write_from_registers/read_and_write_from_registers.md create mode 100755 docs/examples/read_and_write_from_registers/read_and_write_from_registers.py delete mode 100644 docs/examples/wait_for_events.md create mode 100644 docs/examples/wait_for_events/wait_for_events.md create mode 100755 docs/examples/wait_for_events/wait_for_events.py delete mode 100644 docs/examples/write_and_read_from_registers.md diff --git a/docs/examples/check_device_id.md b/docs/examples/check_device_id.md deleted file mode 100644 index c9946ec..0000000 --- a/docs/examples/check_device_id.md +++ /dev/null @@ -1,9 +0,0 @@ -# Check Device Id - -This example demonstrates connecting to a Harp device and checking its ID: - - -```python -[](./code/check_device_id.py) -``` - \ No newline at end of file diff --git a/docs/examples/code/check_device_id.py b/docs/examples/code/check_device_id.py deleted file mode 100755 index 04297a6..0000000 --- a/docs/examples/code/check_device_id.py +++ /dev/null @@ -1,34 +0,0 @@ -import os - -from pyharp.device import Device -from pyharp.device_names import device_names - -# ON THIS EXAMPLE -# -# This code check if the device at COMx is the expected device. -# The device ID used is the 2080, the IblBehavior - - -# Open the device -# Open serial connection -if os.name == "posix": # check for Linux. - device = Device("/dev/ttyUSB0") -else: # assume Windows. - device = Device("COM95") - -# Get some of the device's parameters -device_id = device.WHO_AM_I # Get device's ID -device_id_description = device.DEFAULT_DEVICE_NAME # Get device's user name -device_user_name = device.DEVICE_NAME # Get device's user name - -# Check if we are dealing with the correct device -if device_id in device_names: - print("Correct device was found!") - print(f"Device's ID: {device_id}") - print(f"Device's default name: {device_id_description}") - print(f"Device's user name: {device_user_name}") -else: - print("Device not correct or is not a Harp device!") - -# Close connection -device.disconnect() diff --git a/docs/examples/code/get_info.py b/docs/examples/code/get_info.py deleted file mode 100755 index 05ed9f9..0000000 --- a/docs/examples/code/get_info.py +++ /dev/null @@ -1,42 +0,0 @@ -import os - -from pyharp.device import Device - -# ON THIS EXAMPLE -# -# This code opens the connection with the device and displays the information -# Also saves device's information into variables - - -# Open the device and print the info on screen -# Open serial connection and save communication to a file -if os.name == "posix": # check for Linux. - # device = Device("/dev/harp_device_00", "ibl.bin") - # device = Device("/dev/ttyACM0") - device = Device("/dev/ttyUSB0") -else: # assume Windows. - device = Device("COM95", "ibl.bin") -device.info() # Display device's info on screen - -# Get some of the device's parameters -device_id = device.WHO_AM_I # Get device's ID -device_id_description = device.DEFAULT_DEVICE_NAME # Get device's user name -device_user_name = device.DEVICE_NAME # Get device's user name - -# Get versions -device_fw_h = device.FIRMWARE_VERSION_H # Get device's firmware version -device_fw_l = device.FIRMWARE_VERSION_L # Get device's firmware version -device_hw_h = device.HW_VERSION_H # Get device's hardware version -device_hw_l = device.HW_VERSION_L # Get device's hardware version -device_harp_h = device.HARP_VERSION_H # Get device's harp core version -device_harp_l = device.HARP_VERSION_L # Get device's harp core version -device_assembly = device.ASSEMBLY_VERSION # Get device's assembly version -device_serial_number = device.SERIAL_NUMBER # Get device's serial number - -reg_dump = device.dump_registers() -for reg_reply in reg_dump: - print(reg_reply) - print() - -# Close connection -device.disconnect() diff --git a/docs/examples/code/wait_for_events.py b/docs/examples/code/wait_for_events.py deleted file mode 100755 index 6cdb615..0000000 --- a/docs/examples/code/wait_for_events.py +++ /dev/null @@ -1,26 +0,0 @@ -import os - -from pyharp.device import Device, DeviceMode -from pyharp.drivers.behavior import Behavior - -# Open the device and print the info on screen -# Open serial connection and save communication to a file -device = None -if os.name == "posix": # check for Linux. - # device = Behavior("/dev/harp_device_00", "ibl.bin") - # device = Device("/dev/ttyACM0",) - device = Device( - "/dev/ttyUSB0", - ) -else: # assume Windows. - device = Behavior("COM95", "ibl.bin") - -print("Setting mode to active.") -# Mode will remain active for up to 3 seconds after CTS pin is brought low. -device.set_mode(DeviceMode.Active) -# device.disable_all_events() -# device.enable_events(Events.port_digital_inputs) -while True: - for msg in device.get_events(): - print(msg) - print() diff --git a/docs/examples/code/write_and_read_from_registers.py b/docs/examples/code/write_and_read_from_registers.py deleted file mode 100755 index 91d7c7e..0000000 --- a/docs/examples/code/write_and_read_from_registers.py +++ /dev/null @@ -1,59 +0,0 @@ -import os - -from pyharp.device import Device - -# ON THIS EXAMPLE -# -# This code opens the connection with the device and update content on a register -# It uses register address 42, which stores the analog sensor's higher threshold in the IBLBehavior device -# This register is unsigned with 16 bits (U16) - - -# Open the device and print the info on screen -# Open serial connection and save communication to a file -if os.name == "posix": # check for Linux. - device = Device("/dev/ttyUSB0", "ibl.bin") -else: # assume Windows. - device = Device("COM95", "ibl.bin") - -# Read current analog sensor's higher threshold (ANA_SENSOR_TH0_HIGH) at address 42 -# analog_threshold_h = device.send(HarpMessage.ReadU16(42).frame).payload_as_int() -# print(f"Analog sensor's higher threshold: {analog_threshold_h}") - - -import time - -print(f"System time: {time.perf_counter():.6f}") -data_stream = device.read_u8(33) - -print(f"Data Stream payload type: {data_stream.payload_type.name}") -print(f"Data Stream message type: {data_stream.message_type.name}") -print(f"Data Stream timestamp: {data_stream.timestamp}") -print(f"Data Stream num bytes: {data_stream.length}") -print(f"Data Stream payload: {data_stream.payload}") - -print(f"System time: {time.perf_counter():.6f}") -event_reg_response = device.read_u8(77) # returns a ReplyHarpMessage -print(f"EVNT_ENABLE payload type: {event_reg_response.payload_type.name}") -print(f"EVNT_ENABLE message type: {event_reg_response.message_type.name}") -print(f"EVNT_ENABLE timestamp: {event_reg_response.timestamp}") -print(f"EVNT_ENABLE num bytes: {event_reg_response.length}") -print(f"EVNT_ENABLE payload: {event_reg_response.payload[0]:08b}") - -## Increase current analog sensor's higher threshold by one unit -# device.send(HarpMessage.WriteU16(42, analog_threshold_h+1).frame) -# -## Check if the register was well written -# analog_threshold_h = device.send(HarpMessage.ReadU16(42).frame).payload_as_int() -# print(f"Analog sensor's higher threshold: {analog_threshold_h}") -# -## Read 10 samples of the analog sensor and display the values -## The value is at register STREAM[0], address 33 -# analog_sensor = [] -# for x in range(10): -# value = device.send(HarpMessage.ReadS16(33).frame).payload_as_int() -# analog_sensor.append(value & 0xffff) -# print(f"Analog sensor's values: {analog_sensor}") - -# Close connection -device.disconnect() diff --git a/docs/examples/get_info.md b/docs/examples/get_info.md deleted file mode 100644 index 96d03e9..0000000 --- a/docs/examples/get_info.md +++ /dev/null @@ -1,9 +0,0 @@ -# Getting Device Info - -This example demonstrates connecting to a Harp device and reading its information: - - -```python -[](./code/get_info.py) -``` - diff --git a/docs/examples/get_info/get_info.md b/docs/examples/get_info/get_info.md new file mode 100644 index 0000000..0ebfe38 --- /dev/null +++ b/docs/examples/get_info/get_info.md @@ -0,0 +1,12 @@ +# Getting Device Info + +This example demonstrates how to connect to a Harp device, read its info and dump the device's registers. + +!!! warning + Don't forget to change the `SERIAL_PORT` to the one that corresponds to your device! The `SERIAL_PORT` must be denoted as `/dev/ttyUSBx` in Linux and `COMx` in Windows, where `x` is the number of the serial port. + + +```python +[](./get_info.py) +``` + diff --git a/docs/examples/get_info/get_info.py b/docs/examples/get_info/get_info.py new file mode 100755 index 0000000..318c307 --- /dev/null +++ b/docs/examples/get_info/get_info.py @@ -0,0 +1,20 @@ +from pyharp.device import Device + +SERIAL_PORT = ( + "/dev/ttyUSB0" # or "COMx" in Windows ("x" is the number of the serial port) +) + +# Open serial connection and save communication to a file +device = Device(SERIAL_PORT, "dump.bin") + +# Display device's info on screen +device.info() + +# Dump device's registers +reg_dump = device.dump_registers() +for reg_reply in reg_dump: + print(reg_reply) + print() + +# Close connection +device.disconnect() diff --git a/docs/examples/index.md b/docs/examples/index.md index 6c4ac5b..73bad13 100644 --- a/docs/examples/index.md +++ b/docs/examples/index.md @@ -5,6 +5,6 @@ This section contains some examples to help you get started with `pyharp`. Here's the complete list of available examples: - [Getting Device Info](get_info.md) - connect to a Harp device and read its information. -- [Check Device ID](check_device.md) - connect to a Harp device and check its ID. - [Wait for Events](wait_for_events.md) - connect to a Harp device and wait for events. -- [Write and Read from Registers](write_and_read_from_registers.md) - connect to a Harp device and read from registers. \ No newline at end of file +- [Write and Read from Registers](write_and_read_from_registers.md) - connect to the Harp Behavior, read from a digital input and write to a digital output. +- [Olfactometer Example](olfactometer_example.md) - connect to the Harp Olfactometer, enable flow, open and close the odor valves and monitor the measured flow values. \ No newline at end of file diff --git a/docs/examples/read_and_write_from_registers/read_and_write_from_registers.md b/docs/examples/read_and_write_from_registers/read_and_write_from_registers.md new file mode 100644 index 0000000..ea819d9 --- /dev/null +++ b/docs/examples/read_and_write_from_registers/read_and_write_from_registers.md @@ -0,0 +1,17 @@ +# Read and Write from Registers + +This example demonstrates how to read and write from registers. In this particular example, the [Harp Behavior](https://harp-tech.org/api/Harp.Behavior.html) is used to read from the DI3 pin and to turn on and off the DO0 pin, according to the schematics shown [below](#schematics). + +!!! warning + Don't forget to change the `SERIAL_PORT` to the one that corresponds to your device! The `SERIAL_PORT` must be denoted as `/dev/ttyUSBx` in Linux and `COMx` in Windows, where `x` is the number of the serial port. + + +```python +[](./read_and_write_from_registers.py) +``` + + +## Schematics + +!!! warning + _TODO_ \ No newline at end of file diff --git a/docs/examples/read_and_write_from_registers/read_and_write_from_registers.py b/docs/examples/read_and_write_from_registers/read_and_write_from_registers.py new file mode 100755 index 0000000..cd8fab0 --- /dev/null +++ b/docs/examples/read_and_write_from_registers/read_and_write_from_registers.py @@ -0,0 +1,33 @@ +from serial import SerialException + +from pyharp import MessageType, PayloadType +from pyharp.device import Device +from pyharp.messages import HarpMessage + +SERIAL_PORT = ( + "/dev/ttyUSB0" # or "COMx" in Windows ("x" is the number of the serial port) +) + +# Open serial connection and save communication to a file +device = Device(SERIAL_PORT, "dump.bin") + +# Check if the device is a Harp Behavior +if not device.WHO_AM_I == 1216: + raise SerialException("This is not a Harp Behavior.") + +# Read initial DI3 state +reply = device.send(HarpMessage.create(MessageType.READ, 32, PayloadType.U8)) +print(reply.payload & 0x08) + +# Turn DO0 on and read DI3 state after it +reply = device.send(HarpMessage.create(MessageType.READ, 34, PayloadType.U8, 0x400)) +reply = device.send(HarpMessage.create(MessageType.READ, 32, PayloadType.U8)) +print(reply.payload & 0x08) + +# Turn DO0 off and read DI3 state again +reply = device.send(HarpMessage.create(MessageType.READ, 35, PayloadType.U8, 0x400)) +reply = device.send(HarpMessage.create(MessageType.READ, 32, PayloadType.U8)) +print(reply.payload & 0x08) + +# Close connection +device.disconnect() diff --git a/docs/examples/wait_for_events.md b/docs/examples/wait_for_events.md deleted file mode 100644 index a40c86a..0000000 --- a/docs/examples/wait_for_events.md +++ /dev/null @@ -1,9 +0,0 @@ -# Wait for Events - -This example demonstrates connecting to a Harp device and waiting for events: - - -```python -[](./code/wait_for_events.py) -``` - \ No newline at end of file diff --git a/docs/examples/wait_for_events/wait_for_events.md b/docs/examples/wait_for_events/wait_for_events.md new file mode 100644 index 0000000..c4aa6a0 --- /dev/null +++ b/docs/examples/wait_for_events/wait_for_events.md @@ -0,0 +1,12 @@ +# Wait for Events + +This example demonstrates how to read the events sent by the Harp device. + +!!! warning + Don't forget to change the `SERIAL_PORT` to the one that corresponds to your device! The `SERIAL_PORT` must be denoted as `/dev/ttyUSBx` in Linux and `COMx` in Windows, where `x` is the number of the serial port. + + +```python +[](./wait_for_events.py) +``` + \ No newline at end of file diff --git a/docs/examples/wait_for_events/wait_for_events.py b/docs/examples/wait_for_events/wait_for_events.py new file mode 100755 index 0000000..b428b60 --- /dev/null +++ b/docs/examples/wait_for_events/wait_for_events.py @@ -0,0 +1,19 @@ +from pyharp import DeviceMode +from pyharp.device import Device + +SERIAL_PORT = ( + "/dev/ttyUSB0" # or "COMx" in Windows ("x" is the number of the serial port) +) + +# Open serial connection and save communication to a file +device = Device(SERIAL_PORT, "dump.bin") + +# Set device to Active Mode +device.set_mode(DeviceMode.Active) +print("Setting mode to active.") + +# Read device's events +while True: + for msg in device.get_events(): + print(msg) + print() diff --git a/docs/examples/write_and_read_from_registers.md b/docs/examples/write_and_read_from_registers.md deleted file mode 100644 index 55f9e64..0000000 --- a/docs/examples/write_and_read_from_registers.md +++ /dev/null @@ -1,9 +0,0 @@ -# Write and Read from registers - -This example demonstrates connecting to a Harp device and writing and reading from registers: - - -```python -[](./code/write_and_read_from_registers.py) -``` - \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 46dace4..647dbea 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -76,10 +76,10 @@ nav: - Home: index.md - Examples: - examples/index.md - - Getting Device Info: examples/get_info.md - - Check Device ID: examples/check_device_id.md - - Wait for Events: examples/wait_for_events.md - - Write and Read from Registers: examples/write_and_read_from_registers.md + - Getting Device Info: examples/get_info/get_info.md + - Wait for Events: examples/wait_for_events/wait_for_events.md + - Read and Write from Registers: examples/read_and_write_from_registers/read_and_write_from_registers.md + - Olfactometer Example: examples/olfactometer_example/olfactometer_example.md - API: - Device: api/device.md - Messages: api/messages.md From c853f05f34954adfd0d85240a8bf57742fdceac6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Grilo?= Date: Sun, 6 Apr 2025 14:26:07 +0100 Subject: [PATCH 082/159] Docs: add olfactometer example --- .../olfactometer_example.md | 14 ++ .../olfactometer_example.py | 124 ++++++++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 docs/examples/olfactometer_example/olfactometer_example.md create mode 100644 docs/examples/olfactometer_example/olfactometer_example.py diff --git a/docs/examples/olfactometer_example/olfactometer_example.md b/docs/examples/olfactometer_example/olfactometer_example.md new file mode 100644 index 0000000..f78d2e6 --- /dev/null +++ b/docs/examples/olfactometer_example/olfactometer_example.md @@ -0,0 +1,14 @@ +# Olfactometer Example + +This example shows how to interface with the [Harp Olfactometer](https://github.com/harp-tech/device.olfactometer). + +In this example, the flows for the different channels are enabled to random flow values, then every odor valve is opened, one at a time every 5 seconds, and finally the flow is disabled before closing the connection with the device. During this time, the actual flows in every channel are being printed out in the terminal. + +!!! warning + Don't forget to change the `SERIAL_PORT` to the one that corresponds to your device! The `SERIAL_PORT` must be denoted as `/dev/ttyUSBx` in Linux and `COMx` in Windows, where `x` is the number of the serial port. + + +```python +[](./olfactometer_example.py) +``` + diff --git a/docs/examples/olfactometer_example/olfactometer_example.py b/docs/examples/olfactometer_example/olfactometer_example.py new file mode 100644 index 0000000..aa8faa0 --- /dev/null +++ b/docs/examples/olfactometer_example/olfactometer_example.py @@ -0,0 +1,124 @@ +import random +import time +from threading import Event, Thread + +from serial import SerialException + +from pyharp import MessageType, PayloadType +from pyharp.device import Device, DeviceMode +from pyharp.messages import HarpMessage + +SERIAL_PORT = ( + "/dev/ttyUSB0" # or "COMx" in Windows ("x" is the number of the serial port) +) + + +def print_events(device, stop_flag): + while not stop_flag.is_set(): + for msg in device.get_events(): + if ( + msg.address == 48 + or msg.address == 49 + or msg.address == 50 + or msg.address == 51 + or msg.address == 52 + ): + print(msg.address - 48) + print(msg.payload[0]) + print() + + +def main(): + # Open connection + device = Device(SERIAL_PORT) + time.sleep(1) + + stop_flag = Event() + + # Check if the device is a Harp Olfactometer + if not device.WHO_AM_I == 1140: + raise SerialException("This is not a Harp Olfactometer.") + + device.set_mode(DeviceMode.Active) + + # Enable flow + device.send(HarpMessage.WriteU8(32, 0x01).frame) + + # Initialize thread for events + events_thread = Thread( + target=print_events, + args=( + device, + stop_flag, + ), + ) + events_thread.start() + + # Set the valves to a random flow + device.send( + HarpMessage.create( + MessageType.WRITE, 42, PayloadType.Float, int(random.random() * 100) + ).frame + ) + device.send( + HarpMessage.create( + MessageType.WRITE, 43, PayloadType.Float, int(random.random() * 100) + ).frame + ) + device.send( + HarpMessage.create( + MessageType.WRITE, 44, PayloadType.Float, int(random.random() * 100) + ).frame + ) + device.send( + HarpMessage.create( + MessageType.WRITE, 45, PayloadType.Float, int(random.random() * 100) + ).frame + ) + + # Open every odor valve, one at a time every 5 seconds + device.send( + HarpMessage.create(MessageType.WRITE, 68, PayloadType.Float, 0x01).frame + ) + time.sleep(5) + device.send( + HarpMessage.create(MessageType.WRITE, 69, PayloadType.Float, 0x01).frame + ) + device.send( + HarpMessage.create(MessageType.WRITE, 68, PayloadType.Float, 0x02).frame + ) + time.sleep(5) + device.send( + HarpMessage.create(MessageType.WRITE, 69, PayloadType.Float, 0x02).frame + ) + device.send( + HarpMessage.create(MessageType.WRITE, 68, PayloadType.Float, 0x04).frame + ) + time.sleep(5) + device.send( + HarpMessage.create(MessageType.WRITE, 69, PayloadType.Float, 0x04).frame + ) + device.send( + HarpMessage.create(MessageType.WRITE, 68, PayloadType.Float, 0x08).frame + ) + time.sleep(5) + device.send( + HarpMessage.create(MessageType.WRITE, 69, PayloadType.Float, 0x08).frame + ) + time.sleep(5) + + # Disable flow + device.send( + HarpMessage.create(MessageType.WRITE, 32, PayloadType.Float, 0x00).frame + ) + time.sleep(1) + + stop_flag.set() + events_thread.join() + + # Close connection + device.disconnect() + + +if __name__ == "__main__": + main() From 5c77aa6af8b10117d998c536541137cec7220859 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Grilo?= Date: Sun, 6 Apr 2025 14:26:46 +0100 Subject: [PATCH 083/159] Docs: add pyharp core API to documentation --- docs/api/core.md | 4 ++++ mkdocs.yml | 1 + 2 files changed, 5 insertions(+) create mode 100644 docs/api/core.md diff --git a/docs/api/core.md b/docs/api/core.md new file mode 100644 index 0000000..df5441d --- /dev/null +++ b/docs/api/core.md @@ -0,0 +1,4 @@ +::: pyharp.MessageType +::: pyharp.PayloadType +::: pyharp.CommonRegisters +::: pyharp.DeviceMode \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 647dbea..85e4ba7 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -81,6 +81,7 @@ nav: - Read and Write from Registers: examples/read_and_write_from_registers/read_and_write_from_registers.md - Olfactometer Example: examples/olfactometer_example/olfactometer_example.md - API: + - Core: api/core.md - Device: api/device.md - Messages: api/messages.md From 299b4d020b067c715da244b9a46406e7e0664cd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 9 Apr 2025 10:11:12 +0100 Subject: [PATCH 084/159] Change dump file usage convention --- pyharp/device.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/pyharp/device.py b/pyharp/device.py index 27212fd..64695bf 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -2,6 +2,7 @@ import logging import queue +from io import BufferedWriter from pathlib import Path from typing import Optional, Union @@ -57,6 +58,7 @@ class Device: _ser: HarpSerial _dump_file_path: Path + _dump_file: Optional[BufferedWriter] _read_timeout_s: float _TIMEOUT_S: float = 1.0 @@ -79,9 +81,8 @@ def __init__( """ self.log = logging.getLogger(f"{__name__}.{self.__class__.__name__}") self._serial_port = serial_port - if dump_file_path is None: - self._dump_file_path = None - else: + self._dump_file_path = dump_file_path + if dump_file_path is not None: self._dump_file_path = Path() / dump_file_path self._read_timeout_s = read_timeout_s @@ -135,10 +136,19 @@ def connect(self) -> None: rtscts=True, ) + # open file if it is defined + if self._dump_file_path is not None: + self._dump_file = open(self._dump_file_path, "ab") + def disconnect(self) -> None: """ Disconnects from the Harp device. """ + # close file if it exists + if self._dump_file: + self._dump_file.close() + self._dump_file = None + self._ser.close() def _read_device_mode(self) -> DeviceMode: @@ -358,7 +368,7 @@ def send(self, message_bytes: bytearray, dump: bool = True) -> ReplyHarpMessage: # TODO: handle case where read is None reply: ReplyHarpMessage = self._read() - if dump and self._dump_file_path is not None: + if dump: self._dump_reply(reply.frame) return reply @@ -381,10 +391,8 @@ def _dump_reply(self, reply: bytes): """ Dumps the reply to a Harp message in the dump file in case it exists. """ - # TODO: try to handle a None _dump_file_path in a different way - assert self._dump_file_path is not None - with self._dump_file_path.open(mode="ab") as f: - f.write(reply) + if self._dump_file: + self._dump_file.write(reply) def get_events(self) -> list[ReplyHarpMessage]: """ From b924837a4dbdb41ebc18ca21afce30830f61c302 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 9 Apr 2025 10:37:37 +0100 Subject: [PATCH 085/159] Change DeviceMode to OperationMode for official Harp documentation coherence --- .../olfactometer_example.py | 4 +-- .../wait_for_events/wait_for_events.py | 4 +-- pyharp/__init__.py | 2 +- pyharp/base.py | 25 +++++++++++++++---- pyharp/device.py | 8 +++--- 5 files changed, 29 insertions(+), 14 deletions(-) diff --git a/docs/examples/olfactometer_example/olfactometer_example.py b/docs/examples/olfactometer_example/olfactometer_example.py index aa8faa0..aea85b1 100644 --- a/docs/examples/olfactometer_example/olfactometer_example.py +++ b/docs/examples/olfactometer_example/olfactometer_example.py @@ -5,7 +5,7 @@ from serial import SerialException from pyharp import MessageType, PayloadType -from pyharp.device import Device, DeviceMode +from pyharp.device import Device, OperationMode from pyharp.messages import HarpMessage SERIAL_PORT = ( @@ -39,7 +39,7 @@ def main(): if not device.WHO_AM_I == 1140: raise SerialException("This is not a Harp Olfactometer.") - device.set_mode(DeviceMode.Active) + device.set_mode(OperationMode.ACTIVE) # Enable flow device.send(HarpMessage.WriteU8(32, 0x01).frame) diff --git a/docs/examples/wait_for_events/wait_for_events.py b/docs/examples/wait_for_events/wait_for_events.py index b428b60..e1064c2 100755 --- a/docs/examples/wait_for_events/wait_for_events.py +++ b/docs/examples/wait_for_events/wait_for_events.py @@ -1,4 +1,4 @@ -from pyharp import DeviceMode +from pyharp import OperationMode from pyharp.device import Device SERIAL_PORT = ( @@ -9,7 +9,7 @@ device = Device(SERIAL_PORT, "dump.bin") # Set device to Active Mode -device.set_mode(DeviceMode.Active) +device.set_mode(OperationMode.ACTIVE) print("Setting mode to active.") # Read device's events diff --git a/pyharp/__init__.py b/pyharp/__init__.py index fc569a5..d40c35b 100644 --- a/pyharp/__init__.py +++ b/pyharp/__init__.py @@ -1,3 +1,3 @@ -from pyharp.base import CommonRegisters, DeviceMode, MessageType, PayloadType +from pyharp.base import CommonRegisters, MessageType, OperationMode, PayloadType __version__ = "0.1.0" diff --git a/pyharp/base.py b/pyharp/base.py index 6962a0e..38e7214 100644 --- a/pyharp/base.py +++ b/pyharp/base.py @@ -148,8 +148,23 @@ class CommonRegisters(IntEnum): SERIAL_NUMBER = 0x0D -class DeviceMode(IntEnum): - Standby = 0 - Active = 1 - Reserved = 2 - Speed = 3 +class OperationMode(IntEnum): + """ + An enumeration with the operation modes of a Harp device. More information on the operation modes can be found [here](https://harp-tech.org/protocol/Device.html#r_operation_ctrl-u16--operation-mode-configuration). + + Attributes + ---------- + STANDBY : int + the value that corresponds to the Standby operation mode (0). The device has all the Events turned off. + ACTIVE : int + the value that corresponds to the Active operation mode (1). The device turns ON the Events detection. Only the enabled Events will be operating. + RESERVED : int + the value that corresponds to the Reserved operation mode (2) + SPEED : int + the value that corresponds to the Speed operation mode (3). The device enters Speed Mode. + """ + + STANDBY = 0 + ACTIVE = 1 + RESERVED = 2 + SPEED = 3 diff --git a/pyharp/device.py b/pyharp/device.py index 64695bf..56af486 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -8,7 +8,7 @@ import serial -from pyharp import CommonRegisters, DeviceMode, MessageType, PayloadType +from pyharp import CommonRegisters, MessageType, OperationMode, PayloadType from pyharp.device_names import device_names from pyharp.harp_serial import HarpSerial from pyharp.messages import HarpMessage, ReplyHarpMessage @@ -151,7 +151,7 @@ def disconnect(self) -> None: self._ser.close() - def _read_device_mode(self) -> DeviceMode: + def _read_device_mode(self) -> OperationMode: """ Reads the current operation mode of the Harp device. @@ -164,7 +164,7 @@ def _read_device_mode(self) -> DeviceMode: reply = self.send( HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame ) - return DeviceMode(reply.payload_as_int() & 0x03) + return OperationMode(reply.payload_as_int() & 0x03) def dump_registers(self) -> list: """ @@ -198,7 +198,7 @@ def dump_registers(self) -> list: break return replies - def set_mode(self, mode: DeviceMode) -> ReplyHarpMessage: + def set_mode(self, mode: OperationMode) -> ReplyHarpMessage: """ Sets the operation mode of the device. From 1d126339272f3a6a8fd5d185d9ad1bf6e7004919 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 9 Apr 2025 10:42:03 +0100 Subject: [PATCH 086/159] Change return type of Device's send method --- pyharp/device.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pyharp/device.py b/pyharp/device.py index 56af486..c4ef087 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -347,7 +347,9 @@ def reset_device(self) -> ReplyHarpMessage: return reply - def send(self, message_bytes: bytearray, dump: bool = True) -> ReplyHarpMessage: + def send( + self, message_bytes: bytearray, dump: bool = True + ) -> Optional[ReplyHarpMessage]: """ Sends a Harp message. @@ -360,13 +362,12 @@ def send(self, message_bytes: bytearray, dump: bool = True) -> ReplyHarpMessage: Returns ------- - ReplyHarpMessage - the reply to the Harp message + Optional[ReplyHarpMessage] + the reply to the Harp message or None if no reply is given """ self._ser.write(message_bytes) - # TODO: handle case where read is None - reply: ReplyHarpMessage = self._read() + reply = self._read() if dump: self._dump_reply(reply.frame) From 98046e6bb73cb5b14cfa3d359d58d7e083ee7b62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 9 Apr 2025 11:45:20 +0100 Subject: [PATCH 087/159] Update and add missing OperationControl methods --- pyharp/device.py | 82 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 61 insertions(+), 21 deletions(-) diff --git a/pyharp/device.py b/pyharp/device.py index c4ef087..40c19f0 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -232,14 +232,19 @@ def set_mode(self, mode: OperationMode) -> ReplyHarpMessage: return reply - def enable_status_led(self) -> ReplyHarpMessage: + def alive_en(self, enable: bool) -> bool: """ - Enables the device's status led. + Sets the ALIVE_EN bit of the device. + + Parameters + ---------- + enable : bool + If True, enables the ALIVE_EN bit. If False, disables it. Returns ------- - ReplyHarpMessage - the reply to the Harp message + bool + True if the operation was successful, False otherwise. """ address = CommonRegisters.OPERATION_CTRL @@ -247,7 +252,12 @@ def enable_status_led(self) -> ReplyHarpMessage: reg_value = self.send( HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame ).payload_as_int() - reg_value |= 1 << 5 + + if enable: + reg_value |= 1 << 7 + else: + reg_value &= ~(1 << 7) + reply = self.send( HarpMessage.create( MessageType.WRITE, address, PayloadType.U8, reg_value @@ -256,14 +266,19 @@ def enable_status_led(self) -> ReplyHarpMessage: return reply - def disable_status_led(self) -> ReplyHarpMessage: + def op_led_en(self, enable: bool) -> bool: """ - Disables the device's status led. + Sets the operation LED of the device. + + Parameters + ---------- + enable : bool + If True, enables the operation LED. If False, disables it. Returns ------- - ReplyHarpMessage - the reply to the Harp message + bool + True if the operation was successful, False otherwise. """ address = CommonRegisters.OPERATION_CTRL @@ -271,7 +286,12 @@ def disable_status_led(self) -> ReplyHarpMessage: reg_value = self.send( HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame ).payload_as_int() - reg_value &= ~(1 << 5) + + if enable: + reg_value |= 1 << 6 + else: + reg_value &= ~(1 << 6) + reply = self.send( HarpMessage.create( MessageType.WRITE, address, PayloadType.U8, reg_value @@ -280,14 +300,19 @@ def disable_status_led(self) -> ReplyHarpMessage: return reply - def enable_alive_en(self) -> ReplyHarpMessage: + def status_led(self, enable: bool) -> bool: """ - Enables the ALIVE_EN bit so that the device sends an event each second. More information on the ALIVE_EN bit can be found [here](https://harp-tech.org/protocol/Device.html#r_operation_ctrl-u16--operation-mode-configuration). + Sets the status led of the device. + + Parameters + ---------- + enable : bool + If True, enables the status led. If False, disables it. Returns ------- - ReplyHarpMessage - the reply to the Harp message + bool + True if the operation was successful, False otherwise. """ address = CommonRegisters.OPERATION_CTRL @@ -295,7 +320,12 @@ def enable_alive_en(self) -> ReplyHarpMessage: reg_value = self.send( HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame ).payload_as_int() - reg_value |= 1 << 7 + + if enable: + reg_value |= 1 << 5 + else: + reg_value &= ~(1 << 5) + reply = self.send( HarpMessage.create( MessageType.WRITE, address, PayloadType.U8, reg_value @@ -304,22 +334,32 @@ def enable_alive_en(self) -> ReplyHarpMessage: return reply - def disable_alive_en(self) -> ReplyHarpMessage: + def mute_reply(self, enable: bool) -> bool: """ - Disables the ALIVE_EN bit so that the device does not send an event each second. More information on the ALIVE_EN bit can be found [here](https://harp-tech.org/protocol/Device.html#r_operation_ctrl-u16--operation-mode-configuration). + Sets the MUTE_REPLY bit of the device. + + Parameters + ---------- + enable : bool + If True, the Replies to all the Commands are muted. If False, un-mutes them. Returns ------- - ReplyHarpMessage - the reply to the Harp message + bool + True if the operation was successful, False otherwise. """ address = CommonRegisters.OPERATION_CTRL # Read register first reg_value = self.send( HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame - ).payload[0] - reg_value &= ~(1 << 7) + ).payload_as_int() + + if enable: + reg_value |= 1 << 4 + else: + reg_value &= ~(1 << 4) + reply = self.send( HarpMessage.create( MessageType.WRITE, address, PayloadType.U8, reg_value From 9959cdd1c767682ae5cf35ca789aa6a803438a75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 9 Apr 2025 11:45:43 +0100 Subject: [PATCH 088/159] Update docs links --- docs/examples/index.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/examples/index.md b/docs/examples/index.md index 73bad13..695565a 100644 --- a/docs/examples/index.md +++ b/docs/examples/index.md @@ -4,7 +4,7 @@ This section contains some examples to help you get started with `pyharp`. Here's the complete list of available examples: -- [Getting Device Info](get_info.md) - connect to a Harp device and read its information. -- [Wait for Events](wait_for_events.md) - connect to a Harp device and wait for events. -- [Write and Read from Registers](write_and_read_from_registers.md) - connect to the Harp Behavior, read from a digital input and write to a digital output. -- [Olfactometer Example](olfactometer_example.md) - connect to the Harp Olfactometer, enable flow, open and close the odor valves and monitor the measured flow values. \ No newline at end of file +- [Getting Device Info](./get_info/get_info.md) - connect to a Harp device and read its information. +- [Wait for Events](./wait_for_events/wait_for_events.md) - connect to a Harp device and wait for events. +- [Write and Read from Registers](./read_and_write_from_registers/read_and_write_from_registers.md) - connect to the Harp Behavior, read from a digital input and write to a digital output. +- [Olfactometer Example](./olfactometer_example/olfactometer_example.md) - connect to the Harp Olfactometer, enable flow, open and close the odor valves and monitor the measured flow values. \ No newline at end of file From 61c25629f52b9a1dc2e60ee2d813beaa23f99b7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 9 Apr 2025 16:37:00 +0100 Subject: [PATCH 089/159] Fix doc link --- docs/api/core.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/core.md b/docs/api/core.md index df5441d..e3d8970 100644 --- a/docs/api/core.md +++ b/docs/api/core.md @@ -1,4 +1,4 @@ ::: pyharp.MessageType ::: pyharp.PayloadType ::: pyharp.CommonRegisters -::: pyharp.DeviceMode \ No newline at end of file +::: pyharp.OperationMode \ No newline at end of file From d1ef6e0e616759ed0799298461000308cf6fd697 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 9 Apr 2025 16:37:20 +0100 Subject: [PATCH 090/159] Comment device tests --- tests/test_device.py | 184 +++++++++++++++++++++---------------------- 1 file changed, 92 insertions(+), 92 deletions(-) diff --git a/tests/test_device.py b/tests/test_device.py index 3a79b1b..fb85b39 100644 --- a/tests/test_device.py +++ b/tests/test_device.py @@ -1,98 +1,98 @@ -import time +# import time -from pyharp import MessageType, PayloadType -from pyharp.device import Device -from pyharp.messages import HarpMessage, ReplyHarpMessage +# from pyharp import MessageType, PayloadType +# from pyharp.device import Device +# from pyharp.messages import HarpMessage, ReplyHarpMessage -DEFAULT_ADDRESS = 42 +# DEFAULT_ADDRESS = 42 -# FIXME -# def test_create_device() -> None: +# # FIXME +# # def test_create_device() -> None: +# # # open serial connection and load info +# # device = Device("COM74", "dump.bin") +# # assert device._ser.is_open +# # device.info() +# # device.disconnect() +# # assert not device._ser.is_open + + +# def test_read_U8() -> None: # # open serial connection and load info -# device = Device("COM74", "dump.bin") -# assert device._ser.is_open -# device.info() +# device = Device("/dev/ttyUSB0", "dump.bin") + +# # read register 38 +# register: int = 38 +# read_size: int = 35 # TODO: automatically calculate this! + +# reply: ReplyHarpMessage = device.send( +# HarpMessage.create(MessageType.READ, register, PayloadType.U8).frame +# ) +# assert reply is not None +# # assert reply.payload_as_int() == write_value + +# print(reply) +# assert device._dump_file_path.exists() # device.disconnect() -# assert not device._ser.is_open - - -def test_read_U8() -> None: - # open serial connection and load info - device = Device("/dev/ttyUSB0", "dump.bin") - - # read register 38 - register: int = 38 - read_size: int = 35 # TODO: automatically calculate this! - - reply: ReplyHarpMessage = device.send( - HarpMessage.create(MessageType.READ, register, PayloadType.U8).frame - ) - assert reply is not None - # assert reply.payload_as_int() == write_value - - print(reply) - assert device._dump_file_path.exists() - device.disconnect() - - -def test_U8() -> None: - # open serial connection and load info - device = Device("/dev/ttyUSB0", "dump.bin") - assert device._dump_file_path.exists() - - register: int = 38 - read_size: int = 20 # TODO: automatically calculate this! - write_value: int = 65 - - # assert reply[11] == 0 # what is the default register value?! - - # write 65 on register 38 - reply: ReplyHarpMessage = device.send( - HarpMessage.create( - MessageType.WRITE, register, PayloadType.U8, write_value - ).frame - ) - assert reply is not None - - # read register 38 - reply = device.read_u8(register) - assert reply is not None - assert reply.payload_as_int() == write_value - - device.disconnect() - - -# def test_read_hw_version_integration() -> None: -# -# # serial settings -# ser = serial.Serial( -# "/dev/tty.usbserial-A106C8O9", -# baudrate=1000000, -# timeout=5, -# parity=serial.PARITY_NONE, -# stopbits=1, -# bytesize=8, -# rtscts=True, + +# # FIXME: this seems to be testing the Behavior device, not a generic harp device. +# def test_U8() -> None: +# # open serial connection and load info +# device = Device("/dev/ttyUSB0", "dump.bin") +# assert device._dump_file_path.exists() + +# register: int = 38 +# read_size: int = 20 # TODO: automatically calculate this! +# write_value: int = 65 + +# # assert reply[11] == 0 # what is the default register value?! + +# # write 65 on register 38 +# reply = device.send( +# HarpMessage.create( +# MessageType.WRITE, register, PayloadType.U8, write_value +# ).frame # ) -# -# assert ser.is_open -# -# ser.write(b"\x01\x04\x01\xff\x01\x06") # read HW major version (register 1) -# ser.write(b"\x01\x04\x02\xff\x01\x07") # read HW minor version (register 2) -# # print(f"In waiting: <{ser.in_waiting}>") -# -# data = ser.read(100) -# print(f"Data: {data}") -# ser.close() -# assert not ser.is_open -# -# # assert data[0] == '\t' - - -# FIXME -# def test_device_events(device: Device) -> None: -# while True: -# print(device.event_count()) -# for msg in device.get_events(): -# print(msg) -# time.sleep(0.3) +# assert reply is not None + +# # read register 38 +# reply = device.read_u8(register) +# assert reply is not None +# assert reply.payload_as_int() == write_value + +# device.disconnect() + + +# # def test_read_hw_version_integration() -> None: +# # +# # # serial settings +# # ser = serial.Serial( +# # "/dev/tty.usbserial-A106C8O9", +# # baudrate=1000000, +# # timeout=5, +# # parity=serial.PARITY_NONE, +# # stopbits=1, +# # bytesize=8, +# # rtscts=True, +# # ) +# # +# # assert ser.is_open +# # +# # ser.write(b"\x01\x04\x01\xff\x01\x06") # read HW major version (register 1) +# # ser.write(b"\x01\x04\x02\xff\x01\x07") # read HW minor version (register 2) +# # # print(f"In waiting: <{ser.in_waiting}>") +# # +# # data = ser.read(100) +# # print(f"Data: {data}") +# # ser.close() +# # assert not ser.is_open +# # +# # # assert data[0] == '\t' + + +# # FIXME +# # def test_device_events(device: Device) -> None: +# # while True: +# # print(device.event_count()) +# # for msg in device.get_events(): +# # print(msg) +# # time.sleep(0.3) From 439802429ed222db9f703051f160f73d3c50195d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 9 Apr 2025 16:37:41 +0100 Subject: [PATCH 091/159] Add pytest-cov dev dependency --- pyproject.toml | 1 + uv.lock | 109 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index b124dea..7225be3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,6 +19,7 @@ dev = [ "mkdocs-material>=9.6.9", "mkdocstrings-python>=1.16.6", "pytest>=8.3.5", + "pytest-cov>=6.1.1", "ruff>=0.11.0", ] diff --git a/uv.lock b/uv.lock index 046433d..9a082b9 100644 --- a/uv.lock +++ b/uv.lock @@ -102,6 +102,61 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, ] +[[package]] +name = "coverage" +version = "7.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/19/4f/2251e65033ed2ce1e68f00f91a0294e0f80c80ae8c3ebbe2f12828c4cd53/coverage-7.8.0.tar.gz", hash = "sha256:7a3d62b3b03b4b6fd41a085f3574874cf946cb4604d2b4d3e8dca8cd570ca501", size = 811872 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/77/074d201adb8383addae5784cb8e2dac60bb62bfdf28b2b10f3a3af2fda47/coverage-7.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7ac22a0bb2c7c49f441f7a6d46c9c80d96e56f5a8bc6972529ed43c8b694e27", size = 211493 }, + { url = "https://files.pythonhosted.org/packages/a9/89/7a8efe585750fe59b48d09f871f0e0c028a7b10722b2172dfe021fa2fdd4/coverage-7.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf13d564d310c156d1c8e53877baf2993fb3073b2fc9f69790ca6a732eb4bfea", size = 211921 }, + { url = "https://files.pythonhosted.org/packages/e9/ef/96a90c31d08a3f40c49dbe897df4f1fd51fb6583821a1a1c5ee30cc8f680/coverage-7.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5761c70c017c1b0d21b0815a920ffb94a670c8d5d409d9b38857874c21f70d7", size = 244556 }, + { url = "https://files.pythonhosted.org/packages/89/97/dcd5c2ce72cee9d7b0ee8c89162c24972fb987a111b92d1a3d1d19100c61/coverage-7.8.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5ff52d790c7e1628241ffbcaeb33e07d14b007b6eb00a19320c7b8a7024c040", size = 242245 }, + { url = "https://files.pythonhosted.org/packages/b2/7b/b63cbb44096141ed435843bbb251558c8e05cc835c8da31ca6ffb26d44c0/coverage-7.8.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d39fc4817fd67b3915256af5dda75fd4ee10621a3d484524487e33416c6f3543", size = 244032 }, + { url = "https://files.pythonhosted.org/packages/97/e3/7fa8c2c00a1ef530c2a42fa5df25a6971391f92739d83d67a4ee6dcf7a02/coverage-7.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b44674870709017e4b4036e3d0d6c17f06a0e6d4436422e0ad29b882c40697d2", size = 243679 }, + { url = "https://files.pythonhosted.org/packages/4f/b3/e0a59d8df9150c8a0c0841d55d6568f0a9195692136c44f3d21f1842c8f6/coverage-7.8.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8f99eb72bf27cbb167b636eb1726f590c00e1ad375002230607a844d9e9a2318", size = 241852 }, + { url = "https://files.pythonhosted.org/packages/9b/82/db347ccd57bcef150c173df2ade97976a8367a3be7160e303e43dd0c795f/coverage-7.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b571bf5341ba8c6bc02e0baeaf3b061ab993bf372d982ae509807e7f112554e9", size = 242389 }, + { url = "https://files.pythonhosted.org/packages/21/f6/3f7d7879ceb03923195d9ff294456241ed05815281f5254bc16ef71d6a20/coverage-7.8.0-cp311-cp311-win32.whl", hash = "sha256:e75a2ad7b647fd8046d58c3132d7eaf31b12d8a53c0e4b21fa9c4d23d6ee6d3c", size = 213997 }, + { url = "https://files.pythonhosted.org/packages/28/87/021189643e18ecf045dbe1e2071b2747901f229df302de01c998eeadf146/coverage-7.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:3043ba1c88b2139126fc72cb48574b90e2e0546d4c78b5299317f61b7f718b78", size = 214911 }, + { url = "https://files.pythonhosted.org/packages/aa/12/4792669473297f7973518bec373a955e267deb4339286f882439b8535b39/coverage-7.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bbb5cc845a0292e0c520656d19d7ce40e18d0e19b22cb3e0409135a575bf79fc", size = 211684 }, + { url = "https://files.pythonhosted.org/packages/be/e1/2a4ec273894000ebedd789e8f2fc3813fcaf486074f87fd1c5b2cb1c0a2b/coverage-7.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4dfd9a93db9e78666d178d4f08a5408aa3f2474ad4d0e0378ed5f2ef71640cb6", size = 211935 }, + { url = "https://files.pythonhosted.org/packages/f8/3a/7b14f6e4372786709a361729164125f6b7caf4024ce02e596c4a69bccb89/coverage-7.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f017a61399f13aa6d1039f75cd467be388d157cd81f1a119b9d9a68ba6f2830d", size = 245994 }, + { url = "https://files.pythonhosted.org/packages/54/80/039cc7f1f81dcbd01ea796d36d3797e60c106077e31fd1f526b85337d6a1/coverage-7.8.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0915742f4c82208ebf47a2b154a5334155ed9ef9fe6190674b8a46c2fb89cb05", size = 242885 }, + { url = "https://files.pythonhosted.org/packages/10/e0/dc8355f992b6cc2f9dcd5ef6242b62a3f73264893bc09fbb08bfcab18eb4/coverage-7.8.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a40fcf208e021eb14b0fac6bdb045c0e0cab53105f93ba0d03fd934c956143a", size = 245142 }, + { url = "https://files.pythonhosted.org/packages/43/1b/33e313b22cf50f652becb94c6e7dae25d8f02e52e44db37a82de9ac357e8/coverage-7.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a1f406a8e0995d654b2ad87c62caf6befa767885301f3b8f6f73e6f3c31ec3a6", size = 244906 }, + { url = "https://files.pythonhosted.org/packages/05/08/c0a8048e942e7f918764ccc99503e2bccffba1c42568693ce6955860365e/coverage-7.8.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:77af0f6447a582fdc7de5e06fa3757a3ef87769fbb0fdbdeba78c23049140a47", size = 243124 }, + { url = "https://files.pythonhosted.org/packages/5b/62/ea625b30623083c2aad645c9a6288ad9fc83d570f9adb913a2abdba562dd/coverage-7.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f2d32f95922927186c6dbc8bc60df0d186b6edb828d299ab10898ef3f40052fe", size = 244317 }, + { url = "https://files.pythonhosted.org/packages/62/cb/3871f13ee1130a6c8f020e2f71d9ed269e1e2124aa3374d2180ee451cee9/coverage-7.8.0-cp312-cp312-win32.whl", hash = "sha256:769773614e676f9d8e8a0980dd7740f09a6ea386d0f383db6821df07d0f08545", size = 214170 }, + { url = "https://files.pythonhosted.org/packages/88/26/69fe1193ab0bfa1eb7a7c0149a066123611baba029ebb448500abd8143f9/coverage-7.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:e5d2b9be5b0693cf21eb4ce0ec8d211efb43966f6657807f6859aab3814f946b", size = 214969 }, + { url = "https://files.pythonhosted.org/packages/f3/21/87e9b97b568e223f3438d93072479c2f36cc9b3f6b9f7094b9d50232acc0/coverage-7.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ac46d0c2dd5820ce93943a501ac5f6548ea81594777ca585bf002aa8854cacd", size = 211708 }, + { url = "https://files.pythonhosted.org/packages/75/be/882d08b28a0d19c9c4c2e8a1c6ebe1f79c9c839eb46d4fca3bd3b34562b9/coverage-7.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:771eb7587a0563ca5bb6f622b9ed7f9d07bd08900f7589b4febff05f469bea00", size = 211981 }, + { url = "https://files.pythonhosted.org/packages/7a/1d/ce99612ebd58082fbe3f8c66f6d8d5694976c76a0d474503fa70633ec77f/coverage-7.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42421e04069fb2cbcbca5a696c4050b84a43b05392679d4068acbe65449b5c64", size = 245495 }, + { url = "https://files.pythonhosted.org/packages/dc/8d/6115abe97df98db6b2bd76aae395fcc941d039a7acd25f741312ced9a78f/coverage-7.8.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:554fec1199d93ab30adaa751db68acec2b41c5602ac944bb19187cb9a41a8067", size = 242538 }, + { url = "https://files.pythonhosted.org/packages/cb/74/2f8cc196643b15bc096d60e073691dadb3dca48418f08bc78dd6e899383e/coverage-7.8.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aaeb00761f985007b38cf463b1d160a14a22c34eb3f6a39d9ad6fc27cb73008", size = 244561 }, + { url = "https://files.pythonhosted.org/packages/22/70/c10c77cd77970ac965734fe3419f2c98665f6e982744a9bfb0e749d298f4/coverage-7.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:581a40c7b94921fffd6457ffe532259813fc68eb2bdda60fa8cc343414ce3733", size = 244633 }, + { url = "https://files.pythonhosted.org/packages/38/5a/4f7569d946a07c952688debee18c2bb9ab24f88027e3d71fd25dbc2f9dca/coverage-7.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f319bae0321bc838e205bf9e5bc28f0a3165f30c203b610f17ab5552cff90323", size = 242712 }, + { url = "https://files.pythonhosted.org/packages/bb/a1/03a43b33f50475a632a91ea8c127f7e35e53786dbe6781c25f19fd5a65f8/coverage-7.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04bfec25a8ef1c5f41f5e7e5c842f6b615599ca8ba8391ec33a9290d9d2db3a3", size = 244000 }, + { url = "https://files.pythonhosted.org/packages/6a/89/ab6c43b1788a3128e4d1b7b54214548dcad75a621f9d277b14d16a80d8a1/coverage-7.8.0-cp313-cp313-win32.whl", hash = "sha256:dd19608788b50eed889e13a5d71d832edc34fc9dfce606f66e8f9f917eef910d", size = 214195 }, + { url = "https://files.pythonhosted.org/packages/12/12/6bf5f9a8b063d116bac536a7fb594fc35cb04981654cccb4bbfea5dcdfa0/coverage-7.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:a9abbccd778d98e9c7e85038e35e91e67f5b520776781d9a1e2ee9d400869487", size = 214998 }, + { url = "https://files.pythonhosted.org/packages/2a/e6/1e9df74ef7a1c983a9c7443dac8aac37a46f1939ae3499424622e72a6f78/coverage-7.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:18c5ae6d061ad5b3e7eef4363fb27a0576012a7447af48be6c75b88494c6cf25", size = 212541 }, + { url = "https://files.pythonhosted.org/packages/04/51/c32174edb7ee49744e2e81c4b1414ac9df3dacfcb5b5f273b7f285ad43f6/coverage-7.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:95aa6ae391a22bbbce1b77ddac846c98c5473de0372ba5c463480043a07bff42", size = 212767 }, + { url = "https://files.pythonhosted.org/packages/e9/8f/f454cbdb5212f13f29d4a7983db69169f1937e869a5142bce983ded52162/coverage-7.8.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e013b07ba1c748dacc2a80e69a46286ff145935f260eb8c72df7185bf048f502", size = 256997 }, + { url = "https://files.pythonhosted.org/packages/e6/74/2bf9e78b321216d6ee90a81e5c22f912fc428442c830c4077b4a071db66f/coverage-7.8.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d766a4f0e5aa1ba056ec3496243150698dc0481902e2b8559314368717be82b1", size = 252708 }, + { url = "https://files.pythonhosted.org/packages/92/4d/50d7eb1e9a6062bee6e2f92e78b0998848a972e9afad349b6cdde6fa9e32/coverage-7.8.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad80e6b4a0c3cb6f10f29ae4c60e991f424e6b14219d46f1e7d442b938ee68a4", size = 255046 }, + { url = "https://files.pythonhosted.org/packages/40/9e/71fb4e7402a07c4198ab44fc564d09d7d0ffca46a9fb7b0a7b929e7641bd/coverage-7.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b87eb6fc9e1bb8f98892a2458781348fa37e6925f35bb6ceb9d4afd54ba36c73", size = 256139 }, + { url = "https://files.pythonhosted.org/packages/49/1a/78d37f7a42b5beff027e807c2843185961fdae7fe23aad5a4837c93f9d25/coverage-7.8.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d1ba00ae33be84066cfbe7361d4e04dec78445b2b88bdb734d0d1cbab916025a", size = 254307 }, + { url = "https://files.pythonhosted.org/packages/58/e9/8fb8e0ff6bef5e170ee19d59ca694f9001b2ec085dc99b4f65c128bb3f9a/coverage-7.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f3c38e4e5ccbdc9198aecc766cedbb134b2d89bf64533973678dfcf07effd883", size = 255116 }, + { url = "https://files.pythonhosted.org/packages/56/b0/d968ecdbe6fe0a863de7169bbe9e8a476868959f3af24981f6a10d2b6924/coverage-7.8.0-cp313-cp313t-win32.whl", hash = "sha256:379fe315e206b14e21db5240f89dc0774bdd3e25c3c58c2c733c99eca96f1ada", size = 214909 }, + { url = "https://files.pythonhosted.org/packages/87/e9/d6b7ef9fecf42dfb418d93544af47c940aa83056c49e6021a564aafbc91f/coverage-7.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2e4b6b87bb0c846a9315e3ab4be2d52fac905100565f4b92f02c445c8799e257", size = 216068 }, + { url = "https://files.pythonhosted.org/packages/c4/f1/1da77bb4c920aa30e82fa9b6ea065da3467977c2e5e032e38e66f1c57ffd/coverage-7.8.0-pp39.pp310.pp311-none-any.whl", hash = "sha256:b8194fb8e50d556d5849753de991d390c5a1edeeba50f68e3a9253fbd8bf8ccd", size = 203443 }, + { url = "https://files.pythonhosted.org/packages/59/f1/4da7717f0063a222db253e7121bd6a56f6fb1ba439dcc36659088793347c/coverage-7.8.0-py3-none-any.whl", hash = "sha256:dbf364b4c5e7bae9250528167dfe40219b62e2d573c854d74be213e1e52069f7", size = 203435 }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version <= '3.11'" }, +] + [[package]] name = "ghp-import" version = "2.1.0" @@ -470,6 +525,7 @@ dev = [ { name = "mkdocs-material" }, { name = "mkdocstrings-python" }, { name = "pytest" }, + { name = "pytest-cov" }, { name = "ruff" }, ] @@ -485,6 +541,7 @@ dev = [ { name = "mkdocs-material", specifier = ">=9.6.9" }, { name = "mkdocstrings-python", specifier = ">=1.16.6" }, { name = "pytest", specifier = ">=8.3.5" }, + { name = "pytest-cov", specifier = ">=6.1.1" }, { name = "ruff", specifier = ">=0.11.0" }, ] @@ -525,6 +582,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 }, ] +[[package]] +name = "pytest-cov" +version = "6.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage", extra = ["toml"] }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/25/69/5f1e57f6c5a39f81411b550027bf72842c4567ff5fd572bed1edc9e4b5d9/pytest_cov-6.1.1.tar.gz", hash = "sha256:46935f7aaefba760e716c2ebfbe1c216240b9592966e7da99ea8292d4d3e2a0a", size = 66857 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/d0/def53b4a790cfb21483016430ed828f64830dd981ebe1089971cd10cab25/pytest_cov-6.1.1-py3-none-any.whl", hash = "sha256:bddf29ed2d0ab6f4df17b4c55b0a657287db8684af9c42ea546b21b1041b3dde", size = 23841 }, +] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -642,6 +712,45 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/04/be/d09147ad1ec7934636ad912901c5fd7667e1c858e19d355237db0d0cd5e4/smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e", size = 24303 }, ] +[[package]] +name = "tomli" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077 }, + { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429 }, + { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067 }, + { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030 }, + { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898 }, + { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894 }, + { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319 }, + { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273 }, + { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310 }, + { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309 }, + { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762 }, + { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453 }, + { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486 }, + { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349 }, + { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159 }, + { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243 }, + { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645 }, + { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584 }, + { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875 }, + { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418 }, + { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708 }, + { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582 }, + { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543 }, + { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691 }, + { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170 }, + { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530 }, + { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666 }, + { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954 }, + { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724 }, + { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383 }, + { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 }, +] + [[package]] name = "urllib3" version = "2.3.0" From 9c04d7281e61af27544a4bfc26455272d45a0f71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 9 Apr 2025 16:38:11 +0100 Subject: [PATCH 092/159] Add is_error to HarpMessage --- pyharp/device.py | 2 +- pyharp/messages.py | 14 +++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/pyharp/device.py b/pyharp/device.py index 40c19f0..dc71690 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -652,7 +652,7 @@ def _read_serial_number(self) -> int: dump=False, ) - if reply.has_error(): + if reply.is_error(): return 0 return reply.payload_as_int() diff --git a/pyharp/messages.py b/pyharp/messages.py index 526948d..9b469a5 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -298,6 +298,18 @@ def __str__(self) -> str: + f"Checksum: {self.checksum}" ) + @property + def is_error(self) -> bool: + """ + Indicates if this HarpMessage is an error message or not. + + Returns + ------- + bool + Returns True if this HarpMessage is an error message, False otherwise. + """ + return self.message_type in [MessageType.READ_ERROR, MessageType.WRITE_ERROR] + # TODO: handle float case @property def payload(self) -> Union[int, list[int]]: @@ -425,7 +437,7 @@ def __init__( value : int, float, List[int], or List[float], optional Value(s) to write - can be a single value or list of values - Notes + Note ----- The message frame is constructed according to the HARP binary protocol. The length is calculated as BASE_LENGTH + payload size in bytes. From 960ce9c77cbd75214e2de6d35553fc7d071ff5ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 9 Apr 2025 16:38:40 +0100 Subject: [PATCH 093/159] Read Timestamp entry for PayloadType --- pyharp/base.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyharp/base.py b/pyharp/base.py index 38e7214..c3a0750 100644 --- a/pyharp/base.py +++ b/pyharp/base.py @@ -56,6 +56,8 @@ class PayloadType(IntEnum): the value that corresponds to a message of type S64 Float : PayloadType the value that corresponds to a message of type Float + Timestamp: PayloadType + the value that corresponds to a message of type Timestamp. This is not a valid PayloadType, but it is used to indicate that the message has a timestamp. TimestampedU8 : PayloadType the value that corresponds to a message of type TimestampedU8 TimestampedS8 : PayloadType @@ -85,6 +87,7 @@ class PayloadType(IntEnum): U64 = _isUnsigned | 8 S64 = _isSigned | 8 Float = _isFloat | 4 + Timestamp = _hasTimestamp TimestampedU8 = _hasTimestamp | U8 TimestampedS8 = _hasTimestamp | S8 TimestampedU16 = _hasTimestamp | U16 From bfcb9ecdfe9a07ee5b1018438bb8de8de87caf7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 16 Apr 2025 11:38:46 +0100 Subject: [PATCH 094/159] Handle HarpMessage with arrays and timestamps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also moved the common properties to HarpMessage Co-authored-by: José Grilo --- pyharp/messages.py | 310 ++++++++++++++------------ pyproject.toml | 8 + tests/test_messages.py | 483 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 656 insertions(+), 145 deletions(-) diff --git a/pyharp/messages.py b/pyharp/messages.py index 9b469a5..cc03b85 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -29,6 +29,7 @@ class HarpMessage: """ DEFAULT_PORT: int = 255 + BASE_LENGTH: int = 4 _frame: bytearray def __init__(self): @@ -120,6 +121,135 @@ def payload_type(self) -> PayloadType: """ return PayloadType(self._frame[4]) + @property + def payload(self) -> Union[int, list[int]]: + """ + The payload sent in the write Harp message. + + Returns + ------- + Union[int, list[int]] + the payload sent in the write Harp message + """ + payload_start = self.BASE_LENGTH + if self.payload_type & PayloadType.Timestamp: + payload_start += 6 + + # length is payload_start + payload type size + match self.payload_type: + case PayloadType.U8 | PayloadType.TimestampedU8: + if self.length == payload_start + 1: + return self._frame[5] + else: # array case + return [ + int.from_bytes([self._frame[i]], byteorder="little") + for i in range(5, self.length + 1) + ] + + case PayloadType.S8 | PayloadType.TimestampedS8: + if self.length == payload_start + 1: + return int.from_bytes( + [self._frame[5]], byteorder="little", signed=True + ) + else: # array case + return [ + int.from_bytes( + [self._frame[i]], byteorder="little", signed=True + ) + for i in range(5, self.length + 1) + ] + + case PayloadType.U16 | PayloadType.TimestampedU16: + if self.length == payload_start + 2: + return int.from_bytes( + self._frame[5:7], byteorder="little", signed=False + ) + else: # array case + return [ + int.from_bytes( + self._frame[i : i + 2], byteorder="little", signed=False + ) + for i in range(5, self.length + 1, 2) + ] + + case PayloadType.S16 | PayloadType.TimestampedS16: + if self.length == payload_start + 2: + return int.from_bytes( + self._frame[5:7], byteorder="little", signed=True + ) + else: + return [ + int.from_bytes( + self._frame[i : i + 2], byteorder="little", signed=True + ) + for i in range(5, self.length + 1, 2) + ] + + case PayloadType.U32 | PayloadType.TimestampedU32: + if self.length == payload_start + 4: + return int.from_bytes( + self._frame[5:9], byteorder="little", signed=False + ) + else: + return [ + int.from_bytes( + self._frame[i : i + 4], byteorder="little", signed=False + ) + for i in range(5, self.length + 1, 4) + ] + + case PayloadType.S32 | PayloadType.TimestampedS32: + if self.length == payload_start + 4: + return int.from_bytes( + self._frame[5:9], byteorder="little", signed=True + ) + else: + return [ + int.from_bytes( + self._frame[i : i + 4], byteorder="little", signed=True + ) + for i in range(5, self.length + 1, 4) + ] + + case PayloadType.U64 | PayloadType.TimestampedU64: + if self.length == payload_start + 8: + return int.from_bytes( + self._frame[5:13], byteorder="little", signed=False + ) + else: + return [ + int.from_bytes( + self._frame[i : i + 8], byteorder="little", signed=False + ) + for i in range(5, self.length + 1, 8) + ] + + case PayloadType.S64 | PayloadType.TimestampedS64: + if self.length == payload_start + 8: + return int.from_bytes( + self._frame[5:13], byteorder="little", signed=True + ) + else: + return [ + int.from_bytes( + self._frame[i : i + 8], byteorder="little", signed=True + ) + for i in range(5, self.length + 1, 8) + ] + + case PayloadType.Float | PayloadType.TimestampedFloat: + if self.length == payload_start + 4: + return struct.unpack(" int: """ @@ -183,79 +313,6 @@ def create( "The value cannot be None is message type is equal to MessageType.WRITE!" ) - -class ReplyHarpMessage(HarpMessage): - """ - A response message from a Harp device. - - Attributes - ---------- - payload : Union[int, list[int]] - the message payload formatted as the appropriate type - timestamp : float - the Harp timestamp at which the message was sent - """ - - def __init__( - self, - frame: bytearray, - ): - """ - Parameters - ---------- - frame : bytearray - the Harp message in bytearray format - """ - - self._frame = frame - # Retrieve all content from 11 (where payload starts) until the checksum (not inclusive) - self._raw_payload = frame[11:-1] - - # Format payload as list[PayloadType] - self._payload = self._parse_payload(self._raw_payload) - - # Assign timestamp after _payload since @properties all rely on self._payload. - self._timestamp = ( - int.from_bytes(frame[5:9], byteorder="little", signed=False) - + int.from_bytes(frame[9:11], byteorder="little", signed=False) * 32e-6 - ) - - # Timestamp is junk if it's not present. - if not (self.payload_type & PayloadType.Timestamp): - self._timestamp = None - - # TODO: handle the case of the payload being of type float - def _parse_payload(self, raw_payload: bytearray) -> list[int]: - """ - Returns the payload as a list of ints after parsing it from the raw payload. - - Parameters - ---------- - raw_payload : bytearray - the bytearray containing the raw payload - - Returns - ------- - list[int] - the list of ints containing the payload - """ - is_signed = True if (self.payload_type & 0x80) else False - is_float = True if (self.payload_type & 0x40) else False - bytes_per_word = self.payload_type & 0x07 - payload_len = len(raw_payload) - - word_chunks = [ - raw_payload[i : i + bytes_per_word] - for i in range(0, payload_len, bytes_per_word) - ] - if not is_float: - return [ - int.from_bytes(chunk, byteorder="little", signed=is_signed) - for chunk in word_chunks - ] - else: # TODO: handle float case - return [struct.unpack(" str: """ Prints debug representation of the reply message. @@ -269,12 +326,12 @@ def __repr__(self) -> str: def __str__(self) -> str: """ - Prints friendly representation of the reply message. + Prints friendly representation of a Harp message. Returns ------- str - the representation of the reply message + the representation of the Harp message """ payload_str = "" format_str = "" @@ -284,7 +341,10 @@ def __str__(self) -> str: bytes_per_word = self.payload_type & 0x07 format_str = f"0{bytes_per_word}b" - payload_str = "".join(f"{item:{format_str}} " for item in self.payload) + payload_str = "".join( + f"{item:{format_str}} " + for item in (self.payload if self.payload is list else [self.payload]) + ) return ( f"Type: {self.message_type.name}\r\n" @@ -293,11 +353,49 @@ def __str__(self) -> str: + f"Port: {self.port}\r\n" + f"Timestamp: {self.timestamp}\r\n" + f"Payload Type: {self.payload_type.name}\r\n" - + f"Payload Length: {len(self.payload)}\r\n" + + f"Payload Length: {len(self.payload) if self.payload is list else 1}\r\n" + f"Payload: {payload_str}\r\n" + f"Checksum: {self.checksum}" ) + +class ReplyHarpMessage(HarpMessage): + """ + A response message from a Harp device. + + Attributes + ---------- + payload : Union[int, list[int]] + the message payload formatted as the appropriate type + timestamp : float + the Harp timestamp at which the message was sent + """ + + def __init__( + self, + frame: bytearray, + ): + """ + Parameters + ---------- + frame : bytearray + the Harp message in bytearray format + """ + + self._frame = frame + # Retrieve all content from 11 (where payload starts) until the checksum (not inclusive) + self._raw_payload = frame[11:-1] + + # Assign timestamp after _payload since @properties all rely on self._payload. + self._timestamp = ( + int.from_bytes(frame[5:9], byteorder="little", signed=False) + + int.from_bytes(frame[9:11], byteorder="little", signed=False) * 32e-6 + ) + + # Timestamp is junk if it's not present. + if not (self.payload_type & PayloadType.Timestamp): + self._timestamp = None + @property def is_error(self) -> bool: """ @@ -310,19 +408,6 @@ def is_error(self) -> bool: """ return self.message_type in [MessageType.READ_ERROR, MessageType.WRITE_ERROR] - # TODO: handle float case - @property - def payload(self) -> Union[int, list[int]]: - """ - The message payload formatted as the appropriate type. - - Returns - ------- - Union[int, list[int]] - the message payload formatted as the appropriate type - """ - return self._payload - @property def timestamp(self) -> float: """ @@ -345,7 +430,7 @@ def payload_as_int(self) -> int: int the payload parsed as an int """ - return self.payload[0] + return self._raw_payload[0] def payload_as_string(self) -> str: """ @@ -401,8 +486,6 @@ class WriteHarpMessage(HarpMessage): the payload sent in the write Harp message """ - BASE_LENGTH: int = 5 - BASE_LENGTH: int = 4 MESSAGE_TYPE: int = MessageType.WRITE # Define payload type properties @@ -469,44 +552,3 @@ def __init__( self._frame.append(payload_type) self._frame += payload self._frame.append(self.calculate_checksum()) - - # TODO: handle float and array cases - @property - def payload(self) -> Union[int, list[int]]: - """ - The payload sent in the write Harp message. - - Returns - ------- - Union[int, list[int]] - the payload sent in the write Harp message - """ - match self.payload_type: - case PayloadType.U8: - return self._frame[5] - case PayloadType.S8: - return int.from_bytes([self.frame[5]], byteorder="little", signed=True) - case PayloadType.U16: - return int.from_bytes( - self._frame[5:7], byteorder="little", signed=False - ) - case PayloadType.S16: - return int.from_bytes(self._frame[5:7], byteorder="little", signed=True) - case PayloadType.Float: - return struct.unpack(" None: message = ReadHarpMessage(payload_type=PayloadType.U8, address=DEFAULT_ADDRESS) assert message.message_type == MessageType.READ assert message.checksum == 47 # 1 + 4 + 42 + 255 + 1 - 256 - print(message.frame) def test_create_read_S8() -> None: @@ -17,7 +115,6 @@ def test_create_read_S8() -> None: assert message.message_type == MessageType.READ assert message.checksum == 175 # 1 + 4 + 42 + 255 + 129 - 256 - print(message.frame) def test_create_read_U16() -> None: @@ -25,7 +122,6 @@ def test_create_read_U16() -> None: assert message.message_type == MessageType.READ assert message.checksum == 48 # 1 + 4 + 42 + 255 + 2 - 256 - print(message.frame) def test_create_read_S16() -> None: @@ -33,7 +129,41 @@ def test_create_read_S16() -> None: assert message.message_type == MessageType.READ assert message.checksum == 176 # 1 + 4 + 42 + 255 + 130 - 256 - print(message.frame) + + +def test_create_read_U32() -> None: + message = ReadHarpMessage(payload_type=PayloadType.U32, address=DEFAULT_ADDRESS) + + assert message.message_type == MessageType.READ + assert message.checksum == 50 # 1 + 4 + 42 + 255 + 4 - 256 + + +def test_create_read_S32() -> None: + message = ReadHarpMessage(payload_type=PayloadType.S32, address=DEFAULT_ADDRESS) + + assert message.message_type == MessageType.READ + assert message.checksum == 178 # 1 + 4 + 42 + 255 + 130 - 256 + + +def test_create_read_U64() -> None: + message = ReadHarpMessage(payload_type=PayloadType.U64, address=DEFAULT_ADDRESS) + + assert message.message_type == MessageType.READ + assert message.checksum == 54 # 1 + 4 + 42 + 255 + 2 - 256 + + +def test_create_read_S64() -> None: + message = ReadHarpMessage(payload_type=PayloadType.S64, address=DEFAULT_ADDRESS) + + assert message.message_type == MessageType.READ + assert message.checksum == 182 # 1 + 4 + 42 + 255 + 130 - 256 + + +def test_create_read_float() -> None: + message = ReadHarpMessage(payload_type=PayloadType.Float, address=DEFAULT_ADDRESS) + + assert message.message_type == MessageType.READ + assert message.checksum == 114 # 1 + 4 + 42 + 255 + 4 - 256 def test_create_write_U8() -> None: @@ -42,8 +172,7 @@ def test_create_write_U8() -> None: assert message.message_type == MessageType.WRITE assert message.payload == value - assert message.checksum == 72 # 2 + 5 + 42 + 255 + 1 + 23 - 256 - print(message.frame) + assert message.checksum == 72 # 2 + 4 + 42 + 255 + 1 + 23 = 328 → 328 % 256 = 73 def test_create_write_S8() -> None: @@ -53,7 +182,6 @@ def test_create_write_S8() -> None: assert message.message_type == MessageType.WRITE assert message.payload == value assert message.checksum == 174 # (2 + 5 + 42 + 255 + 129 + 253) & 255 - print(message.frame) def test_create_write_U16() -> None: @@ -64,7 +192,6 @@ def test_create_write_U16() -> None: assert message.length == 6 assert message.payload == value assert message.checksum == 55 # (2 + 6 + 42 + 255 + 2 + 4 + 0) & 255 - print(message.frame) def test_create_write_S16() -> None: @@ -75,7 +202,115 @@ def test_create_write_S16() -> None: assert message.length == 6 assert message.payload == value assert message.checksum == 187 # (2 + 6 + 42 + 255 + 130 + 27 + 237) & 255 - print(message.frame) + + +def test_create_write_U8_array() -> None: + values: list[int] = [1, 2, 3, 4, 5] + message = WriteHarpMessage(PayloadType.U8, DEFAULT_ADDRESS, values) + + assert message.message_type == MessageType.WRITE + assert message.length == 4 + len( + values + ) # 7 header bytes + len(values) payload bytes + assert message.payload == values + assert message.checksum == 68 # (2 + (4 + 5) + 42 + 255 + 1 + 5) & 255 + + +def test_create_write_S8_array() -> None: + values: list[int] = [-1, -2, -3, -4, -5] + message = WriteHarpMessage(PayloadType.S8, DEFAULT_ADDRESS, values) + + assert message.message_type == MessageType.WRITE + assert message.length == 4 + len( + values + ) # 7 header bytes + len(values) payload bytes + assert message.payload == values + assert message.checksum == 166 # (2 + (4 + 5) + 42 + 255 + 129 + 5) & 255 + + +def test_create_write_U16_array() -> None: + values: list[int] = [1, 2, 3, 4, 5] + message = WriteHarpMessage(PayloadType.U16, DEFAULT_ADDRESS, values) + + assert message.message_type == MessageType.WRITE + assert ( + message.length == 4 + len(values) * 2 + ) # 7 header bytes + len(values) * 2 payload bytes + assert message.payload == values + assert message.checksum == 74 # (2 + (4 + 5 * 2) + 42 + 255 + 2 + 5) & 255 + + +def test_create_write_S16_array() -> None: + values: list[int] = [-1, -2, -3, -4, -5] + message = WriteHarpMessage(PayloadType.S16, DEFAULT_ADDRESS, values) + + assert message.message_type == MessageType.WRITE + assert ( + message.length == 4 + len(values) * 2 + ) # 7 header bytes + len(values) * 2 payload bytes + assert message.payload == values + assert message.checksum == 167 # (2 + (4 + 5) + 42 + 255 + 129 + 5) & 255 + + +def test_create_write_U32_array() -> None: + values: list[int] = [1, 2, 3, 4, 5] + message = WriteHarpMessage(PayloadType.U32, DEFAULT_ADDRESS, values) + + assert message.message_type == MessageType.WRITE + assert ( + message.length == 4 + len(values) * 4 + ) # 7 header bytes + len(values) * 4 payload bytes + assert message.payload == values + assert message.checksum == 86 # (2 + (4 + 5 * 4) + 42 + 255 + 4 + 5) & 255 + + +def test_create_write_S32_array() -> None: + values: list[int] = [-1, -2, -3, -4, -5] + message = WriteHarpMessage(PayloadType.S32, DEFAULT_ADDRESS, values) + + assert message.message_type == MessageType.WRITE + assert ( + message.length == 4 + len(values) * 4 + ) # 7 header bytes + len(values) * 4 payload bytes + assert message.payload == values + assert message.checksum == 169 # (2 + (4 + 5 * 4) + 42 + 255 + 130 + 5) & 255 + + +def test_create_write_U64_array() -> None: + values: list[int] = [1, 2, 3, 4, 5] + message = WriteHarpMessage(PayloadType.U64, DEFAULT_ADDRESS, values) + + assert message.message_type == MessageType.WRITE + assert ( + message.length == 4 + len(values) * 8 + ) # 7 header bytes + len(values) * 8 payload bytes + assert message.payload == values + assert message.checksum == 110 # (2 + (4 + 5 * 8) + 42 + 255 + 2 + 5) & 255 + + +def test_create_write_S64_array() -> None: + values: list[int] = [-1, -2, -3, -4, -5] + message = WriteHarpMessage(PayloadType.S64, DEFAULT_ADDRESS, values) + + assert message.message_type == MessageType.WRITE + assert ( + message.length == 4 + len(values) * 8 + ) # 7 header bytes + len(values) * 8 payload bytes + assert message.payload == values + assert message.checksum == 173 # (2 + (4 + 5 * 8) + 42 + 255 + 130 + 5) & 255 + + +def test_create_write_float_array() -> None: + """Test creating a write message with float array values.""" + values = [1.1, 2.2, 3.3] + message = WriteHarpMessage(PayloadType.Float, DEFAULT_ADDRESS, values) + + assert message.message_type == MessageType.WRITE + expected_checksum = 193 # (2 + 4 + 42 + 255 + 1 + 3 * 4) & 255 + assert len(message.payload) == len(values) + for actual, expected in zip(message.payload, values): + assert abs(actual - expected) < 0.0001 # Float comparison with error margin + assert message.checksum == expected_checksum def test_read_who_am_i() -> None: @@ -84,3 +319,229 @@ def test_read_who_am_i() -> None: ) assert str(message.frame) == str(bytearray(b"\x01\x04\x00\xff\x02\x06")) + + +def test_create_write_U32() -> None: + """Test creating a write message with S32 value.""" + value: int = 2147483000 # Large number + message = WriteHarpMessage(PayloadType.U32, DEFAULT_ADDRESS, value) + + assert message.message_type == MessageType.WRITE + assert message.length == 8 + assert message.payload == value + assert len(message.frame) == 10 # length + checksum byte + # Calculate checksum as in other tests + expected_checksum = 42 # (2 + 8 + 42 + 255 + 4 + 0 + 0 + 0 + 0) & 255 + assert message.checksum == expected_checksum + + +def test_create_write_S32() -> None: + """Test creating a write message with S32 value.""" + value: int = -2147483000 # Large negative number + message = WriteHarpMessage(PayloadType.S32, DEFAULT_ADDRESS, value) + + assert message.message_type == MessageType.WRITE + assert message.length == 8 + assert message.payload == value + assert len(message.frame) == 10 # length + checksum byte + # Calculate checksum as in other tests + expected_checksum = 193 # (2 + 8 + 42 + 255 + 130 + 0 + 0 + 0 + 0) & 255 + assert message.checksum == expected_checksum + + +def test_create_write_U64() -> None: + """Test creating a write message with U64 value.""" + value: int = 9223372036854775807 # Large 64-bit value + message = WriteHarpMessage(PayloadType.U64, DEFAULT_ADDRESS, value) + + assert message.message_type == MessageType.WRITE + assert message.length == 12 # 5 header bytes + 8 payload bytes + assert message.payload == value + assert len(message.frame) == 14 # length + checksum byte + # Calculate checksum for 64-bit value + expected_checksum = 183 # (2 + 12 + 42 + 255 + 2 + 0 + 0 + 0 + 0) & 255 + assert message.checksum == expected_checksum + + +def test_create_write_S64() -> None: + """Test creating a write message with S64 value.""" + value: int = -9223372036854775807 + message = WriteHarpMessage(PayloadType.S64, DEFAULT_ADDRESS, value) + assert message.message_type == MessageType.WRITE + assert message.length == 12 + assert message.payload == value + assert len(message.frame) == 14 # length + checksum byte + # Calculate checksum for 64-bit signed value + expected_checksum = 64 # (2 + 12 + 42 + 255 + 130 + 0 + 0 + 0 + 0) & 255 + assert message.checksum == expected_checksum + + +def test_reply_message_str_repr() -> None: + """Test string representation of Reply message.""" + # Create a simple reply message + frame = bytearray( + [ + MessageType.READ, + 5, + 42, + 255, + PayloadType.U8, + 0, + 0, + 0, + 0, # timestamp seconds + 0, + 0, # timestamp micros + 123, # payload + 0, + ] + ) # checksum placeholder + + # Fix checksum + checksum = sum(frame[:-1]) & 255 + frame[-1] = checksum + + reply = ReplyHarpMessage(frame) + str_repr = str(reply) + repr_str = repr(reply) + + assert "Type: READ" in str_repr + assert "Length: 5" in str_repr + assert "Address: 42" in str_repr + assert "Port: 255" in str_repr + assert "Payload: " in str_repr + assert "Raw Frame" in repr_str + + +def test_payload_as_string() -> None: + """Test ReplyHarpMessage.payload_as_string().""" + test_string = "Hello" + encoded = test_string.encode("utf-8") + + frame = bytearray( + [ + MessageType.READ, + 5 + len(encoded), + 42, + 255, + PayloadType.U8, + 0, + 0, + 0, + 0, # timestamp seconds + 0, + 0, + ] + ) # timestamp micros + + # Add string payload + frame.extend(encoded) + # Add checksum + frame.append(0) # placeholder + + # Fix checksum + checksum = sum(frame[:-1]) & 255 + frame[-1] = checksum + + reply = ReplyHarpMessage(frame) + assert reply.payload_as_string() == test_string + + +def test_harp_message_parse() -> None: + """Test the static parse method of HarpMessage.""" + frame = bytearray( + [ + MessageType.READ, + 5, + 42, + 255, + PayloadType.U8, + 0, + 0, + 0, + 0, # timestamp seconds + 0, + 0, # timestamp micros + 123, # payload + 0, + ] + ) # checksum placeholder + + # Fix checksum + checksum = sum(frame[:-1]) & 255 + frame[-1] = checksum + + message = HarpMessage.parse(frame) + assert isinstance(message, ReplyHarpMessage) + assert message.message_type == MessageType.READ + assert message.address == 42 + assert message.payload_as_int() == 123 + + +def test_timestamp_handling() -> None: + """Test timestamp handling in ReplyHarpMessage.""" + # Create a timestamped message + frame = bytearray( + [ + MessageType.READ, + 5, + 42, + 255, + PayloadType.TimestampedU8, + 1, + 0, + 0, + 0, # timestamp seconds = 1 + 32, + 0, # timestamp micros = 32 (= 1ms) + 123, # payload + 0, + ] + ) # checksum placeholder + + # Fix checksum + checksum = sum(frame[:-1]) & 255 + frame[-1] = checksum + + reply = ReplyHarpMessage(frame) + assert reply.timestamp is not None + assert reply.timestamp == 1 + 32 * 32e-6 # 1 second + 1 millisecond + + # Create a non-timestamped message + frame = bytearray( + [ + MessageType.READ, + 5, + 42, + 255, + PayloadType.U8, + 1, + 0, + 0, + 0, # timestamp seconds = 1 + 32, + 0, # timestamp micros = 32 (= 1ms) + 123, # payload + 0, + ] + ) # checksum placeholder + + # Fix checksum + checksum = sum(frame[:-1]) & 255 + frame[-1] = checksum + + reply = ReplyHarpMessage(frame) + assert reply.timestamp is None + + +def test_calculate_checksum() -> None: + """Test the calculate_checksum method.""" + message = HarpMessage() + message._frame = bytearray([1, 2, 3, 4, 5]) + + # Sum is 15, checksum is 15 (no overflow) + assert message.calculate_checksum() == 15 + + message._frame = bytearray([200, 100, 50, 20, 10]) + # Sum is 380, checksum is 380 % 256 = 124 + assert message.calculate_checksum() == 124 From 7223e0c52e89591fdbf988a8fcea50882daede42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 23 Apr 2025 10:52:54 +0100 Subject: [PATCH 095/159] Restore read and write methods from Device MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Grilo --- pyharp/device.py | 423 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 423 insertions(+) diff --git a/pyharp/device.py b/pyharp/device.py index dc71690..355ab6d 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -463,6 +463,429 @@ def event_count(self) -> int: """ return self._ser.event_q.qsize() + def read_u8(self, address: int) -> ReplyHarpMessage: + """ + Reads the value of a register of type U8. + + Parameters + ---------- + address : int + the register to be read + + Returns + ------- + ReplyHarpMessage + the reply to the Harp message that will contain the value read from the register + """ + return self.send( + HarpMessage.create( + message_type=MessageType.READ, + address=address, + payload_type=PayloadType.U8, + ).frame + ) + + def read_s8(self, address: int) -> ReplyHarpMessage: + """ + Reads the value of a register of type S8. + + Parameters + ---------- + address : int + the register to be read + + Returns + ------- + ReplyHarpMessage + the reply to the Harp message that will contain the value read from the register + """ + return self.send( + HarpMessage.create( + message_type=MessageType.READ, + address=address, + payload_type=PayloadType.S8, + ).frame + ) + + def read_u16(self, address: int) -> ReplyHarpMessage: + """ + Reads the value of a register of type U16. + + Parameters + ---------- + address : int + the register to be read + + Returns + ------- + ReplyHarpMessage + the reply to the Harp message that will contain the value read from the register + """ + return self.send( + HarpMessage.create( + message_type=MessageType.READ, + address=address, + payload_type=PayloadType.U16, + ).frame + ) + + def read_s16(self, address: int) -> ReplyHarpMessage: + """ + Reads the value of a register of type S16. + + Parameters + ---------- + address : int + the register to be read + + Returns + ------- + ReplyHarpMessage + the reply to the Harp message that will contain the value read from the register + """ + return self.send( + HarpMessage.create( + message_type=MessageType.READ, + address=address, + payload_type=PayloadType.S16, + ).frame + ) + + def read_u32(self, address: int) -> ReplyHarpMessage: + """ + Reads the value of a register of type U32. + + Parameters + ---------- + address : int + the register to be read + + Returns + ------- + ReplyHarpMessage + the reply to the Harp message that will contain the value read from the register + """ + return self.send( + HarpMessage.create( + message_type=MessageType.READ, + address=address, + payload_type=PayloadType.U32, + ).frame + ) + + def read_s32(self, address: int) -> ReplyHarpMessage: + """ + Reads the value of a register of type S32. + + Parameters + ---------- + address : int + the register to be read + + Returns + ------- + ReplyHarpMessage + the reply to the Harp message that will contain the value read from the register + """ + return self.send( + HarpMessage.create( + message_type=MessageType.READ, + address=address, + payload_type=PayloadType.S32, + ).frame + ) + + def read_u64(self, address: int) -> ReplyHarpMessage: + """ + Reads the value of a register of type U64. + + Parameters + ---------- + address : int + the register to be read + + Returns + ------- + ReplyHarpMessage + the reply to the Harp message that will contain the value read from the register + """ + return self.send( + HarpMessage.create( + message_type=MessageType.READ, + address=address, + payload_type=PayloadType.U64, + ).frame + ) + + def read_s64(self, address: int) -> ReplyHarpMessage: + """ + Reads the value of a register of type S64. + + Parameters + ---------- + address : int + the register to be read + + Returns + ------- + ReplyHarpMessage + the reply to the Harp message that will contain the value read from the register + """ + return self.send( + HarpMessage.create( + message_type=MessageType.READ, + address=address, + payload_type=PayloadType.S64, + ).frame + ) + + def read_float(self, address: int) -> ReplyHarpMessage: + """ + Reads the value of a register of type Float. + + Parameters + ---------- + address : int + the register to be read + + Returns + ------- + ReplyHarpMessage + the reply to the Harp message that will contain the value read from the register + """ + return self.send( + HarpMessage.create( + message_type=MessageType.READ, + address=address, + payload_type=PayloadType.Float, + ).frame + ) + + def write_u8(self, address: int, value: int | list[int]) -> ReplyHarpMessage: + """ + Writes the value of a register of type U8. + + Parameters + ---------- + address : int + the register to be written on + value: int | list[int] + the value to be written to the register + + Returns + ------- + ReplyHarpMessage + the reply to the Harp message + """ + return self.send( + HarpMessage.create( + message_type=MessageType.WRITE, + address=address, + payload_type=PayloadType.U8, + value=value, + ).frame + ) + + def write_s8(self, address: int, value: int | list[int]) -> ReplyHarpMessage: + """ + Writes the value of a register of type S8. + + Parameters + ---------- + address : int + the register to be written on + value: int | list[int] + the value to be written to the register + + Returns + ------- + ReplyHarpMessage + the reply to the Harp message + """ + return self.send( + HarpMessage.create( + message_type=MessageType.WRITE, + address=address, + payload_type=PayloadType.S8, + value=value, + ).frame + ) + + def write_u16(self, address: int, value: int | list[int]) -> ReplyHarpMessage: + """ + Writes the value of a register of type U16. + + Parameters + ---------- + address : int + the register to be written on + value: int | list[int] + the value to be written to the register + + Returns + ------- + ReplyHarpMessage + the reply to the Harp message + """ + return self.send( + HarpMessage.create( + message_type=MessageType.WRITE, + address=address, + payload_type=PayloadType.U16, + value=value, + ).frame + ) + + def write_s16(self, address: int, value: int | list[int]) -> ReplyHarpMessage: + """ + Writes the value of a register of type S16. + + Parameters + ---------- + address : int + the register to be written on + value: int | list[int] + the value to be written to the register + + Returns + ------- + ReplyHarpMessage + the reply to the Harp message + """ + return self.send( + HarpMessage.create( + message_type=MessageType.WRITE, + address=address, + payload_type=PayloadType.S16, + value=value, + ).frame + ) + + def write_u32(self, address: int, value: int | list[int]) -> ReplyHarpMessage: + """ + Writes the value of a register of type U32. + + Parameters + ---------- + address : int + the register to be written on + value: int | list[int] + the value to be written to the register + + Returns + ------- + ReplyHarpMessage + the reply to the Harp message + """ + return self.send( + HarpMessage.create( + message_type=MessageType.WRITE, + address=address, + payload_type=PayloadType.U32, + value=value, + ).frame + ) + + def write_s32(self, address: int, value: int | list[int]) -> ReplyHarpMessage: + """ + Writes the value of a register of type S32. + + Parameters + ---------- + address : int + the register to be written on + value: int | list[int] + the value to be written to the register + + Returns + ------- + ReplyHarpMessage + the reply to the Harp message + """ + return self.send( + HarpMessage.create( + message_type=MessageType.WRITE, + address=address, + payload_type=PayloadType.S32, + value=value, + ).frame + ) + + def write_u64(self, address: int, value: int | list[int]) -> ReplyHarpMessage: + """ + Writes the value of a register of type U64. + + Parameters + ---------- + address : int + the register to be written on + value: int | list[int] + the value to be written to the register + + Returns + ------- + ReplyHarpMessage + the reply to the Harp message + """ + return self.send( + HarpMessage.create( + message_type=MessageType.WRITE, + address=address, + payload_type=PayloadType.U64, + value=value, + ).frame + ) + + def write_s64(self, address: int, value: int | list[int]) -> ReplyHarpMessage: + """ + Writes the value of a register of type S64. + + Parameters + ---------- + address : int + the register to be written on + value: int | list[int] + the value to be written to the register + + Returns + ------- + ReplyHarpMessage + the reply to the Harp message + """ + return self.send( + HarpMessage.create( + message_type=MessageType.WRITE, + address=address, + payload_type=PayloadType.S64, + value=value, + ).frame + ) + + def write_float(self, address: int, value: float | list[float]) -> ReplyHarpMessage: + """ + Writes the value of a register of type Float. + + Parameters + ---------- + address : int + the register to be written on + value: int | list[int] + the value to be written to the register + + Returns + ------- + ReplyHarpMessage + the reply to the Harp message + """ + return self.send( + HarpMessage.create( + message_type=MessageType.WRITE, + address=address, + payload_type=PayloadType.Float, + value=value, + ).frame + ) + def _read_who_am_i(self) -> int: """ Reads the value stored in the `WHO_AM_I` register. From 3f7922f271b7d50435b7595801ec37e266b81c52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 23 Apr 2025 10:54:58 +0100 Subject: [PATCH 096/159] Remove dump from send method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Grilo --- pyharp/device.py | 39 ++++++++++++--------------------------- 1 file changed, 12 insertions(+), 27 deletions(-) diff --git a/pyharp/device.py b/pyharp/device.py index 355ab6d..2a73eec 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -387,9 +387,7 @@ def reset_device(self) -> ReplyHarpMessage: return reply - def send( - self, message_bytes: bytearray, dump: bool = True - ) -> Optional[ReplyHarpMessage]: + def send(self, message_bytes: bytearray) -> Optional[ReplyHarpMessage]: """ Sends a Harp message. @@ -397,8 +395,6 @@ def send( ---------- message_bytes : bytearray the bytearray containing the message to be sent to the device - dump : bool, optional - indicates whether the reply message should be dumped or not Returns ------- @@ -409,8 +405,7 @@ def send( reply = self._read() - if dump: - self._dump_reply(reply.frame) + self._dump_reply(reply.frame) return reply @@ -898,8 +893,7 @@ def _read_who_am_i(self) -> int: address = CommonRegisters.WHO_AM_I reply: ReplyHarpMessage = self.send( - HarpMessage.create(MessageType.READ, address, PayloadType.U16).frame, - dump=False, + HarpMessage.create(MessageType.READ, address, PayloadType.U16).frame ) return reply.payload_as_int() @@ -927,8 +921,7 @@ def _read_hw_version_h(self) -> int: address = CommonRegisters.HW_VERSION_H reply: ReplyHarpMessage = self.send( - HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame, - dump=False, + HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame ) return reply.payload_as_int() @@ -945,8 +938,7 @@ def _read_hw_version_l(self) -> int: address = CommonRegisters.HW_VERSION_L reply: ReplyHarpMessage = self.send( - HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame, - dump=False, + HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame ) return reply.payload_as_int() @@ -963,8 +955,7 @@ def _read_assembly_version(self) -> int: address = CommonRegisters.ASSEMBLY_VERSION reply: ReplyHarpMessage = self.send( - HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame, - dump=False, + HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame ) return reply.payload_as_int() @@ -981,8 +972,7 @@ def _read_harp_version_h(self) -> int: address = CommonRegisters.HARP_VERSION_H reply: ReplyHarpMessage = self.send( - HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame, - dump=False, + HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame ) return reply.payload_as_int() @@ -999,8 +989,7 @@ def _read_harp_version_l(self) -> int: address = CommonRegisters.HARP_VERSION_L reply: ReplyHarpMessage = self.send( - HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame, - dump=False, + HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame ) return reply.payload_as_int() @@ -1017,8 +1006,7 @@ def _read_fw_version_h(self) -> int: address = CommonRegisters.FIRMWARE_VERSION_H reply: ReplyHarpMessage = self.send( - HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame, - dump=False, + HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame ) return reply.payload_as_int() @@ -1035,8 +1023,7 @@ def _read_fw_version_l(self) -> int: address = CommonRegisters.FIRMWARE_VERSION_L reply: ReplyHarpMessage = self.send( - HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame, - dump=False, + HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame ) return reply.payload_as_int() @@ -1053,8 +1040,7 @@ def _read_device_name(self) -> str: address = CommonRegisters.DEVICE_NAME reply: ReplyHarpMessage = self.send( - HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame, - dump=False, + HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame ) return reply.payload_as_string() @@ -1071,8 +1057,7 @@ def _read_serial_number(self) -> int: address = CommonRegisters.SERIAL_NUMBER reply: ReplyHarpMessage = self.send( - HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame, - dump=False, + HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame ) if reply.is_error(): From 924997be28b3162e8a57a340221bf9a26640a652 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 23 Apr 2025 11:38:46 +0100 Subject: [PATCH 097/159] Add ClockConfig and TimestampOffset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Grilo --- pyharp/base.py | 41 ++++++++++++++++++++++- pyharp/device.py | 85 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+), 1 deletion(-) diff --git a/pyharp/base.py b/pyharp/base.py index c3a0750..2c2d393 100644 --- a/pyharp/base.py +++ b/pyharp/base.py @@ -1,4 +1,4 @@ -from enum import IntEnum +from enum import IntEnum, IntFlag # Bit masks for the PayloadType _isUnsigned: int = 0x00 @@ -133,6 +133,10 @@ class CommonRegisters(IntEnum): the number of the `DEVICE_NAME` register SERIAL_NUMBER : int the number of the `SERIAL_NUMBER` register + R_CLOCK_CONFIG : int + the number of the `R_CLOCK_CONFIG` register + R_TIMESTAMP_OFFSET : int + the number of the `R_TIMESTAMP_OFFSET` register """ WHO_AM_I = 0x00 @@ -149,6 +153,8 @@ class CommonRegisters(IntEnum): RESET_DEV = 0x0B DEVICE_NAME = 0x0C SERIAL_NUMBER = 0x0D + R_CLOCK_CONFIG = 0x0E + R_TIMESTAMP_OFFSET = 0x0F class OperationMode(IntEnum): @@ -171,3 +177,36 @@ class OperationMode(IntEnum): ACTIVE = 1 RESERVED = 2 SPEED = 3 + + +class ClockConfig(IntFlag): + """ + An enumeration with the clock configuration bits for the R_CLOCK_CONFIG register of a Harp device. + More information can be found [here](https://harp-tech.org/protocol/Device.html#r_clock_config-u8--synchronization-clock-configuration). + + Attributes + ---------- + CLK_REP : int + Bit 0 (0x01): If set to 1, the device will repeat the Harp Synchronization Clock to the Clock Output connector, if available. + Acts as a daisy-chain by repeating the Clock Input to the Clock Output. Setting this bit also unlocks the Harp Synchronization Clock. + CLK_GEN : int + Bit 1 (0x02): If set to 1, the device will generate Harp Synchronization Clock to the Clock Output connector, if available. + The Clock Input will be ignored. Read as 1 if the device is generating the Harp Synchronization Clock. + REP_ABLE : int + Bit 3 (0x08, read-only): Indicates if the device is able (1) to repeat the Harp Synchronization Clock timestamp. + GEN_ABLE : int + Bit 4 (0x10, read-only): Indicates if the device is able (1) to generate the Harp Synchronization Clock timestamp. + CLK_UNLOCK : int + Bit 6 (0x40): If set to 1, the device will unlock the timestamp register counter (R_TIMESTAMP_SECOND) and accept new timestamp values. + Read as 1 if the timestamp register is unlocked. + CLK_LOCK : int + Bit 7 (0x80): If set to 1, the device will lock the current timestamp register counter (R_TIMESTAMP_SECOND) and reject new timestamp values. + Read as 1 if the timestamp register is locked. + """ + + CLK_REP = 0x01 + CLK_GEN = 0x02 + REP_ABLE = 0x08 + GEN_ABLE = 0x10 + CLK_UNLOCK = 0x40 + CLK_LOCK = 0x80 \ No newline at end of file diff --git a/pyharp/device.py b/pyharp/device.py index 2a73eec..feb9490 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -9,6 +9,7 @@ import serial from pyharp import CommonRegisters, MessageType, OperationMode, PayloadType +from pyharp.base import ClockConfig from pyharp.device_names import device_names from pyharp.harp_serial import HarpSerial from pyharp.messages import HarpMessage, ReplyHarpMessage @@ -55,6 +56,8 @@ class Device: FIRMWARE_VERSION_L: int DEVICE_NAME: str SERIAL_NUMBER: int + CLOCK_CONFIG: int + TIMESTAMP_OFFSET: int _ser: HarpSerial _dump_file_path: Path @@ -105,6 +108,8 @@ def load(self) -> None: self.FIRMWARE_VERSION_L = self._read_fw_version_l() self.DEVICE_NAME = self._read_device_name() self.SERIAL_NUMBER = self._read_serial_number() + self.CLOCK_CONFIG = self._read_clock_config() + self.TIMESTAMP_OFFSET = self._read_timestamp_offset() def info(self) -> None: """ @@ -387,6 +392,52 @@ def reset_device(self) -> ReplyHarpMessage: return reply + def set_clock_config(self, clock_config: ClockConfig) -> ReplyHarpMessage: + """ + Sets the clock configuration of the device. + + Parameters + ---------- + clock_config : ClockConfig + the clock configuration value + + Returns + ------- + ReplyHarpMessage + the reply to the Harp message + """ + address = CommonRegisters.CLOCK_CONFIG + reply = self.send( + HarpMessage.create( + MessageType.WRITE, address, PayloadType.U8, clock_config + ).frame + ) + + return reply + + def set_timestamp_offset(self, timestamp_offset: int) -> ReplyHarpMessage: + """ + When the value of this register is above 0 (zero), the device's timestamp will be offset by this amount. The register is sensitive to 500 microsecond increments. This register is non-volatile. + + Parameters + ---------- + timestamp_offset : int + the timestamp offset value + + Returns + ------- + ReplyHarpMessage + the reply to the Harp message + """ + address = CommonRegisters.TIMESTAMP_OFFSET + reply = self.send( + HarpMessage.create( + MessageType.WRITE, address, PayloadType.U8, timestamp_offset + ).frame + ) + + return reply + def send(self, message_bytes: bytearray) -> Optional[ReplyHarpMessage]: """ Sends a Harp message. @@ -1065,6 +1116,40 @@ def _read_serial_number(self) -> int: return reply.payload_as_int() + def _read_clock_config(self) -> int: + """ + Reads the value stored in the `CLOCK_CONFIG` register. + + Returns + ------- + int + the value of the `CLOCK_CONFIG` register. + """ + address = CommonRegisters.CLOCK_CONFIG + + reply: ReplyHarpMessage = self.send( + HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame + ) + + return reply.payload_as_int() + + def _read_timestamp_offset(self) -> int: + """ + Reads the value stored in the `TIMESTAMP_OFFSET` register. + + Returns + ------- + int + the value of the `TIMESTAMP_OFFSET` register. + """ + address = CommonRegisters.TIMESTAMP_OFFSET + + reply: ReplyHarpMessage = self.send( + HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame + ) + + return reply.payload_as_int() + def __enter__(self): """ Support for using Device with 'with' statement. From 3c21927802d064e12f01c3d4e68096c6317baacc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 23 Apr 2025 11:39:56 +0100 Subject: [PATCH 098/159] Add OperationCtrl and ResetMode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Grilo --- pyharp/base.py | 63 ++++++++++++++++++++++++++++++++++++++++++++++++ pyharp/device.py | 31 ++++++++++++------------ 2 files changed, 79 insertions(+), 15 deletions(-) diff --git a/pyharp/base.py b/pyharp/base.py index 2c2d393..7867df9 100644 --- a/pyharp/base.py +++ b/pyharp/base.py @@ -178,6 +178,69 @@ class OperationMode(IntEnum): RESERVED = 2 SPEED = 3 +class OperationCtrl(IntFlag): + """ + An enumeration with the operation control bits of a Harp device. More information on the operation control bits can be found [here](https://harp-tech.org/protocol/Device.html#r_operation_ctrl-u16--operation-mode-configuration). + + Attributes + ---------- + OP_MODE : int + Bits 1:0 (0x03): Operation mode of the device. + 0: Standby Mode (all Events off, mandatory) + 1: Active Mode (Events detection enabled, mandatory) + 2: Reserved + 3: Speed Mode (device enters Speed Mode, optional; only responds to Speed Mode commands) + DUMP : int + Bit 3 (0x08): When set to 1, the device adds the content of all registers to the streaming buffer as Read messages. Always read as 0. + MUTE_RPL : int + Bit 4 (0x10): If set to 1, replies to all commands are muted (not sent by the device). + VISUALEN : int + Bit 5 (0x20): If set to 1, visual indications (e.g., LEDs) operate. If 0, all visual indications are turned off. + OPLEDEN : int + Bit 6 (0x40): If set to 1, the LED indicates the selected Operation Mode (see LED feedback table in documentation). + ALIVE_EN : int + Bit 7 (0x80): If set to 1, the device sends an Event Message with the R_TIMESTAMP_SECONDS content each second (heartbeat). + """ + + OP_MODE = 3 << 0 + DUMP = 1 << 3 + MUTE_RPL = 1 << 4 + VISUALEN = 1 << 5 + OPLEDEN = 1 << 6 + ALIVE_EN = 1 << 7 + + +class ResetMode(IntEnum): + """ + An enumeration with the reset modes and actions for the R_RESET_DEV register of a Harp device. + More information on the reset modes can be found [here](https://harp-tech.org/protocol/Device.html#r_reset_dev-u8--reset-device-and-save-non-volatile-registers). + + Attributes + ---------- + RST_DEF : int + Bit 0 (0x01): If set, resets the device and restores all registers (Common and Application) to default values. + EEPROM is erased and defaults become the permanent boot option. + RST_EE : int + Bit 1 (0x02): If set, resets the device and restores all registers (Common and Application) from non-volatile memory (EEPROM). + EEPROM values remain the permanent boot option. + SAVE : int + Bit 3 (0x08): If set, saves all non-volatile registers (Common and Application) to EEPROM and reboots. + EEPROM becomes the permanent boot option. + NAME_TO_DEFAULT : int + Bit 4 (0x10): If set, reboots the device with the default name. + BOOT_DEF : int + Bit 6 (0x40, read-only): Indicates the device booted with default register values. + BOOT_EE : int + Bit 7 (0x80, read-only): Indicates the device booted with register values saved on the EEPROM. + """ + + RST_DEF = 0x01 + RST_EE = 0x02 + SAVE = 0x08 + NAME_TO_DEFAULT = 0x10 + BOOT_DEF = 0x40 + BOOT_EE = 0x80 + class ClockConfig(IntFlag): """ diff --git a/pyharp/device.py b/pyharp/device.py index feb9490..aa096fb 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -9,7 +9,7 @@ import serial from pyharp import CommonRegisters, MessageType, OperationMode, PayloadType -from pyharp.base import ClockConfig +from pyharp.base import ClockConfig, OperationCtrl, ResetMode from pyharp.device_names import device_names from pyharp.harp_serial import HarpSerial from pyharp.messages import HarpMessage, ReplyHarpMessage @@ -169,7 +169,7 @@ def _read_device_mode(self) -> OperationMode: reply = self.send( HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame ) - return OperationMode(reply.payload_as_int() & 0x03) + return OperationMode(reply.payload_as_int() & OperationCtrl.OP_MODE) def dump_registers(self) -> list: """ @@ -186,7 +186,7 @@ def dump_registers(self) -> list: HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame ).payload_as_int() # Assert DUMP bit - reg_value |= 0x08 + reg_value |= OperationCtrl.DUMP self.send( HarpMessage.create( MessageType.WRITE, address, PayloadType.U8, reg_value @@ -225,7 +225,7 @@ def set_mode(self, mode: OperationMode) -> ReplyHarpMessage: ).payload_as_int() # Clear old operation mode - reg_value &= ~0x03 + reg_value &= ~OperationCtrl.OP_MODE # Set new operation mode reg_value |= mode @@ -259,9 +259,9 @@ def alive_en(self, enable: bool) -> bool: ).payload_as_int() if enable: - reg_value |= 1 << 7 + reg_value |= OperationCtrl.ALIVE_EN else: - reg_value &= ~(1 << 7) + reg_value &= ~OperationCtrl.ALIVE_EN reply = self.send( HarpMessage.create( @@ -293,9 +293,9 @@ def op_led_en(self, enable: bool) -> bool: ).payload_as_int() if enable: - reg_value |= 1 << 6 + reg_value |= OperationCtrl.OPLEDEN else: - reg_value &= ~(1 << 6) + reg_value &= ~OperationCtrl.OPLEDEN reply = self.send( HarpMessage.create( @@ -327,9 +327,9 @@ def status_led(self, enable: bool) -> bool: ).payload_as_int() if enable: - reg_value |= 1 << 5 + reg_value |= OperationCtrl.STATUS_LED else: - reg_value &= ~(1 << 5) + reg_value &= ~OperationCtrl.STATUS_LED reply = self.send( HarpMessage.create( @@ -361,9 +361,9 @@ def mute_reply(self, enable: bool) -> bool: ).payload_as_int() if enable: - reg_value |= 1 << 4 + reg_value |= OperationCtrl.MUTE_RPL else: - reg_value &= ~(1 << 4) + reg_value &= ~OperationCtrl.MUTE_RPL reply = self.send( HarpMessage.create( @@ -373,7 +373,9 @@ def mute_reply(self, enable: bool) -> bool: return reply - def reset_device(self) -> ReplyHarpMessage: + def reset_device( + self, reset_mode: ResetMode = ResetMode.RST_DEF + ) -> ReplyHarpMessage: """ Resets the device and reboots with all the registers with the default values. Beware that the EEPROM will be erased. More information on the reset device register can be found [here](https://harp-tech.org/protocol/Device.html#r_reset_dev-u8--reset-device-and-save-non-volatile-registers). @@ -383,10 +385,9 @@ def reset_device(self) -> ReplyHarpMessage: the reply to the Harp message """ address = CommonRegisters.RESET_DEV - reset_value = 0x01 reply = self.send( HarpMessage.create( - MessageType.WRITE, address, PayloadType.U8, reset_value + MessageType.WRITE, address, PayloadType.U8, reset_mode ).frame ) From ec85295a2ac6e7c084ec5ff95e5bfed0659255d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 23 Apr 2025 11:40:29 +0100 Subject: [PATCH 099/159] Allow Port control MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Grilo --- pyharp/messages.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/pyharp/messages.py b/pyharp/messages.py index cc03b85..19559a6 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -34,6 +34,7 @@ class HarpMessage: def __init__(self): self._frame = bytearray() + self._port = self.DEFAULT_PORT def calculate_checksum(self) -> int: """ @@ -109,6 +110,18 @@ def port(self) -> int: """ return self._frame[3] + @port.setter + def port(self, value: int) -> None: + """ + Sets the port value. + + Parameters + ---------- + value : int + the port value to set + """ + self._port = value + @property def payload_type(self) -> PayloadType: """ @@ -471,7 +484,7 @@ def __init__(self, payload_type: PayloadType, address: int): length: int = 4 self._frame.append(length) self._frame.append(address) - self._frame.append(self.DEFAULT_PORT) + self._frame.append(self._port) self._frame.append(payload_type) self._frame.append(self.calculate_checksum()) @@ -548,7 +561,7 @@ def __init__( # Length is BASE_LENGTH + payload size self._frame.append(self.BASE_LENGTH + len(payload)) self._frame.append(address) - self._frame.append(self.DEFAULT_PORT) + self._frame.append(self._port) self._frame.append(payload_type) self._frame += payload self._frame.append(self.calculate_checksum()) From be6f11c60c2239b0ee059cb959fe104557bf0604 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 23 Apr 2025 16:45:16 +0100 Subject: [PATCH 100/159] Fix issue with payload property MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Grilo --- pyharp/messages.py | 82 +++++++++++++++++++++++++++++++--------------- 1 file changed, 56 insertions(+), 26 deletions(-) diff --git a/pyharp/messages.py b/pyharp/messages.py index 19559a6..76b1ef8 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -148,120 +148,150 @@ def payload(self) -> Union[int, list[int]]: if self.payload_type & PayloadType.Timestamp: payload_start += 6 + payload_index = payload_start + 1 + # length is payload_start + payload type size match self.payload_type: case PayloadType.U8 | PayloadType.TimestampedU8: if self.length == payload_start + 1: - return self._frame[5] + return self._frame[payload_index] else: # array case return [ int.from_bytes([self._frame[i]], byteorder="little") - for i in range(5, self.length + 1) + for i in range(payload_index, self.length + 1) ] case PayloadType.S8 | PayloadType.TimestampedS8: if self.length == payload_start + 1: return int.from_bytes( - [self._frame[5]], byteorder="little", signed=True + [self._frame[payload_index]], byteorder="little", signed=True ) else: # array case return [ int.from_bytes( - [self._frame[i]], byteorder="little", signed=True + [self._frame[i]], + byteorder="little", + signed=True, ) - for i in range(5, self.length + 1) + for i in range(payload_index, self.length + 1) ] case PayloadType.U16 | PayloadType.TimestampedU16: if self.length == payload_start + 2: return int.from_bytes( - self._frame[5:7], byteorder="little", signed=False + self._frame[payload_index : payload_index + 2], + byteorder="little", + signed=False, ) else: # array case return [ int.from_bytes( - self._frame[i : i + 2], byteorder="little", signed=False + self._frame[i : i + 2], + byteorder="little", + signed=False, ) - for i in range(5, self.length + 1, 2) + for i in range(payload_index, self.length + 1, 2) ] case PayloadType.S16 | PayloadType.TimestampedS16: if self.length == payload_start + 2: return int.from_bytes( - self._frame[5:7], byteorder="little", signed=True + self._frame[payload_index : payload_index + 2], + byteorder="little", + signed=True, ) else: return [ int.from_bytes( - self._frame[i : i + 2], byteorder="little", signed=True + self._frame[i : i + 2], + byteorder="little", + signed=True, ) - for i in range(5, self.length + 1, 2) + for i in range(payload_index, self.length + 1, 2) ] case PayloadType.U32 | PayloadType.TimestampedU32: if self.length == payload_start + 4: return int.from_bytes( - self._frame[5:9], byteorder="little", signed=False + self._frame[payload_index : payload_index + 4], + byteorder="little", + signed=False, ) else: return [ int.from_bytes( - self._frame[i : i + 4], byteorder="little", signed=False + self._frame[i : i + 4], + byteorder="little", + signed=False, ) - for i in range(5, self.length + 1, 4) + for i in range(payload_index, self.length + 1, 4) ] case PayloadType.S32 | PayloadType.TimestampedS32: if self.length == payload_start + 4: return int.from_bytes( - self._frame[5:9], byteorder="little", signed=True + self._frame[payload_index : payload_index + 4], + byteorder="little", + signed=True, ) else: return [ int.from_bytes( - self._frame[i : i + 4], byteorder="little", signed=True + self._frame[i : i + 4], + byteorder="little", + signed=True, ) - for i in range(5, self.length + 1, 4) + for i in range(payload_index, self.length + 1, 4) ] case PayloadType.U64 | PayloadType.TimestampedU64: if self.length == payload_start + 8: return int.from_bytes( - self._frame[5:13], byteorder="little", signed=False + self._frame[payload_index : payload_index + 8], + byteorder="little", + signed=False, ) else: return [ int.from_bytes( - self._frame[i : i + 8], byteorder="little", signed=False + self._frame[i : i + 8], + byteorder="little", + signed=False, ) - for i in range(5, self.length + 1, 8) + for i in range(payload_index, self.length + 1, 8) ] case PayloadType.S64 | PayloadType.TimestampedS64: if self.length == payload_start + 8: return int.from_bytes( - self._frame[5:13], byteorder="little", signed=True + self._frame[payload_index : payload_index + 8], + byteorder="little", + signed=True, ) else: return [ int.from_bytes( - self._frame[i : i + 8], byteorder="little", signed=True + self._frame[i : i + 8], + byteorder="little", + signed=True, ) - for i in range(5, self.length + 1, 8) + for i in range(payload_index, self.length + 1, 8) ] case PayloadType.Float | PayloadType.TimestampedFloat: if self.length == payload_start + 4: - return struct.unpack(" int: From de5a799f90d6727ab83ae3af52e3a9f50ca9944d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 23 Apr 2025 16:46:34 +0100 Subject: [PATCH 101/159] Update payload_as_string MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Grilo --- pyharp/messages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyharp/messages.py b/pyharp/messages.py index 76b1ef8..023ff41 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -484,7 +484,7 @@ def payload_as_string(self) -> str: str the payload parsed as a str """ - return self._raw_payload.decode("utf-8") + return self._raw_payload.decode("utf-8").rstrip("\x00") # TODO: handle float case and/or delete functional altogether def payload_as_float(self) -> float: From 542de32de7fbc60c6655a06c9a3c84a131a360eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 23 Apr 2025 16:50:01 +0100 Subject: [PATCH 102/159] Update CommonRegisters members name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Grilo --- pyharp/base.py | 12 ++++++------ tests/test_device.py | 2 +- tests/test_messages.py | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/pyharp/base.py b/pyharp/base.py index 7867df9..0d5d3f0 100644 --- a/pyharp/base.py +++ b/pyharp/base.py @@ -133,10 +133,10 @@ class CommonRegisters(IntEnum): the number of the `DEVICE_NAME` register SERIAL_NUMBER : int the number of the `SERIAL_NUMBER` register - R_CLOCK_CONFIG : int - the number of the `R_CLOCK_CONFIG` register - R_TIMESTAMP_OFFSET : int - the number of the `R_TIMESTAMP_OFFSET` register + CLOCK_CONFIG : int + the number of the `CLOCK_CONFIG` register + TIMESTAMP_OFFSET : int + the number of the `TIMESTAMP_OFFSET` register """ WHO_AM_I = 0x00 @@ -153,8 +153,8 @@ class CommonRegisters(IntEnum): RESET_DEV = 0x0B DEVICE_NAME = 0x0C SERIAL_NUMBER = 0x0D - R_CLOCK_CONFIG = 0x0E - R_TIMESTAMP_OFFSET = 0x0F + CLOCK_CONFIG = 0x0E + TIMESTAMP_OFFSET = 0x0F class OperationMode(IntEnum): diff --git a/tests/test_device.py b/tests/test_device.py index fb85b39..d511549 100644 --- a/tests/test_device.py +++ b/tests/test_device.py @@ -57,7 +57,7 @@ # # read register 38 # reply = device.read_u8(register) # assert reply is not None -# assert reply.payload_as_int() == write_value +# assert reply.payload == write_value # device.disconnect() diff --git a/tests/test_messages.py b/tests/test_messages.py index ed0679d..cf5ba1d 100644 --- a/tests/test_messages.py +++ b/tests/test_messages.py @@ -452,10 +452,10 @@ def test_harp_message_parse() -> None: frame = bytearray( [ MessageType.READ, - 5, + 11, 42, 255, - PayloadType.U8, + PayloadType.TimestampedU8, 0, 0, 0, @@ -475,7 +475,7 @@ def test_harp_message_parse() -> None: assert isinstance(message, ReplyHarpMessage) assert message.message_type == MessageType.READ assert message.address == 42 - assert message.payload_as_int() == 123 + assert message.payload == 123 def test_timestamp_handling() -> None: From 57966217b300bbd1334b2f03f164f28c10e42156 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 23 Apr 2025 16:55:06 +0100 Subject: [PATCH 103/159] Update send method to receive HarpMessage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Grilo --- README.md | 8 +- .../olfactometer_example.py | 56 ++++---- docs/index.md | 8 +- pyharp/device.py | 134 ++++++++---------- pyharp/drivers/behavior.py | 28 ++-- tests/test_device.py | 4 +- 6 files changed, 104 insertions(+), 134 deletions(-) diff --git a/README.md b/README.md index d1485ef..79b36f3 100644 --- a/README.md +++ b/README.md @@ -27,10 +27,10 @@ device.info() register_address = 32 # Read from register -value = device.send(HarpMessage.create(MessageType.READ, register_address, PayloadType.U8).frame) +value = device.send(HarpMessage.create(MessageType.READ, register_address, PayloadType.U8)) # Write to register -device.send(HarpMessage.create(MessageType.WRITE, register_address, PayloadType.U8, value).frame) +device.send(HarpMessage.create(MessageType.WRITE, register_address, PayloadType.U8, value)) # Disconnect when done device.disconnect() @@ -51,10 +51,10 @@ with Device("/dev/ttyUSB0") as device: register_address = 32 # Read from register - value = device.send(HarpMessage.create(MessageType.READ, register_address, PayloadType.U8).frame) + value = device.send(HarpMessage.create(MessageType.READ, register_address, PayloadType.U8)) # Write to register - device.send(HarpMessage.create(MessageType.WRITE, register_address, PayloadType.U8, value).frame) + device.send(HarpMessage.create(MessageType.WRITE, register_address, PayloadType.U8, value)) ``` ## for Linux diff --git a/docs/examples/olfactometer_example/olfactometer_example.py b/docs/examples/olfactometer_example/olfactometer_example.py index aea85b1..c4b2fe3 100644 --- a/docs/examples/olfactometer_example/olfactometer_example.py +++ b/docs/examples/olfactometer_example/olfactometer_example.py @@ -42,7 +42,7 @@ def main(): device.set_mode(OperationMode.ACTIVE) # Enable flow - device.send(HarpMessage.WriteU8(32, 0x01).frame) + device.send(HarpMessage.create(MessageType.WRITE, 32, PayloadType.U8, 0x01)) # Initialize thread for events events_thread = Thread( @@ -58,59 +58,51 @@ def main(): device.send( HarpMessage.create( MessageType.WRITE, 42, PayloadType.Float, int(random.random() * 100) - ).frame + ) ) device.send( HarpMessage.create( MessageType.WRITE, 43, PayloadType.Float, int(random.random() * 100) - ).frame + ) ) device.send( HarpMessage.create( MessageType.WRITE, 44, PayloadType.Float, int(random.random() * 100) - ).frame + ) ) device.send( HarpMessage.create( MessageType.WRITE, 45, PayloadType.Float, int(random.random() * 100) - ).frame + ) ) # Open every odor valve, one at a time every 5 seconds - device.send( - HarpMessage.create(MessageType.WRITE, 68, PayloadType.Float, 0x01).frame - ) + device.send(HarpMessage.create(MessageType.WRITE, 68, PayloadType.Float, 0x01)) + time.sleep(5) - device.send( - HarpMessage.create(MessageType.WRITE, 69, PayloadType.Float, 0x01).frame - ) - device.send( - HarpMessage.create(MessageType.WRITE, 68, PayloadType.Float, 0x02).frame - ) + + device.send(HarpMessage.create(MessageType.WRITE, 69, PayloadType.Float, 0x01)) + device.send(HarpMessage.create(MessageType.WRITE, 68, PayloadType.Float, 0x02)) + time.sleep(5) - device.send( - HarpMessage.create(MessageType.WRITE, 69, PayloadType.Float, 0x02).frame - ) - device.send( - HarpMessage.create(MessageType.WRITE, 68, PayloadType.Float, 0x04).frame - ) + + device.send(HarpMessage.create(MessageType.WRITE, 69, PayloadType.Float, 0x02)) + device.send(HarpMessage.create(MessageType.WRITE, 68, PayloadType.Float, 0x04)) + time.sleep(5) - device.send( - HarpMessage.create(MessageType.WRITE, 69, PayloadType.Float, 0x04).frame - ) - device.send( - HarpMessage.create(MessageType.WRITE, 68, PayloadType.Float, 0x08).frame - ) + + device.send(HarpMessage.create(MessageType.WRITE, 69, PayloadType.Float, 0x04)) + device.send(HarpMessage.create(MessageType.WRITE, 68, PayloadType.Float, 0x08)) + time.sleep(5) - device.send( - HarpMessage.create(MessageType.WRITE, 69, PayloadType.Float, 0x08).frame - ) + + device.send(HarpMessage.create(MessageType.WRITE, 69, PayloadType.Float, 0x08)) + time.sleep(5) # Disable flow - device.send( - HarpMessage.create(MessageType.WRITE, 32, PayloadType.Float, 0x00).frame - ) + device.send(HarpMessage.create(MessageType.WRITE, 32, PayloadType.Float, 0x00)) + time.sleep(1) stop_flag.set() diff --git a/docs/index.md b/docs/index.md index d1485ef..79b36f3 100644 --- a/docs/index.md +++ b/docs/index.md @@ -27,10 +27,10 @@ device.info() register_address = 32 # Read from register -value = device.send(HarpMessage.create(MessageType.READ, register_address, PayloadType.U8).frame) +value = device.send(HarpMessage.create(MessageType.READ, register_address, PayloadType.U8)) # Write to register -device.send(HarpMessage.create(MessageType.WRITE, register_address, PayloadType.U8, value).frame) +device.send(HarpMessage.create(MessageType.WRITE, register_address, PayloadType.U8, value)) # Disconnect when done device.disconnect() @@ -51,10 +51,10 @@ with Device("/dev/ttyUSB0") as device: register_address = 32 # Read from register - value = device.send(HarpMessage.create(MessageType.READ, register_address, PayloadType.U8).frame) + value = device.send(HarpMessage.create(MessageType.READ, register_address, PayloadType.U8)) # Write to register - device.send(HarpMessage.create(MessageType.WRITE, register_address, PayloadType.U8, value).frame) + device.send(HarpMessage.create(MessageType.WRITE, register_address, PayloadType.U8, value)) ``` ## for Linux diff --git a/pyharp/device.py b/pyharp/device.py index aa096fb..a6d10f2 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -166,10 +166,8 @@ def _read_device_mode(self) -> OperationMode: the current device mode """ address = CommonRegisters.OPERATION_CTRL - reply = self.send( - HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame - ) - return OperationMode(reply.payload_as_int() & OperationCtrl.OP_MODE) + reply = self.send(HarpMessage.create(MessageType.READ, address, PayloadType.U8)) + return OperationMode(reply.payload & OperationCtrl.OP_MODE) def dump_registers(self) -> list: """ @@ -183,14 +181,12 @@ def dump_registers(self) -> list: """ address = CommonRegisters.OPERATION_CTRL reg_value = self.send( - HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame - ).payload_as_int() + HarpMessage.create(MessageType.READ, address, PayloadType.U8) + ).payload # Assert DUMP bit reg_value |= OperationCtrl.DUMP self.send( - HarpMessage.create( - MessageType.WRITE, address, PayloadType.U8, reg_value - ).frame + HarpMessage.create(MessageType.WRITE, address, PayloadType.U8, reg_value) ) # Receive the contents of all registers as Harp Read Reply Messages @@ -221,8 +217,8 @@ def set_mode(self, mode: OperationMode) -> ReplyHarpMessage: # Read register first reg_value = self.send( - HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame - ).payload_as_int() + HarpMessage.create(MessageType.READ, address, PayloadType.U8) + ).payload # Clear old operation mode reg_value &= ~OperationCtrl.OP_MODE @@ -230,9 +226,7 @@ def set_mode(self, mode: OperationMode) -> ReplyHarpMessage: # Set new operation mode reg_value |= mode reply = self.send( - HarpMessage.create( - MessageType.WRITE, address, PayloadType.U8, reg_value - ).frame + HarpMessage.create(MessageType.WRITE, address, PayloadType.U8, reg_value) ) return reply @@ -255,8 +249,8 @@ def alive_en(self, enable: bool) -> bool: # Read register first reg_value = self.send( - HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame - ).payload_as_int() + HarpMessage.create(MessageType.READ, address, PayloadType.U8) + ).payload if enable: reg_value |= OperationCtrl.ALIVE_EN @@ -264,9 +258,7 @@ def alive_en(self, enable: bool) -> bool: reg_value &= ~OperationCtrl.ALIVE_EN reply = self.send( - HarpMessage.create( - MessageType.WRITE, address, PayloadType.U8, reg_value - ).frame + HarpMessage.create(MessageType.WRITE, address, PayloadType.U8, reg_value) ) return reply @@ -289,8 +281,8 @@ def op_led_en(self, enable: bool) -> bool: # Read register first reg_value = self.send( - HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame - ).payload_as_int() + HarpMessage.create(MessageType.READ, address, PayloadType.U8) + ).payload if enable: reg_value |= OperationCtrl.OPLEDEN @@ -298,9 +290,7 @@ def op_led_en(self, enable: bool) -> bool: reg_value &= ~OperationCtrl.OPLEDEN reply = self.send( - HarpMessage.create( - MessageType.WRITE, address, PayloadType.U8, reg_value - ).frame + HarpMessage.create(MessageType.WRITE, address, PayloadType.U8, reg_value) ) return reply @@ -323,8 +313,8 @@ def status_led(self, enable: bool) -> bool: # Read register first reg_value = self.send( - HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame - ).payload_as_int() + HarpMessage.create(MessageType.READ, address, PayloadType.U8) + ).payload if enable: reg_value |= OperationCtrl.STATUS_LED @@ -332,9 +322,7 @@ def status_led(self, enable: bool) -> bool: reg_value &= ~OperationCtrl.STATUS_LED reply = self.send( - HarpMessage.create( - MessageType.WRITE, address, PayloadType.U8, reg_value - ).frame + HarpMessage.create(MessageType.WRITE, address, PayloadType.U8, reg_value) ) return reply @@ -357,8 +345,8 @@ def mute_reply(self, enable: bool) -> bool: # Read register first reg_value = self.send( - HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame - ).payload_as_int() + HarpMessage.create(MessageType.READ, address, PayloadType.U8) + ).payload if enable: reg_value |= OperationCtrl.MUTE_RPL @@ -366,9 +354,7 @@ def mute_reply(self, enable: bool) -> bool: reg_value &= ~OperationCtrl.MUTE_RPL reply = self.send( - HarpMessage.create( - MessageType.WRITE, address, PayloadType.U8, reg_value - ).frame + HarpMessage.create(MessageType.WRITE, address, PayloadType.U8, reg_value) ) return reply @@ -386,9 +372,7 @@ def reset_device( """ address = CommonRegisters.RESET_DEV reply = self.send( - HarpMessage.create( - MessageType.WRITE, address, PayloadType.U8, reset_mode - ).frame + HarpMessage.create(MessageType.WRITE, address, PayloadType.U8, reset_mode) ) return reply @@ -409,9 +393,7 @@ def set_clock_config(self, clock_config: ClockConfig) -> ReplyHarpMessage: """ address = CommonRegisters.CLOCK_CONFIG reply = self.send( - HarpMessage.create( - MessageType.WRITE, address, PayloadType.U8, clock_config - ).frame + HarpMessage.create(MessageType.WRITE, address, PayloadType.U8, clock_config) ) return reply @@ -434,30 +416,30 @@ def set_timestamp_offset(self, timestamp_offset: int) -> ReplyHarpMessage: reply = self.send( HarpMessage.create( MessageType.WRITE, address, PayloadType.U8, timestamp_offset - ).frame + ) ) return reply - def send(self, message_bytes: bytearray) -> Optional[ReplyHarpMessage]: + def send(self, message: HarpMessage) -> Optional[ReplyHarpMessage]: """ Sends a Harp message. Parameters ---------- - message_bytes : bytearray - the bytearray containing the message to be sent to the device + message_bytes : HarpMessage + the HarpMessage containing the message to be sent to the device Returns ------- Optional[ReplyHarpMessage] the reply to the Harp message or None if no reply is given """ - self._ser.write(message_bytes) + self._ser.write(message) reply = self._read() - self._dump_reply(reply.frame) + self._dump_reply(reply) return reply @@ -529,7 +511,7 @@ def read_u8(self, address: int) -> ReplyHarpMessage: message_type=MessageType.READ, address=address, payload_type=PayloadType.U8, - ).frame + ) ) def read_s8(self, address: int) -> ReplyHarpMessage: @@ -551,7 +533,7 @@ def read_s8(self, address: int) -> ReplyHarpMessage: message_type=MessageType.READ, address=address, payload_type=PayloadType.S8, - ).frame + ) ) def read_u16(self, address: int) -> ReplyHarpMessage: @@ -573,7 +555,7 @@ def read_u16(self, address: int) -> ReplyHarpMessage: message_type=MessageType.READ, address=address, payload_type=PayloadType.U16, - ).frame + ) ) def read_s16(self, address: int) -> ReplyHarpMessage: @@ -595,7 +577,7 @@ def read_s16(self, address: int) -> ReplyHarpMessage: message_type=MessageType.READ, address=address, payload_type=PayloadType.S16, - ).frame + ) ) def read_u32(self, address: int) -> ReplyHarpMessage: @@ -617,7 +599,7 @@ def read_u32(self, address: int) -> ReplyHarpMessage: message_type=MessageType.READ, address=address, payload_type=PayloadType.U32, - ).frame + ) ) def read_s32(self, address: int) -> ReplyHarpMessage: @@ -639,7 +621,7 @@ def read_s32(self, address: int) -> ReplyHarpMessage: message_type=MessageType.READ, address=address, payload_type=PayloadType.S32, - ).frame + ) ) def read_u64(self, address: int) -> ReplyHarpMessage: @@ -661,7 +643,7 @@ def read_u64(self, address: int) -> ReplyHarpMessage: message_type=MessageType.READ, address=address, payload_type=PayloadType.U64, - ).frame + ) ) def read_s64(self, address: int) -> ReplyHarpMessage: @@ -683,7 +665,7 @@ def read_s64(self, address: int) -> ReplyHarpMessage: message_type=MessageType.READ, address=address, payload_type=PayloadType.S64, - ).frame + ) ) def read_float(self, address: int) -> ReplyHarpMessage: @@ -705,7 +687,7 @@ def read_float(self, address: int) -> ReplyHarpMessage: message_type=MessageType.READ, address=address, payload_type=PayloadType.Float, - ).frame + ) ) def write_u8(self, address: int, value: int | list[int]) -> ReplyHarpMessage: @@ -730,7 +712,7 @@ def write_u8(self, address: int, value: int | list[int]) -> ReplyHarpMessage: address=address, payload_type=PayloadType.U8, value=value, - ).frame + ) ) def write_s8(self, address: int, value: int | list[int]) -> ReplyHarpMessage: @@ -755,7 +737,7 @@ def write_s8(self, address: int, value: int | list[int]) -> ReplyHarpMessage: address=address, payload_type=PayloadType.S8, value=value, - ).frame + ) ) def write_u16(self, address: int, value: int | list[int]) -> ReplyHarpMessage: @@ -780,7 +762,7 @@ def write_u16(self, address: int, value: int | list[int]) -> ReplyHarpMessage: address=address, payload_type=PayloadType.U16, value=value, - ).frame + ) ) def write_s16(self, address: int, value: int | list[int]) -> ReplyHarpMessage: @@ -805,7 +787,7 @@ def write_s16(self, address: int, value: int | list[int]) -> ReplyHarpMessage: address=address, payload_type=PayloadType.S16, value=value, - ).frame + ) ) def write_u32(self, address: int, value: int | list[int]) -> ReplyHarpMessage: @@ -830,7 +812,7 @@ def write_u32(self, address: int, value: int | list[int]) -> ReplyHarpMessage: address=address, payload_type=PayloadType.U32, value=value, - ).frame + ) ) def write_s32(self, address: int, value: int | list[int]) -> ReplyHarpMessage: @@ -855,7 +837,7 @@ def write_s32(self, address: int, value: int | list[int]) -> ReplyHarpMessage: address=address, payload_type=PayloadType.S32, value=value, - ).frame + ) ) def write_u64(self, address: int, value: int | list[int]) -> ReplyHarpMessage: @@ -880,7 +862,7 @@ def write_u64(self, address: int, value: int | list[int]) -> ReplyHarpMessage: address=address, payload_type=PayloadType.U64, value=value, - ).frame + ) ) def write_s64(self, address: int, value: int | list[int]) -> ReplyHarpMessage: @@ -905,7 +887,7 @@ def write_s64(self, address: int, value: int | list[int]) -> ReplyHarpMessage: address=address, payload_type=PayloadType.S64, value=value, - ).frame + ) ) def write_float(self, address: int, value: float | list[float]) -> ReplyHarpMessage: @@ -930,7 +912,7 @@ def write_float(self, address: int, value: float | list[float]) -> ReplyHarpMess address=address, payload_type=PayloadType.Float, value=value, - ).frame + ) ) def _read_who_am_i(self) -> int: @@ -945,7 +927,7 @@ def _read_who_am_i(self) -> int: address = CommonRegisters.WHO_AM_I reply: ReplyHarpMessage = self.send( - HarpMessage.create(MessageType.READ, address, PayloadType.U16).frame + HarpMessage.create(MessageType.READ, address, PayloadType.U16) ) return reply.payload_as_int() @@ -973,7 +955,7 @@ def _read_hw_version_h(self) -> int: address = CommonRegisters.HW_VERSION_H reply: ReplyHarpMessage = self.send( - HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame + HarpMessage.create(MessageType.READ, address, PayloadType.U8) ) return reply.payload_as_int() @@ -990,7 +972,7 @@ def _read_hw_version_l(self) -> int: address = CommonRegisters.HW_VERSION_L reply: ReplyHarpMessage = self.send( - HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame + HarpMessage.create(MessageType.READ, address, PayloadType.U8) ) return reply.payload_as_int() @@ -1007,7 +989,7 @@ def _read_assembly_version(self) -> int: address = CommonRegisters.ASSEMBLY_VERSION reply: ReplyHarpMessage = self.send( - HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame + HarpMessage.create(MessageType.READ, address, PayloadType.U8) ) return reply.payload_as_int() @@ -1024,7 +1006,7 @@ def _read_harp_version_h(self) -> int: address = CommonRegisters.HARP_VERSION_H reply: ReplyHarpMessage = self.send( - HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame + HarpMessage.create(MessageType.READ, address, PayloadType.U8) ) return reply.payload_as_int() @@ -1041,7 +1023,7 @@ def _read_harp_version_l(self) -> int: address = CommonRegisters.HARP_VERSION_L reply: ReplyHarpMessage = self.send( - HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame + HarpMessage.create(MessageType.READ, address, PayloadType.U8) ) return reply.payload_as_int() @@ -1058,7 +1040,7 @@ def _read_fw_version_h(self) -> int: address = CommonRegisters.FIRMWARE_VERSION_H reply: ReplyHarpMessage = self.send( - HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame + HarpMessage.create(MessageType.READ, address, PayloadType.U8) ) return reply.payload_as_int() @@ -1075,7 +1057,7 @@ def _read_fw_version_l(self) -> int: address = CommonRegisters.FIRMWARE_VERSION_L reply: ReplyHarpMessage = self.send( - HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame + HarpMessage.create(MessageType.READ, address, PayloadType.U8) ) return reply.payload_as_int() @@ -1092,7 +1074,7 @@ def _read_device_name(self) -> str: address = CommonRegisters.DEVICE_NAME reply: ReplyHarpMessage = self.send( - HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame + HarpMessage.create(MessageType.READ, address, PayloadType.U8) ) return reply.payload_as_string() @@ -1109,7 +1091,7 @@ def _read_serial_number(self) -> int: address = CommonRegisters.SERIAL_NUMBER reply: ReplyHarpMessage = self.send( - HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame + HarpMessage.create(MessageType.READ, address, PayloadType.U8) ) if reply.is_error(): @@ -1129,7 +1111,7 @@ def _read_clock_config(self) -> int: address = CommonRegisters.CLOCK_CONFIG reply: ReplyHarpMessage = self.send( - HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame + HarpMessage.create(MessageType.READ, address, PayloadType.U8) ) return reply.payload_as_int() @@ -1146,7 +1128,7 @@ def _read_timestamp_offset(self) -> int: address = CommonRegisters.TIMESTAMP_OFFSET reply: ReplyHarpMessage = self.send( - HarpMessage.create(MessageType.READ, address, PayloadType.U8).frame + HarpMessage.create(MessageType.READ, address, PayloadType.U8) ) return reply.payload_as_int() diff --git a/pyharp/drivers/behavior.py b/pyharp/drivers/behavior.py index 2f4b333..04aa2d4 100644 --- a/pyharp/drivers/behavior.py +++ b/pyharp/drivers/behavior.py @@ -124,7 +124,7 @@ def disable_all_events(self) -> ReplyHarpMessage: (1 << Events.cam1.value) ) ^ 0xFF) reg_type, reg_index, _ = REGISTERS["EVNT_ENABLE"] return self.device.send( - WriteHarpMessage(reg_type, reg_index, event_reg_bitmask).frame + WriteHarpMessage(reg_type, reg_index, event_reg_bitmask) ) @@ -135,7 +135,7 @@ def enable_events(self, *events: Events) -> ReplyHarpMessage: event_reg_bitmask |= (1 << event.value) reg_type, reg_index, _ = REGISTERS["EVNT_ENABLE"] return self.device.send( - WriteHarpMessage(reg_type, reg_index, event_reg_bitmask).frame + WriteHarpMessage(reg_type, reg_index, event_reg_bitmask) ) @@ -145,9 +145,7 @@ def enable_events(self, *events: Events) -> ReplyHarpMessage: def all_input_states(self): """return the state of all PORT digital inputs.""" reg_type, reg_index, _ = REGISTERS["PORT_DIS"] - return self.device.send( - ReadHarpMessage(reg_type, reg_index).frame - ).payload_as_int() + return self.device.send(ReadHarpMessage(reg_type, reg_index)).payload @property def DI0(self): @@ -173,14 +171,14 @@ def DI2(self): # """set the state of all PORT digital ios. (1 is output.)""" # reg_type, reg_index, _ = REGISTERS["PORT_DIOS_CONF"] - # self.device.send(WriteHarpMessage(reg_type, reg_index, bitmask).frame) + # self.device.send(WriteHarpMessage(reg_type, reg_index, bitmask)) # # @property # def all_io_states(self): # """return the state of all PORT digital ios.""" # reg_type, reg_index, _ = REGISTERS["PORT_DIOS_IN"] # read_message_type = self.__class__.READ_MSG_LOOKUP[reg_type] -# return self.device.send(read_message_type(reg_index).frame).payload_as_int() + # return self.device.send(read_message_type(reg_index)).payload # # @all_io_states.setter # def all_io_states(self, bitmask : int): @@ -189,19 +187,19 @@ def DI2(self): # # _IN register, which is different from the OUTPUT # reg_type, reg_index, _ = REGISTERS["PORT_DIOS_IN"] - # return self.device.send(WriteHarpMessage(reg_type, reg_index, bitmask).frame) + # return self.device.send(WriteHarpMessage(reg_type, reg_index, bitmask)) # # def set_io_outputs(self, bitmask : int): # """set digital input/outputs to logic 1 according to bitmask.""" # reg_type, reg_index, _ = REGISTERS["PORT_DIOS_SET"] - # return self.device.send(WriteHarpMessage(reg_type, reg_index, bitmask).frame) + # return self.device.send(WriteHarpMessage(reg_type, reg_index, bitmask)) # # def clear_io_outputs(self, bitmask : int): # """clear digital input/outputs (specified with logic 1) according to bitmask.""" # reg_type, reg_index, _ = REGISTERS["PORT_DIOS_CLEAR"] - # return self.device.send(WriteHarpMessage(reg_type, reg_index, bitmask).frame) + # return self.device.send(WriteHarpMessage(reg_type, reg_index, bitmask)) # # @property # def port0_io0(self): @@ -239,25 +237,23 @@ def DI2(self): def all_output_states(self): """return the state of all PORT digital inputs.""" reg_type, reg_index, _ = REGISTERS["OUTPUTS_OUT"] - return self.device.send( - ReadHarpMessage(reg_type, reg_index).frame - ).payload_as_int() + return self.device.send(ReadHarpMessage(reg_type, reg_index)).payload @all_output_states.setter def all_output_states(self, bitmask : int): """set the state of all PORT digital inputs.""" reg_type, reg_index, _ = REGISTERS["OUTPUTS_OUT"] - return self.device.send(WriteHarpMessage(reg_type, reg_index, bitmask).frame) + return self.device.send(WriteHarpMessage(reg_type, reg_index, bitmask)) def set_outputs(self, bitmask : int): """set digital outputs to logic 1 according to bitmask.""" reg_type, reg_index, _ = REGISTERS["OUTPUTS_SET"] - return self.device.send(WriteHarpMessage(reg_type, reg_index, bitmask).frame) + return self.device.send(WriteHarpMessage(reg_type, reg_index, bitmask)) def clear_outputs(self, bitmask : int): """clear digital outputs (specified with logic 1) according to bitmask.""" reg_type, reg_index, _ = REGISTERS["OUTPUTS_CLR"] - return self.device.send(WriteHarpMessage(reg_type, reg_index, bitmask).frame) + return self.device.send(WriteHarpMessage(reg_type, reg_index, bitmask)) @property def D0(self): diff --git a/tests/test_device.py b/tests/test_device.py index d511549..7e8292c 100644 --- a/tests/test_device.py +++ b/tests/test_device.py @@ -25,7 +25,7 @@ # read_size: int = 35 # TODO: automatically calculate this! # reply: ReplyHarpMessage = device.send( -# HarpMessage.create(MessageType.READ, register, PayloadType.U8).frame +# HarpMessage.create(MessageType.READ, register, PayloadType.U8) # ) # assert reply is not None # # assert reply.payload_as_int() == write_value @@ -50,7 +50,7 @@ # reply = device.send( # HarpMessage.create( # MessageType.WRITE, register, PayloadType.U8, write_value -# ).frame +# ) # ) # assert reply is not None From 6b69b65090a4207a069474f98193975c905c07f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 23 Apr 2025 16:56:00 +0100 Subject: [PATCH 104/159] Remove unused methods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Grilo --- pyharp/device.py | 22 +++++++++++----------- pyharp/messages.py | 24 ------------------------ tests/test_device.py | 2 +- 3 files changed, 12 insertions(+), 36 deletions(-) diff --git a/pyharp/device.py b/pyharp/device.py index a6d10f2..aa1c1cf 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -930,7 +930,7 @@ def _read_who_am_i(self) -> int: HarpMessage.create(MessageType.READ, address, PayloadType.U16) ) - return reply.payload_as_int() + return reply.payload def _read_default_device_name(self) -> str: """ @@ -958,7 +958,7 @@ def _read_hw_version_h(self) -> int: HarpMessage.create(MessageType.READ, address, PayloadType.U8) ) - return reply.payload_as_int() + return reply.payload def _read_hw_version_l(self) -> int: """ @@ -975,7 +975,7 @@ def _read_hw_version_l(self) -> int: HarpMessage.create(MessageType.READ, address, PayloadType.U8) ) - return reply.payload_as_int() + return reply.payload def _read_assembly_version(self) -> int: """ @@ -992,7 +992,7 @@ def _read_assembly_version(self) -> int: HarpMessage.create(MessageType.READ, address, PayloadType.U8) ) - return reply.payload_as_int() + return reply.payload def _read_harp_version_h(self) -> int: """ @@ -1009,7 +1009,7 @@ def _read_harp_version_h(self) -> int: HarpMessage.create(MessageType.READ, address, PayloadType.U8) ) - return reply.payload_as_int() + return reply.payload def _read_harp_version_l(self) -> int: """ @@ -1026,7 +1026,7 @@ def _read_harp_version_l(self) -> int: HarpMessage.create(MessageType.READ, address, PayloadType.U8) ) - return reply.payload_as_int() + return reply.payload def _read_fw_version_h(self) -> int: """ @@ -1043,7 +1043,7 @@ def _read_fw_version_h(self) -> int: HarpMessage.create(MessageType.READ, address, PayloadType.U8) ) - return reply.payload_as_int() + return reply.payload def _read_fw_version_l(self) -> int: """ @@ -1060,7 +1060,7 @@ def _read_fw_version_l(self) -> int: HarpMessage.create(MessageType.READ, address, PayloadType.U8) ) - return reply.payload_as_int() + return reply.payload def _read_device_name(self) -> str: """ @@ -1097,7 +1097,7 @@ def _read_serial_number(self) -> int: if reply.is_error(): return 0 - return reply.payload_as_int() + return reply.payload def _read_clock_config(self) -> int: """ @@ -1114,7 +1114,7 @@ def _read_clock_config(self) -> int: HarpMessage.create(MessageType.READ, address, PayloadType.U8) ) - return reply.payload_as_int() + return reply.payload def _read_timestamp_offset(self) -> int: """ @@ -1131,7 +1131,7 @@ def _read_timestamp_offset(self) -> int: HarpMessage.create(MessageType.READ, address, PayloadType.U8) ) - return reply.payload_as_int() + return reply.payload def __enter__(self): """ diff --git a/pyharp/messages.py b/pyharp/messages.py index 023ff41..54b86c3 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -463,18 +463,6 @@ def timestamp(self) -> float: """ return self._timestamp - # TODO: does this function makes sense since self.payload() already exists? - def payload_as_int(self) -> int: - """ - Returns the payload as an int. - - Returns - ------- - int - the payload parsed as an int - """ - return self._raw_payload[0] - def payload_as_string(self) -> str: """ Returns the payload as a str. @@ -486,18 +474,6 @@ def payload_as_string(self) -> str: """ return self._raw_payload.decode("utf-8").rstrip("\x00") - # TODO: handle float case and/or delete functional altogether - def payload_as_float(self) -> float: - """ - Returns the payload as a float. - - Returns - ------- - float - the payload parsed as a float - """ - return self.payload[0] - class ReadHarpMessage(HarpMessage): """ diff --git a/tests/test_device.py b/tests/test_device.py index 7e8292c..a0137ae 100644 --- a/tests/test_device.py +++ b/tests/test_device.py @@ -28,7 +28,7 @@ # HarpMessage.create(MessageType.READ, register, PayloadType.U8) # ) # assert reply is not None -# # assert reply.payload_as_int() == write_value +# # assert reply.payload == write_value # print(reply) # assert device._dump_file_path.exists() From 897ffd3cbc321ab5140b2e069913a37620b533db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 23 Apr 2025 16:57:04 +0100 Subject: [PATCH 105/159] Fix bug with uninitialized fields and wrong property call MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Grilo --- pyharp/device.py | 4 ++-- pyharp/messages.py | 7 ++----- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/pyharp/device.py b/pyharp/device.py index aa1c1cf..7de2ec9 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -61,7 +61,7 @@ class Device: _ser: HarpSerial _dump_file_path: Path - _dump_file: Optional[BufferedWriter] + _dump_file: Optional[BufferedWriter] = None _read_timeout_s: float _TIMEOUT_S: float = 1.0 @@ -1094,7 +1094,7 @@ def _read_serial_number(self) -> int: HarpMessage.create(MessageType.READ, address, PayloadType.U8) ) - if reply.is_error(): + if reply.is_error: return 0 return reply.payload diff --git a/pyharp/messages.py b/pyharp/messages.py index 54b86c3..c914c52 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -30,11 +30,8 @@ class HarpMessage: DEFAULT_PORT: int = 255 BASE_LENGTH: int = 4 - _frame: bytearray - - def __init__(self): - self._frame = bytearray() - self._port = self.DEFAULT_PORT + _frame: bytearray = bytearray() + _port: int = DEFAULT_PORT def calculate_checksum(self) -> int: """ From 9dfcf15c83dddfa0746463c897924e99794f45ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 30 Apr 2025 10:25:45 +0100 Subject: [PATCH 106/159] Fix bug on Device's send method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Grilo --- pyharp/device.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyharp/device.py b/pyharp/device.py index 7de2ec9..f6c9356 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -435,11 +435,11 @@ def send(self, message: HarpMessage) -> Optional[ReplyHarpMessage]: Optional[ReplyHarpMessage] the reply to the Harp message or None if no reply is given """ - self._ser.write(message) + self._ser.write(message.frame) reply = self._read() - self._dump_reply(reply) + self._dump_reply(reply.frame) return reply From 2fd965fa00b89a5ff8c4e84e2820df7014ae9c1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 30 Apr 2025 10:26:09 +0100 Subject: [PATCH 107/159] Handle empty replies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Grilo --- pyharp/device.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyharp/device.py b/pyharp/device.py index f6c9356..ebc94ea 100644 --- a/pyharp/device.py +++ b/pyharp/device.py @@ -438,6 +438,8 @@ def send(self, message: HarpMessage) -> Optional[ReplyHarpMessage]: self._ser.write(message.frame) reply = self._read() + if reply is None: + return None self._dump_reply(reply.frame) From 4e3ee84bb6c37eba21c0187cc6ba83bc70a01432 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 30 Apr 2025 10:26:27 +0100 Subject: [PATCH 108/159] Fix bug with str method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Grilo --- pyharp/messages.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/pyharp/messages.py b/pyharp/messages.py index c914c52..e0bb046 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -383,15 +383,24 @@ def __str__(self) -> str: payload_str = "".join( f"{item:{format_str}} " - for item in (self.payload if self.payload is list else [self.payload]) + for item in ( + self.payload if isinstance(self.payload, list) else [self.payload] + ) ) + # Check if the object has a 'timestamp' property and it's not None + timestamp_line = "" + if hasattr(self, "timestamp"): + ts = getattr(self, "timestamp") + if ts is not None: + timestamp_line = f"Timestamp: {ts}\r\n" + return ( f"Type: {self.message_type.name}\r\n" + f"Length: {self.length}\r\n" + f"Address: {self.address}\r\n" + f"Port: {self.port}\r\n" - + f"Timestamp: {self.timestamp}\r\n" + + timestamp_line + f"Payload Type: {self.payload_type.name}\r\n" + f"Payload Length: {len(self.payload) if self.payload is list else 1}\r\n" + f"Payload: {payload_str}\r\n" From f310406ffd9c91062bb037381e5467b139671db1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Tue, 20 May 2025 09:37:57 +0100 Subject: [PATCH 109/159] Reformat --- .gitignore | 2 +- .vscode/extensions.json | 10 +++++----- docs/api/core.md | 2 +- docs/api/device.md | 2 +- docs/api/messages.md | 2 +- docs/assets/logo.svg | 14 +++++++------- docs/examples/index.md | 2 +- .../olfactometer_example/olfactometer_example.md | 2 +- .../read_and_write_from_registers.md | 2 +- docs/examples/wait_for_events/wait_for_events.md | 2 +- docs/stylesheets/extra.css | 2 +- pyharp/base.py | 3 ++- 12 files changed, 23 insertions(+), 22 deletions(-) diff --git a/.gitignore b/.gitignore index 1800114..0a19790 100644 --- a/.gitignore +++ b/.gitignore @@ -171,4 +171,4 @@ cython_debug/ .ruff_cache/ # PyPI configuration file -.pypirc \ No newline at end of file +.pypirc diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 4e265ea..2faf153 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,6 +1,6 @@ { - "recommendations": [ - "ms-python.python", - "charliermarsh.ruff" - ] -} \ No newline at end of file + "recommendations": [ + "ms-python.python", + "charliermarsh.ruff" + ] +} diff --git a/docs/api/core.md b/docs/api/core.md index e3d8970..6ec54e7 100644 --- a/docs/api/core.md +++ b/docs/api/core.md @@ -1,4 +1,4 @@ ::: pyharp.MessageType ::: pyharp.PayloadType ::: pyharp.CommonRegisters -::: pyharp.OperationMode \ No newline at end of file +::: pyharp.OperationMode diff --git a/docs/api/device.md b/docs/api/device.md index 9e949dc..842f814 100644 --- a/docs/api/device.md +++ b/docs/api/device.md @@ -1 +1 @@ -::: pyharp.device.Device \ No newline at end of file +::: pyharp.device.Device diff --git a/docs/api/messages.md b/docs/api/messages.md index 17f4233..9d85270 100644 --- a/docs/api/messages.md +++ b/docs/api/messages.md @@ -1,4 +1,4 @@ ::: pyharp.messages.HarpMessage ::: pyharp.messages.ReplyHarpMessage ::: pyharp.messages.ReadHarpMessage -::: pyharp.messages.WriteHarpMessage \ No newline at end of file +::: pyharp.messages.WriteHarpMessage diff --git a/docs/assets/logo.svg b/docs/assets/logo.svg index acb0503..96224d9 100644 --- a/docs/assets/logo.svg +++ b/docs/assets/logo.svg @@ -56,16 +56,16 @@ id="g4" transform="translate(0,326)" style="fill:#ffffff"> - - - - - - + + + + + + \ No newline at end of file + sodipodi:nodetypes="cccccssscccsssssccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccsssssccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccsssss" /> diff --git a/docs/examples/index.md b/docs/examples/index.md index 695565a..f66468a 100644 --- a/docs/examples/index.md +++ b/docs/examples/index.md @@ -7,4 +7,4 @@ Here's the complete list of available examples: - [Getting Device Info](./get_info/get_info.md) - connect to a Harp device and read its information. - [Wait for Events](./wait_for_events/wait_for_events.md) - connect to a Harp device and wait for events. - [Write and Read from Registers](./read_and_write_from_registers/read_and_write_from_registers.md) - connect to the Harp Behavior, read from a digital input and write to a digital output. -- [Olfactometer Example](./olfactometer_example/olfactometer_example.md) - connect to the Harp Olfactometer, enable flow, open and close the odor valves and monitor the measured flow values. \ No newline at end of file +- [Olfactometer Example](./olfactometer_example/olfactometer_example.md) - connect to the Harp Olfactometer, enable flow, open and close the odor valves and monitor the measured flow values. diff --git a/docs/examples/olfactometer_example/olfactometer_example.md b/docs/examples/olfactometer_example/olfactometer_example.md index f78d2e6..91b1024 100644 --- a/docs/examples/olfactometer_example/olfactometer_example.md +++ b/docs/examples/olfactometer_example/olfactometer_example.md @@ -1,6 +1,6 @@ # Olfactometer Example -This example shows how to interface with the [Harp Olfactometer](https://github.com/harp-tech/device.olfactometer). +This example shows how to interface with the [Harp Olfactometer](https://github.com/harp-tech/device.olfactometer). In this example, the flows for the different channels are enabled to random flow values, then every odor valve is opened, one at a time every 5 seconds, and finally the flow is disabled before closing the connection with the device. During this time, the actual flows in every channel are being printed out in the terminal. diff --git a/docs/examples/read_and_write_from_registers/read_and_write_from_registers.md b/docs/examples/read_and_write_from_registers/read_and_write_from_registers.md index ea819d9..040f6e4 100644 --- a/docs/examples/read_and_write_from_registers/read_and_write_from_registers.md +++ b/docs/examples/read_and_write_from_registers/read_and_write_from_registers.md @@ -14,4 +14,4 @@ This example demonstrates how to read and write from registers. In this particul ## Schematics !!! warning - _TODO_ \ No newline at end of file + _TODO_ diff --git a/docs/examples/wait_for_events/wait_for_events.md b/docs/examples/wait_for_events/wait_for_events.md index c4aa6a0..8db54fa 100644 --- a/docs/examples/wait_for_events/wait_for_events.md +++ b/docs/examples/wait_for_events/wait_for_events.md @@ -9,4 +9,4 @@ This example demonstrates how to read the events sent by the Harp device. ```python [](./wait_for_events.py) ``` - \ No newline at end of file + diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index 5829909..b417550 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -6,4 +6,4 @@ .md-nav__title { display: none; -} \ No newline at end of file +} diff --git a/pyharp/base.py b/pyharp/base.py index 0d5d3f0..fe3ad59 100644 --- a/pyharp/base.py +++ b/pyharp/base.py @@ -178,6 +178,7 @@ class OperationMode(IntEnum): RESERVED = 2 SPEED = 3 + class OperationCtrl(IntFlag): """ An enumeration with the operation control bits of a Harp device. More information on the operation control bits can be found [here](https://harp-tech.org/protocol/Device.html#r_operation_ctrl-u16--operation-mode-configuration). @@ -272,4 +273,4 @@ class ClockConfig(IntFlag): REP_ABLE = 0x08 GEN_ABLE = 0x10 CLK_UNLOCK = 0x40 - CLK_LOCK = 0x80 \ No newline at end of file + CLK_LOCK = 0x80 From 721fcb4e7140a0608ae6405c113346d39745345a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Tue, 20 May 2025 09:38:10 +0100 Subject: [PATCH 110/159] Update pre-commit config --- .pre-commit-config.yaml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..23d8811 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,22 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: "v5.0.0" + hooks: + - id: check-case-conflict + - id: check-merge-conflict + - id: check-toml + - id: check-yaml + - id: check-json + exclude: ^.devcontainer/devcontainer.json + - id: pretty-format-json + exclude: ^.devcontainer/devcontainer.json + args: [--autofix, --no-sort-keys] + - id: end-of-file-fixer + - id: trailing-whitespace + + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: "v0.11.5" + hooks: + - id: ruff + args: [--exit-non-zero-on-fix] + - id: ruff-format From d6a886a15f0e7da20cb46c3597974c527e2430e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Tue, 20 May 2025 09:39:10 +0100 Subject: [PATCH 111/159] Add monorepo-plugin to mkdocs --- mkdocs.yml | 6 +- pyproject.toml | 1 + uv.lock | 664 ++++++++++++++++++++++++++----------------------- 3 files changed, 355 insertions(+), 316 deletions(-) diff --git a/mkdocs.yml b/mkdocs.yml index 85e4ba7..19c56c7 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -6,6 +6,7 @@ plugins: - search - autorefs - codeinclude + - monorepo - mkdocstrings: handlers: python: @@ -18,7 +19,7 @@ plugins: repository: fchampalimaud/pyharp branch: main - git-authors - + markdown_extensions: - abbr @@ -84,6 +85,7 @@ nav: - Core: api/core.md - Device: api/device.md - Messages: api/messages.md + - Devices: '*include ./pyharp.devices/*/mkdocs.yml' extra_css: -- stylesheets/extra.css \ No newline at end of file +- stylesheets/extra.css diff --git a/pyproject.toml b/pyproject.toml index 0e23e36..666bc37 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,6 +17,7 @@ dev = [ "mkdocs-git-authors-plugin>=0.9.4", "mkdocs-git-committers-plugin-2>=2.5.0", "mkdocs-material>=9.6.9", + "mkdocs-monorepo-plugin>=1.1.0", "mkdocstrings-python>=1.16.6", "pytest>=8.3.5", "pytest-cov>=6.1.1", diff --git a/uv.lock b/uv.lock index 9a082b9..de644e6 100644 --- a/uv.lock +++ b/uv.lock @@ -1,84 +1,84 @@ version = 1 -revision = 1 +revision = 2 requires-python = ">=3.11" [[package]] name = "babel" version = "2.17.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852 } +sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537 }, + { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" }, ] [[package]] name = "backrefs" version = "5.8" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6c/46/caba1eb32fa5784428ab401a5487f73db4104590ecd939ed9daaf18b47e0/backrefs-5.8.tar.gz", hash = "sha256:2cab642a205ce966af3dd4b38ee36009b31fa9502a35fd61d59ccc116e40a6bd", size = 6773994 } +sdist = { url = "https://files.pythonhosted.org/packages/6c/46/caba1eb32fa5784428ab401a5487f73db4104590ecd939ed9daaf18b47e0/backrefs-5.8.tar.gz", hash = "sha256:2cab642a205ce966af3dd4b38ee36009b31fa9502a35fd61d59ccc116e40a6bd", size = 6773994, upload-time = "2025-02-25T18:15:32.003Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/cb/d019ab87fe70e0fe3946196d50d6a4428623dc0c38a6669c8cae0320fbf3/backrefs-5.8-py310-none-any.whl", hash = "sha256:c67f6638a34a5b8730812f5101376f9d41dc38c43f1fdc35cb54700f6ed4465d", size = 380337 }, - { url = "https://files.pythonhosted.org/packages/a9/86/abd17f50ee21b2248075cb6924c6e7f9d23b4925ca64ec660e869c2633f1/backrefs-5.8-py311-none-any.whl", hash = "sha256:2e1c15e4af0e12e45c8701bd5da0902d326b2e200cafcd25e49d9f06d44bb61b", size = 392142 }, - { url = "https://files.pythonhosted.org/packages/b3/04/7b415bd75c8ab3268cc138c76fa648c19495fcc7d155508a0e62f3f82308/backrefs-5.8-py312-none-any.whl", hash = "sha256:bbef7169a33811080d67cdf1538c8289f76f0942ff971222a16034da88a73486", size = 398021 }, - { url = "https://files.pythonhosted.org/packages/04/b8/60dcfb90eb03a06e883a92abbc2ab95c71f0d8c9dd0af76ab1d5ce0b1402/backrefs-5.8-py313-none-any.whl", hash = "sha256:e3a63b073867dbefd0536425f43db618578528e3896fb77be7141328642a1585", size = 399915 }, - { url = "https://files.pythonhosted.org/packages/0c/37/fb6973edeb700f6e3d6ff222400602ab1830446c25c7b4676d8de93e65b8/backrefs-5.8-py39-none-any.whl", hash = "sha256:a66851e4533fb5b371aa0628e1fee1af05135616b86140c9d787a2ffdf4b8fdc", size = 380336 }, + { url = "https://files.pythonhosted.org/packages/bf/cb/d019ab87fe70e0fe3946196d50d6a4428623dc0c38a6669c8cae0320fbf3/backrefs-5.8-py310-none-any.whl", hash = "sha256:c67f6638a34a5b8730812f5101376f9d41dc38c43f1fdc35cb54700f6ed4465d", size = 380337, upload-time = "2025-02-25T16:53:14.607Z" }, + { url = "https://files.pythonhosted.org/packages/a9/86/abd17f50ee21b2248075cb6924c6e7f9d23b4925ca64ec660e869c2633f1/backrefs-5.8-py311-none-any.whl", hash = "sha256:2e1c15e4af0e12e45c8701bd5da0902d326b2e200cafcd25e49d9f06d44bb61b", size = 392142, upload-time = "2025-02-25T16:53:17.266Z" }, + { url = "https://files.pythonhosted.org/packages/b3/04/7b415bd75c8ab3268cc138c76fa648c19495fcc7d155508a0e62f3f82308/backrefs-5.8-py312-none-any.whl", hash = "sha256:bbef7169a33811080d67cdf1538c8289f76f0942ff971222a16034da88a73486", size = 398021, upload-time = "2025-02-25T16:53:26.378Z" }, + { url = "https://files.pythonhosted.org/packages/04/b8/60dcfb90eb03a06e883a92abbc2ab95c71f0d8c9dd0af76ab1d5ce0b1402/backrefs-5.8-py313-none-any.whl", hash = "sha256:e3a63b073867dbefd0536425f43db618578528e3896fb77be7141328642a1585", size = 399915, upload-time = "2025-02-25T16:53:28.167Z" }, + { url = "https://files.pythonhosted.org/packages/0c/37/fb6973edeb700f6e3d6ff222400602ab1830446c25c7b4676d8de93e65b8/backrefs-5.8-py39-none-any.whl", hash = "sha256:a66851e4533fb5b371aa0628e1fee1af05135616b86140c9d787a2ffdf4b8fdc", size = 380336, upload-time = "2025-02-25T16:53:29.858Z" }, ] [[package]] name = "certifi" version = "2025.1.31" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 } +sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577, upload-time = "2025-01-31T02:16:47.166Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 }, + { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393, upload-time = "2025-01-31T02:16:45.015Z" }, ] [[package]] name = "charset-normalizer" version = "3.4.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/72/80/41ef5d5a7935d2d3a773e3eaebf0a9350542f2cab4eac59a7a4741fbbbbe/charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125", size = 194995 }, - { url = "https://files.pythonhosted.org/packages/7a/28/0b9fefa7b8b080ec492110af6d88aa3dea91c464b17d53474b6e9ba5d2c5/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1", size = 139471 }, - { url = "https://files.pythonhosted.org/packages/71/64/d24ab1a997efb06402e3fc07317e94da358e2585165930d9d59ad45fcae2/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3", size = 149831 }, - { url = "https://files.pythonhosted.org/packages/37/ed/be39e5258e198655240db5e19e0b11379163ad7070962d6b0c87ed2c4d39/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd", size = 142335 }, - { url = "https://files.pythonhosted.org/packages/88/83/489e9504711fa05d8dde1574996408026bdbdbd938f23be67deebb5eca92/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00", size = 143862 }, - { url = "https://files.pythonhosted.org/packages/c6/c7/32da20821cf387b759ad24627a9aca289d2822de929b8a41b6241767b461/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12", size = 145673 }, - { url = "https://files.pythonhosted.org/packages/68/85/f4288e96039abdd5aeb5c546fa20a37b50da71b5cf01e75e87f16cd43304/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77", size = 140211 }, - { url = "https://files.pythonhosted.org/packages/28/a3/a42e70d03cbdabc18997baf4f0227c73591a08041c149e710045c281f97b/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146", size = 148039 }, - { url = "https://files.pythonhosted.org/packages/85/e4/65699e8ab3014ecbe6f5c71d1a55d810fb716bbfd74f6283d5c2aa87febf/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd", size = 151939 }, - { url = "https://files.pythonhosted.org/packages/b1/82/8e9fe624cc5374193de6860aba3ea8070f584c8565ee77c168ec13274bd2/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6", size = 149075 }, - { url = "https://files.pythonhosted.org/packages/3d/7b/82865ba54c765560c8433f65e8acb9217cb839a9e32b42af4aa8e945870f/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8", size = 144340 }, - { url = "https://files.pythonhosted.org/packages/b5/b6/9674a4b7d4d99a0d2df9b215da766ee682718f88055751e1e5e753c82db0/charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b", size = 95205 }, - { url = "https://files.pythonhosted.org/packages/1e/ab/45b180e175de4402dcf7547e4fb617283bae54ce35c27930a6f35b6bef15/charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76", size = 102441 }, - { url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105 }, - { url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404 }, - { url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423 }, - { url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184 }, - { url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268 }, - { url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601 }, - { url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098 }, - { url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520 }, - { url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852 }, - { url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488 }, - { url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192 }, - { url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550 }, - { url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785 }, - { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698 }, - { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162 }, - { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263 }, - { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966 }, - { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992 }, - { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162 }, - { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972 }, - { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095 }, - { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668 }, - { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073 }, - { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732 }, - { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391 }, - { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702 }, - { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 }, +sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188, upload-time = "2024-12-24T18:12:35.43Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/80/41ef5d5a7935d2d3a773e3eaebf0a9350542f2cab4eac59a7a4741fbbbbe/charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125", size = 194995, upload-time = "2024-12-24T18:10:12.838Z" }, + { url = "https://files.pythonhosted.org/packages/7a/28/0b9fefa7b8b080ec492110af6d88aa3dea91c464b17d53474b6e9ba5d2c5/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1", size = 139471, upload-time = "2024-12-24T18:10:14.101Z" }, + { url = "https://files.pythonhosted.org/packages/71/64/d24ab1a997efb06402e3fc07317e94da358e2585165930d9d59ad45fcae2/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3", size = 149831, upload-time = "2024-12-24T18:10:15.512Z" }, + { url = "https://files.pythonhosted.org/packages/37/ed/be39e5258e198655240db5e19e0b11379163ad7070962d6b0c87ed2c4d39/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd", size = 142335, upload-time = "2024-12-24T18:10:18.369Z" }, + { url = "https://files.pythonhosted.org/packages/88/83/489e9504711fa05d8dde1574996408026bdbdbd938f23be67deebb5eca92/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00", size = 143862, upload-time = "2024-12-24T18:10:19.743Z" }, + { url = "https://files.pythonhosted.org/packages/c6/c7/32da20821cf387b759ad24627a9aca289d2822de929b8a41b6241767b461/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12", size = 145673, upload-time = "2024-12-24T18:10:21.139Z" }, + { url = "https://files.pythonhosted.org/packages/68/85/f4288e96039abdd5aeb5c546fa20a37b50da71b5cf01e75e87f16cd43304/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77", size = 140211, upload-time = "2024-12-24T18:10:22.382Z" }, + { url = "https://files.pythonhosted.org/packages/28/a3/a42e70d03cbdabc18997baf4f0227c73591a08041c149e710045c281f97b/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146", size = 148039, upload-time = "2024-12-24T18:10:24.802Z" }, + { url = "https://files.pythonhosted.org/packages/85/e4/65699e8ab3014ecbe6f5c71d1a55d810fb716bbfd74f6283d5c2aa87febf/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd", size = 151939, upload-time = "2024-12-24T18:10:26.124Z" }, + { url = "https://files.pythonhosted.org/packages/b1/82/8e9fe624cc5374193de6860aba3ea8070f584c8565ee77c168ec13274bd2/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6", size = 149075, upload-time = "2024-12-24T18:10:30.027Z" }, + { url = "https://files.pythonhosted.org/packages/3d/7b/82865ba54c765560c8433f65e8acb9217cb839a9e32b42af4aa8e945870f/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8", size = 144340, upload-time = "2024-12-24T18:10:32.679Z" }, + { url = "https://files.pythonhosted.org/packages/b5/b6/9674a4b7d4d99a0d2df9b215da766ee682718f88055751e1e5e753c82db0/charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b", size = 95205, upload-time = "2024-12-24T18:10:34.724Z" }, + { url = "https://files.pythonhosted.org/packages/1e/ab/45b180e175de4402dcf7547e4fb617283bae54ce35c27930a6f35b6bef15/charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76", size = 102441, upload-time = "2024-12-24T18:10:37.574Z" }, + { url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105, upload-time = "2024-12-24T18:10:38.83Z" }, + { url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404, upload-time = "2024-12-24T18:10:44.272Z" }, + { url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423, upload-time = "2024-12-24T18:10:45.492Z" }, + { url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184, upload-time = "2024-12-24T18:10:47.898Z" }, + { url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268, upload-time = "2024-12-24T18:10:50.589Z" }, + { url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601, upload-time = "2024-12-24T18:10:52.541Z" }, + { url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098, upload-time = "2024-12-24T18:10:53.789Z" }, + { url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520, upload-time = "2024-12-24T18:10:55.048Z" }, + { url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852, upload-time = "2024-12-24T18:10:57.647Z" }, + { url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488, upload-time = "2024-12-24T18:10:59.43Z" }, + { url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192, upload-time = "2024-12-24T18:11:00.676Z" }, + { url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550, upload-time = "2024-12-24T18:11:01.952Z" }, + { url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785, upload-time = "2024-12-24T18:11:03.142Z" }, + { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698, upload-time = "2024-12-24T18:11:05.834Z" }, + { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162, upload-time = "2024-12-24T18:11:07.064Z" }, + { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263, upload-time = "2024-12-24T18:11:08.374Z" }, + { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966, upload-time = "2024-12-24T18:11:09.831Z" }, + { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992, upload-time = "2024-12-24T18:11:12.03Z" }, + { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162, upload-time = "2024-12-24T18:11:13.372Z" }, + { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972, upload-time = "2024-12-24T18:11:14.628Z" }, + { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095, upload-time = "2024-12-24T18:11:17.672Z" }, + { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668, upload-time = "2024-12-24T18:11:18.989Z" }, + { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073, upload-time = "2024-12-24T18:11:21.507Z" }, + { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732, upload-time = "2024-12-24T18:11:22.774Z" }, + { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391, upload-time = "2024-12-24T18:11:24.139Z" }, + { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702, upload-time = "2024-12-24T18:11:26.535Z" }, + { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767, upload-time = "2024-12-24T18:12:32.852Z" }, ] [[package]] @@ -88,68 +88,68 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 } +sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 }, + { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" }, ] [[package]] name = "colorama" version = "0.4.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] [[package]] name = "coverage" version = "7.8.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/19/4f/2251e65033ed2ce1e68f00f91a0294e0f80c80ae8c3ebbe2f12828c4cd53/coverage-7.8.0.tar.gz", hash = "sha256:7a3d62b3b03b4b6fd41a085f3574874cf946cb4604d2b4d3e8dca8cd570ca501", size = 811872 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/77/074d201adb8383addae5784cb8e2dac60bb62bfdf28b2b10f3a3af2fda47/coverage-7.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7ac22a0bb2c7c49f441f7a6d46c9c80d96e56f5a8bc6972529ed43c8b694e27", size = 211493 }, - { url = "https://files.pythonhosted.org/packages/a9/89/7a8efe585750fe59b48d09f871f0e0c028a7b10722b2172dfe021fa2fdd4/coverage-7.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf13d564d310c156d1c8e53877baf2993fb3073b2fc9f69790ca6a732eb4bfea", size = 211921 }, - { url = "https://files.pythonhosted.org/packages/e9/ef/96a90c31d08a3f40c49dbe897df4f1fd51fb6583821a1a1c5ee30cc8f680/coverage-7.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5761c70c017c1b0d21b0815a920ffb94a670c8d5d409d9b38857874c21f70d7", size = 244556 }, - { url = "https://files.pythonhosted.org/packages/89/97/dcd5c2ce72cee9d7b0ee8c89162c24972fb987a111b92d1a3d1d19100c61/coverage-7.8.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5ff52d790c7e1628241ffbcaeb33e07d14b007b6eb00a19320c7b8a7024c040", size = 242245 }, - { url = "https://files.pythonhosted.org/packages/b2/7b/b63cbb44096141ed435843bbb251558c8e05cc835c8da31ca6ffb26d44c0/coverage-7.8.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d39fc4817fd67b3915256af5dda75fd4ee10621a3d484524487e33416c6f3543", size = 244032 }, - { url = "https://files.pythonhosted.org/packages/97/e3/7fa8c2c00a1ef530c2a42fa5df25a6971391f92739d83d67a4ee6dcf7a02/coverage-7.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b44674870709017e4b4036e3d0d6c17f06a0e6d4436422e0ad29b882c40697d2", size = 243679 }, - { url = "https://files.pythonhosted.org/packages/4f/b3/e0a59d8df9150c8a0c0841d55d6568f0a9195692136c44f3d21f1842c8f6/coverage-7.8.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8f99eb72bf27cbb167b636eb1726f590c00e1ad375002230607a844d9e9a2318", size = 241852 }, - { url = "https://files.pythonhosted.org/packages/9b/82/db347ccd57bcef150c173df2ade97976a8367a3be7160e303e43dd0c795f/coverage-7.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b571bf5341ba8c6bc02e0baeaf3b061ab993bf372d982ae509807e7f112554e9", size = 242389 }, - { url = "https://files.pythonhosted.org/packages/21/f6/3f7d7879ceb03923195d9ff294456241ed05815281f5254bc16ef71d6a20/coverage-7.8.0-cp311-cp311-win32.whl", hash = "sha256:e75a2ad7b647fd8046d58c3132d7eaf31b12d8a53c0e4b21fa9c4d23d6ee6d3c", size = 213997 }, - { url = "https://files.pythonhosted.org/packages/28/87/021189643e18ecf045dbe1e2071b2747901f229df302de01c998eeadf146/coverage-7.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:3043ba1c88b2139126fc72cb48574b90e2e0546d4c78b5299317f61b7f718b78", size = 214911 }, - { url = "https://files.pythonhosted.org/packages/aa/12/4792669473297f7973518bec373a955e267deb4339286f882439b8535b39/coverage-7.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bbb5cc845a0292e0c520656d19d7ce40e18d0e19b22cb3e0409135a575bf79fc", size = 211684 }, - { url = "https://files.pythonhosted.org/packages/be/e1/2a4ec273894000ebedd789e8f2fc3813fcaf486074f87fd1c5b2cb1c0a2b/coverage-7.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4dfd9a93db9e78666d178d4f08a5408aa3f2474ad4d0e0378ed5f2ef71640cb6", size = 211935 }, - { url = "https://files.pythonhosted.org/packages/f8/3a/7b14f6e4372786709a361729164125f6b7caf4024ce02e596c4a69bccb89/coverage-7.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f017a61399f13aa6d1039f75cd467be388d157cd81f1a119b9d9a68ba6f2830d", size = 245994 }, - { url = "https://files.pythonhosted.org/packages/54/80/039cc7f1f81dcbd01ea796d36d3797e60c106077e31fd1f526b85337d6a1/coverage-7.8.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0915742f4c82208ebf47a2b154a5334155ed9ef9fe6190674b8a46c2fb89cb05", size = 242885 }, - { url = "https://files.pythonhosted.org/packages/10/e0/dc8355f992b6cc2f9dcd5ef6242b62a3f73264893bc09fbb08bfcab18eb4/coverage-7.8.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a40fcf208e021eb14b0fac6bdb045c0e0cab53105f93ba0d03fd934c956143a", size = 245142 }, - { url = "https://files.pythonhosted.org/packages/43/1b/33e313b22cf50f652becb94c6e7dae25d8f02e52e44db37a82de9ac357e8/coverage-7.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a1f406a8e0995d654b2ad87c62caf6befa767885301f3b8f6f73e6f3c31ec3a6", size = 244906 }, - { url = "https://files.pythonhosted.org/packages/05/08/c0a8048e942e7f918764ccc99503e2bccffba1c42568693ce6955860365e/coverage-7.8.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:77af0f6447a582fdc7de5e06fa3757a3ef87769fbb0fdbdeba78c23049140a47", size = 243124 }, - { url = "https://files.pythonhosted.org/packages/5b/62/ea625b30623083c2aad645c9a6288ad9fc83d570f9adb913a2abdba562dd/coverage-7.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f2d32f95922927186c6dbc8bc60df0d186b6edb828d299ab10898ef3f40052fe", size = 244317 }, - { url = "https://files.pythonhosted.org/packages/62/cb/3871f13ee1130a6c8f020e2f71d9ed269e1e2124aa3374d2180ee451cee9/coverage-7.8.0-cp312-cp312-win32.whl", hash = "sha256:769773614e676f9d8e8a0980dd7740f09a6ea386d0f383db6821df07d0f08545", size = 214170 }, - { url = "https://files.pythonhosted.org/packages/88/26/69fe1193ab0bfa1eb7a7c0149a066123611baba029ebb448500abd8143f9/coverage-7.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:e5d2b9be5b0693cf21eb4ce0ec8d211efb43966f6657807f6859aab3814f946b", size = 214969 }, - { url = "https://files.pythonhosted.org/packages/f3/21/87e9b97b568e223f3438d93072479c2f36cc9b3f6b9f7094b9d50232acc0/coverage-7.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ac46d0c2dd5820ce93943a501ac5f6548ea81594777ca585bf002aa8854cacd", size = 211708 }, - { url = "https://files.pythonhosted.org/packages/75/be/882d08b28a0d19c9c4c2e8a1c6ebe1f79c9c839eb46d4fca3bd3b34562b9/coverage-7.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:771eb7587a0563ca5bb6f622b9ed7f9d07bd08900f7589b4febff05f469bea00", size = 211981 }, - { url = "https://files.pythonhosted.org/packages/7a/1d/ce99612ebd58082fbe3f8c66f6d8d5694976c76a0d474503fa70633ec77f/coverage-7.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42421e04069fb2cbcbca5a696c4050b84a43b05392679d4068acbe65449b5c64", size = 245495 }, - { url = "https://files.pythonhosted.org/packages/dc/8d/6115abe97df98db6b2bd76aae395fcc941d039a7acd25f741312ced9a78f/coverage-7.8.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:554fec1199d93ab30adaa751db68acec2b41c5602ac944bb19187cb9a41a8067", size = 242538 }, - { url = "https://files.pythonhosted.org/packages/cb/74/2f8cc196643b15bc096d60e073691dadb3dca48418f08bc78dd6e899383e/coverage-7.8.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aaeb00761f985007b38cf463b1d160a14a22c34eb3f6a39d9ad6fc27cb73008", size = 244561 }, - { url = "https://files.pythonhosted.org/packages/22/70/c10c77cd77970ac965734fe3419f2c98665f6e982744a9bfb0e749d298f4/coverage-7.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:581a40c7b94921fffd6457ffe532259813fc68eb2bdda60fa8cc343414ce3733", size = 244633 }, - { url = "https://files.pythonhosted.org/packages/38/5a/4f7569d946a07c952688debee18c2bb9ab24f88027e3d71fd25dbc2f9dca/coverage-7.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f319bae0321bc838e205bf9e5bc28f0a3165f30c203b610f17ab5552cff90323", size = 242712 }, - { url = "https://files.pythonhosted.org/packages/bb/a1/03a43b33f50475a632a91ea8c127f7e35e53786dbe6781c25f19fd5a65f8/coverage-7.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04bfec25a8ef1c5f41f5e7e5c842f6b615599ca8ba8391ec33a9290d9d2db3a3", size = 244000 }, - { url = "https://files.pythonhosted.org/packages/6a/89/ab6c43b1788a3128e4d1b7b54214548dcad75a621f9d277b14d16a80d8a1/coverage-7.8.0-cp313-cp313-win32.whl", hash = "sha256:dd19608788b50eed889e13a5d71d832edc34fc9dfce606f66e8f9f917eef910d", size = 214195 }, - { url = "https://files.pythonhosted.org/packages/12/12/6bf5f9a8b063d116bac536a7fb594fc35cb04981654cccb4bbfea5dcdfa0/coverage-7.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:a9abbccd778d98e9c7e85038e35e91e67f5b520776781d9a1e2ee9d400869487", size = 214998 }, - { url = "https://files.pythonhosted.org/packages/2a/e6/1e9df74ef7a1c983a9c7443dac8aac37a46f1939ae3499424622e72a6f78/coverage-7.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:18c5ae6d061ad5b3e7eef4363fb27a0576012a7447af48be6c75b88494c6cf25", size = 212541 }, - { url = "https://files.pythonhosted.org/packages/04/51/c32174edb7ee49744e2e81c4b1414ac9df3dacfcb5b5f273b7f285ad43f6/coverage-7.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:95aa6ae391a22bbbce1b77ddac846c98c5473de0372ba5c463480043a07bff42", size = 212767 }, - { url = "https://files.pythonhosted.org/packages/e9/8f/f454cbdb5212f13f29d4a7983db69169f1937e869a5142bce983ded52162/coverage-7.8.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e013b07ba1c748dacc2a80e69a46286ff145935f260eb8c72df7185bf048f502", size = 256997 }, - { url = "https://files.pythonhosted.org/packages/e6/74/2bf9e78b321216d6ee90a81e5c22f912fc428442c830c4077b4a071db66f/coverage-7.8.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d766a4f0e5aa1ba056ec3496243150698dc0481902e2b8559314368717be82b1", size = 252708 }, - { url = "https://files.pythonhosted.org/packages/92/4d/50d7eb1e9a6062bee6e2f92e78b0998848a972e9afad349b6cdde6fa9e32/coverage-7.8.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad80e6b4a0c3cb6f10f29ae4c60e991f424e6b14219d46f1e7d442b938ee68a4", size = 255046 }, - { url = "https://files.pythonhosted.org/packages/40/9e/71fb4e7402a07c4198ab44fc564d09d7d0ffca46a9fb7b0a7b929e7641bd/coverage-7.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b87eb6fc9e1bb8f98892a2458781348fa37e6925f35bb6ceb9d4afd54ba36c73", size = 256139 }, - { url = "https://files.pythonhosted.org/packages/49/1a/78d37f7a42b5beff027e807c2843185961fdae7fe23aad5a4837c93f9d25/coverage-7.8.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d1ba00ae33be84066cfbe7361d4e04dec78445b2b88bdb734d0d1cbab916025a", size = 254307 }, - { url = "https://files.pythonhosted.org/packages/58/e9/8fb8e0ff6bef5e170ee19d59ca694f9001b2ec085dc99b4f65c128bb3f9a/coverage-7.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f3c38e4e5ccbdc9198aecc766cedbb134b2d89bf64533973678dfcf07effd883", size = 255116 }, - { url = "https://files.pythonhosted.org/packages/56/b0/d968ecdbe6fe0a863de7169bbe9e8a476868959f3af24981f6a10d2b6924/coverage-7.8.0-cp313-cp313t-win32.whl", hash = "sha256:379fe315e206b14e21db5240f89dc0774bdd3e25c3c58c2c733c99eca96f1ada", size = 214909 }, - { url = "https://files.pythonhosted.org/packages/87/e9/d6b7ef9fecf42dfb418d93544af47c940aa83056c49e6021a564aafbc91f/coverage-7.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2e4b6b87bb0c846a9315e3ab4be2d52fac905100565f4b92f02c445c8799e257", size = 216068 }, - { url = "https://files.pythonhosted.org/packages/c4/f1/1da77bb4c920aa30e82fa9b6ea065da3467977c2e5e032e38e66f1c57ffd/coverage-7.8.0-pp39.pp310.pp311-none-any.whl", hash = "sha256:b8194fb8e50d556d5849753de991d390c5a1edeeba50f68e3a9253fbd8bf8ccd", size = 203443 }, - { url = "https://files.pythonhosted.org/packages/59/f1/4da7717f0063a222db253e7121bd6a56f6fb1ba439dcc36659088793347c/coverage-7.8.0-py3-none-any.whl", hash = "sha256:dbf364b4c5e7bae9250528167dfe40219b62e2d573c854d74be213e1e52069f7", size = 203435 }, +sdist = { url = "https://files.pythonhosted.org/packages/19/4f/2251e65033ed2ce1e68f00f91a0294e0f80c80ae8c3ebbe2f12828c4cd53/coverage-7.8.0.tar.gz", hash = "sha256:7a3d62b3b03b4b6fd41a085f3574874cf946cb4604d2b4d3e8dca8cd570ca501", size = 811872, upload-time = "2025-03-30T20:36:45.376Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/77/074d201adb8383addae5784cb8e2dac60bb62bfdf28b2b10f3a3af2fda47/coverage-7.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7ac22a0bb2c7c49f441f7a6d46c9c80d96e56f5a8bc6972529ed43c8b694e27", size = 211493, upload-time = "2025-03-30T20:35:12.286Z" }, + { url = "https://files.pythonhosted.org/packages/a9/89/7a8efe585750fe59b48d09f871f0e0c028a7b10722b2172dfe021fa2fdd4/coverage-7.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf13d564d310c156d1c8e53877baf2993fb3073b2fc9f69790ca6a732eb4bfea", size = 211921, upload-time = "2025-03-30T20:35:14.18Z" }, + { url = "https://files.pythonhosted.org/packages/e9/ef/96a90c31d08a3f40c49dbe897df4f1fd51fb6583821a1a1c5ee30cc8f680/coverage-7.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5761c70c017c1b0d21b0815a920ffb94a670c8d5d409d9b38857874c21f70d7", size = 244556, upload-time = "2025-03-30T20:35:15.616Z" }, + { url = "https://files.pythonhosted.org/packages/89/97/dcd5c2ce72cee9d7b0ee8c89162c24972fb987a111b92d1a3d1d19100c61/coverage-7.8.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5ff52d790c7e1628241ffbcaeb33e07d14b007b6eb00a19320c7b8a7024c040", size = 242245, upload-time = "2025-03-30T20:35:18.648Z" }, + { url = "https://files.pythonhosted.org/packages/b2/7b/b63cbb44096141ed435843bbb251558c8e05cc835c8da31ca6ffb26d44c0/coverage-7.8.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d39fc4817fd67b3915256af5dda75fd4ee10621a3d484524487e33416c6f3543", size = 244032, upload-time = "2025-03-30T20:35:20.131Z" }, + { url = "https://files.pythonhosted.org/packages/97/e3/7fa8c2c00a1ef530c2a42fa5df25a6971391f92739d83d67a4ee6dcf7a02/coverage-7.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b44674870709017e4b4036e3d0d6c17f06a0e6d4436422e0ad29b882c40697d2", size = 243679, upload-time = "2025-03-30T20:35:21.636Z" }, + { url = "https://files.pythonhosted.org/packages/4f/b3/e0a59d8df9150c8a0c0841d55d6568f0a9195692136c44f3d21f1842c8f6/coverage-7.8.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8f99eb72bf27cbb167b636eb1726f590c00e1ad375002230607a844d9e9a2318", size = 241852, upload-time = "2025-03-30T20:35:23.525Z" }, + { url = "https://files.pythonhosted.org/packages/9b/82/db347ccd57bcef150c173df2ade97976a8367a3be7160e303e43dd0c795f/coverage-7.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b571bf5341ba8c6bc02e0baeaf3b061ab993bf372d982ae509807e7f112554e9", size = 242389, upload-time = "2025-03-30T20:35:25.09Z" }, + { url = "https://files.pythonhosted.org/packages/21/f6/3f7d7879ceb03923195d9ff294456241ed05815281f5254bc16ef71d6a20/coverage-7.8.0-cp311-cp311-win32.whl", hash = "sha256:e75a2ad7b647fd8046d58c3132d7eaf31b12d8a53c0e4b21fa9c4d23d6ee6d3c", size = 213997, upload-time = "2025-03-30T20:35:26.914Z" }, + { url = "https://files.pythonhosted.org/packages/28/87/021189643e18ecf045dbe1e2071b2747901f229df302de01c998eeadf146/coverage-7.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:3043ba1c88b2139126fc72cb48574b90e2e0546d4c78b5299317f61b7f718b78", size = 214911, upload-time = "2025-03-30T20:35:28.498Z" }, + { url = "https://files.pythonhosted.org/packages/aa/12/4792669473297f7973518bec373a955e267deb4339286f882439b8535b39/coverage-7.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bbb5cc845a0292e0c520656d19d7ce40e18d0e19b22cb3e0409135a575bf79fc", size = 211684, upload-time = "2025-03-30T20:35:29.959Z" }, + { url = "https://files.pythonhosted.org/packages/be/e1/2a4ec273894000ebedd789e8f2fc3813fcaf486074f87fd1c5b2cb1c0a2b/coverage-7.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4dfd9a93db9e78666d178d4f08a5408aa3f2474ad4d0e0378ed5f2ef71640cb6", size = 211935, upload-time = "2025-03-30T20:35:31.912Z" }, + { url = "https://files.pythonhosted.org/packages/f8/3a/7b14f6e4372786709a361729164125f6b7caf4024ce02e596c4a69bccb89/coverage-7.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f017a61399f13aa6d1039f75cd467be388d157cd81f1a119b9d9a68ba6f2830d", size = 245994, upload-time = "2025-03-30T20:35:33.455Z" }, + { url = "https://files.pythonhosted.org/packages/54/80/039cc7f1f81dcbd01ea796d36d3797e60c106077e31fd1f526b85337d6a1/coverage-7.8.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0915742f4c82208ebf47a2b154a5334155ed9ef9fe6190674b8a46c2fb89cb05", size = 242885, upload-time = "2025-03-30T20:35:35.354Z" }, + { url = "https://files.pythonhosted.org/packages/10/e0/dc8355f992b6cc2f9dcd5ef6242b62a3f73264893bc09fbb08bfcab18eb4/coverage-7.8.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a40fcf208e021eb14b0fac6bdb045c0e0cab53105f93ba0d03fd934c956143a", size = 245142, upload-time = "2025-03-30T20:35:37.121Z" }, + { url = "https://files.pythonhosted.org/packages/43/1b/33e313b22cf50f652becb94c6e7dae25d8f02e52e44db37a82de9ac357e8/coverage-7.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a1f406a8e0995d654b2ad87c62caf6befa767885301f3b8f6f73e6f3c31ec3a6", size = 244906, upload-time = "2025-03-30T20:35:39.07Z" }, + { url = "https://files.pythonhosted.org/packages/05/08/c0a8048e942e7f918764ccc99503e2bccffba1c42568693ce6955860365e/coverage-7.8.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:77af0f6447a582fdc7de5e06fa3757a3ef87769fbb0fdbdeba78c23049140a47", size = 243124, upload-time = "2025-03-30T20:35:40.598Z" }, + { url = "https://files.pythonhosted.org/packages/5b/62/ea625b30623083c2aad645c9a6288ad9fc83d570f9adb913a2abdba562dd/coverage-7.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f2d32f95922927186c6dbc8bc60df0d186b6edb828d299ab10898ef3f40052fe", size = 244317, upload-time = "2025-03-30T20:35:42.204Z" }, + { url = "https://files.pythonhosted.org/packages/62/cb/3871f13ee1130a6c8f020e2f71d9ed269e1e2124aa3374d2180ee451cee9/coverage-7.8.0-cp312-cp312-win32.whl", hash = "sha256:769773614e676f9d8e8a0980dd7740f09a6ea386d0f383db6821df07d0f08545", size = 214170, upload-time = "2025-03-30T20:35:44.216Z" }, + { url = "https://files.pythonhosted.org/packages/88/26/69fe1193ab0bfa1eb7a7c0149a066123611baba029ebb448500abd8143f9/coverage-7.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:e5d2b9be5b0693cf21eb4ce0ec8d211efb43966f6657807f6859aab3814f946b", size = 214969, upload-time = "2025-03-30T20:35:45.797Z" }, + { url = "https://files.pythonhosted.org/packages/f3/21/87e9b97b568e223f3438d93072479c2f36cc9b3f6b9f7094b9d50232acc0/coverage-7.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ac46d0c2dd5820ce93943a501ac5f6548ea81594777ca585bf002aa8854cacd", size = 211708, upload-time = "2025-03-30T20:35:47.417Z" }, + { url = "https://files.pythonhosted.org/packages/75/be/882d08b28a0d19c9c4c2e8a1c6ebe1f79c9c839eb46d4fca3bd3b34562b9/coverage-7.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:771eb7587a0563ca5bb6f622b9ed7f9d07bd08900f7589b4febff05f469bea00", size = 211981, upload-time = "2025-03-30T20:35:49.002Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1d/ce99612ebd58082fbe3f8c66f6d8d5694976c76a0d474503fa70633ec77f/coverage-7.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42421e04069fb2cbcbca5a696c4050b84a43b05392679d4068acbe65449b5c64", size = 245495, upload-time = "2025-03-30T20:35:51.073Z" }, + { url = "https://files.pythonhosted.org/packages/dc/8d/6115abe97df98db6b2bd76aae395fcc941d039a7acd25f741312ced9a78f/coverage-7.8.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:554fec1199d93ab30adaa751db68acec2b41c5602ac944bb19187cb9a41a8067", size = 242538, upload-time = "2025-03-30T20:35:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/cb/74/2f8cc196643b15bc096d60e073691dadb3dca48418f08bc78dd6e899383e/coverage-7.8.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aaeb00761f985007b38cf463b1d160a14a22c34eb3f6a39d9ad6fc27cb73008", size = 244561, upload-time = "2025-03-30T20:35:54.658Z" }, + { url = "https://files.pythonhosted.org/packages/22/70/c10c77cd77970ac965734fe3419f2c98665f6e982744a9bfb0e749d298f4/coverage-7.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:581a40c7b94921fffd6457ffe532259813fc68eb2bdda60fa8cc343414ce3733", size = 244633, upload-time = "2025-03-30T20:35:56.221Z" }, + { url = "https://files.pythonhosted.org/packages/38/5a/4f7569d946a07c952688debee18c2bb9ab24f88027e3d71fd25dbc2f9dca/coverage-7.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f319bae0321bc838e205bf9e5bc28f0a3165f30c203b610f17ab5552cff90323", size = 242712, upload-time = "2025-03-30T20:35:57.801Z" }, + { url = "https://files.pythonhosted.org/packages/bb/a1/03a43b33f50475a632a91ea8c127f7e35e53786dbe6781c25f19fd5a65f8/coverage-7.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04bfec25a8ef1c5f41f5e7e5c842f6b615599ca8ba8391ec33a9290d9d2db3a3", size = 244000, upload-time = "2025-03-30T20:35:59.378Z" }, + { url = "https://files.pythonhosted.org/packages/6a/89/ab6c43b1788a3128e4d1b7b54214548dcad75a621f9d277b14d16a80d8a1/coverage-7.8.0-cp313-cp313-win32.whl", hash = "sha256:dd19608788b50eed889e13a5d71d832edc34fc9dfce606f66e8f9f917eef910d", size = 214195, upload-time = "2025-03-30T20:36:01.005Z" }, + { url = "https://files.pythonhosted.org/packages/12/12/6bf5f9a8b063d116bac536a7fb594fc35cb04981654cccb4bbfea5dcdfa0/coverage-7.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:a9abbccd778d98e9c7e85038e35e91e67f5b520776781d9a1e2ee9d400869487", size = 214998, upload-time = "2025-03-30T20:36:03.006Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e6/1e9df74ef7a1c983a9c7443dac8aac37a46f1939ae3499424622e72a6f78/coverage-7.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:18c5ae6d061ad5b3e7eef4363fb27a0576012a7447af48be6c75b88494c6cf25", size = 212541, upload-time = "2025-03-30T20:36:04.638Z" }, + { url = "https://files.pythonhosted.org/packages/04/51/c32174edb7ee49744e2e81c4b1414ac9df3dacfcb5b5f273b7f285ad43f6/coverage-7.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:95aa6ae391a22bbbce1b77ddac846c98c5473de0372ba5c463480043a07bff42", size = 212767, upload-time = "2025-03-30T20:36:06.503Z" }, + { url = "https://files.pythonhosted.org/packages/e9/8f/f454cbdb5212f13f29d4a7983db69169f1937e869a5142bce983ded52162/coverage-7.8.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e013b07ba1c748dacc2a80e69a46286ff145935f260eb8c72df7185bf048f502", size = 256997, upload-time = "2025-03-30T20:36:08.137Z" }, + { url = "https://files.pythonhosted.org/packages/e6/74/2bf9e78b321216d6ee90a81e5c22f912fc428442c830c4077b4a071db66f/coverage-7.8.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d766a4f0e5aa1ba056ec3496243150698dc0481902e2b8559314368717be82b1", size = 252708, upload-time = "2025-03-30T20:36:09.781Z" }, + { url = "https://files.pythonhosted.org/packages/92/4d/50d7eb1e9a6062bee6e2f92e78b0998848a972e9afad349b6cdde6fa9e32/coverage-7.8.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad80e6b4a0c3cb6f10f29ae4c60e991f424e6b14219d46f1e7d442b938ee68a4", size = 255046, upload-time = "2025-03-30T20:36:11.409Z" }, + { url = "https://files.pythonhosted.org/packages/40/9e/71fb4e7402a07c4198ab44fc564d09d7d0ffca46a9fb7b0a7b929e7641bd/coverage-7.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b87eb6fc9e1bb8f98892a2458781348fa37e6925f35bb6ceb9d4afd54ba36c73", size = 256139, upload-time = "2025-03-30T20:36:13.86Z" }, + { url = "https://files.pythonhosted.org/packages/49/1a/78d37f7a42b5beff027e807c2843185961fdae7fe23aad5a4837c93f9d25/coverage-7.8.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d1ba00ae33be84066cfbe7361d4e04dec78445b2b88bdb734d0d1cbab916025a", size = 254307, upload-time = "2025-03-30T20:36:16.074Z" }, + { url = "https://files.pythonhosted.org/packages/58/e9/8fb8e0ff6bef5e170ee19d59ca694f9001b2ec085dc99b4f65c128bb3f9a/coverage-7.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f3c38e4e5ccbdc9198aecc766cedbb134b2d89bf64533973678dfcf07effd883", size = 255116, upload-time = "2025-03-30T20:36:18.033Z" }, + { url = "https://files.pythonhosted.org/packages/56/b0/d968ecdbe6fe0a863de7169bbe9e8a476868959f3af24981f6a10d2b6924/coverage-7.8.0-cp313-cp313t-win32.whl", hash = "sha256:379fe315e206b14e21db5240f89dc0774bdd3e25c3c58c2c733c99eca96f1ada", size = 214909, upload-time = "2025-03-30T20:36:19.644Z" }, + { url = "https://files.pythonhosted.org/packages/87/e9/d6b7ef9fecf42dfb418d93544af47c940aa83056c49e6021a564aafbc91f/coverage-7.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2e4b6b87bb0c846a9315e3ab4be2d52fac905100565f4b92f02c445c8799e257", size = 216068, upload-time = "2025-03-30T20:36:21.282Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f1/1da77bb4c920aa30e82fa9b6ea065da3467977c2e5e032e38e66f1c57ffd/coverage-7.8.0-pp39.pp310.pp311-none-any.whl", hash = "sha256:b8194fb8e50d556d5849753de991d390c5a1edeeba50f68e3a9253fbd8bf8ccd", size = 203443, upload-time = "2025-03-30T20:36:41.959Z" }, + { url = "https://files.pythonhosted.org/packages/59/f1/4da7717f0063a222db253e7121bd6a56f6fb1ba439dcc36659088793347c/coverage-7.8.0-py3-none-any.whl", hash = "sha256:dbf364b4c5e7bae9250528167dfe40219b62e2d573c854d74be213e1e52069f7", size = 203435, upload-time = "2025-03-30T20:36:43.61Z" }, ] [package.optional-dependencies] @@ -164,9 +164,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "python-dateutil" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d9/29/d40217cbe2f6b1359e00c6c307bb3fc876ba74068cbab3dde77f03ca0dc4/ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343", size = 10943 } +sdist = { url = "https://files.pythonhosted.org/packages/d9/29/d40217cbe2f6b1359e00c6c307bb3fc876ba74068cbab3dde77f03ca0dc4/ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343", size = 10943, upload-time = "2022-05-02T15:47:16.11Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034 }, + { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034, upload-time = "2022-05-02T15:47:14.552Z" }, ] [[package]] @@ -176,9 +176,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "smmap" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/72/94/63b0fc47eb32792c7ba1fe1b694daec9a63620db1e313033d18140c2320a/gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571", size = 394684 } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/63b0fc47eb32792c7ba1fe1b694daec9a63620db1e313033d18140c2320a/gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571", size = 394684, upload-time = "2025-01-02T07:20:46.413Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf", size = 62794 }, + { url = "https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf", size = 62794, upload-time = "2025-01-02T07:20:43.624Z" }, ] [[package]] @@ -188,9 +188,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "gitdb" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c0/89/37df0b71473153574a5cdef8f242de422a0f5d26d7a9e231e6f169b4ad14/gitpython-3.1.44.tar.gz", hash = "sha256:c87e30b26253bf5418b01b0660f818967f3c503193838337fe5e573331249269", size = 214196 } +sdist = { url = "https://files.pythonhosted.org/packages/c0/89/37df0b71473153574a5cdef8f242de422a0f5d26d7a9e231e6f169b4ad14/gitpython-3.1.44.tar.gz", hash = "sha256:c87e30b26253bf5418b01b0660f818967f3c503193838337fe5e573331249269", size = 214196, upload-time = "2025-01-02T07:32:43.59Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/9a/4114a9057db2f1462d5c8f8390ab7383925fe1ac012eaa42402ad65c2963/GitPython-3.1.44-py3-none-any.whl", hash = "sha256:9e0e10cda9bed1ee64bc9a6de50e7e38a9c9943241cd7f585f6df3ed28011110", size = 207599 }, + { url = "https://files.pythonhosted.org/packages/1d/9a/4114a9057db2f1462d5c8f8390ab7383925fe1ac012eaa42402ad65c2963/GitPython-3.1.44-py3-none-any.whl", hash = "sha256:9e0e10cda9bed1ee64bc9a6de50e7e38a9c9943241cd7f585f6df3ed28011110", size = 207599, upload-time = "2025-01-02T07:32:40.731Z" }, ] [[package]] @@ -200,27 +200,27 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6a/ba/1ebe51a22c491a3fc94b44ef9c46a5b5472540e24a5c3f251cebbab7214b/griffe-1.6.1.tar.gz", hash = "sha256:ff0acf706b2680f8c721412623091c891e752b2c61b7037618f7b77d06732cf5", size = 393112 } +sdist = { url = "https://files.pythonhosted.org/packages/6a/ba/1ebe51a22c491a3fc94b44ef9c46a5b5472540e24a5c3f251cebbab7214b/griffe-1.6.1.tar.gz", hash = "sha256:ff0acf706b2680f8c721412623091c891e752b2c61b7037618f7b77d06732cf5", size = 393112, upload-time = "2025-03-18T15:18:45.489Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/d3/a760d1062e44587230aa65573c70edaad4ee8a0e60e193a3172b304d24d8/griffe-1.6.1-py3-none-any.whl", hash = "sha256:b0131670db16834f82383bcf4f788778853c9bf4dc7a1a2b708bb0808ca56a98", size = 128615 }, + { url = "https://files.pythonhosted.org/packages/1f/d3/a760d1062e44587230aa65573c70edaad4ee8a0e60e193a3172b304d24d8/griffe-1.6.1-py3-none-any.whl", hash = "sha256:b0131670db16834f82383bcf4f788778853c9bf4dc7a1a2b708bb0808ca56a98", size = 128615, upload-time = "2025-03-18T15:18:43.57Z" }, ] [[package]] name = "idna" version = "3.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, ] [[package]] name = "iniconfig" version = "2.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } +sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646, upload-time = "2023-01-07T11:08:11.254Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, + { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892, upload-time = "2023-01-07T11:08:09.864Z" }, ] [[package]] @@ -230,75 +230,75 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115 } +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 }, + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, ] [[package]] name = "markdown" version = "3.7" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/54/28/3af612670f82f4c056911fbbbb42760255801b3068c48de792d354ff4472/markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2", size = 357086 } +sdist = { url = "https://files.pythonhosted.org/packages/54/28/3af612670f82f4c056911fbbbb42760255801b3068c48de792d354ff4472/markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2", size = 357086, upload-time = "2024-08-16T15:55:17.812Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/08/83871f3c50fc983b88547c196d11cf8c3340e37c32d2e9d6152abe2c61f7/Markdown-3.7-py3-none-any.whl", hash = "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803", size = 106349 }, + { url = "https://files.pythonhosted.org/packages/3f/08/83871f3c50fc983b88547c196d11cf8c3340e37c32d2e9d6152abe2c61f7/Markdown-3.7-py3-none-any.whl", hash = "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803", size = 106349, upload-time = "2024-08-16T15:55:16.176Z" }, ] [[package]] name = "markupsafe" version = "3.0.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353 }, - { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392 }, - { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984 }, - { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120 }, - { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032 }, - { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057 }, - { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359 }, - { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306 }, - { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094 }, - { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521 }, - { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274 }, - { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348 }, - { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149 }, - { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118 }, - { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993 }, - { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178 }, - { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319 }, - { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352 }, - { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097 }, - { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601 }, - { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 }, - { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 }, - { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 }, - { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 }, - { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 }, - { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 }, - { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 }, - { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 }, - { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 }, - { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 }, - { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 }, - { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 }, - { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 }, - { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 }, - { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 }, - { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 }, - { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 }, - { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 }, - { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 }, - { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353, upload-time = "2024-10-18T15:21:02.187Z" }, + { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392, upload-time = "2024-10-18T15:21:02.941Z" }, + { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984, upload-time = "2024-10-18T15:21:03.953Z" }, + { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120, upload-time = "2024-10-18T15:21:06.495Z" }, + { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032, upload-time = "2024-10-18T15:21:07.295Z" }, + { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057, upload-time = "2024-10-18T15:21:08.073Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359, upload-time = "2024-10-18T15:21:09.318Z" }, + { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306, upload-time = "2024-10-18T15:21:10.185Z" }, + { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094, upload-time = "2024-10-18T15:21:11.005Z" }, + { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521, upload-time = "2024-10-18T15:21:12.911Z" }, + { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload-time = "2024-10-18T15:21:13.777Z" }, + { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload-time = "2024-10-18T15:21:14.822Z" }, + { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload-time = "2024-10-18T15:21:15.642Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118, upload-time = "2024-10-18T15:21:17.133Z" }, + { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993, upload-time = "2024-10-18T15:21:18.064Z" }, + { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178, upload-time = "2024-10-18T15:21:18.859Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319, upload-time = "2024-10-18T15:21:19.671Z" }, + { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload-time = "2024-10-18T15:21:20.971Z" }, + { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload-time = "2024-10-18T15:21:22.646Z" }, + { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload-time = "2024-10-18T15:21:23.499Z" }, + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" }, ] [[package]] name = "mergedeep" version = "1.3.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3a/41/580bb4006e3ed0361b8151a01d324fb03f420815446c7def45d02f74c270/mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8", size = 4661 } +sdist = { url = "https://files.pythonhosted.org/packages/3a/41/580bb4006e3ed0361b8151a01d324fb03f420815446c7def45d02f74c270/mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8", size = 4661, upload-time = "2021-02-05T18:55:30.623Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307", size = 6354 }, + { url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307", size = 6354, upload-time = "2021-02-05T18:55:29.583Z" }, ] [[package]] @@ -320,9 +320,9 @@ dependencies = [ { name = "pyyaml-env-tag" }, { name = "watchdog" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bc/c6/bbd4f061bd16b378247f12953ffcb04786a618ce5e904b8c5a01a0309061/mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2", size = 3889159 } +sdist = { url = "https://files.pythonhosted.org/packages/bc/c6/bbd4f061bd16b378247f12953ffcb04786a618ce5e904b8c5a01a0309061/mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2", size = 3889159, upload-time = "2024-08-30T12:24:06.899Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e", size = 3864451 }, + { url = "https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e", size = 3864451, upload-time = "2024-08-30T12:24:05.054Z" }, ] [[package]] @@ -334,9 +334,9 @@ dependencies = [ { name = "markupsafe" }, { name = "mkdocs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c2/44/140469d87379c02f1e1870315f3143718036a983dd0416650827b8883192/mkdocs_autorefs-1.4.1.tar.gz", hash = "sha256:4b5b6235a4becb2b10425c2fa191737e415b37aa3418919db33e5d774c9db079", size = 4131355 } +sdist = { url = "https://files.pythonhosted.org/packages/c2/44/140469d87379c02f1e1870315f3143718036a983dd0416650827b8883192/mkdocs_autorefs-1.4.1.tar.gz", hash = "sha256:4b5b6235a4becb2b10425c2fa191737e415b37aa3418919db33e5d774c9db079", size = 4131355, upload-time = "2025-03-08T13:35:21.232Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/29/1125f7b11db63e8e32bcfa0752a4eea30abff3ebd0796f808e14571ddaa2/mkdocs_autorefs-1.4.1-py3-none-any.whl", hash = "sha256:9793c5ac06a6ebbe52ec0f8439256e66187badf4b5334b5fde0b128ec134df4f", size = 5782047 }, + { url = "https://files.pythonhosted.org/packages/f8/29/1125f7b11db63e8e32bcfa0752a4eea30abff3ebd0796f808e14571ddaa2/mkdocs_autorefs-1.4.1-py3-none-any.whl", hash = "sha256:9793c5ac06a6ebbe52ec0f8439256e66187badf4b5334b5fde0b128ec134df4f", size = 5782047, upload-time = "2025-03-08T13:35:18.889Z" }, ] [[package]] @@ -347,9 +347,9 @@ dependencies = [ { name = "mkdocs" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1b/b5/f72df157abc7f85e33ffa417464e9dd535ef5fda7654eda41190047a53b6/mkdocs-codeinclude-plugin-0.2.1.tar.gz", hash = "sha256:305387f67a885f0e36ec1cf977324fe1fe50d31301147194b63631d0864601b1", size = 8140 } +sdist = { url = "https://files.pythonhosted.org/packages/1b/b5/f72df157abc7f85e33ffa417464e9dd535ef5fda7654eda41190047a53b6/mkdocs-codeinclude-plugin-0.2.1.tar.gz", hash = "sha256:305387f67a885f0e36ec1cf977324fe1fe50d31301147194b63631d0864601b1", size = 8140, upload-time = "2023-03-01T19:57:06.724Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/7b/60573ebf2a22b144eeaf3b29db9a6d4d342d68273f716ea2723d1ad723ba/mkdocs_codeinclude_plugin-0.2.1-py3-none-any.whl", hash = "sha256:172a917c9b257fa62850b669336151f85d3cd40312b2b52520cbcceab557ea6c", size = 8093 }, + { url = "https://files.pythonhosted.org/packages/4d/7b/60573ebf2a22b144eeaf3b29db9a6d4d342d68273f716ea2723d1ad723ba/mkdocs_codeinclude_plugin-0.2.1-py3-none-any.whl", hash = "sha256:172a917c9b257fa62850b669336151f85d3cd40312b2b52520cbcceab557ea6c", size = 8093, upload-time = "2023-03-01T19:57:05.207Z" }, ] [[package]] @@ -361,9 +361,9 @@ dependencies = [ { name = "platformdirs" }, { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/98/f5/ed29cd50067784976f25ed0ed6fcd3c2ce9eb90650aa3b2796ddf7b6870b/mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c", size = 10239 } +sdist = { url = "https://files.pythonhosted.org/packages/98/f5/ed29cd50067784976f25ed0ed6fcd3c2ce9eb90650aa3b2796ddf7b6870b/mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c", size = 10239, upload-time = "2023-11-20T17:51:09.981Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134", size = 9521 }, + { url = "https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134", size = 9521, upload-time = "2023-11-20T17:51:08.587Z" }, ] [[package]] @@ -373,9 +373,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mkdocs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/87/9a/063c4a3688e4669eb2054e4bf6e9cc582f6c1d85674e3f5b836ceff97c3b/mkdocs_git_authors_plugin-0.9.4.tar.gz", hash = "sha256:f5cfaf93d08981ce25591bbaf642051ed168c3886bb96ecd2dca53f0ef1973b8", size = 21914 } +sdist = { url = "https://files.pythonhosted.org/packages/87/9a/063c4a3688e4669eb2054e4bf6e9cc582f6c1d85674e3f5b836ceff97c3b/mkdocs_git_authors_plugin-0.9.4.tar.gz", hash = "sha256:f5cfaf93d08981ce25591bbaf642051ed168c3886bb96ecd2dca53f0ef1973b8", size = 21914, upload-time = "2025-03-14T19:26:40.873Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/ac/2b5bae4047276fda2bdd14a6d4af59288fb4d5de54151ae4e6ba17611ceb/mkdocs_git_authors_plugin-0.9.4-py3-none-any.whl", hash = "sha256:84b9b56c703841189c64d8ff6947034fe0a9c14a0a8f1f6255edfcfe3a56825f", size = 20752 }, + { url = "https://files.pythonhosted.org/packages/5c/ac/2b5bae4047276fda2bdd14a6d4af59288fb4d5de54151ae4e6ba17611ceb/mkdocs_git_authors_plugin-0.9.4-py3-none-any.whl", hash = "sha256:84b9b56c703841189c64d8ff6947034fe0a9c14a0a8f1f6255edfcfe3a56825f", size = 20752, upload-time = "2025-03-14T19:26:39.398Z" }, ] [[package]] @@ -387,9 +387,9 @@ dependencies = [ { name = "mkdocs" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b4/8a/4ca4fb7d17f66fa709b49744c597204ad03fb3b011c76919564843426f11/mkdocs_git_committers_plugin_2-2.5.0.tar.gz", hash = "sha256:a01f17369e79ca28651681cddf212770e646e6191954bad884ca3067316aae60", size = 15183 } +sdist = { url = "https://files.pythonhosted.org/packages/b4/8a/4ca4fb7d17f66fa709b49744c597204ad03fb3b011c76919564843426f11/mkdocs_git_committers_plugin_2-2.5.0.tar.gz", hash = "sha256:a01f17369e79ca28651681cddf212770e646e6191954bad884ca3067316aae60", size = 15183, upload-time = "2025-01-30T07:30:48.667Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8e/f5/768590251839a148c188d64779b809bde0e78a306295c18fc29d7fc71ce1/mkdocs_git_committers_plugin_2-2.5.0-py3-none-any.whl", hash = "sha256:1778becf98ccdc5fac809ac7b62cf01d3c67d6e8432723dffbb823307d1193c4", size = 11788 }, + { url = "https://files.pythonhosted.org/packages/8e/f5/768590251839a148c188d64779b809bde0e78a306295c18fc29d7fc71ce1/mkdocs_git_committers_plugin_2-2.5.0-py3-none-any.whl", hash = "sha256:1778becf98ccdc5fac809ac7b62cf01d3c67d6e8432723dffbb823307d1193c4", size = 11788, upload-time = "2025-01-30T07:30:45.748Z" }, ] [[package]] @@ -409,18 +409,31 @@ dependencies = [ { name = "pymdown-extensions" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/11/cb/6dd3b6a7925429c0229738098ee874dbf7fa02db55558adb2c5bf86077b2/mkdocs_material-9.6.9.tar.gz", hash = "sha256:a4872139715a1f27b2aa3f3dc31a9794b7bbf36333c0ba4607cf04786c94f89c", size = 3948083 } +sdist = { url = "https://files.pythonhosted.org/packages/11/cb/6dd3b6a7925429c0229738098ee874dbf7fa02db55558adb2c5bf86077b2/mkdocs_material-9.6.9.tar.gz", hash = "sha256:a4872139715a1f27b2aa3f3dc31a9794b7bbf36333c0ba4607cf04786c94f89c", size = 3948083, upload-time = "2025-03-17T09:28:50.879Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/db/7c/ea5a671b2ff5d0e3f3108a7f7d75b541d683e4969aaead2a8f3e59e0fc27/mkdocs_material-9.6.9-py3-none-any.whl", hash = "sha256:6e61b7fb623ce2aa4622056592b155a9eea56ff3487d0835075360be45a4c8d1", size = 8697935 }, + { url = "https://files.pythonhosted.org/packages/db/7c/ea5a671b2ff5d0e3f3108a7f7d75b541d683e4969aaead2a8f3e59e0fc27/mkdocs_material-9.6.9-py3-none-any.whl", hash = "sha256:6e61b7fb623ce2aa4622056592b155a9eea56ff3487d0835075360be45a4c8d1", size = 8697935, upload-time = "2025-03-17T09:28:47.481Z" }, ] [[package]] name = "mkdocs-material-extensions" version = "1.3.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/79/9b/9b4c96d6593b2a541e1cb8b34899a6d021d208bb357042823d4d2cabdbe7/mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443", size = 11847 } +sdist = { url = "https://files.pythonhosted.org/packages/79/9b/9b4c96d6593b2a541e1cb8b34899a6d021d208bb357042823d4d2cabdbe7/mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443", size = 11847, upload-time = "2023-11-22T19:09:45.208Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31", size = 8728 }, + { url = "https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31", size = 8728, upload-time = "2023-11-22T19:09:43.465Z" }, +] + +[[package]] +name = "mkdocs-monorepo-plugin" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mkdocs" }, + { name = "python-slugify" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f7/6c/5b2a34fd63fe20724e2edf1879e977b40453efe40e1c385a05f38b420664/mkdocs-monorepo-plugin-1.1.0.tar.gz", hash = "sha256:ccc566e166aac5ae7fade498c15c4a337a4892d238629b51aba8ef3fc7099034", size = 13435, upload-time = "2024-01-04T14:29:15.892Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/33/4cc6c70223aee511244f8fe7706df70d1cd253d1446ab466c73f9dfbaab5/mkdocs_monorepo_plugin-1.1.0-py3-none-any.whl", hash = "sha256:7bbfd9756a7fdecf64d6105dad96cce7e7bb5f0d6cfc2bfda31a1919c77cc3b9", size = 14312, upload-time = "2024-01-04T14:29:14.09Z" }, ] [[package]] @@ -435,9 +448,9 @@ dependencies = [ { name = "mkdocs-autorefs" }, { name = "pymdown-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8e/4d/a9484dc5d926295bdf308f1f6c4f07fcc99735b970591edc414d401fcc91/mkdocstrings-0.29.0.tar.gz", hash = "sha256:3657be1384543ce0ee82112c3e521bbf48e41303aa0c229b9ffcccba057d922e", size = 1212185 } +sdist = { url = "https://files.pythonhosted.org/packages/8e/4d/a9484dc5d926295bdf308f1f6c4f07fcc99735b970591edc414d401fcc91/mkdocstrings-0.29.0.tar.gz", hash = "sha256:3657be1384543ce0ee82112c3e521bbf48e41303aa0c229b9ffcccba057d922e", size = 1212185, upload-time = "2025-03-10T13:10:11.445Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/15/47/eb876dfd84e48f31ff60897d161b309cf6a04ca270155b0662aae562b3fb/mkdocstrings-0.29.0-py3-none-any.whl", hash = "sha256:8ea98358d2006f60befa940fdebbbc88a26b37ecbcded10be726ba359284f73d", size = 1630824 }, + { url = "https://files.pythonhosted.org/packages/15/47/eb876dfd84e48f31ff60897d161b309cf6a04ca270155b0662aae562b3fb/mkdocstrings-0.29.0-py3-none-any.whl", hash = "sha256:8ea98358d2006f60befa940fdebbbc88a26b37ecbcded10be726ba359284f73d", size = 1630824, upload-time = "2025-03-10T13:10:09.712Z" }, ] [[package]] @@ -449,63 +462,63 @@ dependencies = [ { name = "mkdocs-autorefs" }, { name = "mkdocstrings" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8e/e7/0691e34e807a8f5c28f0988fcfeeb584f0b569ce433bf341944f14bdb3ff/mkdocstrings_python-1.16.6.tar.gz", hash = "sha256:cefe0f0e17ab4a4611f01b0a2af75e4298664e0ff54feb83c91a485bfed82dc9", size = 201565 } +sdist = { url = "https://files.pythonhosted.org/packages/8e/e7/0691e34e807a8f5c28f0988fcfeeb584f0b569ce433bf341944f14bdb3ff/mkdocstrings_python-1.16.6.tar.gz", hash = "sha256:cefe0f0e17ab4a4611f01b0a2af75e4298664e0ff54feb83c91a485bfed82dc9", size = 201565, upload-time = "2025-03-18T15:34:24.371Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/42/ed682687ef5f248e104f82806d5d9893f6dd81d8cb4561692e190ba1a252/mkdocstrings_python-1.16.6-py3-none-any.whl", hash = "sha256:de877dd71f69878c973c4897a39683b7b6961bee7b058879095b69681488453f", size = 123207 }, + { url = "https://files.pythonhosted.org/packages/6a/42/ed682687ef5f248e104f82806d5d9893f6dd81d8cb4561692e190ba1a252/mkdocstrings_python-1.16.6-py3-none-any.whl", hash = "sha256:de877dd71f69878c973c4897a39683b7b6961bee7b058879095b69681488453f", size = 123207, upload-time = "2025-03-18T15:34:21.357Z" }, ] [[package]] name = "packaging" version = "24.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } +sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950, upload-time = "2024-11-08T09:47:47.202Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, + { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451, upload-time = "2024-11-08T09:47:44.722Z" }, ] [[package]] name = "paginate" version = "0.5.7" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ec/46/68dde5b6bc00c1296ec6466ab27dddede6aec9af1b99090e1107091b3b84/paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945", size = 19252 } +sdist = { url = "https://files.pythonhosted.org/packages/ec/46/68dde5b6bc00c1296ec6466ab27dddede6aec9af1b99090e1107091b3b84/paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945", size = 19252, upload-time = "2024-08-25T14:17:24.139Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591", size = 13746 }, + { url = "https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591", size = 13746, upload-time = "2024-08-25T14:17:22.55Z" }, ] [[package]] name = "pathspec" version = "0.12.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 }, + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, ] [[package]] name = "platformdirs" version = "4.3.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/13/fc/128cc9cb8f03208bdbf93d3aa862e16d376844a14f9a0ce5cf4507372de4/platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", size = 21302 } +sdist = { url = "https://files.pythonhosted.org/packages/13/fc/128cc9cb8f03208bdbf93d3aa862e16d376844a14f9a0ce5cf4507372de4/platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", size = 21302, upload-time = "2024-09-17T19:06:50.688Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439 }, + { url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439, upload-time = "2024-09-17T19:06:49.212Z" }, ] [[package]] name = "pluggy" version = "1.5.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955, upload-time = "2024-04-20T21:34:42.531Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556, upload-time = "2024-04-20T21:34:40.434Z" }, ] [[package]] name = "pygments" version = "2.19.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581 } +sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581, upload-time = "2025-01-06T17:26:30.443Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 }, + { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" }, ] [[package]] @@ -523,6 +536,7 @@ dev = [ { name = "mkdocs-git-authors-plugin" }, { name = "mkdocs-git-committers-plugin-2" }, { name = "mkdocs-material" }, + { name = "mkdocs-monorepo-plugin" }, { name = "mkdocstrings-python" }, { name = "pytest" }, { name = "pytest-cov" }, @@ -539,6 +553,7 @@ dev = [ { name = "mkdocs-git-authors-plugin", specifier = ">=0.9.4" }, { name = "mkdocs-git-committers-plugin-2", specifier = ">=2.5.0" }, { name = "mkdocs-material", specifier = ">=9.6.9" }, + { name = "mkdocs-monorepo-plugin", specifier = ">=1.1.0" }, { name = "mkdocstrings-python", specifier = ">=1.16.6" }, { name = "pytest", specifier = ">=8.3.5" }, { name = "pytest-cov", specifier = ">=6.1.1" }, @@ -553,18 +568,18 @@ dependencies = [ { name = "markdown" }, { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7c/44/e6de2fdc880ad0ec7547ca2e087212be815efbc9a425a8d5ba9ede602cbb/pymdown_extensions-10.14.3.tar.gz", hash = "sha256:41e576ce3f5d650be59e900e4ceff231e0aed2a88cf30acaee41e02f063a061b", size = 846846 } +sdist = { url = "https://files.pythonhosted.org/packages/7c/44/e6de2fdc880ad0ec7547ca2e087212be815efbc9a425a8d5ba9ede602cbb/pymdown_extensions-10.14.3.tar.gz", hash = "sha256:41e576ce3f5d650be59e900e4ceff231e0aed2a88cf30acaee41e02f063a061b", size = 846846, upload-time = "2025-02-01T15:43:15.42Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/eb/f5/b9e2a42aa8f9e34d52d66de87941ecd236570c7ed2e87775ed23bbe4e224/pymdown_extensions-10.14.3-py3-none-any.whl", hash = "sha256:05e0bee73d64b9c71a4ae17c72abc2f700e8bc8403755a00580b49a4e9f189e9", size = 264467 }, + { url = "https://files.pythonhosted.org/packages/eb/f5/b9e2a42aa8f9e34d52d66de87941ecd236570c7ed2e87775ed23bbe4e224/pymdown_extensions-10.14.3-py3-none-any.whl", hash = "sha256:05e0bee73d64b9c71a4ae17c72abc2f700e8bc8403755a00580b49a4e9f189e9", size = 264467, upload-time = "2025-02-01T15:43:13.995Z" }, ] [[package]] name = "pyserial" version = "3.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1e/7d/ae3f0a63f41e4d2f6cb66a5b57197850f919f59e558159a4dd3a818f5082/pyserial-3.5.tar.gz", hash = "sha256:3c77e014170dfffbd816e6ffc205e9842efb10be9f58ec16d3e8675b4925cddb", size = 159125 } +sdist = { url = "https://files.pythonhosted.org/packages/1e/7d/ae3f0a63f41e4d2f6cb66a5b57197850f919f59e558159a4dd3a818f5082/pyserial-3.5.tar.gz", hash = "sha256:3c77e014170dfffbd816e6ffc205e9842efb10be9f58ec16d3e8675b4925cddb", size = 159125, upload-time = "2020-11-23T03:59:15.045Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/07/bc/587a445451b253b285629263eb51c2d8e9bcea4fc97826266d186f96f558/pyserial-3.5-py2.py3-none-any.whl", hash = "sha256:c4451db6ba391ca6ca299fb3ec7bae67a5c55dde170964c7a14ceefec02f2cf0", size = 90585 }, + { url = "https://files.pythonhosted.org/packages/07/bc/587a445451b253b285629263eb51c2d8e9bcea4fc97826266d186f96f558/pyserial-3.5-py2.py3-none-any.whl", hash = "sha256:c4451db6ba391ca6ca299fb3ec7bae67a5c55dde170964c7a14ceefec02f2cf0", size = 90585, upload-time = "2020-11-23T03:59:13.41Z" }, ] [[package]] @@ -577,9 +592,9 @@ dependencies = [ { name = "packaging" }, { name = "pluggy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891 } +sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload-time = "2025-03-02T12:54:54.503Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 }, + { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload-time = "2025-03-02T12:54:52.069Z" }, ] [[package]] @@ -590,9 +605,9 @@ dependencies = [ { name = "coverage", extra = ["toml"] }, { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/25/69/5f1e57f6c5a39f81411b550027bf72842c4567ff5fd572bed1edc9e4b5d9/pytest_cov-6.1.1.tar.gz", hash = "sha256:46935f7aaefba760e716c2ebfbe1c216240b9592966e7da99ea8292d4d3e2a0a", size = 66857 } +sdist = { url = "https://files.pythonhosted.org/packages/25/69/5f1e57f6c5a39f81411b550027bf72842c4567ff5fd572bed1edc9e4b5d9/pytest_cov-6.1.1.tar.gz", hash = "sha256:46935f7aaefba760e716c2ebfbe1c216240b9592966e7da99ea8292d4d3e2a0a", size = 66857, upload-time = "2025-04-05T14:07:51.592Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/28/d0/def53b4a790cfb21483016430ed828f64830dd981ebe1089971cd10cab25/pytest_cov-6.1.1-py3-none-any.whl", hash = "sha256:bddf29ed2d0ab6f4df17b4c55b0a657287db8684af9c42ea546b21b1041b3dde", size = 23841 }, + { url = "https://files.pythonhosted.org/packages/28/d0/def53b4a790cfb21483016430ed828f64830dd981ebe1089971cd10cab25/pytest_cov-6.1.1-py3-none-any.whl", hash = "sha256:bddf29ed2d0ab6f4df17b4c55b0a657287db8684af9c42ea546b21b1041b3dde", size = 23841, upload-time = "2025-04-05T14:07:49.641Z" }, ] [[package]] @@ -602,44 +617,56 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "six" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 } +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-slugify" +version = "8.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "text-unidecode" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/87/c7/5e1547c44e31da50a460df93af11a535ace568ef89d7a811069ead340c4a/python-slugify-8.0.4.tar.gz", hash = "sha256:59202371d1d05b54a9e7720c5e038f928f45daaffe41dd10822f3907b937c856", size = 10921, upload-time = "2024-02-08T18:32:45.488Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, + { url = "https://files.pythonhosted.org/packages/a4/62/02da182e544a51a5c3ccf4b03ab79df279f9c60c5e82d5e8bec7ca26ac11/python_slugify-8.0.4-py2.py3-none-any.whl", hash = "sha256:276540b79961052b66b7d116620b36518847f52d5fd9e3a70164fc8c50faa6b8", size = 10051, upload-time = "2024-02-08T18:32:43.911Z" }, ] [[package]] name = "pyyaml" version = "6.0.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612 }, - { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040 }, - { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829 }, - { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167 }, - { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952 }, - { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301 }, - { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638 }, - { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850 }, - { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980 }, - { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, - { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, - { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, - { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, - { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, - { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, - { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, - { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, - { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, - { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, - { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, - { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, - { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, - { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, - { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, - { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, - { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, - { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612, upload-time = "2024-08-06T20:32:03.408Z" }, + { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040, upload-time = "2024-08-06T20:32:04.926Z" }, + { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829, upload-time = "2024-08-06T20:32:06.459Z" }, + { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167, upload-time = "2024-08-06T20:32:08.338Z" }, + { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952, upload-time = "2024-08-06T20:32:14.124Z" }, + { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301, upload-time = "2024-08-06T20:32:16.17Z" }, + { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638, upload-time = "2024-08-06T20:32:18.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850, upload-time = "2024-08-06T20:32:19.889Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980, upload-time = "2024-08-06T20:32:21.273Z" }, + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873, upload-time = "2024-08-06T20:32:25.131Z" }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302, upload-time = "2024-08-06T20:32:26.511Z" }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154, upload-time = "2024-08-06T20:32:28.363Z" }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223, upload-time = "2024-08-06T20:32:30.058Z" }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542, upload-time = "2024-08-06T20:32:31.881Z" }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164, upload-time = "2024-08-06T20:32:37.083Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611, upload-time = "2024-08-06T20:32:38.898Z" }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload-time = "2024-08-06T20:32:40.241Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload-time = "2024-08-06T20:32:41.93Z" }, + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, ] [[package]] @@ -649,9 +676,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fb/8e/da1c6c58f751b70f8ceb1eb25bc25d524e8f14fe16edcce3f4e3ba08629c/pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb", size = 5631 } +sdist = { url = "https://files.pythonhosted.org/packages/fb/8e/da1c6c58f751b70f8ceb1eb25bc25d524e8f14fe16edcce3f4e3ba08629c/pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb", size = 5631, upload-time = "2020-11-12T02:38:26.239Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/66/bbb1dd374f5c870f59c5bb1db0e18cbe7fa739415a24cbd95b2d1f5ae0c4/pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069", size = 3911 }, + { url = "https://files.pythonhosted.org/packages/5a/66/bbb1dd374f5c870f59c5bb1db0e18cbe7fa739415a24cbd95b2d1f5ae0c4/pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069", size = 3911, upload-time = "2020-11-12T02:38:24.638Z" }, ] [[package]] @@ -664,125 +691,134 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } +sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218, upload-time = "2024-05-29T15:37:49.536Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, + { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload-time = "2024-05-29T15:37:47.027Z" }, ] [[package]] name = "ruff" version = "0.11.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/77/2b/7ca27e854d92df5e681e6527dc0f9254c9dc06c8408317893cf96c851cdd/ruff-0.11.0.tar.gz", hash = "sha256:e55c620690a4a7ee6f1cccb256ec2157dc597d109400ae75bbf944fc9d6462e2", size = 3799407 } +sdist = { url = "https://files.pythonhosted.org/packages/77/2b/7ca27e854d92df5e681e6527dc0f9254c9dc06c8408317893cf96c851cdd/ruff-0.11.0.tar.gz", hash = "sha256:e55c620690a4a7ee6f1cccb256ec2157dc597d109400ae75bbf944fc9d6462e2", size = 3799407, upload-time = "2025-03-14T13:52:36.539Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/48/40/3d0340a9e5edc77d37852c0cd98c5985a5a8081fc3befaeb2ae90aaafd2b/ruff-0.11.0-py3-none-linux_armv6l.whl", hash = "sha256:dc67e32bc3b29557513eb7eeabb23efdb25753684b913bebb8a0c62495095acb", size = 10098158 }, - { url = "https://files.pythonhosted.org/packages/ec/a9/d8f5abb3b87b973b007649ac7bf63665a05b2ae2b2af39217b09f52abbbf/ruff-0.11.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:38c23fd9bdec4eb437b4c1e3595905a0a8edfccd63a790f818b28c78fe345639", size = 10879071 }, - { url = "https://files.pythonhosted.org/packages/ab/62/aaa198614c6211677913ec480415c5e6509586d7b796356cec73a2f8a3e6/ruff-0.11.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7c8661b0be91a38bd56db593e9331beaf9064a79028adee2d5f392674bbc5e88", size = 10247944 }, - { url = "https://files.pythonhosted.org/packages/9f/52/59e0a9f2cf1ce5e6cbe336b6dd0144725c8ea3b97cac60688f4e7880bf13/ruff-0.11.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b6c0e8d3d2db7e9f6efd884f44b8dc542d5b6b590fc4bb334fdbc624d93a29a2", size = 10421725 }, - { url = "https://files.pythonhosted.org/packages/a6/c3/dcd71acc6dff72ce66d13f4be5bca1dbed4db678dff2f0f6f307b04e5c02/ruff-0.11.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c3156d3f4b42e57247275a0a7e15a851c165a4fc89c5e8fa30ea6da4f7407b8", size = 9954435 }, - { url = "https://files.pythonhosted.org/packages/a6/9a/342d336c7c52dbd136dee97d4c7797e66c3f92df804f8f3b30da59b92e9c/ruff-0.11.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:490b1e147c1260545f6d041c4092483e3f6d8eba81dc2875eaebcf9140b53905", size = 11492664 }, - { url = "https://files.pythonhosted.org/packages/84/35/6e7defd2d7ca95cc385ac1bd9f7f2e4a61b9cc35d60a263aebc8e590c462/ruff-0.11.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1bc09a7419e09662983b1312f6fa5dab829d6ab5d11f18c3760be7ca521c9329", size = 12207856 }, - { url = "https://files.pythonhosted.org/packages/22/78/da669c8731bacf40001c880ada6d31bcfb81f89cc996230c3b80d319993e/ruff-0.11.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bcfa478daf61ac8002214eb2ca5f3e9365048506a9d52b11bea3ecea822bb844", size = 11645156 }, - { url = "https://files.pythonhosted.org/packages/ee/47/e27d17d83530a208f4a9ab2e94f758574a04c51e492aa58f91a3ed7cbbcb/ruff-0.11.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6fbb2aed66fe742a6a3a0075ed467a459b7cedc5ae01008340075909d819df1e", size = 13884167 }, - { url = "https://files.pythonhosted.org/packages/9f/5e/42ffbb0a5d4b07bbc642b7d58357b4e19a0f4774275ca6ca7d1f7b5452cd/ruff-0.11.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:92c0c1ff014351c0b0cdfdb1e35fa83b780f1e065667167bb9502d47ca41e6db", size = 11348311 }, - { url = "https://files.pythonhosted.org/packages/c8/51/dc3ce0c5ce1a586727a3444a32f98b83ba99599bb1ebca29d9302886e87f/ruff-0.11.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e4fd5ff5de5f83e0458a138e8a869c7c5e907541aec32b707f57cf9a5e124445", size = 10305039 }, - { url = "https://files.pythonhosted.org/packages/60/e0/475f0c2f26280f46f2d6d1df1ba96b3399e0234cf368cc4c88e6ad10dcd9/ruff-0.11.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:96bc89a5c5fd21a04939773f9e0e276308be0935de06845110f43fd5c2e4ead7", size = 9937939 }, - { url = "https://files.pythonhosted.org/packages/e2/d3/3e61b7fd3e9cdd1e5b8c7ac188bec12975c824e51c5cd3d64caf81b0331e/ruff-0.11.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a9352b9d767889ec5df1483f94870564e8102d4d7e99da52ebf564b882cdc2c7", size = 10923259 }, - { url = "https://files.pythonhosted.org/packages/30/32/cd74149ebb40b62ddd14bd2d1842149aeb7f74191fb0f49bd45c76909ff2/ruff-0.11.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:049a191969a10897fe052ef9cc7491b3ef6de79acd7790af7d7897b7a9bfbcb6", size = 11406212 }, - { url = "https://files.pythonhosted.org/packages/00/ef/033022a6b104be32e899b00de704d7c6d1723a54d4c9e09d147368f14b62/ruff-0.11.0-py3-none-win32.whl", hash = "sha256:3191e9116b6b5bbe187447656f0c8526f0d36b6fd89ad78ccaad6bdc2fad7df2", size = 10310905 }, - { url = "https://files.pythonhosted.org/packages/ed/8a/163f2e78c37757d035bd56cd60c8d96312904ca4a6deeab8442d7b3cbf89/ruff-0.11.0-py3-none-win_amd64.whl", hash = "sha256:c58bfa00e740ca0a6c43d41fb004cd22d165302f360aaa56f7126d544db31a21", size = 11411730 }, - { url = "https://files.pythonhosted.org/packages/4e/f7/096f6efabe69b49d7ca61052fc70289c05d8d35735c137ef5ba5ef423662/ruff-0.11.0-py3-none-win_arm64.whl", hash = "sha256:868364fc23f5aa122b00c6f794211e85f7e78f5dffdf7c590ab90b8c4e69b657", size = 10538956 }, + { url = "https://files.pythonhosted.org/packages/48/40/3d0340a9e5edc77d37852c0cd98c5985a5a8081fc3befaeb2ae90aaafd2b/ruff-0.11.0-py3-none-linux_armv6l.whl", hash = "sha256:dc67e32bc3b29557513eb7eeabb23efdb25753684b913bebb8a0c62495095acb", size = 10098158, upload-time = "2025-03-14T13:51:55.69Z" }, + { url = "https://files.pythonhosted.org/packages/ec/a9/d8f5abb3b87b973b007649ac7bf63665a05b2ae2b2af39217b09f52abbbf/ruff-0.11.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:38c23fd9bdec4eb437b4c1e3595905a0a8edfccd63a790f818b28c78fe345639", size = 10879071, upload-time = "2025-03-14T13:51:58.989Z" }, + { url = "https://files.pythonhosted.org/packages/ab/62/aaa198614c6211677913ec480415c5e6509586d7b796356cec73a2f8a3e6/ruff-0.11.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7c8661b0be91a38bd56db593e9331beaf9064a79028adee2d5f392674bbc5e88", size = 10247944, upload-time = "2025-03-14T13:52:02.318Z" }, + { url = "https://files.pythonhosted.org/packages/9f/52/59e0a9f2cf1ce5e6cbe336b6dd0144725c8ea3b97cac60688f4e7880bf13/ruff-0.11.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b6c0e8d3d2db7e9f6efd884f44b8dc542d5b6b590fc4bb334fdbc624d93a29a2", size = 10421725, upload-time = "2025-03-14T13:52:04.303Z" }, + { url = "https://files.pythonhosted.org/packages/a6/c3/dcd71acc6dff72ce66d13f4be5bca1dbed4db678dff2f0f6f307b04e5c02/ruff-0.11.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c3156d3f4b42e57247275a0a7e15a851c165a4fc89c5e8fa30ea6da4f7407b8", size = 9954435, upload-time = "2025-03-14T13:52:06.602Z" }, + { url = "https://files.pythonhosted.org/packages/a6/9a/342d336c7c52dbd136dee97d4c7797e66c3f92df804f8f3b30da59b92e9c/ruff-0.11.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:490b1e147c1260545f6d041c4092483e3f6d8eba81dc2875eaebcf9140b53905", size = 11492664, upload-time = "2025-03-14T13:52:08.613Z" }, + { url = "https://files.pythonhosted.org/packages/84/35/6e7defd2d7ca95cc385ac1bd9f7f2e4a61b9cc35d60a263aebc8e590c462/ruff-0.11.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1bc09a7419e09662983b1312f6fa5dab829d6ab5d11f18c3760be7ca521c9329", size = 12207856, upload-time = "2025-03-14T13:52:11.019Z" }, + { url = "https://files.pythonhosted.org/packages/22/78/da669c8731bacf40001c880ada6d31bcfb81f89cc996230c3b80d319993e/ruff-0.11.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bcfa478daf61ac8002214eb2ca5f3e9365048506a9d52b11bea3ecea822bb844", size = 11645156, upload-time = "2025-03-14T13:52:13.383Z" }, + { url = "https://files.pythonhosted.org/packages/ee/47/e27d17d83530a208f4a9ab2e94f758574a04c51e492aa58f91a3ed7cbbcb/ruff-0.11.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6fbb2aed66fe742a6a3a0075ed467a459b7cedc5ae01008340075909d819df1e", size = 13884167, upload-time = "2025-03-14T13:52:15.528Z" }, + { url = "https://files.pythonhosted.org/packages/9f/5e/42ffbb0a5d4b07bbc642b7d58357b4e19a0f4774275ca6ca7d1f7b5452cd/ruff-0.11.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:92c0c1ff014351c0b0cdfdb1e35fa83b780f1e065667167bb9502d47ca41e6db", size = 11348311, upload-time = "2025-03-14T13:52:18.088Z" }, + { url = "https://files.pythonhosted.org/packages/c8/51/dc3ce0c5ce1a586727a3444a32f98b83ba99599bb1ebca29d9302886e87f/ruff-0.11.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e4fd5ff5de5f83e0458a138e8a869c7c5e907541aec32b707f57cf9a5e124445", size = 10305039, upload-time = "2025-03-14T13:52:20.476Z" }, + { url = "https://files.pythonhosted.org/packages/60/e0/475f0c2f26280f46f2d6d1df1ba96b3399e0234cf368cc4c88e6ad10dcd9/ruff-0.11.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:96bc89a5c5fd21a04939773f9e0e276308be0935de06845110f43fd5c2e4ead7", size = 9937939, upload-time = "2025-03-14T13:52:22.798Z" }, + { url = "https://files.pythonhosted.org/packages/e2/d3/3e61b7fd3e9cdd1e5b8c7ac188bec12975c824e51c5cd3d64caf81b0331e/ruff-0.11.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a9352b9d767889ec5df1483f94870564e8102d4d7e99da52ebf564b882cdc2c7", size = 10923259, upload-time = "2025-03-14T13:52:24.89Z" }, + { url = "https://files.pythonhosted.org/packages/30/32/cd74149ebb40b62ddd14bd2d1842149aeb7f74191fb0f49bd45c76909ff2/ruff-0.11.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:049a191969a10897fe052ef9cc7491b3ef6de79acd7790af7d7897b7a9bfbcb6", size = 11406212, upload-time = "2025-03-14T13:52:27.493Z" }, + { url = "https://files.pythonhosted.org/packages/00/ef/033022a6b104be32e899b00de704d7c6d1723a54d4c9e09d147368f14b62/ruff-0.11.0-py3-none-win32.whl", hash = "sha256:3191e9116b6b5bbe187447656f0c8526f0d36b6fd89ad78ccaad6bdc2fad7df2", size = 10310905, upload-time = "2025-03-14T13:52:30.46Z" }, + { url = "https://files.pythonhosted.org/packages/ed/8a/163f2e78c37757d035bd56cd60c8d96312904ca4a6deeab8442d7b3cbf89/ruff-0.11.0-py3-none-win_amd64.whl", hash = "sha256:c58bfa00e740ca0a6c43d41fb004cd22d165302f360aaa56f7126d544db31a21", size = 11411730, upload-time = "2025-03-14T13:52:32.508Z" }, + { url = "https://files.pythonhosted.org/packages/4e/f7/096f6efabe69b49d7ca61052fc70289c05d8d35735c137ef5ba5ef423662/ruff-0.11.0-py3-none-win_arm64.whl", hash = "sha256:868364fc23f5aa122b00c6f794211e85f7e78f5dffdf7c590ab90b8c4e69b657", size = 10538956, upload-time = "2025-03-14T13:52:34.491Z" }, ] [[package]] name = "six" version = "1.17.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, ] [[package]] name = "smmap" version = "5.0.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/44/cd/a040c4b3119bbe532e5b0732286f805445375489fceaec1f48306068ee3b/smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5", size = 22329 } +sdist = { url = "https://files.pythonhosted.org/packages/44/cd/a040c4b3119bbe532e5b0732286f805445375489fceaec1f48306068ee3b/smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5", size = 22329, upload-time = "2025-01-02T07:14:40.909Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/be/d09147ad1ec7934636ad912901c5fd7667e1c858e19d355237db0d0cd5e4/smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e", size = 24303, upload-time = "2025-01-02T07:14:38.724Z" }, +] + +[[package]] +name = "text-unidecode" +version = "1.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ab/e2/e9a00f0ccb71718418230718b3d900e71a5d16e701a3dae079a21e9cd8f8/text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93", size = 76885, upload-time = "2019-08-30T21:36:45.405Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/be/d09147ad1ec7934636ad912901c5fd7667e1c858e19d355237db0d0cd5e4/smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e", size = 24303 }, + { url = "https://files.pythonhosted.org/packages/a6/a5/c0b6468d3824fe3fde30dbb5e1f687b291608f9473681bbf7dabbf5a87d7/text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8", size = 78154, upload-time = "2019-08-30T21:37:03.543Z" }, ] [[package]] name = "tomli" version = "2.2.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077 }, - { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429 }, - { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067 }, - { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030 }, - { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898 }, - { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894 }, - { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319 }, - { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273 }, - { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310 }, - { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309 }, - { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762 }, - { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453 }, - { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486 }, - { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349 }, - { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159 }, - { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243 }, - { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645 }, - { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584 }, - { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875 }, - { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418 }, - { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708 }, - { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582 }, - { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543 }, - { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691 }, - { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170 }, - { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530 }, - { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666 }, - { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954 }, - { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724 }, - { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383 }, - { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 }, +sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" }, + { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" }, + { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" }, + { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" }, + { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" }, + { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" }, + { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" }, + { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" }, + { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" }, + { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" }, + { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" }, + { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" }, + { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" }, + { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" }, + { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" }, + { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" }, + { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" }, + { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" }, + { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" }, + { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" }, + { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" }, + { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" }, + { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" }, + { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" }, + { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" }, + { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" }, + { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, ] [[package]] name = "urllib3" version = "2.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268 } +sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268, upload-time = "2024-12-22T07:47:30.032Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369 }, + { url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369, upload-time = "2024-12-22T07:47:28.074Z" }, ] [[package]] name = "watchdog" version = "6.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/24/d9be5cd6642a6aa68352ded4b4b10fb0d7889cb7f45814fb92cecd35f101/watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c", size = 96393 }, - { url = "https://files.pythonhosted.org/packages/63/7a/6013b0d8dbc56adca7fdd4f0beed381c59f6752341b12fa0886fa7afc78b/watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2", size = 88392 }, - { url = "https://files.pythonhosted.org/packages/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c", size = 89019 }, - { url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471 }, - { url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449 }, - { url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054 }, - { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480 }, - { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451 }, - { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057 }, - { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079 }, - { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078 }, - { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076 }, - { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077 }, - { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078 }, - { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077 }, - { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078 }, - { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065 }, - { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070 }, - { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067 }, +sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/24/d9be5cd6642a6aa68352ded4b4b10fb0d7889cb7f45814fb92cecd35f101/watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c", size = 96393, upload-time = "2024-11-01T14:06:31.756Z" }, + { url = "https://files.pythonhosted.org/packages/63/7a/6013b0d8dbc56adca7fdd4f0beed381c59f6752341b12fa0886fa7afc78b/watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2", size = 88392, upload-time = "2024-11-01T14:06:32.99Z" }, + { url = "https://files.pythonhosted.org/packages/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c", size = 89019, upload-time = "2024-11-01T14:06:34.963Z" }, + { url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471, upload-time = "2024-11-01T14:06:37.745Z" }, + { url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449, upload-time = "2024-11-01T14:06:39.748Z" }, + { url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054, upload-time = "2024-11-01T14:06:41.009Z" }, + { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480, upload-time = "2024-11-01T14:06:42.952Z" }, + { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451, upload-time = "2024-11-01T14:06:45.084Z" }, + { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057, upload-time = "2024-11-01T14:06:47.324Z" }, + { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" }, + { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" }, + { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" }, + { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" }, + { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload-time = "2024-11-01T14:07:09.525Z" }, + { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" }, + { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" }, ] From 0f7bb9f28c9cce7dc0480f88d32095c7cd6f5787 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Tue, 20 May 2025 09:39:43 +0100 Subject: [PATCH 112/159] Fix typo --- pyharp/messages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyharp/messages.py b/pyharp/messages.py index e0bb046..c7df70b 100644 --- a/pyharp/messages.py +++ b/pyharp/messages.py @@ -350,7 +350,7 @@ def create( ) else: raise Exception( - "The value cannot be None is message type is equal to MessageType.WRITE!" + "The value cannot be None if the message type is equal to MessageType.WRITE!" ) def __repr__(self) -> str: From b977d7d3aa517a8105e0c08e167fe1efc760abad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Tue, 20 May 2025 14:11:45 +0100 Subject: [PATCH 113/159] Test new module structure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Grilo --- docs/examples/get_info/get_info.py | 2 +- .../olfactometer_example.py | 6 +- .../read_and_write_from_registers.py | 6 +- .../wait_for_events/wait_for_events.py | 4 +- pyharp/__init__.py | 3 - pyharp/devices/.gitignore | 0 pyharp/drivers/behavior.py | 6 +- pyharp/protocol/__init__.py | 9 +++ pyharp/{ => protocol}/base.py | 0 pyharp/{ => protocol}/device.py | 18 +++-- pyharp/{ => protocol}/device_names.py | 0 pyharp/protocol/exceptions.py | 4 + pyharp/{ => protocol}/harp_serial.py | 74 ++++++++++++++++--- pyharp/{ => protocol}/messages.py | 2 +- tests/test_messages.py | 4 +- 15 files changed, 106 insertions(+), 32 deletions(-) delete mode 100644 pyharp/__init__.py create mode 100644 pyharp/devices/.gitignore create mode 100644 pyharp/protocol/__init__.py rename pyharp/{ => protocol}/base.py (100%) rename pyharp/{ => protocol}/device.py (98%) rename pyharp/{ => protocol}/device_names.py (100%) create mode 100644 pyharp/protocol/exceptions.py rename pyharp/{ => protocol}/harp_serial.py (64%) rename pyharp/{ => protocol}/messages.py (99%) diff --git a/docs/examples/get_info/get_info.py b/docs/examples/get_info/get_info.py index 318c307..ea88143 100755 --- a/docs/examples/get_info/get_info.py +++ b/docs/examples/get_info/get_info.py @@ -1,4 +1,4 @@ -from pyharp.device import Device +from pyharp.protocol.device import Device SERIAL_PORT = ( "/dev/ttyUSB0" # or "COMx" in Windows ("x" is the number of the serial port) diff --git a/docs/examples/olfactometer_example/olfactometer_example.py b/docs/examples/olfactometer_example/olfactometer_example.py index c4b2fe3..243e3c2 100644 --- a/docs/examples/olfactometer_example/olfactometer_example.py +++ b/docs/examples/olfactometer_example/olfactometer_example.py @@ -4,9 +4,9 @@ from serial import SerialException -from pyharp import MessageType, PayloadType -from pyharp.device import Device, OperationMode -from pyharp.messages import HarpMessage +from pyharp.protocol import MessageType, PayloadType +from pyharp.protocol.device import Device, OperationMode +from pyharp.protocol.messages import HarpMessage SERIAL_PORT = ( "/dev/ttyUSB0" # or "COMx" in Windows ("x" is the number of the serial port) diff --git a/docs/examples/read_and_write_from_registers/read_and_write_from_registers.py b/docs/examples/read_and_write_from_registers/read_and_write_from_registers.py index cd8fab0..0858bb6 100755 --- a/docs/examples/read_and_write_from_registers/read_and_write_from_registers.py +++ b/docs/examples/read_and_write_from_registers/read_and_write_from_registers.py @@ -1,8 +1,8 @@ from serial import SerialException -from pyharp import MessageType, PayloadType -from pyharp.device import Device -from pyharp.messages import HarpMessage +from pyharp.protocol import MessageType, PayloadType +from pyharp.protocol.device import Device +from pyharp.protocol.messages import HarpMessage SERIAL_PORT = ( "/dev/ttyUSB0" # or "COMx" in Windows ("x" is the number of the serial port) diff --git a/docs/examples/wait_for_events/wait_for_events.py b/docs/examples/wait_for_events/wait_for_events.py index e1064c2..a33c59a 100755 --- a/docs/examples/wait_for_events/wait_for_events.py +++ b/docs/examples/wait_for_events/wait_for_events.py @@ -1,5 +1,5 @@ -from pyharp import OperationMode -from pyharp.device import Device +from pyharp.protocol import OperationMode +from pyharp.protocol.device import Device SERIAL_PORT = ( "/dev/ttyUSB0" # or "COMx" in Windows ("x" is the number of the serial port) diff --git a/pyharp/__init__.py b/pyharp/__init__.py deleted file mode 100644 index d40c35b..0000000 --- a/pyharp/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from pyharp.base import CommonRegisters, MessageType, OperationMode, PayloadType - -__version__ = "0.1.0" diff --git a/pyharp/devices/.gitignore b/pyharp/devices/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/pyharp/drivers/behavior.py b/pyharp/drivers/behavior.py index 04aa2d4..804b7c5 100644 --- a/pyharp/drivers/behavior.py +++ b/pyharp/drivers/behavior.py @@ -4,9 +4,9 @@ from serial.serialutil import SerialException -from pyharp.base import PayloadType -from pyharp.device import Device -from pyharp.messages import ReadHarpMessage, ReplyHarpMessage, WriteHarpMessage +from pyharp.protocol import PayloadType +from pyharp.protocol.device import Device +from pyharp.protocol.messages import ReadHarpMessage, ReplyHarpMessage, WriteHarpMessage # These definitions are from app_regs.h in the firmware. # Type, Base Address, "Description." diff --git a/pyharp/protocol/__init__.py b/pyharp/protocol/__init__.py new file mode 100644 index 0000000..2f73504 --- /dev/null +++ b/pyharp/protocol/__init__.py @@ -0,0 +1,9 @@ +from .base import ( + ClockConfig, + CommonRegisters, + MessageType, + OperationCtrl, + OperationMode, + PayloadType, + ResetMode, +) diff --git a/pyharp/base.py b/pyharp/protocol/base.py similarity index 100% rename from pyharp/base.py rename to pyharp/protocol/base.py diff --git a/pyharp/device.py b/pyharp/protocol/device.py similarity index 98% rename from pyharp/device.py rename to pyharp/protocol/device.py index ebc94ea..7125c73 100644 --- a/pyharp/device.py +++ b/pyharp/protocol/device.py @@ -8,11 +8,18 @@ import serial -from pyharp import CommonRegisters, MessageType, OperationMode, PayloadType -from pyharp.base import ClockConfig, OperationCtrl, ResetMode -from pyharp.device_names import device_names -from pyharp.harp_serial import HarpSerial -from pyharp.messages import HarpMessage, ReplyHarpMessage +from pyharp.protocol import ( + ClockConfig, + CommonRegisters, + MessageType, + OperationCtrl, + OperationMode, + PayloadType, + ResetMode, +) +from pyharp.protocol.device_names import device_names +from pyharp.protocol.harp_serial import HarpSerial +from pyharp.protocol.messages import HarpMessage, ReplyHarpMessage class Device: @@ -133,6 +140,7 @@ def connect(self) -> None: """ self._ser = HarpSerial( self._serial_port, # "/dev/tty.usbserial-A106C8O9" + use_buffered_protocol=False, baudrate=1000000, timeout=self._TIMEOUT_S, parity=serial.PARITY_NONE, diff --git a/pyharp/device_names.py b/pyharp/protocol/device_names.py similarity index 100% rename from pyharp/device_names.py rename to pyharp/protocol/device_names.py diff --git a/pyharp/protocol/exceptions.py b/pyharp/protocol/exceptions.py new file mode 100644 index 0000000..81cd8ae --- /dev/null +++ b/pyharp/protocol/exceptions.py @@ -0,0 +1,4 @@ +class HarpException(Exception): + """Base class for all exceptions raised by pyHARP.""" + + pass diff --git a/pyharp/harp_serial.py b/pyharp/protocol/harp_serial.py similarity index 64% rename from pyharp/harp_serial.py rename to pyharp/protocol/harp_serial.py index bed20b3..f264f34 100644 --- a/pyharp/harp_serial.py +++ b/pyharp/protocol/harp_serial.py @@ -7,7 +7,19 @@ import serial import serial.threaded -from pyharp.messages import HarpMessage, MessageType +from pyharp.protocol.messages import HarpMessage, MessageType + + +class HarpSerialProtocolOld(serial.threaded.Protocol): + # Old implementation (per-byte queue) + def __init__(self, read_q: queue.Queue, *args, **kwargs): + self._read_q = read_q + super().__init__(*args, **kwargs) + + def data_received(self, data: bytes) -> None: + for byte in data: + self._read_q.put(byte) + return super().data_received(data) class HarpSerialProtocol(serial.threaded.Protocol): @@ -25,6 +37,7 @@ def __init__(self, read_q: queue.Queue, *args, **kwargs): the queue to where the data received will be put """ self._read_q = read_q + self._buffer = bytearray() super().__init__(*args, **kwargs) def connection_made(self, transport: serial.threaded.ReaderThread) -> None: @@ -47,9 +60,21 @@ def data_received(self, data: bytes) -> None: data : bytes the data received from the serial communication """ - for byte in data: - self._read_q.put(byte) - return super().data_received(data) + self._buffer.extend(data) + while True: + if len(self._buffer) < 2: + # not enough data to read the message type and length + break + + message_type = self._buffer[0] + message_length = self._buffer[1] + total_length = 2 + message_length + if len(self._buffer) < total_length: + break + + frame = self._buffer[:total_length] + self._buffer = self._buffer[total_length:] + self._read_q.put(frame) def connection_lost(self, exc: Union[BaseException, None]) -> None: """ @@ -78,12 +103,14 @@ class HarpSerial: msg_q: queue.Queue event_q: queue.Queue - def __init__(self, serial_port: str, **kwargs): + def __init__(self, serial_port: str, use_buffered_protocol: bool = True, **kwargs): """ Parameters ---------- serial_port : str the serial port used to establish the connection with the Harp device. It must be denoted as `/dev/ttyUSBx` in Linux and `COMx` in Windows, where `x` is the number of the serial port + use_buffered_protocol : bool + whether to use the buffered protocol for reading data """ # Connect to the Harp device self._ser = serial.Serial(serial_port, **kwargs) @@ -96,16 +123,28 @@ def __init__(self, serial_port: str, **kwargs): self.event_q = queue.Queue() # Start the thread with the `HarpSerialProtocol` + self.use_buffered_protocol = use_buffered_protocol + protocol_cls = ( + HarpSerialProtocol if use_buffered_protocol else HarpSerialProtocolOld + ) + self._reader = serial.threaded.ReaderThread( self._ser, - partial(HarpSerialProtocol, self._read_q), + partial(protocol_cls, self._read_q), ) self._reader.start() - transport, protocol = self._reader.connect() + self._reader.connect() + + # Choose parsing method based on protocol + parse_target = ( + self.parse_harp_msgs_threaded_buffered + if use_buffered_protocol + else self.parse_harp_msgs_threaded_per_byte + ) # Start the thread that parses and separates the events from the remaining messages self._parse_thread = threading.Thread( - target=self.parse_harp_msgs_threaded, + target=parse_target, daemon=True, ) self._parse_thread.start() @@ -122,7 +161,24 @@ def write(self, data): """ self._reader.write(data) - def parse_harp_msgs_threaded(self): + def parse_harp_msgs_threaded_buffered(self): + """ + Parses the Harp messages and separates the events from the remaining messages. + """ + while True: + frame = self._read_q.get() + try: + # Parses the bytearray into a ReplyHarpMessage object + msg = HarpMessage.parse(frame) + if msg.message_type == MessageType.EVENT: + self.event_q.put(msg) + else: + self.msg_q.put(msg) + except Exception as e: + self.log.error(f"Error parsing message: {e}") + self.log.debug(f"Raw data: {frame}") + + def parse_harp_msgs_threaded_per_byte(self): """ Parses the Harp messages and separates the events from the remaining messages. """ diff --git a/pyharp/messages.py b/pyharp/protocol/messages.py similarity index 99% rename from pyharp/messages.py rename to pyharp/protocol/messages.py index c7df70b..f03862b 100644 --- a/pyharp/messages.py +++ b/pyharp/protocol/messages.py @@ -3,7 +3,7 @@ import struct from typing import List, Union -from pyharp import MessageType, PayloadType +from pyharp.protocol import MessageType, PayloadType class HarpMessage: diff --git a/tests/test_messages.py b/tests/test_messages.py index cf5ba1d..5b406ac 100644 --- a/tests/test_messages.py +++ b/tests/test_messages.py @@ -1,7 +1,7 @@ import pytest -from pyharp import CommonRegisters, MessageType, PayloadType -from pyharp.messages import ( +from pyharp.protocol import CommonRegisters, MessageType, PayloadType +from pyharp.protocol.messages import ( HarpMessage, ReadHarpMessage, ReplyHarpMessage, From 9e9a95a815a593d1de8fb85aa3263e6834d5e44e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Tue, 20 May 2025 14:24:54 +0100 Subject: [PATCH 114/159] Update project MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Grilo --- pyproject.toml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 666bc37..cdb74c8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,6 +28,12 @@ dev = [ requires = ["hatchling"] build-backend = "hatchling.build" +[tool.hatch.build.targets.wheel] +include = [ + "pyharp", + "pyharp/**/*", +] + [tool.ruff.lint.pydocstyle] convention = "numpy" From b8e4bed02146b98ae125c26dac6d956a716396fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Thu, 22 May 2025 10:18:00 +0100 Subject: [PATCH 115/159] Delete behavior driver --- pyharp/drivers/__init__.py | 0 pyharp/drivers/behavior.py | 394 ------------------------------------- 2 files changed, 394 deletions(-) delete mode 100644 pyharp/drivers/__init__.py delete mode 100644 pyharp/drivers/behavior.py diff --git a/pyharp/drivers/__init__.py b/pyharp/drivers/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/pyharp/drivers/behavior.py b/pyharp/drivers/behavior.py deleted file mode 100644 index 804b7c5..0000000 --- a/pyharp/drivers/behavior.py +++ /dev/null @@ -1,394 +0,0 @@ -"""Behavior Device Driver.""" - -from enum import Enum - -from serial.serialutil import SerialException - -from pyharp.protocol import PayloadType -from pyharp.protocol.device import Device -from pyharp.protocol.messages import ReadHarpMessage, ReplyHarpMessage, WriteHarpMessage - -# These definitions are from app_regs.h in the firmware. -# Type, Base Address, "Description." -REGISTERS = { # RJ45 "PORT" (0, 1, 2) Digital Inputs - "PORT_DIS": ( - PayloadType.U8, - 32, - "Reflects the state of DI digital lines of each Port.", - ), - # Manipulate any of the board's digital outputs. - "OUTPUTS_SET": (PayloadType.U16, 34, "Set the corresponding output."), - "OUTPUTS_CLR": (PayloadType.U16, 35, "Clear the corresponding output."), - "OUTPUTS_TOGGLE": (PayloadType.U16, 36, "Toggle the corresponding output."), - "OUTPUTS_OUT": (PayloadType.U16, 37, "Control corresponding output."), - # RJ45 "PORT" (0, 1, 2) Digital IOs - "PORT_DIOS_SET": (PayloadType.U8, 38, "Set the corresponding DIO."), - "PORT_DIOS_CLEAR": (PayloadType.U8, 39, "Clear the corresponding DIO."), - "PORT_DIOS_TOGGLE": (PayloadType.U8, 40, "Toggle the corresponding DIO."), - "PORT_DIOS_OUT": (PayloadType.U8, 41, "Control the corresponding DIO."), - "PORT_DIOS_CONF": (PayloadType.U8, 42, "Set the DIOs direction (1 is output)."), - "PORT_DIOS_IN": (PayloadType.U8, 43, "State of the DIOs."), - "ADD_REG_DATA": ( - PayloadType.S16, - 44, - "Voltage at ADC input and decoder (poke2) value.", - ), - "EVNT_ENABLE": (PayloadType.U8, 77, "Enable events within the bitfields."), -} - - -# Register Bitfields -class PORT_DIS(Enum): - DI0 = 0 - DI1 = 1 - DI2 = 2 - - -class OUTPUTS_OUT(Enum): - PORT0_DO = 0 - PORT0_D1 = 1 - PORT0_D2 = 2 - - PORT0_12V = 3 - PORT1_12V = 4 - PORT2_12V = 5 - - B_LED0 = 6 - B_LED1 = 7 - B_RGB0 = 8 - B_RGB1 = 9 - - DO0 = 10 - DO1 = 11 - DO2 = 12 - DO3 = 13 - - -class PORT_DIOS_IN(Enum): - DIO0 = 0 - DIO1 = 0 - DIO2 = 0 - - -# reader-friendly events for enabling/disabling. -class Events(Enum): - port_digital_inputs = 0 # PORT_DIS - port_digital_ios = 1 # PORT_DIOS_IN - analog_input = 2 # DATA - cam0 = 3 # CAM0 - cam1 = 3 # CAM1 - - -class Behavior: - """Driver for Behavior Device.""" - - # On Linux, the symlink to the first detected harp device. - # Name set in udev rules and will increment with subsequent devices. - DEVICE_NAME = "Behavior" - DEFAULT_PORT_NAME = "/dev/harp_device_00" - ID = 1216 - - def __init__(self, port_name=None, output_filename=None): - """Class constructor. Connect to a device.""" - - self.device = None - - try: - if port_name is None: - self.device = Device(self.__class__.DEFAULT_PORT_NAME, output_filename) - else: - self.device = Device(port_name, output_filename) - except (FileNotFoundError, SerialException): - print("Error: Failed to connect to Behavior Device. Is it plugged in?") - raise - - if self.device.WHO_AM_I != self.__class__.ID: - raise IOError("Error: Did not connect to Harp Behavior Device.") - - - def get_reg_info(self, reg_name: str) -> str: - """get info for this device's particular reg.""" - try: - return REGISTERS[reg_name][2] - except KeyError: - raise KeyError(f"reg: {reg_name} is not a register in " - "{self.__class__.name} Device's register map.") - - - def disable_all_events(self) -> ReplyHarpMessage: - """Disable the publishing of all events from Behavior device.""" - event_reg_bitmask = (((1 << Events.port_digital_inputs.value) | \ - (1 << Events.port_digital_ios.value) | \ - (1 << Events.analog_input.value) | \ - (1 << Events.cam0.value) | \ - (1 << Events.cam1.value) ) ^ 0xFF) - reg_type, reg_index, _ = REGISTERS["EVNT_ENABLE"] - return self.device.send( - WriteHarpMessage(reg_type, reg_index, event_reg_bitmask) - ) - - - def enable_events(self, *events: Events) -> ReplyHarpMessage: - """enable any events passed in as arguments.""" - event_reg_bitmask = 0x00 - for event in events: - event_reg_bitmask |= (1 << event.value) - reg_type, reg_index, _ = REGISTERS["EVNT_ENABLE"] - return self.device.send( - WriteHarpMessage(reg_type, reg_index, event_reg_bitmask) - ) - - -# Board inputs, outputs, and some settings configured as @properties. - # INPUTS - @property - def all_input_states(self): - """return the state of all PORT digital inputs.""" - reg_type, reg_index, _ = REGISTERS["PORT_DIS"] - return self.device.send(ReadHarpMessage(reg_type, reg_index)).payload - - @property - def DI0(self): - """return the state of port0 digital input 0.""" - return self.all_port_input_states & 0x01 - - @property - def DI1(self): - """return the state of port1 digital input 0.""" - offset = PORT_DIS.DI1.value - return (self.all_port_input_states >> offset) & 0x01 - - @property - def DI2(self): - """return the state of port2 digital input 0.""" - offset = PORT_DIS.DI2.value - return (self.all_input_states >> offset) & 0x01 - -# These do not work currently. Perhaps something needs to be cleared (MIMIC?) -# before they will configure properly. -# # IOs -# def set_io_configuration(self, bitmask : int): -# """set the state of all PORT digital ios. (1 is output.)""" -# reg_type, reg_index, _ = REGISTERS["PORT_DIOS_CONF"] - - # self.device.send(WriteHarpMessage(reg_type, reg_index, bitmask)) -# -# @property -# def all_io_states(self): -# """return the state of all PORT digital ios.""" -# reg_type, reg_index, _ = REGISTERS["PORT_DIOS_IN"] -# read_message_type = self.__class__.READ_MSG_LOOKUP[reg_type] - # return self.device.send(read_message_type(reg_index)).payload -# -# @all_io_states.setter -# def all_io_states(self, bitmask : int): -# """set the state of all PORT digital input/outputs.""" -# # Setting the state of the "DIO" pins, requires writing to the -# # _IN register, which is different from the OUTPUT -# reg_type, reg_index, _ = REGISTERS["PORT_DIOS_IN"] - - # return self.device.send(WriteHarpMessage(reg_type, reg_index, bitmask)) -# -# def set_io_outputs(self, bitmask : int): -# """set digital input/outputs to logic 1 according to bitmask.""" -# reg_type, reg_index, _ = REGISTERS["PORT_DIOS_SET"] - - # return self.device.send(WriteHarpMessage(reg_type, reg_index, bitmask)) -# -# def clear_io_outputs(self, bitmask : int): -# """clear digital input/outputs (specified with logic 1) according to bitmask.""" -# reg_type, reg_index, _ = REGISTERS["PORT_DIOS_CLEAR"] - - # return self.device.send(WriteHarpMessage(reg_type, reg_index, bitmask)) -# -# @property -# def port0_io0(self): -# """read the digital io state.""" -# return self.all_port_io_states & 0x01 -# -# @port0_io0.setter -# def port0_io0(self, value: int): -# """write port0 digital io state.""" -# pass -# -# @property -# def port1_io0(self): -# """read the digital io state.""" -# return (self.all_port_io_states >> 1) & 0x01 -# -# @port0_io0.setter -# def port1_io0(self, value: int): -# """write port0 digital io state.""" -# self.set_outputs(value&0x01) -# -# @property -# def port2_io0(self): -# """read the digital io state.""" -# return (self.all_port_io_states >> 2) & 0x01 -# -# @port0_io0.setter -# def port2_io0(self, value: int): -# """write port0 digital io state.""" -# pass - - - # OUTPUTS - @property - def all_output_states(self): - """return the state of all PORT digital inputs.""" - reg_type, reg_index, _ = REGISTERS["OUTPUTS_OUT"] - return self.device.send(ReadHarpMessage(reg_type, reg_index)).payload - - @all_output_states.setter - def all_output_states(self, bitmask : int): - """set the state of all PORT digital inputs.""" - reg_type, reg_index, _ = REGISTERS["OUTPUTS_OUT"] - return self.device.send(WriteHarpMessage(reg_type, reg_index, bitmask)) - - def set_outputs(self, bitmask : int): - """set digital outputs to logic 1 according to bitmask.""" - reg_type, reg_index, _ = REGISTERS["OUTPUTS_SET"] - return self.device.send(WriteHarpMessage(reg_type, reg_index, bitmask)) - - def clear_outputs(self, bitmask : int): - """clear digital outputs (specified with logic 1) according to bitmask.""" - reg_type, reg_index, _ = REGISTERS["OUTPUTS_CLR"] - return self.device.send(WriteHarpMessage(reg_type, reg_index, bitmask)) - - @property - def D0(self): - """read the digital output D0 state.""" - return (self.all_output_states >> 10) & 0x01 - - @D0.setter - def D0(self, value): - """set the digital output D0 state.""" - if value: - self.set_outputs(1 << 10) - else: - self.clear_outputs(1 << 10) - - @property - def D1(self): - """read the digital output D1 state.""" - return (self.all_output_states >> 11) & 0x01 - - @D1.setter - def D1(self, value): - """set the digital output D1 state.""" - if value: - self.set_outputs(1 << 11) - else: - self.clear_outputs(1 << 11) - - @property - def D2(self): - """read the digital output D2 state.""" - return (self.all_output_states >> 12) & 0x01 - - @D2.setter - def D2(self, value): - """set the digital output D2 state.""" - if value: - self.set_outputs(1 << 12) - else: - self.clear_outputs(1 << 12) - - @property - def D3(self): - """read the digital output D3 state.""" - return (self.all_output_states >> 10) & 0x01 - - @D3.setter - def D3(self, value): - """set the digital output D3 state.""" - if value: - self.set_outputs(1 << 13) - else: - self.clear_outputs(1 << 13) - - @property - def port0_D0(self): - return self.all_output_states & 0x01 - - @port0_D0.setter - def port0_D0(self, value): - if value: - self.set_outputs(1) - else: - self.clear_outputs(1) - - @property - def port1_D0(self): - return (self.all_output_states >> 1) & 0x01 - - @port1_D0.setter - def port1_D0(self, value): - if value: - self.set_outputs(1 << 1) - else: - self.clear_outputs(1 << 1) - - @property - def port2_D0(self): - return (self.all_output_states >> 2) & 0x01 - - @port2_D0.setter - def port2_D0(self, value): - if value: - self.set_outputs(1 << 2) - else: - self.clear_outputs(1 << 2) - - - @property - def port0_12V(self): - return (self.all_output_states >> 3) & 0x01 - - @port0_12V.setter - def port0_12V(self, value): - if value: - self.set_outputs(1 << 3) - else: - self.clear_outputs(1 << 3) - - @property - def port1_12V(self): - return (self.all_output_states >> 4) & 0x01 - - @port1_12V.setter - def port1_12V(self, value): - if value: - self.set_outputs(1 << 4) - else: - self.clear_outputs(1 << 4) - - @property - def port2_12V(self): - return (self.all_output_states >> 5) & 0x01 - - @port2_12V.setter - def port2_12V(self, value): - if value: - self.set_outputs(1 << 5) - else: - self.clear_outputs(1 << 5) - - - - - def __enter__(self): - """Setup for the 'with' statement""" - return self - - - def __exit__(self, *args): - """Cleanup for the 'with' statement""" - if self.device is not None: - self.device.disconnect() - - - def __del__(self): - """Cleanup when Device gets garbage collected.""" - if self.device is not None: - self.device.disconnect() From 9aedecfeca403c377595de1a5dbb08b89a86490a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Thu, 22 May 2025 13:05:03 +0100 Subject: [PATCH 116/159] Move device.py and harp_serial.py to communication folder --- .../olfactometer_example.py | 2 +- .../read_and_write_from_registers.py | 2 +- .../wait_for_events/wait_for_events.py | 2 +- pyharp/communication/__init__.py | 2 + pyharp/{protocol => communication}/device.py | 3 +- .../harp_serial.py | 61 ++----------------- 6 files changed, 10 insertions(+), 62 deletions(-) create mode 100644 pyharp/communication/__init__.py rename pyharp/{protocol => communication}/device.py (99%) rename pyharp/{protocol => communication}/harp_serial.py (65%) diff --git a/docs/examples/olfactometer_example/olfactometer_example.py b/docs/examples/olfactometer_example/olfactometer_example.py index 243e3c2..1b7017b 100644 --- a/docs/examples/olfactometer_example/olfactometer_example.py +++ b/docs/examples/olfactometer_example/olfactometer_example.py @@ -4,8 +4,8 @@ from serial import SerialException +from pyharp.communication.device import Device, OperationMode from pyharp.protocol import MessageType, PayloadType -from pyharp.protocol.device import Device, OperationMode from pyharp.protocol.messages import HarpMessage SERIAL_PORT = ( diff --git a/docs/examples/read_and_write_from_registers/read_and_write_from_registers.py b/docs/examples/read_and_write_from_registers/read_and_write_from_registers.py index 0858bb6..2f21927 100755 --- a/docs/examples/read_and_write_from_registers/read_and_write_from_registers.py +++ b/docs/examples/read_and_write_from_registers/read_and_write_from_registers.py @@ -1,7 +1,7 @@ from serial import SerialException +from pyharp.devices.device import Device from pyharp.protocol import MessageType, PayloadType -from pyharp.protocol.device import Device from pyharp.protocol.messages import HarpMessage SERIAL_PORT = ( diff --git a/docs/examples/wait_for_events/wait_for_events.py b/docs/examples/wait_for_events/wait_for_events.py index a33c59a..432fed9 100755 --- a/docs/examples/wait_for_events/wait_for_events.py +++ b/docs/examples/wait_for_events/wait_for_events.py @@ -1,5 +1,5 @@ +from pyharp.communication.device import Device from pyharp.protocol import OperationMode -from pyharp.protocol.device import Device SERIAL_PORT = ( "/dev/ttyUSB0" # or "COMx" in Windows ("x" is the number of the serial port) diff --git a/pyharp/communication/__init__.py b/pyharp/communication/__init__.py new file mode 100644 index 0000000..7af9dc5 --- /dev/null +++ b/pyharp/communication/__init__.py @@ -0,0 +1,2 @@ +from .device import Device as Device +from .harp_serial import HarpSerial as HarpSerial diff --git a/pyharp/protocol/device.py b/pyharp/communication/device.py similarity index 99% rename from pyharp/protocol/device.py rename to pyharp/communication/device.py index 7125c73..7718f24 100644 --- a/pyharp/protocol/device.py +++ b/pyharp/communication/device.py @@ -8,6 +8,7 @@ import serial +from pyharp.communication import HarpSerial from pyharp.protocol import ( ClockConfig, CommonRegisters, @@ -18,7 +19,6 @@ ResetMode, ) from pyharp.protocol.device_names import device_names -from pyharp.protocol.harp_serial import HarpSerial from pyharp.protocol.messages import HarpMessage, ReplyHarpMessage @@ -140,7 +140,6 @@ def connect(self) -> None: """ self._ser = HarpSerial( self._serial_port, # "/dev/tty.usbserial-A106C8O9" - use_buffered_protocol=False, baudrate=1000000, timeout=self._TIMEOUT_S, parity=serial.PARITY_NONE, diff --git a/pyharp/protocol/harp_serial.py b/pyharp/communication/harp_serial.py similarity index 65% rename from pyharp/protocol/harp_serial.py rename to pyharp/communication/harp_serial.py index f264f34..738ec06 100644 --- a/pyharp/protocol/harp_serial.py +++ b/pyharp/communication/harp_serial.py @@ -10,18 +10,6 @@ from pyharp.protocol.messages import HarpMessage, MessageType -class HarpSerialProtocolOld(serial.threaded.Protocol): - # Old implementation (per-byte queue) - def __init__(self, read_q: queue.Queue, *args, **kwargs): - self._read_q = read_q - super().__init__(*args, **kwargs) - - def data_received(self, data: bytes) -> None: - for byte in data: - self._read_q.put(byte) - return super().data_received(data) - - class HarpSerialProtocol(serial.threaded.Protocol): """ The `HarpSerialProtocol` class deals with the data received from the serial communication. @@ -66,7 +54,7 @@ def data_received(self, data: bytes) -> None: # not enough data to read the message type and length break - message_type = self._buffer[0] + # Read length (we can ignore the message type) message_length = self._buffer[1] total_length = 2 + message_length if len(self._buffer) < total_length: @@ -103,14 +91,12 @@ class HarpSerial: msg_q: queue.Queue event_q: queue.Queue - def __init__(self, serial_port: str, use_buffered_protocol: bool = True, **kwargs): + def __init__(self, serial_port: str, **kwargs): """ Parameters ---------- serial_port : str the serial port used to establish the connection with the Harp device. It must be denoted as `/dev/ttyUSBx` in Linux and `COMx` in Windows, where `x` is the number of the serial port - use_buffered_protocol : bool - whether to use the buffered protocol for reading data """ # Connect to the Harp device self._ser = serial.Serial(serial_port, **kwargs) @@ -123,28 +109,16 @@ def __init__(self, serial_port: str, use_buffered_protocol: bool = True, **kwarg self.event_q = queue.Queue() # Start the thread with the `HarpSerialProtocol` - self.use_buffered_protocol = use_buffered_protocol - protocol_cls = ( - HarpSerialProtocol if use_buffered_protocol else HarpSerialProtocolOld - ) - self._reader = serial.threaded.ReaderThread( self._ser, - partial(protocol_cls, self._read_q), + partial(HarpSerialProtocol, self._read_q), ) self._reader.start() self._reader.connect() - # Choose parsing method based on protocol - parse_target = ( - self.parse_harp_msgs_threaded_buffered - if use_buffered_protocol - else self.parse_harp_msgs_threaded_per_byte - ) - # Start the thread that parses and separates the events from the remaining messages self._parse_thread = threading.Thread( - target=parse_target, + target=self.parse_harp_msgs_threaded_buffered, daemon=True, ) self._parse_thread.start() @@ -177,30 +151,3 @@ def parse_harp_msgs_threaded_buffered(self): except Exception as e: self.log.error(f"Error parsing message: {e}") self.log.debug(f"Raw data: {frame}") - - def parse_harp_msgs_threaded_per_byte(self): - """ - Parses the Harp messages and separates the events from the remaining messages. - """ - while True: - # Gets the Harp message bytes based on the length byte of the message - message_type = self._read_q.get(1) - message_length = self._read_q.get(1) - message_content = bytes([self._read_q.get() for _ in range(message_length)]) - self.log.debug(f"reply (type): {message_type}") - self.log.debug(f"reply (length): {message_length}") - self.log.debug(f"reply (payload): {message_content}") - - # Reconstructs the message into a bytearray - frame = bytearray() - frame.append(message_type) - frame.append(message_length) - frame += message_content - # Parses the bytearray into a ReplyHarpMessage object - msg = HarpMessage.parse(frame) - - # Puts the parsed Harp message into the correct queue - if msg.message_type == MessageType.EVENT: - self.event_q.put(msg) - else: - self.msg_q.put(msg) From 2ff6bc2bc5d45d0b0ebc02b466b1ca675a902547 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Thu, 22 May 2025 13:06:11 +0100 Subject: [PATCH 117/159] Move core from docs to protocol --- docs/api/core.md | 4 ---- docs/api/protocol.md | 7 +++++++ mkdocs.yml | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) delete mode 100644 docs/api/core.md create mode 100644 docs/api/protocol.md diff --git a/docs/api/core.md b/docs/api/core.md deleted file mode 100644 index 6ec54e7..0000000 --- a/docs/api/core.md +++ /dev/null @@ -1,4 +0,0 @@ -::: pyharp.MessageType -::: pyharp.PayloadType -::: pyharp.CommonRegisters -::: pyharp.OperationMode diff --git a/docs/api/protocol.md b/docs/api/protocol.md new file mode 100644 index 0000000..9841eec --- /dev/null +++ b/docs/api/protocol.md @@ -0,0 +1,7 @@ +::: pyharp.protocol.MessageType +::: pyharp.protocol.PayloadType +::: pyharp.protocol.CommonRegisters +::: pyharp.protocol.OperationMode +::: pyharp.protocol.OperationCtrl +::: pyharp.protocol.ResetMode +::: pyharp.protocol.ClockConfig diff --git a/mkdocs.yml b/mkdocs.yml index 19c56c7..566edef 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -82,7 +82,7 @@ nav: - Read and Write from Registers: examples/read_and_write_from_registers/read_and_write_from_registers.md - Olfactometer Example: examples/olfactometer_example/olfactometer_example.md - API: - - Core: api/core.md + - Protocol: api/protocol.md - Device: api/device.md - Messages: api/messages.md - Devices: '*include ./pyharp.devices/*/mkdocs.yml' From 1ae9aee219124bd6889b8c9980c375c145d9d617 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Thu, 22 May 2025 13:06:34 +0100 Subject: [PATCH 118/159] Update docs links with correct namespaces --- docs/api/device.md | 2 +- docs/api/messages.md | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/api/device.md b/docs/api/device.md index 842f814..42b9b4a 100644 --- a/docs/api/device.md +++ b/docs/api/device.md @@ -1 +1 @@ -::: pyharp.device.Device +::: pyharp.communication.Device diff --git a/docs/api/messages.md b/docs/api/messages.md index 9d85270..569adf5 100644 --- a/docs/api/messages.md +++ b/docs/api/messages.md @@ -1,4 +1,4 @@ -::: pyharp.messages.HarpMessage -::: pyharp.messages.ReplyHarpMessage -::: pyharp.messages.ReadHarpMessage -::: pyharp.messages.WriteHarpMessage +::: pyharp.protocol.messages.HarpMessage +::: pyharp.protocol.messages.ReplyHarpMessage +::: pyharp.protocol.messages.ReadHarpMessage +::: pyharp.protocol.messages.WriteHarpMessage From 55f38d3d5d6bf2700e6d2a8a87df09188144bbf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Thu, 22 May 2025 13:13:49 +0100 Subject: [PATCH 119/159] Remove duplicated extension from mkdocs config --- mkdocs.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/mkdocs.yml b/mkdocs.yml index 566edef..debd540 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -26,7 +26,6 @@ markdown_extensions: - attr_list - admonition - pymdownx.details - - pymdownx.superfences - pymdownx.emoji: emoji_index: !!python/name:material.extensions.emoji.twemoji emoji_generator: !!python/name:material.extensions.emoji.to_svg From c1d1a5941dd40fae05de27a3d5d0be459cf8f9ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Thu, 22 May 2025 13:29:48 +0100 Subject: [PATCH 120/159] Minor fix on example --- .../read_and_write_from_registers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/examples/read_and_write_from_registers/read_and_write_from_registers.py b/docs/examples/read_and_write_from_registers/read_and_write_from_registers.py index 2f21927..4ebbb94 100755 --- a/docs/examples/read_and_write_from_registers/read_and_write_from_registers.py +++ b/docs/examples/read_and_write_from_registers/read_and_write_from_registers.py @@ -1,6 +1,6 @@ from serial import SerialException -from pyharp.devices.device import Device +from pyharp.communication.device import Device from pyharp.protocol import MessageType, PayloadType from pyharp.protocol.messages import HarpMessage From deef783d2b4cddb571e5614fee441592f51d9372 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Fri, 23 May 2025 09:09:51 +0100 Subject: [PATCH 121/159] Update files to prevent circular imports --- pyharp/communication/device.py | 2 +- pyharp/protocol/__init__.py | 10 +--------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/pyharp/communication/device.py b/pyharp/communication/device.py index 7718f24..b66e5fc 100644 --- a/pyharp/communication/device.py +++ b/pyharp/communication/device.py @@ -8,7 +8,7 @@ import serial -from pyharp.communication import HarpSerial +from pyharp.communication.harp_serial import HarpSerial from pyharp.protocol import ( ClockConfig, CommonRegisters, diff --git a/pyharp/protocol/__init__.py b/pyharp/protocol/__init__.py index 2f73504..12e69f4 100644 --- a/pyharp/protocol/__init__.py +++ b/pyharp/protocol/__init__.py @@ -1,9 +1 @@ -from .base import ( - ClockConfig, - CommonRegisters, - MessageType, - OperationCtrl, - OperationMode, - PayloadType, - ResetMode, -) +from .base import * # noqa: F403 From 74daef6447e439380ef33cfd8b97f61468e01012 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Thu, 12 Jun 2025 08:13:57 +0100 Subject: [PATCH 122/159] Update project related information for pushing it to PyPI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Grilo --- LICENSE | 2 +- README.md | 1 + pyharp/communication/device.py | 2 +- pyproject.toml | 20 ++++++++++++++++---- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/LICENSE b/LICENSE index 86b9923..0c33b6f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020 OEPS & Filipe Carvalho +Copyright (c) 2025 Hardware and Software Platform, Champalimaud Foundation Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 79b36f3..cd80ce8 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ from pyharp.messages import HarpMessage # Connect to a device device = Device("/dev/ttyUSB0") +#device = Device("COM3") # for Windows # Get device information device.info() diff --git a/pyharp/communication/device.py b/pyharp/communication/device.py index b66e5fc..c6b2df2 100644 --- a/pyharp/communication/device.py +++ b/pyharp/communication/device.py @@ -434,7 +434,7 @@ def send(self, message: HarpMessage) -> Optional[ReplyHarpMessage]: Parameters ---------- - message_bytes : HarpMessage + message : HarpMessage the HarpMessage containing the message to be sent to the device Returns diff --git a/pyproject.toml b/pyproject.toml index cdb74c8..e7c93f8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,15 +1,21 @@ [project] -name = "pyharp" -version = "0.1.0" +name = "harp-protocol" +version = "0.2.0a1" description = "Library for data acquisition and control of devices implementing the Harp protocol." -authors = [{ name= "Filipe Carvalho", email="filipe@open-ephys.org"}] +authors = [{ name= "Hardware and Software Platform, Champalimaud Foundation", email="software@research.fchampalimaud.org"}] license = "MIT" readme = 'README.md' -requires-python = ">=3.11" +keywords = ['python', 'harp'] +requires-python = ">=3.9,<4.0" dependencies = [ "pyserial>=3.5", ] +[project.urls] +Repository = "https://github.com/fchampalimaud/pyharp/" +"Bug Tracker" = "https://github.com/fchampalimaud/pyharp/issues" +Documentation = "https://fchampalimaud.github.io/pyharp/" + [dependency-groups] dev = [ "mkdocs>=1.6.1", @@ -44,3 +50,9 @@ python_files = [ "tests.py", "test_*.py" ] + +[[tool.uv.index]] +name = "testpypi" +url = "https://test.pypi.org/simple/" +publish-url = "https://test.pypi.org/legacy/" +explicit = true From 60bff1237267bba4e1e90cfcea67dcef665a8d68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Mon, 30 Jun 2025 10:42:09 +0100 Subject: [PATCH 123/159] Change namespace to "harp" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Grilo --- README.md | 18 +++++++++--------- docs/api/device.md | 2 +- docs/api/messages.md | 8 ++++---- docs/api/protocol.md | 14 +++++++------- docs/examples/index.md | 2 +- .../olfactometer_example.py | 6 +++--- .../read_and_write_from_registers.py | 6 +++--- .../wait_for_events/wait_for_events.py | 4 ++-- docs/index.md | 18 +++++++++--------- {pyharp => harp}/communication/__init__.py | 0 {pyharp => harp}/communication/device.py | 8 ++++---- {pyharp => harp}/communication/harp_serial.py | 2 +- {pyharp => harp}/devices/.gitignore | 0 {pyharp => harp}/protocol/__init__.py | 0 {pyharp => harp}/protocol/base.py | 0 {pyharp => harp}/protocol/device_names.py | 2 +- harp/protocol/exceptions.py | 18 ++++++++++++++++++ {pyharp => harp}/protocol/messages.py | 2 +- mkdocs.yml | 2 +- pyharp/protocol/exceptions.py | 4 ---- pyproject.toml | 4 ++-- tests/test_messages.py | 4 ++-- 22 files changed, 69 insertions(+), 55 deletions(-) rename {pyharp => harp}/communication/__init__.py (100%) rename {pyharp => harp}/communication/device.py (99%) rename {pyharp => harp}/communication/harp_serial.py (98%) rename {pyharp => harp}/devices/.gitignore (100%) rename {pyharp => harp}/protocol/__init__.py (100%) rename {pyharp => harp}/protocol/base.py (100%) rename {pyharp => harp}/protocol/device_names.py (98%) create mode 100644 harp/protocol/exceptions.py rename {pyharp => harp}/protocol/messages.py (99%) delete mode 100644 pyharp/protocol/exceptions.py diff --git a/README.md b/README.md index cd80ce8..7bf37af 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,21 @@ -# pyharp +# harp Python implementation of the Harp protocol for hardware control and data acquisition. ## Installation ```bash -uv add pyharp +uv add harp-protocol # or -pip install pyharp +pip install harp-protocol ``` ## Quick Start ```python -from pyharp import MessageType, PayloadType -from pyharp.device import Device -from pyharp.messages import HarpMessage +from harp import MessageType, PayloadType +from harp.device import Device +from harp.messages import HarpMessage # Connect to a device device = Device("/dev/ttyUSB0") @@ -40,9 +40,9 @@ device.disconnect() or using the `with` statement: ```python -from pyharp import MessageType, PayloadType -from pyharp.device import Device -from pyharp.messages import HarpMessage +from harp import MessageType, PayloadType +from harp.device import Device +from harp.messages import HarpMessage with Device("/dev/ttyUSB0") as device: # Get device information diff --git a/docs/api/device.md b/docs/api/device.md index 42b9b4a..451d0eb 100644 --- a/docs/api/device.md +++ b/docs/api/device.md @@ -1 +1 @@ -::: pyharp.communication.Device +::: harp.communication.Device diff --git a/docs/api/messages.md b/docs/api/messages.md index 569adf5..0621fd0 100644 --- a/docs/api/messages.md +++ b/docs/api/messages.md @@ -1,4 +1,4 @@ -::: pyharp.protocol.messages.HarpMessage -::: pyharp.protocol.messages.ReplyHarpMessage -::: pyharp.protocol.messages.ReadHarpMessage -::: pyharp.protocol.messages.WriteHarpMessage +::: harp.protocol.messages.HarpMessage +::: harp.protocol.messages.ReplyHarpMessage +::: harp.protocol.messages.ReadHarpMessage +::: harp.protocol.messages.WriteHarpMessage diff --git a/docs/api/protocol.md b/docs/api/protocol.md index 9841eec..4e54672 100644 --- a/docs/api/protocol.md +++ b/docs/api/protocol.md @@ -1,7 +1,7 @@ -::: pyharp.protocol.MessageType -::: pyharp.protocol.PayloadType -::: pyharp.protocol.CommonRegisters -::: pyharp.protocol.OperationMode -::: pyharp.protocol.OperationCtrl -::: pyharp.protocol.ResetMode -::: pyharp.protocol.ClockConfig +::: harp.protocol.MessageType +::: harp.protocol.PayloadType +::: harp.protocol.CommonRegisters +::: harp.protocol.OperationMode +::: harp.protocol.OperationCtrl +::: harp.protocol.ResetMode +::: harp.protocol.ClockConfig diff --git a/docs/examples/index.md b/docs/examples/index.md index f66468a..d045e5d 100644 --- a/docs/examples/index.md +++ b/docs/examples/index.md @@ -1,6 +1,6 @@ # Examples -This section contains some examples to help you get started with `pyharp`. +This section contains some examples to help you get started with `harp`. Here's the complete list of available examples: diff --git a/docs/examples/olfactometer_example/olfactometer_example.py b/docs/examples/olfactometer_example/olfactometer_example.py index 1b7017b..6078a77 100644 --- a/docs/examples/olfactometer_example/olfactometer_example.py +++ b/docs/examples/olfactometer_example/olfactometer_example.py @@ -4,9 +4,9 @@ from serial import SerialException -from pyharp.communication.device import Device, OperationMode -from pyharp.protocol import MessageType, PayloadType -from pyharp.protocol.messages import HarpMessage +from harp.communication.device import Device, OperationMode +from harp.protocol import MessageType, PayloadType +from harp.protocol.messages import HarpMessage SERIAL_PORT = ( "/dev/ttyUSB0" # or "COMx" in Windows ("x" is the number of the serial port) diff --git a/docs/examples/read_and_write_from_registers/read_and_write_from_registers.py b/docs/examples/read_and_write_from_registers/read_and_write_from_registers.py index 4ebbb94..a43019f 100755 --- a/docs/examples/read_and_write_from_registers/read_and_write_from_registers.py +++ b/docs/examples/read_and_write_from_registers/read_and_write_from_registers.py @@ -1,8 +1,8 @@ from serial import SerialException -from pyharp.communication.device import Device -from pyharp.protocol import MessageType, PayloadType -from pyharp.protocol.messages import HarpMessage +from harp.communication.device import Device +from harp.protocol import MessageType, PayloadType +from harp.protocol.messages import HarpMessage SERIAL_PORT = ( "/dev/ttyUSB0" # or "COMx" in Windows ("x" is the number of the serial port) diff --git a/docs/examples/wait_for_events/wait_for_events.py b/docs/examples/wait_for_events/wait_for_events.py index 432fed9..7946464 100755 --- a/docs/examples/wait_for_events/wait_for_events.py +++ b/docs/examples/wait_for_events/wait_for_events.py @@ -1,5 +1,5 @@ -from pyharp.communication.device import Device -from pyharp.protocol import OperationMode +from harp.communication.device import Device +from harp.protocol import OperationMode SERIAL_PORT = ( "/dev/ttyUSB0" # or "COMx" in Windows ("x" is the number of the serial port) diff --git a/docs/index.md b/docs/index.md index 79b36f3..5dd1923 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,21 +1,21 @@ -# pyharp +# harp Python implementation of the Harp protocol for hardware control and data acquisition. ## Installation ```bash -uv add pyharp +uv add harp-protocol # or -pip install pyharp +pip install harp-protocol ``` ## Quick Start ```python -from pyharp import MessageType, PayloadType -from pyharp.device import Device -from pyharp.messages import HarpMessage +from harp import MessageType, PayloadType +from harp.device import Device +from harp.messages import HarpMessage # Connect to a device device = Device("/dev/ttyUSB0") @@ -39,9 +39,9 @@ device.disconnect() or using the `with` statement: ```python -from pyharp import MessageType, PayloadType -from pyharp.device import Device -from pyharp.messages import HarpMessage +from harp import MessageType, PayloadType +from harp.device import Device +from harp.messages import HarpMessage with Device("/dev/ttyUSB0") as device: # Get device information diff --git a/pyharp/communication/__init__.py b/harp/communication/__init__.py similarity index 100% rename from pyharp/communication/__init__.py rename to harp/communication/__init__.py diff --git a/pyharp/communication/device.py b/harp/communication/device.py similarity index 99% rename from pyharp/communication/device.py rename to harp/communication/device.py index c6b2df2..5647293 100644 --- a/pyharp/communication/device.py +++ b/harp/communication/device.py @@ -8,8 +8,8 @@ import serial -from pyharp.communication.harp_serial import HarpSerial -from pyharp.protocol import ( +from harp.communication.harp_serial import HarpSerial +from harp.protocol import ( ClockConfig, CommonRegisters, MessageType, @@ -18,8 +18,8 @@ PayloadType, ResetMode, ) -from pyharp.protocol.device_names import device_names -from pyharp.protocol.messages import HarpMessage, ReplyHarpMessage +from harp.protocol.device_names import device_names +from harp.protocol.messages import HarpMessage, ReplyHarpMessage class Device: diff --git a/pyharp/communication/harp_serial.py b/harp/communication/harp_serial.py similarity index 98% rename from pyharp/communication/harp_serial.py rename to harp/communication/harp_serial.py index 738ec06..d5d5578 100644 --- a/pyharp/communication/harp_serial.py +++ b/harp/communication/harp_serial.py @@ -7,7 +7,7 @@ import serial import serial.threaded -from pyharp.protocol.messages import HarpMessage, MessageType +from harp.protocol.messages import HarpMessage, MessageType class HarpSerialProtocol(serial.threaded.Protocol): diff --git a/pyharp/devices/.gitignore b/harp/devices/.gitignore similarity index 100% rename from pyharp/devices/.gitignore rename to harp/devices/.gitignore diff --git a/pyharp/protocol/__init__.py b/harp/protocol/__init__.py similarity index 100% rename from pyharp/protocol/__init__.py rename to harp/protocol/__init__.py diff --git a/pyharp/protocol/base.py b/harp/protocol/base.py similarity index 100% rename from pyharp/protocol/base.py rename to harp/protocol/base.py diff --git a/pyharp/protocol/device_names.py b/harp/protocol/device_names.py similarity index 98% rename from pyharp/protocol/device_names.py rename to harp/protocol/device_names.py index c166ff0..4b7efcb 100644 --- a/pyharp/protocol/device_names.py +++ b/harp/protocol/device_names.py @@ -1,6 +1,6 @@ from collections import defaultdict -# This file contains the device names for the current version of the pyHarp library. +# This file contains the device names for the current version of the harp library. # These names were extracted from https://github.com/harp-tech/protocol/blob/main/whoami.yml # commit used: https://github.com/harp-tech/protocol/commit/3e2a228 diff --git a/harp/protocol/exceptions.py b/harp/protocol/exceptions.py new file mode 100644 index 0000000..058e23b --- /dev/null +++ b/harp/protocol/exceptions.py @@ -0,0 +1,18 @@ +class HarpException(Exception): + """Base class for all exceptions raised related with Harp.""" + + pass + + +class HarpWriteException(HarpException): + def __init__(self, register, message): + super().__init__(f"Error writing to register {register}: {message}") + self.register = register + self.message = message + + +class HarpReadException(HarpException): + def __init__(self, register, message): + super().__init__(f"Error reading from register {register}: {message}") + self.register = register + self.message = message diff --git a/pyharp/protocol/messages.py b/harp/protocol/messages.py similarity index 99% rename from pyharp/protocol/messages.py rename to harp/protocol/messages.py index f03862b..4437e82 100644 --- a/pyharp/protocol/messages.py +++ b/harp/protocol/messages.py @@ -3,7 +3,7 @@ import struct from typing import List, Union -from pyharp.protocol import MessageType, PayloadType +from harp.protocol import MessageType, PayloadType class HarpMessage: diff --git a/mkdocs.yml b/mkdocs.yml index debd540..de3b1b3 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -84,7 +84,7 @@ nav: - Protocol: api/protocol.md - Device: api/device.md - Messages: api/messages.md - - Devices: '*include ./pyharp.devices/*/mkdocs.yml' + - Devices: '*include ./harp.devices/*/mkdocs.yml' extra_css: - stylesheets/extra.css diff --git a/pyharp/protocol/exceptions.py b/pyharp/protocol/exceptions.py deleted file mode 100644 index 81cd8ae..0000000 --- a/pyharp/protocol/exceptions.py +++ /dev/null @@ -1,4 +0,0 @@ -class HarpException(Exception): - """Base class for all exceptions raised by pyHARP.""" - - pass diff --git a/pyproject.toml b/pyproject.toml index e7c93f8..934a2a8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,8 +36,8 @@ build-backend = "hatchling.build" [tool.hatch.build.targets.wheel] include = [ - "pyharp", - "pyharp/**/*", + "harp", + "harp/**/*", ] [tool.ruff.lint.pydocstyle] diff --git a/tests/test_messages.py b/tests/test_messages.py index 5b406ac..45ab698 100644 --- a/tests/test_messages.py +++ b/tests/test_messages.py @@ -1,7 +1,7 @@ import pytest -from pyharp.protocol import CommonRegisters, MessageType, PayloadType -from pyharp.protocol.messages import ( +from harp.protocol import CommonRegisters, MessageType, PayloadType +from harp.protocol.messages import ( HarpMessage, ReadHarpMessage, ReplyHarpMessage, From f50be3246945987c43d4df54a6fbcf89404a8545 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Mon, 30 Jun 2025 10:44:28 +0100 Subject: [PATCH 124/159] Update minimum Python version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Grilo --- uv.lock | 226 ++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 186 insertions(+), 40 deletions(-) diff --git a/uv.lock b/uv.lock index de644e6..26c81af 100644 --- a/uv.lock +++ b/uv.lock @@ -1,6 +1,6 @@ version = 1 revision = 2 -requires-python = ">=3.11" +requires-python = ">=3.9, <4.0" [[package]] name = "babel" @@ -39,6 +39,19 @@ version = "3.4.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188, upload-time = "2024-12-24T18:12:35.43Z" } wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/58/5580c1716040bc89206c77d8f74418caf82ce519aae06450393ca73475d1/charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de", size = 198013, upload-time = "2024-12-24T18:09:43.671Z" }, + { url = "https://files.pythonhosted.org/packages/d0/11/00341177ae71c6f5159a08168bcb98c6e6d196d372c94511f9f6c9afe0c6/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176", size = 141285, upload-time = "2024-12-24T18:09:48.113Z" }, + { url = "https://files.pythonhosted.org/packages/01/09/11d684ea5819e5a8f5100fb0b38cf8d02b514746607934134d31233e02c8/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037", size = 151449, upload-time = "2024-12-24T18:09:50.845Z" }, + { url = "https://files.pythonhosted.org/packages/08/06/9f5a12939db324d905dc1f70591ae7d7898d030d7662f0d426e2286f68c9/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f", size = 143892, upload-time = "2024-12-24T18:09:52.078Z" }, + { url = "https://files.pythonhosted.org/packages/93/62/5e89cdfe04584cb7f4d36003ffa2936681b03ecc0754f8e969c2becb7e24/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a", size = 146123, upload-time = "2024-12-24T18:09:54.575Z" }, + { url = "https://files.pythonhosted.org/packages/a9/ac/ab729a15c516da2ab70a05f8722ecfccc3f04ed7a18e45c75bbbaa347d61/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a", size = 147943, upload-time = "2024-12-24T18:09:57.324Z" }, + { url = "https://files.pythonhosted.org/packages/03/d2/3f392f23f042615689456e9a274640c1d2e5dd1d52de36ab8f7955f8f050/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247", size = 142063, upload-time = "2024-12-24T18:09:59.794Z" }, + { url = "https://files.pythonhosted.org/packages/f2/e3/e20aae5e1039a2cd9b08d9205f52142329f887f8cf70da3650326670bddf/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408", size = 150578, upload-time = "2024-12-24T18:10:02.357Z" }, + { url = "https://files.pythonhosted.org/packages/8d/af/779ad72a4da0aed925e1139d458adc486e61076d7ecdcc09e610ea8678db/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb", size = 153629, upload-time = "2024-12-24T18:10:03.678Z" }, + { url = "https://files.pythonhosted.org/packages/c2/b6/7aa450b278e7aa92cf7732140bfd8be21f5f29d5bf334ae987c945276639/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d", size = 150778, upload-time = "2024-12-24T18:10:06.197Z" }, + { url = "https://files.pythonhosted.org/packages/39/f4/d9f4f712d0951dcbfd42920d3db81b00dd23b6ab520419626f4023334056/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807", size = 146453, upload-time = "2024-12-24T18:10:08.848Z" }, + { url = "https://files.pythonhosted.org/packages/49/2b/999d0314e4ee0cff3cb83e6bc9aeddd397eeed693edb4facb901eb8fbb69/charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f", size = 95479, upload-time = "2024-12-24T18:10:10.044Z" }, + { url = "https://files.pythonhosted.org/packages/2d/ce/3cbed41cff67e455a386fb5e5dd8906cdda2ed92fbc6297921f2e4419309/charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f", size = 102790, upload-time = "2024-12-24T18:10:11.323Z" }, { url = "https://files.pythonhosted.org/packages/72/80/41ef5d5a7935d2d3a773e3eaebf0a9350542f2cab4eac59a7a4741fbbbbe/charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125", size = 194995, upload-time = "2024-12-24T18:10:12.838Z" }, { url = "https://files.pythonhosted.org/packages/7a/28/0b9fefa7b8b080ec492110af6d88aa3dea91c464b17d53474b6e9ba5d2c5/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1", size = 139471, upload-time = "2024-12-24T18:10:14.101Z" }, { url = "https://files.pythonhosted.org/packages/71/64/d24ab1a997efb06402e3fc07317e94da358e2585165930d9d59ad45fcae2/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3", size = 149831, upload-time = "2024-12-24T18:10:15.512Z" }, @@ -78,6 +91,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732, upload-time = "2024-12-24T18:11:22.774Z" }, { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391, upload-time = "2024-12-24T18:11:24.139Z" }, { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702, upload-time = "2024-12-24T18:11:26.535Z" }, + { url = "https://files.pythonhosted.org/packages/7f/c0/b913f8f02836ed9ab32ea643c6fe4d3325c3d8627cf6e78098671cafff86/charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41", size = 197867, upload-time = "2024-12-24T18:12:10.438Z" }, + { url = "https://files.pythonhosted.org/packages/0f/6c/2bee440303d705b6fb1e2ec789543edec83d32d258299b16eed28aad48e0/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f", size = 141385, upload-time = "2024-12-24T18:12:11.847Z" }, + { url = "https://files.pythonhosted.org/packages/3d/04/cb42585f07f6f9fd3219ffb6f37d5a39b4fd2db2355b23683060029c35f7/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2", size = 151367, upload-time = "2024-12-24T18:12:13.177Z" }, + { url = "https://files.pythonhosted.org/packages/54/54/2412a5b093acb17f0222de007cc129ec0e0df198b5ad2ce5699355269dfe/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770", size = 143928, upload-time = "2024-12-24T18:12:14.497Z" }, + { url = "https://files.pythonhosted.org/packages/5a/6d/e2773862b043dcf8a221342954f375392bb2ce6487bcd9f2c1b34e1d6781/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4", size = 146203, upload-time = "2024-12-24T18:12:15.731Z" }, + { url = "https://files.pythonhosted.org/packages/b9/f8/ca440ef60d8f8916022859885f231abb07ada3c347c03d63f283bec32ef5/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537", size = 148082, upload-time = "2024-12-24T18:12:18.641Z" }, + { url = "https://files.pythonhosted.org/packages/04/d2/42fd330901aaa4b805a1097856c2edf5095e260a597f65def493f4b8c833/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496", size = 142053, upload-time = "2024-12-24T18:12:20.036Z" }, + { url = "https://files.pythonhosted.org/packages/9e/af/3a97a4fa3c53586f1910dadfc916e9c4f35eeada36de4108f5096cb7215f/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78", size = 150625, upload-time = "2024-12-24T18:12:22.804Z" }, + { url = "https://files.pythonhosted.org/packages/26/ae/23d6041322a3556e4da139663d02fb1b3c59a23ab2e2b56432bd2ad63ded/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7", size = 153549, upload-time = "2024-12-24T18:12:24.163Z" }, + { url = "https://files.pythonhosted.org/packages/94/22/b8f2081c6a77cb20d97e57e0b385b481887aa08019d2459dc2858ed64871/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6", size = 150945, upload-time = "2024-12-24T18:12:25.415Z" }, + { url = "https://files.pythonhosted.org/packages/c7/0b/c5ec5092747f801b8b093cdf5610e732b809d6cb11f4c51e35fc28d1d389/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294", size = 146595, upload-time = "2024-12-24T18:12:28.03Z" }, + { url = "https://files.pythonhosted.org/packages/0c/5a/0b59704c38470df6768aa154cc87b1ac7c9bb687990a1559dc8765e8627e/charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5", size = 95453, upload-time = "2024-12-24T18:12:29.569Z" }, + { url = "https://files.pythonhosted.org/packages/85/2d/a9790237cb4d01a6d57afadc8573c8b73c609ade20b80f4cda30802009ee/charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765", size = 102811, upload-time = "2024-12-24T18:12:30.83Z" }, { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767, upload-time = "2024-12-24T18:12:32.852Z" }, ] @@ -108,6 +134,16 @@ version = "7.8.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/19/4f/2251e65033ed2ce1e68f00f91a0294e0f80c80ae8c3ebbe2f12828c4cd53/coverage-7.8.0.tar.gz", hash = "sha256:7a3d62b3b03b4b6fd41a085f3574874cf946cb4604d2b4d3e8dca8cd570ca501", size = 811872, upload-time = "2025-03-30T20:36:45.376Z" } wheels = [ + { url = "https://files.pythonhosted.org/packages/78/01/1c5e6ee4ebaaa5e079db933a9a45f61172048c7efa06648445821a201084/coverage-7.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2931f66991175369859b5fd58529cd4b73582461877ecfd859b6549869287ffe", size = 211379, upload-time = "2025-03-30T20:34:53.904Z" }, + { url = "https://files.pythonhosted.org/packages/e9/16/a463389f5ff916963471f7c13585e5f38c6814607306b3cb4d6b4cf13384/coverage-7.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52a523153c568d2c0ef8826f6cc23031dc86cffb8c6aeab92c4ff776e7951b28", size = 211814, upload-time = "2025-03-30T20:34:56.959Z" }, + { url = "https://files.pythonhosted.org/packages/b8/b1/77062b0393f54d79064dfb72d2da402657d7c569cfbc724d56ac0f9c67ed/coverage-7.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c8a5c139aae4c35cbd7cadca1df02ea8cf28a911534fc1b0456acb0b14234f3", size = 240937, upload-time = "2025-03-30T20:34:58.751Z" }, + { url = "https://files.pythonhosted.org/packages/d7/54/c7b00a23150083c124e908c352db03bcd33375494a4beb0c6d79b35448b9/coverage-7.8.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a26c0c795c3e0b63ec7da6efded5f0bc856d7c0b24b2ac84b4d1d7bc578d676", size = 238849, upload-time = "2025-03-30T20:35:00.521Z" }, + { url = "https://files.pythonhosted.org/packages/f7/ec/a6b7cfebd34e7b49f844788fda94713035372b5200c23088e3bbafb30970/coverage-7.8.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:821f7bcbaa84318287115d54becb1915eece6918136c6f91045bb84e2f88739d", size = 239986, upload-time = "2025-03-30T20:35:02.307Z" }, + { url = "https://files.pythonhosted.org/packages/21/8c/c965ecef8af54e6d9b11bfbba85d4f6a319399f5f724798498387f3209eb/coverage-7.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a321c61477ff8ee705b8a5fed370b5710c56b3a52d17b983d9215861e37b642a", size = 239896, upload-time = "2025-03-30T20:35:04.141Z" }, + { url = "https://files.pythonhosted.org/packages/40/83/070550273fb4c480efa8381735969cb403fa8fd1626d74865bfaf9e4d903/coverage-7.8.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ed2144b8a78f9d94d9515963ed273d620e07846acd5d4b0a642d4849e8d91a0c", size = 238613, upload-time = "2025-03-30T20:35:05.889Z" }, + { url = "https://files.pythonhosted.org/packages/07/76/fbb2540495b01d996d38e9f8897b861afed356be01160ab4e25471f4fed1/coverage-7.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:042e7841a26498fff7a37d6fda770d17519982f5b7d8bf5278d140b67b61095f", size = 238909, upload-time = "2025-03-30T20:35:07.76Z" }, + { url = "https://files.pythonhosted.org/packages/a3/7e/76d604db640b7d4a86e5dd730b73e96e12a8185f22b5d0799025121f4dcb/coverage-7.8.0-cp310-cp310-win32.whl", hash = "sha256:f9983d01d7705b2d1f7a95e10bbe4091fabc03a46881a256c2787637b087003f", size = 213948, upload-time = "2025-03-30T20:35:09.144Z" }, + { url = "https://files.pythonhosted.org/packages/5c/a7/f8ce4aafb4a12ab475b56c76a71a40f427740cf496c14e943ade72e25023/coverage-7.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:5a570cd9bd20b85d1a0d7b009aaf6c110b52b5755c17be6962f8ccd65d1dbd23", size = 214844, upload-time = "2025-03-30T20:35:10.734Z" }, { url = "https://files.pythonhosted.org/packages/2b/77/074d201adb8383addae5784cb8e2dac60bb62bfdf28b2b10f3a3af2fda47/coverage-7.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7ac22a0bb2c7c49f441f7a6d46c9c80d96e56f5a8bc6972529ed43c8b694e27", size = 211493, upload-time = "2025-03-30T20:35:12.286Z" }, { url = "https://files.pythonhosted.org/packages/a9/89/7a8efe585750fe59b48d09f871f0e0c028a7b10722b2172dfe021fa2fdd4/coverage-7.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf13d564d310c156d1c8e53877baf2993fb3073b2fc9f69790ca6a732eb4bfea", size = 211921, upload-time = "2025-03-30T20:35:14.18Z" }, { url = "https://files.pythonhosted.org/packages/e9/ef/96a90c31d08a3f40c49dbe897df4f1fd51fb6583821a1a1c5ee30cc8f680/coverage-7.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5761c70c017c1b0d21b0815a920ffb94a670c8d5d409d9b38857874c21f70d7", size = 244556, upload-time = "2025-03-30T20:35:15.616Z" }, @@ -148,6 +184,16 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/58/e9/8fb8e0ff6bef5e170ee19d59ca694f9001b2ec085dc99b4f65c128bb3f9a/coverage-7.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f3c38e4e5ccbdc9198aecc766cedbb134b2d89bf64533973678dfcf07effd883", size = 255116, upload-time = "2025-03-30T20:36:18.033Z" }, { url = "https://files.pythonhosted.org/packages/56/b0/d968ecdbe6fe0a863de7169bbe9e8a476868959f3af24981f6a10d2b6924/coverage-7.8.0-cp313-cp313t-win32.whl", hash = "sha256:379fe315e206b14e21db5240f89dc0774bdd3e25c3c58c2c733c99eca96f1ada", size = 214909, upload-time = "2025-03-30T20:36:19.644Z" }, { url = "https://files.pythonhosted.org/packages/87/e9/d6b7ef9fecf42dfb418d93544af47c940aa83056c49e6021a564aafbc91f/coverage-7.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2e4b6b87bb0c846a9315e3ab4be2d52fac905100565f4b92f02c445c8799e257", size = 216068, upload-time = "2025-03-30T20:36:21.282Z" }, + { url = "https://files.pythonhosted.org/packages/60/0c/5da94be095239814bf2730a28cffbc48d6df4304e044f80d39e1ae581997/coverage-7.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fa260de59dfb143af06dcf30c2be0b200bed2a73737a8a59248fcb9fa601ef0f", size = 211377, upload-time = "2025-03-30T20:36:23.298Z" }, + { url = "https://files.pythonhosted.org/packages/d5/cb/b9e93ebf193a0bb89dbcd4f73d7b0e6ecb7c1b6c016671950e25f041835e/coverage-7.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:96121edfa4c2dfdda409877ea8608dd01de816a4dc4a0523356067b305e4e17a", size = 211803, upload-time = "2025-03-30T20:36:25.74Z" }, + { url = "https://files.pythonhosted.org/packages/78/1a/cdbfe9e1bb14d3afcaf6bb6e1b9ba76c72666e329cd06865bbd241efd652/coverage-7.8.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b8af63b9afa1031c0ef05b217faa598f3069148eeee6bb24b79da9012423b82", size = 240561, upload-time = "2025-03-30T20:36:27.548Z" }, + { url = "https://files.pythonhosted.org/packages/59/04/57f1223f26ac018d7ce791bfa65b0c29282de3e041c1cd3ed430cfeac5a5/coverage-7.8.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:89b1f4af0d4afe495cd4787a68e00f30f1d15939f550e869de90a86efa7e0814", size = 238488, upload-time = "2025-03-30T20:36:29.175Z" }, + { url = "https://files.pythonhosted.org/packages/b7/b1/0f25516ae2a35e265868670384feebe64e7857d9cffeeb3887b0197e2ba2/coverage-7.8.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94ec0be97723ae72d63d3aa41961a0b9a6f5a53ff599813c324548d18e3b9e8c", size = 239589, upload-time = "2025-03-30T20:36:30.876Z" }, + { url = "https://files.pythonhosted.org/packages/e0/a4/99d88baac0d1d5a46ceef2dd687aac08fffa8795e4c3e71b6f6c78e14482/coverage-7.8.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8a1d96e780bdb2d0cbb297325711701f7c0b6f89199a57f2049e90064c29f6bd", size = 239366, upload-time = "2025-03-30T20:36:32.563Z" }, + { url = "https://files.pythonhosted.org/packages/ea/9e/1db89e135feb827a868ed15f8fc857160757f9cab140ffee21342c783ceb/coverage-7.8.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f1d8a2a57b47142b10374902777e798784abf400a004b14f1b0b9eaf1e528ba4", size = 237591, upload-time = "2025-03-30T20:36:34.721Z" }, + { url = "https://files.pythonhosted.org/packages/1b/6d/ac4d6fdfd0e201bc82d1b08adfacb1e34b40d21a22cdd62cfaf3c1828566/coverage-7.8.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cf60dd2696b457b710dd40bf17ad269d5f5457b96442f7f85722bdb16fa6c899", size = 238572, upload-time = "2025-03-30T20:36:36.805Z" }, + { url = "https://files.pythonhosted.org/packages/25/5e/917cbe617c230f7f1745b6a13e780a3a1cd1cf328dbcd0fd8d7ec52858cd/coverage-7.8.0-cp39-cp39-win32.whl", hash = "sha256:be945402e03de47ba1872cd5236395e0f4ad635526185a930735f66710e1bd3f", size = 213966, upload-time = "2025-03-30T20:36:38.551Z" }, + { url = "https://files.pythonhosted.org/packages/bd/93/72b434fe550135869f9ea88dd36068af19afce666db576e059e75177e813/coverage-7.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:90e7fbc6216ecaffa5a880cdc9c77b7418c1dcb166166b78dbc630d07f278cc3", size = 214852, upload-time = "2025-03-30T20:36:40.209Z" }, { url = "https://files.pythonhosted.org/packages/c4/f1/1da77bb4c920aa30e82fa9b6ea065da3467977c2e5e032e38e66f1c57ffd/coverage-7.8.0-pp39.pp310.pp311-none-any.whl", hash = "sha256:b8194fb8e50d556d5849753de991d390c5a1edeeba50f68e3a9253fbd8bf8ccd", size = 203443, upload-time = "2025-03-30T20:36:41.959Z" }, { url = "https://files.pythonhosted.org/packages/59/f1/4da7717f0063a222db253e7121bd6a56f6fb1ba439dcc36659088793347c/coverage-7.8.0-py3-none-any.whl", hash = "sha256:dbf364b4c5e7bae9250528167dfe40219b62e2d573c854d74be213e1e52069f7", size = 203435, upload-time = "2025-03-30T20:36:43.61Z" }, ] @@ -157,6 +203,18 @@ toml = [ { name = "tomli", marker = "python_full_version <= '3.11'" }, ] +[[package]] +name = "exceptiongroup" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" }, +] + [[package]] name = "ghp-import" version = "2.1.0" @@ -205,6 +263,45 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1f/d3/a760d1062e44587230aa65573c70edaad4ee8a0e60e193a3172b304d24d8/griffe-1.6.1-py3-none-any.whl", hash = "sha256:b0131670db16834f82383bcf4f788778853c9bf4dc7a1a2b708bb0808ca56a98", size = 128615, upload-time = "2025-03-18T15:18:43.57Z" }, ] +[[package]] +name = "harp-protocol" +version = "0.2.0a2" +source = { editable = "." } +dependencies = [ + { name = "pyserial" }, +] + +[package.dev-dependencies] +dev = [ + { name = "mkdocs" }, + { name = "mkdocs-codeinclude-plugin" }, + { name = "mkdocs-git-authors-plugin" }, + { name = "mkdocs-git-committers-plugin-2" }, + { name = "mkdocs-material" }, + { name = "mkdocs-monorepo-plugin" }, + { name = "mkdocstrings-python" }, + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [{ name = "pyserial", specifier = ">=3.5" }] + +[package.metadata.requires-dev] +dev = [ + { name = "mkdocs", specifier = ">=1.6.1" }, + { name = "mkdocs-codeinclude-plugin", specifier = ">=0.2.1" }, + { name = "mkdocs-git-authors-plugin", specifier = ">=0.9.4" }, + { name = "mkdocs-git-committers-plugin-2", specifier = ">=2.5.0" }, + { name = "mkdocs-material", specifier = ">=9.6.9" }, + { name = "mkdocs-monorepo-plugin", specifier = ">=1.1.0" }, + { name = "mkdocstrings-python", specifier = ">=1.16.6" }, + { name = "pytest", specifier = ">=8.3.5" }, + { name = "pytest-cov", specifier = ">=6.1.1" }, + { name = "ruff", specifier = ">=0.11.0" }, +] + [[package]] name = "idna" version = "3.10" @@ -214,6 +311,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, ] +[[package]] +name = "importlib-metadata" +version = "8.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641, upload-time = "2025-04-27T15:29:01.736Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd", size = 27656, upload-time = "2025-04-27T15:29:00.214Z" }, +] + [[package]] name = "iniconfig" version = "2.0.0" @@ -239,6 +348,9 @@ wheels = [ name = "markdown" version = "3.7" source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, +] sdist = { url = "https://files.pythonhosted.org/packages/54/28/3af612670f82f4c056911fbbbb42760255801b3068c48de792d354ff4472/markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2", size = 357086, upload-time = "2024-08-16T15:55:17.812Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/3f/08/83871f3c50fc983b88547c196d11cf8c3340e37c32d2e9d6152abe2c61f7/Markdown-3.7-py3-none-any.whl", hash = "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803", size = 106349, upload-time = "2024-08-16T15:55:16.176Z" }, @@ -250,6 +362,16 @@ version = "3.0.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } wheels = [ + { url = "https://files.pythonhosted.org/packages/04/90/d08277ce111dd22f77149fd1a5d4653eeb3b3eaacbdfcbae5afb2600eebd/MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", size = 14357, upload-time = "2024-10-18T15:20:51.44Z" }, + { url = "https://files.pythonhosted.org/packages/04/e1/6e2194baeae0bca1fae6629dc0cbbb968d4d941469cbab11a3872edff374/MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", size = 12393, upload-time = "2024-10-18T15:20:52.426Z" }, + { url = "https://files.pythonhosted.org/packages/1d/69/35fa85a8ece0a437493dc61ce0bb6d459dcba482c34197e3efc829aa357f/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", size = 21732, upload-time = "2024-10-18T15:20:53.578Z" }, + { url = "https://files.pythonhosted.org/packages/22/35/137da042dfb4720b638d2937c38a9c2df83fe32d20e8c8f3185dbfef05f7/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", size = 20866, upload-time = "2024-10-18T15:20:55.06Z" }, + { url = "https://files.pythonhosted.org/packages/29/28/6d029a903727a1b62edb51863232152fd335d602def598dade38996887f0/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", size = 20964, upload-time = "2024-10-18T15:20:55.906Z" }, + { url = "https://files.pythonhosted.org/packages/cc/cd/07438f95f83e8bc028279909d9c9bd39e24149b0d60053a97b2bc4f8aa51/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", size = 21977, upload-time = "2024-10-18T15:20:57.189Z" }, + { url = "https://files.pythonhosted.org/packages/29/01/84b57395b4cc062f9c4c55ce0df7d3108ca32397299d9df00fedd9117d3d/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", size = 21366, upload-time = "2024-10-18T15:20:58.235Z" }, + { url = "https://files.pythonhosted.org/packages/bd/6e/61ebf08d8940553afff20d1fb1ba7294b6f8d279df9fd0c0db911b4bbcfd/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", size = 21091, upload-time = "2024-10-18T15:20:59.235Z" }, + { url = "https://files.pythonhosted.org/packages/11/23/ffbf53694e8c94ebd1e7e491de185124277964344733c45481f32ede2499/MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50", size = 15065, upload-time = "2024-10-18T15:21:00.307Z" }, + { url = "https://files.pythonhosted.org/packages/44/06/e7175d06dd6e9172d4a69a72592cb3f7a996a9c396eee29082826449bbc3/MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", size = 15514, upload-time = "2024-10-18T15:21:01.122Z" }, { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353, upload-time = "2024-10-18T15:21:02.187Z" }, { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392, upload-time = "2024-10-18T15:21:02.941Z" }, { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984, upload-time = "2024-10-18T15:21:03.953Z" }, @@ -290,6 +412,16 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" }, { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" }, { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" }, + { url = "https://files.pythonhosted.org/packages/a7/ea/9b1530c3fdeeca613faeb0fb5cbcf2389d816072fab72a71b45749ef6062/MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a", size = 14344, upload-time = "2024-10-18T15:21:43.721Z" }, + { url = "https://files.pythonhosted.org/packages/4b/c2/fbdbfe48848e7112ab05e627e718e854d20192b674952d9042ebd8c9e5de/MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff", size = 12389, upload-time = "2024-10-18T15:21:44.666Z" }, + { url = "https://files.pythonhosted.org/packages/f0/25/7a7c6e4dbd4f867d95d94ca15449e91e52856f6ed1905d58ef1de5e211d0/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13", size = 21607, upload-time = "2024-10-18T15:21:45.452Z" }, + { url = "https://files.pythonhosted.org/packages/53/8f/f339c98a178f3c1e545622206b40986a4c3307fe39f70ccd3d9df9a9e425/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144", size = 20728, upload-time = "2024-10-18T15:21:46.295Z" }, + { url = "https://files.pythonhosted.org/packages/1a/03/8496a1a78308456dbd50b23a385c69b41f2e9661c67ea1329849a598a8f9/MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29", size = 20826, upload-time = "2024-10-18T15:21:47.134Z" }, + { url = "https://files.pythonhosted.org/packages/e6/cf/0a490a4bd363048c3022f2f475c8c05582179bb179defcee4766fb3dcc18/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0", size = 21843, upload-time = "2024-10-18T15:21:48.334Z" }, + { url = "https://files.pythonhosted.org/packages/19/a3/34187a78613920dfd3cdf68ef6ce5e99c4f3417f035694074beb8848cd77/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0", size = 21219, upload-time = "2024-10-18T15:21:49.587Z" }, + { url = "https://files.pythonhosted.org/packages/17/d8/5811082f85bb88410ad7e452263af048d685669bbbfb7b595e8689152498/MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178", size = 20946, upload-time = "2024-10-18T15:21:50.441Z" }, + { url = "https://files.pythonhosted.org/packages/7c/31/bd635fb5989440d9365c5e3c47556cfea121c7803f5034ac843e8f37c2f2/MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f", size = 15063, upload-time = "2024-10-18T15:21:51.385Z" }, + { url = "https://files.pythonhosted.org/packages/b3/73/085399401383ce949f727afec55ec3abd76648d04b9f22e1c0e99cb4bec3/MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a", size = 15506, upload-time = "2024-10-18T15:21:52.974Z" }, ] [[package]] @@ -309,6 +441,7 @@ dependencies = [ { name = "click" }, { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "ghp-import" }, + { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, { name = "jinja2" }, { name = "markdown" }, { name = "markupsafe" }, @@ -357,6 +490,7 @@ name = "mkdocs-get-deps" version = "0.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, { name = "mergedeep" }, { name = "platformdirs" }, { name = "pyyaml" }, @@ -441,12 +575,14 @@ name = "mkdocstrings" version = "0.29.0" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, { name = "jinja2" }, { name = "markdown" }, { name = "markupsafe" }, { name = "mkdocs" }, { name = "mkdocs-autorefs" }, { name = "pymdown-extensions" }, + { name = "typing-extensions", marker = "python_full_version < '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/8e/4d/a9484dc5d926295bdf308f1f6c4f07fcc99735b970591edc414d401fcc91/mkdocstrings-0.29.0.tar.gz", hash = "sha256:3657be1384543ce0ee82112c3e521bbf48e41303aa0c229b9ffcccba057d922e", size = 1212185, upload-time = "2025-03-10T13:10:11.445Z" } wheels = [ @@ -461,6 +597,7 @@ dependencies = [ { name = "griffe" }, { name = "mkdocs-autorefs" }, { name = "mkdocstrings" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/8e/e7/0691e34e807a8f5c28f0988fcfeeb584f0b569ce433bf341944f14bdb3ff/mkdocstrings_python-1.16.6.tar.gz", hash = "sha256:cefe0f0e17ab4a4611f01b0a2af75e4298664e0ff54feb83c91a485bfed82dc9", size = 201565, upload-time = "2025-03-18T15:34:24.371Z" } wheels = [ @@ -521,45 +658,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" }, ] -[[package]] -name = "pyharp" -version = "0.1.0" -source = { editable = "." } -dependencies = [ - { name = "pyserial" }, -] - -[package.dev-dependencies] -dev = [ - { name = "mkdocs" }, - { name = "mkdocs-codeinclude-plugin" }, - { name = "mkdocs-git-authors-plugin" }, - { name = "mkdocs-git-committers-plugin-2" }, - { name = "mkdocs-material" }, - { name = "mkdocs-monorepo-plugin" }, - { name = "mkdocstrings-python" }, - { name = "pytest" }, - { name = "pytest-cov" }, - { name = "ruff" }, -] - -[package.metadata] -requires-dist = [{ name = "pyserial", specifier = ">=3.5" }] - -[package.metadata.requires-dev] -dev = [ - { name = "mkdocs", specifier = ">=1.6.1" }, - { name = "mkdocs-codeinclude-plugin", specifier = ">=0.2.1" }, - { name = "mkdocs-git-authors-plugin", specifier = ">=0.9.4" }, - { name = "mkdocs-git-committers-plugin-2", specifier = ">=2.5.0" }, - { name = "mkdocs-material", specifier = ">=9.6.9" }, - { name = "mkdocs-monorepo-plugin", specifier = ">=1.1.0" }, - { name = "mkdocstrings-python", specifier = ">=1.16.6" }, - { name = "pytest", specifier = ">=8.3.5" }, - { name = "pytest-cov", specifier = ">=6.1.1" }, - { name = "ruff", specifier = ">=0.11.0" }, -] - [[package]] name = "pymdown-extensions" version = "10.14.3" @@ -588,9 +686,11 @@ version = "8.3.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, { name = "iniconfig" }, { name = "packaging" }, { name = "pluggy" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload-time = "2025-03-02T12:54:54.503Z" } wheels = [ @@ -640,6 +740,15 @@ version = "6.0.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199, upload-time = "2024-08-06T20:31:40.178Z" }, + { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758, upload-time = "2024-08-06T20:31:42.173Z" }, + { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463, upload-time = "2024-08-06T20:31:44.263Z" }, + { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280, upload-time = "2024-08-06T20:31:50.199Z" }, + { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239, upload-time = "2024-08-06T20:31:52.292Z" }, + { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802, upload-time = "2024-08-06T20:31:53.836Z" }, + { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527, upload-time = "2024-08-06T20:31:55.565Z" }, + { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052, upload-time = "2024-08-06T20:31:56.914Z" }, + { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774, upload-time = "2024-08-06T20:31:58.304Z" }, { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612, upload-time = "2024-08-06T20:32:03.408Z" }, { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040, upload-time = "2024-08-06T20:32:04.926Z" }, { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829, upload-time = "2024-08-06T20:32:06.459Z" }, @@ -667,6 +776,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" }, { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" }, { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, + { url = "https://files.pythonhosted.org/packages/65/d8/b7a1db13636d7fb7d4ff431593c510c8b8fca920ade06ca8ef20015493c5/PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", size = 184777, upload-time = "2024-08-06T20:33:25.896Z" }, + { url = "https://files.pythonhosted.org/packages/0a/02/6ec546cd45143fdf9840b2c6be8d875116a64076218b61d68e12548e5839/PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", size = 172318, upload-time = "2024-08-06T20:33:27.212Z" }, + { url = "https://files.pythonhosted.org/packages/0e/9a/8cc68be846c972bda34f6c2a93abb644fb2476f4dcc924d52175786932c9/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", size = 720891, upload-time = "2024-08-06T20:33:28.974Z" }, + { url = "https://files.pythonhosted.org/packages/e9/6c/6e1b7f40181bc4805e2e07f4abc10a88ce4648e7e95ff1abe4ae4014a9b2/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", size = 722614, upload-time = "2024-08-06T20:33:34.157Z" }, + { url = "https://files.pythonhosted.org/packages/3d/32/e7bd8535d22ea2874cef6a81021ba019474ace0d13a4819c2a4bce79bd6a/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", size = 737360, upload-time = "2024-08-06T20:33:35.84Z" }, + { url = "https://files.pythonhosted.org/packages/d7/12/7322c1e30b9be969670b672573d45479edef72c9a0deac3bb2868f5d7469/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", size = 699006, upload-time = "2024-08-06T20:33:37.501Z" }, + { url = "https://files.pythonhosted.org/packages/82/72/04fcad41ca56491995076630c3ec1e834be241664c0c09a64c9a2589b507/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", size = 723577, upload-time = "2024-08-06T20:33:39.389Z" }, + { url = "https://files.pythonhosted.org/packages/ed/5e/46168b1f2757f1fcd442bc3029cd8767d88a98c9c05770d8b420948743bb/PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", size = 144593, upload-time = "2024-08-06T20:33:46.63Z" }, + { url = "https://files.pythonhosted.org/packages/19/87/5124b1c1f2412bb95c59ec481eaf936cd32f0fe2a7b16b97b81c4c017a6a/PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", size = 162312, upload-time = "2024-08-06T20:33:49.073Z" }, ] [[package]] @@ -787,6 +905,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, ] +[[package]] +name = "typing-extensions" +version = "4.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d1/bc/51647cd02527e87d05cb083ccc402f93e441606ff1f01739a62c8ad09ba5/typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4", size = 107423, upload-time = "2025-06-02T14:52:11.399Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/e0/552843e0d356fbb5256d21449fa957fa4eff3bbc135a74a691ee70c7c5da/typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af", size = 43839, upload-time = "2025-06-02T14:52:10.026Z" }, +] + [[package]] name = "urllib3" version = "2.3.0" @@ -802,6 +929,9 @@ version = "6.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" } wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/56/90994d789c61df619bfc5ce2ecdabd5eeff564e1eb47512bd01b5e019569/watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26", size = 96390, upload-time = "2024-11-01T14:06:24.793Z" }, + { url = "https://files.pythonhosted.org/packages/55/46/9a67ee697342ddf3c6daa97e3a587a56d6c4052f881ed926a849fcf7371c/watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112", size = 88389, upload-time = "2024-11-01T14:06:27.112Z" }, + { url = "https://files.pythonhosted.org/packages/44/65/91b0985747c52064d8701e1075eb96f8c40a79df889e59a399453adfb882/watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3", size = 89020, upload-time = "2024-11-01T14:06:29.876Z" }, { url = "https://files.pythonhosted.org/packages/e0/24/d9be5cd6642a6aa68352ded4b4b10fb0d7889cb7f45814fb92cecd35f101/watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c", size = 96393, upload-time = "2024-11-01T14:06:31.756Z" }, { url = "https://files.pythonhosted.org/packages/63/7a/6013b0d8dbc56adca7fdd4f0beed381c59f6752341b12fa0886fa7afc78b/watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2", size = 88392, upload-time = "2024-11-01T14:06:32.99Z" }, { url = "https://files.pythonhosted.org/packages/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c", size = 89019, upload-time = "2024-11-01T14:06:34.963Z" }, @@ -811,6 +941,13 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480, upload-time = "2024-11-01T14:06:42.952Z" }, { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451, upload-time = "2024-11-01T14:06:45.084Z" }, { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057, upload-time = "2024-11-01T14:06:47.324Z" }, + { url = "https://files.pythonhosted.org/packages/05/52/7223011bb760fce8ddc53416beb65b83a3ea6d7d13738dde75eeb2c89679/watchdog-6.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e6f0e77c9417e7cd62af82529b10563db3423625c5fce018430b249bf977f9e8", size = 96390, upload-time = "2024-11-01T14:06:49.325Z" }, + { url = "https://files.pythonhosted.org/packages/9c/62/d2b21bc4e706d3a9d467561f487c2938cbd881c69f3808c43ac1ec242391/watchdog-6.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:90c8e78f3b94014f7aaae121e6b909674df5b46ec24d6bebc45c44c56729af2a", size = 88386, upload-time = "2024-11-01T14:06:50.536Z" }, + { url = "https://files.pythonhosted.org/packages/ea/22/1c90b20eda9f4132e4603a26296108728a8bfe9584b006bd05dd94548853/watchdog-6.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e7631a77ffb1f7d2eefa4445ebbee491c720a5661ddf6df3498ebecae5ed375c", size = 89017, upload-time = "2024-11-01T14:06:51.717Z" }, + { url = "https://files.pythonhosted.org/packages/30/ad/d17b5d42e28a8b91f8ed01cb949da092827afb9995d4559fd448d0472763/watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881", size = 87902, upload-time = "2024-11-01T14:06:53.119Z" }, + { url = "https://files.pythonhosted.org/packages/5c/ca/c3649991d140ff6ab67bfc85ab42b165ead119c9e12211e08089d763ece5/watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11", size = 88380, upload-time = "2024-11-01T14:06:55.19Z" }, + { url = "https://files.pythonhosted.org/packages/5b/79/69f2b0e8d3f2afd462029031baafb1b75d11bb62703f0e1022b2e54d49ee/watchdog-6.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7a0e56874cfbc4b9b05c60c8a1926fedf56324bb08cfbc188969777940aef3aa", size = 87903, upload-time = "2024-11-01T14:06:57.052Z" }, + { url = "https://files.pythonhosted.org/packages/e2/2b/dc048dd71c2e5f0f7ebc04dd7912981ec45793a03c0dc462438e0591ba5d/watchdog-6.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6439e374fc012255b4ec786ae3c4bc838cd7309a540e5fe0952d03687d8804e", size = 88381, upload-time = "2024-11-01T14:06:58.193Z" }, { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" }, { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" }, { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" }, @@ -822,3 +959,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" }, { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" }, ] + +[[package]] +name = "zipp" +version = "3.23.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, +] From 3cf6d125b6b5132dbf787915dda3ef7572c2b512 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Mon, 30 Jun 2025 10:45:21 +0100 Subject: [PATCH 125/159] Add "include markdown plugin" to docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Grilo --- mkdocs.yml | 1 + pyproject.toml | 1 + uv.lock | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 38 insertions(+) diff --git a/mkdocs.yml b/mkdocs.yml index de3b1b3..245523a 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -7,6 +7,7 @@ plugins: - autorefs - codeinclude - monorepo + - include-markdown - mkdocstrings: handlers: python: diff --git a/pyproject.toml b/pyproject.toml index 934a2a8..807c115 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,6 +22,7 @@ dev = [ "mkdocs-codeinclude-plugin>=0.2.1", "mkdocs-git-authors-plugin>=0.9.4", "mkdocs-git-committers-plugin-2>=2.5.0", + "mkdocs-include-markdown-plugin>=7.1.6", "mkdocs-material>=9.6.9", "mkdocs-monorepo-plugin>=1.1.0", "mkdocstrings-python>=1.16.6", diff --git a/uv.lock b/uv.lock index 26c81af..50bde1d 100644 --- a/uv.lock +++ b/uv.lock @@ -24,6 +24,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0c/37/fb6973edeb700f6e3d6ff222400602ab1830446c25c7b4676d8de93e65b8/backrefs-5.8-py39-none-any.whl", hash = "sha256:a66851e4533fb5b371aa0628e1fee1af05135616b86140c9d787a2ffdf4b8fdc", size = 380336, upload-time = "2025-02-25T16:53:29.858Z" }, ] +[[package]] +name = "bracex" +version = "2.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/63/9a/fec38644694abfaaeca2798b58e276a8e61de49e2e37494ace423395febc/bracex-2.6.tar.gz", hash = "sha256:98f1347cd77e22ee8d967a30ad4e310b233f7754dbf31ff3fceb76145ba47dc7", size = 26642, upload-time = "2025-06-22T19:12:31.254Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/2a/9186535ce58db529927f6cf5990a849aa9e052eea3e2cfefe20b9e1802da/bracex-2.6-py3-none-any.whl", hash = "sha256:0b0049264e7340b3ec782b5cb99beb325f36c3782a32e36e876452fd49a09952", size = 11508, upload-time = "2025-06-22T19:12:29.781Z" }, +] + [[package]] name = "certifi" version = "2025.1.31" @@ -277,6 +286,7 @@ dev = [ { name = "mkdocs-codeinclude-plugin" }, { name = "mkdocs-git-authors-plugin" }, { name = "mkdocs-git-committers-plugin-2" }, + { name = "mkdocs-include-markdown-plugin" }, { name = "mkdocs-material" }, { name = "mkdocs-monorepo-plugin" }, { name = "mkdocstrings-python" }, @@ -294,6 +304,7 @@ dev = [ { name = "mkdocs-codeinclude-plugin", specifier = ">=0.2.1" }, { name = "mkdocs-git-authors-plugin", specifier = ">=0.9.4" }, { name = "mkdocs-git-committers-plugin-2", specifier = ">=2.5.0" }, + { name = "mkdocs-include-markdown-plugin", specifier = ">=7.1.6" }, { name = "mkdocs-material", specifier = ">=9.6.9" }, { name = "mkdocs-monorepo-plugin", specifier = ">=1.1.0" }, { name = "mkdocstrings-python", specifier = ">=1.16.6" }, @@ -526,6 +537,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8e/f5/768590251839a148c188d64779b809bde0e78a306295c18fc29d7fc71ce1/mkdocs_git_committers_plugin_2-2.5.0-py3-none-any.whl", hash = "sha256:1778becf98ccdc5fac809ac7b62cf01d3c67d6e8432723dffbb823307d1193c4", size = 11788, upload-time = "2025-01-30T07:30:45.748Z" }, ] +[[package]] +name = "mkdocs-include-markdown-plugin" +version = "7.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mkdocs" }, + { name = "wcmatch" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2c/17/988d97ac6849b196f54d45ca9c60ca894880c160a512785f03834704b3d9/mkdocs_include_markdown_plugin-7.1.6.tar.gz", hash = "sha256:a0753cb82704c10a287f1e789fc9848f82b6beb8749814b24b03dd9f67816677", size = 23391, upload-time = "2025-06-13T18:25:51.193Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e2/a1/6cf1667a05e5f468e1263fcf848772bca8cc9e358cd57ae19a01f92c9f6f/mkdocs_include_markdown_plugin-7.1.6-py3-none-any.whl", hash = "sha256:7975a593514887c18ecb68e11e35c074c5499cfa3e51b18cd16323862e1f7345", size = 27161, upload-time = "2025-06-13T18:25:49.847Z" }, +] + [[package]] name = "mkdocs-material" version = "9.6.9" @@ -960,6 +984,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" }, ] +[[package]] +name = "wcmatch" +version = "10.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "bracex" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/3e/c0bdc27cf06f4e47680bd5803a07cb3dfd17de84cde92dd217dcb9e05253/wcmatch-10.1.tar.gz", hash = "sha256:f11f94208c8c8484a16f4f48638a85d771d9513f4ab3f37595978801cb9465af", size = 117421, upload-time = "2025-06-22T19:14:02.49Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/d8/0d1d2e9d3fabcf5d6840362adcf05f8cf3cd06a73358140c3a97189238ae/wcmatch-10.1-py3-none-any.whl", hash = "sha256:5848ace7dbb0476e5e55ab63c6bbd529745089343427caa5537f230cc01beb8a", size = 39854, upload-time = "2025-06-22T19:14:00.978Z" }, +] + [[package]] name = "zipp" version = "3.23.0" From c8af5a1d69dfd44a6d6d452359fba404a98ebf3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Mon, 30 Jun 2025 10:46:05 +0100 Subject: [PATCH 126/159] Update version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Grilo --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 807c115..cd83f38 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "harp-protocol" -version = "0.2.0a1" +version = "0.2.0a2" description = "Library for data acquisition and control of devices implementing the Harp protocol." authors = [{ name= "Hardware and Software Platform, Champalimaud Foundation", email="software@research.fchampalimaud.org"}] license = "MIT" From 01b42cf76312e9e650edb1654e7e66bd3d9d8ae1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Tue, 1 Jul 2025 14:32:26 +0100 Subject: [PATCH 127/159] Add dependency to handle dataclasses on documentation generation --- mkdocs.yml | 2 ++ pyproject.toml | 3 ++- uv.lock | 35 +++++++++++++++++++++++++++++++---- 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/mkdocs.yml b/mkdocs.yml index 245523a..c18a114 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -16,6 +16,8 @@ plugins: show_root_heading: true show_submodules: true show_source: false + extensions: + - griffe_fieldz - git-committers: repository: fchampalimaud/pyharp branch: main diff --git a/pyproject.toml b/pyproject.toml index cd83f38..3fdaa18 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,13 +18,14 @@ Documentation = "https://fchampalimaud.github.io/pyharp/" [dependency-groups] dev = [ + "griffe-fieldz>=0.2.1", "mkdocs>=1.6.1", "mkdocs-codeinclude-plugin>=0.2.1", "mkdocs-git-authors-plugin>=0.9.4", "mkdocs-git-committers-plugin-2>=2.5.0", "mkdocs-include-markdown-plugin>=7.1.6", "mkdocs-material>=9.6.9", - "mkdocs-monorepo-plugin>=1.1.0", + "mkdocs-monorepo-plugin>=1.1.2", "mkdocstrings-python>=1.16.6", "pytest>=8.3.5", "pytest-cov>=6.1.1", diff --git a/uv.lock b/uv.lock index 50bde1d..343c1ec 100644 --- a/uv.lock +++ b/uv.lock @@ -224,6 +224,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" }, ] +[[package]] +name = "fieldz" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/90/62/698c5cc2e7d4c8c89e63033e2e9d3c74902a1bf28782712eacb0653097ce/fieldz-0.1.2.tar.gz", hash = "sha256:0448ed5dacb13eaa49da0db786e87fae298fbd2652d26c510e5d7aea6b6bebf4", size = 17277, upload-time = "2025-06-30T18:06:40.881Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/8c/8958392cade27a272daf45d09a08473073dedeccad94b097dfeb898d969f/fieldz-0.1.2-py3-none-any.whl", hash = "sha256:e25884d2821a2d5638ef8d4d8bce5d1039359cfcb46d0f93df8cb1f7c2eb3a2e", size = 17878, upload-time = "2025-06-30T18:06:39.322Z" }, +] + [[package]] name = "ghp-import" version = "2.1.0" @@ -272,6 +284,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1f/d3/a760d1062e44587230aa65573c70edaad4ee8a0e60e193a3172b304d24d8/griffe-1.6.1-py3-none-any.whl", hash = "sha256:b0131670db16834f82383bcf4f788778853c9bf4dc7a1a2b708bb0808ca56a98", size = 128615, upload-time = "2025-03-18T15:18:43.57Z" }, ] +[[package]] +name = "griffe-fieldz" +version = "0.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fieldz" }, + { name = "griffe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/95/5c47b6c54d106c76aad9b51177401ad4215a0de910c6853b689a2c6d07da/griffe_fieldz-0.2.1.tar.gz", hash = "sha256:7371693cdd045ac95aaebd16a81586d8dfa72c30e2d4519e0dde0d80f8ef0dc7", size = 8148, upload-time = "2025-01-12T20:55:09.063Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6f/fc/c059d128d77a369f54a3753162a38cd7668e5b4d14464672001a50cf2e9d/griffe_fieldz-0.2.1-py3-none-any.whl", hash = "sha256:04ae78b487c832a38b0495f971784d513da413b867c51e429f39d74f76d4f941", size = 5691, upload-time = "2025-01-12T20:55:07.634Z" }, +] + [[package]] name = "harp-protocol" version = "0.2.0a2" @@ -282,6 +307,7 @@ dependencies = [ [package.dev-dependencies] dev = [ + { name = "griffe-fieldz" }, { name = "mkdocs" }, { name = "mkdocs-codeinclude-plugin" }, { name = "mkdocs-git-authors-plugin" }, @@ -300,13 +326,14 @@ requires-dist = [{ name = "pyserial", specifier = ">=3.5" }] [package.metadata.requires-dev] dev = [ + { name = "griffe-fieldz", specifier = ">=0.2.1" }, { name = "mkdocs", specifier = ">=1.6.1" }, { name = "mkdocs-codeinclude-plugin", specifier = ">=0.2.1" }, { name = "mkdocs-git-authors-plugin", specifier = ">=0.9.4" }, { name = "mkdocs-git-committers-plugin-2", specifier = ">=2.5.0" }, { name = "mkdocs-include-markdown-plugin", specifier = ">=7.1.6" }, { name = "mkdocs-material", specifier = ">=9.6.9" }, - { name = "mkdocs-monorepo-plugin", specifier = ">=1.1.0" }, + { name = "mkdocs-monorepo-plugin", specifier = ">=1.1.2" }, { name = "mkdocstrings-python", specifier = ">=1.16.6" }, { name = "pytest", specifier = ">=8.3.5" }, { name = "pytest-cov", specifier = ">=6.1.1" }, @@ -583,15 +610,15 @@ wheels = [ [[package]] name = "mkdocs-monorepo-plugin" -version = "1.1.0" +version = "1.1.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mkdocs" }, { name = "python-slugify" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f7/6c/5b2a34fd63fe20724e2edf1879e977b40453efe40e1c385a05f38b420664/mkdocs-monorepo-plugin-1.1.0.tar.gz", hash = "sha256:ccc566e166aac5ae7fade498c15c4a337a4892d238629b51aba8ef3fc7099034", size = 13435, upload-time = "2024-01-04T14:29:15.892Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d4/6a/a75245020e44beb9d7c806158f8a2cda37597711409d40c5a37c70078a7e/mkdocs-monorepo-plugin-1.1.2.tar.gz", hash = "sha256:09200bcf837ad35070e6da973aa0cb682e69ed6e16f254a30584550c6d2d8ebb", size = 13723, upload-time = "2025-06-05T19:09:45.042Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d3/33/4cc6c70223aee511244f8fe7706df70d1cd253d1446ab466c73f9dfbaab5/mkdocs_monorepo_plugin-1.1.0-py3-none-any.whl", hash = "sha256:7bbfd9756a7fdecf64d6105dad96cce7e7bb5f0d6cfc2bfda31a1919c77cc3b9", size = 14312, upload-time = "2024-01-04T14:29:14.09Z" }, + { url = "https://files.pythonhosted.org/packages/57/26/4f4c19457d1d4e6d571a3b092921b7a0ce9477d18d997755ac615d72b96b/mkdocs_monorepo_plugin-1.1.2-py3-none-any.whl", hash = "sha256:4b917bc224b89e34e1736bb31ad5ae9deb0a907da879e03bb9454b41fb8b1cac", size = 14539, upload-time = "2025-06-05T19:09:43.74Z" }, ] [[package]] From 7ec55183d63fe9370e408ab0c20d1d2a158592bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Tue, 1 Jul 2025 14:33:37 +0100 Subject: [PATCH 128/159] Update mkdocs config - Removed emojis - Added footer next/prev navigation - Added copyright to footer - Added github link to footer --- mkdocs.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/mkdocs.yml b/mkdocs.yml index c18a114..97b0140 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,6 +1,7 @@ site_name: pyharp repo_url: "https://github.com/fchampalimaud/pyharp" # repo_name: "pyharp" +copyright: Copyright © 2025, Hardware and Software Platform, Champalimaud Foundation plugins: - search @@ -29,9 +30,6 @@ markdown_extensions: - attr_list - admonition - pymdownx.details - - pymdownx.emoji: - emoji_index: !!python/name:material.extensions.emoji.twemoji - emoji_generator: !!python/name:material.extensions.emoji.to_svg - pymdownx.highlight: anchor_linenums: true line_spans: __span @@ -55,6 +53,7 @@ theme: # - navigation.sections - navigation.indexes - navigation.expand + - navigation.footer palette: - media: "(prefers-color-scheme)" toggle: @@ -87,7 +86,12 @@ nav: - Protocol: api/protocol.md - Device: api/device.md - Messages: api/messages.md - - Devices: '*include ./harp.devices/*/mkdocs.yml' + - Devices: '*include ../harp.devices/*/mkdocs.yml' + +extra: + social: + - icon: fontawesome/brands/github + link: https://github.com/fchampalimaud/pyharp extra_css: - stylesheets/extra.css From a97fd0a80244e41a2238fc16bab9b9b37ffb9f47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 2 Jul 2025 10:16:35 +0100 Subject: [PATCH 129/159] Update docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Grilo --- harp/communication/device.py | 186 +++++++++++++++--------------- harp/communication/harp_serial.py | 10 +- harp/protocol/base.py | 160 ++++++++++++------------- harp/protocol/exceptions.py | 8 ++ harp/protocol/messages.py | 62 +++++----- 5 files changed, 217 insertions(+), 209 deletions(-) diff --git a/harp/communication/device.py b/harp/communication/device.py index 5647293..6662c62 100644 --- a/harp/communication/device.py +++ b/harp/communication/device.py @@ -29,27 +29,27 @@ class Device: Attributes ---------- WHO_AM_I : int - the device ID number. A list of devices can be found [here](https://github.com/harp-tech/protocol/blob/main/whoami.md) + The device ID number. A list of devices can be found [here](https://github.com/harp-tech/protocol/blob/main/whoami.md) DEFAULT_DEVICE_NAME : str - the device name, i.e. "Behavior". This name is derived by cross-referencing the `WHO_AM_I` identifier with the corresponding device name in the `device_names` dictionary + The device name, i.e. "Behavior". This name is derived by cross-referencing the `WHO_AM_I` identifier with the corresponding device name in the `device_names` dictionary HW_VERSION_H : int - the major hardware version + The major hardware version HW_VERSION_L : int - the minor hardware version + The minor hardware version ASSEMBLY_VERSION : int - the version of the assembled components + The version of the assembled components HARP_VERSION_H : int - the major Harp core version + The major Harp core version HARP_VERSION_L : int - the minor Harp core version + The minor Harp core version FIRMWARE_VERSION_H : int - the major firmware version + The major firmware version FIRMWARE_VERSION_L : int - the minor firmware version + The minor firmware version DEVICE_NAME : str - the device name stored in the Harp device + The device name stored in the Harp device SERIAL_NUMBER : int, optional - the serial number of the device + The serial number of the device """ WHO_AM_I: int @@ -83,9 +83,9 @@ def __init__( Parameters ---------- serial_port : str - the serial port used to establish the connection with the Harp device. It must be denoted as `/dev/ttyUSBx` in Linux and `COMx` in Windows, where `x` is the number of the serial port + The serial port used to establish the connection with the Harp device. It must be denoted as `/dev/ttyUSBx` in Linux and `COMx` in Windows, where `x` is the number of the serial port dump_file_path: str, optional - the binary file to which all Harp messages will be written + The binary file to which all Harp messages will be written read_timeout_s: float, optional _TODO_ """ @@ -170,7 +170,7 @@ def _read_device_mode(self) -> OperationMode: Returns ------- DeviceMode - the current device mode + The current device mode """ address = CommonRegisters.OPERATION_CTRL reply = self.send(HarpMessage.create(MessageType.READ, address, PayloadType.U8)) @@ -184,7 +184,7 @@ def dump_registers(self) -> list: Returns ------- list - the list containing the reply Harp messages for all the device's registers + The list containing the reply Harp messages for all the device's registers """ address = CommonRegisters.OPERATION_CTRL reg_value = self.send( @@ -213,12 +213,12 @@ def set_mode(self, mode: OperationMode) -> ReplyHarpMessage: Parameters ---------- mode : DeviceMode - the new device mode value + The new device mode value Returns ------- ReplyHarpMessage - the reply to the Harp message + The reply to the Harp message """ address = CommonRegisters.OPERATION_CTRL @@ -245,12 +245,12 @@ def alive_en(self, enable: bool) -> bool: Parameters ---------- enable : bool - If True, enables the ALIVE_EN bit. If False, disables it. + If True, enables the ALIVE_EN bit. If False, disables it Returns ------- bool - True if the operation was successful, False otherwise. + True if the operation was successful, False otherwise """ address = CommonRegisters.OPERATION_CTRL @@ -277,12 +277,12 @@ def op_led_en(self, enable: bool) -> bool: Parameters ---------- enable : bool - If True, enables the operation LED. If False, disables it. + If True, enables the operation LED. If False, disables it Returns ------- bool - True if the operation was successful, False otherwise. + True if the operation was successful, False otherwise """ address = CommonRegisters.OPERATION_CTRL @@ -309,12 +309,12 @@ def status_led(self, enable: bool) -> bool: Parameters ---------- enable : bool - If True, enables the status led. If False, disables it. + If True, enables the status led. If False, disables it Returns ------- bool - True if the operation was successful, False otherwise. + True if the operation was successful, False otherwise """ address = CommonRegisters.OPERATION_CTRL @@ -341,12 +341,12 @@ def mute_reply(self, enable: bool) -> bool: Parameters ---------- enable : bool - If True, the Replies to all the Commands are muted. If False, un-mutes them. + If True, the Replies to all the Commands are muted. If False, un-mutes them Returns ------- bool - True if the operation was successful, False otherwise. + True if the operation was successful, False otherwise """ address = CommonRegisters.OPERATION_CTRL @@ -375,7 +375,7 @@ def reset_device( Returns ------- ReplyHarpMessage - the reply to the Harp message + The reply to the Harp message """ address = CommonRegisters.RESET_DEV reply = self.send( @@ -391,12 +391,12 @@ def set_clock_config(self, clock_config: ClockConfig) -> ReplyHarpMessage: Parameters ---------- clock_config : ClockConfig - the clock configuration value + The clock configuration value Returns ------- ReplyHarpMessage - the reply to the Harp message + The reply to the Harp message """ address = CommonRegisters.CLOCK_CONFIG reply = self.send( @@ -412,12 +412,12 @@ def set_timestamp_offset(self, timestamp_offset: int) -> ReplyHarpMessage: Parameters ---------- timestamp_offset : int - the timestamp offset value + The timestamp offset value Returns ------- ReplyHarpMessage - the reply to the Harp message + The reply to the Harp message """ address = CommonRegisters.TIMESTAMP_OFFSET reply = self.send( @@ -435,12 +435,12 @@ def send(self, message: HarpMessage) -> Optional[ReplyHarpMessage]: Parameters ---------- message : HarpMessage - the HarpMessage containing the message to be sent to the device + The HarpMessage containing the message to be sent to the device Returns ------- Optional[ReplyHarpMessage] - the reply to the Harp message or None if no reply is given + The reply to the Harp message or None if no reply is given """ self._ser.write(message.frame) @@ -459,7 +459,7 @@ def _read(self) -> Union[ReplyHarpMessage, None]: Returns ------- Union[ReplyHarpMessage, None] - the incoming Harp message in case it exists + The incoming Harp message in case it exists """ try: return self._ser.msg_q.get(block=True, timeout=self._read_timeout_s) @@ -480,7 +480,7 @@ def get_events(self) -> list[ReplyHarpMessage]: Returns ------- list - the list containing every Harp event message that were on the queue + The list containing every Harp event message that were on the queue """ msgs = [] while True: @@ -497,7 +497,7 @@ def event_count(self) -> int: Returns ------- int - the number of events in the event queue + The number of events in the event queue """ return self._ser.event_q.qsize() @@ -508,12 +508,12 @@ def read_u8(self, address: int) -> ReplyHarpMessage: Parameters ---------- address : int - the register to be read + The register to be read Returns ------- ReplyHarpMessage - the reply to the Harp message that will contain the value read from the register + The reply to the Harp message that will contain the value read from the register """ return self.send( HarpMessage.create( @@ -530,12 +530,12 @@ def read_s8(self, address: int) -> ReplyHarpMessage: Parameters ---------- address : int - the register to be read + The register to be read Returns ------- ReplyHarpMessage - the reply to the Harp message that will contain the value read from the register + The reply to the Harp message that will contain the value read from the register """ return self.send( HarpMessage.create( @@ -552,12 +552,12 @@ def read_u16(self, address: int) -> ReplyHarpMessage: Parameters ---------- address : int - the register to be read + The register to be read Returns ------- ReplyHarpMessage - the reply to the Harp message that will contain the value read from the register + The reply to the Harp message that will contain the value read from the register """ return self.send( HarpMessage.create( @@ -574,12 +574,12 @@ def read_s16(self, address: int) -> ReplyHarpMessage: Parameters ---------- address : int - the register to be read + The register to be read Returns ------- ReplyHarpMessage - the reply to the Harp message that will contain the value read from the register + The reply to the Harp message that will contain the value read from the register """ return self.send( HarpMessage.create( @@ -596,12 +596,12 @@ def read_u32(self, address: int) -> ReplyHarpMessage: Parameters ---------- address : int - the register to be read + The register to be read Returns ------- ReplyHarpMessage - the reply to the Harp message that will contain the value read from the register + The reply to the Harp message that will contain the value read from the register """ return self.send( HarpMessage.create( @@ -618,12 +618,12 @@ def read_s32(self, address: int) -> ReplyHarpMessage: Parameters ---------- address : int - the register to be read + The register to be read Returns ------- ReplyHarpMessage - the reply to the Harp message that will contain the value read from the register + The reply to the Harp message that will contain the value read from the register """ return self.send( HarpMessage.create( @@ -640,12 +640,12 @@ def read_u64(self, address: int) -> ReplyHarpMessage: Parameters ---------- address : int - the register to be read + The register to be read Returns ------- ReplyHarpMessage - the reply to the Harp message that will contain the value read from the register + The reply to the Harp message that will contain the value read from the register """ return self.send( HarpMessage.create( @@ -662,12 +662,12 @@ def read_s64(self, address: int) -> ReplyHarpMessage: Parameters ---------- address : int - the register to be read + The register to be read Returns ------- ReplyHarpMessage - the reply to the Harp message that will contain the value read from the register + The reply to the Harp message that will contain the value read from the register """ return self.send( HarpMessage.create( @@ -684,12 +684,12 @@ def read_float(self, address: int) -> ReplyHarpMessage: Parameters ---------- address : int - the register to be read + The register to be read Returns ------- ReplyHarpMessage - the reply to the Harp message that will contain the value read from the register + The reply to the Harp message that will contain the value read from the register """ return self.send( HarpMessage.create( @@ -706,14 +706,14 @@ def write_u8(self, address: int, value: int | list[int]) -> ReplyHarpMessage: Parameters ---------- address : int - the register to be written on + The register to be written on value: int | list[int] - the value to be written to the register + The value to be written to the register Returns ------- ReplyHarpMessage - the reply to the Harp message + The reply to the Harp message """ return self.send( HarpMessage.create( @@ -731,14 +731,14 @@ def write_s8(self, address: int, value: int | list[int]) -> ReplyHarpMessage: Parameters ---------- address : int - the register to be written on + The register to be written on value: int | list[int] - the value to be written to the register + The value to be written to the register Returns ------- ReplyHarpMessage - the reply to the Harp message + The reply to the Harp message """ return self.send( HarpMessage.create( @@ -756,14 +756,14 @@ def write_u16(self, address: int, value: int | list[int]) -> ReplyHarpMessage: Parameters ---------- address : int - the register to be written on + The register to be written on value: int | list[int] - the value to be written to the register + The value to be written to the register Returns ------- ReplyHarpMessage - the reply to the Harp message + The reply to the Harp message """ return self.send( HarpMessage.create( @@ -781,14 +781,14 @@ def write_s16(self, address: int, value: int | list[int]) -> ReplyHarpMessage: Parameters ---------- address : int - the register to be written on + The register to be written on value: int | list[int] - the value to be written to the register + The value to be written to the register Returns ------- ReplyHarpMessage - the reply to the Harp message + The reply to the Harp message """ return self.send( HarpMessage.create( @@ -806,14 +806,14 @@ def write_u32(self, address: int, value: int | list[int]) -> ReplyHarpMessage: Parameters ---------- address : int - the register to be written on + The register to be written on value: int | list[int] - the value to be written to the register + The value to be written to the register Returns ------- ReplyHarpMessage - the reply to the Harp message + The reply to the Harp message """ return self.send( HarpMessage.create( @@ -831,14 +831,14 @@ def write_s32(self, address: int, value: int | list[int]) -> ReplyHarpMessage: Parameters ---------- address : int - the register to be written on + The register to be written on value: int | list[int] - the value to be written to the register + The value to be written to the register Returns ------- ReplyHarpMessage - the reply to the Harp message + The reply to the Harp message """ return self.send( HarpMessage.create( @@ -856,14 +856,14 @@ def write_u64(self, address: int, value: int | list[int]) -> ReplyHarpMessage: Parameters ---------- address : int - the register to be written on + The register to be written on value: int | list[int] - the value to be written to the register + The value to be written to the register Returns ------- ReplyHarpMessage - the reply to the Harp message + The reply to the Harp message """ return self.send( HarpMessage.create( @@ -881,14 +881,14 @@ def write_s64(self, address: int, value: int | list[int]) -> ReplyHarpMessage: Parameters ---------- address : int - the register to be written on + The register to be written on value: int | list[int] - the value to be written to the register + The value to be written to the register Returns ------- ReplyHarpMessage - the reply to the Harp message + The reply to the Harp message """ return self.send( HarpMessage.create( @@ -906,14 +906,14 @@ def write_float(self, address: int, value: float | list[float]) -> ReplyHarpMess Parameters ---------- address : int - the register to be written on + The register to be written on value: int | list[int] - the value to be written to the register + The value to be written to the register Returns ------- ReplyHarpMessage - the reply to the Harp message + The reply to the Harp message """ return self.send( HarpMessage.create( @@ -931,7 +931,7 @@ def _read_who_am_i(self) -> int: Returns ------- int - the value of the `WHO_AM_I` register. + The value of the `WHO_AM_I` register """ address = CommonRegisters.WHO_AM_I @@ -948,7 +948,7 @@ def _read_default_device_name(self) -> str: Returns ------- str - the default device name. + The default device name """ return device_names.get(self.WHO_AM_I, "Unknown device") @@ -959,7 +959,7 @@ def _read_hw_version_h(self) -> int: Returns ------- int - the value of the `HW_VERSION_H` register. + The value of the `HW_VERSION_H` register """ address = CommonRegisters.HW_VERSION_H @@ -976,7 +976,7 @@ def _read_hw_version_l(self) -> int: Returns ------- int - the value of the `HW_VERSION_L` register. + The value of the `HW_VERSION_L` register """ address = CommonRegisters.HW_VERSION_L @@ -993,7 +993,7 @@ def _read_assembly_version(self) -> int: Returns ------- int - the value of the `ASSEMBLY_VERSION` register. + The value of the `ASSEMBLY_VERSION` register """ address = CommonRegisters.ASSEMBLY_VERSION @@ -1010,7 +1010,7 @@ def _read_harp_version_h(self) -> int: Returns ------- int - the value of the `HARP_VERSION_H` register. + The value of the `HARP_VERSION_H` register """ address = CommonRegisters.HARP_VERSION_H @@ -1027,7 +1027,7 @@ def _read_harp_version_l(self) -> int: Returns ------- int - the value of the `HARP_VERSION_L` register. + The value of the `HARP_VERSION_L` register """ address = CommonRegisters.HARP_VERSION_L @@ -1044,7 +1044,7 @@ def _read_fw_version_h(self) -> int: Returns ------- int - the value of the `FW_VERSION_H` register. + The value of the `FW_VERSION_H` register """ address = CommonRegisters.FIRMWARE_VERSION_H @@ -1061,7 +1061,7 @@ def _read_fw_version_l(self) -> int: Returns ------- int - the value of the `FW_VERSION_L` register. + The value of the `FW_VERSION_L` register """ address = CommonRegisters.FIRMWARE_VERSION_L @@ -1078,7 +1078,7 @@ def _read_device_name(self) -> str: Returns ------- int - the value of the `DEVICE_NAME` register. + The value of the `DEVICE_NAME` register """ address = CommonRegisters.DEVICE_NAME @@ -1095,7 +1095,7 @@ def _read_serial_number(self) -> int: Returns ------- int - the value of the `SERIAL_NUMBER` register. + The value of the `SERIAL_NUMBER` register """ address = CommonRegisters.SERIAL_NUMBER @@ -1115,7 +1115,7 @@ def _read_clock_config(self) -> int: Returns ------- int - the value of the `CLOCK_CONFIG` register. + The value of the `CLOCK_CONFIG` register """ address = CommonRegisters.CLOCK_CONFIG @@ -1132,7 +1132,7 @@ def _read_timestamp_offset(self) -> int: Returns ------- int - the value of the `TIMESTAMP_OFFSET` register. + The value of the `TIMESTAMP_OFFSET` register """ address = CommonRegisters.TIMESTAMP_OFFSET diff --git a/harp/communication/harp_serial.py b/harp/communication/harp_serial.py index d5d5578..c966d79 100644 --- a/harp/communication/harp_serial.py +++ b/harp/communication/harp_serial.py @@ -22,7 +22,7 @@ def __init__(self, read_q: queue.Queue, *args, **kwargs): Parameters ---------- read_q : queue.Queue - the queue to where the data received will be put + The queue to where the data received will be put """ self._read_q = read_q self._buffer = bytearray() @@ -46,7 +46,7 @@ def data_received(self, data: bytes) -> None: Parameters ---------- data : bytes - the data received from the serial communication + The data received from the serial communication """ self._buffer.extend(data) while True: @@ -83,9 +83,9 @@ class HarpSerial: Attributes ---------- msg_q : queue.Queue - the queue containing the Harp messages that are not of the type `MessageType.EVENT` + The queue containing the Harp messages that are not of the type `MessageType.EVENT` event_q : queue.Queue - the queue containing the Harp messages of `MessageType.EVENT` + The queue containing the Harp messages of `MessageType.EVENT` """ msg_q: queue.Queue @@ -96,7 +96,7 @@ def __init__(self, serial_port: str, **kwargs): Parameters ---------- serial_port : str - the serial port used to establish the connection with the Harp device. It must be denoted as `/dev/ttyUSBx` in Linux and `COMx` in Windows, where `x` is the number of the serial port + The serial port used to establish the connection with the Harp device. It must be denoted as `/dev/ttyUSBx` in Linux and `COMx` in Windows, where `x` is the number of the serial port """ # Connect to the Harp device self._ser = serial.Serial(serial_port, **kwargs) diff --git a/harp/protocol/base.py b/harp/protocol/base.py index fe3ad59..a634f52 100644 --- a/harp/protocol/base.py +++ b/harp/protocol/base.py @@ -14,15 +14,15 @@ class MessageType(IntEnum): Attributes ---------- READ : int - the value that corresponds to a Read Harp message (1) + The value that corresponds to a Read Harp message (1) WRITE : int - the value that corresponds to a Write Harp message (2) + The value that corresponds to a Write Harp message (2) EVENT : int - the value that corresponds to an Event Harp message (3). Messages of this type are only meant to be send by the device + The value that corresponds to an Event Harp message (3). Messages of this type are only meant to be send by the device READ_ERROR : int - the value that corresponds to a Read Error Harp message (9). Messages of this type are only meant to be send by the device + The value that corresponds to a Read Error Harp message (9). Messages of this type are only meant to be send by the device WRITE_ERROR : int - the value that corresponds to a Write Error Harp message (10). Messages of this type are only meant to be send by the device + The value that corresponds to a Write Error Harp message (10). Messages of this type are only meant to be send by the device """ READ: int = 1 @@ -38,44 +38,44 @@ class PayloadType(IntEnum): Attributes ---------- - U8 : PayloadType - the value that corresponds to a message of type U8 - S8 : PayloadType - the value that corresponds to a message of type S8 - U16 : PayloadType - the value that corresponds to a message of type U16 - S16 : PayloadType - the value that corresponds to a message of type S16 - U32 : PayloadType - the value that corresponds to a message of type U32 - S32 : PayloadType - the value that corresponds to a message of type S32 - U64 : PayloadType - the value that corresponds to a message of type U64 - S64 : PayloadType - the value that corresponds to a message of type S64 - Float : PayloadType - the value that corresponds to a message of type Float - Timestamp: PayloadType - the value that corresponds to a message of type Timestamp. This is not a valid PayloadType, but it is used to indicate that the message has a timestamp. - TimestampedU8 : PayloadType - the value that corresponds to a message of type TimestampedU8 - TimestampedS8 : PayloadType - the value that corresponds to a message of type TimestampedS8 - TimestampedU16 : PayloadType - the value that corresponds to a message of type TimestampedU16 - TimestampedS16 : PayloadType - the value that corresponds to a message of type TimestampedS16 - TimestampedU32 : PayloadType - the value that corresponds to a message of type TimestampedU32 - TimestampedS32 : PayloadType - the value that corresponds to a message of type TimestampedS32 - TimestampedU64 : PayloadType - the value that corresponds to a message of type TimestampedU64 - TimestampedS64 : PayloadType - the value that corresponds to a message of type TimestampedS64 - TimestampedFloat : PayloadType - the value that corresponds to a message of type TimestampedFloat + U8 : int + The value that corresponds to a message of type U8 + S8 : int + The value that corresponds to a message of type S8 + U16 : int + The value that corresponds to a message of type U16 + S16 : int + The value that corresponds to a message of type S16 + U32 : int + The value that corresponds to a message of type U32 + S32 : int + The value that corresponds to a message of type S32 + U64 : int + The value that corresponds to a message of type U64 + S64 : int + The value that corresponds to a message of type S64 + Float : int + The value that corresponds to a message of type Float + Timestamp: int + The value that corresponds to a message of type Timestamp. This is not a valid PayloadType, but it is used to indicate that the message has a timestamp. + TimestampedU8 : int + The value that corresponds to a message of type TimestampedU8 + TimestampedS8 : int + The value that corresponds to a message of type TimestampedS8 + TimestampedU16 : int + The value that corresponds to a message of type TimestampedU16 + TimestampedS16 : int + The value that corresponds to a message of type TimestampedS16 + TimestampedU32 : int + The value that corresponds to a message of type TimestampedU32 + TimestampedS32 : int + The value that corresponds to a message of type TimestampedS32 + TimestampedU64 : int + The value that corresponds to a message of type TimestampedU64 + TimestampedS64 : int + The value that corresponds to a message of type TimestampedS64 + TimestampedFloat : int + The value that corresponds to a message of type TimestampedFloat """ U8 = _isUnsigned | 1 @@ -106,37 +106,37 @@ class CommonRegisters(IntEnum): Attributes ---------- WHO_AM_I : int - the number of the `WHO_AM_I` register + The number of the `WHO_AM_I` register HW_VERSION_H : int - the number of the `HW_VERSION_H` register + The number of the `HW_VERSION_H` register HW_VERSION_L : int - the number of the `HW_VERSION_L` register + The number of the `HW_VERSION_L` register ASSEMBLY_VERSION : int - the number of the `ASSEMBLY_VERSION` register + The number of the `ASSEMBLY_VERSION` register HARP_VERSION_H : int - the number of the `HARP_VERSION_H` register + The number of the `HARP_VERSION_H` register HARP_VERSION_L : int - the number of the `HARP_VERSION_L` register + The number of the `HARP_VERSION_L` register FIRMWARE_VERSION_H : int - the number of the `FIRMWARE_VERSION_H` register + The number of the `FIRMWARE_VERSION_H` register FIRMWARE_VERSION_L : int - the number of the `FIRMWARE_VERSION_L` register + The number of the `FIRMWARE_VERSION_L` register TIMESTAMP_SECOND : int - the number of the `TIMESTAMP_SECOND` register + The number of the `TIMESTAMP_SECOND` register TIMESTAMP_MICRO : int - the number of the `TIMESTAMP_MICRO` register + The number of the `TIMESTAMP_MICRO` register OPERATION_CTRL : int - the number of the `OPERATION_CTRL` register + The number of the `OPERATION_CTRL` register RESET_DEV : int - the number of the `RESET_DEV` register + The number of the `RESET_DEV` register DEVICE_NAME : int - the number of the `DEVICE_NAME` register + The number of the `DEVICE_NAME` register SERIAL_NUMBER : int - the number of the `SERIAL_NUMBER` register + The number of the `SERIAL_NUMBER` register CLOCK_CONFIG : int - the number of the `CLOCK_CONFIG` register + The number of the `CLOCK_CONFIG` register TIMESTAMP_OFFSET : int - the number of the `TIMESTAMP_OFFSET` register + The number of the `TIMESTAMP_OFFSET` register """ WHO_AM_I = 0x00 @@ -164,13 +164,13 @@ class OperationMode(IntEnum): Attributes ---------- STANDBY : int - the value that corresponds to the Standby operation mode (0). The device has all the Events turned off. + The value that corresponds to the Standby operation mode (0). The device has all the Events turned off ACTIVE : int - the value that corresponds to the Active operation mode (1). The device turns ON the Events detection. Only the enabled Events will be operating. + The value that corresponds to the Active operation mode (1). The device turns ON the Events detection. Only the enabled Events will be operating RESERVED : int - the value that corresponds to the Reserved operation mode (2) + The value that corresponds to the Reserved operation mode (2) SPEED : int - the value that corresponds to the Speed operation mode (3). The device enters Speed Mode. + The value that corresponds to the Speed operation mode (3). The device enters Speed Mode """ STANDBY = 0 @@ -192,15 +192,15 @@ class OperationCtrl(IntFlag): 2: Reserved 3: Speed Mode (device enters Speed Mode, optional; only responds to Speed Mode commands) DUMP : int - Bit 3 (0x08): When set to 1, the device adds the content of all registers to the streaming buffer as Read messages. Always read as 0. + Bit 3 (0x08): When set to 1, the device adds the content of all registers to the streaming buffer as Read messages. Always read as 0 MUTE_RPL : int - Bit 4 (0x10): If set to 1, replies to all commands are muted (not sent by the device). + Bit 4 (0x10): If set to 1, replies to all commands are muted (not sent by the device) VISUALEN : int - Bit 5 (0x20): If set to 1, visual indications (e.g., LEDs) operate. If 0, all visual indications are turned off. + Bit 5 (0x20): If set to 1, visual indications (e.g., LEDs) operate. If 0, all visual indications are turned off OPLEDEN : int - Bit 6 (0x40): If set to 1, the LED indicates the selected Operation Mode (see LED feedback table in documentation). + Bit 6 (0x40): If set to 1, the LED indicates the selected Operation Mode (see LED feedback table in documentation) ALIVE_EN : int - Bit 7 (0x80): If set to 1, the device sends an Event Message with the R_TIMESTAMP_SECONDS content each second (heartbeat). + Bit 7 (0x80): If set to 1, the device sends an Event Message with the R_TIMESTAMP_SECONDS content each second (heartbeat) """ OP_MODE = 3 << 0 @@ -220,19 +220,19 @@ class ResetMode(IntEnum): ---------- RST_DEF : int Bit 0 (0x01): If set, resets the device and restores all registers (Common and Application) to default values. - EEPROM is erased and defaults become the permanent boot option. + EEPROM is erased and defaults become the permanent boot option RST_EE : int Bit 1 (0x02): If set, resets the device and restores all registers (Common and Application) from non-volatile memory (EEPROM). - EEPROM values remain the permanent boot option. + EEPROM values remain the permanent boot option SAVE : int Bit 3 (0x08): If set, saves all non-volatile registers (Common and Application) to EEPROM and reboots. - EEPROM becomes the permanent boot option. + EEPROM becomes the permanent boot option NAME_TO_DEFAULT : int - Bit 4 (0x10): If set, reboots the device with the default name. + Bit 4 (0x10): If set, reboots the device with the default name BOOT_DEF : int - Bit 6 (0x40, read-only): Indicates the device booted with default register values. + Bit 6 (0x40, read-only): Indicates the device booted with default register values BOOT_EE : int - Bit 7 (0x80, read-only): Indicates the device booted with register values saved on the EEPROM. + Bit 7 (0x80, read-only): Indicates the device booted with register values saved on the EEPROM """ RST_DEF = 0x01 @@ -252,20 +252,20 @@ class ClockConfig(IntFlag): ---------- CLK_REP : int Bit 0 (0x01): If set to 1, the device will repeat the Harp Synchronization Clock to the Clock Output connector, if available. - Acts as a daisy-chain by repeating the Clock Input to the Clock Output. Setting this bit also unlocks the Harp Synchronization Clock. + Acts as a daisy-chain by repeating the Clock Input to the Clock Output. Setting this bit also unlocks the Harp Synchronization Clock CLK_GEN : int Bit 1 (0x02): If set to 1, the device will generate Harp Synchronization Clock to the Clock Output connector, if available. - The Clock Input will be ignored. Read as 1 if the device is generating the Harp Synchronization Clock. + The Clock Input will be ignored. Read as 1 if the device is generating the Harp Synchronization Clock REP_ABLE : int - Bit 3 (0x08, read-only): Indicates if the device is able (1) to repeat the Harp Synchronization Clock timestamp. + Bit 3 (0x08, read-only): Indicates if the device is able (1) to repeat the Harp Synchronization Clock timestamp GEN_ABLE : int - Bit 4 (0x10, read-only): Indicates if the device is able (1) to generate the Harp Synchronization Clock timestamp. + Bit 4 (0x10, read-only): Indicates if the device is able (1) to generate the Harp Synchronization Clock timestamp CLK_UNLOCK : int Bit 6 (0x40): If set to 1, the device will unlock the timestamp register counter (R_TIMESTAMP_SECOND) and accept new timestamp values. - Read as 1 if the timestamp register is unlocked. + Read as 1 if the timestamp register is unlocked CLK_LOCK : int Bit 7 (0x80): If set to 1, the device will lock the current timestamp register counter (R_TIMESTAMP_SECOND) and reject new timestamp values. - Read as 1 if the timestamp register is locked. + Read as 1 if the timestamp register is locked """ CLK_REP = 0x01 diff --git a/harp/protocol/exceptions.py b/harp/protocol/exceptions.py index 058e23b..26b74cf 100644 --- a/harp/protocol/exceptions.py +++ b/harp/protocol/exceptions.py @@ -5,6 +5,10 @@ class HarpException(Exception): class HarpWriteException(HarpException): + """ + Exception raised when there is an error writing to a register in the Harp device. + """ + def __init__(self, register, message): super().__init__(f"Error writing to register {register}: {message}") self.register = register @@ -12,6 +16,10 @@ def __init__(self, register, message): class HarpReadException(HarpException): + """ + Exception raised when there is an error reading from a register in the Harp device. + """ + def __init__(self, register, message): super().__init__(f"Error reading from register {register}: {message}") self.register = register diff --git a/harp/protocol/messages.py b/harp/protocol/messages.py index 4437e82..a676195 100644 --- a/harp/protocol/messages.py +++ b/harp/protocol/messages.py @@ -13,19 +13,19 @@ class HarpMessage: Attributes ---------- frame : bytearray - the bytearray containing the whole Harp message + The bytearray containing the whole Harp message message_type : MessageType - the message type + The message type length : int - the length parameter of the Harp message + The length parameter of the Harp message address : int - the address of the register to which the Harp message refers to + The address of the register to which the Harp message refers to port : int - indicates the origin or destination of the Harp message in case the device is a hub of Harp devices. The value 255 points to the device itself (default value). + Indicates the origin or destination of the Harp message in case the device is a hub of Harp devices. The value 255 points to the device itself (default value). payload_type : PayloadType - the payload type + The payload type checksum : int - the sum of all bytes contained in the Harp message + The sum of all bytes contained in the Harp message """ DEFAULT_PORT: int = 255 @@ -40,7 +40,7 @@ def calculate_checksum(self) -> int: Returns ------- int - the value of the checksum + The value of the checksum """ checksum: int = 0 for i in self.frame: @@ -55,7 +55,7 @@ def frame(self) -> bytearray: Returns ------- bytearray - the bytearray containing the whole Harp message + The bytearray containing the whole Harp message """ return self._frame @@ -67,7 +67,7 @@ def message_type(self) -> MessageType: Returns ------- MessageType - the message type + The message type """ return MessageType(self._frame[0]) @@ -79,7 +79,7 @@ def length(self) -> int: Returns ------- int - the length parameter of the Harp message + The length parameter of the Harp message """ return self._frame[1] @@ -91,7 +91,7 @@ def address(self) -> int: Returns ------- int - the address of the register to which the Harp message refers to + The address of the register to which the Harp message refers to """ return self._frame[2] @@ -103,7 +103,7 @@ def port(self) -> int: Returns ------- int - the port value + The port value """ return self._frame[3] @@ -115,7 +115,7 @@ def port(self, value: int) -> None: Parameters ---------- value : int - the port value to set + The port value to set """ self._port = value @@ -127,7 +127,7 @@ def payload_type(self) -> PayloadType: Returns ------- PayloadType - the payload type + The payload type """ return PayloadType(self._frame[4]) @@ -139,7 +139,7 @@ def payload(self) -> Union[int, list[int]]: Returns ------- Union[int, list[int]] - the payload sent in the write Harp message + The payload sent in the write Harp message """ payload_start = self.BASE_LENGTH if self.payload_type & PayloadType.Timestamp: @@ -298,7 +298,7 @@ def checksum(self) -> int: Returns ------- int - the sum of all bytes contained in the Harp message + The sum of all bytes contained in the Harp message """ return self._frame[-1] @@ -310,12 +310,12 @@ def parse(frame: bytearray) -> ReplyHarpMessage: Parameters ---------- frame : bytearray - the bytearray will be parsed into a (reply) Harp message + The bytearray will be parsed into a (reply) Harp message Returns ------- ReplyHarpMessage - the Harp message object parsed from the original bytearray + The Harp message object parsed from the original bytearray """ return ReplyHarpMessage(frame) @@ -332,13 +332,13 @@ def create( Parameters ---------- message_type : MessageType - the message type. It can only be of type READ or WRITE + The message type. It can only be of type READ or WRITE address : int - the address of the register that the message will interact with + The address of the register that the message will interact with payload_type : PayloadType - the payload type + The payload type value: int | list[int] | float | list[float], optional - the payload of the message. If message_type == MessageType.WRITE, the value cannot be None + The payload of the message. If message_type == MessageType.WRITE, the value cannot be None """ if message_type == MessageType.READ: return ReadHarpMessage(payload_type, address) @@ -360,7 +360,7 @@ def __repr__(self) -> str: Returns ------- str - the debug representation of the reply message + The debug representation of the reply message """ return self.__str__() + f"\r\nRaw Frame: {self.frame}" @@ -371,7 +371,7 @@ def __str__(self) -> str: Returns ------- str - the representation of the Harp message + The representation of the Harp message """ payload_str = "" format_str = "" @@ -415,9 +415,9 @@ class ReplyHarpMessage(HarpMessage): Attributes ---------- payload : Union[int, list[int]] - the message payload formatted as the appropriate type + The message payload formatted as the appropriate type timestamp : float - the Harp timestamp at which the message was sent + The Harp timestamp at which the message was sent """ def __init__( @@ -428,7 +428,7 @@ def __init__( Parameters ---------- frame : bytearray - the Harp message in bytearray format + The Harp message in bytearray format """ self._frame = frame @@ -465,7 +465,7 @@ def timestamp(self) -> float: Returns ------- float - the Harp timestamp at which the message was sent + The Harp timestamp at which the message was sent """ return self._timestamp @@ -476,7 +476,7 @@ def payload_as_string(self) -> str: Returns ------- str - the payload parsed as a str + The payload parsed as a str """ return self._raw_payload.decode("utf-8").rstrip("\x00") @@ -508,7 +508,7 @@ class WriteHarpMessage(HarpMessage): Attributes ---------- payload : Union[int, list[int]] - the payload sent in the write Harp message + The payload sent in the write Harp message """ MESSAGE_TYPE: int = MessageType.WRITE From d760086b4b513eb01ea59e764deb09f91eb2d5df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 2 Jul 2025 10:17:02 +0100 Subject: [PATCH 130/159] Add CurrentDriver to known device names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Grilo --- harp/protocol/device_names.py | 1 + 1 file changed, 1 insertion(+) diff --git a/harp/protocol/device_names.py b/harp/protocol/device_names.py index 4b7efcb..3f31656 100644 --- a/harp/protocol/device_names.py +++ b/harp/protocol/device_names.py @@ -33,6 +33,7 @@ 1236: "AnalogInput", 1248: "RgbArray", 1280: "SoundCard", + 1282: "CurrentDriver", 1296: "SyringePump", 1400: "LicketySplit", 1401: "SniffDetector", From aca830a381a52b0dd2abf44e5998811fdb9d951d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 2 Jul 2025 10:17:21 +0100 Subject: [PATCH 131/159] Reorder docs' TOC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Grilo --- mkdocs.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mkdocs.yml b/mkdocs.yml index 97b0140..56f2896 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -76,16 +76,16 @@ theme: nav: - Home: index.md + - API: + - Protocol: api/protocol.md + - Device: api/device.md + - Messages: api/messages.md - Examples: - examples/index.md - Getting Device Info: examples/get_info/get_info.md - Wait for Events: examples/wait_for_events/wait_for_events.md - Read and Write from Registers: examples/read_and_write_from_registers/read_and_write_from_registers.md - Olfactometer Example: examples/olfactometer_example/olfactometer_example.md - - API: - - Protocol: api/protocol.md - - Device: api/device.md - - Messages: api/messages.md - Devices: '*include ../harp.devices/*/mkdocs.yml' extra: From 2113d59e46bb562234d834e42bb314fa724ebfe8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 2 Jul 2025 10:23:22 +0100 Subject: [PATCH 132/159] Remove type annotation on Enum MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Grilo --- harp/protocol/base.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/harp/protocol/base.py b/harp/protocol/base.py index a634f52..2217dcf 100644 --- a/harp/protocol/base.py +++ b/harp/protocol/base.py @@ -25,11 +25,11 @@ class MessageType(IntEnum): The value that corresponds to a Write Error Harp message (10). Messages of this type are only meant to be send by the device """ - READ: int = 1 - WRITE: int = 2 - EVENT: int = 3 - READ_ERROR: int = 9 - WRITE_ERROR: int = 10 + READ = 1 + WRITE = 2 + EVENT = 3 + READ_ERROR = 9 + WRITE_ERROR = 10 class PayloadType(IntEnum): From ece40f72b84a25a45cb7acc1e87f1afa1e91a0d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 2 Jul 2025 10:28:53 +0100 Subject: [PATCH 133/159] Update version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3fdaa18..5700762 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "harp-protocol" -version = "0.2.0a2" +version = "0.2.0" description = "Library for data acquisition and control of devices implementing the Harp protocol." authors = [{ name= "Hardware and Software Platform, Champalimaud Foundation", email="software@research.fchampalimaud.org"}] license = "MIT" From 3515020355193925a9569fa389c7e399c94370d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Thu, 3 Jul 2025 15:41:50 +0100 Subject: [PATCH 134/159] Add github action to build and deploy docs --- .github/workflows/build-docs.yml | 56 ++++++++++++++++++++++++++++++++ mkdocs.yml | 2 +- run_docs.sh | 19 +++++++++++ 3 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/build-docs.yml create mode 100755 run_docs.sh diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml new file mode 100644 index 0000000..72c7f7e --- /dev/null +++ b/.github/workflows/build-docs.yml @@ -0,0 +1,56 @@ +name: Build Documentation + +on: + workflow_dispatch: + +jobs: + build-docs: + runs-on: ubuntu-latest + steps: + - name: Checkout pyharp repository + uses: actions/checkout@v4 + + - name: Clone harp.devices repository + uses: actions/checkout@v4 + with: + repository: fchampalimaud/harp.devices + token: ${{ secrets.HARP_DEVICES_TOKEN }} + path: harp.devices + + - name: Install uv + uses: astral-sh/setup-uv@v5 + + - name: Install project dependencies + run: | + uv sync + + - name: Build documentation + run: | + chmod +x ./run_docs.sh + ./run_docs.sh build + + - name: Upload Build Artifact + uses: actions/upload-pages-artifact@v3 + with: + path: site + + + deploy: + name: Deploy to Github pages + needs: build-docs + + # Grant GITHUB_TOKEN the permissions required to make a Pages deployment + permissions: + pages: write # to deploy to Pages + id-token: write # to verify the deployment originates from an appropriate source + + # Deploy to the github-pages environment + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + + runs-on: ubuntu-latest + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/mkdocs.yml b/mkdocs.yml index 56f2896..1cd5215 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -86,7 +86,7 @@ nav: - Wait for Events: examples/wait_for_events/wait_for_events.md - Read and Write from Registers: examples/read_and_write_from_registers/read_and_write_from_registers.md - Olfactometer Example: examples/olfactometer_example/olfactometer_example.md - - Devices: '*include ../harp.devices/*/mkdocs.yml' + - Devices: '*include ./harp.devices/*/mkdocs.yml' extra: social: diff --git a/run_docs.sh b/run_docs.sh new file mode 100755 index 0000000..d95ab3c --- /dev/null +++ b/run_docs.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +# Find all subdirectories of harp.devices and join them with ':' +HARP_DEVICES_PATHS=$(find -L ./harp.devices -mindepth 1 -maxdepth 1 -type d | paste -sd ":" -) + +# Prepend to PYTHONPATH (preserve existing PYTHONPATH) +export PYTHONPATH="$HARP_DEVICES_PATHS:$PYTHONPATH" + +# Optionally print for debugging +echo "PYTHONPATH set to: $PYTHONPATH" + +# Launch mkdocs build or serve +if [ "$1" = "build" ]; then + uv run mkdocs build +elif [ "$1" = "deploy" ]; then + uv run mkdocs gh-deploy +else + uv run mkdocs serve +fi From 47cfb1d5cd32639237c753d0873a8185bb44d753 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Fri, 18 Jul 2025 10:00:25 +0100 Subject: [PATCH 135/159] Update LICENSE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Grilo --- LICENSE | 1 + 1 file changed, 1 insertion(+) diff --git a/LICENSE b/LICENSE index 0c33b6f..b7afd7f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,6 @@ MIT License +Copyright (c) 2020 OEPS & Filipe Carvalho Copyright (c) 2025 Hardware and Software Platform, Champalimaud Foundation Permission is hereby granted, free of charge, to any person obtaining a copy From 39e3ca354697fa35000651b259e7502129fd64c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Tue, 5 Aug 2025 09:27:13 +0100 Subject: [PATCH 136/159] Rename communication namespace to 'serial' --- harp/{communication => serial}/__init__.py | 0 harp/{communication => serial}/device.py | 5 ++--- harp/{communication => serial}/harp_serial.py | 0 3 files changed, 2 insertions(+), 3 deletions(-) rename harp/{communication => serial}/__init__.py (100%) rename harp/{communication => serial}/device.py (99%) rename harp/{communication => serial}/harp_serial.py (100%) diff --git a/harp/communication/__init__.py b/harp/serial/__init__.py similarity index 100% rename from harp/communication/__init__.py rename to harp/serial/__init__.py diff --git a/harp/communication/device.py b/harp/serial/device.py similarity index 99% rename from harp/communication/device.py rename to harp/serial/device.py index 6662c62..b9e57f6 100644 --- a/harp/communication/device.py +++ b/harp/serial/device.py @@ -7,8 +7,6 @@ from typing import Optional, Union import serial - -from harp.communication.harp_serial import HarpSerial from harp.protocol import ( ClockConfig, CommonRegisters, @@ -20,6 +18,7 @@ ) from harp.protocol.device_names import device_names from harp.protocol.messages import HarpMessage, ReplyHarpMessage +from harp.serial.harp_serial import HarpSerial class Device: @@ -67,7 +66,7 @@ class Device: TIMESTAMP_OFFSET: int _ser: HarpSerial - _dump_file_path: Path + _dump_file_path: Optional[Path] _dump_file: Optional[BufferedWriter] = None _read_timeout_s: float diff --git a/harp/communication/harp_serial.py b/harp/serial/harp_serial.py similarity index 100% rename from harp/communication/harp_serial.py rename to harp/serial/harp_serial.py From 4d9a3a410df70dd22243b4dcd705c5b463b8a2d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Tue, 5 Aug 2025 09:27:55 +0100 Subject: [PATCH 137/159] Remove unnecessary devices folder --- harp/devices/.gitignore | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 harp/devices/.gitignore diff --git a/harp/devices/.gitignore b/harp/devices/.gitignore deleted file mode 100644 index e69de29..0000000 From efa5e046e5c6e0935b1f92fbb287f61a8a10d305 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 27 Aug 2025 10:02:20 +0100 Subject: [PATCH 138/159] Fix issue with Visual_en flag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Grilo --- harp/serial/device.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/harp/serial/device.py b/harp/serial/device.py index b9e57f6..30ef63d 100644 --- a/harp/serial/device.py +++ b/harp/serial/device.py @@ -301,7 +301,7 @@ def op_led_en(self, enable: bool) -> bool: return reply - def status_led(self, enable: bool) -> bool: + def visual_en(self, enable: bool) -> bool: """ Sets the status led of the device. @@ -323,9 +323,9 @@ def status_led(self, enable: bool) -> bool: ).payload if enable: - reg_value |= OperationCtrl.STATUS_LED + reg_value |= OperationCtrl.VISUALEN else: - reg_value &= ~OperationCtrl.STATUS_LED + reg_value &= ~OperationCtrl.VISUALEN reply = self.send( HarpMessage.create(MessageType.WRITE, address, PayloadType.U8, reg_value) From d27c2267d03bfa36b4f8422c3c8b491d59057e73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 27 Aug 2025 10:05:21 +0100 Subject: [PATCH 139/159] Fix documentation path for Device MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Grilo --- docs/api/device.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/device.md b/docs/api/device.md index 451d0eb..a128d21 100644 --- a/docs/api/device.md +++ b/docs/api/device.md @@ -1 +1 @@ -::: harp.communication.Device +::: harp.serial.Device From 77607aead28e8aecf61adced90b7d54ae1e3fd3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 27 Aug 2025 10:08:23 +0100 Subject: [PATCH 140/159] Implement a Timeout handling strategy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Grilo --- harp/protocol/exceptions.py | 8 +++++ harp/serial/device.py | 68 ++++++++++++++++++++++++++++++------- 2 files changed, 64 insertions(+), 12 deletions(-) diff --git a/harp/protocol/exceptions.py b/harp/protocol/exceptions.py index 26b74cf..915b9ce 100644 --- a/harp/protocol/exceptions.py +++ b/harp/protocol/exceptions.py @@ -24,3 +24,11 @@ def __init__(self, register, message): super().__init__(f"Error reading from register {register}: {message}") self.register = register self.message = message + + +class HarpTimeoutError(HarpException): + """Raised when no reply is received within the configured timeout.""" + + def __init__(self, timeout_s: float): + super().__init__(f"No reply received within {timeout_s} seconds.") + self.timeout_s = timeout_s diff --git a/harp/serial/device.py b/harp/serial/device.py index 30ef63d..8d2e020 100644 --- a/harp/serial/device.py +++ b/harp/serial/device.py @@ -2,9 +2,10 @@ import logging import queue +from enum import Enum from io import BufferedWriter from pathlib import Path -from typing import Optional, Union +from typing import Optional import serial from harp.protocol import ( @@ -17,10 +18,18 @@ ResetMode, ) from harp.protocol.device_names import device_names +from harp.protocol.exceptions import HarpTimeoutError from harp.protocol.messages import HarpMessage, ReplyHarpMessage from harp.serial.harp_serial import HarpSerial +class TimeoutStrategy(Enum): + RAISE = "raise" # Raise HarpTimeoutError + RETURN_NONE = "return_none" # Return None + LOG_AND_RAISE = "log_and_raise" + LOG_AND_NONE = "log_and_none" + + class Device: """ The `Device` class provides the interface for interacting with Harp devices. This implementation of the Harp device was based on the official documentation available on the [harp-tech website](https://harp-tech.org/protocol/Device.html). @@ -77,6 +86,7 @@ def __init__( serial_port: str, dump_file_path: Optional[str] = None, read_timeout_s: float = 1, + timeout_strategy: TimeoutStrategy = TimeoutStrategy.RAISE, ): """ Parameters @@ -94,6 +104,7 @@ def __init__( if dump_file_path is not None: self._dump_file_path = Path() / dump_file_path self._read_timeout_s = read_timeout_s + self._timeout_strategy = timeout_strategy # Connect to the Harp device and load the data stored in the device's common registers self.connect() @@ -427,43 +438,76 @@ def set_timestamp_offset(self, timestamp_offset: int) -> ReplyHarpMessage: return reply - def send(self, message: HarpMessage) -> Optional[ReplyHarpMessage]: + def send( + self, + message: HarpMessage, + *, + expect_reply: bool = True, + timeout_strategy: TimeoutStrategy | None = None, + ) -> ReplyHarpMessage | None: """ - Sends a Harp message. + Sends a Harp message and (optionally) waits for a reply. Parameters ---------- message : HarpMessage - The HarpMessage containing the message to be sent to the device + The HarpMessage to be sent to the device + expect_reply : bool, optional + If False, do not wait for a reply (fire-and-forget) + timeout_strategy : TimeoutStrategy | None + Override the device-level timeout strategy for this call Returns ------- - Optional[ReplyHarpMessage] - The reply to the Harp message or None if no reply is given + ReplyHarpMessage | None + Reply (or None when allowed by the timeout strategy or expect_reply=False) + + Raises + ------- + HarpTimeoutError + If no reply is received and the effective strategy requires raising """ self._ser.write(message.frame) - reply = self._read() - if reply is None: + if not expect_reply: return None - self._dump_reply(reply.frame) + strategy = timeout_strategy or self._timeout_strategy + + try: + reply = self._read() + except TimeoutError: + hte = HarpTimeoutError(self._read_timeout_s) + if strategy in ( + TimeoutStrategy.LOG_AND_RAISE, + TimeoutStrategy.LOG_AND_NONE, + ): + self.log.warning(str(hte)) + if strategy in (TimeoutStrategy.RAISE, TimeoutStrategy.LOG_AND_RAISE): + raise hte + return None + self._dump_reply(reply.frame) return reply - def _read(self) -> Union[ReplyHarpMessage, None]: + def _read(self) -> ReplyHarpMessage: """ Reads an incoming serial message in a blocking way. Returns ------- - Union[ReplyHarpMessage, None] + ReplyHarpMessage The incoming Harp message in case it exists + + Raises + ------- + TimeoutError + If no reply is received within the timeout period """ try: return self._ser.msg_q.get(block=True, timeout=self._read_timeout_s) except queue.Empty: - return None + raise TimeoutError("No reply received within the timeout period.") def _dump_reply(self, reply: bytes): """ From 11c47df039ac659cf8396628943f8d337449372d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 27 Aug 2025 10:13:08 +0100 Subject: [PATCH 141/159] Update payload handling taking into account the timeout strategy in send method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Grilo --- harp/protocol/messages.py | 8 +- harp/serial/device.py | 276 +++++++++++++++++++++++++++++++------- 2 files changed, 229 insertions(+), 55 deletions(-) diff --git a/harp/protocol/messages.py b/harp/protocol/messages.py index a676195..9b6e366 100644 --- a/harp/protocol/messages.py +++ b/harp/protocol/messages.py @@ -1,7 +1,7 @@ from __future__ import annotations # for type hints (PEP 563) import struct -from typing import List, Union +from typing import Union, Optional from harp.protocol import MessageType, PayloadType @@ -132,7 +132,7 @@ def payload_type(self) -> PayloadType: return PayloadType(self._frame[4]) @property - def payload(self) -> Union[int, list[int]]: + def payload(self) -> Union[int, list[int], bytearray, float, list[float]]: """ The payload sent in the write Harp message. @@ -324,7 +324,7 @@ def create( message_type: MessageType, address: int, payload_type: PayloadType, - value: int | list[int] | float | list[float] = None, + value: Optional[int | list[int] | float | list[float]] = None, ) -> HarpMessage: """ Creates a Harp message. @@ -531,7 +531,7 @@ def __init__( self, payload_type: PayloadType, address: int, - value: int | float | List[int] | List[float] = None, + value: Optional[int | float | list[int] | list[float]] = None, ): """ Create a WriteHarpMessage to send to a device. diff --git a/harp/serial/device.py b/harp/serial/device.py index 8d2e020..bdc554f 100644 --- a/harp/serial/device.py +++ b/harp/serial/device.py @@ -199,7 +199,13 @@ def dump_registers(self) -> list: address = CommonRegisters.OPERATION_CTRL reg_value = self.send( HarpMessage.create(MessageType.READ, address, PayloadType.U8) - ).payload + ) + + if reg_value is None: + return [] + + reg_value = reg_value.payload + # Assert DUMP bit reg_value |= OperationCtrl.DUMP self.send( @@ -216,7 +222,7 @@ def dump_registers(self) -> list: break return replies - def set_mode(self, mode: OperationMode) -> ReplyHarpMessage: + def set_mode(self, mode: OperationMode) -> ReplyHarpMessage | None: """ Sets the operation mode of the device. @@ -235,7 +241,12 @@ def set_mode(self, mode: OperationMode) -> ReplyHarpMessage: # Read register first reg_value = self.send( HarpMessage.create(MessageType.READ, address, PayloadType.U8) - ).payload + ) + + if reg_value is None: + return reg_value + + reg_value = reg_value.payload # Clear old operation mode reg_value &= ~OperationCtrl.OP_MODE @@ -267,7 +278,12 @@ def alive_en(self, enable: bool) -> bool: # Read register first reg_value = self.send( HarpMessage.create(MessageType.READ, address, PayloadType.U8) - ).payload + ) + + if reg_value is None: + return False + + reg_value = reg_value.payload if enable: reg_value |= OperationCtrl.ALIVE_EN @@ -278,7 +294,10 @@ def alive_en(self, enable: bool) -> bool: HarpMessage.create(MessageType.WRITE, address, PayloadType.U8, reg_value) ) - return reply + if reply is None: + return False + + return reply is not None def op_led_en(self, enable: bool) -> bool: """ @@ -299,7 +318,12 @@ def op_led_en(self, enable: bool) -> bool: # Read register first reg_value = self.send( HarpMessage.create(MessageType.READ, address, PayloadType.U8) - ).payload + ) + + if reg_value is None: + return False + + reg_value = reg_value.payload if enable: reg_value |= OperationCtrl.OPLEDEN @@ -310,7 +334,7 @@ def op_led_en(self, enable: bool) -> bool: HarpMessage.create(MessageType.WRITE, address, PayloadType.U8, reg_value) ) - return reply + return reply is not None def visual_en(self, enable: bool) -> bool: """ @@ -331,7 +355,12 @@ def visual_en(self, enable: bool) -> bool: # Read register first reg_value = self.send( HarpMessage.create(MessageType.READ, address, PayloadType.U8) - ).payload + ) + + if reg_value is None: + return False + + reg_value = reg_value.payload if enable: reg_value |= OperationCtrl.VISUALEN @@ -342,7 +371,7 @@ def visual_en(self, enable: bool) -> bool: HarpMessage.create(MessageType.WRITE, address, PayloadType.U8, reg_value) ) - return reply + return reply is not None def mute_reply(self, enable: bool) -> bool: """ @@ -363,7 +392,12 @@ def mute_reply(self, enable: bool) -> bool: # Read register first reg_value = self.send( HarpMessage.create(MessageType.READ, address, PayloadType.U8) - ).payload + ) + + if reg_value is None: + return False + + reg_value = reg_value.payload if enable: reg_value |= OperationCtrl.MUTE_RPL @@ -374,11 +408,11 @@ def mute_reply(self, enable: bool) -> bool: HarpMessage.create(MessageType.WRITE, address, PayloadType.U8, reg_value) ) - return reply + return reply is not None def reset_device( self, reset_mode: ResetMode = ResetMode.RST_DEF - ) -> ReplyHarpMessage: + ) -> ReplyHarpMessage | None: """ Resets the device and reboots with all the registers with the default values. Beware that the EEPROM will be erased. More information on the reset device register can be found [here](https://harp-tech.org/protocol/Device.html#r_reset_dev-u8--reset-device-and-save-non-volatile-registers). @@ -394,7 +428,7 @@ def reset_device( return reply - def set_clock_config(self, clock_config: ClockConfig) -> ReplyHarpMessage: + def set_clock_config(self, clock_config: ClockConfig) -> ReplyHarpMessage | None: """ Sets the clock configuration of the device. @@ -415,7 +449,7 @@ def set_clock_config(self, clock_config: ClockConfig) -> ReplyHarpMessage: return reply - def set_timestamp_offset(self, timestamp_offset: int) -> ReplyHarpMessage: + def set_timestamp_offset(self, timestamp_offset: int) -> ReplyHarpMessage | None: """ When the value of this register is above 0 (zero), the device's timestamp will be offset by this amount. The register is sensitive to 500 microsecond increments. This register is non-volatile. @@ -509,7 +543,7 @@ def _read(self) -> ReplyHarpMessage: except queue.Empty: raise TimeoutError("No reply received within the timeout period.") - def _dump_reply(self, reply: bytes): + def _dump_reply(self, reply: bytearray): """ Dumps the reply to a Harp message in the dump file in case it exists. """ @@ -544,7 +578,7 @@ def event_count(self) -> int: """ return self._ser.event_q.qsize() - def read_u8(self, address: int) -> ReplyHarpMessage: + def read_u8(self, address: int) -> ReplyHarpMessage | None: """ Reads the value of a register of type U8. @@ -557,8 +591,13 @@ def read_u8(self, address: int) -> ReplyHarpMessage: ------- ReplyHarpMessage The reply to the Harp message that will contain the value read from the register + + Raises + ------ + HarpTimeoutError + If no reply is received and the effective strategy requires raising """ - return self.send( + reply = self.send( HarpMessage.create( message_type=MessageType.READ, address=address, @@ -566,7 +605,9 @@ def read_u8(self, address: int) -> ReplyHarpMessage: ) ) - def read_s8(self, address: int) -> ReplyHarpMessage: + return reply + + def read_s8(self, address: int) -> ReplyHarpMessage | None: """ Reads the value of a register of type S8. @@ -579,8 +620,13 @@ def read_s8(self, address: int) -> ReplyHarpMessage: ------- ReplyHarpMessage The reply to the Harp message that will contain the value read from the register + + Raises + ------ + HarpTimeoutError + If no reply is received and the effective strategy requires raising """ - return self.send( + reply = self.send( HarpMessage.create( message_type=MessageType.READ, address=address, @@ -588,7 +634,9 @@ def read_s8(self, address: int) -> ReplyHarpMessage: ) ) - def read_u16(self, address: int) -> ReplyHarpMessage: + return reply + + def read_u16(self, address: int) -> ReplyHarpMessage | None: """ Reads the value of a register of type U16. @@ -601,8 +649,13 @@ def read_u16(self, address: int) -> ReplyHarpMessage: ------- ReplyHarpMessage The reply to the Harp message that will contain the value read from the register + + Raises + ------ + HarpTimeoutError + If no reply is received and the effective strategy requires raising """ - return self.send( + reply = self.send( HarpMessage.create( message_type=MessageType.READ, address=address, @@ -610,7 +663,9 @@ def read_u16(self, address: int) -> ReplyHarpMessage: ) ) - def read_s16(self, address: int) -> ReplyHarpMessage: + return reply + + def read_s16(self, address: int) -> ReplyHarpMessage | None: """ Reads the value of a register of type S16. @@ -623,8 +678,13 @@ def read_s16(self, address: int) -> ReplyHarpMessage: ------- ReplyHarpMessage The reply to the Harp message that will contain the value read from the register + + Raises + ------ + HarpTimeoutError + If no reply is received and the effective strategy requires raising """ - return self.send( + reply = self.send( HarpMessage.create( message_type=MessageType.READ, address=address, @@ -632,7 +692,9 @@ def read_s16(self, address: int) -> ReplyHarpMessage: ) ) - def read_u32(self, address: int) -> ReplyHarpMessage: + return reply + + def read_u32(self, address: int) -> ReplyHarpMessage | None: """ Reads the value of a register of type U32. @@ -645,8 +707,13 @@ def read_u32(self, address: int) -> ReplyHarpMessage: ------- ReplyHarpMessage The reply to the Harp message that will contain the value read from the register + + Raises + ------ + HarpTimeoutError + If no reply is received and the effective strategy requires raising """ - return self.send( + reply = self.send( HarpMessage.create( message_type=MessageType.READ, address=address, @@ -654,7 +721,9 @@ def read_u32(self, address: int) -> ReplyHarpMessage: ) ) - def read_s32(self, address: int) -> ReplyHarpMessage: + return reply + + def read_s32(self, address: int) -> ReplyHarpMessage | None: """ Reads the value of a register of type S32. @@ -667,8 +736,13 @@ def read_s32(self, address: int) -> ReplyHarpMessage: ------- ReplyHarpMessage The reply to the Harp message that will contain the value read from the register + + Raises + ------ + HarpTimeoutError + If no reply is received and the effective strategy requires raising """ - return self.send( + reply = self.send( HarpMessage.create( message_type=MessageType.READ, address=address, @@ -676,7 +750,9 @@ def read_s32(self, address: int) -> ReplyHarpMessage: ) ) - def read_u64(self, address: int) -> ReplyHarpMessage: + return reply + + def read_u64(self, address: int) -> ReplyHarpMessage | None: """ Reads the value of a register of type U64. @@ -689,8 +765,13 @@ def read_u64(self, address: int) -> ReplyHarpMessage: ------- ReplyHarpMessage The reply to the Harp message that will contain the value read from the register + + Raises + ------ + HarpTimeoutError + If no reply is received and the effective strategy requires raising """ - return self.send( + reply = self.send( HarpMessage.create( message_type=MessageType.READ, address=address, @@ -698,7 +779,9 @@ def read_u64(self, address: int) -> ReplyHarpMessage: ) ) - def read_s64(self, address: int) -> ReplyHarpMessage: + return reply + + def read_s64(self, address: int) -> ReplyHarpMessage | None: """ Reads the value of a register of type S64. @@ -711,8 +794,13 @@ def read_s64(self, address: int) -> ReplyHarpMessage: ------- ReplyHarpMessage The reply to the Harp message that will contain the value read from the register + + Raises + ------ + HarpTimeoutError + If no reply is received and the effective strategy requires raising """ - return self.send( + reply = self.send( HarpMessage.create( message_type=MessageType.READ, address=address, @@ -720,7 +808,9 @@ def read_s64(self, address: int) -> ReplyHarpMessage: ) ) - def read_float(self, address: int) -> ReplyHarpMessage: + return reply + + def read_float(self, address: int) -> ReplyHarpMessage | None: """ Reads the value of a register of type Float. @@ -733,8 +823,13 @@ def read_float(self, address: int) -> ReplyHarpMessage: ------- ReplyHarpMessage The reply to the Harp message that will contain the value read from the register + + Raises + ------ + HarpTimeoutError + If no reply is received and the effective strategy requires raising """ - return self.send( + reply = self.send( HarpMessage.create( message_type=MessageType.READ, address=address, @@ -742,7 +837,9 @@ def read_float(self, address: int) -> ReplyHarpMessage: ) ) - def write_u8(self, address: int, value: int | list[int]) -> ReplyHarpMessage: + return reply + + def write_u8(self, address: int, value: int | list[int]) -> ReplyHarpMessage | None: """ Writes the value of a register of type U8. @@ -757,8 +854,13 @@ def write_u8(self, address: int, value: int | list[int]) -> ReplyHarpMessage: ------- ReplyHarpMessage The reply to the Harp message + + Raises + ------ + HarpTimeoutError + If no reply is received and the effective strategy requires raising """ - return self.send( + reply = self.send( HarpMessage.create( message_type=MessageType.WRITE, address=address, @@ -767,7 +869,9 @@ def write_u8(self, address: int, value: int | list[int]) -> ReplyHarpMessage: ) ) - def write_s8(self, address: int, value: int | list[int]) -> ReplyHarpMessage: + return reply + + def write_s8(self, address: int, value: int | list[int]) -> ReplyHarpMessage | None: """ Writes the value of a register of type S8. @@ -782,8 +886,13 @@ def write_s8(self, address: int, value: int | list[int]) -> ReplyHarpMessage: ------- ReplyHarpMessage The reply to the Harp message + + Raises + ------ + HarpTimeoutError + If no reply is received and the effective strategy requires raising """ - return self.send( + reply = self.send( HarpMessage.create( message_type=MessageType.WRITE, address=address, @@ -792,7 +901,11 @@ def write_s8(self, address: int, value: int | list[int]) -> ReplyHarpMessage: ) ) - def write_u16(self, address: int, value: int | list[int]) -> ReplyHarpMessage: + return reply + + def write_u16( + self, address: int, value: int | list[int] + ) -> ReplyHarpMessage | None: """ Writes the value of a register of type U16. @@ -807,8 +920,13 @@ def write_u16(self, address: int, value: int | list[int]) -> ReplyHarpMessage: ------- ReplyHarpMessage The reply to the Harp message + + Raises + ------ + HarpTimeoutError + If no reply is received and the effective strategy requires raising """ - return self.send( + reply = self.send( HarpMessage.create( message_type=MessageType.WRITE, address=address, @@ -817,7 +935,11 @@ def write_u16(self, address: int, value: int | list[int]) -> ReplyHarpMessage: ) ) - def write_s16(self, address: int, value: int | list[int]) -> ReplyHarpMessage: + return reply + + def write_s16( + self, address: int, value: int | list[int] + ) -> ReplyHarpMessage | None: """ Writes the value of a register of type S16. @@ -832,8 +954,13 @@ def write_s16(self, address: int, value: int | list[int]) -> ReplyHarpMessage: ------- ReplyHarpMessage The reply to the Harp message + + Raises + ------ + HarpTimeoutError + If no reply is received and the effective strategy requires raising """ - return self.send( + reply = self.send( HarpMessage.create( message_type=MessageType.WRITE, address=address, @@ -842,7 +969,11 @@ def write_s16(self, address: int, value: int | list[int]) -> ReplyHarpMessage: ) ) - def write_u32(self, address: int, value: int | list[int]) -> ReplyHarpMessage: + return reply + + def write_u32( + self, address: int, value: int | list[int] + ) -> ReplyHarpMessage | None: """ Writes the value of a register of type U32. @@ -857,8 +988,13 @@ def write_u32(self, address: int, value: int | list[int]) -> ReplyHarpMessage: ------- ReplyHarpMessage The reply to the Harp message + + Raises + ------ + HarpTimeoutError + If no reply is received and the effective strategy requires raising """ - return self.send( + reply = self.send( HarpMessage.create( message_type=MessageType.WRITE, address=address, @@ -867,7 +1003,11 @@ def write_u32(self, address: int, value: int | list[int]) -> ReplyHarpMessage: ) ) - def write_s32(self, address: int, value: int | list[int]) -> ReplyHarpMessage: + return reply + + def write_s32( + self, address: int, value: int | list[int] + ) -> ReplyHarpMessage | None: """ Writes the value of a register of type S32. @@ -882,8 +1022,13 @@ def write_s32(self, address: int, value: int | list[int]) -> ReplyHarpMessage: ------- ReplyHarpMessage The reply to the Harp message + + Raises + ------ + HarpTimeoutError + If no reply is received and the effective strategy requires raising """ - return self.send( + reply = self.send( HarpMessage.create( message_type=MessageType.WRITE, address=address, @@ -892,7 +1037,11 @@ def write_s32(self, address: int, value: int | list[int]) -> ReplyHarpMessage: ) ) - def write_u64(self, address: int, value: int | list[int]) -> ReplyHarpMessage: + return reply + + def write_u64( + self, address: int, value: int | list[int] + ) -> ReplyHarpMessage | None: """ Writes the value of a register of type U64. @@ -907,8 +1056,13 @@ def write_u64(self, address: int, value: int | list[int]) -> ReplyHarpMessage: ------- ReplyHarpMessage The reply to the Harp message + + Raises + ------ + HarpTimeoutError + If no reply is received and the effective strategy requires raising """ - return self.send( + reply = self.send( HarpMessage.create( message_type=MessageType.WRITE, address=address, @@ -917,7 +1071,11 @@ def write_u64(self, address: int, value: int | list[int]) -> ReplyHarpMessage: ) ) - def write_s64(self, address: int, value: int | list[int]) -> ReplyHarpMessage: + return reply + + def write_s64( + self, address: int, value: int | list[int] + ) -> ReplyHarpMessage | None: """ Writes the value of a register of type S64. @@ -932,8 +1090,13 @@ def write_s64(self, address: int, value: int | list[int]) -> ReplyHarpMessage: ------- ReplyHarpMessage The reply to the Harp message + + Raises + ------ + HarpTimeoutError + If no reply is received and the effective strategy requires raising """ - return self.send( + reply = self.send( HarpMessage.create( message_type=MessageType.WRITE, address=address, @@ -942,7 +1105,11 @@ def write_s64(self, address: int, value: int | list[int]) -> ReplyHarpMessage: ) ) - def write_float(self, address: int, value: float | list[float]) -> ReplyHarpMessage: + return reply + + def write_float( + self, address: int, value: float | list[float] + ) -> ReplyHarpMessage | None: """ Writes the value of a register of type Float. @@ -957,8 +1124,13 @@ def write_float(self, address: int, value: float | list[float]) -> ReplyHarpMess ------- ReplyHarpMessage The reply to the Harp message + + Raises + ------ + HarpTimeoutError + If no reply is received and the effective strategy requires raising """ - return self.send( + reply = self.send( HarpMessage.create( message_type=MessageType.WRITE, address=address, @@ -967,6 +1139,8 @@ def write_float(self, address: int, value: float | list[float]) -> ReplyHarpMess ) ) + return reply + def _read_who_am_i(self) -> int: """ Reads the value stored in the `WHO_AM_I` register. From a0be5e27d02130a9b4fb4bbcf3be72f1aa43ddc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 27 Aug 2025 15:21:44 +0100 Subject: [PATCH 142/159] Reorganize project directory structure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Grilo --- {harp => src/harp-protocol/harp}/protocol/__init__.py | 0 {harp => src/harp-protocol/harp}/protocol/base.py | 0 {harp => src/harp-protocol/harp}/protocol/device_names.py | 0 {harp => src/harp-protocol/harp}/protocol/exceptions.py | 0 {harp => src/harp-protocol/harp}/protocol/messages.py | 0 {harp => src/harp-serial/harp}/serial/__init__.py | 0 {harp => src/harp-serial/harp}/serial/device.py | 0 {harp => src/harp-serial/harp}/serial/harp_serial.py | 0 8 files changed, 0 insertions(+), 0 deletions(-) rename {harp => src/harp-protocol/harp}/protocol/__init__.py (100%) rename {harp => src/harp-protocol/harp}/protocol/base.py (100%) rename {harp => src/harp-protocol/harp}/protocol/device_names.py (100%) rename {harp => src/harp-protocol/harp}/protocol/exceptions.py (100%) rename {harp => src/harp-protocol/harp}/protocol/messages.py (100%) rename {harp => src/harp-serial/harp}/serial/__init__.py (100%) rename {harp => src/harp-serial/harp}/serial/device.py (100%) rename {harp => src/harp-serial/harp}/serial/harp_serial.py (100%) diff --git a/harp/protocol/__init__.py b/src/harp-protocol/harp/protocol/__init__.py similarity index 100% rename from harp/protocol/__init__.py rename to src/harp-protocol/harp/protocol/__init__.py diff --git a/harp/protocol/base.py b/src/harp-protocol/harp/protocol/base.py similarity index 100% rename from harp/protocol/base.py rename to src/harp-protocol/harp/protocol/base.py diff --git a/harp/protocol/device_names.py b/src/harp-protocol/harp/protocol/device_names.py similarity index 100% rename from harp/protocol/device_names.py rename to src/harp-protocol/harp/protocol/device_names.py diff --git a/harp/protocol/exceptions.py b/src/harp-protocol/harp/protocol/exceptions.py similarity index 100% rename from harp/protocol/exceptions.py rename to src/harp-protocol/harp/protocol/exceptions.py diff --git a/harp/protocol/messages.py b/src/harp-protocol/harp/protocol/messages.py similarity index 100% rename from harp/protocol/messages.py rename to src/harp-protocol/harp/protocol/messages.py diff --git a/harp/serial/__init__.py b/src/harp-serial/harp/serial/__init__.py similarity index 100% rename from harp/serial/__init__.py rename to src/harp-serial/harp/serial/__init__.py diff --git a/harp/serial/device.py b/src/harp-serial/harp/serial/device.py similarity index 100% rename from harp/serial/device.py rename to src/harp-serial/harp/serial/device.py diff --git a/harp/serial/harp_serial.py b/src/harp-serial/harp/serial/harp_serial.py similarity index 100% rename from harp/serial/harp_serial.py rename to src/harp-serial/harp/serial/harp_serial.py From 87a8b14b8ac5fe1fb4bf44238ba8b613a9577e23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 27 Aug 2025 15:22:56 +0100 Subject: [PATCH 143/159] Create workspaces for harp-protocol and harp-serial MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Grilo --- pyproject.toml | 25 +- src/harp-protocol/pyproject.toml | 18 + src/harp-serial/pyproject.toml | 25 ++ uv.lock | 610 ++++++++++++++++++------------- 4 files changed, 402 insertions(+), 276 deletions(-) create mode 100644 src/harp-protocol/pyproject.toml create mode 100644 src/harp-serial/pyproject.toml diff --git a/pyproject.toml b/pyproject.toml index 5700762..ae3c412 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [project] -name = "harp-protocol" +name = "pyharp" version = "0.2.0" description = "Library for data acquisition and control of devices implementing the Harp protocol." authors = [{ name= "Hardware and Software Platform, Champalimaud Foundation", email="software@research.fchampalimaud.org"}] @@ -7,15 +7,22 @@ license = "MIT" readme = 'README.md' keywords = ['python', 'harp'] requires-python = ">=3.9,<4.0" -dependencies = [ - "pyserial>=3.5", -] +dependencies = ["harp-protocol", "harp-serial"] [project.urls] Repository = "https://github.com/fchampalimaud/pyharp/" "Bug Tracker" = "https://github.com/fchampalimaud/pyharp/issues" Documentation = "https://fchampalimaud.github.io/pyharp/" +[tool.uv.sources] +harp-protocol = { workspace = true } +harp-serial = { workspace = true } + +[tool.uv.workspace] +members = [ + "src/*", +] + [dependency-groups] dev = [ "griffe-fieldz>=0.2.1", @@ -32,16 +39,6 @@ dev = [ "ruff>=0.11.0", ] -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[tool.hatch.build.targets.wheel] -include = [ - "harp", - "harp/**/*", -] - [tool.ruff.lint.pydocstyle] convention = "numpy" diff --git a/src/harp-protocol/pyproject.toml b/src/harp-protocol/pyproject.toml new file mode 100644 index 0000000..7bffea0 --- /dev/null +++ b/src/harp-protocol/pyproject.toml @@ -0,0 +1,18 @@ +[project] +name = "harp-protocol" +version = "0.3.0" +description = "Library with the base types for Harp protocol usage." +authors = [{ name= "Hardware and Software Platform, Champalimaud Foundation", email="software@research.fchampalimaud.org"}] +license = "MIT" +keywords = ['python', 'harp'] +requires-python = ">=3.9,<4.0" + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel] +include = [ + "harp", + "harp/**/*", +] diff --git a/src/harp-serial/pyproject.toml b/src/harp-serial/pyproject.toml new file mode 100644 index 0000000..0432a75 --- /dev/null +++ b/src/harp-serial/pyproject.toml @@ -0,0 +1,25 @@ +[project] +name = "harp-serial" +version = "0.3.0" +description = "Library for data acquisition and control of devices implementing the Harp protocol." +authors = [{ name= "Hardware and Software Platform, Champalimaud Foundation", email="software@research.fchampalimaud.org"}] +license = "MIT" +keywords = ['python', 'harp'] +requires-python = ">=3.9,<4.0" +dependencies = [ + "harp-protocol", + "pyserial>=3.5", +] + +[tool.uv.sources] +harp-protocol = { workspace = true } + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel] +include = [ + "harp", + "harp/**/*", +] diff --git a/uv.lock b/uv.lock index 343c1ec..d3ac2cc 100644 --- a/uv.lock +++ b/uv.lock @@ -1,6 +1,17 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.9, <4.0" +resolution-markers = [ + "python_full_version >= '3.10'", + "python_full_version < '3.10'", +] + +[manifest] +members = [ + "harp-protocol", + "harp-serial", + "pyharp", +] [[package]] name = "babel" @@ -13,15 +24,16 @@ wheels = [ [[package]] name = "backrefs" -version = "5.8" +version = "5.9" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6c/46/caba1eb32fa5784428ab401a5487f73db4104590ecd939ed9daaf18b47e0/backrefs-5.8.tar.gz", hash = "sha256:2cab642a205ce966af3dd4b38ee36009b31fa9502a35fd61d59ccc116e40a6bd", size = 6773994, upload-time = "2025-02-25T18:15:32.003Z" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/a7/312f673df6a79003279e1f55619abbe7daebbb87c17c976ddc0345c04c7b/backrefs-5.9.tar.gz", hash = "sha256:808548cb708d66b82ee231f962cb36faaf4f2baab032f2fbb783e9c2fdddaa59", size = 5765857, upload-time = "2025-06-22T19:34:13.97Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/cb/d019ab87fe70e0fe3946196d50d6a4428623dc0c38a6669c8cae0320fbf3/backrefs-5.8-py310-none-any.whl", hash = "sha256:c67f6638a34a5b8730812f5101376f9d41dc38c43f1fdc35cb54700f6ed4465d", size = 380337, upload-time = "2025-02-25T16:53:14.607Z" }, - { url = "https://files.pythonhosted.org/packages/a9/86/abd17f50ee21b2248075cb6924c6e7f9d23b4925ca64ec660e869c2633f1/backrefs-5.8-py311-none-any.whl", hash = "sha256:2e1c15e4af0e12e45c8701bd5da0902d326b2e200cafcd25e49d9f06d44bb61b", size = 392142, upload-time = "2025-02-25T16:53:17.266Z" }, - { url = "https://files.pythonhosted.org/packages/b3/04/7b415bd75c8ab3268cc138c76fa648c19495fcc7d155508a0e62f3f82308/backrefs-5.8-py312-none-any.whl", hash = "sha256:bbef7169a33811080d67cdf1538c8289f76f0942ff971222a16034da88a73486", size = 398021, upload-time = "2025-02-25T16:53:26.378Z" }, - { url = "https://files.pythonhosted.org/packages/04/b8/60dcfb90eb03a06e883a92abbc2ab95c71f0d8c9dd0af76ab1d5ce0b1402/backrefs-5.8-py313-none-any.whl", hash = "sha256:e3a63b073867dbefd0536425f43db618578528e3896fb77be7141328642a1585", size = 399915, upload-time = "2025-02-25T16:53:28.167Z" }, - { url = "https://files.pythonhosted.org/packages/0c/37/fb6973edeb700f6e3d6ff222400602ab1830446c25c7b4676d8de93e65b8/backrefs-5.8-py39-none-any.whl", hash = "sha256:a66851e4533fb5b371aa0628e1fee1af05135616b86140c9d787a2ffdf4b8fdc", size = 380336, upload-time = "2025-02-25T16:53:29.858Z" }, + { url = "https://files.pythonhosted.org/packages/19/4d/798dc1f30468134906575156c089c492cf79b5a5fd373f07fe26c4d046bf/backrefs-5.9-py310-none-any.whl", hash = "sha256:db8e8ba0e9de81fcd635f440deab5ae5f2591b54ac1ebe0550a2ca063488cd9f", size = 380267, upload-time = "2025-06-22T19:34:05.252Z" }, + { url = "https://files.pythonhosted.org/packages/55/07/f0b3375bf0d06014e9787797e6b7cc02b38ac9ff9726ccfe834d94e9991e/backrefs-5.9-py311-none-any.whl", hash = "sha256:6907635edebbe9b2dc3de3a2befff44d74f30a4562adbb8b36f21252ea19c5cf", size = 392072, upload-time = "2025-06-22T19:34:06.743Z" }, + { url = "https://files.pythonhosted.org/packages/9d/12/4f345407259dd60a0997107758ba3f221cf89a9b5a0f8ed5b961aef97253/backrefs-5.9-py312-none-any.whl", hash = "sha256:7fdf9771f63e6028d7fee7e0c497c81abda597ea45d6b8f89e8ad76994f5befa", size = 397947, upload-time = "2025-06-22T19:34:08.172Z" }, + { url = "https://files.pythonhosted.org/packages/10/bf/fa31834dc27a7f05e5290eae47c82690edc3a7b37d58f7fb35a1bdbf355b/backrefs-5.9-py313-none-any.whl", hash = "sha256:cc37b19fa219e93ff825ed1fed8879e47b4d89aa7a1884860e2db64ccd7c676b", size = 399843, upload-time = "2025-06-22T19:34:09.68Z" }, + { url = "https://files.pythonhosted.org/packages/fc/24/b29af34b2c9c41645a9f4ff117bae860291780d73880f449e0b5d948c070/backrefs-5.9-py314-none-any.whl", hash = "sha256:df5e169836cc8acb5e440ebae9aad4bf9d15e226d3bad049cf3f6a5c20cc8dc9", size = 411762, upload-time = "2025-06-22T19:34:11.037Z" }, + { url = "https://files.pythonhosted.org/packages/41/ff/392bff89415399a979be4a65357a41d92729ae8580a66073d8ec8d810f98/backrefs-5.9-py39-none-any.whl", hash = "sha256:f48ee18f6252b8f5777a22a00a09a85de0ca931658f1dd96d4406a34f3748c60", size = 380265, upload-time = "2025-06-22T19:34:12.405Z" }, ] [[package]] @@ -35,99 +47,118 @@ wheels = [ [[package]] name = "certifi" -version = "2025.1.31" +version = "2025.8.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577, upload-time = "2025-01-31T02:16:47.166Z" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393, upload-time = "2025-01-31T02:16:45.015Z" }, + { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" }, ] [[package]] name = "charset-normalizer" -version = "3.4.1" +version = "3.4.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188, upload-time = "2024-12-24T18:12:35.43Z" } +sdist = { url = "https://files.pythonhosted.org/packages/83/2d/5fd176ceb9b2fc619e63405525573493ca23441330fcdaee6bef9460e924/charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", size = 122371, upload-time = "2025-08-09T07:57:28.46Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0d/58/5580c1716040bc89206c77d8f74418caf82ce519aae06450393ca73475d1/charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de", size = 198013, upload-time = "2024-12-24T18:09:43.671Z" }, - { url = "https://files.pythonhosted.org/packages/d0/11/00341177ae71c6f5159a08168bcb98c6e6d196d372c94511f9f6c9afe0c6/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176", size = 141285, upload-time = "2024-12-24T18:09:48.113Z" }, - { url = "https://files.pythonhosted.org/packages/01/09/11d684ea5819e5a8f5100fb0b38cf8d02b514746607934134d31233e02c8/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037", size = 151449, upload-time = "2024-12-24T18:09:50.845Z" }, - { url = "https://files.pythonhosted.org/packages/08/06/9f5a12939db324d905dc1f70591ae7d7898d030d7662f0d426e2286f68c9/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f", size = 143892, upload-time = "2024-12-24T18:09:52.078Z" }, - { url = "https://files.pythonhosted.org/packages/93/62/5e89cdfe04584cb7f4d36003ffa2936681b03ecc0754f8e969c2becb7e24/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a", size = 146123, upload-time = "2024-12-24T18:09:54.575Z" }, - { url = "https://files.pythonhosted.org/packages/a9/ac/ab729a15c516da2ab70a05f8722ecfccc3f04ed7a18e45c75bbbaa347d61/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a", size = 147943, upload-time = "2024-12-24T18:09:57.324Z" }, - { url = "https://files.pythonhosted.org/packages/03/d2/3f392f23f042615689456e9a274640c1d2e5dd1d52de36ab8f7955f8f050/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247", size = 142063, upload-time = "2024-12-24T18:09:59.794Z" }, - { url = "https://files.pythonhosted.org/packages/f2/e3/e20aae5e1039a2cd9b08d9205f52142329f887f8cf70da3650326670bddf/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408", size = 150578, upload-time = "2024-12-24T18:10:02.357Z" }, - { url = "https://files.pythonhosted.org/packages/8d/af/779ad72a4da0aed925e1139d458adc486e61076d7ecdcc09e610ea8678db/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb", size = 153629, upload-time = "2024-12-24T18:10:03.678Z" }, - { url = "https://files.pythonhosted.org/packages/c2/b6/7aa450b278e7aa92cf7732140bfd8be21f5f29d5bf334ae987c945276639/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d", size = 150778, upload-time = "2024-12-24T18:10:06.197Z" }, - { url = "https://files.pythonhosted.org/packages/39/f4/d9f4f712d0951dcbfd42920d3db81b00dd23b6ab520419626f4023334056/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807", size = 146453, upload-time = "2024-12-24T18:10:08.848Z" }, - { url = "https://files.pythonhosted.org/packages/49/2b/999d0314e4ee0cff3cb83e6bc9aeddd397eeed693edb4facb901eb8fbb69/charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f", size = 95479, upload-time = "2024-12-24T18:10:10.044Z" }, - { url = "https://files.pythonhosted.org/packages/2d/ce/3cbed41cff67e455a386fb5e5dd8906cdda2ed92fbc6297921f2e4419309/charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f", size = 102790, upload-time = "2024-12-24T18:10:11.323Z" }, - { url = "https://files.pythonhosted.org/packages/72/80/41ef5d5a7935d2d3a773e3eaebf0a9350542f2cab4eac59a7a4741fbbbbe/charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125", size = 194995, upload-time = "2024-12-24T18:10:12.838Z" }, - { url = "https://files.pythonhosted.org/packages/7a/28/0b9fefa7b8b080ec492110af6d88aa3dea91c464b17d53474b6e9ba5d2c5/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1", size = 139471, upload-time = "2024-12-24T18:10:14.101Z" }, - { url = "https://files.pythonhosted.org/packages/71/64/d24ab1a997efb06402e3fc07317e94da358e2585165930d9d59ad45fcae2/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3", size = 149831, upload-time = "2024-12-24T18:10:15.512Z" }, - { url = "https://files.pythonhosted.org/packages/37/ed/be39e5258e198655240db5e19e0b11379163ad7070962d6b0c87ed2c4d39/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd", size = 142335, upload-time = "2024-12-24T18:10:18.369Z" }, - { url = "https://files.pythonhosted.org/packages/88/83/489e9504711fa05d8dde1574996408026bdbdbd938f23be67deebb5eca92/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00", size = 143862, upload-time = "2024-12-24T18:10:19.743Z" }, - { url = "https://files.pythonhosted.org/packages/c6/c7/32da20821cf387b759ad24627a9aca289d2822de929b8a41b6241767b461/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12", size = 145673, upload-time = "2024-12-24T18:10:21.139Z" }, - { url = "https://files.pythonhosted.org/packages/68/85/f4288e96039abdd5aeb5c546fa20a37b50da71b5cf01e75e87f16cd43304/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77", size = 140211, upload-time = "2024-12-24T18:10:22.382Z" }, - { url = "https://files.pythonhosted.org/packages/28/a3/a42e70d03cbdabc18997baf4f0227c73591a08041c149e710045c281f97b/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146", size = 148039, upload-time = "2024-12-24T18:10:24.802Z" }, - { url = "https://files.pythonhosted.org/packages/85/e4/65699e8ab3014ecbe6f5c71d1a55d810fb716bbfd74f6283d5c2aa87febf/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd", size = 151939, upload-time = "2024-12-24T18:10:26.124Z" }, - { url = "https://files.pythonhosted.org/packages/b1/82/8e9fe624cc5374193de6860aba3ea8070f584c8565ee77c168ec13274bd2/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6", size = 149075, upload-time = "2024-12-24T18:10:30.027Z" }, - { url = "https://files.pythonhosted.org/packages/3d/7b/82865ba54c765560c8433f65e8acb9217cb839a9e32b42af4aa8e945870f/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8", size = 144340, upload-time = "2024-12-24T18:10:32.679Z" }, - { url = "https://files.pythonhosted.org/packages/b5/b6/9674a4b7d4d99a0d2df9b215da766ee682718f88055751e1e5e753c82db0/charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b", size = 95205, upload-time = "2024-12-24T18:10:34.724Z" }, - { url = "https://files.pythonhosted.org/packages/1e/ab/45b180e175de4402dcf7547e4fb617283bae54ce35c27930a6f35b6bef15/charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76", size = 102441, upload-time = "2024-12-24T18:10:37.574Z" }, - { url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105, upload-time = "2024-12-24T18:10:38.83Z" }, - { url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404, upload-time = "2024-12-24T18:10:44.272Z" }, - { url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423, upload-time = "2024-12-24T18:10:45.492Z" }, - { url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184, upload-time = "2024-12-24T18:10:47.898Z" }, - { url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268, upload-time = "2024-12-24T18:10:50.589Z" }, - { url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601, upload-time = "2024-12-24T18:10:52.541Z" }, - { url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098, upload-time = "2024-12-24T18:10:53.789Z" }, - { url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520, upload-time = "2024-12-24T18:10:55.048Z" }, - { url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852, upload-time = "2024-12-24T18:10:57.647Z" }, - { url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488, upload-time = "2024-12-24T18:10:59.43Z" }, - { url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192, upload-time = "2024-12-24T18:11:00.676Z" }, - { url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550, upload-time = "2024-12-24T18:11:01.952Z" }, - { url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785, upload-time = "2024-12-24T18:11:03.142Z" }, - { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698, upload-time = "2024-12-24T18:11:05.834Z" }, - { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162, upload-time = "2024-12-24T18:11:07.064Z" }, - { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263, upload-time = "2024-12-24T18:11:08.374Z" }, - { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966, upload-time = "2024-12-24T18:11:09.831Z" }, - { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992, upload-time = "2024-12-24T18:11:12.03Z" }, - { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162, upload-time = "2024-12-24T18:11:13.372Z" }, - { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972, upload-time = "2024-12-24T18:11:14.628Z" }, - { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095, upload-time = "2024-12-24T18:11:17.672Z" }, - { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668, upload-time = "2024-12-24T18:11:18.989Z" }, - { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073, upload-time = "2024-12-24T18:11:21.507Z" }, - { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732, upload-time = "2024-12-24T18:11:22.774Z" }, - { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391, upload-time = "2024-12-24T18:11:24.139Z" }, - { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702, upload-time = "2024-12-24T18:11:26.535Z" }, - { url = "https://files.pythonhosted.org/packages/7f/c0/b913f8f02836ed9ab32ea643c6fe4d3325c3d8627cf6e78098671cafff86/charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41", size = 197867, upload-time = "2024-12-24T18:12:10.438Z" }, - { url = "https://files.pythonhosted.org/packages/0f/6c/2bee440303d705b6fb1e2ec789543edec83d32d258299b16eed28aad48e0/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f", size = 141385, upload-time = "2024-12-24T18:12:11.847Z" }, - { url = "https://files.pythonhosted.org/packages/3d/04/cb42585f07f6f9fd3219ffb6f37d5a39b4fd2db2355b23683060029c35f7/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2", size = 151367, upload-time = "2024-12-24T18:12:13.177Z" }, - { url = "https://files.pythonhosted.org/packages/54/54/2412a5b093acb17f0222de007cc129ec0e0df198b5ad2ce5699355269dfe/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770", size = 143928, upload-time = "2024-12-24T18:12:14.497Z" }, - { url = "https://files.pythonhosted.org/packages/5a/6d/e2773862b043dcf8a221342954f375392bb2ce6487bcd9f2c1b34e1d6781/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4", size = 146203, upload-time = "2024-12-24T18:12:15.731Z" }, - { url = "https://files.pythonhosted.org/packages/b9/f8/ca440ef60d8f8916022859885f231abb07ada3c347c03d63f283bec32ef5/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537", size = 148082, upload-time = "2024-12-24T18:12:18.641Z" }, - { url = "https://files.pythonhosted.org/packages/04/d2/42fd330901aaa4b805a1097856c2edf5095e260a597f65def493f4b8c833/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496", size = 142053, upload-time = "2024-12-24T18:12:20.036Z" }, - { url = "https://files.pythonhosted.org/packages/9e/af/3a97a4fa3c53586f1910dadfc916e9c4f35eeada36de4108f5096cb7215f/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78", size = 150625, upload-time = "2024-12-24T18:12:22.804Z" }, - { url = "https://files.pythonhosted.org/packages/26/ae/23d6041322a3556e4da139663d02fb1b3c59a23ab2e2b56432bd2ad63ded/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7", size = 153549, upload-time = "2024-12-24T18:12:24.163Z" }, - { url = "https://files.pythonhosted.org/packages/94/22/b8f2081c6a77cb20d97e57e0b385b481887aa08019d2459dc2858ed64871/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6", size = 150945, upload-time = "2024-12-24T18:12:25.415Z" }, - { url = "https://files.pythonhosted.org/packages/c7/0b/c5ec5092747f801b8b093cdf5610e732b809d6cb11f4c51e35fc28d1d389/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294", size = 146595, upload-time = "2024-12-24T18:12:28.03Z" }, - { url = "https://files.pythonhosted.org/packages/0c/5a/0b59704c38470df6768aa154cc87b1ac7c9bb687990a1559dc8765e8627e/charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5", size = 95453, upload-time = "2024-12-24T18:12:29.569Z" }, - { url = "https://files.pythonhosted.org/packages/85/2d/a9790237cb4d01a6d57afadc8573c8b73c609ade20b80f4cda30802009ee/charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765", size = 102811, upload-time = "2024-12-24T18:12:30.83Z" }, - { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767, upload-time = "2024-12-24T18:12:32.852Z" }, + { url = "https://files.pythonhosted.org/packages/d6/98/f3b8013223728a99b908c9344da3aa04ee6e3fa235f19409033eda92fb78/charset_normalizer-3.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72", size = 207695, upload-time = "2025-08-09T07:55:36.452Z" }, + { url = "https://files.pythonhosted.org/packages/21/40/5188be1e3118c82dcb7c2a5ba101b783822cfb413a0268ed3be0468532de/charset_normalizer-3.4.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe", size = 147153, upload-time = "2025-08-09T07:55:38.467Z" }, + { url = "https://files.pythonhosted.org/packages/37/60/5d0d74bc1e1380f0b72c327948d9c2aca14b46a9efd87604e724260f384c/charset_normalizer-3.4.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601", size = 160428, upload-time = "2025-08-09T07:55:40.072Z" }, + { url = "https://files.pythonhosted.org/packages/85/9a/d891f63722d9158688de58d050c59dc3da560ea7f04f4c53e769de5140f5/charset_normalizer-3.4.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c", size = 157627, upload-time = "2025-08-09T07:55:41.706Z" }, + { url = "https://files.pythonhosted.org/packages/65/1a/7425c952944a6521a9cfa7e675343f83fd82085b8af2b1373a2409c683dc/charset_normalizer-3.4.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2", size = 152388, upload-time = "2025-08-09T07:55:43.262Z" }, + { url = "https://files.pythonhosted.org/packages/f0/c9/a2c9c2a355a8594ce2446085e2ec97fd44d323c684ff32042e2a6b718e1d/charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0", size = 150077, upload-time = "2025-08-09T07:55:44.903Z" }, + { url = "https://files.pythonhosted.org/packages/3b/38/20a1f44e4851aa1c9105d6e7110c9d020e093dfa5836d712a5f074a12bf7/charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0", size = 161631, upload-time = "2025-08-09T07:55:46.346Z" }, + { url = "https://files.pythonhosted.org/packages/a4/fa/384d2c0f57edad03d7bec3ebefb462090d8905b4ff5a2d2525f3bb711fac/charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0", size = 159210, upload-time = "2025-08-09T07:55:47.539Z" }, + { url = "https://files.pythonhosted.org/packages/33/9e/eca49d35867ca2db336b6ca27617deed4653b97ebf45dfc21311ce473c37/charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a", size = 153739, upload-time = "2025-08-09T07:55:48.744Z" }, + { url = "https://files.pythonhosted.org/packages/2a/91/26c3036e62dfe8de8061182d33be5025e2424002125c9500faff74a6735e/charset_normalizer-3.4.3-cp310-cp310-win32.whl", hash = "sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f", size = 99825, upload-time = "2025-08-09T07:55:50.305Z" }, + { url = "https://files.pythonhosted.org/packages/e2/c6/f05db471f81af1fa01839d44ae2a8bfeec8d2a8b4590f16c4e7393afd323/charset_normalizer-3.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669", size = 107452, upload-time = "2025-08-09T07:55:51.461Z" }, + { url = "https://files.pythonhosted.org/packages/7f/b5/991245018615474a60965a7c9cd2b4efbaabd16d582a5547c47ee1c7730b/charset_normalizer-3.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b", size = 204483, upload-time = "2025-08-09T07:55:53.12Z" }, + { url = "https://files.pythonhosted.org/packages/c7/2a/ae245c41c06299ec18262825c1569c5d3298fc920e4ddf56ab011b417efd/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64", size = 145520, upload-time = "2025-08-09T07:55:54.712Z" }, + { url = "https://files.pythonhosted.org/packages/3a/a4/b3b6c76e7a635748c4421d2b92c7b8f90a432f98bda5082049af37ffc8e3/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91", size = 158876, upload-time = "2025-08-09T07:55:56.024Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e6/63bb0e10f90a8243c5def74b5b105b3bbbfb3e7bb753915fe333fb0c11ea/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f", size = 156083, upload-time = "2025-08-09T07:55:57.582Z" }, + { url = "https://files.pythonhosted.org/packages/87/df/b7737ff046c974b183ea9aa111b74185ac8c3a326c6262d413bd5a1b8c69/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07", size = 150295, upload-time = "2025-08-09T07:55:59.147Z" }, + { url = "https://files.pythonhosted.org/packages/61/f1/190d9977e0084d3f1dc169acd060d479bbbc71b90bf3e7bf7b9927dec3eb/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30", size = 148379, upload-time = "2025-08-09T07:56:00.364Z" }, + { url = "https://files.pythonhosted.org/packages/4c/92/27dbe365d34c68cfe0ca76f1edd70e8705d82b378cb54ebbaeabc2e3029d/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14", size = 160018, upload-time = "2025-08-09T07:56:01.678Z" }, + { url = "https://files.pythonhosted.org/packages/99/04/baae2a1ea1893a01635d475b9261c889a18fd48393634b6270827869fa34/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c", size = 157430, upload-time = "2025-08-09T07:56:02.87Z" }, + { url = "https://files.pythonhosted.org/packages/2f/36/77da9c6a328c54d17b960c89eccacfab8271fdaaa228305330915b88afa9/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae", size = 151600, upload-time = "2025-08-09T07:56:04.089Z" }, + { url = "https://files.pythonhosted.org/packages/64/d4/9eb4ff2c167edbbf08cdd28e19078bf195762e9bd63371689cab5ecd3d0d/charset_normalizer-3.4.3-cp311-cp311-win32.whl", hash = "sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849", size = 99616, upload-time = "2025-08-09T07:56:05.658Z" }, + { url = "https://files.pythonhosted.org/packages/f4/9c/996a4a028222e7761a96634d1820de8a744ff4327a00ada9c8942033089b/charset_normalizer-3.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c", size = 107108, upload-time = "2025-08-09T07:56:07.176Z" }, + { url = "https://files.pythonhosted.org/packages/e9/5e/14c94999e418d9b87682734589404a25854d5f5d0408df68bc15b6ff54bb/charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1", size = 205655, upload-time = "2025-08-09T07:56:08.475Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a8/c6ec5d389672521f644505a257f50544c074cf5fc292d5390331cd6fc9c3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884", size = 146223, upload-time = "2025-08-09T07:56:09.708Z" }, + { url = "https://files.pythonhosted.org/packages/fc/eb/a2ffb08547f4e1e5415fb69eb7db25932c52a52bed371429648db4d84fb1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018", size = 159366, upload-time = "2025-08-09T07:56:11.326Z" }, + { url = "https://files.pythonhosted.org/packages/82/10/0fd19f20c624b278dddaf83b8464dcddc2456cb4b02bb902a6da126b87a1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392", size = 157104, upload-time = "2025-08-09T07:56:13.014Z" }, + { url = "https://files.pythonhosted.org/packages/16/ab/0233c3231af734f5dfcf0844aa9582d5a1466c985bbed6cedab85af9bfe3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f", size = 151830, upload-time = "2025-08-09T07:56:14.428Z" }, + { url = "https://files.pythonhosted.org/packages/ae/02/e29e22b4e02839a0e4a06557b1999d0a47db3567e82989b5bb21f3fbbd9f/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154", size = 148854, upload-time = "2025-08-09T07:56:16.051Z" }, + { url = "https://files.pythonhosted.org/packages/05/6b/e2539a0a4be302b481e8cafb5af8792da8093b486885a1ae4d15d452bcec/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491", size = 160670, upload-time = "2025-08-09T07:56:17.314Z" }, + { url = "https://files.pythonhosted.org/packages/31/e7/883ee5676a2ef217a40ce0bffcc3d0dfbf9e64cbcfbdf822c52981c3304b/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93", size = 158501, upload-time = "2025-08-09T07:56:18.641Z" }, + { url = "https://files.pythonhosted.org/packages/c1/35/6525b21aa0db614cf8b5792d232021dca3df7f90a1944db934efa5d20bb1/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f", size = 153173, upload-time = "2025-08-09T07:56:20.289Z" }, + { url = "https://files.pythonhosted.org/packages/50/ee/f4704bad8201de513fdc8aac1cabc87e38c5818c93857140e06e772b5892/charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37", size = 99822, upload-time = "2025-08-09T07:56:21.551Z" }, + { url = "https://files.pythonhosted.org/packages/39/f5/3b3836ca6064d0992c58c7561c6b6eee1b3892e9665d650c803bd5614522/charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc", size = 107543, upload-time = "2025-08-09T07:56:23.115Z" }, + { url = "https://files.pythonhosted.org/packages/65/ca/2135ac97709b400c7654b4b764daf5c5567c2da45a30cdd20f9eefe2d658/charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe", size = 205326, upload-time = "2025-08-09T07:56:24.721Z" }, + { url = "https://files.pythonhosted.org/packages/71/11/98a04c3c97dd34e49c7d247083af03645ca3730809a5509443f3c37f7c99/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8", size = 146008, upload-time = "2025-08-09T07:56:26.004Z" }, + { url = "https://files.pythonhosted.org/packages/60/f5/4659a4cb3c4ec146bec80c32d8bb16033752574c20b1252ee842a95d1a1e/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9", size = 159196, upload-time = "2025-08-09T07:56:27.25Z" }, + { url = "https://files.pythonhosted.org/packages/86/9e/f552f7a00611f168b9a5865a1414179b2c6de8235a4fa40189f6f79a1753/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31", size = 156819, upload-time = "2025-08-09T07:56:28.515Z" }, + { url = "https://files.pythonhosted.org/packages/7e/95/42aa2156235cbc8fa61208aded06ef46111c4d3f0de233107b3f38631803/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f", size = 151350, upload-time = "2025-08-09T07:56:29.716Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a9/3865b02c56f300a6f94fc631ef54f0a8a29da74fb45a773dfd3dcd380af7/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927", size = 148644, upload-time = "2025-08-09T07:56:30.984Z" }, + { url = "https://files.pythonhosted.org/packages/77/d9/cbcf1a2a5c7d7856f11e7ac2d782aec12bdfea60d104e60e0aa1c97849dc/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9", size = 160468, upload-time = "2025-08-09T07:56:32.252Z" }, + { url = "https://files.pythonhosted.org/packages/f6/42/6f45efee8697b89fda4d50580f292b8f7f9306cb2971d4b53f8914e4d890/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5", size = 158187, upload-time = "2025-08-09T07:56:33.481Z" }, + { url = "https://files.pythonhosted.org/packages/70/99/f1c3bdcfaa9c45b3ce96f70b14f070411366fa19549c1d4832c935d8e2c3/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc", size = 152699, upload-time = "2025-08-09T07:56:34.739Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ad/b0081f2f99a4b194bcbb1934ef3b12aa4d9702ced80a37026b7607c72e58/charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce", size = 99580, upload-time = "2025-08-09T07:56:35.981Z" }, + { url = "https://files.pythonhosted.org/packages/9a/8f/ae790790c7b64f925e5c953b924aaa42a243fb778fed9e41f147b2a5715a/charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef", size = 107366, upload-time = "2025-08-09T07:56:37.339Z" }, + { url = "https://files.pythonhosted.org/packages/8e/91/b5a06ad970ddc7a0e513112d40113e834638f4ca1120eb727a249fb2715e/charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15", size = 204342, upload-time = "2025-08-09T07:56:38.687Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ec/1edc30a377f0a02689342f214455c3f6c2fbedd896a1d2f856c002fc3062/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db", size = 145995, upload-time = "2025-08-09T07:56:40.048Z" }, + { url = "https://files.pythonhosted.org/packages/17/e5/5e67ab85e6d22b04641acb5399c8684f4d37caf7558a53859f0283a650e9/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d", size = 158640, upload-time = "2025-08-09T07:56:41.311Z" }, + { url = "https://files.pythonhosted.org/packages/f1/e5/38421987f6c697ee3722981289d554957c4be652f963d71c5e46a262e135/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096", size = 156636, upload-time = "2025-08-09T07:56:43.195Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e4/5a075de8daa3ec0745a9a3b54467e0c2967daaaf2cec04c845f73493e9a1/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa", size = 150939, upload-time = "2025-08-09T07:56:44.819Z" }, + { url = "https://files.pythonhosted.org/packages/02/f7/3611b32318b30974131db62b4043f335861d4d9b49adc6d57c1149cc49d4/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049", size = 148580, upload-time = "2025-08-09T07:56:46.684Z" }, + { url = "https://files.pythonhosted.org/packages/7e/61/19b36f4bd67f2793ab6a99b979b4e4f3d8fc754cbdffb805335df4337126/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0", size = 159870, upload-time = "2025-08-09T07:56:47.941Z" }, + { url = "https://files.pythonhosted.org/packages/06/57/84722eefdd338c04cf3030ada66889298eaedf3e7a30a624201e0cbe424a/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92", size = 157797, upload-time = "2025-08-09T07:56:49.756Z" }, + { url = "https://files.pythonhosted.org/packages/72/2a/aff5dd112b2f14bcc3462c312dce5445806bfc8ab3a7328555da95330e4b/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16", size = 152224, upload-time = "2025-08-09T07:56:51.369Z" }, + { url = "https://files.pythonhosted.org/packages/b7/8c/9839225320046ed279c6e839d51f028342eb77c91c89b8ef2549f951f3ec/charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce", size = 100086, upload-time = "2025-08-09T07:56:52.722Z" }, + { url = "https://files.pythonhosted.org/packages/ee/7a/36fbcf646e41f710ce0a563c1c9a343c6edf9be80786edeb15b6f62e17db/charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c", size = 107400, upload-time = "2025-08-09T07:56:55.172Z" }, + { url = "https://files.pythonhosted.org/packages/c2/ca/9a0983dd5c8e9733565cf3db4df2b0a2e9a82659fd8aa2a868ac6e4a991f/charset_normalizer-3.4.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:70bfc5f2c318afece2f5838ea5e4c3febada0be750fcf4775641052bbba14d05", size = 207520, upload-time = "2025-08-09T07:57:11.026Z" }, + { url = "https://files.pythonhosted.org/packages/39/c6/99271dc37243a4f925b09090493fb96c9333d7992c6187f5cfe5312008d2/charset_normalizer-3.4.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:23b6b24d74478dc833444cbd927c338349d6ae852ba53a0d02a2de1fce45b96e", size = 147307, upload-time = "2025-08-09T07:57:12.4Z" }, + { url = "https://files.pythonhosted.org/packages/e4/69/132eab043356bba06eb333cc2cc60c6340857d0a2e4ca6dc2b51312886b3/charset_normalizer-3.4.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:34a7f768e3f985abdb42841e20e17b330ad3aaf4bb7e7aeeb73db2e70f077b99", size = 160448, upload-time = "2025-08-09T07:57:13.712Z" }, + { url = "https://files.pythonhosted.org/packages/04/9a/914d294daa4809c57667b77470533e65def9c0be1ef8b4c1183a99170e9d/charset_normalizer-3.4.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb731e5deb0c7ef82d698b0f4c5bb724633ee2a489401594c5c88b02e6cb15f7", size = 157758, upload-time = "2025-08-09T07:57:14.979Z" }, + { url = "https://files.pythonhosted.org/packages/b0/a8/6f5bcf1bcf63cb45625f7c5cadca026121ff8a6c8a3256d8d8cd59302663/charset_normalizer-3.4.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:257f26fed7d7ff59921b78244f3cd93ed2af1800ff048c33f624c87475819dd7", size = 152487, upload-time = "2025-08-09T07:57:16.332Z" }, + { url = "https://files.pythonhosted.org/packages/c4/72/d3d0e9592f4e504f9dea08b8db270821c909558c353dc3b457ed2509f2fb/charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1ef99f0456d3d46a50945c98de1774da86f8e992ab5c77865ea8b8195341fc19", size = 150054, upload-time = "2025-08-09T07:57:17.576Z" }, + { url = "https://files.pythonhosted.org/packages/20/30/5f64fe3981677fe63fa987b80e6c01042eb5ff653ff7cec1b7bd9268e54e/charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2c322db9c8c89009a990ef07c3bcc9f011a3269bc06782f916cd3d9eed7c9312", size = 161703, upload-time = "2025-08-09T07:57:20.012Z" }, + { url = "https://files.pythonhosted.org/packages/e1/ef/dd08b2cac9284fd59e70f7d97382c33a3d0a926e45b15fc21b3308324ffd/charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:511729f456829ef86ac41ca78c63a5cb55240ed23b4b737faca0eb1abb1c41bc", size = 159096, upload-time = "2025-08-09T07:57:21.329Z" }, + { url = "https://files.pythonhosted.org/packages/45/8c/dcef87cfc2b3f002a6478f38906f9040302c68aebe21468090e39cde1445/charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:88ab34806dea0671532d3f82d82b85e8fc23d7b2dd12fa837978dad9bb392a34", size = 153852, upload-time = "2025-08-09T07:57:22.608Z" }, + { url = "https://files.pythonhosted.org/packages/63/86/9cbd533bd37883d467fcd1bd491b3547a3532d0fbb46de2b99feeebf185e/charset_normalizer-3.4.3-cp39-cp39-win32.whl", hash = "sha256:16a8770207946ac75703458e2c743631c79c59c5890c80011d536248f8eaa432", size = 99840, upload-time = "2025-08-09T07:57:23.883Z" }, + { url = "https://files.pythonhosted.org/packages/ce/d6/7e805c8e5c46ff9729c49950acc4ee0aeb55efb8b3a56687658ad10c3216/charset_normalizer-3.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:d22dbedd33326a4a5190dd4fe9e9e693ef12160c77382d9e87919bce54f3d4ca", size = 107438, upload-time = "2025-08-09T07:57:25.287Z" }, + { url = "https://files.pythonhosted.org/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", size = 53175, upload-time = "2025-08-09T07:57:26.864Z" }, ] [[package]] name = "click" version = "8.1.8" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" }, ] +[[package]] +name = "click" +version = "8.2.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, +] + [[package]] name = "colorama" version = "0.4.6" @@ -139,72 +170,97 @@ wheels = [ [[package]] name = "coverage" -version = "7.8.0" +version = "7.10.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/19/4f/2251e65033ed2ce1e68f00f91a0294e0f80c80ae8c3ebbe2f12828c4cd53/coverage-7.8.0.tar.gz", hash = "sha256:7a3d62b3b03b4b6fd41a085f3574874cf946cb4604d2b4d3e8dca8cd570ca501", size = 811872, upload-time = "2025-03-30T20:36:45.376Z" } +sdist = { url = "https://files.pythonhosted.org/packages/61/83/153f54356c7c200013a752ce1ed5448573dca546ce125801afca9e1ac1a4/coverage-7.10.5.tar.gz", hash = "sha256:f2e57716a78bc3ae80b2207be0709a3b2b63b9f2dcf9740ee6ac03588a2015b6", size = 821662, upload-time = "2025-08-23T14:42:44.78Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/78/01/1c5e6ee4ebaaa5e079db933a9a45f61172048c7efa06648445821a201084/coverage-7.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2931f66991175369859b5fd58529cd4b73582461877ecfd859b6549869287ffe", size = 211379, upload-time = "2025-03-30T20:34:53.904Z" }, - { url = "https://files.pythonhosted.org/packages/e9/16/a463389f5ff916963471f7c13585e5f38c6814607306b3cb4d6b4cf13384/coverage-7.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52a523153c568d2c0ef8826f6cc23031dc86cffb8c6aeab92c4ff776e7951b28", size = 211814, upload-time = "2025-03-30T20:34:56.959Z" }, - { url = "https://files.pythonhosted.org/packages/b8/b1/77062b0393f54d79064dfb72d2da402657d7c569cfbc724d56ac0f9c67ed/coverage-7.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c8a5c139aae4c35cbd7cadca1df02ea8cf28a911534fc1b0456acb0b14234f3", size = 240937, upload-time = "2025-03-30T20:34:58.751Z" }, - { url = "https://files.pythonhosted.org/packages/d7/54/c7b00a23150083c124e908c352db03bcd33375494a4beb0c6d79b35448b9/coverage-7.8.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a26c0c795c3e0b63ec7da6efded5f0bc856d7c0b24b2ac84b4d1d7bc578d676", size = 238849, upload-time = "2025-03-30T20:35:00.521Z" }, - { url = "https://files.pythonhosted.org/packages/f7/ec/a6b7cfebd34e7b49f844788fda94713035372b5200c23088e3bbafb30970/coverage-7.8.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:821f7bcbaa84318287115d54becb1915eece6918136c6f91045bb84e2f88739d", size = 239986, upload-time = "2025-03-30T20:35:02.307Z" }, - { url = "https://files.pythonhosted.org/packages/21/8c/c965ecef8af54e6d9b11bfbba85d4f6a319399f5f724798498387f3209eb/coverage-7.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a321c61477ff8ee705b8a5fed370b5710c56b3a52d17b983d9215861e37b642a", size = 239896, upload-time = "2025-03-30T20:35:04.141Z" }, - { url = "https://files.pythonhosted.org/packages/40/83/070550273fb4c480efa8381735969cb403fa8fd1626d74865bfaf9e4d903/coverage-7.8.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ed2144b8a78f9d94d9515963ed273d620e07846acd5d4b0a642d4849e8d91a0c", size = 238613, upload-time = "2025-03-30T20:35:05.889Z" }, - { url = "https://files.pythonhosted.org/packages/07/76/fbb2540495b01d996d38e9f8897b861afed356be01160ab4e25471f4fed1/coverage-7.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:042e7841a26498fff7a37d6fda770d17519982f5b7d8bf5278d140b67b61095f", size = 238909, upload-time = "2025-03-30T20:35:07.76Z" }, - { url = "https://files.pythonhosted.org/packages/a3/7e/76d604db640b7d4a86e5dd730b73e96e12a8185f22b5d0799025121f4dcb/coverage-7.8.0-cp310-cp310-win32.whl", hash = "sha256:f9983d01d7705b2d1f7a95e10bbe4091fabc03a46881a256c2787637b087003f", size = 213948, upload-time = "2025-03-30T20:35:09.144Z" }, - { url = "https://files.pythonhosted.org/packages/5c/a7/f8ce4aafb4a12ab475b56c76a71a40f427740cf496c14e943ade72e25023/coverage-7.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:5a570cd9bd20b85d1a0d7b009aaf6c110b52b5755c17be6962f8ccd65d1dbd23", size = 214844, upload-time = "2025-03-30T20:35:10.734Z" }, - { url = "https://files.pythonhosted.org/packages/2b/77/074d201adb8383addae5784cb8e2dac60bb62bfdf28b2b10f3a3af2fda47/coverage-7.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7ac22a0bb2c7c49f441f7a6d46c9c80d96e56f5a8bc6972529ed43c8b694e27", size = 211493, upload-time = "2025-03-30T20:35:12.286Z" }, - { url = "https://files.pythonhosted.org/packages/a9/89/7a8efe585750fe59b48d09f871f0e0c028a7b10722b2172dfe021fa2fdd4/coverage-7.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf13d564d310c156d1c8e53877baf2993fb3073b2fc9f69790ca6a732eb4bfea", size = 211921, upload-time = "2025-03-30T20:35:14.18Z" }, - { url = "https://files.pythonhosted.org/packages/e9/ef/96a90c31d08a3f40c49dbe897df4f1fd51fb6583821a1a1c5ee30cc8f680/coverage-7.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5761c70c017c1b0d21b0815a920ffb94a670c8d5d409d9b38857874c21f70d7", size = 244556, upload-time = "2025-03-30T20:35:15.616Z" }, - { url = "https://files.pythonhosted.org/packages/89/97/dcd5c2ce72cee9d7b0ee8c89162c24972fb987a111b92d1a3d1d19100c61/coverage-7.8.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5ff52d790c7e1628241ffbcaeb33e07d14b007b6eb00a19320c7b8a7024c040", size = 242245, upload-time = "2025-03-30T20:35:18.648Z" }, - { url = "https://files.pythonhosted.org/packages/b2/7b/b63cbb44096141ed435843bbb251558c8e05cc835c8da31ca6ffb26d44c0/coverage-7.8.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d39fc4817fd67b3915256af5dda75fd4ee10621a3d484524487e33416c6f3543", size = 244032, upload-time = "2025-03-30T20:35:20.131Z" }, - { url = "https://files.pythonhosted.org/packages/97/e3/7fa8c2c00a1ef530c2a42fa5df25a6971391f92739d83d67a4ee6dcf7a02/coverage-7.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b44674870709017e4b4036e3d0d6c17f06a0e6d4436422e0ad29b882c40697d2", size = 243679, upload-time = "2025-03-30T20:35:21.636Z" }, - { url = "https://files.pythonhosted.org/packages/4f/b3/e0a59d8df9150c8a0c0841d55d6568f0a9195692136c44f3d21f1842c8f6/coverage-7.8.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8f99eb72bf27cbb167b636eb1726f590c00e1ad375002230607a844d9e9a2318", size = 241852, upload-time = "2025-03-30T20:35:23.525Z" }, - { url = "https://files.pythonhosted.org/packages/9b/82/db347ccd57bcef150c173df2ade97976a8367a3be7160e303e43dd0c795f/coverage-7.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b571bf5341ba8c6bc02e0baeaf3b061ab993bf372d982ae509807e7f112554e9", size = 242389, upload-time = "2025-03-30T20:35:25.09Z" }, - { url = "https://files.pythonhosted.org/packages/21/f6/3f7d7879ceb03923195d9ff294456241ed05815281f5254bc16ef71d6a20/coverage-7.8.0-cp311-cp311-win32.whl", hash = "sha256:e75a2ad7b647fd8046d58c3132d7eaf31b12d8a53c0e4b21fa9c4d23d6ee6d3c", size = 213997, upload-time = "2025-03-30T20:35:26.914Z" }, - { url = "https://files.pythonhosted.org/packages/28/87/021189643e18ecf045dbe1e2071b2747901f229df302de01c998eeadf146/coverage-7.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:3043ba1c88b2139126fc72cb48574b90e2e0546d4c78b5299317f61b7f718b78", size = 214911, upload-time = "2025-03-30T20:35:28.498Z" }, - { url = "https://files.pythonhosted.org/packages/aa/12/4792669473297f7973518bec373a955e267deb4339286f882439b8535b39/coverage-7.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bbb5cc845a0292e0c520656d19d7ce40e18d0e19b22cb3e0409135a575bf79fc", size = 211684, upload-time = "2025-03-30T20:35:29.959Z" }, - { url = "https://files.pythonhosted.org/packages/be/e1/2a4ec273894000ebedd789e8f2fc3813fcaf486074f87fd1c5b2cb1c0a2b/coverage-7.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4dfd9a93db9e78666d178d4f08a5408aa3f2474ad4d0e0378ed5f2ef71640cb6", size = 211935, upload-time = "2025-03-30T20:35:31.912Z" }, - { url = "https://files.pythonhosted.org/packages/f8/3a/7b14f6e4372786709a361729164125f6b7caf4024ce02e596c4a69bccb89/coverage-7.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f017a61399f13aa6d1039f75cd467be388d157cd81f1a119b9d9a68ba6f2830d", size = 245994, upload-time = "2025-03-30T20:35:33.455Z" }, - { url = "https://files.pythonhosted.org/packages/54/80/039cc7f1f81dcbd01ea796d36d3797e60c106077e31fd1f526b85337d6a1/coverage-7.8.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0915742f4c82208ebf47a2b154a5334155ed9ef9fe6190674b8a46c2fb89cb05", size = 242885, upload-time = "2025-03-30T20:35:35.354Z" }, - { url = "https://files.pythonhosted.org/packages/10/e0/dc8355f992b6cc2f9dcd5ef6242b62a3f73264893bc09fbb08bfcab18eb4/coverage-7.8.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a40fcf208e021eb14b0fac6bdb045c0e0cab53105f93ba0d03fd934c956143a", size = 245142, upload-time = "2025-03-30T20:35:37.121Z" }, - { url = "https://files.pythonhosted.org/packages/43/1b/33e313b22cf50f652becb94c6e7dae25d8f02e52e44db37a82de9ac357e8/coverage-7.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a1f406a8e0995d654b2ad87c62caf6befa767885301f3b8f6f73e6f3c31ec3a6", size = 244906, upload-time = "2025-03-30T20:35:39.07Z" }, - { url = "https://files.pythonhosted.org/packages/05/08/c0a8048e942e7f918764ccc99503e2bccffba1c42568693ce6955860365e/coverage-7.8.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:77af0f6447a582fdc7de5e06fa3757a3ef87769fbb0fdbdeba78c23049140a47", size = 243124, upload-time = "2025-03-30T20:35:40.598Z" }, - { url = "https://files.pythonhosted.org/packages/5b/62/ea625b30623083c2aad645c9a6288ad9fc83d570f9adb913a2abdba562dd/coverage-7.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f2d32f95922927186c6dbc8bc60df0d186b6edb828d299ab10898ef3f40052fe", size = 244317, upload-time = "2025-03-30T20:35:42.204Z" }, - { url = "https://files.pythonhosted.org/packages/62/cb/3871f13ee1130a6c8f020e2f71d9ed269e1e2124aa3374d2180ee451cee9/coverage-7.8.0-cp312-cp312-win32.whl", hash = "sha256:769773614e676f9d8e8a0980dd7740f09a6ea386d0f383db6821df07d0f08545", size = 214170, upload-time = "2025-03-30T20:35:44.216Z" }, - { url = "https://files.pythonhosted.org/packages/88/26/69fe1193ab0bfa1eb7a7c0149a066123611baba029ebb448500abd8143f9/coverage-7.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:e5d2b9be5b0693cf21eb4ce0ec8d211efb43966f6657807f6859aab3814f946b", size = 214969, upload-time = "2025-03-30T20:35:45.797Z" }, - { url = "https://files.pythonhosted.org/packages/f3/21/87e9b97b568e223f3438d93072479c2f36cc9b3f6b9f7094b9d50232acc0/coverage-7.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ac46d0c2dd5820ce93943a501ac5f6548ea81594777ca585bf002aa8854cacd", size = 211708, upload-time = "2025-03-30T20:35:47.417Z" }, - { url = "https://files.pythonhosted.org/packages/75/be/882d08b28a0d19c9c4c2e8a1c6ebe1f79c9c839eb46d4fca3bd3b34562b9/coverage-7.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:771eb7587a0563ca5bb6f622b9ed7f9d07bd08900f7589b4febff05f469bea00", size = 211981, upload-time = "2025-03-30T20:35:49.002Z" }, - { url = "https://files.pythonhosted.org/packages/7a/1d/ce99612ebd58082fbe3f8c66f6d8d5694976c76a0d474503fa70633ec77f/coverage-7.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42421e04069fb2cbcbca5a696c4050b84a43b05392679d4068acbe65449b5c64", size = 245495, upload-time = "2025-03-30T20:35:51.073Z" }, - { url = "https://files.pythonhosted.org/packages/dc/8d/6115abe97df98db6b2bd76aae395fcc941d039a7acd25f741312ced9a78f/coverage-7.8.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:554fec1199d93ab30adaa751db68acec2b41c5602ac944bb19187cb9a41a8067", size = 242538, upload-time = "2025-03-30T20:35:52.941Z" }, - { url = "https://files.pythonhosted.org/packages/cb/74/2f8cc196643b15bc096d60e073691dadb3dca48418f08bc78dd6e899383e/coverage-7.8.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aaeb00761f985007b38cf463b1d160a14a22c34eb3f6a39d9ad6fc27cb73008", size = 244561, upload-time = "2025-03-30T20:35:54.658Z" }, - { url = "https://files.pythonhosted.org/packages/22/70/c10c77cd77970ac965734fe3419f2c98665f6e982744a9bfb0e749d298f4/coverage-7.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:581a40c7b94921fffd6457ffe532259813fc68eb2bdda60fa8cc343414ce3733", size = 244633, upload-time = "2025-03-30T20:35:56.221Z" }, - { url = "https://files.pythonhosted.org/packages/38/5a/4f7569d946a07c952688debee18c2bb9ab24f88027e3d71fd25dbc2f9dca/coverage-7.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f319bae0321bc838e205bf9e5bc28f0a3165f30c203b610f17ab5552cff90323", size = 242712, upload-time = "2025-03-30T20:35:57.801Z" }, - { url = "https://files.pythonhosted.org/packages/bb/a1/03a43b33f50475a632a91ea8c127f7e35e53786dbe6781c25f19fd5a65f8/coverage-7.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04bfec25a8ef1c5f41f5e7e5c842f6b615599ca8ba8391ec33a9290d9d2db3a3", size = 244000, upload-time = "2025-03-30T20:35:59.378Z" }, - { url = "https://files.pythonhosted.org/packages/6a/89/ab6c43b1788a3128e4d1b7b54214548dcad75a621f9d277b14d16a80d8a1/coverage-7.8.0-cp313-cp313-win32.whl", hash = "sha256:dd19608788b50eed889e13a5d71d832edc34fc9dfce606f66e8f9f917eef910d", size = 214195, upload-time = "2025-03-30T20:36:01.005Z" }, - { url = "https://files.pythonhosted.org/packages/12/12/6bf5f9a8b063d116bac536a7fb594fc35cb04981654cccb4bbfea5dcdfa0/coverage-7.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:a9abbccd778d98e9c7e85038e35e91e67f5b520776781d9a1e2ee9d400869487", size = 214998, upload-time = "2025-03-30T20:36:03.006Z" }, - { url = "https://files.pythonhosted.org/packages/2a/e6/1e9df74ef7a1c983a9c7443dac8aac37a46f1939ae3499424622e72a6f78/coverage-7.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:18c5ae6d061ad5b3e7eef4363fb27a0576012a7447af48be6c75b88494c6cf25", size = 212541, upload-time = "2025-03-30T20:36:04.638Z" }, - { url = "https://files.pythonhosted.org/packages/04/51/c32174edb7ee49744e2e81c4b1414ac9df3dacfcb5b5f273b7f285ad43f6/coverage-7.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:95aa6ae391a22bbbce1b77ddac846c98c5473de0372ba5c463480043a07bff42", size = 212767, upload-time = "2025-03-30T20:36:06.503Z" }, - { url = "https://files.pythonhosted.org/packages/e9/8f/f454cbdb5212f13f29d4a7983db69169f1937e869a5142bce983ded52162/coverage-7.8.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e013b07ba1c748dacc2a80e69a46286ff145935f260eb8c72df7185bf048f502", size = 256997, upload-time = "2025-03-30T20:36:08.137Z" }, - { url = "https://files.pythonhosted.org/packages/e6/74/2bf9e78b321216d6ee90a81e5c22f912fc428442c830c4077b4a071db66f/coverage-7.8.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d766a4f0e5aa1ba056ec3496243150698dc0481902e2b8559314368717be82b1", size = 252708, upload-time = "2025-03-30T20:36:09.781Z" }, - { url = "https://files.pythonhosted.org/packages/92/4d/50d7eb1e9a6062bee6e2f92e78b0998848a972e9afad349b6cdde6fa9e32/coverage-7.8.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad80e6b4a0c3cb6f10f29ae4c60e991f424e6b14219d46f1e7d442b938ee68a4", size = 255046, upload-time = "2025-03-30T20:36:11.409Z" }, - { url = "https://files.pythonhosted.org/packages/40/9e/71fb4e7402a07c4198ab44fc564d09d7d0ffca46a9fb7b0a7b929e7641bd/coverage-7.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b87eb6fc9e1bb8f98892a2458781348fa37e6925f35bb6ceb9d4afd54ba36c73", size = 256139, upload-time = "2025-03-30T20:36:13.86Z" }, - { url = "https://files.pythonhosted.org/packages/49/1a/78d37f7a42b5beff027e807c2843185961fdae7fe23aad5a4837c93f9d25/coverage-7.8.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d1ba00ae33be84066cfbe7361d4e04dec78445b2b88bdb734d0d1cbab916025a", size = 254307, upload-time = "2025-03-30T20:36:16.074Z" }, - { url = "https://files.pythonhosted.org/packages/58/e9/8fb8e0ff6bef5e170ee19d59ca694f9001b2ec085dc99b4f65c128bb3f9a/coverage-7.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f3c38e4e5ccbdc9198aecc766cedbb134b2d89bf64533973678dfcf07effd883", size = 255116, upload-time = "2025-03-30T20:36:18.033Z" }, - { url = "https://files.pythonhosted.org/packages/56/b0/d968ecdbe6fe0a863de7169bbe9e8a476868959f3af24981f6a10d2b6924/coverage-7.8.0-cp313-cp313t-win32.whl", hash = "sha256:379fe315e206b14e21db5240f89dc0774bdd3e25c3c58c2c733c99eca96f1ada", size = 214909, upload-time = "2025-03-30T20:36:19.644Z" }, - { url = "https://files.pythonhosted.org/packages/87/e9/d6b7ef9fecf42dfb418d93544af47c940aa83056c49e6021a564aafbc91f/coverage-7.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2e4b6b87bb0c846a9315e3ab4be2d52fac905100565f4b92f02c445c8799e257", size = 216068, upload-time = "2025-03-30T20:36:21.282Z" }, - { url = "https://files.pythonhosted.org/packages/60/0c/5da94be095239814bf2730a28cffbc48d6df4304e044f80d39e1ae581997/coverage-7.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fa260de59dfb143af06dcf30c2be0b200bed2a73737a8a59248fcb9fa601ef0f", size = 211377, upload-time = "2025-03-30T20:36:23.298Z" }, - { url = "https://files.pythonhosted.org/packages/d5/cb/b9e93ebf193a0bb89dbcd4f73d7b0e6ecb7c1b6c016671950e25f041835e/coverage-7.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:96121edfa4c2dfdda409877ea8608dd01de816a4dc4a0523356067b305e4e17a", size = 211803, upload-time = "2025-03-30T20:36:25.74Z" }, - { url = "https://files.pythonhosted.org/packages/78/1a/cdbfe9e1bb14d3afcaf6bb6e1b9ba76c72666e329cd06865bbd241efd652/coverage-7.8.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b8af63b9afa1031c0ef05b217faa598f3069148eeee6bb24b79da9012423b82", size = 240561, upload-time = "2025-03-30T20:36:27.548Z" }, - { url = "https://files.pythonhosted.org/packages/59/04/57f1223f26ac018d7ce791bfa65b0c29282de3e041c1cd3ed430cfeac5a5/coverage-7.8.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:89b1f4af0d4afe495cd4787a68e00f30f1d15939f550e869de90a86efa7e0814", size = 238488, upload-time = "2025-03-30T20:36:29.175Z" }, - { url = "https://files.pythonhosted.org/packages/b7/b1/0f25516ae2a35e265868670384feebe64e7857d9cffeeb3887b0197e2ba2/coverage-7.8.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94ec0be97723ae72d63d3aa41961a0b9a6f5a53ff599813c324548d18e3b9e8c", size = 239589, upload-time = "2025-03-30T20:36:30.876Z" }, - { url = "https://files.pythonhosted.org/packages/e0/a4/99d88baac0d1d5a46ceef2dd687aac08fffa8795e4c3e71b6f6c78e14482/coverage-7.8.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8a1d96e780bdb2d0cbb297325711701f7c0b6f89199a57f2049e90064c29f6bd", size = 239366, upload-time = "2025-03-30T20:36:32.563Z" }, - { url = "https://files.pythonhosted.org/packages/ea/9e/1db89e135feb827a868ed15f8fc857160757f9cab140ffee21342c783ceb/coverage-7.8.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f1d8a2a57b47142b10374902777e798784abf400a004b14f1b0b9eaf1e528ba4", size = 237591, upload-time = "2025-03-30T20:36:34.721Z" }, - { url = "https://files.pythonhosted.org/packages/1b/6d/ac4d6fdfd0e201bc82d1b08adfacb1e34b40d21a22cdd62cfaf3c1828566/coverage-7.8.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cf60dd2696b457b710dd40bf17ad269d5f5457b96442f7f85722bdb16fa6c899", size = 238572, upload-time = "2025-03-30T20:36:36.805Z" }, - { url = "https://files.pythonhosted.org/packages/25/5e/917cbe617c230f7f1745b6a13e780a3a1cd1cf328dbcd0fd8d7ec52858cd/coverage-7.8.0-cp39-cp39-win32.whl", hash = "sha256:be945402e03de47ba1872cd5236395e0f4ad635526185a930735f66710e1bd3f", size = 213966, upload-time = "2025-03-30T20:36:38.551Z" }, - { url = "https://files.pythonhosted.org/packages/bd/93/72b434fe550135869f9ea88dd36068af19afce666db576e059e75177e813/coverage-7.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:90e7fbc6216ecaffa5a880cdc9c77b7418c1dcb166166b78dbc630d07f278cc3", size = 214852, upload-time = "2025-03-30T20:36:40.209Z" }, - { url = "https://files.pythonhosted.org/packages/c4/f1/1da77bb4c920aa30e82fa9b6ea065da3467977c2e5e032e38e66f1c57ffd/coverage-7.8.0-pp39.pp310.pp311-none-any.whl", hash = "sha256:b8194fb8e50d556d5849753de991d390c5a1edeeba50f68e3a9253fbd8bf8ccd", size = 203443, upload-time = "2025-03-30T20:36:41.959Z" }, - { url = "https://files.pythonhosted.org/packages/59/f1/4da7717f0063a222db253e7121bd6a56f6fb1ba439dcc36659088793347c/coverage-7.8.0-py3-none-any.whl", hash = "sha256:dbf364b4c5e7bae9250528167dfe40219b62e2d573c854d74be213e1e52069f7", size = 203435, upload-time = "2025-03-30T20:36:43.61Z" }, + { url = "https://files.pythonhosted.org/packages/af/70/e77b0061a6c7157bfce645c6b9a715a08d4c86b3360a7b3252818080b817/coverage-7.10.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c6a5c3414bfc7451b879141ce772c546985163cf553f08e0f135f0699a911801", size = 216774, upload-time = "2025-08-23T14:40:26.301Z" }, + { url = "https://files.pythonhosted.org/packages/91/08/2a79de5ecf37ee40f2d898012306f11c161548753391cec763f92647837b/coverage-7.10.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bc8e4d99ce82f1710cc3c125adc30fd1487d3cf6c2cd4994d78d68a47b16989a", size = 217175, upload-time = "2025-08-23T14:40:29.142Z" }, + { url = "https://files.pythonhosted.org/packages/64/57/0171d69a699690149a6ba6a4eb702814448c8d617cf62dbafa7ce6bfdf63/coverage-7.10.5-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:02252dc1216e512a9311f596b3169fad54abcb13827a8d76d5630c798a50a754", size = 243931, upload-time = "2025-08-23T14:40:30.735Z" }, + { url = "https://files.pythonhosted.org/packages/15/06/3a67662c55656702bd398a727a7f35df598eb11104fcb34f1ecbb070291a/coverage-7.10.5-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:73269df37883e02d460bee0cc16be90509faea1e3bd105d77360b512d5bb9c33", size = 245740, upload-time = "2025-08-23T14:40:32.302Z" }, + { url = "https://files.pythonhosted.org/packages/00/f4/f8763aabf4dc30ef0d0012522d312f0b7f9fede6246a1f27dbcc4a1e523c/coverage-7.10.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f8a81b0614642f91c9effd53eec284f965577591f51f547a1cbeb32035b4c2f", size = 247600, upload-time = "2025-08-23T14:40:33.66Z" }, + { url = "https://files.pythonhosted.org/packages/9c/31/6632219a9065e1b83f77eda116fed4c76fb64908a6a9feae41816dab8237/coverage-7.10.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6a29f8e0adb7f8c2b95fa2d4566a1d6e6722e0a637634c6563cb1ab844427dd9", size = 245640, upload-time = "2025-08-23T14:40:35.248Z" }, + { url = "https://files.pythonhosted.org/packages/6e/e2/3dba9b86037b81649b11d192bb1df11dde9a81013e434af3520222707bc8/coverage-7.10.5-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fcf6ab569436b4a647d4e91accba12509ad9f2554bc93d3aee23cc596e7f99c3", size = 243659, upload-time = "2025-08-23T14:40:36.815Z" }, + { url = "https://files.pythonhosted.org/packages/02/b9/57170bd9f3e333837fc24ecc88bc70fbc2eb7ccfd0876854b0c0407078c3/coverage-7.10.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:90dc3d6fb222b194a5de60af8d190bedeeddcbc7add317e4a3cd333ee6b7c879", size = 244537, upload-time = "2025-08-23T14:40:38.737Z" }, + { url = "https://files.pythonhosted.org/packages/b3/1c/93ac36ef1e8b06b8d5777393a3a40cb356f9f3dab980be40a6941e443588/coverage-7.10.5-cp310-cp310-win32.whl", hash = "sha256:414a568cd545f9dc75f0686a0049393de8098414b58ea071e03395505b73d7a8", size = 219285, upload-time = "2025-08-23T14:40:40.342Z" }, + { url = "https://files.pythonhosted.org/packages/30/95/23252277e6e5fe649d6cd3ed3f35d2307e5166de4e75e66aa7f432abc46d/coverage-7.10.5-cp310-cp310-win_amd64.whl", hash = "sha256:e551f9d03347196271935fd3c0c165f0e8c049220280c1120de0084d65e9c7ff", size = 220185, upload-time = "2025-08-23T14:40:42.026Z" }, + { url = "https://files.pythonhosted.org/packages/cb/f2/336d34d2fc1291ca7c18eeb46f64985e6cef5a1a7ef6d9c23720c6527289/coverage-7.10.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c177e6ffe2ebc7c410785307758ee21258aa8e8092b44d09a2da767834f075f2", size = 216890, upload-time = "2025-08-23T14:40:43.627Z" }, + { url = "https://files.pythonhosted.org/packages/39/ea/92448b07cc1cf2b429d0ce635f59cf0c626a5d8de21358f11e92174ff2a6/coverage-7.10.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:14d6071c51ad0f703d6440827eaa46386169b5fdced42631d5a5ac419616046f", size = 217287, upload-time = "2025-08-23T14:40:45.214Z" }, + { url = "https://files.pythonhosted.org/packages/96/ba/ad5b36537c5179c808d0ecdf6e4aa7630b311b3c12747ad624dcd43a9b6b/coverage-7.10.5-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:61f78c7c3bc272a410c5ae3fde7792b4ffb4acc03d35a7df73ca8978826bb7ab", size = 247683, upload-time = "2025-08-23T14:40:46.791Z" }, + { url = "https://files.pythonhosted.org/packages/28/e5/fe3bbc8d097029d284b5fb305b38bb3404895da48495f05bff025df62770/coverage-7.10.5-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f39071caa126f69d63f99b324fb08c7b1da2ec28cbb1fe7b5b1799926492f65c", size = 249614, upload-time = "2025-08-23T14:40:48.082Z" }, + { url = "https://files.pythonhosted.org/packages/69/9c/a1c89a8c8712799efccb32cd0a1ee88e452f0c13a006b65bb2271f1ac767/coverage-7.10.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:343a023193f04d46edc46b2616cdbee68c94dd10208ecd3adc56fcc54ef2baa1", size = 251719, upload-time = "2025-08-23T14:40:49.349Z" }, + { url = "https://files.pythonhosted.org/packages/e9/be/5576b5625865aa95b5633315f8f4142b003a70c3d96e76f04487c3b5cc95/coverage-7.10.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:585ffe93ae5894d1ebdee69fc0b0d4b7c75d8007983692fb300ac98eed146f78", size = 249411, upload-time = "2025-08-23T14:40:50.624Z" }, + { url = "https://files.pythonhosted.org/packages/94/0a/e39a113d4209da0dbbc9385608cdb1b0726a4d25f78672dc51c97cfea80f/coverage-7.10.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b0ef4e66f006ed181df29b59921bd8fc7ed7cd6a9289295cd8b2824b49b570df", size = 247466, upload-time = "2025-08-23T14:40:52.362Z" }, + { url = "https://files.pythonhosted.org/packages/40/cb/aebb2d8c9e3533ee340bea19b71c5b76605a0268aa49808e26fe96ec0a07/coverage-7.10.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:eb7b0bbf7cc1d0453b843eca7b5fa017874735bef9bfdfa4121373d2cc885ed6", size = 248104, upload-time = "2025-08-23T14:40:54.064Z" }, + { url = "https://files.pythonhosted.org/packages/08/e6/26570d6ccce8ff5de912cbfd268e7f475f00597cb58da9991fa919c5e539/coverage-7.10.5-cp311-cp311-win32.whl", hash = "sha256:1d043a8a06987cc0c98516e57c4d3fc2c1591364831e9deb59c9e1b4937e8caf", size = 219327, upload-time = "2025-08-23T14:40:55.424Z" }, + { url = "https://files.pythonhosted.org/packages/79/79/5f48525e366e518b36e66167e3b6e5db6fd54f63982500c6a5abb9d3dfbd/coverage-7.10.5-cp311-cp311-win_amd64.whl", hash = "sha256:fefafcca09c3ac56372ef64a40f5fe17c5592fab906e0fdffd09543f3012ba50", size = 220213, upload-time = "2025-08-23T14:40:56.724Z" }, + { url = "https://files.pythonhosted.org/packages/40/3c/9058128b7b0bf333130c320b1eb1ae485623014a21ee196d68f7737f8610/coverage-7.10.5-cp311-cp311-win_arm64.whl", hash = "sha256:7e78b767da8b5fc5b2faa69bb001edafcd6f3995b42a331c53ef9572c55ceb82", size = 218893, upload-time = "2025-08-23T14:40:58.011Z" }, + { url = "https://files.pythonhosted.org/packages/27/8e/40d75c7128f871ea0fd829d3e7e4a14460cad7c3826e3b472e6471ad05bd/coverage-7.10.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c2d05c7e73c60a4cecc7d9b60dbfd603b4ebc0adafaef371445b47d0f805c8a9", size = 217077, upload-time = "2025-08-23T14:40:59.329Z" }, + { url = "https://files.pythonhosted.org/packages/18/a8/f333f4cf3fb5477a7f727b4d603a2eb5c3c5611c7fe01329c2e13b23b678/coverage-7.10.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:32ddaa3b2c509778ed5373b177eb2bf5662405493baeff52278a0b4f9415188b", size = 217310, upload-time = "2025-08-23T14:41:00.628Z" }, + { url = "https://files.pythonhosted.org/packages/ec/2c/fbecd8381e0a07d1547922be819b4543a901402f63930313a519b937c668/coverage-7.10.5-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:dd382410039fe062097aa0292ab6335a3f1e7af7bba2ef8d27dcda484918f20c", size = 248802, upload-time = "2025-08-23T14:41:02.012Z" }, + { url = "https://files.pythonhosted.org/packages/3f/bc/1011da599b414fb6c9c0f34086736126f9ff71f841755786a6b87601b088/coverage-7.10.5-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7fa22800f3908df31cea6fb230f20ac49e343515d968cc3a42b30d5c3ebf9b5a", size = 251550, upload-time = "2025-08-23T14:41:03.438Z" }, + { url = "https://files.pythonhosted.org/packages/4c/6f/b5c03c0c721c067d21bc697accc3642f3cef9f087dac429c918c37a37437/coverage-7.10.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f366a57ac81f5e12797136552f5b7502fa053c861a009b91b80ed51f2ce651c6", size = 252684, upload-time = "2025-08-23T14:41:04.85Z" }, + { url = "https://files.pythonhosted.org/packages/f9/50/d474bc300ebcb6a38a1047d5c465a227605d6473e49b4e0d793102312bc5/coverage-7.10.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5f1dc8f1980a272ad4a6c84cba7981792344dad33bf5869361576b7aef42733a", size = 250602, upload-time = "2025-08-23T14:41:06.719Z" }, + { url = "https://files.pythonhosted.org/packages/4a/2d/548c8e04249cbba3aba6bd799efdd11eee3941b70253733f5d355d689559/coverage-7.10.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:2285c04ee8676f7938b02b4936d9b9b672064daab3187c20f73a55f3d70e6b4a", size = 248724, upload-time = "2025-08-23T14:41:08.429Z" }, + { url = "https://files.pythonhosted.org/packages/e2/96/a7c3c0562266ac39dcad271d0eec8fc20ab576e3e2f64130a845ad2a557b/coverage-7.10.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c2492e4dd9daab63f5f56286f8a04c51323d237631eb98505d87e4c4ff19ec34", size = 250158, upload-time = "2025-08-23T14:41:09.749Z" }, + { url = "https://files.pythonhosted.org/packages/f3/75/74d4be58c70c42ef0b352d597b022baf12dbe2b43e7cb1525f56a0fb1d4b/coverage-7.10.5-cp312-cp312-win32.whl", hash = "sha256:38a9109c4ee8135d5df5505384fc2f20287a47ccbe0b3f04c53c9a1989c2bbaf", size = 219493, upload-time = "2025-08-23T14:41:11.095Z" }, + { url = "https://files.pythonhosted.org/packages/4f/08/364e6012d1d4d09d1e27437382967efed971d7613f94bca9add25f0c1f2b/coverage-7.10.5-cp312-cp312-win_amd64.whl", hash = "sha256:6b87f1ad60b30bc3c43c66afa7db6b22a3109902e28c5094957626a0143a001f", size = 220302, upload-time = "2025-08-23T14:41:12.449Z" }, + { url = "https://files.pythonhosted.org/packages/db/d5/7c8a365e1f7355c58af4fe5faf3f90cc8e587590f5854808d17ccb4e7077/coverage-7.10.5-cp312-cp312-win_arm64.whl", hash = "sha256:672a6c1da5aea6c629819a0e1461e89d244f78d7b60c424ecf4f1f2556c041d8", size = 218936, upload-time = "2025-08-23T14:41:13.872Z" }, + { url = "https://files.pythonhosted.org/packages/9f/08/4166ecfb60ba011444f38a5a6107814b80c34c717bc7a23be0d22e92ca09/coverage-7.10.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ef3b83594d933020f54cf65ea1f4405d1f4e41a009c46df629dd964fcb6e907c", size = 217106, upload-time = "2025-08-23T14:41:15.268Z" }, + { url = "https://files.pythonhosted.org/packages/25/d7/b71022408adbf040a680b8c64bf6ead3be37b553e5844f7465643979f7ca/coverage-7.10.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2b96bfdf7c0ea9faebce088a3ecb2382819da4fbc05c7b80040dbc428df6af44", size = 217353, upload-time = "2025-08-23T14:41:16.656Z" }, + { url = "https://files.pythonhosted.org/packages/74/68/21e0d254dbf8972bb8dd95e3fe7038f4be037ff04ba47d6d1b12b37510ba/coverage-7.10.5-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:63df1fdaffa42d914d5c4d293e838937638bf75c794cf20bee12978fc8c4e3bc", size = 248350, upload-time = "2025-08-23T14:41:18.128Z" }, + { url = "https://files.pythonhosted.org/packages/90/65/28752c3a896566ec93e0219fc4f47ff71bd2b745f51554c93e8dcb659796/coverage-7.10.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8002dc6a049aac0e81ecec97abfb08c01ef0c1fbf962d0c98da3950ace89b869", size = 250955, upload-time = "2025-08-23T14:41:19.577Z" }, + { url = "https://files.pythonhosted.org/packages/a5/eb/ca6b7967f57f6fef31da8749ea20417790bb6723593c8cd98a987be20423/coverage-7.10.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:63d4bb2966d6f5f705a6b0c6784c8969c468dbc4bcf9d9ded8bff1c7e092451f", size = 252230, upload-time = "2025-08-23T14:41:20.959Z" }, + { url = "https://files.pythonhosted.org/packages/bc/29/17a411b2a2a18f8b8c952aa01c00f9284a1fbc677c68a0003b772ea89104/coverage-7.10.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1f672efc0731a6846b157389b6e6d5d5e9e59d1d1a23a5c66a99fd58339914d5", size = 250387, upload-time = "2025-08-23T14:41:22.644Z" }, + { url = "https://files.pythonhosted.org/packages/c7/89/97a9e271188c2fbb3db82235c33980bcbc733da7da6065afbaa1d685a169/coverage-7.10.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3f39cef43d08049e8afc1fde4a5da8510fc6be843f8dea350ee46e2a26b2f54c", size = 248280, upload-time = "2025-08-23T14:41:24.061Z" }, + { url = "https://files.pythonhosted.org/packages/d1/c6/0ad7d0137257553eb4706b4ad6180bec0a1b6a648b092c5bbda48d0e5b2c/coverage-7.10.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2968647e3ed5a6c019a419264386b013979ff1fb67dd11f5c9886c43d6a31fc2", size = 249894, upload-time = "2025-08-23T14:41:26.165Z" }, + { url = "https://files.pythonhosted.org/packages/84/56/fb3aba936addb4c9e5ea14f5979393f1c2466b4c89d10591fd05f2d6b2aa/coverage-7.10.5-cp313-cp313-win32.whl", hash = "sha256:0d511dda38595b2b6934c2b730a1fd57a3635c6aa2a04cb74714cdfdd53846f4", size = 219536, upload-time = "2025-08-23T14:41:27.694Z" }, + { url = "https://files.pythonhosted.org/packages/fc/54/baacb8f2f74431e3b175a9a2881feaa8feb6e2f187a0e7e3046f3c7742b2/coverage-7.10.5-cp313-cp313-win_amd64.whl", hash = "sha256:9a86281794a393513cf117177fd39c796b3f8e3759bb2764259a2abba5cce54b", size = 220330, upload-time = "2025-08-23T14:41:29.081Z" }, + { url = "https://files.pythonhosted.org/packages/64/8a/82a3788f8e31dee51d350835b23d480548ea8621f3effd7c3ba3f7e5c006/coverage-7.10.5-cp313-cp313-win_arm64.whl", hash = "sha256:cebd8e906eb98bb09c10d1feed16096700b1198d482267f8bf0474e63a7b8d84", size = 218961, upload-time = "2025-08-23T14:41:30.511Z" }, + { url = "https://files.pythonhosted.org/packages/d8/a1/590154e6eae07beee3b111cc1f907c30da6fc8ce0a83ef756c72f3c7c748/coverage-7.10.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0520dff502da5e09d0d20781df74d8189ab334a1e40d5bafe2efaa4158e2d9e7", size = 217819, upload-time = "2025-08-23T14:41:31.962Z" }, + { url = "https://files.pythonhosted.org/packages/0d/ff/436ffa3cfc7741f0973c5c89405307fe39b78dcf201565b934e6616fc4ad/coverage-7.10.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d9cd64aca68f503ed3f1f18c7c9174cbb797baba02ca8ab5112f9d1c0328cd4b", size = 218040, upload-time = "2025-08-23T14:41:33.472Z" }, + { url = "https://files.pythonhosted.org/packages/a0/ca/5787fb3d7820e66273913affe8209c534ca11241eb34ee8c4fd2aaa9dd87/coverage-7.10.5-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0913dd1613a33b13c4f84aa6e3f4198c1a21ee28ccb4f674985c1f22109f0aae", size = 259374, upload-time = "2025-08-23T14:41:34.914Z" }, + { url = "https://files.pythonhosted.org/packages/b5/89/21af956843896adc2e64fc075eae3c1cadb97ee0a6960733e65e696f32dd/coverage-7.10.5-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1b7181c0feeb06ed8a02da02792f42f829a7b29990fef52eff257fef0885d760", size = 261551, upload-time = "2025-08-23T14:41:36.333Z" }, + { url = "https://files.pythonhosted.org/packages/e1/96/390a69244ab837e0ac137989277879a084c786cf036c3c4a3b9637d43a89/coverage-7.10.5-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36d42b7396b605f774d4372dd9c49bed71cbabce4ae1ccd074d155709dd8f235", size = 263776, upload-time = "2025-08-23T14:41:38.25Z" }, + { url = "https://files.pythonhosted.org/packages/00/32/cfd6ae1da0a521723349f3129b2455832fc27d3f8882c07e5b6fefdd0da2/coverage-7.10.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b4fdc777e05c4940b297bf47bf7eedd56a39a61dc23ba798e4b830d585486ca5", size = 261326, upload-time = "2025-08-23T14:41:40.343Z" }, + { url = "https://files.pythonhosted.org/packages/4c/c4/bf8d459fb4ce2201e9243ce6c015936ad283a668774430a3755f467b39d1/coverage-7.10.5-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:42144e8e346de44a6f1dbd0a56575dd8ab8dfa7e9007da02ea5b1c30ab33a7db", size = 259090, upload-time = "2025-08-23T14:41:42.106Z" }, + { url = "https://files.pythonhosted.org/packages/f4/5d/a234f7409896468e5539d42234016045e4015e857488b0b5b5f3f3fa5f2b/coverage-7.10.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:66c644cbd7aed8fe266d5917e2c9f65458a51cfe5eeff9c05f15b335f697066e", size = 260217, upload-time = "2025-08-23T14:41:43.591Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ad/87560f036099f46c2ddd235be6476dd5c1d6be6bb57569a9348d43eeecea/coverage-7.10.5-cp313-cp313t-win32.whl", hash = "sha256:2d1b73023854068c44b0c554578a4e1ef1b050ed07cf8b431549e624a29a66ee", size = 220194, upload-time = "2025-08-23T14:41:45.051Z" }, + { url = "https://files.pythonhosted.org/packages/36/a8/04a482594fdd83dc677d4a6c7e2d62135fff5a1573059806b8383fad9071/coverage-7.10.5-cp313-cp313t-win_amd64.whl", hash = "sha256:54a1532c8a642d8cc0bd5a9a51f5a9dcc440294fd06e9dda55e743c5ec1a8f14", size = 221258, upload-time = "2025-08-23T14:41:46.44Z" }, + { url = "https://files.pythonhosted.org/packages/eb/ad/7da28594ab66fe2bc720f1bc9b131e62e9b4c6e39f044d9a48d18429cc21/coverage-7.10.5-cp313-cp313t-win_arm64.whl", hash = "sha256:74d5b63fe3f5f5d372253a4ef92492c11a4305f3550631beaa432fc9df16fcff", size = 219521, upload-time = "2025-08-23T14:41:47.882Z" }, + { url = "https://files.pythonhosted.org/packages/d3/7f/c8b6e4e664b8a95254c35a6c8dd0bf4db201ec681c169aae2f1256e05c85/coverage-7.10.5-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:68c5e0bc5f44f68053369fa0d94459c84548a77660a5f2561c5e5f1e3bed7031", size = 217090, upload-time = "2025-08-23T14:41:49.327Z" }, + { url = "https://files.pythonhosted.org/packages/44/74/3ee14ede30a6e10a94a104d1d0522d5fb909a7c7cac2643d2a79891ff3b9/coverage-7.10.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cf33134ffae93865e32e1e37df043bef15a5e857d8caebc0099d225c579b0fa3", size = 217365, upload-time = "2025-08-23T14:41:50.796Z" }, + { url = "https://files.pythonhosted.org/packages/41/5f/06ac21bf87dfb7620d1f870dfa3c2cae1186ccbcdc50b8b36e27a0d52f50/coverage-7.10.5-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ad8fa9d5193bafcf668231294241302b5e683a0518bf1e33a9a0dfb142ec3031", size = 248413, upload-time = "2025-08-23T14:41:52.5Z" }, + { url = "https://files.pythonhosted.org/packages/21/bc/cc5bed6e985d3a14228539631573f3863be6a2587381e8bc5fdf786377a1/coverage-7.10.5-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:146fa1531973d38ab4b689bc764592fe6c2f913e7e80a39e7eeafd11f0ef6db2", size = 250943, upload-time = "2025-08-23T14:41:53.922Z" }, + { url = "https://files.pythonhosted.org/packages/8d/43/6a9fc323c2c75cd80b18d58db4a25dc8487f86dd9070f9592e43e3967363/coverage-7.10.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6013a37b8a4854c478d3219ee8bc2392dea51602dd0803a12d6f6182a0061762", size = 252301, upload-time = "2025-08-23T14:41:56.528Z" }, + { url = "https://files.pythonhosted.org/packages/69/7c/3e791b8845f4cd515275743e3775adb86273576596dc9f02dca37357b4f2/coverage-7.10.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:eb90fe20db9c3d930fa2ad7a308207ab5b86bf6a76f54ab6a40be4012d88fcae", size = 250302, upload-time = "2025-08-23T14:41:58.171Z" }, + { url = "https://files.pythonhosted.org/packages/5c/bc/5099c1e1cb0c9ac6491b281babea6ebbf999d949bf4aa8cdf4f2b53505e8/coverage-7.10.5-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:384b34482272e960c438703cafe63316dfbea124ac62006a455c8410bf2a2262", size = 248237, upload-time = "2025-08-23T14:41:59.703Z" }, + { url = "https://files.pythonhosted.org/packages/7e/51/d346eb750a0b2f1e77f391498b753ea906fde69cc11e4b38dca28c10c88c/coverage-7.10.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:467dc74bd0a1a7de2bedf8deaf6811f43602cb532bd34d81ffd6038d6d8abe99", size = 249726, upload-time = "2025-08-23T14:42:01.343Z" }, + { url = "https://files.pythonhosted.org/packages/a3/85/eebcaa0edafe427e93286b94f56ea7e1280f2c49da0a776a6f37e04481f9/coverage-7.10.5-cp314-cp314-win32.whl", hash = "sha256:556d23d4e6393ca898b2e63a5bca91e9ac2d5fb13299ec286cd69a09a7187fde", size = 219825, upload-time = "2025-08-23T14:42:03.263Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f7/6d43e037820742603f1e855feb23463979bf40bd27d0cde1f761dcc66a3e/coverage-7.10.5-cp314-cp314-win_amd64.whl", hash = "sha256:f4446a9547681533c8fa3e3c6cf62121eeee616e6a92bd9201c6edd91beffe13", size = 220618, upload-time = "2025-08-23T14:42:05.037Z" }, + { url = "https://files.pythonhosted.org/packages/4a/b0/ed9432e41424c51509d1da603b0393404b828906236fb87e2c8482a93468/coverage-7.10.5-cp314-cp314-win_arm64.whl", hash = "sha256:5e78bd9cf65da4c303bf663de0d73bf69f81e878bf72a94e9af67137c69b9fe9", size = 219199, upload-time = "2025-08-23T14:42:06.662Z" }, + { url = "https://files.pythonhosted.org/packages/2f/54/5a7ecfa77910f22b659c820f67c16fc1e149ed132ad7117f0364679a8fa9/coverage-7.10.5-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:5661bf987d91ec756a47c7e5df4fbcb949f39e32f9334ccd3f43233bbb65e508", size = 217833, upload-time = "2025-08-23T14:42:08.262Z" }, + { url = "https://files.pythonhosted.org/packages/4e/0e/25672d917cc57857d40edf38f0b867fb9627115294e4f92c8fcbbc18598d/coverage-7.10.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a46473129244db42a720439a26984f8c6f834762fc4573616c1f37f13994b357", size = 218048, upload-time = "2025-08-23T14:42:10.247Z" }, + { url = "https://files.pythonhosted.org/packages/cb/7c/0b2b4f1c6f71885d4d4b2b8608dcfc79057adb7da4143eb17d6260389e42/coverage-7.10.5-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1f64b8d3415d60f24b058b58d859e9512624bdfa57a2d1f8aff93c1ec45c429b", size = 259549, upload-time = "2025-08-23T14:42:11.811Z" }, + { url = "https://files.pythonhosted.org/packages/94/73/abb8dab1609abec7308d83c6aec547944070526578ee6c833d2da9a0ad42/coverage-7.10.5-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:44d43de99a9d90b20e0163f9770542357f58860a26e24dc1d924643bd6aa7cb4", size = 261715, upload-time = "2025-08-23T14:42:13.505Z" }, + { url = "https://files.pythonhosted.org/packages/0b/d1/abf31de21ec92731445606b8d5e6fa5144653c2788758fcf1f47adb7159a/coverage-7.10.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a931a87e5ddb6b6404e65443b742cb1c14959622777f2a4efd81fba84f5d91ba", size = 263969, upload-time = "2025-08-23T14:42:15.422Z" }, + { url = "https://files.pythonhosted.org/packages/9c/b3/ef274927f4ebede96056173b620db649cc9cb746c61ffc467946b9d0bc67/coverage-7.10.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f9559b906a100029274448f4c8b8b0a127daa4dade5661dfd821b8c188058842", size = 261408, upload-time = "2025-08-23T14:42:16.971Z" }, + { url = "https://files.pythonhosted.org/packages/20/fc/83ca2812be616d69b4cdd4e0c62a7bc526d56875e68fd0f79d47c7923584/coverage-7.10.5-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:b08801e25e3b4526ef9ced1aa29344131a8f5213c60c03c18fe4c6170ffa2874", size = 259168, upload-time = "2025-08-23T14:42:18.512Z" }, + { url = "https://files.pythonhosted.org/packages/fc/4f/e0779e5716f72d5c9962e709d09815d02b3b54724e38567308304c3fc9df/coverage-7.10.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ed9749bb8eda35f8b636fb7632f1c62f735a236a5d4edadd8bbcc5ea0542e732", size = 260317, upload-time = "2025-08-23T14:42:20.005Z" }, + { url = "https://files.pythonhosted.org/packages/2b/fe/4247e732f2234bb5eb9984a0888a70980d681f03cbf433ba7b48f08ca5d5/coverage-7.10.5-cp314-cp314t-win32.whl", hash = "sha256:609b60d123fc2cc63ccee6d17e4676699075db72d14ac3c107cc4976d516f2df", size = 220600, upload-time = "2025-08-23T14:42:22.027Z" }, + { url = "https://files.pythonhosted.org/packages/a7/a0/f294cff6d1034b87839987e5b6ac7385bec599c44d08e0857ac7f164ad0c/coverage-7.10.5-cp314-cp314t-win_amd64.whl", hash = "sha256:0666cf3d2c1626b5a3463fd5b05f5e21f99e6aec40a3192eee4d07a15970b07f", size = 221714, upload-time = "2025-08-23T14:42:23.616Z" }, + { url = "https://files.pythonhosted.org/packages/23/18/fa1afdc60b5528d17416df440bcbd8fd12da12bfea9da5b6ae0f7a37d0f7/coverage-7.10.5-cp314-cp314t-win_arm64.whl", hash = "sha256:bc85eb2d35e760120540afddd3044a5bf69118a91a296a8b3940dfc4fdcfe1e2", size = 219735, upload-time = "2025-08-23T14:42:25.156Z" }, + { url = "https://files.pythonhosted.org/packages/3b/21/05248e8bc74683488cb7477e6b6b878decadd15af0ec96f56381d3d7ff2d/coverage-7.10.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:62835c1b00c4a4ace24c1a88561a5a59b612fbb83a525d1c70ff5720c97c0610", size = 216763, upload-time = "2025-08-23T14:42:26.75Z" }, + { url = "https://files.pythonhosted.org/packages/a9/7f/161a0ad40cb1c7e19dc1aae106d3430cc88dac3d651796d6cf3f3730c800/coverage-7.10.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5255b3bbcc1d32a4069d6403820ac8e6dbcc1d68cb28a60a1ebf17e47028e898", size = 217154, upload-time = "2025-08-23T14:42:28.238Z" }, + { url = "https://files.pythonhosted.org/packages/de/31/41929ee53af829ea5a88e71d335ea09d0bb587a3da1c5e58e59b48473ed8/coverage-7.10.5-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3876385722e335d6e991c430302c24251ef9c2a9701b2b390f5473199b1b8ebf", size = 243588, upload-time = "2025-08-23T14:42:29.798Z" }, + { url = "https://files.pythonhosted.org/packages/6e/4e/2649344e33eeb3567041e8255a1942173cae81817fe06b60f3fafaafe111/coverage-7.10.5-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8048ce4b149c93447a55d279078c8ae98b08a6951a3c4d2d7e87f4efc7bfe100", size = 245412, upload-time = "2025-08-23T14:42:31.296Z" }, + { url = "https://files.pythonhosted.org/packages/ac/b1/b21e1e69986ad89b051dd42c3ef06d9326e03ac3c0c844fc33385d1d9e35/coverage-7.10.5-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4028e7558e268dd8bcf4d9484aad393cafa654c24b4885f6f9474bf53183a82a", size = 247182, upload-time = "2025-08-23T14:42:33.155Z" }, + { url = "https://files.pythonhosted.org/packages/4c/b5/80837be411ae092e03fcc2a7877bd9a659c531eff50453e463057a9eee44/coverage-7.10.5-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:03f47dc870eec0367fcdd603ca6a01517d2504e83dc18dbfafae37faec66129a", size = 245066, upload-time = "2025-08-23T14:42:34.754Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ed/fcb0838ddf149d68d09f89af57397b0dd9d26b100cc729daf1b0caf0b2d3/coverage-7.10.5-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2d488d7d42b6ded7ea0704884f89dcabd2619505457de8fc9a6011c62106f6e5", size = 243138, upload-time = "2025-08-23T14:42:36.311Z" }, + { url = "https://files.pythonhosted.org/packages/75/0f/505c6af24a9ae5d8919d209b9c31b7092815f468fa43bec3b1118232c62a/coverage-7.10.5-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b3dcf2ead47fa8be14224ee817dfc1df98043af568fe120a22f81c0eb3c34ad2", size = 244095, upload-time = "2025-08-23T14:42:38.227Z" }, + { url = "https://files.pythonhosted.org/packages/e4/7e/c82a8bede46217c1d944bd19b65e7106633b998640f00ab49c5f747a5844/coverage-7.10.5-cp39-cp39-win32.whl", hash = "sha256:02650a11324b80057b8c9c29487020073d5e98a498f1857f37e3f9b6ea1b2426", size = 219289, upload-time = "2025-08-23T14:42:39.827Z" }, + { url = "https://files.pythonhosted.org/packages/9a/ac/46645ef6be543f2e7de08cc2601a0b67e130c816be3b749ab741be689fb9/coverage-7.10.5-cp39-cp39-win_amd64.whl", hash = "sha256:b45264dd450a10f9e03237b41a9a24e85cbb1e278e5a32adb1a303f58f0017f3", size = 220199, upload-time = "2025-08-23T14:42:41.363Z" }, + { url = "https://files.pythonhosted.org/packages/08/b6/fff6609354deba9aeec466e4bcaeb9d1ed3e5d60b14b57df2a36fb2273f2/coverage-7.10.5-py3-none-any.whl", hash = "sha256:0be24d35e4db1d23d0db5c0f6a74a962e2ec83c426b5cac09f4234aadef38e4a", size = 208736, upload-time = "2025-08-23T14:42:43.145Z" }, ] [package.optional-dependencies] @@ -262,82 +318,60 @@ wheels = [ [[package]] name = "gitpython" -version = "3.1.44" +version = "3.1.45" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "gitdb" }, + { name = "typing-extensions", marker = "python_full_version < '3.10'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c0/89/37df0b71473153574a5cdef8f242de422a0f5d26d7a9e231e6f169b4ad14/gitpython-3.1.44.tar.gz", hash = "sha256:c87e30b26253bf5418b01b0660f818967f3c503193838337fe5e573331249269", size = 214196, upload-time = "2025-01-02T07:32:43.59Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/c8/dd58967d119baab745caec2f9d853297cec1989ec1d63f677d3880632b88/gitpython-3.1.45.tar.gz", hash = "sha256:85b0ee964ceddf211c41b9f27a49086010a190fd8132a24e21f362a4b36a791c", size = 215076, upload-time = "2025-07-24T03:45:54.871Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/9a/4114a9057db2f1462d5c8f8390ab7383925fe1ac012eaa42402ad65c2963/GitPython-3.1.44-py3-none-any.whl", hash = "sha256:9e0e10cda9bed1ee64bc9a6de50e7e38a9c9943241cd7f585f6df3ed28011110", size = 207599, upload-time = "2025-01-02T07:32:40.731Z" }, + { url = "https://files.pythonhosted.org/packages/01/61/d4b89fec821f72385526e1b9d9a3a0385dda4a72b206d28049e2c7cd39b8/gitpython-3.1.45-py3-none-any.whl", hash = "sha256:8908cb2e02fb3b93b7eb0f2827125cb699869470432cc885f019b8fd0fccff77", size = 208168, upload-time = "2025-07-24T03:45:52.517Z" }, ] [[package]] name = "griffe" -version = "1.6.1" +version = "1.13.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6a/ba/1ebe51a22c491a3fc94b44ef9c46a5b5472540e24a5c3f251cebbab7214b/griffe-1.6.1.tar.gz", hash = "sha256:ff0acf706b2680f8c721412623091c891e752b2c61b7037618f7b77d06732cf5", size = 393112, upload-time = "2025-03-18T15:18:45.489Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c6/b5/23b91f22b7b3a7f8f62223f6664946271c0f5cb4179605a3e6bbae863920/griffe-1.13.0.tar.gz", hash = "sha256:246ea436a5e78f7fbf5f24ca8a727bb4d2a4b442a2959052eea3d0bfe9a076e0", size = 412759, upload-time = "2025-08-26T13:27:11.422Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/d3/a760d1062e44587230aa65573c70edaad4ee8a0e60e193a3172b304d24d8/griffe-1.6.1-py3-none-any.whl", hash = "sha256:b0131670db16834f82383bcf4f788778853c9bf4dc7a1a2b708bb0808ca56a98", size = 128615, upload-time = "2025-03-18T15:18:43.57Z" }, + { url = "https://files.pythonhosted.org/packages/aa/8c/b7cfdd8dfe48f6b09f7353323732e1a290c388bd14f216947928dc85f904/griffe-1.13.0-py3-none-any.whl", hash = "sha256:470fde5b735625ac0a36296cd194617f039e9e83e301fcbd493e2b58382d0559", size = 139365, upload-time = "2025-08-26T13:27:09.882Z" }, ] [[package]] name = "griffe-fieldz" -version = "0.2.1" +version = "0.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "fieldz" }, { name = "griffe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/95/95/5c47b6c54d106c76aad9b51177401ad4215a0de910c6853b689a2c6d07da/griffe_fieldz-0.2.1.tar.gz", hash = "sha256:7371693cdd045ac95aaebd16a81586d8dfa72c30e2d4519e0dde0d80f8ef0dc7", size = 8148, upload-time = "2025-01-12T20:55:09.063Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c5/6a/94754bf39fd63ba424c667b2abf0ade78e3878e223591d1fb9c3e8a77bce/griffe_fieldz-0.3.0.tar.gz", hash = "sha256:42e7707dac51d38e26fb7f3f7f51429da9b47e98060bfeb81a4287456d5b8a89", size = 10149, upload-time = "2025-07-30T21:43:10.042Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6f/fc/c059d128d77a369f54a3753162a38cd7668e5b4d14464672001a50cf2e9d/griffe_fieldz-0.2.1-py3-none-any.whl", hash = "sha256:04ae78b487c832a38b0495f971784d513da413b867c51e429f39d74f76d4f941", size = 5691, upload-time = "2025-01-12T20:55:07.634Z" }, + { url = "https://files.pythonhosted.org/packages/4d/33/cc527c11132a6274724a04938d50e1ff2b54a5f5943cd0480427571e1adb/griffe_fieldz-0.3.0-py3-none-any.whl", hash = "sha256:52e02fdcbdf6dea3c8c95756d1e0b30861569f871d19437fda702776fde4e64d", size = 6577, upload-time = "2025-07-30T21:43:09.073Z" }, ] [[package]] name = "harp-protocol" -version = "0.2.0a2" -source = { editable = "." } +version = "0.3.0" +source = { editable = "src/harp-protocol" } + +[[package]] +name = "harp-serial" +version = "0.3.0" +source = { editable = "src/harp-serial" } dependencies = [ + { name = "harp-protocol" }, { name = "pyserial" }, ] -[package.dev-dependencies] -dev = [ - { name = "griffe-fieldz" }, - { name = "mkdocs" }, - { name = "mkdocs-codeinclude-plugin" }, - { name = "mkdocs-git-authors-plugin" }, - { name = "mkdocs-git-committers-plugin-2" }, - { name = "mkdocs-include-markdown-plugin" }, - { name = "mkdocs-material" }, - { name = "mkdocs-monorepo-plugin" }, - { name = "mkdocstrings-python" }, - { name = "pytest" }, - { name = "pytest-cov" }, - { name = "ruff" }, -] - [package.metadata] -requires-dist = [{ name = "pyserial", specifier = ">=3.5" }] - -[package.metadata.requires-dev] -dev = [ - { name = "griffe-fieldz", specifier = ">=0.2.1" }, - { name = "mkdocs", specifier = ">=1.6.1" }, - { name = "mkdocs-codeinclude-plugin", specifier = ">=0.2.1" }, - { name = "mkdocs-git-authors-plugin", specifier = ">=0.9.4" }, - { name = "mkdocs-git-committers-plugin-2", specifier = ">=2.5.0" }, - { name = "mkdocs-include-markdown-plugin", specifier = ">=7.1.6" }, - { name = "mkdocs-material", specifier = ">=9.6.9" }, - { name = "mkdocs-monorepo-plugin", specifier = ">=1.1.2" }, - { name = "mkdocstrings-python", specifier = ">=1.16.6" }, - { name = "pytest", specifier = ">=8.3.5" }, - { name = "pytest-cov", specifier = ">=6.1.1" }, - { name = "ruff", specifier = ">=0.11.0" }, +requires-dist = [ + { name = "harp-protocol", editable = "src/harp-protocol" }, + { name = "pyserial", specifier = ">=3.5" }, ] [[package]] @@ -363,11 +397,11 @@ wheels = [ [[package]] name = "iniconfig" -version = "2.0.0" +version = "2.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646, upload-time = "2023-01-07T11:08:11.254Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892, upload-time = "2023-01-07T11:08:09.864Z" }, + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, ] [[package]] @@ -384,14 +418,14 @@ wheels = [ [[package]] name = "markdown" -version = "3.7" +version = "3.8.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/54/28/3af612670f82f4c056911fbbbb42760255801b3068c48de792d354ff4472/markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2", size = 357086, upload-time = "2024-08-16T15:55:17.812Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/c2/4ab49206c17f75cb08d6311171f2d65798988db4360c4d1485bd0eedd67c/markdown-3.8.2.tar.gz", hash = "sha256:247b9a70dd12e27f67431ce62523e675b866d254f900c4fe75ce3dda62237c45", size = 362071, upload-time = "2025-06-19T17:12:44.483Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/08/83871f3c50fc983b88547c196d11cf8c3340e37c32d2e9d6152abe2c61f7/Markdown-3.7-py3-none-any.whl", hash = "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803", size = 106349, upload-time = "2024-08-16T15:55:16.176Z" }, + { url = "https://files.pythonhosted.org/packages/96/2b/34cc11786bc00d0f04d0f5fdc3a2b1ae0b6239eef72d3d345805f9ad92a1/markdown-3.8.2-py3-none-any.whl", hash = "sha256:5c83764dbd4e00bdd94d85a19b8d55ccca20fe35b2e678a1422b380324dd5f24", size = 106827, upload-time = "2025-06-19T17:12:42.994Z" }, ] [[package]] @@ -476,7 +510,8 @@ name = "mkdocs" version = "1.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "click" }, + { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "click", version = "8.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "ghp-import" }, { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, @@ -498,16 +533,16 @@ wheels = [ [[package]] name = "mkdocs-autorefs" -version = "1.4.1" +version = "1.4.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown" }, { name = "markupsafe" }, { name = "mkdocs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c2/44/140469d87379c02f1e1870315f3143718036a983dd0416650827b8883192/mkdocs_autorefs-1.4.1.tar.gz", hash = "sha256:4b5b6235a4becb2b10425c2fa191737e415b37aa3418919db33e5d774c9db079", size = 4131355, upload-time = "2025-03-08T13:35:21.232Z" } +sdist = { url = "https://files.pythonhosted.org/packages/51/fa/9124cd63d822e2bcbea1450ae68cdc3faf3655c69b455f3a7ed36ce6c628/mkdocs_autorefs-1.4.3.tar.gz", hash = "sha256:beee715b254455c4aa93b6ef3c67579c399ca092259cc41b7d9342573ff1fc75", size = 55425, upload-time = "2025-08-26T14:23:17.223Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/29/1125f7b11db63e8e32bcfa0752a4eea30abff3ebd0796f808e14571ddaa2/mkdocs_autorefs-1.4.1-py3-none-any.whl", hash = "sha256:9793c5ac06a6ebbe52ec0f8439256e66187badf4b5334b5fde0b128ec134df4f", size = 5782047, upload-time = "2025-03-08T13:35:18.889Z" }, + { url = "https://files.pythonhosted.org/packages/9f/4d/7123b6fa2278000688ebd338e2a06d16870aaf9eceae6ba047ea05f92df1/mkdocs_autorefs-1.4.3-py3-none-any.whl", hash = "sha256:469d85eb3114801d08e9cc55d102b3ba65917a869b893403b8987b601cf55dc9", size = 25034, upload-time = "2025-08-26T14:23:15.906Z" }, ] [[package]] @@ -540,14 +575,14 @@ wheels = [ [[package]] name = "mkdocs-git-authors-plugin" -version = "0.9.4" +version = "0.10.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mkdocs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/87/9a/063c4a3688e4669eb2054e4bf6e9cc582f6c1d85674e3f5b836ceff97c3b/mkdocs_git_authors_plugin-0.9.4.tar.gz", hash = "sha256:f5cfaf93d08981ce25591bbaf642051ed168c3886bb96ecd2dca53f0ef1973b8", size = 21914, upload-time = "2025-03-14T19:26:40.873Z" } +sdist = { url = "https://files.pythonhosted.org/packages/64/f1/b784c631b812aab80030db80127a576b68a84caac5229836fb7fcc00e055/mkdocs_git_authors_plugin-0.10.0.tar.gz", hash = "sha256:29d1973b2835663d79986fb756e02f1f0ff3fe35c278e993206bd3c550c205e4", size = 23432, upload-time = "2025-06-10T05:42:40.94Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/ac/2b5bae4047276fda2bdd14a6d4af59288fb4d5de54151ae4e6ba17611ceb/mkdocs_git_authors_plugin-0.9.4-py3-none-any.whl", hash = "sha256:84b9b56c703841189c64d8ff6947034fe0a9c14a0a8f1f6255edfcfe3a56825f", size = 20752, upload-time = "2025-03-14T19:26:39.398Z" }, + { url = "https://files.pythonhosted.org/packages/41/bc/a4166201c2789657c4d370bfcd71a5107edec185ae245675c8b9a6719243/mkdocs_git_authors_plugin-0.10.0-py3-none-any.whl", hash = "sha256:28421a99c3e872a8e205674bb80ec48524838243e5f59eaf9bd97df103e38901", size = 21899, upload-time = "2025-06-10T05:42:39.244Z" }, ] [[package]] @@ -579,11 +614,13 @@ wheels = [ [[package]] name = "mkdocs-material" -version = "9.6.9" +version = "9.6.18" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "babel" }, { name = "backrefs" }, + { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "click", version = "8.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "colorama" }, { name = "jinja2" }, { name = "markdown" }, @@ -594,9 +631,9 @@ dependencies = [ { name = "pymdown-extensions" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/11/cb/6dd3b6a7925429c0229738098ee874dbf7fa02db55558adb2c5bf86077b2/mkdocs_material-9.6.9.tar.gz", hash = "sha256:a4872139715a1f27b2aa3f3dc31a9794b7bbf36333c0ba4607cf04786c94f89c", size = 3948083, upload-time = "2025-03-17T09:28:50.879Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e6/46/db0d78add5aac29dfcd0a593bcc6049c86c77ba8a25b3a5b681c190d5e99/mkdocs_material-9.6.18.tar.gz", hash = "sha256:a2eb253bcc8b66f8c6eaf8379c10ed6e9644090c2e2e9d0971c7722dc7211c05", size = 4034856, upload-time = "2025-08-22T08:21:47.575Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/db/7c/ea5a671b2ff5d0e3f3108a7f7d75b541d683e4969aaead2a8f3e59e0fc27/mkdocs_material-9.6.9-py3-none-any.whl", hash = "sha256:6e61b7fb623ce2aa4622056592b155a9eea56ff3487d0835075360be45a4c8d1", size = 8697935, upload-time = "2025-03-17T09:28:47.481Z" }, + { url = "https://files.pythonhosted.org/packages/22/0b/545a4f8d4f9057e77f1d99640eb09aaae40c4f9034707f25636caf716ff9/mkdocs_material-9.6.18-py3-none-any.whl", hash = "sha256:dbc1e146a0ecce951a4d84f97b816a54936cdc9e1edd1667fc6868878ac06701", size = 9232642, upload-time = "2025-08-22T08:21:44.52Z" }, ] [[package]] @@ -623,7 +660,7 @@ wheels = [ [[package]] name = "mkdocstrings" -version = "0.29.0" +version = "0.30.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, @@ -633,16 +670,15 @@ dependencies = [ { name = "mkdocs" }, { name = "mkdocs-autorefs" }, { name = "pymdown-extensions" }, - { name = "typing-extensions", marker = "python_full_version < '3.10'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8e/4d/a9484dc5d926295bdf308f1f6c4f07fcc99735b970591edc414d401fcc91/mkdocstrings-0.29.0.tar.gz", hash = "sha256:3657be1384543ce0ee82112c3e521bbf48e41303aa0c229b9ffcccba057d922e", size = 1212185, upload-time = "2025-03-10T13:10:11.445Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/0a/7e4776217d4802009c8238c75c5345e23014a4706a8414a62c0498858183/mkdocstrings-0.30.0.tar.gz", hash = "sha256:5d8019b9c31ddacd780b6784ffcdd6f21c408f34c0bd1103b5351d609d5b4444", size = 106597, upload-time = "2025-07-22T23:48:45.998Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/15/47/eb876dfd84e48f31ff60897d161b309cf6a04ca270155b0662aae562b3fb/mkdocstrings-0.29.0-py3-none-any.whl", hash = "sha256:8ea98358d2006f60befa940fdebbbc88a26b37ecbcded10be726ba359284f73d", size = 1630824, upload-time = "2025-03-10T13:10:09.712Z" }, + { url = "https://files.pythonhosted.org/packages/de/b4/3c5eac68f31e124a55d255d318c7445840fa1be55e013f507556d6481913/mkdocstrings-0.30.0-py3-none-any.whl", hash = "sha256:ae9e4a0d8c1789697ac776f2e034e2ddd71054ae1cf2c2bb1433ccfd07c226f2", size = 36579, upload-time = "2025-07-22T23:48:44.152Z" }, ] [[package]] name = "mkdocstrings-python" -version = "1.16.6" +version = "1.18.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "griffe" }, @@ -650,18 +686,18 @@ dependencies = [ { name = "mkdocstrings" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8e/e7/0691e34e807a8f5c28f0988fcfeeb584f0b569ce433bf341944f14bdb3ff/mkdocstrings_python-1.16.6.tar.gz", hash = "sha256:cefe0f0e17ab4a4611f01b0a2af75e4298664e0ff54feb83c91a485bfed82dc9", size = 201565, upload-time = "2025-03-18T15:34:24.371Z" } +sdist = { url = "https://files.pythonhosted.org/packages/13/d4/6327c4e82dda667b0ff83b6f6b6a03e7b81dfd1f28cd5eda50ffe66d546f/mkdocstrings_python-1.18.0.tar.gz", hash = "sha256:0b9924b4034fe9ae43604d78fe8e5107ea2c2391620124fc833043a62e83c744", size = 207601, upload-time = "2025-08-26T14:02:30.839Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/42/ed682687ef5f248e104f82806d5d9893f6dd81d8cb4561692e190ba1a252/mkdocstrings_python-1.16.6-py3-none-any.whl", hash = "sha256:de877dd71f69878c973c4897a39683b7b6961bee7b058879095b69681488453f", size = 123207, upload-time = "2025-03-18T15:34:21.357Z" }, + { url = "https://files.pythonhosted.org/packages/5d/96/7ecc71bb9f01ee20f201b2531960b401159c6730aec90ec76a1b74bc81e1/mkdocstrings_python-1.18.0-py3-none-any.whl", hash = "sha256:f5056d8afe9a9683ad0c59001df1ecd9668b51c19b9a6b4dc0ff02cc9b76265a", size = 138182, upload-time = "2025-08-26T14:02:28.076Z" }, ] [[package]] name = "packaging" -version = "24.2" +version = "25.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950, upload-time = "2024-11-08T09:47:47.202Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451, upload-time = "2024-11-08T09:47:44.722Z" }, + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, ] [[package]] @@ -684,42 +720,89 @@ wheels = [ [[package]] name = "platformdirs" -version = "4.3.6" +version = "4.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/13/fc/128cc9cb8f03208bdbf93d3aa862e16d376844a14f9a0ce5cf4507372de4/platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", size = 21302, upload-time = "2024-09-17T19:06:50.688Z" } +sdist = { url = "https://files.pythonhosted.org/packages/23/e8/21db9c9987b0e728855bd57bff6984f67952bea55d6f75e055c46b5383e8/platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf", size = 21634, upload-time = "2025-08-26T14:32:04.268Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439, upload-time = "2024-09-17T19:06:49.212Z" }, + { url = "https://files.pythonhosted.org/packages/40/4b/2028861e724d3bd36227adfa20d3fd24c3fc6d52032f4a93c133be5d17ce/platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85", size = 18654, upload-time = "2025-08-26T14:32:02.735Z" }, ] [[package]] name = "pluggy" -version = "1.5.0" +version = "1.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955, upload-time = "2024-04-20T21:34:42.531Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556, upload-time = "2024-04-20T21:34:40.434Z" }, + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] [[package]] name = "pygments" -version = "2.19.1" +version = "2.19.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581, upload-time = "2025-01-06T17:26:30.443Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" }, + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pyharp" +version = "0.2.0" +source = { virtual = "." } +dependencies = [ + { name = "harp-protocol" }, + { name = "harp-serial" }, +] + +[package.dev-dependencies] +dev = [ + { name = "griffe-fieldz" }, + { name = "mkdocs" }, + { name = "mkdocs-codeinclude-plugin" }, + { name = "mkdocs-git-authors-plugin" }, + { name = "mkdocs-git-committers-plugin-2" }, + { name = "mkdocs-include-markdown-plugin" }, + { name = "mkdocs-material" }, + { name = "mkdocs-monorepo-plugin" }, + { name = "mkdocstrings-python" }, + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [ + { name = "harp-protocol", editable = "src/harp-protocol" }, + { name = "harp-serial", editable = "src/harp-serial" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "griffe-fieldz", specifier = ">=0.2.1" }, + { name = "mkdocs", specifier = ">=1.6.1" }, + { name = "mkdocs-codeinclude-plugin", specifier = ">=0.2.1" }, + { name = "mkdocs-git-authors-plugin", specifier = ">=0.9.4" }, + { name = "mkdocs-git-committers-plugin-2", specifier = ">=2.5.0" }, + { name = "mkdocs-include-markdown-plugin", specifier = ">=7.1.6" }, + { name = "mkdocs-material", specifier = ">=9.6.9" }, + { name = "mkdocs-monorepo-plugin", specifier = ">=1.1.2" }, + { name = "mkdocstrings-python", specifier = ">=1.16.6" }, + { name = "pytest", specifier = ">=8.3.5" }, + { name = "pytest-cov", specifier = ">=6.1.1" }, + { name = "ruff", specifier = ">=0.11.0" }, ] [[package]] name = "pymdown-extensions" -version = "10.14.3" +version = "10.16.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown" }, { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7c/44/e6de2fdc880ad0ec7547ca2e087212be815efbc9a425a8d5ba9ede602cbb/pymdown_extensions-10.14.3.tar.gz", hash = "sha256:41e576ce3f5d650be59e900e4ceff231e0aed2a88cf30acaee41e02f063a061b", size = 846846, upload-time = "2025-02-01T15:43:15.42Z" } +sdist = { url = "https://files.pythonhosted.org/packages/55/b3/6d2b3f149bc5413b0a29761c2c5832d8ce904a1d7f621e86616d96f505cc/pymdown_extensions-10.16.1.tar.gz", hash = "sha256:aace82bcccba3efc03e25d584e6a22d27a8e17caa3f4dd9f207e49b787aa9a91", size = 853277, upload-time = "2025-07-28T16:19:34.167Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/eb/f5/b9e2a42aa8f9e34d52d66de87941ecd236570c7ed2e87775ed23bbe4e224/pymdown_extensions-10.14.3-py3-none-any.whl", hash = "sha256:05e0bee73d64b9c71a4ae17c72abc2f700e8bc8403755a00580b49a4e9f189e9", size = 264467, upload-time = "2025-02-01T15:43:13.995Z" }, + { url = "https://files.pythonhosted.org/packages/e4/06/43084e6cbd4b3bc0e80f6be743b2e79fbc6eed8de9ad8c629939fa55d972/pymdown_extensions-10.16.1-py3-none-any.whl", hash = "sha256:d6ba157a6c03146a7fb122b2b9a121300056384eafeec9c9f9e584adfdb2a32d", size = 266178, upload-time = "2025-07-28T16:19:31.401Z" }, ] [[package]] @@ -733,7 +816,7 @@ wheels = [ [[package]] name = "pytest" -version = "8.3.5" +version = "8.4.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -741,24 +824,26 @@ dependencies = [ { name = "iniconfig" }, { name = "packaging" }, { name = "pluggy" }, + { name = "pygments" }, { name = "tomli", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload-time = "2025-03-02T12:54:54.503Z" } +sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload-time = "2025-03-02T12:54:52.069Z" }, + { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" }, ] [[package]] name = "pytest-cov" -version = "6.1.1" +version = "6.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "coverage", extra = ["toml"] }, + { name = "pluggy" }, { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/25/69/5f1e57f6c5a39f81411b550027bf72842c4567ff5fd572bed1edc9e4b5d9/pytest_cov-6.1.1.tar.gz", hash = "sha256:46935f7aaefba760e716c2ebfbe1c216240b9592966e7da99ea8292d4d3e2a0a", size = 66857, upload-time = "2025-04-05T14:07:51.592Z" } +sdist = { url = "https://files.pythonhosted.org/packages/18/99/668cade231f434aaa59bbfbf49469068d2ddd945000621d3d165d2e7dd7b/pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2", size = 69432, upload-time = "2025-06-12T10:47:47.684Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/28/d0/def53b4a790cfb21483016430ed828f64830dd981ebe1089971cd10cab25/pytest_cov-6.1.1-py3-none-any.whl", hash = "sha256:bddf29ed2d0ab6f4df17b4c55b0a657287db8684af9c42ea546b21b1041b3dde", size = 23841, upload-time = "2025-04-05T14:07:49.641Z" }, + { url = "https://files.pythonhosted.org/packages/bc/16/4ea354101abb1287856baa4af2732be351c7bee728065aed451b678153fd/pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5", size = 24644, upload-time = "2025-06-12T10:47:45.932Z" }, ] [[package]] @@ -840,19 +925,19 @@ wheels = [ [[package]] name = "pyyaml-env-tag" -version = "0.1" +version = "1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fb/8e/da1c6c58f751b70f8ceb1eb25bc25d524e8f14fe16edcce3f4e3ba08629c/pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb", size = 5631, upload-time = "2020-11-12T02:38:26.239Z" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/2e/79c822141bfd05a853236b504869ebc6b70159afc570e1d5a20641782eaa/pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff", size = 5737, upload-time = "2025-05-13T15:24:01.64Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/66/bbb1dd374f5c870f59c5bb1db0e18cbe7fa739415a24cbd95b2d1f5ae0c4/pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069", size = 3911, upload-time = "2020-11-12T02:38:24.638Z" }, + { url = "https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04", size = 4722, upload-time = "2025-05-13T15:23:59.629Z" }, ] [[package]] name = "requests" -version = "2.32.3" +version = "2.32.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, @@ -860,34 +945,35 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218, upload-time = "2024-05-29T15:37:49.536Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload-time = "2024-05-29T15:37:47.027Z" }, + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, ] [[package]] name = "ruff" -version = "0.11.0" +version = "0.12.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/77/2b/7ca27e854d92df5e681e6527dc0f9254c9dc06c8408317893cf96c851cdd/ruff-0.11.0.tar.gz", hash = "sha256:e55c620690a4a7ee6f1cccb256ec2157dc597d109400ae75bbf944fc9d6462e2", size = 3799407, upload-time = "2025-03-14T13:52:36.539Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/eb/8c073deb376e46ae767f4961390d17545e8535921d2f65101720ed8bd434/ruff-0.12.10.tar.gz", hash = "sha256:189ab65149d11ea69a2d775343adf5f49bb2426fc4780f65ee33b423ad2e47f9", size = 5310076, upload-time = "2025-08-21T18:23:22.595Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/48/40/3d0340a9e5edc77d37852c0cd98c5985a5a8081fc3befaeb2ae90aaafd2b/ruff-0.11.0-py3-none-linux_armv6l.whl", hash = "sha256:dc67e32bc3b29557513eb7eeabb23efdb25753684b913bebb8a0c62495095acb", size = 10098158, upload-time = "2025-03-14T13:51:55.69Z" }, - { url = "https://files.pythonhosted.org/packages/ec/a9/d8f5abb3b87b973b007649ac7bf63665a05b2ae2b2af39217b09f52abbbf/ruff-0.11.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:38c23fd9bdec4eb437b4c1e3595905a0a8edfccd63a790f818b28c78fe345639", size = 10879071, upload-time = "2025-03-14T13:51:58.989Z" }, - { url = "https://files.pythonhosted.org/packages/ab/62/aaa198614c6211677913ec480415c5e6509586d7b796356cec73a2f8a3e6/ruff-0.11.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7c8661b0be91a38bd56db593e9331beaf9064a79028adee2d5f392674bbc5e88", size = 10247944, upload-time = "2025-03-14T13:52:02.318Z" }, - { url = "https://files.pythonhosted.org/packages/9f/52/59e0a9f2cf1ce5e6cbe336b6dd0144725c8ea3b97cac60688f4e7880bf13/ruff-0.11.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b6c0e8d3d2db7e9f6efd884f44b8dc542d5b6b590fc4bb334fdbc624d93a29a2", size = 10421725, upload-time = "2025-03-14T13:52:04.303Z" }, - { url = "https://files.pythonhosted.org/packages/a6/c3/dcd71acc6dff72ce66d13f4be5bca1dbed4db678dff2f0f6f307b04e5c02/ruff-0.11.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c3156d3f4b42e57247275a0a7e15a851c165a4fc89c5e8fa30ea6da4f7407b8", size = 9954435, upload-time = "2025-03-14T13:52:06.602Z" }, - { url = "https://files.pythonhosted.org/packages/a6/9a/342d336c7c52dbd136dee97d4c7797e66c3f92df804f8f3b30da59b92e9c/ruff-0.11.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:490b1e147c1260545f6d041c4092483e3f6d8eba81dc2875eaebcf9140b53905", size = 11492664, upload-time = "2025-03-14T13:52:08.613Z" }, - { url = "https://files.pythonhosted.org/packages/84/35/6e7defd2d7ca95cc385ac1bd9f7f2e4a61b9cc35d60a263aebc8e590c462/ruff-0.11.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1bc09a7419e09662983b1312f6fa5dab829d6ab5d11f18c3760be7ca521c9329", size = 12207856, upload-time = "2025-03-14T13:52:11.019Z" }, - { url = "https://files.pythonhosted.org/packages/22/78/da669c8731bacf40001c880ada6d31bcfb81f89cc996230c3b80d319993e/ruff-0.11.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bcfa478daf61ac8002214eb2ca5f3e9365048506a9d52b11bea3ecea822bb844", size = 11645156, upload-time = "2025-03-14T13:52:13.383Z" }, - { url = "https://files.pythonhosted.org/packages/ee/47/e27d17d83530a208f4a9ab2e94f758574a04c51e492aa58f91a3ed7cbbcb/ruff-0.11.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6fbb2aed66fe742a6a3a0075ed467a459b7cedc5ae01008340075909d819df1e", size = 13884167, upload-time = "2025-03-14T13:52:15.528Z" }, - { url = "https://files.pythonhosted.org/packages/9f/5e/42ffbb0a5d4b07bbc642b7d58357b4e19a0f4774275ca6ca7d1f7b5452cd/ruff-0.11.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:92c0c1ff014351c0b0cdfdb1e35fa83b780f1e065667167bb9502d47ca41e6db", size = 11348311, upload-time = "2025-03-14T13:52:18.088Z" }, - { url = "https://files.pythonhosted.org/packages/c8/51/dc3ce0c5ce1a586727a3444a32f98b83ba99599bb1ebca29d9302886e87f/ruff-0.11.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e4fd5ff5de5f83e0458a138e8a869c7c5e907541aec32b707f57cf9a5e124445", size = 10305039, upload-time = "2025-03-14T13:52:20.476Z" }, - { url = "https://files.pythonhosted.org/packages/60/e0/475f0c2f26280f46f2d6d1df1ba96b3399e0234cf368cc4c88e6ad10dcd9/ruff-0.11.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:96bc89a5c5fd21a04939773f9e0e276308be0935de06845110f43fd5c2e4ead7", size = 9937939, upload-time = "2025-03-14T13:52:22.798Z" }, - { url = "https://files.pythonhosted.org/packages/e2/d3/3e61b7fd3e9cdd1e5b8c7ac188bec12975c824e51c5cd3d64caf81b0331e/ruff-0.11.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a9352b9d767889ec5df1483f94870564e8102d4d7e99da52ebf564b882cdc2c7", size = 10923259, upload-time = "2025-03-14T13:52:24.89Z" }, - { url = "https://files.pythonhosted.org/packages/30/32/cd74149ebb40b62ddd14bd2d1842149aeb7f74191fb0f49bd45c76909ff2/ruff-0.11.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:049a191969a10897fe052ef9cc7491b3ef6de79acd7790af7d7897b7a9bfbcb6", size = 11406212, upload-time = "2025-03-14T13:52:27.493Z" }, - { url = "https://files.pythonhosted.org/packages/00/ef/033022a6b104be32e899b00de704d7c6d1723a54d4c9e09d147368f14b62/ruff-0.11.0-py3-none-win32.whl", hash = "sha256:3191e9116b6b5bbe187447656f0c8526f0d36b6fd89ad78ccaad6bdc2fad7df2", size = 10310905, upload-time = "2025-03-14T13:52:30.46Z" }, - { url = "https://files.pythonhosted.org/packages/ed/8a/163f2e78c37757d035bd56cd60c8d96312904ca4a6deeab8442d7b3cbf89/ruff-0.11.0-py3-none-win_amd64.whl", hash = "sha256:c58bfa00e740ca0a6c43d41fb004cd22d165302f360aaa56f7126d544db31a21", size = 11411730, upload-time = "2025-03-14T13:52:32.508Z" }, - { url = "https://files.pythonhosted.org/packages/4e/f7/096f6efabe69b49d7ca61052fc70289c05d8d35735c137ef5ba5ef423662/ruff-0.11.0-py3-none-win_arm64.whl", hash = "sha256:868364fc23f5aa122b00c6f794211e85f7e78f5dffdf7c590ab90b8c4e69b657", size = 10538956, upload-time = "2025-03-14T13:52:34.491Z" }, + { url = "https://files.pythonhosted.org/packages/24/e7/560d049d15585d6c201f9eeacd2fd130def3741323e5ccf123786e0e3c95/ruff-0.12.10-py3-none-linux_armv6l.whl", hash = "sha256:8b593cb0fb55cc8692dac7b06deb29afda78c721c7ccfed22db941201b7b8f7b", size = 11935161, upload-time = "2025-08-21T18:22:26.965Z" }, + { url = "https://files.pythonhosted.org/packages/d1/b0/ad2464922a1113c365d12b8f80ed70fcfb39764288ac77c995156080488d/ruff-0.12.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ebb7333a45d56efc7c110a46a69a1b32365d5c5161e7244aaf3aa20ce62399c1", size = 12660884, upload-time = "2025-08-21T18:22:30.925Z" }, + { url = "https://files.pythonhosted.org/packages/d7/f1/97f509b4108d7bae16c48389f54f005b62ce86712120fd8b2d8e88a7cb49/ruff-0.12.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d59e58586829f8e4a9920788f6efba97a13d1fa320b047814e8afede381c6839", size = 11872754, upload-time = "2025-08-21T18:22:34.035Z" }, + { url = "https://files.pythonhosted.org/packages/12/ad/44f606d243f744a75adc432275217296095101f83f966842063d78eee2d3/ruff-0.12.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:822d9677b560f1fdeab69b89d1f444bf5459da4aa04e06e766cf0121771ab844", size = 12092276, upload-time = "2025-08-21T18:22:36.764Z" }, + { url = "https://files.pythonhosted.org/packages/06/1f/ed6c265e199568010197909b25c896d66e4ef2c5e1c3808caf461f6f3579/ruff-0.12.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:37b4a64f4062a50c75019c61c7017ff598cb444984b638511f48539d3a1c98db", size = 11734700, upload-time = "2025-08-21T18:22:39.822Z" }, + { url = "https://files.pythonhosted.org/packages/63/c5/b21cde720f54a1d1db71538c0bc9b73dee4b563a7dd7d2e404914904d7f5/ruff-0.12.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2c6f4064c69d2542029b2a61d39920c85240c39837599d7f2e32e80d36401d6e", size = 13468783, upload-time = "2025-08-21T18:22:42.559Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/39369e6ac7f2a1848f22fb0b00b690492f20811a1ac5c1fd1d2798329263/ruff-0.12.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:059e863ea3a9ade41407ad71c1de2badfbe01539117f38f763ba42a1206f7559", size = 14436642, upload-time = "2025-08-21T18:22:45.612Z" }, + { url = "https://files.pythonhosted.org/packages/e3/03/5da8cad4b0d5242a936eb203b58318016db44f5c5d351b07e3f5e211bb89/ruff-0.12.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1bef6161e297c68908b7218fa6e0e93e99a286e5ed9653d4be71e687dff101cf", size = 13859107, upload-time = "2025-08-21T18:22:48.886Z" }, + { url = "https://files.pythonhosted.org/packages/19/19/dd7273b69bf7f93a070c9cec9494a94048325ad18fdcf50114f07e6bf417/ruff-0.12.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4f1345fbf8fb0531cd722285b5f15af49b2932742fc96b633e883da8d841896b", size = 12886521, upload-time = "2025-08-21T18:22:51.567Z" }, + { url = "https://files.pythonhosted.org/packages/c0/1d/b4207ec35e7babaee62c462769e77457e26eb853fbdc877af29417033333/ruff-0.12.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f68433c4fbc63efbfa3ba5db31727db229fa4e61000f452c540474b03de52a9", size = 13097528, upload-time = "2025-08-21T18:22:54.609Z" }, + { url = "https://files.pythonhosted.org/packages/ff/00/58f7b873b21114456e880b75176af3490d7a2836033779ca42f50de3b47a/ruff-0.12.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:141ce3d88803c625257b8a6debf4a0473eb6eed9643a6189b68838b43e78165a", size = 13080443, upload-time = "2025-08-21T18:22:57.413Z" }, + { url = "https://files.pythonhosted.org/packages/12/8c/9e6660007fb10189ccb78a02b41691288038e51e4788bf49b0a60f740604/ruff-0.12.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f3fc21178cd44c98142ae7590f42ddcb587b8e09a3b849cbc84edb62ee95de60", size = 11896759, upload-time = "2025-08-21T18:23:00.473Z" }, + { url = "https://files.pythonhosted.org/packages/67/4c/6d092bb99ea9ea6ebda817a0e7ad886f42a58b4501a7e27cd97371d0ba54/ruff-0.12.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7d1a4e0bdfafcd2e3e235ecf50bf0176f74dd37902f241588ae1f6c827a36c56", size = 11701463, upload-time = "2025-08-21T18:23:03.211Z" }, + { url = "https://files.pythonhosted.org/packages/59/80/d982c55e91df981f3ab62559371380616c57ffd0172d96850280c2b04fa8/ruff-0.12.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:e67d96827854f50b9e3e8327b031647e7bcc090dbe7bb11101a81a3a2cbf1cc9", size = 12691603, upload-time = "2025-08-21T18:23:06.935Z" }, + { url = "https://files.pythonhosted.org/packages/ad/37/63a9c788bbe0b0850611669ec6b8589838faf2f4f959647f2d3e320383ae/ruff-0.12.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:ae479e1a18b439c59138f066ae79cc0f3ee250712a873d00dbafadaad9481e5b", size = 13164356, upload-time = "2025-08-21T18:23:10.225Z" }, + { url = "https://files.pythonhosted.org/packages/47/d4/1aaa7fb201a74181989970ebccd12f88c0fc074777027e2a21de5a90657e/ruff-0.12.10-py3-none-win32.whl", hash = "sha256:9de785e95dc2f09846c5e6e1d3a3d32ecd0b283a979898ad427a9be7be22b266", size = 11896089, upload-time = "2025-08-21T18:23:14.232Z" }, + { url = "https://files.pythonhosted.org/packages/ad/14/2ad38fd4037daab9e023456a4a40ed0154e9971f8d6aed41bdea390aabd9/ruff-0.12.10-py3-none-win_amd64.whl", hash = "sha256:7837eca8787f076f67aba2ca559cefd9c5cbc3a9852fd66186f4201b87c1563e", size = 13004616, upload-time = "2025-08-21T18:23:17.422Z" }, + { url = "https://files.pythonhosted.org/packages/24/3c/21cf283d67af33a8e6ed242396863af195a8a6134ec581524fd22b9811b6/ruff-0.12.10-py3-none-win_arm64.whl", hash = "sha256:cc138cc06ed9d4bfa9d667a65af7172b47840e1a98b02ce7011c391e54635ffc", size = 12074225, upload-time = "2025-08-21T18:23:20.137Z" }, ] [[package]] @@ -958,20 +1044,20 @@ wheels = [ [[package]] name = "typing-extensions" -version = "4.14.0" +version = "4.15.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d1/bc/51647cd02527e87d05cb083ccc402f93e441606ff1f01739a62c8ad09ba5/typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4", size = 107423, upload-time = "2025-06-02T14:52:11.399Z" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/69/e0/552843e0d356fbb5256d21449fa957fa4eff3bbc135a74a691ee70c7c5da/typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af", size = 43839, upload-time = "2025-06-02T14:52:10.026Z" }, + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, ] [[package]] name = "urllib3" -version = "2.3.0" +version = "2.5.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268, upload-time = "2024-12-22T07:47:30.032Z" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369, upload-time = "2024-12-22T07:47:28.074Z" }, + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, ] [[package]] From 9073f4cc81bba6277dee1be094b65f1a34a3120b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Wed, 27 Aug 2025 15:23:22 +0100 Subject: [PATCH 144/159] Update docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Grilo --- docs/index.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/index.md b/docs/index.md index 5dd1923..d50f918 100644 --- a/docs/index.md +++ b/docs/index.md @@ -14,8 +14,8 @@ pip install harp-protocol ```python from harp import MessageType, PayloadType -from harp.device import Device -from harp.messages import HarpMessage +from harp.serial import Device +from harp.protocol.messages import HarpMessage # Connect to a device device = Device("/dev/ttyUSB0") @@ -30,7 +30,7 @@ register_address = 32 value = device.send(HarpMessage.create(MessageType.READ, register_address, PayloadType.U8)) # Write to register -device.send(HarpMessage.create(MessageType.WRITE, register_address, PayloadType.U8, value)) +device.send(HarpMessage.create(MessageType.WRITE, register_address, PayloadType.U8, value.payload)) # Disconnect when done device.disconnect() @@ -40,8 +40,8 @@ or using the `with` statement: ```python from harp import MessageType, PayloadType -from harp.device import Device -from harp.messages import HarpMessage +from harp.serial import Device +from harp.protocol.messages import HarpMessage with Device("/dev/ttyUSB0") as device: # Get device information @@ -54,7 +54,7 @@ with Device("/dev/ttyUSB0") as device: value = device.send(HarpMessage.create(MessageType.READ, register_address, PayloadType.U8)) # Write to register - device.send(HarpMessage.create(MessageType.WRITE, register_address, PayloadType.U8, value)) + device.send(HarpMessage.create(MessageType.WRITE, register_address, PayloadType.U8, value.payload)) ``` ## for Linux From 8c07844983a30bc92443bde071859066089f7ecf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Grilo?= Date: Wed, 27 Aug 2025 17:25:04 +0100 Subject: [PATCH 145/159] Chore: add pyrefly extension to `extensions.json` --- .vscode/extensions.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 2faf153..dd4b3f2 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,6 +1,7 @@ { "recommendations": [ "ms-python.python", - "charliermarsh.ruff" + "charliermarsh.ruff", + "meta.pyrefly" ] } From ee5aa325160ba515853bc2f403ecb1c91af92990 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Grilo?= Date: Wed, 27 Aug 2025 17:25:39 +0100 Subject: [PATCH 146/159] Chore: add script to run docs locally on Windows --- run_docs.ps1 | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 run_docs.ps1 diff --git a/run_docs.ps1 b/run_docs.ps1 new file mode 100644 index 0000000..913404a --- /dev/null +++ b/run_docs.ps1 @@ -0,0 +1,31 @@ +# Resolve script directory (optional) and change to that directory if desired: +# Set-Location -Path (Split-Path -Path $MyInvocation.MyCommand.Definition -Parent) + +# Launch mkdocs via uv (argument handling) +param( + [string]$action = "" +) + +# Find all subdirectories of .\harp.devices (follow symlinks) +$harpRoot = Join-Path -Path (Get-Location) -ChildPath "harp.devices" +$dirs = Get-ChildItem -LiteralPath $harpRoot -Directory -Force -ErrorAction SilentlyContinue | + ForEach-Object { $_.FullName } + +# Join them with ':' (Unix-style path separator) +$harpDevicesPaths = ($dirs -join ";") + +# Prepend to PYTHONPATH (preserve existing) +if ($env:PYTHONPATH) { + $env:PYTHONPATH = "{0};{1}" -f $harpDevicesPaths, $env:PYTHONPATH +} else { + $env:PYTHONPATH = $harpDevicesPaths +} + +# Optionally print for debugging +Write-Output "PYTHONPATH set to: $env:PYTHONPATH" + +switch ($action) { + "build" { uv run mkdocs build } + "deploy" { uv run mkdocs gh-deploy } + default { uv run mkdocs serve } +} From 594399a7153e9a6cf2529d0812eedfe8f1537d8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Grilo?= Date: Wed, 27 Aug 2025 17:26:49 +0100 Subject: [PATCH 147/159] Add harp reference epoch constant --- src/harp-protocol/harp/protocol/base.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/harp-protocol/harp/protocol/base.py b/src/harp-protocol/harp/protocol/base.py index 2217dcf..8282c93 100644 --- a/src/harp-protocol/harp/protocol/base.py +++ b/src/harp-protocol/harp/protocol/base.py @@ -1,5 +1,9 @@ +from datetime import datetime from enum import IntEnum, IntFlag +# The reference epoch for UTC harp time +REFERENCE_EPOCH = datetime(1904, 1, 1) + # Bit masks for the PayloadType _isUnsigned: int = 0x00 _isSigned: int = 0x80 From 93aa50c4e2f1c90d69e4e82b9ea3f22e9b899595 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Grilo?= Date: Wed, 27 Aug 2025 17:27:34 +0100 Subject: [PATCH 148/159] Fix issues with types --- src/harp-serial/harp/serial/device.py | 51 ++++++++------------------- 1 file changed, 15 insertions(+), 36 deletions(-) diff --git a/src/harp-serial/harp/serial/device.py b/src/harp-serial/harp/serial/device.py index bdc554f..5c4063d 100644 --- a/src/harp-serial/harp/serial/device.py +++ b/src/harp-serial/harp/serial/device.py @@ -7,7 +7,6 @@ from pathlib import Path from typing import Optional -import serial from harp.protocol import ( ClockConfig, CommonRegisters, @@ -22,6 +21,8 @@ from harp.protocol.messages import HarpMessage, ReplyHarpMessage from harp.serial.harp_serial import HarpSerial +import serial + class TimeoutStrategy(Enum): RAISE = "raise" # Raise HarpTimeoutError @@ -100,7 +101,7 @@ def __init__( """ self.log = logging.getLogger(f"{__name__}.{self.__class__.__name__}") self._serial_port = serial_port - self._dump_file_path = dump_file_path + self._dump_file_path = None if dump_file_path is not None: self._dump_file_path = Path() / dump_file_path self._read_timeout_s = read_timeout_s @@ -1152,7 +1153,7 @@ def _read_who_am_i(self) -> int: """ address = CommonRegisters.WHO_AM_I - reply: ReplyHarpMessage = self.send( + reply = self.send( HarpMessage.create(MessageType.READ, address, PayloadType.U16) ) @@ -1180,9 +1181,7 @@ def _read_hw_version_h(self) -> int: """ address = CommonRegisters.HW_VERSION_H - reply: ReplyHarpMessage = self.send( - HarpMessage.create(MessageType.READ, address, PayloadType.U8) - ) + reply = self.send(HarpMessage.create(MessageType.READ, address, PayloadType.U8)) return reply.payload @@ -1197,9 +1196,7 @@ def _read_hw_version_l(self) -> int: """ address = CommonRegisters.HW_VERSION_L - reply: ReplyHarpMessage = self.send( - HarpMessage.create(MessageType.READ, address, PayloadType.U8) - ) + reply = self.send(HarpMessage.create(MessageType.READ, address, PayloadType.U8)) return reply.payload @@ -1214,9 +1211,7 @@ def _read_assembly_version(self) -> int: """ address = CommonRegisters.ASSEMBLY_VERSION - reply: ReplyHarpMessage = self.send( - HarpMessage.create(MessageType.READ, address, PayloadType.U8) - ) + reply = self.send(HarpMessage.create(MessageType.READ, address, PayloadType.U8)) return reply.payload @@ -1231,9 +1226,7 @@ def _read_harp_version_h(self) -> int: """ address = CommonRegisters.HARP_VERSION_H - reply: ReplyHarpMessage = self.send( - HarpMessage.create(MessageType.READ, address, PayloadType.U8) - ) + reply = self.send(HarpMessage.create(MessageType.READ, address, PayloadType.U8)) return reply.payload @@ -1248,9 +1241,7 @@ def _read_harp_version_l(self) -> int: """ address = CommonRegisters.HARP_VERSION_L - reply: ReplyHarpMessage = self.send( - HarpMessage.create(MessageType.READ, address, PayloadType.U8) - ) + reply = self.send(HarpMessage.create(MessageType.READ, address, PayloadType.U8)) return reply.payload @@ -1265,9 +1256,7 @@ def _read_fw_version_h(self) -> int: """ address = CommonRegisters.FIRMWARE_VERSION_H - reply: ReplyHarpMessage = self.send( - HarpMessage.create(MessageType.READ, address, PayloadType.U8) - ) + reply = self.send(HarpMessage.create(MessageType.READ, address, PayloadType.U8)) return reply.payload @@ -1282,9 +1271,7 @@ def _read_fw_version_l(self) -> int: """ address = CommonRegisters.FIRMWARE_VERSION_L - reply: ReplyHarpMessage = self.send( - HarpMessage.create(MessageType.READ, address, PayloadType.U8) - ) + reply = self.send(HarpMessage.create(MessageType.READ, address, PayloadType.U8)) return reply.payload @@ -1299,9 +1286,7 @@ def _read_device_name(self) -> str: """ address = CommonRegisters.DEVICE_NAME - reply: ReplyHarpMessage = self.send( - HarpMessage.create(MessageType.READ, address, PayloadType.U8) - ) + reply = self.send(HarpMessage.create(MessageType.READ, address, PayloadType.U8)) return reply.payload_as_string() @@ -1316,9 +1301,7 @@ def _read_serial_number(self) -> int: """ address = CommonRegisters.SERIAL_NUMBER - reply: ReplyHarpMessage = self.send( - HarpMessage.create(MessageType.READ, address, PayloadType.U8) - ) + reply = self.send(HarpMessage.create(MessageType.READ, address, PayloadType.U8)) if reply.is_error: return 0 @@ -1336,9 +1319,7 @@ def _read_clock_config(self) -> int: """ address = CommonRegisters.CLOCK_CONFIG - reply: ReplyHarpMessage = self.send( - HarpMessage.create(MessageType.READ, address, PayloadType.U8) - ) + reply = self.send(HarpMessage.create(MessageType.READ, address, PayloadType.U8)) return reply.payload @@ -1353,9 +1334,7 @@ def _read_timestamp_offset(self) -> int: """ address = CommonRegisters.TIMESTAMP_OFFSET - reply: ReplyHarpMessage = self.send( - HarpMessage.create(MessageType.READ, address, PayloadType.U8) - ) + reply = self.send(HarpMessage.create(MessageType.READ, address, PayloadType.U8)) return reply.payload From 2481ec1f537a6f13788bce1ed6909f0b4abc26cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Grilo?= Date: Wed, 27 Aug 2025 17:28:58 +0100 Subject: [PATCH 149/159] Docs: fix imports from examples to take the new project structure into account --- docs/examples/olfactometer_example/olfactometer_example.py | 5 ++--- .../read_and_write_from_registers.py | 5 ++--- docs/examples/wait_for_events/wait_for_events.py | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/docs/examples/olfactometer_example/olfactometer_example.py b/docs/examples/olfactometer_example/olfactometer_example.py index 6078a77..b4ac526 100644 --- a/docs/examples/olfactometer_example/olfactometer_example.py +++ b/docs/examples/olfactometer_example/olfactometer_example.py @@ -2,11 +2,10 @@ import time from threading import Event, Thread -from serial import SerialException - -from harp.communication.device import Device, OperationMode from harp.protocol import MessageType, PayloadType from harp.protocol.messages import HarpMessage +from harp.serial.device import Device, OperationMode +from serial import SerialException SERIAL_PORT = ( "/dev/ttyUSB0" # or "COMx" in Windows ("x" is the number of the serial port) diff --git a/docs/examples/read_and_write_from_registers/read_and_write_from_registers.py b/docs/examples/read_and_write_from_registers/read_and_write_from_registers.py index a43019f..7533fe1 100755 --- a/docs/examples/read_and_write_from_registers/read_and_write_from_registers.py +++ b/docs/examples/read_and_write_from_registers/read_and_write_from_registers.py @@ -1,8 +1,7 @@ -from serial import SerialException - -from harp.communication.device import Device from harp.protocol import MessageType, PayloadType from harp.protocol.messages import HarpMessage +from harp.serial.device import Device +from serial import SerialException SERIAL_PORT = ( "/dev/ttyUSB0" # or "COMx" in Windows ("x" is the number of the serial port) diff --git a/docs/examples/wait_for_events/wait_for_events.py b/docs/examples/wait_for_events/wait_for_events.py index 7946464..e830e3d 100755 --- a/docs/examples/wait_for_events/wait_for_events.py +++ b/docs/examples/wait_for_events/wait_for_events.py @@ -1,5 +1,5 @@ -from harp.communication.device import Device from harp.protocol import OperationMode +from harp.serial.device import Device SERIAL_PORT = ( "/dev/ttyUSB0" # or "COMx" in Windows ("x" is the number of the serial port) From ea50e70799c47f594f63a02f6879f0265fc81587 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Grilo?= Date: Tue, 16 Sep 2025 10:31:08 +0100 Subject: [PATCH 150/159] Refactor: simplify WriteHarpMessage handling of PayloadType --- src/harp-protocol/harp/protocol/messages.py | 34 +++++++++++---------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/src/harp-protocol/harp/protocol/messages.py b/src/harp-protocol/harp/protocol/messages.py index 9b6e366..05b6311 100644 --- a/src/harp-protocol/harp/protocol/messages.py +++ b/src/harp-protocol/harp/protocol/messages.py @@ -1,7 +1,7 @@ from __future__ import annotations # for type hints (PEP 563) import struct -from typing import Union, Optional +from typing import Optional, Union from harp.protocol import MessageType, PayloadType @@ -516,22 +516,22 @@ class WriteHarpMessage(HarpMessage): # Define payload type properties _PAYLOAD_CONFIG = { # payload_type: (byte_size, signed, is_float) - PayloadType.U8: (1, False, False), - PayloadType.S8: (1, True, False), - PayloadType.U16: (2, False, False), - PayloadType.S16: (2, True, False), - PayloadType.U32: (4, False, False), - PayloadType.S32: (4, True, False), - PayloadType.U64: (8, False, False), - PayloadType.S64: (8, True, False), - PayloadType.Float: (4, False, True), + PayloadType.U8: (1, False), + PayloadType.S8: (1, True), + PayloadType.U16: (2, False), + PayloadType.S16: (2, True), + PayloadType.U32: (4, False), + PayloadType.S32: (4, True), + PayloadType.U64: (8, False), + PayloadType.S64: (8, True), + PayloadType.Float: (4, False), } def __init__( self, payload_type: PayloadType, address: int, - value: Optional[int | float | list[int] | list[float]] = None, + value: int | float | list[int] | list[float], ): """ Create a WriteHarpMessage to send to a device. @@ -554,16 +554,18 @@ def __init__( self._frame = bytearray() # Get configuration for this payload type - byte_size, signed, is_float = self._PAYLOAD_CONFIG.get( - payload_type, (1, False, False) - ) + byte_size, signed = self._PAYLOAD_CONFIG.get(payload_type, (1, False)) # Convert value to payload bytes payload = bytearray() - values = value if isinstance(value, list) else [value] + + if isinstance(value, int) or isinstance(value, float): + values = [value] + else: + values = value for val in values: - if is_float: + if isinstance(val, float): payload += struct.pack(" Date: Tue, 16 Sep 2025 10:32:15 +0100 Subject: [PATCH 151/159] Chore: add VSCode setting that enables pyrefly to display type errors --- .vscode/settings.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index fcf64d1..3ad43fe 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,5 +10,6 @@ "tests" ], "python.testing.unittestEnabled": false, - "python.testing.pytestEnabled": true + "python.testing.pytestEnabled": true, + "python.pyrefly.displayTypeErrors": "force-on", } \ No newline at end of file From 36ec9adb3ee97e5cecb56c197718b5fb642d1944 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Tue, 16 Sep 2025 10:35:25 +0100 Subject: [PATCH 152/159] Removed custom message from exceptions --- src/harp-protocol/harp/protocol/exceptions.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/harp-protocol/harp/protocol/exceptions.py b/src/harp-protocol/harp/protocol/exceptions.py index 915b9ce..61650af 100644 --- a/src/harp-protocol/harp/protocol/exceptions.py +++ b/src/harp-protocol/harp/protocol/exceptions.py @@ -9,10 +9,9 @@ class HarpWriteException(HarpException): Exception raised when there is an error writing to a register in the Harp device. """ - def __init__(self, register, message): - super().__init__(f"Error writing to register {register}: {message}") + def __init__(self, register): + super().__init__(f"Error writing to register {register}") self.register = register - self.message = message class HarpReadException(HarpException): @@ -20,10 +19,9 @@ class HarpReadException(HarpException): Exception raised when there is an error reading from a register in the Harp device. """ - def __init__(self, register, message): - super().__init__(f"Error reading from register {register}: {message}") + def __init__(self, register): + super().__init__(f"Error reading from register {register}") self.register = register - self.message = message class HarpTimeoutError(HarpException): From 3272e1084182a380f9609e9a6481e1b9e02c10fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Tue, 16 Sep 2025 10:37:15 +0100 Subject: [PATCH 153/159] Pin harp-protocol version on harp-serial --- src/harp-serial/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/harp-serial/pyproject.toml b/src/harp-serial/pyproject.toml index 0432a75..cd4421b 100644 --- a/src/harp-serial/pyproject.toml +++ b/src/harp-serial/pyproject.toml @@ -7,7 +7,7 @@ license = "MIT" keywords = ['python', 'harp'] requires-python = ">=3.9,<4.0" dependencies = [ - "harp-protocol", + "harp-protocol==0.3.0", "pyserial>=3.5", ] From 34de54d8eb8dc56aaceb75df347ae1fa79273c3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Tue, 16 Sep 2025 10:52:35 +0100 Subject: [PATCH 154/159] Fix: raise HarpReadException for missing timestamp in ReplyHarpMessage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Grilo --- src/harp-protocol/harp/protocol/messages.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/harp-protocol/harp/protocol/messages.py b/src/harp-protocol/harp/protocol/messages.py index 05b6311..75f77d0 100644 --- a/src/harp-protocol/harp/protocol/messages.py +++ b/src/harp-protocol/harp/protocol/messages.py @@ -4,6 +4,7 @@ from typing import Optional, Union from harp.protocol import MessageType, PayloadType +from harp.protocol.exceptions import HarpReadException class HarpMessage: @@ -443,7 +444,7 @@ def __init__( # Timestamp is junk if it's not present. if not (self.payload_type & PayloadType.Timestamp): - self._timestamp = None + raise HarpReadException(self.address) @property def is_error(self) -> bool: From a107ebb6c3a5c73082f3705c246ff82574797748 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Tue, 16 Sep 2025 10:53:17 +0100 Subject: [PATCH 155/159] Add LaserDriverController to device_names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Grilo --- src/harp-protocol/harp/protocol/device_names.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/harp-protocol/harp/protocol/device_names.py b/src/harp-protocol/harp/protocol/device_names.py index 3f31656..4682267 100644 --- a/src/harp-protocol/harp/protocol/device_names.py +++ b/src/harp-protocol/harp/protocol/device_names.py @@ -35,6 +35,7 @@ 1280: "SoundCard", 1282: "CurrentDriver", 1296: "SyringePump", + 1298: "LaserDriverController", 1400: "LicketySplit", 1401: "SniffDetector", 1402: "Treadmill", From ed7388aa0bb7702d2dcdc63fe1605ea19f9f27f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Tue, 16 Sep 2025 10:54:27 +0100 Subject: [PATCH 156/159] Refactor payload handling in HarpMessage to properly support Python 3.9 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Grilo --- src/harp-protocol/harp/protocol/messages.py | 244 ++++++++++---------- 1 file changed, 122 insertions(+), 122 deletions(-) diff --git a/src/harp-protocol/harp/protocol/messages.py b/src/harp-protocol/harp/protocol/messages.py index 75f77d0..1ee5595 100644 --- a/src/harp-protocol/harp/protocol/messages.py +++ b/src/harp-protocol/harp/protocol/messages.py @@ -149,147 +149,147 @@ def payload(self) -> Union[int, list[int], bytearray, float, list[float]]: payload_index = payload_start + 1 # length is payload_start + payload type size - match self.payload_type: - case PayloadType.U8 | PayloadType.TimestampedU8: - if self.length == payload_start + 1: - return self._frame[payload_index] - else: # array case - return [ - int.from_bytes([self._frame[i]], byteorder="little") - for i in range(payload_index, self.length + 1) - ] - - case PayloadType.S8 | PayloadType.TimestampedS8: - if self.length == payload_start + 1: - return int.from_bytes( - [self._frame[payload_index]], byteorder="little", signed=True + pt = self.payload_type + if pt == PayloadType.U8 or pt == PayloadType.TimestampedU8: + if self.length == payload_start + 1: + return self._frame[payload_index] + else: # array case + return [ + int.from_bytes([self._frame[i]], byteorder="little") + for i in range(payload_index, self.length + 1) + ] + + elif pt == PayloadType.S8 or pt == PayloadType.TimestampedS8: + if self.length == payload_start + 1: + return int.from_bytes( + [self._frame[payload_index]], byteorder="little", signed=True + ) + else: # array case + return [ + int.from_bytes( + [self._frame[i]], + byteorder="little", + signed=True, ) - else: # array case - return [ - int.from_bytes( - [self._frame[i]], - byteorder="little", - signed=True, - ) - for i in range(payload_index, self.length + 1) - ] - - case PayloadType.U16 | PayloadType.TimestampedU16: - if self.length == payload_start + 2: - return int.from_bytes( - self._frame[payload_index : payload_index + 2], + for i in range(payload_index, self.length + 1) + ] + + elif pt == PayloadType.U16 or pt == PayloadType.TimestampedU16: + if self.length == payload_start + 2: + return int.from_bytes( + self._frame[payload_index : payload_index + 2], + byteorder="little", + signed=False, + ) + else: # array case + return [ + int.from_bytes( + self._frame[i : i + 2], byteorder="little", signed=False, ) - else: # array case - return [ - int.from_bytes( - self._frame[i : i + 2], - byteorder="little", - signed=False, - ) - for i in range(payload_index, self.length + 1, 2) - ] - - case PayloadType.S16 | PayloadType.TimestampedS16: - if self.length == payload_start + 2: - return int.from_bytes( - self._frame[payload_index : payload_index + 2], + for i in range(payload_index, self.length + 1, 2) + ] + + elif pt == PayloadType.S16 or pt == PayloadType.TimestampedS16: + if self.length == payload_start + 2: + return int.from_bytes( + self._frame[payload_index : payload_index + 2], + byteorder="little", + signed=True, + ) + else: + return [ + int.from_bytes( + self._frame[i : i + 2], byteorder="little", signed=True, ) - else: - return [ - int.from_bytes( - self._frame[i : i + 2], - byteorder="little", - signed=True, - ) - for i in range(payload_index, self.length + 1, 2) - ] - - case PayloadType.U32 | PayloadType.TimestampedU32: - if self.length == payload_start + 4: - return int.from_bytes( - self._frame[payload_index : payload_index + 4], + for i in range(payload_index, self.length + 1, 2) + ] + + elif pt == PayloadType.U32 or pt == PayloadType.TimestampedU32: + if self.length == payload_start + 4: + return int.from_bytes( + self._frame[payload_index : payload_index + 4], + byteorder="little", + signed=False, + ) + else: + return [ + int.from_bytes( + self._frame[i : i + 4], byteorder="little", signed=False, ) - else: - return [ - int.from_bytes( - self._frame[i : i + 4], - byteorder="little", - signed=False, - ) - for i in range(payload_index, self.length + 1, 4) - ] - - case PayloadType.S32 | PayloadType.TimestampedS32: - if self.length == payload_start + 4: - return int.from_bytes( - self._frame[payload_index : payload_index + 4], + for i in range(payload_index, self.length + 1, 4) + ] + + elif pt == PayloadType.S32 or pt == PayloadType.TimestampedS32: + if self.length == payload_start + 4: + return int.from_bytes( + self._frame[payload_index : payload_index + 4], + byteorder="little", + signed=True, + ) + else: + return [ + int.from_bytes( + self._frame[i : i + 4], byteorder="little", signed=True, ) - else: - return [ - int.from_bytes( - self._frame[i : i + 4], - byteorder="little", - signed=True, - ) - for i in range(payload_index, self.length + 1, 4) - ] - - case PayloadType.U64 | PayloadType.TimestampedU64: - if self.length == payload_start + 8: - return int.from_bytes( - self._frame[payload_index : payload_index + 8], + for i in range(payload_index, self.length + 1, 4) + ] + + elif pt == PayloadType.U64 or pt == PayloadType.TimestampedU64: + if self.length == payload_start + 8: + return int.from_bytes( + self._frame[payload_index : payload_index + 8], + byteorder="little", + signed=False, + ) + else: + return [ + int.from_bytes( + self._frame[i : i + 8], byteorder="little", signed=False, ) - else: - return [ - int.from_bytes( - self._frame[i : i + 8], - byteorder="little", - signed=False, - ) - for i in range(payload_index, self.length + 1, 8) - ] - - case PayloadType.S64 | PayloadType.TimestampedS64: - if self.length == payload_start + 8: - return int.from_bytes( - self._frame[payload_index : payload_index + 8], + for i in range(payload_index, self.length + 1, 8) + ] + + elif pt == PayloadType.S64 or pt == PayloadType.TimestampedS64: + if self.length == payload_start + 8: + return int.from_bytes( + self._frame[payload_index : payload_index + 8], + byteorder="little", + signed=True, + ) + else: + return [ + int.from_bytes( + self._frame[i : i + 8], byteorder="little", signed=True, ) - else: - return [ - int.from_bytes( - self._frame[i : i + 8], - byteorder="little", - signed=True, - ) - for i in range(payload_index, self.length + 1, 8) - ] - - case PayloadType.Float | PayloadType.TimestampedFloat: - if self.length == payload_start + 4: - return struct.unpack( - " int: From fbd7d64265bcde0e02654f254606e667f0d47a27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Thu, 18 Sep 2025 10:57:05 +0100 Subject: [PATCH 157/159] Add operation_ctrl methods to device MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Grilo --- src/harp-serial/harp/serial/device.py | 105 +++++++++++++++++++++++++- 1 file changed, 103 insertions(+), 2 deletions(-) diff --git a/src/harp-serial/harp/serial/device.py b/src/harp-serial/harp/serial/device.py index 5c4063d..107f313 100644 --- a/src/harp-serial/harp/serial/device.py +++ b/src/harp-serial/harp/serial/device.py @@ -7,6 +7,7 @@ from pathlib import Path from typing import Optional +import serial from harp.protocol import ( ClockConfig, CommonRegisters, @@ -21,8 +22,6 @@ from harp.protocol.messages import HarpMessage, ReplyHarpMessage from harp.serial.harp_serial import HarpSerial -import serial - class TimeoutStrategy(Enum): RAISE = "raise" # Raise HarpTimeoutError @@ -223,6 +222,108 @@ def dump_registers(self) -> list: break return replies + def read_operation_ctrl(self): + """ + Reads the OPERATION_CTRL register of the device. + + Returns + ------- + ReplyHarpMessage + The reply to the Harp message + """ + address = CommonRegisters.OPERATION_CTRL + reply = self.send(HarpMessage.create(MessageType.READ, address, PayloadType.U8)) + + # create dict with complete byte and then decode each bit according to the OperationCtrl entries + if reply is not None: + reg_value = reply.payload + result = { + "REG_VALUE": reply.payload, + "OP_MODE": OperationMode(reg_value & OperationCtrl.OP_MODE), + "DUMP": bool(reg_value & OperationCtrl.DUMP), + "MUTE_RPL": bool(reg_value & OperationCtrl.MUTE_RPL), + "VISUALEN": bool(reg_value & OperationCtrl.VISUALEN), + "OPLEDEN": bool(reg_value & OperationCtrl.OPLEDEN), + "ALIVE_EN": bool(reg_value & OperationCtrl.ALIVE_EN), + } + return result + + def write_operation_ctrl( + self, + mode: Optional[OperationMode] = None, + mute_rpl: Optional[bool] = None, + visual_en: Optional[bool] = None, + op_led_en: Optional[bool] = None, + alive_en: Optional[bool] = None, + ) -> ReplyHarpMessage | None: + """ + Writes the OPERATION_CTRL register of the device. + + Parameters + ---------- + mode : OperationMode, optional + The new operation mode value + mute_rpl : bool, optional + If True, the Replies to all the Commands are muted + visual_en : bool, optional + If True, enables the status led + op_led_en : bool, optional + If True, enables the operation LED + alive_en : bool, optional + If True, enables the ALIVE_EN bit + Returns + ------- + ReplyHarpMessage + The reply to the Harp message + """ + address = CommonRegisters.OPERATION_CTRL + + # Read register first + reg_value = self.send( + HarpMessage.create(MessageType.READ, address, PayloadType.U8) + ) + + if reg_value is None: + return reg_value + + reg_value = reg_value.payload + + if mode is not None: + # Clear old operation mode + reg_value &= ~OperationCtrl.OP_MODE + # Set new operation mode + reg_value |= mode + + if mute_rpl is not None: + if mute_rpl: + reg_value |= OperationCtrl.MUTE_RPL + else: + reg_value &= ~OperationCtrl.MUTE_RPL + + if visual_en is not None: + if visual_en: + reg_value |= OperationCtrl.VISUALEN + else: + reg_value &= ~OperationCtrl.VISUALEN + + if op_led_en is not None: + if op_led_en: + reg_value |= OperationCtrl.OPLEDEN + else: + reg_value &= ~OperationCtrl.OPLEDEN + + if alive_en is not None: + if alive_en: + reg_value |= OperationCtrl.ALIVE_EN + else: + reg_value &= ~OperationCtrl.ALIVE_EN + + reply = self.send( + HarpMessage.create(MessageType.WRITE, address, PayloadType.U8, reg_value) + ) + + return reply + def set_mode(self, mode: OperationMode) -> ReplyHarpMessage | None: """ Sets the operation mode of the device. From af7e79f00158b91b2662ea86b5a5fb7b019fbddc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Thu, 18 Sep 2025 11:12:36 +0100 Subject: [PATCH 158/159] Update documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Grilo --- README.md | 75 +++---------------------- docs/api/device.md | 1 - docs/api/messages.md | 4 -- docs/api/protocol.md | 8 +++ docs/api/serial.md | 6 ++ docs/index.md | 73 +----------------------- mkdocs.yml | 3 +- src/harp-protocol/README.md | 7 +++ src/harp-serial/README.md | 75 +++++++++++++++++++++++++ src/harp-serial/harp/serial/__init__.py | 1 + src/harp-serial/harp/serial/device.py | 15 +++++ 11 files changed, 121 insertions(+), 147 deletions(-) delete mode 100644 docs/api/device.md delete mode 100644 docs/api/messages.md create mode 100644 docs/api/serial.md create mode 100644 src/harp-protocol/README.md create mode 100644 src/harp-serial/README.md diff --git a/README.md b/README.md index 7bf37af..811f3d9 100644 --- a/README.md +++ b/README.md @@ -1,73 +1,12 @@ -# harp +# pyharp -Python implementation of the Harp protocol for hardware control and data acquisition. +This project includes two main packages: -## Installation + - **harp-protocol**: Provides the core protocol definitions and utilities for the Harp protocol. + See [Protocol API Documentation](https://fchampalimaud.github.io/pyharp/api/protocol) for details. -```bash -uv add harp-protocol -# or -pip install harp-protocol -``` + - **harp-serial**: Implements serial communication functionalities for generic Harp devices. + See [Serial API Documentation](https://fchampalimaud.github.io/pyharp/api/serial) for more information. -## Quick Start -```python -from harp import MessageType, PayloadType -from harp.device import Device -from harp.messages import HarpMessage - -# Connect to a device -device = Device("/dev/ttyUSB0") -#device = Device("COM3") # for Windows - -# Get device information -device.info() - -# define register_address -register_address = 32 - -# Read from register -value = device.send(HarpMessage.create(MessageType.READ, register_address, PayloadType.U8)) - -# Write to register -device.send(HarpMessage.create(MessageType.WRITE, register_address, PayloadType.U8, value)) - -# Disconnect when done -device.disconnect() -``` - -or using the `with` statement: - -```python -from harp import MessageType, PayloadType -from harp.device import Device -from harp.messages import HarpMessage - -with Device("/dev/ttyUSB0") as device: - # Get device information - device.info() - - # define register_address - register_address = 32 - - # Read from register - value = device.send(HarpMessage.create(MessageType.READ, register_address, PayloadType.U8)) - - # Write to register - device.send(HarpMessage.create(MessageType.WRITE, register_address, PayloadType.U8, value)) -``` - -## for Linux - -### Install UDEV Rules - -Install by either copying `10-harp.rules` over to your `/etc/udev/rules.d` folder or by symlinking it with: -```` -sudo ln -s /absolute/path/to/10-harp.rules /etc/udev/rules.d/10-harp.rules -```` - -Then reload udev rules with -```` -sudo udevadm control --reload-rules -```` +For specific Harp devices' packages please select the corresponding Harp device under the Devices section on the menu. \ No newline at end of file diff --git a/docs/api/device.md b/docs/api/device.md deleted file mode 100644 index a128d21..0000000 --- a/docs/api/device.md +++ /dev/null @@ -1 +0,0 @@ -::: harp.serial.Device diff --git a/docs/api/messages.md b/docs/api/messages.md deleted file mode 100644 index 0621fd0..0000000 --- a/docs/api/messages.md +++ /dev/null @@ -1,4 +0,0 @@ -::: harp.protocol.messages.HarpMessage -::: harp.protocol.messages.ReplyHarpMessage -::: harp.protocol.messages.ReadHarpMessage -::: harp.protocol.messages.WriteHarpMessage diff --git a/docs/api/protocol.md b/docs/api/protocol.md index 4e54672..f2e55ee 100644 --- a/docs/api/protocol.md +++ b/docs/api/protocol.md @@ -1,3 +1,7 @@ +{% include-markdown "../../src/harp-protocol/README.md" %} + +--- + ::: harp.protocol.MessageType ::: harp.protocol.PayloadType ::: harp.protocol.CommonRegisters @@ -5,3 +9,7 @@ ::: harp.protocol.OperationCtrl ::: harp.protocol.ResetMode ::: harp.protocol.ClockConfig +::: harp.protocol.messages.HarpMessage +::: harp.protocol.messages.ReplyHarpMessage +::: harp.protocol.messages.ReadHarpMessage +::: harp.protocol.messages.WriteHarpMessage diff --git a/docs/api/serial.md b/docs/api/serial.md new file mode 100644 index 0000000..c598ff0 --- /dev/null +++ b/docs/api/serial.md @@ -0,0 +1,6 @@ +{% include-markdown "../../src/harp-serial/README.md" %} + +--- + +::: harp.serial.Device +::: harp.serial.TimeoutStrategy diff --git a/docs/index.md b/docs/index.md index d50f918..96d83c6 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,72 +1 @@ -# harp - -Python implementation of the Harp protocol for hardware control and data acquisition. - -## Installation - -```bash -uv add harp-protocol -# or -pip install harp-protocol -``` - -## Quick Start - -```python -from harp import MessageType, PayloadType -from harp.serial import Device -from harp.protocol.messages import HarpMessage - -# Connect to a device -device = Device("/dev/ttyUSB0") - -# Get device information -device.info() - -# define register_address -register_address = 32 - -# Read from register -value = device.send(HarpMessage.create(MessageType.READ, register_address, PayloadType.U8)) - -# Write to register -device.send(HarpMessage.create(MessageType.WRITE, register_address, PayloadType.U8, value.payload)) - -# Disconnect when done -device.disconnect() -``` - -or using the `with` statement: - -```python -from harp import MessageType, PayloadType -from harp.serial import Device -from harp.protocol.messages import HarpMessage - -with Device("/dev/ttyUSB0") as device: - # Get device information - device.info() - - # define register_address - register_address = 32 - - # Read from register - value = device.send(HarpMessage.create(MessageType.READ, register_address, PayloadType.U8)) - - # Write to register - device.send(HarpMessage.create(MessageType.WRITE, register_address, PayloadType.U8, value.payload)) -``` - -## for Linux - -### Install UDEV Rules - -Install by either copying `10-harp.rules` over to your `/etc/udev/rules.d` folder or by symlinking it with: -```` -sudo ln -s /absolute/path/to/10-harp.rules /etc/udev/rules.d/10-harp.rules -```` - -Then reload udev rules with -```` -sudo udevadm control --reload-rules -```` +{% include-markdown "../README.md" %} \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 1cd5215..32e9df3 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -78,8 +78,7 @@ nav: - Home: index.md - API: - Protocol: api/protocol.md - - Device: api/device.md - - Messages: api/messages.md + - Serial: api/serial.md - Examples: - examples/index.md - Getting Device Info: examples/get_info/get_info.md diff --git a/src/harp-protocol/README.md b/src/harp-protocol/README.md new file mode 100644 index 0000000..80289eb --- /dev/null +++ b/src/harp-protocol/README.md @@ -0,0 +1,7 @@ +# harp-protocol + +[![PyPI version](https://badge.fury.io/py/harp-protocol.svg)](https://badge.fury.io/py/harp-protocol) + +The Harp Protocol is a binary communication protocol created in order to facilitate and unify the interaction between different devices. It was designed with efficiency and ease of parsing in mind. + +For more detail please check Harp Tech's official documentation [here](https://harp-tech.org/protocol/BinaryProtocol-8bit.html). diff --git a/src/harp-serial/README.md b/src/harp-serial/README.md new file mode 100644 index 0000000..d41a8f9 --- /dev/null +++ b/src/harp-serial/README.md @@ -0,0 +1,75 @@ +# harp-serial + +[![PyPI version](https://badge.fury.io/py/harp-serial.svg)](https://badge.fury.io/py/harp-serial) + +A Python library for communicating with Harp devices over serial connections. + +## Installation + +```bash +uv add harp-serial +# or +pip install harp-serial +``` + +## Quick Start + +```python +from harp.protocol import MessageType, PayloadType +from harp.protocol.messages import HarpMessage +from harp.serial.device import Device + +# Connect to a device +device = Device("/dev/ttyUSB0") +#device = Device("COM3") # for Windows + +# Get device information +device.info() + +# define register_address +register_address = 32 + +# Read from register +reply = device.send(HarpMessage.create(MessageType.READ, register_address, PayloadType.U8)) + +# Write to register +device.send(HarpMessage.create(MessageType.WRITE, register_address, PayloadType.U8, reply.payload)) + +# Disconnect when done +device.disconnect() +``` + +or using the `with` statement: + +```python +from harp.protocol import MessageType, PayloadType +from harp.protocol.messages import HarpMessage +from harp.serial.device import Device + +with Device("/dev/ttyUSB0") as device: + # Get device information + device.info() + + # define register_address + register_address = 32 + + # Read from register + reply = device.send(HarpMessage.create(MessageType.READ, register_address, PayloadType.U8)) + + # Write to register + device.send(HarpMessage.create(MessageType.WRITE, register_address, PayloadType.U8, reply.payload)) +``` + +## for Linux + +### Install UDEV Rules + +Install by either copying `10-harp.rules` over to your `/etc/udev/rules.d` folder or by symlinking it with: +```` +sudo ln -s /absolute/path/to/10-harp.rules /etc/udev/rules.d/10-harp.rules +```` + +Then reload udev rules with +```` +sudo udevadm control --reload-rules +```` diff --git a/src/harp-serial/harp/serial/__init__.py b/src/harp-serial/harp/serial/__init__.py index 7af9dc5..811fc78 100644 --- a/src/harp-serial/harp/serial/__init__.py +++ b/src/harp-serial/harp/serial/__init__.py @@ -1,2 +1,3 @@ from .device import Device as Device +from .device import TimeoutStrategy as TimeoutStrategy from .harp_serial import HarpSerial as HarpSerial diff --git a/src/harp-serial/harp/serial/device.py b/src/harp-serial/harp/serial/device.py index 107f313..c680687 100644 --- a/src/harp-serial/harp/serial/device.py +++ b/src/harp-serial/harp/serial/device.py @@ -24,6 +24,21 @@ class TimeoutStrategy(Enum): + """ + Strategy to handle timeouts when waiting for a reply from the device. + + Attributes + ---------- + RAISE : str + Raise HarpTimeoutError + RETURN_NONE : str + Return None + LOG_AND_RAISE : str + Log the timeout and raise HarpTimeoutError + LOG_AND_NONE : str + Log the timeout and return None + """ + RAISE = "raise" # Raise HarpTimeoutError RETURN_NONE = "return_none" # Return None LOG_AND_RAISE = "log_and_raise" From 5ea52bbbacd5119010c725c9e90755fe3fbebee8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Teixeira?= Date: Thu, 18 Sep 2025 11:21:16 +0100 Subject: [PATCH 159/159] Fix tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Grilo --- tests/test_messages.py | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/tests/test_messages.py b/tests/test_messages.py index 45ab698..6b2fb18 100644 --- a/tests/test_messages.py +++ b/tests/test_messages.py @@ -1,6 +1,7 @@ import pytest from harp.protocol import CommonRegisters, MessageType, PayloadType +from harp.protocol.exceptions import HarpReadException from harp.protocol.messages import ( HarpMessage, ReadHarpMessage, @@ -57,7 +58,7 @@ def test_reply_is_error(): 5, 42, 255, - PayloadType.U8, + PayloadType.TimestampedU8, 0, 0, 0, @@ -83,7 +84,7 @@ def test_reply_is_error(): 5, 42, 255, - PayloadType.U8, + PayloadType.TimestampedU8, 0, 0, 0, @@ -385,7 +386,7 @@ def test_reply_message_str_repr() -> None: 5, 42, 255, - PayloadType.U8, + PayloadType.TimestampedU8, 0, 0, 0, @@ -424,7 +425,7 @@ def test_payload_as_string() -> None: 5 + len(encoded), 42, 255, - PayloadType.U8, + PayloadType.TimestampedU8, 0, 0, 0, @@ -507,20 +508,17 @@ def test_timestamp_handling() -> None: assert reply.timestamp is not None assert reply.timestamp == 1 + 32 * 32e-6 # 1 second + 1 millisecond - # Create a non-timestamped message + +# test ReplyHarpMessage without TimestampedU8 raises HarpReadException +def test_reply_without_timestamp_raises() -> None: + """Test that accessing timestamp in non-timestamped message raises exception.""" frame = bytearray( [ MessageType.READ, 5, 42, 255, - PayloadType.U8, - 1, - 0, - 0, - 0, # timestamp seconds = 1 - 32, - 0, # timestamp micros = 32 (= 1ms) + PayloadType.U8, # Not a timestamped type 123, # payload 0, ] @@ -530,8 +528,9 @@ def test_timestamp_handling() -> None: checksum = sum(frame[:-1]) & 255 frame[-1] = checksum - reply = ReplyHarpMessage(frame) - assert reply.timestamp is None + with pytest.raises(HarpReadException) as excinfo: + ReplyHarpMessage(frame) + assert "not a timestamped payload type" in str(excinfo.value) def test_calculate_checksum() -> None: