simo 2.10.7__py3-none-any.whl → 2.10.11__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 (388) 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 +136 -38
  215. simo/fleet/forms.py +177 -207
  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/0054_auto_20250507_1256.py +36 -0
  220. simo/fleet/migrations/__pycache__/0001_initial.cpython-312.pyc +0 -0
  221. simo/fleet/migrations/__pycache__/0002_auto_20220422_0743.cpython-312.pyc +0 -0
  222. simo/fleet/migrations/__pycache__/0003_auto_20220422_0752.cpython-312.pyc +0 -0
  223. simo/fleet/migrations/__pycache__/0004_auto_20220422_0818.cpython-312.pyc +0 -0
  224. simo/fleet/migrations/__pycache__/0005_auto_20220428_0900.cpython-312.pyc +0 -0
  225. simo/fleet/migrations/__pycache__/0006_rename_mac_colonel_uid.cpython-312.pyc +0 -0
  226. simo/fleet/migrations/__pycache__/0007_colonel_socket_connected.cpython-312.pyc +0 -0
  227. simo/fleet/migrations/__pycache__/0008_i2cinterface.cpython-312.pyc +0 -0
  228. simo/fleet/migrations/__pycache__/0009_i2cinterface_name.cpython-312.pyc +0 -0
  229. simo/fleet/migrations/__pycache__/0010_auto_20220602_0746.cpython-312.pyc +0 -0
  230. simo/fleet/migrations/__pycache__/0011_i2cinterface_freq.cpython-312.pyc +0 -0
  231. simo/fleet/migrations/__pycache__/0012_colonel_logs_stream.cpython-312.pyc +0 -0
  232. simo/fleet/migrations/__pycache__/0013_alter_colonel_last_seen.cpython-312.pyc +0 -0
  233. simo/fleet/migrations/__pycache__/0014_auto_20220614_0659.cpython-312.pyc +0 -0
  234. simo/fleet/migrations/__pycache__/0015_auto_20220614_0754.cpython-312.pyc +0 -0
  235. simo/fleet/migrations/__pycache__/0016_auto_20220704_0840.cpython-312.pyc +0 -0
  236. simo/fleet/migrations/__pycache__/0017_alter_colonel_secret.cpython-312.pyc +0 -0
  237. simo/fleet/migrations/__pycache__/0018_colonel_instance.cpython-312.pyc +0 -0
  238. simo/fleet/migrations/__pycache__/0019_auto_20231006_0749.cpython-312.pyc +0 -0
  239. simo/fleet/migrations/__pycache__/0020_instanceoptions.cpython-312.pyc +0 -0
  240. simo/fleet/migrations/__pycache__/0021_auto_20231006_0819.cpython-312.pyc +0 -0
  241. simo/fleet/migrations/__pycache__/0022_remove_colonel_secret.cpython-312.pyc +0 -0
  242. simo/fleet/migrations/__pycache__/0023_colonel_is_authorized.cpython-312.pyc +0 -0
  243. simo/fleet/migrations/__pycache__/0024_colonel_pwm_frequency.cpython-312.pyc +0 -0
  244. simo/fleet/migrations/__pycache__/0025_auto_20240130_1334.cpython-312.pyc +0 -0
  245. simo/fleet/migrations/__pycache__/0026_rename_i2cinterface_scl_pin_and_more.cpython-312.pyc +0 -0
  246. simo/fleet/migrations/__pycache__/0027_auto_20240306_0802.cpython-312.pyc +0 -0
  247. simo/fleet/migrations/__pycache__/0028_remove_i2cinterface_scl_pin_no_and_more.cpython-312.pyc +0 -0
  248. simo/fleet/migrations/__pycache__/0029_alter_i2cinterface_scl_pin_and_more.cpython-312.pyc +0 -0
  249. simo/fleet/migrations/__pycache__/0030_colonelpin_label_alter_colonel_type.cpython-312.pyc +0 -0
  250. simo/fleet/migrations/__pycache__/0031_alter_colonel_type.cpython-312.pyc +0 -0
  251. simo/fleet/migrations/__pycache__/0032_auto_20240415_0736.cpython-312.pyc +0 -0
  252. simo/fleet/migrations/__pycache__/0033_auto_20240415_0736.cpython-312.pyc +0 -0
  253. simo/fleet/migrations/__pycache__/0034_auto_20240418_0735.cpython-312.pyc +0 -0
  254. simo/fleet/migrations/__pycache__/0035_auto_20240514_0855.cpython-312.pyc +0 -0
  255. simo/fleet/migrations/__pycache__/0036_auto_20240605_0702.cpython-312.pyc +0 -0
  256. simo/fleet/migrations/__pycache__/0037_alter_colonelpin_options_alter_colonelpin_no_and_more.cpython-312.pyc +0 -0
  257. simo/fleet/migrations/__pycache__/0038_alter_colonel_type.cpython-312.pyc +0 -0
  258. simo/fleet/migrations/__pycache__/0039_auto_20241016_1047.cpython-312.pyc +0 -0
  259. simo/fleet/migrations/__pycache__/0040_alter_colonel_pwm_frequency.cpython-312.pyc +0 -0
  260. simo/fleet/migrations/__pycache__/0041_alter_colonel_instance_and_more.cpython-312.pyc +0 -0
  261. simo/fleet/migrations/__pycache__/0042_auto_20241120_1028.cpython-312.pyc +0 -0
  262. simo/fleet/migrations/__pycache__/0043_auto_20241203_0930.cpython-312.pyc +0 -0
  263. simo/fleet/migrations/__pycache__/0044_auto_20241210_0707.cpython-312.pyc +0 -0
  264. simo/fleet/migrations/__pycache__/0045_alter_colonel_type_customdalidevice.cpython-312.pyc +0 -0
  265. simo/fleet/migrations/__pycache__/0046_delete_customdalidevice.cpython-312.pyc +0 -0
  266. simo/fleet/migrations/__pycache__/0047_customdalidevice.cpython-312.pyc +0 -0
  267. simo/fleet/migrations/__pycache__/0048_remove_customdalidevice_colonel_and_more.cpython-312.pyc +0 -0
  268. simo/fleet/migrations/__pycache__/0049_alter_customdalidevice_interface.cpython-312.pyc +0 -0
  269. simo/fleet/migrations/__pycache__/0050_customdalidevice_uid.cpython-312.pyc +0 -0
  270. simo/fleet/migrations/__pycache__/0051_customdalidevice_components.cpython-312.pyc +0 -0
  271. simo/fleet/migrations/__pycache__/0052_colonelpin_interface.cpython-312.pyc +0 -0
  272. simo/fleet/migrations/__pycache__/0053_auto_20250507_0713.cpython-312.pyc +0 -0
  273. simo/fleet/migrations/__pycache__/0054_auto_20250507_1256.cpython-312.pyc +0 -0
  274. simo/fleet/migrations/__pycache__/__init__.cpython-312.pyc +0 -0
  275. simo/fleet/models.py +96 -34
  276. simo/fleet/serializers.py +2 -2
  277. simo/fleet/utils.py +126 -0
  278. simo/generic/__pycache__/__init__.cpython-312.pyc +0 -0
  279. simo/generic/__pycache__/app_widgets.cpython-312.pyc +0 -0
  280. simo/generic/__pycache__/base_types.cpython-312.pyc +0 -0
  281. simo/generic/__pycache__/controllers.cpython-312.pyc +0 -0
  282. simo/generic/__pycache__/forms.cpython-312.pyc +0 -0
  283. simo/generic/__pycache__/gateways.cpython-312.pyc +0 -0
  284. simo/generic/__pycache__/models.cpython-312.pyc +0 -0
  285. simo/generic/__pycache__/routing.cpython-312.pyc +0 -0
  286. simo/generic/__pycache__/socket_consumers.cpython-312.pyc +0 -0
  287. simo/generic/__pycache__/tasks.cpython-312.pyc +0 -0
  288. simo/generic/migrations/__pycache__/0001_initial.cpython-312.pyc +0 -0
  289. simo/generic/migrations/__pycache__/0002_auto_20241126_0726.cpython-312.pyc +0 -0
  290. simo/generic/migrations/__pycache__/0003_auto_20250409_1404.cpython-312.pyc +0 -0
  291. simo/generic/migrations/__pycache__/__init__.cpython-312.pyc +0 -0
  292. simo/multimedia/__pycache__/__init__.cpython-312.pyc +0 -0
  293. simo/multimedia/__pycache__/admin.cpython-312.pyc +0 -0
  294. simo/multimedia/__pycache__/api.cpython-312.pyc +0 -0
  295. simo/multimedia/__pycache__/app_widgets.cpython-312.pyc +0 -0
  296. simo/multimedia/__pycache__/auto_urls.cpython-312.pyc +0 -0
  297. simo/multimedia/__pycache__/base_types.cpython-312.pyc +0 -0
  298. simo/multimedia/__pycache__/controllers.cpython-312.pyc +0 -0
  299. simo/multimedia/__pycache__/forms.cpython-312.pyc +0 -0
  300. simo/multimedia/__pycache__/models.cpython-312.pyc +0 -0
  301. simo/multimedia/__pycache__/serializers.cpython-312.pyc +0 -0
  302. simo/multimedia/__pycache__/views.cpython-312.pyc +0 -0
  303. simo/multimedia/migrations/__pycache__/0001_initial.cpython-312.pyc +0 -0
  304. simo/multimedia/migrations/__pycache__/0002_sound_length.cpython-312.pyc +0 -0
  305. simo/multimedia/migrations/__pycache__/0003_alter_sound_length.cpython-312.pyc +0 -0
  306. simo/multimedia/migrations/__pycache__/0004_auto_20231023_1055.cpython-312.pyc +0 -0
  307. simo/multimedia/migrations/__pycache__/0005_remove_sound_slug_sound_date_uploaded.cpython-312.pyc +0 -0
  308. simo/multimedia/migrations/__pycache__/0006_remove_sound_length_sound_duration.cpython-312.pyc +0 -0
  309. simo/multimedia/migrations/__pycache__/__init__.cpython-312.pyc +0 -0
  310. simo/notifications/__pycache__/__init__.cpython-312.pyc +0 -0
  311. simo/notifications/__pycache__/admin.cpython-312.pyc +0 -0
  312. simo/notifications/__pycache__/api.cpython-312.pyc +0 -0
  313. simo/notifications/__pycache__/models.cpython-312.pyc +0 -0
  314. simo/notifications/__pycache__/serializers.cpython-312.pyc +0 -0
  315. simo/notifications/__pycache__/utils.cpython-312.pyc +0 -0
  316. simo/notifications/migrations/__pycache__/0001_initial.cpython-312.pyc +0 -0
  317. simo/notifications/migrations/__pycache__/0002_notification_instance.cpython-312.pyc +0 -0
  318. simo/notifications/migrations/__pycache__/0003_alter_notification_instance.cpython-312.pyc +0 -0
  319. simo/notifications/migrations/__pycache__/__init__.cpython-312.pyc +0 -0
  320. simo/notifications/serializers.py +1 -1
  321. simo/users/__pycache__/__init__.cpython-312.pyc +0 -0
  322. simo/users/__pycache__/admin.cpython-312.pyc +0 -0
  323. simo/users/__pycache__/api.cpython-312.pyc +0 -0
  324. simo/users/__pycache__/apps.cpython-312.pyc +0 -0
  325. simo/users/__pycache__/auth_backends.cpython-312.pyc +0 -0
  326. simo/users/__pycache__/auto_urls.cpython-312.pyc +0 -0
  327. simo/users/__pycache__/dynamic_settings.cpython-312.pyc +0 -0
  328. simo/users/__pycache__/managers.cpython-312.pyc +0 -0
  329. simo/users/__pycache__/middleware.cpython-312.pyc +0 -0
  330. simo/users/__pycache__/models.cpython-312.pyc +0 -0
  331. simo/users/__pycache__/permissions.cpython-312.pyc +0 -0
  332. simo/users/__pycache__/serializers.cpython-312.pyc +0 -0
  333. simo/users/__pycache__/sso_urls.cpython-312.pyc +0 -0
  334. simo/users/__pycache__/sso_views.cpython-312.pyc +0 -0
  335. simo/users/__pycache__/tasks.cpython-312.pyc +0 -0
  336. simo/users/__pycache__/utils.cpython-312.pyc +0 -0
  337. simo/users/__pycache__/views.cpython-312.pyc +0 -0
  338. simo/users/migrations/__pycache__/0001_initial.cpython-312.pyc +0 -0
  339. simo/users/migrations/__pycache__/0002_componentpermission.cpython-312.pyc +0 -0
  340. simo/users/migrations/__pycache__/0003_create_roles_and_system_user.cpython-312.pyc +0 -0
  341. simo/users/migrations/__pycache__/0004_user_secret_key.cpython-312.pyc +0 -0
  342. simo/users/migrations/__pycache__/0005_permissionsrole_instance.cpython-312.pyc +0 -0
  343. simo/users/migrations/__pycache__/0006_auto_20231003_0850.cpython-312.pyc +0 -0
  344. simo/users/migrations/__pycache__/0007_auto_20231003_1228.cpython-312.pyc +0 -0
  345. simo/users/migrations/__pycache__/0008_auto_20231003_1229.cpython-312.pyc +0 -0
  346. simo/users/migrations/__pycache__/0009_remove_user_role.cpython-312.pyc +0 -0
  347. simo/users/migrations/__pycache__/0010_auto_20231004_1313.cpython-312.pyc +0 -0
  348. simo/users/migrations/__pycache__/0011_auto_20231004_1313.cpython-312.pyc +0 -0
  349. simo/users/migrations/__pycache__/0012_alter_userinstancerole_unique_together.cpython-312.pyc +0 -0
  350. simo/users/migrations/__pycache__/0013_remove_user_roles.cpython-312.pyc +0 -0
  351. simo/users/migrations/__pycache__/0014_user_roles.cpython-312.pyc +0 -0
  352. simo/users/migrations/__pycache__/0015_remove_user_at_home.cpython-312.pyc +0 -0
  353. simo/users/migrations/__pycache__/0016_auto_20231005_1050.cpython-312.pyc +0 -0
  354. simo/users/migrations/__pycache__/0017_auto_20231221_0735.cpython-312.pyc +0 -0
  355. simo/users/migrations/__pycache__/0018_user_is_god.cpython-312.pyc +0 -0
  356. simo/users/migrations/__pycache__/0019_auto_20231221_1155.cpython-312.pyc +0 -0
  357. simo/users/migrations/__pycache__/0020_rename_is_god_user_is_master.cpython-312.pyc +0 -0
  358. simo/users/migrations/__pycache__/0021_alter_permissionsrole_instance.cpython-312.pyc +0 -0
  359. simo/users/migrations/__pycache__/0022_userdevicereportlog_instance.cpython-312.pyc +0 -0
  360. simo/users/migrations/__pycache__/0023_auto_20240105_0719.cpython-312.pyc +0 -0
  361. simo/users/migrations/__pycache__/0024_fingerprint.cpython-312.pyc +0 -0
  362. simo/users/migrations/__pycache__/0025_rename_name_fingerprint_type_and_more.cpython-312.pyc +0 -0
  363. simo/users/migrations/__pycache__/0026_fingerprint_name.cpython-312.pyc +0 -0
  364. simo/users/migrations/__pycache__/0027_permissionsrole_can_manage_components.cpython-312.pyc +0 -0
  365. simo/users/migrations/__pycache__/0028_auto_20240506_1146.cpython-312.pyc +0 -0
  366. simo/users/migrations/__pycache__/0029_alter_instanceuser_instance.cpython-312.pyc +0 -0
  367. simo/users/migrations/__pycache__/0030_userdevice_users.cpython-312.pyc +0 -0
  368. simo/users/migrations/__pycache__/0031_auto_20240923_1115.cpython-312.pyc +0 -0
  369. simo/users/migrations/__pycache__/0032_remove_userdevice_user_alter_userdevice_users.cpython-312.pyc +0 -0
  370. simo/users/migrations/__pycache__/0033_alter_user_ssh_key.cpython-312.pyc +0 -0
  371. simo/users/migrations/__pycache__/0034_instanceuser_last_seen_location_and_more.cpython-312.pyc +0 -0
  372. simo/users/migrations/__pycache__/0035_instanceuser_last_seen_speed_kmh_and_more.cpython-312.pyc +0 -0
  373. simo/users/migrations/__pycache__/0036_instanceuser_phone_on_charge_user_phone_on_charge.cpython-312.pyc +0 -0
  374. simo/users/migrations/__pycache__/0037_rename_last_seen_location_datetime_instanceuser_last_seen_and_more.cpython-312.pyc +0 -0
  375. simo/users/migrations/__pycache__/0038_userdevicereportlog_at_home_and_more.cpython-312.pyc +0 -0
  376. simo/users/migrations/__pycache__/0039_auto_20241117_1039.cpython-312.pyc +0 -0
  377. simo/users/migrations/__pycache__/0040_userdevicereportlog_location_smoothed_and_more.cpython-312.pyc +0 -0
  378. simo/users/migrations/__pycache__/0041_userdevicereportlog_speed_kmh_received.cpython-312.pyc +0 -0
  379. simo/users/migrations/__pycache__/0042_remove_userdevicereportlog_location_smoothed_and_more.cpython-312.pyc +0 -0
  380. simo/users/migrations/__pycache__/0043_userdevicereportlog_avg_speed_kmh.cpython-312.pyc +0 -0
  381. simo/users/migrations/__pycache__/0044_permissionsrole_is_person.cpython-312.pyc +0 -0
  382. simo/users/migrations/__pycache__/__init__.cpython-312.pyc +0 -0
  383. {simo-2.10.7.dist-info → simo-2.10.11.dist-info}/METADATA +1 -1
  384. {simo-2.10.7.dist-info → simo-2.10.11.dist-info}/RECORD +388 -338
  385. {simo-2.10.7.dist-info → simo-2.10.11.dist-info}/WHEEL +1 -1
  386. {simo-2.10.7.dist-info → simo-2.10.11.dist-info}/entry_points.txt +0 -0
  387. {simo-2.10.7.dist-info → simo-2.10.11.dist-info}/licenses/LICENSE.md +0 -0
  388. {simo-2.10.7.dist-info → simo-2.10.11.dist-info}/top_level.txt +0 -0
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(
@@ -727,7 +689,75 @@ class ColonelSwitchConfigForm(ColonelComponentForm):
727
689
  return obj
728
690
 
729
691
 
730
- class ColonelPWMOutputConfigForm(ColonelComponentForm):
692
+ class PWMOutputBaseConfig(ColonelComponentForm):
693
+
694
+ def __init__(self, *args, **kwargs):
695
+ super().__init__(*args, **kwargs)
696
+ if 'value_units' in self.fields:
697
+ self.fields['value_units'].initial = self.controller.default_value_units
698
+ self.basic_fields.extend(
699
+ ['value_units', 'turn_on_time', 'turn_off_time', 'skew']
700
+ )
701
+ if self.instance.pk and 'slaves' in self.fields:
702
+ self.fields['slaves'].initial = self.instance.slaves.all()
703
+
704
+ def clean_slaves(self):
705
+ if not self.cleaned_data['slaves'] or not self.instance:
706
+ return self.cleaned_data['slaves']
707
+ return validate_slaves(self.cleaned_data['slaves'], self.instance)
708
+
709
+ def clean(self):
710
+ super().clean()
711
+ if 'output_pin' in self.cleaned_data:
712
+ self._clean_pin('output_pin')
713
+ if 'controls' in self.cleaned_data:
714
+ self._clean_controls()
715
+
716
+ if self.cleaned_data.get('output_pin') and self.cleaned_data.get('controls'):
717
+ for ctrl in self.cleaned_data['controls']:
718
+ if not ctrl['input'].startswith('pin'):
719
+ continue
720
+ if int(ctrl['input'][4:]) == self.cleaned_data['output_pin'].id:
721
+ self.add_error(
722
+ "output_pin",
723
+ "Can't be used as control pin at the same time!"
724
+ )
725
+ return self.cleaned_data
726
+
727
+
728
+ def save(self, commit=True):
729
+ if 'output_pin' in self.cleaned_data:
730
+ self.instance.config['output_pin_no'] = self.cleaned_data['output_pin'].no
731
+
732
+ update_colonel = False
733
+ if not self.instance.pk:
734
+ update_colonel = True
735
+ elif 'output_pin' in self.changed_data:
736
+ update_colonel = True
737
+ elif 'slaves' in self.changed_data:
738
+ update_colonel = True
739
+ if not update_colonel:
740
+ old = Component.objects.get(id=self.instance.id)
741
+ if old.config.get('controls') != self.cleaned_data.get('controls'):
742
+ update_colonel = True
743
+
744
+ obj = super().save(commit=commit)
745
+ if commit and 'slaves' in self.cleaned_data:
746
+ obj.slaves.set(self.cleaned_data['slaves'])
747
+ if not update_colonel:
748
+ GatewayObjectCommand(
749
+ obj.gateway, self.cleaned_data['colonel'], id=obj.id,
750
+ command='call', method='update_config', args=[
751
+ obj.controller._get_colonel_config()
752
+ ]
753
+ ).publish()
754
+ if commit and self.cleaned_data.get('controls'):
755
+ GatewayObjectCommand(
756
+ self.instance.gateway, obj, command='watch_buttons'
757
+ ).publish()
758
+ return obj
759
+
760
+ class ColonelPWMOutputConfigForm(PWMOutputBaseConfig):
731
761
  output_pin = Select2ModelChoiceField(
732
762
  label="Port",
733
763
  queryset=ColonelPin.objects.filter(output=True),
@@ -796,74 +826,8 @@ class ColonelPWMOutputConfigForm(ColonelComponentForm):
796
826
  )
797
827
  )
