pycontrol-core 3.0.0b1__tar.gz → 3.0.0b2__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 (311) hide show
  1. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/PKG-INFO +1 -1
  2. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/docs/changelog.md +13 -0
  3. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/docs/hardware-definitions.md +33 -0
  4. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/__init__.py +1 -1
  5. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/firmware/devices/VL53L4.py +53 -32
  6. pycontrol_core-3.0.0b1/src/pycontrol/firmware/devices/breakout_10port_r0_6_h7.py → pycontrol_core-3.0.0b2/src/pycontrol/firmware/devices/breakout_10port_r0_4_h7.py +3 -3
  7. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/firmware/devices/breakout_10port_r0_8_h7.py +1 -1
  8. pycontrol_core-3.0.0b2/src/pycontrol/firmware/devices/breakout_6port_r0_1_h7.py +33 -0
  9. pycontrol_core-3.0.0b2/src/pycontrol/firmware/devices/breakout_6port_r0_2_h7.py +34 -0
  10. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/firmware/pycontrol/framework.py +1 -1
  11. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/firmware/pycontrol/hardware.py +10 -0
  12. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/firmware/pycontrol/state_machine.py +17 -0
  13. pycontrol_core-3.0.0b2/tests/unit/firmware/test_breakout_qwiic.py +21 -0
  14. pycontrol_core-3.0.0b2/tests/unit/firmware/test_state_machine_variables.py +50 -0
  15. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/firmware/test_vl53l4.py +95 -29
  16. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/.complexipy_cache/.gitignore +0 -0
  17. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/.complexipy_cache/CACHEDIR.TAG +0 -0
  18. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/.complexipy_cache/README.md +0 -0
  19. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/.complexipy_cache/v/cache/functions +0 -0
  20. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/.gitignore +0 -0
  21. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/.pre-commit-config.yaml +0 -0
  22. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/.python-version +0 -0
  23. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/CONTRIBUTING.md +0 -0
  24. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/LICENSE.txt +0 -0
  25. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/README.md +0 -0
  26. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/docs/addon-settings.md +0 -0
  27. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/docs/architecture.md +0 -0
  28. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/docs/cli-reference.md +0 -0
  29. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/docs/contracts.md +0 -0
  30. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/docs/core-walkthrough.md +0 -0
  31. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/docs/data-format.md +0 -0
  32. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/docs/design-history/simplified-pycontrol-provenance-and-sharing-motivation.md +0 -0
  33. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/docs/design-history/simplified-pycontrol-provenance-and-sharing-plan.md +0 -0
  34. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/docs/experiment-handlers.md +0 -0
  35. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/docs/getting-started.md +0 -0
  36. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/docs/maintainability.md +0 -0
  37. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/docs/migration-from-official.md +0 -0
  38. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/docs/public-api.md +0 -0
  39. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/docs/release-acceptance.md +0 -0
  40. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/docs/sharing.md +0 -0
  41. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/docs/subscriber-api.md +0 -0
  42. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/docs/task-handlers.md +0 -0
  43. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/docs/testing.md +0 -0
  44. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/docs/threading-and-state.md +0 -0
  45. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/docs/troubleshooting.md +0 -0
  46. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/docs/wire-protocol.md +0 -0
  47. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/docs/writing-tasks.md +0 -0
  48. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/pyproject.toml +0 -0
  49. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/addons/README.md +0 -0
  50. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/addons/__init__.py +0 -0
  51. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/addons/controls_context.py +0 -0
  52. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/addons/experiment_api.py +0 -0
  53. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/addons/loader.py +0 -0
  54. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/addons/task_api.py +0 -0
  55. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/analysis/README.md +0 -0
  56. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/analysis/__init__.py +0 -0
  57. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/analysis/data_import.py +0 -0
  58. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/bundles/README.md +0 -0
  59. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/bundles/__init__.py +0 -0
  60. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/bundles/collect.py +0 -0
  61. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/bundles/models.py +0 -0
  62. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/bundles/protocol.py +0 -0
  63. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/bundles/run.py +0 -0
  64. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/bundles/zipio.py +0 -0
  65. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/bus/README.md +0 -0
  66. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/bus/__init__.py +0 -0
  67. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/bus/events.py +0 -0
  68. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/bus/status.py +0 -0
  69. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/bus/subscriber.py +0 -0
  70. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/cli/README.md +0 -0
  71. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/cli/__init__.py +0 -0
  72. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/cli/commands/__init__.py +0 -0
  73. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/cli/commands/board.py +0 -0
  74. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/cli/commands/board_impl.py +0 -0
  75. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/cli/commands/common.py +0 -0
  76. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/cli/commands/data.py +0 -0
  77. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/cli/commands/data_impl.py +0 -0
  78. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/cli/commands/experiment.py +0 -0
  79. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/cli/commands/experiment_impl.py +0 -0
  80. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/cli/commands/firmware.py +0 -0
  81. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/cli/commands/firmware_impl.py +0 -0
  82. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/cli/commands/gui.py +0 -0
  83. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/cli/commands/gui_impl.py +0 -0
  84. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/cli/commands/run.py +0 -0
  85. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/cli/commands/run_impl.py +0 -0
  86. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/cli/commands/setups.py +0 -0
  87. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/cli/commands/setups_impl.py +0 -0
  88. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/cli/commands/vars.py +0 -0
  89. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/cli/commands/vars_impl.py +0 -0
  90. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/cli/commands/workspace.py +0 -0
  91. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/cli/commands/workspace_impl.py +0 -0
  92. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/cli/main.py +0 -0
  93. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/config/README.md +0 -0
  94. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/config/__init__.py +0 -0
  95. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/config/io.py +0 -0
  96. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/config/models.py +0 -0
  97. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/experiment/README.md +0 -0
  98. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/experiment/__init__.py +0 -0
  99. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/experiment/hooks.py +0 -0
  100. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/experiment/models.py +0 -0
  101. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/experiment/planning.py +0 -0
  102. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/experiment/run.py +0 -0
  103. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/experiment/run_recording.py +0 -0
  104. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/experiment/supervisor.py +0 -0
  105. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/firmware/README.md +0 -0
  106. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/firmware/__init__.py +0 -0
  107. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/firmware/device_helpers.py +0 -0
  108. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/firmware/devices/LED_driver.py +0 -0
  109. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/firmware/devices/MCP.py +0 -0
  110. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/firmware/devices/__init__.py +0 -0
  111. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/firmware/devices/analog_LED.py +0 -0
  112. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/firmware/devices/audio_board.py +0 -0
  113. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/firmware/devices/audio_player.py +0 -0
  114. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/firmware/devices/breakout_6port_r1_2_f4.py +0 -0
  115. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/firmware/devices/five_poke.py +0 -0
  116. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/firmware/devices/frame_logger.py +0 -0
  117. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/firmware/devices/frame_trigger.py +0 -0
  118. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/firmware/devices/grid_maze.py +0 -0
  119. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/firmware/devices/lickometer.py +0 -0
  120. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/firmware/devices/load_cell.py +0 -0
  121. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/firmware/devices/nine_poke.py +0 -0
  122. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/firmware/devices/poke.py +0 -0
  123. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/firmware/devices/port_expander.py +0 -0
  124. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/firmware/devices/rotary_encoder.py +0 -0
  125. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/firmware/devices/schmitt_trigger.py +0 -0
  126. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/firmware/devices/solenoid_driver.py +0 -0
  127. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/firmware/devices/stepper_motor.py +0 -0
  128. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/firmware/devices/uRFID.py +0 -0
  129. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/firmware/devices/uart_handler.py +0 -0
  130. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/firmware/pycontrol/__init__.py +0 -0
  131. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/firmware/pycontrol/audio.py +0 -0
  132. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/firmware/pycontrol/profiles.py +0 -0
  133. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/firmware/pycontrol/timer.py +0 -0
  134. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/firmware/pycontrol/utility.py +0 -0
  135. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/fsutil.py +0 -0
  136. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/integrations/README.md +0 -0
  137. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/integrations/__init__.py +0 -0
  138. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/integrations/_queue.py +0 -0
  139. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/integrations/notify/__init__.py +0 -0
  140. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/integrations/notify/slack.py +0 -0
  141. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/integrations/notify/telegram.py +0 -0
  142. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/mcu.py +0 -0
  143. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/protocol/README.md +0 -0
  144. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/protocol/__init__.py +0 -0
  145. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/protocol/analog.py +0 -0
  146. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/protocol/decoder.py +0 -0
  147. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/protocol/encoder.py +0 -0
  148. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/protocol/messages.py +0 -0
  149. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/provenance/README.md +0 -0
  150. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/provenance/__init__.py +0 -0
  151. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/provenance/archive.py +0 -0
  152. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/provenance/collect.py +0 -0
  153. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/provenance/hashing.py +0 -0
  154. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/provenance/models.py +0 -0
  155. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/provenance/source.py +0 -0
  156. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/recording/README.md +0 -0
  157. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/recording/__init__.py +0 -0
  158. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/recording/analog_writer.py +0 -0
  159. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/recording/console.py +0 -0
  160. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/recording/event_rows.py +0 -0
  161. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/recording/native_event_logger.py +0 -0
  162. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/repl/README.md +0 -0
  163. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/repl/__init__.py +0 -0
  164. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/repl/device_helpers.py +0 -0
  165. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/repl/file_transfer.py +0 -0
  166. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/repl/json_repl.py +0 -0
  167. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/repl/raw_repl.py +0 -0
  168. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/session/README.md +0 -0
  169. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/session/__init__.py +0 -0
  170. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/session/board_session.py +0 -0
  171. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/session/device_resolution.py +0 -0
  172. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/session/info.py +0 -0
  173. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/session/link.py +0 -0
  174. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/session/phases.py +0 -0
  175. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/session/protocols.py +0 -0
  176. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/session/pump.py +0 -0
  177. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/session/rows.py +0 -0
  178. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/session/services/__init__.py +0 -0
  179. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/session/services/firmware.py +0 -0
  180. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/session/services/maintenance.py +0 -0
  181. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/session/services/setup.py +0 -0
  182. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/session/services/sync.py +0 -0
  183. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/session/types.py +0 -0
  184. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/session/variables.py +0 -0
  185. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/testing/__init__.py +0 -0
  186. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/testing/fake_board.py +0 -0
  187. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/testing/frames.py +0 -0
  188. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/transport/README.md +0 -0
  189. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/transport/__init__.py +0 -0
  190. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/transport/base.py +0 -0
  191. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/transport/mock.py +0 -0
  192. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/transport/replay.py +0 -0
  193. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/transport/serial.py +0 -0
  194. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/workspace/README.md +0 -0
  195. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/workspace/__init__.py +0 -0
  196. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/workspace/creation.py +0 -0
  197. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/workspace/hwdef_inspect.py +0 -0
  198. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/workspace/task_inspect.py +0 -0
  199. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/workspace_template/__init__.py +0 -0
  200. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/workspace_template/addons/controls/README.md +0 -0
  201. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/workspace_template/addons/controls/blinker_gui.json +0 -0
  202. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/workspace_template/addons/controls/example_dialog.py +0 -0
  203. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/workspace_template/addons/experiment_handlers/README.md +0 -0
  204. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/workspace_template/addons/experiment_handlers/notion_logger.py +0 -0
  205. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/workspace_template/addons/experiment_handlers/s3_data_exporter.py +0 -0
  206. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/workspace_template/addons/plotters/README.md +0 -0
  207. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/workspace_template/addons/task_handlers/README.md +0 -0
  208. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/workspace_template/addons/task_handlers/example_handler.py +0 -0
  209. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/workspace_template/addons/task_handlers/run_notify.py +0 -0
  210. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/workspace_template/addons/task_handlers/trial_summary.py +0 -0
  211. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/workspace_template/devices/__init__.py +0 -0
  212. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/workspace_template/devices/example_device.py +0 -0
  213. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/workspace_template/experiments/blink_demo.json +0 -0
  214. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/workspace_template/settings.json +0 -0
  215. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/workspace_template/setups.json +0 -0
  216. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/src/pycontrol/workspace_template/tasks/examples/blinker-pattern-h7.py +0 -0
  217. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/CLASSIFICATION.md +0 -0
  218. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/__init__.py +0 -0
  219. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/contracts/test_wire_contract.py +0 -0
  220. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/firmware_stubs/__init__.py +0 -0
  221. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/firmware_stubs/machine.py +0 -0
  222. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/firmware_stubs/micropython.py +0 -0
  223. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/firmware_stubs/pyb.py +0 -0
  224. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/firmware_stubs/ucollections.py +0 -0
  225. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/firmware_stubs/ujson.py +0 -0
  226. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/golden/__init__.py +0 -0
  227. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/golden/frames/blinker_real.in.bin +0 -0
  228. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/golden/frames/blinker_real.out.bin +0 -0
  229. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/golden/frames/counter_demo_real.in.bin +0 -0
  230. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/golden/frames/counter_demo_real.out.bin +0 -0
  231. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/golden/frames/encoder_real.in.bin +0 -0
  232. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/golden/frames/encoder_real.out.bin +0 -0
  233. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/golden/replay.py +0 -0
  234. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/golden/sessions/blinker_real.meta.json +0 -0
  235. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/golden/sessions/blinker_real.tsv +0 -0
  236. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/golden/sessions/counter_demo_real.meta.json +0 -0
  237. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/golden/sessions/counter_demo_real.tsv +0 -0
  238. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/golden/sessions/encoder_real.meta.json +0 -0
  239. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/golden/sessions/encoder_real.tsv +0 -0
  240. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/golden/sessions/encoder_real_encoder.data.npy +0 -0
  241. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/golden/sessions/encoder_real_encoder.time.npy +0 -0
  242. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/golden/tasks/blinker_real.task.py +0 -0
  243. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/golden/tasks/counter_demo_real.task.py +0 -0
  244. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/golden/tasks/encoder_real.task.py +0 -0
  245. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/integration/__init__.py +0 -0
  246. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/integration/test_board_session.py +0 -0
  247. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/integration/test_cli_workspace.py +0 -0
  248. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/integration/test_experiment_runner.py +0 -0
  249. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/integration/test_real_hardware_replay.py +0 -0
  250. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/integration/test_repl.py +0 -0
  251. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/integration/test_run.py +0 -0
  252. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/__init__.py +0 -0
  253. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/firmware/__init__.py +0 -0
  254. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/firmware/conftest.py +0 -0
  255. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/firmware/test_analog_trigger_variables.py +0 -0
  256. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/firmware/test_audio.py +0 -0
  257. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/firmware/test_device_helpers.py +0 -0
  258. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/firmware/test_devices_init.py +0 -0
  259. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/firmware/test_hardware_uses_profile.py +0 -0
  260. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/firmware/test_profiles.py +0 -0
  261. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/firmware/test_rotary_encoder.py +0 -0
  262. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/firmware/test_run_sequence.py +0 -0
  263. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/firmware/test_schmitt_trigger_threshold.py +0 -0
  264. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/firmware/test_timer_rounding.py +0 -0
  265. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/firmware/test_utility_log.py +0 -0
  266. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/test_addons.py +0 -0
  267. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/test_analysis_data_import.py +0 -0
  268. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/test_board_link.py +0 -0
  269. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/test_bundles_collect.py +0 -0
  270. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/test_bus_subscriber.py +0 -0
  271. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/test_cli.py +0 -0
  272. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/test_cli_setups.py +0 -0
  273. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/test_config_models.py +0 -0
  274. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/test_device_resolution.py +0 -0
  275. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/test_device_table_consistency.py +0 -0
  276. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/test_experiment_hook_sequencer.py +0 -0
  277. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/test_experiment_planning.py +0 -0
  278. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/test_file_transfer_errors.py +0 -0
  279. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/test_firmware_root.py +0 -0
  280. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/test_firmware_service.py +0 -0
  281. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/test_firmware_status.py +0 -0
  282. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/test_integrations_notify.py +0 -0
  283. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/test_maintenance_service.py +0 -0
  284. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/test_phases.py +0 -0
  285. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/test_protocol_decoder.py +0 -0
  286. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/test_protocol_encoder.py +0 -0
  287. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/test_provenance.py +0 -0
  288. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/test_public_api.py +0 -0
  289. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/test_raw_repl.py +0 -0
  290. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/test_recording.py +0 -0
  291. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/test_run_bundle_format.py +0 -0
  292. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/test_run_data_durability.py +0 -0
  293. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/test_run_recovery.py +0 -0
  294. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/test_serial_transport.py +0 -0
  295. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/test_session_pump.py +0 -0
  296. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/test_session_rows.py +0 -0
  297. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/test_session_variables.py +0 -0
  298. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/test_setup_identity.py +0 -0
  299. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/test_status_fields.py +0 -0
  300. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/test_subject_id.py +0 -0
  301. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/test_supervisor_join.py +0 -0
  302. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/test_transport_mock.py +0 -0
  303. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/test_workspace.py +0 -0
  304. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/test_workspace_hwdef_inspect.py +0 -0
  305. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tests/unit/test_workspace_template.py +0 -0
  306. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tools/README.md +0 -0
  307. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tools/capture_fixtures.py +0 -0
  308. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tools/hardware_acceptance.py +0 -0
  309. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tools/maintainability_metrics.py +0 -0
  310. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/tools/run_tests.py +0 -0
  311. {pycontrol_core-3.0.0b1 → pycontrol_core-3.0.0b2}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pycontrol-core
