espark-core 0.4.9__py3-none-any.whl → 0.5.0__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 espark-core might be problematic. Click here for more details.

@@ -1,24 +1,24 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: espark-core
3
- Version: 0.4.9
3
+ Version: 0.5.0
4
4
  Summary: The core module of the Espark ESP32-based IoT device management framework.
5
5
  License: MIT
6
6
  Requires-Python: >=3.8
7
7
  Description-Content-Type: text/markdown
8
8
  License-File: LICENSE
9
9
  Requires-Dist: aiomqtt>=2.4.0
10
- Requires-Dist: apscheduler>=3.11.1
11
- Requires-Dist: fastapi>=0.124.4
10
+ Requires-Dist: apscheduler>=3.11.2
11
+ Requires-Dist: fastapi>=0.127.0
12
12
  Requires-Dist: packaging>=25.0
13
13
  Requires-Dist: slack-sdk==3.39.0
14
14
  Requires-Dist: sqlalchemy>=2.0.45
15
- Requires-Dist: sqlmodel>=0.0.27
16
- Requires-Dist: uvicorn[standard]>=0.38.0
15
+ Requires-Dist: sqlmodel>=0.0.29
16
+ Requires-Dist: uvicorn[standard]>=0.40.0
17
17
  Provides-Extra: dev
18
- Requires-Dist: aiosqlite==0.22.0; extra == "dev"
18
+ Requires-Dist: aiosqlite==0.22.1; extra == "dev"
19
19
  Requires-Dist: autopep8==2.3.2; extra == "dev"
20
20
  Requires-Dist: build==1.3.0; extra == "dev"
21
- Requires-Dist: fastapi-cli[standard-no-fastapi-cloud-cli]==0.0.16; extra == "dev"
21
+ Requires-Dist: fastapi-cli[standard-no-fastapi-cloud-cli]==0.0.20; extra == "dev"
22
22
  Requires-Dist: httpx==0.28.1; extra == "dev"
23
23
  Requires-Dist: pycodestyle==2.14.0; extra == "dev"
24
24
  Requires-Dist: pylint==4.0.4; extra == "dev"
@@ -1,35 +1,41 @@
1
- espark_core-0.4.9.dist-info/licenses/LICENSE,sha256=wkIXJHYIspOGVvn_ajKyLucpIyw9_pO3QExFcNTCY78,1065
1
+ espark_core-0.5.0.dist-info/licenses/LICENSE,sha256=wkIXJHYIspOGVvn_ajKyLucpIyw9_pO3QExFcNTCY78,1065
2
2
  esparkcore/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- esparkcore/constants.py,sha256=v5D_z1nST50FpA5FvpGYz9rk6wngQleCyFU1wtmk58I,417
3
+ esparkcore/constants.py,sha256=2Bmx62j4kykkPhcQ9ygEJ08uyVlykXMNFhpO26-Czlg,417
4
4
  esparkcore/data/__init__.py,sha256=abMOPq6jC5qOQCl9j7iPDv8iyjzDF-oVd_gW0la5kL4,45
5
5
  esparkcore/data/database.py,sha256=OaJSBDW6Qge-rsKkSIksJXOg3xF_y6LuBew--6uFbG4,532
6
- esparkcore/data/models/__init__.py,sha256=9YBRwn7R_aovQjT-ZR64fBm7TPs0dsjoq3LzsUTaRtA,124
6
+ esparkcore/data/models/__init__.py,sha256=hJwtKxw02gDB6jot_l8I47AeU2UwT9plrd5haIGB3fw,192
7
7
  esparkcore/data/models/device.py,sha256=a1Tfw8xHzDLEiVxANZ-lR2aVaWaWJFa8tZQwHBzDgu0,1135
8
+ esparkcore/data/models/notification.py,sha256=NhkTZbNsY1ljTmVJNrDC68W0Jd2BcACxfWomrgkO4zM,621
8
9
  esparkcore/data/models/outbox.py,sha256=r5bgjIbj9H-OsGhE-CWKGRf4Tp88qMvmxPYHNGOApY4,1309
9
10
  esparkcore/data/models/telemetry.py,sha256=OfzGqGj2Y1sdK519sSOEXhfuErHoeEdpy5qmJMIc27A,652
11
+ esparkcore/data/models/trigger.py,sha256=3gApG5RHtwIKL_CX_Fx8oqCaLfgYveqzHy5mSmFpWxM,986
10
12
  esparkcore/data/models/version.py,sha256=h2O_Owx-exx5tiQiiauCSELkUHDx-QZjiIGqFPybT4M,269
