conson-xp 1.22.0__py3-none-any.whl → 1.23.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.22.0.dist-info → conson_xp-1.23.0.dist-info}/METADATA +1 -1
- {conson_xp-1.22.0.dist-info → conson_xp-1.23.0.dist-info}/RECORD +13 -11
- xp/__init__.py +1 -1
- xp/models/term/connection_state.py +58 -0
- xp/models/term/status_message.py +16 -0
- xp/services/protocol/conbus_event_protocol.py +19 -6
- xp/term/protocol.py +10 -0
- xp/term/protocol.tcss +1 -1
- xp/term/widgets/protocol_log.py +37 -84
- xp/term/widgets/status_footer.py +10 -0
- {conson_xp-1.22.0.dist-info → conson_xp-1.23.0.dist-info}/WHEEL +0 -0
- {conson_xp-1.22.0.dist-info → conson_xp-1.23.0.dist-info}/entry_points.txt +0 -0
- {conson_xp-1.22.0.dist-info → conson_xp-1.23.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
conson_xp-1.
|
|
2
|
-
conson_xp-1.
|
|
3
|
-
conson_xp-1.
|
|
4
|
-
conson_xp-1.
|
|
5
|
-
xp/__init__.py,sha256=
|
|
1
|
+
conson_xp-1.23.0.dist-info/METADATA,sha256=z9DdbWCsagoeEX32rY76lsao7924etwx1cYvdWevwSg,10298
|
|
2
|
+
conson_xp-1.23.0.dist-info/WHEEL,sha256=tsUv_t7BDeJeRHaSrczbGeuK-TtDpGsWi_JfpzD255I,90
|
|
3
|
+
conson_xp-1.23.0.dist-info/entry_points.txt,sha256=1OcdIcDM1hz3ljCXgybaPUh1IOFEwkaTgLIW9u9zGeg,50
|
|
4
|
+
conson_xp-1.23.0.dist-info/licenses/LICENSE,sha256=rxj6woMM-r3YCyGq_UHFtbh7kHTAJgrccH6O-33IDE4,1419
|
|
5
|
+
xp/__init__.py,sha256=a3NpXD1LnawKeJVM7klz-0Z1EzpYFllOS4ifHJUbU_4,181
|
|
6
6
|
xp/cli/__init__.py,sha256=QjnKB1KaI2aIyKlzrnvCwfbBuUj8HNgwNMvNJVQofbI,81
|
|
7
7
|
xp/cli/__main__.py,sha256=l2iKwMdat5rTGd3JWs-uGksnYYDDffp_Npz05QdKEeU,117
|
|
8
8
|
xp/cli/commands/__init__.py,sha256=noh8fdZAWq-ihJEboP8WugbIgq4LJ3jUWMRA7720xWE,4909
|
|
@@ -105,7 +105,9 @@ xp/models/telegram/telegram.py,sha256=IJUxHX6ftLcET9C1pjvLhUO5Db5JO6W7rUItzdEW30
|
|
|
105
105
|
xp/models/telegram/telegram_type.py,sha256=GhqKP63oNMyh2tIvCPcsC5RFp4s4JjhmEqCLCC-8XMk,423
|
|
106
106
|
xp/models/telegram/timeparam_type.py,sha256=Ar8xvSfPmOAgR2g2Je0FgvP01SL7bPvZn5_HrVDpmJM,1137
|
|
107
107
|
xp/models/term/__init__.py,sha256=c1AMtVitYk80o9K_zWjYNzZYpFDASqM8S1Djm1PD4Qo,192
|
|
108
|
+
xp/models/term/connection_state.py,sha256=floDRMeMcfgMrYIVsyoVHBXHtxd3hqm-xOdr3oXtaHY,1793
|
|
108
109
|
xp/models/term/protocol_keys_config.py,sha256=CTujcfI2_NOeltjvHy_cnsHzxLSVsGFXieMZlD-zj0Q,1204
|
|
110
|
+
xp/models/term/status_message.py,sha256=DOmzL0dbig5mP1UEoXdgzGT4UG2RyAXa_yRVo5c4x8w,394
|
|
109
111
|
xp/models/write_config_type.py,sha256=T2RaO52RpzoJ4782uMHE-fX7Ymx3CaIQAEwByydXq1M,881
|
|
110
112
|
xp/services/__init__.py,sha256=W9YZyrkh7vm--ZHhAXNQiOYQs5yhhmUHXP5I0Lf1XBg,782
|
|
111
113
|
xp/services/actiontable/__init__.py,sha256=z6js4EuJ6xKHaseTEhuEvKo1tr9K1XyQiruReJtBiPY,26
|
|
@@ -151,7 +153,7 @@ xp/services/homekit/homekit_service.py,sha256=0lW-hg40ETco3gDBEYkR_sX-UIYsLSKCD4
|
|
|
151
153
|
xp/services/log_file_service.py,sha256=fvPcZQj8XOKUA-4ep5R8n0PelvwvRlTLlVxvIWM5KR4,10506
|
|
152
154
|
xp/services/module_type_service.py,sha256=xWhr1EAZMykL5uNWHWdpa5T8yNruGKH43XRTOS8GwZg,7477
|
|
153
155
|
xp/services/protocol/__init__.py,sha256=qRufBmqRKGzpuzZ5bxBbmwf510TT00Ke8s5HcWGnqRY,818
|
|
154
|
-
xp/services/protocol/conbus_event_protocol.py,sha256=
|
|
156
|
+
xp/services/protocol/conbus_event_protocol.py,sha256=h9ZdnN9CWSBXQqE-M9qmPPbMT3r25cxzXuJsvURp1WQ,14390
|
|
155
157
|
xp/services/protocol/conbus_protocol.py,sha256=JO7yLkD_ohPT0ETjnAIx4CGpZyobf4LdbuozM_43btE,10276
|
|
156
158
|
xp/services/protocol/protocol_factory.py,sha256=PmjN9AtW9sxNo3voqUiNgQA-pTvX1RW4XXFlHKfFr5Q,2470
|
|
157
159
|
xp/services/protocol/telegram_protocol.py,sha256=Ki5DrXsKxiaqLcdP9WWUuhUI7cPu2DfwyZkh-Gv9Lb8,9496
|
|
@@ -176,13 +178,13 @@ xp/services/telegram/telegram_output_service.py,sha256=UaUv_14fR8o5K2PxQBXrCzx-H
|
|
|
176
178
|
xp/services/telegram/telegram_service.py,sha256=XrP1CPi0ckxoKBaNwLA6lo-TogWxXgmXDOsU4Xl8BlY,13237
|
|
177
179
|
xp/services/telegram/telegram_version_service.py,sha256=M5HdOTsLdcwo122FP-jW6R740ktLrtKf2TiMDVz23h8,10528
|
|
178
180
|
xp/term/__init__.py,sha256=Xg2DhBeI3xQJLfc7_BPWI1por-rUXemyer5OtOt9Cus,51
|
|
179
|
-
xp/term/protocol.py,sha256=
|
|
180
|
-
xp/term/protocol.tcss,sha256=
|
|
181
|
+
xp/term/protocol.py,sha256=acYYsv7_4z5ePrnslSC1exKzqbKOE5ZGds4J33Q2XNs,4784
|
|
182
|
+
xp/term/protocol.tcss,sha256=r_KfxrbpycGHLVXqZc6INBBcUJME0hLrAZkF1oqnab4,2126
|
|
181
183
|
xp/term/protocol.yml,sha256=kiTe_QSMPmLvLA0ZyIhNaDPwBdi6khh5C1NSR7I9TN0,2124
|
|
182
184
|
xp/term/widgets/__init__.py,sha256=ftWmN_fmjxy2E8Qfm-YSRmzKfgL0KTBCTpgvYWCPbUY,274
|
|
183
185
|
xp/term/widgets/help_menu.py,sha256=bdT5AYRdtKt_tvZTVbG7-DPMb1mj78kggtjjsa-95BA,1780
|
|
184
|
-
xp/term/widgets/protocol_log.py,sha256=
|
|
185
|
-
xp/term/widgets/status_footer.py,sha256=
|
|
186
|
+
xp/term/widgets/protocol_log.py,sha256=OeVg9VCvIl9HXm6j_HPAGOIAYxO0MxlKcS9QmBEOKx4,13018
|
|
187
|
+
xp/term/widgets/status_footer.py,sha256=eRZHkrG5aZCMulibX56KfFyGHS8IgL-7psvr9f9S6FI,1992
|
|
186
188
|
xp/utils/__init__.py,sha256=_avMF_UOkfR3tNeDIPqQ5odmbq5raKkaq1rZ9Cn1CJs,332
|
|
187
189
|
xp/utils/checksum.py,sha256=HDpiQxmdIedbCbZ4o_Box0i_Zig417BtCV_46ZyhiTk,1711
|
|
188
190
|
xp/utils/dependencies.py,sha256=ECS6p0eXzocM5INLwJeckHXn_Dim18uOjXTJ29qQvkQ,22001
|
|
@@ -191,4 +193,4 @@ xp/utils/logging.py,sha256=rZDXwlBrYK8A6MPq5StsMNpgsRowzJXM6fvROPwJdGM,3750
|
|
|
191
193
|
xp/utils/serialization.py,sha256=RWHHk86feaB4ZP7rjE4qOWK0900yg2joUBDkP76gfOY,4618
|
|
192
194
|
xp/utils/state_machine.py,sha256=Oe2sLwCh9z_vr1tF6X0ZRGTeuckRQAGzmef7xc9CNdc,2413
|
|
193
195
|
xp/utils/time_utils.py,sha256=dEyViDlAG9GWU-J3D_YVa-sGma6yiyyMTgN4h2x3PY4,3781
|
|
194
|
-
conson_xp-1.
|
|
196
|
+
conson_xp-1.23.0.dist-info/RECORD,,
|
xp/__init__.py
CHANGED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Connection state management module."""
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
|
|
5
|
+
from xp.utils.state_machine import StateMachine
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ConnectionState(str, Enum):
|
|
9
|
+
"""Connection state enumeration.
|
|
10
|
+
|
|
11
|
+
Attributes:
|
|
12
|
+
DISCONNECTING: Disconnecting to server.
|
|
13
|
+
DISCONNECTED: Not connected to server.
|
|
14
|
+
CONNECTING: Connection in progress.
|
|
15
|
+
CONNECTED: Successfully connected.
|
|
16
|
+
FAILED: Connection failed.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
DISCONNECTING = "DISCONNECTING"
|
|
20
|
+
DISCONNECTED = "DISCONNECTED"
|
|
21
|
+
CONNECTING = "CONNECTING"
|
|
22
|
+
CONNECTED = "CONNECTED"
|
|
23
|
+
FAILED = "FAILED"
|
|
24
|
+
|
|
25
|
+
@staticmethod
|
|
26
|
+
def create_state_machine() -> StateMachine:
|
|
27
|
+
"""Create and configure state machine for connection management.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
Configured StateMachine with connection state transitions.
|
|
31
|
+
"""
|
|
32
|
+
sm = StateMachine(ConnectionState.DISCONNECTED)
|
|
33
|
+
|
|
34
|
+
# Define valid transitions
|
|
35
|
+
sm.define_transition(
|
|
36
|
+
"connect", {ConnectionState.DISCONNECTED, ConnectionState.FAILED}
|
|
37
|
+
)
|
|
38
|
+
sm.define_transition(
|
|
39
|
+
"disconnect", {ConnectionState.CONNECTED, ConnectionState.CONNECTING}
|
|
40
|
+
)
|
|
41
|
+
sm.define_transition(
|
|
42
|
+
"connecting", {ConnectionState.DISCONNECTED, ConnectionState.FAILED}
|
|
43
|
+
)
|
|
44
|
+
sm.define_transition("connected", {ConnectionState.CONNECTING})
|
|
45
|
+
sm.define_transition(
|
|
46
|
+
"disconnecting", {ConnectionState.CONNECTED, ConnectionState.CONNECTING}
|
|
47
|
+
)
|
|
48
|
+
sm.define_transition("disconnected", {ConnectionState.DISCONNECTING})
|
|
49
|
+
sm.define_transition(
|
|
50
|
+
"failed",
|
|
51
|
+
{
|
|
52
|
+
ConnectionState.CONNECTING,
|
|
53
|
+
ConnectionState.CONNECTED,
|
|
54
|
+
ConnectionState.DISCONNECTING,
|
|
55
|
+
},
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
return sm
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Status message models for terminal UI."""
|
|
2
|
+
|
|
3
|
+
from textual.message import Message
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class StatusMessageChanged(Message):
|
|
7
|
+
"""Message posted when status message changes."""
|
|
8
|
+
|
|
9
|
+
def __init__(self, message: str) -> None:
|
|
10
|
+
"""Initialize the message.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
message: The status message to display.
|
|
14
|
+
"""
|
|
15
|
+
super().__init__()
|
|
16
|
+
self.message = message
|
|
@@ -17,7 +17,7 @@ from twisted.internet.interfaces import IAddress, IConnector
|
|
|
17
17
|
from twisted.internet.posixbase import PosixReactorBase
|
|
18
18
|
from twisted.python.failure import Failure
|
|
19
19
|
|
|
20
|
-
from xp.models import ConbusClientConfig
|
|
20
|
+
from xp.models import ConbusClientConfig, ModuleTypeCode
|
|
21
21
|
from xp.models.protocol.conbus_protocol import (
|
|
22
22
|
TelegramReceivedEvent,
|
|
23
23
|
)
|
|
@@ -168,9 +168,6 @@ class ConbusEventProtocol(protocol.Protocol, protocol.ClientFactory):
|
|
|
168
168
|
|
|
169
169
|
Args:
|
|
170
170
|
data: Raw telegram payload (without checksum/framing).
|
|
171
|
-
|
|
172
|
-
Raises:
|
|
173
|
-
IOError: If transport is not open.
|
|
174
171
|
"""
|
|
175
172
|
self.on_send_frame.emit(data)
|
|
176
173
|
|
|
@@ -180,8 +177,9 @@ class ConbusEventProtocol(protocol.Protocol, protocol.ClientFactory):
|
|
|
180
177
|
frame = b"<" + frame_data.encode() + b">"
|
|
181
178
|
|
|
182
179
|
if not self.transport:
|
|
183
|
-
self.logger.info("Invalid transport")
|
|
184
|
-
|
|
180
|
+
self.logger.info("Invalid transport, connection closed.")
|
|
181
|
+
self.on_connection_failed.emit(Failure("Invalid transport."))
|
|
182
|
+
return
|
|
185
183
|
|
|
186
184
|
self.logger.debug(f"Sending frame: {frame.decode()}")
|
|
187
185
|
self.transport.write(frame) # type: ignore
|
|
@@ -211,6 +209,21 @@ class ConbusEventProtocol(protocol.Protocol, protocol.ClientFactory):
|
|
|
211
209
|
)
|
|
212
210
|
self.send_raw_telegram(payload)
|
|
213
211
|
|
|
212
|
+
def send_event_telegram(
|
|
213
|
+
self, module_type_code: ModuleTypeCode, link_number: int, input_number: int
|
|
214
|
+
) -> None:
|
|
215
|
+
"""Send telegram with specified parameters.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
module_type_code: Type code of module.
|
|
219
|
+
link_number: Link number.
|
|
220
|
+
input_number: Input number.
|
|
221
|
+
"""
|
|
222
|
+
payload = (
|
|
223
|
+
f"E" f"{module_type_code}" f"L{link_number:02d}" f"I{input_number:02d}"
|
|
224
|
+
)
|
|
225
|
+
self.send_raw_telegram(payload)
|
|
226
|
+
|
|
214
227
|
def send_raw_telegram(self, payload: str) -> None:
|
|
215
228
|
"""Send telegram with specified parameters.
|
|
216
229
|
|
xp/term/protocol.py
CHANGED
|
@@ -7,6 +7,7 @@ from textual.app import App, ComposeResult
|
|
|
7
7
|
from textual.containers import Horizontal
|
|
8
8
|
|
|
9
9
|
from xp.models.term import ProtocolKeysConfig
|
|
10
|
+
from xp.models.term.status_message import StatusMessageChanged
|
|
10
11
|
from xp.term.widgets.help_menu import HelpMenuWidget
|
|
11
12
|
from xp.term.widgets.protocol_log import ProtocolLogWidget
|
|
12
13
|
from xp.term.widgets.status_footer import StatusFooterWidget
|
|
@@ -125,3 +126,12 @@ class ProtocolMonitorApp(App[None]):
|
|
|
125
126
|
"""
|
|
126
127
|
if self.footer_widget:
|
|
127
128
|
self.footer_widget.update_status(state)
|
|
129
|
+
|
|
130
|
+
def on_status_message_changed(self, message: StatusMessageChanged) -> None:
|
|
131
|
+
"""Handle status message changes from protocol widget.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
message: Message containing the status text.
|
|
135
|
+
"""
|
|
136
|
+
if self.footer_widget:
|
|
137
|
+
self.footer_widget.update_message(message.message)
|
xp/term/protocol.tcss
CHANGED
xp/term/widgets/protocol_log.py
CHANGED
|
@@ -2,71 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import logging
|
|
5
|
-
from enum import Enum
|
|
6
5
|
from typing import Any, Optional
|
|
7
6
|
|
|
8
|
-
from textual.message import Message
|
|
9
7
|
from textual.reactive import reactive
|
|
10
8
|
from textual.widget import Widget
|
|
11
9
|
from textual.widgets import RichLog
|
|
10
|
+
from twisted.python.failure import Failure
|
|
12
11
|
|
|
13
12
|
from xp.models.protocol.conbus_protocol import TelegramReceivedEvent
|
|
13
|
+
from xp.models.term.connection_state import ConnectionState
|
|
14
|
+
from xp.models.term.status_message import StatusMessageChanged
|
|
14
15
|
from xp.services.conbus.conbus_receive_service import ConbusReceiveService
|
|
15
16
|
from xp.services.protocol import ConbusEventProtocol
|
|
16
|
-
from xp.utils.state_machine import StateMachine
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class ConnectionState(str, Enum):
|
|
20
|
-
"""Connection state enumeration.
|
|
21
|
-
|
|
22
|
-
Attributes:
|
|
23
|
-
DISCONNECTING: Disconnecting to server.
|
|
24
|
-
DISCONNECTED: Not connected to server.
|
|
25
|
-
CONNECTING: Connection in progress.
|
|
26
|
-
CONNECTED: Successfully connected.
|
|
27
|
-
FAILED: Connection failed.
|
|
28
|
-
"""
|
|
29
|
-
|
|
30
|
-
DISCONNECTING = "DISCONNECTING"
|
|
31
|
-
DISCONNECTED = "DISCONNECTED"
|
|
32
|
-
CONNECTING = "CONNECTING"
|
|
33
|
-
CONNECTED = "CONNECTED"
|
|
34
|
-
FAILED = "FAILED"
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def create_connection_state_machine() -> StateMachine:
|
|
38
|
-
"""Create and configure state machine for connection management.
|
|
39
|
-
|
|
40
|
-
Returns:
|
|
41
|
-
Configured StateMachine with connection state transitions.
|
|
42
|
-
"""
|
|
43
|
-
sm = StateMachine(ConnectionState.DISCONNECTED)
|
|
44
|
-
|
|
45
|
-
# Define valid transitions
|
|
46
|
-
sm.define_transition(
|
|
47
|
-
"connect", {ConnectionState.DISCONNECTED, ConnectionState.FAILED}
|
|
48
|
-
)
|
|
49
|
-
sm.define_transition(
|
|
50
|
-
"disconnect", {ConnectionState.CONNECTED, ConnectionState.CONNECTING}
|
|
51
|
-
)
|
|
52
|
-
sm.define_transition(
|
|
53
|
-
"connecting", {ConnectionState.DISCONNECTED, ConnectionState.FAILED}
|
|
54
|
-
)
|
|
55
|
-
sm.define_transition("connected", {ConnectionState.CONNECTING})
|
|
56
|
-
sm.define_transition(
|
|
57
|
-
"disconnecting", {ConnectionState.CONNECTED, ConnectionState.CONNECTING}
|
|
58
|
-
)
|
|
59
|
-
sm.define_transition("disconnected", {ConnectionState.DISCONNECTING})
|
|
60
|
-
sm.define_transition(
|
|
61
|
-
"failed",
|
|
62
|
-
{
|
|
63
|
-
ConnectionState.CONNECTING,
|
|
64
|
-
ConnectionState.CONNECTED,
|
|
65
|
-
ConnectionState.DISCONNECTING,
|
|
66
|
-
},
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
return sm
|
|
70
17
|
|
|
71
18
|
|
|
72
19
|
class ProtocolLogWidget(Widget):
|
|
@@ -84,18 +31,6 @@ class ProtocolLogWidget(Widget):
|
|
|
84
31
|
log_widget: RichLog widget for displaying messages.
|
|
85
32
|
"""
|
|
86
33
|
|
|
87
|
-
class StatusMessageChanged(Message):
|
|
88
|
-
"""Message posted when status message changes."""
|
|
89
|
-
|
|
90
|
-
def __init__(self, message: str) -> None:
|
|
91
|
-
"""Initialize the message.
|
|
92
|
-
|
|
93
|
-
Args:
|
|
94
|
-
message: The status message to display.
|
|
95
|
-
"""
|
|
96
|
-
super().__init__()
|
|
97
|
-
self.message = message
|
|
98
|
-
|
|
99
34
|
connection_state = reactive(ConnectionState.DISCONNECTED)
|
|
100
35
|
|
|
101
36
|
def __init__(self, container: Any) -> None:
|
|
@@ -111,7 +46,7 @@ class ProtocolLogWidget(Widget):
|
|
|
111
46
|
self.service: Optional[ConbusReceiveService] = None
|
|
112
47
|
self.logger = logging.getLogger(__name__)
|
|
113
48
|
self.log_widget: Optional[RichLog] = None
|
|
114
|
-
self._state_machine =
|
|
49
|
+
self._state_machine = ConnectionState.create_state_machine()
|
|
115
50
|
|
|
116
51
|
def compose(self) -> Any:
|
|
117
52
|
"""Compose the widget layout.
|
|
@@ -134,6 +69,7 @@ class ProtocolLogWidget(Widget):
|
|
|
134
69
|
|
|
135
70
|
# Connect psygnal signals
|
|
136
71
|
self.protocol.on_connection_made.connect(self._on_connection_made)
|
|
72
|
+
self.protocol.on_connection_failed.connect(self._on_connection_failed)
|
|
137
73
|
self.protocol.on_telegram_received.connect(self._on_telegram_received)
|
|
138
74
|
self.protocol.on_telegram_sent.connect(self._on_telegram_sent)
|
|
139
75
|
self.protocol.on_timeout.connect(self._on_timeout)
|
|
@@ -169,10 +105,8 @@ class ProtocolLogWidget(Widget):
|
|
|
169
105
|
# Transition to CONNECTING
|
|
170
106
|
if self._state_machine.transition("connecting", ConnectionState.CONNECTING):
|
|
171
107
|
self.connection_state = ConnectionState.CONNECTING
|
|
172
|
-
self.
|
|
173
|
-
self.
|
|
174
|
-
f"Connecting to {self.protocol.cli_config.ip}:{self.protocol.cli_config.port}..."
|
|
175
|
-
)
|
|
108
|
+
self.post_status(
|
|
109
|
+
f"Connecting to {self.protocol.cli_config.ip}:{self.protocol.cli_config.port}..."
|
|
176
110
|
)
|
|
177
111
|
|
|
178
112
|
# Store protocol reference
|
|
@@ -222,15 +156,16 @@ class ProtocolLogWidget(Widget):
|
|
|
222
156
|
|
|
223
157
|
except Exception as e:
|
|
224
158
|
self.logger.error(f"Connection failed: {e}")
|
|
159
|
+
self.post_status(f"Connection failed: {e}")
|
|
225
160
|
# Transition to FAILED
|
|
226
161
|
if self._state_machine.transition("failed", ConnectionState.FAILED):
|
|
227
162
|
self.connection_state = ConnectionState.FAILED
|
|
228
|
-
self.post_message(self.StatusMessageChanged(f"Connection error: {e}"))
|
|
229
163
|
|
|
230
164
|
def _start_connection(self) -> None:
|
|
231
165
|
"""Start connection (sync wrapper for async method)."""
|
|
232
166
|
# Use run_worker to run async method from sync context
|
|
233
167
|
self.logger.debug("Start connection")
|
|
168
|
+
self.post_status("Start connection")
|
|
234
169
|
self.run_worker(self._start_connection_async(), exclusive=True)
|
|
235
170
|
|
|
236
171
|
def _on_connection_made(self) -> None:
|
|
@@ -239,16 +174,26 @@ class ProtocolLogWidget(Widget):
|
|
|
239
174
|
Sets state to CONNECTED and displays success message.
|
|
240
175
|
"""
|
|
241
176
|
self.logger.debug("Connection made")
|
|
177
|
+
self.post_status("Connection made")
|
|
242
178
|
# Transition to CONNECTED
|
|
243
179
|
if self._state_machine.transition("connected", ConnectionState.CONNECTED):
|
|
244
180
|
self.connection_state = ConnectionState.CONNECTED
|
|
245
181
|
if self.protocol:
|
|
246
|
-
self.
|
|
247
|
-
self.
|
|
248
|
-
f"Connected to {self.protocol.cli_config.ip}:{self.protocol.cli_config.port}"
|
|
249
|
-
)
|
|
182
|
+
self.post_status(
|
|
183
|
+
f"Connected to {self.protocol.cli_config.ip}:{self.protocol.cli_config.port}"
|
|
250
184
|
)
|
|
251
185
|
|
|
186
|
+
def _on_connection_failed(self, failure: Failure) -> None:
|
|
187
|
+
"""Handle connection failed signal.
|
|
188
|
+
|
|
189
|
+
Sets state to DISCONNECTED and displays success message.
|
|
190
|
+
"""
|
|
191
|
+
self.logger.debug("Connection failed")
|
|
192
|
+
self.post_status(failure.getErrorMessage())
|
|
193
|
+
# Transition to CONNECTED
|
|
194
|
+
if self._state_machine.transition("disconnected", ConnectionState.DISCONNECTED):
|
|
195
|
+
self.connection_state = ConnectionState.DISCONNECTED
|
|
196
|
+
|
|
252
197
|
def _on_telegram_received(self, event: TelegramReceivedEvent) -> None:
|
|
253
198
|
"""Handle telegram received signal.
|
|
254
199
|
|
|
@@ -289,7 +234,15 @@ class ProtocolLogWidget(Widget):
|
|
|
289
234
|
if self._state_machine.transition("failed", ConnectionState.FAILED):
|
|
290
235
|
self.connection_state = ConnectionState.FAILED
|
|
291
236
|
self.logger.error(f"Connection failed: {error}")
|
|
292
|
-
self.
|
|
237
|
+
self.post_status(f"Failed: {error}")
|
|
238
|
+
|
|
239
|
+
def post_status(self, message: str) -> None:
|
|
240
|
+
"""Post status message.
|
|
241
|
+
|
|
242
|
+
Args:
|
|
243
|
+
message: message to be sent to status bar.
|
|
244
|
+
"""
|
|
245
|
+
self.post_message(StatusMessageChanged(message))
|
|
293
246
|
|
|
294
247
|
def connect(self) -> None:
|
|
295
248
|
"""Connect to Conbus server.
|
|
@@ -326,7 +279,7 @@ class ProtocolLogWidget(Widget):
|
|
|
326
279
|
"disconnecting", ConnectionState.DISCONNECTING
|
|
327
280
|
):
|
|
328
281
|
self.connection_state = ConnectionState.DISCONNECTING
|
|
329
|
-
self.
|
|
282
|
+
self.post_status("Disconnecting...")
|
|
330
283
|
|
|
331
284
|
if self.protocol:
|
|
332
285
|
self.protocol.disconnect()
|
|
@@ -334,7 +287,7 @@ class ProtocolLogWidget(Widget):
|
|
|
334
287
|
# Transition to DISCONNECTED
|
|
335
288
|
if self._state_machine.transition("disconnected", ConnectionState.DISCONNECTED):
|
|
336
289
|
self.connection_state = ConnectionState.DISCONNECTED
|
|
337
|
-
self.
|
|
290
|
+
self.post_status("Disconnected")
|
|
338
291
|
|
|
339
292
|
def send_telegram(self, name: str, telegram: str) -> None:
|
|
340
293
|
"""Send a raw telegram string.
|
|
@@ -349,19 +302,19 @@ class ProtocolLogWidget(Widget):
|
|
|
349
302
|
|
|
350
303
|
try:
|
|
351
304
|
# Remove angle brackets if present
|
|
352
|
-
self.
|
|
305
|
+
self.post_status(f"{name} sent.")
|
|
353
306
|
# Send raw telegram
|
|
354
307
|
self.protocol.send_raw_telegram(telegram)
|
|
355
308
|
|
|
356
309
|
except Exception as e:
|
|
357
310
|
self.logger.error(f"Failed to send telegram: {e}")
|
|
358
|
-
self.
|
|
311
|
+
self.post_status(f"Failed: {e}")
|
|
359
312
|
|
|
360
313
|
def clear_log(self) -> None:
|
|
361
314
|
"""Clear the protocol log widget."""
|
|
362
315
|
if self.log_widget:
|
|
363
316
|
self.log_widget.clear()
|
|
364
|
-
self.
|
|
317
|
+
self.post_status("Log cleared")
|
|
365
318
|
|
|
366
319
|
def on_unmount(self) -> None:
|
|
367
320
|
"""Clean up when widget unmounts.
|
xp/term/widgets/status_footer.py
CHANGED
|
@@ -25,6 +25,7 @@ class StatusFooterWidget(Horizontal):
|
|
|
25
25
|
kwargs: Additional keyword arguments for Horizontal.
|
|
26
26
|
"""
|
|
27
27
|
super().__init__(*args, **kwargs)
|
|
28
|
+
self.status_text_widget: Static = Static("", id="status-text")
|
|
28
29
|
self.status_widget: Static = Static("○", id="status-line")
|
|
29
30
|
|
|
30
31
|
def compose(self) -> ComposeResult:
|
|
@@ -34,6 +35,7 @@ class StatusFooterWidget(Horizontal):
|
|
|
34
35
|
Footer and status indicator widgets.
|
|
35
36
|
"""
|
|
36
37
|
yield Footer()
|
|
38
|
+
yield self.status_text_widget
|
|
37
39
|
yield self.status_widget
|
|
38
40
|
|
|
39
41
|
def update_status(self, state: Any) -> None:
|
|
@@ -51,3 +53,11 @@ class StatusFooterWidget(Horizontal):
|
|
|
51
53
|
"DISCONNECTED": "○",
|
|
52
54
|
}.get(state.value, "○")
|
|
53
55
|
self.status_widget.update(dot)
|
|
56
|
+
|
|
57
|
+
def update_message(self, message: str) -> None:
|
|
58
|
+
"""Update status text with message.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
message: Status message to display.
|
|
62
|
+
"""
|
|
63
|
+
self.status_text_widget.update(message)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|