simo 1.7.20__py3-none-any.whl → 2.0.0__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (267) hide show
  1. simo/__pycache__/asgi.cpython-38.pyc +0 -0
  2. simo/__pycache__/settings.cpython-38.pyc +0 -0
  3. simo/__pycache__/urls.cpython-38.pyc +0 -0
  4. simo/__pycache__/wsgi.cpython-38.pyc +0 -0
  5. simo/core/__pycache__/admin.cpython-38.pyc +0 -0
  6. simo/core/__pycache__/api.cpython-38.pyc +0 -0
  7. simo/core/__pycache__/api_meta.cpython-38.pyc +0 -0
  8. simo/core/__pycache__/auto_urls.cpython-38.pyc +0 -0
  9. simo/core/__pycache__/autocomplete_views.cpython-38.pyc +0 -0
  10. simo/core/__pycache__/base_types.cpython-38.pyc +0 -0
  11. simo/core/__pycache__/context.cpython-38.pyc +0 -0
  12. simo/core/__pycache__/controllers.cpython-38.pyc +0 -0
  13. simo/core/__pycache__/events.cpython-38.pyc +0 -0
  14. simo/core/__pycache__/forms.cpython-38.pyc +0 -0
  15. simo/core/__pycache__/gateways.cpython-38.pyc +0 -0
  16. simo/core/__pycache__/managers.cpython-38.pyc +0 -0
  17. simo/core/__pycache__/middleware.cpython-38.pyc +0 -0
  18. simo/core/__pycache__/models.cpython-38.pyc +0 -0
  19. simo/core/__pycache__/permissions.cpython-38.pyc +0 -0
  20. simo/core/__pycache__/serializers.cpython-38.pyc +0 -0
  21. simo/core/__pycache__/signal_receivers.cpython-38.pyc +0 -0
  22. simo/core/__pycache__/socket_consumers.cpython-38.pyc +0 -0
  23. simo/core/__pycache__/tasks.cpython-38.pyc +0 -0
  24. simo/core/__pycache__/views.cpython-38.pyc +0 -0
  25. simo/core/admin.py +28 -18
  26. simo/core/api.py +157 -16
  27. simo/core/api_meta.py +87 -0
  28. simo/core/auto_urls.py +4 -1
  29. simo/core/autocomplete_views.py +8 -4
  30. simo/core/base_types.py +1 -0
  31. simo/core/context.py +3 -1
  32. simo/core/controllers.py +112 -32
  33. simo/core/db_backend/base.py +7 -22
  34. simo/core/drf_braces/README +3 -0
  35. simo/core/drf_braces/__init__.py +7 -0
  36. simo/core/drf_braces/__pycache__/__init__.cpython-38.pyc +0 -0
  37. simo/core/drf_braces/__pycache__/utils.cpython-38.pyc +0 -0
  38. simo/core/drf_braces/fields/__init__.py +5 -0
  39. simo/core/drf_braces/fields/__pycache__/__init__.cpython-38.pyc +0 -0
  40. simo/core/drf_braces/fields/__pycache__/_fields.cpython-38.pyc +0 -0
  41. simo/core/drf_braces/fields/__pycache__/custom.cpython-38.pyc +0 -0
  42. simo/core/drf_braces/fields/__pycache__/mixins.cpython-38.pyc +0 -0
  43. simo/core/drf_braces/fields/__pycache__/modified.cpython-38.pyc +0 -0
  44. simo/core/drf_braces/fields/_fields.py +48 -0
  45. simo/core/drf_braces/fields/custom.py +107 -0
  46. simo/core/drf_braces/fields/mixins.py +58 -0
  47. simo/core/drf_braces/fields/modified.py +41 -0
  48. simo/core/drf_braces/forms/__init__.py +0 -0
  49. simo/core/drf_braces/forms/fields.py +20 -0
  50. simo/core/drf_braces/forms/serializer_form.py +156 -0
  51. simo/core/drf_braces/mixins.py +52 -0
  52. simo/core/drf_braces/models.py +0 -0
  53. simo/core/drf_braces/parsers.py +72 -0
  54. simo/core/drf_braces/renderers.py +37 -0
  55. simo/core/drf_braces/serializers/__init__.py +0 -0
  56. simo/core/drf_braces/serializers/__pycache__/__init__.cpython-38.pyc +0 -0
  57. simo/core/drf_braces/serializers/__pycache__/form_serializer.cpython-38.pyc +0 -0
  58. simo/core/drf_braces/serializers/enforce_validation_serializer.py +214 -0
  59. simo/core/drf_braces/serializers/form_serializer.py +391 -0
  60. simo/core/drf_braces/serializers/swapping.py +48 -0
  61. simo/core/drf_braces/tests/__init__.py +0 -0
  62. simo/core/drf_braces/tests/fields/__init__.py +0 -0
  63. simo/core/drf_braces/tests/fields/test_custom.py +94 -0
  64. simo/core/drf_braces/tests/fields/test_fields.py +13 -0
  65. simo/core/drf_braces/tests/fields/test_mixins.py +96 -0
  66. simo/core/drf_braces/tests/fields/test_modified.py +40 -0
  67. simo/core/drf_braces/tests/forms/__init__.py +0 -0
  68. simo/core/drf_braces/tests/forms/test_fields.py +46 -0
  69. simo/core/drf_braces/tests/forms/test_serializer_form.py +256 -0
  70. simo/core/drf_braces/tests/serializers/__init__.py +0 -0
  71. simo/core/drf_braces/tests/serializers/test_enforce_validation_serializer.py +169 -0
  72. simo/core/drf_braces/tests/serializers/test_form_serializer.py +387 -0
  73. simo/core/drf_braces/tests/serializers/test_swapping.py +40 -0
  74. simo/core/drf_braces/tests/test_mixins.py +111 -0
  75. simo/core/drf_braces/tests/test_parsers.py +73 -0
  76. simo/core/drf_braces/tests/test_renderers.py +23 -0
  77. simo/core/drf_braces/tests/test_utils.py +73 -0
  78. simo/core/drf_braces/utils.py +209 -0
  79. simo/core/events.py +3 -3
  80. simo/core/forms.py +79 -37
  81. simo/core/gateways.py +31 -14
  82. simo/core/management/commands/gateways_manager.py +0 -1
  83. simo/core/managers.py +81 -0
  84. simo/core/middleware.py +25 -0
  85. simo/core/migrations/0026_category_instance.py +20 -0
  86. simo/core/migrations/0027_remove_component_tags.py +17 -0
  87. simo/core/migrations/0028_rename_subcomponents_component_slaves.py +18 -0
  88. simo/core/migrations/0029_auto_20240229_1331.py +33 -0
  89. simo/core/migrations/__pycache__/0026_category_instance.cpython-38.pyc +0 -0
  90. simo/core/migrations/__pycache__/0027_remove_component_tags.cpython-38.pyc +0 -0
  91. simo/core/migrations/__pycache__/0028_rename_subcomponents_component_slaves.cpython-38.pyc +0 -0
  92. simo/core/migrations/__pycache__/0029_auto_20240229_1331.cpython-38.pyc +0 -0
  93. simo/core/models.py +103 -66
  94. simo/core/permissions.py +28 -2
  95. simo/core/serializers.py +330 -26
  96. simo/core/socket_consumers.py +5 -14
  97. simo/core/tasks.py +11 -1
  98. simo/core/templates/admin/base.html +37 -10
  99. simo/core/templates/admin/wizard/discovery.html +188 -0
  100. simo/core/templates/admin/wizard/wizard_add.html +5 -5
  101. simo/core/utils/__pycache__/serialization.cpython-38.pyc +0 -0
  102. simo/core/utils/admin.py +9 -2
  103. simo/core/utils/formsets.py +17 -16
  104. simo/core/utils/helpers.py +1 -0
  105. simo/core/utils/serialization.py +56 -0
  106. simo/core/utils/type_constants.py +1 -1
  107. simo/core/utils/validators.py +14 -1
  108. simo/core/views.py +13 -0
  109. simo/fleet/__pycache__/admin.cpython-38.pyc +0 -0
  110. simo/fleet/__pycache__/api.cpython-38.pyc +0 -0
  111. simo/fleet/__pycache__/auto_urls.cpython-38.pyc +0 -0
  112. simo/fleet/__pycache__/controllers.cpython-38.pyc +0 -0
  113. simo/fleet/__pycache__/forms.cpython-38.pyc +0 -0
  114. simo/fleet/__pycache__/gateways.cpython-38.pyc +0 -0
  115. simo/fleet/__pycache__/managers.cpython-38.pyc +0 -0
  116. simo/fleet/__pycache__/models.cpython-38.pyc +0 -0
  117. simo/fleet/__pycache__/serializers.cpython-38.pyc +0 -0
  118. simo/fleet/__pycache__/socket_consumers.cpython-38.pyc +0 -0
  119. simo/fleet/__pycache__/utils.cpython-38.pyc +0 -0
  120. simo/fleet/__pycache__/views.cpython-38.pyc +0 -0
  121. simo/fleet/admin.py +54 -25
  122. simo/fleet/api.py +59 -3
  123. simo/fleet/auto_urls.py +2 -3
  124. simo/fleet/controllers.py +199 -16
  125. simo/fleet/forms.py +325 -483
  126. simo/fleet/gateways.py +44 -2
  127. simo/fleet/managers.py +32 -0
  128. simo/fleet/migrations/0025_auto_20240130_1334.py +27 -0
  129. simo/fleet/migrations/0026_rename_i2cinterface_scl_pin_and_more.py +64 -0
  130. simo/fleet/migrations/0027_auto_20240306_0802.py +170 -0
  131. simo/fleet/migrations/0028_remove_i2cinterface_scl_pin_no_and_more.py +21 -0
  132. simo/fleet/migrations/0029_alter_i2cinterface_scl_pin_and_more.py +24 -0
  133. simo/fleet/migrations/0030_colonelpin_label_alter_colonel_type.py +24 -0
  134. simo/fleet/migrations/0031_alter_colonel_type.py +18 -0
  135. simo/fleet/migrations/__pycache__/0025_auto_20240130_1334.cpython-38.pyc +0 -0
  136. simo/fleet/migrations/__pycache__/0026_rename_i2cinterface_scl_pin_and_more.cpython-38.pyc +0 -0
  137. simo/fleet/migrations/__pycache__/0027_auto_20240306_0802.cpython-38.pyc +0 -0
  138. simo/fleet/migrations/__pycache__/0028_remove_i2cinterface_scl_pin_no_and_more.cpython-38.pyc +0 -0
  139. simo/fleet/migrations/__pycache__/0029_alter_i2cinterface_scl_pin_and_more.cpython-38.pyc +0 -0
  140. simo/fleet/migrations/__pycache__/0030_colonelpin_label_alter_colonel_type.cpython-38.pyc +0 -0
  141. simo/fleet/migrations/__pycache__/0031_alter_colonel_type.cpython-38.pyc +0 -0
  142. simo/fleet/models.py +134 -82
  143. simo/fleet/serializers.py +35 -1
  144. simo/fleet/socket_consumers.py +239 -76
  145. simo/fleet/utils.py +15 -53
  146. simo/fleet/views.py +28 -14
  147. simo/generic/controllers.py +13 -89
  148. simo/generic/forms.py +29 -18
  149. simo/generic/gateways.py +73 -2
  150. simo/generic/models.py +3 -3
  151. simo/multimedia/controllers.py +9 -8
  152. simo/settings.py +7 -4
  153. simo/urls.py +4 -8
  154. simo/users/__pycache__/admin.cpython-38.pyc +0 -0
  155. simo/users/__pycache__/api.cpython-38.pyc +0 -0
  156. simo/users/__pycache__/auto_urls.cpython-38.pyc +0 -0
  157. simo/users/__pycache__/models.cpython-38.pyc +0 -0
  158. simo/users/__pycache__/serializers.cpython-38.pyc +0 -0
  159. simo/users/__pycache__/sso_urls.cpython-38.pyc +0 -0
  160. simo/users/admin.py +8 -1
  161. simo/users/api.py +38 -2
  162. simo/users/auto_urls.py +2 -2
  163. simo/users/migrations/0025_rename_name_fingerprint_type_and_more.py +22 -0
  164. simo/users/migrations/__pycache__/0025_rename_name_fingerprint_type_and_more.cpython-38.pyc +0 -0
  165. simo/users/models.py +2 -3
  166. simo/users/serializers.py +15 -1
  167. simo/users/sso_urls.py +3 -3
  168. simo/wsgi.py +7 -0
  169. {simo-1.7.20.dist-info → simo-2.0.0.dist-info}/METADATA +8 -9
  170. {simo-1.7.20.dist-info → simo-2.0.0.dist-info}/RECORD +173 -189
  171. {simo-1.7.20.dist-info → simo-2.0.0.dist-info}/WHEEL +1 -1
  172. simo/core/db_backend/__pycache__/__init__.cpython-38.pyc +0 -0
  173. simo/core/db_backend/__pycache__/base.cpython-38.pyc +0 -0
  174. simo/core/management/commands/__pycache__/gateways_manager.cpython-38.pyc +0 -0
  175. simo/core/migrations/__pycache__/0001_initial.cpython-38.pyc +0 -0
  176. simo/core/migrations/__pycache__/0002_load_icons.cpython-38.pyc +0 -0
  177. simo/core/migrations/__pycache__/0003_create_default_zones_and_categories.cpython-38.pyc +0 -0
  178. simo/core/migrations/__pycache__/0004_create_generic.cpython-38.pyc +0 -0
  179. simo/core/migrations/__pycache__/0005_component_subcomponents.cpython-38.pyc +0 -0
  180. simo/core/migrations/__pycache__/0006_alter_component_subcomponents.cpython-38.pyc +0 -0
  181. simo/core/migrations/__pycache__/0007_component_change_init_to.cpython-38.pyc +0 -0
  182. simo/core/migrations/__pycache__/0008_alter_component_change_init_to.cpython-38.pyc +0 -0
  183. simo/core/migrations/__pycache__/0009_auto_20220707_1404.cpython-38.pyc +0 -0
  184. simo/core/migrations/__pycache__/0010_historyaggregate.cpython-38.pyc +0 -0
  185. simo/core/migrations/__pycache__/0011_component_last_change.cpython-38.pyc +0 -0
  186. simo/core/migrations/__pycache__/0012_instance.cpython-38.pyc +0 -0
  187. simo/core/migrations/__pycache__/0013_auto_20231003_0754.cpython-38.pyc +0 -0
  188. simo/core/migrations/__pycache__/0014_zone_instance.cpython-38.pyc +0 -0
  189. simo/core/migrations/__pycache__/0015_auto_20231004_1113.cpython-38.pyc +0 -0
  190. simo/core/migrations/__pycache__/0016_auto_20231004_1113.cpython-38.pyc +0 -0
  191. simo/core/migrations/__pycache__/0017_auto_20231004_1313.cpython-38.pyc +0 -0
  192. simo/core/migrations/__pycache__/0018_auto_20231005_0622.cpython-38.pyc +0 -0
  193. simo/core/migrations/__pycache__/0019_alter_gateway_type.cpython-38.pyc +0 -0
  194. simo/core/migrations/__pycache__/0020_component_meta.cpython-38.pyc +0 -0
  195. simo/core/migrations/__pycache__/0021_auto_20231020_1041.cpython-38.pyc +0 -0
  196. simo/core/migrations/__pycache__/__init__.cpython-38.pyc +0 -0
  197. simo/core/templatetags/__pycache__/__init__.cpython-38.pyc +0 -0
  198. simo/core/templatetags/__pycache__/components_list.cpython-38.pyc +0 -0
  199. simo/core/utils/__pycache__/__init__.cpython-38.pyc +0 -0
  200. simo/core/utils/__pycache__/admin.cpython-38.pyc +0 -0
  201. simo/core/utils/__pycache__/config_values.cpython-38.pyc +0 -0
  202. simo/core/utils/__pycache__/easing.cpython-38.pyc +0 -0
  203. simo/core/utils/__pycache__/form_fields.cpython-38.pyc +0 -0
  204. simo/core/utils/__pycache__/form_widgets.cpython-38.pyc +0 -0
  205. simo/core/utils/__pycache__/formsets.cpython-38.pyc +0 -0
  206. simo/core/utils/__pycache__/helpers.cpython-38.pyc +0 -0
  207. simo/core/utils/__pycache__/logs.cpython-38.pyc +0 -0
  208. simo/core/utils/__pycache__/mixins.cpython-38.pyc +0 -0
  209. simo/core/utils/__pycache__/model_helpers.cpython-38.pyc +0 -0
  210. simo/core/utils/__pycache__/relay.cpython-38.pyc +0 -0
  211. simo/core/utils/__pycache__/type_constants.cpython-38.pyc +0 -0
  212. simo/core/utils/__pycache__/validators.cpython-38.pyc +0 -0
  213. simo/fleet/tasks.py +0 -25
  214. simo/generic/__pycache__/__init__.cpython-37.pyc +0 -0
  215. simo/generic/__pycache__/__init__.cpython-38.pyc +0 -0
  216. simo/generic/__pycache__/app_widgets.cpython-38.pyc +0 -0
  217. simo/generic/__pycache__/base_types.cpython-38.pyc +0 -0
  218. simo/generic/__pycache__/controllers.cpython-37.pyc +0 -0
  219. simo/generic/__pycache__/controllers.cpython-38.pyc +0 -0
  220. simo/generic/__pycache__/forms.cpython-38.pyc +0 -0
  221. simo/generic/__pycache__/gateways.cpython-38.pyc +0 -0
  222. simo/generic/__pycache__/models.cpython-38.pyc +0 -0
  223. simo/generic/__pycache__/routing.cpython-38.pyc +0 -0
  224. simo/generic/__pycache__/socket_consumers.cpython-38.pyc +0 -0
  225. simo/generic/__pycache__/widgets.cpython-37.pyc +0 -0
  226. simo/multimedia/__pycache__/__init__.cpython-38.pyc +0 -0
  227. simo/multimedia/__pycache__/admin.cpython-38.pyc +0 -0
  228. simo/multimedia/__pycache__/api.cpython-38.pyc +0 -0
  229. simo/multimedia/__pycache__/app_widgets.cpython-38.pyc +0 -0
  230. simo/multimedia/__pycache__/base_types.cpython-38.pyc +0 -0
  231. simo/multimedia/__pycache__/controllers.cpython-38.pyc +0 -0
  232. simo/multimedia/__pycache__/forms.cpython-38.pyc +0 -0
  233. simo/multimedia/__pycache__/models.cpython-38.pyc +0 -0
  234. simo/multimedia/__pycache__/serializers.cpython-38.pyc +0 -0
  235. simo/multimedia/migrations/__pycache__/0001_initial.cpython-38.pyc +0 -0
  236. simo/multimedia/migrations/__pycache__/0002_sound_length.cpython-38.pyc +0 -0
  237. simo/multimedia/migrations/__pycache__/0003_alter_sound_length.cpython-38.pyc +0 -0
  238. simo/multimedia/migrations/__pycache__/0004_auto_20231023_1055.cpython-38.pyc +0 -0
  239. simo/multimedia/migrations/__pycache__/__init__.cpython-38.pyc +0 -0
  240. simo/notifications/__pycache__/__init__.cpython-38.pyc +0 -0
  241. simo/notifications/__pycache__/admin.cpython-38.pyc +0 -0
  242. simo/notifications/__pycache__/api.cpython-38.pyc +0 -0
  243. simo/notifications/__pycache__/models.cpython-38.pyc +0 -0
  244. simo/notifications/__pycache__/serializers.cpython-38.pyc +0 -0
  245. simo/notifications/__pycache__/utils.cpython-38.pyc +0 -0
  246. simo/notifications/migrations/__pycache__/0001_initial.cpython-38.pyc +0 -0
  247. simo/notifications/migrations/__pycache__/0002_notification_instance.cpython-38.pyc +0 -0
  248. simo/notifications/migrations/__pycache__/__init__.cpython-38.pyc +0 -0
  249. simo/users/migrations/__pycache__/0001_initial.cpython-38.pyc +0 -0
  250. simo/users/migrations/__pycache__/0002_componentpermission.cpython-38.pyc +0 -0
  251. simo/users/migrations/__pycache__/0003_create_roles_and_system_user.cpython-38.pyc +0 -0
  252. simo/users/migrations/__pycache__/0004_user_secret_key.cpython-38.pyc +0 -0
  253. simo/users/migrations/__pycache__/0005_permissionsrole_instance.cpython-38.pyc +0 -0
  254. simo/users/migrations/__pycache__/0006_auto_20231003_0850.cpython-38.pyc +0 -0
  255. simo/users/migrations/__pycache__/0007_auto_20231003_1228.cpython-38.pyc +0 -0
  256. simo/users/migrations/__pycache__/0008_auto_20231003_1229.cpython-38.pyc +0 -0
  257. simo/users/migrations/__pycache__/0009_remove_user_role.cpython-38.pyc +0 -0
  258. simo/users/migrations/__pycache__/0010_auto_20231004_1313.cpython-38.pyc +0 -0
  259. simo/users/migrations/__pycache__/0011_auto_20231004_1313.cpython-38.pyc +0 -0
  260. simo/users/migrations/__pycache__/0012_alter_userinstancerole_unique_together.cpython-38.pyc +0 -0
  261. simo/users/migrations/__pycache__/0013_remove_user_roles.cpython-38.pyc +0 -0
  262. simo/users/migrations/__pycache__/0014_user_roles.cpython-38.pyc +0 -0
  263. simo/users/migrations/__pycache__/0015_remove_user_at_home.cpython-38.pyc +0 -0
  264. simo/users/migrations/__pycache__/0016_auto_20231005_1050.cpython-38.pyc +0 -0
  265. simo/users/migrations/__pycache__/__init__.cpython-38.pyc +0 -0
  266. {simo-1.7.20.dist-info → simo-2.0.0.dist-info}/LICENSE.md +0 -0
  267. {simo-1.7.20.dist-info → simo-2.0.0.dist-info}/top_level.txt +0 -0
