conson-xp 1.45.0__py3-none-any.whl → 1.47.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.
- {conson_xp-1.45.0.dist-info → conson_xp-1.47.0.dist-info}/METADATA +1 -1
- conson_xp-1.47.0.dist-info/RECORD +210 -0
- xp/__init__.py +3 -2
- xp/cli/commands/conbus/conbus.py +1 -1
- xp/cli/commands/conbus/conbus_actiontable_commands.py +33 -15
- xp/cli/commands/conbus/conbus_autoreport_commands.py +8 -4
- xp/cli/commands/conbus/conbus_blink_commands.py +20 -10
- xp/cli/commands/conbus/conbus_config_commands.py +2 -1
- xp/cli/commands/conbus/conbus_custom_commands.py +4 -2
- xp/cli/commands/conbus/conbus_datapoint_commands.py +10 -5
- xp/cli/commands/conbus/conbus_discover_commands.py +8 -4
- xp/cli/commands/conbus/conbus_event_commands.py +8 -4
- xp/cli/commands/conbus/conbus_export_commands.py +8 -4
- xp/cli/commands/conbus/conbus_lightlevel_commands.py +16 -8
- xp/cli/commands/conbus/conbus_linknumber_commands.py +8 -4
- xp/cli/commands/conbus/conbus_modulenumber_commands.py +8 -4
- xp/cli/commands/conbus/conbus_msactiontable_commands.py +78 -40
- xp/cli/commands/conbus/conbus_output_commands.py +16 -8
- xp/cli/commands/conbus/conbus_raw_commands.py +6 -3
- xp/cli/commands/conbus/conbus_receive_commands.py +6 -3
- xp/cli/commands/conbus/conbus_scan_commands.py +6 -3
- xp/cli/commands/file_commands.py +6 -3
- xp/cli/commands/homekit/homekit.py +4 -2
- xp/cli/commands/homekit/homekit_start_commands.py +2 -1
- xp/cli/commands/module_commands.py +8 -4
- xp/cli/commands/reverse_proxy_commands.py +8 -4
- xp/cli/commands/server/server_commands.py +6 -3
- xp/cli/commands/telegram/telegram_blink_commands.py +4 -2
- xp/cli/commands/telegram/telegram_checksum_commands.py +4 -2
- xp/cli/commands/telegram/telegram_discover_commands.py +2 -1
- xp/cli/commands/telegram/telegram_linknumber_commands.py +4 -2
- xp/cli/commands/telegram/telegram_parse_commands.py +4 -2
- xp/cli/commands/telegram/telegram_version_commands.py +2 -1
- xp/cli/commands/term/term_commands.py +4 -2
- xp/cli/main.py +2 -1
- xp/cli/utils/click_tree.py +6 -3
- xp/cli/utils/datapoint_type_choice.py +4 -2
- xp/cli/utils/decorators.py +42 -21
- xp/cli/utils/error_handlers.py +16 -8
- xp/cli/utils/formatters.py +22 -11
- xp/cli/utils/module_type_choice.py +4 -2
- xp/cli/utils/serial_number_type.py +4 -2
- xp/cli/utils/system_function_choice.py +4 -2
- xp/cli/utils/xp_module_type.py +4 -2
- xp/models/actiontable/actiontable.py +8 -8
- xp/models/actiontable/actiontable_type.py +20 -0
- xp/models/actiontable/msactiontable_xp20.py +8 -4
- xp/models/actiontable/msactiontable_xp24.py +12 -6
- xp/models/actiontable/msactiontable_xp33.py +20 -10
- xp/models/conbus/conbus.py +8 -4
- xp/models/conbus/conbus_autoreport.py +4 -2
- xp/models/conbus/conbus_blink.py +4 -2
- xp/models/conbus/conbus_client_config.py +6 -3
- xp/models/conbus/conbus_connection_status.py +4 -2
- xp/models/conbus/conbus_custom.py +4 -2
- xp/models/conbus/conbus_datapoint.py +4 -2
- xp/models/conbus/conbus_discover.py +6 -3
- xp/models/conbus/conbus_event_list.py +4 -2
- xp/models/conbus/conbus_event_raw.py +4 -2
- xp/models/conbus/conbus_export.py +2 -1
- xp/models/conbus/conbus_lightlevel.py +4 -2
- xp/models/conbus/conbus_linknumber.py +4 -2
- xp/models/conbus/conbus_logger_config.py +8 -4
- xp/models/conbus/conbus_output.py +4 -2
- xp/models/conbus/conbus_raw.py +4 -2
- xp/models/conbus/conbus_receive.py +4 -2
- xp/models/conbus/conbus_writeconfig.py +4 -2
- xp/models/config/conson_module_config.py +8 -4
- xp/models/homekit/homekit_accessory.py +4 -2
- xp/models/homekit/homekit_config.py +12 -6
- xp/models/log_entry.py +16 -8
- xp/models/protocol/conbus_protocol.py +36 -18
- xp/models/response.py +12 -8
- xp/models/telegram/action_type.py +4 -2
- xp/models/telegram/datapoint_type.py +4 -2
- xp/models/telegram/event_telegram.py +14 -7
- xp/models/telegram/event_type.py +2 -1
- xp/models/telegram/input_action_type.py +2 -1
- xp/models/telegram/input_type.py +2 -1
- xp/models/telegram/module_type.py +24 -12
- xp/models/telegram/module_type_code.py +2 -1
- xp/models/telegram/output_telegram.py +16 -10
- xp/models/telegram/reply_telegram.py +24 -13
- xp/models/telegram/system_function.py +6 -3
- xp/models/telegram/system_telegram.py +10 -6
- xp/models/telegram/telegram.py +2 -1
- xp/models/telegram/telegram_type.py +2 -1
- xp/models/telegram/timeparam_type.py +2 -1
- xp/models/term/connection_state.py +4 -2
- xp/models/term/module_state.py +2 -1
- xp/models/term/protocol_keys_config.py +6 -3
- xp/models/term/status_message.py +2 -1
- xp/models/term/telegram_display.py +2 -1
- xp/models/write_config_type.py +4 -2
- xp/services/actiontable/actiontable_serializer.py +34 -41
- xp/services/actiontable/download_state_machine.py +281 -0
- xp/services/actiontable/msactiontable_xp20_serializer.py +77 -49
- xp/services/actiontable/msactiontable_xp24_serializer.py +78 -53
- xp/services/actiontable/msactiontable_xp33_serializer.py +39 -9
- xp/services/actiontable/serializer_protocol.py +76 -0
- xp/services/conbus/actiontable/actiontable_download_service.py +134 -280
- xp/services/conbus/actiontable/actiontable_list_service.py +17 -4
- xp/services/conbus/actiontable/actiontable_show_service.py +10 -6
- xp/services/conbus/actiontable/actiontable_upload_service.py +17 -9
- xp/services/conbus/conbus_blink_all_service.py +16 -8
- xp/services/conbus/conbus_blink_service.py +14 -7
- xp/services/conbus/conbus_custom_service.py +16 -8
- xp/services/conbus/conbus_datapoint_queryall_service.py +18 -9
- xp/services/conbus/conbus_datapoint_service.py +18 -9
- xp/services/conbus/conbus_discover_service.py +24 -13
- xp/services/conbus/conbus_event_list_service.py +11 -7
- xp/services/conbus/conbus_event_raw_service.py +18 -10
- xp/services/conbus/conbus_export_service.py +28 -14
- xp/services/conbus/conbus_output_service.py +18 -10
- xp/services/conbus/conbus_raw_service.py +16 -8
- xp/services/conbus/conbus_receive_service.py +18 -10
- xp/services/conbus/conbus_scan_service.py +18 -10
- xp/services/conbus/msactiontable/msactiontable_upload_service.py +17 -9
- xp/services/conbus/write_config_service.py +18 -9
- xp/services/homekit/homekit_cache_service.py +12 -6
- xp/services/homekit/homekit_conbus_service.py +12 -6
- xp/services/homekit/homekit_config_validator.py +34 -17
- xp/services/homekit/homekit_conson_validator.py +18 -9
- xp/services/homekit/homekit_dimminglight.py +14 -7
- xp/services/homekit/homekit_dimminglight_service.py +14 -7
- xp/services/homekit/homekit_hap_service.py +18 -9
- xp/services/homekit/homekit_lightbulb.py +10 -5
- xp/services/homekit/homekit_lightbulb_service.py +10 -5
- xp/services/homekit/homekit_module_service.py +8 -4
- xp/services/homekit/homekit_outlet.py +14 -7
- xp/services/homekit/homekit_outlet_service.py +12 -6
- xp/services/homekit/homekit_service.py +24 -12
- xp/services/log_file_service.py +16 -8
- xp/services/module_type_service.py +10 -5
- xp/services/protocol/conbus_event_protocol.py +140 -21
- xp/services/protocol/conbus_protocol.py +36 -19
- xp/services/protocol/protocol_factory.py +12 -6
- xp/services/protocol/telegram_protocol.py +12 -6
- xp/services/reverse_proxy_service.py +26 -14
- xp/services/server/base_server_service.py +42 -23
- xp/services/server/client_buffer_manager.py +12 -7
- xp/services/server/cp20_server_service.py +10 -7
- xp/services/server/device_service_factory.py +12 -8
- xp/services/server/server_service.py +18 -11
- xp/services/server/xp130_server_service.py +11 -8
- xp/services/server/xp20_server_service.py +16 -10
- xp/services/server/xp230_server_service.py +10 -7
- xp/services/server/xp24_server_service.py +22 -13
- xp/services/server/xp33_server_service.py +44 -25
- xp/services/telegram/telegram_blink_service.py +14 -8
- xp/services/telegram/telegram_checksum_service.py +12 -7
- xp/services/telegram/telegram_datapoint_service.py +14 -9
- xp/services/telegram/telegram_discover_service.py +28 -15
- xp/services/telegram/telegram_link_number_service.py +18 -10
- xp/services/telegram/telegram_output_service.py +24 -12
- xp/services/telegram/telegram_service.py +22 -11
- xp/services/telegram/telegram_version_service.py +14 -8
- xp/services/term/protocol_monitor_service.py +30 -16
- xp/services/term/state_monitor_service.py +39 -21
- xp/term/protocol.py +12 -6
- xp/term/state.py +12 -7
- xp/term/widgets/help_menu.py +6 -3
- xp/term/widgets/modules_list.py +20 -10
- xp/term/widgets/protocol_log.py +12 -6
- xp/term/widgets/status_footer.py +10 -5
- xp/utils/checksum.py +6 -3
- xp/utils/dependencies.py +26 -31
- xp/utils/event_helper.py +6 -4
- xp/utils/logging.py +6 -3
- xp/utils/serialization.py +30 -16
- xp/utils/state_machine.py +16 -9
- xp/utils/time_utils.py +6 -3
- conson_xp-1.45.0.dist-info/RECORD +0 -210
- xp/services/conbus/msactiontable/msactiontable_download_service.py +0 -275
- xp/services/conbus/msactiontable/msactiontable_list_service.py +0 -100
- xp/services/conbus/msactiontable/msactiontable_show_service.py +0 -89
- {conson_xp-1.45.0.dist-info → conson_xp-1.47.0.dist-info}/WHEEL +0 -0
- {conson_xp-1.45.0.dist-info → conson_xp-1.47.0.dist-info}/entry_points.txt +0 -0
- {conson_xp-1.45.0.dist-info → conson_xp-1.47.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,104 +1,58 @@
|
|
|
1
1
|
"""Service for downloading ActionTable via Conbus protocol."""
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
-
from dataclasses import asdict
|
|
5
|
-
from enum import Enum
|
|
6
4
|
from typing import Any, Optional
|
|
7
5
|
|
|
8
|
-
from psygnal import
|
|
9
|
-
from statemachine import State, StateMachine
|
|
6
|
+
from psygnal import Signal
|
|
10
7
|
|
|
11
|
-
from xp.models.actiontable.
|
|
8
|
+
from xp.models.actiontable.actiontable_type import ActionTableType
|
|
12
9
|
from xp.models.protocol.conbus_protocol import TelegramReceivedEvent
|
|
13
10
|
from xp.models.telegram.datapoint_type import DataPointType
|
|
14
11
|
from xp.models.telegram.reply_telegram import ReplyTelegram
|
|
15
|
-
from xp.models.telegram.system_function import SystemFunction
|
|
16
|
-
from xp.models.telegram.telegram_type import TelegramType
|
|
17
12
|
from xp.services.actiontable.actiontable_serializer import ActionTableSerializer
|
|
18
|
-
from xp.services.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
13
|
+
from xp.services.actiontable.download_state_machine import (
|
|
14
|
+
MAX_ERROR_RETRIES,
|
|
15
|
+
DownloadStateMachine,
|
|
16
|
+
)
|
|
17
|
+
from xp.services.actiontable.msactiontable_xp20_serializer import (
|
|
18
|
+
Xp20MsActionTableSerializer,
|
|
19
|
+
)
|
|
20
|
+
from xp.services.actiontable.msactiontable_xp24_serializer import (
|
|
21
|
+
Xp24MsActionTableSerializer,
|
|
22
|
+
)
|
|
23
|
+
from xp.services.actiontable.msactiontable_xp33_serializer import (
|
|
24
|
+
Xp33MsActionTableSerializer,
|
|
25
|
+
)
|
|
26
|
+
from xp.services.actiontable.serializer_protocol import ActionTableSerializerProtocol
|
|
27
|
+
from xp.services.protocol.conbus_event_protocol import (
|
|
28
|
+
NO_ERROR_CODE,
|
|
29
|
+
ConbusEventProtocol,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ActionTableDownloadService(DownloadStateMachine):
|
|
39
34
|
"""
|
|
35
|
+
Service for downloading action tables from Conbus modules via TCP.
|
|
40
36
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
CLEANUP = "cleanup"
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
class ActionTableDownloadService(StateMachine):
|
|
47
|
-
"""Service for downloading action tables from Conbus modules via TCP.
|
|
48
|
-
|
|
49
|
-
Inherits from StateMachine - the service IS the state machine. Uses guard
|
|
50
|
-
conditions to share states between INIT and CLEANUP phases.
|
|
51
|
-
see: Download-ActionTable-Workflow.dot
|
|
37
|
+
Inherits from ActionTableDownloadStateMachine and overrides on_enter_*
|
|
38
|
+
methods to add protocol-specific behavior.
|
|
52
39
|
|
|
53
|
-
|
|
54
|
-
idle -> receiving -> resetting -> waiting_ok -> requesting
|
|
55
|
-
-> waiting_data <-> receiving_chunk -> processing_eof -> completed
|
|
56
|
-
|
|
57
|
-
Phases - INIT and CLEANUP share the same states (receiving, resetting, waiting_ok):
|
|
40
|
+
The workflow consists of three phases:
|
|
58
41
|
|
|
59
42
|
INIT phase (drain → reset → wait_ok):
|
|
60
|
-
|
|
43
|
+
Connection established, drain pending telegrams, query error status.
|
|
61
44
|
|
|
62
45
|
DOWNLOAD phase (request → receive chunks → EOF):
|
|
63
|
-
|
|
46
|
+
Request actiontable, receive and ACK chunks until EOF.
|
|
64
47
|
|
|
65
48
|
CLEANUP phase (drain → reset → wait_ok):
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
The drain/reset/wait_ok cycle:
|
|
69
|
-
1. Drain pending telegrams (receiving state discards telegram without reading them).
|
|
70
|
-
There may be a lot of telegram. We are listening and receiving until no more
|
|
71
|
-
telegram arrive and timeout occurs.
|
|
72
|
-
2. Timeout triggers error status query (resetting)
|
|
73
|
-
3. Wait for response (waiting_ok)
|
|
74
|
-
4. On no error: guard determines target (requesting or completed)
|
|
75
|
-
On error: retry from drain step
|
|
49
|
+
After EOF, drain remaining telegrams and verify final status.
|
|
76
50
|
|
|
77
51
|
Attributes:
|
|
78
52
|
on_progress: Signal emitted with "." for each chunk received.
|
|
79
53
|
on_error: Signal emitted with error message string.
|
|
80
54
|
on_actiontable_received: Signal emitted with (ActionTable, dict, list).
|
|
81
55
|
on_finish: Signal emitted when download and cleanup completed.
|
|
82
|
-
idle: Initial state - waiting for connection.
|
|
83
|
-
receiving: Drain telegrams state (INIT or CLEANUP phase).
|
|
84
|
-
resetting: Query error status state.
|
|
85
|
-
waiting_ok: Await error status response state.
|
|
86
|
-
requesting: DOWNLOAD phase - send download request state.
|
|
87
|
-
waiting_data: DOWNLOAD phase - await chunks state.
|
|
88
|
-
receiving_chunk: DOWNLOAD phase - process chunk state.
|
|
89
|
-
processing_eof: DOWNLOAD phase - deserialize result state.
|
|
90
|
-
completed: Final state - download finished.
|
|
91
|
-
do_connect: Transition from idle to receiving.
|
|
92
|
-
filter_telegram: Self-transition in receiving to drain telegrams.
|
|
93
|
-
do_timeout: Transition on timeout events.
|
|
94
|
-
send_error_status: Transition from resetting to waiting_ok.
|
|
95
|
-
error_status_received: Transition when error status is received.
|
|
96
|
-
no_error_status_received: Transition when no error status received.
|
|
97
|
-
send_download: Transition from requesting to waiting_data.
|
|
98
|
-
receive_chunk: Transition from waiting_data to receiving_chunk.
|
|
99
|
-
send_ack: Transition from receiving_chunk to waiting_data.
|
|
100
|
-
receive_eof: Transition from waiting_data to processing_eof.
|
|
101
|
-
do_finish: Transition from processing_eof to receiving (cleanup).
|
|
102
56
|
|
|
103
57
|
Example:
|
|
104
58
|
>>> with download_service as service:
|
|
@@ -107,141 +61,72 @@ class ActionTableDownloadService(StateMachine):
|
|
|
107
61
|
... service.start_reactor()
|
|
108
62
|
"""
|
|
109
63
|
|
|
110
|
-
#
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
requesting = State() # DOWNLOAD phase: send download request
|
|
117
|
-
waiting_data = State() # DOWNLOAD phase: await chunks
|
|
118
|
-
receiving_chunk = State() # DOWNLOAD phase: process chunk
|
|
119
|
-
processing_eof = State() # DOWNLOAD phase: deserialize result
|
|
120
|
-
|
|
121
|
-
completed = State(final=True)
|
|
122
|
-
|
|
123
|
-
# Phase transitions - shared states with guards for phase-dependent routing
|
|
124
|
-
do_connect = idle.to(receiving)
|
|
125
|
-
filter_telegram = receiving.to(receiving) # Self-transition: drain to /dev/null
|
|
126
|
-
do_timeout = receiving.to(resetting) | waiting_ok.to(receiving)
|
|
127
|
-
send_error_status = resetting.to(waiting_ok)
|
|
128
|
-
error_status_received = waiting_ok.to(
|
|
129
|
-
receiving, cond="can_retry"
|
|
130
|
-
) # Retry if under limit
|
|
131
|
-
|
|
132
|
-
# Conditional transitions based on phase
|
|
133
|
-
no_error_status_received = waiting_ok.to(
|
|
134
|
-
requesting, cond="is_init_phase"
|
|
135
|
-
) | waiting_ok.to(completed, cond="is_cleanup_phase")
|
|
136
|
-
|
|
137
|
-
# DOWNLOAD phase transitions
|
|
138
|
-
send_download = requesting.to(waiting_data)
|
|
139
|
-
receive_chunk = waiting_data.to(receiving_chunk)
|
|
140
|
-
send_ack = receiving_chunk.to(waiting_data)
|
|
141
|
-
receive_eof = waiting_data.to(processing_eof)
|
|
142
|
-
|
|
143
|
-
# Return to drain/reset cycle for CLEANUP phase
|
|
144
|
-
do_finish = processing_eof.to(receiving)
|
|
64
|
+
# Service signals
|
|
65
|
+
on_progress: Signal = Signal(str)
|
|
66
|
+
on_error: Signal = Signal(str)
|
|
67
|
+
on_finish: Signal = Signal()
|
|
68
|
+
on_actiontable_received: Signal = Signal(Any, dict[str, Any], list[str])
|
|
145
69
|
|
|
146
70
|
def __init__(
|
|
147
71
|
self,
|
|
148
72
|
conbus_protocol: ConbusEventProtocol,
|
|
149
73
|
actiontable_serializer: ActionTableSerializer,
|
|
150
|
-
|
|
74
|
+
msactiontable_serializer_xp20: Xp20MsActionTableSerializer,
|
|
75
|
+
msactiontable_serializer_xp24: Xp24MsActionTableSerializer,
|
|
76
|
+
msactiontable_serializer_xp33: Xp33MsActionTableSerializer,
|
|
151
77
|
) -> None:
|
|
152
|
-
"""
|
|
78
|
+
"""
|
|
79
|
+
Initialize the action table download service.
|
|
153
80
|
|
|
154
81
|
Args:
|
|
155
82
|
conbus_protocol: ConbusEventProtocol instance.
|
|
156
83
|
actiontable_serializer: Action table serializer.
|
|
157
|
-
|
|
84
|
+
msactiontable_serializer_xp20: XP20 master station action table serializer.
|
|
85
|
+
msactiontable_serializer_xp24: XP24 master station action table serializer.
|
|
86
|
+
msactiontable_serializer_xp33: XP33 master station action table serializer.
|
|
158
87
|
"""
|
|
159
88
|
self.conbus_protocol = conbus_protocol
|
|
160
|
-
self.
|
|
161
|
-
self.
|
|
89
|
+
self.actiontable_serializer = actiontable_serializer
|
|
90
|
+
self.msactiontable_serializer_xp20 = msactiontable_serializer_xp20
|
|
91
|
+
self.msactiontable_serializer_xp24 = msactiontable_serializer_xp24
|
|
92
|
+
self.msactiontable_serializer_xp33 = msactiontable_serializer_xp33
|
|
93
|
+
self.serializer: ActionTableSerializerProtocol = actiontable_serializer
|
|
94
|
+
|
|
162
95
|
self.serial_number: str = ""
|
|
163
96
|
self.actiontable_data: list[str] = []
|
|
164
|
-
self.logger = logging.getLogger(__name__)
|
|
165
|
-
self._phase: Phase = Phase.INIT
|
|
166
|
-
self._error_retry_count: int = 0
|
|
167
97
|
self._signals_connected: bool = False
|
|
168
98
|
|
|
169
|
-
#
|
|
170
|
-
|
|
171
|
-
self.on_error: SignalInstance = SignalInstance((str,))
|
|
172
|
-
self.on_finish: SignalInstance = SignalInstance()
|
|
173
|
-
self.on_actiontable_received: SignalInstance = SignalInstance(
|
|
174
|
-
(ActionTable, dict[str, Any], list[str])
|
|
175
|
-
)
|
|
99
|
+
# Initialize state machine (must be last - triggers introspection)
|
|
100
|
+
super().__init__()
|
|
176
101
|
|
|
177
|
-
#
|
|
178
|
-
|
|
102
|
+
# Override logger for service-specific logging
|
|
103
|
+
self.logger = logging.getLogger(__name__)
|
|
179
104
|
|
|
180
105
|
# Connect protocol signals
|
|
181
106
|
self._connect_signals()
|
|
182
107
|
|
|
183
|
-
#
|
|
184
|
-
|
|
185
|
-
def is_init_phase(self) -> bool:
|
|
186
|
-
"""Guard: check if currently in INIT phase.
|
|
187
|
-
|
|
188
|
-
Returns:
|
|
189
|
-
True if in INIT phase, False otherwise.
|
|
190
|
-
"""
|
|
191
|
-
return self._phase == Phase.INIT
|
|
192
|
-
|
|
193
|
-
def is_cleanup_phase(self) -> bool:
|
|
194
|
-
"""Guard: check if currently in CLEANUP phase.
|
|
195
|
-
|
|
196
|
-
Returns:
|
|
197
|
-
True if in CLEANUP phase, False otherwise.
|
|
198
|
-
"""
|
|
199
|
-
return self._phase == Phase.CLEANUP
|
|
200
|
-
|
|
201
|
-
def can_retry(self) -> bool:
|
|
202
|
-
"""Guard: check if retry is allowed (under max limit).
|
|
203
|
-
|
|
204
|
-
Returns:
|
|
205
|
-
True if retry count is below MAX_ERROR_RETRIES, False otherwise.
|
|
206
|
-
"""
|
|
207
|
-
return self._error_retry_count < MAX_ERROR_RETRIES
|
|
208
|
-
|
|
209
|
-
# State machine lifecycle hooks
|
|
210
|
-
# Note: receiving state is used to drain pending telegrams from the connection
|
|
211
|
-
# pipe. Any telegram received in this state is intentionally discarded (sent
|
|
212
|
-
# to /dev/null) to ensure a clean state before processing.
|
|
108
|
+
# Override state entry hooks with protocol behavior
|
|
213
109
|
|
|
214
110
|
def on_enter_receiving(self) -> None:
|
|
215
|
-
"""Enter receiving state -
|
|
216
|
-
self.logger.debug(f"Entering RECEIVING state (phase={self.
|
|
111
|
+
"""Enter receiving state - wait for telegrams to drain."""
|
|
112
|
+
self.logger.debug(f"Entering RECEIVING state (phase={self.phase.value})")
|
|
217
113
|
self.conbus_protocol.wait()
|
|
218
114
|
|
|
219
115
|
def on_enter_resetting(self) -> None:
|
|
220
|
-
"""Enter resetting state -
|
|
221
|
-
self.logger.debug(f"Entering RESETTING state (phase={self.
|
|
222
|
-
self.conbus_protocol.
|
|
223
|
-
telegram_type=TelegramType.SYSTEM,
|
|
224
|
-
serial_number=self.serial_number,
|
|
225
|
-
system_function=SystemFunction.READ_DATAPOINT,
|
|
226
|
-
data_value=DataPointType.MODULE_ERROR_CODE.value,
|
|
227
|
-
)
|
|
116
|
+
"""Enter resetting state - send error status query."""
|
|
117
|
+
self.logger.debug(f"Entering RESETTING state (phase={self.phase.value})")
|
|
118
|
+
self.conbus_protocol.send_error_status_query(serial_number=self.serial_number)
|
|
228
119
|
self.send_error_status()
|
|
229
120
|
|
|
230
121
|
def on_enter_waiting_ok(self) -> None:
|
|
231
|
-
"""Enter waiting_ok state -
|
|
232
|
-
self.logger.debug(f"Entering WAITING_OK state (phase={self.
|
|
122
|
+
"""Enter waiting_ok state - wait for error status response."""
|
|
123
|
+
self.logger.debug(f"Entering WAITING_OK state (phase={self.phase.value})")
|
|
233
124
|
self.conbus_protocol.wait()
|
|
234
125
|
|
|
235
126
|
def on_enter_requesting(self) -> None:
|
|
236
127
|
"""Enter requesting state - send download request."""
|
|
237
|
-
self.
|
|
238
|
-
self.
|
|
239
|
-
self.conbus_protocol.send_telegram(
|
|
240
|
-
telegram_type=TelegramType.SYSTEM,
|
|
241
|
-
serial_number=self.serial_number,
|
|
242
|
-
system_function=SystemFunction.DOWNLOAD_ACTIONTABLE,
|
|
243
|
-
data_value=NO_ERROR_CODE,
|
|
244
|
-
)
|
|
128
|
+
self.enter_download_phase() # Sets phase to DOWNLOAD
|
|
129
|
+
self.conbus_protocol.send_download_request(serial_number=self.serial_number)
|
|
245
130
|
self.send_download()
|
|
246
131
|
|
|
247
132
|
def on_enter_waiting_data(self) -> None:
|
|
@@ -252,33 +137,29 @@ class ActionTableDownloadService(StateMachine):
|
|
|
252
137
|
def on_enter_receiving_chunk(self) -> None:
|
|
253
138
|
"""Enter receiving_chunk state - send ACK."""
|
|
254
139
|
self.logger.debug("Entering RECEIVING_CHUNK state - sending ACK")
|
|
255
|
-
self.conbus_protocol.
|
|
256
|
-
telegram_type=TelegramType.SYSTEM,
|
|
257
|
-
serial_number=self.serial_number,
|
|
258
|
-
system_function=SystemFunction.ACK,
|
|
259
|
-
data_value=NO_ERROR_CODE,
|
|
260
|
-
)
|
|
140
|
+
self.conbus_protocol.send_ack(serial_number=self.serial_number)
|
|
261
141
|
self.send_ack()
|
|
262
142
|
|
|
263
143
|
def on_enter_processing_eof(self) -> None:
|
|
264
|
-
"""Enter processing_eof state - deserialize and emit result
|
|
144
|
+
"""Enter processing_eof state - deserialize and emit result."""
|
|
265
145
|
self.logger.debug("Entering PROCESSING_EOF state - deserializing")
|
|
266
146
|
all_data = "".join(self.actiontable_data)
|
|
267
147
|
actiontable = self.serializer.from_encoded_string(all_data)
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
)
|
|
273
|
-
# Switch to CLEANUP phase before returning to receiving state
|
|
274
|
-
self._phase = Phase.CLEANUP
|
|
275
|
-
self.do_finish()
|
|
148
|
+
actiontable_short = self.serializer.to_short_string(actiontable)
|
|
149
|
+
self.on_actiontable_received.emit(actiontable, actiontable_short)
|
|
150
|
+
# Switch to CLEANUP phase
|
|
151
|
+
self.start_cleanup_phase()
|
|
276
152
|
|
|
277
153
|
def on_enter_completed(self) -> None:
|
|
278
|
-
"""Enter completed state -
|
|
154
|
+
"""Enter completed state - emit finish signal."""
|
|
279
155
|
self.logger.debug("Entering COMPLETED state - download finished")
|
|
280
156
|
self.on_finish.emit()
|
|
281
157
|
|
|
158
|
+
def on_max_retries_exceeded(self) -> None:
|
|
159
|
+
"""Handle max retries exceeded - emit error signal."""
|
|
160
|
+
self.logger.error(f"Max error retries ({MAX_ERROR_RETRIES}) exceeded")
|
|
161
|
+
self.on_error.emit(f"Module error persists after {MAX_ERROR_RETRIES} retries")
|
|
162
|
+
|
|
282
163
|
# Protocol event handlers
|
|
283
164
|
|
|
284
165
|
def _on_connection_made(self) -> None:
|
|
@@ -287,26 +168,18 @@ class ActionTableDownloadService(StateMachine):
|
|
|
287
168
|
if self.idle.is_active:
|
|
288
169
|
self.do_connect()
|
|
289
170
|
|
|
290
|
-
def _on_telegram_sent(self, telegram_sent: str) -> None:
|
|
291
|
-
"""Handle telegram sent event.
|
|
292
|
-
|
|
293
|
-
Args:
|
|
294
|
-
telegram_sent: The telegram that was sent.
|
|
295
|
-
"""
|
|
296
|
-
self.logger.debug(f"Telegram sent: {telegram_sent}")
|
|
297
|
-
|
|
298
171
|
def _on_read_datapoint_received(self, reply_telegram: ReplyTelegram) -> None:
|
|
299
|
-
"""
|
|
172
|
+
"""
|
|
173
|
+
Handle READ_DATAPOINT response for error status check.
|
|
300
174
|
|
|
301
175
|
Args:
|
|
302
176
|
reply_telegram: The parsed reply telegram.
|
|
303
177
|
"""
|
|
304
178
|
self.logger.debug(f"Received READ_DATAPOINT in {self.current_state}")
|
|
179
|
+
if reply_telegram.serial_number != self.serial_number:
|
|
180
|
+
return
|
|
305
181
|
|
|
306
182
|
if reply_telegram.datapoint_type != DataPointType.MODULE_ERROR_CODE:
|
|
307
|
-
self.logger.debug(
|
|
308
|
-
f"Filtered: not a MODULE_ERROR_CODE (got {reply_telegram.datapoint_type})"
|
|
309
|
-
)
|
|
310
183
|
return
|
|
311
184
|
|
|
312
185
|
if not self.waiting_ok.is_active:
|
|
@@ -314,49 +187,46 @@ class ActionTableDownloadService(StateMachine):
|
|
|
314
187
|
|
|
315
188
|
is_no_error = reply_telegram.data_value == NO_ERROR_CODE
|
|
316
189
|
if is_no_error:
|
|
317
|
-
self.
|
|
318
|
-
self.no_error_status_received() # Guards determine target state
|
|
190
|
+
self.handle_no_error_received()
|
|
319
191
|
else:
|
|
320
|
-
self.
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
if self.waiting_ok.is_active:
|
|
328
|
-
self.logger.error(
|
|
329
|
-
f"Max error retries ({MAX_ERROR_RETRIES}) exceeded, giving up"
|
|
330
|
-
)
|
|
331
|
-
self.on_error.emit(
|
|
332
|
-
f"Module error persists after {MAX_ERROR_RETRIES} retries"
|
|
333
|
-
)
|
|
334
|
-
|
|
335
|
-
def _on_actiontable_chunk_received(self, reply_telegram: ReplyTelegram) -> None:
|
|
336
|
-
"""Handle actiontable chunk telegram received.
|
|
192
|
+
self.handle_error_received()
|
|
193
|
+
|
|
194
|
+
def _on_actiontable_chunk_received(
|
|
195
|
+
self, reply_telegram: ReplyTelegram, actiontable_chunk: str
|
|
196
|
+
) -> None:
|
|
197
|
+
"""
|
|
198
|
+
Handle actiontable chunk telegram received.
|
|
337
199
|
|
|
338
200
|
Args:
|
|
339
201
|
reply_telegram: The parsed reply telegram containing chunk data.
|
|
202
|
+
actiontable_chunk: The chunk data.
|
|
340
203
|
"""
|
|
341
204
|
self.logger.debug(f"Received actiontable chunk in {self.current_state}")
|
|
205
|
+
if reply_telegram.serial_number != self.serial_number:
|
|
206
|
+
return
|
|
207
|
+
|
|
342
208
|
if self.waiting_data.is_active:
|
|
343
|
-
|
|
344
|
-
self.actiontable_data.append(data_part)
|
|
209
|
+
self.actiontable_data.append(actiontable_chunk)
|
|
345
210
|
self.on_progress.emit(".")
|
|
346
211
|
self.receive_chunk()
|
|
347
212
|
|
|
348
|
-
def _on_eof_received(self,
|
|
349
|
-
"""
|
|
213
|
+
def _on_eof_received(self, reply_telegram: ReplyTelegram) -> None:
|
|
214
|
+
"""
|
|
215
|
+
Handle EOF telegram received.
|
|
350
216
|
|
|
351
217
|
Args:
|
|
352
|
-
|
|
218
|
+
reply_telegram: The parsed reply telegram (unused).
|
|
353
219
|
"""
|
|
354
220
|
self.logger.debug(f"Received EOF in {self.current_state}")
|
|
221
|
+
if reply_telegram.serial_number != self.serial_number:
|
|
222
|
+
return
|
|
223
|
+
|
|
355
224
|
if self.waiting_data.is_active:
|
|
356
225
|
self.receive_eof()
|
|
357
226
|
|
|
358
227
|
def _on_telegram_received(self, telegram_received: TelegramReceivedEvent) -> None:
|
|
359
|
-
"""
|
|
228
|
+
"""
|
|
229
|
+
Handle telegram received event.
|
|
360
230
|
|
|
361
231
|
Args:
|
|
362
232
|
telegram_received: The telegram received event.
|
|
@@ -369,42 +239,9 @@ class ActionTableDownloadService(StateMachine):
|
|
|
369
239
|
self.filter_telegram()
|
|
370
240
|
return
|
|
371
241
|
|
|
372
|
-
# Filter invalid telegrams
|
|
373
|
-
if not telegram_received.checksum_valid:
|
|
374
|
-
self.logger.debug("Filtered: invalid checksum")
|
|
375
|
-
return
|
|
376
|
-
|
|
377
|
-
if telegram_received.telegram_type != TelegramType.REPLY.value:
|
|
378
|
-
self.logger.debug(
|
|
379
|
-
f"Filtered: not a reply (got {telegram_received.telegram_type})"
|
|
380
|
-
)
|
|
381
|
-
return
|
|
382
|
-
|
|
383
|
-
if telegram_received.serial_number != self.serial_number:
|
|
384
|
-
self.logger.debug(
|
|
385
|
-
f"Filtered: wrong serial {telegram_received.serial_number} != {self.serial_number}"
|
|
386
|
-
)
|
|
387
|
-
return
|
|
388
|
-
|
|
389
|
-
reply_telegram = self.telegram_service.parse_reply_telegram(
|
|
390
|
-
telegram_received.frame
|
|
391
|
-
)
|
|
392
|
-
|
|
393
|
-
if reply_telegram.system_function == SystemFunction.READ_DATAPOINT:
|
|
394
|
-
self._on_read_datapoint_received(reply_telegram)
|
|
395
|
-
return
|
|
396
|
-
|
|
397
|
-
if reply_telegram.system_function == SystemFunction.ACTIONTABLE:
|
|
398
|
-
self._on_actiontable_chunk_received(reply_telegram)
|
|
399
|
-
return
|
|
400
|
-
|
|
401
|
-
if reply_telegram.system_function == SystemFunction.EOF:
|
|
402
|
-
self._on_eof_received(reply_telegram)
|
|
403
|
-
return
|
|
404
|
-
|
|
405
242
|
def _on_timeout(self) -> None:
|
|
406
243
|
"""Handle timeout event."""
|
|
407
|
-
self.logger.debug(f"Timeout occurred (phase={self.
|
|
244
|
+
self.logger.debug(f"Timeout occurred (phase={self.phase.value})")
|
|
408
245
|
if self.receiving.is_active:
|
|
409
246
|
self.do_timeout() # receiving -> resetting
|
|
410
247
|
elif self.waiting_ok.is_active:
|
|
@@ -417,7 +254,8 @@ class ActionTableDownloadService(StateMachine):
|
|
|
417
254
|
self.on_error.emit("Timeout")
|
|
418
255
|
|
|
419
256
|
def _on_failed(self, message: str) -> None:
|
|
420
|
-
"""
|
|
257
|
+
"""
|
|
258
|
+
Handle failed connection event.
|
|
421
259
|
|
|
422
260
|
Args:
|
|
423
261
|
message: Failure message.
|
|
@@ -430,15 +268,18 @@ class ActionTableDownloadService(StateMachine):
|
|
|
430
268
|
def configure(
|
|
431
269
|
self,
|
|
432
270
|
serial_number: str,
|
|
271
|
+
actiontable_type: ActionTableType,
|
|
433
272
|
timeout_seconds: Optional[float] = 2.0,
|
|
434
273
|
) -> None:
|
|
435
|
-
"""
|
|
274
|
+
"""
|
|
275
|
+
Configure download parameters before starting.
|
|
436
276
|
|
|
437
277
|
Sets the target module serial number and timeout. Call this before
|
|
438
278
|
start_reactor() to configure the download target.
|
|
439
279
|
|
|
440
280
|
Args:
|
|
441
281
|
serial_number: Module serial number to download from.
|
|
282
|
+
actiontable_type: Type of action table to download.
|
|
442
283
|
timeout_seconds: Timeout in seconds for each operation (default 2.0).
|
|
443
284
|
|
|
444
285
|
Raises:
|
|
@@ -448,11 +289,20 @@ class ActionTableDownloadService(StateMachine):
|
|
|
448
289
|
raise RuntimeError("Cannot configure while download in progress")
|
|
449
290
|
self.logger.info("Configuring actiontable download")
|
|
450
291
|
self.serial_number = serial_number
|
|
292
|
+
if actiontable_type == ActionTableType.ACTIONTABLE:
|
|
293
|
+
self.serializer = self.actiontable_serializer
|
|
294
|
+
elif actiontable_type == ActionTableType.MSACTIONTABLE_XP20:
|
|
295
|
+
self.serializer = self.msactiontable_serializer_xp20
|
|
296
|
+
elif actiontable_type == ActionTableType.MSACTIONTABLE_XP24:
|
|
297
|
+
self.serializer = self.msactiontable_serializer_xp24
|
|
298
|
+
elif actiontable_type == ActionTableType.MSACTIONTABLE_XP33:
|
|
299
|
+
self.serializer = self.msactiontable_serializer_xp33
|
|
451
300
|
if timeout_seconds:
|
|
452
301
|
self.conbus_protocol.timeout_seconds = timeout_seconds
|
|
453
302
|
|
|
454
303
|
def set_timeout(self, timeout_seconds: float) -> None:
|
|
455
|
-
"""
|
|
304
|
+
"""
|
|
305
|
+
Set operation timeout.
|
|
456
306
|
|
|
457
307
|
Args:
|
|
458
308
|
timeout_seconds: Timeout in seconds.
|
|
@@ -472,8 +322,14 @@ class ActionTableDownloadService(StateMachine):
|
|
|
472
322
|
if self._signals_connected:
|
|
473
323
|
return
|
|
474
324
|
self.conbus_protocol.on_connection_made.connect(self._on_connection_made)
|
|
475
|
-
self.conbus_protocol.on_telegram_sent.connect(self._on_telegram_sent)
|
|
476
325
|
self.conbus_protocol.on_telegram_received.connect(self._on_telegram_received)
|
|
326
|
+
self.conbus_protocol.on_read_datapoint_received.connect(
|
|
327
|
+
self._on_read_datapoint_received
|
|
328
|
+
)
|
|
329
|
+
self.conbus_protocol.on_actiontable_chunk_received.connect(
|
|
330
|
+
self._on_actiontable_chunk_received
|
|
331
|
+
)
|
|
332
|
+
self.conbus_protocol.on_eof_received.connect(self._on_eof_received)
|
|
477
333
|
self.conbus_protocol.on_timeout.connect(self._on_timeout)
|
|
478
334
|
self.conbus_protocol.on_failed.connect(self._on_failed)
|
|
479
335
|
self._signals_connected = True
|
|
@@ -483,8 +339,14 @@ class ActionTableDownloadService(StateMachine):
|
|
|
483
339
|
if not self._signals_connected:
|
|
484
340
|
return
|
|
485
341
|
self.conbus_protocol.on_connection_made.disconnect(self._on_connection_made)
|
|
486
|
-
self.conbus_protocol.on_telegram_sent.disconnect(self._on_telegram_sent)
|
|
487
342
|
self.conbus_protocol.on_telegram_received.disconnect(self._on_telegram_received)
|
|
343
|
+
self.conbus_protocol.on_read_datapoint_received.disconnect(
|
|
344
|
+
self._on_read_datapoint_received
|
|
345
|
+
)
|
|
346
|
+
self.conbus_protocol.on_actiontable_chunk_received.disconnect(
|
|
347
|
+
self._on_actiontable_chunk_received
|
|
348
|
+
)
|
|
349
|
+
self.conbus_protocol.on_eof_received.disconnect(self._on_eof_received)
|
|
488
350
|
self.conbus_protocol.on_timeout.disconnect(self._on_timeout)
|
|
489
351
|
self.conbus_protocol.on_failed.disconnect(self._on_failed)
|
|
490
352
|
self._signals_connected = False
|
|
@@ -497,20 +359,12 @@ class ActionTableDownloadService(StateMachine):
|
|
|
497
359
|
"""
|
|
498
360
|
# Reset state for singleton reuse
|
|
499
361
|
self.actiontable_data = []
|
|
500
|
-
|
|
501
|
-
self.
|
|
502
|
-
# Reset state machine to idle
|
|
503
|
-
self._reset_state()
|
|
362
|
+
# Reset state machine
|
|
363
|
+
self.reset()
|
|
504
364
|
# Reconnect signals (in case previously disconnected)
|
|
505
365
|
self._connect_signals()
|
|
506
366
|
return self
|
|
507
367
|
|
|
508
|
-
def _reset_state(self) -> None:
|
|
509
|
-
"""Reset state machine to initial state."""
|
|
510
|
-
# python-statemachine uses model.state to track current state
|
|
511
|
-
# Set it directly to the initial state id
|
|
512
|
-
self.model.state = self.idle.id
|
|
513
|
-
|
|
514
368
|
def __exit__(
|
|
515
369
|
self, _exc_type: Optional[type], _exc_val: Optional[Exception], _exc_tb: Any
|
|
516
370
|
) -> None:
|
|
@@ -8,7 +8,8 @@ from psygnal import Signal
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class ActionTableListService:
|
|
11
|
-
"""
|
|
11
|
+
"""
|
|
12
|
+
Service for listing modules with action table configurations.
|
|
12
13
|
|
|
13
14
|
Reads conson.yml and returns a list of all modules that have action table
|
|
14
15
|
configurations defined.
|
|
@@ -26,7 +27,8 @@ class ActionTableListService:
|
|
|
26
27
|
self.logger = logging.getLogger(__name__)
|
|
27
28
|
|
|
28
29
|
def __enter__(self) -> "ActionTableListService":
|
|
29
|
-
"""
|
|
30
|
+
"""
|
|
31
|
+
Context manager entry.
|
|
30
32
|
|
|
31
33
|
Returns:
|
|
32
34
|
Self for context manager use.
|
|
@@ -43,7 +45,8 @@ class ActionTableListService:
|
|
|
43
45
|
self,
|
|
44
46
|
config_path: Optional[Path] = None,
|
|
45
47
|
) -> None:
|
|
46
|
-
"""
|
|
48
|
+
"""
|
|
49
|
+
List all modules with action table configurations.
|
|
47
50
|
|
|
48
51
|
Args:
|
|
49
52
|
config_path: Optional path to conson.yml. Defaults to current directory.
|
|
@@ -73,6 +76,15 @@ class ActionTableListService:
|
|
|
73
76
|
"serial_number": module.serial_number,
|
|
74
77
|
"module_type": module.module_type,
|
|
75
78
|
"action_table": len(module.action_table) if module.action_table else 0,
|
|
79
|
+
"msaction_table": (
|
|
80
|
+
1
|
|
81
|
+
if (
|
|
82
|
+
module.xp20_msaction_table
|
|
83
|
+
or module.xp24_msaction_table
|
|
84
|
+
or module.xp33_msaction_table
|
|
85
|
+
)
|
|
86
|
+
else 0
|
|
87
|
+
),
|
|
76
88
|
}
|
|
77
89
|
for module in config.root
|
|
78
90
|
]
|
|
@@ -84,7 +96,8 @@ class ActionTableListService:
|
|
|
84
96
|
self.on_finish.emit(result)
|
|
85
97
|
|
|
86
98
|
def _handle_error(self, message: str) -> None:
|
|
87
|
-
"""
|
|
99
|
+
"""
|
|
100
|
+
Handle error and emit error signal.
|
|
88
101
|
|
|
89
102
|
Args:
|
|
90
103
|
message: Error message.
|