3
- Version: 3.0.0b1
3
+ Version: 3.0.0b2
4
4
  Summary: Sans-IO host library for the pyControl behavioural-experiment framework.
5
5
  Project-URL: Homepage, https://github.com/pyControl/core
6
6
  Author: pyControl contributors
@@ -17,6 +17,19 @@
17
17
  names, then sync the pyControl framework to the board. The old names are no
18
18
  longer exported. Pin mappings are unchanged.
19
19
 
20
+ ### I2C connectors
21
+
22
+ - New `pycontrol.hardware.I2CConnector` for 4-pin JST-SH I2C connectors
23
+ (SparkFun Qwiic / Adafruit STEMMA QT). The six-port H7 breakouts expose one
24
+ as `board.qwiic`, on the same I2C-1 bus as `port_4`.
25
+ - `VL53L4CD` is now only the sensor: `VL53L4CD(i2c, name, sampling_rate,
26
+ address=0x29, enable_pin=None, trigger=None)`. It no longer takes a `port` or
27
+ creates `LED`/`motor` outputs; build those in a nosepoke device instead.
28
+ `i2c_setup(new_address)` and `begin()` are replaced by the `address` argument
29
+ and an automatic bring-up at the first run start (or `setup()`), so several
30
+ sensors on one bus can be constructed independently. Sensors sharing a bus
31
+ need distinct addresses and enable pins; clashes raise `ValueError`.
32
+
20
33
  ### Add-on naming