@@ -63,7 +63,7 @@ class Script(ControllerBase, TimerMixin):
63
63
  )
64
64
  return value
65
65
 
66
- def _send_to_device(self, value):
66
+ def _prepare_for_send(self, value):
67
67
  if value == 'start':
68
68
  new_code = getattr(self.component, 'new_code', None)
69
69
  if new_code:
@@ -71,7 +71,7 @@ class Script(ControllerBase, TimerMixin):
71
71
  self.component.refresh_from_db()
72
72
  self.component.config['code'] = new_code
73
73
  self.component.save(update_fields=['config'])
74
- return super()._send_to_device(value)
74
+ return value
75
75
 
76
76
  def _val_to_success(self, value):
77
77
  if value == 'start':
@@ -208,9 +208,13 @@ class Thermostat(ControllerBase):
208
208
  heater = Component.objects.filter(
209
209
  pk=self.component.config.get('heater')
210
210
  ).first()
211
+ if heater:
212
+ heater.prepare_controller()
211
213
  cooler = Component.objects.filter(
212
214
  pk=self.component.config.get('cooler')
213
215
  ).first()
216
+ if cooler:
217
+ cooler.prepare_controller()
214
218
 
215
219
  if not temperature_sensor or not temperature_sensor.alive:
216
220
  print(f"No temperature sensor on {self.component}!")
