Skip to content

Commit 7becb8f

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

File tree

5 files changed

+152
-0
lines changed

5 files changed

+152
-0
lines changed

Diff for: 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"]

Diff for: 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"

Diff for: examples/vucm/ni-daq/manifest.yml

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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+
nidaq_error:
15+
display_name: Data processing failed
16+
severity: error
17+
18+
telemetry:
19+
status:
20+
display_name: Status
21+
type: string
22+
enum:
23+
- ok
24+
- error
25+
26+
commands: {}

Diff for: examples/vucm/ni-daq/requirements.txt

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

Diff for: examples/vucm/ni-daq/script.py

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

0 commit comments

Comments
 (0)