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

Binary file
Binary file
simo/core/admin.py CHANGED
@@ -60,6 +60,18 @@ class IconAdmin(EasyObjectsDeleteMixin, admin.ModelAdmin):
60
60
 
61
61
 
62
62
 
63
+ @admin.register(Instance)
64
+ class InstanceAdmin(admin.ModelAdmin):
65
+ list_display = 'name', 'slug', 'uid', 'timezone', 'is_active'
66
+ list_filter = 'is_active',
67
+ readonly_fields = 'uid',
68
+
69
+ def has_add_permission(self, request):
70
+ # instances are added via SIMO.io
71
+ return False
72
+
73
+
74
+
63
75
  @admin.register(Zone)
64
76
  class ZoneAdmin(EasyObjectsDeleteMixin, SortableAdminMixin, admin.ModelAdmin):
65
77
  list_display = 'name', 'instance'
@@ -0,0 +1,36 @@
1
+ # Generated by Django 4.2.10 on 2024-11-18 09:32
2
+
3
+ from django.conf import settings
4
+ from django.db import migrations, models
5
+ import django.db.models.deletion
6
+
7
+
8
+ class Migration(migrations.Migration):
9
+
10
+ dependencies = [
11
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
12
+ ('core', '0044_alter_gateway_type'),
13
+ ]
14
+
15
+ operations = [
16
+ migrations.AlterField(
17
+ model_name='instance',
18
+ name='device_report_history_days',
19
+ field=models.PositiveIntegerField(default=0, help_text='How many days of user device reports logs to keep? <br>Use 0 if you do not want to keep these logs at all.'),
20
+ ),
21
+ migrations.AlterField(
22
+ model_name='instance',
23
+ name='history_days',
24
+ field=models.PositiveIntegerField(default=90, help_text='How many days of component history to keep?'),
25
+ ),
26
+ migrations.AlterField(
27
+ model_name='instance',
28
+ name='learn_fingerprints',
29
+ field=models.ForeignKey(blank=True, editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL),
30
+ ),
31
+ migrations.AlterField(
32
+ model_name='instance',
33
+ name='learn_fingerprints_start',
34
+ field=models.DateTimeField(blank=True, editable=False, null=True),
35
+ ),
36
+ ]
simo/core/models.py CHANGED
@@ -90,16 +90,19 @@ class Instance(DirtyFieldsMixin, models.Model, SimoAdminMixin):
90
90
  limit_choices_to={'base_type__in': ['numeric-sensor', 'multi-sensor']}
91
91
  )
92
92
  history_days = models.PositiveIntegerField(
93
- default=90, help_text="How many days of component history do we keep?"
93
+ default=90, help_text="How many days of component history to keep?"
94
94
  )
95
95
  device_report_history_days = models.PositiveIntegerField(
96
96
  default=0,
97
- help_text="How many days of user device reports log do we keep? "
97
+ help_text="How many days of user device reports logs to keep? <br>"
98
98
  "Use 0 if you do not want to keep these logs at all."
99
99
  )
100
- learn_fingerprints_start = models.DateTimeField(null=True, blank=True)
100
+ learn_fingerprints_start = models.DateTimeField(
101
+ null=True, blank=True, editable=False
102
+ )
101
103
  learn_fingerprints = models.ForeignKey(
102
- User, null=True, blank=True, on_delete=models.SET_NULL
104
+ User, null=True, blank=True, on_delete=models.SET_NULL,
105
+ editable=False
103
106
  )
104
107
 
105
108
  #objects = InstanceManager()
Binary file
Binary file
Binary file
simo/users/admin.py CHANGED
@@ -4,6 +4,7 @@ from django.utils.safestring import mark_safe
4
4
  from django.contrib import messages
5
5
  from django.contrib.auth.admin import UserAdmin as OrgUserAdmin
6
6
  from django.contrib import admin
7
+ from django.utils import timezone
7
8
  from simo.core.middleware import get_current_instance
