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

@@ -1,6 +1,6 @@
1
1
  server{
2
- listen [::]:80 default_server ssl;
3
- listen 80 default_server ssl;
2
+ listen [::]:80 default_server;
3
+ listen 80 default_server;
4
4
 
5
5
  charset utf-8;
6
6
 
simo/core/controllers.py CHANGED
@@ -228,7 +228,7 @@ class ControllerBase(ABC):
228
228
  self.component.change_init_fingerprint = None
229
229
  self.component.save()
230
230
 
231
- def _receive_from_device(self, value, is_alive=True):
231
+ def _receive_from_device(self, value, is_alive=True, battery_level=None):
232
232
  value = self._prepare_for_set(value)
233
233
  actor = self._get_actor(value)
234
234
 
@@ -241,7 +241,9 @@ class ControllerBase(ABC):
241
241
  # in relation to the change of this component
242
242
  introduce(actor)
243
243
  self.component.alive = is_alive
244
- self.component.save(update_fields=['alive'])
244
+ if battery_level:
245
+ self.battery_level = battery_level
246
+ self.component.save(update_fields=['alive', 'battery_level'])
245
247
  self.set(value, actor)
246
248
 
247
249
  if init_by_device and self.component.slaves.count():
@@ -762,19 +764,36 @@ class Lock(Switch):
762
764
  app_widget = LockWidget
763
765
  admin_widget_template = 'admin/controller_widgets/lock.html'
764
766
 
767
+ UNLOCKED = 0
768
+ LOCKED = 1
769
+ LOCKING = 2
770
+ UNLOCKING = 3
771
+ FAULT = 4
772
+
765
773
  def lock(self):
766
774
  self.turn_on()
767
775
 
768
776
  def unlock(self):
769
777
  self.turn_off()
770
778
 
771
- def _receive_from_device(self, value, is_alive=True):
772
- if type(value) in (int, bool):
779
+ def _receive_from_device(self, value, is_alive=True, battery_level=None):
780
+ if type(value) == bool:
773
781
  if value:
774
782
  value = 'locked'
775
783
  else:
776
784
  value = 'unlocked'
777
- return super()._receive_from_device(value, is_alive=is_alive)
785
+ if type(value) == int:
786
+ values_map = {
787
+ self.UNLOCKED: 'unlocked',
788
+ self.LOCKED: 'locked',
789
+ self.LOCKING: 'locking',
790
+ self.UNLOCKING: 'unlocking',
791
+ self.FAULT: 'fault'
792
+ }
793
+ value = values_map.get(value, 'fault')
794
+ return super()._receive_from_device(
795
+ value, is_alive=is_alive, battery_level=battery_level
796
+ )
778
797
 
779
798
  def _validate_val(self, value, occasion=None):
780
799
  if occasion == BEFORE_SEND:
@@ -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/forms.py CHANGED
@@ -106,6 +106,8 @@ class ColonelComponentForm(BaseComponentForm):
106
106
  )
107
107
 
108
108
  def clean_colonel(self):
109
+ if not self.instance.pk:
110
+ return self.cleaned_data['colonel']
109
111
  org = self.instance.config.get('colonel')
110
112
  if org and org != self.cleaned_data['colonel'].id:
111
113
  raise forms.ValidationError(
@@ -618,7 +620,7 @@ class ColonelPWMOutputConfigForm(ColonelComponentForm):
618
620
  help_text="easeOutSine - offers most naturally looking effect."
619
621
  )
620
622
  inverse = forms.BooleanField(
621
- label=_("Inverse dimmer signal"), required=False
623
+ label=_("Inverse dimmer signal"), required=False, initial=True
622
624
  )
