robot-hat 3.3.2__tar.gz → 3.3.4__tar.gz

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.
Files changed (181) hide show
  1. {robot_hat-3.3.2 → robot_hat-3.3.4}/.github/workflows/ci.yml +5 -5
  2. {robot_hat-3.3.2 → robot_hat-3.3.4}/.github/workflows/release.yml +2 -2
  3. {robot_hat-3.3.2 → robot_hat-3.3.4}/CHANGELOG.md +45 -0
  4. {robot_hat-3.3.2 → robot_hat-3.3.4}/PKG-INFO +44 -8
  5. {robot_hat-3.3.2 → robot_hat-3.3.4}/README.md +43 -7
  6. {robot_hat-3.3.2 → robot_hat-3.3.4}/docs/localization_sensors.md +29 -10
  7. {robot_hat-3.3.2 → robot_hat-3.3.4}/docs/sense_hat_sensors.md +7 -0
  8. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/data_types/imu.py +23 -4
  9. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/i2c/i2c_bus.py +78 -32
  10. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/i2c/smbus_manager.py +1 -1
  11. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/interfaces/imu_abc.py +8 -1
  12. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/mock/imu.py +5 -1
  13. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/motor/gpio_dc_motor.py +101 -22
  14. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/motor/phase_motor.py +37 -8
  15. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/pin.py +1 -4
  16. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/sensors/imu/lsm6ds33.py +2 -1
  17. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/sensors/imu/lsm9ds1.py +19 -9
  18. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/sensors/imu/sh3001.py +8 -0
  19. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/services/base_motor_service.py +61 -13
  20. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/services/motor_service.py +12 -0
  21. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/services/servo_service.py +56 -40
  22. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/servos/gpio_angular_servo.py +65 -21
  23. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/servos/servo.py +79 -43
  24. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/version.py +3 -3
  25. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat.egg-info/PKG-INFO +44 -8
  26. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat.egg-info/scm_file_list.json +1 -0
  27. robot_hat-3.3.4/robot_hat.egg-info/scm_version.json +8 -0
  28. robot_hat-3.3.2/robot_hat.egg-info/scm_version.json +0 -8
  29. {robot_hat-3.3.2 → robot_hat-3.3.4}/.github/dependabot.yml +0 -0
  30. {robot_hat-3.3.2 → robot_hat-3.3.4}/.gitignore +0 -0
  31. {robot_hat-3.3.2 → robot_hat-3.3.4}/.pre-commit-config.yaml +0 -0
  32. {robot_hat-3.3.2 → robot_hat-3.3.4}/AGENTS.md +0 -0
  33. {robot_hat-3.3.2 → robot_hat-3.3.4}/LICENSE +0 -0
  34. {robot_hat-3.3.2 → robot_hat-3.3.4}/MANIFEST.in +0 -0
  35. {robot_hat-3.3.2 → robot_hat-3.3.4}/Makefile +0 -0
  36. {robot_hat-3.3.2 → robot_hat-3.3.4}/docs/as5048a.md +0 -0
  37. {robot_hat-3.3.2 → robot_hat-3.3.4}/docs/hardware_validation.md +0 -0
  38. {robot_hat-3.3.2 → robot_hat-3.3.4}/docs/lidar_2d.md +0 -0
  39. {robot_hat-3.3.2 → robot_hat-3.3.4}/docs/migration_guide_v2.md +0 -0
  40. {robot_hat-3.3.2 → robot_hat-3.3.4}/docs/migration_guide_v3.md +0 -0
  41. {robot_hat-3.3.2 → robot_hat-3.3.4}/docs/pololu_miniimu9_v5.md +0 -0
  42. {robot_hat-3.3.2 → robot_hat-3.3.4}/hardware-validation/evidence/README.md +0 -0
  43. {robot_hat-3.3.2 → robot_hat-3.3.4}/pyproject.toml +0 -0
  44. {robot_hat-3.3.2 → robot_hat-3.3.4}/pyrightconfig.json +0 -0
  45. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/__init__.py +0 -0
  46. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/common/__init__.py +0 -0
  47. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/common/event_emitter.py +0 -0
  48. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/common/singleton_meta.py +0 -0
  49. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/data_types/__init__.py +0 -0
  50. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/data_types/angular_position.py +0 -0
  51. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/data_types/battery.py +0 -0
  52. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/data_types/bus.py +0 -0
  53. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/data_types/config/__init__.py +0 -0
  54. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/data_types/config/battery.py +0 -0
  55. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/data_types/config/hts221.py +0 -0
  56. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/data_types/config/ina219.py +0 -0
  57. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/data_types/config/ina226.py +0 -0
  58. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/data_types/config/ina260.py +0 -0
  59. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/data_types/config/lidar.py +0 -0
  60. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/data_types/config/lis3mdl.py +0 -0
  61. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/data_types/config/lps25h.py +0 -0
  62. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/data_types/config/lsm6ds33.py +0 -0
  63. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/data_types/config/lsm9ds1.py +0 -0
  64. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/data_types/config/lsm9ds1_magnetometer.py +0 -0
  65. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/data_types/config/motor.py +0 -0
  66. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/data_types/config/pwm.py +0 -0
  67. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/data_types/config/sh3001.py +0 -0
  68. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/data_types/encoder.py +0 -0
  69. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/data_types/environment.py +0 -0
  70. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/data_types/lidar.py +0 -0
  71. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/data_types/magnetometer.py +0 -0
  72. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/data_types/motor.py +0 -0
  73. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/data_types/quadrature.py +0 -0
  74. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/data_types/uart.py +0 -0
  75. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/drivers/__init__.py +0 -0
  76. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/drivers/adc/INA219.py +0 -0
  77. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/drivers/adc/INA226.py +0 -0
  78. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/drivers/adc/INA260.py +0 -0
  79. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/drivers/adc/__init__.py +0 -0
  80. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/drivers/adc/sunfounder_adc.py +0 -0
  81. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/drivers/angle/__init__.py +0 -0
  82. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/drivers/angle/as5048a.py +0 -0
  83. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/drivers/angle/as5600l.py +0 -0
  84. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/drivers/gpio/__init__.py +0 -0
  85. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/drivers/gpio/gpiozero_digital_edge_input.py +0 -0
  86. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/drivers/pwm/__init__.py +0 -0
  87. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/drivers/pwm/pca9685.py +0 -0
  88. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/drivers/pwm/sunfounder_pwm.py +0 -0
  89. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/exceptions.py +0 -0
  90. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/factories/__init__.py +0 -0
  91. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/factories/battery_factory.py +0 -0
  92. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/factories/motor_factory.py +0 -0
  93. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/factories/pwm_factory.py +0 -0
  94. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/filedb.py +0 -0
  95. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/i2c/__init__.py +0 -0
  96. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/i2c/i2c_manager.py +0 -0
  97. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/i2c/retry_decorator.py +0 -0
  98. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/i2c/smbus_protocol.py +0 -0
  99. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/interfaces/__init__.py +0 -0
  100. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/interfaces/angular_position_abc.py +0 -0
  101. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/interfaces/battery_abc.py +0 -0
  102. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/interfaces/digital_edge_input_abc.py +0 -0
  103. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/interfaces/encoder_abc.py +0 -0
  104. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/interfaces/environmental_sensor_abc.py +0 -0
  105. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/interfaces/lidar_2d_abc.py +0 -0
  106. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/interfaces/magnetometer_abc.py +0 -0
  107. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/interfaces/motor_abc.py +0 -0
  108. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/interfaces/pwm_driver_abc.py +0 -0
  109. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/interfaces/quadrature_counter_backend_abc.py +0 -0
  110. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/interfaces/servo_abc.py +0 -0
  111. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/interfaces/smbus_abc.py +0 -0
  112. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/interfaces/spi_abc.py +0 -0
  113. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/interfaces/uart_abc.py +0 -0
  114. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/mock/__init__.py +0 -0
  115. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/mock/angular_position.py +0 -0
  116. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/mock/encoder.py +0 -0
  117. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/mock/environmental_sensor.py +0 -0
  118. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/mock/lidar.py +0 -0
  119. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/mock/magnetometer.py +0 -0
  120. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/mock/quadrature_counter.py +0 -0
  121. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/mock/smbus2.py +0 -0
  122. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/mock/spi.py +0 -0
  123. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/mock/uart.py +0 -0
  124. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/mock/ultrasonic.py +0 -0
  125. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/motor/__init__.py +0 -0
  126. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/motor/i2c_dc_motor.py +0 -0
  127. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/motor/mixins/__init__.py +0 -0
  128. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/motor/mixins/motor_calibration.py +0 -0
  129. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/music.py +0 -0
  130. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/py.typed +0 -0
  131. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/sensors/__init__.py +0 -0
  132. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/sensors/angular_position/__init__.py +0 -0
  133. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/sensors/angular_position/as5048a_angular_position.py +0 -0
  134. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/sensors/angular_position/as5600l_angular_position.py +0 -0
  135. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/sensors/encoder/__init__.py +0 -0
  136. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/sensors/encoder/as5048a_encoder.py +0 -0
  137. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/sensors/encoder/as5600l_encoder.py +0 -0
  138. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/sensors/encoder/gpio_quadrature_counter.py +0 -0
  139. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/sensors/encoder/quadrature_decoder.py +0 -0
  140. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/sensors/encoder/quadrature_encoder.py +0 -0
  141. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/sensors/environmental/__init__.py +0 -0
  142. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/sensors/environmental/hts221.py +0 -0
  143. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/sensors/environmental/lps25h.py +0 -0
  144. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/sensors/imu/__init__.py +0 -0
  145. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/sensors/lidar/__init__.py +0 -0
  146. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/sensors/lidar/rplidar_c1.py +0 -0
  147. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/sensors/magnetometer/__init__.py +0 -0
  148. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/sensors/magnetometer/lis3mdl.py +0 -0
  149. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/sensors/magnetometer/lsm9ds1.py +0 -0
  150. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/sensors/ultrasonic/HC_SR04.py +0 -0
  151. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/sensors/ultrasonic/__init__.py +0 -0
  152. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/services/__init__.py +0 -0
  153. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/services/battery/__init__.py +0 -0
  154. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/services/battery/ina219_battery.py +0 -0
  155. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/services/battery/ina226_battery.py +0 -0
  156. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/services/battery/ina260_battery.py +0 -0
  157. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/services/battery/sunfounder_battery.py +0 -0
  158. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/services/single_motor_service.py +0 -0
  159. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/servos/__init__.py +0 -0
  160. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/spi/__init__.py +0 -0
  161. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/spi/spidev_device.py +0 -0
  162. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/sunfounder/__init__.py +0 -0
  163. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/sunfounder/accelerometer.py +0 -0
  164. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/sunfounder/address_descriptions.py +0 -0
  165. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/sunfounder/grayscale.py +0 -0
  166. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/sunfounder/motor.py +0 -0
  167. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/sunfounder/pin_descriptions.py +0 -0
  168. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/sunfounder/pwm.py +0 -0
  169. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/sunfounder/robot.py +0 -0
  170. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/sunfounder/sunfounder_servo.py +0 -0
  171. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/sunfounder/utils.py +0 -0
  172. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/uart/__init__.py +0 -0
  173. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/uart/serial_uart.py +0 -0
  174. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/uart/usb_uart.py +0 -0
  175. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat/utils.py +0 -0
  176. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat.egg-info/SOURCES.txt +0 -0
  177. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat.egg-info/dependency_links.txt +0 -0
  178. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat.egg-info/requires.txt +0 -0
  179. {robot_hat-3.3.2 → robot_hat-3.3.4}/robot_hat.egg-info/top_level.txt +0 -0
  180. {robot_hat-3.3.2 → robot_hat-3.3.4}/setup.cfg +0 -0
  181. {robot_hat-3.3.2 → robot_hat-3.3.4}/tools/hil_motor_check.py +0 -0