@@ -322,47 +326,6 @@ class AlarmGroup(ControllerBase):
322
326
  )
323
327
  return value
324
328
 
325
- def _send_to_device(self, value):
326
- assert value in ('armed', 'disarmed')
327
-
328
- other_alarm_groups = {}
329
- stats = {
330
- 'disarmed': 0, 'pending-arm': 0, 'armed': 0, 'breached': 0
331
- }
332
-
333
- for c_id in self.component.config['components']:
334
- slave = Component.objects.filter(pk=c_id).first()
335
- if not slave:
336
- continue
337
- if value == 'armed':
338
- if not slave.is_in_alarm():
339
- slave.arm_status = 'armed'
340
- stats['armed'] += 1
341
- else:
342
- slave.arm_status = 'pending-arm'
343
- stats['pending-arm'] += 1
344
- elif value == 'disarmed':
345
- stats['disarmed'] += 1
346
- slave.arm_status = 'disarmed'
347
-
348
- slave.do_not_update_alarm_group = True
349
- slave.save(update_fields=['arm_status'])
350
-
351
- for other_group in Component.objects.filter(
352
- controller_uid=AlarmGroup.uid,
353
- config__components__contains=slave.id
354
- ).exclude(pk=self.component.pk):
355
- other_alarm_groups[other_group.pk] = other_group
356
-
357
- self.component.value = value
358
- if stats['pending-arm']:
359
- self.component.value = 'pending-arm'
360
- self.component.config['stats'] = stats
361
- self.component.save()
362
-
363
- for pk, other_group in other_alarm_groups.items():
364
- other_group.refresh_status()
365
-
366
329
  def arm(self):