21
34
 
22
35
  - Workspace add-ons live in `addons/task_handlers/`,
@@ -126,6 +126,39 @@ counter.
126
126
  Timer and pin ownership lasts for the hardware object's lifetime. Switching a
127
127
  port between UART and encoder mode while a task is running is not supported.
128
128
 
129
+ ## I2C Connectors
130
+
131
+ Some breakouts have a 4-pin JST-SH I2C connector (SparkFun Qwiic / Adafruit
132
+ STEMMA QT). It is an `I2CConnector` with `SCL`, `SDA` and `I2C` (the bus
133
+ number). The six-port H7 breakouts expose it as `board.qwiic`, wired to
134
+ `port_4`'s DIO_A/DIO_B, so both connectors are the same I2C-1 bus:
135
+
136
+ ```python
137
+ from machine import I2C
138
+ from devices.VL53L4 import VL53L4CD
139
+
140
+ i2c = I2C(board.qwiic.I2C, freq=400_000)
141
+ tof = VL53L4CD(i2c, "tof", sampling_rate=40)
142
+ ```
143
+
144
+ Create one `I2C` object per bus and pass it to every device on that bus: a
145
+ second `I2C(1, freq=...)` re-clocks the bus for all of them.
146
+
147
+ ## I2C Time-of-Flight Sensors
148
+
149
+ `VL53L4CD(i2c, name, sampling_rate, address=0x29, enable_pin=None, trigger=None)`
150
+ streams `entrance - distance` (mm) as the analog input `name`. `enable_pin`
151
+ drives the chip's XSHUT pin. The chip is brought up when the first run starts,
152
+ once the task file has constructed every device; call `setup()` at import to
153
+ bring a sensor that is alone on its bus up earlier and report wiring problems
154
+ at upload.
155
+
156
+ Sensors that share a bus all start at address 0x29, so each needs an
157
+ `enable_pin` and its own `address`. Each constructor holds its chip in reset;
158
+ at run start each chip boots at 0x29 in turn and moves to its own address
159
+ before the next is released. A clashing address, or a sensor without an
160
+ `enable_pin` on a shared bus, raises `ValueError` at construction.
161
+
129
162
  ## Analog sources and DAC buffering
