Skip to content

Commit f7559c6

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

File tree

5 files changed

+152
-0
lines changed

5 files changed

+152
-0
lines changed

examples/vucm/ni-daq/Dockerfile

Lines changed: 13 additions & 0 deletions
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

Lines changed: 17 additions & 0 deletions
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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
blueprint_spec: "device/1.0"
2+
3+
display_name: ATS stack
4+
5+
communication_module:
6+
product: ENP-VIRTUAL
7+
8+
command_groups: {}
9+
10+
properties:
11+
model:
12+
display_name: Model
13+
type: string
14+
15+
alerts:
16+
nidaq_error:
17+
display_name: Data processing failed
18+
severity: error
19+
20+
telemetry:
21+
status:
22+
display_name: Status
23+
type: string
24+
enum:
25+
- ok
26+
- error
27+
28+
commands: {}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
enapter==0.9.2

examples/vucm/ni-daq/script.py

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

0 commit comments

Comments
 (0)