simo 1.7.19__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 (289) hide show
  1. simo/__pycache__/asgi.cpython-38.pyc +0 -0
  2. simo/__pycache__/on_http_start.cpython-38.pyc +0 -0
  3. simo/__pycache__/settings.cpython-38.pyc +0 -0
  4. simo/__pycache__/urls.cpython-38.pyc +0 -0
  5. simo/__pycache__/wsgi.cpython-38.pyc +0 -0
  6. simo/core/__pycache__/__init__.cpython-38.pyc +0 -0
  7. simo/core/__pycache__/admin.cpython-38.pyc +0 -0
  8. simo/core/__pycache__/api.cpython-38.pyc +0 -0
  9. simo/core/__pycache__/api_meta.cpython-38.pyc +0 -0
  10. simo/core/__pycache__/auto_urls.cpython-38.pyc +0 -0
  11. simo/core/__pycache__/autocomplete_views.cpython-38.pyc +0 -0
  12. simo/core/__pycache__/base_types.cpython-38.pyc +0 -0
  13. simo/core/__pycache__/context.cpython-38.pyc +0 -0
  14. simo/core/__pycache__/controllers.cpython-38.pyc +0 -0
  15. simo/core/__pycache__/events.cpython-38.pyc +0 -0
  16. simo/core/__pycache__/filters.cpython-38.pyc +0 -0
  17. simo/core/__pycache__/forms.cpython-38.pyc +0 -0
  18. simo/core/__pycache__/gateways.cpython-38.pyc +0 -0
  19. simo/core/__pycache__/managers.cpython-38.pyc +0 -0
  20. simo/core/__pycache__/middleware.cpython-38.pyc +0 -0
  21. simo/core/__pycache__/models.cpython-38.pyc +0 -0
  22. simo/core/__pycache__/permissions.cpython-38.pyc +0 -0
  23. simo/core/__pycache__/routing.cpython-38.pyc +0 -0
  24. simo/core/__pycache__/serializers.cpython-38.pyc +0 -0
  25. simo/core/__pycache__/signal_receivers.cpython-38.pyc +0 -0
  26. simo/core/__pycache__/socket_consumers.cpython-38.pyc +0 -0
  27. simo/core/__pycache__/tasks.cpython-38.pyc +0 -0
  28. simo/core/__pycache__/todos.cpython-38.pyc +0 -0
  29. simo/core/__pycache__/views.cpython-38.pyc +0 -0
  30. simo/core/admin.py +28 -18
  31. simo/core/api.py +157 -16
  32. simo/core/api_meta.py +87 -0
  33. simo/core/auto_urls.py +4 -1
  34. simo/core/autocomplete_views.py +8 -4
  35. simo/core/base_types.py +1 -0
  36. simo/core/context.py +3 -1
  37. simo/core/controllers.py +112 -32
  38. simo/core/db_backend/base.py +7 -22
  39. simo/core/drf_braces/README +3 -0
  40. simo/core/drf_braces/__init__.py +7 -0
  41. simo/core/drf_braces/__pycache__/__init__.cpython-38.pyc +0 -0
  42. simo/core/drf_braces/__pycache__/utils.cpython-38.pyc +0 -0
  43. simo/core/drf_braces/fields/__init__.py +5 -0
  44. simo/core/drf_braces/fields/__pycache__/__init__.cpython-38.pyc +0 -0
  45. simo/core/drf_braces/fields/__pycache__/_fields.cpython-38.pyc +0 -0
  46. simo/core/drf_braces/fields/__pycache__/custom.cpython-38.pyc +0 -0
  47. simo/core/drf_braces/fields/__pycache__/mixins.cpython-38.pyc +0 -0
  48. simo/core/drf_braces/fields/__pycache__/modified.cpython-38.pyc +0 -0
  49. simo/core/drf_braces/fields/_fields.py +48 -0
  50. simo/core/drf_braces/fields/custom.py +107 -0
  51. simo/core/drf_braces/fields/mixins.py +58 -0
  52. simo/core/drf_braces/fields/modified.py +41 -0
  53. simo/core/drf_braces/forms/__init__.py +0 -0
  54. simo/core/drf_braces/forms/fields.py +20 -0
  55. simo/core/drf_braces/forms/serializer_form.py +156 -0
  56. simo/core/drf_braces/mixins.py +52 -0
  57. simo/core/drf_braces/models.py +0 -0
  58. simo/core/drf_braces/parsers.py +72 -0
  59. simo/core/drf_braces/renderers.py +37 -0
  60. simo/core/drf_braces/serializers/__init__.py +0 -0
  61. simo/core/drf_braces/serializers/__pycache__/__init__.cpython-38.pyc +0 -0
  62. simo/core/drf_braces/serializers/__pycache__/form_serializer.cpython-38.pyc +0 -0
  63. simo/core/drf_braces/serializers/enforce_validation_serializer.py +214 -0
  64. simo/core/drf_braces/serializers/form_serializer.py +391 -0
  65. simo/core/drf_braces/serializers/swapping.py +48 -0
  66. simo/core/drf_braces/tests/__init__.py +0 -0
  67. simo/core/drf_braces/tests/fields/__init__.py +0 -0
  68. simo/core/drf_braces/tests/fields/test_custom.py +94 -0
  69. simo/core/drf_braces/tests/fields/test_fields.py +13 -0
  70. simo/core/drf_braces/tests/fields/test_mixins.py +96 -0
  71. simo/core/drf_braces/tests/fields/test_modified.py +40 -0
  72. simo/core/drf_braces/tests/forms/__init__.py +0 -0
  73. simo/core/drf_braces/tests/forms/test_fields.py +46 -0
  74. simo/core/drf_braces/tests/forms/test_serializer_form.py +256 -0
  75. simo/core/drf_braces/tests/serializers/__init__.py +0 -0
  76. simo/core/drf_braces/tests/serializers/test_enforce_validation_serializer.py +169 -0
  77. simo/core/drf_braces/tests/serializers/test_form_serializer.py +387 -0
  78. simo/core/drf_braces/tests/serializers/test_swapping.py +40 -0
  79. simo/core/drf_braces/tests/test_mixins.py +111 -0
  80. simo/core/drf_braces/tests/test_parsers.py +73 -0
  81. simo/core/drf_braces/tests/test_renderers.py +23 -0
  82. simo/core/drf_braces/tests/test_utils.py +73 -0
  83. simo/core/drf_braces/utils.py +209 -0
  84. simo/core/events.py +3 -3
  85. simo/core/forms.py +79 -37
  86. simo/core/gateways.py +31 -14
  87. simo/core/management/__pycache__/__init__.cpython-38.pyc +0 -0
  88. simo/core/management/commands/__pycache__/__init__.cpython-38.pyc +0 -0
  89. simo/core/management/commands/gateways_manager.py +0 -1
  90. simo/core/managers.py +81 -0
  91. simo/core/middleware.py +25 -0
  92. simo/core/migrations/0026_category_instance.py +20 -0
  93. simo/core/migrations/0027_remove_component_tags.py +17 -0
  94. simo/core/migrations/0028_rename_subcomponents_component_slaves.py +18 -0
  95. simo/core/migrations/0029_auto_20240229_1331.py +33 -0
  96. simo/core/migrations/__pycache__/0026_category_instance.cpython-38.pyc +0 -0
  97. simo/core/migrations/__pycache__/0027_remove_component_tags.cpython-38.pyc +0 -0
  98. simo/core/migrations/__pycache__/0028_rename_subcomponents_component_slaves.cpython-38.pyc +0 -0
  99. simo/core/migrations/__pycache__/0029_auto_20240229_1331.cpython-38.pyc +0 -0
  100. simo/core/models.py +103 -66
  101. simo/core/permissions.py +28 -2
  102. simo/core/serializers.py +330 -26
  103. simo/core/socket_consumers.py +5 -14
  104. simo/core/tasks.py +11 -1
  105. simo/core/templates/admin/base.html +37 -10
  106. simo/core/templates/admin/wizard/discovery.html +188 -0
  107. simo/core/templates/admin/wizard/wizard_add.html +5 -5
  108. simo/core/utils/__pycache__/serialization.cpython-38.pyc +0 -0
  109. simo/core/utils/admin.py +9 -2
  110. simo/core/utils/formsets.py +17 -16
  111. simo/core/utils/helpers.py +1 -0
  112. simo/core/utils/serialization.py +56 -0
  113. simo/core/utils/type_constants.py +1 -1
  114. simo/core/utils/validators.py +14 -1
  115. simo/core/views.py +13 -0
  116. simo/fleet/__pycache__/admin.cpython-38.pyc +0 -0
  117. simo/fleet/__pycache__/api.cpython-38.pyc +0 -0
  118. simo/fleet/__pycache__/auto_urls.cpython-38.pyc +0 -0
  119. simo/fleet/__pycache__/controllers.cpython-38.pyc +0 -0
  120. simo/fleet/__pycache__/forms.cpython-38.pyc +0 -0
  121. simo/fleet/__pycache__/gateways.cpython-38.pyc +0 -0
  122. simo/fleet/__pycache__/managers.cpython-38.pyc +0 -0
  123. simo/fleet/__pycache__/models.cpython-38.pyc +0 -0
  124. simo/fleet/__pycache__/serializers.cpython-38.pyc +0 -0
  125. simo/fleet/__pycache__/socket_consumers.cpython-38.pyc +0 -0
  126. simo/fleet/__pycache__/utils.cpython-38.pyc +0 -0
  127. simo/fleet/__pycache__/views.cpython-38.pyc +0 -0
  128. simo/fleet/admin.py +54 -25
  129. simo/fleet/api.py +59 -3
  130. simo/fleet/auto_urls.py +2 -3
  131. simo/fleet/controllers.py +199 -16
  132. simo/fleet/forms.py +325 -483
  133. simo/fleet/gateways.py +44 -2
  134. simo/fleet/managers.py +32 -0
  135. simo/fleet/migrations/0025_auto_20240130_1334.py +27 -0
  136. simo/fleet/migrations/0026_rename_i2cinterface_scl_pin_and_more.py +64 -0
  137. simo/fleet/migrations/0027_auto_20240306_0802.py +170 -0
  138. simo/fleet/migrations/0028_remove_i2cinterface_scl_pin_no_and_more.py +21 -0
  139. simo/fleet/migrations/0029_alter_i2cinterface_scl_pin_and_more.py +24 -0
  140. simo/fleet/migrations/0030_colonelpin_label_alter_colonel_type.py +24 -0
  141. simo/fleet/migrations/0031_alter_colonel_type.py +18 -0
  142. simo/fleet/migrations/__pycache__/0025_auto_20240130_1334.cpython-38.pyc +0 -0
  143. simo/fleet/migrations/__pycache__/0026_rename_i2cinterface_scl_pin_and_more.cpython-38.pyc +0 -0
  144. simo/fleet/migrations/__pycache__/0027_auto_20240306_0802.cpython-38.pyc +0 -0
  145. simo/fleet/migrations/__pycache__/0028_remove_i2cinterface_scl_pin_no_and_more.cpython-38.pyc +0 -0
  146. simo/fleet/migrations/__pycache__/0029_alter_i2cinterface_scl_pin_and_more.cpython-38.pyc +0 -0
  147. simo/fleet/migrations/__pycache__/0030_colonelpin_label_alter_colonel_type.cpython-38.pyc +0 -0
  148. simo/fleet/migrations/__pycache__/0031_alter_colonel_type.cpython-38.pyc +0 -0
  149. simo/fleet/models.py +134 -82
  150. simo/fleet/serializers.py +35 -1
  151. simo/fleet/socket_consumers.py +239 -76
  152. simo/fleet/utils.py +15 -53
  153. simo/fleet/views.py +28 -14
  154. simo/generic/controllers.py +13 -89
  155. simo/generic/forms.py +30 -23
  156. simo/generic/gateways.py +98 -10
  157. simo/generic/models.py +3 -3
  158. simo/multimedia/controllers.py +9 -8
  159. simo/settings.py +7 -4
  160. simo/urls.py +4 -8
  161. simo/users/__pycache__/admin.cpython-38.pyc +0 -0
  162. simo/users/__pycache__/api.cpython-38.pyc +0 -0
  163. simo/users/__pycache__/auto_urls.cpython-38.pyc +0 -0
  164. simo/users/__pycache__/models.cpython-38.pyc +0 -0
  165. simo/users/__pycache__/serializers.cpython-38.pyc +0 -0
  166. simo/users/__pycache__/sso_urls.cpython-38.pyc +0 -0
  167. simo/users/admin.py +8 -1
  168. simo/users/api.py +38 -2
  169. simo/users/auto_urls.py +2 -2
  170. simo/users/migrations/0025_rename_name_fingerprint_type_and_more.py +22 -0
  171. simo/users/migrations/__pycache__/0025_rename_name_fingerprint_type_and_more.cpython-38.pyc +0 -0
  172. simo/users/models.py +2 -3
  173. simo/users/serializers.py +15 -1
  174. simo/users/sso_urls.py +3 -3
  175. simo/wsgi.py +7 -0
  176. {simo-1.7.19.dist-info → simo-2.0.0.dist-info}/METADATA +8 -9
  177. {simo-1.7.19.dist-info → simo-2.0.0.dist-info}/RECORD +180 -210
  178. {simo-1.7.19.dist-info → simo-2.0.0.dist-info}/WHEEL +1 -1
  179. simo/core/__pycache__/__init__.cpython-37.pyc +0 -0
  180. simo/core/__pycache__/admin.cpython-37.pyc +0 -0
  181. simo/core/__pycache__/api.cpython-37.pyc +0 -0
  182. simo/core/__pycache__/apps.cpython-38.pyc +0 -0
  183. simo/core/__pycache__/controllers.cpython-37.pyc +0 -0
  184. simo/core/__pycache__/events.cpython-37.pyc +0 -0
  185. simo/core/__pycache__/forms.cpython-37.pyc +0 -0
  186. simo/core/__pycache__/gateways.cpython-37.pyc +0 -0
  187. simo/core/__pycache__/models.cpython-37.pyc +0 -0
  188. simo/core/__pycache__/scripts.cpython-37.pyc +0 -0
  189. simo/core/__pycache__/serializers.cpython-37.pyc +0 -0
  190. simo/core/__pycache__/widgets.cpython-37.pyc +0 -0
  191. simo/core/db_backend/__pycache__/__init__.cpython-38.pyc +0 -0
  192. simo/core/db_backend/__pycache__/base.cpython-38.pyc +0 -0
  193. simo/core/management/commands/__pycache__/gateways_manager.cpython-38.pyc +0 -0
  194. simo/core/management/commands/__pycache__/run_gateway.cpython-38.pyc +0 -0
  195. simo/core/migrations/__pycache__/0001_initial.cpython-38.pyc +0 -0
  196. simo/core/migrations/__pycache__/0002_load_icons.cpython-38.pyc +0 -0
  197. simo/core/migrations/__pycache__/0003_create_default_zones_and_categories.cpython-38.pyc +0 -0
  198. simo/core/migrations/__pycache__/0004_create_generic.cpython-38.pyc +0 -0
  199. simo/core/migrations/__pycache__/0005_component_subcomponents.cpython-38.pyc +0 -0
  200. simo/core/migrations/__pycache__/0006_alter_component_subcomponents.cpython-38.pyc +0 -0
  201. simo/core/migrations/__pycache__/0007_component_change_init_to.cpython-38.pyc +0 -0
  202. simo/core/migrations/__pycache__/0008_alter_component_change_init_to.cpython-38.pyc +0 -0
  203. simo/core/migrations/__pycache__/0009_auto_20220707_1404.cpython-38.pyc +0 -0
  204. simo/core/migrations/__pycache__/0010_historyaggregate.cpython-38.pyc +0 -0
  205. simo/core/migrations/__pycache__/0011_component_last_change.cpython-38.pyc +0 -0
  206. simo/core/migrations/__pycache__/0012_instance.cpython-38.pyc +0 -0
  207. simo/core/migrations/__pycache__/0013_auto_20231003_0754.cpython-38.pyc +0 -0
  208. simo/core/migrations/__pycache__/0014_zone_instance.cpython-38.pyc +0 -0
  209. simo/core/migrations/__pycache__/0015_auto_20231004_1113.cpython-38.pyc +0 -0
  210. simo/core/migrations/__pycache__/0016_auto_20231004_1113.cpython-38.pyc +0 -0
  211. simo/core/migrations/__pycache__/0017_auto_20231004_1313.cpython-38.pyc +0 -0
  212. simo/core/migrations/__pycache__/0018_auto_20231005_0622.cpython-38.pyc +0 -0
  213. simo/core/migrations/__pycache__/0019_alter_gateway_type.cpython-38.pyc +0 -0
  214. simo/core/migrations/__pycache__/0020_component_meta.cpython-38.pyc +0 -0
  215. simo/core/migrations/__pycache__/0021_auto_20231020_1041.cpython-38.pyc +0 -0
  216. simo/core/migrations/__pycache__/__init__.cpython-38.pyc +0 -0
  217. simo/core/templatetags/__pycache__/__init__.cpython-38.pyc +0 -0
  218. simo/core/templatetags/__pycache__/components_list.cpython-38.pyc +0 -0
  219. simo/core/utils/__pycache__/__init__.cpython-38.pyc +0 -0
  220. simo/core/utils/__pycache__/admin.cpython-38.pyc +0 -0
  221. simo/core/utils/__pycache__/config_values.cpython-38.pyc +0 -0
  222. simo/core/utils/__pycache__/easing.cpython-38.pyc +0 -0
  223. simo/core/utils/__pycache__/form_fields.cpython-38.pyc +0 -0
  224. simo/core/utils/__pycache__/form_widgets.cpython-38.pyc +0 -0
  225. simo/core/utils/__pycache__/formsets.cpython-38.pyc +0 -0
  226. simo/core/utils/__pycache__/helpers.cpython-38.pyc +0 -0
  227. simo/core/utils/__pycache__/logs.cpython-38.pyc +0 -0
  228. simo/core/utils/__pycache__/mixins.cpython-38.pyc +0 -0
  229. simo/core/utils/__pycache__/model_helpers.cpython-38.pyc +0 -0
  230. simo/core/utils/__pycache__/relay.cpython-38.pyc +0 -0
  231. simo/core/utils/__pycache__/type_constants.cpython-38.pyc +0 -0
  232. simo/core/utils/__pycache__/validators.cpython-38.pyc +0 -0
  233. simo/fleet/tasks.py +0 -25
  234. simo/generic/__pycache__/__init__.cpython-37.pyc +0 -0
  235. simo/generic/__pycache__/__init__.cpython-38.pyc +0 -0
  236. simo/generic/__pycache__/app_widgets.cpython-38.pyc +0 -0
  237. simo/generic/__pycache__/base_types.cpython-38.pyc +0 -0
  238. simo/generic/__pycache__/controllers.cpython-37.pyc +0 -0
  239. simo/generic/__pycache__/controllers.cpython-38.pyc +0 -0
  240. simo/generic/__pycache__/forms.cpython-38.pyc +0 -0
  241. simo/generic/__pycache__/gateways.cpython-38.pyc +0 -0
  242. simo/generic/__pycache__/models.cpython-38.pyc +0 -0
  243. simo/generic/__pycache__/routing.cpython-38.pyc +0 -0
  244. simo/generic/__pycache__/socket_consumers.cpython-38.pyc +0 -0
  245. simo/generic/__pycache__/tasks.cpython-38.pyc +0 -0
  246. simo/generic/__pycache__/widgets.cpython-37.pyc +0 -0
  247. simo/generic/tasks.py +0 -41
  248. simo/multimedia/__pycache__/__init__.cpython-38.pyc +0 -0
  249. simo/multimedia/__pycache__/admin.cpython-38.pyc +0 -0
  250. simo/multimedia/__pycache__/api.cpython-38.pyc +0 -0
  251. simo/multimedia/__pycache__/app_widgets.cpython-38.pyc +0 -0
  252. simo/multimedia/__pycache__/base_types.cpython-38.pyc +0 -0
  253. simo/multimedia/__pycache__/controllers.cpython-38.pyc +0 -0
  254. simo/multimedia/__pycache__/forms.cpython-38.pyc +0 -0
  255. simo/multimedia/__pycache__/models.cpython-38.pyc +0 -0
  256. simo/multimedia/__pycache__/serializers.cpython-38.pyc +0 -0
  257. simo/multimedia/migrations/__pycache__/0001_initial.cpython-38.pyc +0 -0
  258. simo/multimedia/migrations/__pycache__/0002_sound_length.cpython-38.pyc +0 -0
  259. simo/multimedia/migrations/__pycache__/0003_alter_sound_length.cpython-38.pyc +0 -0
  260. simo/multimedia/migrations/__pycache__/0004_auto_20231023_1055.cpython-38.pyc +0 -0
  261. simo/multimedia/migrations/__pycache__/__init__.cpython-38.pyc +0 -0
  262. simo/notifications/__pycache__/__init__.cpython-38.pyc +0 -0
  263. simo/notifications/__pycache__/admin.cpython-38.pyc +0 -0
  264. simo/notifications/__pycache__/api.cpython-38.pyc +0 -0
  265. simo/notifications/__pycache__/models.cpython-38.pyc +0 -0
  266. simo/notifications/__pycache__/serializers.cpython-38.pyc +0 -0
  267. simo/notifications/__pycache__/utils.cpython-38.pyc +0 -0
  268. simo/notifications/migrations/__pycache__/0001_initial.cpython-38.pyc +0 -0
  269. simo/notifications/migrations/__pycache__/0002_notification_instance.cpython-38.pyc +0 -0
  270. simo/notifications/migrations/__pycache__/__init__.cpython-38.pyc +0 -0
  271. simo/users/migrations/__pycache__/0001_initial.cpython-38.pyc +0 -0
  272. simo/users/migrations/__pycache__/0002_componentpermission.cpython-38.pyc +0 -0
  273. simo/users/migrations/__pycache__/0003_create_roles_and_system_user.cpython-38.pyc +0 -0
  274. simo/users/migrations/__pycache__/0004_user_secret_key.cpython-38.pyc +0 -0
  275. simo/users/migrations/__pycache__/0005_permissionsrole_instance.cpython-38.pyc +0 -0
  276. simo/users/migrations/__pycache__/0006_auto_20231003_0850.cpython-38.pyc +0 -0
  277. simo/users/migrations/__pycache__/0007_auto_20231003_1228.cpython-38.pyc +0 -0
  278. simo/users/migrations/__pycache__/0008_auto_20231003_1229.cpython-38.pyc +0 -0
  279. simo/users/migrations/__pycache__/0009_remove_user_role.cpython-38.pyc +0 -0
  280. simo/users/migrations/__pycache__/0010_auto_20231004_1313.cpython-38.pyc +0 -0
  281. simo/users/migrations/__pycache__/0011_auto_20231004_1313.cpython-38.pyc +0 -0
  282. simo/users/migrations/__pycache__/0012_alter_userinstancerole_unique_together.cpython-38.pyc +0 -0
  283. simo/users/migrations/__pycache__/0013_remove_user_roles.cpython-38.pyc +0 -0
  284. simo/users/migrations/__pycache__/0014_user_roles.cpython-38.pyc +0 -0
  285. simo/users/migrations/__pycache__/0015_remove_user_at_home.cpython-38.pyc +0 -0
  286. simo/users/migrations/__pycache__/0016_auto_20231005_1050.cpython-38.pyc +0 -0
  287. simo/users/migrations/__pycache__/__init__.cpython-38.pyc +0 -0
  288. {simo-1.7.19.dist-info → simo-2.0.0.dist-info}/LICENSE.md +0 -0
  289. {simo-1.7.19.dist-info → simo-2.0.0.dist-info}/top_level.txt +0 -0
