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/models.py CHANGED
@@ -16,7 +16,11 @@ from simo.core.models import Instance, Gateway, Component
16
16
  from simo.core.utils.helpers import get_random_string
17
17
  from simo.core.events import GatewayObjectCommand
18
18
  from .managers import ColonelsManager, ColonelPinsManager, InterfacesManager
19
- from .utils import GPIO_PINS, INTERFACES_PINS_MAP
19
+ # Now imported from utils
20
+ from .utils import GPIO_PINS, INTERFACES_PINS_MAP, \
21
+ _sync_interface_address_occupancy, _release_interface_addresses
22
+
23
+ # -------------------------------------------------------------------------
20
24
 
21
25
 
22
26
 
@@ -219,6 +223,7 @@ class ColonelPin(models.Model):
219
223
  capacitive = models.BooleanField(default=False, db_index=True)
220
224
  adc = models.BooleanField(default=False)
221
225
  native = models.BooleanField(default=True, db_index=True)
226
+ interface = models.PositiveIntegerField(null=True, blank=True)
222
227
  default_pull = models.CharField(
223
228
  max_length=50, db_index=True, null=True, blank=True,
224
229
  choices=(('LOW', "LOW"), ("HIGH", "HIGH"))
@@ -246,6 +251,12 @@ class ColonelPin(models.Model):
246
251
  if not self.label:
247
252
  # Might be created via migration...
248
253
  self.save()
254
+ if self.interface:
255
+ interface = Interface.objects.filter(
256
+ colonel=self.colonel, no=self.interface
257
+ ).first()
258
+ if interface and interface.type:
259
+ return f"{self.label} - {interface.get_type_display()}"
249
260
  return self.label
250
261
 
251
262
  def save(self, *args, **kwargs):
@@ -268,7 +279,8 @@ def after_colonel_save(sender, instance, created, *args, **kwargs):
268
279
  defaults={
269
280
  'input': data.get('input'), 'output': data.get('output'),
270
281
  'capacitive': data.get('capacitive'), 'adc': data.get('adc'),
271
- 'native': data.get('native'), 'note': data.get('note')
282
+ 'native': data.get('native'), 'note': data.get('note'),
283
+ 'interface': data.get('interface')
272
284
  }
273
285
  )
274
286
  fleet_gateway, new = Gateway.objects.get_or_create(
@@ -276,15 +288,6 @@ def after_colonel_save(sender, instance, created, *args, **kwargs):
276
288
  )
277
289
  if fleet_gateway.status != 'running':
278
290
  fleet_gateway.start()
279
- # create i2c and dali interfaces automatically for game-changer boards
280
- if instance.type == 'game-changer':
281
- # occupy ports immediately
282
- Interface.objects.create(colonel=instance, no=1, type='i2c')
283
- Interface.objects.create(colonel=instance, no=2, type='dali')
284
- elif instance.type == 'game-changer-mini':
285
- # only create interfaces, but do not occupy ports
286
- Interface.objects.create(colonel=instance, no=1)
287
- Interface.objects.create(colonel=instance, no=2)
288
291
 
289
292
  if 'socket_connected' in instance.get_dirty_fields():
290
293
  if instance.socket_connected:
@@ -308,15 +311,26 @@ def post_component_save(sender, instance, created, *args, **kwargs):
308
311
  if not colonel:
309
312
  return
310
313
  colonel.components.add(instance)
314
+
315
+ # ------------------------------------------------------------------
316
+ # InterfaceAddress occupancy sync (always run for fleet components)
317
+ # ------------------------------------------------------------------
318
+ try:
319
+ _sync_interface_address_occupancy(instance)
320
+ except ValidationError:
321
+ raise
322
+
311
323
  from .controllers import (
312
324
  TTLock, DALILamp, DALIGearGroup, DALIRelay, DALIOccupancySensor,
313
325
  DALILightSensor, DALIButton,
314
- AirQualitySensor, TempHumSensor, AmbientLightSensor, RoomPresenceSensor
326
+ AirQualitySensor, TempHumSensor, AmbientLightSensor,
327
+ RoomPresenceSensor, RoomZonePresenceSensor
315
328
  )
316
329
  if instance.controller and instance.controller_cls in (
317
330
  TTLock, DALILamp, DALIGearGroup, DALIRelay, DALIOccupancySensor,
318
331
  DALILightSensor, DALIButton,
319
- AirQualitySensor, TempHumSensor, AmbientLightSensor, RoomPresenceSensor
332
+ AirQualitySensor, TempHumSensor, AmbientLightSensor,
333
+ RoomPresenceSensor, RoomZonePresenceSensor
320
334
  ):
321
335
  return
322
336
  colonel.rebuild_occupied_pins()
@@ -328,6 +342,9 @@ def post_component_save(sender, instance, created, *args, **kwargs):
328
342
  @receiver(pre_delete, sender=Component)
