simo 2.10.7__py3-none-any.whl → 2.10.9__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 simo might be problematic. Click here for more details.

Files changed (386) hide show
  1. simo/__pycache__/__init__.cpython-312.pyc +0 -0
  2. simo/__pycache__/asgi.cpython-312.pyc +0 -0
  3. simo/__pycache__/celeryc.cpython-312.pyc +0 -0
  4. simo/__pycache__/conf.cpython-312.pyc +0 -0
  5. simo/__pycache__/settings.cpython-312.pyc +0 -0
  6. simo/__pycache__/urls.cpython-312.pyc +0 -0
  7. simo/automation/__pycache__/__init__.cpython-312.pyc +0 -0
  8. simo/automation/__pycache__/app_widgets.cpython-312.pyc +0 -0
  9. simo/automation/__pycache__/controllers.cpython-312.pyc +0 -0
  10. simo/automation/__pycache__/forms.cpython-312.pyc +0 -0
  11. simo/automation/__pycache__/gateways.cpython-312.pyc +0 -0
  12. simo/automation/__pycache__/helpers.cpython-312.pyc +0 -0
  13. simo/automation/__pycache__/models.cpython-312.pyc +0 -0
  14. simo/automation/__pycache__/serializers.cpython-312.pyc +0 -0
  15. simo/automation/__pycache__/state.cpython-312.pyc +0 -0
  16. simo/automation/migrations/__pycache__/0001_initial.cpython-312.pyc +0 -0
  17. simo/automation/migrations/__pycache__/0002_update_helpers_in_scripts.cpython-312.pyc +0 -0
  18. simo/automation/migrations/__pycache__/__init__.cpython-312.pyc +0 -0
  19. simo/automation/templates/automations/__pycache__/auto_away.cpython-312.pyc +0 -0
  20. simo/automation/templates/automations/__pycache__/auto_state_script.cpython-312.pyc +0 -0
  21. simo/automation/templates/automations/__pycache__/phones_sleep_script.cpython-312.pyc +0 -0
  22. simo/backups/__pycache__/__init__.cpython-312.pyc +0 -0
  23. simo/backups/__pycache__/admin.cpython-312.pyc +0 -0
  24. simo/backups/__pycache__/dynamic_settings.cpython-312.pyc +0 -0
  25. simo/backups/__pycache__/models.cpython-312.pyc +0 -0
  26. simo/backups/__pycache__/tasks.cpython-312.pyc +0 -0
  27. simo/backups/migrations/__pycache__/0001_initial.cpython-312.pyc +0 -0
  28. simo/backups/migrations/__pycache__/0002_backuplog_backup_level_backup_size.cpython-312.pyc +0 -0
  29. simo/backups/migrations/__pycache__/0003_alter_backuplog_options_alter_backup_size.cpython-312.pyc +0 -0
  30. simo/backups/migrations/__pycache__/0004_alter_backup_options_alter_backuplog_options_and_more.cpython-312.pyc +0 -0
  31. simo/backups/migrations/__pycache__/__init__.cpython-312.pyc +0 -0
  32. simo/core/__pycache__/__init__.cpython-312.pyc +0 -0
  33. simo/core/__pycache__/admin.cpython-312.pyc +0 -0
  34. simo/core/__pycache__/api.cpython-312.pyc +0 -0
  35. simo/core/__pycache__/api_auth.cpython-312.pyc +0 -0
  36. simo/core/__pycache__/api_meta.cpython-312.pyc +0 -0
  37. simo/core/__pycache__/app_widgets.cpython-312.pyc +0 -0
  38. simo/core/__pycache__/apps.cpython-312.pyc +0 -0
  39. simo/core/__pycache__/auto_urls.cpython-312.pyc +0 -0
  40. simo/core/__pycache__/autocomplete_views.cpython-312.pyc +0 -0
  41. simo/core/__pycache__/base_types.cpython-312.pyc +0 -0
  42. simo/core/__pycache__/context.cpython-312.pyc +0 -0
  43. simo/core/__pycache__/controllers.cpython-312.pyc +0 -0
  44. simo/core/__pycache__/dynamic_settings.cpython-312.pyc +0 -0
  45. simo/core/__pycache__/events.cpython-312.pyc +0 -0
  46. simo/core/__pycache__/filters.cpython-312.pyc +0 -0
  47. simo/core/__pycache__/form_fields.cpython-312.pyc +0 -0
  48. simo/core/__pycache__/forms.cpython-312.pyc +0 -0
  49. simo/core/__pycache__/gateways.cpython-312.pyc +0 -0
  50. simo/core/__pycache__/loggers.cpython-312.pyc +0 -0
  51. simo/core/__pycache__/managers.cpython-312.pyc +0 -0
  52. simo/core/__pycache__/middleware.cpython-312.pyc +0 -0
  53. simo/core/__pycache__/models.cpython-312.pyc +0 -0
  54. simo/core/__pycache__/permissions.cpython-312.pyc +0 -0
  55. simo/core/__pycache__/routing.cpython-312.pyc +0 -0
  56. simo/core/__pycache__/serializers.cpython-312.pyc +0 -0
  57. simo/core/__pycache__/signal_receivers.cpython-312.pyc +0 -0
  58. simo/core/__pycache__/socket_consumers.cpython-312.pyc +0 -0
  59. simo/core/__pycache__/storage.cpython-312.pyc +0 -0
  60. simo/core/__pycache__/tasks.cpython-312.pyc +0 -0
  61. simo/core/__pycache__/todos.cpython-312.pyc +0 -0
  62. simo/core/__pycache__/types.cpython-312.pyc +0 -0
  63. simo/core/__pycache__/views.cpython-312.pyc +0 -0
  64. simo/core/__pycache__/widgets.cpython-312.pyc +0 -0
  65. simo/core/controllers.py +2 -2
  66. simo/core/db_backend/__pycache__/__init__.cpython-312.pyc +0 -0
  67. simo/core/db_backend/__pycache__/base.cpython-312.pyc +0 -0
  68. simo/core/drf_braces/__pycache__/__init__.cpython-312.pyc +0 -0
  69. simo/core/drf_braces/__pycache__/mixins.cpython-312.pyc +0 -0
  70. simo/core/drf_braces/__pycache__/models.cpython-312.pyc +0 -0
  71. simo/core/drf_braces/__pycache__/parsers.cpython-312.pyc +0 -0
  72. simo/core/drf_braces/__pycache__/renderers.cpython-312.pyc +0 -0
  73. simo/core/drf_braces/__pycache__/utils.cpython-312.pyc +0 -0
  74. simo/core/drf_braces/fields/__pycache__/__init__.cpython-312.pyc +0 -0
  75. simo/core/drf_braces/fields/__pycache__/_fields.cpython-312.pyc +0 -0
  76. simo/core/drf_braces/fields/__pycache__/custom.cpython-312.pyc +0 -0
  77. simo/core/drf_braces/fields/__pycache__/mixins.cpython-312.pyc +0 -0
  78. simo/core/drf_braces/fields/__pycache__/modified.cpython-312.pyc +0 -0
  79. simo/core/drf_braces/forms/__pycache__/__init__.cpython-312.pyc +0 -0
  80. simo/core/drf_braces/forms/__pycache__/fields.cpython-312.pyc +0 -0
  81. simo/core/drf_braces/forms/__pycache__/serializer_form.cpython-312.pyc +0 -0
  82. simo/core/drf_braces/serializers/__pycache__/__init__.cpython-312.pyc +0 -0
  83. simo/core/drf_braces/serializers/__pycache__/enforce_validation_serializer.cpython-312.pyc +0 -0
  84. simo/core/drf_braces/serializers/__pycache__/form_serializer.cpython-312.pyc +0 -0
  85. simo/core/drf_braces/serializers/__pycache__/swapping.cpython-312.pyc +0 -0
  86. simo/core/drf_braces/tests/__pycache__/__init__.cpython-312.pyc +0 -0
  87. simo/core/drf_braces/tests/__pycache__/test_mixins.cpython-312.pyc +0 -0
  88. simo/core/drf_braces/tests/__pycache__/test_parsers.cpython-312.pyc +0 -0
  89. simo/core/drf_braces/tests/__pycache__/test_renderers.cpython-312.pyc +0 -0
  90. simo/core/drf_braces/tests/__pycache__/test_utils.cpython-312.pyc +0 -0
  91. simo/core/drf_braces/tests/fields/__pycache__/__init__.cpython-312.pyc +0 -0
  92. simo/core/drf_braces/tests/fields/__pycache__/test_custom.cpython-312.pyc +0 -0
  93. simo/core/drf_braces/tests/fields/__pycache__/test_fields.cpython-312.pyc +0 -0
  94. simo/core/drf_braces/tests/fields/__pycache__/test_mixins.cpython-312.pyc +0 -0
  95. simo/core/drf_braces/tests/fields/__pycache__/test_modified.cpython-312.pyc +0 -0
  96. simo/core/drf_braces/tests/forms/__pycache__/__init__.cpython-312.pyc +0 -0
  97. simo/core/drf_braces/tests/forms/__pycache__/test_fields.cpython-312.pyc +0 -0
  98. simo/core/drf_braces/tests/forms/__pycache__/test_serializer_form.cpython-312.pyc +0 -0
  99. simo/core/drf_braces/tests/serializers/__pycache__/__init__.cpython-312.pyc +0 -0
  100. simo/core/drf_braces/tests/serializers/__pycache__/test_enforce_validation_serializer.cpython-312.pyc +0 -0
  101. simo/core/drf_braces/tests/serializers/__pycache__/test_form_serializer.cpython-312.pyc +0 -0
  102. simo/core/drf_braces/tests/serializers/__pycache__/test_swapping.cpython-312.pyc +0 -0
  103. simo/core/management/__pycache__/__init__.cpython-312.pyc +0 -0
  104. simo/core/management/__pycache__/update.cpython-312.pyc +0 -0
  105. simo/core/management/_hub_template/hub/__pycache__/asgi.cpython-312.pyc +0 -0
  106. simo/core/management/_hub_template/hub/__pycache__/celeryc.cpython-312.pyc +0 -0
  107. simo/core/management/_hub_template/hub/__pycache__/manage.cpython-312.pyc +0 -0
  108. simo/core/management/_hub_template/hub/__pycache__/settings.cpython-312.pyc +0 -0
  109. simo/core/management/_hub_template/hub/__pycache__/urls.cpython-312.pyc +0 -0
  110. simo/core/management/_hub_template/hub/__pycache__/wsgi.cpython-312.pyc +0 -0
  111. simo/core/management/commands/__pycache__/__init__.cpython-312.pyc +0 -0
  112. simo/core/management/commands/__pycache__/gateways_manager.cpython-312.pyc +0 -0
  113. simo/core/management/commands/__pycache__/on_http_start.cpython-312.pyc +0 -0
  114. simo/core/management/commands/__pycache__/run_gateway.cpython-312.pyc +0 -0
  115. simo/core/migrations/0050_componenthistory_alive.py +18 -0
  116. simo/core/migrations/__pycache__/0001_initial.cpython-312.pyc +0 -0
  117. simo/core/migrations/__pycache__/0002_load_icons.cpython-312.pyc +0 -0
  118. simo/core/migrations/__pycache__/0003_create_default_zones_and_categories.cpython-312.pyc +0 -0
  119. simo/core/migrations/__pycache__/0004_create_generic.cpython-312.pyc +0 -0
  120. simo/core/migrations/__pycache__/0005_component_subcomponents.cpython-312.pyc +0 -0
  121. simo/core/migrations/__pycache__/0006_alter_component_subcomponents.cpython-312.pyc +0 -0
  122. simo/core/migrations/__pycache__/0007_component_change_init_to.cpython-312.pyc +0 -0
  123. simo/core/migrations/__pycache__/0008_alter_component_change_init_to.cpython-312.pyc +0 -0
  124. simo/core/migrations/__pycache__/0009_auto_20220707_1404.cpython-312.pyc +0 -0
  125. simo/core/migrations/__pycache__/0010_historyaggregate.cpython-312.pyc +0 -0
  126. simo/core/migrations/__pycache__/0011_component_last_change.cpython-312.pyc +0 -0
  127. simo/core/migrations/__pycache__/0012_instance.cpython-312.pyc +0 -0
  128. simo/core/migrations/__pycache__/0013_auto_20231003_0754.cpython-312.pyc +0 -0
  129. simo/core/migrations/__pycache__/0014_zone_instance.cpython-312.pyc +0 -0
  130. simo/core/migrations/__pycache__/0015_auto_20231004_1113.cpython-312.pyc +0 -0
  131. simo/core/migrations/__pycache__/0016_auto_20231004_1113.cpython-312.pyc +0 -0
  132. simo/core/migrations/__pycache__/0017_auto_20231004_1313.cpython-312.pyc +0 -0
  133. simo/core/migrations/__pycache__/0018_auto_20231005_0622.cpython-312.pyc +0 -0
  134. simo/core/migrations/__pycache__/0019_alter_gateway_type.cpython-312.pyc +0 -0
  135. simo/core/migrations/__pycache__/0020_component_meta.cpython-312.pyc +0 -0
  136. simo/core/migrations/__pycache__/0021_auto_20231020_1041.cpython-312.pyc +0 -0
  137. simo/core/migrations/__pycache__/0022_auto_20231221_0735.cpython-312.pyc +0 -0
  138. simo/core/migrations/__pycache__/0023_auto_20231229_1352.cpython-312.pyc +0 -0
  139. simo/core/migrations/__pycache__/0024_alter_instance_device_report_history_days.cpython-312.pyc +0 -0
  140. simo/core/migrations/__pycache__/0025_auto_20240122_1321.cpython-312.pyc +0 -0
  141. simo/core/migrations/__pycache__/0026_category_instance.cpython-312.pyc +0 -0
  142. simo/core/migrations/__pycache__/0027_remove_component_tags.cpython-312.pyc +0 -0
  143. simo/core/migrations/__pycache__/0028_rename_subcomponents_component_slaves.cpython-312.pyc +0 -0
  144. simo/core/migrations/__pycache__/0029_auto_20240229_1331.cpython-312.pyc +0 -0
  145. simo/core/migrations/__pycache__/0030_alter_instance_timezone.cpython-312.pyc +0 -0
  146. simo/core/migrations/__pycache__/0031_auto_20240429_1231.cpython-312.pyc +0 -0
  147. simo/core/migrations/__pycache__/0032_auto_20240506_0834.cpython-312.pyc +0 -0
  148. simo/core/migrations/__pycache__/0033_auto_20240509_0821.cpython-312.pyc +0 -0
  149. simo/core/migrations/__pycache__/0034_component_error_msg.cpython-312.pyc +0 -0
  150. simo/core/migrations/__pycache__/0035_remove_instance_share_location.cpython-312.pyc +0 -0
  151. simo/core/migrations/__pycache__/0036_auto_20240521_0823.cpython-312.pyc +0 -0
  152. simo/core/migrations/__pycache__/0037_auto_20240606_1057.cpython-312.pyc +0 -0
  153. simo/core/migrations/__pycache__/0038_remove_instance_cover_image_and_more.cpython-312.pyc +0 -0
  154. simo/core/migrations/__pycache__/0039_instance_is_active_alter_instance_timezone.cpython-312.pyc +0 -0
  155. simo/core/migrations/__pycache__/0040_alter_instance_name.cpython-312.pyc +0 -0
  156. simo/core/migrations/__pycache__/0041_alter_instance_slug.cpython-312.pyc +0 -0
  157. simo/core/migrations/__pycache__/0042_alter_instance_timezone.cpython-312.pyc +0 -0
  158. simo/core/migrations/__pycache__/0043_alter_category_instance_alter_instance_timezone_and_more.cpython-312.pyc +0 -0
  159. simo/core/migrations/__pycache__/0044_alter_gateway_type.cpython-312.pyc +0 -0
  160. simo/core/migrations/__pycache__/0045_alter_instance_device_report_history_days_and_more.cpython-312.pyc +0 -0
  161. simo/core/migrations/__pycache__/0046_component_value_translation_alter_gateway_type.cpython-312.pyc +0 -0
  162. simo/core/migrations/__pycache__/0047_alter_component_value_translation.cpython-312.pyc +0 -0
  163. simo/core/migrations/__pycache__/0048_publicfile_privatefile.cpython-312.pyc +0 -0
  164. simo/core/migrations/__pycache__/0049_alter_gateway_type.cpython-312.pyc +0 -0
  165. simo/core/migrations/__pycache__/0050_componenthistory_alive.cpython-312.pyc +0 -0
  166. simo/core/migrations/__pycache__/__init__.cpython-312.pyc +0 -0
  167. simo/core/models.py +3 -1
  168. simo/core/serializers.py +2 -2
  169. simo/core/tasks.py +0 -1
  170. simo/core/templates/admin/component_history.html +18 -2
  171. simo/core/templates/core/__pycache__/value_translation.cpython-312.pyc +0 -0
  172. simo/core/templatetags/__pycache__/__init__.cpython-312.pyc +0 -0
  173. simo/core/templatetags/__pycache__/components_list.cpython-312.pyc +0 -0
  174. simo/core/utils/__pycache__/__init__.cpython-312.pyc +0 -0
  175. simo/core/utils/__pycache__/admin.cpython-312.pyc +0 -0
  176. simo/core/utils/__pycache__/api.cpython-312.pyc +0 -0
  177. simo/core/utils/__pycache__/cache.cpython-312.pyc +0 -0
  178. simo/core/utils/__pycache__/config_values.cpython-312.pyc +0 -0
  179. simo/core/utils/__pycache__/converters.cpython-312.pyc +0 -0
  180. simo/core/utils/__pycache__/easing.cpython-312.pyc +0 -0
  181. simo/core/utils/__pycache__/form_fields.cpython-312.pyc +0 -0
  182. simo/core/utils/__pycache__/form_widgets.cpython-312.pyc +0 -0
  183. simo/core/utils/__pycache__/formsets.cpython-312.pyc +0 -0
  184. simo/core/utils/__pycache__/helpers.cpython-312.pyc +0 -0
  185. simo/core/utils/__pycache__/json.cpython-312.pyc +0 -0
  186. simo/core/utils/__pycache__/logs.cpython-312.pyc +0 -0
  187. simo/core/utils/__pycache__/mixins.cpython-312.pyc +0 -0
  188. simo/core/utils/__pycache__/model_helpers.cpython-312.pyc +0 -0
  189. simo/core/utils/__pycache__/operations.cpython-312.pyc +0 -0
  190. simo/core/utils/__pycache__/relay.cpython-312.pyc +0 -0
  191. simo/core/utils/__pycache__/serialization.cpython-312.pyc +0 -0
  192. simo/core/utils/__pycache__/type_constants.cpython-312.pyc +0 -0
  193. simo/core/utils/__pycache__/validators.cpython-312.pyc +0 -0
  194. simo/fleet/__pycache__/__init__.cpython-312.pyc +0 -0
  195. simo/fleet/__pycache__/admin.cpython-312.pyc +0 -0
  196. simo/fleet/__pycache__/api.cpython-312.pyc +0 -0
  197. simo/fleet/__pycache__/apps.cpython-312.pyc +0 -0
  198. simo/fleet/__pycache__/auto_urls.cpython-312.pyc +0 -0
  199. simo/fleet/__pycache__/base_types.cpython-312.pyc +0 -0
  200. simo/fleet/__pycache__/ble.cpython-312.pyc +0 -0
  201. simo/fleet/__pycache__/controllers.cpython-312.pyc +0 -0
  202. simo/fleet/__pycache__/custom_dali_operations.cpython-312.pyc +0 -0
  203. simo/fleet/__pycache__/forms.cpython-312.pyc +0 -0
  204. simo/fleet/__pycache__/gateways.cpython-312.pyc +0 -0
  205. simo/fleet/__pycache__/managers.cpython-312.pyc +0 -0
  206. simo/fleet/__pycache__/models.cpython-312.pyc +0 -0
  207. simo/fleet/__pycache__/routing.cpython-312.pyc +0 -0
  208. simo/fleet/__pycache__/serializers.cpython-312.pyc +0 -0
  209. simo/fleet/__pycache__/socket_consumers.cpython-312.pyc +0 -0
  210. simo/fleet/__pycache__/tasks.cpython-312.pyc +0 -0
  211. simo/fleet/__pycache__/utils.cpython-312.pyc +0 -0
  212. simo/fleet/__pycache__/views.cpython-312.pyc +0 -0
  213. simo/fleet/api.py +5 -2
  214. simo/fleet/controllers.py +98 -14
  215. simo/fleet/forms.py +67 -107
  216. simo/fleet/gateways.py +34 -4
  217. simo/fleet/migrations/0052_colonelpin_interface.py +18 -0
  218. simo/fleet/migrations/0053_auto_20250507_0713.py +24 -0
  219. simo/fleet/migrations/__pycache__/0001_initial.cpython-312.pyc +0 -0
  220. simo/fleet/migrations/__pycache__/0002_auto_20220422_0743.cpython-312.pyc +0 -0
  221. simo/fleet/migrations/__pycache__/0003_auto_20220422_0752.cpython-312.pyc +0 -0
  222. simo/fleet/migrations/__pycache__/0004_auto_20220422_0818.cpython-312.pyc +0 -0
  223. simo/fleet/migrations/__pycache__/0005_auto_20220428_0900.cpython-312.pyc +0 -0
  224. simo/fleet/migrations/__pycache__/0006_rename_mac_colonel_uid.cpython-312.pyc +0 -0
  225. simo/fleet/migrations/__pycache__/0007_colonel_socket_connected.cpython-312.pyc +0 -0
  226. simo/fleet/migrations/__pycache__/0008_i2cinterface.cpython-312.pyc +0 -0
  227. simo/fleet/migrations/__pycache__/0009_i2cinterface_name.cpython-312.pyc +0 -0
  228. simo/fleet/migrations/__pycache__/0010_auto_20220602_0746.cpython-312.pyc +0 -0
  229. simo/fleet/migrations/__pycache__/0011_i2cinterface_freq.cpython-312.pyc +0 -0
  230. simo/fleet/migrations/__pycache__/0012_colonel_logs_stream.cpython-312.pyc +0 -0
  231. simo/fleet/migrations/__pycache__/0013_alter_colonel_last_seen.cpython-312.pyc +0 -0
  232. simo/fleet/migrations/__pycache__/0014_auto_20220614_0659.cpython-312.pyc +0 -0
  233. simo/fleet/migrations/__pycache__/0015_auto_20220614_0754.cpython-312.pyc +0 -0
  234. simo/fleet/migrations/__pycache__/0016_auto_20220704_0840.cpython-312.pyc +0 -0
  235. simo/fleet/migrations/__pycache__/0017_alter_colonel_secret.cpython-312.pyc +0 -0
  236. simo/fleet/migrations/__pycache__/0018_colonel_instance.cpython-312.pyc +0 -0
  237. simo/fleet/migrations/__pycache__/0019_auto_20231006_0749.cpython-312.pyc +0 -0
  238. simo/fleet/migrations/__pycache__/0020_instanceoptions.cpython-312.pyc +0 -0
  239. simo/fleet/migrations/__pycache__/0021_auto_20231006_0819.cpython-312.pyc +0 -0
  240. simo/fleet/migrations/__pycache__/0022_remove_colonel_secret.cpython-312.pyc +0 -0
  241. simo/fleet/migrations/__pycache__/0023_colonel_is_authorized.cpython-312.pyc +0 -0
  242. simo/fleet/migrations/__pycache__/0024_colonel_pwm_frequency.cpython-312.pyc +0 -0
  243. simo/fleet/migrations/__pycache__/0025_auto_20240130_1334.cpython-312.pyc +0 -0
  244. simo/fleet/migrations/__pycache__/0026_rename_i2cinterface_scl_pin_and_more.cpython-312.pyc +0 -0
  245. simo/fleet/migrations/__pycache__/0027_auto_20240306_0802.cpython-312.pyc +0 -0
  246. simo/fleet/migrations/__pycache__/0028_remove_i2cinterface_scl_pin_no_and_more.cpython-312.pyc +0 -0
  247. simo/fleet/migrations/__pycache__/0029_alter_i2cinterface_scl_pin_and_more.cpython-312.pyc +0 -0
  248. simo/fleet/migrations/__pycache__/0030_colonelpin_label_alter_colonel_type.cpython-312.pyc +0 -0
  249. simo/fleet/migrations/__pycache__/0031_alter_colonel_type.cpython-312.pyc +0 -0
  250. simo/fleet/migrations/__pycache__/0032_auto_20240415_0736.cpython-312.pyc +0 -0
  251. simo/fleet/migrations/__pycache__/0033_auto_20240415_0736.cpython-312.pyc +0 -0
  252. simo/fleet/migrations/__pycache__/0034_auto_20240418_0735.cpython-312.pyc +0 -0
  253. simo/fleet/migrations/__pycache__/0035_auto_20240514_0855.cpython-312.pyc +0 -0
  254. simo/fleet/migrations/__pycache__/0036_auto_20240605_0702.cpython-312.pyc +0 -0
  255. simo/fleet/migrations/__pycache__/0037_alter_colonelpin_options_alter_colonelpin_no_and_more.cpython-312.pyc +0 -0
  256. simo/fleet/migrations/__pycache__/0038_alter_colonel_type.cpython-312.pyc +0 -0
  257. simo/fleet/migrations/__pycache__/0039_auto_20241016_1047.cpython-312.pyc +0 -0
  258. simo/fleet/migrations/__pycache__/0040_alter_colonel_pwm_frequency.cpython-312.pyc +0 -0
  259. simo/fleet/migrations/__pycache__/0041_alter_colonel_instance_and_more.cpython-312.pyc +0 -0
  260. simo/fleet/migrations/__pycache__/0042_auto_20241120_1028.cpython-312.pyc +0 -0
  261. simo/fleet/migrations/__pycache__/0043_auto_20241203_0930.cpython-312.pyc +0 -0
  262. simo/fleet/migrations/__pycache__/0044_auto_20241210_0707.cpython-312.pyc +0 -0
  263. simo/fleet/migrations/__pycache__/0045_alter_colonel_type_customdalidevice.cpython-312.pyc +0 -0
  264. simo/fleet/migrations/__pycache__/0046_delete_customdalidevice.cpython-312.pyc +0 -0
  265. simo/fleet/migrations/__pycache__/0047_customdalidevice.cpython-312.pyc +0 -0
  266. simo/fleet/migrations/__pycache__/0048_remove_customdalidevice_colonel_and_more.cpython-312.pyc +0 -0
  267. simo/fleet/migrations/__pycache__/0049_alter_customdalidevice_interface.cpython-312.pyc +0 -0
  268. simo/fleet/migrations/__pycache__/0050_customdalidevice_uid.cpython-312.pyc +0 -0
  269. simo/fleet/migrations/__pycache__/0051_customdalidevice_components.cpython-312.pyc +0 -0
  270. simo/fleet/migrations/__pycache__/0052_colonelpin_interface.cpython-312.pyc +0 -0
  271. simo/fleet/migrations/__pycache__/0053_auto_20250507_0713.cpython-312.pyc +0 -0
  272. simo/fleet/migrations/__pycache__/__init__.cpython-312.pyc +0 -0
  273. simo/fleet/models.py +96 -34
  274. simo/fleet/serializers.py +2 -2
  275. simo/fleet/utils.py +126 -0
  276. simo/generic/__pycache__/__init__.cpython-312.pyc +0 -0
  277. simo/generic/__pycache__/app_widgets.cpython-312.pyc +0 -0
  278. simo/generic/__pycache__/base_types.cpython-312.pyc +0 -0
  279. simo/generic/__pycache__/controllers.cpython-312.pyc +0 -0
  280. simo/generic/__pycache__/forms.cpython-312.pyc +0 -0
  281. simo/generic/__pycache__/gateways.cpython-312.pyc +0 -0
  282. simo/generic/__pycache__/models.cpython-312.pyc +0 -0
  283. simo/generic/__pycache__/routing.cpython-312.pyc +0 -0
  284. simo/generic/__pycache__/socket_consumers.cpython-312.pyc +0 -0
  285. simo/generic/__pycache__/tasks.cpython-312.pyc +0 -0
  286. simo/generic/migrations/__pycache__/0001_initial.cpython-312.pyc +0 -0
  287. simo/generic/migrations/__pycache__/0002_auto_20241126_0726.cpython-312.pyc +0 -0
  288. simo/generic/migrations/__pycache__/0003_auto_20250409_1404.cpython-312.pyc +0 -0
  289. simo/generic/migrations/__pycache__/__init__.cpython-312.pyc +0 -0
  290. simo/multimedia/__pycache__/__init__.cpython-312.pyc +0 -0
  291. simo/multimedia/__pycache__/admin.cpython-312.pyc +0 -0
  292. simo/multimedia/__pycache__/api.cpython-312.pyc +0 -0
  293. simo/multimedia/__pycache__/app_widgets.cpython-312.pyc +0 -0
  294. simo/multimedia/__pycache__/auto_urls.cpython-312.pyc +0 -0
  295. simo/multimedia/__pycache__/base_types.cpython-312.pyc +0 -0
  296. simo/multimedia/__pycache__/controllers.cpython-312.pyc +0 -0
  297. simo/multimedia/__pycache__/forms.cpython-312.pyc +0 -0
  298. simo/multimedia/__pycache__/models.cpython-312.pyc +0 -0
  299. simo/multimedia/__pycache__/serializers.cpython-312.pyc +0 -0
  300. simo/multimedia/__pycache__/views.cpython-312.pyc +0 -0
  301. simo/multimedia/migrations/__pycache__/0001_initial.cpython-312.pyc +0 -0
  302. simo/multimedia/migrations/__pycache__/0002_sound_length.cpython-312.pyc +0 -0
  303. simo/multimedia/migrations/__pycache__/0003_alter_sound_length.cpython-312.pyc +0 -0
  304. simo/multimedia/migrations/__pycache__/0004_auto_20231023_1055.cpython-312.pyc +0 -0
  305. simo/multimedia/migrations/__pycache__/0005_remove_sound_slug_sound_date_uploaded.cpython-312.pyc +0 -0
  306. simo/multimedia/migrations/__pycache__/0006_remove_sound_length_sound_duration.cpython-312.pyc +0 -0
  307. simo/multimedia/migrations/__pycache__/__init__.cpython-312.pyc +0 -0
  308. simo/notifications/__pycache__/__init__.cpython-312.pyc +0 -0
  309. simo/notifications/__pycache__/admin.cpython-312.pyc +0 -0
  310. simo/notifications/__pycache__/api.cpython-312.pyc +0 -0
  311. simo/notifications/__pycache__/models.cpython-312.pyc +0 -0
  312. simo/notifications/__pycache__/serializers.cpython-312.pyc +0 -0
  313. simo/notifications/__pycache__/utils.cpython-312.pyc +0 -0
  314. simo/notifications/migrations/__pycache__/0001_initial.cpython-312.pyc +0 -0
  315. simo/notifications/migrations/__pycache__/0002_notification_instance.cpython-312.pyc +0 -0
  316. simo/notifications/migrations/__pycache__/0003_alter_notification_instance.cpython-312.pyc +0 -0
  317. simo/notifications/migrations/__pycache__/__init__.cpython-312.pyc +0 -0
  318. simo/notifications/serializers.py +1 -1
  319. simo/users/__pycache__/__init__.cpython-312.pyc +0 -0
  320. simo/users/__pycache__/admin.cpython-312.pyc +0 -0
  321. simo/users/__pycache__/api.cpython-312.pyc +0 -0
  322. simo/users/__pycache__/apps.cpython-312.pyc +0 -0
  323. simo/users/__pycache__/auth_backends.cpython-312.pyc +0 -0
  324. simo/users/__pycache__/auto_urls.cpython-312.pyc +0 -0
  325. simo/users/__pycache__/dynamic_settings.cpython-312.pyc +0 -0
  326. simo/users/__pycache__/managers.cpython-312.pyc +0 -0
  327. simo/users/__pycache__/middleware.cpython-312.pyc +0 -0
  328. simo/users/__pycache__/models.cpython-312.pyc +0 -0
  329. simo/users/__pycache__/permissions.cpython-312.pyc +0 -0
  330. simo/users/__pycache__/serializers.cpython-312.pyc +0 -0
  331. simo/users/__pycache__/sso_urls.cpython-312.pyc +0 -0
  332. simo/users/__pycache__/sso_views.cpython-312.pyc +0 -0
  333. simo/users/__pycache__/tasks.cpython-312.pyc +0 -0
  334. simo/users/__pycache__/utils.cpython-312.pyc +0 -0
  335. simo/users/__pycache__/views.cpython-312.pyc +0 -0
  336. simo/users/migrations/__pycache__/0001_initial.cpython-312.pyc +0 -0
  337. simo/users/migrations/__pycache__/0002_componentpermission.cpython-312.pyc +0 -0
  338. simo/users/migrations/__pycache__/0003_create_roles_and_system_user.cpython-312.pyc +0 -0
  339. simo/users/migrations/__pycache__/0004_user_secret_key.cpython-312.pyc +0 -0
  340. simo/users/migrations/__pycache__/0005_permissionsrole_instance.cpython-312.pyc +0 -0
  341. simo/users/migrations/__pycache__/0006_auto_20231003_0850.cpython-312.pyc +0 -0
  342. simo/users/migrations/__pycache__/0007_auto_20231003_1228.cpython-312.pyc +0 -0
  343. simo/users/migrations/__pycache__/0008_auto_20231003_1229.cpython-312.pyc +0 -0
  344. simo/users/migrations/__pycache__/0009_remove_user_role.cpython-312.pyc +0 -0
  345. simo/users/migrations/__pycache__/0010_auto_20231004_1313.cpython-312.pyc +0 -0
  346. simo/users/migrations/__pycache__/0011_auto_20231004_1313.cpython-312.pyc +0 -0
  347. simo/users/migrations/__pycache__/0012_alter_userinstancerole_unique_together.cpython-312.pyc +0 -0
  348. simo/users/migrations/__pycache__/0013_remove_user_roles.cpython-312.pyc +0 -0
  349. simo/users/migrations/__pycache__/0014_user_roles.cpython-312.pyc +0 -0
  350. simo/users/migrations/__pycache__/0015_remove_user_at_home.cpython-312.pyc +0 -0
  351. simo/users/migrations/__pycache__/0016_auto_20231005_1050.cpython-312.pyc +0 -0
  352. simo/users/migrations/__pycache__/0017_auto_20231221_0735.cpython-312.pyc +0 -0
  353. simo/users/migrations/__pycache__/0018_user_is_god.cpython-312.pyc +0 -0
  354. simo/users/migrations/__pycache__/0019_auto_20231221_1155.cpython-312.pyc +0 -0
  355. simo/users/migrations/__pycache__/0020_rename_is_god_user_is_master.cpython-312.pyc +0 -0
  356. simo/users/migrations/__pycache__/0021_alter_permissionsrole_instance.cpython-312.pyc +0 -0
  357. simo/users/migrations/__pycache__/0022_userdevicereportlog_instance.cpython-312.pyc +0 -0
  358. simo/users/migrations/__pycache__/0023_auto_20240105_0719.cpython-312.pyc +0 -0
  359. simo/users/migrations/__pycache__/0024_fingerprint.cpython-312.pyc +0 -0
  360. simo/users/migrations/__pycache__/0025_rename_name_fingerprint_type_and_more.cpython-312.pyc +0 -0
  361. simo/users/migrations/__pycache__/0026_fingerprint_name.cpython-312.pyc +0 -0
  362. simo/users/migrations/__pycache__/0027_permissionsrole_can_manage_components.cpython-312.pyc +0 -0
  363. simo/users/migrations/__pycache__/0028_auto_20240506_1146.cpython-312.pyc +0 -0
  364. simo/users/migrations/__pycache__/0029_alter_instanceuser_instance.cpython-312.pyc +0 -0
  365. simo/users/migrations/__pycache__/0030_userdevice_users.cpython-312.pyc +0 -0
  366. simo/users/migrations/__pycache__/0031_auto_20240923_1115.cpython-312.pyc +0 -0
  367. simo/users/migrations/__pycache__/0032_remove_userdevice_user_alter_userdevice_users.cpython-312.pyc +0 -0
  368. simo/users/migrations/__pycache__/0033_alter_user_ssh_key.cpython-312.pyc +0 -0
  369. simo/users/migrations/__pycache__/0034_instanceuser_last_seen_location_and_more.cpython-312.pyc +0 -0
  370. simo/users/migrations/__pycache__/0035_instanceuser_last_seen_speed_kmh_and_more.cpython-312.pyc +0 -0
  371. simo/users/migrations/__pycache__/0036_instanceuser_phone_on_charge_user_phone_on_charge.cpython-312.pyc +0 -0
  372. simo/users/migrations/__pycache__/0037_rename_last_seen_location_datetime_instanceuser_last_seen_and_more.cpython-312.pyc +0 -0
  373. simo/users/migrations/__pycache__/0038_userdevicereportlog_at_home_and_more.cpython-312.pyc +0 -0
  374. simo/users/migrations/__pycache__/0039_auto_20241117_1039.cpython-312.pyc +0 -0
  375. simo/users/migrations/__pycache__/0040_userdevicereportlog_location_smoothed_and_more.cpython-312.pyc +0 -0
  376. simo/users/migrations/__pycache__/0041_userdevicereportlog_speed_kmh_received.cpython-312.pyc +0 -0
  377. simo/users/migrations/__pycache__/0042_remove_userdevicereportlog_location_smoothed_and_more.cpython-312.pyc +0 -0
  378. simo/users/migrations/__pycache__/0043_userdevicereportlog_avg_speed_kmh.cpython-312.pyc +0 -0
  379. simo/users/migrations/__pycache__/0044_permissionsrole_is_person.cpython-312.pyc +0 -0
  380. simo/users/migrations/__pycache__/__init__.cpython-312.pyc +0 -0
  381. {simo-2.10.7.dist-info → simo-2.10.9.dist-info}/METADATA +1 -1
  382. {simo-2.10.7.dist-info → simo-2.10.9.dist-info}/RECORD +386 -338
  383. {simo-2.10.7.dist-info → simo-2.10.9.dist-info}/WHEEL +1 -1
  384. {simo-2.10.7.dist-info → simo-2.10.9.dist-info}/entry_points.txt +0 -0
  385. {simo-2.10.7.dist-info → simo-2.10.9.dist-info}/licenses/LICENSE.md +0 -0
  386. {simo-2.10.7.dist-info → simo-2.10.9.dist-info}/top_level.txt +0 -0