@@ -2,23 +2,24 @@ import asyncio
2
2
  import json
3
3
  import logging
4
4
  import pytz
5
- import time
5
+ import traceback
6
6
  import sys
7
+ import zlib
8
+ from django.db import transaction
7
9
  from logging.handlers import RotatingFileHandler
8
- from django.urls import reverse
9
10
  from django.utils import timezone
10
11
  from django.conf import settings
11
12
  import paho.mqtt.client as mqtt
12
13
  from channels.generic.websocket import AsyncWebsocketConsumer
13
14
  from asgiref.sync import sync_to_async
14
- from simo.core.utils.helpers import get_random_string
15
15
  from simo.core.utils.model_helpers import get_log_file_path
16
16
  from simo.core.events import GatewayObjectCommand, get_event_obj
17
- from simo.core.utils.helpers import get_self_ip
18
17
  from simo.core.models import Gateway, Instance, Component
19
18
  from simo.conf import dynamic_settings
19
+ from simo.users.models import Fingerprint
20
20
  from .gateways import FleetGatewayHandler
21
21
  from .models import Colonel
22
+ from .controllers import TTLock
22
23
 
23
24
 
24
25
  class FleetConsumer(AsyncWebsocketConsumer):
@@ -43,9 +44,8 @@ class FleetConsumer(AsyncWebsocketConsumer):
43
44
 