623
625
  on_value = forms.FloatField(
624
626
  required=True, initial=100,
simo/fleet/models.py CHANGED
@@ -1,4 +1,5 @@
1
1
  import requests
2
+ import time
2
3
  from django.db import transaction
3
4
  from django.db import models
4
5
  from django.db.models.signals import post_save, pre_delete, post_delete
@@ -82,10 +83,12 @@ class Colonel(DirtyFieldsMixin, models.Model):
82
83
  occupied_pins = models.JSONField(default=dict, blank=True)
83
84
 
84
85
  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."
86
+ default=False, help_text="ATENTION! Causes serious overhead and "
87
+ "significantly degrades the lifespan of a chip "
88
+ "due to a lot of writes to the memory. "
89
+ "It also causes Colonel websocket to run out of memory "
90
+ "and reset if a lot of data is being transmitted. "
91
+ "Leave this off, unleess you know what you are doing!"
89
92
  )
90
93
  pwm_frequency = models.IntegerField(default=1, choices=(
91
94
  (0, "3kHz"), (1, "22kHz")
@@ -183,39 +186,13 @@ class Colonel(DirtyFieldsMixin, models.Model):
183
186
  interface.scl_pin.save()
184
187
 
185
188
 
186
- @transaction.atomic()
187
189
  def move_to(self, other_colonel):
188
- # TODO: Need to replace pins on components!
189
- other_colonel.refresh_from_db()
190
- assert list(other_colonel.components.all()) == [], \
191
- "Other colonel must be completely empty!"
192
-
193
- for component in self.components.all():
194
- component.config['colonel'] = other_colonel.id
195
- component.save()
196
- self.components.remove(component)
197
- other_colonel.components.add(component)
198
-
199
- self.rebuild_occupied_pins()
200
- other_colonel.rebuild_occupied_pins()
201
-
202
- other_colonel.i2c_interfaces.all().delete()
203
-
204
- for i2c_interface in self.i2c_interfaces.all():
205
- I2CInterface.objects.create(
206
- no=i2c_interface.no,
207
- colonel=other_colonel, name=i2c_interface.name,
208
- freq=i2c_interface.freq,
209
- scl_pin=ColonelPin.objects.get(
210
- colonel=other_colonel, no=i2c_interface.scl_pin.no,
211
- ),
212
- sda_pin=ColonelPin.objects.get(
213
- colonel=other_colonel, no=i2c_interface.sda_pin.no,
214
- ),
215
- )
216
-
217
- self.update_config()
218
- other_colonel.update_config()
190
+ self.restart()
191
+ other_colonel.restart()
192
+ time.sleep(1)
193
+ self.uid = other_colonel.uid
194
+ other_colonel.delete()
195
+ self.save()
219
196
 
220
197
 
221
198
  class ColonelPin(models.Model):
@@ -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
@@ -43,6 +46,8 @@ class FleetConsumer(AsyncWebsocketConsumer):
43
46
 
44
47
 
45
48
  async def connect(self):
49
+ print("Fleet Socket Connect with headers:", self.scope.get('headers'))
50
+ await self.accept()
46
51
 
47
52
  headers = {
48
53
  item[0].decode().lower(): item[1].decode() for item in self.scope['headers']
@@ -88,7 +93,8 @@ class FleetConsumer(AsyncWebsocketConsumer):
88
93
  'name': headers.get('colonel-name'),
89
94
  'type': headers['colonel-type'],
90
95
  'firmware_version': headers['firmware-version'],
91
- 'last_seen': timezone.now()
96
+ 'last_seen': timezone.now(),
97
+ 'enabled': True
92
98
  }
93
99
  with transaction.atomic():
94
100
  colonel, new = Colonel.objects.get_or_create(
@@ -96,10 +102,10 @@ class FleetConsumer(AsyncWebsocketConsumer):
96
102
  )
97
103
  if not new:
98
104
  for key, val in defaults.items():
105
+ if key in ('new', ):
106
+ continue
99
107
  setattr(colonel, key, val)
100
- if new:
101
- colonel.enabled = True
102
- colonel.save()
108
+ colonel.save()
103
109
 
104
110
  return colonel, new
105
111
 
@@ -110,7 +116,6 @@ class FleetConsumer(AsyncWebsocketConsumer):
110
116
  print(f"Colonel {self.colonel} connected!")
111
117
  if not self.colonel.enabled:
112
118
  print("Colonel %s drop, it's not enabled!" % str(self.colonel))
113
- await self.accept()
114
119
  return await self.close()
115
120
 
116
121
  if headers.get('instance-uid') != self.colonel.instance.uid \
@@ -120,8 +125,6 @@ class FleetConsumer(AsyncWebsocketConsumer):
120
125
 
121
126
  self.connected = True
122
127
 
123
- await self.accept()
124
-
125
128
  await self.log_colonel_connected()
126
129
 
127
130
 
@@ -345,106 +348,111 @@ 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"}
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(data):
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
+ data['val'], bool(data.get('alive')),
381
+ data.get('battery_level')
397
382
  )
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"}
409
- )
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)
383
+ await sync_to_async(
384
+ receive_val, thread_sensitive=True
385
+ )(data)
386
+
387
+ if 'options' in data:
388
+ def receive_options(val):
389
+ component.meta['options'] = val
390
+ component.save()
391
+ await sync_to_async(
392
+ receive_options, thread_sensitive=True
393
+ )(data['options'])
394
+
395
+ if 'codes' in data and component.controller_uid == TTLock.uid:
396
+ def save_codes(codes):
397
+ component.meta['codes'] = codes
398
+ for code in codes:
399
+ Fingerprint.objects.get_or_create(
400
+ value=f"ttlock-{component.id}-code-{str(code)}",
401
+ defaults={'type': "TTLock code"}
402
+ )
403
+ component.save()
404
+ await sync_to_async(
405
+ save_codes, thread_sensitive=True
406
+ )(data['codes'])
407
+ if 'fingerprints' in data and component.controller_uid == TTLock.uid:
408
+ def save_codes(codes):
409
+ component.meta['fingerprints'] = codes
410
+ for code in codes:
411
+ Fingerprint.objects.get_or_create(
412
+ value=f"ttlock-{component.id}-finger-{str(code)}",
413
+ defaults={'type': "TTLock Fingerprint"}
414
+ )
415
+ component.save()
416
+ await sync_to_async(
417
+ save_codes, thread_sensitive=True
418
+ )(data['fingerprints'])
419
+
427
420
  except Exception as e:
428
421
  print(traceback.format_exc(), file=sys.stderr)
429
- self.gateway.finish_discovery()
430
422
 
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
- })
423
+ elif 'discover-ttlock' in data:
424
+ def process_discovery_result():
425
+ self.gateway.refresh_from_db()
426
+ if self.gateway.discovery.get('finished'):
427
+ return Component.objects.filter(
428
+ meta__finalization_data__temp_id=data['result']['id']
429
+ ).first()
430
+ try:
431
+ self.gateway.process_discovery(data)
432
+ except Exception as e:
433
+ print(traceback.format_exc(), file=sys.stderr)
434
+ self.gateway.finish_discovery()
435
+
436
+ finished_comp = await sync_to_async(
437
+ process_discovery_result, thread_sensitive=True
438
+ )()
439
+ if finished_comp:
440
+ await self.send_data({
441
+ 'command': 'finalize',
442
+ 'data': finished_comp.meta['finalization_data']
443
+ })
439
444
 
440
- elif bytes_data:
441
- if not self.colonel_logger:
442
- await self.start_logger()
445
+ elif bytes_data:
446
+ if not self.colonel_logger:
447
+ await self.start_logger()
443
448
 
444
- for logline in bytes_data.decode(errors='replace').split('\n'):
445
- self.colonel_logger.log(logging.INFO, logline)
449
+ for logline in bytes_data.decode(errors='replace').split('\n'):
450
+ self.colonel_logger.log(logging.INFO, logline)
451
+
452
+ await self.log_colonel_connected()
453
+ except Exception as e:
454
+ print(traceback.format_exc(), file=sys.stderr)
446
455
 
447
- await self.log_colonel_connected()
448
456
 
449
457
 
450
458
  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.4
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
@@ -43,4 +43,5 @@ Requires-Dist: ansi2html ==1.7.0
43
43
  Requires-Dist: slugify ==0.0.1