Binary file
Binary file
simo/fleet/api.py CHANGED
@@ -1,3 +1,4 @@
1
+ import json
1
2
  from django.db.models import Count
2
3
  from django.utils.translation import gettext_lazy as _
3
4
  from rest_framework import viewsets
@@ -66,12 +67,14 @@ class ColonelsViewSet(InstanceMixin, viewsets.ModelViewSet):
66
67
  @action(detail=True, methods=['post'])
67
68
  def move_to(self, request, pk, *args, **kwargs):
68
69
  colonel = self.get_object()
70
+ data = json.loads(request.body)
71
+
69
72
  target = Colonel.objects.annotate(
70
73
  components_count=Count('components')
71
74
  ).filter(
72
- pk=request.POST.get('target'), instance=self.instance,
75
+ pk=data.get('target'), instance=self.instance,
73
76
  components_count=0, type=colonel.type
74
- )
77
+ ).first()
75
78
  if not target:
76
79
  raise APIValidationError(_('Invalid target.'), code=400)
77
80
  colonel.move_to(target)
simo/fleet/controllers.py CHANGED
@@ -164,6 +164,11 @@ class BME680Sensor(DHTSensor):
164
164
  config_form = BME680SensorConfigForm
165
165
  name = "BME68X Climate Sensor (I2C)"
