simo 1.7.20__py3-none-any.whl → 2.0.1__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 +134 -36
  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 +53 -29
  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 +140 -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.1.dist-info}/METADATA +8 -9
  170. {simo-1.7.20.dist-info → simo-2.0.1.dist-info}/RECORD +173 -189
  171. {simo-1.7.20.dist-info → simo-2.0.1.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.1.dist-info}/LICENSE.md +0 -0
  267. {simo-1.7.20.dist-info → simo-2.0.1.dist-info}/top_level.txt +0 -0
simo/fleet/models.py CHANGED
@@ -3,12 +3,15 @@ from django.db import transaction
3
3
  from django.db import models
4
4
  from django.db.models.signals import post_save, pre_delete, post_delete
5
5
  from django.dispatch import receiver
6
+ from django.contrib.contenttypes.models import ContentType
7
+ from django.contrib.contenttypes.fields import GenericForeignKey
6
8
  from dirtyfields import DirtyFieldsMixin
7
9
  from simo.core.models import Instance, Gateway, Component
8
10
  from simo.core.utils.helpers import get_random_string
9
11
  from simo.core.events import GatewayObjectCommand
10
12
  from .gateways import FleetGatewayHandler
11
- from .utils import get_gpio_pins_choices
13
+ from .managers import ColonelsManager, ColonelPinsManager, I2CInterfacesManager
14
+ from .utils import GPIO_PINS
12
15
 
13
16
 
14
17
 
@@ -49,10 +52,11 @@ class Colonel(DirtyFieldsMixin, models.Model):
49
52
  )
50
53
  name = models.CharField(max_length=100, blank=True)
51
54
  type = models.CharField(
52
- max_length=20, default='wESP32',
55
+ max_length=20, default='ample-wall',
53
56
  choices=(
54
- ('wESP32', 'wESP32'), ('4-relays', '4 Relays'),
55
- ('ample-wall', "Ample Wall")
57
+ ('4-relays', "4 Relay"),
58
+ ('ample-wall', "Ample Wall"),
59
+ ('game-changer', "Game Changer"),
56
60
  )
57
61
  )
58
62
  firmware_version = models.CharField(
@@ -81,11 +85,11 @@ class Colonel(DirtyFieldsMixin, models.Model):
81
85
  default=False, help_text="Might cause unnecessary overhead. "
82
86
  "Better to leave this off if things are running smoothly."
83
87
  )