367
330
  self.send('armed')
368
331
 
@@ -375,7 +338,6 @@ class AlarmGroup(ControllerBase):
375
338
  )
376
339
 
377
340
  def refresh_status(self):
378
-
379
341
  stats = {
380
342
  'disarmed': 0, 'pending-arm': 0, 'armed': 0, 'breached': 0
381
343
  }
@@ -515,21 +477,6 @@ class Gate(ControllerBase, TimerMixin):
515
477
  self.component.set(self.component.value + '_moving')
516
478
  threading.Thread(target=cancel_move, daemon=True).start()
517
479
 
518
- def _send_to_device(self, value):
519
- switch = Component.objects.filter(
520
- pk=self.component.config.get('action_switch')
521
- ).first()
522
- if not switch:
523
- return
524
-
525
- if self.component.config.get('action_method') == 'click':
526
- switch.click()
527
- else:
528
- if value == 'open':
529
- switch.turn_on()
530
- else:
531
- switch.turn_off()
532
-
533
480
  def open(self):
534
481
  self.send('open')
535
482
 
@@ -662,9 +609,6 @@ class Blinds(ControllerBase, TimerMixin):
662
609
 
663
610
  return value
664
611
 
665
- def _send_to_device(self, value):
666
- GatewayObjectCommand(self.component, **{'set_val': value}).publish()
667
-
668
612
  def open(self):