166
166
 
167
+ def _get_occupied_pins(self):
168
+ return [
169
+ self.component.config['i2c_interface'] + 100,
170
+ ]
171
+
167
172
 
168
173
 
169
174
  class MCP9808TempSensor(FleeDeviceMixin, BaseNumericSensor):
@@ -180,6 +185,11 @@ class MCP9808TempSensor(FleeDeviceMixin, BaseNumericSensor):
180
185
  return 'F'
181
186
  return 'C'
182
187
 
188
+ def _get_occupied_pins(self):
189
+ return [
190
+ self.component.config['i2c_interface'] + 100,
191
+ ]
192
+
183
193
  def _prepare_for_set(self, value):
184
194
  if self.component.zone.instance.units_of_measure == 'imperial':
185
195
  return round((value[0][1] * 9 / 5) + 32, 1)
@@ -198,6 +208,11 @@ class ENS160AirQualitySensor(FleeDeviceMixin, BaseMultiSensor):
198
208
  ["AQI (UBA)", 0, ""]
199
209
  ]
200
210
 
211
+ def _get_occupied_pins(self):
212
+ return [
213
+ self.component.config['i2c_interface'] + 100,
214
+ ]
215
+
201
216
  def get_co2(self):
202
217
  try:
203
218
  for entry in self.component.value:
