Skip to content

Commit 382d167

Browse files
committed
feat(vucm): add integration with ATS3 via NI-cDAQ
1 parent b17ac35 commit 382d167

File tree

11 files changed

+355
-0
lines changed

11 files changed

+355
-0
lines changed

examples/vucm/ats3/README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
## Setup
2+
3+
- Install Docker or Docker Desktop
4+
- Create Standalone Device on your site in Enapter Cloud
5+
- Generate config for your Standalone device
6+
- Copy it to `env.list` file as `ENAPTER_VUCM_BLOB` value
7+
- Verify that your LabVIEW program sends data as JSON over TCP connection. Only one message per connection is allowed.
8+
- Take number of TCP port, to which data is being sent from LabVIEW and set it to `LISTEN_TCP_PORT` variable in `env.list`.
9+
10+
## Run
11+
- Copy `*_run.sh` script to directory with `env.list`
12+
- Open Terminal in Docker Desktop. Change working directory to the same as in 1.
13+
- `./*_run.sh`
14+
15+
16+
## Development
17+
1. Run `*_build.sh`
18+
2. Notify colleagues to pull the latest image

examples/vucm/ats3/ats3.Dockerfile

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
FROM python:3.11-alpine3.22
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"]
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
FROM python:3.11-alpine3.22
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 backup.py backup.py
12+
COPY backup/* .
13+
14+
CMD [".venv/bin/python", "script.py"]

examples/vucm/ats3/backup.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import asyncio
2+
import csv
3+
import enapter
4+
import functools
5+
import glob
6+
from datetime import datetime
7+
8+
9+
def parse_timestamp(date_str, time_str):
10+
"""
11+
Combine date and time strings into a UNIX timestamp (int), or return None if parsing fails.
12+
"""
13+
if date_str and time_str:
14+
try:
15+
dt_str = f"{date_str} {time_str}"
16+
date = datetime.strptime(dt_str, "%d/%m/%Y %H:%M:%S")
17+
return int(date.timestamp())
18+
except Exception as e:
19+
print(f"Failed to parse timestamp: {e}")
20+
return None
21+
return None
22+
23+
24+
async def main():
25+
csv_files = sorted(glob.glob("*.csv"))
26+
device_factory = functools.partial(CSVBackup, csv_files=csv_files)
27+
await enapter.vucm.run(device_factory)
28+
29+
30+
class CSVBackup(enapter.vucm.Device):
31+
def __init__(self, csv_files, **kwargs):
32+
super().__init__(**kwargs)
33+
self.csv_files = csv_files
34+
35+
async def task_send_csv_telemetry(self):
36+
"""
37+
Read CSV file line by line, send each row as telemetry every second.
38+
"""
39+
while True:
40+
for f in self.csv_files:
41+
try:
42+
with open(f, newline="") as csv_file:
43+
reader = csv.DictReader(csv_file)
44+
headers = reader.fieldnames or []
45+
for row in reader:
46+
telemetry = {}
47+
telemetry["status"] = "ok"
48+
for key in headers:
49+
if key in ("Date", "Time"):
50+
continue
51+
value = row.get(key)
52+
telemetry[key] = value if value != "" else None
53+
await self.log.info(f" {key}: {telemetry[key]}")
54+
telemetry["timestamp"] = parse_timestamp(
55+
row.get("Date"), row.get("Time")
56+
)
57+
await self.send_telemetry(telemetry)
58+
await asyncio.sleep(1)
59+
except Exception as e:
60+
await self.log.error(f"Failed to read CSV: {e}")
61+
await asyncio.sleep(5)
62+
63+
64+
if __name__ == "__main__":
65+
asyncio.run(main())

examples/vucm/ats3/backup_build.sh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#!/bin/bash
2+
3+
set -euo pipefail
4+
IFS=$'\n\t'
5+
6+
IMAGE_TAG="tyenap/ats3-backup:latest"
7+
8+
docker build --file backup.Dockerfile --tag "$IMAGE_TAG"
9+
10+
docker push tyenap/ats3-backup:latest

examples/vucm/ats3/backup_run.sh

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/bin/bash
2+
3+
set -euo pipefail
4+
IFS=$'\n\t'
5+
6+
IMAGE_TAG="tyenap/ats3-backup:latest"
7+
8+
docker run --rm -it \
9+
--name "ats3" \
10+
--network host \
11+
--mount type=volume,dst=/app \
12+
--env-file env.list \
13+
-e ENAPTER_LOG_LEVEL="${ENAPTER_LOG_LEVEL:-info}" \
14+
"$IMAGE_TAG"

examples/vucm/ats3/docker_build.sh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/bin/bash
2+
3+
set -euo pipefail
4+
IFS=$'\n\t'
5+
6+
IMAGE_TAG="tyenap/ats3:latest"
7+
8+
docker build --file ats3.Dockerfile --tag "$IMAGE_TAG"
9+

examples/vucm/ats3/docker_run.sh

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/bin/bash
2+
3+
set -euo pipefail
4+
IFS=$'\n\t'
5+
6+
IMAGE_TAG="tyenap/standalone-devices:latest"
7+
8+
docker run --rm -it \
9+
--name "ni-daq" \
10+
--network host \
11+
--env-file env.list \
12+
-e ENAPTER_LOG_LEVEL="${ENAPTER_LOG_LEVEL:-info}" \
13+
"$IMAGE_TAG"

examples/vucm/ats3/manifest.yml

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

0 commit comments

Comments
 (0)