simo 2.5.42__py3-none-any.whl → 2.6.3__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 (60) hide show
  1. simo/__pycache__/settings.cpython-38.pyc +0 -0
  2. simo/automation/__pycache__/__init__.cpython-38.pyc +0 -0
  3. simo/automation/__pycache__/app_widgets.cpython-38.pyc +0 -0
  4. simo/automation/__pycache__/controllers.cpython-38.pyc +0 -0
  5. simo/automation/__pycache__/forms.cpython-38.pyc +0 -0
  6. simo/automation/__pycache__/gateways.cpython-38.pyc +0 -0
  7. simo/{generic/scripting → automation}/__pycache__/helpers.cpython-38.pyc +0 -0
  8. simo/automation/__pycache__/models.cpython-38.pyc +0 -0
  9. simo/{generic/scripting → automation}/__pycache__/serializers.cpython-38.pyc +0 -0
  10. simo/{generic/scripting/__pycache__/__init__.cpython-38.pyc → automation/__pycache__/state.cpython-38.pyc} +0 -0
  11. simo/automation/app_widgets.py +8 -0
  12. simo/automation/controllers.py +273 -0
  13. simo/automation/forms.py +273 -0
  14. simo/automation/gateways.py +257 -0
  15. simo/automation/migrations/0001_initial.py +39 -0
  16. simo/automation/migrations/0002_update_helpers_in_scripts.py +29 -0
  17. simo/automation/migrations/__init__.py +0 -0
  18. simo/automation/migrations/__pycache__/0001_initial.cpython-38.pyc +0 -0
  19. simo/automation/migrations/__pycache__/0002_update_helpers_in_scripts.cpython-38.pyc +0 -0
  20. simo/automation/migrations/__pycache__/__init__.cpython-38.pyc +0 -0
  21. simo/automation/models.py +31 -0
  22. simo/automation/templates/automations/auto_away.py +55 -0
  23. simo/automation/templates/automations/auto_state_script.py +31 -0
  24. simo/{core/templates/core/auto_night_day_script.py → automation/templates/automations/phones_sleep_script.py} +25 -13
  25. simo/core/__pycache__/admin.cpython-38.pyc +0 -0
  26. simo/core/__pycache__/filters.cpython-38.pyc +0 -0
  27. simo/core/__pycache__/models.cpython-38.pyc +0 -0
  28. simo/core/__pycache__/signal_receivers.cpython-38.pyc +0 -0
  29. simo/core/admin.py +7 -4
  30. simo/core/filters.py +61 -0
  31. simo/core/signal_receivers.py +50 -17
  32. simo/core/utils/__pycache__/type_constants.cpython-38.pyc +0 -0
  33. simo/core/utils/type_constants.py +1 -1
  34. simo/fleet/__pycache__/api.cpython-38.pyc +0 -0
  35. simo/fleet/__pycache__/serializers.cpython-38.pyc +0 -0
  36. simo/fleet/api.py +6 -0
  37. simo/fleet/serializers.py +9 -1
  38. simo/generic/__pycache__/app_widgets.cpython-38.pyc +0 -0
  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/app_widgets.py +0 -6
  43. simo/generic/controllers.py +4 -262
  44. simo/generic/forms.py +2 -280
  45. simo/generic/gateways.py +4 -193
  46. simo/settings.py +1 -0
  47. simo/users/__pycache__/api.cpython-38.pyc +0 -0
  48. simo/users/api.py +1 -2
  49. {simo-2.5.42.dist-info → simo-2.6.3.dist-info}/METADATA +1 -1
  50. {simo-2.5.42.dist-info → simo-2.6.3.dist-info}/RECORD +59 -41
  51. simo/core/templates/core/auto_state_script.py +0 -78
  52. /simo/{generic/scripting/example.py → automation/__init__.py} +0 -0
  53. /simo/{generic/scripting → automation}/helpers.py +0 -0
  54. /simo/{generic/scripting → automation}/serializers.py +0 -0
  55. /simo/{generic/scripting/__init__.py → automation/state.py} +0 -0
  56. /simo/{generic → automation}/templates/admin/controller_widgets/script.html +0 -0
  57. {simo-2.5.42.dist-info → simo-2.6.3.dist-info}/LICENSE.md +0 -0
  58. {simo-2.5.42.dist-info → simo-2.6.3.dist-info}/WHEEL +0 -0
  59. {simo-2.5.42.dist-info → simo-2.6.3.dist-info}/entry_points.txt +0 -0
  60. {simo-2.5.42.dist-info → simo-2.6.3.dist-info}/top_level.txt +0 -0
