harp-protocol 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1173 @@
1
+ from __future__ import annotations # enable subscriptable type hints for lists.
2
+
3
+ import logging
4
+ import queue
5
+ from io import BufferedWriter
6
+ from pathlib import Path
7
+ from typing import Optional, Union
8
+
9
+ import serial
10
+
11
+ from harp.communication.harp_serial import HarpSerial
12
+ from harp.protocol import (
13
+ ClockConfig,
14
+ CommonRegisters,
15
+ MessageType,
16
+ OperationCtrl,
17
+ OperationMode,
18
+ PayloadType,
19
+ ResetMode,
20
+ )
21
+ from harp.protocol.device_names import device_names
22
+ from harp.protocol.messages import HarpMessage, ReplyHarpMessage
23
+
24
+
25
+ class Device:
26
+ """
27
+ 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).
28
+
29
+ Attributes
30
+ ----------
31
+ WHO_AM_I : int
32
+ The device ID number. A list of devices can be found [here](https://github.com/harp-tech/protocol/blob/main/whoami.md)
33
+ DEFAULT_DEVICE_NAME : str
34
+ 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
35
+ HW_VERSION_H : int
36
+ The major hardware version
37
+ HW_VERSION_L : int
38
+ The minor hardware version
39
+ ASSEMBLY_VERSION : int
40
+ The version of the assembled components
41
+ HARP_VERSION_H : int
42
+ The major Harp core version
43
+ HARP_VERSION_L : int
44
+ The minor Harp core version
45
+ FIRMWARE_VERSION_H : int
46
+ The major firmware version
47
+ FIRMWARE_VERSION_L : int
48
+ The minor firmware version
49
+ DEVICE_NAME : str
50
+ The device name stored in the Harp device
51
+ SERIAL_NUMBER : int, optional
52
+ The serial number of the device
53
+ """
54
+
55
+ WHO_AM_I: int
56
+ DEFAULT_DEVICE_NAME: str
57
+ HW_VERSION_H: int
58
+ HW_VERSION_L: int
59
+ ASSEMBLY_VERSION: int
60
+ HARP_VERSION_H: int
61
+ HARP_VERSION_L: int
62
+ FIRMWARE_VERSION_H: int
63
+ FIRMWARE_VERSION_L: int
64
+ DEVICE_NAME: str
65
+ SERIAL_NUMBER: int
66
+ CLOCK_CONFIG: int
67
+ TIMESTAMP_OFFSET: int
68
+
69
+ _ser: HarpSerial
70
+ _dump_file_path: Path
71
+ _dump_file: Optional[BufferedWriter] = None
72
+ _read_timeout_s: float
73
+
74
+ _TIMEOUT_S: float = 1.0
75
+
76
+ def __init__(
77
+ self,
78
+ serial_port: str,
79
+ dump_file_path: Optional[str] = None,
80
+ read_timeout_s: float = 1,
81
+ ):
82
+ """
83
+ Parameters
84
+ ----------
85
+ serial_port : str
86
+ 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
87
+ dump_file_path: str, optional
88
+ The binary file to which all Harp messages will be written
89
+ read_timeout_s: float, optional
90
+ _TODO_
91
+ """
92
+ self.log = logging.getLogger(f"{__name__}.{self.__class__.__name__}")
93
+ self._serial_port = serial_port
94
+ self._dump_file_path = dump_file_path
95
+ if dump_file_path is not None:
96
+ self._dump_file_path = Path() / dump_file_path
97
+ self._read_timeout_s = read_timeout_s
98
+
99
+ # Connect to the Harp device and load the data stored in the device's common registers
100
+ self.connect()
101
+ self.load()
102
+
103
+ def load(self) -> None:
104
+ """
105
+ Loads the data stored in the device's common registers.
106
+ """
107
+ self.WHO_AM_I = self._read_who_am_i()
108
+ self.DEFAULT_DEVICE_NAME = self._read_default_device_name()
109
+ self.HW_VERSION_H = self._read_hw_version_h()
110
+ self.HW_VERSION_L = self._read_hw_version_l()
111
+ self.ASSEMBLY_VERSION = self._read_assembly_version()
112
+ self.HARP_VERSION_H = self._read_harp_version_h()
113
+ self.HARP_VERSION_L = self._read_harp_version_l()
114
+ self.FIRMWARE_VERSION_H = self._read_fw_version_h()
115
+ self.FIRMWARE_VERSION_L = self._read_fw_version_l()
116
+ self.DEVICE_NAME = self._read_device_name()
117
+ self.SERIAL_NUMBER = self._read_serial_number()
118
+ self.CLOCK_CONFIG = self._read_clock_config()
119
+ self.TIMESTAMP_OFFSET = self._read_timestamp_offset()
120
+
121
+ def info(self) -> None:
122
+ """
123
+ Prints the device information.
124
+ """
125
+ print("Device info:")
126
+ print(f"* Who am I: ({self.WHO_AM_I}) {self.DEFAULT_DEVICE_NAME}")
127
+ print(f"* HW version: {self.HW_VERSION_H}.{self.HW_VERSION_L}")
128
+ print(f"* Assembly version: {self.ASSEMBLY_VERSION}")
129
+ print(f"* HARP version: {self.HARP_VERSION_H}.{self.HARP_VERSION_L}")
130
+ print(
131
+ f"* Firmware version: {self.FIRMWARE_VERSION_H}.{self.FIRMWARE_VERSION_L}"
132
+ )
133
+ print(f"* Device user name: {self.DEVICE_NAME}")
134
+ print(f"* Serial number: {self.SERIAL_NUMBER}")
135
+ print(f"* Mode: {self._read_device_mode().name}")
136
+
137
+ def connect(self) -> None:
138
+ """
139
+ Connects to the Harp device.
140
+ """
141
+ self._ser = HarpSerial(
142
+ self._serial_port, # "/dev/tty.usbserial-A106C8O9"
143
+ baudrate=1000000,
144
+ timeout=self._TIMEOUT_S,
145
+ parity=serial.PARITY_NONE,
146
+ stopbits=1,
147
+ bytesize=8,
148
+ rtscts=True,
149
+ )
150
+
151
+ # open file if it is defined
152
+ if self._dump_file_path is not None:
153
+ self._dump_file = open(self._dump_file_path, "ab")
154
+
155
+ def disconnect(self) -> None:
156
+ """
157
+ Disconnects from the Harp device.
158
+ """
159
+ # close file if it exists
160
+ if self._dump_file:
161
+ self._dump_file.close()
162
+ self._dump_file = None
163
+
164
+ self._ser.close()
165
+
166
+ def _read_device_mode(self) -> OperationMode:
167
+ """
168
+ Reads the current operation mode of the Harp device.
169
+
170
+ Returns
171
+ -------
172
+ DeviceMode
173
+ The current device mode
174
+ """
175
+ address = CommonRegisters.OPERATION_CTRL
176
+ reply = self.send(HarpMessage.create(MessageType.READ, address, PayloadType.U8))
177
+ return OperationMode(reply.payload & OperationCtrl.OP_MODE)
178
+
179
+ def dump_registers(self) -> list:
180
+ """
181
+ Asserts the DUMP bit to dump the values of all core and app registers
182
+ 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).
183
+
184
+ Returns
185
+ -------
186
+ list
187
+ The list containing the reply Harp messages for all the device's registers
188
+ """
189
+ address = CommonRegisters.OPERATION_CTRL
190
+ reg_value = self.send(
191
+ HarpMessage.create(MessageType.READ, address, PayloadType.U8)
192
+ ).payload
193
+ # Assert DUMP bit
194
+ reg_value |= OperationCtrl.DUMP
195
+ self.send(
196
+ HarpMessage.create(MessageType.WRITE, address, PayloadType.U8, reg_value)
197
+ )
198
+
199
+ # Receive the contents of all registers as Harp Read Reply Messages
200
+ replies = []
201
+ while True:
202
+ msg = self._read()
203
+ if msg is not None:
204
+ replies.append(msg)
205
+ else:
206
+ break
207
+ return replies
208
+
209
+ def set_mode(self, mode: OperationMode) -> ReplyHarpMessage:
210
+ """
211
+ Sets the operation mode of the device.
212
+
213
+ Parameters
214
+ ----------
215
+ mode : DeviceMode
216
+ The new device mode value
217
+
218
+ Returns
219
+ -------
220
+ ReplyHarpMessage
221
+ The reply to the Harp message
222
+ """
223
+ address = CommonRegisters.OPERATION_CTRL
224
+
225
+ # Read register first
226
+ reg_value = self.send(
227
+ HarpMessage.create(MessageType.READ, address, PayloadType.U8)
228
+ ).payload
229
+
230
+ # Clear old operation mode
231
+ reg_value &= ~OperationCtrl.OP_MODE
232
+
233
+ # Set new operation mode
234
+ reg_value |= mode
235
+ reply = self.send(
236
+ HarpMessage.create(MessageType.WRITE, address, PayloadType.U8, reg_value)
237
+ )
238
+
239
+ return reply
240
+
241
+ def alive_en(self, enable: bool) -> bool:
242
+ """
243
+ Sets the ALIVE_EN bit of the device.
244
+
245
+ Parameters
246
+ ----------
247
+ enable : bool
248
+ If True, enables the ALIVE_EN bit. If False, disables it
249
+
250
+ Returns
251
+ -------
252
+ bool
253
+ True if the operation was successful, False otherwise
254
+ """
255
+ address = CommonRegisters.OPERATION_CTRL
256
+
257
+ # Read register first
258
+ reg_value = self.send(
259
+ HarpMessage.create(MessageType.READ, address, PayloadType.U8)
260
+ ).payload
261
+
262
+ if enable:
263
+ reg_value |= OperationCtrl.ALIVE_EN
264
+ else:
265
+ reg_value &= ~OperationCtrl.ALIVE_EN
266
+
267
+ reply = self.send(
268
+ HarpMessage.create(MessageType.WRITE, address, PayloadType.U8, reg_value)
269
+ )
270
+
271
+ return reply
272
+
273
+ def op_led_en(self, enable: bool) -> bool:
274
+ """
275
+ Sets the operation LED of the device.
276
+
277
+ Parameters
278
+ ----------
279
+ enable : bool
280
+ If True, enables the operation LED. If False, disables it
281
+
282
+ Returns
283
+ -------
284
+ bool
285
+ True if the operation was successful, False otherwise
286
+ """
287
+ address = CommonRegisters.OPERATION_CTRL
288
+
289
+ # Read register first
290
+ reg_value = self.send(
291
+ HarpMessage.create(MessageType.READ, address, PayloadType.U8)
292
+ ).payload
293
+
294
+ if enable:
295
+ reg_value |= OperationCtrl.OPLEDEN
296
+ else:
297
+ reg_value &= ~OperationCtrl.OPLEDEN
298
+
299
+ reply = self.send(
300
+ HarpMessage.create(MessageType.WRITE, address, PayloadType.U8, reg_value)
301
+ )
302
+
303
+ return reply
304
+
305
+ def status_led(self, enable: bool) -> bool:
306
+ """
307
+ Sets the status led of the device.
308
+
309
+ Parameters
310
+ ----------
311
+ enable : bool
312
+ If True, enables the status led. If False, disables it
313
+
314
+ Returns
315
+ -------
316
+ bool
317
+ True if the operation was successful, False otherwise
318
+ """
319
+ address = CommonRegisters.OPERATION_CTRL
320
+
321
+ # Read register first
322
+ reg_value = self.send(
323
+ HarpMessage.create(MessageType.READ, address, PayloadType.U8)
324
+ ).payload
325
+
326
+ if enable:
327
+ reg_value |= OperationCtrl.STATUS_LED
328
+ else:
329
+ reg_value &= ~OperationCtrl.STATUS_LED
330
+
331
+ reply = self.send(
332
+ HarpMessage.create(MessageType.WRITE, address, PayloadType.U8, reg_value)
333
+ )
334
+
335
+ return reply
336
+
337
+ def mute_reply(self, enable: bool) -> bool:
338
+ """
339
+ Sets the MUTE_REPLY bit of the device.
340
+
341
+ Parameters
342
+ ----------
343
+ enable : bool
344
+ If True, the Replies to all the Commands are muted. If False, un-mutes them
345
+
346
+ Returns
347
+ -------
348
+ bool
349
+ True if the operation was successful, False otherwise
350
+ """
351
+ address = CommonRegisters.OPERATION_CTRL
352
+
353
+ # Read register first
354
+ reg_value = self.send(
355
+ HarpMessage.create(MessageType.READ, address, PayloadType.U8)
356
+ ).payload
357
+
358
+ if enable:
359
+ reg_value |= OperationCtrl.MUTE_RPL
360
+ else:
361
+ reg_value &= ~OperationCtrl.MUTE_RPL
362
+
363
+ reply = self.send(
364
+ HarpMessage.create(MessageType.WRITE, address, PayloadType.U8, reg_value)
365
+ )
366
+
367
+ return reply
368
+
369
+ def reset_device(
370
+ self, reset_mode: ResetMode = ResetMode.RST_DEF
371
+ ) -> ReplyHarpMessage:
372
+ """
373
+ 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).
374
+
375
+ Returns
376
+ -------
377
+ ReplyHarpMessage
378
+ The reply to the Harp message
379
+ """
380
+ address = CommonRegisters.RESET_DEV
381
+ reply = self.send(
382
+ HarpMessage.create(MessageType.WRITE, address, PayloadType.U8, reset_mode)
383
+ )
384
+
385
+ return reply
386
+
387
+ def set_clock_config(self, clock_config: ClockConfig) -> ReplyHarpMessage:
388
+ """
389
+ Sets the clock configuration of the device.
390
+
391
+ Parameters
392
+ ----------
393
+ clock_config : ClockConfig
394
+ The clock configuration value
395
+
396
+ Returns
397
+ -------
398
+ ReplyHarpMessage
399
+ The reply to the Harp message
400
+ """
401
+ address = CommonRegisters.CLOCK_CONFIG
402
+ reply = self.send(
403
+ HarpMessage.create(MessageType.WRITE, address, PayloadType.U8, clock_config)
404
+ )
405
+
406
+ return reply
407
+
408
+ def set_timestamp_offset(self, timestamp_offset: int) -> ReplyHarpMessage:
409
+ """
410
+ 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.
411
+
412
+ Parameters
413
+ ----------
414
+ timestamp_offset : int
415
+ The timestamp offset value
416
+
417
+ Returns
418
+ -------
419
+ ReplyHarpMessage
420
+ The reply to the Harp message
421
+ """
422
+ address = CommonRegisters.TIMESTAMP_OFFSET
423
+ reply = self.send(
424
+ HarpMessage.create(
425
+ MessageType.WRITE, address, PayloadType.U8, timestamp_offset
426
+ )
427
+ )
428
+
429
+ return reply
430
+
431
+ def send(self, message: HarpMessage) -> Optional[ReplyHarpMessage]:
432
+ """
433
+ Sends a Harp message.
434
+
435
+ Parameters
436
+ ----------
437
+ message : HarpMessage
438
+ The HarpMessage containing the message to be sent to the device
439
+
440
+ Returns
441
+ -------
442
+ Optional[ReplyHarpMessage]
443
+ The reply to the Harp message or None if no reply is given
444
+ """
445
+ self._ser.write(message.frame)
446
+
447
+ reply = self._read()
448
+ if reply is None:
449
+ return None
450
+
451
+ self._dump_reply(reply.frame)
452
+
453
+ return reply
454
+
455
+ def _read(self) -> Union[ReplyHarpMessage, None]:
456
+ """
457
+ Reads an incoming serial message in a blocking way.
458
+
459
+ Returns
460
+ -------
461
+ Union[ReplyHarpMessage, None]
462
+ The incoming Harp message in case it exists
463
+ """
464
+ try:
465
+ return self._ser.msg_q.get(block=True, timeout=self._read_timeout_s)
466
+ except queue.Empty:
467
+ return None
468
+
469
+ def _dump_reply(self, reply: bytes):
470
+ """
471
+ Dumps the reply to a Harp message in the dump file in case it exists.
472
+ """
473
+ if self._dump_file:
474
+ self._dump_file.write(reply)
475
+
476
+ def get_events(self) -> list[ReplyHarpMessage]:
477
+ """
478
+ Gets all events from the event queue.
479
+
480
+ Returns
481
+ -------
482
+ list
483
+ The list containing every Harp event message that were on the queue
484
+ """
485
+ msgs = []
486
+ while True:
487
+ try:
488
+ msgs.append(self._ser.event_q.get(timeout=False))
489
+ except queue.Empty:
490
+ break
491
+ return msgs
492
+
493
+ def event_count(self) -> int:
494
+ """
495
+ Gets the number of events in the event queue.
496
+
497
+ Returns
498
+ -------
499
+ int
500
+ The number of events in the event queue
501
+ """
502
+ return self._ser.event_q.qsize()
503
+
504
+ def read_u8(self, address: int) -> ReplyHarpMessage:
505
+ """
506
+ Reads the value of a register of type U8.
507
+
508
+ Parameters
509
+ ----------
510
+ address : int
511
+ The register to be read
512
+
513
+ Returns
514
+ -------
515
+ ReplyHarpMessage
516
+ The reply to the Harp message that will contain the value read from the register
517
+ """
518
+ return self.send(
519
+ HarpMessage.create(
520
+ message_type=MessageType.READ,
521
+ address=address,
522
+ payload_type=PayloadType.U8,
523
+ )
524
+ )
525
+
526
+ def read_s8(self, address: int) -> ReplyHarpMessage:
527
+ """
528
+ Reads the value of a register of type S8.
529
+
530
+ Parameters
531
+ ----------
532
+ address : int
533
+ The register to be read
534
+
535
+ Returns
536
+ -------
537
+ ReplyHarpMessage
538
+ The reply to the Harp message that will contain the value read from the register
539
+ """
540
+ return self.send(
541
+ HarpMessage.create(
542
+ message_type=MessageType.READ,
543
+ address=address,
544
+ payload_type=PayloadType.S8,
545
+ )
546
+ )
547
+
548
+ def read_u16(self, address: int) -> ReplyHarpMessage:
549
+ """
550
+ Reads the value of a register of type U16.
551
+
552
+ Parameters
553
+ ----------
554
+ address : int
555
+ The register to be read
556
+
557
+ Returns
558
+ -------
559
+ ReplyHarpMessage
560
+ The reply to the Harp message that will contain the value read from the register
561
+ """
562
+ return self.send(
563
+ HarpMessage.create(
564
+ message_type=MessageType.READ,
565
+ address=address,
566
+ payload_type=PayloadType.U16,
567
+ )
568
+ )
569
+
570
+ def read_s16(self, address: int) -> ReplyHarpMessage:
571
+ """
572
+ Reads the value of a register of type S16.
573
+
574
+ Parameters
575
+ ----------
576
+ address : int
577
+ The register to be read
578
+
579
+ Returns
580
+ -------
581
+ ReplyHarpMessage
582
+ The reply to the Harp message that will contain the value read from the register
583
+ """
584
+ return self.send(
585
+ HarpMessage.create(
586
+ message_type=MessageType.READ,
587
+ address=address,
588
+ payload_type=PayloadType.S16,
589
+ )
590
+ )
591
+
592
+ def read_u32(self, address: int) -> ReplyHarpMessage:
593
+ """
594
+ Reads the value of a register of type U32.
595
+
596
+ Parameters
597
+ ----------
598
+ address : int
599
+ The register to be read
600
+
601
+ Returns
602
+ -------
603
+ ReplyHarpMessage
604
+ The reply to the Harp message that will contain the value read from the register
605
+ """
606
+ return self.send(
607
+ HarpMessage.create(
608
+ message_type=MessageType.READ,
609
+ address=address,
610
+ payload_type=PayloadType.U32,
611
+ )
612
+ )
613
+
614
+ def read_s32(self, address: int) -> ReplyHarpMessage:
615
+ """
616
+ Reads the value of a register of type S32.
617
+
618
+ Parameters
619
+ ----------
620
+ address : int
621
+ The register to be read
622
+
623
+ Returns
624
+ -------
625
+ ReplyHarpMessage
626
+ The reply to the Harp message that will contain the value read from the register
627
+ """
628
+ return self.send(
629
+ HarpMessage.create(
630
+ message_type=MessageType.READ,
631
+ address=address,
632
+ payload_type=PayloadType.S32,
633
+ )
634
+ )
635
+
636
+ def read_u64(self, address: int) -> ReplyHarpMessage:
637
+ """
638
+ Reads the value of a register of type U64.
639
+
640
+ Parameters
641
+ ----------
642
+ address : int
643
+ The register to be read
644
+
645
+ Returns
646
+ -------
647
+ ReplyHarpMessage
648
+ The reply to the Harp message that will contain the value read from the register
649
+ """
650
+ return self.send(
651
+ HarpMessage.create(
652
+ message_type=MessageType.READ,
653
+ address=address,
654
+ payload_type=PayloadType.U64,
655
+ )
656
+ )
657
+
658
+ def read_s64(self, address: int) -> ReplyHarpMessage:
659
+ """
660
+ Reads the value of a register of type S64.
661
+
662
+ Parameters
663
+ ----------
664
+ address : int
665
+ The register to be read
666
+
667
+ Returns
668
+ -------
669
+ ReplyHarpMessage
670
+ The reply to the Harp message that will contain the value read from the register
671
+ """
672
+ return self.send(
673
+ HarpMessage.create(
674
+ message_type=MessageType.READ,
675
+ address=address,
676
+ payload_type=PayloadType.S64,
677
+ )
678
+ )
679
+
680
+ def read_float(self, address: int) -> ReplyHarpMessage:
681
+ """
682
+ Reads the value of a register of type Float.
683
+
684
+ Parameters
685
+ ----------
686
+ address : int
687
+ The register to be read
688
+
689
+ Returns
690
+ -------
691
+ ReplyHarpMessage
692
+ The reply to the Harp message that will contain the value read from the register
693
+ """
694
+ return self.send(
695
+ HarpMessage.create(
696
+ message_type=MessageType.READ,
697
+ address=address,
698
+ payload_type=PayloadType.Float,
699
+ )
700
+ )
701
+
702
+ def write_u8(self, address: int, value: int | list[int]) -> ReplyHarpMessage:
703
+ """
704
+ Writes the value of a register of type U8.
705
+
706
+ Parameters
707
+ ----------
708
+ address : int
709
+ The register to be written on
710
+ value: int | list[int]
711
+ The value to be written to the register
712
+
713
+ Returns
714
+ -------
715
+ ReplyHarpMessage
716
+ The reply to the Harp message
717
+ """
718
+ return self.send(
719
+ HarpMessage.create(
720
+ message_type=MessageType.WRITE,
721
+ address=address,
722
+ payload_type=PayloadType.U8,
723
+ value=value,
724
+ )
725
+ )
726
+
727
+ def write_s8(self, address: int, value: int | list[int]) -> ReplyHarpMessage:
728
+ """
729
+ Writes the value of a register of type S8.
730
+
731
+ Parameters
732
+ ----------
733
+ address : int
734
+ The register to be written on
735
+ value: int | list[int]
736
+ The value to be written to the register
737
+
738
+ Returns
739
+ -------
740
+ ReplyHarpMessage
741
+ The reply to the Harp message
742
+ """
743
+ return self.send(
744
+ HarpMessage.create(
745
+ message_type=MessageType.WRITE,
746
+ address=address,
747
+ payload_type=PayloadType.S8,
748
+ value=value,
749
+ )
750
+ )
751
+
752
+ def write_u16(self, address: int, value: int | list[int]) -> ReplyHarpMessage:
753
+ """
754
+ Writes the value of a register of type U16.
755
+
756
+ Parameters
757
+ ----------
758
+ address : int
759
+ The register to be written on
760
+ value: int | list[int]
761
+ The value to be written to the register
762
+
763
+ Returns
764
+ -------
765
+ ReplyHarpMessage
766
+ The reply to the Harp message
767
+ """
768
+ return self.send(
769
+ HarpMessage.create(
770
+ message_type=MessageType.WRITE,
771
+ address=address,
772
+ payload_type=PayloadType.U16,
773
+ value=value,
774
+ )
775
+ )
776
+
777
+ def write_s16(self, address: int, value: int | list[int]) -> ReplyHarpMessage:
778
+ """
779
+ Writes the value of a register of type S16.
780
+
781
+ Parameters
782
+ ----------
783
+ address : int
784
+ The register to be written on
785
+ value: int | list[int]
786
+ The value to be written to the register
787
+
788
+ Returns
789
+ -------
790
+ ReplyHarpMessage
791
+ The reply to the Harp message
792
+ """
793
+ return self.send(
794
+ HarpMessage.create(
795
+ message_type=MessageType.WRITE,
796
+ address=address,
797
+ payload_type=PayloadType.S16,
798
+ value=value,
799
+ )
800
+ )
801
+
802
+ def write_u32(self, address: int, value: int | list[int]) -> ReplyHarpMessage:
803
+ """
804
+ Writes the value of a register of type U32.
805
+
806
+ Parameters
807
+ ----------
808
+ address : int
809
+ The register to be written on
810
+ value: int | list[int]
811
+ The value to be written to the register
812
+
813
+ Returns
814
+ -------
815
+ ReplyHarpMessage
816
+ The reply to the Harp message
817
+ """
818
+ return self.send(
819
+ HarpMessage.create(
820
+ message_type=MessageType.WRITE,
821
+ address=address,
822
+ payload_type=PayloadType.U32,
823
+ value=value,
824
+ )
825
+ )
826
+
827
+ def write_s32(self, address: int, value: int | list[int]) -> ReplyHarpMessage:
828
+ """
829
+ Writes the value of a register of type S32.
830
+
831
+ Parameters
832
+ ----------
833
+ address : int
834
+ The register to be written on
835
+ value: int | list[int]
836
+ The value to be written to the register
837
+
838
+ Returns
839
+ -------
840
+ ReplyHarpMessage
841
+ The reply to the Harp message
842
+ """
843
+ return self.send(
844
+ HarpMessage.create(
845
+ message_type=MessageType.WRITE,
846
+ address=address,
847
+ payload_type=PayloadType.S32,
848
+ value=value,
849
+ )
850
+ )
851
+
852
+ def write_u64(self, address: int, value: int | list[int]) -> ReplyHarpMessage:
853
+ """
854
+ Writes the value of a register of type U64.
855
+
856
+ Parameters
857
+ ----------
858
+ address : int
859
+ The register to be written on
860
+ value: int | list[int]
861
+ The value to be written to the register
862
+
863
+ Returns
864
+ -------
865
+ ReplyHarpMessage
866
+ The reply to the Harp message
867
+ """
868
+ return self.send(
869
+ HarpMessage.create(
870
+ message_type=MessageType.WRITE,
871
+ address=address,
872
+ payload_type=PayloadType.U64,
873
+ value=value,
874
+ )
875
+ )
876
+
877
+ def write_s64(self, address: int, value: int | list[int]) -> ReplyHarpMessage:
878
+ """
879
+ Writes the value of a register of type S64.
880
+
881
+ Parameters
882
+ ----------
883
+ address : int
884
+ The register to be written on
885
+ value: int | list[int]
886
+ The value to be written to the register
887
+
888
+ Returns
889
+ -------
890
+ ReplyHarpMessage
891
+ The reply to the Harp message
892
+ """
893
+ return self.send(
894
+ HarpMessage.create(
895
+ message_type=MessageType.WRITE,
896
+ address=address,
897
+ payload_type=PayloadType.S64,
898
+ value=value,
899
+ )
900
+ )
901
+
902
+ def write_float(self, address: int, value: float | list[float]) -> ReplyHarpMessage:
903
+ """
904
+ Writes the value of a register of type Float.
905
+
906
+ Parameters
907
+ ----------
908
+ address : int
909
+ The register to be written on
910
+ value: int | list[int]
911
+ The value to be written to the register
912
+
913
+ Returns
914
+ -------
915
+ ReplyHarpMessage
916
+ The reply to the Harp message
917
+ """
918
+ return self.send(
919
+ HarpMessage.create(
920
+ message_type=MessageType.WRITE,
921
+ address=address,
922
+ payload_type=PayloadType.Float,
923
+ value=value,
924
+ )
925
+ )
926
+
927
+ def _read_who_am_i(self) -> int:
928
+ """
929
+ Reads the value stored in the `WHO_AM_I` register.
930
+
931
+ Returns
932
+ -------
933
+ int
934
+ The value of the `WHO_AM_I` register
935
+ """
936
+ address = CommonRegisters.WHO_AM_I
937
+
938
+ reply: ReplyHarpMessage = self.send(
939
+ HarpMessage.create(MessageType.READ, address, PayloadType.U16)
940
+ )
941
+
942
+ return reply.payload
943
+
944
+ def _read_default_device_name(self) -> str:
945
+ """
946
+ Returns the `DEFAULT_DEVICE_NAME` by cross-referencing the `WHO_AM_I` with the corresponding device name in the `device_names` dictionary.
947
+
948
+ Returns
949
+ -------
950
+ str
951
+ The default device name
952
+ """
953
+ return device_names.get(self.WHO_AM_I, "Unknown device")
954
+
955
+ def _read_hw_version_h(self) -> int:
956
+ """
957
+ Reads the value stored in the `HW_VERSION_H` register.
958
+
959
+ Returns
960
+ -------
961
+ int
962
+ The value of the `HW_VERSION_H` register
963
+ """
964
+ address = CommonRegisters.HW_VERSION_H
965
+
966
+ reply: ReplyHarpMessage = self.send(
967
+ HarpMessage.create(MessageType.READ, address, PayloadType.U8)
968
+ )
969
+
970
+ return reply.payload
971
+
972
+ def _read_hw_version_l(self) -> int:
973
+ """
974
+ Reads the value stored in the `HW_VERSION_L` register.
975
+
976
+ Returns
977
+ -------
978
+ int
979
+ The value of the `HW_VERSION_L` register
980
+ """
981
+ address = CommonRegisters.HW_VERSION_L
982
+
983
+ reply: ReplyHarpMessage = self.send(
984
+ HarpMessage.create(MessageType.READ, address, PayloadType.U8)
985
+ )
986
+
987
+ return reply.payload
988
+
989
+ def _read_assembly_version(self) -> int:
990
+ """
991
+ Reads the value stored in the `ASSEMBLY_VERSION` register.
992
+
993
+ Returns
994
+ -------
995
+ int
996
+ The value of the `ASSEMBLY_VERSION` register
997
+ """
998
+ address = CommonRegisters.ASSEMBLY_VERSION
999
+
1000
+ reply: ReplyHarpMessage = self.send(
1001
+ HarpMessage.create(MessageType.READ, address, PayloadType.U8)
1002
+ )
1003
+
1004
+ return reply.payload
1005
+
1006
+ def _read_harp_version_h(self) -> int:
1007
+ """
1008
+ Reads the value stored in the `HARP_VERSION_H` register.
1009
+
1010
+ Returns
1011
+ -------
1012
+ int
1013
+ The value of the `HARP_VERSION_H` register
1014
+ """
1015
+ address = CommonRegisters.HARP_VERSION_H
1016
+
1017
+ reply: ReplyHarpMessage = self.send(
1018
+ HarpMessage.create(MessageType.READ, address, PayloadType.U8)
1019
+ )
1020
+
1021
+ return reply.payload
1022
+
1023
+ def _read_harp_version_l(self) -> int:
1024
+ """
1025
+ Reads the value stored in the `HARP_VERSION_L` register.
1026
+
1027
+ Returns
1028
+ -------
1029
+ int
1030
+ The value of the `HARP_VERSION_L` register
1031
+ """
1032
+ address = CommonRegisters.HARP_VERSION_L
1033
+
1034
+ reply: ReplyHarpMessage = self.send(
1035
+ HarpMessage.create(MessageType.READ, address, PayloadType.U8)
1036
+ )
1037
+
1038
+ return reply.payload
1039
+
1040
+ def _read_fw_version_h(self) -> int:
1041
+ """
1042
+ Reads the value stored in the `FW_VERSION_H` register.
1043
+
1044
+ Returns
1045
+ -------
1046
+ int
1047
+ The value of the `FW_VERSION_H` register
1048
+ """
1049
+ address = CommonRegisters.FIRMWARE_VERSION_H
1050
+
1051
+ reply: ReplyHarpMessage = self.send(
1052
+ HarpMessage.create(MessageType.READ, address, PayloadType.U8)
1053
+ )
1054
+
1055
+ return reply.payload
1056
+
1057
+ def _read_fw_version_l(self) -> int:
1058
+ """
1059
+ Reads the value stored in the `FW_VERSION_L` register.
1060
+
1061
+ Returns
1062
+ -------
1063
+ int
1064
+ The value of the `FW_VERSION_L` register
1065
+ """
1066
+ address = CommonRegisters.FIRMWARE_VERSION_L
1067
+
1068
+ reply: ReplyHarpMessage = self.send(
1069
+ HarpMessage.create(MessageType.READ, address, PayloadType.U8)
1070
+ )
1071
+
1072
+ return reply.payload
1073
+
1074
+ def _read_device_name(self) -> str:
1075
+ """
1076
+ Reads the value stored in the `DEVICE_NAME` register.
1077
+
1078
+ Returns
1079
+ -------
1080
+ int
1081
+ The value of the `DEVICE_NAME` register
1082
+ """
1083
+ address = CommonRegisters.DEVICE_NAME
1084
+
1085
+ reply: ReplyHarpMessage = self.send(
1086
+ HarpMessage.create(MessageType.READ, address, PayloadType.U8)
1087
+ )
1088
+
1089
+ return reply.payload_as_string()
1090
+
1091
+ def _read_serial_number(self) -> int:
1092
+ """
1093
+ Reads the value stored in the `SERIAL_NUMBER` register.
1094
+
1095
+ Returns
1096
+ -------
1097
+ int
1098
+ The value of the `SERIAL_NUMBER` register
1099
+ """
1100
+ address = CommonRegisters.SERIAL_NUMBER
1101
+
1102
+ reply: ReplyHarpMessage = self.send(
1103
+ HarpMessage.create(MessageType.READ, address, PayloadType.U8)
1104
+ )
1105
+
1106
+ if reply.is_error:
1107
+ return 0
1108
+
1109
+ return reply.payload
1110
+
1111
+ def _read_clock_config(self) -> int:
1112
+ """
1113
+ Reads the value stored in the `CLOCK_CONFIG` register.
1114
+
1115
+ Returns
1116
+ -------
1117
+ int
1118
+ The value of the `CLOCK_CONFIG` register
1119
+ """
1120
+ address = CommonRegisters.CLOCK_CONFIG
1121
+
1122
+ reply: ReplyHarpMessage = self.send(
1123
+ HarpMessage.create(MessageType.READ, address, PayloadType.U8)
1124
+ )
1125
+
1126
+ return reply.payload
1127
+
1128
+ def _read_timestamp_offset(self) -> int:
1129
+ """
1130
+ Reads the value stored in the `TIMESTAMP_OFFSET` register.
1131
+
1132
+ Returns
1133
+ -------
1134
+ int
1135
+ The value of the `TIMESTAMP_OFFSET` register
1136
+ """
1137
+ address = CommonRegisters.TIMESTAMP_OFFSET
1138
+
1139
+ reply: ReplyHarpMessage = self.send(
1140
+ HarpMessage.create(MessageType.READ, address, PayloadType.U8)
1141
+ )
1142
+
1143
+ return reply.payload
1144
+
1145
+ def __enter__(self):
1146
+ """
1147
+ Support for using Device with 'with' statement.
1148
+
1149
+ Returns
1150
+ -------
1151
+ Device
1152
+ The Device instance
1153
+ """
1154
+ # Connection is already established in __init__
1155
+ # but we could add additional setup if needed
1156
+ return self
1157
+
1158
+ def __exit__(self, exc_type, exc_val, exc_tb):
1159
+ """
1160
+ Cleanup resources when exiting the 'with' block.
1161
+
1162
+ Parameters
1163
+ ----------
1164
+ exc_type : Exception type or None
1165
+ Type of the exception that caused the context to be exited
1166
+ exc_val : Exception or None
1167
+ Exception instance that caused the context to be exited
1168
+ exc_tb : traceback or None
1169
+ Traceback if an exception occurred
1170
+ """
1171
+ self.disconnect()
1172
+ # Return False to propagate exceptions that occurred in the with block
1173
+ return False