simo 2.0.5__py3-none-any.whl → 2.0.7__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 (183) hide show
  1. simo/__pycache__/settings.cpython-38.pyc +0 -0
  2. simo/__pycache__/urls.cpython-38.pyc +0 -0
  3. simo/core/__pycache__/admin.cpython-38.pyc +0 -0
  4. simo/core/__pycache__/api.cpython-38.pyc +0 -0
  5. simo/core/__pycache__/api_auth.cpython-38.pyc +0 -0
  6. simo/core/__pycache__/api_meta.cpython-38.pyc +0 -0
  7. simo/core/__pycache__/auto_urls.cpython-38.pyc +0 -0
  8. simo/core/__pycache__/autocomplete_views.cpython-38.pyc +0 -0
  9. simo/core/__pycache__/base_types.cpython-38.pyc +0 -0
  10. simo/core/__pycache__/context.cpython-38.pyc +0 -0
  11. simo/core/__pycache__/controllers.cpython-38.pyc +0 -0
  12. simo/core/__pycache__/events.cpython-38.pyc +0 -0
  13. simo/core/__pycache__/forms.cpython-38.pyc +0 -0
  14. simo/core/__pycache__/gateways.cpython-38.pyc +0 -0
  15. simo/core/__pycache__/managers.cpython-38.pyc +0 -0
  16. simo/core/__pycache__/middleware.cpython-38.pyc +0 -0
  17. simo/core/__pycache__/models.cpython-38.pyc +0 -0
  18. simo/core/__pycache__/permissions.cpython-38.pyc +0 -0
  19. simo/core/__pycache__/serializers.cpython-38.pyc +0 -0
  20. simo/core/__pycache__/socket_consumers.cpython-38.pyc +0 -0
  21. simo/core/__pycache__/tasks.cpython-38.pyc +0 -0
  22. simo/core/__pycache__/views.cpython-38.pyc +0 -0
  23. simo/core/admin.py +10 -10
  24. simo/core/api.py +7 -6
  25. simo/core/controllers.py +1 -0
  26. simo/core/db_backend/__pycache__/__init__.cpython-38.pyc +0 -0
  27. simo/core/db_backend/__pycache__/base.cpython-38.pyc +0 -0
  28. simo/core/drf_braces/__pycache__/__init__.cpython-38.pyc +0 -0
  29. simo/core/drf_braces/__pycache__/utils.cpython-38.pyc +0 -0
  30. simo/core/drf_braces/fields/__pycache__/__init__.cpython-38.pyc +0 -0
  31. simo/core/drf_braces/fields/__pycache__/_fields.cpython-38.pyc +0 -0
  32. simo/core/drf_braces/fields/__pycache__/custom.cpython-38.pyc +0 -0
  33. simo/core/drf_braces/fields/__pycache__/mixins.cpython-38.pyc +0 -0
  34. simo/core/drf_braces/fields/__pycache__/modified.cpython-38.pyc +0 -0
  35. simo/core/drf_braces/serializers/__pycache__/__init__.cpython-38.pyc +0 -0
  36. simo/core/drf_braces/serializers/__pycache__/form_serializer.cpython-38.pyc +0 -0
  37. simo/core/forms.py +8 -6
  38. simo/core/migrations/0030_alter_instance_timezone.py +18 -0
  39. simo/core/migrations/__pycache__/0001_initial.cpython-38.pyc +0 -0
  40. simo/core/migrations/__pycache__/0002_load_icons.cpython-38.pyc +0 -0
  41. simo/core/migrations/__pycache__/0003_create_default_zones_and_categories.cpython-38.pyc +0 -0
  42. simo/core/migrations/__pycache__/0004_create_generic.cpython-38.pyc +0 -0
  43. simo/core/migrations/__pycache__/0005_component_subcomponents.cpython-38.pyc +0 -0
  44. simo/core/migrations/__pycache__/0006_alter_component_subcomponents.cpython-38.pyc +0 -0
  45. simo/core/migrations/__pycache__/0007_component_change_init_to.cpython-38.pyc +0 -0
  46. simo/core/migrations/__pycache__/0008_alter_component_change_init_to.cpython-38.pyc +0 -0
  47. simo/core/migrations/__pycache__/0009_auto_20220707_1404.cpython-38.pyc +0 -0
  48. simo/core/migrations/__pycache__/0010_historyaggregate.cpython-38.pyc +0 -0
  49. simo/core/migrations/__pycache__/0011_component_last_change.cpython-38.pyc +0 -0
  50. simo/core/migrations/__pycache__/0012_instance.cpython-38.pyc +0 -0
  51. simo/core/migrations/__pycache__/0013_auto_20231003_0754.cpython-38.pyc +0 -0
  52. simo/core/migrations/__pycache__/0014_zone_instance.cpython-38.pyc +0 -0
  53. simo/core/migrations/__pycache__/0015_auto_20231004_1113.cpython-38.pyc +0 -0
  54. simo/core/migrations/__pycache__/0016_auto_20231004_1113.cpython-38.pyc +0 -0
  55. simo/core/migrations/__pycache__/0017_auto_20231004_1313.cpython-38.pyc +0 -0
  56. simo/core/migrations/__pycache__/0018_auto_20231005_0622.cpython-38.pyc +0 -0
  57. simo/core/migrations/__pycache__/0019_alter_gateway_type.cpython-38.pyc +0 -0
  58. simo/core/migrations/__pycache__/0020_component_meta.cpython-38.pyc +0 -0
  59. simo/core/migrations/__pycache__/0021_auto_20231020_1041.cpython-38.pyc +0 -0
  60. simo/core/migrations/__pycache__/0026_category_instance.cpython-38.pyc +0 -0
  61. simo/core/migrations/__pycache__/0027_remove_component_tags.cpython-38.pyc +0 -0
  62. simo/core/migrations/__pycache__/0028_rename_subcomponents_component_slaves.cpython-38.pyc +0 -0
  63. simo/core/migrations/__pycache__/0029_auto_20240229_1331.cpython-38.pyc +0 -0
  64. simo/core/migrations/__pycache__/0030_alter_instance_timezone.cpython-38.pyc +0 -0
  65. simo/core/migrations/__pycache__/__init__.cpython-38.pyc +0 -0
  66. simo/core/models.py +31 -46
  67. simo/core/serializers.py +3 -4
  68. simo/core/templatetags/__pycache__/__init__.cpython-38.pyc +0 -0
  69. simo/core/templatetags/__pycache__/components_list.cpython-38.pyc +0 -0
  70. simo/core/utils/__pycache__/__init__.cpython-38.pyc +0 -0
  71. simo/core/utils/__pycache__/admin.cpython-38.pyc +0 -0
  72. simo/core/utils/__pycache__/config_values.cpython-38.pyc +0 -0
  73. simo/core/utils/__pycache__/easing.cpython-38.pyc +0 -0
  74. simo/core/utils/__pycache__/form_fields.cpython-38.pyc +0 -0
  75. simo/core/utils/__pycache__/form_widgets.cpython-38.pyc +0 -0
  76. simo/core/utils/__pycache__/formsets.cpython-38.pyc +0 -0
  77. simo/core/utils/__pycache__/helpers.cpython-38.pyc +0 -0
  78. simo/core/utils/__pycache__/logs.cpython-38.pyc +0 -0
  79. simo/core/utils/__pycache__/mixins.cpython-38.pyc +0 -0
  80. simo/core/utils/__pycache__/model_helpers.cpython-38.pyc +0 -0
  81. simo/core/utils/__pycache__/relay.cpython-38.pyc +0 -0
  82. simo/core/utils/__pycache__/serialization.cpython-38.pyc +0 -0
  83. simo/core/utils/__pycache__/type_constants.cpython-38.pyc +0 -0
  84. simo/core/utils/__pycache__/validators.cpython-38.pyc +0 -0
  85. simo/core/utils/serialization.py +4 -2
  86. simo/core/utils/type_constants.py +32 -11
  87. simo/fleet/__pycache__/admin.cpython-38.pyc +0 -0
  88. simo/fleet/__pycache__/api.cpython-38.pyc +0 -0
  89. simo/fleet/__pycache__/auto_urls.cpython-38.pyc +0 -0
  90. simo/fleet/__pycache__/controllers.cpython-38.pyc +0 -0
  91. simo/fleet/__pycache__/forms.cpython-38.pyc +0 -0
  92. simo/fleet/__pycache__/gateways.cpython-38.pyc +0 -0
  93. simo/fleet/__pycache__/managers.cpython-38.pyc +0 -0
  94. simo/fleet/__pycache__/models.cpython-38.pyc +0 -0
  95. simo/fleet/__pycache__/serializers.cpython-38.pyc +0 -0
  96. simo/fleet/__pycache__/socket_consumers.cpython-38.pyc +0 -0
  97. simo/fleet/__pycache__/utils.cpython-38.pyc +0 -0
  98. simo/fleet/__pycache__/views.cpython-38.pyc +0 -0
  99. simo/fleet/admin.py +6 -6
  100. simo/fleet/auto_urls.py +4 -4
  101. simo/fleet/controllers.py +75 -2
  102. simo/fleet/forms.py +68 -33
  103. simo/fleet/gateways.py +7 -3
  104. simo/fleet/managers.py +4 -2
  105. simo/fleet/migrations/0032_auto_20240415_0736.py +33 -0
  106. simo/fleet/migrations/0033_auto_20240415_0736.py +28 -0
  107. simo/fleet/migrations/__pycache__/0025_auto_20240130_1334.cpython-38.pyc +0 -0
  108. simo/fleet/migrations/__pycache__/0026_rename_i2cinterface_scl_pin_and_more.cpython-38.pyc +0 -0
  109. simo/fleet/migrations/__pycache__/0027_auto_20240306_0802.cpython-38.pyc +0 -0
  110. simo/fleet/migrations/__pycache__/0028_remove_i2cinterface_scl_pin_no_and_more.cpython-38.pyc +0 -0
  111. simo/fleet/migrations/__pycache__/0029_alter_i2cinterface_scl_pin_and_more.cpython-38.pyc +0 -0
  112. simo/fleet/migrations/__pycache__/0030_colonelpin_label_alter_colonel_type.cpython-38.pyc +0 -0
  113. simo/fleet/migrations/__pycache__/0031_alter_colonel_type.cpython-38.pyc +0 -0
  114. simo/fleet/migrations/__pycache__/0032_auto_20240415_0736.cpython-38.pyc +0 -0
  115. simo/fleet/migrations/__pycache__/0033_auto_20240415_0736.cpython-38.pyc +0 -0
  116. simo/fleet/models.py +82 -14
  117. simo/fleet/socket_consumers.py +19 -12
  118. simo/fleet/utils.py +6 -1
  119. simo/fleet/views.py +10 -9
  120. simo/generic/__pycache__/__init__.cpython-38.pyc +0 -0
  121. simo/generic/__pycache__/app_widgets.cpython-38.pyc +0 -0
  122. simo/generic/__pycache__/base_types.cpython-38.pyc +0 -0
  123. simo/generic/__pycache__/controllers.cpython-38.pyc +0 -0
  124. simo/generic/__pycache__/forms.cpython-38.pyc +0 -0
  125. simo/generic/__pycache__/gateways.cpython-38.pyc +0 -0
  126. simo/generic/__pycache__/models.cpython-38.pyc +0 -0
  127. simo/generic/__pycache__/routing.cpython-38.pyc +0 -0
  128. simo/generic/__pycache__/socket_consumers.cpython-38.pyc +0 -0
  129. simo/generic/controllers.py +0 -7
  130. simo/generic/forms.py +0 -3
  131. simo/generic/gateways.py +0 -8
  132. simo/multimedia/__pycache__/__init__.cpython-38.pyc +0 -0
  133. simo/multimedia/__pycache__/admin.cpython-38.pyc +0 -0
  134. simo/multimedia/__pycache__/api.cpython-38.pyc +0 -0
  135. simo/multimedia/__pycache__/app_widgets.cpython-38.pyc +0 -0
  136. simo/multimedia/__pycache__/base_types.cpython-38.pyc +0 -0
  137. simo/multimedia/__pycache__/controllers.cpython-38.pyc +0 -0
  138. simo/multimedia/__pycache__/forms.cpython-38.pyc +0 -0
  139. simo/multimedia/__pycache__/models.cpython-38.pyc +0 -0
  140. simo/multimedia/__pycache__/serializers.cpython-38.pyc +0 -0
  141. simo/multimedia/migrations/__pycache__/0001_initial.cpython-38.pyc +0 -0
  142. simo/multimedia/migrations/__pycache__/0002_sound_length.cpython-38.pyc +0 -0
  143. simo/multimedia/migrations/__pycache__/0003_alter_sound_length.cpython-38.pyc +0 -0
  144. simo/multimedia/migrations/__pycache__/0004_auto_20231023_1055.cpython-38.pyc +0 -0
  145. simo/multimedia/migrations/__pycache__/__init__.cpython-38.pyc +0 -0
  146. simo/notifications/__pycache__/__init__.cpython-38.pyc +0 -0
  147. simo/notifications/__pycache__/admin.cpython-38.pyc +0 -0
  148. simo/notifications/__pycache__/api.cpython-38.pyc +0 -0
  149. simo/notifications/__pycache__/models.cpython-38.pyc +0 -0
  150. simo/notifications/__pycache__/serializers.cpython-38.pyc +0 -0
  151. simo/notifications/migrations/__pycache__/0001_initial.cpython-38.pyc +0 -0
  152. simo/notifications/migrations/__pycache__/0002_notification_instance.cpython-38.pyc +0 -0
  153. simo/notifications/migrations/__pycache__/__init__.cpython-38.pyc +0 -0
  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__/middleware.cpython-38.pyc +0 -0
  158. simo/users/__pycache__/models.cpython-38.pyc +0 -0
  159. simo/users/__pycache__/serializers.cpython-38.pyc +0 -0
  160. simo/users/__pycache__/sso_urls.cpython-38.pyc +0 -0
  161. simo/users/migrations/__pycache__/0001_initial.cpython-38.pyc +0 -0
  162. simo/users/migrations/__pycache__/0002_componentpermission.cpython-38.pyc +0 -0
  163. simo/users/migrations/__pycache__/0003_create_roles_and_system_user.cpython-38.pyc +0 -0
  164. simo/users/migrations/__pycache__/0004_user_secret_key.cpython-38.pyc +0 -0
  165. simo/users/migrations/__pycache__/0005_permissionsrole_instance.cpython-38.pyc +0 -0
  166. simo/users/migrations/__pycache__/0006_auto_20231003_0850.cpython-38.pyc +0 -0
  167. simo/users/migrations/__pycache__/0007_auto_20231003_1228.cpython-38.pyc +0 -0
  168. simo/users/migrations/__pycache__/0008_auto_20231003_1229.cpython-38.pyc +0 -0
  169. simo/users/migrations/__pycache__/0009_remove_user_role.cpython-38.pyc +0 -0
  170. simo/users/migrations/__pycache__/0010_auto_20231004_1313.cpython-38.pyc +0 -0
  171. simo/users/migrations/__pycache__/0011_auto_20231004_1313.cpython-38.pyc +0 -0
  172. simo/users/migrations/__pycache__/0012_alter_userinstancerole_unique_together.cpython-38.pyc +0 -0
  173. simo/users/migrations/__pycache__/0013_remove_user_roles.cpython-38.pyc +0 -0
  174. simo/users/migrations/__pycache__/0014_user_roles.cpython-38.pyc +0 -0
  175. simo/users/migrations/__pycache__/0015_remove_user_at_home.cpython-38.pyc +0 -0
  176. simo/users/migrations/__pycache__/0016_auto_20231005_1050.cpython-38.pyc +0 -0
  177. simo/users/migrations/__pycache__/0025_rename_name_fingerprint_type_and_more.cpython-38.pyc +0 -0
  178. simo/users/migrations/__pycache__/__init__.cpython-38.pyc +0 -0
  179. {simo-2.0.5.dist-info → simo-2.0.7.dist-info}/METADATA +1 -1
  180. {simo-2.0.5.dist-info → simo-2.0.7.dist-info}/RECORD +183 -89
  181. {simo-2.0.5.dist-info → simo-2.0.7.dist-info}/LICENSE.md +0 -0
  182. {simo-2.0.5.dist-info → simo-2.0.7.dist-info}/WHEEL +0 -0
  183. {simo-2.0.5.dist-info → simo-2.0.7.dist-info}/top_level.txt +0 -0