130
163
 
131
164
  `AnalogInput` records the ADC's raw codes. A plausible trace does not establish
@@ -1,5 +1,5 @@
1
1
  """pycontrol host library: sans-IO core + addon points."""
2
2
 
3
- __version__ = "3.0.0b1"
3
+ __version__ = "3.0.0b2"
4
4
 
5
5
  __all__ = ["__version__"]
@@ -48,24 +48,40 @@ _BOOT_S = 0.002 # Datasheet tBOOT: the chip answers on I2C 1.2 ms after XSHUT r
48
48
  _BOOT_POLL_ATTEMPTS = 100 # x 1 ms between polls of the firmware status register.
49
49
 
50
50
 
51
+ # Sensors constructed on each I2C object, so address clashes fail at import.
52
+ _buses = {}
53
+
54
+
51
55
  class VL53L4CD(AnalogInput):
52
- def __init__(self, port, i2c_instance, sampling_rate, nose_event, trigger=None):
53
- super().__init__(None, nose_event, int(sampling_rate), data_type="h", triggers=[trigger] if trigger else None)
56
+ """VL53L4CD time-of-flight sensor, streamed as the analog input `name`.
57
+
58
+ Samples are entrance - distance (mm), so they rise as a target approaches.
59
+ enable_pin drives the chip's XSHUT; while it is low the chip is in reset.
60
+
61
+ The chip is brought up when the first run starts, after every device in the
62
+ task file has been constructed (call setup() to do it earlier). Sensors
63
+ sharing an I2C bus each need an enable_pin and their own address: every
64
+ constructor holds its chip in reset, then each chip boots at the default
65
+ address in turn and is moved to its own before the next is released.
66
+ """
67
+
68
+ def __init__(self, i2c, name, sampling_rate, address=_DEFAULT_I2C_ADDRESS, enable_pin=None, trigger=None):
69
+ super().__init__(None, name, int(sampling_rate), data_type="h", triggers=[trigger] if trigger else None)
54
70
  self.trigger = trigger
55
71
  if self.trigger:
56
- self.trigger.attached_to = nose_event
57
- self.i2c = i2c_instance
72
+ self.trigger.attached_to = name
73
+ sharing = _buses.setdefault(id(i2c), [])
74
+ for other in sharing:
75
+ if other._target_address == address:
76
+ raise ValueError("VL53L4CD '{}' and '{}' share address 0x{:02X}.".format(other.name, name, address))
77
+ if other.enable is None or enable_pin is None:
78
+ raise ValueError("VL53L4CDs sharing an I2C bus each need an enable_pin.")
79
+ sharing.append(self)
80
+ self.i2c = i2c
58
81
  self._address = _DEFAULT_I2C_ADDRESS
59
- self._target_address = _DEFAULT_I2C_ADDRESS
82
+ self._target_address = address
60
83
  self._powered = False
61
-
62
- self.LED = DigitalOutput(port.POW_A)
63
- self.LED.off()
64
- self.motor = DigitalOutput(port.POW_B)
65
- self.motor.off()
66
- assert port.DIO_C is not None, "! VL53L4CD needs a port with DIO_C."
67
- self.tof_enable = DigitalOutput(port.DIO_C)
68
- self.tof_enable.off()
84
+ self.enable = DigitalOutput(enable_pin) if enable_pin is not None else None # Starts off: chip in reset.
69
85
 
70
86
  self.buffer = bytearray(2)
71
87
  self.get_range_packet = struct.pack(">H", 0x0096)
@@ -75,15 +91,12 @@ class VL53L4CD(AnalogInput):
75
91
  self._ranging = False
76
92
  self.connection_error = False
77
93
 