669
613
  self.send(0)
670
614
 
@@ -776,6 +720,7 @@ class Watering(ControllerBase):
776
720
  switch = Component.objects.get(pk=contour_data['switch'])
777
721
  except Component.DoesNotExist:
778
722
  continue
723
+ switch.prepare_controller()
779
724
  if run:
780
725
  if switch.timer_engaged():
781
726
  switch.stop_timer()
@@ -900,6 +845,7 @@ class Watering(ControllerBase):
900
845
  switch = Component.objects.get(pk=contour_data['switch'])
901
846
  except Component.DoesNotExist:
902
847
  continue
848
+ switch.prepare_controller()
903
849
  if switch.timer_engaged():
904
850
  switch.stop_timer()
905
851
  switch.turn_off()
@@ -1131,6 +1077,7 @@ class AlarmClock(ControllerBase):
1131
1077
  print(f"Reverse event {event['uid']}!")
1132
1078
  comp = Component.objects.filter(id=event['component']).first()
1133
1079
  if comp:
1080
+ comp.prepare_controller()
1134
1081
  if forward:
1135
1082
  action_name = 'play_action'
1136
1083
  else:
@@ -1370,7 +1317,8 @@ class AlarmClock(ControllerBase):
1370
1317
  return current_value
1371
1318
 
1372
1319
 
1373
- # ----------- Dummy controllers -----------------------------
1320
+ # ----------- Dummy controllers -----------------------------
1321
+
1374
1322
  class StateSelect(ControllerBase):
1375
1323
  gateway_class = DummyGatewayHandler
1376
1324
  name = _("State select")
@@ -1387,9 +1335,6 @@ class StateSelect(ControllerBase):
1387
1335
  raise ValidationError("Unsupported value!")
1388
1336
  return value
1389
1337
 
1390
- def _send_to_device(self, value):
1391
- self.component.set(value)
1392
-
1393
1338
 
1394
1339
  class DummyBinarySensor(BinarySensor):
1395
1340
  gateway_class = DummyGatewayHandler
@@ -1406,56 +1351,35 @@ class DummyMultiSensor(MultiSensor):
1406
1351
  class DummySwitch(Switch):
1407
1352
  gateway_class = DummyGatewayHandler
1408
1353
 
1409
- def _send_to_device(self, value):
1410
- self.component.set(value)
1411
-
1412
1354
 
1413
1355
  class DummyDoubleSwitch(DoubleSwitch):
1414
1356
  gateway_class = DummyGatewayHandler
1415
1357
 
1416
- def _send_to_device(self, value):
1417
- self.component.set(value)
1418
-
1419
1358
 
1420
1359
  class DummyTripleSwitch(TripleSwitch):
1421
1360
  gateway_class = DummyGatewayHandler
1422
1361
 
1423
- def _send_to_device(self, value):
1424
- self.component.set(value)
1425
-
1426
1362
 
1427
1363
  class DummyQuadrupleSwitch(QuadrupleSwitch):
1428
1364
  gateway_class = DummyGatewayHandler
1429
1365
 
1430
- def _send_to_device(self, value):
1431
- self.component.set(value)
1432
-
1433
1366
 
1434
1367
  class DummyQuintupleSwitch(QuintupleSwitch):
1435
1368
  gateway_class = DummyGatewayHandler
1436
1369
 
1437
- def _send_to_device(self, value):
1438
- self.component.set(value)
1439
-
1440
1370
 
1441
1371
  class DummyDimmer(Dimmer):
1442
1372
  gateway_class = DummyGatewayHandler
1443
1373
 
1444
- def _send_to_device(self, value):
1374
+ def _prepare_for_send(self, value):
1445
1375
  if self.component.config.get('inverse'):
1446
1376
  value = self.component.config.get('max') - value
1447
- self.component.set(value)
1377
+ return value
1448
1378
 
1449
1379
 
1450
1380
  class DummyDimmerPlus(DimmerPlus):
1451
1381
  gateway_class = DummyGatewayHandler
1452
1382
 
1453
- def _send_to_device(self, value):
1454
- self.component.set(value)
1455
-
1456
1383
 
1457
1384
  class DummyRGBWLight(RGBWLight):
1458
1385
  gateway_class = DummyGatewayHandler
