simo 2.10.6__py3-none-any.whl → 2.10.9__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (389) 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 +16 -10
  214. simo/fleet/controllers.py +98 -14
  215. simo/fleet/forms.py +70 -110
  216. simo/fleet/gateways.py +34 -4
  217. simo/fleet/migrations/0050_customdalidevice_uid.py +19 -0
  218. simo/fleet/migrations/0051_customdalidevice_components.py +19 -0
  219. simo/fleet/migrations/0052_colonelpin_interface.py +18 -0
  220. simo/fleet/migrations/0053_auto_20250507_0713.py +24 -0
  221. simo/fleet/migrations/__pycache__/0001_initial.cpython-312.pyc +0 -0
  222. simo/fleet/migrations/__pycache__/0002_auto_20220422_0743.cpython-312.pyc +0 -0
  223. simo/fleet/migrations/__pycache__/0003_auto_20220422_0752.cpython-312.pyc +0 -0
  224. simo/fleet/migrations/__pycache__/0004_auto_20220422_0818.cpython-312.pyc +0 -0
  225. simo/fleet/migrations/__pycache__/0005_auto_20220428_0900.cpython-312.pyc +0 -0
  226. simo/fleet/migrations/__pycache__/0006_rename_mac_colonel_uid.cpython-312.pyc +0 -0
  227. simo/fleet/migrations/__pycache__/0007_colonel_socket_connected.cpython-312.pyc +0 -0
  228. simo/fleet/migrations/__pycache__/0008_i2cinterface.cpython-312.pyc +0 -0
  229. simo/fleet/migrations/__pycache__/0009_i2cinterface_name.cpython-312.pyc +0 -0
  230. simo/fleet/migrations/__pycache__/0010_auto_20220602_0746.cpython-312.pyc +0 -0
  231. simo/fleet/migrations/__pycache__/0011_i2cinterface_freq.cpython-312.pyc +0 -0
  232. simo/fleet/migrations/__pycache__/0012_colonel_logs_stream.cpython-312.pyc +0 -0
  233. simo/fleet/migrations/__pycache__/0013_alter_colonel_last_seen.cpython-312.pyc +0 -0
  234. simo/fleet/migrations/__pycache__/0014_auto_20220614_0659.cpython-312.pyc +0 -0
  235. simo/fleet/migrations/__pycache__/0015_auto_20220614_0754.cpython-312.pyc +0 -0
  236. simo/fleet/migrations/__pycache__/0016_auto_20220704_0840.cpython-312.pyc +0 -0
  237. simo/fleet/migrations/__pycache__/0017_alter_colonel_secret.cpython-312.pyc +0 -0
  238. simo/fleet/migrations/__pycache__/0018_colonel_instance.cpython-312.pyc +0 -0
  239. simo/fleet/migrations/__pycache__/0019_auto_20231006_0749.cpython-312.pyc +0 -0
  240. simo/fleet/migrations/__pycache__/0020_instanceoptions.cpython-312.pyc +0 -0
  241. simo/fleet/migrations/__pycache__/0021_auto_20231006_0819.cpython-312.pyc +0 -0
  242. simo/fleet/migrations/__pycache__/0022_remove_colonel_secret.cpython-312.pyc +0 -0
  243. simo/fleet/migrations/__pycache__/0023_colonel_is_authorized.cpython-312.pyc +0 -0
  244. simo/fleet/migrations/__pycache__/0024_colonel_pwm_frequency.cpython-312.pyc +0 -0
  245. simo/fleet/migrations/__pycache__/0025_auto_20240130_1334.cpython-312.pyc +0 -0
  246. simo/fleet/migrations/__pycache__/0026_rename_i2cinterface_scl_pin_and_more.cpython-312.pyc +0 -0
  247. simo/fleet/migrations/__pycache__/0027_auto_20240306_0802.cpython-312.pyc +0 -0
  248. simo/fleet/migrations/__pycache__/0028_remove_i2cinterface_scl_pin_no_and_more.cpython-312.pyc +0 -0
  249. simo/fleet/migrations/__pycache__/0029_alter_i2cinterface_scl_pin_and_more.cpython-312.pyc +0 -0
  250. simo/fleet/migrations/__pycache__/0030_colonelpin_label_alter_colonel_type.cpython-312.pyc +0 -0
  251. simo/fleet/migrations/__pycache__/0031_alter_colonel_type.cpython-312.pyc +0 -0
  252. simo/fleet/migrations/__pycache__/0032_auto_20240415_0736.cpython-312.pyc +0 -0
  253. simo/fleet/migrations/__pycache__/0033_auto_20240415_0736.cpython-312.pyc +0 -0
  254. simo/fleet/migrations/__pycache__/0034_auto_20240418_0735.cpython-312.pyc +0 -0
  255. simo/fleet/migrations/__pycache__/0035_auto_20240514_0855.cpython-312.pyc +0 -0
  256. simo/fleet/migrations/__pycache__/0036_auto_20240605_0702.cpython-312.pyc +0 -0
  257. simo/fleet/migrations/__pycache__/0037_alter_colonelpin_options_alter_colonelpin_no_and_more.cpython-312.pyc +0 -0
  258. simo/fleet/migrations/__pycache__/0038_alter_colonel_type.cpython-312.pyc +0 -0
  259. simo/fleet/migrations/__pycache__/0039_auto_20241016_1047.cpython-312.pyc +0 -0
  260. simo/fleet/migrations/__pycache__/0040_alter_colonel_pwm_frequency.cpython-312.pyc +0 -0
  261. simo/fleet/migrations/__pycache__/0041_alter_colonel_instance_and_more.cpython-312.pyc +0 -0
  262. simo/fleet/migrations/__pycache__/0042_auto_20241120_1028.cpython-312.pyc +0 -0
  263. simo/fleet/migrations/__pycache__/0043_auto_20241203_0930.cpython-312.pyc +0 -0
  264. simo/fleet/migrations/__pycache__/0044_auto_20241210_0707.cpython-312.pyc +0 -0
  265. simo/fleet/migrations/__pycache__/0045_alter_colonel_type_customdalidevice.cpython-312.pyc +0 -0
  266. simo/fleet/migrations/__pycache__/0046_delete_customdalidevice.cpython-312.pyc +0 -0
  267. simo/fleet/migrations/__pycache__/0047_customdalidevice.cpython-312.pyc +0 -0
  268. simo/fleet/migrations/__pycache__/0048_remove_customdalidevice_colonel_and_more.cpython-312.pyc +0 -0
  269. simo/fleet/migrations/__pycache__/0049_alter_customdalidevice_interface.cpython-312.pyc +0 -0
  270. simo/fleet/migrations/__pycache__/0050_customdalidevice_uid.cpython-312.pyc +0 -0
  271. simo/fleet/migrations/__pycache__/0051_customdalidevice_components.cpython-312.pyc +0 -0
  272. simo/fleet/migrations/__pycache__/0052_colonelpin_interface.cpython-312.pyc +0 -0
  273. simo/fleet/migrations/__pycache__/0053_auto_20250507_0713.cpython-312.pyc +0 -0
  274. simo/fleet/migrations/__pycache__/__init__.cpython-312.pyc +0 -0
  275. simo/fleet/models.py +124 -34
  276. simo/fleet/serializers.py +40 -4
  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/controllers.py +2 -2
  289. simo/generic/migrations/__pycache__/0001_initial.cpython-312.pyc +0 -0
  290. simo/generic/migrations/__pycache__/0002_auto_20241126_0726.cpython-312.pyc +0 -0
  291. simo/generic/migrations/__pycache__/0003_auto_20250409_1404.cpython-312.pyc +0 -0
  292. simo/generic/migrations/__pycache__/__init__.cpython-312.pyc +0 -0
  293. simo/multimedia/__pycache__/__init__.cpython-312.pyc +0 -0
  294. simo/multimedia/__pycache__/admin.cpython-312.pyc +0 -0
  295. simo/multimedia/__pycache__/api.cpython-312.pyc +0 -0
  296. simo/multimedia/__pycache__/app_widgets.cpython-312.pyc +0 -0
  297. simo/multimedia/__pycache__/auto_urls.cpython-312.pyc +0 -0
  298. simo/multimedia/__pycache__/base_types.cpython-312.pyc +0 -0
  299. simo/multimedia/__pycache__/controllers.cpython-312.pyc +0 -0
  300. simo/multimedia/__pycache__/forms.cpython-312.pyc +0 -0
  301. simo/multimedia/__pycache__/models.cpython-312.pyc +0 -0
  302. simo/multimedia/__pycache__/serializers.cpython-312.pyc +0 -0
  303. simo/multimedia/__pycache__/views.cpython-312.pyc +0 -0
  304. simo/multimedia/migrations/__pycache__/0001_initial.cpython-312.pyc +0 -0
  305. simo/multimedia/migrations/__pycache__/0002_sound_length.cpython-312.pyc +0 -0
  306. simo/multimedia/migrations/__pycache__/0003_alter_sound_length.cpython-312.pyc +0 -0
  307. simo/multimedia/migrations/__pycache__/0004_auto_20231023_1055.cpython-312.pyc +0 -0
  308. simo/multimedia/migrations/__pycache__/0005_remove_sound_slug_sound_date_uploaded.cpython-312.pyc +0 -0
  309. simo/multimedia/migrations/__pycache__/0006_remove_sound_length_sound_duration.cpython-312.pyc +0 -0
  310. simo/multimedia/migrations/__pycache__/__init__.cpython-312.pyc +0 -0
  311. simo/notifications/__pycache__/__init__.cpython-312.pyc +0 -0
  312. simo/notifications/__pycache__/admin.cpython-312.pyc +0 -0
  313. simo/notifications/__pycache__/api.cpython-312.pyc +0 -0
  314. simo/notifications/__pycache__/models.cpython-312.pyc +0 -0
  315. simo/notifications/__pycache__/serializers.cpython-312.pyc +0 -0
  316. simo/notifications/__pycache__/utils.cpython-312.pyc +0 -0
  317. simo/notifications/migrations/__pycache__/0001_initial.cpython-312.pyc +0 -0
  318. simo/notifications/migrations/__pycache__/0002_notification_instance.cpython-312.pyc +0 -0
  319. simo/notifications/migrations/__pycache__/0003_alter_notification_instance.cpython-312.pyc +0 -0
  320. simo/notifications/migrations/__pycache__/__init__.cpython-312.pyc +0 -0
  321. simo/notifications/serializers.py +1 -1
  322. simo/users/__pycache__/__init__.cpython-312.pyc +0 -0
  323. simo/users/__pycache__/admin.cpython-312.pyc +0 -0
  324. simo/users/__pycache__/api.cpython-312.pyc +0 -0
  325. simo/users/__pycache__/apps.cpython-312.pyc +0 -0
  326. simo/users/__pycache__/auth_backends.cpython-312.pyc +0 -0
  327. simo/users/__pycache__/auto_urls.cpython-312.pyc +0 -0
  328. simo/users/__pycache__/dynamic_settings.cpython-312.pyc +0 -0
  329. simo/users/__pycache__/managers.cpython-312.pyc +0 -0
  330. simo/users/__pycache__/middleware.cpython-312.pyc +0 -0
  331. simo/users/__pycache__/models.cpython-312.pyc +0 -0
  332. simo/users/__pycache__/permissions.cpython-312.pyc +0 -0
  333. simo/users/__pycache__/serializers.cpython-312.pyc +0 -0
  334. simo/users/__pycache__/sso_urls.cpython-312.pyc +0 -0
  335. simo/users/__pycache__/sso_views.cpython-312.pyc +0 -0
  336. simo/users/__pycache__/tasks.cpython-312.pyc +0 -0
  337. simo/users/__pycache__/utils.cpython-312.pyc +0 -0
  338. simo/users/__pycache__/views.cpython-312.pyc +0 -0
  339. simo/users/migrations/__pycache__/0001_initial.cpython-312.pyc +0 -0
  340. simo/users/migrations/__pycache__/0002_componentpermission.cpython-312.pyc +0 -0
  341. simo/users/migrations/__pycache__/0003_create_roles_and_system_user.cpython-312.pyc +0 -0
  342. simo/users/migrations/__pycache__/0004_user_secret_key.cpython-312.pyc +0 -0
  343. simo/users/migrations/__pycache__/0005_permissionsrole_instance.cpython-312.pyc +0 -0
  344. simo/users/migrations/__pycache__/0006_auto_20231003_0850.cpython-312.pyc +0 -0
  345. simo/users/migrations/__pycache__/0007_auto_20231003_1228.cpython-312.pyc +0 -0
  346. simo/users/migrations/__pycache__/0008_auto_20231003_1229.cpython-312.pyc +0 -0
  347. simo/users/migrations/__pycache__/0009_remove_user_role.cpython-312.pyc +0 -0
  348. simo/users/migrations/__pycache__/0010_auto_20231004_1313.cpython-312.pyc +0 -0
  349. simo/users/migrations/__pycache__/0011_auto_20231004_1313.cpython-312.pyc +0 -0
  350. simo/users/migrations/__pycache__/0012_alter_userinstancerole_unique_together.cpython-312.pyc +0 -0
  351. simo/users/migrations/__pycache__/0013_remove_user_roles.cpython-312.pyc +0 -0
  352. simo/users/migrations/__pycache__/0014_user_roles.cpython-312.pyc +0 -0
  353. simo/users/migrations/__pycache__/0015_remove_user_at_home.cpython-312.pyc +0 -0
  354. simo/users/migrations/__pycache__/0016_auto_20231005_1050.cpython-312.pyc +0 -0
  355. simo/users/migrations/__pycache__/0017_auto_20231221_0735.cpython-312.pyc +0 -0
  356. simo/users/migrations/__pycache__/0018_user_is_god.cpython-312.pyc +0 -0
  357. simo/users/migrations/__pycache__/0019_auto_20231221_1155.cpython-312.pyc +0 -0
  358. simo/users/migrations/__pycache__/0020_rename_is_god_user_is_master.cpython-312.pyc +0 -0
  359. simo/users/migrations/__pycache__/0021_alter_permissionsrole_instance.cpython-312.pyc +0 -0
  360. simo/users/migrations/__pycache__/0022_userdevicereportlog_instance.cpython-312.pyc +0 -0
  361. simo/users/migrations/__pycache__/0023_auto_20240105_0719.cpython-312.pyc +0 -0
  362. simo/users/migrations/__pycache__/0024_fingerprint.cpython-312.pyc +0 -0
  363. simo/users/migrations/__pycache__/0025_rename_name_fingerprint_type_and_more.cpython-312.pyc +0 -0
  364. simo/users/migrations/__pycache__/0026_fingerprint_name.cpython-312.pyc +0 -0
  365. simo/users/migrations/__pycache__/0027_permissionsrole_can_manage_components.cpython-312.pyc +0 -0
  366. simo/users/migrations/__pycache__/0028_auto_20240506_1146.cpython-312.pyc +0 -0
  367. simo/users/migrations/__pycache__/0029_alter_instanceuser_instance.cpython-312.pyc +0 -0
  368. simo/users/migrations/__pycache__/0030_userdevice_users.cpython-312.pyc +0 -0
  369. simo/users/migrations/__pycache__/0031_auto_20240923_1115.cpython-312.pyc +0 -0
  370. simo/users/migrations/__pycache__/0032_remove_userdevice_user_alter_userdevice_users.cpython-312.pyc +0 -0
  371. simo/users/migrations/__pycache__/0033_alter_user_ssh_key.cpython-312.pyc +0 -0
  372. simo/users/migrations/__pycache__/0034_instanceuser_last_seen_location_and_more.cpython-312.pyc +0 -0
  373. simo/users/migrations/__pycache__/0035_instanceuser_last_seen_speed_kmh_and_more.cpython-312.pyc +0 -0
  374. simo/users/migrations/__pycache__/0036_instanceuser_phone_on_charge_user_phone_on_charge.cpython-312.pyc +0 -0
  375. simo/users/migrations/__pycache__/0037_rename_last_seen_location_datetime_instanceuser_last_seen_and_more.cpython-312.pyc +0 -0
  376. simo/users/migrations/__pycache__/0038_userdevicereportlog_at_home_and_more.cpython-312.pyc +0 -0
  377. simo/users/migrations/__pycache__/0039_auto_20241117_1039.cpython-312.pyc +0 -0
  378. simo/users/migrations/__pycache__/0040_userdevicereportlog_location_smoothed_and_more.cpython-312.pyc +0 -0
  379. simo/users/migrations/__pycache__/0041_userdevicereportlog_speed_kmh_received.cpython-312.pyc +0 -0
  380. simo/users/migrations/__pycache__/0042_remove_userdevicereportlog_location_smoothed_and_more.cpython-312.pyc +0 -0
  381. simo/users/migrations/__pycache__/0043_userdevicereportlog_avg_speed_kmh.cpython-312.pyc +0 -0
  382. simo/users/migrations/__pycache__/0044_permissionsrole_is_person.cpython-312.pyc +0 -0
  383. simo/users/migrations/__pycache__/__init__.cpython-312.pyc +0 -0
  384. {simo-2.10.6.dist-info → simo-2.10.9.dist-info}/METADATA +1 -1
  385. {simo-2.10.6.dist-info → simo-2.10.9.dist-info}/RECORD +389 -337
  386. {simo-2.10.6.dist-info → simo-2.10.9.dist-info}/WHEEL +1 -1
  387. {simo-2.10.6.dist-info → simo-2.10.9.dist-info}/entry_points.txt +0 -0
  388. {simo-2.10.6.dist-info → simo-2.10.9.dist-info}/licenses/LICENSE.md +0 -0
  389. {simo-2.10.6.dist-info → simo-2.10.9.dist-info}/top_level.txt +0 -0