11
- esparkcore/data/repositories/__init__.py,sha256=R4cFUr137IxhyQVwFaXRHikPkTBE_4nUAAFRchxcohA,248
13
+ esparkcore/data/repositories/__init__.py,sha256=zlTYJ_KKBqkY7KpIi1kJlCG5UA6zXlLm5wK9INLpZQY,358
12
14
  esparkcore/data/repositories/base_repository.py,sha256=L5APTp8SczndbWqgJyEqKe-4NQoL_YYOapUN6i572cM,2097
13
15
  esparkcore/data/repositories/device_repository.py,sha256=P9KK-Ta9MgMdrnct5hbGEoipPOPrrDQ4wINQnZZ6iZY,1131
16
+ esparkcore/data/repositories/notification_repository.py,sha256=N-bcprhnG6noTdhIzLMBJtCmGeMtnZw2bPx8JdjamW8,205
14
17
  esparkcore/data/repositories/outbox_repository.py,sha256=PdPyKq2Km4_7732O6HhA4BlV_zBjYF91GkMltNKUovg,1015
15
- esparkcore/data/repositories/telemetry_repository.py,sha256=5UBBmY-ugcgP0Xji7cu58_nGt8MWKeHz9DHNVJxk0BY,1116
18
+ esparkcore/data/repositories/telemetry_repository.py,sha256=9wkgE2SU6_fqJMxpnG_IOF_ONxi9qi0coy_gVSJG2q8,2105
19
+ esparkcore/data/repositories/trigger_repository.py,sha256=xHYky1ZAT2H0cLPGTjVbmnH6ZHPgFXzga2SeOx-xSNo,185
16
20
  esparkcore/data/repositories/version_repository.py,sha256=QZZX2BpFx1i0oeFJ6j0nZvlZH7cViKU8VERBE8AU3ZY,395
17
21
  esparkcore/notifications/__init__.py,sha256=WrdUPsRS0tg2rAhv4aj4eGDekkfAsKjcRPRlT6btzQs,77
18
22
  esparkcore/notifications/notifier.py,sha256=d4a8leU-oTwWe7ZTLw-vxfCrYlFnCIvbcXabiXkT6Lc,446
19
23
  esparkcore/notifications/slack_notifier.py,sha256=-H-3YYm0CRTV8pZgggrNPJKz6815HTumKvE7VsvvSAA,644
20
- esparkcore/routers/__init__.py,sha256=K8-S0hugkQT5DB8i8O5YqeRsu9fGfLep6UQzCIpdgGg,131
24
+ esparkcore/routers/__init__.py,sha256=sZv812GJl-JEWJQ7IR1gAQ5BdCf5nTkfbJZXv5jZv_4,225
21
25
  esparkcore/routers/base_router.py,sha256=t-x8tYZVzBk9bD__9yaGYhm66glq_oM_lAHn0eplM2M,3902
22
- esparkcore/routers/device_router.py,sha256=mIBAyTyxl9AF8Os_Sms6Bb0vEvWfljyElbTWEGLo0iU,1172
23
- esparkcore/routers/telemetry_router.py,sha256=sLO5-oN73IjdGC_ASNif5xmVZHjka_iLv0BKZ_deZP0,1981
26
+ esparkcore/routers/device_router.py,sha256=KM6MI6BaxDbZJaqDPEi6iJYPwnOIujtAFG8J1oGRq3I,2506
27
+ esparkcore/routers/notification_router.py,sha256=vLG99eW-b9UlO9NiV1yZZeqkG4XLVv5Yj5JvHZLP6Dg,411
28
+ esparkcore/routers/telemetry_router.py,sha256=3690D4603gVK3-yaJT-sAVKwhIYEYKxQZpIu84a9flg,2584
29
+ esparkcore/routers/trigger_router.py,sha256=YSNYrLTQYeGToVBOlSK4PtDKpdFpesk-ZnNn_lXu6Ec,366
24
30
  esparkcore/routers/version_router.py,sha256=7HYD6sbGWow3DDvK62RXPZu5Qz8DCTTs7wkksQKBq9c,383
25
31
  esparkcore/schedules/__init__.py,sha256=v5Wy3Cbt2AnudGMGXv_Cyaa10s6NAmP66rDjcMSvf0o,74
26
32
  esparkcore/schedules/outbox.py,sha256=GGANvjkI7w1BdmmVRgKog498ASD_xRN0g8bG6yC_SN4,811