@@ -855,13 +870,13 @@ class AirQualitySensor(FleeDeviceMixin, BaseMultiSensor):
855
870
 
856
871
  def _receive_from_device(self, value, *args, **kwargs):
857
872
  aqi = 5
858
- if value < 812:
873
+ if value < 2000:
859
874
  aqi = 4
860
- if value < 325:
875
+ if value < 800:
861
876
  aqi = 3
862
- if value < 162:
877
+ if value < 400:
863
878
  aqi = 2
864
- if value < 65:
879
+ if value < 200:
865
880
  aqi = 1
866
881
  value = [
867
882
  ["TVOC", value, "ppb"],
@@ -968,10 +983,14 @@ class RoomZonePresenceSensor(FleeDeviceMixin, BaseBinarySensor):
968
983
  config_form = BaseComponentForm
969
984
  name = "Room zone presence"
970
985
  discovery_msg = _(
971
- "Move vigorously in particular zone of the room, "
972
- "where presence needs to be detected. "
973
- "Your movements are being recorded. "
974
- "Hit Done, once you are done."
986
+ "Your body is a a live space marker now! Your movements are being recorded.<br>"
987
+ "Whenever you hear a beep, new space blob is being included.<br>"
988
+ "Move vigorously in the zone where you want presence to be detected until "
989
+ "you hear no more beeps.<br> "
990
+ "Green color of a sensor indicates, "
991
+ "that the space you are currently in is "
992
+ "already included.<br>"
993
+ "Tip: Dance! :)"
975
994
  )
976
995
 
977
996
  @classmethod
@@ -991,10 +1010,10 @@ class RoomZonePresenceSensor(FleeDeviceMixin, BaseBinarySensor):
991
1010
  command='discover', type=self.uid.split('.')[-1],
992
1011
  ).publish()