@@ -21,14 +21,14 @@ def serialize_form_data(data):
21
21
  serialized_data[field_name] = {
22
22
  'model': 'many',
23
23
  'val': json.loads(model_serializers.serialize(
24
- 'json', val
24
+ 'json', val, fields=['pk']
25
25
  ))
26
26
  }
27
27
  else:
28
28
  serialized_data[field_name] = {
29
29
  'model': 'single',
30
30
  'val': json.loads(model_serializers.serialize(
31
- 'json', [val]
31
+ 'json', [val], fields=['pk']
32
32
  ))
33
33
  }
34
34
  else:
@@ -46,11 +46,13 @@ def deserialize_form_data(data):
46
46
  if val['model'] == 'single':
47
47
  for item in deserializer_generator:
48
48
  deserialized_data[field_name] = item.object
49
+ deserialized_data[field_name].refresh_from_db()
49
50
  break
50
51
  else:
51
52
  deserialized_data[field_name] = []
52
53
  for item in deserializer_generator:
53
54
  deserialized_data[field_name].append(item.object)
55
+ deserialized_data[field_name][-1].refresh_from_db()
54
56
  else:
55
57
  deserialized_data[field_name] = val
56
58
  return deserialized_data
@@ -19,20 +19,32 @@ def get_controller_types_map(gateway=None):
19
19
  except ModuleNotFoundError:
20
20
  continue
21
21
  for cls_name, cls in configs.__dict__.items():
22
- if inspect.isclass(cls) and issubclass(cls, ControllerBase) \
23
- and not inspect.isabstract(cls):
24
- if gateway:
22
+ if not inspect.isclass(cls):
23
+ continue
24
+ if not issubclass(cls, ControllerBase):
25
+ continue
26
+ if inspect.isabstract(cls):
27
+ continue
28
+ if gateway:
29
+ if issubclass(gateway, BaseGatewayHandler) \
30
+ or isinstance(gateway, BaseGatewayHandler):
31
+ if gateway.uid != cls.gateway_class.uid:
32
+ continue
33
+ else:
25
34
  try:
26
- same = gateway.type == cls.gateway_class.uid
35
+ same = gateway.handler.uid == cls.gateway_class.uid
27
36
  except:
28
37
  continue
29
38
  else:
30
39
  if not same:
31
40
  continue
32
- controllers_map[cls.uid] = cls
41
+ controllers_map[cls.uid] = cls
33
42
  return controllers_map
34
43
 
35
44
 
45
+ CONTROLLER_TYPES_MAP = get_controller_types_map()
46
+
47
+
36
48
  def get_controller_types_choices(gateway=None):
37
49
  choices = []
38
50
  for controller_cls in get_controller_types_map(gateway).values():