27
33
  esparkcore/schedules/scheduler.py,sha256=aYQXImLZ4tW-hRavn3ObuV3lYjmOZD813YkeG1NsYfY,170
28
34
  esparkcore/services/__init__.py,sha256=RIZOHBfkWn3HsApdA5rH9EZbbXGq0NfiGoOWDO_JX1k,30
29
- esparkcore/services/mqtt.py,sha256=EFRVT7QYEpWFhTopS2T6J5O69NWbxeuBQnytCjvd23k,6379
35
+ esparkcore/services/mqtt.py,sha256=5h_mhXhFt11TyhGwNhaY08Y4tttvSTPkiR3veTqpb3A,8972
30
36
  esparkcore/utils/__init__.py,sha256=rzuCJUAaQl-yPhM1vcWB_1zVXqtA71ChYP2aM_3UfKk,42
31
37
  esparkcore/utils/logging.py,sha256=sRO8uOcPkFH-DwepqLOTS2qGaopXpMdVOjzZL6RpGs4,257
32
- espark_core-0.4.9.dist-info/METADATA,sha256=kCGaK8PkyR5DMoJCxhwk56RN4sz91l9lJS5CW-bB2xo,6329
33
- espark_core-0.4.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
34
- espark_core-0.4.9.dist-info/top_level.txt,sha256=hXVyhIPB4aGskFm5queWALxDPhcDkrN7_8vcjwA1iE4,11
35
- espark_core-0.4.9.dist-info/RECORD,,
38
+ espark_core-0.5.0.dist-info/METADATA,sha256=Ez2PWfUcMCe7__fNBbp9bl0cGAGdPuyqfaL3OXYcNfg,6329
39
+ espark_core-0.5.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
40
+ espark_core-0.5.0.dist-info/top_level.txt,sha256=hXVyhIPB4aGskFm5queWALxDPhcDkrN7_8vcjwA1iE4,11
41
+ espark_core-0.5.0.dist-info/RECORD,,
esparkcore/constants.py CHANGED
@@ -7,5 +7,5 @@ TOPIC_ACTION : str = 'espark/action'
7
7
  TOPIC_DEVICE : str = 'espark/device'
8
8
  TOPIC_REGISTRATION : str = 'espark/registration'
9
9
  TOPIC_TELEMETRY : str = 'espark/telemetry'
10
- TOPIC_OTP : str = 'espark/otp'
10
+ TOPIC_OTA : str = 'espark/ota'
11
11
  TOPIC_CRASH : str = 'espark/crash'
@@ -1,4 +1,6 @@
1
1
  from .device import Device
2
+ from .notification import Notification
2
3
  from .outbox import OutboxEvent
3
4
  from .telemetry import Telemetry
5
+ from .trigger import Trigger
4
6
  from .version import AppVersion
@@ -0,0 +1,11 @@
1
+ from typing import Dict
2
+
3
+ from sqlalchemy import Column
4
+ from sqlmodel import SQLModel, Field, JSON
5
+
6
+
7
+ class Notification(SQLModel, table=True):
8
+ id : int = Field(primary_key=True, description='Unique identifier for the notification')
9
+ name : str = Field(index=True, unique=True, description='Name of the notification')
10
+ provider : str = Field(index=True, description='Notification provider (e.g., Slack, Twilio)')
11
+ config : Dict[str, str] = Field(default_factory=dict, sa_column=Column(JSON), description='JSON string of provider-specific configuration parameters')
@@ -0,0 +1,13 @@
1
+ from typing import Optional
2
+
3
+ from sqlmodel import Field, SQLModel
4
+
5
+
6
+ class Trigger(SQLModel, table=True):
7
+ id : str = Field(primary_key=True, description='Unique identifier for the trigger')
8
+ name : str = Field(index=True, unique=True, description='Name of the trigger')
9
+ device_id : Optional[str] = Field(index=True, default=None, description='Identifier of the associated device')
10
+ data_type : Optional[str] = Field(index=True, default=None, description='Type of telemetry data the trigger monitors (e.g., temperature, humidity)')
11
+ condition : str = Field(description='Condition to evaluate (e.g., ">", "<=")')
12
+ value : int = Field(description='Value to compare against for the trigger condition')
13
+ notification_ids : Optional[str] = Field(default=None, description='Comma-separated list of associated notification IDs to be sent when the trigger condition is met')
@@ -1,5 +1,7 @@
1
1
  from .base_repository import AsyncRepository
