@uns-kit/cli 0.0.34 → 0.0.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +128 -128
- package/dist/index.js +181 -6
- package/package.json +2 -2
- package/templates/api/src/examples/api-example.ts +62 -61
- package/templates/azure-pipelines.yml +21 -21
- package/templates/codegen/codegen.ts +15 -15
- package/templates/codegen/src/uns/uns-tags.ts +1 -1
- package/templates/codegen/src/uns/uns-topics.ts +1 -1
- package/templates/config-files/config-docker.json +27 -0
- package/templates/config-files/config-localhost.json +27 -0
- package/templates/cron/src/examples/cron-example.ts +46 -45
- package/templates/default/README.md +30 -30
- package/templates/default/config.json +27 -27
- package/templates/default/gitignore +51 -48
- package/templates/default/package.json +19 -19
- package/templates/default/src/config/project.config.extension.example +23 -23
- package/templates/default/src/config/project.config.extension.ts +6 -6
- package/templates/default/src/examples/data-example.ts +68 -68
- package/templates/default/src/examples/load-test-data.ts +108 -108
- package/templates/default/src/examples/table-example.ts +66 -66
- package/templates/default/src/examples/uns-gateway-cli.ts +7 -7
- package/templates/default/src/index.ts +15 -15
- package/templates/default/src/uns/uns-tags.ts +2 -2
- package/templates/default/src/uns/uns-topics.ts +2 -2
- package/templates/default/tsconfig.json +16 -16
- package/templates/python/app/README.md +8 -0
- package/templates/python/app/__init__.py +0 -0
- package/templates/python/examples/README.md +134 -84
- package/templates/python/examples/__init__.py +0 -0
- package/templates/python/examples/api_handler.py +28 -101
- package/templates/python/examples/data_publish.py +11 -0
- package/templates/python/examples/data_subscribe.py +8 -124
- package/templates/python/examples/data_transformer.py +17 -147
- package/templates/python/examples/table_transformer.py +16 -163
- package/templates/python/gateway/__init__.py +0 -0
- package/templates/python/gateway/cli.py +75 -0
- package/templates/python/gateway/client.py +155 -0
- package/templates/python/gateway/manager.py +97 -0
- package/templates/python/gen/__init__.py +1 -0
- package/templates/python/gen/uns_gateway_pb2.py +70 -0
- package/templates/python/gen/uns_gateway_pb2_grpc.py +312 -0
- package/templates/python/main.py +1 -0
- package/templates/python/proto/uns-gateway.proto +102 -102
- package/templates/python/pyproject.toml +5 -0
- package/templates/python/scripts/setup.sh +87 -64
- package/templates/temporal/src/examples/temporal-example.ts +35 -34
- package/templates/vscode/.vscode/launch.json +164 -164
- package/templates/vscode/.vscode/settings.json +9 -9
- package/templates/vscode/uns-kit.code-workspace +13 -13
- package/templates/python/examples/api_register_and_serve.py +0 -159
- package/templates/python/examples/data_publish_once.py +0 -140
- package/templates/python/examples/data_publisher_loop.py +0 -142
- package/templates/python/gateway_client.py +0 -242
- package/templates/python/local/README.md +0 -8
- package/templates/python/local/__init__.py +0 -2
- package/templates/python/requirements.txt +0 -3
|
@@ -1,84 +1,134 @@
|
|
|
1
|
-
# Python Examples for the UNS gRPC Gateway
|
|
2
|
-
|
|
3
|
-
These examples demonstrate how to use the Node-based UNS Gateway (MQTT + UNS infra) from Python via gRPC. They mirror the TypeScript examples for data, table, cron-like publishing, and API endpoints.
|
|
4
|
-
|
|
5
|
-
The gateway centralizes all UNS rules (packet format, sequenceId, interval, produced topics, handover). Python focuses on business logic and ML/ETL while calling the gateway.
|
|
6
|
-
|
|
7
|
-
## Prerequisites
|
|
8
|
-
|
|
9
|
-
- Build the Node project once: `npm run build`
|
|
10
|
-
- Generate Python stubs and venv: `cd python && ./scripts/setup.sh && source venv/bin/activate`
|
|
11
|
-
- Ensure `config.json` is present at repo root (Node gateway reads brokers and UNS options from it)
|
|
12
|
-
|
|
13
|
-
## Gateway Address
|
|
14
|
-
|
|
15
|
-
- If `--addr` is omitted, each script auto‑generates a unique address:
|
|
16
|
-
- Unix/macOS: `unix:/tmp/uns-gateway-<script>-<pid>.sock`
|
|
17
|
-
- Windows: `127.0.0.1:<ephemeral_port>`
|
|
18
|
-
- You can still pass an explicit `--addr` if you want a fixed address.
|
|
19
|
-
|
|
20
|
-
All examples accept `--addr` and `--auto`. With `--auto`, the example will spawn the gateway via `npm run gateway` and wait until it's ready.
|
|
21
|
-
|
|
22
|
-
Imports note: Examples add the generated stubs folder (`python/gen`) to `sys.path` so you can run them from the repo root or from the `python/` folder. Ensure you’ve run `./scripts/setup.sh` so `gen` exists.
|
|
23
|
-
|
|
24
|
-
Readiness: Examples call the gateway’s `Ready()` RPC to wait for the requested components (publisher/subscriber/API) to be active before publishing or subscribing. No manual sleeps are required.
|
|
25
|
-
|
|
26
|
-
## Common Flags
|
|
27
|
-
|
|
28
|
-
- `--addr`: Gateway address (`unix:/path.sock` or `host:port`)
|
|
29
|
-
- `--auto`: Auto-start the gateway if not running
|
|
30
|
-
|
|
31
|
-
## Scripts
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
1
|
+
# Python Examples for the UNS gRPC Gateway
|
|
2
|
+
|
|
3
|
+
These examples demonstrate how to use the Node-based UNS Gateway (MQTT + UNS infra) from Python via gRPC. They mirror the TypeScript examples for data, table, cron-like publishing, and API endpoints.
|
|
4
|
+
|
|
5
|
+
The gateway centralizes all UNS rules (packet format, sequenceId, interval, produced topics, handover). Python focuses on business logic and ML/ETL while calling the gateway.
|
|
6
|
+
|
|
7
|
+
## Prerequisites
|
|
8
|
+
|
|
9
|
+
- Build the Node project once: `npm run build`
|
|
10
|
+
- Generate Python stubs and venv: `cd python && ./scripts/setup.sh && source venv/bin/activate`
|
|
11
|
+
- Ensure `config.json` is present at repo root (Node gateway reads brokers and UNS options from it)
|
|
12
|
+
|
|
13
|
+
## Gateway Address
|
|
14
|
+
|
|
15
|
+
- If `--addr` is omitted, each script auto‑generates a unique address:
|
|
16
|
+
- Unix/macOS: `unix:/tmp/uns-gateway-<script>-<pid>.sock`
|
|
17
|
+
- Windows: `127.0.0.1:<ephemeral_port>`
|
|
18
|
+
- You can still pass an explicit `--addr` if you want a fixed address.
|
|
19
|
+
|
|
20
|
+
All examples accept `--addr` and `--auto`. With `--auto`, the example will spawn the gateway via `npm run gateway` and wait until it's ready.
|
|
21
|
+
|
|
22
|
+
Imports note: Examples add the generated stubs folder (`python/gen`) to `sys.path` so you can run them from the repo root or from the `python/` folder. Ensure you’ve run `./scripts/setup.sh` so `gen` exists.
|
|
23
|
+
|
|
24
|
+
Readiness: Examples call the gateway’s `Ready()` RPC to wait for the requested components (publisher/subscriber/API) to be active before publishing or subscribing. No manual sleeps are required.
|
|
25
|
+
|
|
26
|
+
## Common Flags
|
|
27
|
+
|
|
28
|
+
- `--addr`: Gateway address (`unix:/path.sock` or `host:port`)
|
|
29
|
+
- `--auto`: Auto-start the gateway if not running
|
|
30
|
+
|
|
31
|
+
## Scripts
|
|
32
|
+
### `data_transformer.py`
|
|
33
|
+
|
|
34
|
+
Transforms incoming sensor data and republishes it to a target topic.
|
|
35
|
+
|
|
36
|
+
- **Description:**
|
|
37
|
+
Subscribes to `sensors/temperature`, applies a transformation (e.g., scales the value), and republishes it as processed data.
|
|
38
|
+
|
|
39
|
+
- **Example:**
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
python python/examples/data_transformer.py
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Transforms data from `sensors/temperature` and republishes to `sensors/temperature/room1` it with a modified value.
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
### `data_subscribe.py`
|
|
51
|
+
|
|
52
|
+
Subscribes to topics via the gateway and prints received messages.
|
|
53
|
+
|
|
54
|
+
- **Description:**
|
|
55
|
+
Useful for monitoring live data updates.
|
|
56
|
+
|
|
57
|
+
- **Example:**
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
python python/examples/data_subscribe.py
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Prints all messages received from `sensors/temperature`.
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
### `data_publish.py`
|
|
69
|
+
|
|
70
|
+
Publishes a single data point to a UNS topic.
|
|
71
|
+
|
|
72
|
+
- **Description:**
|
|
73
|
+
Sends a single temperature measurement (e.g., 22.5 °C) to the gateway, waits 10 seconds, then exits.
|
|
74
|
+
|
|
75
|
+
- **Example:**
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
python python/examples/data_publish.py
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Publishes one temperature value to `sensors/temperature/room1`.
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
### `table_transformer.py`
|
|
87
|
+
|
|
88
|
+
Publishes a structured table-like JSON object as UNS table data.
|
|
89
|
+
|
|
90
|
+
- **Description:**
|
|
91
|
+
Sends summarized or grouped sensor data (e.g., temperature, humidity, status) to a topic.
|
|
92
|
+
|
|
93
|
+
- **Example:**
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
python python/examples/dtable_transformer.py
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Publishes a table with room statistics (temperature, humidity, etc.) to `sensors/summary/room1`.
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
### `api_handler.py`
|
|
105
|
+
|
|
106
|
+
Registers and handles an example API endpoint for querying data.
|
|
107
|
+
|
|
108
|
+
- **Description:**
|
|
109
|
+
Demonstrates registering an API endpoint (`example/summary-1`) with query parameters and returning JSON responses.
|
|
110
|
+
|
|
111
|
+
- **Example:**
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
python python/examples/api_handler.py
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Then test it with:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
http://<gateway-host>:<gateway-port>/api/example/summary-1?filter=test&limit=10
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Auth
|
|
124
|
+
|
|
125
|
+
- If `config.json` sets `uns.jwksWellKnownUrl`, the gateway enforces JWT via JWKS (and path rules), matching UnsApiProxy behavior.
|
|
126
|
+
- Otherwise, it uses a dev `jwtSecret` ("CHANGEME"). For local testing, you can:
|
|
127
|
+
- Remove JWKS settings in `config.json` and rely on the dev secret, or
|
|
128
|
+
- Provide a valid Bearer token as required by your environment.
|
|
129
|
+
|
|
130
|
+
## Notes
|
|
131
|
+
|
|
132
|
+
- The gateway binds to `--addr` or the `UNS_GATEWAY_ADDR` env var. Examples pass `--addr` explicitly.
|
|
133
|
+
- On Unix/macOS, a stable UDS path (e.g., `/tmp/uns-gateway.sock`) is recommended for simplicity.
|
|
134
|
+
- All examples will auto-start the gateway when `--auto` is used.
|
|
File without changes
|
|
@@ -1,101 +1,28 @@
|
|
|
1
|
-
|
|
2
|
-
from
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
]
|
|
30
|
-
|
|
31
|
-
for idx, tag in [(1, "Tag1"), (2, "Tag2")]:
|
|
32
|
-
req = pb2.RegisterApiGetRequest(
|
|
33
|
-
topic="example/",
|
|
34
|
-
attribute=f"summary-{idx}",
|
|
35
|
-
api_description=f"Test API endpoint {idx}",
|
|
36
|
-
tags=[tag],
|
|
37
|
-
query_params=qp,
|
|
38
|
-
)
|
|
39
|
-
res = stub.RegisterApiGet(req)
|
|
40
|
-
if not res.ok:
|
|
41
|
-
raise RuntimeError(f"Failed to register summary-{idx}: {res.error}")
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def serve_requests(stub: gw.UnsGatewayStub, echo: bool = False) -> None:
|
|
45
|
-
q: "queue.Queue[pb2.ApiEventResponse|None]" = queue.Queue()
|
|
46
|
-
|
|
47
|
-
def req_iter():
|
|
48
|
-
while True:
|
|
49
|
-
item = q.get()
|
|
50
|
-
if item is None:
|
|
51
|
-
break
|
|
52
|
-
yield item
|
|
53
|
-
|
|
54
|
-
stream = stub.ApiEventStream(req_iter())
|
|
55
|
-
try:
|
|
56
|
-
for ev in stream:
|
|
57
|
-
# ev.path e.g. /example/summary-1; ev.query is a map<string,string>
|
|
58
|
-
path = ev.path
|
|
59
|
-
query: Dict[str, str] = dict(ev.query)
|
|
60
|
-
filt = query.get("filter", "")
|
|
61
|
-
limit = query.get("limit")
|
|
62
|
-
endpoint = "summary-1" if path.endswith("summary-1") else ("summary-2" if path.endswith("summary-2") else "unknown")
|
|
63
|
-
|
|
64
|
-
# Your business logic here. This example returns JSON reflecting the request.
|
|
65
|
-
body = {
|
|
66
|
-
"status": "OK",
|
|
67
|
-
"endpoint": endpoint,
|
|
68
|
-
"filter": filt,
|
|
69
|
-
"limit": int(limit) if (limit is not None and limit.isdigit()) else None,
|
|
70
|
-
"data": [
|
|
71
|
-
{"id": 1, "value": 42},
|
|
72
|
-
{"id": 2, "value": 43},
|
|
73
|
-
],
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
q.put(
|
|
77
|
-
pb2.ApiEventResponse(
|
|
78
|
-
id=ev.id,
|
|
79
|
-
status=200,
|
|
80
|
-
headers={"Content-Type": "application/json"},
|
|
81
|
-
body=json.dumps(body),
|
|
82
|
-
)
|
|
83
|
-
)
|
|
84
|
-
except KeyboardInterrupt:
|
|
85
|
-
q.put(None)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
def main():
|
|
89
|
-
parser = argparse.ArgumentParser(description="Python API handler for UNS gRPC Gateway")
|
|
90
|
-
parser.add_argument("--addr", default=os.environ.get("GATEWAY_ADDR", "unix:/tmp/template-uns-rtt-1.6.14-uns-gateway.sock"), help="Gateway address (unix:/path.sock or host:port)")
|
|
91
|
-
parser.add_argument("--echo", action="store_true", help="Echo simple OK response instead of JSON body")
|
|
92
|
-
args = parser.parse_args()
|
|
93
|
-
|
|
94
|
-
with make_channel(args.addr) as ch:
|
|
95
|
-
stub = gw.UnsGatewayStub(ch)
|
|
96
|
-
register_endpoints(stub)
|
|
97
|
-
serve_requests(stub, echo=args.echo)
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
if __name__ == "__main__":
|
|
101
|
-
main()
|
|
1
|
+
from gateway.client import Client
|
|
2
|
+
from uns_gateway_pb2 import ApiQueryParam, ApiEventResponse
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
client = Client()
|
|
6
|
+
|
|
7
|
+
# Register API with structured query params
|
|
8
|
+
qp = [
|
|
9
|
+
ApiQueryParam(name="filter", type="string", required=True, description="Filter za podatke"),
|
|
10
|
+
ApiQueryParam(name="limit", type="number", required=False, description="Koliko podatkov želiš"),
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
client.register_api(
|
|
14
|
+
topic="example/",
|
|
15
|
+
attribute="summary-1",
|
|
16
|
+
desc="Example API endpoint",
|
|
17
|
+
tags=["example", "demo"],
|
|
18
|
+
query_params=qp
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
# Handle API events
|
|
22
|
+
def handle_api(ev):
|
|
23
|
+
print(f"API request: {ev.path}, query={dict(ev.query)}")
|
|
24
|
+
body = {"status": "OK", "path": ev.path, "query": dict(ev.query)}
|
|
25
|
+
return ApiEventResponse(id=ev.id, status=200, headers={"Content-Type": "application/json"},
|
|
26
|
+
body=json.dumps(body))
|
|
27
|
+
|
|
28
|
+
client.api_stream(handle_event=handle_api)
|
|
@@ -1,124 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
import grpc
|
|
10
|
-
|
|
11
|
-
import sys, os
|
|
12
|
-
# Ensure generated stubs (python/gen) are importable as top-level modules
|
|
13
|
-
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'gen')))
|
|
14
|
-
import uns_gateway_pb2 as pb2
|
|
15
|
-
import uns_gateway_pb2_grpc as gw
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def make_channel(addr: str) -> grpc.Channel:
|
|
19
|
-
return grpc.insecure_channel(addr)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
import atexit, signal, subprocess
|
|
23
|
-
_AUTO_GATEWAY_PROC = None
|
|
24
|
-
def _cleanup_gateway():
|
|
25
|
-
global _AUTO_GATEWAY_PROC
|
|
26
|
-
if _AUTO_GATEWAY_PROC and _AUTO_GATEWAY_PROC.poll() is None:
|
|
27
|
-
try:
|
|
28
|
-
if os.name == 'nt':
|
|
29
|
-
_AUTO_GATEWAY_PROC.terminate()
|
|
30
|
-
else:
|
|
31
|
-
os.killpg(os.getpgid(_AUTO_GATEWAY_PROC.pid), signal.SIGTERM)
|
|
32
|
-
try:
|
|
33
|
-
_AUTO_GATEWAY_PROC.wait(timeout=3)
|
|
34
|
-
except Exception:
|
|
35
|
-
if os.name == 'nt':
|
|
36
|
-
_AUTO_GATEWAY_PROC.kill()
|
|
37
|
-
else:
|
|
38
|
-
os.killpg(os.getpgid(_AUTO_GATEWAY_PROC.pid), signal.SIGKILL)
|
|
39
|
-
except Exception:
|
|
40
|
-
pass
|
|
41
|
-
_AUTO_GATEWAY_PROC = None
|
|
42
|
-
|
|
43
|
-
def _default_addr() -> str:
|
|
44
|
-
if os.name == 'nt':
|
|
45
|
-
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
46
|
-
s.bind(('127.0.0.1', 0))
|
|
47
|
-
port = s.getsockname()[1]
|
|
48
|
-
return f"127.0.0.1:{port}"
|
|
49
|
-
else:
|
|
50
|
-
return f"unix:/tmp/uns-gateway-data-subscribe-{os.getpid()}.sock"
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def ensure_gateway_running(addr: str | None, *, auto: bool, timeout_s: int = 20) -> str:
|
|
54
|
-
if not addr:
|
|
55
|
-
addr = _default_addr()
|
|
56
|
-
ch = make_channel(addr)
|
|
57
|
-
try:
|
|
58
|
-
grpc.channel_ready_future(ch).result(timeout=2)
|
|
59
|
-
ch.close(); return addr
|
|
60
|
-
except Exception:
|
|
61
|
-
ch.close()
|
|
62
|
-
if not auto:
|
|
63
|
-
return addr
|
|
64
|
-
repo_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
|
|
65
|
-
cli_path = os.path.join(repo_root, "dist", "uns-grpc", "uns-gateway-cli")
|
|
66
|
-
popen_kwargs = {}
|
|
67
|
-
creationflags = 0
|
|
68
|
-
if os.name != 'nt':
|
|
69
|
-
popen_kwargs['preexec_fn'] = os.setsid
|
|
70
|
-
else:
|
|
71
|
-
creationflags = getattr(subprocess, 'CREATE_NEW_PROCESS_GROUP', 0)
|
|
72
|
-
suffix = f"py-{os.path.basename(__file__).replace('.py','')}-{os.getpid()}"
|
|
73
|
-
proc = subprocess.Popen(["node", cli_path, "--addr", addr, "--instanceSuffix", suffix, "--instanceMode", "force"], cwd=repo_root, creationflags=creationflags, **popen_kwargs)
|
|
74
|
-
global _AUTO_GATEWAY_PROC
|
|
75
|
-
_AUTO_GATEWAY_PROC = proc
|
|
76
|
-
atexit.register(_cleanup_gateway)
|
|
77
|
-
start = time.time()
|
|
78
|
-
while time.time() - start < timeout_s:
|
|
79
|
-
ch2 = make_channel(addr)
|
|
80
|
-
try:
|
|
81
|
-
grpc.channel_ready_future(ch2).result(timeout=2)
|
|
82
|
-
ch2.close();
|
|
83
|
-
try:
|
|
84
|
-
wait_s = int(os.environ.get("UNS_GATEWAY_HANDOVER_WAIT", "11"))
|
|
85
|
-
except Exception:
|
|
86
|
-
wait_s = 11
|
|
87
|
-
if wait_s > 0:
|
|
88
|
-
time.sleep(wait_s)
|
|
89
|
-
return addr
|
|
90
|
-
except Exception:
|
|
91
|
-
ch2.close(); time.sleep(0.5)
|
|
92
|
-
raise RuntimeError("Gateway did not become ready in time")
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
def subscribe(addr: str | None, topics: list[str], auto: bool) -> None:
|
|
96
|
-
addr = ensure_gateway_running(addr, auto=auto)
|
|
97
|
-
# Ensure subscriber readiness
|
|
98
|
-
try:
|
|
99
|
-
with make_channel(addr) as ch:
|
|
100
|
-
gw.UnsGatewayStub(ch).Ready(pb2.ReadyRequest(timeout_ms=15000, wait_input=True))
|
|
101
|
-
except Exception:
|
|
102
|
-
pass
|
|
103
|
-
with make_channel(addr) as ch:
|
|
104
|
-
stub = gw.UnsGatewayStub(ch)
|
|
105
|
-
stream = stub.Subscribe(pb2.SubscribeRequest(topics=topics))
|
|
106
|
-
try:
|
|
107
|
-
for msg in stream:
|
|
108
|
-
print(f"{msg.topic}: {msg.payload}")
|
|
109
|
-
except KeyboardInterrupt:
|
|
110
|
-
pass
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
def main():
|
|
114
|
-
parser = argparse.ArgumentParser(description="Subscribe to MQTT topics via gateway")
|
|
115
|
-
parser.add_argument("--addr", default=None)
|
|
116
|
-
parser.add_argument("--auto", action="store_true")
|
|
117
|
-
parser.add_argument("topics", nargs="+")
|
|
118
|
-
args = parser.parse_args()
|
|
119
|
-
|
|
120
|
-
subscribe(args.addr, args.topics, args.auto)
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if __name__ == "__main__":
|
|
124
|
-
main()
|
|
1
|
+
from gateway.client import Client
|
|
2
|
+
|
|
3
|
+
def handle_message(msg):
|
|
4
|
+
print(f"Callback got {msg.topic}: {msg.payload}")
|
|
5
|
+
# You could also transform, save, or push to another service
|
|
6
|
+
|
|
7
|
+
client = Client()
|
|
8
|
+
client.subscribe(["sensors/temperature"], callback=handle_message)
|
|
@@ -1,147 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
import uns_gateway_pb2_grpc as gw
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def iso_now() -> str:
|
|
22
|
-
dt = datetime.now(timezone.utc)
|
|
23
|
-
return dt.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def make_channel(addr: str) -> grpc.Channel:
|
|
27
|
-
return grpc.insecure_channel(addr)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
import atexit, signal, subprocess
|
|
31
|
-
_AUTO_GATEWAY_PROC = None
|
|
32
|
-
def _cleanup_gateway():
|
|
33
|
-
global _AUTO_GATEWAY_PROC
|
|
34
|
-
if _AUTO_GATEWAY_PROC and _AUTO_GATEWAY_PROC.poll() is None:
|
|
35
|
-
try:
|
|
36
|
-
if os.name == 'nt':
|
|
37
|
-
_AUTO_GATEWAY_PROC.terminate()
|
|
38
|
-
else:
|
|
39
|
-
os.killpg(os.getpgid(_AUTO_GATEWAY_PROC.pid), signal.SIGTERM)
|
|
40
|
-
try:
|
|
41
|
-
_AUTO_GATEWAY_PROC.wait(timeout=3)
|
|
42
|
-
except Exception:
|
|
43
|
-
if os.name == 'nt':
|
|
44
|
-
_AUTO_GATEWAY_PROC.kill()
|
|
45
|
-
else:
|
|
46
|
-
os.killpg(os.getpgid(_AUTO_GATEWAY_PROC.pid), signal.SIGKILL)
|
|
47
|
-
except Exception:
|
|
48
|
-
pass
|
|
49
|
-
_AUTO_GATEWAY_PROC = None
|
|
50
|
-
|
|
51
|
-
def _default_addr() -> str:
|
|
52
|
-
if os.name == 'nt':
|
|
53
|
-
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
54
|
-
s.bind(('127.0.0.1', 0))
|
|
55
|
-
port = s.getsockname()[1]
|
|
56
|
-
return f"127.0.0.1:{port}"
|
|
57
|
-
else:
|
|
58
|
-
return f"unix:/tmp/uns-gateway-data-transformer-{os.getpid()}.sock"
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def ensure_gateway_running(addr: str | None, *, auto: bool, timeout_s: int = 20) -> str:
|
|
62
|
-
if not addr:
|
|
63
|
-
addr = _default_addr()
|
|
64
|
-
ch = make_channel(addr)
|
|
65
|
-
try:
|
|
66
|
-
grpc.channel_ready_future(ch).result(timeout=2)
|
|
67
|
-
ch.close(); return addr
|
|
68
|
-
except Exception:
|
|
69
|
-
ch.close()
|
|
70
|
-
if not auto:
|
|
71
|
-
return addr
|
|
72
|
-
repo_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
|
|
73
|
-
cli_path = os.path.join(repo_root, "dist", "uns-grpc", "uns-gateway-cli")
|
|
74
|
-
popen_kwargs = {}
|
|
75
|
-
creationflags = 0
|
|
76
|
-
if os.name != 'nt':
|
|
77
|
-
popen_kwargs['preexec_fn'] = os.setsid
|
|
78
|
-
else:
|
|
79
|
-
creationflags = getattr(subprocess, 'CREATE_NEW_PROCESS_GROUP', 0)
|
|
80
|
-
suffix = f"py-{os.path.basename(__file__).replace('.py','')}-{os.getpid()}"
|
|
81
|
-
proc = subprocess.Popen(["node", cli_path, "--addr", addr, "--instanceSuffix", suffix, "--instanceMode", "force"], cwd=repo_root, creationflags=creationflags, **popen_kwargs)
|
|
82
|
-
global _AUTO_GATEWAY_PROC
|
|
83
|
-
_AUTO_GATEWAY_PROC = proc
|
|
84
|
-
atexit.register(_cleanup_gateway)
|
|
85
|
-
start = time.time()
|
|
86
|
-
while time.time() - start < timeout_s:
|
|
87
|
-
ch2 = make_channel(addr)
|
|
88
|
-
try:
|
|
89
|
-
grpc.channel_ready_future(ch2).result(timeout=2)
|
|
90
|
-
ch2.close();
|
|
91
|
-
try:
|
|
92
|
-
wait_s = int(os.environ.get("UNS_GATEWAY_HANDOVER_WAIT", "11"))
|
|
93
|
-
except Exception:
|
|
94
|
-
wait_s = 11
|
|
95
|
-
if wait_s > 0:
|
|
96
|
-
time.sleep(wait_s)
|
|
97
|
-
return addr
|
|
98
|
-
except Exception:
|
|
99
|
-
ch2.close(); time.sleep(0.5)
|
|
100
|
-
raise RuntimeError("Gateway did not become ready in time")
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
def transform(addr: str | None, in_topics: Iterable[str], out_topic: str, out_attribute: str, uom: str, data_group: str, auto: bool) -> None:
|
|
104
|
-
addr = ensure_gateway_running(addr, auto=auto)
|
|
105
|
-
# Ensure both subscriber and publisher are ready
|
|
106
|
-
try:
|
|
107
|
-
with make_channel(addr) as ch:
|
|
108
|
-
gw.UnsGatewayStub(ch).Ready(pb2.ReadyRequest(timeout_ms=15000, wait_input=True, wait_output=True))
|
|
109
|
-
except Exception:
|
|
110
|
-
pass
|
|
111
|
-
with make_channel(addr) as ch:
|
|
112
|
-
stub = gw.UnsGatewayStub(ch)
|
|
113
|
-
stream = stub.Subscribe(pb2.SubscribeRequest(topics=list(in_topics)))
|
|
114
|
-
for msg in stream:
|
|
115
|
-
# Expecting raw/data with payload "<number>,<timestamp>"
|
|
116
|
-
if msg.topic == "raw/data":
|
|
117
|
-
parts = msg.payload.split(",")
|
|
118
|
-
try:
|
|
119
|
-
value = float(parts[0])
|
|
120
|
-
except Exception:
|
|
121
|
-
continue
|
|
122
|
-
req = pb2.PublishRequest(
|
|
123
|
-
topic=out_topic,
|
|
124
|
-
attribute=out_attribute,
|
|
125
|
-
data=pb2.Data(time=iso_now(), value_number=value, uom=uom, data_group=data_group),
|
|
126
|
-
)
|
|
127
|
-
res = stub.Publish(req)
|
|
128
|
-
if not res.ok:
|
|
129
|
-
print("publish error:", res.error)
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
def main():
|
|
133
|
-
parser = argparse.ArgumentParser(description="Subscribe to raw/# and publish UNS data packets")
|
|
134
|
-
parser.add_argument("--addr", default=None, help="Gateway address (unix:/path.sock or host:port); defaults to unique per-script")
|
|
135
|
-
parser.add_argument("--auto", action="store_true", help="auto-start gateway if not running")
|
|
136
|
-
parser.add_argument("--in", dest="in_topics", nargs="+", default=["raw/#"], help="input topics to subscribe")
|
|
137
|
-
parser.add_argument("--out-topic", default="example/", help="output UNS base topic")
|
|
138
|
-
parser.add_argument("--attribute", default="data-number", help="output attribute")
|
|
139
|
-
parser.add_argument("--uom", default="mV", help="unit of measure")
|
|
140
|
-
parser.add_argument("--group", default="electricity", help="dataGroup")
|
|
141
|
-
args = parser.parse_args()
|
|
142
|
-
|
|
143
|
-
transform(args.addr, args.in_topics, args.out_topic, args.attribute, args.uom, args.group, args.auto)
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
if __name__ == "__main__":
|
|
147
|
-
main()
|
|
1
|
+
from gateway.client import Client, iso_now
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
client = Client()
|
|
5
|
+
|
|
6
|
+
def transform_data(value):
|
|
7
|
+
|
|
8
|
+
return value * 1.1
|
|
9
|
+
|
|
10
|
+
def handle_message(msg):
|
|
11
|
+
payload = json.loads(msg.payload)
|
|
12
|
+
value = float(payload["value"])
|
|
13
|
+
|
|
14
|
+
transformed_value = transform_data(value)
|
|
15
|
+
client.publish_data("sensors/temperature", "room1", value=transformed_value, uom="°C")
|
|
16
|
+
|
|
17
|
+
client.subscribe(["sensors/temperature"], callback=handle_message)
|