@@ -40,11 +52,8 @@ def get_controller_types_choices(gateway=None):
40
52
  return choices
41
53
 
42
54
 
43
- #ALL_CONTROLLER_TYPES = get_controller_types_map()
44
- # CONTROLLER_TYPE_CHOICES = [
45
- # (slug, cls.name) for slug, cls in ALL_CONTROLLER_TYPES.items()
46
- # ]
47
- # CONTROLLER_TYPE_CHOICES.sort(key=lambda e: e[0])
55
+ CONTROLLER_TYPES_CHOICES = get_controller_types_choices()
56
+
48
57
 
49
58
  def get_all_gateways():
50
59
  all_gateways = {}
@@ -65,13 +74,25 @@ def get_all_gateways():
65
74
  return all_gateways
66
75
 
67
76
 
77
+ GATEWAYS_MAP = get_all_gateways()
78
+
79
+
68
80
  def get_gateway_choices():
69
81
  choices = [
70
- (slug, cls.name) for slug, cls in get_all_gateways().items()
82
+ (slug, cls.name) for slug, cls in GATEWAYS_MAP.items()
71
83
  ]
72
84
  choices.sort(key=lambda e: e[1])
73
85
  return choices
74
86
 
87
+ GATEWAYS_CHOICES = get_gateway_choices()
88
+
89
+
90
+ CONTROLLERS_BY_GATEWAY = {}
91
+ for gateway_slug, gateway_cls in GATEWAYS_MAP.items():
92
+ CONTROLLERS_BY_GATEWAY[gateway_slug] = {}
93
+ for ctrl_uid, ctrl_cls in get_controller_types_map(gateway_cls).items():
94
+ CONTROLLERS_BY_GATEWAY[gateway_slug][ctrl_uid] = ctrl_cls
95
+
75
96
 