44
45
  async def connect(self):
45
46
 
46
- print("Fleet socket connect! Headers: ", self.scope['headers'])
47
47
  headers = {
48
- item[0].decode(): item[1].decode() for item in self.scope['headers']
48
+ item[0].decode().lower(): item[1].decode() for item in self.scope['headers']
49
49
  }
50
50
 
51
51
  instance_uid = headers.get('instance-uid')
@@ -81,44 +81,40 @@ class FleetConsumer(AsyncWebsocketConsumer):
81
81
  tz = await sync_to_async(get_tz, thread_sensitive=True)()
82
82
  timezone.activate(tz)
83
83
 
84
- self.colonel, new = await sync_to_async(
85
- Colonel.objects.update_or_create, thread_sensitive=True)(
86
- uid=headers['colonel-uid'], defaults={
84
+ def get_colonel():
85
+ # looks like update_or_create doesn't work in socket/async environment.
86
+ defaults={
87
87
  'instance': self.instance,
88
+ 'name': headers.get('colonel-name'),
88
89
  'type': headers['colonel-type'],
89
90
  'firmware_version': headers['firmware-version'],
90
91
  'last_seen': timezone.now()
91
92
  }
92
- )
93
+ with transaction.atomic():
94
+ colonel, new = Colonel.objects.get_or_create(
95
+ uid=headers['colonel-uid'], defaults=defaults
96
+ )
97
+ if not new:
98
+ for key, val in defaults.items():
99
+ setattr(colonel, key, val)
100
+ if new:
101
+ colonel.enabled = True
102
+ colonel.save()
103
+
104
+ return colonel, new
105
+
106
+ self.colonel, new = await sync_to_async(
107
+ get_colonel, thread_sensitive=True
108
+ )()
93
109
 
94
- print(f"Colonel {self.colonel} connected with headers: {headers}")
110
+ print(f"Colonel {self.colonel} connected!")
95
111
  if not self.colonel.enabled:
96
112
  print("Colonel %s drop, it's not enabled!" % str(self.colonel))
97
113
  await self.accept()
98
114
  return await self.close()
99
115
 
100
- if not self.colonel.name and headers.get('colonel-name'):
101
- def save_colonel_name(name):
102
- self.colonel.name = name
103
- self.colonel.save()
104
-
105
- await sync_to_async(
106
- save_colonel_name, thread_sensitive=True
107
- )(headers['colonel-name'])
108
-
109
- def set_colonel_authorized(val):
110
- self.colonel.is_authorized = val
111
- self.colonel.save()
112
-
113
- if headers.get('instance-uid') == self.colonel.instance.uid \
114
- and headers.get('instance-secret') == self.colonel.instance.fleet_options.secret_key:
115
- await sync_to_async(
116
- set_colonel_authorized, thread_sensitive=True
117
- )(True)
118
- else:
119
- await sync_to_async(
120
- set_colonel_authorized, thread_sensitive=True
121
- )(False)
116
+ if headers.get('instance-uid') != self.colonel.instance.uid \
117
+ or headers.get('instance-secret') != self.colonel.instance.fleet_options.secret_key:
122
118
  print("NOT authorized!")
123
119
  return await self.close()
124
120
 
@@ -126,16 +122,27 @@ class FleetConsumer(AsyncWebsocketConsumer):
126
122
 
127
123
  await self.accept()
128
124
 
125
+ await self.log_colonel_connected()
126
+
127
+
128
+ def get_gateway():
129
+ return Gateway.objects.filter(
130
+ type=FleetGatewayHandler.uid
131
+ ).first()
132
+
133
+ self.gateway = await sync_to_async(
134
+ get_gateway, thread_sensitive=True
135
+ )()
136
+
129
137
  if self.colonel.firmware_auto_update \
130
138
  and self.colonel.minor_upgrade_available:
131
139
  await self.firmware_update(self.colonel.minor_upgrade_available)
132
140
  else:
133
141
  def on_mqtt_connect(mqtt_client, userdata, flags, rc):
134
- for gateway in Gateway.objects.filter(
135
- type=FleetGatewayHandler.uid
136
- ):
137
- command = GatewayObjectCommand(gateway)
138
- mqtt_client.subscribe(command.get_topic())
142
+ command = GatewayObjectCommand(self.gateway)
143
+ TOPIC = command.get_topic()
144
+ print("SUBSCRIBE TO TOPIC: ", TOPIC)
145
+ mqtt_client.subscribe(TOPIC)
139
146
 
140
147
  self.mqtt_client = mqtt.Client()
141
148
  self.mqtt_client.username_pw_set('root', settings.SECRET_KEY)
@@ -153,9 +160,11 @@ class FleetConsumer(AsyncWebsocketConsumer):
153
160
  # If we force this, vales get overridden by what is last
154
161
  # known by the hub
155
162
  # config = await self.get_config_data()
156
- # await self.send(json.dumps({
163
+ # await self.send_data(
157
164
  # 'command': 'set_config', 'data': config
158
- # }))
165
+ # })
166
+
167
+ await self.send_data({'command': 'hello'})
159
168
 
160
169
  asyncio.create_task(self.watch_connection())
161
170
 
@@ -174,11 +183,11 @@ class FleetConsumer(AsyncWebsocketConsumer):
174
183
  await asyncio.sleep(10)
175
184
  # Default pinging system sometimes get's lost somewhere,
176
185
  # therefore we use our own to ensure connection
177
- await self.send(json.dumps({'command': 'ping'}))
186
+ await self.send_data({'command': 'ping'})
178
187
 
179
188
  async def firmware_update(self, to_version):
180
189
  print("Firmware update: ", str(self.colonel))
181
- await self.send(json.dumps({'command': 'ota_update', 'version': to_version}))
190
+ await self.send_data({'command': 'ota_update', 'version': to_version})
182
191
 
183
192
  async def get_config_data(self):
184
193
  self.colonel = await sync_to_async(
@@ -207,67 +216,162 @@ class FleetConsumer(AsyncWebsocketConsumer):
207
216
  }
208
217
  config_data['settings'].update(instance_options)
209
218
  i2c_interfaces = await sync_to_async(list, thread_sensitive=True)(
210
- self.colonel.i2c_interfaces.all()
219
+ self.colonel.i2c_interfaces.all().select_related(
220
+ 'scl_pin', 'sda_pin'
221
+ )
211
222
  )
212
223
  for i2c_interface in i2c_interfaces:
213
224
  config_data['interfaces']['i2c-%d' % i2c_interface.no] = {
214
- 'scl': i2c_interface.scl_pin, 'sda': i2c_interface.sda_pin,
225
+ 'scl': i2c_interface.scl_pin.no, 'sda': i2c_interface.sda_pin.no,
215
226
  'freq': i2c_interface.freq
216
227
  }
217
228
  components = await sync_to_async(
218
229
  list, thread_sensitive=True
219
- )(self.colonel.components.all())
220
- for component in components:
230
+ )(self.colonel.components.all().prefetch_related('slaves'))
231
+
232
+ def get_comp_config(comp):
221
233
  try:
222
- config_data['devices'][str(component.id)] = {
223
- 'type': component.controller.uid.split('.')[-1],
224
- 'config': component.config,
225
- 'options': component.meta.get('options', {}),
226
- 'val': component.controller._prepare_for_send(component.value)
234
+ comp_config = {
235
+ 'type': comp.controller.uid.split('.')[-1],
236
+ 'val': comp.controller._prepare_for_send(
237
+ comp.value
238
+ ),
239
+ 'config': comp.controller._get_colonel_config()
227
240
  }
241
+ slaves = [
242
+ s.id for s in comp.slaves.all()
243
+ if s.config.get('colonel') == self.colonel.id
244
+ ]
245
+ if slaves:
246
+ comp_config['slaves'] = slaves
247
+ if comp.meta.get('options'):
248
+ comp_config['options'] = comp.meta['options']
249
+
250
+ config_data['devices'][str(comp.id)] = comp_config
228
251
  except:
252
+ print("Error preparing component config")
253
+ print(traceback.format_exc(), file=sys.stderr)
254
+ else:
255
+ return comp_config
256
+
257
+ for component in components:
258
+
259
+ comp_config = components = await sync_to_async(
260
+ get_comp_config, thread_sensitive=True
261
+ )(component)
262
+
263
+ if not comp_config:
229
264
  continue
230
265
 
266
+ slaves = [
267
+ s.id for s in component.slaves.all()
268
+ if s.config.get('colonel') == self.colonel.id
269
+ ]
270
+ if slaves:
271
+ comp_config['slaves'] = slaves
272
+ if component.meta.get('options'):
273
+ comp_config['options'] = component.meta['options']
274
+
275
+ config_data['devices'][str(component.id)] = comp_config
276
+
277
+
231
278
  return config_data
232
279
 
233
280
  def on_mqtt_message(self, client, userdata, msg):
234
- payload = json.loads(msg.payload)
235
- colonel = get_event_obj(payload, Colonel)
236
- if not colonel or colonel != self.colonel:
237
- return
238
- if payload.get('command') == 'update_firmware':
239
- asyncio.run(self.firmware_update(payload['kwargs'].get('to_version')))
240
- elif payload.get('command') == 'update_config':
241
- async def send_config():
242
- config = await self.get_config_data()
243
- await self.send(json.dumps({
244
- 'command': 'set_config', 'data': config
245
- }))
246
- asyncio.run(send_config())
247
- elif 'set_val' in payload:
248
- asyncio.run(self.send(json.dumps({
249
- 'command': 'set_val',
250
- 'id': payload.get('component_id'),
251
- 'val': payload['set_val']
252
- })))
281
+ try:
282
+ payload = json.loads(msg.payload)
283
+
284
+ if 'bulk_send' in payload:
285
+ colonel_component_ids = [c['id'] for c in Component.objects.filter(
286
+ config__colonel=self.colonel.id,
287
+ gateway__in=Gateway.objects.filter(type=FleetGatewayHandler.uid),
288
+ id__in=[int(id) for id in payload['bulk_send'].keys()]
289
+ ).values('id')]
290
+ bulk_send_data = []
291
+ for comp_id, value in payload['bulk_send'].items():
292
+ if int(comp_id) not in colonel_component_ids:
293
+ continue
294
+ bulk_send_data.append({'id': int(comp_id), 'val': value})
295
+ if bulk_send_data:
296
+ asyncio.run(self.send_data({
297
+ 'command': 'bulk_set',
298
+ 'values': bulk_send_data
299
+ }))
300
+ return
301
+
302
+ obj = get_event_obj(payload)
303
+
304
+ if obj == self.colonel:
305
+ if payload.get('command') == 'update_firmware':
306
+ asyncio.run(self.firmware_update(payload['to_version']))
307
+ elif payload.get('command') == 'update_config':
308
+ async def send_config():
309
+ config = await self.get_config_data()
310
+ await self.send_data({
311
+ 'command': 'set_config', 'data': config
312
+ }, compress=True)
313
+ asyncio.run(send_config())
314
+ elif payload.get('command') == 'discover-ttlock':
315
+ print("SEND discover-ttlock command!")
316
+ asyncio.run(self.send_data({
317
+ 'command': 'discover-ttlock'
318
+ }))
319
+ elif payload.get('command') == 'finalize':
320
+ asyncio.run(self.send_data({
321
+ 'command': 'finalize',
322
+ 'data': payload.get('data', {})
323
+ }))
324
+ else:
325
+ asyncio.run(self.send_data(payload))
326
+
327
+ elif isinstance(obj, Component):
328
+ if int(obj.config.get('colonel')) != self.colonel.id:
329
+ return
330
+ if 'set_val' in payload:
331
+ asyncio.run(self.send_data({
332
+ 'command': 'set_val',
333
+ 'id': obj.id,
334
+ 'val': payload['set_val']
335
+ }))
336
+ if 'update_options' in payload:
337
+ asyncio.run(self.send_data({
338
+ 'command': 'update_options',
339
+ 'id': obj.id,
340
+ 'options': payload['options']
341
+ }))
342
+
343
+ except Exception as e:
344
+ print(traceback.format_exc(), file=sys.stderr)
345
+
253
346
 
254
347
  async def receive(self, text_data=None, bytes_data=None):
255
348
  if text_data:
256
- print("[%s]" % str(self.colonel), text_data)
349
+ print(f"{self.colonel}: {text_data}")
257
350
  data = json.loads(text_data)
258
351
  if 'get_config' in data:
259
352
  config = await self.get_config_data()
260
- await self.send(json.dumps({
353
+ print("Send config: ", config)
354
+ await self.send_data({
261
355
  'command': 'set_config', 'data': config
262
- }))
356
+ }, compress=True)
263
357
  elif 'comp' in data:
264
358
  try:
359
+ try:
360
+ id=int(data['comp'])
361
+ except:
362
+ return
363
+
265
364
  component = await sync_to_async(
266
365
  Component.objects.get, thread_sensitive=True
267
- )(id=int(data['comp']))
366
+ )(id=id)
268
367
 