1459
-
1460
- def _send_to_device(self, value):
1461
- self.component.set(value)
simo/generic/forms.py CHANGED
@@ -14,7 +14,7 @@ from dal import autocomplete, forward
14
14
  from simo.core.utils.config_values import config_to_dict
15
15
  from simo.core.utils.formsets import FormsetField
16
16
  from simo.core.utils.helpers import get_random_string
17
- from simo.core.utils.form_fields import AutocompleteSelect2
17
+ from simo.core.utils.form_fields import ListSelect2Widget
18
18
  from simo.conf import dynamic_settings
19
19
 
20
20
 
@@ -45,7 +45,7 @@ class ScriptConfigForm(BaseComponentForm):
45
45
  def get_admin_fieldsets(cls, request, obj=None):
46
46
  base_fields = (
47
47
  'id', 'gateway', 'base_type', 'name', 'icon', 'zone', 'category',
48
- 'tags', 'show_in_app', 'autostart',
48
+ 'show_in_app', 'autostart',
49
49
  'code', 'control', 'log'
50
50
  )
51
51
 
@@ -196,6 +196,7 @@ class AlarmGroupConfigForm(BaseComponentForm):
196
196
  "Can not cover self. Please remove - [%s]" % str(check_cmp)
197
197
  )
198
198
  if comp.base_type == 'alarm-group':
199
+ comp.prepare_controller()
199
200
  self.recurse_check_alarm_groups(
200
201
  comp.get_children(), check_cmp
201
202
  )
@@ -220,6 +221,7 @@ class AlarmGroupConfigForm(BaseComponentForm):
220
221
  c.save(update_fields=('config',))
221
222
  if obj.id:
222
223
  comp = Component.objects.get(id=obj.id)
224
+ comp.prepare_controller()
223
225
  comp.refresh_status()
224
226
  return obj
225
227
 
@@ -440,21 +442,29 @@ class WateringConfigForm(BaseComponentForm):
440
442
  )
441
443
  obj = super().save(commit=commit)
442
444
  if commit:
443
- obj.subcomponents.clear()
445
+ obj.slaves.clear()
444
446
  for contour in self.cleaned_data['contours']:
445
- obj.subcomponents.add(
447
+ obj.slaves.add(
446
448
  Component.objects.get(pk=contour['switch'])
447
449
  )
448
450
  return obj
449
451
 
450
452
 
451
453
  class StateForm(forms.Form):
452
- icon = AutocompleteSelect2(url='autocomplete-icon')
454
+ icon = forms.CharField(
455
+ widget=ListSelect2Widget(
456
+ url='autocomplete-icon', attrs={'data-html': True}
457
+ )
458
+ )
453
459
  slug = forms.SlugField(required=True)
454
460
  name = forms.CharField(required=True)
455
461
  help_text = forms.CharField(required=False, widget=forms.Textarea(attrs={'rows': 3}))
456
462
  prefix = 'states'
457
463
 
464
+ def clean(self):
465
+ print("Let's clean the data! ", self.cleaned_data)
466
+ return self.cleaned_data
467
+
458
468
 
459
469
  class StateSelectForm(BaseComponentForm):
460
470
  states = FormsetField(
@@ -493,20 +503,21 @@ class AlarmClockEventForm(forms.Form):
493
503
  def clean(self):
494
504
  if not self.cleaned_data.get('component'):
495
505
  return self.cleaned_data
496
- if not self.cleaned_data.get('play_method'):
506
+ if not self.cleaned_data.get('play_action'):
497
507
  return self.cleaned_data
498
508
  component = self.cleaned_data.get('component')
499
- if not hasattr(component, self.cleaned_data['play_method']):
509
+ component.prepare_controller()
510
+ if not hasattr(component, self.cleaned_data['play_action']):
500
511
  self.add_error(
501
- 'play_method',
502
- f"{component} has no {self.cleaned_data['play_method']} method!"
512
+ 'play_action',
513
+ f"{component} has no {self.cleaned_data['play_action']} action!"
503
514
  )
504
- if self.cleaned_data.get('reverse_method'):
505
- if not hasattr(component, self.cleaned_data['reverse_method']):
515
+ if self.cleaned_data.get('reverse_action'):
516
+ if not hasattr(component, self.cleaned_data['reverse_action']):
506
517
  self.add_error(
507
- 'reverse_method',
518
+ 'reverse_action',
508
519
  f"{component} has no "
509
- f"{self.cleaned_data['reverse_method']} method!"
520
+ f"{self.cleaned_data['reverse_action']} action!"
510
521
  )
511
522
  return self.cleaned_data
512
523
 
@@ -521,16 +532,16 @@ class AlarmClockConfigForm(BaseComponentForm):
521
532
  def clean_default_events(self):
522
533
  events = self.cleaned_data['default_events']
523
534
  for i, cont in enumerate(events):
524
- if not cont['uid']:
535
+ if not cont.get('uid'):
525
536
  cont['uid'] = get_random_string(6)
526
537
  return events
527
538
 
528
539
  def save(self, commit=True):
529
540
  obj = super().save(commit=commit)
530
541
  if commit:
531
- obj.subcomponents.clear()
542
+ obj.slaves.clear()
532
543
  for comp in self.cleaned_data['default_events']:
533
- obj.subcomponents.add(
534
- Component.objects.get(pk=comp['component'])
535
- )
544
+ c = Component.objects.filter(pk=comp['component']).first()
545
+ if c:
546
+ obj.slaves.add(c)
536
547
  return obj
simo/generic/gateways.py CHANGED
@@ -175,6 +175,7 @@ class GenericGatewayHandler(BaseObjectCommandsGatewayHandler):
175
175
  ):
176
176
  tz = pytz.timezone(thermostat.zone.instance.timezone)
177
177
  timezone.activate(tz)
178
+ thermostat.prepare_controller()
178
179
  thermostat.evaluate()
179
180
 
180
181
  def watch_alarm_clocks(self):
@@ -184,6 +185,7 @@ class GenericGatewayHandler(BaseObjectCommandsGatewayHandler):
184
185
  ):
185
186
  tz = pytz.timezone(alarm_clock.zone.instance.timezone)
186
187
  timezone.activate(tz)
188
+ alarm_clock.prepare_controller()
187
189
  alarm_clock.tick()
188
190
 
189
191
  def watch_scripts(self):