78
- def i2c_setup(self, new_address=None):
79
- """Reset the chip through XSHUT, initialise it and move it to new_address.
80
-
81
- Every sensor sharing the bus must be held in reset (tof_enable off,
82
- which the constructor does) until its own i2c_setup() runs: a reset
83
- chip answers at the default address, so two live chips would collide.
84
- """
85
- self._target_address = new_address or _DEFAULT_I2C_ADDRESS
86
- self._power_up()
94
+ def setup(self):
95
+ """Reset the chip, initialise it, move it to its address and start ranging."""
96
+ try:
97
+ self._power_up()
98
+ except Exception as exc:
99
+ raise RuntimeError("Trouble connecting to VL53L4CD '{}': {!r}".format(self.name, exc))
87
100
 
88
101
  def _power_up(self):
89
102
  # XSHUT low is a full hardware reset: it discards the remapped address
@@ -91,23 +104,25 @@ class VL53L4CD(AnalogInput):
91
104
  # chip was mid-transaction. The pyboard soft-reset between uploads
92
105
  # leaves GPIO state alone, so the chips are otherwise still live at
93
106
  # their old addresses when the next task file imports.
94
- self.tof_enable.off()
95
- time.sleep(_XSHUT_LOW_S)
96
- self.tof_enable.on()
97
- time.sleep(_BOOT_S)
107
+ if self.enable:
108
+ self.enable.off()
109
+ time.sleep(_XSHUT_LOW_S)
110
+ self.enable.on()
111
+ time.sleep(_BOOT_S)
98
112
  self._address = _DEFAULT_I2C_ADDRESS
99
113
  try:
100
114
  self._wait_for_boot()
101
115
  except OSError:
102
116
  if self._target_address == _DEFAULT_I2C_ADDRESS:
103
117
  raise
104
- # Nothing at the default address: XSHUT did not reset the chip, so
105
- # it is still running at the address the previous run gave it.
118
+ # Nothing at the default address: the chip was not reset, so it is
119
+ # still running at the address the previous run gave it.
106
120
  self._address = self._target_address
107
121
  self._wait_for_boot()
108
122
  self._sensor_init()
109
123
  if self._address != self._target_address:
110
124
  self.set_address(self._target_address)
125
+ self.begin()
111
126
  self._powered = True
112
127
 
113
128
  def set_address(self, new_address):
@@ -119,10 +134,15 @@ class VL53L4CD(AnalogInput):
119
134
  self.timing_budget = 20
120
135
  self.start_ranging()
121
136
 
137
+ def _initialise(self):
138
+ # Runs once, before the first run's clock starts, after the task file
139
+ # has constructed every sensor and so put every chip in reset.
140
+ if not self._powered:
141
+ self.setup()
142
+
122
143
  def _run_start(self):
123
- if not self._powered: # Re-run without a fresh task import.
124
- self._power_up()
125
- self.begin()
144
+ if not self._powered: # Re-run: _run_stop powered the chip down.
145
+ self.setup()
126
146
  super()._run_start()
127
147
 
128
148
  def _run_stop(self):
@@ -131,7 +151,8 @@ class VL53L4CD(AnalogInput):
131
151
  self.stop_ranging()
132
152
  except OSError:
133
153
  pass
134
- self.tof_enable.off()
154
+ if self.enable:
155
+ self.enable.off()
135
156
  self._powered = False
136
157
 
137
158
  def read_sample(self):
@@ -1,12 +1,12 @@
1
- from pycontrol.hardware import Port, DigitalInput, DigitalOutput
1
+ from pycontrol.hardware import Port, DigitalOutput
2
2
 
3
3
  # MCU family this breakout's pin geometry is wired for. The host reads it
4
4
  # statically (pycontrol.workspace.hwdef_inspect) to pre-flight setups.
5
5
  MCU = "stm32h743"
6
6
 
7
7
 
8
- class Breakout10PortR0_6H7:
9
- """Pin geometry for the ten-port H7 breakout, PCB revision 0.6."""
8
+ class Breakout10PortR0_4H7:
9
+ """Pin geometry for the ten-port H7 breakout, PCB revision 0.4-0.7"""
10
10
 
11
11
  def __init__(self):
12
12
  self.port_1 = Port(DIO_A="B10", DIO_B="B11", DIO_C="A4", POW_A="C2", POW_B="C3", UART=3, I2C=2, DAC=1)
@@ -1,4 +1,4 @@
1
- from pycontrol.hardware import Port, DigitalInput, DigitalOutput
1
+ from pycontrol.hardware import Port, DigitalOutput
2
2
 
3
3
  # MCU family this breakout's pin geometry is wired for. The host reads it
4
4
  # statically (pycontrol.workspace.hwdef_inspect) to pre-flight setups.
