simo 2.0.2__py3-none-any.whl → 2.0.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.

simo/core/controllers.py CHANGED
@@ -762,6 +762,12 @@ class Lock(Switch):
762
762
  app_widget = LockWidget
763
763
  admin_widget_template = 'admin/controller_widgets/lock.html'
764
764
 
765
+ UNLOCKED = 0
766
+ LOCKED = 1
767
+ LOCKING = 2
768
+ UNLOCKING = 3
769
+ FAULT = 4
770
+
765
771
  def lock(self):
766
772
  self.turn_on()
767
773
 
@@ -769,11 +775,20 @@ class Lock(Switch):
769
775
  self.turn_off()
770
776
 
771
777
  def _receive_from_device(self, value, is_alive=True):
772
- if type(value) in (int, bool):
778
+ if type(value) == bool:
773
779
  if value:
774
780
  value = 'locked'
775
781
  else:
776
782
  value = 'unlocked'
783
+ if type(value) == int:
784
+ values_map = {
785
+ self.UNLOCKED: 'unlocked',
786
+ self.LOCKED: 'locked',
787
+ self.LOCKING: 'locking',
788
+ self.UNLOCKING: 'unlocking',
789
+ self.FAULT: 'fault'
790
+ }
791
+ value = values_map.get(value, 'fault')
777
792
  return super()._receive_from_device(value, is_alive=is_alive)
778
793
 
779
794
  def _validate_val(self, value, occasion=None):
@@ -7,6 +7,7 @@ from django.conf import settings
7
7
  from django.contrib.contenttypes.models import ContentType
8
8
  from channels.generic.websocket import AsyncWebsocketConsumer, WebsocketConsumer
9
9
  from simo.core.events import ObjectChangeEvent, get_event_obj
10
+ from simo.core.utils.logs import capture_socket_errors
10
11
  import paho.mqtt.client as mqtt
11
12
  from simo.users.middleware import introduce
12
13
  from simo.core.models import Component, Gateway
@@ -14,6 +15,7 @@ from simo.core.utils.model_helpers import get_log_file_path
14
15
  from simo.core.middleware import introduce_instance
15
16
 
16
17
 
18
+ @capture_socket_errors
17
19
  class SIMOWebsocketConsumer(WebsocketConsumer):
18
20
  headers = {}
19
21
 
@@ -24,6 +26,7 @@ class SIMOWebsocketConsumer(WebsocketConsumer):
24
26
  }
25
27
 
26
28
 
29
+ @capture_socket_errors
27
30
  class LogConsumer(AsyncWebsocketConsumer):
28
31
  log_file = None
29
32
  in_error = False
@@ -123,6 +126,7 @@ class LogConsumer(AsyncWebsocketConsumer):
123
126
  self.log_file = None
124
127
 
125
128
 
129
+ @capture_socket_errors
126
130
  class GatewayController(SIMOWebsocketConsumer):
127
131
  gateway = None
128
132
  _mqtt_client = None
@@ -178,6 +182,7 @@ class GatewayController(SIMOWebsocketConsumer):
178
182
  pass
179
183
 
180
184
 
185
+ @capture_socket_errors
181
186
  class ComponentController(SIMOWebsocketConsumer):
182
187
  component = None
183
188
  send_value = False
simo/core/utils/logs.py CHANGED
@@ -1,4 +1,10 @@
1
1
  import logging
2
+ from functools import wraps
3
+ from inspect import iscoroutinefunction
4
+ from logging import getLogger
5
+ from channels.exceptions import AcceptConnection, DenyConnection, StopConsumer
6
+
7
+ logger = getLogger()
2
8
 
3
9
 
4
10
  class StreamToLogger(object):
@@ -28,3 +34,31 @@ class StreamToLogger(object):
28
34
  if self.linebuf != '':
29
35
  self.logger.log(self.log_level, self.linebuf.rstrip())
30
36
  self.linebuf = ''