@@ -37,7 +37,7 @@ jobs:
37
37
  persist-credentials: false
38
38
 
39
39
  - name: Set up Python 3.14
40
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
40
+ uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
41
41
  with:
42
42
  python-version: "3.14"
43
43
  cache: pip
@@ -69,7 +69,7 @@ jobs:
69
69
  persist-credentials: false
70
70
 
71
71
  - name: Set up Python 3.10
72
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
72
+ uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
73
73
  with:
74
74
  python-version: "3.10"
75
75
  cache: pip
@@ -132,7 +132,7 @@ jobs:
132
132
  persist-credentials: false
133
133
 
134
134
  - name: Set up Python ${{ matrix.python-version }}
135
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
135
+ uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
136
136
  with:
137
137
  python-version: ${{ matrix.python-version }}
138
138
  cache: pip
@@ -159,7 +159,7 @@ jobs:
159
159
  persist-credentials: false
160
160
 
161
161
  - name: Set up Python 3.12
162
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
162
+ uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
163
163
  with:
164
164
  python-version: "3.12"
165
165
  cache: pip
@@ -208,7 +208,7 @@ jobs:
208
208
  persist-credentials: false
209
209
 
210
210
  - name: Set up Python 3.14
211
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
211
+ uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
212
212
  with:
213
213
  python-version: "3.14"
