simo 2.6.9__py3-none-any.whl → 2.7.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 (51) hide show
  1. simo/__pycache__/settings.cpython-38.pyc +0 -0
  2. simo/automation/__pycache__/controllers.cpython-38.pyc +0 -0
  3. simo/automation/__pycache__/gateways.cpython-38.pyc +0 -0
  4. simo/automation/controllers.py +18 -1
  5. simo/automation/gateways.py +130 -1
  6. simo/core/__pycache__/api_meta.cpython-38.pyc +0 -0
  7. simo/core/__pycache__/autocomplete_views.cpython-38.pyc +0 -0
  8. simo/core/__pycache__/controllers.cpython-38.pyc +0 -0
  9. simo/core/__pycache__/form_fields.cpython-38.pyc +0 -0
  10. simo/core/__pycache__/forms.cpython-38.pyc +0 -0
  11. simo/core/__pycache__/gateways.cpython-38.pyc +0 -0
  12. simo/core/__pycache__/middleware.cpython-38.pyc +0 -0
  13. simo/core/__pycache__/serializers.cpython-38.pyc +0 -0
  14. simo/core/__pycache__/signal_receivers.cpython-38.pyc +0 -0
  15. simo/core/api_meta.py +6 -2
  16. simo/core/autocomplete_views.py +4 -3
  17. simo/core/controllers.py +13 -4
  18. simo/core/form_fields.py +92 -1
  19. simo/core/forms.py +10 -4
  20. simo/core/gateways.py +11 -1
  21. simo/core/serializers.py +8 -1
  22. simo/core/signal_receivers.py +7 -83
  23. simo/core/utils/__pycache__/converters.cpython-38.pyc +0 -0
  24. simo/core/utils/converters.py +59 -0
  25. simo/fleet/__pycache__/auto_urls.cpython-38.pyc +0 -0
  26. simo/fleet/__pycache__/forms.cpython-38.pyc +0 -0
  27. simo/fleet/__pycache__/models.cpython-38.pyc +0 -0
  28. simo/fleet/__pycache__/views.cpython-38.pyc +0 -0
  29. simo/fleet/auto_urls.py +5 -0
  30. simo/fleet/forms.py +52 -4
  31. simo/fleet/views.py +37 -6
  32. simo/generic/__pycache__/controllers.cpython-38.pyc +0 -0
  33. simo/generic/__pycache__/forms.cpython-38.pyc +0 -0
  34. simo/generic/__pycache__/gateways.cpython-38.pyc +0 -0
  35. simo/generic/controllers.py +120 -1
  36. simo/generic/forms.py +77 -9
  37. simo/generic/gateways.py +81 -2
  38. simo/users/__pycache__/admin.cpython-38.pyc +0 -0
  39. simo/users/__pycache__/api.cpython-38.pyc +0 -0
  40. simo/users/__pycache__/auto_urls.cpython-38.pyc +0 -0
  41. simo/users/__pycache__/views.cpython-38.pyc +0 -0
  42. simo/users/admin.py +9 -0
  43. simo/users/api.py +2 -0
  44. simo/users/auto_urls.py +6 -3
  45. simo/users/views.py +20 -1
  46. {simo-2.6.9.dist-info → simo-2.7.1.dist-info}/METADATA +1 -1
  47. {simo-2.6.9.dist-info → simo-2.7.1.dist-info}/RECORD +51 -49
  48. {simo-2.6.9.dist-info → simo-2.7.1.dist-info}/LICENSE.md +0 -0
  49. {simo-2.6.9.dist-info → simo-2.7.1.dist-info}/WHEEL +0 -0
  50. {simo-2.6.9.dist-info → simo-2.7.1.dist-info}/entry_points.txt +0 -0
  51. {simo-2.6.9.dist-info → simo-2.7.1.dist-info}/top_level.txt +0 -0
Binary file
@@ -133,6 +133,7 @@ class PresenceLighting(Script):
133
133
  self.last_presence = 0
134
134
  self.hold_time = 60
135
135
  self.conditions = []
136
+ self.expected_light_values = {}
136
137
 
137
138
  def _run(self):
138
139
  self.hold_time = self.component.config.get('hold_time', 0) * 10
@@ -166,6 +167,16 @@ class PresenceLighting(Script):
166
167
  self.condition_comps[comp.id] = comp
167
168
 
168
169
  while True:
170
+ # Resend expected values if they have failed to reach
171
+ # corresponding light
172
+ for c_id, [timestamp, expected_val] in self.expected_light_values.items():
173
+ if time.time() - timestamp < 5:
174
+ continue
175
+ comp = Component.objects.filter(id=c_id).first()
176
+ if not comp:
177
+ continue
178
+ print(f"Resending [{expected_val}] to {comp}")
179
+ comp.send(expected_val)
169
180
  self._regulate()
170
181
  time.sleep(random.randint(5, 15))
171
182
 
@@ -182,8 +193,10 @@ class PresenceLighting(Script):
182
193
  self._regulate(on_condition_change=True)
183
194
 
184
195
  def _on_light_change(self, light):
196
+ # If we were expecting some value change from the light
197
+ # We have received something. So we stop demanding it!
198
+ self.expected_light_values.pop(light.id, None)
185
199
  # change original value if it has been changed to something different
186
- # than this script does.
187
200
  if self.is_on and light.value != self.light_send_values[light.id]:
188
201
  self.light_send_values[light.id] = light.value
189
202
  self.light_org_values[light.id] = light.value
@@ -252,6 +265,8 @@ class PresenceLighting(Script):
252
265
  on_val = bool(on_val)
253
266
  print(f"Send {on_val} to {comp}!")
254
267
  self.light_send_values[comp.id] = on_val
268
+ if comp.value != on_val:
269
+ self.expected_light_values[comp.id] = [time.time(), on_val]
255
270
  comp.controller.send(on_val)
256
271
  return
257
272
 
@@ -287,6 +302,8 @@ class PresenceLighting(Script):
287
302
  else:
288
303
  off_val = self.light_org_values.get(comp.id, 0)
289
304
  print(f"Send {off_val} to {comp}!")
305
+ if comp.value != off_val:
306
+ self.expected_light_values[comp.id] = [time.time(), off_val]
290
307
  comp.send(off_val)
291
308
 
292
309
 
@@ -17,8 +17,11 @@ from simo.core.middleware import introduce_instance, drop_current_instance
17
17
  from simo.core.gateways import BaseObjectCommandsGatewayHandler
18
18
  from simo.core.forms import BaseGatewayForm
19
19
  from simo.core.utils.logs import StreamToLogger
20
+ from simo.core.utils.converters import input_to_meters
20
21
  from simo.core.events import GatewayObjectCommand, get_event_obj
21
22
  from simo.core.loggers import get_gw_logger, get_component_logger
23
+ from simo.users.models import InstanceUser
24
+ from .helpers import haversine_distance
22
25
 
23
26
 
24
27
  class ScriptRunHandler(multiprocessing.Process):
@@ -77,7 +80,132 @@ class ScriptRunHandler(multiprocessing.Process):
77
80
  return
78
81
 
79
82
 