84
- pwm_frequency = models.IntegerField(default=0, choices=(
88
+ pwm_frequency = models.IntegerField(default=1, choices=(
85
89
  (0, "3kHz"), (1, "22kHz")
86
90
  ), help_text="Affects Ample Wall dimmer PWM output (dimmer) frequency")
87
91
 
88
- is_authorized = models.BooleanField(default=False, help_text="Temporrary field")
92
+ objects = ColonelsManager()
89
93
 
90
94
  def __str__(self):
91
95
  return self.name if self.name else self.uid
@@ -95,20 +99,13 @@ class Colonel(DirtyFieldsMixin, models.Model):
95
99
  for comp in self.components.all():
96
100
  comp.alive = self.is_connected
97
101
  comp.save()
98
- is_new = self.pk is None
99
102
 
100
103
  if self.minor_upgrade_available and self.firmware_version == self.minor_upgrade_available:
101
104
  self.minor_upgrade_available = None
102
105
  if self.major_upgrade_available and self.firmware_version == self.major_upgrade_available:
103
106
  self.major_upgrade_available = None
104
107
 
105
- obj = super().save(*args, **kwargs)
106
- if is_new and self.type == 'ample-wall':
107
- I2CInterface.objects.create(
108
- colonel=self, name="Main", no=0, scl_pin=4, sda_pin=15,
109
- freq=100000
110
- )
111
- return obj
108
+ return super().save(*args, **kwargs)
112
109
 
113
110
  @property
114
111
  def is_connected(self):
@@ -161,21 +158,28 @@ class Colonel(DirtyFieldsMixin, models.Model):
161
158
  gateway, self, command='update_config'
162
159
  ).publish()
163
160
 
161
+ @transaction.atomic
164
162
  def rebuild_occupied_pins(self):
165
- self.occupied_pins = {}
163
+ for pin in ColonelPin.objects.filter(colonel=self):
164
+ if isinstance(pin.occupied_by, Component):
165
+ pin.occupied_by_content_type = None
166
+ pin.occupied_by_id = None
167
+ pin.save()
168
+
166
169
  for component in self.components.all():
167
170
  try:
168
171
  pins = component.controller._get_occupied_pins()
169
172
  except:
170
173
  pins = []
171
- for pin in pins:
172
- self.occupied_pins[pin] = component.id
174
+ for no in pins:
175
+ pin, new = ColonelPin.objects.get_or_create(colonel=self, no=no)
176
+ pin.occupied_by = component
177
+ pin.save()
173
178
 
174
- for i2c_interface in self.i2c_interfaces.all():
175
- self.occupied_pins[i2c_interface.scl_pin] = 'scl_%d' % i2c_interface.no
176
- self.occupied_pins[i2c_interface.sda_pin] = 'sda_%d' % i2c_interface.no
177
179
 
180
+ @transaction.atomic()
178
181
  def move_to(self, other_colonel):
182
+ # TODO: Need to replace pins on components!
179
183
  other_colonel.refresh_from_db()
180
184
  assert list(other_colonel.components.all()) == [], \
181
185
  "Other colonel must be completely empty!"
@@ -184,21 +188,102 @@ class Colonel(DirtyFieldsMixin, models.Model):
184
188
  component.config['colonel'] = other_colonel.id
185
189
  component.save()
186
190
  self.components.remove(component)
187
- other_colonel.add(component)
188
-
189
- other_colonel.i2c_interfaces.all().delete()
190
- self.i2c_interfaces.all().update(colonel=other_colonel)
191
+ other_colonel.components.add(component)
191
192
 
192
193
  self.rebuild_occupied_pins()
193
- self.save()
194
-
195
194
  other_colonel.rebuild_occupied_pins()
196
- other_colonel.save()
195
+
196
+ other_colonel.i2c_interfaces.all().delete()
197
+
198
+ for i2c_interface in self.i2c_interfaces.all():
199
+ I2CInterface.objects.create(
200
+ colonel=other_colonel, name=i2c_interface.name,
201
+ freq=i2c_interface.freq,
202
+ scl_pin=ColonelPin.objects.get(
203
+ colonel=other_colonel, no=i2c_interface.scl_pin.no,
204
+ ),
205
+ sda_pin=ColonelPin.objects.get(
206
+ colonel=other_colonel, no=i2c_interface.sda_pin.no,
207
+ ),
208
+ )
197
209
 
198
210
  self.update_config()
199
211
  other_colonel.update_config()
200
212
 
201
213
 
214
+ class ColonelPin(models.Model):
215
+ colonel = models.ForeignKey(
216
+ Colonel, related_name='pins', on_delete=models.CASCADE
217
+ )
218
+ no = models.PositiveIntegerField()
219
+ label = models.CharField(db_index=True, max_length=200)
220
+ input = models.BooleanField(default=False, db_index=True)
221
+ output = models.BooleanField(default=False, db_index=True)
222
+ capacitive = models.BooleanField(default=False, db_index=True)
223
+ adc = models.BooleanField(default=False)
224
+ native = models.BooleanField(default=True, db_index=True)
225
+ default_pull = models.CharField(
226
+ max_length=50, db_index=True, null=True, blank=True,
227
+ choices=(('LOW', "LOW"), ("HIGH", "HIGH"))
228
+ )
229
+ note = models.CharField(max_length=100)
230
+ occupied_by_content_type = models.ForeignKey(
231
+ ContentType, on_delete=models.CASCADE, null=True
232
+ )
233
+ occupied_by_id = models.PositiveIntegerField(null=True)
234
+ occupied_by = GenericForeignKey(
235
+ "occupied_by_content_type", "occupied_by_id"
236
+ )
237
+
238
+ objects = ColonelPinsManager()
239
+
240
+ class Meta:
241
+ unique_together = 'colonel', 'no'
242
+ indexes = [
243
+ models.Index(
244
+ fields=["occupied_by_content_type", "occupied_by_id"]
245
+ ),
246
+ ]
247
+ def __str__(self):
248
+ if not self.label:
249
+ # Might be created via migration...
250
+ self.save()
251
+ return self.label
252
+
253
+ def save(self, *args, **kwargs):
254
+ if self.native:
255
+ self.label = f'GPIO{self.no}'
256
+ else:
257
+ no = self.no - 100
258
+ self.label = f'IO{no}'
259
+ if self.note:
260
+ self.label += ' | %s' % self.note
261
+ return super().save(*args, **kwargs)
262
+
263
+
264
+ @receiver(post_save, sender=Colonel)
265
+ def after_colonel_save(sender, instance, created, *args, **kwargs):
266
+ if not created:
267
+ return
268
+
269
+ def after_update():
270
+ for no, data in GPIO_PINS.get(instance.type).items():
271
+ ColonelPin.objects.get_or_create(
272
+ colonel=instance, no=no,
273
+ input=data.get('input'), output=data.get('output'),
274
+ capacitive=data.get('capacitive'), adc=data.get('adc'),
275
+ native=data.get('native'), note=data.get('note')
276
+ )
277
+ if instance.type in ('ample-wall', 'game-changer'):
278
+ I2CInterface.objects.create(
279
+ colonel=instance, name='Main', no=0,
280
+ scl_pin=ColonelPin.objects.get(colonel=instance, no=4),
281
+ sda_pin=ColonelPin.objects.get(colonel=instance, no=15),
282
+ )
283
+ instance.update_config()
284
+
285
+ transaction.on_commit(after_update)
286
+
202
287
 
203
288
  @receiver(pre_delete, sender=Component)
204
289
  def post_component_delete(sender, instance, *args, **kwargs):
@@ -211,8 +296,7 @@ def post_component_delete(sender, instance, *args, **kwargs):
211
296
  for colonel in affected_colonels:
212
297
  print("Rebuild occupied pins for :", colonel)
213
298
  colonel.rebuild_occupied_pins()
214
- colonel.save()
215
- colonel.restart()
299
+ colonel.update_config()
216
300
 
217
301
  transaction.on_commit(update_colonel)
218
302
 
@@ -231,12 +315,24 @@ class I2CInterface(models.Model):
231
315
  no = models.IntegerField(
232
316
  default=0, choices=i2c_interface_no_choices
233
317
  )
234
- scl_pin = models.IntegerField(default=4, choices=get_gpio_pins_choices())
235
- sda_pin = models.IntegerField(default=15, choices=get_gpio_pins_choices())
318
+ scl_pin = models.ForeignKey(
319
+ ColonelPin, on_delete=models.CASCADE, limit_choices_to={
320
+ 'native': True, 'output': True,
321
+ },
322
+ null=True, related_name='i2c_scl'
323
+ )
324
+ sda_pin = models.ForeignKey(
325
+ ColonelPin, on_delete=models.CASCADE, limit_choices_to={
326
+ 'native': True, 'output': True,
327
+ },
328
+ null=True, related_name='i2c_sda'
329
+ )
236
330
  freq = models.IntegerField(
237
331
  default=100000, help_text="100000 - is a good middle point!"
238
332
  )
239
333
 
334
+ objects = I2CInterfacesManager()
335
+
240
336
  class Meta:
241
337
  unique_together = 'colonel', 'no'
242
338
 
@@ -244,56 +340,18 @@ class I2CInterface(models.Model):
244
340
  return self.name
245
341
 
246
342
 
247
- @receiver(post_delete, sender=I2CInterface)
248
- def post_i2c_interface_delete(sender, instance, *args, **kwargs):
249
-
250
- def update_colonel():
251
- try:
252
- instance.colonel.rebuild_occupied_pins()
253
- instance.colonel.save()
254
- except Colonel.DoesNotExist: # deleting colonel
255
- pass
256
- transaction.on_commit(update_colonel)
257
-
258
-
259
343
  @receiver(post_save, sender=I2CInterface)
260
344
  def post_i2c_interface_delete(sender, instance, *args, **kwargs):
261
-
262
- def update_colonel():
263
- instance.colonel.rebuild_occupied_pins()
264
- instance.colonel.save()
265
- transaction.on_commit(update_colonel)
266
-
267
-
268
- # class BLEDevice(models.Model):
269
- # mac = models.CharField(max_length=50, unique=True)
270
- # name = models.CharField(max_length=50)
271
- # addr = models.BinaryField(max_length=50)
272
- # type = models.PositiveIntegerField(default=0, choices=(
273
- # (0, "Unknown"),
274
- # (BLE_DEVICE_TYPE_GOVEE_MULTISENSOR, "GOVEE Climate sensor")
275
- # ))
276
- # last_seen = models.DateTimeField(auto_now_add=True)
277
- # component = models.ForeignKey(
278
- # Component, null=True, blank=True, on_delete=models.SET_NULL,
279
- # help_text='Only for tracking if it is already used as a component'
280
- # )
281
- # colonels = models.ManyToManyField(
282
- # Colonel, through='ColonelBLEDevice', related_name='ble_devices'
283
- # )
284
- #
285
- # def __str__(self):
286
- # return '%s (%s)' % (self.name, self.mac)
287
- #
288
- #
289
- # class ColonelBLEDevice(models.Model):
290
- # colonel = models.ForeignKey(Colonel, on_delete=models.CASCADE)
291
- # device = models.ForeignKey(BLEDevice, on_delete=models.CASCADE)
292
- # last_seen = models.DateTimeField(auto_now_add=True)
293
- # data = JSONField(default={})
294
- #
295
- # def save(self, *args, **kwargs):
296
- # obj = super().save(*args, **kwargs)
297
- # self.device.last_seen = self.last_seen
298
- # self.device.save()
299
- # return obj
345
+ with transaction.atomic():
346
+ ct = ContentType.objects.get_for_model(instance)
347
+ for pin in ColonelPin.objects.filter(
348
+ occupied_by_content_type=ct,
349
+ occupied_by_id=instance.id
350
+ ):
351
+ pin.occupied_by_content_type = None
352
+ pin.occupied_by_content_id = None
353
+ pin.save()
354
+ instance.scl_pin.occupied_by = instance
355
+ instance.scl_pin.save()
356
+ instance.sda_pin.occupied_by = instance
357
+ instance.sda_pin.save()
simo/fleet/serializers.py CHANGED
@@ -1,5 +1,5 @@
1
1
  from rest_framework import serializers
2
- from .models import InstanceOptions
2
+ from .models import InstanceOptions, Colonel, ColonelPin
3
3
 
4
4
 
5
5
  class InstanceOptionsSerializer(serializers.ModelSerializer):
@@ -11,3 +11,37 @@ class InstanceOptionsSerializer(serializers.ModelSerializer):
11
11
 
12
12
  def get_instance(self, obj):
13
13
  return obj.instance.uid
14
+
15
+
16
+ class ColonelPinSerializer(serializers.ModelSerializer):
17
+ occupied = serializers.SerializerMethodField()
18
+
19
+ class Meta:
20
+ model = ColonelPin
21
+ fields = 'id', 'label', 'occupied'
22
+ read_only_fields = fields
23
+
24
+ def get_occupied(self, obj):
25
+ return bool(obj.occupied_by)
26
+
27
+
28
+ class ColonelSerializer(serializers.ModelSerializer):
29
+ pins = serializers.SerializerMethodField()
30
+
31
+ class Meta:
32
+ model = Colonel
33
+ fields = (
34
+ 'id', 'uid', 'name', 'type', 'firmware_version', 'firmware_auto_update',
35
+ 'socket_connected', 'last_seen', 'enabled', 'pwm_frequency',
36
+ 'logs_stream', 'pins'
37
+ )
38
+ read_only_fields = [
39
+ 'uid', 'type', 'firmware_version', 'socket_connected',
40
+ 'last_seen', 'pins'
41
+ ]
42
+
43
+ def get_pins(self, obj):
44
+ result = []
45
+ for pin in obj.pins.all():
46
+ result.append(ColonelPinSerializer(pin).data)
47
+ return result