329
343
  def post_component_delete(sender, instance, *args, **kwargs):
330
344
  if not instance.controller_uid.startswith('simo.fleet'):
345
+ # Still ensure we release any InterfaceAddress occupied by this
346
+ # component to avoid dangling references.
347
+ _release_interface_addresses(instance)
331
348
  return
332
349
 
333
350
  from .controllers import DALIGearGroup
@@ -359,6 +376,9 @@ def post_component_delete(sender, instance, *args, **kwargs):
359
376
 
360
377
  transaction.on_commit(update_colonel)
361
378
 
379
+ # Finally release any InterfaceAddress rows occupied by this component
380
+ _release_interface_addresses(instance)
381
+
362
382
 
363
383
  class Interface(models.Model):
364
384
  colonel = models.ForeignKey(
@@ -393,27 +413,48 @@ class Interface(models.Model):
393
413
  def save(self, *args, **kwargs):
394
414
  if not self.pin_a:
395
415
  self.pin_a = ColonelPin.objects.get(
396
- colonel=self.colonel, no=INTERFACES_PINS_MAP[self.no][0]
416
+ colonel=self.colonel,
417
+ no=INTERFACES_PINS_MAP[self.no][0],
397
418
  )
398
419
  if not self.pin_b:
399
420
  self.pin_b = ColonelPin.objects.get(
400
- colonel=self.colonel, no=INTERFACES_PINS_MAP[self.no][1]
421
+ colonel=self.colonel,
422
+ no=INTERFACES_PINS_MAP[self.no][1],
401
423
  )
402
- if self.type:
403
- for pin_no in INTERFACES_PINS_MAP[self.no]:
404
- cpin = ColonelPin.objects.get(colonel=self.colonel, no=pin_no)
405
- if cpin.occupied_by and cpin.occupied_by != self:
406
- raise ValidationError(
407
- f"Interface can not be created, because "
408
- f"{cpin} is already occupied by {cpin.occupied_by}."
409
- )
410
- self.pin_a.occupied_by = self
411
- self.pin_b.occupied_by = self
412
- else:
413
- self.pin_a.occupied_by = None
414
- self.pin_b.occupied_by = None
415
424
 
416
- return super().save(*args, **kwargs)
425
+ with transaction.atomic():
426
+ created = self.pk is None
427
+ super_save = super().save # keep lint happy
428
+ retval = super_save(*args, **kwargs)
429
+
430
+ pins = list(
431
+ ColonelPin.objects.select_for_update().filter(
432
+ colonel=self.colonel,
433
+ no__in=INTERFACES_PINS_MAP[self.no],
434
+ )
435
+ )
436
+
437
+ if self.type: # claim the pins
438
+ for pin in pins:
439
+ # If already occupied by another object – abort.
440
+ if pin.occupied_by and pin.occupied_by != self:
441
+ raise ValidationError(
442
+ f"Interface cannot claim {pin}. Currently "
443
+ f"occupied by {pin.occupied_by}.",
444
+ )
445
+ pin.occupied_by = self
446
+ ColonelPin.objects.bulk_update(
447
+ pins, ["occupied_by_content_type", "occupied_by_id"]
448
+ )
449
+ else: # release them if *we* were occupying
450
+ for pin in pins:
451
+ if pin.occupied_by == self:
452
+ pin.occupied_by = None
453
+ ColonelPin.objects.bulk_update(
454
+ pins, ["occupied_by_content_type", "occupied_by_id"]
455
+ )
456
+
457
+ return retval
417
458
 
418
459
  def broadcast_reset(self):
419
460
  from .gateways import FleetGatewayHandler
@@ -472,6 +513,11 @@ def post_interface_save(sender, instance, created, *args, **kwargs):
472
513
  address=addr,
473
514
  )
474
515
  elif instance.type == 'dali':
516
+ InterfaceAddress.objects.filter(
517
+ interface=instance
518
+ ).exclude(
519
+ address_type__in=('dali-gear', 'dali-group', 'dali-device')
520
+ ).delete()
475
521
  for addr in range(64):
