Skip to content

Commit 6e78491

Browse files
committed
fix
1 parent 18860f4 commit 6e78491

File tree

3 files changed

+28
-18
lines changed

3 files changed

+28
-18
lines changed

src/arduino/app_peripherals/camera/base_camera.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def __init__(
4646
self.adjustments = adjustments
4747
self.logger = logger # This will be overridden by subclasses if needed
4848
self.name = self.__class__.__name__ # This will be overridden by subclasses if needed
49-
self._status: Literal['disconnected', 'connected', 'streaming', 'paused'] = "disconnected"
49+
self._status: Literal["disconnected", "connected", "streaming", "paused"] = "disconnected"
5050

5151
self._camera_lock = threading.Lock()
5252
self._is_started = False
@@ -66,10 +66,10 @@ def __init__(
6666
self._event_executor = ThreadPoolExecutor(max_workers=1, thread_name_prefix="CameraEvent")
6767

6868
@property
69-
def status(self) -> Literal['disconnected', 'connected', 'streaming', 'paused']:
69+
def status(self) -> Literal["disconnected", "connected", "streaming", "paused"]:
7070
"""Read-only property for camera status."""
7171
return self._status
72-
72+
7373
@property
7474
def _none_frame_threshold(self) -> int:
7575
"""Heuristic: 750ms of empty frames based on current fps."""
@@ -255,7 +255,7 @@ def _read_frame(self) -> Optional[np.ndarray]:
255255
"""
256256
pass
257257

258-
def _set_status(self, new_status: str, data: dict | None = None) -> None:
258+
def _set_status(self, new_status: Literal["disconnected", "connected", "streaming", "paused"], data: dict | None = None) -> None:
259259
"""
260260
Updates the current status of the camera and invokes the registered status
261261
changed callback in the background, if any.
@@ -271,15 +271,19 @@ def _set_status(self, new_status: str, data: dict | None = None) -> None:
271271
new_status (str): The name of the new status.
272272
data (dict): Additional data associated with the status change.
273273
"""
274+
275+
if self.status == new_status:
276+
return
277+
274278
allowed_transitions = {
275279
"disconnected": ["connected"],
276280
"connected": ["disconnected", "streaming"],
277281
"streaming": ["paused", "disconnected"],
278282
"paused": ["streaming", "disconnected"],
279283
}
280284

281-
# If current status is not in the state machine, do nothing
282-
if self._status not in allowed_transitions:
285+
# If new status is not in the state machine, ignore it
286+
if new_status not in allowed_transitions:
283287
return
284288

285289
# Check if new_status is an allowed transition for the current status

tests/arduino/app_peripherals/camera/test_base_camera.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,7 @@ def event_callback(event, data):
403403
camera.capture()
404404
camera.capture() # Should emit "paused" event
405405
camera.frame = np.zeros((480, 640, 3), dtype=np.uint8)
406-
406+
407407
assert camera.status == "paused"
408408

409409
camera.capture() # Should emit "streaming" event

tests/arduino/app_peripherals/camera/test_websocket_camera.py

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,23 +43,25 @@ def encoded_frame_json(encoded_frame_binary):
4343
def test_websocket_camera_init_default():
4444
"""Test WebSocketCamera initialization with default parameters."""
4545
camera = WebSocketCamera()
46-
assert camera.host == "0.0.0.0"
46+
assert camera.address == "ws://0.0.0.0:8080"
4747
assert camera.port == 8080
4848
assert camera.timeout == 3
4949
assert camera.frame_format == "binary"
5050
assert camera.resolution == (640, 480)
5151
assert camera.fps == 10
52+
assert camera.status == "disconnected"
5253

5354

5455
def test_websocket_camera_init_custom():
5556
"""Test WebSocketCamera initialization with custom parameters."""
5657
camera = WebSocketCamera(host="127.0.0.1", port=9090, timeout=30, frame_format="json", resolution=(1920, 1080), fps=30)
57-
assert camera.host == "127.0.0.1"
58+
assert camera.address == "ws://127.0.0.1:9090"
5859
assert camera.port == 9090
5960
assert camera.timeout == 30
6061
assert camera.frame_format == "json"
6162
assert camera.resolution == (1920, 1080)
6263
assert camera.fps == 30
64+
assert camera.status == "disconnected"
6365

6466

6567
def test_websocket_camera_start_stop():
@@ -73,13 +75,17 @@ def test_websocket_camera_start_stop():
7375
pytest.fail("Camera start failed")
7476

7577
assert camera.is_started()
78+
# Starting does not coincide with being connected in case of WebSocketCamera
79+
# as that depends on client activity
80+
assert camera.status == "disconnected"
7681

7782
try:
7883
camera.stop()
7984
except Exception:
8085
pytest.fail("Camera stop failed")
8186

8287
assert not camera.is_started()
88+
assert camera.status == "disconnected"
8389

8490

8591
def test_websocket_camera_parse_message_binary(sample_frame, encoded_frame_binary):
@@ -161,7 +167,7 @@ def test_websocket_camera_read_frame_empty_queue():
161167
async def test_websocket_camera_capture_frame(encoded_frame_binary):
162168
"""Test capturing frame from WebSocket camera."""
163169
with WebSocketCamera(port=0, frame_format="binary") as camera:
164-
async with websockets.connect(f"{camera.protocol}://{camera.host}:{camera.port}") as ws:
170+
async with websockets.connect(camera.address) as ws:
165171
# Skip welcome message
166172
await ws.recv()
167173

@@ -183,15 +189,15 @@ async def test_websocket_camera_single_client():
183189

184190
try:
185191
# Connect first client
186-
async with websockets.connect(f"{camera.protocol}://{camera.host}:{camera.port}") as ws1:
192+
async with websockets.connect(camera.address) as ws1:
187193
# First client should receive welcome message
188194
welcome = await ws1.recv()
189195
message = json.loads(welcome)
190196
assert message["status"] == "connected"
191197

192198
# Try to connect second client while first is connected
193199
try:
194-
async with websockets.connect(f"{camera.protocol}://{camera.host}:{camera.port}") as ws2:
200+
async with websockets.connect(camera.address) as ws2:
195201
# Second client should receive rejection message
196202
rejection = await asyncio.wait_for(ws2.recv(), timeout=1.0)
197203
message = json.loads(rejection)
@@ -207,7 +213,7 @@ async def test_websocket_camera_single_client():
207213
async def test_websocket_camera_welcome_message():
208214
"""Test that welcome message is sent to connected client."""
209215
with WebSocketCamera(port=0) as camera:
210-
async with websockets.connect(f"{camera.protocol}://{camera.host}:{camera.port}") as ws:
216+
async with websockets.connect(camera.address) as ws:
211217
# Should receive welcome message
212218
welcome = await asyncio.wait_for(ws.recv(), timeout=1.0)
213219
message = json.loads(welcome)
@@ -220,7 +226,7 @@ async def test_websocket_camera_welcome_message():
220226
async def test_websocket_camera_receives_frames(encoded_frame_binary):
221227
"""Test that server receives and queues frames from client."""
222228
with WebSocketCamera(port=0, frame_format="binary") as camera:
223-
async with websockets.connect(f"{camera.protocol}://{camera.host}:{camera.port}") as ws:
229+
async with websockets.connect(camera.address) as ws:
224230
# Skip welcome message
225231
await ws.recv()
226232

@@ -241,7 +247,7 @@ async def test_websocket_camera_disconnects_client_on_stop():
241247
camera.start()
242248

243249
try:
244-
async with websockets.connect(f"{camera.protocol}://{camera.host}:{camera.port}") as ws:
250+
async with websockets.connect(camera.address) as ws:
245251
# Client connected, receive welcome message
246252
welcome = await ws.recv()
247253
message = json.loads(welcome)
@@ -280,7 +286,7 @@ def test_websocket_camera_stop_without_client():
280286
async def test_websocket_camera_backpressure(sample_frame):
281287
"""Test that old frames are dropped when new frames arrive faster than they're consumed."""
282288
with WebSocketCamera(port=0, frame_format="binary") as camera:
283-
async with websockets.connect(f"{camera.protocol}://{camera.host}:{camera.port}") as ws:
289+
async with websockets.connect(camera.address) as ws:
284290
await ws.recv() # Skip welcome message
285291

286292
_, buffer1 = cv2.imencode(".jpg", np.ones((480, 640, 3), dtype=np.uint8) * 1)
@@ -349,7 +355,7 @@ def event_listener(event_type, data):
349355

350356
# This should emit connection and disconnection events
351357
async def client_task():
352-
async with websockets.connect(f"{camera.protocol}://{camera.host}:{camera.port}"):
358+
async with websockets.connect(camera.address):
353359
pass
354360

355361
# Run client concurrently to properly test event handling
@@ -432,7 +438,7 @@ def event_listener(event_type, data):
432438

433439
# This should emit a connection event but no disconnection event
434440
async def client_task():
435-
async with websockets.connect(f"{camera.protocol}://{camera.host}:{camera.port}"):
441+
async with websockets.connect(camera.address):
436442
pass
437443
await can_close.wait()
438444

0 commit comments

Comments
 (0)