993
1012
  else:
1013
+ from .custom_dali_operations import Frame
994
1014
  dali_device = CustomDaliDevice.objects.filter(
995
1015
  id=form_cleaned_data['device'][5:]
996
1016
  ).first()
997
- from .custom_dali_operations import Frame
998
1017
  frame = Frame(40, bytes(bytearray(5)))
999
1018
  frame[8:11] = 15 # command to custom dali device
1000
1019
  frame[12:15] = 0 # action to perform: start room zone discovery
@@ -1008,6 +1027,11 @@ class RoomZonePresenceSensor(FleeDeviceMixin, BaseBinarySensor):
1008
1027
  form = cls.add_form(
1009
1028
  controller_uid=cls.uid, data=started_with
1010
1029
  )
1030
+ from simo.core.middleware import introduce_instance
1031
+ introduce_instance(form.data['zone'].instance)
1032
+ form = cls.add_form(
1033
+ controller_uid=cls.uid, data=started_with
1034
+ )
1011
1035
  form.is_valid()
1012
1036
  if form.cleaned_data['device'].startswith('wifi'):
1013
1037
  form.instance.alive = False
@@ -1020,15 +1044,17 @@ class RoomZonePresenceSensor(FleeDeviceMixin, BaseBinarySensor):
1020
1044
  id=new_component.config['colonel']