76
97
  ALL_BASE_TYPES = {}
77
98
  for name, app in apps.app_configs.items():
Binary file
Binary file
Binary file
Binary file
Binary file
simo/fleet/admin.py CHANGED
@@ -5,14 +5,14 @@ from django.template.loader import render_to_string
5
5
  from django.templatetags.static import static
6
6
  from simo.core.models import Component
7
7
  from simo.core.utils.admin import FormAction
8
- from .models import Colonel, I2CInterface, ColonelPin
9
- from .forms import ColonelAdminForm, MoveColonelForm, I2CInterfaceAdminForm
8
+ from .models import Colonel, Interface, ColonelPin
9
+ from .forms import ColonelAdminForm, MoveColonelForm, InterfaceAdminForm
10
10
 
11
11
 
12
- class I2CInterfaceInline(admin.TabularInline):
13
- model = I2CInterface
12
+ class InterfaceInline(admin.TabularInline):
13
+ model = Interface
14
14
  extra = 0
15
- form = I2CInterfaceAdminForm
15
+ form = InterfaceAdminForm
16
16
 
17
17
 
18
18
  class ColonelPinsInline(admin.TabularInline):
@@ -71,7 +71,7 @@ class ColonelAdmin(admin.ModelAdmin):
71
71
  'rebuild_occupied_pins'
72
72
  )
73
73
 
74
- inlines = I2CInterfaceInline, ColonelPinsInline
74
+ inlines = InterfaceInline, ColonelPinsInline
75
75
 
76
76
  def get_queryset(self, request):
77
77
  qs = super().get_queryset(request)
simo/fleet/auto_urls.py CHANGED
@@ -1,9 +1,8 @@
1
1
  from django.urls import path, re_path
2
- from django.views.generic import TemplateView
3
2
  from .views import (
4
3
  colonels_ping,
5
4
  PinsSelectAutocomplete,
6
- I2CInterfaceSelectAutocomplete
5
+ InterfaceSelectAutocomplete
7
6
  )
8
7
 
9
8
  urlpatterns = [
@@ -15,7 +14,8 @@ urlpatterns = [
15
14
  PinsSelectAutocomplete.as_view(), name='autocomplete-colonel-pins'
16
15
  ),
17
16
  path(
18
- 'autocomplete-colonel-i2c_interfaces',
19
- I2CInterfaceSelectAutocomplete.as_view(), name='autocomplete-colonel-i2c_interfaces'
17
+ 'autocomplete-colonel-interfaces',
18
+ InterfaceSelectAutocomplete.as_view(),
19
+ name='autocomplete-interfaces'
20
20
  )
21
21
  ]
simo/fleet/controllers.py CHANGED
@@ -24,7 +24,7 @@ from .forms import (
24
24
  ColonelDHTSensorConfigForm, DS18B20SensorConfigForm,
25
25
  BME680SensorConfigForm, MPC9808SensorConfigForm,
26
26
  DualMotorValveForm, BlindsConfigForm, BurglarSmokeDetectorConfigForm,
27
- TTLockConfigForm
27
+ TTLockConfigForm, DALIDeviceConfigForm
28
28
  )
29
29
 
30
30
 
@@ -294,6 +294,7 @@ class TTLock(FleeDeviceMixin, Lock):
294
294
  @classmethod
295
295
  def init_discovery(self, form_cleaned_data):
296
296
  from simo.core.models import Gateway
297
+ print("INIT discovery form cleaned data: ", form_cleaned_data)
297
298
  print("Serialized form: ", serialize_form_data(form_cleaned_data))
298
299
  gateway = Gateway.objects.filter(type=self.gateway_class.uid).first()
299
300
  gateway.start_discovery(
@@ -307,7 +308,7 @@ class TTLock(FleeDeviceMixin, Lock):
307
308
 
308
309
  @classmethod
309
310
  def _process_discovery(cls, started_with, data):
310
- if data['discover-ttlock'] == 'fail':
311
+ if data['discovery-result'] == 'fail':
311
312
  if data['result'] == 0:
312
313
  return {'error': 'Internal Colonel error. See Colonel logs.'}
313
314
  if data['result'] == 1:
@@ -348,6 +349,7 @@ class TTLock(FleeDeviceMixin, Lock):
348
349
  },
349
350
  }
