simo 2.4.2__py3-none-any.whl → 2.5.2__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 (73) hide show
  1. simo/backups/tasks.py +11 -1
  2. simo/core/__pycache__/admin.cpython-38.pyc +0 -0
  3. simo/core/__pycache__/api.cpython-38.pyc +0 -0
  4. simo/core/__pycache__/app_widgets.cpython-38.pyc +0 -0
  5. simo/core/__pycache__/controllers.cpython-38.pyc +0 -0
  6. simo/core/__pycache__/events.cpython-38.pyc +0 -0
  7. simo/core/__pycache__/forms.cpython-38.pyc +0 -0
  8. simo/core/__pycache__/middleware.cpython-38.pyc +0 -0
  9. simo/core/__pycache__/models.cpython-38.pyc +0 -0
  10. simo/core/__pycache__/serializers.cpython-38.pyc +0 -0
  11. simo/core/__pycache__/socket_consumers.cpython-38.pyc +0 -0
  12. simo/core/__pycache__/tasks.cpython-38.pyc +0 -0
  13. simo/core/__pycache__/views.cpython-38.pyc +0 -0
  14. simo/core/admin.py +4 -4
  15. simo/core/api.py +20 -4
  16. simo/core/app_widgets.py +5 -0
  17. simo/core/controllers.py +4 -3
  18. simo/core/events.py +13 -4
  19. simo/core/forms.py +2 -0
  20. simo/core/management/commands/gateways_manager.py +0 -3
  21. simo/core/middleware.py +12 -6
  22. simo/core/models.py +26 -6
  23. simo/core/serializers.py +17 -17
  24. simo/core/socket_consumers.py +6 -2
  25. simo/core/static/admin/js/codemirror-init.js +1 -0
  26. simo/core/tasks.py +10 -7
  27. simo/fleet/__pycache__/controllers.cpython-38.pyc +0 -0
  28. simo/fleet/__pycache__/forms.cpython-38.pyc +0 -0
  29. simo/fleet/__pycache__/models.cpython-38.pyc +0 -0
  30. simo/fleet/__pycache__/socket_consumers.cpython-38.pyc +0 -0
  31. simo/fleet/controllers.py +86 -22
  32. simo/fleet/forms.py +84 -9
  33. simo/fleet/migrations/0039_auto_20241016_1047.py +28 -0
  34. simo/fleet/migrations/0040_alter_colonel_pwm_frequency.py +18 -0
  35. simo/fleet/migrations/__pycache__/0039_auto_20241016_1047.cpython-38.pyc +0 -0
  36. simo/fleet/migrations/__pycache__/0040_alter_colonel_pwm_frequency.cpython-38.pyc +0 -0
  37. simo/fleet/models.py +6 -2
  38. simo/fleet/socket_consumers.py +13 -5
  39. simo/generic/__pycache__/controllers.cpython-38.pyc +0 -0
  40. simo/generic/__pycache__/forms.cpython-38.pyc +0 -0
  41. simo/generic/__pycache__/gateways.cpython-38.pyc +0 -0
  42. simo/generic/__pycache__/models.cpython-38.pyc +0 -0
  43. simo/generic/controllers.py +45 -2
  44. simo/generic/forms.py +81 -7
  45. simo/generic/models.py +0 -1
  46. simo/generic/scripting/__init__.py +16 -0
  47. simo/generic/scripting/__pycache__/__init__.cpython-38.pyc +0 -0
  48. simo/generic/scripting/__pycache__/helpers.cpython-38.pyc +0 -0
  49. simo/generic/scripting/__pycache__/serializers.cpython-38.pyc +0 -0
  50. simo/generic/scripting/helpers.py +35 -0
  51. simo/generic/scripting/serializers.py +77 -0
  52. simo/generic/templates/admin/controller_widgets/weather_forecast.html +2 -2
  53. simo/notifications/__pycache__/utils.cpython-38.pyc +0 -0
  54. simo/notifications/utils.py +30 -12
  55. simo/scripting.py +2 -2
  56. simo/users/__pycache__/api.cpython-38.pyc +0 -0
  57. simo/users/__pycache__/managers.cpython-38.pyc +0 -0
  58. simo/users/__pycache__/models.cpython-38.pyc +0 -0
  59. simo/users/__pycache__/serializers.cpython-38.pyc +0 -0
  60. simo/users/__pycache__/utils.cpython-38.pyc +0 -0
  61. simo/users/api.py +36 -7
  62. simo/users/managers.py +5 -1
  63. simo/users/migrations/0034_instanceuser_last_seen_location_and_more.py +24 -0
  64. simo/users/migrations/__pycache__/0034_instanceuser_last_seen_location_and_more.cpython-38.pyc +0 -0
  65. simo/users/models.py +37 -32
  66. simo/users/serializers.py +11 -8
  67. simo/users/utils.py +14 -3
  68. {simo-2.4.2.dist-info → simo-2.5.2.dist-info}/METADATA +1 -1
  69. {simo-2.4.2.dist-info → simo-2.5.2.dist-info}/RECORD +73 -60
  70. {simo-2.4.2.dist-info → simo-2.5.2.dist-info}/WHEEL +1 -1
  71. {simo-2.4.2.dist-info → simo-2.5.2.dist-info}/LICENSE.md +0 -0
  72. {simo-2.4.2.dist-info → simo-2.5.2.dist-info}/entry_points.txt +0 -0
  73. {simo-2.4.2.dist-info → simo-2.5.2.dist-info}/top_level.txt +0 -0