1021
1045
  ), command='finalize',
1022
1046
  data={
1047
+ 'permanent_id': new_component.id,
1023
1048
  'comp_config': {
1024
- 'type': str(cls).split('.')[-1],
1049
+ 'type': cls.uid.split('.')[-1],
1025
1050
  'family': new_component.controller.family,
1026
- 'config': json.loads(json.dumps(new_component.config))
1051
+ 'config': json.loads(json.dumps(new_component.config)),
1027
1052
  }
1028
1053
  }
1029
1054
  ).publish()
1030
1055
  else:
1031
1056
  from simo.core.models import Component
1057
+ from .custom_dali_operations import Frame
1032
1058
  dali_device = CustomDaliDevice.objects.filter(
1033
1059
  id=form.cleaned_data['device'][5:]
1034
1060
  ).first()
@@ -1049,12 +1075,70 @@ class RoomZonePresenceSensor(FleeDeviceMixin, BaseBinarySensor):
1049
1075
  )
1050
1076
  form.instance.config['slot'] = free_slots.pop()
1051
1077
  new_component = form.save()
1052
- from .custom_dali_operations import Frame
1053
1078
  frame = Frame(40, bytes(bytearray(5)))
1054
1079
  frame[8:11] = 15 # command to custom dali device
1055
1080
  frame[12:15] = 1 # action to perform: stop room zone discovery
1056
1081
  frame[16:18] = new_component.config['slot']
1057
1082
  dali_device.transmit(frame)
1058
1083
 