2
2
  from .device_repository import DeviceRepository
3
+ from .notification_repository import NotificationRepository
3
4
  from .outbox_repository import OutboxRepository
4
5
  from .telemetry_repository import TelemetryRepository
6
+ from .trigger_repository import TriggerRepository
5
7
  from .version_repository import AppVersionRepository
@@ -0,0 +1,7 @@
1
+ from ..models import Notification
2
+ from .base_repository import AsyncRepository
3
+
4
+
5
+ class NotificationRepository(AsyncRepository[Notification]):
6
+ def __init__(self):
7
+ super().__init__(Notification)
@@ -2,7 +2,7 @@ from typing import Optional, Sequence
2
2
 
3
3
  from sqlalchemy import ColumnElement
4
4
  from sqlalchemy.ext.asyncio import AsyncSession
5
- from sqlmodel import and_
5
+ from sqlmodel import and_, select
6
6
 
7
7
  from ..models import Telemetry
8
8
  from .base_repository import AsyncRepository
@@ -23,3 +23,26 @@ class TelemetryRepository(AsyncRepository[Telemetry]):
23
23
  order_by = Telemetry.timestamp.desc()
24
24
 
25
25
  return await super().list(session, *conditions, offset=offset, order_by=order_by, limit=limit)
26
+
27
+ async def search(self, session: AsyncSession, device_id: str = None, data_type: str = None, condition: str = None, value: int = None):
28
+ query = select(self.model)
29
+
30
+ if device_id is not None:
31
+ query = query.where(Telemetry.device_id == device_id)
32
+
33
+ if data_type is not None:
34
+ query = query.where(Telemetry.data_type == data_type)
35
+
36
+ if condition is not None and value is not None:
37
+ if condition == '<':
38
+ query = query.where(Telemetry.value < value)
39
+ elif condition == '<=':
40
+ query = query.where(Telemetry.value <= value)
41
+ elif condition == '==':
42
+ query = query.where(Telemetry.value == value)
43
+ elif condition == '>=':
44
+ query = query.where(Telemetry.value >= value)
45
+ elif condition == '>':
46
+ query = query.where(Telemetry.value > value)
47
+
48
+ return (await session.execute(query)).scalars().all()
@@ -0,0 +1,7 @@
1
+ from ..models import Trigger
2
+ from .base_repository import AsyncRepository
3
+
4
+
5
+ class TriggerRepository(AsyncRepository[Trigger]):
6
+ def __init__(self):
7
+ super().__init__(Trigger)
@@ -1,3 +1,5 @@
1
1
  from .device_router import DeviceRouter
2
+ from .notification_router import NotificationRouter
2
3
  from .telemetry_router import TelemetryRouter
4
+ from .trigger_router import TriggerRouter
3
5
  from .version_router import AppVersionRouter
@@ -2,11 +2,12 @@ from json import dumps
2
2
  from os import getenv
3
3
 
4
4
  from aiomqtt import Client
5
+ from fastapi import Depends, Query, Response
5
6
  from sqlalchemy.ext.asyncio import AsyncSession
6
7
 
7
8
  from ..constants import ENV_MQTT_HOST, ENV_MQTT_PORT, TOPIC_DEVICE
8
9
  from ..data.models import Device
9
- from ..data.repositories import DeviceRepository
10
+ from ..data.repositories import DeviceRepository, TelemetryRepository
10
11
  from ..utils import log_debug
11
12
  from .base_router import BaseRouter
12
13
 
@@ -27,3 +28,30 @@ class DeviceRouter(BaseRouter):
27
28
  await super()._after_update(entity, session)
28
29
 
29
30
  await self._publish_update(entity)
