opentrons 8.6.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of opentrons might be problematic. Click here for more details.

Files changed (601) hide show
  1. opentrons/__init__.py +150 -0
  2. opentrons/_version.py +34 -0
  3. opentrons/calibration_storage/__init__.py +54 -0
  4. opentrons/calibration_storage/deck_configuration.py +62 -0
  5. opentrons/calibration_storage/encoder_decoder.py +31 -0
  6. opentrons/calibration_storage/file_operators.py +142 -0
  7. opentrons/calibration_storage/helpers.py +103 -0
  8. opentrons/calibration_storage/ot2/__init__.py +34 -0
  9. opentrons/calibration_storage/ot2/deck_attitude.py +85 -0
  10. opentrons/calibration_storage/ot2/mark_bad_calibration.py +27 -0
  11. opentrons/calibration_storage/ot2/models/__init__.py +0 -0
  12. opentrons/calibration_storage/ot2/models/v1.py +149 -0
  13. opentrons/calibration_storage/ot2/pipette_offset.py +129 -0
  14. opentrons/calibration_storage/ot2/tip_length.py +281 -0
  15. opentrons/calibration_storage/ot3/__init__.py +31 -0
  16. opentrons/calibration_storage/ot3/deck_attitude.py +83 -0
  17. opentrons/calibration_storage/ot3/gripper_offset.py +156 -0
  18. opentrons/calibration_storage/ot3/models/__init__.py +0 -0
  19. opentrons/calibration_storage/ot3/models/v1.py +122 -0
  20. opentrons/calibration_storage/ot3/module_offset.py +138 -0
  21. opentrons/calibration_storage/ot3/pipette_offset.py +95 -0
  22. opentrons/calibration_storage/types.py +45 -0
  23. opentrons/cli/__init__.py +21 -0
  24. opentrons/cli/__main__.py +5 -0
  25. opentrons/cli/analyze.py +557 -0
  26. opentrons/config/__init__.py +631 -0
  27. opentrons/config/advanced_settings.py +871 -0
  28. opentrons/config/defaults_ot2.py +214 -0
  29. opentrons/config/defaults_ot3.py +499 -0
  30. opentrons/config/feature_flags.py +86 -0
  31. opentrons/config/gripper_config.py +55 -0
  32. opentrons/config/reset.py +203 -0
  33. opentrons/config/robot_configs.py +187 -0
  34. opentrons/config/types.py +183 -0
  35. opentrons/drivers/__init__.py +0 -0
  36. opentrons/drivers/absorbance_reader/__init__.py +11 -0
  37. opentrons/drivers/absorbance_reader/abstract.py +72 -0
  38. opentrons/drivers/absorbance_reader/async_byonoy.py +352 -0
  39. opentrons/drivers/absorbance_reader/driver.py +81 -0
  40. opentrons/drivers/absorbance_reader/hid_protocol.py +161 -0
  41. opentrons/drivers/absorbance_reader/simulator.py +84 -0
  42. opentrons/drivers/asyncio/__init__.py +0 -0
  43. opentrons/drivers/asyncio/communication/__init__.py +22 -0
  44. opentrons/drivers/asyncio/communication/async_serial.py +187 -0
  45. opentrons/drivers/asyncio/communication/errors.py +88 -0
  46. opentrons/drivers/asyncio/communication/serial_connection.py +557 -0
  47. opentrons/drivers/command_builder.py +102 -0
  48. opentrons/drivers/flex_stacker/__init__.py +13 -0
  49. opentrons/drivers/flex_stacker/abstract.py +214 -0
  50. opentrons/drivers/flex_stacker/driver.py +768 -0
  51. opentrons/drivers/flex_stacker/errors.py +68 -0
  52. opentrons/drivers/flex_stacker/simulator.py +309 -0
  53. opentrons/drivers/flex_stacker/types.py +367 -0
  54. opentrons/drivers/flex_stacker/utils.py +19 -0
  55. opentrons/drivers/heater_shaker/__init__.py +5 -0
  56. opentrons/drivers/heater_shaker/abstract.py +76 -0
  57. opentrons/drivers/heater_shaker/driver.py +204 -0
  58. opentrons/drivers/heater_shaker/simulator.py +94 -0
  59. opentrons/drivers/mag_deck/__init__.py +6 -0
  60. opentrons/drivers/mag_deck/abstract.py +44 -0
  61. opentrons/drivers/mag_deck/driver.py +208 -0
  62. opentrons/drivers/mag_deck/simulator.py +63 -0
  63. opentrons/drivers/rpi_drivers/__init__.py +33 -0
  64. opentrons/drivers/rpi_drivers/dev_types.py +94 -0
  65. opentrons/drivers/rpi_drivers/gpio.py +282 -0
  66. opentrons/drivers/rpi_drivers/gpio_simulator.py +127 -0
  67. opentrons/drivers/rpi_drivers/interfaces.py +15 -0
  68. opentrons/drivers/rpi_drivers/types.py +364 -0
  69. opentrons/drivers/rpi_drivers/usb.py +102 -0
  70. opentrons/drivers/rpi_drivers/usb_simulator.py +22 -0
  71. opentrons/drivers/serial_communication.py +151 -0
  72. opentrons/drivers/smoothie_drivers/__init__.py +4 -0
  73. opentrons/drivers/smoothie_drivers/connection.py +51 -0
  74. opentrons/drivers/smoothie_drivers/constants.py +121 -0
  75. opentrons/drivers/smoothie_drivers/driver_3_0.py +1933 -0
  76. opentrons/drivers/smoothie_drivers/errors.py +49 -0
  77. opentrons/drivers/smoothie_drivers/parse_utils.py +143 -0
  78. opentrons/drivers/smoothie_drivers/simulator.py +99 -0
  79. opentrons/drivers/smoothie_drivers/types.py +16 -0
  80. opentrons/drivers/temp_deck/__init__.py +10 -0
  81. opentrons/drivers/temp_deck/abstract.py +54 -0
  82. opentrons/drivers/temp_deck/driver.py +197 -0
  83. opentrons/drivers/temp_deck/simulator.py +57 -0
  84. opentrons/drivers/thermocycler/__init__.py +12 -0
  85. opentrons/drivers/thermocycler/abstract.py +99 -0
  86. opentrons/drivers/thermocycler/driver.py +395 -0
  87. opentrons/drivers/thermocycler/simulator.py +126 -0
  88. opentrons/drivers/types.py +107 -0
  89. opentrons/drivers/utils.py +222 -0
  90. opentrons/execute.py +742 -0
  91. opentrons/hardware_control/__init__.py +65 -0
  92. opentrons/hardware_control/__main__.py +77 -0
  93. opentrons/hardware_control/adapters.py +98 -0
  94. opentrons/hardware_control/api.py +1347 -0
  95. opentrons/hardware_control/backends/__init__.py +7 -0
  96. opentrons/hardware_control/backends/controller.py +400 -0
  97. opentrons/hardware_control/backends/errors.py +9 -0
  98. opentrons/hardware_control/backends/estop_state.py +164 -0
  99. opentrons/hardware_control/backends/flex_protocol.py +497 -0
  100. opentrons/hardware_control/backends/ot3controller.py +1930 -0
  101. opentrons/hardware_control/backends/ot3simulator.py +900 -0
  102. opentrons/hardware_control/backends/ot3utils.py +664 -0
  103. opentrons/hardware_control/backends/simulator.py +442 -0
  104. opentrons/hardware_control/backends/status_bar_state.py +240 -0
  105. opentrons/hardware_control/backends/subsystem_manager.py +431 -0
  106. opentrons/hardware_control/backends/tip_presence_manager.py +173 -0
  107. opentrons/hardware_control/backends/types.py +14 -0
  108. opentrons/hardware_control/constants.py +6 -0
  109. opentrons/hardware_control/dev_types.py +125 -0
  110. opentrons/hardware_control/emulation/__init__.py +0 -0
  111. opentrons/hardware_control/emulation/abstract_emulator.py +21 -0
  112. opentrons/hardware_control/emulation/app.py +56 -0
  113. opentrons/hardware_control/emulation/connection_handler.py +38 -0
  114. opentrons/hardware_control/emulation/heater_shaker.py +150 -0
  115. opentrons/hardware_control/emulation/magdeck.py +60 -0
  116. opentrons/hardware_control/emulation/module_server/__init__.py +8 -0
  117. opentrons/hardware_control/emulation/module_server/client.py +78 -0
  118. opentrons/hardware_control/emulation/module_server/helpers.py +130 -0
  119. opentrons/hardware_control/emulation/module_server/models.py +31 -0
  120. opentrons/hardware_control/emulation/module_server/server.py +110 -0
  121. opentrons/hardware_control/emulation/parser.py +74 -0
  122. opentrons/hardware_control/emulation/proxy.py +241 -0
  123. opentrons/hardware_control/emulation/run_emulator.py +68 -0
  124. opentrons/hardware_control/emulation/scripts/__init__.py +0 -0
  125. opentrons/hardware_control/emulation/scripts/run_app.py +54 -0
  126. opentrons/hardware_control/emulation/scripts/run_module_emulator.py +72 -0
  127. opentrons/hardware_control/emulation/scripts/run_smoothie.py +37 -0
  128. opentrons/hardware_control/emulation/settings.py +119 -0
  129. opentrons/hardware_control/emulation/simulations.py +133 -0
  130. opentrons/hardware_control/emulation/smoothie.py +192 -0
  131. opentrons/hardware_control/emulation/tempdeck.py +69 -0
  132. opentrons/hardware_control/emulation/thermocycler.py +128 -0
  133. opentrons/hardware_control/emulation/types.py +10 -0
  134. opentrons/hardware_control/emulation/util.py +38 -0
  135. opentrons/hardware_control/errors.py +43 -0
  136. opentrons/hardware_control/execution_manager.py +164 -0
  137. opentrons/hardware_control/instruments/__init__.py +5 -0
  138. opentrons/hardware_control/instruments/instrument_abc.py +39 -0
  139. opentrons/hardware_control/instruments/ot2/__init__.py +0 -0
  140. opentrons/hardware_control/instruments/ot2/instrument_calibration.py +152 -0
  141. opentrons/hardware_control/instruments/ot2/pipette.py +777 -0
  142. opentrons/hardware_control/instruments/ot2/pipette_handler.py +995 -0
  143. opentrons/hardware_control/instruments/ot3/__init__.py +0 -0
  144. opentrons/hardware_control/instruments/ot3/gripper.py +420 -0
  145. opentrons/hardware_control/instruments/ot3/gripper_handler.py +173 -0
  146. opentrons/hardware_control/instruments/ot3/instrument_calibration.py +214 -0
  147. opentrons/hardware_control/instruments/ot3/pipette.py +858 -0
  148. opentrons/hardware_control/instruments/ot3/pipette_handler.py +1030 -0
  149. opentrons/hardware_control/module_control.py +332 -0
  150. opentrons/hardware_control/modules/__init__.py +69 -0
  151. opentrons/hardware_control/modules/absorbance_reader.py +373 -0
  152. opentrons/hardware_control/modules/errors.py +7 -0
  153. opentrons/hardware_control/modules/flex_stacker.py +948 -0
  154. opentrons/hardware_control/modules/heater_shaker.py +426 -0
  155. opentrons/hardware_control/modules/lid_temp_status.py +35 -0
  156. opentrons/hardware_control/modules/magdeck.py +233 -0
  157. opentrons/hardware_control/modules/mod_abc.py +245 -0
  158. opentrons/hardware_control/modules/module_calibration.py +93 -0
  159. opentrons/hardware_control/modules/plate_temp_status.py +61 -0
  160. opentrons/hardware_control/modules/tempdeck.py +299 -0
  161. opentrons/hardware_control/modules/thermocycler.py +731 -0
  162. opentrons/hardware_control/modules/types.py +417 -0
  163. opentrons/hardware_control/modules/update.py +255 -0
  164. opentrons/hardware_control/modules/utils.py +73 -0
  165. opentrons/hardware_control/motion_utilities.py +318 -0
  166. opentrons/hardware_control/nozzle_manager.py +422 -0
  167. opentrons/hardware_control/ot3_calibration.py +1171 -0
  168. opentrons/hardware_control/ot3api.py +3227 -0
  169. opentrons/hardware_control/pause_manager.py +31 -0
  170. opentrons/hardware_control/poller.py +112 -0
  171. opentrons/hardware_control/protocols/__init__.py +106 -0
  172. opentrons/hardware_control/protocols/asyncio_configurable.py +11 -0
  173. opentrons/hardware_control/protocols/calibratable.py +45 -0
  174. opentrons/hardware_control/protocols/chassis_accessory_manager.py +90 -0
  175. opentrons/hardware_control/protocols/configurable.py +48 -0
  176. opentrons/hardware_control/protocols/event_sourcer.py +18 -0
  177. opentrons/hardware_control/protocols/execution_controllable.py +33 -0
  178. opentrons/hardware_control/protocols/flex_calibratable.py +96 -0
  179. opentrons/hardware_control/protocols/flex_instrument_configurer.py +52 -0
  180. opentrons/hardware_control/protocols/gripper_controller.py +55 -0
  181. opentrons/hardware_control/protocols/hardware_manager.py +51 -0
  182. opentrons/hardware_control/protocols/identifiable.py +16 -0
  183. opentrons/hardware_control/protocols/instrument_configurer.py +206 -0
  184. opentrons/hardware_control/protocols/liquid_handler.py +266 -0
  185. opentrons/hardware_control/protocols/module_provider.py +16 -0
  186. opentrons/hardware_control/protocols/motion_controller.py +243 -0
  187. opentrons/hardware_control/protocols/position_estimator.py +45 -0
  188. opentrons/hardware_control/protocols/simulatable.py +10 -0
  189. opentrons/hardware_control/protocols/stoppable.py +9 -0
  190. opentrons/hardware_control/protocols/types.py +27 -0
  191. opentrons/hardware_control/robot_calibration.py +224 -0
  192. opentrons/hardware_control/scripts/README.md +28 -0
  193. opentrons/hardware_control/scripts/__init__.py +1 -0
  194. opentrons/hardware_control/scripts/gripper_control.py +208 -0
  195. opentrons/hardware_control/scripts/ot3gripper +7 -0
  196. opentrons/hardware_control/scripts/ot3repl +7 -0
  197. opentrons/hardware_control/scripts/repl.py +187 -0
  198. opentrons/hardware_control/scripts/tc_control.py +97 -0
  199. opentrons/hardware_control/scripts/update_module_fw.py +274 -0
  200. opentrons/hardware_control/simulator_setup.py +260 -0
  201. opentrons/hardware_control/thread_manager.py +431 -0
  202. opentrons/hardware_control/threaded_async_lock.py +97 -0
  203. opentrons/hardware_control/types.py +792 -0
  204. opentrons/hardware_control/util.py +234 -0
  205. opentrons/legacy_broker.py +53 -0
  206. opentrons/legacy_commands/__init__.py +1 -0
  207. opentrons/legacy_commands/commands.py +483 -0
  208. opentrons/legacy_commands/helpers.py +153 -0
  209. opentrons/legacy_commands/module_commands.py +276 -0
  210. opentrons/legacy_commands/protocol_commands.py +54 -0
  211. opentrons/legacy_commands/publisher.py +155 -0
  212. opentrons/legacy_commands/robot_commands.py +51 -0
  213. opentrons/legacy_commands/types.py +1186 -0
  214. opentrons/motion_planning/__init__.py +32 -0
  215. opentrons/motion_planning/adjacent_slots_getters.py +168 -0
  216. opentrons/motion_planning/deck_conflict.py +501 -0
  217. opentrons/motion_planning/errors.py +35 -0
  218. opentrons/motion_planning/types.py +42 -0
  219. opentrons/motion_planning/waypoints.py +218 -0
  220. opentrons/ordered_set.py +138 -0
  221. opentrons/protocol_api/__init__.py +105 -0
  222. opentrons/protocol_api/_liquid.py +157 -0
  223. opentrons/protocol_api/_liquid_properties.py +814 -0
  224. opentrons/protocol_api/_nozzle_layout.py +31 -0
  225. opentrons/protocol_api/_parameter_context.py +300 -0
  226. opentrons/protocol_api/_parameters.py +31 -0
  227. opentrons/protocol_api/_transfer_liquid_validation.py +108 -0
  228. opentrons/protocol_api/_types.py +43 -0
  229. opentrons/protocol_api/config.py +23 -0
  230. opentrons/protocol_api/core/__init__.py +23 -0
  231. opentrons/protocol_api/core/common.py +33 -0
  232. opentrons/protocol_api/core/core_map.py +74 -0
  233. opentrons/protocol_api/core/engine/__init__.py +22 -0
  234. opentrons/protocol_api/core/engine/_default_labware_versions.py +179 -0
  235. opentrons/protocol_api/core/engine/deck_conflict.py +400 -0
  236. opentrons/protocol_api/core/engine/exceptions.py +19 -0
  237. opentrons/protocol_api/core/engine/instrument.py +2391 -0
  238. opentrons/protocol_api/core/engine/labware.py +238 -0
  239. opentrons/protocol_api/core/engine/load_labware_params.py +73 -0
  240. opentrons/protocol_api/core/engine/module_core.py +1027 -0
  241. opentrons/protocol_api/core/engine/overlap_versions.py +20 -0
  242. opentrons/protocol_api/core/engine/pipette_movement_conflict.py +358 -0
  243. opentrons/protocol_api/core/engine/point_calculations.py +64 -0
  244. opentrons/protocol_api/core/engine/protocol.py +1153 -0
  245. opentrons/protocol_api/core/engine/robot.py +139 -0
  246. opentrons/protocol_api/core/engine/stringify.py +74 -0
  247. opentrons/protocol_api/core/engine/transfer_components_executor.py +1006 -0
  248. opentrons/protocol_api/core/engine/well.py +241 -0
  249. opentrons/protocol_api/core/instrument.py +459 -0
  250. opentrons/protocol_api/core/labware.py +151 -0
  251. opentrons/protocol_api/core/legacy/__init__.py +11 -0
  252. opentrons/protocol_api/core/legacy/_labware_geometry.py +37 -0
  253. opentrons/protocol_api/core/legacy/deck.py +369 -0
  254. opentrons/protocol_api/core/legacy/labware_offset_provider.py +108 -0
  255. opentrons/protocol_api/core/legacy/legacy_instrument_core.py +709 -0
  256. opentrons/protocol_api/core/legacy/legacy_labware_core.py +235 -0
  257. opentrons/protocol_api/core/legacy/legacy_module_core.py +592 -0
  258. opentrons/protocol_api/core/legacy/legacy_protocol_core.py +612 -0
  259. opentrons/protocol_api/core/legacy/legacy_well_core.py +162 -0
  260. opentrons/protocol_api/core/legacy/load_info.py +67 -0
  261. opentrons/protocol_api/core/legacy/module_geometry.py +547 -0
  262. opentrons/protocol_api/core/legacy/well_geometry.py +148 -0
  263. opentrons/protocol_api/core/legacy_simulator/__init__.py +16 -0
  264. opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +624 -0
  265. opentrons/protocol_api/core/legacy_simulator/legacy_protocol_core.py +85 -0
  266. opentrons/protocol_api/core/module.py +484 -0
  267. opentrons/protocol_api/core/protocol.py +311 -0
  268. opentrons/protocol_api/core/robot.py +51 -0
  269. opentrons/protocol_api/core/well.py +116 -0
  270. opentrons/protocol_api/core/well_grid.py +45 -0
  271. opentrons/protocol_api/create_protocol_context.py +177 -0
  272. opentrons/protocol_api/deck.py +223 -0
  273. opentrons/protocol_api/disposal_locations.py +244 -0
  274. opentrons/protocol_api/instrument_context.py +3272 -0
  275. opentrons/protocol_api/labware.py +1579 -0
  276. opentrons/protocol_api/module_contexts.py +1447 -0
  277. opentrons/protocol_api/module_validation_and_errors.py +61 -0
  278. opentrons/protocol_api/protocol_context.py +1688 -0
  279. opentrons/protocol_api/robot_context.py +303 -0
  280. opentrons/protocol_api/validation.py +761 -0
  281. opentrons/protocol_engine/__init__.py +155 -0
  282. opentrons/protocol_engine/actions/__init__.py +65 -0
  283. opentrons/protocol_engine/actions/action_dispatcher.py +30 -0
  284. opentrons/protocol_engine/actions/action_handler.py +13 -0
  285. opentrons/protocol_engine/actions/actions.py +302 -0
  286. opentrons/protocol_engine/actions/get_state_update.py +38 -0
  287. opentrons/protocol_engine/clients/__init__.py +5 -0
  288. opentrons/protocol_engine/clients/sync_client.py +174 -0
  289. opentrons/protocol_engine/clients/transports.py +197 -0
  290. opentrons/protocol_engine/commands/__init__.py +757 -0
  291. opentrons/protocol_engine/commands/absorbance_reader/__init__.py +61 -0
  292. opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +154 -0
  293. opentrons/protocol_engine/commands/absorbance_reader/common.py +6 -0
  294. opentrons/protocol_engine/commands/absorbance_reader/initialize.py +151 -0
  295. opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +154 -0
  296. opentrons/protocol_engine/commands/absorbance_reader/read.py +226 -0
  297. opentrons/protocol_engine/commands/air_gap_in_place.py +162 -0
  298. opentrons/protocol_engine/commands/aspirate.py +244 -0
  299. opentrons/protocol_engine/commands/aspirate_in_place.py +184 -0
  300. opentrons/protocol_engine/commands/aspirate_while_tracking.py +211 -0
  301. opentrons/protocol_engine/commands/blow_out.py +146 -0
  302. opentrons/protocol_engine/commands/blow_out_in_place.py +119 -0
  303. opentrons/protocol_engine/commands/calibration/__init__.py +60 -0
  304. opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +166 -0
  305. opentrons/protocol_engine/commands/calibration/calibrate_module.py +117 -0
  306. opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +96 -0
  307. opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +156 -0
  308. opentrons/protocol_engine/commands/command.py +308 -0
  309. opentrons/protocol_engine/commands/command_unions.py +974 -0
  310. opentrons/protocol_engine/commands/comment.py +57 -0
  311. opentrons/protocol_engine/commands/configure_for_volume.py +108 -0
  312. opentrons/protocol_engine/commands/configure_nozzle_layout.py +115 -0
  313. opentrons/protocol_engine/commands/custom.py +67 -0
  314. opentrons/protocol_engine/commands/dispense.py +194 -0
  315. opentrons/protocol_engine/commands/dispense_in_place.py +179 -0
  316. opentrons/protocol_engine/commands/dispense_while_tracking.py +204 -0
  317. opentrons/protocol_engine/commands/drop_tip.py +232 -0
  318. opentrons/protocol_engine/commands/drop_tip_in_place.py +205 -0
  319. opentrons/protocol_engine/commands/flex_stacker/__init__.py +64 -0
  320. opentrons/protocol_engine/commands/flex_stacker/common.py +900 -0
  321. opentrons/protocol_engine/commands/flex_stacker/empty.py +293 -0
  322. opentrons/protocol_engine/commands/flex_stacker/fill.py +281 -0
  323. opentrons/protocol_engine/commands/flex_stacker/retrieve.py +339 -0
  324. opentrons/protocol_engine/commands/flex_stacker/set_stored_labware.py +328 -0
  325. opentrons/protocol_engine/commands/flex_stacker/store.py +339 -0
  326. opentrons/protocol_engine/commands/generate_command_schema.py +61 -0
  327. opentrons/protocol_engine/commands/get_next_tip.py +134 -0
  328. opentrons/protocol_engine/commands/get_tip_presence.py +87 -0
  329. opentrons/protocol_engine/commands/hash_command_params.py +38 -0
  330. opentrons/protocol_engine/commands/heater_shaker/__init__.py +102 -0
  331. opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +83 -0
  332. opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +82 -0
  333. opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +84 -0
  334. opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +110 -0
  335. opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +125 -0
  336. opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +90 -0
  337. opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +102 -0
  338. opentrons/protocol_engine/commands/home.py +100 -0
  339. opentrons/protocol_engine/commands/identify_module.py +86 -0
  340. opentrons/protocol_engine/commands/labware_handling_common.py +29 -0
  341. opentrons/protocol_engine/commands/liquid_probe.py +464 -0
  342. opentrons/protocol_engine/commands/load_labware.py +210 -0
  343. opentrons/protocol_engine/commands/load_lid.py +154 -0
  344. opentrons/protocol_engine/commands/load_lid_stack.py +272 -0
  345. opentrons/protocol_engine/commands/load_liquid.py +95 -0
  346. opentrons/protocol_engine/commands/load_liquid_class.py +144 -0
  347. opentrons/protocol_engine/commands/load_module.py +223 -0
  348. opentrons/protocol_engine/commands/load_pipette.py +167 -0
  349. opentrons/protocol_engine/commands/magnetic_module/__init__.py +32 -0
  350. opentrons/protocol_engine/commands/magnetic_module/disengage.py +97 -0
  351. opentrons/protocol_engine/commands/magnetic_module/engage.py +119 -0
  352. opentrons/protocol_engine/commands/move_labware.py +546 -0
  353. opentrons/protocol_engine/commands/move_relative.py +102 -0
  354. opentrons/protocol_engine/commands/move_to_addressable_area.py +176 -0
  355. opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +198 -0
  356. opentrons/protocol_engine/commands/move_to_coordinates.py +107 -0
  357. opentrons/protocol_engine/commands/move_to_well.py +119 -0
  358. opentrons/protocol_engine/commands/movement_common.py +338 -0
  359. opentrons/protocol_engine/commands/pick_up_tip.py +241 -0
  360. opentrons/protocol_engine/commands/pipetting_common.py +443 -0
  361. opentrons/protocol_engine/commands/prepare_to_aspirate.py +121 -0
  362. opentrons/protocol_engine/commands/pressure_dispense.py +155 -0
  363. opentrons/protocol_engine/commands/reload_labware.py +90 -0
  364. opentrons/protocol_engine/commands/retract_axis.py +75 -0
  365. opentrons/protocol_engine/commands/robot/__init__.py +70 -0
  366. opentrons/protocol_engine/commands/robot/close_gripper_jaw.py +96 -0
  367. opentrons/protocol_engine/commands/robot/common.py +18 -0
  368. opentrons/protocol_engine/commands/robot/move_axes_relative.py +101 -0
  369. opentrons/protocol_engine/commands/robot/move_axes_to.py +100 -0
  370. opentrons/protocol_engine/commands/robot/move_to.py +94 -0
  371. opentrons/protocol_engine/commands/robot/open_gripper_jaw.py +86 -0
  372. opentrons/protocol_engine/commands/save_position.py +109 -0
  373. opentrons/protocol_engine/commands/seal_pipette_to_tip.py +353 -0
  374. opentrons/protocol_engine/commands/set_rail_lights.py +67 -0
  375. opentrons/protocol_engine/commands/set_status_bar.py +89 -0
  376. opentrons/protocol_engine/commands/temperature_module/__init__.py +46 -0
  377. opentrons/protocol_engine/commands/temperature_module/deactivate.py +86 -0
  378. opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +97 -0
  379. opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +104 -0
  380. opentrons/protocol_engine/commands/thermocycler/__init__.py +152 -0
  381. opentrons/protocol_engine/commands/thermocycler/close_lid.py +87 -0
  382. opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +80 -0
  383. opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +80 -0
  384. opentrons/protocol_engine/commands/thermocycler/open_lid.py +87 -0
  385. opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py +171 -0
  386. opentrons/protocol_engine/commands/thermocycler/run_profile.py +124 -0
  387. opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +140 -0
  388. opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +100 -0
  389. opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +93 -0
  390. opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +89 -0
  391. opentrons/protocol_engine/commands/touch_tip.py +189 -0
  392. opentrons/protocol_engine/commands/unsafe/__init__.py +161 -0
  393. opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +100 -0
  394. opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +121 -0
  395. opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py +82 -0
  396. opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py +208 -0
  397. opentrons/protocol_engine/commands/unsafe/unsafe_stacker_close_latch.py +94 -0
  398. opentrons/protocol_engine/commands/unsafe/unsafe_stacker_manual_retrieve.py +295 -0
  399. opentrons/protocol_engine/commands/unsafe/unsafe_stacker_open_latch.py +91 -0
  400. opentrons/protocol_engine/commands/unsafe/unsafe_stacker_prepare_shuttle.py +136 -0
  401. opentrons/protocol_engine/commands/unsafe/unsafe_ungrip_labware.py +77 -0
  402. opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +90 -0
  403. opentrons/protocol_engine/commands/unseal_pipette_from_tip.py +153 -0
  404. opentrons/protocol_engine/commands/verify_tip_presence.py +100 -0
  405. opentrons/protocol_engine/commands/wait_for_duration.py +76 -0
  406. opentrons/protocol_engine/commands/wait_for_resume.py +75 -0
  407. opentrons/protocol_engine/create_protocol_engine.py +193 -0
  408. opentrons/protocol_engine/engine_support.py +28 -0
  409. opentrons/protocol_engine/error_recovery_policy.py +81 -0
  410. opentrons/protocol_engine/errors/__init__.py +191 -0
  411. opentrons/protocol_engine/errors/error_occurrence.py +182 -0
  412. opentrons/protocol_engine/errors/exceptions.py +1308 -0
  413. opentrons/protocol_engine/execution/__init__.py +50 -0
  414. opentrons/protocol_engine/execution/command_executor.py +216 -0
  415. opentrons/protocol_engine/execution/create_queue_worker.py +102 -0
  416. opentrons/protocol_engine/execution/door_watcher.py +119 -0
  417. opentrons/protocol_engine/execution/equipment.py +819 -0
  418. opentrons/protocol_engine/execution/error_recovery_hardware_state_synchronizer.py +101 -0
  419. opentrons/protocol_engine/execution/gantry_mover.py +686 -0
  420. opentrons/protocol_engine/execution/hardware_stopper.py +147 -0
  421. opentrons/protocol_engine/execution/heater_shaker_movement_flagger.py +207 -0
  422. opentrons/protocol_engine/execution/labware_movement.py +297 -0
  423. opentrons/protocol_engine/execution/movement.py +350 -0
  424. opentrons/protocol_engine/execution/pipetting.py +607 -0
  425. opentrons/protocol_engine/execution/queue_worker.py +86 -0
  426. opentrons/protocol_engine/execution/rail_lights.py +25 -0
  427. opentrons/protocol_engine/execution/run_control.py +33 -0
  428. opentrons/protocol_engine/execution/status_bar.py +34 -0
  429. opentrons/protocol_engine/execution/thermocycler_movement_flagger.py +188 -0
  430. opentrons/protocol_engine/execution/thermocycler_plate_lifter.py +81 -0
  431. opentrons/protocol_engine/execution/tip_handler.py +550 -0
  432. opentrons/protocol_engine/labware_offset_standardization.py +194 -0
  433. opentrons/protocol_engine/notes/__init__.py +17 -0
  434. opentrons/protocol_engine/notes/notes.py +59 -0
  435. opentrons/protocol_engine/plugins.py +104 -0
  436. opentrons/protocol_engine/protocol_engine.py +683 -0
  437. opentrons/protocol_engine/resources/__init__.py +26 -0
  438. opentrons/protocol_engine/resources/deck_configuration_provider.py +232 -0
  439. opentrons/protocol_engine/resources/deck_data_provider.py +94 -0
  440. opentrons/protocol_engine/resources/file_provider.py +161 -0
  441. opentrons/protocol_engine/resources/fixture_validation.py +68 -0
  442. opentrons/protocol_engine/resources/labware_data_provider.py +106 -0
  443. opentrons/protocol_engine/resources/labware_validation.py +73 -0
  444. opentrons/protocol_engine/resources/model_utils.py +32 -0
  445. opentrons/protocol_engine/resources/module_data_provider.py +44 -0
  446. opentrons/protocol_engine/resources/ot3_validation.py +21 -0
  447. opentrons/protocol_engine/resources/pipette_data_provider.py +379 -0
  448. opentrons/protocol_engine/slot_standardization.py +128 -0
  449. opentrons/protocol_engine/state/__init__.py +1 -0
  450. opentrons/protocol_engine/state/_abstract_store.py +27 -0
  451. opentrons/protocol_engine/state/_axis_aligned_bounding_box.py +50 -0
  452. opentrons/protocol_engine/state/_labware_origin_math.py +636 -0
  453. opentrons/protocol_engine/state/_move_types.py +83 -0
  454. opentrons/protocol_engine/state/_well_math.py +193 -0
  455. opentrons/protocol_engine/state/addressable_areas.py +699 -0
  456. opentrons/protocol_engine/state/command_history.py +309 -0
  457. opentrons/protocol_engine/state/commands.py +1164 -0
  458. opentrons/protocol_engine/state/config.py +39 -0
  459. opentrons/protocol_engine/state/files.py +57 -0
  460. opentrons/protocol_engine/state/fluid_stack.py +138 -0
  461. opentrons/protocol_engine/state/geometry.py +2408 -0
  462. opentrons/protocol_engine/state/inner_well_math_utils.py +548 -0
  463. opentrons/protocol_engine/state/labware.py +1432 -0
  464. opentrons/protocol_engine/state/liquid_classes.py +82 -0
  465. opentrons/protocol_engine/state/liquids.py +73 -0
  466. opentrons/protocol_engine/state/module_substates/__init__.py +45 -0
  467. opentrons/protocol_engine/state/module_substates/absorbance_reader_substate.py +35 -0
  468. opentrons/protocol_engine/state/module_substates/flex_stacker_substate.py +112 -0
  469. opentrons/protocol_engine/state/module_substates/heater_shaker_module_substate.py +115 -0
  470. opentrons/protocol_engine/state/module_substates/magnetic_block_substate.py +17 -0
  471. opentrons/protocol_engine/state/module_substates/magnetic_module_substate.py +65 -0
  472. opentrons/protocol_engine/state/module_substates/temperature_module_substate.py +67 -0
  473. opentrons/protocol_engine/state/module_substates/thermocycler_module_substate.py +163 -0
  474. opentrons/protocol_engine/state/modules.py +1515 -0
  475. opentrons/protocol_engine/state/motion.py +373 -0
  476. opentrons/protocol_engine/state/pipettes.py +905 -0
  477. opentrons/protocol_engine/state/state.py +421 -0
  478. opentrons/protocol_engine/state/state_summary.py +36 -0
  479. opentrons/protocol_engine/state/tips.py +420 -0
  480. opentrons/protocol_engine/state/update_types.py +904 -0
  481. opentrons/protocol_engine/state/wells.py +290 -0
  482. opentrons/protocol_engine/types/__init__.py +310 -0
  483. opentrons/protocol_engine/types/automatic_tip_selection.py +39 -0
  484. opentrons/protocol_engine/types/command_annotations.py +53 -0
  485. opentrons/protocol_engine/types/deck_configuration.py +81 -0
  486. opentrons/protocol_engine/types/execution.py +96 -0
  487. opentrons/protocol_engine/types/hardware_passthrough.py +25 -0
  488. opentrons/protocol_engine/types/instrument.py +47 -0
  489. opentrons/protocol_engine/types/instrument_sensors.py +47 -0
  490. opentrons/protocol_engine/types/labware.py +131 -0
  491. opentrons/protocol_engine/types/labware_movement.py +22 -0
  492. opentrons/protocol_engine/types/labware_offset_location.py +111 -0
  493. opentrons/protocol_engine/types/labware_offset_vector.py +16 -0
  494. opentrons/protocol_engine/types/liquid.py +40 -0
  495. opentrons/protocol_engine/types/liquid_class.py +59 -0
  496. opentrons/protocol_engine/types/liquid_handling.py +13 -0
  497. opentrons/protocol_engine/types/liquid_level_detection.py +191 -0
  498. opentrons/protocol_engine/types/location.py +194 -0
  499. opentrons/protocol_engine/types/module.py +310 -0
  500. opentrons/protocol_engine/types/partial_tip_configuration.py +76 -0
  501. opentrons/protocol_engine/types/run_time_parameters.py +133 -0
  502. opentrons/protocol_engine/types/tip.py +18 -0
  503. opentrons/protocol_engine/types/util.py +21 -0
  504. opentrons/protocol_engine/types/well_position.py +124 -0
  505. opentrons/protocol_reader/__init__.py +37 -0
  506. opentrons/protocol_reader/extract_labware_definitions.py +66 -0
  507. opentrons/protocol_reader/file_format_validator.py +152 -0
  508. opentrons/protocol_reader/file_hasher.py +27 -0
  509. opentrons/protocol_reader/file_identifier.py +284 -0
  510. opentrons/protocol_reader/file_reader_writer.py +90 -0
  511. opentrons/protocol_reader/input_file.py +16 -0
  512. opentrons/protocol_reader/protocol_files_invalid_error.py +6 -0
  513. opentrons/protocol_reader/protocol_reader.py +188 -0
  514. opentrons/protocol_reader/protocol_source.py +124 -0
  515. opentrons/protocol_reader/role_analyzer.py +86 -0
  516. opentrons/protocol_runner/__init__.py +26 -0
  517. opentrons/protocol_runner/create_simulating_orchestrator.py +118 -0
  518. opentrons/protocol_runner/json_file_reader.py +55 -0
  519. opentrons/protocol_runner/json_translator.py +314 -0
  520. opentrons/protocol_runner/legacy_command_mapper.py +852 -0
  521. opentrons/protocol_runner/legacy_context_plugin.py +116 -0
  522. opentrons/protocol_runner/protocol_runner.py +530 -0
  523. opentrons/protocol_runner/python_protocol_wrappers.py +179 -0
  524. opentrons/protocol_runner/run_orchestrator.py +496 -0
  525. opentrons/protocol_runner/task_queue.py +95 -0
  526. opentrons/protocols/__init__.py +6 -0
  527. opentrons/protocols/advanced_control/__init__.py +0 -0
  528. opentrons/protocols/advanced_control/common.py +38 -0
  529. opentrons/protocols/advanced_control/mix.py +60 -0
  530. opentrons/protocols/advanced_control/transfers/__init__.py +0 -0
  531. opentrons/protocols/advanced_control/transfers/common.py +180 -0
  532. opentrons/protocols/advanced_control/transfers/transfer.py +972 -0
  533. opentrons/protocols/advanced_control/transfers/transfer_liquid_utils.py +231 -0
  534. opentrons/protocols/api_support/__init__.py +0 -0
  535. opentrons/protocols/api_support/constants.py +8 -0
  536. opentrons/protocols/api_support/deck_type.py +110 -0
  537. opentrons/protocols/api_support/definitions.py +18 -0
  538. opentrons/protocols/api_support/instrument.py +151 -0
  539. opentrons/protocols/api_support/labware_like.py +233 -0
  540. opentrons/protocols/api_support/tip_tracker.py +175 -0
  541. opentrons/protocols/api_support/types.py +32 -0
  542. opentrons/protocols/api_support/util.py +403 -0
  543. opentrons/protocols/bundle.py +89 -0
  544. opentrons/protocols/duration/__init__.py +4 -0
  545. opentrons/protocols/duration/errors.py +5 -0
  546. opentrons/protocols/duration/estimator.py +628 -0
  547. opentrons/protocols/execution/__init__.py +0 -0
  548. opentrons/protocols/execution/dev_types.py +181 -0
  549. opentrons/protocols/execution/errors.py +40 -0
  550. opentrons/protocols/execution/execute.py +84 -0
  551. opentrons/protocols/execution/execute_json_v3.py +275 -0
  552. opentrons/protocols/execution/execute_json_v4.py +359 -0
  553. opentrons/protocols/execution/execute_json_v5.py +28 -0
  554. opentrons/protocols/execution/execute_python.py +169 -0
  555. opentrons/protocols/execution/json_dispatchers.py +87 -0
  556. opentrons/protocols/execution/types.py +7 -0
  557. opentrons/protocols/geometry/__init__.py +0 -0
  558. opentrons/protocols/geometry/planning.py +297 -0
  559. opentrons/protocols/labware.py +312 -0
  560. opentrons/protocols/models/__init__.py +0 -0
  561. opentrons/protocols/models/json_protocol.py +679 -0
  562. opentrons/protocols/parameters/__init__.py +0 -0
  563. opentrons/protocols/parameters/csv_parameter_definition.py +77 -0
  564. opentrons/protocols/parameters/csv_parameter_interface.py +96 -0
  565. opentrons/protocols/parameters/exceptions.py +34 -0
  566. opentrons/protocols/parameters/parameter_definition.py +272 -0
  567. opentrons/protocols/parameters/types.py +17 -0
  568. opentrons/protocols/parameters/validation.py +267 -0
  569. opentrons/protocols/parse.py +671 -0
  570. opentrons/protocols/types.py +159 -0
  571. opentrons/py.typed +0 -0
  572. opentrons/resources/scripts/lpc21isp +0 -0
  573. opentrons/resources/smoothie-edge-8414642.hex +23010 -0
  574. opentrons/simulate.py +1065 -0
  575. opentrons/system/__init__.py +6 -0
  576. opentrons/system/camera.py +51 -0
  577. opentrons/system/log_control.py +59 -0
  578. opentrons/system/nmcli.py +856 -0
  579. opentrons/system/resin.py +24 -0
  580. opentrons/system/smoothie_update.py +15 -0
  581. opentrons/system/wifi.py +204 -0
  582. opentrons/tools/__init__.py +0 -0
  583. opentrons/tools/args_handler.py +22 -0
  584. opentrons/tools/write_pipette_memory.py +157 -0
  585. opentrons/types.py +618 -0
  586. opentrons/util/__init__.py +1 -0
  587. opentrons/util/async_helpers.py +166 -0
  588. opentrons/util/broker.py +84 -0
  589. opentrons/util/change_notifier.py +47 -0
  590. opentrons/util/entrypoint_util.py +278 -0
  591. opentrons/util/get_union_elements.py +26 -0
  592. opentrons/util/helpers.py +6 -0
  593. opentrons/util/linal.py +178 -0
  594. opentrons/util/logging_config.py +265 -0
  595. opentrons/util/logging_queue_handler.py +61 -0
  596. opentrons/util/performance_helpers.py +157 -0
  597. opentrons-8.6.0.dist-info/METADATA +37 -0
  598. opentrons-8.6.0.dist-info/RECORD +601 -0
  599. opentrons-8.6.0.dist-info/WHEEL +4 -0
  600. opentrons-8.6.0.dist-info/entry_points.txt +3 -0
  601. opentrons-8.6.0.dist-info/licenses/LICENSE +202 -0