@@ -192,6 +194,7 @@ class GenericGatewayHandler(BaseObjectCommandsGatewayHandler):
192
194
  controller_uid=Script.uid,
193
195
  config__autostart=True
194
196
  ).exclude(value='running'):
197
+ script.prepare_controller()
195
198
  self.start_script(script)
196
199
 
197
200
  def watch_watering(self):
@@ -199,6 +202,7 @@ class GenericGatewayHandler(BaseObjectCommandsGatewayHandler):
199
202
  for watering in Component.objects.filter(controller_uid=Watering.uid):
200
203
  tz = pytz.timezone(watering.zone.instance.timezone)
201
204
  timezone.activate(tz)
205
+ watering.prepare_controller()
202
206
  if watering.value['status'] == 'running_program':
203
207
  watering.set_program_progress(
204
208
  watering.value['program_progress'] + 1
@@ -211,7 +215,7 @@ class GenericGatewayHandler(BaseObjectCommandsGatewayHandler):
211
215
  self.logger = get_gw_logger(self.gateway_instance.id)
212
216
  for task, period in self.periodic_tasks:
213
217
  threading.Thread(
214
- target=self._run_periodic_task, args=(task, period), daemon=True
218
+ target=self._run_periodic_task, args=(exit, task, period), daemon=True
215
219
  ).start()
216
220
 
217
221
  from simo.generic.controllers import Script, IPCamera
@@ -261,7 +265,7 @@ class GenericGatewayHandler(BaseObjectCommandsGatewayHandler):
261
265
  mqtt_client.subscribe(command.get_topic())
262
266
 
263
267
  def on_mqtt_message(self, client, userdata, msg):
264
- from simo.generic.controllers import Script, Blinds
268
+ from simo.generic.controllers import Script, Blinds, AlarmGroup, Gate
265
269
  payload = json.loads(msg.payload)
266
270
  component = get_event_obj(payload, Component)
267
271
  if not component:
@@ -275,6 +279,10 @@ class GenericGatewayHandler(BaseObjectCommandsGatewayHandler):
275
279
  return
276
280
  elif component.controller_uid == Blinds.uid:
277
281
  self.control_blinds(component, payload.get('set_val'))
282
+ elif component.controller_uid == AlarmGroup.uid:
283
+ self.control_alarm_group(component, payload.get('set_val'))
284
+ elif component.controller_uid == Gate:
285
+ self.control_gate(component, payload.get('set_val'))
278
286
 
279
287
  def start_script(self, component):
280
288
  print("START SCRIPT %s" % str(component))
@@ -325,9 +333,11 @@ class GenericGatewayHandler(BaseObjectCommandsGatewayHandler):
325
333
  open_switch = Component.objects.get(
326
334
  pk=blinds.config['open_switch']
327
335
  )
336
+ open_switch.prepare_controller()
328
337
  close_switch = Component.objects.get(
329
338
  pk=blinds.config['close_switch']
330
339
  )
340
+ close_switch.prepare_controller()
331
341
  except:
332
342
  return
333
343
 
@@ -357,9 +367,70 @@ class GenericGatewayHandler(BaseObjectCommandsGatewayHandler):
357
367
 
358
368
  self.blinds_runners[blinds.id].start()
359
369
 
370
+ def control_alarm_group(self, alarm_group, value):
371
+ from simo.generic.controllers import AlarmGroup
372
+
373
+ other_alarm_groups = {}
374
+ stats = {
375
+ 'disarmed': 0, 'pending-arm': 0, 'armed': 0, 'breached': 0
376
+ }
377
+
378
+ for c_id in alarm_group.config['components']:
379
+ slave = Component.objects.filter(pk=c_id).first()
380
+ if not slave:
381
+ continue
382
+ if value == 'armed':
383
+ if not slave.is_in_alarm():
384
+ slave.arm_status = 'armed'
385
+ stats['armed'] += 1
386
+ else:
387
+ slave.arm_status = 'pending-arm'
388
+ stats['pending-arm'] += 1
389
+ elif value == 'disarmed':
390
+ stats['disarmed'] += 1
391
+ slave.arm_status = 'disarmed'
392
+
393
+ slave.do_not_update_alarm_group = True
394
+ slave.save(update_fields=['arm_status'])
395
+
396
+ for other_group in Component.objects.filter(
397
+ controller_uid=AlarmGroup.uid,
398
+ config__components__contains=slave.id
399
+ ).exclude(pk=alarm_group.pk):
400
+ other_alarm_groups[other_group.pk] = other_group
401
+
402
+ alarm_group.value = value
403
+ if stats['pending-arm']:
404
+ alarm_group.value = 'pending-arm'
405
+ alarm_group.config['stats'] = stats
406
+ alarm_group.save()
407
+
408
+ for pk, other_group in other_alarm_groups.items():
409
+ other_group.prepare_controller()
410
+ other_group.refresh_status()
411
+
412
+
413
+ def control_gate(self, gate, value):
414
+ switch = Component.objects.filter(
415
+ pk=gate.config.get('action_switch')
416
+ ).first()
417
+ if not switch:
418
+ return
419
+ switch.prepare_controller()
360
420
 
421
+ if gate.config.get('action_method') == 'click':
422
+ switch.click()
423
+ else:
424
+ if value == 'open':
425
+ switch.turn_on()
426
+ else:
427
+ switch.turn_off()
361
428
 
362
429
 
363
430
  class DummyGatewayHandler(BaseObjectCommandsGatewayHandler):
364
431
  name = "Dummy"
365
432
  config_form = BaseGatewayForm
433
+
434
+ def perform_value_send(self, component, value):
435
+ component.controller.set(value)
436
+
simo/generic/models.py CHANGED
@@ -63,9 +63,9 @@ def handle_alarm_groups(sender, instance, *args, **kwargs):
63
63
  alarm_group.set(alarm_group_value)
64
64
 
65
65
 
66
- @receiver(pre_save, sender=Component)
67
- def set_initial_alarm_group_stats(sender, instance, *args, **kwargs):
68
- if instance.pk:
66
+ @receiver(post_save, sender=Component)
67
+ def set_initial_alarm_group_stats(sender, instance, created, *args, **kwargs):
68
+ if not created:
69
69
  return
70
70
  if instance.controller_uid != AlarmGroup.uid:
71
71
  return
@@ -1,11 +1,11 @@
1
1
  from django.utils.translation import gettext_lazy as _
2
2
  from django.core.exceptions import ValidationError
3
3
  from simo.core.controllers import BEFORE_SEND, BEFORE_SET
4
- from simo.core.controllers import ControllerBase, TimerMixin
4
+ from simo.core.controllers import Switch, TimerMixin
5
5
  from .app_widgets import AudioPlayerWidget, VideoPlayerWidget
6
6
 
7
7
 
8
- class BasePlayer(ControllerBase, TimerMixin):
8
+ class BasePlayer(Switch):
9
9
  default_config = {
10
10
  'has_volume_control': True,
11
11
  }
@@ -22,6 +22,13 @@ class BasePlayer(ControllerBase, TimerMixin):
22
22
  }