350
351
  new_component.save()
352
+ new_component.gateway.finish_discovery()
351
353
  GatewayObjectCommand(
352
354
  new_component.gateway, Colonel(
353
355
  id=new_component.config['colonel']
@@ -417,4 +419,75 @@ class TTLock(FleeDeviceMixin, Lock):
417
419
  ).publish()
418
420
 
419
421
 
422
+ class DALIDevice(FleeDeviceMixin):
423
+ gateway_class = FleetGatewayHandler
424
+ config_form = DALIDeviceConfigForm
425
+ discovery_msg = _("Please hook up your new DALI device to your DALI bus.")
426
+
427
+ @classmethod
428
+ def init_discovery(self, form_cleaned_data):
429
+ from simo.core.models import Gateway
430
+ gateway = Gateway.objects.filter(type=self.gateway_class.uid).first()
431
+ gateway.start_discovery(
432
+ self.uid, serialize_form_data(form_cleaned_data),
433
+ timeout=60
434
+ )
435
+ GatewayObjectCommand(
436
+ gateway, form_cleaned_data['colonel'],
437
+ command=f'discover-dali',
438
+ interface=form_cleaned_data['interface'].no
439
+ ).publish()
440
+
441
+ @classmethod
442
+ def _process_discovery(cls, started_with, data):
443
+ started_with = deserialize_form_data(started_with)
444
+ form = TTLockConfigForm(controller_uid=cls.uid, data=started_with)
445
+
446
+ if form.is_valid():
447
+ new_component = form.save()
448
+ new_component.config.update(data.get('result', {}).get('config'))
449
+ new_component.meta['finalization_data'] = {
450
+ 'temp_id': data['result']['id'],
451
+ 'permanent_id': new_component.id,
452
+ 'config': {
453
+ 'type': cls.uid.split('.')[-1],
454
+ 'config': new_component.config,
455
+ 'val': False,
456
+ },
457
+ }
458
+ new_component.save()
459
+ new_component.gateway.finish_discovery()
460
+ GatewayObjectCommand(
461
+ new_component.gateway, Colonel(
462
+ id=new_component.config['colonel']
463
+ ), command='finalize',
464
+ data=new_component.meta['finalization_data'],
465
+ ).publish()
466
+ return [new_component]
467
+
468
+ # Literally impossible, but just in case...
469
+ return {'error': 'INVALID INITIAL DISCOVERY FORM!'}
470
+
471
+
472
+ class DALIGear(DALIDevice):
473
+
474
+ def _send_to_device(self, value):
475
+ GatewayObjectCommand(
476
+ self.component.gateway,
477
+ Colonel(id=self.component.config['colonel']),
478
+ set_val=value,
479
+ component_id=self.component.id,
480
+ ).publish()
481
+
482
+
483
+ class DALILamp(DALIGear, BaseSwitch):
484
+ manual_add = False
485
+ name = 'DALI Lamp'
486
+ discovery_msg = _("Please hook up your new DALI device to your DALI bus.")
487
+
488
+
489
+ class DALIDimmableLamp(DALIGear, BaseDimmer):
490
+ manual_add = False
491
+ name = 'DALI Dimmable Lamp'
492
+ discovery_msg = _("Please hook up your new DALI lamp to your DALI bus.")
420
493
 
simo/fleet/forms.py CHANGED
@@ -12,7 +12,8 @@ from simo.core.widgets import LogOutputWidget
12
12
  from simo.core.utils.easing import EASING_CHOICES
13
13
  from simo.core.utils.validators import validate_slaves
14
14
  from simo.core.utils.admin import AdminFormActionForm
15
- from .models import Colonel, ColonelPin, I2CInterface, i2c_interface_no_choices
15
+ from .models import Colonel, ColonelPin, Interface
16
+ from .utils import INTERFACES_PINS_MAP
16
17
 
17
18
 
18
19
  class ColonelPinChoiceField(forms.ModelChoiceField):
@@ -23,6 +24,11 @@ class ColonelPinChoiceField(forms.ModelChoiceField):
23
24
  filter_by = 'colonel'
24
25
 
25
26
 
27
+ class ColonelInterfacesChoiceField(forms.ModelChoiceField):
28
+ filter_by = 'colonel'
29
+
30
+
31
+
26
32
  class ColonelAdminForm(forms.ModelForm):
27
33
  log = forms.CharField(
28
34
  widget=forms.HiddenInput, required=False
@@ -52,34 +58,29 @@ class MoveColonelForm(AdminFormActionForm):
52
58
  )
53
59
 
54
60
 
55
- class I2CInterfaceAdminForm(forms.ModelForm):
56
- scl_pin = ColonelPinChoiceField(
57
- queryset=ColonelPin.objects.filter(output=True, native=True),
58
- widget=autocomplete.ListSelect2(
59
- url='autocomplete-colonel-pins',
60
- forward=[
61
- forward.Self(),
62
- forward.Field('colonel'),
63
- forward.Const({'output': True, 'native': True}, 'filters')
64
- ]
65
- )
66
- )
67
- sda_pin = ColonelPinChoiceField(
68
- queryset=ColonelPin.objects.filter(output=True, native=True),
69
- widget=autocomplete.ListSelect2(
70
- url='autocomplete-colonel-pins',
71
- forward=[
72
- forward.Self(),
73
- forward.Field('colonel'),
74
- forward.Const({'output': True, 'native': True}, 'filters')
75
- ]
76
- )
77
- )
61
+ class InterfaceAdminForm(forms.ModelForm):
78
62
 
79
63
  class Meta:
80
- model = I2CInterface
64
+ model = Interface
81
65
  fields = '__all__'
82
66
 
67
+
68
+ def clean(self):
69
+ if self.instance.pk:
70
+ return self.cleaned_data
71
+
72
+ for pin_no in INTERFACES_PINS_MAP[self.cleaned_data['no']]:
73
+ cpin = ColonelPin.objects.get(
74
+ colonel=self.instance.colonel, no=pin_no
75
+ )
76
+ if cpin.occupied_by:
77
+ raise forms.ValidationError(
78
+ f"Interface can not be created, because "
79
+ f"GPIO{cpin} is already occupied by {cpin.occupied_by}."
80
+ )
81
+
82
+
83
+
83
84
  def clean_scl_pin(self):
84
85
  if self.cleaned_data['scl_pin'].occupied_by \
85
86
  and self.cleaned_data['scl_pin'].occupied_by != self.instance:
@@ -416,13 +417,16 @@ class ColonelDHTSensorConfigForm(ColonelComponentForm):
416
417
 
417
418
 
418
419
  class BME680SensorConfigForm(ColonelComponentForm):
419
- i2c_interface = forms.TypedChoiceField(
420
- coerce=int, choices=i2c_interface_no_choices,
420
+ interface = ColonelInterfacesChoiceField(
421
+ queryset=Interface.objects.filter(type='i2c'),
421
422
  widget=autocomplete.ListSelect2(
422
- url='autocomplete-colonel-i2c_interfaces',
423
+ url='autocomplete-interfaces',
423
424
  forward=[
424
425
  forward.Self(),
425
426
  forward.Field('colonel'),
427
+ forward.Const(
428
+ {'type': 'i2c'}, 'filters'
429
+ )
426
430
  ]
427
431
  )
428
432
  )
@@ -437,15 +441,22 @@ class BME680SensorConfigForm(ColonelComponentForm):
437
441
 
438
442
  )
439
443
 
444
+ def save(self, commit=True):
445
+ self.instance.config['i2c_interface'] = self.cleaned_data['interface'].no
446
+ return super().save(commit=commit)
447
+
440
448
 
441
449
  class MPC9808SensorConfigForm(ColonelComponentForm):
442
- i2c_interface = forms.TypedChoiceField(
443
- coerce=int, choices=i2c_interface_no_choices,
450
+ interface = ColonelInterfacesChoiceField(
451
+ queryset=Interface.objects.filter(type='i2c'),
444
452
  widget=autocomplete.ListSelect2(
445
- url='autocomplete-colonel-i2c_interfaces',
453
+ url='autocomplete-interfaces',
446
454
  forward=[
447
455
  forward.Self(),
448
456
  forward.Field('colonel'),
457
+ forward.Const(
458
+ {'type': 'i2c'}, 'filters'
459
+ )
449
460
  ]
450
461
  )
451
462
  )
@@ -460,6 +471,10 @@ class MPC9808SensorConfigForm(ColonelComponentForm):
460
471
 
461
472
  )
462
473
 
474
+ def save(self, commit=True):
475
+ self.instance.config['i2c_interface'] = self.cleaned_data['interface'].no
476
+ return super().save(commit=commit)
477
+
463
478
 
464
479
  class ColonelTouchSensorConfigForm(ColonelComponentForm):
465
480
  pin = ColonelPinChoiceField(
@@ -987,7 +1002,6 @@ class BurglarSmokeDetectorConfigForm(ColonelComponentForm):
987
1002
 
988
1003
 
989
1004
  class TTLockConfigForm(ColonelComponentForm):
990
- pass
991
1005
 
992
1006
  def clean(self):
993
1007
  if not self.instance or not self.instance.pk:
@@ -1002,7 +1016,6 @@ class TTLockConfigForm(ColonelComponentForm):
1002
1016
  )
1003
1017
  return self.cleaned_data
1004
1018
 
1005
-
1006
1019
  def save(self, commit=True):
1007
1020
  obj = super(ColonelComponentForm, self).save(commit)
1008
1021
  if commit:
@@ -1010,3 +1023,25 @@ class TTLockConfigForm(ColonelComponentForm):
1010
1023
  self.cleaned_data['colonel'].save()
1011
1024
  return obj
1012
1025
 
1026
+
1027
+
1028
+ class DALIDeviceConfigForm(ColonelComponentForm):
1029
+ interface = ColonelInterfacesChoiceField(
1030
+ queryset=Interface.objects.filter(type='dali'),
1031
+ widget=autocomplete.ListSelect2(
1032
+ url='autocomplete-interfaces',
1033
+ forward=[
1034
+ forward.Self(),
1035
+ forward.Field('colonel'),
1036
+ forward.Const(
1037
+ {'type': 'dali'}, 'filters'
1038
+ )
1039
+ ]
1040
+ )
1041
+ )
1042
+
1043
+ def save(self, commit=True):
1044
+ self.instance.config['dali_interface'] = self.cleaned_data['interface'].no
1045
+ return super().save(commit=commit)
1046
+
1047
+
simo/fleet/gateways.py CHANGED
@@ -1,4 +1,5 @@
1
1
  import datetime
2
+ import time
2
3
  from django.utils import timezone
3
4
  from simo.core.gateways import BaseObjectCommandsGatewayHandler
4
5
  from simo.core.forms import BaseGatewayForm
@@ -14,7 +15,7 @@ class FleetGatewayHandler(BaseObjectCommandsGatewayHandler):
14
15
  periodic_tasks = (
15
16
  ('look_for_updates', 600),
16
17
  ('watch_colonels_connection', 30),
17
- ('push_discoveries', 3),
18
+ ('push_discoveries', 10),
18
19
  )
19
20
 
20
21
  def _on_mqtt_message(self, client, userdata, msg):
@@ -37,9 +38,12 @@ class FleetGatewayHandler(BaseObjectCommandsGatewayHandler):
37
38
  def push_discoveries(self):
38
39
  from .models import Colonel
39
40
  for gw in Gateway.objects.filter(
40
- type=self.uid,
41
- discovery__has_key='start',
41
+ type=self.uid, discovery__has_key='start',
42
42
  ).exclude(discovery__has_key='finished'):
43
+ if time.time() - gw.discovery.get('last_check') > 10:
44
+ gw.finish_discovery()
45
+ continue
46
+
43
47
  colonel = Colonel.objects.get(
44
48
  id=gw.discovery['init_data']['colonel']['val'][0]['pk']
45
49
  )
simo/fleet/managers.py CHANGED
@@ -22,11 +22,13 @@ class ColonelPinsManager(models.Manager):
22
22
  return qs
23
23
 
24
24
 
25
- class I2CInterfacesManager(models.Manager):
25
+ class InterfacesManager(models.Manager):
26
26
 
27
27
  def get_queryset(self):
28
28
  qs = super().get_queryset()
29
29
  instance = get_current_instance()
30
30
  if instance:
31
31
  qs = qs.filter(colonel__instance=instance)
32
- return qs
32
+ return qs
33
+
34
+
@@ -0,0 +1,33 @@
1
+ # Generated by Django 3.2.9 on 2024-04-15 07:36
2
+
3
+ from django.db import migrations, models
4
+ import django.db.models.deletion
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ dependencies = [
10
+ ('fleet', '0031_alter_colonel_type'),
11
+ ]
12
+
13
+ operations = [
14
+ migrations.AlterField(
15
+ model_name='colonel',
16
+ name='logs_stream',
17
+ field=models.BooleanField(default=False, help_text='ATENTION! Causes serious overhead and significantly degrades the lifespan of a chip due to a lot of writes to the memory. It also causes Colonel websocket to run out of memory and reset if a lot of data is being transmitted. Leave this off, unleess you know what you are doing!'),
18
+ ),
19
+ migrations.CreateModel(
20
+ name='Interface',
21
+ fields=[
22
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
23
+ ('no', models.PositiveIntegerField(choices=[(1, '1'), (2, '2')])),
24
+ ('type', models.CharField(choices=[('i2c', 'I2C'), ('dali', 'DALI')], max_length=20)),
25
+ ('pin_a', models.ForeignKey(editable=False, limit_choices_to={'native': True, 'output': True}, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='interface_a', to='fleet.colonelpin')),
26
+ ('pin_b', models.ForeignKey(editable=False, limit_choices_to={'native': True, 'output': True}, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='interface_b', to='fleet.colonelpin')),
27
+ ('colonel', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='interfaces', to='fleet.colonel')),
28
+ ],
29
+ options={
30
+ 'unique_together': {('colonel', 'no')},
31
+ },
32
+ ),
33
+ ]
@@ -0,0 +1,28 @@
1
+ # Generated by Django 3.2.9 on 2024-04-15 07:36
2
+
3
+ from django.db import migrations
4
+
5
+ def create_objects(apps, schema_editor):
6
+ I2CInterface = apps.get_model("fleet", "I2CInterface")
7
+ Interface = apps.get_model("fleet", "Interface")
8
+ for i2c_i in I2CInterface.objects.filter(no__gt=0):
9
+ Interface.objects.create(
10
+ colonel=i2c_i.colonel, type='i2c',
11
+ pin_a=i2c_i.scl_pin, pin_b=i2c_i.sda_pin
12
+ )
13
+
14
+
15
+ def delete_objects(apps, schema_editor):
16
+ Interface = apps.get_model("fleet", "Interface")
17
+ Interface.delete()
18
+
19
+
20
+ class Migration(migrations.Migration):
21
+
22
+ dependencies = [
23
+ ('fleet', '0032_auto_20240415_0736'),
24
+ ]
25
+
26
+ operations = [
27
+ migrations.RunPython(create_objects, delete_objects),
28
+ ]