798
828
 
799
- def __init__(self, *args, **kwargs):
800
- super().__init__(*args, **kwargs)
801
- if 'value_units' in self.fields:
802
- self.fields['value_units'].initial = self.controller.default_value_units
803
- self.basic_fields.extend(
804
- ['value_units', 'turn_on_time', 'turn_off_time', 'skew']
805
- )
806
- if self.instance.pk and 'slaves' in self.fields:
807
- self.fields['slaves'].initial = self.instance.slaves.all()
808
829
 
809
- def clean_slaves(self):
810
- if not self.cleaned_data['slaves'] or not self.instance:
811
- return self.cleaned_data['slaves']
812
- return validate_slaves(self.cleaned_data['slaves'], self.instance)
813
-
814
- def clean(self):
815
- super().clean()
816
- if 'output_pin' in self.cleaned_data:
817
- self._clean_pin('output_pin')
818
- if 'controls' in self.cleaned_data:
819
- self._clean_controls()
820
-
821
- if self.cleaned_data.get('output_pin') and self.cleaned_data.get('controls'):
822
- for ctrl in self.cleaned_data['controls']:
823
- if not ctrl['input'].startswith('pin'):
824
- continue
825
- if int(ctrl['input'][4:]) == self.cleaned_data['output_pin'].id:
826
- self.add_error(
827
- "output_pin",
828
- "Can't be used as control pin at the same time!"
829
- )
830
- return self.cleaned_data
831
-
832
-
833
- def save(self, commit=True):
834
- if 'output_pin' in self.cleaned_data:
835
- self.instance.config['output_pin_no'] = self.cleaned_data['output_pin'].no
836
-
837
- update_colonel = False
838
- if not self.instance.pk:
839
- update_colonel = True
840
- elif 'output_pin' in self.changed_data:
841
- update_colonel = True
842
- elif 'slaves' in self.changed_data:
843
- update_colonel = True
844
- if not update_colonel:
845
- old = Component.objects.get(id=self.instance.id)
846
- if old.config.get('controls') != self.cleaned_data.get('controls'):
847
- update_colonel = True
848
-
849
- obj = super().save(commit=commit)
850
- if commit and 'slaves' in self.cleaned_data:
851
- obj.slaves.set(self.cleaned_data['slaves'])
852
- if not update_colonel:
853
- GatewayObjectCommand(
854
- obj.gateway, self.cleaned_data['colonel'], id=obj.id,
855
- command='call', method='update_config', args=[
856
- obj.controller._get_colonel_config()
857
- ]
858
- ).publish()
859
- if commit and self.cleaned_data.get('controls'):
860
- GatewayObjectCommand(
861
- self.instance.gateway, obj, command='watch_buttons'
862
- ).publish()
863
- return obj
864
-
865
-
866
- class DCDriverConfigForm(ColonelComponentForm):
830
+ class DC10VConfigForm(PWMOutputBaseConfig):
867
831
  output_pin = Select2ModelChoiceField(
868
832
  label="Port",
869
833
  queryset=ColonelPin.objects.filter(output=True),
@@ -879,53 +843,60 @@ class DCDriverConfigForm(ColonelComponentForm):
879
843
  help_text="Minimum component value displayed to the user."
880
844
  )