8
9
  from .models import (
9
10
  PermissionsRole, ComponentPermission, User, UserDevice, UserDeviceReportLog,
@@ -130,11 +131,13 @@ admin.site.unregister(Group)
130
131
  class UserDeviceLog(admin.ModelAdmin):
131
132
  model = UserDeviceReportLog
132
133
  readonly_fields = (
133
- 'datetime', 'app_open', 'at_home', 'location', 'relay', 'users',
134
+ 'timestamp', 'app_open', 'relay', 'at_home',
135
+ 'location', 'location_smoothed', 'users',
134
136
  'speed_kmh', 'phone_on_charge'
135
137
  )
136
138
  list_display = (
137
- 'datetime', 'at_home', 'app_open', 'location', 'relay', 'speed_kmh',
139
+ 'timestamp', 'app_open', 'relay', 'at_home',
140
+ 'location', 'location_smoothed', 'speed_kmh',
138
141
  'phone_on_charge', 'users'
139
142
  )
140
143
  fields = readonly_fields
@@ -143,6 +146,11 @@ class UserDeviceLog(admin.ModelAdmin):
143
146
  def has_add_permission(self, request, obj=None):
144
147
  return False
145
148
 
149
+ def timestamp(self, obj):
150
+ return obj.datetime.astimezone(
151
+ timezone.get_current_timezone()
152
+ ).strftime("%m/%d/%Y, %H:%M:%S")
153
+
146
154
  def users(self, obj):
147
155
  return mark_safe(', '.join([
148
156
  f'<a href="{user.get_admin_url()}">{user}</a>'
simo/users/api.py CHANGED
@@ -1,4 +1,5 @@
1
1
  import sys
2
+ import pytz
2
3
  import datetime
3
4
  from django.db.models import Q
4
5
  from rest_framework import viewsets, mixins, status
@@ -6,11 +7,11 @@ from rest_framework.serializers import Serializer
6
7
  from rest_framework.decorators import action
7
8
  from rest_framework.response import Response as RESTResponse
8
9
  from rest_framework.exceptions import ValidationError, PermissionDenied
9
- from django.contrib.gis.geos import Point
10
10
  from django.utils import timezone
11
11
  from django_filters.rest_framework import DjangoFilterBackend
12
12
  from simo.conf import dynamic_settings
13
13
  from simo.core.api import InstanceMixin
14
+ from simo.users.utils import get_smoothed_location
14
15
  from .models import (
15
16
  User, UserDevice, UserDeviceReportLog, PermissionsRole, InstanceInvitation,
16
17
  Fingerprint, ComponentPermission, InstanceUser
@@ -192,24 +193,22 @@ class UserDeviceReport(InstanceMixin, viewsets.GenericViewSet):
192
193
  )
193
194
  user_device.users.add(request.user)
194
195
 
195
- try:
196
- location = Point(
197
- *[float(c) for c in request.data.get('location').split(',')],
198
- srid=4326
199
- )
200
- except:
201
- location = None
202
-
203
196
  relay = None
204
197
  if request.META.get('HTTP_HOST', '').endswith('.simo.io'):
205
198
  relay = request.META.get('HTTP_HOST')
206
199
 
207
- last_seen_location = None
200
+
201
+ if relay:
202
+ location = request.data.get('location')
203
+ if location:
204
+ location_smoothed = get_smoothed_location(user_device, location)
205
+ else:
206
+ location_smoothed = None
207
+ else:
208
+ location = self.instance.location
209
+ location_smoothed = location
210
+
208
211
  user_device.last_seen = timezone.now()
209
- if location:
210
- last_seen_location = ','.join(
211
- [str(i) for i in location]
212
- ) if location else None
213
212
 
214
213
  if request.data.get('app_open', False):
215
214
  user_device.is_primary = True
@@ -226,31 +225,36 @@ class UserDeviceReport(InstanceMixin, viewsets.GenericViewSet):
226
225
  at_home = False
227
226
  if not relay:
228
227
  at_home = True
229
- elif last_seen_location:
228
+ elif location_smoothed:
230
229
  at_home = haversine_distance(
231
- self.instance.location, last_seen_location
230
+ self.instance.location, location_smoothed
232
231
  ) < dynamic_settings['users__at_home_radius']
233
232
 
234
-
235
233
  for iu in request.user.instance_roles.filter(is_active=True):
236
234
  if not relay:
237
235
  iu.at_home = True
238
- elif location:
236
+ elif location_smoothed:
239
237
  iu.at_home = haversine_distance(
240
- iu.instance.location, last_seen_location
238
+ iu.instance.location, location_smoothed
241
239
  ) < dynamic_settings['users__at_home_radius']
242
240
 
243
-
244
241
  iu.last_seen = user_device.last_seen
245
- iu.last_seen_location = last_seen_location
242
+ iu.last_seen_location = location_smoothed
246
243
  iu.last_seen_speed_kmh = speed_kmh
247
244
  iu.phone_on_charge = phone_on_charge
248
245
  iu.save()
249
246
 
247
+ log_datetime = timezone.now()
248
+ if request.data.get('timestamp'):
249
+ log_datetime = datetime.datetime.utcfromtimestamp(
250
+ int(float(request.GET['start_from']))
251
+ ).replace(tzinfo=pytz.utc)
252
+
250
253
  UserDeviceReportLog.objects.create(
251
254
  user_device=user_device, instance=self.instance,
252
255
  app_open=request.data.get('app_open', False),
253
- location=last_seen_location,
256
+ location=location, location_smoothed=location_smoothed,
257
+ datetime=log_datetime,
254
258
  relay=relay, speed_kmh=speed_kmh,
255
259
  phone_on_charge=phone_on_charge, at_home=at_home
256
260
  )
@@ -0,0 +1,29 @@
1
+ # Generated by Django 4.2.10 on 2024-11-18 09:32
2
+
3
+ from django.db import migrations, models
4
+ import location_field.models.plain
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ dependencies = [
10
+ ('users', '0039_auto_20241117_1039'),
11
+ ]
12
+
13
+ operations = [
14
+ migrations.AddField(
15
+ model_name='userdevicereportlog',
16
+ name='location_smoothed',
17
+ field=location_field.models.plain.PlainLocationField(blank=True, max_length=63, null=True),
18
+ ),
19
+ migrations.AlterField(
20
+ model_name='userdevicereportlog',
21
+ name='at_home',
22
+ field=models.BooleanField(default=True),
23
+ ),
24
+ migrations.AlterField(
25
+ model_name='userdevicereportlog',
26
+ name='datetime',
27
+ field=models.DateTimeField(auto_now_add=True, db_index=True),
28
+ ),
29
+ ]
simo/users/models.py CHANGED
@@ -438,7 +438,7 @@ class UserDeviceReportLog(models.Model):
438
438
  instance = models.ForeignKey(
439
439
  'core.Instance', null=True, on_delete=models.CASCADE
440
440
  )
441
- datetime = models.DateTimeField(auto_now_add=True)
441
+ datetime = models.DateTimeField(auto_now_add=True, db_index=True)
442
442
  app_open = models.BooleanField(
443
443
  default=False, help_text="Sent while using app or by background process."
444
444
  )
@@ -447,6 +447,7 @@ class UserDeviceReportLog(models.Model):
447
447
  help_text="Sent via remote relay if specified, otherwise it's from LAN."
448
448
  )
449
449
  location = PlainLocationField(zoom=7, null=True, blank=True)
450
+ location_smoothed = PlainLocationField(zoom=7, null=True, blank=True)
450
451
  speed_kmh = models.FloatField(default=0)
451
452
  phone_on_charge = models.BooleanField(default=False, db_index=True)
452
453
  at_home = models.BooleanField(default=True)
simo/users/tasks.py CHANGED
@@ -8,10 +8,12 @@ def clear_device_report_logs():
8
8
  from simo.core.models import Instance
9
9
  from .models import UserDeviceReportLog
10
10
  for instance in Instance.objects.all():
11
+ # keeping at least 1 hour of logs so that we could evaluate
12
+ # user's current location using Kalman filter
11
13
  UserDeviceReportLog.objects.filter(
12
14
  instance=instance,
13
15
  datetime__lt=timezone.now() - datetime.timedelta(
14
- days=instance.device_report_history_days
16
+ days=instance.device_report_history_days, hours=1
15
17
  )
16
18
  ).delete()
17
19
 
simo/users/utils.py CHANGED
@@ -1,6 +1,10 @@
1
1
  import sys
2
2
  import traceback
3
3
  import subprocess
4
+ import datetime
5
+ import numpy as np
6
+ from django.core.cache import cache
7
+ from django.utils import timezone
4
8
  from django.template.loader import render_to_string
5
9
 
6
10
 
@@ -58,4 +62,103 @@ def update_mqtt_acls():
58
62
  )
59
63
  subprocess.run(
60
64
  ['service', 'mosquitto', 'reload'], stdout=subprocess.PIPE
61
- )
65
+ )
66
+
67
+
68
+ class KalmanFilter:
69
+ def __init__(self, process_variance, measurement_variance, x=None, P=None):
70
+ self.x = x if x is not None else np.array([[0], [0], [0], [0]]) # State
71
+ self.P = P if P is not None else np.eye(4) # State covariance
72
+ self.process_variance = process_variance
73
+ self.measurement_variance = measurement_variance
74
+
75
+ def predict(self, delta_t):
76
+ F = np.array([
77
+ [1, 0, delta_t, 0],
78
+ [0, 1, 0, delta_t],
79
+ [0, 0, 1, 0],
80
+ [0, 0, 0, 1]
81
+ ])
82
+ Q = self.process_variance * np.array([
83
+ [delta_t ** 4 / 4, 0, delta_t ** 3 / 2, 0],
84
+ [0, delta_t ** 4 / 4, 0, delta_t ** 3 / 2],
85
+ [delta_t ** 3 / 2, 0, delta_t ** 2, 0],
86
+ [0, delta_t ** 3 / 2, 0, delta_t ** 2]
87
+ ])
88
+ self.x = np.dot(F, self.x)
89
+ self.P = np.dot(np.dot(F, self.P), F.T) + Q
90
+
91
+ def update(self, z):
92
+ H = np.array([
93
+ [1, 0, 0, 0],
94
+ [0, 1, 0, 0]
95
+ ])
96
+ R = self.measurement_variance * np.eye(2)
97
+ y = z - np.dot(H, self.x) # Innovation
98
+ S = np.dot(H, np.dot(self.P, H.T)) + R # Innovation covariance
99
+ K = np.dot(np.dot(self.P, H.T), np.linalg.inv(S)) # Kalman Gain
100
+ self.x = self.x + np.dot(K, y)
101
+ I = np.eye(self.P.shape[0])
102
+ self.P = np.dot(I - np.dot(K, H), self.P)
103
+
104
+ def get_state(self):
105
+ return self.x[:2].flatten() # Latitude and Longitude
106
+
107
+
108
+ def get_smoothed_location(user_device, new_location):
109
+ try:
110
+ new_lat, new_lon = map(float, new_location.split(','))
111
+ except ValueError:
112
+ raise ValueError("Invalid new location format. Expected 'lat,lon'.")
113
+
114
+ cache_key = f"kalman_state_{user_device.id}"
115
+ cached_data = cache.get(cache_key)
116
+
117
+ if cached_data:
118
+ kf = KalmanFilter(
119
+ process_variance=1,
120
+ measurement_variance=10,
121
+ x=np.array(cached_data['x']),
122
+ P=np.array(cached_data['P'])
123
+ )
124
+ last_processed_time = cached_data['last_processed_time']
125
+ else:
126
+ kf = KalmanFilter(process_variance=1, measurement_variance=10)
127
+ last_processed_time = None
128
+
129
+ last_log = None
130
+ logs_query = user_device.report_logs.filter(
131
+ location__isnull=False,
132
+ datetime__gt=last_processed_time or timezone.now() - datetime.timedelta(minutes=20)
133
+ ).order_by('datetime')
134
+
135
+ for log in logs_query:
136
+ try:
137
+ lat, lon = map(float, log.location.split(','))
138
+ except ValueError:
139
+ continue # Skip invalid data
140
+
141
+ if last_log and log.location == last_log.location:
142
+ continue # Skip duplicate locations
143
+
144
+ if last_log:
145
+ delta_t = (log.datetime - last_log.datetime).total_seconds()
146
+ kf.predict(max(delta_t, 0)) # Prevent negative delta_t
147
+
148
+ kf.update(np.array([[lat], [lon]]))
149
+ last_log = log
150
+
151
+ if last_log:
152
+ delta_t = (timezone.now() - last_log.datetime).total_seconds()
153
+ kf.predict(max(delta_t, 0))
154
+
155
+ kf.update(np.array([[new_lat], [new_lon]]))
156
+
157
+ # Cache the updated filter state and last processed log time
158
+ cache.set(cache_key, {
159
+ 'x': kf.x.tolist(), # Convert to list for JSON serialization
160
+ 'P': kf.P.tolist(), # Convert to list for JSON serialization
161
+ 'last_processed_time': timezone.now()
162
+ }, timeout=3600) # Cache for 1 hour
163
+
164
+ return ','.join(f"{coord:.6f}" for coord in kf.get_state())
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: simo
3
- Version: 2.5.30
3
+ Version: 2.5.31
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
@@ -32,7 +32,7 @@ simo/backups/migrations/__pycache__/0003_alter_backuplog_options_alter_backup_si
32
32
  simo/backups/migrations/__pycache__/0004_alter_backup_options_alter_backuplog_options_and_more.cpython-38.pyc,sha256=f4leTBkOB2kraQYyCkz9B_2pb6aKn7srV2unQA5HV1Q,710
33
33
  simo/backups/migrations/__pycache__/__init__.cpython-38.pyc,sha256=Lz1fs6V05h2AoxTOLNye0do9bEMnyuaXB_hHOjG5-HU,172
34
34
  simo/core/__init__.py,sha256=_s2TjJfQImsMrTIxqLAx9AZie1Ojmm6sCHASdl3WLGU,50
35
- simo/core/admin.py,sha256=5mi0Qe9TM-OILCxbcRfGJvq397QCPxjYX30K3CIZrPs,18133
35
+ simo/core/admin.py,sha256=T-NjMq1OxtCt-LjWL-YuuGtAi4JcvtjWhcGrLb8G5D4,18434
36
36
  simo/core/api.py,sha256=id8RCZ7r9IKqpcV6ajqF87k9yKqcFmSc8YfWIP99tQE,28276
37
37
  simo/core/api_auth.py,sha256=vCxvczA8aWNcW0VyKs5WlC_ytlqeGP_H_hkKUNVkCwM,1247
38
38
  simo/core/api_meta.py,sha256=EaiY-dCADP__9MvLpoHvhjytFT92IrxPZDv95xgqasU,4955
@@ -52,7 +52,7 @@ simo/core/gateways.py,sha256=m0eS3XjVe34Dge6xtoCq16kFWCKJcdQrT0JW0REqoq8,3715
52
52
  simo/core/loggers.py,sha256=EBdq23gTQScVfQVH-xeP90-wII2DQFDjoROAW6ggUP4,1645
53
53
  simo/core/managers.py,sha256=n-b3I4uXzfHKTeB1VMjSaMsDUxp8FegFJwnbV1IsWQ4,3019
54
54
  simo/core/middleware.py,sha256=eUFf6iP-Snx_0TE3MoXsSwqrd5IjlukqZk2GQGStRCo,3385
55
- simo/core/models.py,sha256=DXJXTtNdpn9yC4VArHp0yrhpRb7vGpwH2c-JvqLE56M,22784
55
+ simo/core/models.py,sha256=1eJRIxOYtzx7JOGSAyo75QBoTEaigxjsRXIFghDSz9k,22837
56
56
  simo/core/permissions.py,sha256=INQPrUAIM3WXCvd7e6cmYytKaak8fMEn7VooX-fIds0,3002
57
57
  simo/core/routing.py,sha256=X1_IHxyA-_Q7hw1udDoviVP4_FSBDl8GYETTC2zWTbY,499
58
58
  simo/core/serializers.py,sha256=WgksN1Ombv240nfQR_UtmKslTWM9vz9Y0yTdN5usiHU,21892
@@ -65,7 +65,7 @@ simo/core/types.py,sha256=WJEq48mIbFi_5Alt4wxWMGXxNxUTXqfQU5koH7wqHHI,1108
65
65
  simo/core/views.py,sha256=3SRZr00fyLQf8ja3U-9eekKt-ld5TvU1WQqUWprXfQ4,2390
66
66
  simo/core/widgets.py,sha256=J9e06C6I22F6xKic3VMgG7WeX07glAcl-4bF2Mg180A,2827
67
67
  simo/core/__pycache__/__init__.cpython-38.pyc,sha256=ZJFM_XN0RmJMULQulgA_wFiOnEtsMoedcOWnXjH-Y8o,208
68
- simo/core/__pycache__/admin.cpython-38.pyc,sha256=uM58xiBAbbstmKZ0CoxLSk_RRu5TDVuHJNOveXUhFqM,13723
68
+ simo/core/__pycache__/admin.cpython-38.pyc,sha256=mXU-u9Bl5RCa0K4Y9wJAkYns33XCByME3sb_FsMC2DI,14090
69
69
  simo/core/__pycache__/api.cpython-38.pyc,sha256=z5IT2AoH_Y7iXJELeGlgaxJ2RNe-Wwk8YIt_rJcOGMw,21851
70
70
  simo/core/__pycache__/api_auth.cpython-38.pyc,sha256=mi3mu5qEKio_PvfQEvr3Q6AhdPLAHxzxAxrMbAz_pKU,1712
71
71
  simo/core/__pycache__/api_meta.cpython-38.pyc,sha256=VYx5ZeDyNBI4B_CBEIhV5B3GnLsMOx9s3rNZTSMODco,3703
@@ -85,7 +85,7 @@ simo/core/__pycache__/gateways.cpython-38.pyc,sha256=D1ooHL-iSpQrxnD8uAl4xWFJmm-
85
85
  simo/core/__pycache__/loggers.cpython-38.pyc,sha256=Z-cdQnC6XlIonPV4Sl4E52tP4NMEdPAiHK0cFaIL7I8,1623
86
86
  simo/core/__pycache__/managers.cpython-38.pyc,sha256=6RTIxyjOgpQGtAqcUyE2vFPS09w1V5Wmd_vOV7rHRRI,3370
87
87
  simo/core/__pycache__/middleware.cpython-38.pyc,sha256=g3d4L2PwxFyRKIPMP9Hkdjk1PL9NarQd4hSHS55I8n8,2649
88
- simo/core/__pycache__/models.cpython-38.pyc,sha256=HGL3TiaMy-0oobgCGGxH2nvDhh4RnOBeD00rwQsuklU,18591
88
+ simo/core/__pycache__/models.cpython-38.pyc,sha256=m2KdO5zWXwHT3pIDyGW9SQgnesSAGPPcttOXoKCArzM,18610
89
89
  simo/core/__pycache__/permissions.cpython-38.pyc,sha256=bO13B8uGCv4AiWNBoU2Gxt3mNuQ3gCxb7eKFihwFb5o,2974
90
90
  simo/core/__pycache__/routing.cpython-38.pyc,sha256=3T3FPJ8Cn99xZCGvMyg2xjl7al-Shm9CelbSpkJtNP8,599
91
91
  simo/core/__pycache__/serializers.cpython-38.pyc,sha256=d4wpUjFuo8GxaNWbin9GdHKik06IZN32uZL1SnXiL_s,19616
@@ -207,6 +207,7 @@ simo/core/migrations/0041_alter_instance_slug.py,sha256=MmuW0n5CCpF7NyKYINDcqChO
207
207
  simo/core/migrations/0042_alter_instance_timezone.py,sha256=5fLprOmoV06y37rh3uJWq8Fy4SjNmu86Imky6t7j13g,23334
208
208
  simo/core/migrations/0043_alter_category_instance_alter_instance_timezone_and_more.py,sha256=TpJRZjwJu1iDYt1cCQr62jLOz14yy7yjrfbyHYgu9pM,23896
209
209
  simo/core/migrations/0044_alter_gateway_type.py,sha256=xoQvOSz_JrWHECAAII83518tmhEEfLUHqxpIjoo4oLY,758
210
+ simo/core/migrations/0045_alter_instance_device_report_history_days_and_more.py,sha256=saR7gEbTDfXTcKnT1R193Aboqlg32HvDRI_JAvZ97TY,1360
210
211
  simo/core/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
211
212
  simo/core/migrations/__pycache__/0001_initial.cpython-38.pyc,sha256=w6GiBXVxWj30Bg4Sn_pFVeA041d-pCrkaq8mR3KuF70,5381
212
213
  simo/core/migrations/__pycache__/0002_load_icons.cpython-38.pyc,sha256=Nb9RrPjVYo_RpZ5PmzoaEIWGCeVt4kicpmGiKlBrpIw,2123
@@ -252,6 +253,7 @@ simo/core/migrations/__pycache__/0041_alter_instance_slug.cpython-38.pyc,sha256=
252
253
  simo/core/migrations/__pycache__/0042_alter_instance_timezone.cpython-38.pyc,sha256=_IdEOLCC0xgeVcKfCk7x0kKxVPu0iWuCxCg8aqkzeSI,16392
253
254
  simo/core/migrations/__pycache__/0043_alter_category_instance_alter_instance_timezone_and_more.cpython-38.pyc,sha256=97_eFPQ_Iy-M1712Nad1w8t3ITBV4BF0eY1e8N-oOE4,16759
254
255
  simo/core/migrations/__pycache__/0044_alter_gateway_type.cpython-38.pyc,sha256=KwqGKoo8C1wH9ZUhagpC3plTwheSMcWtwlCGcSPd5WQ,964
256
+ simo/core/migrations/__pycache__/0045_alter_instance_device_report_history_days_and_more.cpython-38.pyc,sha256=e1_hKjdokggnPndFkoBWrHQ18IkBi2W7WahcSjAbvKo,1278
255
257
  simo/core/migrations/__pycache__/__init__.cpython-38.pyc,sha256=VZmDQ57BTcebuM0KMhjiTOabgWZCBxQmSJzWZos9SO8,169
256
258
  simo/core/static/ansi_styles.css,sha256=4ieJGrjZPKyPSago9FdB_gflHoGE1vxCHi8qVn5tY-Y,37352
257
259
  simo/core/static/admin/Img/plus.svg,sha256=2NpSFPWqGIjpAQGFI7LDQHPKagEhYkJiJX95ufCoZaI,741
@@ -10444,38 +10446,38 @@ simo/notifications/migrations/__pycache__/0002_notification_instance.cpython-38.
10444
10446
  simo/notifications/migrations/__pycache__/0003_alter_notification_instance.cpython-38.pyc,sha256=awhD1F9RyK_706zVNM5io3WT_konFkKQgL7D5MkONwk,851
10445
10447
  simo/notifications/migrations/__pycache__/__init__.cpython-38.pyc,sha256=YMBRHVon2nWDtIUbghckjnC12sIg_ykPWhV5aM0tto4,178
10446
10448
  simo/users/__init__.py,sha256=6a7uBpCWB_DR7p54rbHusc0xvi1qfT1ZCCQGb6TiBh8,52
10447
- simo/users/admin.py,sha256=2Ay47b1NWBfdUpMZ8qUFAbwiu7-P3JNxNpM7FKVwrLw,6987
10448
- simo/users/api.py,sha256=pWmP201jyz7_KCC_c8Fsrnh1R0HoCkcHfwu4LYCIRfg,12373
10449
+ simo/users/admin.py,sha256=SWKSb4giF69By3JYXuPMEd71Z_aCyin4_9NtIRJOY6I,7237
10450
+ simo/users/api.py,sha256=03QFEOnIlACetS4QsPPBdagsC15_ap0-I64lCnplitI,12651
10449
10451
  simo/users/apps.py,sha256=cq0A8-U1HALEwev0TicgFhr4CAu7Icz8rwq0HfOaL4E,207
10450
10452
  simo/users/auth_backends.py,sha256=KIw2AdjCUKfm_7Lql6aC4qdE6JznP0ECIMA5MVMLeiM,4251
10451
10453
  simo/users/auto_urls.py,sha256=lcJvteBsbHQMJieZpDz-63tDYejLApqsW3CUnDakd7k,272
10452
10454
  simo/users/dynamic_settings.py,sha256=sEIsi4yJw3kH46Jq_aOkSuK7QTfQACGUE-lkyBogCaM,570
10453
10455
  simo/users/managers.py,sha256=OHgEP85MBtdkdYxdstBd8RavTBT8F_2WyDxUJ9aCqqM,246
10454
10456
  simo/users/middleware.py,sha256=GMCrnWSc_2qCleyQIkfQGdL-pU-UTEcSg1wPvIKZ9uk,1210
10455
- simo/users/models.py,sha256=EeIiEPJMS5wibbdYOvNzxTT1eGihFYERlNwERli5Uo8,19671
10457
+ simo/users/models.py,sha256=2NAWk3n_bg7JLHh5y8_dl2-7VZ97HTvhu61kNZYdaJg,19760
10456
10458
  simo/users/permissions.py,sha256=IwtYS8yQdupWbYKR9VimSRDV3qCJ2jXP57Lyjpb2EQM,242
10457
10459
  simo/users/serializers.py,sha256=zzw1KONTnaTNBaU0r4rNVxJ827KzD6Z5LuQt27ZsQ98,2516
10458
10460
  simo/users/sso_urls.py,sha256=gQOaPvGMYFD0NCVSwyoWO-mTEHe5j9sbzV_RK7kdvp0,251
10459
10461
  simo/users/sso_views.py,sha256=5J0D4qUFQDvd-Fcqx_xLJWLJgPdqtVD5DDiPJyPsT2Q,4336
10460
- simo/users/tasks.py,sha256=HJAqiyWGsaN3wSfquU0UyQ20jL-njXeaaTOdDT3TQ3s,979
10461
- simo/users/utils.py,sha256=1HGSZyHRqQvdJ4RtAiZDg1juvgG8aOlrGXR7CcvsyQc,1886
10462
+ simo/users/tasks.py,sha256=3oYo7xv6XIbrl9qY3JuCYJMncRDDXAmfoV59oYIHyXo,1110
10463
+ simo/users/utils.py,sha256=E9t6qljw3GARNMlH5qW7A_Ly1GaSNeAJcacxkQBJZyg,5457
10462
10464
  simo/users/views.py,sha256=dOQVvmlHG7ihWKJLFUBcqKOA0UDctlMKR0pTc36JZqg,3487
10463
10465
  simo/users/__pycache__/__init__.cpython-38.pyc,sha256=VFoDJE_SKKaPqqYaaBYd1Ndb1hjakkTo_u0EG_XJ1GM,211
10464
- simo/users/__pycache__/admin.cpython-38.pyc,sha256=-TXB5LGoAvCfgNhsqaB-1pgyyfW8lbuGcgD8F4HZojk,7947
10465
- simo/users/__pycache__/api.cpython-38.pyc,sha256=4JQ61L-i0GXtnqlGgZfoHHQX9bd8xJc9kkEmvjHC_-Q,10172
10466
+ simo/users/__pycache__/admin.cpython-38.pyc,sha256=1HKy4UfOLaokGkk3s-eJgMEpa4snLEL7qA-b0vT3Rrc,8237
10467
+ simo/users/__pycache__/api.cpython-38.pyc,sha256=hO7qFF6oG-llh6Pm4y_3IU1KHgwWXBy_uqKdOKAK7Gc,10049
10466
10468
  simo/users/__pycache__/apps.cpython-38.pyc,sha256=dgbWL8CxzzISJQTmq_4IztPJ2UzykNVdqA2Ae1PmeGk,605
10467
10469
  simo/users/__pycache__/auth_backends.cpython-38.pyc,sha256=jYS2hlbTZh_ZtPeWcN50pc0IpyfCSO7_MvIbuVwEp8M,3144
10468
10470
  simo/users/__pycache__/auto_urls.cpython-38.pyc,sha256=K-3sz2h-cEitoflSmZk1t0eUg5mQMMGLNZFREVwG7_o,430
10469
10471
  simo/users/__pycache__/dynamic_settings.cpython-38.pyc,sha256=6F8JBjZkHykySnmZjNEzjS0ijbmPdcp9yUAZ5kqq_Fo,864
10470
10472
  simo/users/__pycache__/managers.cpython-38.pyc,sha256=O0Y8ABp42RAosrbODmYsPMaj9AyOPyJ-aqzuO0Qpi2s,679
10471
10473
  simo/users/__pycache__/middleware.cpython-38.pyc,sha256=Tj4nVEAvxEW3xA63fBRiJWRJpz_M848ZOqbHioc_IPE,1149
10472
- simo/users/__pycache__/models.cpython-38.pyc,sha256=aFOdFfAu0XLxg4BOSE_Kj3e-Qr3b8dMH8mWiyM9Egew,17672
10474
+ simo/users/__pycache__/models.cpython-38.pyc,sha256=fPZcTqgvuEokKa_TQyRA2eOZNwK_JYTvzTp1tyF3RwE,17716
10473
10475
  simo/users/__pycache__/permissions.cpython-38.pyc,sha256=ez5NxoL_JUeeH6GsKhvFreuA3FCBgGf9floSypdXUtM,633
10474
10476
  simo/users/__pycache__/serializers.cpython-38.pyc,sha256=Dy8RAcwNkNSXoJHvLp8fozURyHCtucqpSPyqZtbnMZc,3732
10475
10477
  simo/users/__pycache__/sso_urls.cpython-38.pyc,sha256=uAwDozpOmrhUald-8tOHANILXkH7-TI8fNYXOtPkSY8,402
10476
10478
  simo/users/__pycache__/sso_views.cpython-38.pyc,sha256=PLRF6FYCxRhnmgnN_gUS-pdQlH7lofLU1Xhgw3vDO_Y,4019
10477
10479
  simo/users/__pycache__/tasks.cpython-38.pyc,sha256=XLMKt3suT7BlcXrJZoH9ZIhhtBuqyiW4lsOB9IbBkko,1225
10478
- simo/users/__pycache__/utils.cpython-38.pyc,sha256=S-2i_Y11eTcUpmn-RkX1B4_tDvZJdqkZqqoewpkcsUM,1739
10480
+ simo/users/__pycache__/utils.cpython-38.pyc,sha256=9qaNWqNX-5cFMJd9m6qtN-LyiLsVB97ea8y4E9Pr3DM,4701
10479
10481
  simo/users/__pycache__/views.cpython-38.pyc,sha256=AXuUnVYRD0ai4FSFDp4qJwryukujAoN6LD3oIj-Cv3o,2426
10480
10482
  simo/users/migrations/0001_initial.py,sha256=_SnJemhNOs8Jjj-PjyvTVCBoxfs5V3lR_4ypUHUdLUg,7017
10481
10483
  simo/users/migrations/0002_componentpermission.py,sha256=rH9pC9HERf_5WWn3LCsNiu03BiHqURTF62pSNfswStI,918
@@ -10516,6 +10518,7 @@ simo/users/migrations/0036_instanceuser_phone_on_charge_user_phone_on_charge.py,
10516
10518
  simo/users/migrations/0037_rename_last_seen_location_datetime_instanceuser_last_seen_and_more.py,sha256=8f_7fC7k0Dm-0gGVHMI9_490BbEC_SfuAPrAADrZ7BA,1543
10517
10519
  simo/users/migrations/0038_userdevicereportlog_at_home_and_more.py,sha256=qL1ZjUJDiSrJat59ToqpNBwnMMPb3Q5mwHq-qd1eFcI,691
10518
10520
  simo/users/migrations/0039_auto_20241117_1039.py,sha256=e64AJM2ZId516Px-gmAxkp2NmSC5Vjo_BBTGbYrFuKY,1310
10521
+ simo/users/migrations/0040_userdevicereportlog_location_smoothed_and_more.py,sha256=umGDjQGpCrQocNxaCvwdTsOmSxcRC61NcUdHGUczoc4,855
10519
10522
  simo/users/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10520
10523
  simo/users/migrations/__pycache__/0001_initial.cpython-38.pyc,sha256=e4XOKaYRb7l0P7cBnHHi5FQQJMlwjK0g7iqgM-xKmNI,4215
10521
10524
  simo/users/migrations/__pycache__/0002_componentpermission.cpython-38.pyc,sha256=pknJnpic8p6Vdx9DX41FfODXNnvexDswJtUCmC5w1tg,995
@@ -10558,6 +10561,7 @@ simo/users/migrations/__pycache__/0036_instanceuser_phone_on_charge_user_phone_o
10558
10561
  simo/users/migrations/__pycache__/0037_rename_last_seen_location_datetime_instanceuser_last_seen_and_more.cpython-38.pyc,sha256=W4Rc6SRNNJ_yAYrEyoklD5LFmQPAL9mTDrXHMfP8R4I,1162
10559
10562
  simo/users/migrations/__pycache__/0038_userdevicereportlog_at_home_and_more.cpython-38.pyc,sha256=khDSeTH3-jJ4yO1D6-i3Pm_NekJkVwBSUJ-rAxu0cr4,836
10560
10563
  simo/users/migrations/__pycache__/0039_auto_20241117_1039.cpython-38.pyc,sha256=IEOIfvnUiV-GX9VI4W2UKJugC3nfwKfy7QdmS3pW6Ss,1377
10564
+ simo/users/migrations/__pycache__/0040_userdevicereportlog_location_smoothed_and_more.cpython-38.pyc,sha256=I0W00mt73gjKunnwAyf3kz1FKfgm2HRwCJY_ogATSS0,918
10561
10565
  simo/users/migrations/__pycache__/__init__.cpython-38.pyc,sha256=NKq7WLgktK8WV1oOqCPbAbdkrPV5GRGhYx4VxxI4dcs,170
10562
10566
  simo/users/templates/conf/mosquitto.conf,sha256=1eIGNuRu4Y3hfAU6qiWix648eCRrw0oOT24PnyFI4ys,189
10563
10567
  simo/users/templates/conf/mosquitto_acls.conf,sha256=ga44caTDNQE0CBKw55iM2jOuna6-9fKGwAhjyERZdRE,500
@@ -10567,9 +10571,9 @@ simo/users/templates/invitations/expired_msg.html,sha256=47DEQpj8HBSa-_TImW-5JCe
10567
10571
  simo/users/templates/invitations/expired_suggestion.html,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10568
10572
  simo/users/templates/invitations/taken_msg.html,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10569
10573
  simo/users/templates/invitations/taken_suggestion.html,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10570
- simo-2.5.30.dist-info/LICENSE.md,sha256=M7wm1EmMGDtwPRdg7kW4d00h1uAXjKOT3HFScYQMeiE,34916
10571
- simo-2.5.30.dist-info/METADATA,sha256=rnYLYWnVpPGB86Gmr15dmR9wmv--Q42fKSUK_3Uu6iI,1953
10572
- simo-2.5.30.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
10573
- simo-2.5.30.dist-info/entry_points.txt,sha256=S9PwnUYmTSW7681GKDCxUbL0leRJIaRk6fDQIKgbZBA,135
10574
- simo-2.5.30.dist-info/top_level.txt,sha256=GmS1hrAbpVqn9OWZh6UX82eIOdRLgYA82RG9fe8v4Rs,5
10575
- simo-2.5.30.dist-info/RECORD,,
10574
+ simo-2.5.31.dist-info/LICENSE.md,sha256=M7wm1EmMGDtwPRdg7kW4d00h1uAXjKOT3HFScYQMeiE,34916
10575
+ simo-2.5.31.dist-info/METADATA,sha256=nFDDYX4oUK_5gtNMScQSzfboiiS7didaCErf8noKlDM,1953
10576
+ simo-2.5.31.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
10577
+ simo-2.5.31.dist-info/entry_points.txt,sha256=S9PwnUYmTSW7681GKDCxUbL0leRJIaRk6fDQIKgbZBA,135
10578
+ simo-2.5.31.dist-info/top_level.txt,sha256=GmS1hrAbpVqn9OWZh6UX82eIOdRLgYA82RG9fe8v4Rs,5
10579
+ simo-2.5.31.dist-info/RECORD,,
File without changes