conson-xp 1.18.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.18.0.dist-info/METADATA +412 -0
- conson_xp-1.18.0.dist-info/RECORD +176 -0
- conson_xp-1.18.0.dist-info/WHEEL +4 -0
- conson_xp-1.18.0.dist-info/entry_points.txt +5 -0
- conson_xp-1.18.0.dist-info/licenses/LICENSE +29 -0
- xp/__init__.py +9 -0
- xp/cli/__init__.py +5 -0
- xp/cli/__main__.py +6 -0
- xp/cli/commands/__init__.py +153 -0
- xp/cli/commands/conbus/__init__.py +25 -0
- xp/cli/commands/conbus/conbus.py +128 -0
- xp/cli/commands/conbus/conbus_actiontable_commands.py +233 -0
- xp/cli/commands/conbus/conbus_autoreport_commands.py +108 -0
- xp/cli/commands/conbus/conbus_blink_commands.py +163 -0
- xp/cli/commands/conbus/conbus_config_commands.py +29 -0
- xp/cli/commands/conbus/conbus_custom_commands.py +57 -0
- xp/cli/commands/conbus/conbus_datapoint_commands.py +113 -0
- xp/cli/commands/conbus/conbus_discover_commands.py +61 -0
- xp/cli/commands/conbus/conbus_event_commands.py +81 -0
- xp/cli/commands/conbus/conbus_lightlevel_commands.py +207 -0
- xp/cli/commands/conbus/conbus_linknumber_commands.py +102 -0
- xp/cli/commands/conbus/conbus_modulenumber_commands.py +104 -0
- xp/cli/commands/conbus/conbus_msactiontable_commands.py +94 -0
- xp/cli/commands/conbus/conbus_output_commands.py +163 -0
- xp/cli/commands/conbus/conbus_raw_commands.py +62 -0
- xp/cli/commands/conbus/conbus_receive_commands.py +59 -0
- xp/cli/commands/conbus/conbus_scan_commands.py +58 -0
- xp/cli/commands/file_commands.py +186 -0
- xp/cli/commands/homekit/__init__.py +3 -0
- xp/cli/commands/homekit/homekit.py +118 -0
- xp/cli/commands/homekit/homekit_start_commands.py +43 -0
- xp/cli/commands/module_commands.py +187 -0
- xp/cli/commands/reverse_proxy_commands.py +178 -0
- xp/cli/commands/server/__init__.py +3 -0
- xp/cli/commands/server/server_commands.py +135 -0
- xp/cli/commands/telegram/__init__.py +5 -0
- xp/cli/commands/telegram/telegram.py +41 -0
- xp/cli/commands/telegram/telegram_blink_commands.py +79 -0
- xp/cli/commands/telegram/telegram_checksum_commands.py +112 -0
- xp/cli/commands/telegram/telegram_discover_commands.py +41 -0
- xp/cli/commands/telegram/telegram_linknumber_commands.py +86 -0
- xp/cli/commands/telegram/telegram_parse_commands.py +75 -0
- xp/cli/commands/telegram/telegram_version_commands.py +52 -0
- xp/cli/main.py +87 -0
- xp/cli/utils/__init__.py +1 -0
- xp/cli/utils/click_tree.py +57 -0
- xp/cli/utils/datapoint_type_choice.py +57 -0
- xp/cli/utils/decorators.py +351 -0
- xp/cli/utils/error_handlers.py +201 -0
- xp/cli/utils/formatters.py +312 -0
- xp/cli/utils/module_type_choice.py +56 -0
- xp/cli/utils/serial_number_type.py +52 -0
- xp/cli/utils/system_function_choice.py +57 -0
- xp/cli/utils/xp_module_type.py +53 -0
- xp/connection/__init__.py +13 -0
- xp/connection/exceptions.py +22 -0
- xp/models/__init__.py +36 -0
- xp/models/actiontable/__init__.py +1 -0
- xp/models/actiontable/actiontable.py +43 -0
- xp/models/actiontable/msactiontable_xp20.py +53 -0
- xp/models/actiontable/msactiontable_xp24.py +58 -0
- xp/models/actiontable/msactiontable_xp33.py +65 -0
- xp/models/conbus/__init__.py +1 -0
- xp/models/conbus/conbus.py +87 -0
- xp/models/conbus/conbus_autoreport.py +67 -0
- xp/models/conbus/conbus_blink.py +80 -0
- xp/models/conbus/conbus_client_config.py +55 -0
- xp/models/conbus/conbus_connection_status.py +40 -0
- xp/models/conbus/conbus_custom.py +58 -0
- xp/models/conbus/conbus_datapoint.py +89 -0
- xp/models/conbus/conbus_discover.py +64 -0
- xp/models/conbus/conbus_event_raw.py +47 -0
- xp/models/conbus/conbus_lightlevel.py +52 -0
- xp/models/conbus/conbus_linknumber.py +54 -0
- xp/models/conbus/conbus_output.py +57 -0
- xp/models/conbus/conbus_raw.py +45 -0
- xp/models/conbus/conbus_receive.py +42 -0
- xp/models/conbus/conbus_writeconfig.py +60 -0
- xp/models/homekit/__init__.py +1 -0
- xp/models/homekit/homekit_accessory.py +35 -0
- xp/models/homekit/homekit_config.py +106 -0
- xp/models/homekit/homekit_conson_config.py +86 -0
- xp/models/log_entry.py +130 -0
- xp/models/protocol/__init__.py +1 -0
- xp/models/protocol/conbus_protocol.py +312 -0
- xp/models/response.py +42 -0
- xp/models/telegram/__init__.py +1 -0
- xp/models/telegram/action_type.py +31 -0
- xp/models/telegram/datapoint_type.py +82 -0
- xp/models/telegram/event_telegram.py +140 -0
- xp/models/telegram/event_type.py +15 -0
- xp/models/telegram/input_action_type.py +69 -0
- xp/models/telegram/input_type.py +17 -0
- xp/models/telegram/module_type.py +188 -0
- xp/models/telegram/module_type_code.py +205 -0
- xp/models/telegram/output_telegram.py +103 -0
- xp/models/telegram/reply_telegram.py +297 -0
- xp/models/telegram/system_function.py +116 -0
- xp/models/telegram/system_telegram.py +94 -0
- xp/models/telegram/telegram.py +28 -0
- xp/models/telegram/telegram_type.py +19 -0
- xp/models/telegram/timeparam_type.py +51 -0
- xp/models/write_config_type.py +33 -0
- xp/services/__init__.py +26 -0
- xp/services/actiontable/__init__.py +1 -0
- xp/services/actiontable/actiontable_serializer.py +273 -0
- xp/services/actiontable/msactiontable_serializer.py +7 -0
- xp/services/actiontable/msactiontable_xp20_serializer.py +169 -0
- xp/services/actiontable/msactiontable_xp24_serializer.py +120 -0
- xp/services/actiontable/msactiontable_xp33_serializer.py +239 -0
- xp/services/conbus/__init__.py +1 -0
- xp/services/conbus/actiontable/__init__.py +1 -0
- xp/services/conbus/actiontable/actiontable_download_service.py +158 -0
- xp/services/conbus/actiontable/actiontable_list_service.py +91 -0
- xp/services/conbus/actiontable/actiontable_show_service.py +89 -0
- xp/services/conbus/actiontable/actiontable_upload_service.py +211 -0
- xp/services/conbus/actiontable/msactiontable_service.py +232 -0
- xp/services/conbus/conbus_blink_all_service.py +181 -0
- xp/services/conbus/conbus_blink_service.py +158 -0
- xp/services/conbus/conbus_custom_service.py +156 -0
- xp/services/conbus/conbus_datapoint_queryall_service.py +182 -0
- xp/services/conbus/conbus_datapoint_service.py +170 -0
- xp/services/conbus/conbus_discover_service.py +312 -0
- xp/services/conbus/conbus_event_raw_service.py +181 -0
- xp/services/conbus/conbus_output_service.py +194 -0
- xp/services/conbus/conbus_raw_service.py +122 -0
- xp/services/conbus/conbus_receive_service.py +115 -0
- xp/services/conbus/conbus_scan_service.py +150 -0
- xp/services/conbus/write_config_service.py +194 -0
- xp/services/homekit/__init__.py +1 -0
- xp/services/homekit/homekit_cache_service.py +307 -0
- xp/services/homekit/homekit_conbus_service.py +93 -0
- xp/services/homekit/homekit_config_validator.py +310 -0
- xp/services/homekit/homekit_conson_validator.py +121 -0
- xp/services/homekit/homekit_dimminglight.py +182 -0
- xp/services/homekit/homekit_dimminglight_service.py +148 -0
- xp/services/homekit/homekit_hap_service.py +342 -0
- xp/services/homekit/homekit_lightbulb.py +120 -0
- xp/services/homekit/homekit_lightbulb_service.py +86 -0
- xp/services/homekit/homekit_module_service.py +56 -0
- xp/services/homekit/homekit_outlet.py +168 -0
- xp/services/homekit/homekit_outlet_service.py +121 -0
- xp/services/homekit/homekit_service.py +359 -0
- xp/services/log_file_service.py +309 -0
- xp/services/module_type_service.py +257 -0
- xp/services/protocol/__init__.py +21 -0
- xp/services/protocol/conbus_event_protocol.py +360 -0
- xp/services/protocol/conbus_protocol.py +318 -0
- xp/services/protocol/protocol_factory.py +78 -0
- xp/services/protocol/telegram_protocol.py +264 -0
- xp/services/reverse_proxy_service.py +435 -0
- xp/services/server/__init__.py +1 -0
- xp/services/server/base_server_service.py +366 -0
- xp/services/server/cp20_server_service.py +65 -0
- xp/services/server/device_service_factory.py +94 -0
- xp/services/server/server_service.py +428 -0
- xp/services/server/xp130_server_service.py +67 -0
- xp/services/server/xp20_server_service.py +92 -0
- xp/services/server/xp230_server_service.py +58 -0
- xp/services/server/xp24_server_service.py +245 -0
- xp/services/server/xp33_server_service.py +535 -0
- xp/services/telegram/__init__.py +1 -0
- xp/services/telegram/telegram_blink_service.py +138 -0
- xp/services/telegram/telegram_checksum_service.py +149 -0
- xp/services/telegram/telegram_datapoint_service.py +82 -0
- xp/services/telegram/telegram_discover_service.py +277 -0
- xp/services/telegram/telegram_link_number_service.py +216 -0
- xp/services/telegram/telegram_output_service.py +322 -0
- xp/services/telegram/telegram_service.py +380 -0
- xp/services/telegram/telegram_version_service.py +288 -0
- xp/utils/__init__.py +12 -0
- xp/utils/checksum.py +61 -0
- xp/utils/dependencies.py +531 -0
- xp/utils/event_helper.py +31 -0
- xp/utils/serialization.py +205 -0
- xp/utils/time_utils.py +134 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"""Module type operations CLI commands."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Any, Dict, Union
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
from click import Context
|
|
8
|
+
from click_help_colors import HelpColorsGroup
|
|
9
|
+
|
|
10
|
+
from xp.cli.utils.decorators import list_command
|
|
11
|
+
from xp.cli.utils.error_handlers import CLIErrorHandler
|
|
12
|
+
from xp.cli.utils.formatters import ListFormatter, OutputFormatter
|
|
13
|
+
from xp.services.module_type_service import ModuleTypeNotFoundError, ModuleTypeService
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@click.group(
|
|
17
|
+
cls=HelpColorsGroup, help_headers_color="yellow", help_options_color="green"
|
|
18
|
+
)
|
|
19
|
+
def module() -> None:
|
|
20
|
+
"""Perform module type operations."""
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@module.command("info")
|
|
25
|
+
@click.argument("identifier")
|
|
26
|
+
@click.pass_context
|
|
27
|
+
@list_command(ModuleTypeNotFoundError)
|
|
28
|
+
def module_info(ctx: Context, identifier: str) -> None:
|
|
29
|
+
r"""Get information about a module type by code or name.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
ctx: Click context object.
|
|
33
|
+
identifier: Module code or name.
|
|
34
|
+
|
|
35
|
+
Examples:
|
|
36
|
+
\b
|
|
37
|
+
xp module info 14
|
|
38
|
+
xp module info XP2606
|
|
39
|
+
"""
|
|
40
|
+
service: ModuleTypeService = (
|
|
41
|
+
ctx.obj.get("container").get_container().resolve(ModuleTypeService)
|
|
42
|
+
)
|
|
43
|
+
OutputFormatter(True)
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
# Try to parse as integer first, then as string
|
|
47
|
+
module_id: Union[int, str]
|
|
48
|
+
try:
|
|
49
|
+
module_id = int(identifier)
|
|
50
|
+
except ValueError:
|
|
51
|
+
module_id = identifier
|
|
52
|
+
|
|
53
|
+
module_type = service.get_module_type(module_id)
|
|
54
|
+
click.echo(json.dumps(module_type.to_dict(), indent=2))
|
|
55
|
+
|
|
56
|
+
except ModuleTypeNotFoundError as e:
|
|
57
|
+
CLIErrorHandler.handle_not_found_error(e, "module type", identifier)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@module.command("list")
|
|
61
|
+
@click.option("--category", "-c", help="Filter by category")
|
|
62
|
+
@click.option(
|
|
63
|
+
"--group-by-category", "-g", is_flag=True, help="Group modules by category"
|
|
64
|
+
)
|
|
65
|
+
@click.pass_context
|
|
66
|
+
@list_command(Exception)
|
|
67
|
+
def module_list(ctx: Context, category: str, group_by_category: bool) -> None:
|
|
68
|
+
r"""List module types, optionally filtered by category.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
ctx: Click context object.
|
|
72
|
+
category: Filter by category.
|
|
73
|
+
group_by_category: Group modules by category.
|
|
74
|
+
|
|
75
|
+
Examples:
|
|
76
|
+
\b
|
|
77
|
+
xp module list
|
|
78
|
+
xp module list --category "Interface Panels"
|
|
79
|
+
xp module list --group-by-category
|
|
80
|
+
"""
|
|
81
|
+
service: ModuleTypeService = (
|
|
82
|
+
ctx.obj.get("container").get_container().resolve(ModuleTypeService)
|
|
83
|
+
)
|
|
84
|
+
ListFormatter(True)
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
if category:
|
|
88
|
+
modules = service.get_modules_by_category(category)
|
|
89
|
+
if not modules:
|
|
90
|
+
click.echo(json.dumps({"modules": [], "category": category}))
|
|
91
|
+
return
|
|
92
|
+
else:
|
|
93
|
+
modules = service.list_all_modules()
|
|
94
|
+
|
|
95
|
+
if group_by_category:
|
|
96
|
+
categories = service.list_modules_by_category()
|
|
97
|
+
output: Dict[str, Any] = {
|
|
98
|
+
"modules_by_category": {
|
|
99
|
+
cat: [mod.to_dict() for mod in mods]
|
|
100
|
+
for cat, mods in categories.items()
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
else:
|
|
104
|
+
output = {
|
|
105
|
+
"modules": [_module.to_dict() for _module in modules],
|
|
106
|
+
"count": len(modules),
|
|
107
|
+
}
|
|
108
|
+
click.echo(json.dumps(output, indent=2))
|
|
109
|
+
|
|
110
|
+
except Exception as e:
|
|
111
|
+
CLIErrorHandler.handle_service_error(e, "module listing")
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@module.command("search")
|
|
115
|
+
@click.argument("query")
|
|
116
|
+
@click.option(
|
|
117
|
+
"--field",
|
|
118
|
+
multiple=True,
|
|
119
|
+
type=click.Choice(["name", "description"]),
|
|
120
|
+
help="Fields to search in (default: both)",
|
|
121
|
+
)
|
|
122
|
+
@click.pass_context
|
|
123
|
+
@list_command(Exception)
|
|
124
|
+
def module_search(ctx: Context, query: str, field: tuple) -> None:
|
|
125
|
+
r"""Search for module types by name or description.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
ctx: Click context object.
|
|
129
|
+
query: Search query.
|
|
130
|
+
field: Fields to search in.
|
|
131
|
+
|
|
132
|
+
Examples:
|
|
133
|
+
\b
|
|
134
|
+
xp module search "push button"
|
|
135
|
+
xp module search --field name "XP"
|
|
136
|
+
"""
|
|
137
|
+
service: ModuleTypeService = (
|
|
138
|
+
ctx.obj.get("container").get_container().resolve(ModuleTypeService)
|
|
139
|
+
)
|
|
140
|
+
ListFormatter(True)
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
search_fields = list(field) if field else ["name", "description"]
|
|
144
|
+
matching_modules = service.search_modules(query, search_fields)
|
|
145
|
+
|
|
146
|
+
output = {
|
|
147
|
+
"query": query,
|
|
148
|
+
"search_fields": search_fields,
|
|
149
|
+
"matches": [_module.to_dict() for _module in matching_modules],
|
|
150
|
+
"count": len(matching_modules),
|
|
151
|
+
}
|
|
152
|
+
click.echo(json.dumps(output, indent=2))
|
|
153
|
+
|
|
154
|
+
except Exception as e:
|
|
155
|
+
CLIErrorHandler.handle_service_error(e, "module search", {"query": query})
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@module.command("categories")
|
|
159
|
+
@click.pass_context
|
|
160
|
+
@list_command(Exception)
|
|
161
|
+
def module_categories(ctx: Context) -> None:
|
|
162
|
+
r"""List all available module categories.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
ctx: Click context object.
|
|
166
|
+
|
|
167
|
+
Examples:
|
|
168
|
+
\b
|
|
169
|
+
xp module categories
|
|
170
|
+
"""
|
|
171
|
+
service: ModuleTypeService = (
|
|
172
|
+
ctx.obj.get("container").get_container().resolve(ModuleTypeService)
|
|
173
|
+
)
|
|
174
|
+
OutputFormatter(True)
|
|
175
|
+
|
|
176
|
+
try:
|
|
177
|
+
categories = service.list_modules_by_category()
|
|
178
|
+
|
|
179
|
+
output = {
|
|
180
|
+
"categories": {
|
|
181
|
+
category: len(modules) for category, modules in categories.items()
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
click.echo(json.dumps(output, indent=2))
|
|
185
|
+
|
|
186
|
+
except Exception as e:
|
|
187
|
+
CLIErrorHandler.handle_service_error(e, "category listing")
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"""Conbus reverse proxy operations CLI commands."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import signal
|
|
5
|
+
import sys
|
|
6
|
+
from types import FrameType
|
|
7
|
+
from typing import Any, Dict, Optional
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
from click import Context
|
|
11
|
+
from click_help_colors import HelpColorsGroup
|
|
12
|
+
|
|
13
|
+
from xp.cli.utils.decorators import handle_service_errors
|
|
14
|
+
from xp.cli.utils.error_handlers import CLIErrorHandler
|
|
15
|
+
from xp.cli.utils.formatters import OutputFormatter
|
|
16
|
+
from xp.services.reverse_proxy_service import (
|
|
17
|
+
ReverseProxyError,
|
|
18
|
+
ReverseProxyService,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
# Global proxy instance
|
|
22
|
+
global_proxy_instance: Optional[ReverseProxyService] = None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@click.group(
|
|
26
|
+
name="rp",
|
|
27
|
+
cls=HelpColorsGroup,
|
|
28
|
+
help_headers_color="yellow",
|
|
29
|
+
help_options_color="green",
|
|
30
|
+
)
|
|
31
|
+
def reverse_proxy() -> None:
|
|
32
|
+
"""Perform Conbus reverse proxy operations."""
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@reverse_proxy.command("start")
|
|
37
|
+
@click.option(
|
|
38
|
+
"--port", "-p", default=10001, type=int, help="Port to listen on (default: 10001)"
|
|
39
|
+
)
|
|
40
|
+
@click.option("--config", "-c", default="rp.yml", help="Configuration file path")
|
|
41
|
+
@handle_service_errors(ReverseProxyError)
|
|
42
|
+
@click.pass_context
|
|
43
|
+
def start_proxy(ctx: Context, port: int, config: str) -> None:
|
|
44
|
+
r"""Start the Conbus reverse proxy server.
|
|
45
|
+
|
|
46
|
+
The proxy listens on the specified port and forwards all telegrams
|
|
47
|
+
to the target server configured in cli.yml. All traffic is monitored
|
|
48
|
+
and printed with timestamps.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
ctx: Click context object.
|
|
52
|
+
port: Port to listen on.
|
|
53
|
+
config: Configuration file path.
|
|
54
|
+
|
|
55
|
+
Examples:
|
|
56
|
+
\b
|
|
57
|
+
xp rp start
|
|
58
|
+
xp rp start --port 10002 --config my_cli.yml
|
|
59
|
+
|
|
60
|
+
Raises:
|
|
61
|
+
SystemExit: If proxy is already running.
|
|
62
|
+
"""
|
|
63
|
+
global global_proxy_instance
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
# Check if proxy is already running
|
|
67
|
+
if global_proxy_instance and global_proxy_instance.is_running:
|
|
68
|
+
error_response = {
|
|
69
|
+
"success": False,
|
|
70
|
+
"error": "Reverse proxy is already running",
|
|
71
|
+
}
|
|
72
|
+
click.echo(json.dumps(error_response, indent=2))
|
|
73
|
+
raise SystemExit(1)
|
|
74
|
+
|
|
75
|
+
# Load configuration and create proxy instance
|
|
76
|
+
service: ReverseProxyService = (
|
|
77
|
+
ctx.obj.get("container").get_container().resolve(ReverseProxyService)
|
|
78
|
+
)
|
|
79
|
+
global_proxy_instance = service
|
|
80
|
+
|
|
81
|
+
# Handle graceful shutdown on SIGINT
|
|
82
|
+
def signal_handler(signum: int, frame: Optional[FrameType]) -> None:
|
|
83
|
+
"""Handle shutdown signals for graceful proxy termination.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
signum: Signal number received.
|
|
87
|
+
frame: Current stack frame (may be None).
|
|
88
|
+
"""
|
|
89
|
+
if global_proxy_instance and global_proxy_instance.is_running:
|
|
90
|
+
timestamp = global_proxy_instance.timestamp()
|
|
91
|
+
print(f"\n{timestamp} [SHUTDOWN] Received interrupt signal ({signum})")
|
|
92
|
+
print(f"\n{timestamp} [SHUTDOWN] Frame is ({frame})")
|
|
93
|
+
global_proxy_instance.stop_proxy()
|
|
94
|
+
sys.exit(0)
|
|
95
|
+
|
|
96
|
+
signal.signal(signal.SIGINT, signal_handler)
|
|
97
|
+
|
|
98
|
+
# Start proxy (this will block)
|
|
99
|
+
result = global_proxy_instance.start_proxy()
|
|
100
|
+
click.echo(json.dumps(result.to_dict(), indent=2))
|
|
101
|
+
if result.success:
|
|
102
|
+
global_proxy_instance.run_blocking()
|
|
103
|
+
|
|
104
|
+
except ReverseProxyError as e:
|
|
105
|
+
CLIErrorHandler.handle_service_error(
|
|
106
|
+
e, "reverse proxy startup", {"port": port, "config": config}
|
|
107
|
+
)
|
|
108
|
+
except KeyboardInterrupt:
|
|
109
|
+
shutdown_response = {
|
|
110
|
+
"success": True,
|
|
111
|
+
"message": "Reverse proxy shutdown by user",
|
|
112
|
+
}
|
|
113
|
+
click.echo(json.dumps(shutdown_response, indent=2))
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@reverse_proxy.command("stop")
|
|
117
|
+
@handle_service_errors(ReverseProxyError)
|
|
118
|
+
def stop_proxy() -> None:
|
|
119
|
+
r"""Stop the running Conbus reverse proxy server.
|
|
120
|
+
|
|
121
|
+
Examples:
|
|
122
|
+
\b
|
|
123
|
+
xp rp stop
|
|
124
|
+
|
|
125
|
+
Raises:
|
|
126
|
+
SystemExit: If proxy is not running.
|
|
127
|
+
"""
|
|
128
|
+
try:
|
|
129
|
+
if global_proxy_instance is None or not global_proxy_instance.is_running:
|
|
130
|
+
error_response = {
|
|
131
|
+
"success": False,
|
|
132
|
+
"error": "Reverse proxy is not running",
|
|
133
|
+
}
|
|
134
|
+
click.echo(json.dumps(error_response, indent=2))
|
|
135
|
+
raise SystemExit(1)
|
|
136
|
+
|
|
137
|
+
# Stop the proxy
|
|
138
|
+
result = global_proxy_instance.stop_proxy()
|
|
139
|
+
|
|
140
|
+
click.echo(json.dumps(result.to_dict(), indent=2))
|
|
141
|
+
|
|
142
|
+
except ReverseProxyError as e:
|
|
143
|
+
CLIErrorHandler.handle_service_error(e, "reverse proxy stop")
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@reverse_proxy.command("status")
|
|
147
|
+
@handle_service_errors(Exception)
|
|
148
|
+
def proxy_status() -> None:
|
|
149
|
+
r"""Get status of the Conbus reverse proxy server.
|
|
150
|
+
|
|
151
|
+
Shows current running state, listen port, target server,
|
|
152
|
+
and active connection details.
|
|
153
|
+
|
|
154
|
+
Examples:
|
|
155
|
+
\b
|
|
156
|
+
xp rp status
|
|
157
|
+
"""
|
|
158
|
+
OutputFormatter(True)
|
|
159
|
+
|
|
160
|
+
try:
|
|
161
|
+
status_data: Dict[str, Any]
|
|
162
|
+
if global_proxy_instance is None:
|
|
163
|
+
status_data = {
|
|
164
|
+
"running": False,
|
|
165
|
+
"listen_port": None,
|
|
166
|
+
"target_ip": None,
|
|
167
|
+
"target_port": None,
|
|
168
|
+
"active_connections": 0,
|
|
169
|
+
"connections": {},
|
|
170
|
+
}
|
|
171
|
+
else:
|
|
172
|
+
result = global_proxy_instance.get_status()
|
|
173
|
+
status_data = result.data if result.success else {}
|
|
174
|
+
|
|
175
|
+
click.echo(json.dumps(status_data, indent=2))
|
|
176
|
+
|
|
177
|
+
except Exception as e:
|
|
178
|
+
CLIErrorHandler.handle_service_error(e, "reverse proxy status check")
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""Conbus emulator server operations CLI commands."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Any, Dict, Optional
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
from click import Context
|
|
8
|
+
from click_help_colors import HelpColorsGroup
|
|
9
|
+
|
|
10
|
+
from xp.cli.utils.decorators import handle_service_errors
|
|
11
|
+
from xp.cli.utils.error_handlers import ServerErrorHandler
|
|
12
|
+
from xp.cli.utils.formatters import OutputFormatter
|
|
13
|
+
from xp.services.server.server_service import ServerError, ServerService
|
|
14
|
+
|
|
15
|
+
# Global server instance
|
|
16
|
+
_server_instance: Optional[ServerService] = None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@click.group(
|
|
20
|
+
cls=HelpColorsGroup, help_headers_color="yellow", help_options_color="green"
|
|
21
|
+
)
|
|
22
|
+
def server() -> None:
|
|
23
|
+
"""Perform Conbus emulator server operations."""
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@server.command("start")
|
|
28
|
+
@click.option(
|
|
29
|
+
"--port", "-p", default=10001, type=int, help="Port to listen on (default: 10001)"
|
|
30
|
+
)
|
|
31
|
+
@click.option("--config", "-c", default="server.yml", help="Configuration file path")
|
|
32
|
+
@click.pass_context
|
|
33
|
+
@handle_service_errors(ServerError)
|
|
34
|
+
def start_server(ctx: Context, port: int, config: str) -> None:
|
|
35
|
+
r"""Start the Conbus emulator server.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
ctx: Click context object.
|
|
39
|
+
port: Port to listen on.
|
|
40
|
+
config: Configuration file path.
|
|
41
|
+
|
|
42
|
+
Examples:
|
|
43
|
+
\b
|
|
44
|
+
xp server start
|
|
45
|
+
xp server start --port 1001 --config my_config.yml
|
|
46
|
+
|
|
47
|
+
Raises:
|
|
48
|
+
SystemExit: If server is already running.
|
|
49
|
+
"""
|
|
50
|
+
global _server_instance
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
# Check if server is already running
|
|
54
|
+
if _server_instance and _server_instance.is_running:
|
|
55
|
+
error_response = {
|
|
56
|
+
"success": False,
|
|
57
|
+
"error": "Server is already running",
|
|
58
|
+
}
|
|
59
|
+
click.echo(json.dumps(error_response, indent=2))
|
|
60
|
+
raise SystemExit(1)
|
|
61
|
+
|
|
62
|
+
# Get dependencies from container
|
|
63
|
+
_server_instance = (
|
|
64
|
+
ctx.obj.get("container").get_container().resolve(ServerService)
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
status = _server_instance.get_server_status()
|
|
68
|
+
click.echo(json.dumps(status, indent=2))
|
|
69
|
+
|
|
70
|
+
# This will block until server is stopped
|
|
71
|
+
_server_instance.start_server()
|
|
72
|
+
|
|
73
|
+
except ServerError as e:
|
|
74
|
+
ServerErrorHandler.handle_server_startup_error(e, port, config)
|
|
75
|
+
except KeyboardInterrupt:
|
|
76
|
+
shutdown_response = {"success": True, "message": "Server shutdown by user"}
|
|
77
|
+
click.echo(json.dumps(shutdown_response, indent=2))
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@server.command("stop")
|
|
81
|
+
@handle_service_errors(ServerError)
|
|
82
|
+
def stop_server() -> None:
|
|
83
|
+
r"""Stop the running Conbus emulator server.
|
|
84
|
+
|
|
85
|
+
Examples:
|
|
86
|
+
\b
|
|
87
|
+
xp server stop
|
|
88
|
+
"""
|
|
89
|
+
try:
|
|
90
|
+
if _server_instance is None or not _server_instance.is_running:
|
|
91
|
+
ServerErrorHandler.handle_server_not_running_error()
|
|
92
|
+
|
|
93
|
+
# Stop the server
|
|
94
|
+
if _server_instance is not None:
|
|
95
|
+
_server_instance.stop_server()
|
|
96
|
+
|
|
97
|
+
response = {"success": True, "message": "Server stopped successfully"}
|
|
98
|
+
click.echo(json.dumps(response, indent=2))
|
|
99
|
+
|
|
100
|
+
except ServerError as e:
|
|
101
|
+
ServerErrorHandler.handle_server_startup_error(e, 0, "")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@server.command("status")
|
|
105
|
+
@handle_service_errors(Exception)
|
|
106
|
+
def server_status() -> None:
|
|
107
|
+
r"""Get status of the Conbus emulator server.
|
|
108
|
+
|
|
109
|
+
Examples:
|
|
110
|
+
\b
|
|
111
|
+
xp server status
|
|
112
|
+
|
|
113
|
+
Raises:
|
|
114
|
+
SystemExit: If status cannot be retrieved.
|
|
115
|
+
"""
|
|
116
|
+
formatter = OutputFormatter(True)
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
status: Dict[str, Any]
|
|
120
|
+
if _server_instance is None:
|
|
121
|
+
status = {
|
|
122
|
+
"running": False,
|
|
123
|
+
"port": None,
|
|
124
|
+
"devices_configured": 0,
|
|
125
|
+
"device_list": [],
|
|
126
|
+
}
|
|
127
|
+
else:
|
|
128
|
+
status = _server_instance.get_server_status()
|
|
129
|
+
|
|
130
|
+
click.echo(json.dumps(status, indent=2))
|
|
131
|
+
|
|
132
|
+
except Exception as e:
|
|
133
|
+
error_response = formatter.error_response(str(e))
|
|
134
|
+
click.echo(error_response)
|
|
135
|
+
raise SystemExit(1)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Shared conbus CLI group definition."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from click_help_colors import HelpColorsGroup
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@click.group(
|
|
8
|
+
cls=HelpColorsGroup, help_headers_color="yellow", help_options_color="green"
|
|
9
|
+
)
|
|
10
|
+
def telegram() -> None:
|
|
11
|
+
"""Perform event telegram operations."""
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@click.group(
|
|
16
|
+
cls=HelpColorsGroup, help_headers_color="yellow", help_options_color="green"
|
|
17
|
+
)
|
|
18
|
+
def linknumber() -> None:
|
|
19
|
+
"""Perform link number operations for module configuration."""
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@click.group(
|
|
24
|
+
cls=HelpColorsGroup, help_headers_color="yellow", help_options_color="green"
|
|
25
|
+
)
|
|
26
|
+
def blink() -> None:
|
|
27
|
+
"""Perform blink operations for module LED control."""
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@click.group(
|
|
32
|
+
cls=HelpColorsGroup, help_headers_color="yellow", help_options_color="green"
|
|
33
|
+
)
|
|
34
|
+
def checksum() -> None:
|
|
35
|
+
"""Perform checksum calculation and validation operations."""
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
telegram.add_command(linknumber)
|
|
40
|
+
telegram.add_command(blink)
|
|
41
|
+
telegram.add_command(checksum)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Blink operations CLI commands."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from xp.cli.commands.telegram.telegram import blink
|
|
8
|
+
from xp.cli.utils.decorators import handle_service_errors
|
|
9
|
+
from xp.cli.utils.error_handlers import CLIErrorHandler
|
|
10
|
+
from xp.cli.utils.formatters import OutputFormatter
|
|
11
|
+
from xp.cli.utils.serial_number_type import SERIAL
|
|
12
|
+
from xp.services.telegram.telegram_blink_service import BlinkError, TelegramBlinkService
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@blink.command("on")
|
|
16
|
+
@click.argument("serial_number", type=SERIAL)
|
|
17
|
+
@handle_service_errors(BlinkError)
|
|
18
|
+
def blink_on(serial_number: str) -> None:
|
|
19
|
+
r"""Generate a telegram to start blinking module LED.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
serial_number: 10-digit module serial number.
|
|
23
|
+
|
|
24
|
+
Examples:
|
|
25
|
+
\b
|
|
26
|
+
xp blink on 0012345008
|
|
27
|
+
xp blink on 0012345008
|
|
28
|
+
"""
|
|
29
|
+
service = TelegramBlinkService()
|
|
30
|
+
OutputFormatter(True)
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
telegram = service.generate_blink_telegram(serial_number, "on")
|
|
34
|
+
|
|
35
|
+
output = {
|
|
36
|
+
"success": True,
|
|
37
|
+
"telegram": telegram,
|
|
38
|
+
"serial_number": serial_number,
|
|
39
|
+
"operation": "blink_on",
|
|
40
|
+
}
|
|
41
|
+
click.echo(json.dumps(output, indent=2))
|
|
42
|
+
|
|
43
|
+
except BlinkError as e:
|
|
44
|
+
CLIErrorHandler.handle_service_error(
|
|
45
|
+
e, "blink telegram generation", {"serial_number": serial_number}
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@blink.command("off")
|
|
50
|
+
@click.argument("serial_number", type=SERIAL)
|
|
51
|
+
@handle_service_errors(BlinkError)
|
|
52
|
+
def blink_off(serial_number: str) -> None:
|
|
53
|
+
r"""Generate a telegram to stop blinking module LED.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
serial_number: 10-digit module serial number.
|
|
57
|
+
|
|
58
|
+
Examples:
|
|
59
|
+
\b
|
|
60
|
+
xp blink off 0012345011
|
|
61
|
+
"""
|
|
62
|
+
service = TelegramBlinkService()
|
|
63
|
+
OutputFormatter(True)
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
telegram = service.generate_blink_telegram(serial_number, "off")
|
|
67
|
+
|
|
68
|
+
output = {
|
|
69
|
+
"success": True,
|
|
70
|
+
"telegram": telegram,
|
|
71
|
+
"serial_number": serial_number,
|
|
72
|
+
"operation": "blink_off",
|
|
73
|
+
}
|
|
74
|
+
click.echo(json.dumps(output, indent=2))
|
|
75
|
+
|
|
76
|
+
except BlinkError as e:
|
|
77
|
+
CLIErrorHandler.handle_service_error(
|
|
78
|
+
e, "unblink telegram generation", {"serial_number": serial_number}
|
|
79
|
+
)
|