37
+
38
+
39
+
40
+
41
+ def propagate_exceptions(func):
42
+ async def wrapper(*args, **kwargs): # we're wrapping an async function
43
+ try:
44
+ return await func(*args, **kwargs)
45
+ except (AcceptConnection, DenyConnection, StopConsumer): # these are handled by channels
46
+ raise
47
+ except Exception as exception: # any other exception
48
+ # avoid logging the same exception multiple times
49
+ if not getattr(exception, "caught", False):
50
+ setattr(exception, "caught", True)
51
+ logger.error(
52
+ "Exception occurred in {}:".format(func.__qualname__),
53
+ exc_info=exception,
54
+ )
55
+ raise # propagate the exception
56
+ return wraps(func)(wrapper)
57
+
58
+
59
+ def capture_socket_errors(consumer_class):
60
+ for method_name, method in list(consumer_class.__dict__.items()):
61
+ if iscoroutinefunction(method): # an async method
62
+ # wrap the method with a decorator that propagate exceptions
63
+ setattr(consumer_class, method_name, propagate_exceptions(method))
64
+ return consumer_class
simo/fleet/models.py CHANGED
@@ -82,10 +82,12 @@ class Colonel(DirtyFieldsMixin, models.Model):
82
82
  occupied_pins = models.JSONField(default=dict, blank=True)
83
83
 
84
84
  logs_stream = models.BooleanField(
85
- default=False, help_text="ATENTION! Causes serious overhead!!! "
86
- "Leave this off. "
87
- "It causes Colonel websocket to run out of memory "
88
- "and reset if a lot of data is being transmitted."
85
+ default=False, help_text="ATENTION! Causes serious overhead and "
86
+ "significantly degrades the lifespan of a chip "
87
+ "due to a lot of writes to the memory. "
88
+ "It also causes Colonel websocket to run out of memory "
89
+ "and reset if a lot of data is being transmitted. "
90
+ "Leave this off, unleess you know what you are doing!"
89
91
  )
90
92
  pwm_frequency = models.IntegerField(default=1, choices=(
91
93
  (0, "3kHz"), (1, "22kHz")
@@ -196,9 +198,6 @@ class Colonel(DirtyFieldsMixin, models.Model):
196
198
  self.components.remove(component)
197
199
  other_colonel.components.add(component)
198
200
 
199
- self.rebuild_occupied_pins()
200
- other_colonel.rebuild_occupied_pins()
201
-
202
201
  other_colonel.i2c_interfaces.all().delete()
203
202
 
204
203
  for i2c_interface in self.i2c_interfaces.all():
@@ -214,6 +213,8 @@ class Colonel(DirtyFieldsMixin, models.Model):
214
213
  ),
215
214
  )
216
215
 
216
+ self.rebuild_occupied_pins()
217
+ other_colonel.rebuild_occupied_pins()
217
218
  self.update_config()
218
219
  other_colonel.update_config()
219
220
 
@@ -13,15 +13,18 @@ import paho.mqtt.client as mqtt
13
13
  from channels.generic.websocket import AsyncWebsocketConsumer
14
14
  from asgiref.sync import sync_to_async
15
15
  from simo.core.utils.model_helpers import get_log_file_path
16
+ from simo.core.utils.logs import capture_socket_errors
16
17
  from simo.core.events import GatewayObjectCommand, get_event_obj
17
18
  from simo.core.models import Gateway, Instance, Component
18
19
  from simo.conf import dynamic_settings
19
20
  from simo.users.models import Fingerprint
21
+
20
22
  from .gateways import FleetGatewayHandler
21
23
  from .models import Colonel
22
24
  from .controllers import TTLock
23
25
 
24
26
 
27
+ @capture_socket_errors
25
28
  class FleetConsumer(AsyncWebsocketConsumer):
26
29
  colonel = None
27
30
  colonel_logger = None
@@ -345,106 +348,110 @@ class FleetConsumer(AsyncWebsocketConsumer):
345
348
 
346
349
 
347
350
  async def receive(self, text_data=None, bytes_data=None):
