Skip to content

Commit 8b7a7d3

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

File tree

5 files changed

+227
-0
lines changed

5 files changed

+227
-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

+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
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+
- no_data
25+
T1:
26+
display_name: T1
27+
type: float
28+
T2:
29+
display_name: T2
30+
type: float
31+
T3:
32+
display_name: T2
33+
type: float
34+
Current:
35+
display_name: Current
36+
type: float
37+
PSU:
38+
display_name: Current
39+
type: float
40+
P1:
41+
display_name: P1
42+
type: float
43+
P2:
44+
display_name: P2
45+
type: float
46+
P3:
47+
display_name: P3
48+
type: float
49+
Flow:
50+
display_name: Flow
51+
type: float
52+
Conductivity:
53+
display_name: Conductivity
54+
type: float
55+
MFMH2:
56+
display_name: MFMH2
57+
type: float
58+
Theoretical_h2:
59+
display_name: MFMH2
60+
type: float
61+
MCM02:
62+
display_name: MCM02
63+
type: float
64+
Refilling:
65+
display_name: Refilling
66+
type: float
67+
PC:
68+
display_name: PC
69+
type: float
70+
C1:
71+
display_name: Cell 1
72+
type: float
73+
C2:
74+
display_name: Cell 2
75+
type: float
76+
C3:
77+
display_name: Cell 3
78+
type: float
79+
C4:
80+
display_name: Cell 4
81+
type: float
82+
C5:
83+
display_name: Cell 5
84+
type: float
85+
C6:
86+
display_name: Cell 6
87+
type: float
88+
C7:
89+
display_name: Cell 7
90+
type: float
91+
C8:
92+
display_name: Cell 8
93+
type: float
94+
C9:
95+
display_name: Cell 9
96+
type: float
97+
C10:
98+
display_name: Cell 10
99+
type: float
100+
101+
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

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

0 commit comments

Comments
 (0)