269
368
  if 'val' in data:
270
369
  def receive_val(val):
370
+ if data.get('actor'):
371
+ fingerprint = Fingerprint.objects.filter(
372
+ value=f"ttlock-{component.id}-{data.get('actor')}",
373
+ ).first()
374
+ component.change_init_fingerprint = fingerprint
271
375
  component.controller._receive_from_device(
272
376
  val, bool(data.get('alive'))
273
377
  )
@@ -276,7 +380,6 @@ class FleetConsumer(AsyncWebsocketConsumer):
276
380
  )(data['val'])
277
381
 
278
382
  if 'options' in data:
279
- print("Received options update")
280
383
  def receive_options(val):
281
384
  component.meta['options'] = val
282
385
  component.save()
@@ -284,8 +387,55 @@ class FleetConsumer(AsyncWebsocketConsumer):
284
387
  receive_options, thread_sensitive=True
285
388
  )(data['options'])
286
389
 
390
+ if 'codes' in data and component.controller_uid == TTLock.uid:
391
+ def save_codes(codes):
392
+ component.meta['codes'] = codes
393
+ for code in codes:
394
+ Fingerprint.objects.get_or_create(
395
+ value=f"ttlock-{component.id}-code-{str(code)}",
396
+ defaults={'type': "TTLock code"}
397
+ )
398
+ component.save()
399
+ await sync_to_async(
400
+ save_codes, thread_sensitive=True
401
+ )(data['codes'])
402
+ if 'fingerprints' in data and component.controller_uid == TTLock.uid:
403
+ def save_codes(codes):
404
+ component.meta['fingerprints'] = codes
405
+ for code in codes:
406
+ Fingerprint.objects.get_or_create(
407
+ value=f"ttlock-{component.id}-finger-{str(code)}",
408
+ defaults={'type': "TTLock Fingerprint"}
409
+ )
410
+ component.save()
411
+ await sync_to_async(
412
+ save_codes, thread_sensitive=True
413
+ )(data['fingerprints'])
414
+
287
415
  except Exception as e:
288
- print(e)
416
+ print(traceback.format_exc(), file=sys.stderr)
417
+
418
+ elif 'discover-ttlock' in data:
419
+ def process_discovery_result():
420
+ self.gateway.refresh_from_db()
421
+ if self.gateway.discovery.get('finished'):
422
+ return Component.objects.filter(
423
+ meta__finalization_data__temp_id=data['result']['id']
424
+ ).first()
425
+ try:
426
+ self.gateway.process_discovery(data)
427
+ except Exception as e:
428
+ print(traceback.format_exc(), file=sys.stderr)
429
+ self.gateway.finish_discovery()
430
+
431
+ finished_comp = await sync_to_async(
432
+ process_discovery_result, thread_sensitive=True
433
+ )()
434
+ if finished_comp:
435
+ await self.send_data({
436
+ 'command': 'finalize',
437
+ 'data': finished_comp.meta['finalization_data']
438
+ })
289
439
 
290
440
  elif bytes_data:
291
441
  if not self.colonel_logger:
@@ -294,6 +444,11 @@ class FleetConsumer(AsyncWebsocketConsumer):
294
444
  for logline in bytes_data.decode(errors='replace').split('\n'):
295
445
  self.colonel_logger.log(logging.INFO, logline)
296
446
 
447
+ await self.log_colonel_connected()
448
+
449
+
450
+ async def log_colonel_connected(self):
451
+
297
452
  def save_last_seen():