348
- if text_data:
349
- print(f"{self.colonel}: {text_data}")
350
- data = json.loads(text_data)
351
- if 'get_config' in data:
352
- config = await self.get_config_data()
353
- print("Send config: ", config)
354
- await self.send_data({
355
- 'command': 'set_config', 'data': config
356
- }, compress=True)
357
- elif 'comp' in data:
358
- try:
351
+ try:
352
+ if text_data:
353
+ print(f"{self.colonel}: {text_data}")
354
+ data = json.loads(text_data)
355
+ if 'get_config' in data:
356
+ config = await self.get_config_data()
357
+ print("Send config: ", config)
358
+ await self.send_data({
359
+ 'command': 'set_config', 'data': config
360
+ }, compress=True)
361
+ elif 'comp' in data:
359
362
  try:
360
- id=int(data['comp'])
361
- except:
362
- return
363
-
364
- component = await sync_to_async(
365
- Component.objects.get, thread_sensitive=True
366
- )(id=id)
367
-
368
- if 'val' in data:
369
- def receive_val(val):
370
- if data.get('actor'):
371
- fingerprint = Fingerprint.objects.filter(
372
- value=f"ttlock-{component.id}-{data.get('actor')}",
373
- ).first()
374
- component.change_init_fingerprint = fingerprint
375
- component.controller._receive_from_device(
376
- val, bool(data.get('alive'))
377
- )
378
- await sync_to_async(
379
- receive_val, thread_sensitive=True
380
- )(data['val'])
381
-
382
- if 'options' in data:
383
- def receive_options(val):
384
- component.meta['options'] = val
385
- component.save()
386
- await sync_to_async(
387
- receive_options, thread_sensitive=True
388
- )(data['options'])
389
-
390
- if 'codes' in data and component.controller_uid == TTLock.uid:
391
- def save_codes(codes):
392
- component.meta['codes'] = codes
393
- for code in codes:
394
- Fingerprint.objects.get_or_create(
395
- value=f"ttlock-{component.id}-code-{str(code)}",
396
- defaults={'type': "TTLock code"}
397
- )
398
- component.save()
399
- await sync_to_async(
400
- save_codes, thread_sensitive=True
401
- )(data['codes'])
402
- if 'fingerprints' in data and component.controller_uid == TTLock.uid:
403
- def save_codes(codes):
404
- component.meta['fingerprints'] = codes
405
- for code in codes:
406
- Fingerprint.objects.get_or_create(
407
- value=f"ttlock-{component.id}-finger-{str(code)}",
408
- defaults={'type': "TTLock Fingerprint"}
363
+ try:
364
+ id=int(data['comp'])
365
+ except:
366
+ return
367
+
368
+ component = await sync_to_async(
369
+ Component.objects.get, thread_sensitive=True
370
+ )(id=id)
371
+
372
+ if 'val' in data:
373
+ def receive_val(val):
374
+ if data.get('actor'):
375
+ fingerprint = Fingerprint.objects.filter(
376
+ value=f"ttlock-{component.id}-{data.get('actor')}",
377
+ ).first()
378
+ component.change_init_fingerprint = fingerprint
379
+ component.controller._receive_from_device(
380
+ val, bool(data.get('alive'))
409
381
  )
410
- component.save()
411
- await sync_to_async(
412
- save_codes, thread_sensitive=True
413
- )(data['fingerprints'])
414
-
415
- except Exception as e:
416
- print(traceback.format_exc(), file=sys.stderr)
417
-
418
- elif 'discover-ttlock' in data:
419
- def process_discovery_result():
420
- self.gateway.refresh_from_db()
421
- if self.gateway.discovery.get('finished'):
422
- return Component.objects.filter(
423
- meta__finalization_data__temp_id=data['result']['id']
424
- ).first()
425
- try:
426
- self.gateway.process_discovery(data)
382
+ await sync_to_async(
383
+ receive_val, thread_sensitive=True
384
+ )(data['val'])
385
+
386
+ if 'options' in data:
387
+ def receive_options(val):
388
+ component.meta['options'] = val
389
+ component.save()
390
+ await sync_to_async(
391
+ receive_options, thread_sensitive=True
392
+ )(data['options'])
393
+
394
+ if 'codes' in data and component.controller_uid == TTLock.uid:
395
+ def save_codes(codes):
396
+ component.meta['codes'] = codes
397
+ for code in codes:
398
+ Fingerprint.objects.get_or_create(
399
+ value=f"ttlock-{component.id}-code-{str(code)}",
400
+ defaults={'type': "TTLock code"}
401
+ )
402
+ component.save()
403
+ await sync_to_async(
404
+ save_codes, thread_sensitive=True
405
+ )(data['codes'])
406
+ if 'fingerprints' in data and component.controller_uid == TTLock.uid:
407
+ def save_codes(codes):
408
+ component.meta['fingerprints'] = codes
409
+ for code in codes:
410
+ Fingerprint.objects.get_or_create(
411
+ value=f"ttlock-{component.id}-finger-{str(code)}",
412
+ defaults={'type': "TTLock Fingerprint"}
413
+ )
414
+ component.save()
415
+ await sync_to_async(
416
+ save_codes, thread_sensitive=True
417
+ )(data['fingerprints'])
418
+
427
419
  except Exception as e:
428
420
  print(traceback.format_exc(), file=sys.stderr)
429
- self.gateway.finish_discovery()
430
421
 
431
- finished_comp = await sync_to_async(
432
- process_discovery_result, thread_sensitive=True
433
- )()
434
- if finished_comp:
435
- await self.send_data({
436
- 'command': 'finalize',
437
- 'data': finished_comp.meta['finalization_data']
438
- })
422
+ elif 'discover-ttlock' in data:
423
+ def process_discovery_result():
424
+ self.gateway.refresh_from_db()
425
+ if self.gateway.discovery.get('finished'):
426
+ return Component.objects.filter(
427
+ meta__finalization_data__temp_id=data['result']['id']
428
+ ).first()
429
+ try:
430
+ self.gateway.process_discovery(data)
431
+ except Exception as e:
432
+ print(traceback.format_exc(), file=sys.stderr)
433
+ self.gateway.finish_discovery()
434
+
435
+ finished_comp = await sync_to_async(
436
+ process_discovery_result, thread_sensitive=True
437
+ )()
438
+ if finished_comp:
439
+ await self.send_data({
440
+ 'command': 'finalize',
441
+ 'data': finished_comp.meta['finalization_data']
442
+ })
439
443
 
440
- elif bytes_data:
441
- if not self.colonel_logger:
442
- await self.start_logger()
444
+ elif bytes_data:
445
+ if not self.colonel_logger:
446
+ await self.start_logger()
443
447
 
444
- for logline in bytes_data.decode(errors='replace').split('\n'):
445
- self.colonel_logger.log(logging.INFO, logline)
448
+ for logline in bytes_data.decode(errors='replace').split('\n'):
449
+ self.colonel_logger.log(logging.INFO, logline)
450
+
451
+ await self.log_colonel_connected()
452
+ except Exception as e:
453
+ print(traceback.format_exc(), file=sys.stderr)
446
454
 
447
- await self.log_colonel_connected()
448
455
 
449
456
 
450
457
  async def log_colonel_connected(self):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: simo
3
- Version: 2.0.2
3
+ Version: 2.0.3
4
4
  Summary: Smart Home on Steroids!
5
5
  Author-email: Simanas Venčkauskas <simanas@simo.io>
6
6
  Project-URL: Homepage, https://simo.io
@@ -34,7 +34,7 @@ simo/core/auto_urls.py,sha256=0gu-IL7PHobrmKW6ksffiOkAYu-aIorykWdxRNtwGYo,1194
34
34
  simo/core/autocomplete_views.py,sha256=JT5LA2_Wtr60XYSAIqaXFKFYPjrmkEf6yunXD9y2zco,4022
35
35
  simo/core/base_types.py,sha256=yqbIZqBksrAkEuHRbt6iExwPDDy0K5II2NzRCkmOvMU,589
36
36
  simo/core/context.py,sha256=98PXAMie43faRVBFkOG22uNpvGRNprcGhzjBFkrxaRY,1367
37
- simo/core/controllers.py,sha256=bxJRpmREw8CsgDAiuVvpOl8HxHhZqrRM1qXTF_8afV4,26617
37
+ simo/core/controllers.py,sha256=7M28j0I2Eh-Q7jIXZ7FMkNQoA7xluu67NSXhJXaW4gs,27018
38
38
  simo/core/dynamic_settings.py,sha256=U2WNL96JzVXdZh0EqMPWrxqO6BaRR2Eo5KTDqz7MC4o,1943
39
39
  simo/core/events.py,sha256=LvtonJGNyCb6HLozs4EG0WZItnDwNdtnGQ4vTcnKvUs,4438
40
40
  simo/core/filters.py,sha256=ghtOZcrwNAkIyF5_G9Sn73NkiI71mXv0NhwCk4IyMIM,411
@@ -48,7 +48,7 @@ simo/core/permissions.py,sha256=UmFjGPDWtAUbaWxJsWORb2q6BREHqndv9mkSIpnmdLk,1379
48
48
  simo/core/routing.py,sha256=X1_IHxyA-_Q7hw1udDoviVP4_FSBDl8GYETTC2zWTbY,499
49
49
  simo/core/serializers.py,sha256=bkfXZgUzbXZOrJY69VIevBHNLWRd7DmgyFRh4arr-gs,15810
50
50
  simo/core/signal_receivers.py,sha256=EZ8NSYZxUgSaLS16YZdK7T__l8dl0joMRllOxx5PUt4,2809
51
- simo/core/socket_consumers.py,sha256=R8zOkbZvsNf19NU9gz-2HzbTzpocb-j-jTGoC2KZxak,9488
51
+ simo/core/socket_consumers.py,sha256=n7VE2Fvqt4iEAYLTRbTPOcI-7tszMAADu7gimBxB-Fg,9635
52
52
  simo/core/storage.py,sha256=YlxmdRs-zhShWtFKgpJ0qp2NDBuIkJGYC1OJzqkbttQ,572
53
53
  simo/core/tasks.py,sha256=se27V-noW02v4ZY2PMv0AJkXNsY3NtJ4G43__KLW7Kg,11005
54
54
  simo/core/todos.py,sha256=eYVXfLGiapkxKK57XuviSNe3WsUYyIWZ0hgQJk7ThKo,665
@@ -10110,7 +10110,7 @@ simo/core/utils/form_fields.py,sha256=UOzYdPd71qgCw1H3qH01u85YjrOlETPJAHOJrZKhyD
10110
10110
  simo/core/utils/form_widgets.py,sha256=Zxn9jJqPle9Q_BKNJnyTDn7MosYwNp1TFu5LoKs0bfc,408
10111
10111
  simo/core/utils/formsets.py,sha256=1u34QGZ2P67cxZD2uUJS3lAf--E8XsiiqFmZ4P41Vw4,6463
10112
10112
  simo/core/utils/helpers.py,sha256=TOWy3slspaEYEhe9zDcb0RgzHUYslF6LZDlrWPGSqUI,3791
10113
- simo/core/utils/logs.py,sha256=JBUHhnm9Cn81csrQ4xHbujBFRL9YWulMwUj_zJPNvyw,1057
10113
+ simo/core/utils/logs.py,sha256=Zn9JQxqCH9Odx2J1BWT84nFCfkJ4Z4p5X8psdll7hNc,2366
10114
10114
  simo/core/utils/mixins.py,sha256=X6kUPKAi_F-uw7tgm8LEaYalBXpvDA-yrLNFCGr2rks,259
10115
10115
  simo/core/utils/model_helpers.py,sha256=3IzJeOvBoYdUJVXCJkY20npOZXPjNPAiEFvuT0OPhwA,884
10116
10116
  simo/core/utils/relay.py,sha256=i1xy_nPTgY5Xn0l2W4lNI3xeVUpDQTUUfV3M8h2DeBg,457
@@ -10128,10 +10128,10 @@ simo/fleet/controllers.py,sha256=N8Qzdp2RPFrpZ_l9O4u8VjHWoY_WTWGg76s3V3oJqEs,139
10128
10128
  simo/fleet/forms.py,sha256=UGj1mK2Zbl2LRlvLtEDObeGfC2wcuHleRbePo1_Vx6I,34972
10129
10129
  simo/fleet/gateways.py,sha256=xFsmF_SXYXK_kMJOCHkiInPJ_0VcPWz-kJDoMup2lT8,1576
10130
10130
  simo/fleet/managers.py,sha256=kpfvvfdH4LDxddIBDpdAb5gsVk8Gb0-L9biFcj9OFPs,807
10131
- simo/fleet/models.py,sha256=6IzGv4jvLaRInzKX8cetHcD08f59HCLWRClYDQJJyKc,12588
10131
+ simo/fleet/models.py,sha256=Ro0ZkYB3a7ZhczVQOxjAobCRECIdN0Nj0yb5EBybvW0,12809
10132
10132
  simo/fleet/routing.py,sha256=cofGsVWXMfPDwsJ6HM88xxtRxHwERhJ48Xyxc8mxg5o,149
10133
10133
  simo/fleet/serializers.py,sha256=zEpXAXxjk4Rf1JhlNnLTrs20qJggqjvIySbeHVo4Tt4,1505
10134
- simo/fleet/socket_consumers.py,sha256=HbkrV0i1TwBC38otu_2lzN6IlBdyZHVdXIVhU4y-YCM,18872
10134
+ simo/fleet/socket_consumers.py,sha256=o8yr27AYxKFStQGyXZrk7PP1P2fUgSjsWp76DojWqbM,19415
10135
10135
  simo/fleet/utils.py,sha256=D0EGFbDmW8zyhyxf5ozGtRpo4Sy5Ov6ZixukBK_e2Do,3462
10136
10136
  simo/fleet/views.py,sha256=PbdZpsM_7-oyKzuDX1A5WULNABA1_B7ISF70UJX97FE,1662
10137
10137
  simo/fleet/__pycache__/__init__.cpython-38.pyc,sha256=pIZE7EL6-cuJ3pQtaSwjKLrKLsTYelp1k9sRhXKLh6s,159
@@ -10343,8 +10343,8 @@ simo/users/templates/invitations/expired_msg.html,sha256=47DEQpj8HBSa-_TImW-5JCe
10343
10343
  simo/users/templates/invitations/expired_suggestion.html,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10344
10344
  simo/users/templates/invitations/taken_msg.html,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10345
10345
  simo/users/templates/invitations/taken_suggestion.html,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10346
- simo-2.0.2.dist-info/LICENSE.md,sha256=M7wm1EmMGDtwPRdg7kW4d00h1uAXjKOT3HFScYQMeiE,34916
10347
- simo-2.0.2.dist-info/METADATA,sha256=svTddp9-mP3qTEeGmU9RQ__HdbwqgbY6ivmcGiRa4xg,1669
10348
- simo-2.0.2.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
10349
- simo-2.0.2.dist-info/top_level.txt,sha256=GmS1hrAbpVqn9OWZh6UX82eIOdRLgYA82RG9fe8v4Rs,5
10350
- simo-2.0.2.dist-info/RECORD,,
10346
+ simo-2.0.3.dist-info/LICENSE.md,sha256=M7wm1EmMGDtwPRdg7kW4d00h1uAXjKOT3HFScYQMeiE,34916
10347
+ simo-2.0.3.dist-info/METADATA,sha256=kIgoVnqVIeZ9Paw4f7G6PA41Nwe3dafpPmpEbRV5JU4,1669
10348
+ simo-2.0.3.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
10349
+ simo-2.0.3.dist-info/top_level.txt,sha256=GmS1hrAbpVqn9OWZh6UX82eIOdRLgYA82RG9fe8v4Rs,5
10350
+ simo-2.0.3.dist-info/RECORD,,
File without changes