80
- class AutomationsGatewayHandler(BaseObjectCommandsGatewayHandler):
83
+ class GatesHandler:
84
+ '''
85
+ Handles automatic gates openning
86
+ '''
87
+ # users are considered out of gate geofence, when they
88
+ # go out at least this amount of meters away from the gate
89
+ GEOFENCE_CROSS_ZONE = 200
90
+
91
+ def __init__(self, *args, **kwargs):
92
+ super().__init__(*args, **kwargs)
93
+ self.gate_iusers = {}
94
+
95
+ def _is_out_of_geofence(self, gate, location):
96
+ '''
97
+ Returns True if given location is out of geofencing zone
98
+ '''
99
+
100
+ auto_open_distance = gate.config.get('auto_open_distance')
101
+ if not auto_open_distance:
102
+ return False
103
+ auto_open_distance = input_to_meters(auto_open_distance)
104
+
105
+ gate_location = gate.config.get('location')
106
+ try:
107
+ distance_meters = haversine_distance(
108
+ gate_location,
109
+ location, units_of_measure='metric'
110
+ )
111
+ except:
112
+ gate_location = gate.zone.instance.location
113
+ try:
114
+ distance_meters = haversine_distance(
115
+ gate_location,
116
+ location, units_of_measure='metric'
117
+ )
118
+ except:
119
+ print(f"Bad location of {gate}!")
120
+ return False
121
+ print(f"Distance from {gate} : {distance_meters}m")
122
+ return distance_meters > (
123
+ auto_open_distance + self.GEOFENCE_CROSS_ZONE
124
+ )
125
+
126
+ def _is_in_geofence(self, gate, location):
127
+ '''
128
+ Returns True if given location is within geofencing zone
129
+ '''
130
+ auto_open_distance = gate.config.get('auto_open_distance')
131
+ if not auto_open_distance:
132
+ return False
133
+ auto_open_distance = input_to_meters(auto_open_distance)
134
+ gate_location = gate.config.get('location')
135
+ try:
136
+ distance_meters = haversine_distance(
137
+ gate_location,
138
+ location, units_of_measure='metric'
139
+ )
140
+ except:
141
+ gate_location = gate.zone.instance.location
142
+ try:
143
+ distance_meters = haversine_distance(
144
+ gate_location,
145
+ location, units_of_measure='metric'
146
+ )
147
+ except:
148
+ print(f"Bad location of {gate}!")
149
+ return False
150
+
151
+ print(f"Distance from {gate} : {distance_meters}m")
152
+ return distance_meters <= auto_open_distance
153
+
154
+ def check_gates(self, iuser):
155
+ if not iuser.last_seen_location:
156
+ print("User's last seen location is unknown")
157
+ return
158
+ for gate_id, geofence_data in self.gate_iusers.items():
159
+ for iu_id, is_out in geofence_data.items():
160
+ if iu_id != iuser.id:
161
+ continue
162
+ gate = Component.objects.get(id=gate_id)
163
+ if is_out:
164
+ print(
165
+ f"{iuser.user.name} is out, "
166
+ f"let's see if we must open the gates for him"
167
+ )
168
+ # user was fully out, we must check if
169
+ # he is now coming back and open the gate for him
170
+ if self._is_in_geofence(gate, iuser.last_seen_location):
171
+ print("Yes he is back in a geofence! Open THE GATEEE!!")
172
+ self.gate_iusers[gate_id][iuser.id] = False
173
+ gate.open()
174
+ else:
175
+ print("No he is not back yet.")
176
+ else:
177
+ print(f"Check if {iuser.user.name} is out.")
178
+ self.gate_iusers[gate_id][iuser.id] = self._is_out_of_geofence(
179
+ gate, iuser.last_seen_location
180
+ )
181
+ if self.gate_iusers[gate_id][iuser.id]:
182
+ print(f"YES {iuser.user.name} is out!")
183
+
184
+ def watch_gates(self):
185
+ drop_current_instance()
186
+ for gate in Component.objects.filter(base_type='gate').select_related(
187
+ 'zone', 'zone__instance'
188
+ ):
189
+ if not gate.config.get('auto_open_distance'):
190
+ continue
191
+ # Track new users as they appear in the system
192
+ for iuser in InstanceUser.objects.filter(
193
+ is_active=True, instance=gate.zone.instance
194
+ ):
195
+ if gate.config.get('auto_open_for'):
196
+ if iuser.role.id not in gate.config['auto_open_for']:
197
+ continue
198
+ if gate.id not in self.gate_iusers:
199
+ self.gate_iusers[gate.id] = {}
200
+ if iuser.id not in self.gate_iusers[gate.id]:
201
+ if iuser.last_seen_location:
202
+ self.gate_iusers[gate.id][iuser.id] = self._is_out_of_geofence(
203
+ gate, iuser.last_seen_location
204
+ )
205
+ iuser.on_change(self.check_gates)
206
+
207
+
208
+ class AutomationsGatewayHandler(GatesHandler, BaseObjectCommandsGatewayHandler):
81
209
  name = "Automation"
82
210
  config_form = BaseGatewayForm
83
211
  info = "Provides various types of automation capabilities"
@@ -85,6 +213,7 @@ class AutomationsGatewayHandler(BaseObjectCommandsGatewayHandler):
85
213
  running_scripts = {}
86
214
  periodic_tasks = (
87
215
  ('watch_scripts', 10),
216
+ ('watch_gates', 60)
88
217
  )
89
218
 
90
219
  terminating_scripts = set()
Binary file
simo/core/api_meta.py CHANGED
@@ -8,7 +8,7 @@ from simo.core.models import Icon, Instance, Category, Zone
8
8
  from simo.core.middleware import introduce_instance
9
9
  from .serializers import (
10
10
  HiddenSerializerField, ComponentManyToManyRelatedField,
11
- TextAreaSerializerField, Component
11
+ TextAreaSerializerField, Component, LocationSerializer
12
12
  )
13
13
 
14
14
 