298
453
  self.colonel.socket_connected = True
299
454
  self.colonel.last_seen = timezone.now()
@@ -303,6 +458,14 @@ class FleetConsumer(AsyncWebsocketConsumer):
303
458
 
304
459
  await sync_to_async(save_last_seen, thread_sensitive=True)()
305
460
 
461
+ async def send_data(self, data, compress=False):
462
+ data = json.dumps(data)
463
+ if compress:
464
+ data = zlib.compress(data.encode())
465
+ await self.send(bytes_data=data)
466
+ else:
467
+ await self.send(data)
468
+
306
469
 
307
470
  async def start_logger(self):
308
471
  self.colonel_logger = logging.getLogger(
simo/fleet/utils.py CHANGED
@@ -51,26 +51,19 @@ BASE_ESP32_GPIO_PINS = {
51
51
  39: {'output': False, 'adc': True},
52
52
  }
53
53
 
54
- GPIO_PINS = {'generic': {}, 'wESP32': {}, '4-relays': {}, 'ample-wall': {}}
54
+ GPIO_PINS = {'generic': {}, '4-relays': {}, 'ample-wall': {}, 'game-changer': {}}
55
55
 
56
56
  for no, data in BASE_ESP32_GPIO_PINS.items():
57
57
  GPIO_PINS['generic'][no] = GPIO_PIN_DEFAULTS.copy()
58
58
 
59
- #wESP32 & ample-wall
59
+ # ample-wall
60
60
  for no, data in BASE_ESP32_GPIO_PINS.items():
61
- if no in (16, 17, 19, 21, 22, 23, 25, 26, 27):
62
- # occupied by LAN
63
- continue
64
-
65
- if no == 2:
66
- # onboard LED
67
- continue
68
-
69
- GPIO_PINS['wESP32'][no] = GPIO_PIN_DEFAULTS.copy()
70
- GPIO_PINS['wESP32'][no].update(data)
61
+ if no in (4, 12, 13, 14, 15, 23, 32, 33, 34, 36, 39):
62
+ GPIO_PINS['ample-wall'][no] = GPIO_PIN_DEFAULTS.copy()
63
+ GPIO_PINS['ample-wall'][no].update(data)
71
64
 
72
- GPIO_PINS['ample-wall'][no] = GPIO_PIN_DEFAULTS.copy()
73
- GPIO_PINS['ample-wall'][no].update(data)
65
+ GPIO_PINS['game-changer'][no] = GPIO_PIN_DEFAULTS.copy()
66
+ GPIO_PINS['game-changer'][no].update(data)
74
67
 
75
68
 
76
69
  for no in range(101, 126):
@@ -87,6 +80,14 @@ for no in range(126, 133):
87
80
  }