214
214
  cache: pip
@@ -33,7 +33,7 @@ jobs:
33
33
  persist-credentials: false
34
34
 
35
35
  - name: Set up Python 3.14
36
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
36
+ uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
37
37
  with:
38
38
  python-version: "3.14"
39
39
 
@@ -160,6 +160,6 @@ jobs:
160
160
  path: dist/
161
161
 
162
162
  - name: Publish distributions to PyPI
163
- uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
163
+ uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086 # v1.14.3
164
164
  with:
165
165
  attestations: true
@@ -1,5 +1,50 @@
1
1
  # Changelog
2
2
 
3
+ ## v3.3.4 (2026-11-11)
4
+
5
+ ### Fixed
6
+
7
+ - Normalize `LSM9DS1.read_sample()` to a right-handed sensor-local frame by
8
+ reversing native Y for both acceleration and angular velocity. X, Z, SI
9
+ sensitivities, timestamps, and `read_raw_sample()` native counts are unchanged.
10
+ **Compatibility:** remove any application-side Y inversion that performs this
11
+ same normalization, and express mounting rotations relative to
12
+ `(native X, -native Y, native Z)`. A mounting rotation alone cannot correct the
13
+ previous left-handed output. The separate magnetometer driver is unchanged.
14
+
15
+ ### Documentation
16
+
17
+ - Define the shared `IMUSample` and `IMUABC.read_sample()` contract: one
18
+ right-handed local frame, right-hand-rule angular velocity, SI units,
19
+ accelerometer specific force with its stationary gravity response, and host
20
+ monotonic observation timestamps. Document native raw-count semantics and
21
+ keep mounting transforms, bias estimation, and fusion in the application.
22
+ - Identify the native frame references for LSM6DS33 and SH3001 and the
23
+ corresponding sample conventions for the mock IMU.
24
+
25
+ ## v3.3.3 (2026-10-16)
26
+
27
+ ### Fixed
28
+
29
+ - Keep servo travel limits independent of pulse calibration. `Servo` maps
30
+ `real_min_angle`/`real_max_angle` to the configured pulse endpoints; narrower
31
+ `min_angle`/`max_angle` limits reduce travel without stretching the pulse range.
32
+ - Validate servo calibration ranges and reject non-finite commands. Serialize
33
+ servo commands with cleanup and reject writes after close.
34
+ - Initialize GPIO servos without pulses until commanded, and apply the service's
35
+ calibrated center consistently during initialization and calibration updates.
36
+ - Preserve servo calibration state when a hardware write fails and release
37
+ servo resources after failed service initialization.
38
+ - Hold GPIO motor enables low until direction outputs are ready, disable and
39
+ stop motors before releasing GPIO resources. Serialize movement and cleanup.
40
+ - Attempt stops for every managed motor after a command or stop failure, stop
41
+ all motors before service cleanup, and report cleanup failures to callers.
42
+ - Stop phase/enable motors before closing GPIO resources.
43
+ - Serialize shared I²C transactions and cleanup to protect SMBus address
44
+ selection, notify bus managers when context managers close.
45
+ - Keep shared GPIO factories open when deinitializing an individual pin, and
46
+ preserve active bus registrations when a different instance closes.
47
+
3
48
  ## v3.3.2 (2026-10-11)
