@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,140 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
from __future__ import annotations
|
|
3
|
-
|
|
4
|
-
import argparse
|
|
5
|
-
import os
|
|
6
|
-
import time
|
|
7
|
-
from datetime import datetime, timezone
|
|
8
|
-
import socket
|
|
9
|
-
|
|
10
|
-
import grpc
|
|
11
|
-
|
|
12
|
-
import sys, os
|
|
13
|
-
# Ensure generated stubs (python/gen) are importable as top-level modules
|
|
14
|
-
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'gen')))
|
|
15
|
-
import uns_gateway_pb2 as pb2
|
|
16
|
-
import uns_gateway_pb2_grpc as gw
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def iso_now() -> str:
|
|
20
|
-
dt = datetime.now(timezone.utc)
|
|
21
|
-
return dt.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def make_channel(addr: str) -> grpc.Channel:
|
|
25
|
-
return grpc.insecure_channel(addr)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
import atexit, signal, subprocess
|
|
29
|
-
_AUTO_GATEWAY_PROC = None
|
|
30
|
-
|
|
31
|
-
def _cleanup_gateway():
|
|
32
|
-
global _AUTO_GATEWAY_PROC
|
|
33
|
-
if _AUTO_GATEWAY_PROC and _AUTO_GATEWAY_PROC.poll() is None:
|
|
34
|
-
try:
|
|
35
|
-
if os.name == 'nt':
|
|
36
|
-
_AUTO_GATEWAY_PROC.terminate()
|
|
37
|
-
else:
|
|
38
|
-
os.killpg(os.getpgid(_AUTO_GATEWAY_PROC.pid), signal.SIGTERM)
|
|
39
|
-
try:
|
|
40
|
-
_AUTO_GATEWAY_PROC.wait(timeout=3)
|
|
41
|
-
except Exception:
|
|
42
|
-
if os.name == 'nt':
|
|
43
|
-
_AUTO_GATEWAY_PROC.kill()
|
|
44
|
-
else:
|
|
45
|
-
os.killpg(os.getpgid(_AUTO_GATEWAY_PROC.pid), signal.SIGKILL)
|
|
46
|
-
except Exception:
|
|
47
|
-
pass
|
|
48
|
-
_AUTO_GATEWAY_PROC = None
|
|
49
|
-
|
|
50
|
-
def _default_addr() -> str:
|
|
51
|
-
if os.name == 'nt':
|
|
52
|
-
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
53
|
-
s.bind(('127.0.0.1', 0))
|
|
54
|
-
port = s.getsockname()[1]
|
|
55
|
-
return f"127.0.0.1:{port}"
|
|
56
|
-
else:
|
|
57
|
-
return f"unix:/tmp/uns-gateway-data-publish-once-{os.getpid()}.sock"
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def ensure_gateway_running(addr: str | None, *, auto: bool, timeout_s: int = 20) -> str:
|
|
61
|
-
if not addr:
|
|
62
|
-
addr = _default_addr()
|
|
63
|
-
ch = make_channel(addr)
|
|
64
|
-
try:
|
|
65
|
-
grpc.channel_ready_future(ch).result(timeout=2)
|
|
66
|
-
ch.close(); return addr
|
|
67
|
-
except Exception:
|
|
68
|
-
ch.close()
|
|
69
|
-
if not auto:
|
|
70
|
-
return addr
|
|
71
|
-
# spawn gateway directly to get a handle we can clean up
|
|
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 publish_once(addr: str | None, out_topic: str, attribute: str, value: float, uom: str, group: str, auto: bool) -> None:
|
|
104
|
-
addr = ensure_gateway_running(addr, auto=auto)
|
|
105
|
-
# Ensure publisher readiness via gRPC
|
|
106
|
-
try:
|
|
107
|
-
with make_channel(addr) as ch:
|
|
108
|
-
gw.UnsGatewayStub(ch).Ready(pb2.ReadyRequest(timeout_ms=15000, wait_output=True))
|
|
109
|
-
except Exception:
|
|
110
|
-
pass
|
|
111
|
-
with make_channel(addr) as ch:
|
|
112
|
-
stub = gw.UnsGatewayStub(ch)
|
|
113
|
-
req = pb2.PublishRequest(
|
|
114
|
-
topic=out_topic,
|
|
115
|
-
attribute=attribute,
|
|
116
|
-
data=pb2.Data(time=iso_now(), value_number=float(value), uom=uom, data_group=group),
|
|
117
|
-
)
|
|
118
|
-
res = stub.Publish(req)
|
|
119
|
-
if not res.ok:
|
|
120
|
-
print("error:", res.error)
|
|
121
|
-
else:
|
|
122
|
-
print("OK")
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
def main():
|
|
126
|
-
parser = argparse.ArgumentParser(description="Publish one UNS data packet")
|
|
127
|
-
parser.add_argument("--addr", default=None)
|
|
128
|
-
parser.add_argument("--auto", action="store_true")
|
|
129
|
-
parser.add_argument("--out-topic", default="example/")
|
|
130
|
-
parser.add_argument("--attribute", default="data-number")
|
|
131
|
-
parser.add_argument("--value", type=float, default=42.0)
|
|
132
|
-
parser.add_argument("--uom", default="mV")
|
|
133
|
-
parser.add_argument("--group", default="electricity")
|
|
134
|
-
args = parser.parse_args()
|
|
135
|
-
|
|
136
|
-
publish_once(args.addr, args.out_topic, args.attribute, args.value, args.uom, args.group, args.auto)
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
if __name__ == "__main__":
|
|
140
|
-
main()
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
from __future__ import annotations
|
|
3
|
-
|
|
4
|
-
import argparse
|
|
5
|
-
import os
|
|
6
|
-
import time
|
|
7
|
-
from datetime import datetime, timezone
|
|
8
|
-
import socket
|
|
9
|
-
|
|
10
|
-
import grpc
|
|
11
|
-
|
|
12
|
-
import sys, os
|
|
13
|
-
# Ensure generated stubs (python/gen) are importable as top-level modules
|
|
14
|
-
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'gen')))
|
|
15
|
-
import uns_gateway_pb2 as pb2
|
|
16
|
-
import uns_gateway_pb2_grpc as gw
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def iso_now() -> str:
|
|
20
|
-
dt = datetime.now(timezone.utc)
|
|
21
|
-
return dt.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def make_channel(addr: str) -> grpc.Channel:
|
|
25
|
-
return grpc.insecure_channel(addr)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
import atexit, signal, subprocess
|
|
29
|
-
_AUTO_GATEWAY_PROC = None
|
|
30
|
-
def _cleanup_gateway():
|
|
31
|
-
global _AUTO_GATEWAY_PROC
|
|
32
|
-
if _AUTO_GATEWAY_PROC and _AUTO_GATEWAY_PROC.poll() is None:
|
|
33
|
-
try:
|
|
34
|
-
if os.name == 'nt':
|
|
35
|
-
_AUTO_GATEWAY_PROC.terminate()
|
|
36
|
-
else:
|
|
37
|
-
os.killpg(os.getpgid(_AUTO_GATEWAY_PROC.pid), signal.SIGTERM)
|
|
38
|
-
try:
|
|
39
|
-
_AUTO_GATEWAY_PROC.wait(timeout=3)
|
|
40
|
-
except Exception:
|
|
41
|
-
if os.name == 'nt':
|
|
42
|
-
_AUTO_GATEWAY_PROC.kill()
|
|
43
|
-
else:
|
|
44
|
-
os.killpg(os.getpgid(_AUTO_GATEWAY_PROC.pid), signal.SIGKILL)
|
|
45
|
-
except Exception:
|
|
46
|
-
pass
|
|
47
|
-
_AUTO_GATEWAY_PROC = None
|
|
48
|
-
|
|
49
|
-
def _default_addr() -> str:
|
|
50
|
-
if os.name == 'nt':
|
|
51
|
-
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
52
|
-
s.bind(('127.0.0.1', 0))
|
|
53
|
-
port = s.getsockname()[1]
|
|
54
|
-
return f"127.0.0.1:{port}"
|
|
55
|
-
else:
|
|
56
|
-
return f"unix:/tmp/uns-gateway-data-publisher-loop-{os.getpid()}.sock"
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def ensure_gateway_running(addr: str | None, *, auto: bool, timeout_s: int = 20) -> str:
|
|
60
|
-
if not addr:
|
|
61
|
-
addr = _default_addr()
|
|
62
|
-
ch = make_channel(addr)
|
|
63
|
-
try:
|
|
64
|
-
grpc.channel_ready_future(ch).result(timeout=2)
|
|
65
|
-
ch.close(); return addr
|
|
66
|
-
except Exception:
|
|
67
|
-
ch.close()
|
|
68
|
-
if not auto:
|
|
69
|
-
return addr
|
|
70
|
-
repo_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
|
|
71
|
-
cli_path = os.path.join(repo_root, "dist", "uns-grpc", "uns-gateway-cli")
|
|
72
|
-
popen_kwargs = {}
|
|
73
|
-
creationflags = 0
|
|
74
|
-
if os.name != 'nt':
|
|
75
|
-
popen_kwargs['preexec_fn'] = os.setsid
|
|
76
|
-
else:
|
|
77
|
-
creationflags = getattr(subprocess, 'CREATE_NEW_PROCESS_GROUP', 0)
|
|
78
|
-
suffix = f"py-{os.path.basename(__file__).replace('.py','')}-{os.getpid()}"
|
|
79
|
-
proc = subprocess.Popen(["node", cli_path, "--addr", addr, "--instanceSuffix", suffix, "--instanceMode", "force"], cwd=repo_root, creationflags=creationflags, **popen_kwargs)
|
|
80
|
-
global _AUTO_GATEWAY_PROC
|
|
81
|
-
_AUTO_GATEWAY_PROC = proc
|
|
82
|
-
atexit.register(_cleanup_gateway)
|
|
83
|
-
start = time.time()
|
|
84
|
-
while time.time() - start < timeout_s:
|
|
85
|
-
ch2 = make_channel(addr)
|
|
86
|
-
try:
|
|
87
|
-
grpc.channel_ready_future(ch2).result(timeout=2)
|
|
88
|
-
ch2.close();
|
|
89
|
-
try:
|
|
90
|
-
wait_s = int(os.environ.get("UNS_GATEWAY_HANDOVER_WAIT", "11"))
|
|
91
|
-
except Exception:
|
|
92
|
-
wait_s = 11
|
|
93
|
-
if wait_s > 0:
|
|
94
|
-
time.sleep(wait_s)
|
|
95
|
-
return addr
|
|
96
|
-
except Exception:
|
|
97
|
-
ch2.close(); time.sleep(0.5)
|
|
98
|
-
raise RuntimeError("Gateway did not become ready in time")
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
def loop_publish(addr: str | None, out_topic: str, attribute: str, uom: str, period_ms: int, auto: bool) -> None:
|
|
102
|
-
addr = ensure_gateway_running(addr, auto=auto)
|
|
103
|
-
# Ensure publisher readiness via gRPC
|
|
104
|
-
try:
|
|
105
|
-
with make_channel(addr) as ch:
|
|
106
|
-
gw.UnsGatewayStub(ch).Ready(pb2.ReadyRequest(timeout_ms=15000, wait_output=True))
|
|
107
|
-
except Exception:
|
|
108
|
-
pass
|
|
109
|
-
with make_channel(addr) as ch:
|
|
110
|
-
stub = gw.UnsGatewayStub(ch)
|
|
111
|
-
i = 0
|
|
112
|
-
try:
|
|
113
|
-
while True:
|
|
114
|
-
i += 1
|
|
115
|
-
req = pb2.PublishRequest(
|
|
116
|
-
topic=out_topic,
|
|
117
|
-
attribute=attribute,
|
|
118
|
-
data=pb2.Data(time=iso_now(), value_number=float(i % 100), uom=uom),
|
|
119
|
-
)
|
|
120
|
-
res = stub.Publish(req)
|
|
121
|
-
if not res.ok:
|
|
122
|
-
print("publish error:", res.error)
|
|
123
|
-
time.sleep(max(0.0, period_ms / 1000.0))
|
|
124
|
-
except KeyboardInterrupt:
|
|
125
|
-
pass
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
def main():
|
|
129
|
-
parser = argparse.ArgumentParser(description="Publish UNS data packets periodically (cron-like)")
|
|
130
|
-
parser.add_argument("--addr", default=None)
|
|
131
|
-
parser.add_argument("--auto", action="store_true")
|
|
132
|
-
parser.add_argument("--out-topic", default="example/")
|
|
133
|
-
parser.add_argument("--attribute", default="data-number")
|
|
134
|
-
parser.add_argument("--uom", default="mV")
|
|
135
|
-
parser.add_argument("--period-ms", type=int, default=1000)
|
|
136
|
-
args = parser.parse_args()
|
|
137
|
-
|
|
138
|
-
loop_publish(args.addr, args.out_topic, args.attribute, args.uom, args.period_ms, args.auto)
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
if __name__ == "__main__":
|
|
142
|
-
main()
|
|
@@ -1,242 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import argparse
|
|
4
|
-
import asyncio
|
|
5
|
-
import os
|
|
6
|
-
import time
|
|
7
|
-
from typing import Iterable
|
|
8
|
-
import subprocess
|
|
9
|
-
import socket
|
|
10
|
-
|
|
11
|
-
import grpc
|
|
12
|
-
|
|
13
|
-
import sys, os
|
|
14
|
-
# Ensure generated stubs (python/gen) are importable as top-level modules
|
|
15
|
-
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), 'gen')))
|
|
16
|
-
import uns_gateway_pb2 as pb2
|
|
17
|
-
import uns_gateway_pb2_grpc as gw
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def make_channel(addr: str) -> grpc.Channel:
|
|
21
|
-
# addr: e.g. unix:/tmp/xyz.sock or host:port
|
|
22
|
-
if addr.startswith("unix:"):
|
|
23
|
-
return grpc.insecure_channel(addr)
|
|
24
|
-
return grpc.insecure_channel(addr)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
import atexit
|
|
28
|
-
import signal
|
|
29
|
-
|
|
30
|
-
AUTO_GATEWAY_PROC = None
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def _cleanup_gateway():
|
|
34
|
-
global AUTO_GATEWAY_PROC
|
|
35
|
-
if AUTO_GATEWAY_PROC is None:
|
|
36
|
-
return
|
|
37
|
-
try:
|
|
38
|
-
if AUTO_GATEWAY_PROC.poll() is None:
|
|
39
|
-
if os.name == "nt":
|
|
40
|
-
AUTO_GATEWAY_PROC.terminate()
|
|
41
|
-
else:
|
|
42
|
-
os.killpg(os.getpgid(AUTO_GATEWAY_PROC.pid), signal.SIGTERM)
|
|
43
|
-
try:
|
|
44
|
-
AUTO_GATEWAY_PROC.wait(timeout=3)
|
|
45
|
-
except Exception:
|
|
46
|
-
if os.name == "nt":
|
|
47
|
-
AUTO_GATEWAY_PROC.kill()
|
|
48
|
-
else:
|
|
49
|
-
os.killpg(os.getpgid(AUTO_GATEWAY_PROC.pid), signal.SIGKILL)
|
|
50
|
-
except Exception:
|
|
51
|
-
pass
|
|
52
|
-
AUTO_GATEWAY_PROC = None
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def _default_addr() -> str:
|
|
56
|
-
if os.name == "nt":
|
|
57
|
-
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
58
|
-
s.bind(("127.0.0.1", 0))
|
|
59
|
-
port = s.getsockname()[1]
|
|
60
|
-
return f"127.0.0.1:{port}"
|
|
61
|
-
else:
|
|
62
|
-
script = os.path.basename(sys.argv[0]).replace(".py", "")
|
|
63
|
-
return f"unix:/tmp/uns-gateway-{script}-{os.getpid()}.sock"
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def ensure_gateway_running(addr: str | None, *, auto: bool, timeout_s: int = 20) -> str:
|
|
67
|
-
if not addr:
|
|
68
|
-
addr = _default_addr()
|
|
69
|
-
ch = make_channel(addr)
|
|
70
|
-
try:
|
|
71
|
-
grpc.channel_ready_future(ch).result(timeout=2)
|
|
72
|
-
ch.close()
|
|
73
|
-
return addr
|
|
74
|
-
except Exception:
|
|
75
|
-
ch.close()
|
|
76
|
-
if not auto:
|
|
77
|
-
return addr
|
|
78
|
-
# Try to spawn Node gateway directly (avoid npm wrapping) with fixed address
|
|
79
|
-
repo_root = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
|
80
|
-
cli_path = os.path.join(repo_root, "dist", "uns-grpc", "uns-gateway-cli")
|
|
81
|
-
creationflags = 0
|
|
82
|
-
popen_kwargs = {}
|
|
83
|
-
if os.name != "nt":
|
|
84
|
-
popen_kwargs["preexec_fn"] = os.setsid # new process group
|
|
85
|
-
else:
|
|
86
|
-
creationflags = getattr(subprocess, "CREATE_NEW_PROCESS_GROUP", 0)
|
|
87
|
-
# unique suffix for instances, avoid handover by default in per-script mode
|
|
88
|
-
suffix = f"py-{os.path.basename(sys.argv[0]).replace('.py','')}-{os.getpid()}"
|
|
89
|
-
proc = subprocess.Popen(["node", cli_path, "--addr", addr, "--instanceSuffix", suffix, "--instanceMode", "force"], cwd=repo_root, creationflags=creationflags, **popen_kwargs)
|
|
90
|
-
# remember and register cleanup
|
|
91
|
-
global AUTO_GATEWAY_PROC
|
|
92
|
-
AUTO_GATEWAY_PROC = proc
|
|
93
|
-
atexit.register(_cleanup_gateway)
|
|
94
|
-
|
|
95
|
-
# Wait until channel is ready
|
|
96
|
-
start = time.time()
|
|
97
|
-
while time.time() - start < timeout_s:
|
|
98
|
-
ch2 = make_channel(addr)
|
|
99
|
-
try:
|
|
100
|
-
grpc.channel_ready_future(ch2).result(timeout=2)
|
|
101
|
-
ch2.close()
|
|
102
|
-
# Optional handover wait for active publisher/subscriber (default 11s)
|
|
103
|
-
try:
|
|
104
|
-
wait_s = int(os.environ.get("UNS_GATEWAY_HANDOVER_WAIT", "11"))
|
|
105
|
-
except Exception:
|
|
106
|
-
wait_s = 11
|
|
107
|
-
if wait_s > 0:
|
|
108
|
-
time.sleep(wait_s)
|
|
109
|
-
return addr
|
|
110
|
-
except Exception:
|
|
111
|
-
ch2.close()
|
|
112
|
-
time.sleep(0.5)
|
|
113
|
-
raise RuntimeError("Gateway did not become ready in time")
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
def publish_data(addr: str, *, topic: str, attribute: str, time_iso: str, value_number: float, uom: str = "", data_group: str = "", value_is_cumulative: bool = False):
|
|
117
|
-
with make_channel(addr) as ch:
|
|
118
|
-
stub = gw.UnsGatewayStub(ch)
|
|
119
|
-
req = pb2.PublishRequest(
|
|
120
|
-
topic=topic,
|
|
121
|
-
attribute=attribute,
|
|
122
|
-
data=pb2.Data(time=time_iso, value_number=value_number, uom=uom, data_group=data_group),
|
|
123
|
-
value_is_cumulative=value_is_cumulative,
|
|
124
|
-
)
|
|
125
|
-
res = stub.Publish(req)
|
|
126
|
-
if not res.ok:
|
|
127
|
-
raise RuntimeError(res.error)
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
def subscribe(addr: str, topics: Iterable[str]):
|
|
131
|
-
with make_channel(addr) as ch:
|
|
132
|
-
stub = gw.UnsGatewayStub(ch)
|
|
133
|
-
# Ensure subscriber readiness if gateway supports it
|
|
134
|
-
try:
|
|
135
|
-
stub.Ready(pb2.ReadyRequest(timeout_ms=15000, wait_input=True))
|
|
136
|
-
except Exception:
|
|
137
|
-
pass
|
|
138
|
-
stream = stub.Subscribe(pb2.SubscribeRequest(topics=list(topics)))
|
|
139
|
-
try:
|
|
140
|
-
for msg in stream:
|
|
141
|
-
print(f"{msg.topic}: {msg.payload}")
|
|
142
|
-
except KeyboardInterrupt:
|
|
143
|
-
pass
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
def main():
|
|
147
|
-
parser = argparse.ArgumentParser()
|
|
148
|
-
parser.add_argument("--addr", default=None, help="Gateway address, e.g. unix:/tmp/uns-gateway.sock or 127.0.0.1:50051 (defaults to unique per-script)")
|
|
149
|
-
sub = parser.add_subparsers(dest="cmd", required=True)
|
|
150
|
-
|
|
151
|
-
p_pub = sub.add_parser("pub")
|
|
152
|
-
p_pub.add_argument("topic")
|
|
153
|
-
p_pub.add_argument("attribute")
|
|
154
|
-
p_pub.add_argument("time_iso")
|
|
155
|
-
p_pub.add_argument("value", type=float)
|
|
156
|
-
p_pub.add_argument("--uom", default="")
|
|
157
|
-
p_pub.add_argument("--group", default="")
|
|
158
|
-
p_pub.add_argument("--cumulative", action="store_true")
|
|
159
|
-
p_pub.add_argument("--auto", action="store_true", help="auto start gateway if not running")
|
|
160
|
-
|
|
161
|
-
p_sub = sub.add_parser("sub")
|
|
162
|
-
p_sub.add_argument("topics", nargs="+")
|
|
163
|
-
p_sub.add_argument("--auto", action="store_true", help="auto start gateway if not running")
|
|
164
|
-
|
|
165
|
-
p_reg = sub.add_parser("regapi")
|
|
166
|
-
p_reg.add_argument("topic")
|
|
167
|
-
p_reg.add_argument("attribute")
|
|
168
|
-
p_reg.add_argument("--desc", default="")
|
|
169
|
-
p_reg.add_argument("--tag", action="append", default=[])
|
|
170
|
-
p_reg.add_argument("--param", action="append", default=[], help="query param as name:type:required:desc (e.g., filter:string:true:Filter za podatke)")
|
|
171
|
-
p_reg.add_argument("--auto", action="store_true", help="auto start gateway if not running")
|
|
172
|
-
|
|
173
|
-
p_unreg = sub.add_parser("unregapi")
|
|
174
|
-
p_unreg.add_argument("topic")
|
|
175
|
-
p_unreg.add_argument("attribute")
|
|
176
|
-
p_unreg.add_argument("--auto", action="store_true", help="auto start gateway if not running")
|
|
177
|
-
|
|
178
|
-
p_stream = sub.add_parser("apistream")
|
|
179
|
-
p_stream.add_argument("--echo", action="store_true", help="auto 200 OK echo response")
|
|
180
|
-
p_stream.add_argument("--auto", action="store_true", help="auto start gateway if not running")
|
|
181
|
-
|
|
182
|
-
args = parser.parse_args()
|
|
183
|
-
if args.cmd == "pub":
|
|
184
|
-
addr = ensure_gateway_running(args.addr, auto=args.auto)
|
|
185
|
-
# Ensure publisher readiness
|
|
186
|
-
try:
|
|
187
|
-
with make_channel(addr) as ch:
|
|
188
|
-
gw.UnsGatewayStub(ch).Ready(pb2.ReadyRequest(timeout_ms=15000, wait_output=True))
|
|
189
|
-
except Exception:
|
|
190
|
-
pass
|
|
191
|
-
publish_data(addr, topic=args.topic, attribute=args.attribute, time_iso=args.time_iso, value_number=args.value, uom=args.uom, data_group=args.group, value_is_cumulative=args.cumulative)
|
|
192
|
-
elif args.cmd == "sub":
|
|
193
|
-
addr = ensure_gateway_running(args.addr, auto=args.auto)
|
|
194
|
-
subscribe(addr, args.topics)
|
|
195
|
-
elif args.cmd == "regapi":
|
|
196
|
-
addr = ensure_gateway_running(args.addr, auto=args.auto)
|
|
197
|
-
with make_channel(addr) as ch:
|
|
198
|
-
stub = gw.UnsGatewayStub(ch)
|
|
199
|
-
params = []
|
|
200
|
-
for s in args.param:
|
|
201
|
-
try:
|
|
202
|
-
name, typ, req, desc = s.split(":", 3)
|
|
203
|
-
except ValueError:
|
|
204
|
-
name, typ = s.split(":", 1)
|
|
205
|
-
req, desc = "false", ""
|
|
206
|
-
params.append(pb2.ApiQueryParam(name=name, type=typ, required=(req.lower() in ("true","1")), description=desc))
|
|
207
|
-
req = pb2.RegisterApiGetRequest(topic=args.topic, attribute=args.attribute, api_description=args.desc, tags=args.tag, query_params=params)
|
|
208
|
-
res = stub.RegisterApiGet(req)
|
|
209
|
-
print("registered" if res.ok else f"error: {res.error}")
|
|
210
|
-
elif args.cmd == "unregapi":
|
|
211
|
-
addr = ensure_gateway_running(args.addr, auto=args.auto)
|
|
212
|
-
with make_channel(addr) as ch:
|
|
213
|
-
stub = gw.UnsGatewayStub(ch)
|
|
214
|
-
res = stub.UnregisterApiGet(pb2.UnregisterApiGetRequest(topic=args.topic, attribute=args.attribute))
|
|
215
|
-
print("unregistered" if res.ok else f"error: {res.error}")
|
|
216
|
-
elif args.cmd == "apistream":
|
|
217
|
-
addr = ensure_gateway_running(args.addr, auto=args.auto)
|
|
218
|
-
import queue
|
|
219
|
-
import threading
|
|
220
|
-
with make_channel(addr) as ch:
|
|
221
|
-
stub = gw.UnsGatewayStub(ch)
|
|
222
|
-
q: "queue.Queue[pb2.ApiEventResponse|None]" = queue.Queue()
|
|
223
|
-
|
|
224
|
-
def req_iter():
|
|
225
|
-
while True:
|
|
226
|
-
item = q.get()
|
|
227
|
-
if item is None:
|
|
228
|
-
break
|
|
229
|
-
yield item
|
|
230
|
-
|
|
231
|
-
stream = stub.ApiEventStream(req_iter())
|
|
232
|
-
try:
|
|
233
|
-
for ev in stream:
|
|
234
|
-
print(f"API {ev.method} {ev.path} query={dict(ev.query)}")
|
|
235
|
-
if args.echo:
|
|
236
|
-
q.put(pb2.ApiEventResponse(id=ev.id, status=200, body="OK"))
|
|
237
|
-
except KeyboardInterrupt:
|
|
238
|
-
q.put(None)
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
if __name__ == "__main__":
|
|
242
|
-
main()
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
This folder is reserved for your project-specific Python code.
|
|
2
|
-
|
|
3
|
-
The update tools will NOT overwrite `python/local/**` (and also preserve legacy `python/rtt/**`, `python/venv`, `python/.venv`, and `python/__pycache__`).
|
|
4
|
-
|
|
5
|
-
Suggested layout:
|
|
6
|
-
- `python/local/` your packages, modules, or CLI scripts
|
|
7
|
-
- Reference the gateway client in `python/gateway_client.py` if you interact with the UNS gateway from Python
|
|
8
|
-
|