simo/fleet/forms.py CHANGED
@@ -741,14 +741,21 @@ class ColonelPWMOutputConfigForm(ColonelComponentForm):
741
741
  help_text="Maximum component value"
742
742
  )
743
743
  value_units = forms.CharField(required=False)
744
- duty_min = forms.IntegerField(
745
- min_value=0, max_value=1023, required=True, initial=0,
746
- help_text="Minumum PWM signal output duty (0 - 1023)"
744
+
745
+ device_min = forms.IntegerField(
746
+ label="Device minimum (%).",
747
+ help_text="Device will turn off once it reaches this internal value. "
748
+ "Usually it is a good idea to "
749
+ "set this somewhere in between of 5 - 15 %. ",
750
+ initial=10, min_value=0, max_value=100,
747
751
  )
748
- duty_max = forms.IntegerField(
749
- min_value=0, max_value=1023, required=True, initial=900,
750
- help_text="Maximum PWM signal output duty (0 - 1023)"
752
+ device_max = forms.IntegerField(
753
+ label="Device maximum (%).",
754
+ help_text="Can be used to prevent reaching maximum values. "
755
+ "Default is 100%",
756
+ initial=100, min_value=0, max_value=100,
751
757
  )
758
+
752
759
  turn_on_time = forms.IntegerField(
753
760
  min_value=0, max_value=60000, initial=1000,
754
761
  help_text="Turn on speed in ms. 1500 is a great quick default. "
@@ -763,9 +770,6 @@ class ColonelPWMOutputConfigForm(ColonelComponentForm):
763
770
  initial='easeOutSine', choices=EASING_CHOICES,
764
771
  help_text="easeOutSine - offers most naturally looking effect."
765
772
  )
766
- inverse = forms.BooleanField(
767
- label=_("Inverse dimmer signal"), required=False, initial=True
768
- )
769
773
  on_value = forms.FloatField(
770
774
  required=True, initial=100,
771
775
  help_text="ON value when used with toggle switch"
@@ -854,6 +858,71 @@ class ColonelPWMOutputConfigForm(ColonelComponentForm):
854
858
  return obj
855
859
 
856
860
 
861
+ class DCDriverConfigForm(ColonelComponentForm):
862
+ output_pin = Select2ModelChoiceField(
863
+ label="Port",
864
+ queryset=ColonelPin.objects.filter(output=True),
865
+ url='autocomplete-colonel-pins',
866
+ forward=[
867
+ forward.Self(),
868
+ forward.Field('colonel'),
869
+ forward.Const({'output': True}, 'filters')
870
+ ]
871
+ )
872
+ min = forms.FloatField(
873
+ required=True, initial=0,
874
+ help_text="Minimum component value displayed to the user."
875
+ )
876
+ max = forms.FloatField(
877
+ required=True, initial=24,
878
+ help_text="Maximum component value displayed to the user."
879
+ )
880
+ value_units = forms.CharField(required=False)
881
+
882
+ device_min = forms.FloatField(
883
+ label="Device minimum Voltage.",
884
+ help_text="This will be the lowest possible voltage value of a device.\n"
885
+ "Don't forget to adjust your component min value accordingly "
886
+ "if you change this.",
887
+ initial=0, min_value=0, max_value=24,
888
+ )
889
+ device_max = forms.IntegerField(
890
+ label="Device maximum Voltage.",
891
+ help_text="Can be set lower than it's natural maximum of 24V. \n"
892
+ "Don't forget to adjust your component max value accordingly "
893
+ "if you change this.",
894
+ initial=24, min_value=0, max_value=24,
895
+ )
896
+
897
+ def clean(self):
898
+ super().clean()
899
+ if 'output_pin' in self.cleaned_data:
900
+ self._clean_pin('output_pin')
901
+ return self.cleaned_data
902
+
903
+
904
+ def save(self, commit=True):
905
+ if 'output_pin' in self.cleaned_data:
906
+ self.instance.config['output_pin_no'] = self.cleaned_data['output_pin'].no
907
+
908
+ update_colonel = False
909
+ if not self.instance.pk:
910
+ update_colonel = True
911
+ elif 'output_pin' in self.changed_data:
912
+ update_colonel = True
913
+
914
+ obj = super().save(commit=commit)
915
+
916
+ if not update_colonel:
917
+ GatewayObjectCommand(
918
+ obj.gateway, self.cleaned_data['colonel'], id=obj.id,
919
+ command='call', method='update_config', args=[
920
+ obj.controller._get_colonel_config()
921
+ ]
922
+ ).publish()
923
+ return obj
924
+
925
+
857
926
  class ColonelRGBLightConfigForm(ColonelComponentForm):
858
927
  output_pin = Select2ModelChoiceField(
859
928
  label="Port",
@@ -1010,6 +1079,12 @@ class DualMotorValveForm(ColonelComponentForm):
1010
1079
  required=True, min_value=0.01, max_value=1000000000,
1011
1080
  initial=10, help_text="Time in seconds to close."
1012
1081
  )
1082
+ min = forms.FloatField(
1083
+ label="Minimum displayed value", required=True, initial=0
1084
+ )
1085
+ max = forms.FloatField(
1086
+ label="Maximum displayed value", required=True, initial=100
1087
+ )
1013
1088
 
1014
1089
 
1015
1090
  def clean(self):
@@ -0,0 +1,28 @@
1
+ # Generated by Django 4.2.10 on 2024-10-16 10:47
2
+ from django.db import migrations
3
+
4
+
5
+ def forwards_func(apps, schema_editor):
6
+ Component = apps.get_model("core", "Component")
7
+
8
+ for comp in Component.objects.filter(controller_uid='simo.fleet.controllers.PWMOutput'):
9
+ duty_min = comp.config.get('duty_min', 0)
10
+ duty_max = comp.config.get('duty_max', 1023)
11
+ comp.config['device_min'] = int((1023 - duty_max) / 1023 * 100)
12
+ comp.config['device_max'] = int(100 - (duty_min / 1023 * 100))
13
+ comp.save()
14
+
15
+
16
+ def reverse_func(apps, schema_editor):
17
+ pass
18
+
19
+
20
+ class Migration(migrations.Migration):
21
+
22
+ dependencies = [
23
+ ('fleet', '0038_alter_colonel_type'),
24
+ ]
25
+
26
+ operations = [
27
+ migrations.RunPython(forwards_func, reverse_func, elidable=True),
28
+ ]
@@ -0,0 +1,18 @@
1
+ # Generated by Django 4.2.10 on 2024-10-18 08:00
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('fleet', '0039_auto_20241016_1047'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AlterField(
14
+ model_name='colonel',
15
+ name='pwm_frequency',
16
+ field=models.IntegerField(choices=[(0, '3kHz'), (1, '22kHz')], default=0, help_text='Affects Ample Wall dimmer PWM output (dimmer) frequency'),
17
+ ),
18
+ ]
simo/fleet/models.py CHANGED
@@ -91,7 +91,7 @@ class Colonel(DirtyFieldsMixin, models.Model):
91
91
  "and reset if a lot of data is being transmitted. "
92
92
  "Leave this off, unleess you know what you are doing!"
93
93
  )
94
- pwm_frequency = models.IntegerField(default=1, choices=(
94
+ pwm_frequency = models.IntegerField(default=0, choices=(
95
95
  (0, "3kHz"), (1, "22kHz")
96
96
  ), help_text="Affects Ample Wall dimmer PWM output (dimmer) frequency")
97
97
 
@@ -270,6 +270,10 @@ def after_colonel_save(sender, instance, created, *args, **kwargs):
270
270
  )
271
271
  if fleet_gateway.status != 'running':
272
272
  fleet_gateway.start()
273
+ # create i2c and dali interfaces automatically for game-changer boards
274
+ if instance.type == 'game-changer':
275
+ Interface.objects.create(colonel=instance, no=1, type='i2c')
276
+ Interface.objects.create(colonel=instance, no=2, type='dali')
273
277
 
274
278
 
275
279
  @receiver(post_save, sender=Component)
@@ -311,7 +315,7 @@ def post_component_delete(sender, instance, *args, **kwargs):
311
315
  comp, instance.config.get('da', 0), remove=True
312
316
  )
313
317
 
314
- elif instance.controller.family == 'dali':
318
+ elif instance.controller and instance.controller.family == 'dali':
315
319
  colonel = Colonel.objects.filter(id=instance.config['colonel']).first()
316
320
  if colonel:
317
321
  GatewayObjectCommand(
@@ -218,18 +218,26 @@ class FleetConsumer(AsyncWebsocketConsumer):
218
218
  }
219
219
  }
220
220
  config_data['settings'].update(instance_options)
221
- interfaces = await sync_to_async(list, thread_sensitive=True)(
222
- self.colonel.interfaces.all().select_related(
221
+
222
+ def get_interfaces(colonel):
223
+ return list(colonel.interfaces.all().select_related(
223
224
  'pin_a', 'pin_b'
224
- )
225
+ ))
226
+ interfaces = await sync_to_async(get_interfaces, thread_sensitive=True)(
227
+ self.colonel
225
228
  )
226
229
  for interface in interfaces:
227
230
  config_data['interfaces'][f'{interface.type}-{interface.no}'] = {
228
231
  'pin_a': interface.pin_a.no, 'pin_b': interface.pin_b.no,
229
232
  }
233
+
234
+ def get_components(colonel):
235
+ return list(
236
+ colonel.components.all().prefetch_related('slaves')
237
+ )
230
238
  components = await sync_to_async(
231
- list, thread_sensitive=True
232
- )(self.colonel.components.all().prefetch_related('slaves'))
239
+ get_components, thread_sensitive=True
240
+ )(self.colonel)
233
241
 
234
242
  def get_comp_config(comp):
235
243
  try:
@@ -3,6 +3,10 @@ import threading
3
3
  import pytz
4
4
  import datetime
5
5
  import json
6
+ import requests
7
+ import traceback
8
+ import sys
9
+ from bs4 import BeautifulSoup
6
10
  from django.core.exceptions import ValidationError
7
11
  from django.utils import timezone
8
12
  from django.utils.functional import cached_property
@@ -16,6 +20,7 @@ from simo.core.events import GatewayObjectCommand
16
20
  from simo.core.models import RUN_STATUS_CHOICES_MAP, Component
17
21
  from simo.core.utils.helpers import get_random_string
18
22
  from simo.core.utils.operations import OPERATIONS
23
+ from simo.core.middleware import get_current_instance
19
24
  from simo.core.controllers import (
20
25
  BEFORE_SEND, BEFORE_SET, ControllerBase,
21
26
  BinarySensor, NumericSensor, MultiSensor, Switch, Dimmer, DimmerPlus,
@@ -42,12 +47,14 @@ from .forms import (
42
47
  BlindsConfigForm, WateringConfigForm, StateSelectForm,
43
48
  AlarmClockConfigForm
44
49
  )
50
+ from .scripting import get_current_state
51
+ from .scripting.serializers import UserSerializer
45
52
 
46
53
  # ----------- Generic controllers -----------------------------
47
54
 
55
+
48
56
  class Script(ControllerBase, TimerMixin):
49
- masters_only = True
50
- name = _("Script")
57
+ name = _("AI Script")
51
58
  base_type = 'script'
52
59
  gateway_class = GenericGatewayHandler
53
60
  app_widget = ScriptWidget
@@ -101,6 +108,42 @@ class Script(ControllerBase, TimerMixin):
101
108
  else:
102
109
  self.send('start')
103
110
 
111
+ def ai_assistant(self, wish):
112
+ try:
113
+ request_data = {
114
+ 'hub_uid': dynamic_settings['core__hub_uid'],
115
+ 'hub_secret': dynamic_settings['core__hub_secret'],
116
+ 'instance_uid': get_current_instance().uid,
117
+ 'system_data': json.dumps(get_current_state()),
118
+ 'wish': wish,
119
+ }
120
+ except Exception as e:
121
+ print(traceback.format_exc(), file=sys.stderr)
122
+ return {'status': 'error', 'result': f"Internal error: {e}"}
123
+ user = get_current_user()
124
+ if user:
125
+ request_data['current_user'] = UserSerializer(user, many=False).data
126
+ try:
127
+ response = requests.post(
128
+ 'https://simo.io/hubs/ai-assist/scripts/', json=request_data
129
+ )
130
+ except:
131
+ return {'status': 'error', 'result': "Connection error"}
132
+
133
+ if response.status_code != 200:
134
+ content = response.content.decode()
135
+ if '<html' in content:
136
+ # Parse the HTML content
137
+ soup = BeautifulSoup(response.content, 'html.parser')
138
+ content = F"Server error {response.status_code}: {soup.title.string}"
139
+ return {'status': 'error', 'result': content}
140
+
141
+ return {
142
+ 'status': 'success',
143
+ 'result': response.json()['script'],
144
+ 'description': response.json()['description']
145
+ }
146
+
104
147
 
105
148
  class PresenceLighting(Script):
106
149
  masters_only = False
simo/generic/forms.py CHANGED
@@ -1,3 +1,4 @@
1
+ import time
1
2
  from django import forms
2
3
  from django.forms import formset_factory
3
4
  from django.db.models import Q
@@ -35,11 +36,32 @@ class ScriptConfigForm(BaseComponentForm):
35
36
  initial=True, required=False,
36
37
  help_text="Restart the script if it fails. "
37
38
  )
38
- code = forms.CharField(widget=PythonCode)
39
+ assistant_request = forms.CharField(
40
+ label="Request for AI assistant", required=False, max_length=1000,
41
+ widget=forms.Textarea(
42
+ attrs={'placeholder':
43
+ "Close the blind and turn on the main light "
44
+ "in my living room when it get's dark."
45
+ }
46
+ ),
47
+ help_text="Clearly describe in your own words what kind of automation "
48
+ "you want to happen with this scenario script. <br>"
49
+ "The more defined, exact and clear is your description the more "
50
+ "accurate automation script SIMO.io AI assistanw will generate.<br>"
51
+ "Use component, zone and category id's for best accuracy. <br>"
52
+ "SIMO.io AI will re-generate your automation code and update it's description in Notes field "
53
+ "every time this field is changed and it might take up to 60s to do it. <br>"
54
+ "Actual script code can only be edited via SIMO.io Admin.",
55
+ )
56
+ code = forms.CharField(widget=PythonCode, required=False)
39
57
  log = forms.CharField(
40
58
  widget=forms.HiddenInput, required=False
41
59
  )
42
60
 
61
+ app_exclude_fields = ('alarm_category', 'code', 'log')
62
+
63
+ _ai_resp = None
64
+
43
65
  def __init__(self, *args, **kwargs):
44
66
  super().__init__(*args, **kwargs)
45
67
  self.basic_fields.extend(['autostart', 'keep_alive'])
@@ -47,19 +69,20 @@ class ScriptConfigForm(BaseComponentForm):
47
69
  prefix = get_script_prefix()
48
70
  if prefix == '/':
49
71
  prefix = ''
50
- self.fields['log'].widget = LogOutputWidget(
51
- prefix + '/ws/log/%d/%d/' % (
52
- ContentType.objects.get_for_model(Component).id,
53
- self.instance.id
72
+ if 'log' in self.fields:
73
+ self.fields['log'].widget = LogOutputWidget(
74
+ prefix + '/ws/log/%d/%d/' % (
75
+ ContentType.objects.get_for_model(Component).id,
76
+ self.instance.id
77
+ )
54
78
  )
55
- )
56
79
 
57
80
  @classmethod
58
81
  def get_admin_fieldsets(cls, request, obj=None):
59
82
  base_fields = (
60
83
  'id', 'gateway', 'base_type', 'name', 'icon', 'zone', 'category',
61
84
  'show_in_app', 'autostart', 'keep_alive',
62
- 'code', 'control', 'log'
85
+ 'assistant_request', 'notes', 'code', 'control', 'log'
63
86
  )
64
87
 
65
88
  fieldsets = [
@@ -72,6 +95,44 @@ class ScriptConfigForm(BaseComponentForm):
72
95
  return fieldsets
73
96
 
74
97
 
98
+ def clean(self):
99
+ if self.cleaned_data.get('assistant_request'):
100
+ if self.instance.pk:
101
+ org = Component.objects.get(pk=self.instance.pk)
102
+ call_assistant = org.config.get('assistant_request') \
103
+ != self.cleaned_data['assistant_request']
104
+ else:
105
+ call_assistant = True
106
+ call_assistant = False
107
+ if call_assistant:
108
+ resp = self.instance.ai_assistant(
109
+ self.cleaned_data['assistant_request'],
110
+ )
111
+ if resp['status'] == 'success':
112
+ self._ai_resp = resp
113
+ elif resp['status'] == 'error':
114
+ self.add_error('assistant_request', resp['result'])
115
+
116
+ return self.cleaned_data
117
+
118
+ def save(self, commit=True):
119
+ if commit and self._ai_resp:
120
+ self.instance.config['code'] = self._ai_resp['result']
121
+ self.instance.notes = self._ai_resp['description']
122
+ if 'code' in self.cleaned_data:
123
+ self.cleaned_data['code'] = self._ai_resp['result']
124
+ if 'notes' in self.cleaned_data:
125
+ self.cleaned_data['notes'] = self._ai_resp['description']
126
+ obj = super().save(commit)
127
+ if commit:
128
+ obj.controller.stop()
129
+ if self.cleaned_data.get('keep_alive') \
130
+ or self.cleaned_data.get('autostart'):
131
+ time.sleep(2)
132
+ obj.controller.start()
133
+ return obj
134
+
135
+
75
136
  class ConditionForm(forms.Form):
76
137
  component = forms.ModelChoiceField(
77
138
  Component.objects.all(),
@@ -717,6 +778,19 @@ class StateSelectForm(BaseComponentForm):
717
778
  states = FormsetField(
718
779
  formset_factory(StateForm, can_delete=True, can_order=True, extra=0)
719
780
  )
781
+ is_main = forms.BooleanField(
782
+ initial=False, required=False,
783
+ help_text="Will be displayed in the app "
784
+ "right top corner for quick access."
785
+ )
786
+
787
+ def save(self, commit=True):
788
+ if commit and self.cleaned_data['is_main']:
789
+ from .controllers import StateSelect
790
+ for c in Component.objects.filter(controller_uid=StateSelect.uid):
791
+ c.config['is_main'] = False
792
+ c.save()
793
+ return super().save(commit)
720
794
 
721
795
 
722
796
  class AlarmClockEventForm(forms.Form):
simo/generic/models.py CHANGED
@@ -56,7 +56,6 @@ def handle_alarm_groups(sender, instance, *args, **kwargs):
56
56
  )
57
57
  from simo.notifications.utils import notify_users
58
58
  notify_users(
59
- alarm_group_component.zone.instance,
60
59
  'alarm', str(alarm_group_component), body,
61
60
  component=alarm_group_component
62
61
  )
@@ -0,0 +1,16 @@
1
+ from .serializers import *
2
+ from simo.core.models import Zone, Category, Component
3
+ from simo.users.models import InstanceUser
4
+ from simo.core.middleware import get_current_instance
5
+
6
+
7
+ def get_current_state():
8
+ get_current_instance()
9
+ return {
10
+ 'zones': ZoneSerializer(Zone.objects.all(), many=True).data,
11
+ 'categories': CategorySerializer(Category.objects.all(), many=True).data,
12
+ 'component': ComponentSerializer(Component.objects.all(), many=True).data,
13
+ 'instanceusers': InstanceUserSerializer(
14
+ InstanceUser.objects.all(), many=True
15
+ ).data,
16
+ }
@@ -0,0 +1,35 @@
1
+ from django.utils import timezone
2
+ from suntime import Sun
3
+ from simo.core.models import Instance
4
+
5
+
6
+ class LocalSun(Sun):
7
+
8
+ def __init__(self, instance=None):
9
+ if not instance:
10
+ instance = Instance.objects.all().first()
11
+ coordinates = instance.location.split(',')
12
+ try:
13
+ lat = float(coordinates[0])
14
+ except:
15
+ lat = 0
16
+ try:
17
+ lon = float(coordinates[1])
18
+ except:
19
+ lon = 0
20
+ super().__init__(lat, lon)
21
+
22
+ def is_night(self):
23
+ if timezone.now() > self.get_sunset_time():
24
+ return True
25
+ if timezone.now() < self.get_sunrise_time():
26
+ return True
27
+ return False
28
+
29
+ def seconds_to_sunset(self):
30
+ return (self.get_sunset_time() - timezone.now()).total_seconds()
31
+
32
+ def seconds_to_sunrise(self):
33
+ return (self.get_sunrise_time() - timezone.now()).total_seconds()
34
+
35
+
@@ -0,0 +1,77 @@
1
+ from rest_framework import serializers
2
+ from simo.core.models import Zone, Category, Component
3
+ from simo.users.models import User, InstanceUser, PermissionsRole
4
+
5
+
6
+ class ZoneSerializer(serializers.ModelSerializer):
7
+ '''Zone serializer for AI scripts helper'''
8
+
9
+ class Meta:
10
+ model = Zone
11
+ fields = 'id', 'name'
12
+
13
+
14
+ class CategorySerializer(serializers.ModelSerializer):
15
+ '''Category serializer for AI scripts helper'''
16
+
17
+ class Meta:
18
+ model = Category
19
+ fields = 'id', 'name'
20
+
21
+
22
+ class ComponentSerializer(serializers.ModelSerializer):
23
+ '''Component serializer for AI scripts helper'''
24
+
25
+ MAX_LENGTH = 500
26
+
27
+ value = serializers.SerializerMethodField()
28
+ meta = serializers.SerializerMethodField()
29
+ config = serializers.SerializerMethodField()
30
+
31
+ class Meta:
32
+ model = Component
33
+ fields = (
34
+ 'id', 'name', 'icon', 'zone', 'category', 'base_type',
35
+ 'value', 'value_units', 'meta', 'config'
36
+ )
37
+
38
+ def get_value(self, obj):
39
+ if obj.base_type in ('ip-camera', ):
40
+ return 'SKIP'
41
+ if len(str(obj.value)) > self.MAX_LENGTH:
42
+ return 'SKIP'
43
+ return obj.value
44
+
45
+ def get_meta(self, obj):
46
+ if len(str(obj.value)) > self.MAX_LENGTH:
47
+ return 'SKIP'
48
+ return obj.value
49
+
50
+ def get_config(self, obj):
51
+ if len(str(obj.value)) > self.MAX_LENGTH:
52
+ return 'SKIP'
53
+ return obj.value
54
+
55
+
56
+ class UserSerializer(serializers.ModelSerializer):
57
+
58
+ class Meta:
59
+ model = User
60
+ fields = 'email', 'name'
61
+
62
+
63
+ class PermissionsRoleSerializer(serializers.ModelSerializer):
64
+
65
+ class Meta:
66
+ model = PermissionsRole
67
+ fields = 'id', 'name', 'is_owner', 'is_superuser'
68
+
69
+
70
+ class InstanceUserSerializer(serializers.ModelSerializer):
71
+ '''Role serializer for AI scripts helper'''
72
+ user = UserSerializer()
73
+ role = PermissionsRoleSerializer()
74
+
75
+ class Meta:
76
+ model = InstanceUser
77
+ fields = 'user', 'role',
@@ -3,11 +3,11 @@
3
3
  data-ws_url="{{ obj.get_socket_url|default_if_none:"" }}">
4
4
  <span style="
5
5
  width: 20px; height:20px;
6
- background: url({% static 'weather_icons' %}/{{ obj.value.current.weather.0.icon }}@2x.png) center no-repeat;
6
+ background: url({% static 'weather_icons' %}/{{ obj.value.weather.0.icon }}@2x.png) center no-repeat;
7
7
  background-size: 150%;
8
8
  display: inline-block;
9
9
  position: relative;
10
10
  bottom: -4px;
11
11
  "></span>
12
- {{ obj.value.current.temp }}ᴼ {% if obj.zone.instance.units_of_measure == 'metric' %}C{% else %}F{% endif %}
12
+ {{ obj.value.temp }}ᴼ {% if obj.zone.instance.units_of_measure == 'metric' %}C{% else %}F{% endif %}
13
13
  </div>