4
49
 
5
50
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: robot_hat
3
- Version: 3.3.2
3
+ Version: 3.3.4
4
4
  Summary: Extensible, typed robotics hardware library for motors, servos, sensors, 2D lidar, I2C, and UART.
5
5
  Author-email: Karim Aziiev <karim.aziiev@gmail.com>
6
6
  License-Expression: GPL-3.0-or-later
@@ -241,7 +241,8 @@ finally:
241
241
  imu.close()
242
242
  ```
243
243
 
244
- The driver reports the LSM9DS1's native axes; configure the measured transform
244
+ The driver reports a right-handed local frame: native X, reversed native Y,
245
+ and native Z for both acceleration and angular velocity. Configure the transform
245
246
  from the HAT to the robot base in the consuming application.
246
247
 
247
248
  The Sense HAT's independently addressed magnetometer, humidity/temperature
@@ -362,6 +363,22 @@ for examples and custom `MotorABC` requirements.
362
363
 
363
364
  GPIO motors are motors that are controlled entirely via direct GPIO calls; no I²C address or external PWM driver is needed. Examples include the Waveshare RPi Motor Driver Board with the MC33886 module.
364
365
 
366
+ `GPIODCMotor` holds its optional active-high enable pin low during initialization,
367
+ while stopped, and before releasing GPIO resources. It enables the bridge only
368
+ after setting the direction outputs. Each GPIO must belong to one device;
369
+ an indicator LED must not share a motor's enable or direction pin.
370
+
371
+ Motor services stop every motor before releasing resources and attempt every
372
+ stop even if one output fails. Stop and close failures are reported to the caller.
373
+ A failed movement command triggers a stop attempt for all managed motors, and
374
+ closed services reject movement commands.
375
+
376
+ GPIO cleanup releases pin ownership. Software cannot guarantee the electrical
377
+ state during boot, OS halt, power loss, or a crashed process. The motor driver
378
+ must have a hardware-defined disabled state when control inputs are undriven,
379
+ and an independent power cutoff or inhibit appropriate to the mechanism.
380
+ Validate startup, shutdown, and loss of control with wheels clear of the ground.
381
+
365
382
  ```python