@@ -76,6 +76,9 @@ def create_instance_defaults(sender, instance, created, **kwargs):
76
76
  dummy, new = Gateway.objects.get_or_create(
77
77
  type='simo.generic.gateways.DummyGatewayHandler'
78
78
  )
79
+ automation, new = Gateway.objects.get_or_create(
80
+ type='simo.automation.gateways.AutomationsGatewayHandler'
81
+ )
79
82
  weather_icon = Icon.objects.get(slug='cloud-bolt-sun')
80
83
 
81
84
  Component.objects.create(
@@ -95,9 +98,23 @@ def create_instance_defaults(sender, instance, created, **kwargs):
95
98
  controller_uid='simo.generic.controllers.StateSelect',
96
99
  value='day',
97
100
  config={"states": [
98
- {"icon": "house-day", "name": "Day", "slug": "day"},
99
- {"icon": "house-night", "name": "Evening", "slug": "evening"},
100
- {"icon": "moon-cloud", "name": "Night", "slug": "night"},
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"},
101
118
  {"icon": "house-person-leave", "name": "Away", "slug": "away"},
102
119
  {"icon": "island-tropical", "name": "Vacation", "slug": "vacation"}
103
120
  ], "is_main": True}
@@ -105,45 +122,61 @@ def create_instance_defaults(sender, instance, created, **kwargs):
105
122
 
106
123
 
107
124
  auto_state_code = render_to_string(
108
- 'core/auto_state_script.py', {'state_comp_id': state_comp.id}
125
+ 'automations/auto_state_script.py', {'state_comp_id': state_comp.id}
109
126
  )
110
127
  Component.objects.create(
111
128
  name='Auto state', icon=Icon.objects.get(slug='bolt'),
112
129
  zone=other_zone,
113
130
  category=other_category, show_in_app=False,
114
- gateway=generic, base_type='script',
115
- controller_uid='simo.generic.controllers.Script',
131
+ gateway=automation, base_type='script',
132
+ controller_uid='simo.automation.controllers.Script',
116
133
  config={
117
134
  "code": auto_state_code, 'autostart': True, 'keep_alive': True,
118
135
  "notes": f"""
119
- The script automatically controls the states of the "State" component (ID:{state_comp.id}) — 'day,' 'evening,' 'night,' 'away.'
120
- The 'day' state is activated on weekdays from 10 a.m., and on weekends from 11 a.m. When the sun sets, the 'evening' state is activated, and at midnight, the 'night' state is activated.
121
- If no one is home, the 'away' state is activated.
122
- If a different state, such as 'vacation,' is selected, the script stops running and waits until the State is switched back to one of the controlled states.
123
- If one of the controlled states is manually selected, the script waits until that state is reached automatically and, once aligned with the manually set state, resumes its operation in normal mode.
136
+ The script automatically controls the states of the "State" component (ID:{state_comp.id}) — 'morning', 'day', 'evening', 'night'.
137
+
124
138
  """
125
139
  }
126
140
  )
127
141
 
128
142
  code = render_to_string(
129
- 'core/auto_night_day_script.py', {'state_comp_id': state_comp.id}
143
+ 'automations/phones_sleep_script.py', {'state_comp_id': state_comp.id}
130
144
  )
131
145
  Component.objects.create(
132
- name='Auto night/day by owner phones on charge',
146
+ name='Sleep mode when owner phones are charge',
133
147
  icon=Icon.objects.get(slug='bolt'), zone=other_zone,
134
148
  category=other_category, show_in_app=False,
135
- gateway=generic, base_type='script',
136
- controller_uid='simo.generic.controllers.Script',
149
+ gateway=automation, base_type='script',
150
+ controller_uid='simo.automation.controllers.Script',
137
151
  config={
138
152
  "code": code, 'autostart': True, 'keep_alive': True,
139
153
  "notes": f"""
140
- Automatically sets State component (ID: {state_comp.id}) to "night" if it is later than 10pm and all home owners phones who are at home are put on charge.
141
- Sets State component to "day" state as soon as none of the home owners phones are on charge and it is 6am or later.
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.
142
156
 
143
157
  """
144
158
  }
145
159
  )
146
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
+ }
178
+ )
179
+
147
180
  # Create default User permission roles