@@ -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
+ ]
simo/fleet/models.py CHANGED
@@ -1,6 +1,7 @@
1
1
  import requests
2
2
  import time
3
3
  import random
4
+ import datetime
4
5
  from actstream import action
5
6
  from django.core.exceptions import ValidationError
6
7
  from django.db import transaction
@@ -9,12 +10,17 @@ from django.db.models.signals import post_save, pre_delete, post_delete
9
10
  from django.dispatch import receiver
10
11
  from django.contrib.contenttypes.models import ContentType
11
12
  from django.contrib.contenttypes.fields import GenericForeignKey
13
+ from django.utils import timezone
12
14
  from dirtyfields import DirtyFieldsMixin
13
15
  from simo.core.models import Instance, Gateway, Component
14
16
  from simo.core.utils.helpers import get_random_string
15
17
  from simo.core.events import GatewayObjectCommand
16
18
  from .managers import ColonelsManager, ColonelPinsManager, InterfacesManager
17
- 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
+ # -------------------------------------------------------------------------
18
24
 
19
25
 
20
26
 
@@ -217,6 +223,7 @@ class ColonelPin(models.Model):
217
223
  capacitive = models.BooleanField(default=False, db_index=True)
218
224
  adc = models.BooleanField(default=False)
219
225
  native = models.BooleanField(default=True, db_index=True)