@@ -0,0 +1,856 @@
1
+ """ opentrons.system.nmcli: Functions and data for interacting with nmcli
2
+
3
+ The functions contained here are for bridging Python calls with nmcli command
4
+ line invocations. They are in general not safe to call anywhere except an
5
+ Opentrons robot; on systems that do not have network-manager (like OSX, Windows
6
+ and some Linux distributions) they will not work, but on systems that _do_ they
7
+ may alter or destroy networking configurations.
8
+
9
+ In general, the functions here are light shims around nmcli invocations. This
10
+ is relevant in particular because they mostly do not handle exceptions coming
11
+ from subprocess itself, only parsing nmcli output.
12
+ """
13
+
14
+ import csv
15
+ import logging
16
+ import re
17
+ import copy
18
+ from typing import Optional, List, Tuple, Dict, Callable, Any, NamedTuple
19
+ import enum
20
+ import os
21
+
22
+ from shlex import quote
23
+ from asyncio import subprocess as as_subprocess
24
+
25
+ from opentrons import config
26
+
27
+
28
+ log = logging.getLogger(__name__)
29
+
30
+
31
+ class EAPType(NamedTuple):
32
+ name: str
33
+ displayName: str
34
+ args: List[Dict[str, Any]]
35
+
36
+
37
+ class _EAP_OUTER_TYPES(enum.Enum):
38
+ """The types of phase-1 EAP we support."""
39
+
40
+ # The string values of these supported EAP types should match both what
41
+ # is expected by nmcli/wpa_supplicant (in 802-1x.eap) and the keys in
42
+ # the CONFIG_REQUIRES dict above
43
+ # Note: if a file field has an optional password, the password field `name`
44
+ # must start with the `name` of the file field
45
+ TLS = EAPType(
46
+ name="tls",
47
+ displayName="EAP-TLS",
48
+ args=[
49
+ {
50
+ "name": "identity",
51
+ "displayName": "Username",
52
+ "nmName": "identity",
53
+ "required": True,
54
+ "type": "string",
55
+ },
56
+ {
57
+ "name": "caCert",
58
+ "displayName": "CA Certificate File",
59
+ "nmName": "ca-cert",
60
+ "required": False,
61
+ "type": "file",
62
+ },
63
+ {
64
+ "name": "clientCert",
65
+ "displayName": "Client Certificate File",
66
+ "nmName": "client-cert",
67
+ "required": True,
68
+ "type": "file",
69
+ },
70
+ {
71
+ "name": "privateKey",
72
+ "displayName": "Private Key File",
73
+ "nmName": "private-key",
74
+ "required": True,
75
+ "type": "file",
76
+ },
77
+ {
78
+ "name": "privateKeyPassword",
79
+ "displayName": "Private Key Password",
80
+ "nmName": "private-key-password",
81
+ "required": False,
82
+ "type": "password",
83
+ },
84
+ ],
85
+ )
86
+ PEAP = EAPType(
87
+ name="peap",
88
+ displayName="EAP-PEAP",
89
+ args=[
90
+ {
91
+ "name": "identity",
92
+ "displayName": "Username",
93
+ "nmName": "identity",
94
+ "required": True,
95
+ "type": "string",
96
+ },
97
+ {
98
+ "name": "anonymousIdentity",
99
+ "displayName": "Anonymous Identity",
100
+ "nmName": "anonymous-identity",
101
+ "required": False,
102
+ "type": "string",
103
+ },
104
+ {
105
+ "name": "caCert",
106
+ "displayName": "CA Certificate File",
107
+ "nmName": "ca-cert",
108
+ "required": False,
109
+ "type": "file",
110
+ },
111
+ ],
112
+ )
113
+ TTLS = EAPType(
114
+ name="ttls",
115
+ displayName="EAP-TTLS",
116
+ args=[
117
+ {
118
+ "name": "identity",
119
+ "displayName": "Username",
120
+ "nmName": "identity",
121
+ "required": True,
122
+ "type": "string",
123
+ },
124
+ {
125
+ "name": "anonymousIdentity",
126
+ "displayName": "Anonymous Identity",
127
+ "nmName": "anonymous-identity",
128
+ "required": False,
129
+ "type": "string",
130
+ },
131
+ {
132
+ "name": "caCert",
133
+ "displayName": "CA Certificate File",
134
+ "nmName": "ca-cert",
135
+ "required": False,
136
+ "type": "file",
137
+ },
138
+ {
139
+ "name": "clientCert",
140
+ "displayName": "Client Certificate File",
141
+ "nmName": "client-cert",
142
+ "required": False,
143
+ "type": "file",
144
+ },
145
+ {
146
+ "name": "privateKey",
147
+ "displayName": "Private Key File",
148
+ "nmName": "private-key",
149
+ "required": False,
150
+ "type": "file",
151
+ },
152
+ {
153
+ "name": "privateKeyPassword",
154
+ "displayName": "Private Key Password",
155
+ "nmName": "private-key-password",
156
+ "required": False,
157
+ "type": "password",
158
+ },
159
+ ],
160
+ )
161
+
162
+ def qualified_name(self) -> str:
163
+ return self.value.name
164
+
165
+
166
+ class _EAP_PHASE2_TYPES(enum.Enum):
167
+ """The types of EAP phase 2 auth (for tunneled EAP) we support"""
168
+
169
+ # The string values of these supported EAP types should match both what
170
+ # is expected by nmcli/wpa_supplicant (in 802-1x.phase2-autheap) and the
171
+ # keys in the CONFIG_REQUIRES dict above
172
+ MSCHAP_V2 = EAPType(
173
+ name="mschapv2",
174
+ displayName="MS-CHAP v2",
175
+ args=[
176
+ {
177
+ "name": "password",
178
+ "displayName": "Password",
179
+ "nmName": "password",
180
+ "required": True,
181
+ "type": "password",
182
+ }
183
+ ],
184
+ )
185
+ MD5 = EAPType(
186
+ name="md5",
187
+ displayName="MD5",
188
+ args=[
189
+ {
190
+ "name": "password",
191
+ "displayName": "Password",
192
+ "nmName": "password",
193
+ "required": True,
194
+ "type": "password",
195
+ }
196
+ ],
197
+ )
198
+ TLS = EAPType(
199
+ name="tls",
200
+ displayName="TLS",
201
+ args=[
202
+ {
203
+ "name": "phase2CaCert",
204
+ "displayName": "Inner CA Certificate File",
205
+ "nmName": "phase2-ca-cert",
206
+ "required": False,
207
+ "type": "file",
208
+ },
209
+ {
210
+ "name": "phase2ClientCert",
211
+ "displayName": "Inner Client Certificate File",
212
+ "nmName": "phase2-client-cert",
213
+ "required": True,
214
+ "type": "file",
215
+ },
216
+ {
217
+ "name": "phase2PrivateKey",
218
+ "displayName": "Inner Private Key File",
219
+ "nmName": "phase2-private-key",
220
+ "required": True,
221
+ "type": "file",
222
+ },
223
+ {
224
+ "name": "phase2PrivateKeyPassword",
225
+ "displayName": "Inner Private Key Password",
226
+ "nmName": "phase2-private-key-password",
227
+ "required": False,
228
+ "type": "password",
229
+ },
230
+ ],
231
+ )
232
+
233
+ def qualified_name(self) -> str:
234
+ return "eap-" + self.value.name
235
+
236
+
237
+ class EAP_TYPES(enum.Enum):
238
+ """The types of EAP we support, fusing inner and outer methods"""
239
+
240
+ TTLS_EAPTLS = (_EAP_OUTER_TYPES.TTLS, _EAP_PHASE2_TYPES.TLS)
241
+ TTLS_EAPMSCHAPV2 = (_EAP_OUTER_TYPES.TTLS, _EAP_PHASE2_TYPES.MSCHAP_V2)
242
+ TTLS_MD5 = (_EAP_OUTER_TYPES.TTLS, _EAP_PHASE2_TYPES.MD5)
243
+ PEAP_EAPMSCHAPV2 = (_EAP_OUTER_TYPES.PEAP, _EAP_PHASE2_TYPES.MSCHAP_V2)
244
+ TLS = (_EAP_OUTER_TYPES.TLS, None)
245
+
246
+ def __init__(self, outer: _EAP_OUTER_TYPES, inner: _EAP_PHASE2_TYPES) -> None:
247
+ self.outer = outer
248
+ self.inner = inner
249
+
250
+ def qualified_name(self) -> str:
251
+ name = self.outer.qualified_name()
252
+ if self.inner:
253
+ name += "/" + self.inner.qualified_name()
254
+ return name
255
+
256
+ @classmethod
257
+ def by_qualified_name(cls, qname: str) -> "EAP_TYPES":
258
+ for val in cls.__members__.values():
259
+ if val.qualified_name() == qname:
260
+ return val
261
+ raise KeyError(qname)
262
+
263
+ def args(self) -> List[Dict[str, Any]]:
264
+ # Have to copy these or reference semantics modify the version stored
265
+ # in the enums
266
+ to_ret = copy.deepcopy(self.outer.value.args)
267
+ if self.inner:
268
+ to_ret += copy.deepcopy(self.inner.value.args)
269
+ return to_ret
270
+
271
+ def display_name(self) -> str:
272
+ name = self.outer.value.displayName
273
+ if self.inner:
274
+ name += " with " + self.inner.value.displayName
275
+ return name
276
+
277
+
278
+ class SECURITY_TYPES(enum.Enum):
279
+ """The types of security that this module supports."""
280
+
281
+ # The string values of these supported security types are passed
282
+ # directly to nmcli; they should match the security types allowed
283
+ # in the network-manager settings for 802-11-wireless-security.key-mgmt
284
+ NONE = "none"
285
+ WPA_PSK = "wpa-psk"
286
+ WPA_EAP = "wpa-eap"
287
+
288
+
289
+ class CONNECTION_TYPES(enum.Enum):
290
+ """Types of connection (used to parse nmcli results)"""
291
+
292
+ # These connection types are used to parse nmcli results and should be
293
+ # valid results for the last element when splitting connection.type by ’-’
294
+ WIRELESS = "wireless"
295
+ ETHERNET = "ethernet"
296
+
297
+
298
+ class NETWORK_IFACES(enum.Enum):
299
+ """Network interface names that we manage here."""
300
+
301
+ WIFI = {
302
+ config.SystemArchitecture.BUILDROOT: "wlan0",
303
+ config.SystemArchitecture.YOCTO: "mlan0",
304
+ }.get(config.ARCHITECTURE, "wlan0")
305
+ ETH_LL = "eth0"
306
+
307
+
308
+ def _add_security_type_to_scan(scan_out: Dict[str, Any]) -> Dict[str, Any]:
309
+ sec = scan_out["security"]
310
+ if "802.1X" in sec:
311
+ scan_out["securityType"] = "wpa-eap"
312
+ elif "WPA2" in sec:
313
+ scan_out["securityType"] = "wpa-psk"
314
+ elif "" == sec:
315
+ scan_out["securityType"] = "none"
316
+ else:
317
+ scan_out["securityType"] = "unsupported"
318
+ return scan_out
319
+
320
+
321
+ async def available_ssids(rescan: Optional[bool] = False) -> List[Dict[str, Any]]:
322
+ """List the visible (broadcasting SSID) wireless networks.
323
+
324
+ rescan: Forces a rescan instead of using the cached results.
325
+
326
+ Returns a list of the SSIDs. They may contain spaces and should be escaped
327
+ if later passed to a shell.
328
+ """
329
+ # Force nmcli to actually scan if rescan is true rather than reuse cached results.
330
+ # We ignore errors here because NetworkManager yells at you if you do it twice in a
331
+ # row without another operation in between
332
+ if rescan:
333
+ cmd = ["device", "wifi", "rescan"]
334
+ _1, _2 = await _call(cmd, suppress_err=True)
335
+
336
+ fields = ["ssid", "signal", "active", "security"]
337
+ cmd = ["--terse", "--fields", ",".join(fields), "device", "wifi", "list"]
338
+ out, err = await _call(cmd)
339
+ if err:
340
+ raise RuntimeError(err)
341
+ output = _dict_from_terse_tabular(
342
+ fields,
343
+ out,
344
+ transformers={
345
+ "signal": lambda s: int(s) if s.isdigit() else None,
346
+ "active": lambda a: a.lower() == "yes",
347
+ "ssid": lambda s: s if s != "--" else None,
348
+ },
349
+ )
350
+
351
+ return [_add_security_type_to_scan(nw) for nw in output if nw["ssid"]]
352
+
353
+
354
+ async def is_connected() -> str:
355
+ """Return nmcli's connection measure: none/portal/limited/full/unknown"""
356
+ res, _ = await _call(["networking", "connectivity", "check"])
357
+ return res
358
+
359
+
360
+ async def connections(
361
+ for_type: Optional[CONNECTION_TYPES] = None,
362
+ ) -> List[Dict[str, str]]:
363
+ """Return the list of configured connections.
364
+
365
+ This is all connections that nmcli knows about and manages.
366
+ Each connection is a dict containing some basic information - the
367
+ information retrievable from nmcli connection show. Further information
368
+ should be queried on a connection by connection basis.
369
+
370
+ If for_type is not None, it should be a str containing an element of
371
+ CONNECTION_TYPES, and results will be limited to that connection type.
372
+ """
373
+ fields = ["name", "type", "active"]
374
+ res, _ = await _call(["-t", "-f", ",".join(fields), "connection", "show"])
375
+ found = _dict_from_terse_tabular(
376
+ fields,
377
+ res,
378
+ # ’ethernet’ or ’wireless’ from ’802-11-wireless’ or ’802-4-ethernet’
379
+ # and bools from ’yes’ or ’no
380
+ transformers={
381
+ "type": lambda s: s.split("-")[-1],
382
+ "active": lambda s: s.lower() == "yes",
383
+ },
384
+ )
385
+ if for_type is not None:
386
+ should_return = []
387
+ for c in found:
388
+ if c["type"] == for_type.value:
389
+ should_return.append(c)
390
+ return should_return
391
+ else:
392
+ return found
393
+
394
+
395
+ async def connection_exists(ssid: str) -> Optional[str]:
396
+ """If there is already a connection for this ssid, return the name of
397
+ the connection; if there is not, return None.
398
+ """
399
+ nmcli_conns = await connections()
400
+ for wifi in [c["name"] for c in nmcli_conns if c["type"] == "wireless"]:
401
+ res, _ = await _call(
402
+ [
403
+ "-t",
404
+ "-f",
405
+ "802-11-wireless.ssid",
406
+ "-m",
407
+ "tabular",
408
+ "connection",
409
+ "show",
410
+ wifi,
411
+ ]
412
+ )
413
+ if res == ssid:
414
+ return wifi
415
+ return None
416
+
417
+
418
+ async def _trim_old_connections(
419
+ new_name: str, con_type: CONNECTION_TYPES
420
+ ) -> Tuple[bool, str]:
421
+ """Delete all connections of con_type but the one specified."""
422
+ existing_cons = await connections(for_type=con_type)
423
+ not_us = [c["name"] for c in existing_cons if c["name"] != new_name]
424
+ ok = True
425
+ res = []
426
+ for c in not_us:
427
+ this_ok, remove_res = await remove(name=c)
428
+ ok = ok and this_ok
429
+ if not this_ok:
430
+ # This is not a failure case for connection, and indeed the new
431
+ # connection is already established, so just warn about it
432
+ log.warning("Could not remove wifi connection {}: {}".format(c, remove_res))
433
+ res.append(remove_res)
434
+ else:
435
+ log.debug("Removed old wifi connection {}".format(c))
436
+ return ok, ";".join(res)
437
+
438
+
439
+ def _add_eap_args(eap_args: Dict[str, str]) -> List[str]:
440
+ """Add configuration options suitable for an nmcli con add command
441
+ for WPA-EAP configuration. These options are mostly in the
442
+ 802-1x group.
443
+
444
+ The eap_args dict should be a flat structure of arguments. They
445
+ must contain at least 'eapType', specifying the EAP type to use
446
+ (the qualified_name() of one of the members of EAP_TYPES) and the
447
+ required arguments for that EAP type.
448
+ """
449
+ args = ["wifi-sec.key-mgmt", "wpa-eap"]
450
+ eap_type = EAP_TYPES.by_qualified_name(eap_args["eapType"])
451
+ type_args = eap_type.args()
452
+ args += ["802-1x.eap", eap_type.outer.value.name]
453
+ if eap_type.inner:
454
+ args += ["802-1x.phase2-autheap", eap_type.inner.value.name]
455
+ for ta in type_args:
456
+ if ta["name"] in eap_args:
457
+ if ta["type"] == "file":
458
+ # Keyfiles must be prepended with file:// so nm-cli
459
+ # knows that we’re not giving it DER-encoded blobs
460
+ val = "file://" + eap_args[ta["name"]]
461
+ else:
462
+ val = eap_args[ta["name"]]
463
+ args += ["802-1x." + ta["nmName"], val]
464
+ return args
465
+
466
+
467
+ def _build_con_add_cmd(
468
+ ssid: str,
469
+ security_type: SECURITY_TYPES,
470
+ psk: Optional[str],
471
+ hidden: bool,
472
+ eap_args: Optional[Dict[str, Any]],
473
+ ) -> List[str]:
474
+ """Build the nmcli connection add command to configure the new network.
475
+
476
+ The parameters are the same as configure but without the defaults; this
477
+ should be called only by configure.
478
+ """
479
+ configure_cmd = [
480
+ "connection",
481
+ "add",
482
+ "save",
483
+ "yes",
484
+ "autoconnect",
485
+ "yes",
486
+ "ifname",
487
+ NETWORK_IFACES.WIFI.value,
488
+ "type",
489
+ "wifi",
490
+ "con-name",
491
+ ssid,
492
+ "wifi.ssid",
493
+ ssid,
494
+ "802-11-wireless.cloned-mac-address",
495
+ "permanent",
496
+ ]
497
+ if hidden:
498
+ configure_cmd += ["wifi.hidden", "true"]
499
+ if security_type == SECURITY_TYPES.WPA_PSK:
500
+ configure_cmd += ["wifi-sec.key-mgmt", security_type.value]
501
+ if psk is None:
502
+ raise ValueError("wpa-psk security type requires psk")
503
+ configure_cmd += ["wifi-sec.psk", psk]
504
+ elif security_type == SECURITY_TYPES.WPA_EAP:
505
+ if eap_args is None:
506
+ raise ValueError("wpa-eap security type requires eap_args")
507
+ configure_cmd += _add_eap_args(eap_args)
508
+ elif security_type == SECURITY_TYPES.NONE:
509
+ pass
510
+ else:
511
+ raise ValueError("Bad security_type {}".format(security_type))
512
+
513
+ return configure_cmd
514
+
515
+
516
+ async def configure(
517
+ ssid: str,
518
+ securityType: SECURITY_TYPES,
519
+ psk: Optional[str] = None,
520
+ hidden: bool = False,
521
+ eapConfig: Optional[Dict[str, Any]] = None,
522
+ upRetries: int = 2,
523
+ ) -> Tuple[bool, str]:
524
+ """Configure a connection but do not bring it up (though it is configured
525
+ for autoconnect).
526
+
527
+ Returns (success, message) where ``success`` is a ``bool`` and ``message``
528
+ is a ``str``.
529
+
530
+ Only anticipated failures are treated that way - for instance, an ssid
531
+ that doesn't exist will get a False and a message; a system where nmcli
532
+ is not found will raise a CalledProcessError.
533
+
534
+ Input checks should be conducted before calling this function; any issues
535
+ with arguments that make it to this function will probably surface
536
+ themselves as TypeErrors and ValueErrors rather than anything more
537
+ structured.
538
+
539
+ The ssid and security_type arguments are mandatory; the others have
540
+ different requirements depending on the security type.
541
+ """
542
+ already = await connection_exists(ssid)
543
+ if already:
544
+ # TODO(seth, 8/29/2018): We may need to do connection modifies
545
+ # here for EAP configuration if e.g. we’re passing a keyfile in a
546
+ # different http request
547
+ _1, _2 = await _call(["connection", "delete", already])
548
+
549
+ configure_cmd = _build_con_add_cmd(ssid, securityType, psk, hidden, eapConfig)
550
+ res, err = await _call(configure_cmd)
551
+
552
+ # nmcli connection add returns a string that looks like
553
+ # "Connection ’connection-name’ (connection-uuid) successfully added."
554
+ # This unfortunately doesn’t respect the --terse flag, so we need to
555
+ # regex out the name or the uuid to use later in connection up; the
556
+ # uuid is slightly more regular, so that’s what we use.
557
+ uuid_matches = re.search(r"Connection '(.*)'[\s]+\(([\w\d-]+)\) successfully", res)
558
+ if not uuid_matches:
559
+ return False, err.split("\r")[-1]
560
+ name = uuid_matches.group(1)
561
+ uuid = uuid_matches.group(2)
562
+ for _ in range(upRetries):
563
+ res, err = await _call(["connection", "up", "uuid", uuid])
564
+ if "Connection successfully activated" in res:
565
+ # If we successfully added the connection, remove other wifi
566
+ # connections so they don’t accumulate over time
567
+
568
+ _3, _4 = await _trim_old_connections(name, CONNECTION_TYPES.WIRELESS)
569
+ return True, res
570
+ else:
571
+ return False, err.split("\r")[-1]
572
+
573
+
574
+ async def wifi_disconnect(ssid: str) -> Tuple[bool, str]:
575
+ """
576
+ Disconnect from specified wireless network and delete the connection.
577
+
578
+ Ideally, user would be allowed to disconnect a robot from wifi only over an
579
+ ethernet connection to the robot so that, 1) they get the disconnect
580
+ response back and 2) their robot isn't left with no connectivity at all
581
+ However, with robot discovery (over eth0) issues still pending, we will
582
+ allow the users to disconnect the robot from wifi regardless of whether
583
+ they are connected via ethernet.
584
+
585
+ Returns (True, msg) if the network was disconnected from successfully,
586
+ (False, msg) otherwise
587
+ """
588
+ res, err = await _call(["connection", "down", ssid])
589
+ if "successfully deactivated" in res:
590
+ rem_ok, rem_res = await remove(ssid)
591
+ if rem_ok:
592
+ res = res + ".\n " + rem_res
593
+ else:
594
+ res = res + ".\n Error: Could not remove ssid. " + rem_res
595
+ return True, res
596
+ else:
597
+ return False, err
598
+
599
+
600
+ async def remove(
601
+ ssid: Optional[str] = None,
602
+ name: Optional[str] = None,
603
+ ) -> Tuple[bool, str]:
604
+ """Remove a network. Depending on what is known, specify either ssid
605
+ (in which case this function will call ``connection_exists`` to get the
606
+ nmcli connection name) or the nmcli connection name directly.
607
+
608
+ Returns (True, msg) if the connection was deleted, (False, msg) otherwise.
609
+ """
610
+ if None is not ssid:
611
+ name = await connection_exists(ssid)
612
+ if None is not name:
613
+ res, err = await _call(["connection", "delete", name])
614
+ if "successfully deleted" in res:
615
+ return True, res
616
+ else:
617
+ return False, err
618
+ else:
619
+ return False, "No connection for ssid {}".format(ssid)
620
+
621
+
622
+ async def iface_info(which_iface: NETWORK_IFACES) -> Dict[str, Optional[str]]:
623
+ """Get the basic network configuration of an interface.
624
+
625
+ Returns a dict containing the info:
626
+ {
627
+ 'ipAddress': 'xx.xx.xx.xx/yy' (ip4 addr with subnet as CIDR) or None
628
+ 'macAddress': 'aa:bb:cc:dd:ee:ff' or None
629
+ 'gatewayAddress: 'zz.zz.zz.zz' or None
630
+ }
631
+
632
+ which_iface should be a string in IFACE_NAMES.
633
+ """
634
+ # example device info lines
635
+ # GENERAL.HWADDR:B8:27:EB:24:D1:D0
636
+ # IP4.ADDRESS[1]:10.10.2.221/22
637
+ # capture the field name (without the number in brackets) and the value
638
+ # using regex instead of split because there may be ":" in the value
639
+ _DEV_INFO_LINE_RE = re.compile(r"([\w.]+)(?:\[\d+])?:(.*)")
640
+ # example device info: 30 (disconnected)
641
+ # capture the string without the number
642
+ _IFACE_STATE_RE = re.compile(r"\d+ \((.+)\)")
643
+
644
+ info: Dict[str, Optional[str]] = {
645
+ "ipAddress": None,
646
+ "macAddress": None,
647
+ "gatewayAddress": None,
648
+ "state": None,
649
+ "type": None,
650
+ }
651
+ fields = [
652
+ "GENERAL.HWADDR",
653
+ "IP4.ADDRESS",
654
+ "IP4.GATEWAY",
655
+ "GENERAL.TYPE",
656
+ "GENERAL.STATE",
657
+ ]
658
+ # Note on this specific command: Most nmcli commands default to a tabular
659
+ # output mode, where if there are multiple things to pull a couple specific
660
+ # fields from it you’ll get a table where rows are, say, connections, and
661
+ # columns are field name. However, specifically ‘con show <con-name>‘ and
662
+ # ‘dev show <dev-name>’ default to a multiline representation, and even if
663
+ # explicitly ask for it to be tabular, it’s not quite the same as the other
664
+ # commands. So we have to special-case the parsing.
665
+ res, err = await _call(
666
+ [
667
+ "--mode",
668
+ "multiline",
669
+ "--escape",
670
+ "no",
671
+ "--terse",
672
+ "--fields",
673
+ ",".join(fields),
674
+ "dev",
675
+ "show",
676
+ which_iface.value,
677
+ ]
678
+ )
679
+
680
+ field_map = {}
681
+ for line in res.split("\n"):
682
+ # pull the key (without brackets) and the value out of the line
683
+ match = _DEV_INFO_LINE_RE.fullmatch(line)
684
+ if match is None:
685
+ raise ValueError("Bad nmcli result; out: {}; err: {}".format(res, err))
686
+ key, val = match.groups()
687
+ # nmcli can put "--" instead of "" for None
688
+ field_map[key] = None if val == "--" else val
689
+
690
+ info["macAddress"] = field_map.get("GENERAL.HWADDR")
691
+ info["ipAddress"] = field_map.get("IP4.ADDRESS")
692
+ info["gatewayAddress"] = field_map.get("IP4.GATEWAY")
693
+ info["type"] = field_map.get("GENERAL.TYPE")
694
+ state_val = field_map.get("GENERAL.STATE")
695
+
696
+ if state_val:
697
+ state_match = _IFACE_STATE_RE.fullmatch(state_val)
698
+ if state_match:
699
+ info["state"] = state_match.group(1)
700
+
701
+ return info
702
+
703
+
704
+ async def _call(cmd: List[str], suppress_err: bool = False) -> Tuple[str, str]:
705
+ """
706
+ Runs the command in a subprocess and returns the captured stdout output.
707
+ :param cmd: a list of arguments to nmcli. Should not include nmcli itself.
708
+
709
+ :return: (stdout, stderr)
710
+ """
711
+ to_exec = [quote(c) for c in ["nmcli"] + cmd]
712
+ cmd_str = " ".join(to_exec)
713
+ # We have to use a shell invocation here because nmcli will not accept
714
+ # secrets specified on the command line unless it’s in a shell. The other
715
+ # option is editing the connection configuration file in /etc/ afterwards
716
+ # (or using d-bus and pretending to be an auth agent)
717
+ proc = await as_subprocess.create_subprocess_shell(
718
+ cmd_str, stdout=as_subprocess.PIPE, stderr=as_subprocess.PIPE
719
+ )
720
+ out, err = await proc.communicate()
721
+ out_str, err_str = out.decode().strip(), err.decode().strip()
722
+ sanitized = sanitize_args(to_exec)
723
+ log.debug("{}: stdout={}".format(" ".join(sanitized), out_str))
724
+ if err_str and not suppress_err:
725
+ log.warning("{}: stderr={}".format(" ".join(sanitized), err_str))
726
+ return out_str, err_str
727
+
728
+
729
+ def sanitize_args(cmd: List[str]) -> List[str]:
730
+ """Filter the command so that it no longer contains passwords"""
731
+ sanitized = []
732
+ for idx, fieldname in enumerate(cmd):
733
+
734
+ def _is_password(cmdstr: str) -> bool:
735
+ return "wifi-sec.psk" in cmdstr or "password" in cmdstr.lower()
736
+
737
+ if idx > 0 and _is_password(cmd[idx - 1]):
738
+ sanitized.append("****")
739
+ else:
740
+ sanitized.append(fieldname)
741
+ return sanitized
742
+
743
+
744
+ def _parse_colonsep_response(response_fullstring: str) -> List[List[str]]:
745
+ reader = csv.reader(response_fullstring.split("\n"), delimiter=":", escapechar="\\")
746
+ return [row for row in reader if row]
747
+
748
+
749
+ def _dict_from_terse_tabular(
750
+ names: List[str], inp: str, transformers: Dict[str, Callable[[str], Any]] = {}
751
+ ) -> List[Dict[str, Any]]:
752
+ """Parse NMCLI terse tabular output into a list of Python dict.
753
+
754
+ ``names`` is a list of strings of field names to apply to the input data,
755
+ which is assumed to be colon separated.
756
+
757
+ ``inp`` is the input as a string (i.e. already decode()d) from nmcli
758
+
759
+ ``transformers`` is a dict mapping field names to callables of the form
760
+ f: str -> any. If a fieldname is in transformers, that callable will be
761
+ invoked on the field matching the name and the result stored.
762
+
763
+ The return value is a list with one element per valid line of input, where
764
+ each element is a dict with keys taken from names and values from the input
765
+ """
766
+ res = []
767
+ for n in names:
768
+ if n not in transformers:
769
+ transformers[n] = lambda s: s
770
+ for fields in _parse_colonsep_response(inp):
771
+ if len(fields) != len(names):
772
+ log.warning(f"ignoring unparsable fields <{fields}>")
773
+ continue
774
+ res.append(
775
+ dict(
776
+ [
777
+ (elem[0], transformers[elem[0]](elem[1]))
778
+ for elem in zip(names, fields)
779
+ ]
780
+ )
781
+ )
782
+ return res
783
+
784
+
785
+ # The functions that follow are designed to configure the system so that the
786
+ # paths to the keys specified in `nmcli con add`, from our perspective in
787
+ # the container, are the same as the paths to the keys from the perspective
788
+ # of the host.
789
+
790
+ # When we run nmcli, it is talking via D-bus to the NetworkManager daemon
791
+ # running in the host - not in our container. That means that all the config
792
+ # lives in the host’s /etc/NetworkManager/system-connections (you can tell
793
+ # because when we have connections up, the containers
794
+ # /etc/NetworkManager/system-connections is empty - I think it only exists
795
+ # because nmcli creates it on install). That means that file paths must be
796
+ # correct from that perspective.
797
+
798
+ # In addition, `nmcli con add` checks locally, _in the container_, that the
799
+ # paths specified as key files do indeed contain keyfiles. That means
800
+ # whatever paths we specify must be valid in the container at the time that
801
+ # we call `nmcli con add`, and valid in the host when we call `nmcli con up`
802
+ # (i.e., all the time, including when the container isn’t running). So, we
803
+ # make the paths be what the host will see, and we create a symlink in the
804
+ # container to make the path look the same here.
805
+
806
+ # This path depends on the resin app ID, which cannot be hardcoded because
807
+ # it changes based on which Resin application the robot running the code
808
+ # is in. It is also specified by Resin, which means it is given to pid 1
809
+ # and relies on pid 1 passing it to children made with execve. To make this
810
+ # path specification work whenever we run this, including if somebody is
811
+ # running it directly from a shell which _won’t_ correctly pass the data,
812
+ # we need to parse the environment of pid1.
813
+
814
+
815
+ def _get_host_data_prefix() -> str:
816
+ return os.path.join("mnt", "data", "resin-data", _get_resin_app_id())
817
+
818
+
819
+ def _get_resin_app_id() -> str:
820
+ if not config.IS_ROBOT:
821
+ raise RuntimeError("Resin app id is only available on the pi")
822
+ p1_env = open("/proc/1/environ").read()
823
+ # /proc/x/environ is pretty much just the raw memory segment of the
824
+ # process that represents its environment. It contains a
825
+ # NUL-separated list of strings.
826
+ app_id = re.search("RESIN_APP_ID=([^\x00]*)\x00", p1_env)
827
+ if not app_id:
828
+ raise RuntimeError("Cannot find resin app id! /proc/1/env={}".format(p1_env))
829
+ return app_id.group(1)
830
+
831
+
832
+ def _rewrite_key_path_to_host_path(key_path: str) -> str:
833
+ resin_id = _get_resin_app_id()
834
+ key_abspath = os.path.abspath(key_path)
835
+ if key_abspath.startswith("/data"):
836
+ key_relpath = os.path.relpath(key_abspath, "/data")
837
+ key_hostpath = os.path.join(
838
+ "/", "mnt", "data", "resin-data", resin_id, key_relpath
839
+ )
840
+ log.debug("Rewrote key path {} to {} for host".format(key_path, key_hostpath))
841
+ return key_hostpath
842
+ else:
843
+ log.warning(
844
+ "Wifi keys that are not in /data may not work unless"
845
+ "they are specified in a path common to the host and"
846
+ " the container"
847
+ )
848
+ return key_path
849
+
850
+
851
+ def _make_host_symlink_if_necessary() -> None:
852
+ host_data_prefix = _get_host_data_prefix()
853
+ if not os.path.islink(_get_host_data_prefix()):
854
+ parent = os.path.abspath(os.path.join(host_data_prefix, os.pardir))
855
+ os.makedirs(parent, exist_ok=True)
856
+ os.symlink("/data", host_data_prefix)