148
181
 
149
182
  PermissionsRole.objects.create(
@@ -51,7 +51,7 @@ CONTROLLER_TYPES_MAP = get_controller_types_map()
51
51
  def get_controller_types_choices(gateway=None):
52
52
  choices = []
53
53
  for controller_cls in get_controller_types_map(gateway).values():
54
- choices.append((controller_cls.uid, controller_cls.name))
54
+ choices.append((controller_cls.uid, f"{controller_cls.gateway_class.name} | {controller_cls.name}"))
55
55
  return choices
56
56
 
57
57
 
Binary file
simo/fleet/api.py CHANGED
@@ -1,6 +1,7 @@
1
1
  from django.db.models import Count
2
2
  from django.utils.translation import gettext_lazy as _
3
3
  from rest_framework import viewsets
4
+ from rest_framework.response import Response as RESTResponse
4
5
  from rest_framework.decorators import action
5
6
  from rest_framework.exceptions import ValidationError as APIValidationError
6
7
  from simo.core.api import InstanceMixin
@@ -37,6 +38,7 @@ class ColonelsViewSet(InstanceMixin, viewsets.ModelViewSet):
37
38
  def check_for_upgrade(self, request, pk=None, *args, **kwargs):
38
39
  colonel = self.get_object()
39
40
  colonel.check_for_upgrade()
41
+ return RESTResponse({'status': 'success'})
40
42
 
41
43
  @action(detail=True, methods=['post'])
42
44
  def upgrade(self, request, pk=None, *args, **kwargs):
@@ -45,16 +47,19 @@ class ColonelsViewSet(InstanceMixin, viewsets.ModelViewSet):
45
47
  colonel.update_firmware(colonel.major_upgrade_available)
46
48
  elif colonel.minor_upgrade_available:
47
49
  colonel.update_firmware(colonel.minor_upgrade_available)
50
+ return RESTResponse({'status': 'success'})
48
51
 
49
52
  @action(detail=True, methods=['post'])
50
53
  def restart(self, request, pk=None, *args, **kwargs):
51
54
  colonel = self.get_object()
52
55
  colonel.restart()
56
+ return RESTResponse({'status': 'success'})
53
57
 
54
58
  @action(detail=True, methods=['post'])
55
59
  def update_config(self, request, pk=None, *args, **kwargs):
56
60
  colonel = self.get_object()
57
61
  colonel.update_config()
62
+ return RESTResponse({'status': 'success'})
58
63
 
59
64
  @action(detail=True, methods=['post'])
60
65
  def move_to(self, request, pk, *args, **kwargs):
@@ -68,6 +73,7 @@ class ColonelsViewSet(InstanceMixin, viewsets.ModelViewSet):
68
73
  if not target:
69
74
  raise APIValidationError(_('Invalid target.'), code=400)
70
75
  colonel.move_to(target)
76
+ return RESTResponse({'status': 'success'})
71
77
 
72
78
 
73
79
  class InterfaceViewSet(
simo/fleet/serializers.py CHANGED
@@ -1,4 +1,5 @@
1
1
  from rest_framework import serializers
2
+ from simo.core.serializers import TimestampField
2
3
  from .models import InstanceOptions, Colonel, ColonelPin, Interface
3
4
 
4
5
 
@@ -43,6 +44,8 @@ class ColonelSerializer(serializers.ModelSerializer):
43
44
  pins = serializers.SerializerMethodField()
44
45
  interfaces = serializers.SerializerMethodField()
45
46
  newer_firmware_available = serializers.SerializerMethodField()
47
+ last_seen = TimestampField()
48
+ is_empty = serializers.SerializerMethodField()
46
49
 
47
50
  class Meta:
48
51
  model = Colonel
@@ -51,10 +54,12 @@ class ColonelSerializer(serializers.ModelSerializer):
51
54
  'firmware_version', 'firmware_auto_update',
52
55
  'newer_firmware_available',
53
56
  'socket_connected', 'last_seen', 'pins', 'interfaces',
57
+ 'is_empty'
54
58
  )
55
59
  read_only_fields = [
56
60
  'uid', 'type', 'firmware_version', 'newer_firmware_available',
57
- 'socket_connected', 'last_seen', 'pins', 'interfaces'
61
+ 'socket_connected', 'last_seen', 'pins', 'interfaces',
62
+ 'is_empty'
58
63
  ]
59
64
 
60
65
  def get_pins(self, obj):
@@ -72,6 +77,9 @@ class ColonelSerializer(serializers.ModelSerializer):
72
77
  def get_newer_firmware_available(self, obj):
73
78
  return obj.newer_firmware_available()
74
79
 
80
+ def get_is_empty(self, obj):
81
+ return not bool(obj.components.all().count())
82
+
75
83
  def update(self, instance, validated_data):
76
84
  instance = super().update(instance, validated_data)
77
85
  instance.update_config()
@@ -2,12 +2,6 @@ from django.utils.translation import gettext_lazy as _
2
2
  from simo.core.app_widgets import BaseAppWidget
3
3
 
4
4
 
5
- class ScriptWidget(BaseAppWidget):
6
- uid = 'script'
7
- name = _("Script")
8
- size = [2, 1]
9
-
10
-
11
5
  class ThermostatWidget(BaseAppWidget):
12
6
  uid = 'thermostat'
13
7
  name = _("Thermostat")
@@ -1,31 +1,21 @@
1
- import time
2
- import threading
3
1
  import pytz
4
2
  import datetime
5
3
  import json
6
- import requests
7
- import traceback
8
- import sys
9
- import random
10
- from bs4 import BeautifulSoup
11
4
  from django.core.exceptions import ValidationError
12
5
  from django.utils import timezone
13
6
  from django.utils.functional import cached_property
14
7
  from django.utils.translation import gettext_lazy as _
15
8
  from django.conf import settings
16
9
  from django.urls import reverse_lazy
17
- from simo.conf import dynamic_settings
18
10
  from simo.users.middleware import get_current_user, introduce
19
11
  from simo.users.utils import get_system_user
20
- from simo.core.events import GatewayObjectCommand
21
- from simo.core.models import RUN_STATUS_CHOICES_MAP, Component
12
+ from simo.core.models import Component
22
13
  from simo.core.utils.helpers import get_random_string
23
- from simo.core.utils.operations import OPERATIONS
24
14
  from simo.core.middleware import get_current_instance
25
15
  from simo.core.controllers import (
26
- BEFORE_SEND, BEFORE_SET, ControllerBase,
16
+ BEFORE_SEND, ControllerBase,
27
17
  BinarySensor, NumericSensor, MultiSensor, Switch, Dimmer, DimmerPlus,
28
- RGBWLight, TimerMixin,
18
+ RGBWLight,
29
19
  DoubleSwitch, TripleSwitch, QuadrupleSwitch, QuintupleSwitch
30
20
  )
31
21
  from simo.core.utils.config_values import (
@@ -37,267 +27,19 @@ from simo.core.utils.config_values import (
37
27
  )
38
28
  from .gateways import GenericGatewayHandler, DummyGatewayHandler
39
29
  from .app_widgets import (
40
- ScriptWidget, ThermostatWidget, AlarmGroupWidget, IPCameraWidget,
30
+ ThermostatWidget, AlarmGroupWidget, IPCameraWidget,
41
31
  WateringWidget, StateSelectWidget, AlarmClockWidget,
42
32
  WeatherWidget
43
33
  )
44
34
  from .forms import (
45
- ScriptConfigForm, PresenceLightingConfigForm,
46
35
  ThermostatConfigForm, AlarmGroupConfigForm,
47
36
  IPCameraConfigForm, WeatherForm,
48
37
  WateringConfigForm, StateSelectForm,
49
38
  AlarmClockConfigForm
50
39
  )
51
- from .scripting import get_current_state
52
- from .scripting.serializers import UserSerializer
53
40
 
54
41
  # ----------- Generic controllers -----------------------------
55
42
 
56
-
57
- class Script(ControllerBase, TimerMixin):
58
- name = _("AI Script")
59
- base_type = 'script'
60
- gateway_class = GenericGatewayHandler
61
- app_widget = ScriptWidget
62
- config_form = ScriptConfigForm
63
- admin_widget_template = 'admin/controller_widgets/script.html'
64
- default_config = {'autostart': True, 'autorestart': True}
65
- default_value = 'stopped'
66
-
67
- def _validate_val(self, value, occasion=None):
68
- if occasion == BEFORE_SEND:
69
- if value not in ('start', 'stop'):
70
- raise ValidationError("Must be 'start' or 'stop'")
71
- elif occasion == BEFORE_SET:
72
- if value not in RUN_STATUS_CHOICES_MAP.keys():
73
- raise ValidationError(
74
- "Invalid script controller status!"
75
- )
76
- return value
77
-
78
- def _prepare_for_send(self, value):
79
- if value == 'start':
80
- new_code = getattr(self.component, 'new_code', None)
81
- if new_code:
82
- self.component.new_code = None
83
- self.component.refresh_from_db()
84
- self.component.config['code'] = new_code
85
- self.component.save(update_fields=['config'])
86
- return value
87
-
88
- def _val_to_success(self, value):
89
- if value == 'start':
90
- return 'running'
91
- else:
92
- return 'stopped'
93
-
94
- def start(self, new_code=None):
95
- if new_code:
96
- self.component.new_code = new_code
97
- self.send('start')
98
-
99
- def play(self):
100
- return self.start()
101
-
102
- def stop(self):
103
- self.send('stop')
104
-
105
- def toggle(self):
106
- self.component.refresh_from_db()
107
- if self.component.value == 'running':
108
- self.send('stop')
109
- else:
110
- self.send('start')
111
-
112
- def ai_assistant(self, wish):
113
- try:
114
- request_data = {
115
- 'hub_uid': dynamic_settings['core__hub_uid'],
116
- 'hub_secret': dynamic_settings['core__hub_secret'],
117
- 'instance_uid': get_current_instance().uid,
118
- 'system_data': json.dumps(get_current_state()),
119
- 'wish': wish,
120
- }
121
- except Exception as e:
122
- print(traceback.format_exc(), file=sys.stderr)
123
- return {'status': 'error', 'result': f"Internal error: {e}"}
124
- user = get_current_user()
125
- if user:
126
- request_data['current_user'] = UserSerializer(user, many=False).data
127
- try:
128
- response = requests.post(
129
- 'https://simo.io/hubs/ai-assist/scripts/', json=request_data
130
- )
131
- except:
132
- return {'status': 'error', 'result': "Connection error"}
133
-
134
- if response.status_code != 200:
135
- content = response.content.decode()
136
- if '<html' in content:
137
- # Parse the HTML content
138
- soup = BeautifulSoup(response.content, 'html.parser')
139
- content = F"Server error {response.status_code}: {soup.title.string}"
140
- return {'status': 'error', 'result': content}
141
-
142
- return {
143
- 'status': 'success',
144
- 'result': response.json()['script'],
145
- 'description': response.json()['description']
146
- }
147
-
148
-
149
- class PresenceLighting(Script):
150
- masters_only = False
151
- name = _("Presence lighting")
152
- config_form = PresenceLightingConfigForm
153
-
154
- # script specific variables
155
- sensors = {}
156
- condition_comps = {}
157
- light_org_values = {}
158
- is_on = False
159
- turn_off_task = None
160
- last_presence = 0
161
- hold_time = 60
162
- conditions = []
163
-
164
- def _run(self):
165
- self.hold_time = self.component.config.get('hold_time', 0) * 10
166
- for id in self.component.config['presence_sensors']:
167
- sensor = Component.objects.filter(id=id).first()
168
- if sensor:
169
- sensor.on_change(self._on_sensor)
170
- self.sensors[id] = sensor
171
-
172
- for light_params in self.component.config['lights']:
173
- light = Component.objects.filter(
174
- id=light_params.get('light')
175
- ).first()
176
- if not light or not light.controller:
177
- continue
178
- light.on_change(self._on_light_change)
179
-
180
- for condition in self.component.config.get('conditions', []):
181
- comp = Component.objects.filter(
182
- id=condition.get('component', 0)
183
- ).first()
184
- if comp:
185
- condition['component'] = comp
186
- self.conditions.append(condition)
187
- comp.on_change(self._on_condition)
188
- self.condition_comps[comp.id] = comp
189
-
190
- while True:
191
- self._regulate(on_val_change=False)
192
- time.sleep(random.randint(5, 15))
193
-
194
- def _on_sensor(self, sensor=None):
195
- if sensor:
196
- self.sensors[sensor.id] = sensor
197
- self._regulate()
198
-
199
- def _on_condition(self, condition_comp=None):
200
- if condition_comp:
201
- for condition in self.conditions:
202
- if condition['component'].id == condition_comp.id:
203
- condition['component'] = condition_comp
204
- self._regulate()
205
-
206
- def _on_light_change(self, light):
207
- if self.is_on:
208
- self.light_org_values[light.id] = light.value
209
-
210
- def _regulate(self, on_val_change=True):
211
- presence_values = [s.value for id, s in self.sensors.items()]
212
- if self.component.config.get('act_on', 0) == 0:
213
- must_on = any(presence_values)
214
- else:
215
- must_on = all(presence_values)
216
-
217
- if must_on and on_val_change:
218
- print("Presence detected!")
219
-
220
- additional_conditions_met = True
221
- for condition in self.conditions:
222
-
223
- comp = condition['component']
224
-
225
- op = OPERATIONS.get(condition.get('op'))
226
- if not op:
227
- continue
228
-
229
- if condition['op'] == 'in':
230
- if comp.value not in self._string_to_vals(condition['value']):
231
- if must_on and on_val_change:
232
- print(
233
- f"Condition not met: [{comp} value:{comp.value} "
234
- f"{condition['op']} {condition['value']}]"
235
- )
236
- additional_conditions_met = False
237
- break
238
-
239
- if not op(comp.value, condition['value']):
240
- if must_on and on_val_change:
241
- print(
242
- f"Condition not met: [{comp} value:{comp.value} "
243
- f"{condition['op']} {condition['value']}]"
244
- )
245
- additional_conditions_met = False
246
- break
247
-
248
- if must_on and additional_conditions_met and not self.is_on:
249
- print("Turn the lights ON!")
250
- self.is_on = True
251
- self.light_org_values = {}
252
- for light_params in self.component.config['lights']:
253
- comp = Component.objects.filter(
254
- id=light_params.get('light')
255
- ).first()
256
- if not comp or not comp.controller:
257
- continue
258
- self.light_org_values[comp.id] = comp.value
259
- print(f"Send {light_params['on_value']} to {comp}!")
260
- comp.controller.send(light_params['on_value'])
261
- return
262
-
263
- if self.is_on:
264
- if not additional_conditions_met:
265
- return self._turn_it_off()
266
- if not any(presence_values):
267
- if not self.component.config.get('hold_time', 0):
268
- return self._turn_it_off()
269
-
270
- if not self.last_presence:
271
- self.last_presence = time.time()
272
-
273
- if self.hold_time and (
274
- time.time() - self.hold_time > self.last_presence
275
- ):
276
- self._turn_it_off()
277
-
278
-
279
- def _turn_it_off(self):
280
- print("Turn the lights OFF!")
281
- self.is_on = False
282
- self.last_presence = 0
283
- for light_params in self.component.config['lights']:
284
- comp = Component.objects.filter(
285
- id=light_params.get('light')
286
- ).first()
287
- if not comp or not comp.controller:
288
- continue
289
- print(f"Send {light_params['on_value']} to {comp}!")
290
- comp.send(light_params.get('off_value', 0))
291
-
292
-
293
- # TODO: Night lighting
294
- #
295
- # Lights: components (switches, dimmers)
296
- # On value: 40
297
- # Sunset offset (mins): negative = earlier, positive = later
298
- # Save energy at night: 1 - 6 turn the lights completely off at night.
299
-
300
-
301
43
  class Thermostat(ControllerBase):
302
44
  name = _("Thermostat")
303
45
  base_type = 'thermostat'