366
383
  from robot_hat import GPIODCMotorConfig, MotorFactory, MotorService, setup_env_vars
367
384
 
@@ -517,6 +534,29 @@ calibrated_angle = -1 × (input_angle + (-1 × calibration_offset))
517
534
 
518
535
  The `ServoService` provides a high-level abstraction for managing servo operations. It allows easy configuration of the calibration mode, movement bounds, and custom calibration logic if needed.
519
536
 
537
+ `Servo` separates permitted travel (`min_angle`, `max_angle`) from pulse
538
+ calibration (`real_min_angle` at `min_pulse`, `real_max_angle` at `max_pulse`).
539
+ An allowed angle produces the same pulse when travel limits change. For
540
+ example, with measured calibration points of -45° at 1000 µs and +45° at
541
+ 2000 µs, limits of -10° to +10° produce approximately 1389–1611 µs.
542
+ The default calibration assumes -90° to +90°; verify it for the particular
543
+ servo. These are two calibration points, not a request to find mechanical
544
+ end stops. Pulse rounding, PWM resolution, backlash, and servo behavior limit
545
+ physical accuracy. Wheel steering angle also depends on the linkage geometry.
546
+
547
+ When using `ServoService`, set its bounds to the desired movement relative
548
+ to the calibrated center. Give its underlying `Servo` the full calibrated
549
+ angle range so that the center offset can be applied without a second travel
550
+ clamp. `GPIOAngularServo` follows gpiozero's convention: its `min_angle` and
551
+ `max_angle` are the measured pulse endpoints; use `ServoService` for narrower
552
+ travel limits. GPIO servos start with pulses disabled until the first angle
553
+ command, including the calibrated center command issued by `ServoService`.
554
+
555
+ Direct `pulse_width_time()` calls are calibration operations and bypass travel
556
+ limits. Use angle commands for normal movement. Non-finite angle, offset, and
557
+ pulse commands are rejected before hardware writes; closed servos reject
558
+ further commands.
559
+
520
560
  Here's how to use `ServoCalibrationMode` in your servo configuration:
521
561
 
522
562
  **Example 1**: Steering servo using `ServoCalibrationMode.SUM`
@@ -548,6 +588,7 @@ driver = PWMFactory.create_pwm_driver(
548
588
  bus=pwm_config.bus, # either a bus number or an smbus instance.
549
589
  config=pwm_config,
550
590
  )
591
+ driver.set_pwm_freq(pwm_config.freq)
551
592
 