44
44
  Requires-Dist: django-countries ==7.5.1
45
45
  Requires-Dist: librosa ==0.10.1
46
+ Requires-Dist: daphne ==4.1.0
46
47
 
@@ -20,7 +20,7 @@ simo/__pycache__/wsgi.cpython-38.pyc,sha256=Wt9kKkH2Sg5LRL4NrVQQDYPIoDyTvnXwm6jZ
20
20
  simo/_hub_template/hub/asgi.py,sha256=ElN_fdeSkf0Ysa7pS9rJVmZ1HmLhFxb8jFaMLqe1220,126
21
21
  simo/_hub_template/hub/celeryc.py,sha256=3ksDXftIZKJ4Cq9WNKJERdZdQlDEnjTQXycweRFmsSQ,27
22
22
  simo/_hub_template/hub/manage.py,sha256=PNNlw3EVeIJDgkG0l-klqoxsKWfTYWG9jzRG0upmAaI,620
23
- simo/_hub_template/hub/nginx.conf,sha256=k7wH2Z57q6kZCRWqYMxMO6_Dm7ThhqkO7Qz-rdvfz3E,2008
23
+ simo/_hub_template/hub/nginx.conf,sha256=MOJljsz_symwQF_L7ubp2ZnhkE7BrvkzIxZPtd1sWw0,2000
24
24
  simo/_hub_template/hub/settings.py,sha256=4QhvhbtLRxHvAntwqG_qeAAtpDUqKvN4jzw9u3vqff8,361
25
25
  simo/_hub_template/hub/supervisor.conf,sha256=IY3fdK0fDD2eAothB0n54xhjQj8LYoXIR96-Adda5Z8,1353
26
26
  simo/_hub_template/hub/urls.py,sha256=Ydm-1BkYAzWeEF-MKSDIFf-7aE4qNLPm48-SA51XgJQ,25
@@ -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=oxdl3a8cZvR-4_Z-TGUROz2tHjN-T-OuqC-Q4AGcljA,27199
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
@@ -10125,13 +10125,13 @@ simo/fleet/api.py,sha256=Hxn84xI-Q77HxjINgRbjSJQOv9jii4OL20LxK0VSrS8,2499
10125
10125
  simo/fleet/auto_urls.py,sha256=gAXTWUvsWkQHRdZGM_W_5iJBEsM4lY063kIx3f5LUqs,578
10126
10126
  simo/fleet/ble.py,sha256=eHA_9ABjbmH1vUVCv9hiPXQL2GZZSEVwfO0xyI1S0nI,1081
10127
10127
  simo/fleet/controllers.py,sha256=N8Qzdp2RPFrpZ_l9O4u8VjHWoY_WTWGg76s3V3oJqEs,13999
10128
- simo/fleet/forms.py,sha256=UGj1mK2Zbl2LRlvLtEDObeGfC2wcuHleRbePo1_Vx6I,34972
10128
+ simo/fleet/forms.py,sha256=HpvcdFyMl9dLNx1sEOkAkXOkYht6ku2HwWWga-PJzZQ,35067
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=MldGstAZf5k0T_QE2qZEyp-64LhUG8Dqiy22_HptwH0,11779
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=xNeR1IuaBTiHO2GEXwB1_4CJ29uMlqBycIUdxGT96E8,19578
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.4.dist-info/LICENSE.md,sha256=M7wm1EmMGDtwPRdg7kW4d00h1uAXjKOT3HFScYQMeiE,34916
10347
+ simo-2.0.4.dist-info/METADATA,sha256=jFNjSQgwnq0CvLKeo5qrAgzbGkeytZt7Em6JVT7WGJM,1699
10348
+ simo-2.0.4.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
10349
+ simo-2.0.4.dist-info/top_level.txt,sha256=GmS1hrAbpVqn9OWZh6UX82eIOdRLgYA82RG9fe8v4Rs,5
10350
+ simo-2.0.4.dist-info/RECORD,,
File without changes