881
845
  max = forms.FloatField(
882
- required=True, initial=24,
846
+ required=True, initial=100,
883
847
  help_text="Maximum component value displayed to the user."
884
848
  )
885
- value_units = forms.CharField(required=False)
849
+ value_units = forms.CharField(required=False, initial='%')
886
850
 
887
851
  device_min = forms.FloatField(
888
852
  label="Device minimum Voltage.",
889
853
  help_text="This will be the lowest possible voltage value of a device.\n"
890
854
  "Don't forget to adjust your component min value accordingly "
891
855
  "if you change this.",
892
- initial=0, min_value=0, max_value=24,
856
+ initial=0, min_value=0, max_value=10,
893
857
  )
894
- device_max = forms.IntegerField(
858
+ device_max = forms.FloatField(
895
859
  label="Device maximum Voltage.",
896
- help_text="Can be set lower than it's natural maximum of 24V. \n"
860
+ help_text="Can be set lower than it's natural maximum of 10V. \n"
897
861
  "Don't forget to adjust your component max value accordingly "
898
862
  "if you change this.",
899
- initial=24, min_value=0, max_value=24,
863
+ initial=10, min_value=0, max_value=10,
900
864
  )
865
+ inverse = forms.BooleanField(required=False, initial=False)
901
866
 
902
- def clean(self):
903
- super().clean()
904
- if 'output_pin' in self.cleaned_data:
905
- self._clean_pin('output_pin')
906
- return self.cleaned_data
907
-
908
-
909
- def save(self, commit=True):
910
- if 'output_pin' in self.cleaned_data:
911
- self.instance.config['output_pin_no'] = self.cleaned_data['output_pin'].no
912
-
913
- update_colonel = False
914
- if not self.instance.pk:
915
- update_colonel = True
916
- elif 'output_pin' in self.changed_data:
917
- update_colonel = True
918
-
919
- obj = super().save(commit=commit)
867
+ turn_on_time = forms.IntegerField(
868
+ min_value=0, max_value=60000, initial=0,
869
+ help_text="Turn on speed in ms. 1500 is a great quick default for controlling lights. "
870
+ "10000 - great slow default."
871
+ )
872
+ turn_off_time = forms.IntegerField(
873
+ min_value=0, max_value=60000, initial=0,
874
+ help_text="Turn off speed in ms. 3000 is a great quick default when controlling lights. "
875
+ "20000 - great slow default"
876
+ )
877
+ skew = forms.ChoiceField(
878
+ initial='linear', choices=EASING_CHOICES,
879
+ help_text="easeOutSine - offers most naturally looking effect for lights."
880
+ )
881
+ on_value = forms.FloatField(
882
+ required=False,
883
+ help_text="Static ON value used to turn on the device with physical controls. <br>"
884
+ "Leaving this field empty turns the device on to the last used value."
885
+ )
920
886
 
921
- if not update_colonel:
922
- GatewayObjectCommand(
923
- obj.gateway, self.cleaned_data['colonel'], id=obj.id,
924
- command='call', method='update_config', args=[
925
- obj.controller._get_colonel_config()
926
- ]
927
- ).publish()
928
- return obj
887
+ slaves = Select2ModelMultipleChoiceField(
888
+ queryset=Component.objects.filter(
889
+ base_type__in=('dimmer',),
890
+ ),
891
+ url='autocomplete-component',
892
+ forward=(forward.Const(['dimmer', ], 'base_type'),),
893
+ required=False
894
+ )
895
+ controls = FormsetField(
896
+ formset_factory(
897
+ ControlForm, can_delete=True, can_order=True, extra=0, max_num=10
898
+ )
899
+ )
929
900
 
930
901
 
931
902
  class ColonelRGBLightConfigForm(ColonelComponentForm):
@@ -1831,8 +1802,6 @@ class CustomDaliDeviceForm(BaseComponentForm):
1831
1802
  for colonel in Colonel.objects.filter(
1832
1803
  type='room-sensor', instance=instance
1833
1804
  ):
1834
- if not colonel.is_connected:
1835
- continue
1836
1805
  choices.append((f"wifi-{colonel.id}", colonel.name))
1837
1806
  for device in CustomDaliDevice.objects.filter(
1838
1807
  instance=instance,
@@ -1854,6 +1823,7 @@ class CustomDaliDeviceForm(BaseComponentForm):
1854
1823
 
1855
1824
  class RoomSensorDeviceConfigForm(CustomDaliDeviceForm):
1856
1825
 
1826
+
1857
1827
  def save(self, commit=True):
1858
1828
  from simo.core.models import Icon
1859
1829
  colonel = None
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
+ ]
@@ -0,0 +1,36 @@
1
+ # Generated by Django 4.2.10 on 2025-05-07 12:56
2
+
3
+ from django.db import migrations
4
+
5
+ def forwards_func(apps, schema_editor):
6
+ Component = apps.get_model("core", "Component")
7
+ ColonelPin = apps.get_model('fleet', "ColonelPin")
8
+
9
+ for comp in Component.objects.filter(controller_uid__in=(
10
+ 'simo.fleet.controllers.BME680Sensor',
11
+ 'simo.fleet.controllers.MCP9808TempSensor',
12
+ 'simo.fleet.controllers.ENS160AirQualitySensor'
13
+ )):
14
+ cp = ColonelPin.objects.filter(
15
+ colonel__id=comp.config['colonel'],
16
+ interface=comp.config['i2c_interface']
17
+ ).first()
18
+ if cp:
19
+ comp.config['interface_port'] = cp.id
20
+ comp.save()
21
+
22
+
23
+
24
+ def reverse_func(apps, schema_editor):
25
+ pass
26
+
27
+
28
+ class Migration(migrations.Migration):
29
+
30
+ dependencies = [
31
+ ('fleet', '0053_auto_20250507_0713'),
32
+ ]
33
+
34
+ operations = [
35
+ migrations.RunPython(forwards_func, reverse_func, elidable=True),
36
+ ]