Skip to content

Commit e6e43f4

Browse files
committed
feat(vucm): add integration with NI-cDAQ
1 parent bc2b57a commit e6e43f4

File tree

5 files changed

+219
-0
lines changed

5 files changed

+219
-0
lines changed

examples/vucm/ni-daq/Dockerfile

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
FROM python:3.10-alpine3.16
2+
3+
WORKDIR /app
4+
5+
RUN apk add build-base
6+
7+
RUN python -m venv .venv
8+
COPY requirements.txt requirements.txt
9+
RUN .venv/bin/pip install -r requirements.txt
10+
11+
COPY script.py script.py
12+
13+
CMD [".venv/bin/python", "script.py"]

examples/vucm/ni-daq/docker_run.sh

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#!/bin/bash
2+
3+
set -euo pipefail
4+
IFS=$'\n\t'
5+
6+
SCRIPT_DIR="$(realpath "$(dirname "$0")")"
7+
IMAGE_TAG="${IMAGE_TAG:-"enapter-vucm-examples/$(basename "$SCRIPT_DIR"):latest"}"
8+
9+
docker build --tag "$IMAGE_TAG" "$SCRIPT_DIR"
10+
11+
docker run --rm -it \
12+
--name "ni-daq" \
13+
--network host \
14+
-e ENAPTER_LOG_LEVEL="${ENAPTER_LOG_LEVEL:-info}" \
15+
-e ENAPTER_VUCM_BLOB="$ENAPTER_VUCM_BLOB" \
16+
-e LISTEN_TCP_PORT="$LISTEN_TCP_PORT" \
17+
"$IMAGE_TAG"

examples/vucm/ni-daq/manifest.yml

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
blueprint_spec: "device/1.0"
2+
3+
display_name: ATS stack
4+
5+
communication_module:
6+
product: ENP-VIRTUAL
7+
8+
properties:
9+
model:
10+
display_name: Model
11+
type: string
12+
13+
alerts:
14+
parse_error:
15+
display_name: Data processing failed
16+
severity: error
17+
telemetry:
18+
status:
19+
display_name: Status
20+
type: string
21+
enum:
22+
- ok
23+
- error
24+
T1:
25+
display_name: T1
26+
type: float
27+
T2:
28+
display_name: T2
29+
type: float
30+
T3:
31+
display_name: T2
32+
type: float
33+
Current:
34+
display_name: Current
35+
type: float
36+
PSU:
37+
display_name: Current
38+
type: float
39+
P1:
40+
display_name: P1
41+
type: float
42+
P2:
43+
display_name: P2
44+
type: float
45+
P3:
46+
display_name: P3
47+
type: float
48+
Flow:
49+
display_name: Flow
50+
type: float
51+
Conductivity:
52+
display_name: Conductivity
53+
type: float
54+
MFMH2:
55+
display_name: MFMH2
56+
type: float
57+
Theoretical_h2:
58+
display_name: MFMH2
59+
type: float
60+
MCM02:
61+
display_name: MCM02
62+
type: float
63+
Refilling:
64+
display_name: Refilling
65+
type: float
66+
PC:
67+
display_name: PC
68+
type: float
69+
C1:
70+
display_name: Cell 1
71+
type: float
72+
C2:
73+
display_name: Cell 2
74+
type: float
75+
C3:
76+
display_name: Cell 3
77+
type: float
78+
C4:
79+
display_name: Cell 4
80+
type: float
81+
C5:
82+
display_name: Cell 5
83+
type: float
84+
C6:
85+
display_name: Cell 6
86+
type: float
87+
C7:
88+
display_name: Cell 7
89+
type: float
90+
C8:
91+
display_name: Cell 8
92+
type: float
93+
C9:
94+
display_name: Cell 9
95+
type: float
96+
C10:
97+
display_name: Cell 10
98+
type: float
99+
100+
commands: {}

examples/vucm/ni-daq/requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
enapter==0.9.2

examples/vucm/ni-daq/script.py

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import asyncio
2+
import functools
3+
import json
4+
import os
5+
import socket
6+
from datetime import datetime
7+
8+
import enapter
9+
10+
def parse_json(bytes):
11+
return json.loads(bytes.decode())
12+
13+
14+
async def main():
15+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
16+
device_factory = functools.partial(
17+
NIDAQ,
18+
socket=sock,
19+
tcp_port=os.environ["LISTEN_TCP_PORT"],
20+
)
21+
await enapter.vucm.run(device_factory)
22+
23+
24+
class NIDAQ(enapter.vucm.Device):
25+
def __init__(self, socket, tcp_port, **kwargs):
26+
super().__init__(**kwargs)
27+
self.socket = socket
28+
self.tcp_port = tcp_port
29+
30+
async def task_properties_sender(self):
31+
while True:
32+
await self.send_properties(
33+
{
34+
"model": "NI cDAQ 9178",
35+
}
36+
)
37+
await asyncio.sleep(10)
38+
39+
async def task_telemetry_sender(self):
40+
server_address = ('localhost', int(self.tcp_port))
41+
self.socket.bind(server_address)
42+
self.socket.setblocking(False)
43+
self.socket.listen(1)
44+
45+
while True:
46+
try:
47+
await self.log.info('waiting for a connection')
48+
connection, client_address = await asyncio.get_event_loop().sock_accept(self.socket)
49+
50+
await self.log.info(f'connection from {client_address}')
51+
data = bytearray()
52+
53+
while True:
54+
try:
55+
received = await asyncio.get_event_loop().sock_recv(connection, 1024)
56+
if not received:
57+
await self.log.info(f'no more data from {client_address}')
58+
break
59+
data.extend(received)
60+
# await self.log.info(f'got data: {received}')
61+
except Exception as e:
62+
await self.log.error(f"Error receiving data: {e}")
63+
break
64+
65+
try:
66+
telemetry = parse_json(data)
67+
if telemetry['Date'] and telemetry['Time']:
68+
tt = telemetry['Date'] + ' ' + telemetry['Time']
69+
date = datetime.strptime(tt,'%d/%m/%Y %H:%M:%S')
70+
telemetry["timestamp"] = date.timestamp()
71+
telemetry["status"] = "ok" # TODO: define status
72+
await self.log.info(f'data to send: {telemetry}')
73+
await self.send_telemetry(telemetry)
74+
self.alerts.clear()
75+
except Exception as e:
76+
self.alerts.add("parse_error")
77+
await self.log.error(f"failed to process data: {e}")
78+
79+
except Exception as e:
80+
await self.log.error(f"Connection error: {e}")
81+
finally:
82+
connection.close()
83+
await asyncio.sleep(1)
84+
85+
86+
if __name__ == "__main__":
87+
asyncio.run(main())
88+

0 commit comments

Comments
 (0)