conson-xp 1.2.0__py3-none-any.whl → 1.4.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.
Files changed (62) hide show
  1. {conson_xp-1.2.0.dist-info → conson_xp-1.4.0.dist-info}/METADATA +1 -5
  2. {conson_xp-1.2.0.dist-info → conson_xp-1.4.0.dist-info}/RECORD +43 -60
  3. xp/__init__.py +1 -1
  4. xp/cli/commands/__init__.py +0 -2
  5. xp/cli/commands/conbus/conbus_actiontable_commands.py +5 -3
  6. xp/cli/commands/conbus/conbus_autoreport_commands.py +39 -21
  7. xp/cli/commands/conbus/conbus_blink_commands.py +8 -8
  8. xp/cli/commands/conbus/conbus_config_commands.py +3 -1
  9. xp/cli/commands/conbus/conbus_custom_commands.py +3 -1
  10. xp/cli/commands/conbus/conbus_datapoint_commands.py +4 -2
  11. xp/cli/commands/conbus/conbus_discover_commands.py +5 -3
  12. xp/cli/commands/conbus/conbus_lightlevel_commands.py +68 -32
  13. xp/cli/commands/conbus/conbus_linknumber_commands.py +32 -17
  14. xp/cli/commands/conbus/conbus_msactiontable_commands.py +11 -4
  15. xp/cli/commands/conbus/conbus_output_commands.py +6 -2
  16. xp/cli/commands/conbus/conbus_receive_commands.py +5 -3
  17. xp/cli/commands/file_commands.py +9 -3
  18. xp/cli/commands/homekit/homekit_start_commands.py +3 -1
  19. xp/cli/commands/module_commands.py +12 -4
  20. xp/cli/commands/reverse_proxy_commands.py +3 -1
  21. xp/cli/main.py +0 -2
  22. xp/models/conbus/conbus_datapoint.py +3 -0
  23. xp/models/conbus/conbus_discover.py +19 -3
  24. xp/models/conbus/conbus_writeconfig.py +60 -0
  25. xp/models/telegram/system_telegram.py +4 -4
  26. xp/services/conbus/conbus_datapoint_service.py +9 -6
  27. xp/services/conbus/conbus_discover_service.py +120 -2
  28. xp/services/conbus/conbus_scan_service.py +1 -1
  29. xp/services/conbus/{conbus_linknumber_set_service.py → write_config_service.py} +78 -66
  30. xp/services/protocol/telegram_protocol.py +4 -4
  31. xp/services/server/base_server_service.py +9 -4
  32. xp/services/server/cp20_server_service.py +2 -1
  33. xp/services/server/server_service.py +75 -4
  34. xp/services/server/xp130_server_service.py +2 -1
  35. xp/services/server/xp20_server_service.py +2 -1
  36. xp/services/server/xp230_server_service.py +2 -1
  37. xp/services/server/xp24_server_service.py +123 -50
  38. xp/services/server/xp33_server_service.py +150 -20
  39. xp/services/telegram/telegram_datapoint_service.py +70 -0
  40. xp/utils/dependencies.py +4 -46
  41. xp/api/__init__.py +0 -1
  42. xp/api/main.py +0 -125
  43. xp/api/models/__init__.py +0 -1
  44. xp/api/models/api.py +0 -31
  45. xp/api/models/discover.py +0 -31
  46. xp/api/routers/__init__.py +0 -17
  47. xp/api/routers/conbus.py +0 -5
  48. xp/api/routers/conbus_blink.py +0 -117
  49. xp/api/routers/conbus_custom.py +0 -71
  50. xp/api/routers/conbus_datapoint.py +0 -74
  51. xp/api/routers/conbus_output.py +0 -167
  52. xp/api/routers/errors.py +0 -38
  53. xp/cli/commands/api.py +0 -12
  54. xp/cli/commands/api_start_commands.py +0 -132
  55. xp/services/conbus/conbus_autoreport_get_service.py +0 -94
  56. xp/services/conbus/conbus_autoreport_set_service.py +0 -141
  57. xp/services/conbus/conbus_lightlevel_get_service.py +0 -109
  58. xp/services/conbus/conbus_lightlevel_set_service.py +0 -225
  59. xp/services/conbus/conbus_linknumber_get_service.py +0 -94
  60. {conson_xp-1.2.0.dist-info → conson_xp-1.4.0.dist-info}/WHEEL +0 -0
  61. {conson_xp-1.2.0.dist-info → conson_xp-1.4.0.dist-info}/entry_points.txt +0 -0
  62. {conson_xp-1.2.0.dist-info → conson_xp-1.4.0.dist-info}/licenses/LICENSE +0 -0