226
+ interface = models.PositiveIntegerField(null=True, blank=True)
220
227
  default_pull = models.CharField(
221
228
  max_length=50, db_index=True, null=True, blank=True,
222
229
  choices=(('LOW', "LOW"), ("HIGH", "HIGH"))
@@ -244,6 +251,12 @@ class ColonelPin(models.Model):
244
251
  if not self.label:
245
252
  # Might be created via migration...
246
253
  self.save()
254
+ if self.interface:
255
+ interface = Interface.objects.filter(
256
+ colonel=self.colonel, no=self.interface
257
+ ).first()
258
+ if interface:
259
+ return f"{self.label} - {interface.get_type_display()}"
247
260
  return self.label
248
261
 
249
262
  def save(self, *args, **kwargs):
@@ -266,7 +279,8 @@ def after_colonel_save(sender, instance, created, *args, **kwargs):
266
279
  defaults={
267
280
  'input': data.get('input'), 'output': data.get('output'),
268
281
  'capacitive': data.get('capacitive'), 'adc': data.get('adc'),
269
- 'native': data.get('native'), 'note': data.get('note')
282
+ 'native': data.get('native'), 'note': data.get('note'),
283
+ 'interface': data.get('interface')
270
284
  }
271
285
  )
272
286
  fleet_gateway, new = Gateway.objects.get_or_create(
@@ -274,15 +288,6 @@ def after_colonel_save(sender, instance, created, *args, **kwargs):
274
288
  )
