lifx-emulator 1.0.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.
- lifx_emulator/__init__.py +31 -0
- lifx_emulator/__main__.py +607 -0
- lifx_emulator/api.py +1825 -0
- lifx_emulator/async_storage.py +308 -0
- lifx_emulator/constants.py +33 -0
- lifx_emulator/device.py +750 -0
- lifx_emulator/device_states.py +114 -0
- lifx_emulator/factories.py +380 -0
- lifx_emulator/handlers/__init__.py +39 -0
- lifx_emulator/handlers/base.py +49 -0
- lifx_emulator/handlers/device_handlers.py +340 -0
- lifx_emulator/handlers/light_handlers.py +372 -0
- lifx_emulator/handlers/multizone_handlers.py +249 -0
- lifx_emulator/handlers/registry.py +110 -0
- lifx_emulator/handlers/tile_handlers.py +309 -0
- lifx_emulator/observers.py +139 -0
- lifx_emulator/products/__init__.py +28 -0
- lifx_emulator/products/generator.py +771 -0
- lifx_emulator/products/registry.py +1446 -0
- lifx_emulator/products/specs.py +242 -0
- lifx_emulator/products/specs.yml +327 -0
- lifx_emulator/protocol/__init__.py +1 -0
- lifx_emulator/protocol/base.py +334 -0
- lifx_emulator/protocol/const.py +8 -0
- lifx_emulator/protocol/generator.py +1371 -0
- lifx_emulator/protocol/header.py +159 -0
- lifx_emulator/protocol/packets.py +1351 -0
- lifx_emulator/protocol/protocol_types.py +844 -0
- lifx_emulator/protocol/serializer.py +379 -0
- lifx_emulator/scenario_manager.py +402 -0
- lifx_emulator/scenario_persistence.py +206 -0
- lifx_emulator/server.py +482 -0
- lifx_emulator/state_restorer.py +259 -0
- lifx_emulator/state_serializer.py +130 -0
- lifx_emulator/storage_protocol.py +100 -0
- lifx_emulator-1.0.0.dist-info/METADATA +445 -0
- lifx_emulator-1.0.0.dist-info/RECORD +40 -0
- lifx_emulator-1.0.0.dist-info/WHEEL +4 -0
- lifx_emulator-1.0.0.dist-info/entry_points.txt +2 -0
- lifx_emulator-1.0.0.dist-info/licenses/LICENSE +35 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""Observer pattern for activity tracking and event notification.
|
|
2
|
+
|
|
3
|
+
This module implements the Observer pattern to decouple activity tracking
|
|
4
|
+
from the server core, following the Open/Closed Principle.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from collections import deque
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from typing import Any, Protocol
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class PacketEvent:
|
|
16
|
+
"""Represents a packet transmission or reception event.
|
|
17
|
+
|
|
18
|
+
Attributes:
|
|
19
|
+
timestamp: Unix timestamp of the event
|
|
20
|
+
direction: 'rx' for received, 'tx' for transmitted
|
|
21
|
+
packet_type: Numeric packet type identifier
|
|
22
|
+
packet_name: Human-readable packet name
|
|
23
|
+
addr: Network address string (host:port)
|
|
24
|
+
device: Device serial (for tx events) or None
|
|
25
|
+
target: Target identifier (for rx events) or None
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
timestamp: float
|
|
29
|
+
direction: str # 'rx' or 'tx'
|
|
30
|
+
packet_type: int
|
|
31
|
+
packet_name: str
|
|
32
|
+
addr: str
|
|
33
|
+
device: str | None = None # Device serial for tx events
|
|
34
|
+
target: str | None = None # Target for rx events
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ActivityObserver(Protocol):
|
|
38
|
+
"""Protocol for observers that track packet activity.
|
|
39
|
+
|
|
40
|
+
Observers implementing this protocol can be attached to the server
|
|
41
|
+
to receive notifications of packet events.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def on_packet_received(self, event: PacketEvent) -> None:
|
|
45
|
+
"""Called when a packet is received.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
event: PacketEvent with direction='rx'
|
|
49
|
+
"""
|
|
50
|
+
...
|
|
51
|
+
|
|
52
|
+
def on_packet_sent(self, event: PacketEvent) -> None:
|
|
53
|
+
"""Called when a packet is sent.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
event: PacketEvent with direction='tx'
|
|
57
|
+
"""
|
|
58
|
+
...
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class ActivityLogger:
|
|
62
|
+
"""Observer that logs recent packet activity.
|
|
63
|
+
|
|
64
|
+
Maintains a rolling buffer of recent packet events for monitoring
|
|
65
|
+
and debugging purposes.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
def __init__(self, max_events: int = 100):
|
|
69
|
+
"""Initialize activity logger.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
max_events: Maximum number of events to retain
|
|
73
|
+
"""
|
|
74
|
+
self.recent_activity: deque[dict[str, Any]] = deque(maxlen=max_events)
|
|
75
|
+
|
|
76
|
+
def on_packet_received(self, event: PacketEvent) -> None:
|
|
77
|
+
"""Record a received packet event.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
event: PacketEvent with direction='rx'
|
|
81
|
+
"""
|
|
82
|
+
self.recent_activity.append(
|
|
83
|
+
{
|
|
84
|
+
"timestamp": event.timestamp,
|
|
85
|
+
"direction": "rx",
|
|
86
|
+
"packet_type": event.packet_type,
|
|
87
|
+
"packet_name": event.packet_name,
|
|
88
|
+
"target": event.target,
|
|
89
|
+
"addr": event.addr,
|
|
90
|
+
}
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
def on_packet_sent(self, event: PacketEvent) -> None:
|
|
94
|
+
"""Record a sent packet event.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
event: PacketEvent with direction='tx'
|
|
98
|
+
"""
|
|
99
|
+
self.recent_activity.append(
|
|
100
|
+
{
|
|
101
|
+
"timestamp": event.timestamp,
|
|
102
|
+
"direction": "tx",
|
|
103
|
+
"packet_type": event.packet_type,
|
|
104
|
+
"packet_name": event.packet_name,
|
|
105
|
+
"device": event.device,
|
|
106
|
+
"addr": event.addr,
|
|
107
|
+
}
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
def get_recent_activity(self) -> list[dict[str, Any]]:
|
|
111
|
+
"""Get list of recent activity events.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
List of activity event dictionaries
|
|
115
|
+
"""
|
|
116
|
+
return list(self.recent_activity)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class NullObserver:
|
|
120
|
+
"""No-op observer for when activity tracking is disabled.
|
|
121
|
+
|
|
122
|
+
Allows code to unconditionally call notify without checking for None.
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
def on_packet_received(self, event: PacketEvent) -> None:
|
|
126
|
+
"""No-op packet received handler.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
event: PacketEvent (ignored)
|
|
130
|
+
"""
|
|
131
|
+
pass
|
|
132
|
+
|
|
133
|
+
def on_packet_sent(self, event: PacketEvent) -> None:
|
|
134
|
+
"""No-op packet sent handler.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
event: PacketEvent (ignored)
|
|
138
|
+
"""
|
|
139
|
+
pass
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""LIFX product registry module.
|
|
2
|
+
|
|
3
|
+
This module provides product information and capability detection for LIFX devices.
|
|
4
|
+
|
|
5
|
+
The product registry is auto-generated from the official LIFX
|
|
6
|
+
products.json specification.
|
|
7
|
+
To update: run `uv run python -m lifx_emulator.products.generator`
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from .registry import (
|
|
11
|
+
ProductCapability,
|
|
12
|
+
ProductInfo,
|
|
13
|
+
ProductRegistry,
|
|
14
|
+
TemperatureRange,
|
|
15
|
+
get_device_class_name,
|
|
16
|
+
get_product,
|
|
17
|
+
get_registry,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"ProductCapability",
|
|
22
|
+
"ProductInfo",
|
|
23
|
+
"ProductRegistry",
|
|
24
|
+
"TemperatureRange",
|
|
25
|
+
"get_device_class_name",
|
|
26
|
+
"get_product",
|
|
27
|
+
"get_registry",
|
|
28
|
+
]
|