23
23
  default_value = 'stopped'
24
24
 
25
+ def _prepare_for_send(self, value):
26
+ if isinstance(value, bool):
27
+ if value:
28
+ return 'play'
29
+ return 'pause'
30
+ return value
31
+
25
32
  def _validate_val(self, value, occasion=None):
26
33
  return value
27
34
 
@@ -59,12 +66,6 @@ class BasePlayer(ControllerBase, TimerMixin):
59
66
  def play_library_item(self, val):
60
67
  self.send({'play_from_library': val})
61
68
 
62
- def turn_on(self):
63
- self.play()
64
-
65
- def turn_off(self):
66
- self.pause()
67
-
68
69
  def toggle(self):
69
70
  if self.component.value == 'playing':
70
71
  self.turn_off()
simo/settings.py CHANGED
@@ -41,7 +41,6 @@ EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
41
41
 
42
42
  INSTALLED_APPS = [
43
43
  'channels',
44
- 'filebrowser',
45
44
  'dal',
46
45
  'dal_select2',
47
46
  'django.forms',
@@ -87,8 +86,11 @@ MIDDLEWARE = [
87
86
  'simo.core.middleware.instance_middleware'
88
87
  ]
89
88
 
89
+
90
+ FILE_UPLOAD_MAX_MEMORY_SIZE = 20971520 # 20Mb
91
+
90
92
  ROOT_URLCONF = 'urls'
91
- WSGI_APPLICATION = 'wsgi.application'
93
+ WSGI_APPLICATION = 'simo.wsgi.application'
92
94
 
93
95
  CHANNELS_URLCONF = 'simo.asgi'
94
96
  ASGI_APPLICATION = "asgi.application"
@@ -120,7 +122,7 @@ DATABASES = {
120
122
  'default': {
121
123
  'ENGINE': 'simo.core.db_backend',
122
124
  'NAME': 'SIMO',
123
- 'ATOMIC_REQUESTS': False
125
+ 'ATOMIC_REQUESTS': False,
124
126
  }
125
127
  }
126
128
 
@@ -185,7 +187,8 @@ REST_FRAMEWORK = {
185
187
  'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'],
186
188
  'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
187
189
  'PAGE_SIZE': 1000,
188
- 'DATETIME_FORMAT': '%s.%f'
190
+ 'DATETIME_FORMAT': '%s.%f',
191
+ 'DEFAULT_METADATA_CLASS': 'simo.core.api_meta.SIMOAPIMetadata'
189
192
  }
190
193
 
191
194
  REDIS_DB = {
simo/urls.py CHANGED
@@ -1,5 +1,6 @@
1
1
  import importlib
2
2
  import inspect
3
+ import sys
3
4
  from django.urls import path, include
4
5
  from django.conf.urls.static import static
5
6
  from django.conf import settings
@@ -9,23 +10,17 @@ from django.apps import apps
9
10
  from django.contrib.auth.views import LogoutView
10
11
  from rest_framework import routers
11
12
  from rest_framework.viewsets import GenericViewSet
12
- from filebrowser.sites import FileBrowserSite
13
13
  from django.contrib.admin import site as admin_site
14
14
  from simo.users.views import protected_static
15
15
 
16
16
 
17
- filebrowser_site = FileBrowserSite(
18
- name='filebrowser', storage=FileSystemStorage(
19
- settings.VAR_DIR, settings.VAR_DIR_URL
20
- )
21
- )
22
17
 
23
18
  rest_router = routers.DefaultRouter()
24
19
  registered_classes = []
25
20
  for name, app in apps.app_configs.items():
26
21
  try:
27
22
  apis = importlib.import_module('%s.api' % app.name)
28
- except ModuleNotFoundError:
23
+ except ModuleNotFoundError as e:
29
24
  continue
30
25
  for cls_name, cls in apis.__dict__.items():
31
26
  cls_id = '%s.%s' % (app.name, cls_name)
@@ -39,7 +34,8 @@ for name, app in apps.app_configs.items():
39
34
  urlpatterns = [
40
35
  path('', RedirectView.as_view(pattern_name='admin:index')),
41
36
  path('login/', include('simo.users.sso_urls')),
42
- path('admin/filebrowser/', filebrowser_site.urls),
37
+
38
+
43
39
  path('admin/login/',
44
40
  RedirectView.as_view(
45
41
  pattern_name='login', query_string=True
Binary file
Binary file
simo/users/admin.py CHANGED
@@ -6,7 +6,7 @@ from django.contrib.auth.admin import UserAdmin as OrgUserAdmin
6
6
  from django.contrib import admin
7
7
  from .models import (
8
8
  PermissionsRole, ComponentPermission, User, UserDevice, UserDeviceReportLog,
9
- InstanceInvitation, InstanceUser
9
+ InstanceInvitation, InstanceUser, Fingerprint
10
10
  )
11
11
 
12
12
 
@@ -211,4 +211,11 @@ class InstanceInvitationAdmin(admin.ModelAdmin):
211
211
  "No invitations were sent."
212
212
  )
213
213
 
214
+ @admin.register(Fingerprint)
215
+ class FingerprintAdmin(admin.ModelAdmin):
216
+ list_display = 'value', 'type', 'date_created', 'user',
217
+ search_fields = 'value', 'type', 'user__name', 'user__email'
218
+ readonly_fields = 'value', 'type', 'date_created'
214
219
 
220
+ def has_add_permission(self, request):
221
+ return False