xp/api/main.py DELETED
@@ -1,125 +0,0 @@
1
- """FastAPI application for XP Protocol API endpoints."""
2
-
3
- import logging
4
- import os
5
- from pathlib import Path
6
- from typing import Any
7
-
8
- import yaml
9
- from fastapi import FastAPI
10
- from fastapi.middleware.cors import CORSMiddleware
11
-
12
- from xp.api.routers import conbus
13
- from xp.utils.dependencies import ServiceContainer
14
-
15
- # Set up logging
16
- logging.basicConfig(level=logging.INFO)
17
- logger = logging.getLogger(__name__)
18
-
19
-
20
- def load_api_config() -> dict[str, Any]:
21
- """Load API configuration from api.yml or environment variables.
22
-
23
- Returns:
24
- Dictionary containing API configuration settings.
25
- """
26
- config = {
27
- "title": "XP Protocol API",
28
- "description": "REST API for XP Protocol Conbus operations",
29
- "version": "0.2.0",
30
- "cors_origins": ["*"],
31
- "cors_methods": ["GET", "POST"],
32
- "cors_headers": ["*"],
33
- }
34
-
35
- # Try to load from api.yml
36
- try:
37
- if Path("api.yml").exists():
38
- with Path("api.yml").open("r") as file:
39
- file_config = yaml.safe_load(file)
40
- if file_config:
41
- config.update(file_config.get("api", {}))
42
- logger.info("Loaded API configuration from api.yml")
43
- except Exception as e:
44
- logger.warning(f"Could not load api.yml: {e}")
45
-
46
- # Override with environment variables
47
- config["title"] = os.getenv("API_TITLE", config["title"])
48
- config["description"] = os.getenv("API_DESCRIPTION", config["description"])
49
- config["version"] = os.getenv("API_VERSION", config["version"])
50
-
51
- # CORS configuration from environment
52
- cors_origins = os.getenv("CORS_ORIGINS")
53
- if cors_origins is not None:
54
- config["cors_origins"] = cors_origins.split(",")
55
- cors_methods = os.getenv("CORS_METHODS")
56
- if cors_methods is not None:
57
- config["cors_methods"] = cors_methods.split(",")
58
- cors_headers = os.getenv("CORS_HEADERS")
59
- if cors_headers is not None:
60
- config["cors_headers"] = cors_headers.split(",")
61
-
62
- return config
63
-
64
-
65
- def create_app(container: ServiceContainer) -> FastAPI:
66
- """Create and configure the FastAPI application.
67
-
68
- Args:
69
- container: Optional ServiceContainer instance. If not provided, a new one will be created.
70
-
71
- Returns:
72
- Configured FastAPI application instance.
73
- """
74
- config = load_api_config()
75
-
76
- fastapi = FastAPI(
77
- title=config["title"],
78
- description=config["description"],
79
- version=config["version"],
80
- docs_url="/docs",
81
- redoc_url="/redoc",
82
- )
83
-
84
- # Add CORS middleware
85
- fastapi.add_middleware(
86
- CORSMiddleware,
87
- allow_origins=config["cors_origins"],
88
- allow_credentials=True,
89
- allow_methods=config["cors_methods"],
90
- allow_headers=config["cors_headers"],
91
- )
92
-
93
- # Initialize service container
94
- fastapi.state.container = container
95
-
96
- # Include routers
97
- fastapi.include_router(conbus.router)
98
-
99
- # Health check endpoint
100
- @fastapi.get("/health")
101
- async def health_check() -> dict[str, str]:
102
- """Return health status of the API.
103
-
104
- Returns:
105
- Dictionary containing status and service information.
106
- """
107
- return {"status": "healthy", "service": "xp-api"}
108
-
109
- # Root endpoint
110
- @fastapi.get("/")
111
- async def root() -> dict[str, str]:
112
- """Return API information and available endpoints.
113
-
114
- Returns:
115
- Dictionary containing API metadata and endpoint links.
116
- """
117
- return {
118
- "message": "XP Protocol API",
119
- "version": config["version"],
120
- "docs": "/docs",
121
- "health": "/health",
122
- }
123
-
124
- logger.info(f"FastAPI application created: {config['title']} v{config['version']}")
125
- return fastapi
xp/api/models/__init__.py DELETED
@@ -1 +0,0 @@
1
- """API models for request and response validation."""
xp/api/models/api.py DELETED
@@ -1,31 +0,0 @@
1
- """Pydantic models for Input API endpoints."""
2
-
3
- from typing import Optional
4
-
5
- from pydantic import BaseModel, Field
6
-
7
-
8
- class ApiResponse(BaseModel):
9
- """Response model for successful Input operation.
10
-
11
- Attributes:
12
- success: Operation success status.
13
- result: Result value.
14
- description: Description of the result.
15
- """
16
-
17
- success: bool = Field(default=True, description="Operation success status")
18
- result: Optional[str] = Field(default=None, description="Result")
19
- description: Optional[str] = Field(default=None, description="Description")
20
-
21
-
22
- class ApiErrorResponse(BaseModel):
23
- """Response model for failed Input operation.
24
-
25
- Attributes:
26
- success: Operation success status (always False).
27
- error: Error message describing what went wrong.
28
- """
29
-
30
- success: bool = Field(default=False, description="Operation success status")
31
- error: str = Field(..., description="Error message")
xp/api/models/discover.py DELETED
@@ -1,31 +0,0 @@
1
- """Pydantic models for discover API endpoints."""
2
-
3
- from typing import List
4
-
5
- from pydantic import BaseModel, Field
6
-
7
-
8
- class DiscoverResponse(BaseModel):
9
- """Response model for successful discover operation.
10
-
11
- Attributes:
12
- success: Operation success status.
13
- devices: List of discovered device information strings.
14
- """
15
-
16
- success: bool = Field(default=True, description="Operation success status")
17
- devices: List[str] = Field(
18
- default_factory=list, description="Parsed device information"
19
- )
20
-
21
-
22
- class DiscoverErrorResponse(BaseModel):
23
- """Response model for failed discover operation.
24
-
25
- Attributes:
26
- success: Operation success status (always False).
27
- error: Error message describing what went wrong.
28
- """
29
-
30
- success: bool = Field(default=False, description="Operation success status")
31
- error: str = Field(..., description="Error message")
@@ -1,17 +0,0 @@
1
- """API routers for FastAPI endpoints."""
2
-
3
- from xp.api.routers import (
4
- conbus_blink,
5
- conbus_custom,
6
- conbus_datapoint,
7
- conbus_output,
8
- )
9
- from xp.api.routers.conbus import router
10
-
11
- __all__ = [
12
- "router",
13
- "conbus_blink",
14
- "conbus_custom",
15
- "conbus_datapoint",
16
- "conbus_output",
17
- ]
xp/api/routers/conbus.py DELETED
@@ -1,5 +0,0 @@
1
- """FastAPI router for Conbus operations."""
2
-
3
- from fastapi import APIRouter
4
-
5
- router = APIRouter(prefix="/api/conbus", tags=["conbus"])
@@ -1,117 +0,0 @@
1
- """FastAPI router for Conbus operations."""
2
-
3
- import json
4
- import logging
5
- from typing import Union
6
-
7
- from fastapi import Request
8
- from fastapi.responses import JSONResponse
9
-
10
- from xp.api.models.api import ApiErrorResponse, ApiResponse
11
- from xp.api.routers.conbus import router
12
- from xp.api.routers.errors import handle_service_error
13
- from xp.services.conbus.conbus_blink_service import ConbusBlinkService
14
-
15
- logger = logging.getLogger(__name__)
16
-
17
-
18
- @router.get(
19
- "/blink/on/{serial_number}",
20
- response_model=Union[ApiResponse, ApiErrorResponse],
21
- responses={
22
- 200: {"model": ApiResponse, "description": "Input completed successfully"},
23
- 400: {"model": ApiErrorResponse, "description": "Connection or request error"},
24
- 408: {"model": ApiErrorResponse, "description": "Request timeout"},
25
- 500: {"model": ApiErrorResponse, "description": "Internal server error"},
26
- },
27
- )
28
- async def blink_on(
29
- request: Request,
30
- serial_number: str = "1702033007",
31
- ) -> Union[ApiResponse, ApiErrorResponse, JSONResponse]:
32
- """Turn on device blinking.
33
-
34
- Sends a blink on telegram to make the device blink.
35
-
36
- Args:
37
- request: FastAPI request object.
38
- serial_number: Serial number of the device.
39
-
40
- Returns:
41
- API response with blink result or error.
42
- """
43
- service = request.app.state.container.get_container().resolve(ConbusBlinkService)
44
-
45
- # SendInput telegram and receive responses
46
- with service:
47
- response = service.send_blink_telegram(
48
- serial_number=serial_number, on_or_off="on"
49
- )
50
-
51
- if not response.success:
52
- return handle_service_error(response.error or "Unknown error")
53
-
54
- logger.debug(json.dumps(response.to_dict(), indent=2))
55
-
56
- # Build successful response
57
- return ApiResponse(
58
- success=True,
59
- result=response.system_function.name,
60
- description=(
61
- response.reply_telegram.system_function.get_description()
62
- if response.reply_telegram and response.reply_telegram.system_function
63
- else None
64
- ),
65
- # raw_telegram = response.output_telegram.raw_telegram,
66
- )
67
-
68
-
69
- @router.get(
70
- "/blink/off/{serial_number}",
71
- response_model=Union[ApiResponse, ApiErrorResponse],
72
- responses={
73
- 200: {"model": ApiResponse, "description": "Input completed successfully"},
74
- 400: {"model": ApiErrorResponse, "description": "Connection or request error"},
75
- 408: {"model": ApiErrorResponse, "description": "Request timeout"},
76
- 500: {"model": ApiErrorResponse, "description": "Internal server error"},
77
- },
78
- )
79
- async def blink_off(
80
- request: Request,
81
- serial_number: str = "1702033007",
82
- ) -> Union[ApiResponse, ApiErrorResponse, JSONResponse]:
83
- """Turn off device blinking.
84
-
85
- Sends a blink off telegram to stop the device from blinking.
86
-
87
- Args:
88
- request: FastAPI request object.
89
- serial_number: Serial number of the device.
90
-
91
- Returns:
92
- API response with blink result or error.
93
- """
94
- service = request.app.state.container.get_container().resolve(ConbusBlinkService)
95
-
96
- # SendInput telegram and receive responses
97
- with service:
98
- response = service.send_blink_telegram(
99
- serial_number=serial_number, on_or_off="off"
100
- )
101
-
102
- if not response.success:
103
- return handle_service_error(response.error or "Unknown error")
104
-
105
- logger.debug(json.dumps(response.to_dict(), indent=2))
106
-
107
- # Build successful response
108
- return ApiResponse(
109
- success=True,
110
- result=response.system_function.name,
111
- description=(
112
- response.reply_telegram.system_function.get_description()
113
- if response.reply_telegram and response.reply_telegram.system_function
114
- else None
115
- ),
116
- # raw_telegram = response.output_telegram.raw_telegram,
117
- )
@@ -1,71 +0,0 @@
1
- """FastAPI router for Conbus operations."""
2
-
3
- import logging
4
- from typing import Union
5
-
6
- from fastapi import Request
7
- from fastapi.responses import JSONResponse
8
-
9
- from xp.api.models.api import ApiErrorResponse, ApiResponse
10
- from xp.api.routers.conbus import router
11
- from xp.api.routers.errors import handle_service_error
12
- from xp.services.conbus.conbus_custom_service import ConbusCustomService
13
-
14
- logger = logging.getLogger(__name__)
15
-
16
-
17
- @router.get(
18
- "/custom/{serial_number}/{function_code}/{data}",
19
- response_model=Union[ApiResponse, ApiErrorResponse],
20
- responses={
21
- 200: {"model": ApiResponse, "description": "Datapoint completed successfully"},
22
- 400: {"model": ApiErrorResponse, "description": "Connection or request error"},
23
- 408: {"model": ApiErrorResponse, "description": "Request timeout"},
24
- 500: {"model": ApiErrorResponse, "description": "Internal server error"},
25
- },
26
- )
27
- async def custom_function(
28
- request: Request,
29
- serial_number: str = "1702033007",
30
- function_code: str = "02",
31
- data: str = "00",
32
- ) -> Union[ApiResponse, ApiErrorResponse, JSONResponse]:
33
- """Execute a custom function on a device.
34
-
35
- Sends a custom telegram with specified function code and data.
36
-
37
- Args:
38
- request: FastAPI request object.
39
- serial_number: Serial number of the device.
40
- function_code: Function code to execute.
41
- data: Data to send with the function.
42
-
43
- Returns:
44
- API response with custom function result or error.
45
- """
46
- service = request.app.state.container.get_container().resolve(ConbusCustomService)
47
- # SendDatapoint telegram and receive responses
48
- with service:
49
- response = service.send_custom_telegram(serial_number, function_code, data)
50
-
51
- if not response.success:
52
- return handle_service_error(response.error or "Unknown error")
53
-
54
- if response.reply_telegram is None:
55
- return ApiErrorResponse(
56
- success=False,
57
- error=response.error or "Unknown error",
58
- )
59
-
60
- # Build successful response
61
- if response.reply_telegram and response.reply_telegram.datapoint_type:
62
- return ApiResponse(
63
- success=True,
64
- result=response.reply_telegram.data_value,
65
- description=response.reply_telegram.datapoint_type.name,
66
- )
67
- return ApiResponse(
68
- success=True,
69
- result=response.reply_telegram.data_value,
70
- description="Custom command executed successfully",
71
- )
@@ -1,74 +0,0 @@
1
- """FastAPI router for Conbus operations."""
2
-
3
- import logging
4
- from typing import Union
5
-
6
- from fastapi import Request
7
- from fastapi.responses import JSONResponse
8
-
9
- from xp.api.models.api import ApiErrorResponse, ApiResponse
10
- from xp.api.routers.conbus import router
11
- from xp.api.routers.errors import handle_service_error
12
- from xp.models.telegram.datapoint_type import DataPointType
13
- from xp.services.conbus.conbus_datapoint_service import ConbusDatapointService
14
-
15
- logger = logging.getLogger(__name__)
16
-
17
-
18
- @router.get(
19
- "/datapoint/{datapoint}/{serial_number}",
20
- response_model=Union[ApiResponse, ApiErrorResponse],
21
- responses={
22
- 200: {"model": ApiResponse, "description": "Datapoint completed successfully"},
23
- 400: {"model": ApiErrorResponse, "description": "Connection or request error"},
24
- 408: {"model": ApiErrorResponse, "description": "Request timeout"},
25
- 500: {"model": ApiErrorResponse, "description": "Internal server error"},
26
- },
27
- )
28
- async def datapoint_devices(
29
- request: Request,
30
- datapoint: DataPointType = DataPointType.SW_VERSION,
31
- serial_number: str = "1702033007",
32
- ) -> Union[ApiResponse, ApiErrorResponse, JSONResponse]:
33
- """Query a datapoint value from a device.
34
-
35
- Sends a datapoint query telegram and retrieves the requested datapoint value.
36
-
37
- Args:
38
- request: FastAPI request object.
39
- datapoint: Type of datapoint to query (default: SW_VERSION).
40
- serial_number: Serial number of the device.
41
-
42
- Returns:
43
- API response with datapoint value or error.
44
- """
45
- service = request.app.state.container.get_container().resolve(
46
- ConbusDatapointService
47
- )
48
- # SendDatapoint telegram and receive responses
49
- with service:
50
- response = service.query_datapoint(
51
- datapoint_type=datapoint, serial_number=serial_number
52
- )
53
-
54
- if not response.success:
55
- return handle_service_error(response.error or "Unknown error")
56
-
57
- if response.datapoint_telegram is None:
58
- return ApiErrorResponse(
59
- success=False,
60
- error=response.error or "Unknown error",
61
- )
62
-
63
- # Build successful response
64
- if response.datapoint_telegram and response.datapoint_telegram.datapoint_type:
65
- return ApiResponse(
66
- success=True,
67
- result=response.datapoint_telegram.data_value,
68
- description=response.datapoint_telegram.datapoint_type.name,
69
- )
70
- return ApiResponse(
71
- success=True,
72
- result=response.datapoint_telegram.data_value,
73
- description="Datapoint value retrieved",
74
- )
@@ -1,167 +0,0 @@
1
- """FastAPI router for Conbus operations."""
2
-
3
- import json
4
- import logging
5
- from typing import Union
6
-
7
- from fastapi import Request
8
- from fastapi.responses import JSONResponse
9
-
10
- from xp.api.models.api import ApiErrorResponse, ApiResponse
11
- from xp.api.routers.conbus import router
12
- from xp.api.routers.errors import handle_service_error
13
- from xp.models.telegram.action_type import ActionType
14
- from xp.services.conbus.conbus_output_service import ConbusOutputService
15
-
16
- logger = logging.getLogger(__name__)
17
-
18
-
19
- @router.get(
20
- "/output/{action}/{serial}/{device_input}",
21
- response_model=Union[ApiResponse, ApiErrorResponse],
22
- responses={
23
- 200: {"model": ApiResponse, "description": "Input completed successfully"},
24
- 400: {"model": ApiErrorResponse, "description": "Connection or request error"},
25
- 408: {"model": ApiErrorResponse, "description": "Request timeout"},
26
- 500: {"model": ApiErrorResponse, "description": "Internal server error"},
27
- },
28
- )
29
- async def input_action(
30
- request: Request,
31
- action: ActionType = ActionType.OFF_PRESS,
32
- serial: str = "1702033007",
33
- device_input: int = 0,
34
- ) -> Union[ApiResponse, ApiErrorResponse, JSONResponse]:
35
- """Initiate Input operation to find devices on the network.
36
-
37
- Sends a broadcast Input telegram and collects responses from all connected devices.
38
-
39
- Args:
40
- request: FastAPI request object.
41
- action: Action type to perform (default: OFF_PRESS).
42
- serial: Serial number of the device.
43
- device_input: Device input number.
44
-
45
- Returns:
46
- API response with operation result or error.
47
- """
48
- service = request.app.state.container.get_container().resolve(ConbusOutputService)
49
-
50
- # SendInput telegram and receive responses
51
- with service:
52
- response = service.send_action(serial, device_input, action)
53
-
54
- if not response.success:
55
- return handle_service_error(response.error or "Unknown error")
56
-
57
- logger.debug(json.dumps(response.to_dict(), indent=2))
58
-
59
- # Build successful response
60
- if response.output_telegram and response.output_telegram.system_function:
61
- return ApiResponse(
62
- success=True,
63
- result=response.output_telegram.system_function.name,
64
- description=response.output_telegram.system_function.get_description(),
65
- # raw_telegram = response.output_telegram.raw_telegram,
66
- )
67
- return ApiResponse(
68
- success=True,
69
- result="Output command sent",
70
- description="Output command was sent successfully",
71
- )
72
-
73
-
74
- @router.get(
75
- "/output/status/{serial_number}",
76
- response_model=Union[ApiResponse, ApiErrorResponse],
77
- responses={
78
- 200: {"model": ApiResponse, "description": "Query completed successfully"},
79
- 400: {"model": ApiErrorResponse, "description": "Connection or request error"},
80
- 408: {"model": ApiErrorResponse, "description": "Request timeout"},
81
- 500: {"model": ApiErrorResponse, "description": "Internal server error"},
82
- },
83
- )
84
- async def output_status(
85
- request: Request,
86
- serial_number: str,
87
- ) -> Union[ApiResponse, ApiErrorResponse, JSONResponse]:
88
- """Query output status from a device.
89
-
90
- Sends a status query telegram and retrieves the output state.
91
-
92
- Args:
93
- request: FastAPI request object.
94
- serial_number: Serial number of the device to query.
95
-
96
- Returns:
97
- API response with output status or error.
98
- """
99
- service = request.app.state.container.get_container().resolve(ConbusOutputService)
100
-
101
- # SendInput telegram and receive responses
102
- with service:
103
- response = service.get_output_state(serial_number)
104
-
105
- if not response.success:
106
- return handle_service_error(response.error or "Unknown error")
107
-
108
- # Build successful response
109
- if response.datapoint_telegram and response.datapoint_telegram.datapoint_type:
110
- return ApiResponse(
111
- success=True,
112
- result=response.datapoint_telegram.data_value,
113
- description=response.datapoint_telegram.datapoint_type.name,
114
- )
115
- return ApiResponse(
116
- success=True,
117
- result="No data available",
118
- description="Output status retrieved but no data available",
119
- )
120
-
121
-
122
- @router.get(
123
- "/output/state/{serial_number}",
124
- response_model=Union[ApiResponse, ApiErrorResponse],
125
- responses={
126
- 200: {"model": ApiResponse, "description": "Query completed successfully"},
127
- 400: {"model": ApiErrorResponse, "description": "Connection or request error"},
128
- 408: {"model": ApiErrorResponse, "description": "Request timeout"},
129
- 500: {"model": ApiErrorResponse, "description": "Internal server error"},
130
- },
131
- )
132
- async def output_state(
133
- request: Request,
134
- serial_number: str,
135
- ) -> Union[ApiResponse, ApiErrorResponse, JSONResponse]:
136
- """Query module state from a device.
137
-
138
- Sends a state query telegram and retrieves the module state.
139
-
140
- Args:
141
- request: FastAPI request object.
142
- serial_number: Serial number of the device to query.
143
-
144
- Returns:
145
- API response with module state or error.
146
- """
147
- service = request.app.state.container.get_container().resolve(ConbusOutputService)
148
-
149
- # SendInput telegram and receive responses
150
- with service:
151
- response = service.get_module_state(serial_number)
152
-
153
- if not response.success:
154
- return handle_service_error(response.error or "Unknown error")
155
-
156
- # Build successful response
157
- if response.datapoint_telegram and response.datapoint_telegram.datapoint_type:
158
- return ApiResponse(
159
- success=True,
160
- result=response.datapoint_telegram.data_value,
161
- description=response.datapoint_telegram.datapoint_type.name,
162
- )
163
- return ApiResponse(
164
- success=True,
165
- result="No data available",
166
- description="Module state retrieved but no data available",
167
- )
xp/api/routers/errors.py DELETED
@@ -1,38 +0,0 @@
1
- """Error handling utilities for API endpoints."""
2
-
3
- from starlette import status
4
- from starlette.responses import JSONResponse
5
-
6
- from xp.api.models.discover import DiscoverErrorResponse
7
-
8
-
9
- def handle_service_error(
10
- error: str, default_status_code: int = status.HTTP_500_INTERNAL_SERVER_ERROR
11
- ) -> JSONResponse:
12
- """Handle service errors by creating a standardized JSON error response.
13
-
14
- Args:
15
- error: Service response object with success and error attributes.
16
- default_status_code: HTTP status code to use (defaults to 500).
17
-
18
- Returns:
19
- JSONResponse with error details.
20
- """
21
- error_msg = error or "Unknown service error"
22
-
23
- # Map specific error patterns to appropriate HTTP status codes
24
- if "Not connected to server" in error_msg:
25
- status_code = status.HTTP_400_BAD_REQUEST
26
- elif "Failed to generate telegram" in error_msg:
27
- status_code = status.HTTP_400_BAD_REQUEST
28
- elif "Response timeout" in error_msg:
29
- status_code = status.HTTP_408_REQUEST_TIMEOUT
30
- elif "Failed to send telegram" in error_msg:
31
- status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
32
- else:
33
- status_code = default_status_code
34
-
35
- return JSONResponse(
36
- status_code=status_code,
37
- content=DiscoverErrorResponse(error=error_msg).model_dump(),
38
- )