476
522
  InterfaceAddress.objects.get_or_create(
477
523
  interface=instance, address_type='dali-gear',
@@ -485,17 +531,33 @@ def post_interface_save(sender, instance, created, *args, **kwargs):
485
531
  else:
486
532
  InterfaceAddress.objects.filter(interface=instance).delete()
487
533
 
534
+ try:
535
+ instance.colonel.update_config()
536
+ except Exception:
537
+ # Fail silently – configuration push should not prevent saving.
538
+ pass
539
+
488
540
 
489
541
 
490
542
  @receiver(post_delete, sender=Interface)
491
543
  def post_interface_delete(sender, instance, *args, **kwargs):
544
+ """Release GPIO pins that were occupied by the removed Interface."""
492
545
  with transaction.atomic():
493
546
  ct = ContentType.objects.get_for_model(instance)
494
- for pin in ColonelPin.objects.filter(
495
- occupied_by_content_type=ct,
496
- occupied_by_id=instance.id
497
- ):
498
- pin.occupied_by_content_type = None
547
+ pins = list(
548
+ ColonelPin.objects.select_for_update().filter(
549
+ occupied_by_content_type=ct,
550
+ occupied_by_id=instance.id,
551
+ )
552
+ )
553
+
554
+ for pin in pins:
555
+ pin.occupied_by = None
556
+
557
+ if pins:
558
+ ColonelPin.objects.bulk_update(
559
+ pins, ["occupied_by_content_type", "occupied_by_id"]
560
+ )
499
561
 
500
562
 
501
563
  class CustomDaliDevice(models.Model):
simo/fleet/serializers.py CHANGED
@@ -46,7 +46,7 @@ class ColonelSerializer(serializers.ModelSerializer):
46
46
  pins = serializers.SerializerMethodField()
47
47
  interfaces = serializers.SerializerMethodField()
48
48
  newer_firmware_available = serializers.SerializerMethodField()
49
- last_seen = TimestampField()
49
+ last_seen = TimestampField(read_only=True)
50
50
  is_empty = serializers.SerializerMethodField()
51
51
 
52
52
  class Meta:
@@ -91,7 +91,7 @@ class ColonelSerializer(serializers.ModelSerializer):
91
91
  class CustomDaliDeviceSerializer(serializers.ModelSerializer):
92
92
  is_empty = serializers.SerializerMethodField()
93
93
  is_alive = serializers.SerializerMethodField()
94
- last_seen = TimestampField()
94
+ last_seen = TimestampField(read_only=True)
95
95
 
96
96
  class Meta:
97
97
  model = CustomDaliDevice
simo/fleet/utils.py CHANGED
@@ -1,6 +1,10 @@
1
+ from django.db import transaction, models
2
+ from django.core.exceptions import ValidationError
3
+ from django.contrib.contenttypes.models import ContentType
1
4
  from simo.core.utils.cache import get_cached_data
2
5
  from simo.core.middleware import get_current_instance
3
6
 
7
+
4
8
  GPIO_PIN_DEFAULTS = {
5
9
  'output': True, 'input': True, 'default_pull': 'FLOATING',
6
10
  'native': True, 'adc': False,
@@ -71,6 +75,10 @@ for no, data in BASE_ESP32_GPIO_PINS.items():
71
75
  GPIO_PINS['game-changer'][no] = GPIO_PIN_DEFAULTS.copy()
72
76
  GPIO_PINS['game-changer'][no].update(data)
73
77
 
78
+ if no in (13, 23, 32, 33):
79
+ GPIO_PINS['game-changer-mini'][no] = GPIO_PIN_DEFAULTS.copy()
80
+ GPIO_PINS['game-changer-mini'][no].update(data)
81
+
74
82
 
75
83
  for no in range(101, 126):
76
84
  GPIO_PINS['ample-wall'][no] = {
@@ -78,6 +86,9 @@ for no in range(101, 126):
78
86
  'native': False, 'adc': False,
79
87
  'capacitive': False, 'note': ''
80
88
  }
89
+ if no in (101, 102):
90
+ GPIO_PINS['ample-wall'][no]['interface'] = no - 100
91
+
81
92
  for no in range(126, 133):
82
93
  GPIO_PINS['ample-wall'][no] = {
83
94
  'output': True, 'input': True, 'default_pull': 'HIGH',
@@ -92,6 +103,8 @@ for no in range(101, 139):
92
103
  'native': False, 'adc': False,
93
104
  'capacitive': False, 'note': ''
94
105
  }
106
+ if no in (101, 102):
107
+ GPIO_PINS['game-changer'][no]['interface'] = no - 100
95
108
 
96
109
  for no in range(101, 105):
97
110
  GPIO_PINS['game-changer-mini'][no] = {
@@ -99,6 +112,8 @@ for no in range(101, 105):
99
112
  'native': False, 'adc': False,
100
113
  'capacitive': False, 'note': ''
101
114
  }
115
+ if no in (101, 102):
116
+ GPIO_PINS['game-changer-mini'][no]['interface'] = no - 100
102
117
 
103
118
 
104
119
  #4-relays
@@ -134,6 +149,117 @@ INTERFACES_PINS_MAP = {
134
149
  }
135
150
 
136
151
 
152
+ def _get_component_interface_addresses(component):
153
+ """Return list[(interface_id, address_type, address)] component needs.
154
+
155
+ Supports I²C and DALI controllers. Extend when new bus types land.
156
+ """
157
+ from .models import Interface # local import to avoid circular deps
158
+
159
+ cfg = component.config or {}
160
+ desired = []
161
+
162
+ # I²C ----------------------------------------------------------------
163
+ if 'i2c_interface' in cfg and 'i2c_address' in cfg:
164
+ desired.append((cfg['i2c_interface'], 'i2c', cfg['i2c_address']))
165
+
166
+ # DALI ---------------------------------------------------------------
167
+ interface_no = None
168
+ if 'dali_interface' in cfg:
169
+ interface_no = cfg['dali_interface']
170
+ elif 'interface' in cfg:
171
+ interface_no = cfg['interface']
172
+
173
+ if interface_no is not None and 'colonel' in cfg and 'da' in cfg:
174
+ interface_obj = Interface.objects.filter(
175
+ colonel_id=cfg['colonel'], no=interface_no
176
+ ).first()
177
+ if interface_obj:
178
+ uid = component.controller_uid or ''
179
+ if uid.endswith('DALIGearGroup'):
180
+ addr_type = 'dali-group'
181
+ elif uid.endswith(('DALILamp', 'DALIRelay')):
182
+ addr_type = 'dali-gear'
183
+ else:
184
+ addr_type = 'dali-device'
185
+ desired.append((interface_obj.id, addr_type, cfg['da']))
186
+
187
+ return desired
188
+
189
+
190
+ def _sync_interface_address_occupancy(component):
191
+ """Synchronise InterfaceAddress rows for *component* (claim/release)."""
192
+ from .models import InterfaceAddress # local import, avoids circulars
193
+
194
+ desired = set(_get_component_interface_addresses(component))
195
+
196
+ # Short-circuit if nothing desired and nothing currently occupied.
197
+ if not desired and not InterfaceAddress.objects.filter(
198
+ occupied_by_id=component.id,
199
+ occupied_by_content_type=ContentType.objects.get_for_model(component),
200
+ ).exists():
201
+ return
202
+
203
+ with transaction.atomic():
204
+ ct = ContentType.objects.get_for_model(component)
205
+
206
+ iface_ids = [d[0] for d in desired]
207
+ lock_qs = InterfaceAddress.objects.select_for_update().filter(
208
+ models.Q(occupied_by_content_type=ct, occupied_by_id=component.id)
209
+ | models.Q(interface_id__in=iface_ids)
210
+ )
211
+
212
+ addr_map = {(
213
+ ia.interface_id, ia.address_type, ia.address): ia for ia in lock_qs}
214
+
215
+ # Release obsolete ------------------------------------------------
216
+ for ia in lock_qs:
217
+ key = (ia.interface_id, ia.address_type, ia.address)
218
+ if ia.occupied_by_id == component.id and key not in desired:
219
+ ia.occupied_by = None
220
+
221
+ # Claim desired ---------------------------------------------------
222
+ for iface_id, addr_type, addr_val in desired:
223
+ key = (iface_id, addr_type, addr_val)
224
+ ia = addr_map.get(key)
225
+ if not ia:
226
+ ia = InterfaceAddress.objects.select_for_update().create(
227
+ interface_id=iface_id, address_type=addr_type,
228
+ address=addr_val,
229
+ )
230
+ addr_map[key] = ia
231
+ if ia.occupied_by and ia.occupied_by != component:
232
+ raise ValidationError(
233
+ f"Interface address {ia} already occupied by "
234
+ f"{ia.occupied_by}."
235
+ )
236
+ ia.occupied_by = component
237
+
238
+ # Bulk save all modified objects
239
+ InterfaceAddress.objects.bulk_update(
240
+ addr_map.values(),
241
+ ["occupied_by_content_type", "occupied_by_id"],
242
+ )
243
+
244
+
245
+ def _release_interface_addresses(component):
246
+ """Clear all InterfaceAddress rows owned by component."""
247
+ from .models import InterfaceAddress # delayed import
248
+
249
+ ct = ContentType.objects.get_for_model(component)
250
+ with transaction.atomic():
251
+ addresses = InterfaceAddress.objects.select_for_update().filter(
252
+ occupied_by_content_type=ct, occupied_by_id=component.id,
253
+ )
254
+ if not addresses:
255
+ return
256
+ for ia in addresses:
257
+ ia.occupied_by = None
258
+ InterfaceAddress.objects.bulk_update(
259
+ addresses, ["occupied_by_content_type", "occupied_by_id"]
260
+ )
261
+
262
+
137
263
  def get_all_control_input_choices():
138
264
  '''
139
265
  This is called multiple times by component form,
@@ -4,7 +4,7 @@ from .models import Notification
4
4
 
5
5
 
6
6
  class NotificationSerializer(serializers.ModelSerializer):
7
- datetime = TimestampField()
7
+ datetime = TimestampField(read_only=True)
8
8
 
9
9
  class Meta:
10
10
  model = Notification
Binary file
Binary file