From 0dfd85525568b316b727591a961c65865cb32204 Mon Sep 17 00:00:00 2001 From: FERNANDEZ BOADA Daniel Date: Thu, 22 Jan 2026 20:09:56 +0100 Subject: [PATCH 1/7] move config to a different repo --- config/camera_gis_system_config.json | 263 -- config/logging_config.json | 47 - config/security_config.json | 35 - config/svg/0.room.svg | 3840 -------------------------- config/svg/0.room.txt | 5 - config/svg/1.tank.svg | 115 - config/svg/1.tank.txt | 7 - config/svg/2.train.svg | 431 --- config/svg/2.train.txt | 6 - config/svg/3.stress_test.svg | 272 -- config/svg/3.stress_test.txt | 5 - config/svg_system_config.json | 1506 ---------- config/system_config.json | 1730 ------------ src/openscada_lite/app.py | 21 +- 14 files changed, 17 insertions(+), 8266 deletions(-) delete mode 100644 config/camera_gis_system_config.json delete mode 100644 config/logging_config.json delete mode 100644 config/security_config.json delete mode 100644 config/svg/0.room.svg delete mode 100644 config/svg/0.room.txt delete mode 100644 config/svg/1.tank.svg delete mode 100644 config/svg/1.tank.txt delete mode 100644 config/svg/2.train.svg delete mode 100644 config/svg/2.train.txt delete mode 100644 config/svg/3.stress_test.svg delete mode 100644 config/svg/3.stress_test.txt delete mode 100644 config/svg_system_config.json delete mode 100644 config/system_config.json diff --git a/config/camera_gis_system_config.json b/config/camera_gis_system_config.json deleted file mode 100644 index 3cce243..0000000 --- a/config/camera_gis_system_config.json +++ /dev/null @@ -1,263 +0,0 @@ -{ - "dp_types": { - "BOOL": { - "default": "FALSE", - "type": "enum", - "values": [ - "TRUE", - "FALSE" - ] - }, - "CAMERA_POSITION": { - "default": "RIGHT", - "type": "enum", - "values": [ - "LEFT", - "RIGHT" - ] - } - }, - "drivers": [ - { - "command_datapoints": [], - "connection_info": { - "server_name": "OPCUAServerDriver" - }, - "datapoints": [ - { - "name": "CameraDriver@CAMERA_1_POSITION", - "type": "CAMERA_POSITION" - }, - { - "name": "CameraDriver@CAMERA_2_POSITION", - "type": "CAMERA_POSITION" - }, - { - "name": "CameraDriver@CAMERA_3_POSITION", - "type": "CAMERA_POSITION" - }, - { - "name": "CameraDriver@CAMERA_1_POSITION_CMD", - "type": "CAMERA_POSITION" - }, - { - "name": "CameraDriver@CAMERA_2_POSITION_CMD", - "type": "CAMERA_POSITION" - }, - { - "name": "CameraDriver@CAMERA_3_POSITION_CMD", - "type": "CAMERA_POSITION" - } - ], - "driver_class": "OPCUAServerDriver", - "params": { - "allow_write_regex": ".*_CMD$", - "namespaceurl": "http://openscada-lite/opcua/server" - }, - "name": "OPCUAServerDriver" - }, - { - "command_datapoints": [ - { - "name": "CAMERA_1_POSITION_CMD", - "type": "CAMERA_POSITION" - }, - { - "name": "CAMERA_2_POSITION_CMD", - "type": "CAMERA_POSITION" - }, - { - "name": "CAMERA_3_POSITION_CMD", - "type": "CAMERA_POSITION" - } - ], - "connection_info": { - "server_name": "CameraDriver" - }, - "datapoints": [ - { - "name": "CAMERA_1_POSITION", - "type": "CAMERA_POSITION" - }, - { - "name": "CAMERA_2_POSITION", - "type": "CAMERA_POSITION" - }, - { - "name": "CAMERA_3_POSITION", - "type": "CAMERA_POSITION" - }, - { - "name": "CAMERA_1_ALARM", - "type": "BOOL" - }, - { - "name": "CAMERA_2_ALARM", - "type": "BOOL" - }, - { - "name": "CAMERA_3_ALARM", - "type": "BOOL" - } - ], - "driver_class": "CameraDriver", - "name": "CameraDriver" - } - ], - "gis_icons": [ - { - "alarm": { - "ACTIVE": "/static/icons/camera_ko.png" - }, - "datapoint": "CameraDriver@CAMERA_1_POSITION", - "icon": "/static/icons/camera.png", - "id": "camera1", - "label": "Camera 1", - "latitude": 47.4328, - "longitude": 8.2384, - "navigation": "stream/feed1", - "rule_id": "camera_1_alarm", - "states": { - "LEFT": "/static/icons/camera_left.png", - "RIGHT": "/static/icons/camera_right.png" - } - }, - { - "alarm": { - "ACTIVE": "/static/icons/camera_ko.png" - }, - "datapoint": "CameraDriver@CAMERA_2_POSITION", - "icon": "/static/icons/camera.png", - "id": "camera2", - "label": "Camera 2", - "latitude": 47.4202, - "longitude": 8.2249, - "navigation": "stream/feed2", - "navigation_type": "popup", - "rule_id": "camera_2_alarm", - "states": { - "LEFT": "/static/icons/camera_left.png", - "RIGHT": "/static/icons/camera_right.png" - } - }, - { - "alarm": { - "ACTIVE": "/static/icons/camera_ko.png" - }, - "datapoint": "CameraDriver@CAMERA_3_POSITION", - "icon": "/static/icons/camera.png", - "id": "camera3", - "label": "Camera 3", - "latitude": 47.4081, - "longitude": 8.214, - "navigation": "stream/feed3", - "rule_id": "camera_3_alarm", - "states": { - "LEFT": "/static/icons/camera_left.png", - "RIGHT": "/static/icons/camera_right.png" - } - } - ], - "modules": [ - { - "name": "animation" - }, - { - "name": "alarm" - }, - { - "name": "command" - }, - { - "name": "communication" - }, - { - "name": "tracking" - } , - { - "name": "datapoint" - }, - { - "name": "alert" - }, - { - "config": { - "attribution": "© OpenStreetMap contributors", - "baseurl": "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", - "center_x": "47.4189", - "center_y": "8.2246", - "zoom": 13 - }, - "name": "gis" - }, - { - "name": "rule" - }, - { - "name": "stream" - }, - { - "name": "frontend", - "config": { - "tabs": [ - "Gis", - "Datapoints", - "Communications", - "Alarms", - "Commands", - "Tracking", - "Streams" - ] - } - } - ], - "rules": [ - { - "on_actions": [ - "raise_alarm()" - ], - "on_condition": "CameraDriver@CAMERA_1_ALARM == TRUE", - "rule_id": "camera_1_alarm" - }, - { - "on_actions": [ - "raise_alarm()" - ], - "on_condition": "CameraDriver@CAMERA_2_ALARM == TRUE", - "rule_id": "camera_2_alarm" - }, - { - "on_actions": [ - "raise_alarm()" - ], - "on_condition": "CameraDriver@CAMERA_3_ALARM == TRUE", - "rule_id": "camera_3_alarm" - } - ], - "streams": [ - { - "description": "Feed 1 - 1080p", - "id": "feed1", - "path": "feed1/feed1.m3u8", - "port": "8444", - "protocol": "https", - "server": "www.openscadalite.com" - }, - { - "description": "Feed 2 - 4K", - "id": "feed2", - "path": "feed2/feed2.m3u8", - "port": "8444", - "protocol": "https", - "server": "www.openscadalite.com" - }, - { - "description": "Feed 3 - 4K", - "id": "feed3", - "path": "feed3/feed3.m3u8", - "port": "8444", - "protocol": "https", - "server": "www.openscadalite.com" - } - ] -} \ No newline at end of file diff --git a/config/logging_config.json b/config/logging_config.json deleted file mode 100644 index b450091..0000000 --- a/config/logging_config.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "version": 1, - "formatters": { - "default": { - "format": "%(asctime)s %(levelname)s %(name)s %(message)s" - } - }, - "handlers": { - "console": { - "class": "logging.StreamHandler", - "formatter": "default", - "level": "INFO" - }, - "file": { - "class": "logging.FileHandler", - "formatter": "default", - "filename": "app.log", - "level": "INFO" - } - }, - "root": { - "level": "INFO", - "handlers": ["console", "file"] - }, - "loggers": { - "openscada_lite.modules.security.service": { - "level": "INFO", - "handlers": ["console", "file"], - "propagate": false - }, - "openscada_lite.modules.security.controller": { - "level": "INFO", - "handlers": ["console", "file"], - "propagate": false - }, - "openscada_lite.modules.base.base_controller": { - "level": "INFO", - "handlers": ["console", "file"], - "propagate": false - }, - "openscada_lite.modules.communication.controller": { - "level": "INFO", - "handlers": ["console", "file"], - "propagate": false - } - } -} \ No newline at end of file diff --git a/config/security_config.json b/config/security_config.json deleted file mode 100644 index 3fbc10c..0000000 --- a/config/security_config.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "groups": [ - { - "name": "test_group", - "permissions": [ - "security_editor_access", - "datapoint/rawtagupdatemsg", - "save_security_config", - "save_config", - "communication/driverconnectcommand", - "save_config_as", - "alert/clientalertfeedbackmsg", - "alarm/ackalarmmsg", - "animation/animationupdaterequestmsg", - "restart_app", - "command/sendcommandmsg", - "login" - ] - } - ], - "users": [ - { - "allowed_apps": [ - "security_editor", - "scada", - "config_editor" - ], - "groups": [ - "test_group" - ], - "password_hash": "8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918", - "username": "admin" - } - ] -} \ No newline at end of file diff --git a/config/svg/0.room.svg b/config/svg/0.room.svg deleted file mode 100644 index 283913a..0000000 --- a/config/svg/0.room.svg +++ /dev/null @@ -1,3840 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Light 1 - Light 2 - MQTT Driver - diff --git a/config/svg/0.room.txt b/config/svg/0.room.txt deleted file mode 100644 index 67077d3..0000000 --- a/config/svg/0.room.txt +++ /dev/null @@ -1,5 +0,0 @@ -This is the MQTT module. -1. Connect / Disconnect the MQTT Driver -2. Connect the Light 1 or Light 2 -3. Check in the Commands tab the status of the commands -4. Click in the Datapoints the status of the datapoints \ No newline at end of file diff --git a/config/svg/1.tank.svg b/config/svg/1.tank.svg deleted file mode 100644 index a4d39bc..0000000 --- a/config/svg/1.tank.svg +++ /dev/null @@ -1,115 +0,0 @@ - - - - - - - - - - - 0.0 - - - - - - - diff --git a/config/svg/1.tank.txt b/config/svg/1.tank.txt deleted file mode 100644 index 454c21c..0000000 --- a/config/svg/1.tank.txt +++ /dev/null @@ -1,7 +0,0 @@ -This is the Tank Simulation module. -1. Go to the Communications tab and connect to the WaterTank server. -2. Click the "PUMP" button to start pumping water. -3. Open the "VALVE IN" to fill the tank with water. -4. Click the "VALVE OUT" to empty the tank. -5. Observe the alarm when the water level drops below 10. -6. Navigate to the Alarm tab to acknowledge the alarm. \ No newline at end of file diff --git a/config/svg/2.train.svg b/config/svg/2.train.svg deleted file mode 100644 index c57c388..0000000 --- a/config/svg/2.train.svg +++ /dev/null @@ -1,431 +0,0 @@ - -ABAB diff --git a/config/svg/2.train.txt b/config/svg/2.train.txt deleted file mode 100644 index f469cb3..0000000 --- a/config/svg/2.train.txt +++ /dev/null @@ -1,6 +0,0 @@ -This is the Model Train Simulation module. -1. Press the "RaspberryPi" button to connect or disconnect from the IO driver. -2. Use the "Left Control" to change the switch state (Straight or Turn). -3. If the switches are not in the same state (both Straight or both Turn), an alarm will be triggered. -4. A popup will appear, prompting you to set both switches to the same state. -5. Navigate to the Alarm tab to acknowledge the alarm and resolve the issue. \ No newline at end of file diff --git a/config/svg/3.stress_test.svg b/config/svg/3.stress_test.svg deleted file mode 100644 index ef53bd5..0000000 --- a/config/svg/3.stress_test.svg +++ /dev/null @@ -1,272 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/config/svg/3.stress_test.txt b/config/svg/3.stress_test.txt deleted file mode 100644 index 2f47197..0000000 --- a/config/svg/3.stress_test.txt +++ /dev/null @@ -1,5 +0,0 @@ -This is the Stress Test module. -1. Go to the Communications tab and connect the Stress Test driver. -2. Press the "Test" button to start the test. -3. Every 5 seconds, all datapoints will toggle between True and False. -4. Navigate to the Datapoint tab to observe how the datapoints change in real-time. \ No newline at end of file diff --git a/config/svg_system_config.json b/config/svg_system_config.json deleted file mode 100644 index e67927b..0000000 --- a/config/svg_system_config.json +++ /dev/null @@ -1,1506 +0,0 @@ -{ - "animations": { - "ALARM_SWITCHES": [ - { - "attribute": "href", - "default": "#", - "expression": { - "ACTIVE": "#alert", - "FINISHED": "#", - "INACTIVE": "#", - "ACK": "#", - "UNKNOWN": "#" - }, - "quality": { - "unknown": "#alert" - }, - "revert_after": 0, - "trigger_type": "alarm" - } - ], - "LEFT_SWITCH_STRAIGHT": [ - { - "attribute": "visibility", - "expression": { - "STRAIGHT": "visible", - "TURN": "hidden" - }, - "quality": { - "unknown": "hidden" - } - } - ], - "LEFT_SWITCH_TURN": [ - { - "attribute": "visibility", - "expression": { - "STRAIGHT": "hidden", - "TURN": "visible" - }, - "quality": { - "unknown": "hidden" - } - } - ], - "RIGHT_SWITCH_STRAIGHT": [ - { - "attribute": "visibility", - "expression": { - "STRAIGHT": "visible", - "TURN": "hidden" - }, - "quality": { - "unknown": "hidden" - } - } - ], - "RIGHT_SWITCH_TURN": [ - { - "attribute": "visibility", - "expression": { - "STRAIGHT": "hidden", - "TURN": "visible" - }, - "quality": { - "unknown": "hidden" - } - } - ], - "SIGNAL_STRAIGHT": [ - { - "attribute": "href", - "expression": { - "STRAIGHT": "#clear", - "TURN": "#stop" - }, - "quality": { - "unknown": "none" - } - } - ], - "SIGNAL_TURN": [ - { - "attribute": "href", - "expression": { - "STRAIGHT": "#stop", - "TURN": "#clear" - }, - "quality": { - "unknown": "none" - } - } - ], - "SWITCH_CONTROL": [ - { - "attribute": "fill", - "default": "gray", - "expression": { - "STRAIGHT": "green", - "TURN": "red" - }, - "quality": { - "unknown": "gray" - } - } - ], - "driver_connection": [ - { - "attribute": "fill", - "default": "gray", - "duration": 0.5, - "expression": { - "OFFLINE": "red", - "ONLINE": "green" - }, - "quality": { - "unknown": "gray" - }, - "trigger_type": "connection" - } - ], - "fill_level": [ - { - "attribute": "y", - "expression": "280 - ((value/100) * 120)", - "quality": { - "unknown": 280 - } - }, - { - "attribute": "height", - "expression": "(value/100) * 120", - "quality": { - "unknown": 0 - } - } - ], - "level_text": [ - { - "attribute": "text", - "expression": "str(value)", - "quality": { - "unknown": "0.0" - } - } - ], - "stress_square": [ - { - "attribute": "fill", - "expression": { - "FALSE": "red", - "TRUE": "green" - }, - "quality": { - "unknown": "gray" - } - } - ], - "toggle_opened_closed": [ - { - "attribute": "fill", - "expression": { - "CLOSED": "red", - "OPENED": "green" - }, - "quality": { - "unknown": "gray" - } - } - ], - "toggle_start_stop": [ - { - "attribute": "fill", - "expression": { - "STARTED": "green", - "STOPPED": "brown" - }, - "quality": { - "unknown": "gray" - } - } - ] - }, - "dp_types": { - "BOOL": { - "default": "FALSE", - "type": "enum", - "values": [ - "TRUE", - "FALSE" - ] - }, - "LEVEL": { - "default": 0, - "max": 100, - "min": 0, - "type": "float" - }, - "OPENED_CLOSED": { - "default": "CLOSED", - "type": "enum", - "values": [ - "OPENED", - "CLOSED" - ] - }, - "OPEN_CLOSE_CMD": { - "default": "CLOSE", - "type": "enum", - "values": [ - "OPEN", - "CLOSE", - "TOGGLE" - ] - }, - "PRESSURE": { - "default": 50, - "max": 200, - "min": 0, - "type": "float" - }, - "START_STOP": { - "default": "STOPPED", - "type": "enum", - "values": [ - "STARTED", - "STOPPED" - ] - }, - "START_STOP_CMD": { - "default": "STOP", - "type": "enum", - "values": [ - "START", - "STOP", - "TOGGLE" - ] - }, - "SWITCH_CONTROL": { - "default": "STRAIGHT", - "max": 100, - "min": 0, - "type": "enum", - "values": [ - "STRAIGHT", - "TURN" - ] - }, - "SWITCH_CONTROL_CMD": { - "default": "TOGGLE", - "max": 100, - "min": 0, - "type": "enum", - "values": [ - "TOGGLE", - "STRAIGHT", - "TURN" - ] - }, - "TEMPERATURE": { - "default": 120, - "max": 150, - "min": 100, - "type": "enum" - } - }, - "drivers": [ - { - "command_datapoints": [ - { - "name": "PUMP_CMD", - "type": "OPEN_CLOSE_CMD" - }, - { - "name": "DOOR_CMD", - "type": "OPEN_CLOSE_CMD" - }, - { - "name": "TEST_CMD", - "type": "START_STOP_CMD" - } - ], - "connection_info": { - "server_name": "WaterTank" - }, - "datapoints": [ - { - "name": "TANK", - "type": "LEVEL" - }, - { - "name": "PUMP", - "type": "OPENED_CLOSED" - }, - { - "name": "DOOR", - "type": "OPENED_CLOSED" - }, - { - "name": "TEST", - "type": "START_STOP" - } - ], - "driver_class": "TankTestDriver", - "name": "WaterTank" - }, - { - "command_datapoints": [ - { - "name": "VALVE_CMD", - "type": "OPENED_CLOSED_CMD" - }, - { - "name": "HEATER_CMD", - "type": "OPEN_CLOSE_CMD" - }, - { - "name": "TEST_CMD", - "type": "START_STOP_CMD" - } - ], - "connection_info": { - "server_name": "AuxServer" - }, - "datapoints": [ - { - "name": "VALVE", - "type": "OPENED_CLOSED" - }, - { - "name": "PRESSURE", - "type": "PRESSURE" - }, - { - "name": "TEMPERATURE", - "type": "TEMPERATURE" - }, - { - "name": "HEATER", - "type": "OPENED_CLOSED" - }, - { - "name": "TEST", - "type": "START_STOP" - } - ], - "driver_class": "BoilerTestDriver", - "name": "AuxServer" - }, - { - "command_datapoints": [], - "connection_info": { - "server_name": "OPCUAServer" - }, - "datapoints": [ - { - "name": "TrainTestDriver@RIGHT_SWITCH_CONTROL", - "type": "SWITCH_CONTROL" - }, - { - "name": "TrainTestDriver@LEFT_SWITCH_CONTROL", - "type": "SWITCH_CONTROL" - }, - { - "name": "TrainTestDriver@RIGHT_SWITCH_CONTROL_CMD", - "type": "SWITCH_CONTROL_CMD" - }, - { - "name": "TrainTestDriver@LEFT_SWITCH_CONTROL_CMD", - "type": "SWITCH_CONTROL_CMD" - } - ], - "driver_class": "OPCUAServerDriver", - "params": { - "allow_write_regex": ".*_CMD$", - "namespaceurl": "http://openscada-lite/opcua/server" - }, - "name": "OPCUAServer" - }, - { - "command_datapoints": [ - { - "name": "TEST_CMD", - "type": "START_STOP_CMD" - } - ], - "connection_info": { - "server_name": "StressTest" - }, - "datapoints": [ - { - "name": "TEST", - "type": "START_STOP" - }, - { - "name": "DP_0", - "type": "BOOL" - }, - { - "name": "DP_1", - "type": "BOOL" - }, - { - "name": "DP_2", - "type": "BOOL" - }, - { - "name": "DP_3", - "type": "BOOL" - }, - { - "name": "DP_4", - "type": "BOOL" - }, - { - "name": "DP_5", - "type": "BOOL" - }, - { - "name": "DP_6", - "type": "BOOL" - }, - { - "name": "DP_7", - "type": "BOOL" - }, - { - "name": "DP_8", - "type": "BOOL" - }, - { - "name": "DP_9", - "type": "BOOL" - }, - { - "name": "DP_10", - "type": "BOOL" - }, - { - "name": "DP_11", - "type": "BOOL" - }, - { - "name": "DP_12", - "type": "BOOL" - }, - { - "name": "DP_13", - "type": "BOOL" - }, - { - "name": "DP_14", - "type": "BOOL" - }, - { - "name": "DP_15", - "type": "BOOL" - }, - { - "name": "DP_16", - "type": "BOOL" - }, - { - "name": "DP_17", - "type": "BOOL" - }, - { - "name": "DP_18", - "type": "BOOL" - }, - { - "name": "DP_19", - "type": "BOOL" - }, - { - "name": "DP_20", - "type": "BOOL" - }, - { - "name": "DP_21", - "type": "BOOL" - }, - { - "name": "DP_22", - "type": "BOOL" - }, - { - "name": "DP_23", - "type": "BOOL" - }, - { - "name": "DP_24", - "type": "BOOL" - }, - { - "name": "DP_25", - "type": "BOOL" - }, - { - "name": "DP_26", - "type": "BOOL" - }, - { - "name": "DP_27", - "type": "BOOL" - }, - { - "name": "DP_28", - "type": "BOOL" - }, - { - "name": "DP_29", - "type": "BOOL" - }, - { - "name": "DP_30", - "type": "BOOL" - }, - { - "name": "DP_31", - "type": "BOOL" - }, - { - "name": "DP_32", - "type": "BOOL" - }, - { - "name": "DP_33", - "type": "BOOL" - }, - { - "name": "DP_34", - "type": "BOOL" - }, - { - "name": "DP_35", - "type": "BOOL" - }, - { - "name": "DP_36", - "type": "BOOL" - }, - { - "name": "DP_37", - "type": "BOOL" - }, - { - "name": "DP_38", - "type": "BOOL" - }, - { - "name": "DP_39", - "type": "BOOL" - }, - { - "name": "DP_40", - "type": "BOOL" - }, - { - "name": "DP_41", - "type": "BOOL" - }, - { - "name": "DP_42", - "type": "BOOL" - }, - { - "name": "DP_43", - "type": "BOOL" - }, - { - "name": "DP_44", - "type": "BOOL" - }, - { - "name": "DP_45", - "type": "BOOL" - }, - { - "name": "DP_46", - "type": "BOOL" - }, - { - "name": "DP_47", - "type": "BOOL" - }, - { - "name": "DP_48", - "type": "BOOL" - }, - { - "name": "DP_49", - "type": "BOOL" - }, - { - "name": "DP_50", - "type": "BOOL" - }, - { - "name": "DP_51", - "type": "BOOL" - }, - { - "name": "DP_52", - "type": "BOOL" - }, - { - "name": "DP_53", - "type": "BOOL" - }, - { - "name": "DP_54", - "type": "BOOL" - }, - { - "name": "DP_55", - "type": "BOOL" - }, - { - "name": "DP_56", - "type": "BOOL" - }, - { - "name": "DP_57", - "type": "BOOL" - }, - { - "name": "DP_58", - "type": "BOOL" - }, - { - "name": "DP_59", - "type": "BOOL" - }, - { - "name": "DP_60", - "type": "BOOL" - }, - { - "name": "DP_61", - "type": "BOOL" - }, - { - "name": "DP_62", - "type": "BOOL" - }, - { - "name": "DP_63", - "type": "BOOL" - }, - { - "name": "DP_64", - "type": "BOOL" - }, - { - "name": "DP_65", - "type": "BOOL" - }, - { - "name": "DP_66", - "type": "BOOL" - }, - { - "name": "DP_67", - "type": "BOOL" - }, - { - "name": "DP_68", - "type": "BOOL" - }, - { - "name": "DP_69", - "type": "BOOL" - }, - { - "name": "DP_70", - "type": "BOOL" - }, - { - "name": "DP_71", - "type": "BOOL" - }, - { - "name": "DP_72", - "type": "BOOL" - }, - { - "name": "DP_73", - "type": "BOOL" - }, - { - "name": "DP_74", - "type": "BOOL" - }, - { - "name": "DP_75", - "type": "BOOL" - }, - { - "name": "DP_76", - "type": "BOOL" - }, - { - "name": "DP_77", - "type": "BOOL" - }, - { - "name": "DP_78", - "type": "BOOL" - }, - { - "name": "DP_79", - "type": "BOOL" - }, - { - "name": "DP_80", - "type": "BOOL" - }, - { - "name": "DP_81", - "type": "BOOL" - }, - { - "name": "DP_82", - "type": "BOOL" - }, - { - "name": "DP_83", - "type": "BOOL" - }, - { - "name": "DP_84", - "type": "BOOL" - }, - { - "name": "DP_85", - "type": "BOOL" - }, - { - "name": "DP_86", - "type": "BOOL" - }, - { - "name": "DP_87", - "type": "BOOL" - }, - { - "name": "DP_88", - "type": "BOOL" - }, - { - "name": "DP_89", - "type": "BOOL" - }, - { - "name": "DP_90", - "type": "BOOL" - }, - { - "name": "DP_91", - "type": "BOOL" - }, - { - "name": "DP_92", - "type": "BOOL" - }, - { - "name": "DP_93", - "type": "BOOL" - }, - { - "name": "DP_94", - "type": "BOOL" - }, - { - "name": "DP_95", - "type": "BOOL" - }, - { - "name": "DP_96", - "type": "BOOL" - }, - { - "name": "DP_97", - "type": "BOOL" - }, - { - "name": "DP_98", - "type": "BOOL" - }, - { - "name": "DP_99", - "type": "BOOL" - }, - { - "name": "DP_100", - "type": "BOOL" - }, - { - "name": "DP_101", - "type": "BOOL" - }, - { - "name": "DP_102", - "type": "BOOL" - }, - { - "name": "DP_103", - "type": "BOOL" - }, - { - "name": "DP_104", - "type": "BOOL" - }, - { - "name": "DP_105", - "type": "BOOL" - }, - { - "name": "DP_106", - "type": "BOOL" - }, - { - "name": "DP_107", - "type": "BOOL" - }, - { - "name": "DP_108", - "type": "BOOL" - }, - { - "name": "DP_109", - "type": "BOOL" - }, - { - "name": "DP_110", - "type": "BOOL" - }, - { - "name": "DP_111", - "type": "BOOL" - }, - { - "name": "DP_112", - "type": "BOOL" - }, - { - "name": "DP_113", - "type": "BOOL" - }, - { - "name": "DP_114", - "type": "BOOL" - }, - { - "name": "DP_115", - "type": "BOOL" - }, - { - "name": "DP_116", - "type": "BOOL" - }, - { - "name": "DP_117", - "type": "BOOL" - }, - { - "name": "DP_118", - "type": "BOOL" - }, - { - "name": "DP_119", - "type": "BOOL" - }, - { - "name": "DP_120", - "type": "BOOL" - }, - { - "name": "DP_121", - "type": "BOOL" - }, - { - "name": "DP_122", - "type": "BOOL" - }, - { - "name": "DP_123", - "type": "BOOL" - }, - { - "name": "DP_124", - "type": "BOOL" - }, - { - "name": "DP_125", - "type": "BOOL" - }, - { - "name": "DP_126", - "type": "BOOL" - }, - { - "name": "DP_127", - "type": "BOOL" - }, - { - "name": "DP_128", - "type": "BOOL" - }, - { - "name": "DP_129", - "type": "BOOL" - }, - { - "name": "DP_130", - "type": "BOOL" - }, - { - "name": "DP_131", - "type": "BOOL" - }, - { - "name": "DP_132", - "type": "BOOL" - }, - { - "name": "DP_133", - "type": "BOOL" - }, - { - "name": "DP_134", - "type": "BOOL" - }, - { - "name": "DP_135", - "type": "BOOL" - }, - { - "name": "DP_136", - "type": "BOOL" - }, - { - "name": "DP_137", - "type": "BOOL" - }, - { - "name": "DP_138", - "type": "BOOL" - }, - { - "name": "DP_139", - "type": "BOOL" - }, - { - "name": "DP_140", - "type": "BOOL" - }, - { - "name": "DP_141", - "type": "BOOL" - }, - { - "name": "DP_142", - "type": "BOOL" - }, - { - "name": "DP_143", - "type": "BOOL" - }, - { - "name": "DP_144", - "type": "BOOL" - }, - { - "name": "DP_145", - "type": "BOOL" - }, - { - "name": "DP_146", - "type": "BOOL" - }, - { - "name": "DP_147", - "type": "BOOL" - }, - { - "name": "DP_148", - "type": "BOOL" - }, - { - "name": "DP_149", - "type": "BOOL" - }, - { - "name": "DP_150", - "type": "BOOL" - }, - { - "name": "DP_151", - "type": "BOOL" - }, - { - "name": "DP_152", - "type": "BOOL" - }, - { - "name": "DP_153", - "type": "BOOL" - }, - { - "name": "DP_154", - "type": "BOOL" - }, - { - "name": "DP_155", - "type": "BOOL" - }, - { - "name": "DP_156", - "type": "BOOL" - }, - { - "name": "DP_157", - "type": "BOOL" - }, - { - "name": "DP_158", - "type": "BOOL" - }, - { - "name": "DP_159", - "type": "BOOL" - }, - { - "name": "DP_160", - "type": "BOOL" - }, - { - "name": "DP_161", - "type": "BOOL" - }, - { - "name": "DP_162", - "type": "BOOL" - }, - { - "name": "DP_163", - "type": "BOOL" - }, - { - "name": "DP_164", - "type": "BOOL" - }, - { - "name": "DP_165", - "type": "BOOL" - }, - { - "name": "DP_166", - "type": "BOOL" - }, - { - "name": "DP_167", - "type": "BOOL" - }, - { - "name": "DP_168", - "type": "BOOL" - }, - { - "name": "DP_169", - "type": "BOOL" - }, - { - "name": "DP_170", - "type": "BOOL" - }, - { - "name": "DP_171", - "type": "BOOL" - }, - { - "name": "DP_172", - "type": "BOOL" - }, - { - "name": "DP_173", - "type": "BOOL" - }, - { - "name": "DP_174", - "type": "BOOL" - }, - { - "name": "DP_175", - "type": "BOOL" - }, - { - "name": "DP_176", - "type": "BOOL" - }, - { - "name": "DP_177", - "type": "BOOL" - }, - { - "name": "DP_178", - "type": "BOOL" - }, - { - "name": "DP_179", - "type": "BOOL" - }, - { - "name": "DP_180", - "type": "BOOL" - }, - { - "name": "DP_181", - "type": "BOOL" - }, - { - "name": "DP_182", - "type": "BOOL" - }, - { - "name": "DP_183", - "type": "BOOL" - }, - { - "name": "DP_184", - "type": "BOOL" - }, - { - "name": "DP_185", - "type": "BOOL" - }, - { - "name": "DP_186", - "type": "BOOL" - }, - { - "name": "DP_187", - "type": "BOOL" - }, - { - "name": "DP_188", - "type": "BOOL" - }, - { - "name": "DP_189", - "type": "BOOL" - }, - { - "name": "DP_190", - "type": "BOOL" - }, - { - "name": "DP_191", - "type": "BOOL" - }, - { - "name": "DP_192", - "type": "BOOL" - }, - { - "name": "DP_193", - "type": "BOOL" - }, - { - "name": "DP_194", - "type": "BOOL" - }, - { - "name": "DP_195", - "type": "BOOL" - }, - { - "name": "DP_196", - "type": "BOOL" - }, - { - "name": "DP_197", - "type": "BOOL" - }, - { - "name": "DP_198", - "type": "BOOL" - }, - { - "name": "DP_199", - "type": "BOOL" - }, - { - "name": "DP_200", - "type": "BOOL" - }, - { - "name": "DP_201", - "type": "BOOL" - }, - { - "name": "DP_202", - "type": "BOOL" - }, - { - "name": "DP_203", - "type": "BOOL" - }, - { - "name": "DP_204", - "type": "BOOL" - }, - { - "name": "DP_205", - "type": "BOOL" - }, - { - "name": "DP_206", - "type": "BOOL" - }, - { - "name": "DP_207", - "type": "BOOL" - }, - { - "name": "DP_208", - "type": "BOOL" - }, - { - "name": "DP_209", - "type": "BOOL" - }, - { - "name": "DP_210", - "type": "BOOL" - }, - { - "name": "DP_211", - "type": "BOOL" - }, - { - "name": "DP_212", - "type": "BOOL" - }, - { - "name": "DP_213", - "type": "BOOL" - }, - { - "name": "DP_214", - "type": "BOOL" - }, - { - "name": "DP_215", - "type": "BOOL" - }, - { - "name": "DP_216", - "type": "BOOL" - }, - { - "name": "DP_217", - "type": "BOOL" - }, - { - "name": "DP_218", - "type": "BOOL" - }, - { - "name": "DP_219", - "type": "BOOL" - }, - { - "name": "DP_220", - "type": "BOOL" - }, - { - "name": "DP_221", - "type": "BOOL" - }, - { - "name": "DP_222", - "type": "BOOL" - }, - { - "name": "DP_223", - "type": "BOOL" - }, - { - "name": "DP_224", - "type": "BOOL" - }, - { - "name": "DP_225", - "type": "BOOL" - }, - { - "name": "DP_226", - "type": "BOOL" - }, - { - "name": "DP_227", - "type": "BOOL" - }, - { - "name": "DP_228", - "type": "BOOL" - }, - { - "name": "DP_229", - "type": "BOOL" - }, - { - "name": "DP_230", - "type": "BOOL" - }, - { - "name": "DP_231", - "type": "BOOL" - }, - { - "name": "DP_232", - "type": "BOOL" - }, - { - "name": "DP_233", - "type": "BOOL" - }, - { - "name": "DP_234", - "type": "BOOL" - }, - { - "name": "DP_235", - "type": "BOOL" - }, - { - "name": "DP_236", - "type": "BOOL" - }, - { - "name": "DP_237", - "type": "BOOL" - }, - { - "name": "DP_238", - "type": "BOOL" - }, - { - "name": "DP_239", - "type": "BOOL" - }, - { - "name": "DP_240", - "type": "BOOL" - }, - { - "name": "DP_241", - "type": "BOOL" - }, - { - "name": "DP_242", - "type": "BOOL" - }, - { - "name": "DP_243", - "type": "BOOL" - }, - { - "name": "DP_244", - "type": "BOOL" - }, - { - "name": "DP_245", - "type": "BOOL" - }, - { - "name": "DP_246", - "type": "BOOL" - }, - { - "name": "DP_247", - "type": "BOOL" - }, - { - "name": "DP_248", - "type": "BOOL" - }, - { - "name": "DP_249", - "type": "BOOL" - } - ], - "driver_class": "StressTestDriver", - "name": "StressTest", - "params": { - "toggle_delay": 0.02 - } - }, - { - "command_datapoints": [ - { - "name": "RIGHT_SWITCH_CONTROL_CMD", - "type": "SWITCH_CONTROL_CMD" - }, - { - "name": "LEFT_SWITCH_CONTROL_CMD", - "type": "SWITCH_CONTROL_CMD" - } - ], - "connection_info": { - "server_name": "TrainTestDriver" - }, - "datapoints": [ - { - "name": "RIGHT_SWITCH_CONTROL", - "type": "SWITCH_CONTROL" - }, - { - "name": "LEFT_SWITCH_CONTROL", - "type": "SWITCH_CONTROL" - } - ], - "driver_class": "TrainTestDriver", - "name": "TrainTestDriver" - } - ], - "modules": [ - { - "name": "alarm" - }, - { - "name": "command" - }, - { - "name": "communication" - }, - { - "name": "datapoint" - }, - { - "name": "animation" - }, - { - "name": "alert" - }, - { - "name": "rule" - }, - { - "name": "tracking" - }, - { - "name": "frontend", - "config": { - "tabs": [ - "Image", - "Datapoints", - "Alarms", - "Communications", - "Commands", - "Tracking" - ] - } - } - ], - "rules": [ - { - "on_actions": [ - "send_command('WaterTank@PUMP_CMD', 'OPEN')", - "send_command('WaterTank@DOOR_CMD', 'CLOSE')", - "raise_alarm()" - ], - "on_condition": "float(WaterTank@TANK) < 10", - "rule_id": "pump_start_if_low_level" - }, - { - "on_actions": [ - "raise_alarm()" - ], - "on_condition": "WaterTank@DOOR == OPENED", - "rule_id": "door_open_alarm" - }, - { - "off_actions": [], - "off_condition": "", - "on_actions": [ - "raise_alarm()", - "client_alert('Switches are in an invalid state!\\nSolve issue by setting right switch to TURN?', 'confirm_cancel', 'TrainTestDriver@RIGHT_SWITCH_CONTROL_CMD', 'TURN', 30)" - ], - "on_condition": "TrainTestDriver@RIGHT_SWITCH_CONTROL == 'STRAIGHT' and TrainTestDriver@LEFT_SWITCH_CONTROL == 'TURN'", - "rule_id": "switch_error_straight" - }, - { - "off_actions": [], - "off_condition": "", - "on_actions": [ - "raise_alarm()", - "client_alert('Switches are in an invalid state!\\nSolve issue by setting right switch to STRAIGHT?', 'confirm_cancel', 'TrainTestDriver@RIGHT_SWITCH_CONTROL_CMD', 'STRAIGHT', 30)" - ], - "on_condition": "TrainTestDriver@RIGHT_SWITCH_CONTROL == 'TURN' and TrainTestDriver@LEFT_SWITCH_CONTROL == 'STRAIGHT'", - "rule_id": "switch_error_turn" - } - ] -} \ No newline at end of file diff --git a/config/system_config.json b/config/system_config.json deleted file mode 100644 index a7b997d..0000000 --- a/config/system_config.json +++ /dev/null @@ -1,1730 +0,0 @@ -{ - "dp_types": { - "BOOL": { - "default": "FALSE", - "type": "enum", - "values": [ - "TRUE", - "FALSE" - ] - }, - "CAMERA_POSITION": { - "default": "RIGHT", - "type": "enum", - "values": [ - "LEFT", - "RIGHT" - ] - }, - "LEVEL": { - "default": 0, - "max": 100, - "min": 0, - "type": "float" - }, - "OPENED_CLOSED": { - "default": "CLOSED", - "type": "enum", - "values": [ - "OPENED", - "CLOSED" - ] - }, - "OPEN_CLOSE_CMD": { - "default": "CLOSE", - "type": "enum", - "values": [ - "OPEN", - "CLOSE", - "TOGGLE" - ] - }, - "PRESSURE": { - "default": 50, - "max": 200, - "min": 0, - "type": "float" - }, - "START_STOP": { - "default": "STOPPED", - "type": "enum", - "values": [ - "STARTED", - "STOPPED" - ] - }, - "START_STOP_CMD": { - "default": "STOP", - "type": "enum", - "values": [ - "START", - "STOP", - "TOGGLE" - ] - }, - "SWITCH_CONTROL": { - "default": "STRAIGHT", - "max": 100, - "min": 0, - "type": "enum", - "values": [ - "STRAIGHT", - "TURN" - ] - }, - "SWITCH_CONTROL_CMD": { - "default": "TOGGLE", - "max": 100, - "min": 0, - "type": "enum", - "values": [ - "TOGGLE", - "STRAIGHT", - "TURN" - ] - }, - "TEMPERATURE": { - "default": 120, - "max": 150, - "min": 100, - "type": "enum" - }, - "ON_OFF": { - "default": "OFF", - "type": "enum", - "values": [ - "ON", - "OFF", - "TOGGLE" - ] - } - }, - "drivers": [ - { - "name": "TasmotaRelayDriver", - "driver_class": "MQTTTasmotaRelayDriver", - "datapoints": [ - { "name": "RELAY_1_STATUS", "type": "ON_OFF" }, - { "name": "RELAY_2_STATUS", "type": "ON_OFF" } - ], - "command_datapoints": [ - { "name": "RELAY_1_CMD", "type": "ON_OFF" }, - { "name": "RELAY_2_CMD", "type": "ON_OFF" } - ], - "params": { - "host": "orangepizero3", - "port": 1883, - "client_id": "openscada-tasmota-xmas", - "username": "scada", - "password": "dani23", - "device_topic": "xmas", - "relay_mapping": { - "RELAY_1": "POWER1", - "RELAY_2": "POWER2" - }, - "subscriptions": [ - { - "topic": "stat/xmas/POWER+", - "type": "status" - }, - { - "topic": "stat/xmas/RESULT", - "type": "feedback" - } - ], - "publish": { - "command": "cmnd/{device}/{power}" - } - } - }, - { - "command_datapoints": [], - "datapoints": [ - { - "name": "CameraDriver@CAMERA_1_POSITION", - "type": "CAMERA_POSITION" - }, - { - "name": "CameraDriver@CAMERA_2_POSITION", - "type": "CAMERA_POSITION" - }, - { - "name": "CameraDriver@CAMERA_3_POSITION", - "type": "CAMERA_POSITION" - }, - { - "name": "CameraDriver@CAMERA_1_POSITION_CMD", - "type": "CAMERA_POSITION" - }, - { - "name": "CameraDriver@CAMERA_2_POSITION_CMD", - "type": "CAMERA_POSITION" - }, - { - "name": "CameraDriver@CAMERA_3_POSITION_CMD", - "type": "CAMERA_POSITION" - } - ], - "driver_class": "OPCUAServerDriver", - "params": { - "allow_write_regex": ".*_CMD$", - "namespaceurl": "http://openscada-lite/opcua/server" - }, - "name": "OPCUAServerDriver" - }, - { - "command_datapoints": [ - { - "name": "CAMERA_1_POSITION_CMD", - "type": "CAMERA_POSITION" - }, - { - "name": "CAMERA_2_POSITION_CMD", - "type": "CAMERA_POSITION" - }, - { - "name": "CAMERA_3_POSITION_CMD", - "type": "CAMERA_POSITION" - } - ], - "datapoints": [ - { - "name": "CAMERA_1_POSITION", - "type": "CAMERA_POSITION" - }, - { - "name": "CAMERA_2_POSITION", - "type": "CAMERA_POSITION" - }, - { - "name": "CAMERA_3_POSITION", - "type": "CAMERA_POSITION" - }, - { - "name": "CAMERA_1_ALARM", - "type": "BOOL" - }, - { - "name": "CAMERA_2_ALARM", - "type": "BOOL" - }, - { - "name": "CAMERA_3_ALARM", - "type": "BOOL" - } - ], - "driver_class": "CameraDriver", - "name": "CameraDriver" - }, - { - "command_datapoints": [ - { - "name": "PUMP_CMD", - "type": "OPEN_CLOSE_CMD" - }, - { - "name": "DOOR_CMD", - "type": "OPEN_CLOSE_CMD" - }, - { - "name": "TEST_CMD", - "type": "START_STOP_CMD" - } - ], - "datapoints": [ - { - "name": "TANK", - "type": "LEVEL" - }, - { - "name": "PUMP", - "type": "OPENED_CLOSED" - }, - { - "name": "DOOR", - "type": "OPENED_CLOSED" - }, - { - "name": "TEST", - "type": "START_STOP" - } - ], - "driver_class": "TankTestDriver", - "name": "WaterTank" - }, - { - "command_datapoints": [ - { - "name": "TEST_CMD", - "type": "START_STOP_CMD" - } - ], - "datapoints": [ - { - "name": "TEST", - "type": "START_STOP" - }, - { - "name": "DP_0", - "type": "BOOL" - }, - { - "name": "DP_1", - "type": "BOOL" - }, - { - "name": "DP_2", - "type": "BOOL" - }, - { - "name": "DP_3", - "type": "BOOL" - }, - { - "name": "DP_4", - "type": "BOOL" - }, - { - "name": "DP_5", - "type": "BOOL" - }, - { - "name": "DP_6", - "type": "BOOL" - }, - { - "name": "DP_7", - "type": "BOOL" - }, - { - "name": "DP_8", - "type": "BOOL" - }, - { - "name": "DP_9", - "type": "BOOL" - }, - { - "name": "DP_10", - "type": "BOOL" - }, - { - "name": "DP_11", - "type": "BOOL" - }, - { - "name": "DP_12", - "type": "BOOL" - }, - { - "name": "DP_13", - "type": "BOOL" - }, - { - "name": "DP_14", - "type": "BOOL" - }, - { - "name": "DP_15", - "type": "BOOL" - }, - { - "name": "DP_16", - "type": "BOOL" - }, - { - "name": "DP_17", - "type": "BOOL" - }, - { - "name": "DP_18", - "type": "BOOL" - }, - { - "name": "DP_19", - "type": "BOOL" - }, - { - "name": "DP_20", - "type": "BOOL" - }, - { - "name": "DP_21", - "type": "BOOL" - }, - { - "name": "DP_22", - "type": "BOOL" - }, - { - "name": "DP_23", - "type": "BOOL" - }, - { - "name": "DP_24", - "type": "BOOL" - }, - { - "name": "DP_25", - "type": "BOOL" - }, - { - "name": "DP_26", - "type": "BOOL" - }, - { - "name": "DP_27", - "type": "BOOL" - }, - { - "name": "DP_28", - "type": "BOOL" - }, - { - "name": "DP_29", - "type": "BOOL" - }, - { - "name": "DP_30", - "type": "BOOL" - }, - { - "name": "DP_31", - "type": "BOOL" - }, - { - "name": "DP_32", - "type": "BOOL" - }, - { - "name": "DP_33", - "type": "BOOL" - }, - { - "name": "DP_34", - "type": "BOOL" - }, - { - "name": "DP_35", - "type": "BOOL" - }, - { - "name": "DP_36", - "type": "BOOL" - }, - { - "name": "DP_37", - "type": "BOOL" - }, - { - "name": "DP_38", - "type": "BOOL" - }, - { - "name": "DP_39", - "type": "BOOL" - }, - { - "name": "DP_40", - "type": "BOOL" - }, - { - "name": "DP_41", - "type": "BOOL" - }, - { - "name": "DP_42", - "type": "BOOL" - }, - { - "name": "DP_43", - "type": "BOOL" - }, - { - "name": "DP_44", - "type": "BOOL" - }, - { - "name": "DP_45", - "type": "BOOL" - }, - { - "name": "DP_46", - "type": "BOOL" - }, - { - "name": "DP_47", - "type": "BOOL" - }, - { - "name": "DP_48", - "type": "BOOL" - }, - { - "name": "DP_49", - "type": "BOOL" - }, - { - "name": "DP_50", - "type": "BOOL" - }, - { - "name": "DP_51", - "type": "BOOL" - }, - { - "name": "DP_52", - "type": "BOOL" - }, - { - "name": "DP_53", - "type": "BOOL" - }, - { - "name": "DP_54", - "type": "BOOL" - }, - { - "name": "DP_55", - "type": "BOOL" - }, - { - "name": "DP_56", - "type": "BOOL" - }, - { - "name": "DP_57", - "type": "BOOL" - }, - { - "name": "DP_58", - "type": "BOOL" - }, - { - "name": "DP_59", - "type": "BOOL" - }, - { - "name": "DP_60", - "type": "BOOL" - }, - { - "name": "DP_61", - "type": "BOOL" - }, - { - "name": "DP_62", - "type": "BOOL" - }, - { - "name": "DP_63", - "type": "BOOL" - }, - { - "name": "DP_64", - "type": "BOOL" - }, - { - "name": "DP_65", - "type": "BOOL" - }, - { - "name": "DP_66", - "type": "BOOL" - }, - { - "name": "DP_67", - "type": "BOOL" - }, - { - "name": "DP_68", - "type": "BOOL" - }, - { - "name": "DP_69", - "type": "BOOL" - }, - { - "name": "DP_70", - "type": "BOOL" - }, - { - "name": "DP_71", - "type": "BOOL" - }, - { - "name": "DP_72", - "type": "BOOL" - }, - { - "name": "DP_73", - "type": "BOOL" - }, - { - "name": "DP_74", - "type": "BOOL" - }, - { - "name": "DP_75", - "type": "BOOL" - }, - { - "name": "DP_76", - "type": "BOOL" - }, - { - "name": "DP_77", - "type": "BOOL" - }, - { - "name": "DP_78", - "type": "BOOL" - }, - { - "name": "DP_79", - "type": "BOOL" - }, - { - "name": "DP_80", - "type": "BOOL" - }, - { - "name": "DP_81", - "type": "BOOL" - }, - { - "name": "DP_82", - "type": "BOOL" - }, - { - "name": "DP_83", - "type": "BOOL" - }, - { - "name": "DP_84", - "type": "BOOL" - }, - { - "name": "DP_85", - "type": "BOOL" - }, - { - "name": "DP_86", - "type": "BOOL" - }, - { - "name": "DP_87", - "type": "BOOL" - }, - { - "name": "DP_88", - "type": "BOOL" - }, - { - "name": "DP_89", - "type": "BOOL" - }, - { - "name": "DP_90", - "type": "BOOL" - }, - { - "name": "DP_91", - "type": "BOOL" - }, - { - "name": "DP_92", - "type": "BOOL" - }, - { - "name": "DP_93", - "type": "BOOL" - }, - { - "name": "DP_94", - "type": "BOOL" - }, - { - "name": "DP_95", - "type": "BOOL" - }, - { - "name": "DP_96", - "type": "BOOL" - }, - { - "name": "DP_97", - "type": "BOOL" - }, - { - "name": "DP_98", - "type": "BOOL" - }, - { - "name": "DP_99", - "type": "BOOL" - }, - { - "name": "DP_100", - "type": "BOOL" - }, - { - "name": "DP_101", - "type": "BOOL" - }, - { - "name": "DP_102", - "type": "BOOL" - }, - { - "name": "DP_103", - "type": "BOOL" - }, - { - "name": "DP_104", - "type": "BOOL" - }, - { - "name": "DP_105", - "type": "BOOL" - }, - { - "name": "DP_106", - "type": "BOOL" - }, - { - "name": "DP_107", - "type": "BOOL" - }, - { - "name": "DP_108", - "type": "BOOL" - }, - { - "name": "DP_109", - "type": "BOOL" - }, - { - "name": "DP_110", - "type": "BOOL" - }, - { - "name": "DP_111", - "type": "BOOL" - }, - { - "name": "DP_112", - "type": "BOOL" - }, - { - "name": "DP_113", - "type": "BOOL" - }, - { - "name": "DP_114", - "type": "BOOL" - }, - { - "name": "DP_115", - "type": "BOOL" - }, - { - "name": "DP_116", - "type": "BOOL" - }, - { - "name": "DP_117", - "type": "BOOL" - }, - { - "name": "DP_118", - "type": "BOOL" - }, - { - "name": "DP_119", - "type": "BOOL" - }, - { - "name": "DP_120", - "type": "BOOL" - }, - { - "name": "DP_121", - "type": "BOOL" - }, - { - "name": "DP_122", - "type": "BOOL" - }, - { - "name": "DP_123", - "type": "BOOL" - }, - { - "name": "DP_124", - "type": "BOOL" - }, - { - "name": "DP_125", - "type": "BOOL" - }, - { - "name": "DP_126", - "type": "BOOL" - }, - { - "name": "DP_127", - "type": "BOOL" - }, - { - "name": "DP_128", - "type": "BOOL" - }, - { - "name": "DP_129", - "type": "BOOL" - }, - { - "name": "DP_130", - "type": "BOOL" - }, - { - "name": "DP_131", - "type": "BOOL" - }, - { - "name": "DP_132", - "type": "BOOL" - }, - { - "name": "DP_133", - "type": "BOOL" - }, - { - "name": "DP_134", - "type": "BOOL" - }, - { - "name": "DP_135", - "type": "BOOL" - }, - { - "name": "DP_136", - "type": "BOOL" - }, - { - "name": "DP_137", - "type": "BOOL" - }, - { - "name": "DP_138", - "type": "BOOL" - }, - { - "name": "DP_139", - "type": "BOOL" - }, - { - "name": "DP_140", - "type": "BOOL" - }, - { - "name": "DP_141", - "type": "BOOL" - }, - { - "name": "DP_142", - "type": "BOOL" - }, - { - "name": "DP_143", - "type": "BOOL" - }, - { - "name": "DP_144", - "type": "BOOL" - }, - { - "name": "DP_145", - "type": "BOOL" - }, - { - "name": "DP_146", - "type": "BOOL" - }, - { - "name": "DP_147", - "type": "BOOL" - }, - { - "name": "DP_148", - "type": "BOOL" - }, - { - "name": "DP_149", - "type": "BOOL" - }, - { - "name": "DP_150", - "type": "BOOL" - }, - { - "name": "DP_151", - "type": "BOOL" - }, - { - "name": "DP_152", - "type": "BOOL" - }, - { - "name": "DP_153", - "type": "BOOL" - }, - { - "name": "DP_154", - "type": "BOOL" - }, - { - "name": "DP_155", - "type": "BOOL" - }, - { - "name": "DP_156", - "type": "BOOL" - }, - { - "name": "DP_157", - "type": "BOOL" - }, - { - "name": "DP_158", - "type": "BOOL" - }, - { - "name": "DP_159", - "type": "BOOL" - }, - { - "name": "DP_160", - "type": "BOOL" - }, - { - "name": "DP_161", - "type": "BOOL" - }, - { - "name": "DP_162", - "type": "BOOL" - }, - { - "name": "DP_163", - "type": "BOOL" - }, - { - "name": "DP_164", - "type": "BOOL" - }, - { - "name": "DP_165", - "type": "BOOL" - }, - { - "name": "DP_166", - "type": "BOOL" - }, - { - "name": "DP_167", - "type": "BOOL" - }, - { - "name": "DP_168", - "type": "BOOL" - }, - { - "name": "DP_169", - "type": "BOOL" - }, - { - "name": "DP_170", - "type": "BOOL" - }, - { - "name": "DP_171", - "type": "BOOL" - }, - { - "name": "DP_172", - "type": "BOOL" - }, - { - "name": "DP_173", - "type": "BOOL" - }, - { - "name": "DP_174", - "type": "BOOL" - }, - { - "name": "DP_175", - "type": "BOOL" - }, - { - "name": "DP_176", - "type": "BOOL" - }, - { - "name": "DP_177", - "type": "BOOL" - }, - { - "name": "DP_178", - "type": "BOOL" - }, - { - "name": "DP_179", - "type": "BOOL" - }, - { - "name": "DP_180", - "type": "BOOL" - }, - { - "name": "DP_181", - "type": "BOOL" - }, - { - "name": "DP_182", - "type": "BOOL" - }, - { - "name": "DP_183", - "type": "BOOL" - }, - { - "name": "DP_184", - "type": "BOOL" - }, - { - "name": "DP_185", - "type": "BOOL" - }, - { - "name": "DP_186", - "type": "BOOL" - }, - { - "name": "DP_187", - "type": "BOOL" - }, - { - "name": "DP_188", - "type": "BOOL" - }, - { - "name": "DP_189", - "type": "BOOL" - }, - { - "name": "DP_190", - "type": "BOOL" - }, - { - "name": "DP_191", - "type": "BOOL" - }, - { - "name": "DP_192", - "type": "BOOL" - }, - { - "name": "DP_193", - "type": "BOOL" - }, - { - "name": "DP_194", - "type": "BOOL" - }, - { - "name": "DP_195", - "type": "BOOL" - }, - { - "name": "DP_196", - "type": "BOOL" - }, - { - "name": "DP_197", - "type": "BOOL" - }, - { - "name": "DP_198", - "type": "BOOL" - }, - { - "name": "DP_199", - "type": "BOOL" - }, - { - "name": "DP_200", - "type": "BOOL" - }, - { - "name": "DP_201", - "type": "BOOL" - }, - { - "name": "DP_202", - "type": "BOOL" - }, - { - "name": "DP_203", - "type": "BOOL" - }, - { - "name": "DP_204", - "type": "BOOL" - }, - { - "name": "DP_205", - "type": "BOOL" - }, - { - "name": "DP_206", - "type": "BOOL" - }, - { - "name": "DP_207", - "type": "BOOL" - }, - { - "name": "DP_208", - "type": "BOOL" - }, - { - "name": "DP_209", - "type": "BOOL" - }, - { - "name": "DP_210", - "type": "BOOL" - }, - { - "name": "DP_211", - "type": "BOOL" - }, - { - "name": "DP_212", - "type": "BOOL" - }, - { - "name": "DP_213", - "type": "BOOL" - }, - { - "name": "DP_214", - "type": "BOOL" - }, - { - "name": "DP_215", - "type": "BOOL" - }, - { - "name": "DP_216", - "type": "BOOL" - }, - { - "name": "DP_217", - "type": "BOOL" - }, - { - "name": "DP_218", - "type": "BOOL" - }, - { - "name": "DP_219", - "type": "BOOL" - }, - { - "name": "DP_220", - "type": "BOOL" - }, - { - "name": "DP_221", - "type": "BOOL" - }, - { - "name": "DP_222", - "type": "BOOL" - }, - { - "name": "DP_223", - "type": "BOOL" - }, - { - "name": "DP_224", - "type": "BOOL" - }, - { - "name": "DP_225", - "type": "BOOL" - }, - { - "name": "DP_226", - "type": "BOOL" - }, - { - "name": "DP_227", - "type": "BOOL" - }, - { - "name": "DP_228", - "type": "BOOL" - }, - { - "name": "DP_229", - "type": "BOOL" - }, - { - "name": "DP_230", - "type": "BOOL" - }, - { - "name": "DP_231", - "type": "BOOL" - }, - { - "name": "DP_232", - "type": "BOOL" - }, - { - "name": "DP_233", - "type": "BOOL" - }, - { - "name": "DP_234", - "type": "BOOL" - }, - { - "name": "DP_235", - "type": "BOOL" - }, - { - "name": "DP_236", - "type": "BOOL" - }, - { - "name": "DP_237", - "type": "BOOL" - }, - { - "name": "DP_238", - "type": "BOOL" - }, - { - "name": "DP_239", - "type": "BOOL" - }, - { - "name": "DP_240", - "type": "BOOL" - }, - { - "name": "DP_241", - "type": "BOOL" - }, - { - "name": "DP_242", - "type": "BOOL" - }, - { - "name": "DP_243", - "type": "BOOL" - }, - { - "name": "DP_244", - "type": "BOOL" - }, - { - "name": "DP_245", - "type": "BOOL" - }, - { - "name": "DP_246", - "type": "BOOL" - }, - { - "name": "DP_247", - "type": "BOOL" - }, - { - "name": "DP_248", - "type": "BOOL" - }, - { - "name": "DP_249", - "type": "BOOL" - } - ], - "driver_class": "StressTestDriver", - "name": "StressTest", - "params": { - "toggle_delay": 0.02 - } - }, - { - "command_datapoints": [ - { - "name": "RIGHT_SWITCH_CONTROL_CMD", - "type": "SWITCH_CONTROL_CMD" - }, - { - "name": "LEFT_SWITCH_CONTROL_CMD", - "type": "SWITCH_CONTROL_CMD" - } - ], - "datapoints": [ - { - "name": "RIGHT_SWITCH_CONTROL", - "type": "SWITCH_CONTROL" - }, - { - "name": "LEFT_SWITCH_CONTROL", - "type": "SWITCH_CONTROL" - } - ], - "driver_class": "TrainTestDriver", - "name": "TrainTestDriver" - } - ], - "gis_icons": [ - { - "alarm": { - "ACTIVE": "/static/icons/camera_ko.png" - }, - "datapoint": "CameraDriver@CAMERA_1_POSITION", - "icon": "/static/icons/camera.png", - "id": "camera1", - "label": "Camera 1", - "latitude": 47.4328, - "longitude": 8.2384, - "navigation": "stream/feed1", - "rule_id": "camera_1_alarm", - "states": { - "LEFT": "/static/icons/camera_left.png", - "RIGHT": "/static/icons/camera_right.png" - } - }, - { - "alarm": { - "ACTIVE": "/static/icons/camera_ko.png" - }, - "datapoint": "CameraDriver@CAMERA_2_POSITION", - "icon": "/static/icons/camera.png", - "id": "camera2", - "label": "Camera 2", - "latitude": 47.4202, - "longitude": 8.2249, - "navigation": "stream/feed2", - "navigation_type": "popup", - "rule_id": "camera_2_alarm", - "states": { - "LEFT": "/static/icons/camera_left.png", - "RIGHT": "/static/icons/camera_right.png" - } - }, - { - "icon": "/static/icons/help.png", - "id": "help", - "label": "Help", - "latitude": 47.4202, - "longitude": 8.24, - "navigation_type": "popup", - "text": "- In the Communication tab connect the CameraDriver\n\n- In the Command tab the cameras can be moved (LEFT/RIGHT) by sending commands on the _CMD\n\n- In the Datapoint tab you can trigger alarms by setting the _ALARM datapoint to True\n\n- In the Communication tab connect the OPCUAServerDriver, connect with a standard OPC UA browser on the opc.tcp://www.openscadalite.com:4840 to control the cameras from an OPC UA Client" - }, - { - "alarm": { - "ACTIVE": "/static/icons/camera_ko.png" - }, - "datapoint": "CameraDriver@CAMERA_3_POSITION", - "icon": "/static/icons/camera.png", - "id": "camera3", - "label": "Camera 3", - "latitude": 47.4081, - "longitude": 8.214, - "navigation": "stream/feed3", - "rule_id": "camera_3_alarm", - "states": { - "LEFT": "/static/icons/camera_left.png", - "RIGHT": "/static/icons/camera_right.png" - } - } - ], - "modules": [ - { - "name": "animation" - }, - { - "name": "alarm" - }, - { - "name": "command" - }, - { - "name": "communication" - }, - { - "name": "tracking" - }, - { - "name": "datapoint" - }, - { - "name": "schedule", - "config": { - "schedules": [ - { - "schedule_id": "on_mode", - "cron": "0 17 * * *", - "actions": [ - "send_command('TasmotaRelayDriver@RELAY_1_CMD', 'ON')", - "send_command('TasmotaRelayDriver@RELAY_2_CMD', 'ON')" - ] - }, - { - "schedule_id": "off_mode", - "cron": "0 23 * * *", - "actions": [ - "send_command('TasmotaRelayDriver@RELAY_1_CMD', 'OFF')", - "send_command('TasmotaRelayDriver@RELAY_2_CMD', 'OFF')" - ] - } - ] - } - }, - { - "name": "alert" - }, - { - "config": { - "attribution": "© OpenStreetMap contributors", - "baseurl": "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", - "center_x": "47.4189", - "center_y": "8.2246", - "zoom": 13 - }, - "name": "gis" - }, - { - "name": "rule" - }, - { - "name": "stream" - }, - { - "name": "frontend", - "config": { - "tabs": [ - "Image", - "Main", - "Datapoints", - "Communications", - "Alarms", - "Commands", - "Tracking", - "Streams" - ] - } - } - ], - "rules": [ - { - "on_actions": [ - "raise_alarm()" - ], - "on_condition": "CameraDriver@CAMERA_1_ALARM == TRUE", - "rule_id": "camera_1_alarm" - }, - { - "on_actions": [ - "raise_alarm()" - ], - "on_condition": "CameraDriver@CAMERA_2_ALARM == TRUE", - "rule_id": "camera_2_alarm" - }, - { - "on_actions": [ - "raise_alarm()" - ], - "on_condition": "CameraDriver@CAMERA_3_ALARM == TRUE", - "rule_id": "camera_3_alarm" - }, - { - "on_actions": [ - "send_command('WaterTank@PUMP_CMD', 'OPEN')", - "send_command('WaterTank@DOOR_CMD', 'CLOSE')", - "raise_alarm()" - ], - "on_condition": "float(WaterTank@TANK) < 10", - "rule_id": "pump_start_if_low_level" - }, - { - "on_actions": [ - "raise_alarm()" - ], - "on_condition": "WaterTank@DOOR == OPENED", - "rule_id": "door_open_alarm" - }, - { - "off_actions": [], - "off_condition": "", - "on_actions": [ - "raise_alarm()", - "client_alert('Switches are in an invalid state!\\nSolve issue by setting right switch to TURN?', 'confirm_cancel', 'TrainTestDriver@RIGHT_SWITCH_CONTROL_CMD', 'TURN', 30)" - ], - "on_condition": "TrainTestDriver@RIGHT_SWITCH_CONTROL == 'STRAIGHT' and TrainTestDriver@LEFT_SWITCH_CONTROL == 'TURN'", - "rule_id": "switch_error_straight" - }, - { - "off_actions": [], - "off_condition": "", - "on_actions": [ - "raise_alarm()", - "client_alert('Switches are in an invalid state!\\nSolve issue by setting right switch to STRAIGHT?', 'confirm_cancel', 'TrainTestDriver@RIGHT_SWITCH_CONTROL_CMD', 'STRAIGHT', 30)" - ], - "on_condition": "TrainTestDriver@RIGHT_SWITCH_CONTROL == 'TURN' and TrainTestDriver@LEFT_SWITCH_CONTROL == 'STRAIGHT'", - "rule_id": "switch_error_turn" - } - ], - "streams": [ - { - "description": "Feed 1 - 1080", - "id": "feed1", - "path": "hls/stream1.m3u8", - "port": "", - "protocol": "https", - "server": "www.openscadalite.com" - }, - { - "description": "Feed 2 - 4K", - "id": "feed2", - "path": "hls/stream2.m3u8", - "port": "", - "protocol": "https", - "server": "www.openscadalite.com" - }, - { - "description": "Feed 3 - 4K", - "id": "feed3", - "path": "hls/stream3.m3u8", - "port": "", - "protocol": "https", - "server": "www.openscadalite.com" - } - ], - "animations": { - "ALARM_SWITCHES": [ - { - "attribute": "href", - "default": "#", - "expression": { - "ACTIVE": "#alert", - "FINISHED": "#", - "INACTIVE": "#", - "ACK": "#", - "UNKNOWN": "#" - }, - "quality": { - "unknown": "#alert" - }, - "revert_after": 0, - "trigger_type": "alarm" - } - ], - "LEFT_SWITCH_STRAIGHT": [ - { - "attribute": "visibility", - "expression": { - "STRAIGHT": "visible", - "TURN": "hidden" - }, - "quality": { - "unknown": "hidden" - } - } - ], - "LEFT_SWITCH_TURN": [ - { - "attribute": "visibility", - "expression": { - "STRAIGHT": "hidden", - "TURN": "visible" - }, - "quality": { - "unknown": "hidden" - } - } - ], - "RIGHT_SWITCH_STRAIGHT": [ - { - "attribute": "visibility", - "expression": { - "STRAIGHT": "visible", - "TURN": "hidden" - }, - "quality": { - "unknown": "hidden" - } - } - ], - "LIGHTS": [ - { - "attribute": "visibility", - "expression": { - "OFF": "hidden", - "ON": "visible" - }, - "quality": { - "unknown": "hidden" - } - } - ], - "RIGHT_SWITCH_TURN": [ - { - "attribute": "visibility", - "expression": { - "STRAIGHT": "hidden", - "TURN": "visible" - }, - "quality": { - "unknown": "hidden" - } - } - ], - "SIGNAL_STRAIGHT": [ - { - "attribute": "href", - "expression": { - "STRAIGHT": "#clear", - "TURN": "#stop" - }, - "quality": { - "unknown": "none" - } - } - ], - "SIGNAL_TURN": [ - { - "attribute": "href", - "expression": { - "STRAIGHT": "#stop", - "TURN": "#clear" - }, - "quality": { - "unknown": "none" - } - } - ], - "SWITCH_CONTROL": [ - { - "attribute": "fill", - "default": "gray", - "expression": { - "STRAIGHT": "green", - "TURN": "red" - }, - "quality": { - "unknown": "gray" - } - } - ], - "CONNECTOR_ANIMATION": [ - { - "attribute": "fill", - "default": "gray", - "expression": { - "ON": "green", - "OFF": "red" - }, - "quality": { - "unknown": "gray" - } - } - ], - "driver_connection": [ - { - "attribute": "fill", - "default": "gray", - "duration": 0.5, - "expression": { - "OFFLINE": "red", - "ONLINE": "green" - }, - "quality": { - "unknown": "gray" - }, - "trigger_type": "connection" - } - ], - "fill_level": [ - { - "attribute": "y", - "expression": "280 - ((value/100) * 120)", - "quality": { - "unknown": 280 - } - }, - { - "attribute": "height", - "expression": "(value/100) * 120", - "quality": { - "unknown": 0 - } - } - ], - "level_text": [ - { - "attribute": "text", - "expression": "str(value)", - "quality": { - "unknown": "0.0" - } - } - ], - "stress_square": [ - { - "attribute": "fill", - "expression": { - "FALSE": "red", - "TRUE": "green" - }, - "quality": { - "unknown": "gray" - } - } - ], - "toggle_opened_closed": [ - { - "attribute": "fill", - "expression": { - "CLOSED": "red", - "OPENED": "green" - }, - "quality": { - "unknown": "gray" - } - } - ], - "toggle_start_stop": [ - { - "attribute": "fill", - "expression": { - "STARTED": "green", - "STOPPED": "brown" - }, - "quality": { - "unknown": "gray" - } - } - ] - } -} \ No newline at end of file diff --git a/src/openscada_lite/app.py b/src/openscada_lite/app.py index 6e14b62..c318932 100644 --- a/src/openscada_lite/app.py +++ b/src/openscada_lite/app.py @@ -89,7 +89,22 @@ def get_logging_config_path(args=None): # Core singletons # ----------------------------------------------------------------------------- event_bus = EventBus.get_instance() -system_config = Config.get_instance().load_system_config() +# Resolve configuration directory (env > CLI > default) early +def get_config_dir(args=None): + env_var = "SCADA_CONFIG_PATH" + if env_var in os.environ and os.environ[env_var]: + return os.environ[env_var] + cfg = next((arg for arg in (args or []) if arg.startswith("--config-dir=")), None) + if cfg: + return cfg.split("=", 1)[1] + # Default to packaged config directory + return str(Path(__file__).parent.parent / "config") + +CONFIG_DIR = get_config_dir(sys.argv[1:]) +# Ensure downstream utilities relying on env see the same path +os.environ["SCADA_CONFIG_PATH"] = CONFIG_DIR + +system_config = Config.get_instance(CONFIG_DIR).load_system_config() publisher = TrackingPublisher.get_instance() @@ -100,9 +115,7 @@ def get_logging_config_path(args=None): async def lifespan(app: FastAPI): logger.info("[LIFESPAN] Startup starting...") - # Ensure config path is set - if "SCADA_CONFIG_PATH" not in os.environ: - os.environ["SCADA_CONFIG_PATH"] = str(Path(__file__).parent.parent / "config") + # Config path is already set before app init; keep as-is loop = asyncio.get_running_loop() publisher.initialize(loop) From 13747685e2bc928a40b37ea7dea0ec427ec30a7c Mon Sep 17 00:00:00 2001 From: FERNANDEZ BOADA Daniel Date: Tue, 17 Feb 2026 15:37:16 +0100 Subject: [PATCH 2/7] doing --- Dockerfile | 5 +- scripts/deployer/deploy-trigger.path | 9 ++ scripts/deployer/deploy-trigger.service | 10 ++ scripts/deployer/deploy.sh | 104 ++++++++++++++++++ scripts/deployer/deployer.service | 15 +++ scripts/deployer/webhook.py | 51 +++++++++ src/openscada_lite/app.py | 12 ++ .../modules/security/controller.py | 24 +--- .../web/config_editor/routes.py | 8 +- 9 files changed, 211 insertions(+), 27 deletions(-) create mode 100644 scripts/deployer/deploy-trigger.path create mode 100644 scripts/deployer/deploy-trigger.service create mode 100644 scripts/deployer/deploy.sh create mode 100644 scripts/deployer/deployer.service create mode 100644 scripts/deployer/webhook.py diff --git a/Dockerfile b/Dockerfile index 7e33b5f..e6f560b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -55,7 +55,6 @@ RUN pip install gunicorn # Copy backend + config + modules COPY src/openscada_lite ./openscada_lite -COPY config ./config # Copy built frontends from previous stage COPY --from=frontend /app/openscada_lite/web/scada/static/frontend/dist ./openscada_lite/web/scada/static/frontend/dist @@ -64,6 +63,6 @@ COPY --from=frontend /app/openscada_lite/web/security_editor/static/frontend/dis # Expose port (optional, Render uses $PORT) EXPOSE 5443 -ENV SCADA_CONFIG_PATH=/app/config -ENV LOGGING_CONFIG_PATH=/app/config/logging_config.json +VOLUME /config +ENV SCADA_CONFIG_PATH=/config CMD ["uvicorn", "openscada_lite.app:asgi_app", "--host", "0.0.0.0", "--port", "5443"] diff --git a/scripts/deployer/deploy-trigger.path b/scripts/deployer/deploy-trigger.path new file mode 100644 index 0000000..0e5425f --- /dev/null +++ b/scripts/deployer/deploy-trigger.path @@ -0,0 +1,9 @@ +[Unit] +Description=Watch deploy trigger file + +[Path] +PathExists=/opt/deployer/deploy.trigger + +[Install] +WantedBy=multi-user.target + diff --git a/scripts/deployer/deploy-trigger.service b/scripts/deployer/deploy-trigger.service new file mode 100644 index 0000000..0d3f32d --- /dev/null +++ b/scripts/deployer/deploy-trigger.service @@ -0,0 +1,10 @@ +[Unit] +Description=Run SCADA deploy script + +[Service] +Type=oneshot +User=orangepi +WorkingDirectory=/opt/deployer +Environment="SSH_AUTH_SOCK=/tmp/ssh-agent.sock" +ExecStartPre=/usr/bin/ssh-agent -a /tmp/ssh-agent.sock +ExecStart=/bin/bash /opt/deployer/deploy.sh diff --git a/scripts/deployer/deploy.sh b/scripts/deployer/deploy.sh new file mode 100644 index 0000000..d71515c --- /dev/null +++ b/scripts/deployer/deploy.sh @@ -0,0 +1,104 @@ +#!/bin/bash +# deploy.sh - Robust SCADA deployer for NFS/SD/USB + +# --------------------------- +# Absolute paths for commands +# --------------------------- +BASH=/bin/bash +MKDIR=/bin/mkdir +DATE_BIN=/bin/date +GIT=/usr/bin/git +DOCKER=/usr/bin/docker +ECHO=/bin/echo + +# --------------------------- +# Logging +# --------------------------- +LOG_DIR=/opt/deployer/logs +$MKDIR -p "$LOG_DIR" +LOG_FILE="$LOG_DIR/deploy-$($DATE_BIN +%Y%m%d-%H%M%S).log" +exec >> "$LOG_FILE" 2>&1 +set -x # Print commands +set -e # Exit on error + +$ECHO "=== DEPLOY STARTED ===" + +# --------------------------- +# Configuration +# --------------------------- +DATE=$($DATE_BIN +%Y%m%d-%H%M%S) +REGISTRY="localhost:5000" +REPO="python-scada" +IMAGE="$REGISTRY/$REPO:$DATE" +REPO_DIR="/opt/deployer/repo" +DEPLOY_KEY="/home/orangepi/.ssh/id_ed25519" + +# --------------------------- +# SSH agent and deploy key +# --------------------------- +if ! ssh-add -l >/dev/null 2>&1; then + $ECHO "Starting temporary ssh-agent and adding key..." + eval $(ssh-agent -s) + ssh-add "$DEPLOY_KEY" +fi + +# Make repo safe for git +$GIT config --global --add safe.directory "$REPO_DIR" + +# --------------------------- +# Pull or clone repo safely +# --------------------------- +if [ ! -d "$REPO_DIR/.git" ]; then + $ECHO "Directory empty or missing git repo — cleaning and cloning..." + rm -rf "$REPO_DIR" + $MKDIR -p "$REPO_DIR" + $GIT clone git@github.com:boadadf/python-scada.git "$REPO_DIR" +else + $ECHO "Repository exists, updating..." + cd "$REPO_DIR" + $GIT fetch --all --prune + $GIT reset --hard origin/HEAD +fi + +cd "$REPO_DIR" + +# --------------------------- +# Build Docker image +# --------------------------- +$ECHO "=== Building Docker image: $IMAGE ===" +$DOCKER build -t "$IMAGE" . + +# --------------------------- +# Push Docker image +# --------------------------- +$ECHO "=== Pushing Docker image ===" +$DOCKER push "$IMAGE" + +# --------------------------- +# Cleanup old images (keep last 5) +# --------------------------- +$ECHO "=== Cleaning old images (keep last 5) ===" +OLD_IMAGES=$($DOCKER images "$REGISTRY/$REPO" --format "{{.Repository}}:{{.Tag}}" | sort | head -n -5) +if [ -n "$OLD_IMAGES" ]; then + $ECHO "$OLD_IMAGES" + echo "$OLD_IMAGES" | xargs -r $DOCKER rmi +else + $ECHO "No old images to remove." +fi + +# --------------------------- +# Restart container +# --------------------------- +$ECHO "=== Restarting container ===" +$DOCKER stop python-scada || $ECHO "No container to stop" +$DOCKER rm python-scada || $ECHO "No container to remove" +$DOCKER run -d --name python-scada -v /var/run/docker.sock:/var/run/docker.sock --restart=always -p 5443:5443 "$IMAGE" + +$ECHO "=== DEPLOY COMPLETE ===" +$ECHO "Log saved to $LOG_FILE" + +# --------------------------- +# Cleanup trigger +# --------------------------- +rm -f /opt/deployer/deploy.trigger + diff --git a/scripts/deployer/deployer.service b/scripts/deployer/deployer.service new file mode 100644 index 0000000..8029750 --- /dev/null +++ b/scripts/deployer/deployer.service @@ -0,0 +1,15 @@ +[Unit] +Description=SCADA deployer webhook +After=network.target docker.service + +[Service] + +Environment="PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" "GITHUB_SECRET=Tel_0034696312712" +ExecStart=/opt/deployer/venv/bin/python /opt/deployer/webhook.py +WorkingDirectory=/opt/deployer +Restart=always +User=orangepi + +[Install] +WantedBy=multi-user.target + diff --git a/scripts/deployer/webhook.py b/scripts/deployer/webhook.py new file mode 100644 index 0000000..52315d3 --- /dev/null +++ b/scripts/deployer/webhook.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +from flask import Flask, request +import os, hmac, hashlib + +SECRET = os.environ.get("GITHUB_SECRET", "").encode() +BRANCH = "refs/heads/master" +TRIGGER_FILE = "/opt/deployer/deploy.trigger" + +app = Flask(__name__) + +def verify_signature(payload, signature_header): + if not SECRET or not signature_header: + return False + mac = hmac.new(SECRET, msg=payload, digestmod=hashlib.sha256) + expected = "sha256=" + mac.hexdigest() + return hmac.compare_digest(expected, signature_header) + +@app.route("/webhook", methods=["POST"]) +def webhook(): + signature = request.headers.get("X-Hub-Signature-256") + if not verify_signature(request.data, signature): + return "Invalid signature", 403 + + event = request.headers.get("X-GitHub-Event", "") + data = request.json or {} + ref = data.get("ref", None) + + # Debug log + with open("/opt/deployer/webhook-debug.log", "a") as f: + f.write(f"EVENT={event}, REF={ref}\n") + + if event == "ping": + return "pong", 200 + + # Make branch check robust + BRANCHES = ["refs/heads/master", "refs/heads/main"] + if event == "push" and ref in BRANCHES: + with open("/opt/deployer/deploy.trigger", "w") as f: + f.write("deploy\n") + with open("/opt/deployer/webhook-debug.log", "a") as f: + f.write("Trigger file created\n") + return "Deploy triggered", 200 + + with open("/opt/deployer/webhook-debug.log", "a") as f: + f.write("Event ignored\n") + return f"Ignored event={event}, ref={ref}", 200 + + +if __name__ == "__main__": + app.run(host="0.0.0.0", port=5002) + diff --git a/src/openscada_lite/app.py b/src/openscada_lite/app.py index c318932..91a8354 100644 --- a/src/openscada_lite/app.py +++ b/src/openscada_lite/app.py @@ -65,6 +65,15 @@ def get_logging_config_path(args=None): cfg = next((arg for arg in (args or []) if arg.startswith("--logging-config=")), None) if cfg: return cfg.split("=", 1)[1] + # Fallback: use SCADA_CONFIG_PATH or --config-dir if provided + scada_dir = os.environ.get("SCADA_CONFIG_PATH") + if not scada_dir: + cfg_dir = next((arg for arg in (args or []) if arg.startswith("--config-dir=")), None) + if cfg_dir: + scada_dir = cfg_dir.split("=", 1)[1] + if scada_dir: + return str(Path(scada_dir) / "logging_config.json") + # Final fallback: internal packaged config return str(Path(__file__).parent.parent.parent / "config" / "logging_config.json") @@ -89,6 +98,8 @@ def get_logging_config_path(args=None): # Core singletons # ----------------------------------------------------------------------------- event_bus = EventBus.get_instance() + + # Resolve configuration directory (env > CLI > default) early def get_config_dir(args=None): env_var = "SCADA_CONFIG_PATH" @@ -100,6 +111,7 @@ def get_config_dir(args=None): # Default to packaged config directory return str(Path(__file__).parent.parent / "config") + CONFIG_DIR = get_config_dir(sys.argv[1:]) # Ensure downstream utilities relying on env see the same path os.environ["SCADA_CONFIG_PATH"] = CONFIG_DIR diff --git a/src/openscada_lite/modules/security/controller.py b/src/openscada_lite/modules/security/controller.py index bd6c698..56b422b 100644 --- a/src/openscada_lite/modules/security/controller.py +++ b/src/openscada_lite/modules/security/controller.py @@ -30,9 +30,7 @@ class SecurityController( BaseController[None, None], ): - def __init__( - self, model: SecurityModel, socketio, module_name: str, router: APIRouter - ): + def __init__(self, model: SecurityModel, socketio, module_name: str, router: APIRouter): super().__init__(model, socketio, None, None, module_name, router) self.model = model @@ -114,18 +112,14 @@ async def login(data: dict, response: Response, app: str = None): return response # GET security config - @router.get( - "/security/config", tags=[self.base_event], operation_id="getSecurityConfig" - ) + @router.get("/security/config", tags=[self.base_event], operation_id="getSecurityConfig") async def get_security_config(): try: config = self.model.get_security_config() return JSONResponse(content=config) except Exception as e: return JSONResponse( - content=StatusDTO( - status="error", reason=f"Failed to load: {e}" - ).to_dict(), + content=StatusDTO(status="error", reason=f"Failed to load: {e}").to_dict(), status_code=500, ) @@ -138,26 +132,20 @@ async def get_security_config(): async def save_security_config(data: dict): if not data: return JSONResponse( - content=StatusDTO( - status="error", reason="No data provided" - ).to_dict(), + content=StatusDTO(status="error", reason="No data provided").to_dict(), status_code=400, ) try: self.model.save_security_config(data) except Exception as e: return JSONResponse( - content=StatusDTO( - status="error", reason=f"Failed to save: {e}" - ).to_dict(), + content=StatusDTO(status="error", reason=f"Failed to save: {e}").to_dict(), status_code=500, ) # Notify service if applicable if hasattr(self.service, "notify_config_changed"): self.service.notify_config_changed() - return JSONResponse( - content=StatusDTO(status="ok", reason="Config saved").to_dict() - ) + return JSONResponse(content=StatusDTO(status="ok", reason="Config saved").to_dict()) self.model.load_endpoints(router) diff --git a/src/openscada_lite/web/config_editor/routes.py b/src/openscada_lite/web/config_editor/routes.py index 97e6465..0fb60b0 100644 --- a/src/openscada_lite/web/config_editor/routes.py +++ b/src/openscada_lite/web/config_editor/routes.py @@ -71,9 +71,7 @@ async def get_configs(): logger.debug("Getting configs...") config_dir = os.path.dirname(CONFIG_FILE) logger.debug("Config dir:", config_dir) - files = [ - f for f in os.listdir(config_dir) if f.endswith(f"_{SYSTEM_CONFIG_FILENAME}") - ] + files = [f for f in os.listdir(config_dir) if f.endswith(f"_{SYSTEM_CONFIG_FILENAME}")] logger.debug("Found config files:", files) # Strip suffix for user display display_names = [f.replace(f"_{SYSTEM_CONFIG_FILENAME}", "") for f in files] @@ -131,9 +129,7 @@ async def _restart(): container_id = os.environ.get("HOSTNAME") if container_id: container = client.containers.get(container_id) - logger.info( - f"[RESTART] Restarting container {container_id} via Docker socket..." - ) + logger.info(f"[RESTART] Restarting container {container_id} via Docker socket...") container.restart() return except Exception as e: From d262c3f649ed9f8ff390a5866c46ce1fd9001c73 Mon Sep 17 00:00:00 2001 From: FERNANDEZ BOADA Daniel Date: Thu, 19 Feb 2026 20:45:44 +0100 Subject: [PATCH 3/7] Fix routing --- src/openscada_lite/web/config_editor/routes.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/openscada_lite/web/config_editor/routes.py b/src/openscada_lite/web/config_editor/routes.py index 0fb60b0..0da3919 100644 --- a/src/openscada_lite/web/config_editor/routes.py +++ b/src/openscada_lite/web/config_editor/routes.py @@ -27,8 +27,8 @@ SYSTEM_CONFIG_FILENAME = "system_config.json" -CONFIG_FILE = os.path.join( - os.path.dirname(__file__), "..", "..", "..", "..", "config", SYSTEM_CONFIG_FILENAME +CONFIG_FILE = os.environ.get( + "SCADA_CONFIG_PATH" ) config_router = APIRouter(prefix="/config-editor", tags=["ConfigEditor"]) @@ -51,9 +51,10 @@ def normalize_config_filename(name: str) -> str: operation_id="getConfigByName", ) async def get_config_by_name(name: str): - config_dir = os.path.dirname(CONFIG_FILE) filename = normalize_config_filename(name) - path = os.path.join(config_dir, filename) + print(f"Getting config for name: {name}, normalized filename: {filename}") + path = os.path.join(CONFIG_FILE, filename) + print(f"Full path to config file: {path}") if not os.path.isfile(path): return JSONResponse({"error": "File not found"}, status_code=404) async with await anyio.open_file(path, "r") as file: @@ -68,14 +69,14 @@ async def get_config_by_name(name: str): operation_id="getConfigs", ) async def get_configs(): - logger.debug("Getting configs...") + print("Getting configs...") config_dir = os.path.dirname(CONFIG_FILE) - logger.debug("Config dir:", config_dir) + print("Config dir:", config_dir) files = [f for f in os.listdir(config_dir) if f.endswith(f"_{SYSTEM_CONFIG_FILENAME}")] - logger.debug("Found config files:", files) + print("Found config files:", files) # Strip suffix for user display display_names = [f.replace(f"_{SYSTEM_CONFIG_FILENAME}", "") for f in files] - logger.debug("Display names:", display_names) + print("Display names:", display_names) return display_names From cf42b5b7f91ce3062ad26d167adb3bbcaf28192a Mon Sep 17 00:00:00 2001 From: FERNANDEZ BOADA Daniel Date: Thu, 19 Feb 2026 22:25:32 +0100 Subject: [PATCH 4/7] Fix tests --- .../web/config_editor/routes.py | 8 +- tests/config/logging_config.json | 47 ++++++ tests/config/system_config.json | 80 ++++++++++ tests/security_config.json | 35 ----- tests/system_config.json | 137 ------------------ tests/test_alarm_service.py | 3 + tests/test_command_app.py | 4 +- tests/test_datapoint_app.py | 13 +- tests/test_security_app.py | 3 + tests/test_test_driver.py | 3 + 10 files changed, 150 insertions(+), 183 deletions(-) create mode 100644 tests/config/logging_config.json create mode 100644 tests/config/system_config.json delete mode 100644 tests/security_config.json delete mode 100644 tests/system_config.json diff --git a/src/openscada_lite/web/config_editor/routes.py b/src/openscada_lite/web/config_editor/routes.py index 0da3919..83a1370 100644 --- a/src/openscada_lite/web/config_editor/routes.py +++ b/src/openscada_lite/web/config_editor/routes.py @@ -70,9 +70,8 @@ async def get_config_by_name(name: str): ) async def get_configs(): print("Getting configs...") - config_dir = os.path.dirname(CONFIG_FILE) - print("Config dir:", config_dir) - files = [f for f in os.listdir(config_dir) if f.endswith(f"_{SYSTEM_CONFIG_FILENAME}")] + print("Config dir:", CONFIG_FILE) + files = [f for f in os.listdir(CONFIG_FILE) if f.endswith(f"_{SYSTEM_CONFIG_FILENAME}")] print("Found config files:", files) # Strip suffix for user display display_names = [f.replace(f"_{SYSTEM_CONFIG_FILENAME}", "") for f in files] @@ -106,8 +105,7 @@ async def save_config_as(request: Request): if not name: return JSONResponse({"error": "Invalid filename"}, status_code=400) filename = normalize_config_filename(name) - config_dir = os.path.dirname(CONFIG_FILE) - path = os.path.join(config_dir, filename) + path = os.path.join(CONFIG_FILE, filename) async with await anyio.open_file(path, "w") as file: await file.write(json.dumps(config, indent=2)) return {"status": "ok", "filename": filename} diff --git a/tests/config/logging_config.json b/tests/config/logging_config.json new file mode 100644 index 0000000..b450091 --- /dev/null +++ b/tests/config/logging_config.json @@ -0,0 +1,47 @@ +{ + "version": 1, + "formatters": { + "default": { + "format": "%(asctime)s %(levelname)s %(name)s %(message)s" + } + }, + "handlers": { + "console": { + "class": "logging.StreamHandler", + "formatter": "default", + "level": "INFO" + }, + "file": { + "class": "logging.FileHandler", + "formatter": "default", + "filename": "app.log", + "level": "INFO" + } + }, + "root": { + "level": "INFO", + "handlers": ["console", "file"] + }, + "loggers": { + "openscada_lite.modules.security.service": { + "level": "INFO", + "handlers": ["console", "file"], + "propagate": false + }, + "openscada_lite.modules.security.controller": { + "level": "INFO", + "handlers": ["console", "file"], + "propagate": false + }, + "openscada_lite.modules.base.base_controller": { + "level": "INFO", + "handlers": ["console", "file"], + "propagate": false + }, + "openscada_lite.modules.communication.controller": { + "level": "INFO", + "handlers": ["console", "file"], + "propagate": false + } + } +} \ No newline at end of file diff --git a/tests/config/system_config.json b/tests/config/system_config.json new file mode 100644 index 0000000..9c3026b --- /dev/null +++ b/tests/config/system_config.json @@ -0,0 +1,80 @@ +{ + "modules": [ + { "name": "alarm" }, + { "name": "command" }, + { "name": "communication" }, + { "name": "datapoint" }, + { + "name": "tracking", + "config": { + "mode": "file", + "file_path": "flow_events.log" + } + } +], + "dp_types": { + "LEVEL": { "type": "float", "min": 0, "max": 100, "default": 0.0 }, + "STATUS": { "type": "enum", "values": ["OPENED", "CLOSED"], "default": "CLOSED" }, + "PRESSURE": { "type": "float", "min": 0, "max": 200, "default": 50.0 }, + "TEMPERATURE":{ "type": "float", "min": 100, "max": 150, "default": 120.0 } + }, + "drivers": [ + { + "name": "WaterTank", + "driver_class": "TankTestDriver", + "connection_info": { "server_name": "WaterTank" }, + "datapoints": [ + { "name": "TANK", "type": "LEVEL" }, + { "name": "PUMP", "type": "STATUS" }, + { "name": "DOOR", "type": "STATUS" } + ] + }, + { + "name": "AuxServer", + "driver_class": "BoilerTestDriver", + "connection_info": { "server_name": "AuxServer" }, + "datapoints": [ + { "name": "VALVE", "type": "STATUS" }, + { "name": "PRESSURE", "type": "PRESSURE" }, + { "name": "TEMPERATURE", "type": "TEMPERATURE" }, + { "name": "HEATER", "type": "STATUS" } + ] + } + ], + "rules": [ + { + "rule_id": "close_valve_if_high", + "on_condition": "AuxServer@PRESSURE > 100", + "on_actions": [ + "send_command('AuxServer@VALVE1_POS', 0)", + "raise_alarm()" + ], + "off_condition": "AuxServer@PRESSURE < 80", + "off_actions": [ + "lower_alarm()" + ] + }, + { + "rule_id": "pump_shutdown_if_low_level", + "on_condition": "WaterTank@TANK1_LEVEL < 10", + "on_actions": [ + "send_command('WaterTank@PUMP1_STATUS', 'OFF')", + "raise_alarm()" + ] + }, + { + "rule_id": "door_open_alarm", + "on_condition": "WaterTank@DOOR1_OPEN == True", + "on_actions": [ + "raise_alarm()" + ] + }, + { + "rule_id": "temperature_high_warning", + "on_condition": "AuxServer@TEMPERATURE > 80", + "on_actions": [ + "raise_alarm()" + ] + } + ] +} \ No newline at end of file diff --git a/tests/security_config.json b/tests/security_config.json deleted file mode 100644 index 9565543..0000000 --- a/tests/security_config.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "groups": [ - { - "name": "g1", - "permissions": [ - "p1", - "p2" - ] - }, - { - "name": "g2", - "permissions": [ - "p3" - ] - } - ], - "users": [ - { - "username": "u1", - "groups": [ - "g1" - ], - "allowed_apps": [], - "password_hash": "" - }, - { - "username": "u2", - "groups": [ - "g2" - ], - "allowed_apps": [], - "password_hash": "" - } - ] -} \ No newline at end of file diff --git a/tests/system_config.json b/tests/system_config.json deleted file mode 100644 index b3de72d..0000000 --- a/tests/system_config.json +++ /dev/null @@ -1,137 +0,0 @@ -{ - "modules": [ - { "name": "alarm" }, - { "name": "command" }, - { "name": "communication" }, - { "name": "datapoint" }, - { "name": "animation" }, - { "name": "tracking", - "config": { - "mode": "file", - "file_path": "flow_events.log" - } - } -], - "dp_types": { - "LEVEL": { "type": "float", "min": 0, "max": 100, "default": 0.0 }, - "OPENED_CLOSED": { "type": "enum", "values": ["OPENED", "CLOSED"], "default": "CLOSED" }, - "OPEN_CLOSE_CMD": { "type": "enum", "values": ["OPEN", "CLOSE", "TOGGLE"], "default": "CLOSE" }, - "START_STOP": { "type": "enum", "values": ["STARTED", "STOPPED"], "default": "STOPPED" }, - "START_STOP_CMD": { "type": "enum", "values": ["START", "STOP", "TOGGLE"], "default": "STOP" }, - "PRESSURE": { "type": "float", "min": 0, "max": 200, "default": 50.0 }, - "TEMPERATURE":{ "type": "float", "min": 100, "max": 150, "default": 120.0 } - }, - "drivers": [ - { - "name": "WaterTank", - "driver_class": "TankTestDriver", - "connection_info": { "server_name": "WaterTank" }, - "datapoints": [ - { "name": "TANK", "type": "LEVEL" }, - { "name": "PUMP", "type": "OPENED_CLOSED" }, - { "name": "DOOR", "type": "OPENED_CLOSED" }, - { "name": "TEST", "type": "START_STOP" } - ], - "command_datapoints": [ - { "name": "PUMP_CMD", "type": "OPEN_CLOSE_CMD" }, - { "name": "DOOR_CMD", "type": "OPEN_CLOSE_CMD" }, - { "name": "TEST_CMD", "type": "START_STOP_CMD" } - ] - }, - { - "name": "AuxServer", - "driver_class": "BoilerTestDriver", - "connection_info": { "server_name": "AuxServer" }, - "datapoints": [ - { "name": "VALVE", "type": "OPENED_CLOSED" }, - { "name": "PRESSURE", "type": "PRESSURE" }, - { "name": "TEMPERATURE", "type": "TEMPERATURE" }, - { "name": "HEATER", "type": "OPENED_CLOSED" }, - { "name": "TEST", "type": "START_STOP" } - ], - "command_datapoints": [ - { "name": "VALVE_CMD", "type": "OPENED_CLOSED_CMD" }, - { "name": "HEATER_CMD", "type": "OPEN_CLOSE_CMD" }, - { "name": "TEST_CMD", "type": "START_STOP_CMD" } - ] - } - ], - "rules": [ - { - "rule_id": "close_valve_if_high", - "on_condition": "AuxServer@PRESSURE > 100", - "on_actions": [ - "send_command('AuxServer@VALVE', 0)", - "raise_alarm()" - ], - "off_condition": "AuxServer@PRESSURE < 80", - "off_actions": [ - "lower_alarm()" - ] - }, - { - "rule_id": "pump_start_if_low_level", - "on_condition": "float(WaterTank@TANK) < 10", - "on_actions": [ - "send_command('WaterTank@PUMP_CMD', 'OPEN')", - "send_command('WaterTank@DOOR_CMD', 'CLOSE')", - "raise_alarm()" - ] - }, - { - "rule_id": "door_open_alarm", - "on_condition": "WaterTank@DOOR == OPENED", - "on_actions": [ - "raise_alarm()" - ] - }, - { - "rule_id": "temperature_high_warning", - "on_condition": "AuxServer@TEMPERATURE > 80", - "on_actions": [ - "raise_alarm()" - ] - } - ], - "animations": { - "fill_level": [ - { - "attribute": "y", - "quality": { "unknown": 390 }, - "expression": "390 - ((value/100) * 340)" - }, - { - "attribute": "height", - "quality": { "unknown": 0 }, - "expression": "(value/100) * 340" - } - ], - "toggle_opened_closed": [ - { - "attribute": "fill", - "quality": { "unknown": "gray" }, - "expression": { - "OPENED": "green", - "CLOSED": "red" - } - } - ], - "toggle_start_stop": [ - { - "attribute": "fill", - "quality": { "unknown": "gray" }, - "expression": { - "STARTED": "green", - "STOPPED": "brown" - } - } - ], - "level_text": [ - { - "attribute": "text", - "quality": { "unknown": "0.0" }, - "expression": "str(round(value,1))" - } - ] - } -} \ No newline at end of file diff --git a/tests/test_alarm_service.py b/tests/test_alarm_service.py index cf4fc84..951f476 100644 --- a/tests/test_alarm_service.py +++ b/tests/test_alarm_service.py @@ -13,6 +13,9 @@ AlarmUpdateMsg, ) +@pytest.fixture(autouse=True) +def set_config_env(monkeypatch): + monkeypatch.setenv("SCADA_CONFIG_PATH", "tests/config/system_config.json") # Reset the bus for each test @pytest.fixture(autouse=True) diff --git a/tests/test_command_app.py b/tests/test_command_app.py index 023426d..14c2cde 100644 --- a/tests/test_command_app.py +++ b/tests/test_command_app.py @@ -42,8 +42,8 @@ def run_server(): import socket from pathlib import Path - # Ensure SCADA_CONFIG_PATH is set - cfg_file = Path(__file__).parent / "config" / "test_config.json" + # Ensure SCADA_CONFIG_PATH is set + cfg_file = Path(__file__).parent / "config" os.environ["SCADA_CONFIG_PATH"] = str(cfg_file.resolve()) # Start Uvicorn in a subprocess diff --git a/tests/test_datapoint_app.py b/tests/test_datapoint_app.py index 751a145..58da291 100644 --- a/tests/test_datapoint_app.py +++ b/tests/test_datapoint_app.py @@ -11,6 +11,11 @@ SERVER_URL = "http://localhost:5001" +CONFIG_DIR = os.path.join(os.path.dirname(__file__), "config") + +@pytest.fixture(autouse=True) +def set_config_env(monkeypatch): + monkeypatch.setenv("SCADA_CONFIG_PATH", CONFIG_DIR) @pytest.fixture(scope="session", autouse=True) def run_server(): @@ -19,8 +24,8 @@ def run_server(): import socket import os - # Ensure SCADA_CONFIG_PATH is set - os.environ["SCADA_CONFIG_PATH"] = "tests/config/test_config.json" + env = os.environ.copy() + env["SCADA_CONFIG_PATH"] = CONFIG_DIR # Start Uvicorn in a subprocess process = subprocess.Popen( @@ -32,7 +37,7 @@ def run_server(): "--port", "5001", ], - env=os.environ.copy(), # Pass the current environment variables to the subprocess + env=env, ) # Wait for server readiness (avoid race conditions) start = time.time() @@ -81,7 +86,7 @@ def on_datapoint_update(data): await asyncio.sleep(1) # Wait for initial state async with aiofiles.open( - os.path.join(os.path.dirname(__file__), "config", "test_config.json") + os.path.join(os.path.dirname(__file__), "config", "system_config.json") ) as f: content = await f.read() config = json.loads(content) diff --git a/tests/test_security_app.py b/tests/test_security_app.py index 2972c27..7c6f170 100644 --- a/tests/test_security_app.py +++ b/tests/test_security_app.py @@ -7,6 +7,9 @@ from openscada_lite.modules.security.service import SecurityService from openscada_lite.modules.security.controller import SecurityController +@pytest.fixture(autouse=True) +def set_config_env(monkeypatch): + monkeypatch.setenv("SCADA_CONFIG_PATH", "tests/config/system_config.json") @pytest.fixture def app(tmp_path): diff --git a/tests/test_test_driver.py b/tests/test_test_driver.py index 158baba..eb3cd7a 100644 --- a/tests/test_test_driver.py +++ b/tests/test_test_driver.py @@ -6,6 +6,9 @@ from openscada_lite.common.models.dtos import SendCommandMsg, TagUpdateMsg from openscada_lite.common.models.entities import Datapoint +@pytest.fixture(autouse=True) +def set_config_env(monkeypatch): + monkeypatch.setenv("SCADA_CONFIG_PATH", "tests/config/system_config.json") @pytest.mark.asyncio async def test_test_driver_value_callback(): From 314e413600b1b611d3f0c1a1d411abc1ffe9b1ef Mon Sep 17 00:00:00 2001 From: FERNANDEZ BOADA Daniel Date: Thu, 19 Feb 2026 22:57:18 +0100 Subject: [PATCH 5/7] Format --- scripts/deployer/deploy.sh | 3 ++- scripts/deployer/webhook.py | 7 +++++-- src/openscada_lite/web/config_editor/routes.py | 4 +--- tests/test_alarm_service.py | 2 ++ tests/test_command_app.py | 2 +- tests/test_datapoint_app.py | 2 ++ tests/test_security_app.py | 2 ++ tests/test_test_driver.py | 2 ++ 8 files changed, 17 insertions(+), 7 deletions(-) diff --git a/scripts/deployer/deploy.sh b/scripts/deployer/deploy.sh index d71515c..ea3a9f7 100644 --- a/scripts/deployer/deploy.sh +++ b/scripts/deployer/deploy.sh @@ -31,6 +31,7 @@ REGISTRY="localhost:5000" REPO="python-scada" IMAGE="$REGISTRY/$REPO:$DATE" REPO_DIR="/opt/deployer/repo" +CONFIG_DIR="/opt/deployer/config" DEPLOY_KEY="/home/orangepi/.ssh/id_ed25519" # --------------------------- @@ -92,7 +93,7 @@ fi $ECHO "=== Restarting container ===" $DOCKER stop python-scada || $ECHO "No container to stop" $DOCKER rm python-scada || $ECHO "No container to remove" -$DOCKER run -d --name python-scada -v /var/run/docker.sock:/var/run/docker.sock --restart=always -p 5443:5443 "$IMAGE" +$DOCKER run -d --name python-scada -v /var/run/docker.sock:/var/run/docker.sock -v "$CONFIG_DIR":/config --restart=always -p 5443:5443 "$IMAGE" $ECHO "=== DEPLOY COMPLETE ===" $ECHO "Log saved to $LOG_FILE" diff --git a/scripts/deployer/webhook.py b/scripts/deployer/webhook.py index 52315d3..a719885 100644 --- a/scripts/deployer/webhook.py +++ b/scripts/deployer/webhook.py @@ -1,6 +1,8 @@ #!/usr/bin/env python3 +import hashlib from flask import Flask, request -import os, hmac, hashlib +import os +import hmac SECRET = os.environ.get("GITHUB_SECRET", "").encode() BRANCH = "refs/heads/master" @@ -8,6 +10,7 @@ app = Flask(__name__) + def verify_signature(payload, signature_header): if not SECRET or not signature_header: return False @@ -15,6 +18,7 @@ def verify_signature(payload, signature_header): expected = "sha256=" + mac.hexdigest() return hmac.compare_digest(expected, signature_header) + @app.route("/webhook", methods=["POST"]) def webhook(): signature = request.headers.get("X-Hub-Signature-256") @@ -48,4 +52,3 @@ def webhook(): if __name__ == "__main__": app.run(host="0.0.0.0", port=5002) - diff --git a/src/openscada_lite/web/config_editor/routes.py b/src/openscada_lite/web/config_editor/routes.py index 83a1370..6dd8acb 100644 --- a/src/openscada_lite/web/config_editor/routes.py +++ b/src/openscada_lite/web/config_editor/routes.py @@ -27,9 +27,7 @@ SYSTEM_CONFIG_FILENAME = "system_config.json" -CONFIG_FILE = os.environ.get( - "SCADA_CONFIG_PATH" -) +CONFIG_FILE = os.environ.get("SCADA_CONFIG_PATH") config_router = APIRouter(prefix="/config-editor", tags=["ConfigEditor"]) diff --git a/tests/test_alarm_service.py b/tests/test_alarm_service.py index 951f476..4aed2e1 100644 --- a/tests/test_alarm_service.py +++ b/tests/test_alarm_service.py @@ -13,10 +13,12 @@ AlarmUpdateMsg, ) + @pytest.fixture(autouse=True) def set_config_env(monkeypatch): monkeypatch.setenv("SCADA_CONFIG_PATH", "tests/config/system_config.json") + # Reset the bus for each test @pytest.fixture(autouse=True) def reset_event_bus(monkeypatch): diff --git a/tests/test_command_app.py b/tests/test_command_app.py index 14c2cde..2f6d8d7 100644 --- a/tests/test_command_app.py +++ b/tests/test_command_app.py @@ -42,7 +42,7 @@ def run_server(): import socket from pathlib import Path - # Ensure SCADA_CONFIG_PATH is set + # Ensure SCADA_CONFIG_PATH is set cfg_file = Path(__file__).parent / "config" os.environ["SCADA_CONFIG_PATH"] = str(cfg_file.resolve()) diff --git a/tests/test_datapoint_app.py b/tests/test_datapoint_app.py index 58da291..807b28c 100644 --- a/tests/test_datapoint_app.py +++ b/tests/test_datapoint_app.py @@ -13,10 +13,12 @@ CONFIG_DIR = os.path.join(os.path.dirname(__file__), "config") + @pytest.fixture(autouse=True) def set_config_env(monkeypatch): monkeypatch.setenv("SCADA_CONFIG_PATH", CONFIG_DIR) + @pytest.fixture(scope="session", autouse=True) def run_server(): import subprocess diff --git a/tests/test_security_app.py b/tests/test_security_app.py index 7c6f170..eb65173 100644 --- a/tests/test_security_app.py +++ b/tests/test_security_app.py @@ -7,10 +7,12 @@ from openscada_lite.modules.security.service import SecurityService from openscada_lite.modules.security.controller import SecurityController + @pytest.fixture(autouse=True) def set_config_env(monkeypatch): monkeypatch.setenv("SCADA_CONFIG_PATH", "tests/config/system_config.json") + @pytest.fixture def app(tmp_path): """Create a FastAPI app with SecurityController mounted.""" diff --git a/tests/test_test_driver.py b/tests/test_test_driver.py index eb3cd7a..a5483a2 100644 --- a/tests/test_test_driver.py +++ b/tests/test_test_driver.py @@ -6,10 +6,12 @@ from openscada_lite.common.models.dtos import SendCommandMsg, TagUpdateMsg from openscada_lite.common.models.entities import Datapoint + @pytest.fixture(autouse=True) def set_config_env(monkeypatch): monkeypatch.setenv("SCADA_CONFIG_PATH", "tests/config/system_config.json") + @pytest.mark.asyncio async def test_test_driver_value_callback(): driver = TankTestDriver("WaterTank") From d1a2c6956ca90571703fa151e036f758a9dfc852 Mon Sep 17 00:00:00 2001 From: FERNANDEZ BOADA Daniel Date: Thu, 19 Feb 2026 23:05:10 +0100 Subject: [PATCH 6/7] allow not logging --- src/openscada_lite/app.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/openscada_lite/app.py b/src/openscada_lite/app.py index 91a8354..9c0f96d 100644 --- a/src/openscada_lite/app.py +++ b/src/openscada_lite/app.py @@ -78,11 +78,18 @@ def get_logging_config_path(args=None): logging_config_path = get_logging_config_path(sys.argv[1:]) -with open(logging_config_path, "r") as f: - config = json.load(f) -logging.config.dictConfig(config) +if os.path.isfile(logging_config_path): + with open(logging_config_path, "r", encoding="utf-8") as f: + config = json.load(f) + logging.config.dictConfig(config) +else: + logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) +if not os.path.isfile(logging_config_path): + logger.warning( + "Logging config not found at %s. Using basicConfig.", logging_config_path + ) # ----------------------------------------------------------------------------- # Socket.IO server From 96ae137485be9b620780faebb7251bd3a775c1e8 Mon Sep 17 00:00:00 2001 From: FERNANDEZ BOADA Daniel Date: Thu, 19 Feb 2026 23:09:24 +0100 Subject: [PATCH 7/7] allow not logging --- src/openscada_lite/app.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/openscada_lite/app.py b/src/openscada_lite/app.py index 9c0f96d..8748513 100644 --- a/src/openscada_lite/app.py +++ b/src/openscada_lite/app.py @@ -108,6 +108,12 @@ def get_logging_config_path(args=None): # Resolve configuration directory (env > CLI > default) early +def _is_valid_config_path(path: Path) -> bool: + if path.is_file(): + return path.name == "system_config.json" + return (path / "system_config.json").is_file() + + def get_config_dir(args=None): env_var = "SCADA_CONFIG_PATH" if env_var in os.environ and os.environ[env_var]: @@ -115,7 +121,16 @@ def get_config_dir(args=None): cfg = next((arg for arg in (args or []) if arg.startswith("--config-dir=")), None) if cfg: return cfg.split("=", 1)[1] - # Default to packaged config directory + repo_root = Path(__file__).resolve().parents[2] + candidates = [ + repo_root / "config", + repo_root / "tests" / "config", + Path(__file__).parent.parent / "config", + ] + for candidate in candidates: + if _is_valid_config_path(candidate): + return str(candidate) + # Default to previous behavior return str(Path(__file__).parent.parent / "config")