1059
- print("NEW COMPONENT: ", new_component)
1060
- return new_component
1084
+ return new_component
1085
+
1086
+ def repaint(self):
1087
+ """Repaint included 3D space"""
1088
+ if self.component.config['device'].startswith('wifi'):
1089
+ GatewayObjectCommand(
1090
+ self.component.gateway, Colonel(
1091
+ id=self.component.config['colonel']
1092
+ ), command='call', method='repaint', id=self.component.id
1093
+ ).publish()
1094
+ else:
1095
+ dali_device = CustomDaliDevice.objects.filter(
1096
+ id=self.component.config['device'][5:]
1097
+ ).first()
1098
+ from .custom_dali_operations import Frame
1099
+ frame = Frame(40, bytes(bytearray(5)))
1100
+ frame[8:11] = 15 # command to custom dali device
1101
+ frame[12:15] = 3 # action to perform: repaint
1102
+ frame[16:18] = self.component.config['slot']
1103
+ dali_device.transmit(frame)
1104
+
1105
+ def finish_repaint(self):
1106
+ """Finish repainting of 3D space"""
1107
+ if self.component.config['device'].startswith('wifi'):
1108
+ GatewayObjectCommand(
1109
+ self.component.gateway, Colonel(
1110
+ id=self.component.config['colonel']
1111
+ ), command='call', method='finish_repaint',
1112
+ id=self.component.id
1113
+ ).publish()
1114
+ else:
1115
+ dali_device = CustomDaliDevice.objects.filter(
1116
+ id=self.component.config['device'][5:]
1117
+ ).first()
1118
+ from .custom_dali_operations import Frame
1119
+ frame = Frame(40, bytes(bytearray(5)))
1120
+ frame[8:11] = 15 # command to custom dali device
1121
+ frame[12:15] = 4 # action to perform: finish repaint
1122
+ frame[16:18] = self.component.config['slot']
1123
+ dali_device.transmit(frame)
1124
+
1125
+ def cancel_repaint(self):
1126
+ """Finish repainting of 3D space"""
1127
+ if self.component.config['device'].startswith('wifi'):
1128
+ GatewayObjectCommand(
1129
+ self.component.gateway, Colonel(
1130
+ id=self.component.config['colonel']
1131
+ ), command='call', method='cancel_repaint',
1132
+ id=self.component.id
1133
+ ).publish()
1134
+ else:
1135
+ dali_device = CustomDaliDevice.objects.filter(
1136
+ id=self.component.config['device'][5:]
1137
+ ).first()
1138
+ from .custom_dali_operations import Frame
1139
+ frame = Frame(40, bytes(bytearray(5)))
1140
+ frame[8:11] = 15 # command to custom dali device
1141
+ frame[12:15] = 5 # action to perform: cancel repaint
1142
+ frame[16:18] = self.component.config['slot']
1143
+ dali_device.transmit(frame)
1144
+
simo/fleet/forms.py CHANGED
@@ -460,65 +460,86 @@ class ColonelDHTSensorConfigForm(ColonelComponentForm):
460
460
  return super().save(commit=commit)
461
461
 
462
462
 
463
- class BME680SensorConfigForm(ColonelComponentForm):
464
- interface = Select2ModelChoiceField(
465
- queryset=Interface.objects.filter(type='i2c'),
466
- url='autocomplete-interfaces',
463
+ class I2CDevice(ColonelComponentForm):
464
+ interface_port = Select2ModelChoiceField(
465
+ label="Interface",
466
+ queryset=ColonelPin.objects.filter(interface__isnull=False),
467
+ url='autocomplete-colonel-pins',
467
468
  forward=[
468
469
  forward.Self(),
469
470
  forward.Field('colonel'),
470
- forward.Const(
471
- {'type': 'i2c'}, 'filters'
472
- )
473
- ]
474
- )
475
- i2c_address = forms.TypedChoiceField(
476
- coerce=int, initial=118,
477
- choices=((118, "0x76"), (119, "0x77")),
478
- )
479
- read_frequency_s = forms.IntegerField(
480
- initial=60, min_value=1, max_value=60*60*24,
481
- help_text='read and report climate value every s. '
482
- 'Can not be less than 1s.'
483
-
471
+ forward.Const({'interface__isnull': False}, 'filters'),
472
+ ],
484
473
  )
485
474
 
486
475
  def clean(self):
487
- if not self.cleaned_data.get('colonel'):
488
- return self.cleaned_data
489
- if self.cleaned_data['interface'].colonel != self.cleaned_data['colonel']:
476
+ cleaned_data = super().clean()
477
+ colonel = cleaned_data.get('colonel')
478
+ port_choice = cleaned_data.get('interface_port')
479
+ if not colonel or not port_choice:
480
+ return cleaned_data
481
+
482
+ # Create or fetch the I²C interface
483
+ interface, created = Interface.objects.get_or_create(
484
+ colonel=colonel,
485
+ no=port_choice.interface,
486
+ defaults={'type': 'i2c'},
487
+ )
488
+
489
+ # If it already existed as something else, ensure it's free
490
+ if interface.type != 'i2c':
491
+ occupied = interface.addresses.filter(occupied_by__isnull=False).first()
492
+ if occupied:
493
+ self.add_error(
494
+ 'interface_port',
495
+ f"Port already occupied by {occupied.occupied_by}"
496
+ )
497
+ return cleaned_data
498
+ interface.type = 'i2c'
499
+ interface.save()
500
+
501
+ # Check for address collisions on that interface
502
+ other = Component.objects.filter(
503
+ config__colonel=colonel.id,
504
+ config__interface=interface.id,
505
+ config__i2c_address=cleaned_data['i2c_address'],
506
+ ).exclude(id=self.instance.id).first()
507
+ if other:
490
508
  self.add_error(
491
- 'interface',
492
- f"This interface is on {self.cleaned_data['interface'].colonel}, "
493
- f"however we need an interface from {self.cleaned_data['colonel']}."
509
+ 'i2c_address',
510
+ f"Address already occupied by {other}"
494
511
  )
495
- other_comp = Component.objects.filter(
496
- config__colonel=self.cleaned_data['colonel'].id,
497
- config__interface=self.cleaned_data['interface'].id,
498
- config__i2c_address=self.cleaned_data['i2c_address']
499
- ).exclude(id=self.instance.id).first()
500
- if other_comp:
501
- self.add_error('i2c_address', f'Already occupied by {other_comp}!')
502
- return self.cleaned_data
512
+
513
+ # stash for save()
514
+ cleaned_data['i2c_interface'] = interface.id
515
+ return cleaned_data
503
516
 
504
517
  def save(self, commit=True):
505
- if 'interface' in self.cleaned_data:
506
- self.instance.config['i2c_interface'] = self.cleaned_data['interface'].no
518
+ self.instance.config['i2c_interface'] = self.cleaned_data['i2c_interface']
507
519
  return super().save(commit=commit)
508
520
 
509
521
 
