Skip to content

Commit d24efa6

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

File tree

11 files changed

+362
-0
lines changed

11 files changed

+362
-0
lines changed

examples/vucm/ats3/README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
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.txt` 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.txt`
12+
- Open Terminal in Docker Desktop. Change working directory to the same as in previous step.
13+
- `./*_run.sh`
14+
15+
16+
## Development
17+
1. Run `*_build.sh`
18+
2. `docker push ...`
19+
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"]

examples/vucm/ats3/backup.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 backup.py backup.py
12+
13+
CMD [".venv/bin/python", "script.py"]

examples/vucm/ats3/backup.py

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

examples/vucm/ats3/backup_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-backup:latest"
7+
SCRIPT_DIR="$(realpath "$(dirname "$0")")"
8+
9+
docker build --file backup.Dockerfile --tag "$IMAGE_TAG" "$SCRIPT_DIR"

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.txt \
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+
SCRIPT_DIR="$(realpath "$(dirname "$0")")"
8+
9+
docker build --file ats3.Dockerfile --tag "$IMAGE_TAG" "$SCRIPT_DIR"

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/ats3:latest"
7+
8+
docker run --rm -it \
9+
--name "ni-daq" \
10+
--network host \
11+
--env-file env.txt \
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: {}

examples/vucm/ats3/requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
enapter==0.9.2
2+
python-dateutil==2.8.2

0 commit comments

Comments
 (0)