552
593
  steering_servo = ServoService(
553
594
  servo=Servo(
@@ -560,8 +601,7 @@ steering_servo = ServoService(
560
601
  # The minimum and maximum pulse widths (in microseconds) corresponding to the servo's physical movement.
561
602
  min_pulse=500,
562
603
  max_pulse=2500,
563
- # The minimum and maximum physical angles (in degrees) that the servo can achieve.
564
- # These values are used to map the logical angle to the physical angle.
604
+ # Measured angles at min_pulse and max_pulse, respectively.
565
605
  real_min_angle=-90.0,
566
606
  real_max_angle=90.0,
567
607
  ),
@@ -571,8 +611,6 @@ steering_servo = ServoService(
571
611
  calibration_mode=ServoCalibrationMode.SUM,
572
612
  calibration_offset=-14.4,
573
613
  )
574
- driver.set_pwm_freq(pwm_config.freq)
575
-
576
614
  steering_servo.set_angle(-30) # Turn left.
577
615
  steering_servo.set_angle(15) # Turn slightly to the right.
578
616
  steering_servo.reset() # Reset to the center position.
@@ -839,8 +877,6 @@ class MyRobotCar:
839
877
  else:
840
878
  servo = GPIOAngularServo(
841
879
  pin=channel,
842
- min_angle=min_angle,
843
- max_angle=max_angle,
844
880
  )
845
881
  return ServoService(
846
882
  servo=servo,
@@ -198,7 +198,8 @@ finally:
198
198
  imu.close()
199
199
  ```
200
200
 
201
- The driver reports the LSM9DS1's native axes; configure the measured transform
201
+ The driver reports a right-handed local frame: native X, reversed native Y,
202
+ and native Z for both acceleration and angular velocity. Configure the transform
202
203
  from the HAT to the robot base in the consuming application.
203
204
 
204
205
  The Sense HAT's independently addressed magnetometer, humidity/temperature
@@ -319,6 +320,22 @@ for examples and custom `MotorABC` requirements.
319
320
 
320
321
  GPIO motors are motors that are controlled entirely via direct GPIO calls; no I²C address or external PWM driver is needed. Examples include the Waveshare RPi Motor Driver Board with the MC33886 module.
321
322
 
323
+ `GPIODCMotor` holds its optional active-high enable pin low during initialization,
324
+ while stopped, and before releasing GPIO resources. It enables the bridge only
325
+ after setting the direction outputs. Each GPIO must belong to one device;
326
+ an indicator LED must not share a motor's enable or direction pin.
327
+
328
+ Motor services stop every motor before releasing resources and attempt every
329
+ stop even if one output fails. Stop and close failures are reported to the caller.
330
+ A failed movement command triggers a stop attempt for all managed motors, and
331
+ closed services reject movement commands.
332
+
333
+ GPIO cleanup releases pin ownership. Software cannot guarantee the electrical
334
+ state during boot, OS halt, power loss, or a crashed process. The motor driver
335
+ must have a hardware-defined disabled state when control inputs are undriven,
336
+ and an independent power cutoff or inhibit appropriate to the mechanism.
337
+ Validate startup, shutdown, and loss of control with wheels clear of the ground.
338
+
322
339
  ```python
323
340
  from robot_hat import GPIODCMotorConfig, MotorFactory, MotorService, setup_env_vars
324
341
 
@@ -474,6 +491,29 @@ calibrated_angle = -1 × (input_angle + (-1 × calibration_offset))
474
491
 
475
492
  The `ServoService` provides a high-level abstraction for managing servo operations. It allows easy configuration of the calibration mode, movement bounds, and custom calibration logic if needed.
476
493
 
494
+ `Servo` separates permitted travel (`min_angle`, `max_angle`) from pulse
495
+ calibration (`real_min_angle` at `min_pulse`, `real_max_angle` at `max_pulse`).
496
+ An allowed angle produces the same pulse when travel limits change. For
497
+ example, with measured calibration points of -45° at 1000 µs and +45° at
498
+ 2000 µs, limits of -10° to +10° produce approximately 1389–1611 µs.
499
+ The default calibration assumes -90° to +90°; verify it for the particular
500
+ servo. These are two calibration points, not a request to find mechanical
501
+ end stops. Pulse rounding, PWM resolution, backlash, and servo behavior limit
502
+ physical accuracy. Wheel steering angle also depends on the linkage geometry.
503
+
504
+ When using `ServoService`, set its bounds to the desired movement relative
505
+ to the calibrated center. Give its underlying `Servo` the full calibrated
506
+ angle range so that the center offset can be applied without a second travel
507
+ clamp. `GPIOAngularServo` follows gpiozero's convention: its `min_angle` and
508
+ `max_angle` are the measured pulse endpoints; use `ServoService` for narrower
509
+ travel limits. GPIO servos start with pulses disabled until the first angle
510
+ command, including the calibrated center command issued by `ServoService`.
511
+
512
+ Direct `pulse_width_time()` calls are calibration operations and bypass travel
513
+ limits. Use angle commands for normal movement. Non-finite angle, offset, and
514
+ pulse commands are rejected before hardware writes; closed servos reject
515
+ further commands.
516
+
477
517
  Here's how to use `ServoCalibrationMode` in your servo configuration:
478
518
 
479
519
  **Example 1**: Steering servo using `ServoCalibrationMode.SUM`
@@ -505,6 +545,7 @@ driver = PWMFactory.create_pwm_driver(
505
545
  bus=pwm_config.bus, # either a bus number or an smbus instance.
506
546
  config=pwm_config,
507
547
  )
548
+ driver.set_pwm_freq(pwm_config.freq)
508
549
 
509
550
  steering_servo = ServoService(
510
551
  servo=Servo(
@@ -517,8 +558,7 @@ steering_servo = ServoService(
517
558
  # The minimum and maximum pulse widths (in microseconds) corresponding to the servo's physical movement.
518
559
  min_pulse=500,
519
560
  max_pulse=2500,
520
- # The minimum and maximum physical angles (in degrees) that the servo can achieve.
521
- # These values are used to map the logical angle to the physical angle.
561
+ # Measured angles at min_pulse and max_pulse, respectively.
522
562
  real_min_angle=-90.0,
523
563
  real_max_angle=90.0,
524
564
  ),
@@ -528,8 +568,6 @@ steering_servo = ServoService(
528
568
  calibration_mode=ServoCalibrationMode.SUM,
529
569
  calibration_offset=-14.4,
530
570
  )
531
- driver.set_pwm_freq(pwm_config.freq)
532
-
533
571
  steering_servo.set_angle(-30) # Turn left.
534
572
  steering_servo.set_angle(15) # Turn slightly to the right.
535
573
  steering_servo.reset() # Reset to the center position.
@@ -796,8 +834,6 @@ class MyRobotCar:
796
834
  else:
797
835
  servo = GPIOAngularServo(
798
836
  pin=channel,
799
- min_angle=min_angle,
800
- max_angle=max_angle,
801
837
  )
802
838
  return ServoService(
803
839
  servo=servo,
@@ -8,25 +8,40 @@ mounting transforms, filtering, fusion, wheel geometry, and SLAM state.
8
8
 
9
9
  `IMUABC.read_sample()` returns an immutable `IMUSample` with:
10
10
 
11
- - acceleration in metres per second squared;
11
+ - accelerometer specific force in metres per second squared, without gravity removal;
12
12
  - angular velocity in radians per second;
13
13
  - a process-local `time.monotonic_ns()` observation timestamp.
14
14
 
15
- The axis order is `(x, y, z)`. A concrete driver must document its positive axis
16
- directions. The application must transform that local frame into its robot base
17
- frame instead of hiding board mounting assumptions inside the hardware driver.
15
+ Both vectors use axis order `(x, y, z)` in the same right-handed local frame.
16
+ Positive angular velocity follows the right-hand rule. A stationary sensor with
17
+ local +Z pointing upward reports approximately `(0, 0, +9.80665)` m/s²; ideal free
18
+ fall reports zero. A concrete driver must document its positive axis directions
19
+ and normalize hardware conventions before returning an `IMUSample`. The
20
+ application applies the mounting rotation into its robot base frame and owns
21
+ bias estimation, gravity compensation, and fusion.
22
+
23
+ `RawIMUSample` contains signed native register counts for diagnostics. Its axis
24
+ signs may differ from the normalized SI sample, as documented by each driver.
18
25
 
19
26
  `SH3001` defaults to ±2 g and ±2000 degrees/s. Its conversion uses the
20
27
  manufacturer sensitivity values for those configured ranges and standard gravity
21
28
  of 9.80665 m/s². `read_raw_sample()` is available for calibration and diagnostics;
22
- normal application code should consume `read_sample()`.
29
+ normal application code should consume `read_sample()`. Its local frame follows
30
+ the native right-handed axes in the Senodia SH3001 datasheet, section 7.1,
31
+ Figure 15 (pin-1 marker identifies the package orientation).
23
32
 
24
33
  `LSM9DS1` provides the same six-axis contract for the Raspberry Pi Sense HAT v1
25
34
  and v2, and for standalone LSM9DS1 devices. The Sense HAT accelerometer/gyroscope
26
35
  function is wired to I²C bus 1 at `0x6a`; its separately addressed magnetometer at
27
- `0x1c` uses `MagnetometerABC` rather than the six-axis `IMUABC` contract. The IMU driver uses ST's
28
- native X/Y/Z signs without the undocumented application-specific remapping,
29
- calibration, bias learning, or fusion performed by RTIMULib. Its defaults are
36
+ `0x1c` uses `MagnetometerABC` rather than the six-axis `IMUABC` contract. The IMU
37
+ driver reports a right-handed local frame for both acceleration and angular
38
+ velocity: `(native X, -native Y, native Z)`. ST's native A/G axes are left-handed
39
+ ([datasheet, Figure 1](https://www.st.com/resource/en/datasheet/lsm9ds1.pdf));
40
+ the Y sign normalization lets applications use ordinary rigid mounting rotations.
41
+ With a Sense HAT viewed from above and GPIO at the top, local X points right,
42
+ local Y points toward GPIO, and local Z points out of the board. Robot mounting,
43
+ bias calibration, and fusion belong to the application. `read_raw_sample()`
44
+ returns native signed register counts. The driver's defaults are
30
45
  ±2 g, ±245 degrees/s, and 119 Hz. Pass a shared `SMBusABC` to combine it safely
31
46
  with other drivers on the bus; closing the IMU does not close an injected bus.
32
47
  See [Sense HAT environmental and magnetic sensors](sense_hat_sensors.md) for the
@@ -37,7 +52,9 @@ Pololu's MiniIMU-9 v5 and AltIMU-10 v5, as well as standalone LSM6DS33 carriers.
37
52
  It defaults to I²C address `0x6b`, ±2 g, ±245 degrees/s, and 104 Hz. The
38
53
  independent `LIS3MDL` driver provides magnetic samples in teslas at address
39
54
  `0x1e`. Driving the Pololu board's shared `SA0` pin low changes those addresses
40
- to `0x6a` and `0x1c`, respectively. Both drivers expose native axes, typed
55
+ to `0x6a` and `0x1c`, respectively. The IMU's local frame follows the native
56
+ right-handed axes in the [LSM6DS33 datasheet, Figure 1](https://www.pololu.com/file/0J1087/LSM6DS33.pdf).
57
+ Both drivers expose native axes, typed
41
58
  configuration, diagnostic raw counts, and caller-owned shared-bus injection.
42
59
  In a driver selector, these components should be identified as
43
60
  **LSM6DS33 (gyro and accelerometer)** and **LIS3MDL (magnetometer)**.
@@ -47,7 +64,9 @@ See [Pololu MiniIMU-9 v5 support](pololu_miniimu9_v5.md) for wiring, complete
47
64
  configuration ranges, and calibration responsibilities.
48
65
 
49
66
  The monotonic timestamp is not Unix time. It is suitable for ordering and fusing
50
- observations acquired in the same process and monotonic clock domain.
67
+ observations acquired in the same monotonic clock domain. It records host
68
+ observation time, not a hardware sampling timestamp or a guarantee that the
69
+ accelerometer and gyroscope sampled simultaneously.
51
70
 
52
71
  ## Wheel encoders
53
72
 
@@ -31,6 +31,13 @@ driver deliberately does not apply hard-iron offsets, soft-iron correction,
31
31
  declination, board rotation, or heading calculation. Those depend on the
32
32
  installation and belong in application calibration or sensor fusion.
33
33
 
34
+ The six-axis `LSM9DS1.read_sample()` frame is right-handed, with native A/G X,
35
+ reversed native A/G Y, and native A/G Z. Its `read_raw_sample()` frame contains
36
+ native register counts. The independently addressed magnetometer uses the
37
+ magnetic axes shown in ST's datasheet, Figure 1. Applications combining IMU and
38
+ magnetometer samples must align their documented local frames before fusion;
39
+ matching tuple indices alone does not establish a shared frame.
40
+
34
41
  The Sense HAT temperature sensors are physically close to the Raspberry Pi and
35
42
  LED matrix. Their readings can therefore be warmer than ambient air. The two
36
43
  temperature values also come from different physical devices and should remain
@@ -18,9 +18,23 @@ def _validate_vector(name: str, value: Vector3) -> None:
18
18
  class IMUSample:
19
19
  """One six-axis inertial observation in SI units.
20
20
 
21
- Axes use the concrete sensor driver's documented local frame. Consumers are
22
- responsible for applying the rigid transform from that frame to the robot
23
- base frame.
21
+ Both vectors use axis order ``(x, y, z)`` in the same right-handed,
22
+ sensor-local frame. Concrete drivers document the positive axis directions
23
+ and normalize hardware axis conventions before constructing this sample.
24
+ Positive angular velocity follows the right-hand rule about each axis.
25
+
26
+ ``acceleration_mps2`` is accelerometer specific force in m/s², without
27
+ gravity removal. A stationary sensor with local +Z pointing upward reports
28
+ approximately ``(0, 0, +9.80665)``; ideal free fall reports zero.
29
+ ``angular_velocity_radps`` is angular velocity in rad/s.
30
+
31
+ ``timestamp_monotonic_ns`` is the host observation time in nanoseconds,
32
+ using ``time.monotonic_ns()`` or an injected clock with the same semantics.
33
+ It is not Unix time or a guarantee of simultaneous hardware acquisition.
34
+ Compare timestamps only within a shared monotonic clock domain.
35
+
36
+ Consumers apply the installation-specific mounting rotation into the robot
37
+ frame and own bias calibration, gravity compensation, and sensor fusion.
24
38
  """
25
39
 
26
40
  acceleration_mps2: Vector3
@@ -36,7 +50,12 @@ class IMUSample:
36
50
 
37
51
  @dataclass(frozen=True)
38
52
  class RawIMUSample:
39
- """Unscaled signed sensor counts for diagnostics and calibration tools."""
53
+ """Unscaled signed sensor counts for diagnostics and calibration tools.
54
+
55
+ Axis order and signs follow the concrete driver's documented native
56
+ register convention, which may differ from the normalized ``IMUSample``
57
+ frame. The timestamp has the same host monotonic semantics as ``IMUSample``.
58
+ """
40
59
 
41
60
  accelerometer_counts: RawVector3
42
61
  gyroscope_counts: RawVector3