510
- class MCP9808SensorConfigForm(ColonelComponentForm):
511
- interface = Select2ModelChoiceField(
512
- queryset=Interface.objects.filter(type='i2c'),
513
- url='autocomplete-interfaces',
514
- forward=[
515
- forward.Self(),
516
- forward.Field('colonel'),
517
- forward.Const(
518
- {'type': 'i2c'}, 'filters'
519
- )
520
- ]
522
+ class BME680SensorConfigForm(I2CDevice):
523
+ i2c_address = forms.TypedChoiceField(
524
+ coerce=int,
525
+ initial=119, # match “0x77 – default”
526
+ choices=(
527
+ (119, "0x77 – default"),
528
+ (118, "0x76 – soldered"),
529
+ ),
521
530
  )
531
+ read_frequency_s = forms.IntegerField(
532
+ initial=60,
533
+ min_value=1,
534
+ max_value=60 * 60 * 24,
535
+ help_text=(
536
+ "Read and report climate value every second. "
537
+ "Cannot be less than 1 second."
538
+ ),
539
+ )
540
+
541
+
542
+ class MCP9808SensorConfigForm(I2CDevice):
522
543
  i2c_address = forms.TypedChoiceField(
523
544
  coerce=int, initial=24,
524
545
  choices=(
@@ -535,43 +556,8 @@ class MCP9808SensorConfigForm(ColonelComponentForm):
535
556
 
536
557
  )
537
558
 
538
- def clean(self):
539
- if not self.cleaned_data.get('colonel'):
540
- return self.cleaned_data
541
- if self.cleaned_data['interface'].colonel != self.cleaned_data['colonel']:
542
- self.add_error(
543
- 'interface',
544
- f"This interface is on {self.cleaned_data['interface'].colonel}, "
545
- f"however we need an interface from {self.cleaned_data['colonel']}."
546
- )
547
-
548
- other_comp = Component.objects.filter(
549
- config__colonel=self.cleaned_data['colonel'].id,
550
- config__interface=self.cleaned_data['interface'].id,
551
- config__i2c_address=self.cleaned_data['i2c_address']
552
- ).exclude(id=self.instance.id).first()
553
- if other_comp:
554
- self.add_error('i2c_address', f'Already occupied by {other_comp}!')
555
- return self.cleaned_data
556
-
557
- def save(self, commit=True):
558
- if 'interface' in self.cleaned_data:
559
- self.instance.config['i2c_interface'] = self.cleaned_data['interface'].no
560
- return super().save(commit=commit)
561
-
562
559
 
563
- class ENS160SensorConfigForm(ColonelComponentForm):
564
- interface = Select2ModelChoiceField(
565
- queryset=Interface.objects.filter(type='i2c'),
566
- url='autocomplete-interfaces',
567
- forward=[
568
- forward.Self(),
569
- forward.Field('colonel'),
570
- forward.Const(
571
- {'type': 'i2c'}, 'filters'
572
- )
573
- ]
574
- )
560
+ class ENS160SensorConfigForm(I2CDevice):
575
561
  i2c_address = forms.TypedChoiceField(
576
562
  coerce=int, initial=83,
577
563
  choices=((82, "0x52"), (83, "0x53")),
@@ -583,30 +569,6 @@ class ENS160SensorConfigForm(ColonelComponentForm):
583
569
 
584
570
  )
585
571
 
586
- def clean(self):
587
- if not self.cleaned_data.get('colonel'):
588
- return self.cleaned_data
589
- if self.cleaned_data['interface'].colonel != self.cleaned_data['colonel']:
590
- self.add_error(
591
- 'interface',
592
- f"This interface is on {self.cleaned_data['interface'].colonel}, "
593
- f"however we need an interface from {self.cleaned_data['colonel']}."
594
- )
595
- other_comp = Component.objects.filter(
596
- config__colonel=self.cleaned_data['colonel'].id,
597
- config__interface=self.cleaned_data['interface'].id,
598
- config__i2c_address=self.cleaned_data['i2c_address']
599
- ).exclude(id=self.instance.id).first()
600
- if other_comp:
601
- self.add_error('i2c_address', f'Already occupied by {other_comp}!')
602
- return self.cleaned_data
603
-
604
- def save(self, commit=True):
605
- if 'interface' in self.cleaned_data:
606
- self.instance.config['i2c_interface'] = \
607
- self.cleaned_data['interface'].no
608
- return super().save(commit=commit)
609
-
610
572
 
611
573
  class ColonelTouchSensorConfigForm(ColonelComponentForm):
612
574
  pin = Select2ModelChoiceField(
@@ -1831,8 +1793,6 @@ class CustomDaliDeviceForm(BaseComponentForm):
1831
1793
  for colonel in Colonel.objects.filter(
1832
1794
  type='room-sensor', instance=instance
1833
1795
  ):
1834
- if not colonel.is_connected:
1835
- continue
1836
1796
  choices.append((f"wifi-{colonel.id}", colonel.name))
1837
1797
  for device in CustomDaliDevice.objects.filter(
1838
1798
  instance=instance,
simo/fleet/gateways.py CHANGED
@@ -11,6 +11,7 @@ from simo.core.utils.serialization import deserialize_form_data
11
11
 
12
12
 
13
13
 
14
+
14
15
  class FleetGatewayHandler(BaseObjectCommandsGatewayHandler):
15
16
  name = "SIMO.io Fleet"
16
17
  config_form = BaseGatewayForm
@@ -101,15 +102,19 @@ class FleetGatewayHandler(BaseObjectCommandsGatewayHandler):
101
102
  gw.finish_discovery()
102
103
  continue
103
104
 
104
- colonel = Colonel.objects.get(
105
- id=gw.discovery['init_data']['colonel']['val'][0]['pk']
106
- )
107
105
  if gw.discovery['controller_uid'] == 'simo.fleet.controllers.TTLock':
106
+ colonel = Colonel.objects.get(
107
+ id=gw.discovery['init_data']['colonel']['val'][0]['pk']
108
+ )
108
109
  GatewayObjectCommand(
109
110
  gw, colonel, command='discover',
110
111
  type=gw.discovery['controller_uid']
111
112
  ).publish()
112
- elif gw.discovery['controller_uid'] == 'simo.fleet.controllers.DALIDevice':
113
+ elif gw.discovery['controller_uid'] == \
114
+ 'simo.fleet.controllers.DALIDevice':
115
+ colonel = Colonel.objects.get(
116
+ id=gw.discovery['init_data']['colonel']['val'][0]['pk']
117
+ )
113
118
  form_cleaned_data = deserialize_form_data(gw.discovery['init_data'])
114
119
  GatewayObjectCommand(
115
120
  gw, colonel,
@@ -117,6 +122,31 @@ class FleetGatewayHandler(BaseObjectCommandsGatewayHandler):
117
122
  type=gw.discovery['controller_uid'],
118
123
  i=form_cleaned_data['interface'].no
119
124
  ).publish()
125
+ elif gw.discovery['controller_uid'] == \
126
+ 'simo.fleet.controllers.RoomZonePresenceSensor':
127
+ form_cleaned_data = deserialize_form_data(
128
+ gw.discovery['init_data']
129
+ )
130
+ if form_cleaned_data['device'].startswith('wifi'):
131
+ colonel = Colonel.objects.filter(
132
+ id=form_cleaned_data['device'][5:]
133
+ ).first()
134
+ GatewayObjectCommand(
135
+ gw, colonel,
136
+ command='discover', type=self.uid.split('.')[-1],
137
+ ).publish()
138
+ else:
139
+ from .models import CustomDaliDevice
140
+ from .custom_dali_operations import Frame
141
+ dali_device = CustomDaliDevice.objects.filter(
142
+ id=form_cleaned_data['device'][5:]
143
+ ).first()
144
+ frame = Frame(40, bytes(bytearray(5)))
145
+ frame[8:11] = 15 # command to custom dali device
146
+ frame[12:15] = 0 # action to perform: start room zone discovery
147
+ dali_device.transmit(frame)
148
+
149
+
120
150
 
121
151
  def watch_buttons(self, component):
122
152
  for i, ctrl in enumerate(component.config.get('controls', [])):
@@ -0,0 +1,18 @@
1
+ # Generated by Django 4.2.10 on 2025-05-07 07:13
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('fleet', '0051_customdalidevice_components'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AddField(
14
+ model_name='colonelpin',
15
+ name='interface',
16
+ field=models.PositiveIntegerField(blank=True, null=True),
17
+ ),
18
+ ]
@@ -0,0 +1,24 @@
1
+ # Generated by Django 4.2.10 on 2025-05-07 07:13
2
+
3
+ from django.db import migrations
4
+
5
+
6
+ def forwards_func(apps, schema_editor):
7
+ ColonelPin = apps.get_model("fleet", "ColonelPin")
8
+ ColonelPin.objects.filter(no=101).update(interface=1)
9
+ ColonelPin.objects.filter(no=102).update(interface=2)
10
+
11
+
12
+ def reverse_func(apps, schema_editor):
13
+ pass
14
+
15
+
16
+ class Migration(migrations.Migration):
17
+
18
+ dependencies = [
19
+ ('fleet', '0052_colonelpin_interface'),
20
+ ]
21
+
22
+ operations = [
23
+ migrations.RunPython(forwards_func, reverse_func, elidable=True),
24
+ ]