275
289
  if fleet_gateway.status != 'running':
276
290
  fleet_gateway.start()
277
- # create i2c and dali interfaces automatically for game-changer boards
278
- if instance.type == 'game-changer':
279
- # occupy ports immediately
280
- Interface.objects.create(colonel=instance, no=1, type='i2c')
281
- Interface.objects.create(colonel=instance, no=2, type='dali')
282
- elif instance.type == 'game-changer-mini':
283
- # only create interfaces, but do not occupy ports
284
- Interface.objects.create(colonel=instance, no=1)
285
- Interface.objects.create(colonel=instance, no=2)
286
291
 
287
292
  if 'socket_connected' in instance.get_dirty_fields():
288
293
  if instance.socket_connected:
@@ -306,15 +311,26 @@ def post_component_save(sender, instance, created, *args, **kwargs):
306
311
  if not colonel:
307
312
  return
308
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
+
309
323
  from .controllers import (
310
324
  TTLock, DALILamp, DALIGearGroup, DALIRelay, DALIOccupancySensor,
311
325
  DALILightSensor, DALIButton,
312
- AirQualitySensor, TempHumSensor, AmbientLightSensor, RoomPresenceSensor
326
+ AirQualitySensor, TempHumSensor, AmbientLightSensor,
327
+ RoomPresenceSensor, RoomZonePresenceSensor
313
328
  )