@@ -0,0 +1,33 @@
1
+ from pycontrol.hardware import Port, I2CConnector, DigitalOutput
2
+
3
+ # MCU family this breakout's pin geometry is wired for. The host reads it
4
+ # statically (pycontrol.workspace.hwdef_inspect) to pre-flight setups.
5
+ MCU = "stm32h743"
6
+
7
+
8
+ class Breakout6PortR0_1H7:
9
+ """Pin geometry for the six-port H7 breakout, PCB revision 0.1."""
10
+
11
+ def __init__(self):
12
+ self.port_1 = Port(DIO_A="B13", DIO_B="B12", POW_A="E1", POW_B="E4", POW_C="B0", UART=5)
13
+ self.port_2 = Port(DIO_A="A0", DIO_B="A1", POW_A="E5", POW_B="E6", POW_C="D3", UART=4)
14
+ self.port_3 = Port(DIO_A="B10", DIO_B="B11", DIO_C="A4", POW_A="C1", POW_B="C3", UART=3, I2C=2, DAC=1)
15
+ self.port_4 = Port(DIO_A="B8", DIO_B="B9", DIO_C="A5", POW_A="D6", POW_B="D7", I2C=1, DAC=2)
16
+ self.port_5 = Port(DIO_A="A2", DIO_B="A3", DIO_C="D1", POW_A="E10", POW_B="E15", UART=2)
17
+ self.port_6 = Port(DIO_A="C6", DIO_B="C7", DIO_C="B14", POW_A="C0", POW_B="E0", UART=6)
18
+ self.qwiic = I2CConnector(SCL="B8", SDA="B9", I2C=1) # Shares the I2C-1 bus with port_4.
19
+
20
+
21
+ self.led_red = DigitalOutput("D5")
22
+ self.led_yellow = DigitalOutput("D1")
23
+ self.led_green = DigitalOutput("D4")
24
+ self.led_blue = DigitalOutput("E3", inverted=True)
25
+
26
+ self.BUTTON_A = self.BUTTON = "C13"
27
+ self.BUTTON_B = "C12"
28
+ self.BUTTON_C = "D4"
29
+ self.BUTTON_D = "A15"
30
+
31
+ self.BNC_2 = "C7"
32
+ self.BNC_3 = self.DAC_1 = "A4"
33
+ self.BNC_4 = self.DAC_2 = "A5"
@@ -0,0 +1,34 @@
1
+ from pycontrol.hardware import Port, I2CConnector, DigitalOutput
2
+
3
+ # MCU family this breakout's pin geometry is wired for. The host reads it
4
+ # statically (pycontrol.workspace.hwdef_inspect) to pre-flight setups.
5
+ MCU = "stm32h743"
6
+
7
+
8
+ class Breakout6PortR0_2H7:
9
+ """Pin geometry for the six-port H7 breakout, PCB revision 0.1."""
10
+
11
+ def __init__(self):
12
+ self.port_1 = Port(DIO_A="B13", DIO_B="B12", POW_A="E1", POW_B="E4", POW_C="B0", UART=5)
13
+ self.port_2 = Port(DIO_A="A0", DIO_B="A1", POW_A="E5", POW_B="E6", POW_C="D3", UART=4)
14
+ self.port_3 = Port(DIO_A="B10", DIO_B="B11", DIO_C="A4", POW_A="C2", POW_B="C3", UART=3, I2C=2, DAC=1)
15
+ self.port_4 = Port(DIO_A="B8", DIO_B="B9", DIO_C="A5", POW_A="D6", POW_B="D7", I2C=1, DAC=2)
16
+ self.port_5 = Port(DIO_A="A2", DIO_B="A3", DIO_C="D1", POW_A="E10", POW_B="E15", UART=2)
17
+ self.port_6 = Port(DIO_A="C6", DIO_B="C7", DIO_C="B3", POW_A="C0", POW_B="E0", UART=6)
18
+ self.qwiic = I2CConnector(SCL="B8", SDA="B9", I2C=1) # Shares the I2C-1 bus with port_4.
19
+
20
+
21
+ self.led_red = DigitalOutput("D5", inverted=True)
22
+ self.led_yellow = DigitalOutput("D0", inverted=True)
23
+ self.led_green = DigitalOutput("D4", inverted=True)
24
+ self.led_blue = DigitalOutput("E3", inverted=True)
25
+
26
+ self.BUTTON_A = self.BUTTON = "C13"
27
+ self.BUTTON_B = "C12"
28
+ self.BUTTON_C = "D4"
29
+ self.BUTTON_D = "A15"
30
+
31
+ self.BNC_1 = "D4"
32
+ self.BNC_2 = "D5"
33
+ self.BNC_3 = self.DAC_1 = "A4"
34
+ self.BNC_4 = self.DAC_2 = "A5"
@@ -6,7 +6,7 @@ from . import state_machine as sm
6
6
  from . import hardware as hw
7
7
  from . import utility as ut
8
8
 
9
- VERSION = "3.0.0b1"
9
+ VERSION = "3.0.0b2"
10
10
 
11
11
 
12
12
  class FrameworkError(BaseException): # Exception for pycontrol framework errors.
@@ -597,6 +597,16 @@ class Port:
597
597
  return self.UART
598
598
 
599
599
 
600
+ class I2CConnector:
601
+ # 4-pin JST-SH I2C connector (SparkFun Qwiic / Adafruit STEMMA QT). Exposes
602
+ # the same I2C attribute as Port, so drivers that only need the bus accept
603
+ # either. It may share its bus with a Port; share one I2C object between them.
604
+ def __init__(self, SCL, SDA, I2C):
605
+ self.SCL = SCL
606
+ self.SDA = SDA
607
+ self.I2C = I2C
608
+
609
+
600
610
  # IOExpanderPin -------------------------------------------------------------
601
611
 
602
612
 
@@ -83,8 +83,25 @@ def stop():
83
83
  event_dispatch_dict["run_end"]()
84
84
 
85
85
 
86
+ def _match_tuples(value, current):
87
+ # Values from the host may have been through JSON (persistent, setup and CLI
88
+ # variables), which turns tuples into lists. Restore the tuples the task declared.
89
+ if isinstance(value, (list, tuple)) and isinstance(current, (list, tuple)):
90
+ if len(value) == len(current):
91
+ items = [_match_tuples(x, c) for x, c in zip(value, current)]
92
+ elif current:
93
+ items = [_match_tuples(x, current[0]) for x in value]
94
+ else:
95
+ items = list(value)
96
+ return tuple(items) if isinstance(current, tuple) or isinstance(value, tuple) else items
97
+ if isinstance(value, dict) and isinstance(current, dict):
98
+ return {k: _match_tuples(x, current[k]) if k in current else x for k, x in value.items()}
99
+ return value
100
+
101
+
86
102
  def set_variable(v_name, v_value):
87
103
  try:
104
+ v_value = _match_tuples(v_value, getattr(variables, v_name, None))
88
105
  setattr(variables, v_name, v_value)
89
106
  return True
90
107
  except Exception:
@@ -0,0 +1,21 @@
1
+ """The six-port H7 breakouts' Qwiic connector is the I2C-1 bus on port_4's DIO pins."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import importlib
6
+
7
+ import pytest
8
+
9
+
10
+ @pytest.mark.parametrize(
11
+ ("module", "cls"),
12
+ [("breakout_6port_r0_1_h7", "Breakout6PortR0_1H7"), ("breakout_6port_r0_2_h7", "Breakout6PortR0_2H7")],
13
+ )
14
+ def test_qwiic_shares_port_4_i2c_bus(module: str, cls: str) -> None:
15
+ from pycontrol.hardware import I2CConnector
16
+
17
+ board = getattr(importlib.import_module(f"devices.{module}"), cls)()
18
+
19
+ assert isinstance(board.qwiic, I2CConnector)
20
+ assert (board.qwiic.SCL, board.qwiic.SDA) == (board.port_4.DIO_A, board.port_4.DIO_B) == ("B8", "B9")
21
+ assert board.qwiic.I2C == board.port_4.I2C == 1
@@ -0,0 +1,50 @@
1
+ from __future__ import annotations
2
+
3
+
4
+ def _variables(**defaults):
5
+ from pycontrol import state_machine as sm
6
+ from pycontrol import utility as ut
7
+
8
+ for name, value in defaults.items():
9
+ setattr(ut.v, name, value)
10
+ sm.variables = ut.v
11
+ return sm, ut.v
12
+
13
+
14
+ def test_json_lists_are_restored_to_declared_tuples() -> None:
15
+ sm, v = _variables(thresholds=[(-10, 0), (-10, 0), (-10, 0)], lengths=(300, 10))
16
+
17
+ assert sm.set_variable("thresholds", [[-5, 0], [-6, 1], [-7, 2]]) is True
18
+ assert sm.set_variable("lengths", [200, 20]) is True
19
+
20
+ assert v.thresholds == [(-5, 0), (-6, 1), (-7, 2)]
21
+ assert type(v.thresholds) is list
22
+ assert v.lengths == (200, 20)
23
+
24
+
25
+ def test_resized_sequence_uses_first_declared_item_as_template() -> None:
26
+ sm, v = _variables(thresholds=[(-10, 0), (-10, 0)])
27
+
28
+ assert sm.set_variable("thresholds", [[1, 2], [3, 4], [5, 6]]) is True
29
+
30
+ assert v.thresholds == [(1, 2), (3, 4), (5, 6)]
31
+
32
+
33
+ def test_dict_values_are_matched_by_key() -> None:
34
+ sm, v = _variables(windows={"left": (0, 1), "count": 3})
35
+
36
+ assert sm.set_variable("windows", {"left": [2, 3], "right": [4, 5], "count": 4}) is True
37
+
38
+ assert v.windows == {"left": (2, 3), "right": [4, 5], "count": 4}
39
+
40
+
41
+ def test_values_without_declared_tuples_are_unchanged() -> None:
42
+ sm, v = _variables(items=[1, 2], name="x", pair=(1, 2))
43
+
44
+ assert sm.set_variable("items", [3, 4, 5]) is True
45
+ assert sm.set_variable("name", [1, 2]) is True
46
+ assert sm.set_variable("pair", 7) is True
47
+
48
+ assert v.items == [3, 4, 5]
49
+ assert v.name == [1, 2]
50
+ assert v.pair == 7
@@ -8,8 +8,6 @@ the chip's firmware boot.
8
8
 
9
9
  from __future__ import annotations
10
10
 
11
- from types import SimpleNamespace
12
-
13
11
  import pytest
14
12
 
15
13
  DEFAULT = 0x29
@@ -38,15 +36,12 @@ class FakeI2C:
38
36
  buf[0] = 0x03 # Firmware booted.
39
37
 
40
38
 
41
- def _port() -> SimpleNamespace:
42
- return SimpleNamespace(POW_A="X1", POW_B="X2", DIO_C="X3", DIO_A="B8", DIO_B="B9", I2C=1)
43
-
44
-
45
- def _sensor(i2c, name="l_nose"):
39
+ def _sensor(i2c, name="l_nose", address=DEFAULT, enable_pin="X3"):
46
40
  from devices.VL53L4 import VL53L4CD
47
41
 
48
- sensor = VL53L4CD(port=_port(), i2c_instance=i2c, sampling_rate=40, nose_event=name)
42
+ sensor = VL53L4CD(i2c, name, 40, address=address, enable_pin=enable_pin)
49
43
  sensor._sensor_init = lambda: None # type: ignore[method-assign] # Register dump irrelevant here.
44
+ sensor.begin = lambda: None # type: ignore[method-assign] # Ranging configuration irrelevant here.
50
45
  return sensor
51
46
 
52
47
 
@@ -60,17 +55,17 @@ def _fast_sleep(monkeypatch: pytest.MonkeyPatch) -> None:
60
55
  def test_constructor_holds_chip_in_reset() -> None:
61
56
  sensor = _sensor(FakeI2C(present=[DEFAULT]))
62
57
 
63
- assert sensor.tof_enable.state is False
58
+ assert sensor.enable.state is False
64
59
 
65
60
 
66
- def test_i2c_setup_resets_then_readdresses_chip_found_at_default() -> None:
61
+ def test_setup_resets_then_readdresses_chip_found_at_default() -> None:
67
62
  i2c = FakeI2C(present=[DEFAULT])
68
- sensor = _sensor(i2c)
63
+ sensor = _sensor(i2c, address=0x20)
69
64
  xshut: list[bool] = []
70
- sensor.tof_enable.on = lambda: xshut.append(True) # type: ignore[method-assign]
71
- sensor.tof_enable.off = lambda: xshut.append(False) # type: ignore[method-assign]
65
+ sensor.enable.on = lambda: xshut.append(True) # type: ignore[method-assign]
66
+ sensor.enable.off = lambda: xshut.append(False) # type: ignore[method-assign]
72
67
 
73
- sensor.i2c_setup(0x20)
68
+ sensor.setup()
74
69
 
75
70
  assert xshut == [False, True]
76
71
  assert i2c.writes[0] == (DEFAULT, STATUS_REG), "first transfer must be the boot poll at the default address"
@@ -79,22 +74,78 @@ def test_i2c_setup_resets_then_readdresses_chip_found_at_default() -> None:
79
74
  assert i2c.present == {0x20}
80
75
 
81
76
 
82
- def test_i2c_setup_falls_back_to_previous_address_if_xshut_did_not_reset() -> None:
77
+ def test_setup_falls_back_to_previous_address_if_xshut_did_not_reset() -> None:
83
78
  i2c = FakeI2C(present=[0x20])
84
- sensor = _sensor(i2c)
79
+ sensor = _sensor(i2c, address=0x20)
85
80
 
86
- sensor.i2c_setup(0x20)
81
+ sensor.setup()
87
82
 
88
83
  assert sensor._address == 0x20
89
84
  assert all(addr == 0x20 for addr, _ in i2c.writes)
90
85
  assert not any(buf.startswith(ADDRESS_REG) for _, buf in i2c.writes), "no remap needed"
91
86
 
92
87
 
93
- def test_i2c_setup_raises_underlying_oserror_when_chip_missing() -> None:
94
- sensor = _sensor(FakeI2C(present=[]))
88
+ def test_setup_names_the_sensor_when_chip_missing() -> None:
89
+ sensor = _sensor(FakeI2C(present=[]), name="c_nose", address=0x40)
95
90
 
96
- with pytest.raises(OSError):
97
- sensor.i2c_setup(0x40)
91
+ with pytest.raises(RuntimeError, match=r"'c_nose'.*OSError"):
92
+ sensor.setup()
93
+
94
+
95
+ def test_sensors_sharing_a_bus_need_distinct_addresses_and_enable_pins() -> None:
96
+ i2c = FakeI2C()
97
+ _sensor(i2c, name="l_nose", address=0x20, enable_pin="X1")
98
+
99
+ with pytest.raises(ValueError, match="share address 0x20"):
100
+ _sensor(i2c, name="c_nose", address=0x20, enable_pin="X2")
101
+ with pytest.raises(ValueError, match="enable_pin"):
102
+ _sensor(i2c, name="r_nose", address=0x40, enable_pin=None)
103
+ _sensor(FakeI2C(), name="other_bus", address=0x20, enable_pin=None) # Separate buses do not interact.
104
+
105
+
106
+ def test_initialise_brings_shared_bus_sensors_up_one_at_a_time() -> None:
107
+ class SharedBus:
108
+ """Each chip answers only while enabled; two chips at one address NACK, as a collision would."""
109
+
110
+ def __init__(self) -> None:
111
+ self.chips: list[list] = [] # [sensor, address]
112
+
113
+ def _chip(self, addr):
114
+ live = [chip for chip in self.chips if chip[0].enable.state and chip[1] == addr]
115
+ if len(live) != 1:
116
+ raise OSError(19)
117
+ return live[0]
118
+
119
+ def writeto(self, addr, buf):
120
+ chip = self._chip(addr)
121
+ if bytes(buf[:2]) == ADDRESS_REG and len(buf) == 3:
122
+ chip[1] = buf[2]
123
+
124
+ def readfrom_into(self, addr, buf):
125
+ self._chip(addr)
126
+ buf[0] = 0x03
127
+
128
+ bus = SharedBus()
129
+ sensors = [
130
+ _sensor(bus, name=n, address=a, enable_pin=p)
131
+ for n, a, p in (("l", 0x20, "X1"), ("c", 0x30, "X2"), ("r", 0x40, "X3"))
132
+ ]
133
+ for sensor in sensors:
134
+ chip = [sensor, DEFAULT]
135
+ bus.chips.append(chip)
136
+ enable_off = sensor.enable.off
137
+
138
+ def reset_on_off(enable_off=enable_off, chip=chip):
139
+ enable_off()
140
+ chip[1] = DEFAULT # XSHUT low discards the remapped address.
141
+
142
+ sensor.enable.off = reset_on_off # type: ignore[method-assign]
143
+
144
+ for sensor in sensors:
145
+ sensor._initialise()
146
+
147
+ assert [chip[1] for chip in bus.chips] == [0x20, 0x30, 0x40]
148
+ assert all(sensor._powered for sensor in sensors)
98
149
 
99
150
 
100
151
  def test_wait_for_boot_tolerates_nacks_while_firmware_boots() -> None:
@@ -152,15 +203,30 @@ def test_ranging_timeouts_use_supported_exception(monkeypatch: pytest.MonkeyPatc
152
203
  getattr(sensor, method)()
153
204
 
154
205
 
206
+ def test_sensor_without_enable_pin_sets_up_without_reset() -> None:
207
+ i2c = FakeI2C(present=[DEFAULT])
208
+ sensor = _sensor(i2c, enable_pin=None)
209
+
210
+ assert sensor.enable is None
211
+ sensor.setup()
212
+ assert sensor._address == DEFAULT
213
+ assert i2c.writes[0] == (DEFAULT, STATUS_REG)
214
+
215
+ sensor._ranging = True
216
+ sensor._run_stop()
217
+ assert sensor._ranging is False
218
+ assert sensor._powered is False
219
+
220
+
155
221
  def test_run_stop_stops_ranging_and_powers_chip_down() -> None:
156
222
  i2c = FakeI2C(present=[DEFAULT])
157
- sensor = _sensor(i2c)
158
- sensor.i2c_setup(0x20)
223
+ sensor = _sensor(i2c, address=0x20)
224
+ sensor.setup()
159
225
  sensor._ranging = True
160
226
 
161
227
  sensor._run_stop()
162
228
 
163
- assert sensor.tof_enable.state is False
229
+ assert sensor.enable.state is False
164
230
  assert (0x20, b"\x00\x87\x00") in i2c.writes # SYSTEM_START = stop
165
231
  assert sensor._ranging is False
166
232
  assert any(call[0] == "deinit" for call in sensor.timer.calls)
@@ -168,8 +234,8 @@ def test_run_stop_stops_ranging_and_powers_chip_down() -> None:
168
234
 
169
235
  def test_run_start_after_run_stop_powers_chip_back_up() -> None:
170
236
  i2c = FakeI2C(present=[DEFAULT])
171
- sensor = _sensor(i2c)
172
- sensor.i2c_setup(0x20)
237
+ sensor = _sensor(i2c, address=0x20)
238
+ sensor.setup()
173
239
  sensor._run_stop()
174
240
  i2c.present = {DEFAULT} # Powered-down chip comes back at the default address.
175
241
  i2c.writes.clear()
@@ -179,7 +245,7 @@ def test_run_start_after_run_stop_powers_chip_back_up() -> None:
179
245
 
180
246
  sensor._run_start()
181
247
 
182
- assert sensor.tof_enable.state is True
248
+ assert sensor.enable.state is True
183
249
  assert (DEFAULT, ADDRESS_REG + b"\x20") in i2c.writes
184
250
  assert began == [True]
185
251
  assert any(call[0] == "init" for call in sensor.timer.calls)
@@ -187,8 +253,8 @@ def test_run_start_after_run_stop_powers_chip_back_up() -> None:
187
253
 
188
254
  def test_run_start_without_run_stop_does_not_reinitialise() -> None:
189
255
  i2c = FakeI2C(present=[DEFAULT])
190
- sensor = _sensor(i2c)
191
- sensor.i2c_setup(0x20)
256
+ sensor = _sensor(i2c, address=0x20)
257
+ sensor.setup()
192
258
  i2c.writes.clear()
193
259
  sensor.read_sample = lambda: None # type: ignore[method-assign]
194
260