88
81
 
89
82
 
83
+ for no in range(101, 133):
84
+ GPIO_PINS['game-changer'][no] = {
85
+ 'output': True, 'input': True, 'default_pull': 'LOW',
86
+ 'native': False, 'adc': False,
87
+ 'capacitive': False, 'note': ''
88
+ }
89
+
90
+
90
91
  #4-relays
91
92
  for no, data in BASE_ESP32_GPIO_PINS.items():
92
93
  if no == 12:
@@ -113,42 +114,3 @@ for no, data in BASE_ESP32_GPIO_PINS.items():
113
114
  GPIO_PINS['4-relays'][no]['note'] = 'Relay4'
114
115
  else:
115
116
  GPIO_PINS['4-relays'][no].update(data)
116
-
117
-
118
- def get_available_gpio_pins(colonel=None, filters=None, selected=None):
119
- if not colonel:
120
- return {no: GPIO_PIN_DEFAULTS for no in range(200)}
121
- if not filters:
122
- filters = {}
123
- pins = {}
124
- allow_occupied = filters.pop('allow_occupied', None)
125
- for key, data in GPIO_PINS.get(colonel.type, {}).items():
126
- if str(key) in colonel.occupied_pins and not allow_occupied:
127
- if selected:
128
- if int(key) != int(selected):
129
- continue
130
- else:
131
- continue
132
- skip = False
133
- for filter_param, filter_val in filters.items():
134
- if data[filter_param] != filter_val:
135
- skip = True
136
- if skip:
137
- continue
138
- pins[key] = data
139
- return pins
140
-
141
-
142
- def get_gpio_pins_choices(colonel=None, filters=None, selected=None):
143
- choices = []
144
- for key, data in get_available_gpio_pins(
145
- colonel, filters, selected
146
- ).items():
147
- if key < 100:
148
- name = 'GPIO%d' % key
149
- else:
150
- name = 'E-%d' % (key - 100)
151
- if data.get('note'):
152
- name += ' | %s' % data['note']
153
- choices.append((key, name))
154
- return choices
simo/fleet/views.py CHANGED
@@ -1,32 +1,46 @@
1
- import os
2
- from django.http import FileResponse, HttpResponse, Http404
3
- from django.shortcuts import get_object_or_404
1
+ from django.contrib.contenttypes.models import ContentType
2
+ from django.http import HttpResponse, Http404
3
+ from django.db.models import Q
4
4
  from dal import autocomplete
5
- from .models import Colonel, I2CInterface
6
- from .utils import get_gpio_pins_choices
5
+ from simo.core.utils.helpers import search_queryset
6
+ from .models import Colonel, ColonelPin, I2CInterface
7
7
 
8
8
 
9
9
  def colonels_ping(request):
10
10
  return HttpResponse('pong')
11
11
 
12
12
 
13
- class PinsSelectAutocomplete(autocomplete.Select2ListView):
13
+ class PinsSelectAutocomplete(autocomplete.Select2QuerySetView):
14
14
 
15
- def get_list(self):
15
+ def get_queryset(self):
16
16
  if not self.request.user.is_staff:
17
- return []
17
+ return ColonelPin.objects.none()
18
18
 
19
19
  try:
20
- esp_device = Colonel.objects.get(
20
+ colonel = Colonel.objects.get(
21
21
  pk=self.forwarded.get("colonel")
22
22
  )
23
23
  except:
24
- return []
24
+ return ColonelPin.objects.none()
25
+
26
+ qs = ColonelPin.objects.filter(colonel=colonel)
27
+
28
+ if self.forwarded.get('self'):
29
+ qs = qs.filter(
30
+ Q(occupied_by_id=None) | Q(
31
+ id=int(self.forwarded['self'])
32
+ )
33
+ )
34
+ else:
35
+ qs = qs.filter(occupied_by_id=None)
36
+
37
+ if self.forwarded.get('filters'):
38
+ qs = qs.filter(**self.forwarded.get('filters'))
39
+
40
+ if self.q:
41
+ qs = search_queryset(qs, self.q, ('label', ))
25
42
 
26
- return get_gpio_pins_choices(
27
- esp_device, self.forwarded.get('filters'),
28
- self.forwarded.get('self')
29
- )
43
+ return qs
30
44
 
31
45
 
32
46
  class I2CInterfaceSelectAutocomplete(autocomplete.Select2ListView):