314
329
  if instance.controller and instance.controller_cls in (
315
330
  TTLock, DALILamp, DALIGearGroup, DALIRelay, DALIOccupancySensor,
316
331
  DALILightSensor, DALIButton,
317
- AirQualitySensor, TempHumSensor, AmbientLightSensor, RoomPresenceSensor
332
+ AirQualitySensor, TempHumSensor, AmbientLightSensor,
333
+ RoomPresenceSensor, RoomZonePresenceSensor
318
334
  ):
319
335
  return
320
336
  colonel.rebuild_occupied_pins()
@@ -326,6 +342,9 @@ def post_component_save(sender, instance, created, *args, **kwargs):
326
342
  @receiver(pre_delete, sender=Component)
327
343
  def post_component_delete(sender, instance, *args, **kwargs):
328
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)
329
348
  return
330
349
 
331
350
  from .controllers import DALIGearGroup
@@ -357,6 +376,9 @@ def post_component_delete(sender, instance, *args, **kwargs):
357
376
 
358
377
  transaction.on_commit(update_colonel)
359
378
 
379
+ # Finally release any InterfaceAddress rows occupied by this component
380
+ _release_interface_addresses(instance)
381
+
360
382
 
361
383
  class Interface(models.Model):
362
384
  colonel = models.ForeignKey(
@@ -391,27 +413,48 @@ class Interface(models.Model):
391
413
  def save(self, *args, **kwargs):
392
414
  if not self.pin_a:
393
415
  self.pin_a = ColonelPin.objects.get(
394
- colonel=self.colonel, no=INTERFACES_PINS_MAP[self.no][0]
416
+ colonel=self.colonel,
417
+ no=INTERFACES_PINS_MAP[self.no][0],
395
418
  )
396
419
  if not self.pin_b:
397
420
  self.pin_b = ColonelPin.objects.get(
398
- colonel=self.colonel, no=INTERFACES_PINS_MAP[self.no][1]
421
+ colonel=self.colonel,
422
+ no=INTERFACES_PINS_MAP[self.no][1],
399
423
  )
400
- if self.type:
401
- for pin_no in INTERFACES_PINS_MAP[self.no]:
402
- cpin = ColonelPin.objects.get(colonel=self.colonel, no=pin_no)
403
- if cpin.occupied_by and cpin.occupied_by != self:
404
- raise ValidationError(
405
- f"Interface can not be created, because "
406
- f"{cpin} is already occupied by {cpin.occupied_by}."
407
- )
408
- self.pin_a.occupied_by = self
409
- self.pin_b.occupied_by = self
410
- else:
411
- self.pin_a.occupied_by = None
412
- self.pin_b.occupied_by = None
413
424
 
414
- 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
415
458
 
416
459
  def broadcast_reset(self):
417
460
  from .gateways import FleetGatewayHandler
@@ -470,6 +513,11 @@ def post_interface_save(sender, instance, created, *args, **kwargs):
470
513
  address=addr,
471
514
  )