31
+
32
+ def _setup_routes(self) -> None:
33
+ @self.router.get('/all')
34
+ async def list_all(response: Response, session: AsyncSession = Depends(BaseRouter._get_session), offset: int = Query(None, ge=0), limit: int = Query(None, ge=1, le=100)):
35
+ response.headers['X-Total-Count'] = str(await self.repo.count(session))
36
+
37
+ devices = await self.repo.list(session, offset=offset, limit=limit)
38
+ devices_with_telemetry = []
39
+ telemetry_repo = TelemetryRepository()
40
+
41
+ for device in devices:
42
+ battery = await telemetry_repo.get_latest_for_device(session, device.id, 'battery')
43
+
44
+ devices_with_telemetry.append({
45
+ 'id' : device.id,
46
+ 'display_name' : device.display_name,
47
+ 'app_name' : device.app_name,
48
+ 'app_version' : device.app_version,
49
+ 'capabilities' : device.capabilities,
50
+ 'parameters' : device.parameters,
51
+ 'last_seen' : device.last_seen,
52
+ 'battery' : battery.value if battery else None,
53
+ })
54
+
55
+ return devices_with_telemetry
56
+
57
+ super()._setup_routes()
@@ -0,0 +1,10 @@
1
+ from ..data.models import Notification
2
+ from ..data.repositories import NotificationRepository
3
+ from .base_router import BaseRouter
4
+
5
+
6
+ class NotificationRouter(BaseRouter):
7
+ def __init__(self, repo: NotificationRepository = None) -> None:
8
+ self.repo : NotificationRepository = repo or NotificationRepository()
9
+
10
+ super().__init__(Notification, self.repo, '/api/v1/notifications', ['notification'])
@@ -3,6 +3,7 @@ from typing import Sequence
3
3
 
4
4
  from fastapi import Depends, Query, Response
5
5
  from sqlalchemy.ext.asyncio import AsyncSession
6
+ from sqlmodel import and_
6
7
 
7
8
  from ..data.models import Telemetry
8
9
  from ..data.repositories import DeviceRepository, TelemetryRepository
@@ -16,6 +17,15 @@ class TelemetryRouter(BaseRouter):
16
17
  super().__init__(Telemetry, self.repo, '/api/v1/telemetry', ['telemetry'])
17
18
 
18
19
  def _setup_routes(self) -> None:
20
+ @self.router.get('/history', response_model=Sequence[Telemetry])
21
+ async def list_history(response: Response, session: AsyncSession = Depends(BaseRouter._get_session), device_id: int = Query(..., ge=1), offset: int = Query(..., min=0)) -> Sequence[Telemetry]:
22
+ from_date = datetime.now(timezone.utc) - timedelta(seconds=offset)
23
+ results = await self.repo.list(session, and_(Telemetry.device_id == device_id, Telemetry.timestamp >= from_date))
24
+
25
+ response.headers['X-Total-Count'] = str(len(results))
26
+
27
+ return results
28
+
19
29
  @self.router.get('/recent', response_model=Sequence[Telemetry])
20
30
  async def list_recent(response: Response, session: AsyncSession = Depends(BaseRouter._get_session), offset: int = Query(..., min=0)) -> Sequence[Telemetry]:
21
31
  from_date = datetime.now(timezone.utc) - timedelta(seconds=offset)
@@ -0,0 +1,10 @@
1
+ from ..data.models import Trigger
2
+ from ..data.repositories import TriggerRepository
3
+ from .base_router import BaseRouter
4
+
5
+
6
+ class TriggerRouter(BaseRouter):
7
+ def __init__(self, repo: TriggerRepository = None) -> None:
8
+ self.repo : TriggerRepository = repo or TriggerRepository()
9
+
10
+ super().__init__(Trigger, self.repo, '/api/v1/triggers', ['trigger'])
@@ -7,23 +7,26 @@ from uuid import getnode
7
7
  from aiomqtt import Client, MqttError
8
8
  from packaging.version import Version
9
9
 
10
- from ..constants import ENV_MQTT_HOST, ENV_MQTT_PORT, TOPIC_CRASH, TOPIC_OTP, TOPIC_REGISTRATION, TOPIC_TELEMETRY
10
+ from ..constants import ENV_MQTT_HOST, ENV_MQTT_PORT, TOPIC_CRASH, TOPIC_OTA, TOPIC_REGISTRATION, TOPIC_TELEMETRY
11
11
  from ..data import async_session
12
12
  from ..data.models import AppVersion, Device, Telemetry
13
- from ..data.repositories import AppVersionRepository, DeviceRepository, TelemetryRepository
13
+ from ..data.repositories import AppVersionRepository, DeviceRepository, NotificationRepository, TelemetryRepository, TriggerRepository
14
+ from ..notifications import SlackNotifier
14
15
  from ..utils import log_debug, log_error
15
16
 
16
17
  MQTT_RETRY_DELAY: int = 5
17
18
 
18
19
 
19
20
  class MQTTManager:
20
- def __init__(self, version_repo: AppVersionRepository = None, device_repo: DeviceRepository = None, telemetry_repo: TelemetryRepository = None):
21
- self.mqtt_host : str = getenv(ENV_MQTT_HOST, 'localhost')
22
- self.mqtt_port : int = int(getenv(ENV_MQTT_PORT, '1883'))
23
- self.version_repo : AppVersionRepository = version_repo if version_repo else AppVersionRepository()
24
- self.device_repo : DeviceRepository = device_repo if device_repo else DeviceRepository()
25
- self.telemetry_repo : TelemetryRepository = telemetry_repo if telemetry_repo else TelemetryRepository()
26
- self.queue : Queue = Queue()
21
+ def __init__(self, version_repo: AppVersionRepository = None, device_repo: DeviceRepository = None, notification_repo: NotificationRepository = None, telemetry_repo: TelemetryRepository = None, trigger_repo: TriggerRepository = None) -> None:
22
+ self.mqtt_host : str = getenv(ENV_MQTT_HOST, 'localhost')
23
+ self.mqtt_port : int = int(getenv(ENV_MQTT_PORT, '1883'))
24
+ self.version_repo : AppVersionRepository = version_repo if version_repo else AppVersionRepository()
25
+ self.device_repo : DeviceRepository = device_repo if device_repo else DeviceRepository()
26
+ self.notification_repo : NotificationRepository = notification_repo if notification_repo else NotificationRepository()
27
+ self.telemetry_repo : TelemetryRepository = telemetry_repo if telemetry_repo else TelemetryRepository()
28
+ self.trigger_repo : TriggerRepository = trigger_repo if trigger_repo else TriggerRepository()
29
+ self.queue : Queue = Queue()
27
30
 
28
31
  async def _handle_registration(self, device_id: str, payload: dict) -> None:
29
32
  try:
@@ -61,11 +64,11 @@ class MQTTManager:
61
64
  log_debug(f'Device {device_id} is running an outdated version ({current_version} < {latest_version.version})')
62
65
 
63
66
  async with Client(self.mqtt_host, self.mqtt_port, identifier=f'espark-core-{hex(getnode())}') as client:
64
- await client.publish(f'{TOPIC_OTP}/{device_id}', dumps({
67
+ await client.publish(f'{TOPIC_OTA}/{device_id}', dumps({
65
68
  'device_id' : device_id,
66
69
  'app_name' : payload['app_name'],
67
70
  'app_version' : latest_version.version,
68
- 'download_url' : None,
71
+ 'download_url' : f'/downloads/{payload["app_name"]}/{latest_version.version}',
69
72
  }), qos=1)
70
73
  # pylint: disable=broad-exception-caught
71
74
  except Exception as e:
@@ -88,6 +91,40 @@ class MQTTManager:
88
91
  except Exception as e:
89
92
  log_error(e)
90
93
 
94
+ await self._handle_triggers(device_id, payload.get('data_type'), payload.get('value'))
95
+
96
+ async def _handle_triggers(self, device_id: str, data_type: str, value: int) -> None:
97
+ async with async_session() as session:
98
+ triggers = await self.trigger_repo.list(session)
99
+ for trigger in triggers:
100
+ matched_device_id = trigger.device_id is None or trigger.device_id == device_id
101
+ matched_data_type = trigger.data_type is None or trigger.data_type == data_type
102
+ if matched_device_id and matched_data_type:
103
+ condition_met = False
104
+ if trigger.condition is None:
105
+ condition_met = True
106
+ elif trigger.condition == '==' and value == trigger.value:
107
+ condition_met = True
108
+ elif trigger.condition == '>' and value > trigger.value:
109
+ condition_met = True
110
+ elif trigger.condition == '>=' and value >= trigger.value:
111
+ condition_met = True
112
+ elif trigger.condition == '<' and value < trigger.value:
113
+ condition_met = True
114
+ elif trigger.condition == '<=' and value <= trigger.value:
115
+ condition_met = True
116
+
117
+ if condition_met:
118
+ log_debug(f'Trigger {trigger.name} activated for device {device_id} with data type {data_type} and value {value}')
119
+
120
+ notifications = await self.notification_repo.list(session)
121
+ for notification in notifications:
122
+ if trigger.notification_ids.index(str(notification.id)) != -1:
123
+ log_debug(f'Sending notification {notification.name} for trigger {trigger.name}')
124
+
125
+ if notification.provider == 'Slack':
126
+ await SlackNotifier(notification.config['slack_token'], notification.config['slack_channel']).notify(device_id, data_type, value)
127
+
91
128
  async def _process_queue(self) -> None:
92
129
  while True:
93
130
  topic, payload = await self.queue.get()