@@ -41,6 +41,7 @@ class SIMOAPIMetadata(SimpleMetadata):
41
41
  ComponentManyToManyRelatedField: 'many related objects',
42
42
  HiddenSerializerField: 'hidden',
43
43
  TextAreaSerializerField: 'textarea',
44
+ LocationSerializer: 'location',
44
45
  })
45
46
 
46
47
  def determine_metadata(self, request, view):
@@ -84,7 +85,7 @@ class SIMOAPIMetadata(SimpleMetadata):
84
85
  'read_only', 'label', 'help_text',
85
86
  'min_length', 'max_length',
86
87
  'min_value', 'max_value',
87
- 'initial'
88
+ 'initial',
88
89
  ]
89
90
 
90
91
  for attr in attrs:
@@ -115,6 +116,9 @@ class SIMOAPIMetadata(SimpleMetadata):
115
116
  zone__instance=self.instance
116
117
  )
117
118
 
119
+ if form_field and hasattr(form_field, 'zoom'):
120
+ field_info['zoom'] = form_field.zoom
121
+
118
122
  if not field_info.get('read_only') and hasattr(field, 'choices'):# and not hasattr(form_field, 'forward'):
119
123
  field_info['choices'] = [
120
124
  {
@@ -3,6 +3,7 @@ from dal.views import BaseQuerySetView
3
3
  from django.db.models import Q
4
4
  from django.template.loader import render_to_string
5
5
  from simo.core.utils.helpers import search_queryset
6
+ from simo.core.middleware import get_current_instance
6
7
  from .models import Icon, Category, Zone, Component
7
8
 
8
9
 
@@ -53,7 +54,7 @@ class IconModelAutocomplete(autocomplete.Select2QuerySetView):
53
54
  class CategoryAutocomplete(autocomplete.Select2QuerySetView):
54
55
 
55
56
  def get_queryset(self):
56
- qs = Category.objects.all()
57
+ qs = Category.objects.filter(instance=get_current_instance(self.request))
57
58
 
58
59
  if self.forwarded.get("id"):
59
60
  return qs.filter(pk=self.forwarded.get("id"))
@@ -79,7 +80,7 @@ class CategoryAutocomplete(autocomplete.Select2QuerySetView):
79
80
  class ZoneAutocomplete(autocomplete.Select2QuerySetView):
80
81
 
81
82
  def get_queryset(self):
82
- qs = Zone.objects.all()
83
+ qs = Zone.objects.filter(instance=get_current_instance(self.request))
83
84
 
84
85
  if self.forwarded.get("id"):
85
86
  return qs.filter(pk=self.forwarded.get("id"))
@@ -104,7 +105,7 @@ class ZoneAutocomplete(autocomplete.Select2QuerySetView):
104
105
  class ComponentAutocomplete(autocomplete.Select2QuerySetView):
105
106
 
106
107
  def get_queryset(self):
107
- qs = Component.objects.all()
108
+ qs = Component.objects.filter(zone__instance=get_current_instance(self.request))
108
109
 
109
110
  if self.forwarded.get("id"):
110
111
  if isinstance(self.forwarded['id'], list):
simo/core/controllers.py CHANGED
@@ -233,7 +233,12 @@ class ControllerBase(ABC):
233
233
  return vals
234
234
 
235
235
  def send(self, value):
236
- self.component.refresh_from_db()
236
+ from .models import Component
237
+ try:
238
+ self.component.refresh_from_db()
239
+ except Component.DoesNotExist:
240
+ return
241
+
237
242
 
238
243
  # Bulk send if it is a switch or dimmer and has slaves
239
244
  if self.component.base_type in ('switch', 'dimmer') \
@@ -241,7 +246,7 @@ class ControllerBase(ABC):
241
246
  bulk_send_map = {self.component: value}
242
247
  for slave in self.component.slaves.all():
243
248
  bulk_send_map[slave] = value
244
- from .models import Component
249
+
245
250
  Component.objects.bulk_send(bulk_send_map)
246
251
  return
247
252
 
@@ -1027,7 +1032,10 @@ class Gate(ControllerBase, TimerMixin):
1027
1032
  base_type = 'gate'
1028
1033
  app_widget = GateWidget
1029
1034
  admin_widget_template = 'admin/controller_widgets/gate.html'
1030
- default_config = {}
1035
+ default_config = {
1036
+ 'auto_open_distance': '150 m',
1037
+ 'auto_open_for': [],
1038
+ }
1031
1039
 
1032
1040
  @property
1033
1041
  def default_value(self):
@@ -1065,4 +1073,5 @@ class Gate(ControllerBase, TimerMixin):
1065
1073
  self.send('close')
1066
1074
 
1067
1075
  def call(self):
1068
- self.send('call')
1076
+ self.send('call')
1077
+
simo/core/form_fields.py CHANGED
@@ -1,5 +1,10 @@
1
1
  import copy
2
+ import json
3
+ import six
4
+ from django.template.loader import render_to_string
5
+ from django.utils.safestring import mark_safe
2
6
  from django import forms
7
+ from django.conf import settings
3
8
  from dal import autocomplete
4
9
 
5
10
 
@@ -91,4 +96,90 @@ class Select2ModelMultipleChoiceField(
91
96
  class Select2ListMultipleChoiceField(
92
97
  Select2MultipleMixin, forms.MultipleChoiceField
93
98
  ):
94
- pass
99
+ pass
100
+
101
+
102
+ class LocationWidget(forms.widgets.TextInput):
103
+ def __init__(self, **kwargs):
104
+ attrs = kwargs.pop('attrs', None)
105
+
106
+ self.options = dict(settings.LOCATION_FIELD)
107
+ self.options['map.zoom'] = kwargs.get('zoom')
108
+ self.options['field_options'] = {
109
+ 'based_fields': kwargs.pop('based_fields')
110
+ }
111
+
112
+ super(LocationWidget, self).__init__(attrs)
113
+
114
+ def render(self, name, value, attrs=None, renderer=None):
115
+ if value is not None:
116
+ try:
117
+ if isinstance(value, six.string_types):
118
+ lat, lng = value.split(',')
119
+ else:
120
+ lng = value.x
121
+ lat = value.y
122
+
123
+ value = '%s,%s' % (
124
+ float(lat),
125
+ float(lng),
126
+ )
127
+ except ValueError:
128
+ value = ''
129
+ else:
130
+ value = ''
131
+
132
+ if '-' not in name:
133
+ prefix = ''
134
+ else:
135
+ prefix = name[:name.rindex('-') + 1]
136
+
137
+ self.options['field_options']['prefix'] = prefix
138
+
139
+ attrs = attrs or {}
140
+ attrs['data-location-field-options'] = json.dumps(self.options)
141
+
142
+ # Django added renderer parameter in 1.11, made it mandatory in 2.1
143
+ kwargs = {}
144
+ if renderer is not None:
145
+ kwargs['renderer'] = renderer
146
+ text_input = super(LocationWidget, self).render(name, value, attrs=attrs, **kwargs)
147
+
148
+ return render_to_string('location_field/map_widget.html', {
149
+ 'field_name': name,
150
+ 'field_input': mark_safe(text_input)
151
+ })
152
+
153
+ @property
154
+ def media(self):
155
+ return forms.Media(**settings.LOCATION_FIELD['resources.media'])
156
+
157
+
158
+
159
+ class PlainLocationField(forms.fields.CharField):
160
+
161
+ zoom = 13
162
+
163
+ def __init__(self, based_fields=None, zoom=None, suffix='', *args, **kwargs):
164
+ self.zoom = zoom
165
+ if not based_fields:
166
+ based_fields = []
167
+ if not self.zoom:
168
+ self.zoom = settings.LOCATION_FIELD['map.zoom']
169
+ self.widget = LocationWidget(based_fields=based_fields, zoom=self.zoom,
170
+ suffix=suffix, **kwargs)
171
+
172
+ dwargs = {
173
+ 'required': True,
174
+ 'label': None,
175
+ 'initial': None,
176
+ 'help_text': None,
177
+ 'error_messages': None,
178
+ 'show_hidden_initial': False,
179
+ }
180
+
181
+ for attr in dwargs:
182
+ if attr in kwargs:
183
+ dwargs[attr] = kwargs[attr]
184
+
185
+ super(PlainLocationField, self).__init__(*args, **dwargs)
simo/core/forms.py CHANGED
@@ -79,10 +79,11 @@ class ConfigFieldsMixin:
79
79
  if field_name in self.model_fields:
80
80
  continue
81
81
  self.config_fields.append(field_name)
82
- if self.instance.pk:
83
- for field_name in self.config_fields:
84
- if field_name not in self.instance.config:
85
- continue
82
+
83
+ for field_name in self.config_fields:
84
+ if field_name not in self.instance.config:
85
+ continue
86
+ if self.instance.pk:
86
87
  if hasattr(self.fields[field_name], 'queryset'):
87
88
  if isinstance(self.instance.config.get(field_name), list):
88
89
  self.fields[field_name].initial = \
@@ -97,6 +98,11 @@ class ConfigFieldsMixin:
97
98
  else:
98
99
  self.fields[field_name].initial = \
99
100
  self.instance.config.get(field_name)
101
+ else:
102
+ if self.instance.config.get(field_name):
103
+ self.fields[field_name].initial = self.instance.config.get(field_name)
104
+
105
+
100
106
 
101
107
  def save(self, commit=True):
102
108
  for field_name in self.config_fields:
simo/core/gateways.py CHANGED
@@ -1,6 +1,7 @@
1
1
  import threading
2
2
  import time
3
3
  import json
4
+ import random
4
5
  import paho.mqtt.client as mqtt
5
6
  from django.conf import settings
6
7
  from django.template.loader import render_to_string
@@ -78,13 +79,22 @@ class BaseObjectCommandsGatewayHandler(BaseGatewayHandler):
78
79
  self.mqtt_client.loop_stop()
79
80
 
80
81
  def _run_periodic_task(self, exit, task, period):
82
+ first_run = True
81
83
  while not exit.is_set():
82
84
  try:
83
85
  #print(f"Run periodic task {task}!")
84
86
  getattr(self, task)()
85
87
  except Exception as e:
86
88
  self.logger.error(e, exc_info=True)
87
- time.sleep(period)
89
+ # spread tasks around so that they do not happen all
90
+ # at once all the time
91
+ if first_run:
92
+ first_run = False
93
+ randomized_sleep = random.randint(0, period) + random.random()
94
+ time.sleep(randomized_sleep)
95
+ else:
96
+ time.sleep(period)
97
+
88
98
 
89
99
  def _on_mqtt_connect(self, mqtt_client, userdata, flags, rc):
90
100
  print("MQTT Connected!")
simo/core/serializers.py CHANGED
@@ -15,7 +15,8 @@ from actstream.models import Action
15
15
  from simo.core.forms import HiddenField, FormsetField
16
16
  from simo.core.form_fields import (
17
17
  Select2ListChoiceField, Select2ModelChoiceField,
18
- Select2ListMultipleChoiceField, Select2ModelMultipleChoiceField
18
+ Select2ListMultipleChoiceField, Select2ModelMultipleChoiceField,
19
+ PlainLocationField
19
20
  )
20
21
  from simo.core.models import Component
21
22
  from rest_framework.relations import PrimaryKeyRelatedField, ManyRelatedField
@@ -28,6 +29,10 @@ from .forms import ComponentAdminForm
28
29
  from .models import Category, Zone, Icon, ComponentHistory
29
30
 
30
31
 
32
+ class LocationSerializer(serializers.CharField):
33
+ pass
34
+
35
+
31
36
 
32
37
  class TimestampField(serializers.Field):
33
38
 
@@ -139,6 +144,7 @@ class ComponentFormsetField(FormSerializer):
139
144
  form = forms.Form
140
145
  field_mapping = {
141
146
  HiddenField: HiddenSerializerField,
147
+ PlainLocationField: LocationSerializer,
142
148
  Select2ListChoiceField: serializers.ChoiceField,
143
149
  forms.ModelChoiceField: FormsetPrimaryKeyRelatedField,
144
150
  Select2ModelChoiceField: FormsetPrimaryKeyRelatedField,
@@ -293,6 +299,7 @@ class ComponentSerializer(FormSerializer):
293
299
  forms.TypedChoiceField: serializers.ChoiceField,
294
300
  forms.FloatField: serializers.FloatField,
295
301
  forms.SlugField: serializers.CharField,
302
+ PlainLocationField: LocationSerializer,
296
303
  forms.ModelChoiceField: ComponentPrimaryKeyRelatedField,
297
304
  Select2ModelChoiceField: ComponentPrimaryKeyRelatedField,
298
305
  forms.ModelMultipleChoiceField: ComponentManyToManyRelatedField,
@@ -81,100 +81,24 @@ def create_instance_defaults(sender, instance, created, **kwargs):
81
81
  )
82
82
  weather_icon = Icon.objects.get(slug='cloud-bolt-sun')
83
83
 
84
+ from simo.generic.controllers import Weather, MainState
84
85
  Component.objects.create(
85
86
  name='Weather', icon=weather_icon,
86
87
  zone=other_zone,
87
88
  category=climate_category,
88
89
  gateway=generic, base_type='weather',
89
- controller_uid='simo.generic.controllers.Weather',
90
+ controller_uid=Weather.uid,
90
91
  config={'is_main': True}
91
92
  )
92
93
 
93
- state_comp = Component.objects.create(
94
- name='State', icon=Icon.objects.get(slug='home'),
94
+ Component.objects.create(
95
+ name='Main State', icon=Icon.objects.get(slug='home'),
95
96
  zone=other_zone,
96
97
  category=other_category,
97
- gateway=generic, base_type='state-select',
98
- controller_uid='simo.generic.controllers.StateSelect',
98
+ gateway=generic, base_type=MainState.base_type,
99
+ controller_uid=MainState.uid,
99
100
  value='day',
100
- config={"states": [
101
- {
102
- "icon": "sunrise", "name": "Morning", "slug": "morning",
103
- 'help_text': "6:00 AM to sunrise. Activates only in dark time of a year."
104
- },
105
- {
106
- "icon": "house-day", "name": "Day", "slug": "day",
107
- 'help_text': "From sunrise to sunset."
108
- },
109
- {
110
- "icon": "house-night", "name": "Evening", "slug": "evening",
111
- 'help_text': "From sunrise to midnight"
112
- },
113
- {
114
- "icon": "moon-cloud", "name": "Night", "slug": "night",
115
- 'help_text': "From midnight to sunrise or 6:00 AM."
116
- },
117
- {"icon": "snooze", "name": "Sleep time", "slug": "sleep"},
118
- {"icon": "house-person-leave", "name": "Away", "slug": "away"},
119
- {"icon": "island-tropical", "name": "Vacation", "slug": "vacation"}
120
- ], "is_main": True}
121
- )
122
-
123
-
124
- auto_state_code = render_to_string(
125
- 'automations/auto_state_script.py', {'state_comp_id': state_comp.id}
126
- )
127
- Component.objects.create(
128
- name='Auto state', icon=Icon.objects.get(slug='bolt'),
129
- zone=other_zone,
130
- category=other_category, show_in_app=False,
131
- gateway=automation, base_type='script',
132
- controller_uid='simo.automation.controllers.Script',
133
- config={
134
- "code": auto_state_code, 'autostart': True, 'keep_alive': True,
135
- "notes": f"""
136
- The script automatically controls the states of the "State" component (ID:{state_comp.id}) — 'morning', 'day', 'evening', 'night'.
137
-
138
- """
139
- }
140
- )
141
-
142
- code = render_to_string(
143
- 'automations/phones_sleep_script.py', {'state_comp_id': state_comp.id}
144
- )
145
- Component.objects.create(
146
- name='Sleep mode when owner phones are charge',
147
- icon=Icon.objects.get(slug='bolt'), zone=other_zone,
148
- category=other_category, show_in_app=False,
149
- gateway=automation, base_type='script',
150
- controller_uid='simo.automation.controllers.Script',
151
- config={
152
- "code": code, 'autostart': True, 'keep_alive': True,
153
- "notes": f"""
154
- Automatically sets State component (ID: {state_comp.id}) to "Sleep" if it is later than 10pm and all home owners phones who are at home are put on charge.
155
- Sets State component back to regular state as soon as none of the home owners phones are on charge and it is 6am or later.
156
-
157
- """
158
- }
159
- )
160
-
161
- code = render_to_string(
162
- 'automations/auto_away.py', {'state_comp_id': state_comp.id}
163
- )
164
- Component.objects.create(
165
- name='Auto Away State',
166
- icon=Icon.objects.get(slug='bolt'), zone=other_zone,
167
- category=other_category, show_in_app=False,
168
- gateway=automation, base_type='script',
169
- controller_uid='simo.automation.controllers.Script',
170
- config={
171
- "code": code, 'autostart': True, 'keep_alive': True,
172
- "notes": f"""
173
- Automatically set mode to "Away" there are no users at home and there was no motion for more than 30 seconds.
174
- Set it back to a regular mode as soon as somebody comes back home or motion is detected.
175
-
176
- """
177
- }
101
+ config=MainState.default_config
178
102
  )
179
103
 
180
104
  # Create default User permission roles