472
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()
473
521
  for addr in range(64):
474
522
  InterfaceAddress.objects.get_or_create(
475
523
  interface=instance, address_type='dali-gear',
@@ -483,17 +531,33 @@ def post_interface_save(sender, instance, created, *args, **kwargs):
483
531
  else:
484
532
  InterfaceAddress.objects.filter(interface=instance).delete()
485
533
 
534
+ try:
535
+ instance.colonel.update_config()
536
+ except Exception:
537
+ # Fail silently – configuration push should not prevent saving.
538
+ pass
539
+
486
540
 
487
541
 
488
542
  @receiver(post_delete, sender=Interface)
489
543
  def post_interface_delete(sender, instance, *args, **kwargs):
544
+ """Release GPIO pins that were occupied by the removed Interface."""
490
545
  with transaction.atomic():
491
546
  ct = ContentType.objects.get_for_model(instance)
492
- for pin in ColonelPin.objects.filter(
493
- occupied_by_content_type=ct,
494
- occupied_by_id=instance.id
495
- ):
496
- 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
+ )
497
561
 
498
562
 
499
563
  class CustomDaliDevice(models.Model):
@@ -502,6 +566,7 @@ class CustomDaliDevice(models.Model):
502
566
  not compatible with anything else of DALI!
503
567
  '''
504
568
  instance = models.ForeignKey(Instance, on_delete=models.CASCADE)
569
+ uid = models.CharField(max_length=100, db_index=True)
505
570
  random_address = models.PositiveIntegerField(db_index=True, editable=False)
506
571
  name = models.CharField(
507
572
  max_length=200, help_text="User given name on initial pairing"
@@ -512,6 +577,7 @@ class CustomDaliDevice(models.Model):
512
577
  help_text="Colonel interface on which it operates."
513
578
  )
514
579
  last_seen = models.DateTimeField(null=True, editable=False)
580
+ components = models.ManyToManyField(Component)
515
581
 
516
582
  class Meta:
517
583
  unique_together = 'instance', 'random_address'
@@ -538,3 +604,27 @@ class CustomDaliDevice(models.Model):
538
604
  msg=frame.pack.hex()
539
605
  ).publish()
540
606
 
607
+ @property
608
+ def is_alive(self):
609
+ if not self.last_seen:
610
+ return False
611
+ return self.last_seen + datetime.timedelta(seconds=60) > timezone.now()
612
+
613
+
614
+ @receiver(post_save, sender=Component)
615
+ def attatch_components_to_dali_device(sender, instance, created, *args, **kwargs):
616
+ if not instance.controller_uid.startswith('simo.fleet'):
617
+ return
618
+ if 'config' not in instance.get_dirty_fields():
619
+ return
620
+ dali_device = CustomDaliDevice.objects.filter(
621
+ id=instance.config.get('dali_device', 0)
622
+ ).first()
623
+ if not dali_device:
624
+ return
625
+ dali_device.components.add(instance)
626
+
627
+
628
+ @receiver(pre_delete, sender=CustomDaliDevice)
629
+ def delete_dali_device_components(sender, instance, *args, **kwargs):
630
+ instance.components.all().delete()
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:
@@ -89,12 +89,48 @@ class ColonelSerializer(serializers.ModelSerializer):
89
89
 
90
90
 
91
91
  class CustomDaliDeviceSerializer(serializers.ModelSerializer):
92
+ is_empty = serializers.SerializerMethodField()
93
+ is_alive = serializers.SerializerMethodField()
94
+ last_seen = TimestampField(read_only=True)
92
95
 
93
96
  class Meta:
94
97
  model = CustomDaliDevice
95
- fields = 'id', 'random_address', 'name'
96
- read_only_fields = 'random_address',
98
+ fields = (
99
+ 'id', 'uid', 'random_address', 'name', 'is_empty',
100
+ 'is_alive', 'last_seen'
101
+ )
102
+ read_only_fields = (
103
+ 'random_address', 'is_empty', 'is_alive', 'last_seen'
104
+ )
105
+
106
+ def validate(self, data):
107
+ instance = self.context.get('instance')
108
+ uid = data.get('uid')
109
+ if instance and uid:
110
+ if CustomDaliDevice.objects.filter(
111
+ uid=uid, instance=instance
112
+ ).exists():
113
+ raise serializers.ValidationError(
114
+ f"A device with uid '{uid}' already exists for this instance."
115
+ )
116
+ return data
117
+
118
+ def validate_uid(self, value):
119
+ """
120
+ Prevent changing the uid on update.
121
+ """
122
+ # self.instance will be None for creation, but set for updates.
123
+ if self.instance and self.instance.uid != value:
124
+ raise serializers.ValidationError("Changing uid is not allowed.")
125
+ return value
97
126
 
98
127
  def create(self, validated_data):
99
128
  validated_data['instance'] = self.context['instance']
100
- return super().create(validated_data)
129
+ return super().create(validated_data)
130
+
131
+ def get_is_empty(self, obj):
132
+ return not bool(obj.components.all().count())
133
+
134
+ def get_is_alive(self, obj):
135
+ return obj.is_alive
136
+