summaryrefslogtreecommitdiff
path: root/README.md
blob: 6ef85cc1b97f7e3ac137136de2d7373b8e44b970 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# LabDAQ — Modular Python DAQ Frontend

A production-grade, modular PyQt6 data acquisition UI supporting NI-DAQmx and Arduino backends with live strip-chart plotting, CSV logging, and per-device configuration.

---

## Quick Start

```bash
# 1. Install dependencies
pip install PyQt6 pyqtgraph numpy pyserial

# 2. Run (simulation mode — no hardware required)
python main.py

# 3. For real NI hardware
pip install nidaqmx          # also install NI-DAQmx runtime from ni.com

# 4. For real Arduino hardware
#    Upload devices/arduino_firmware/labdaq.ino to your board
#    Set port in device Configure dialog (e.g. COM3 / /dev/ttyUSB0)
```

---

## Project Structure

```
daq_system/
├── main.py                        # Entry point
├── requirements.txt
│
├── api_layers/                    # Hardware abstraction layers
│   ├── nidaqmx_layer.py           # NI-DAQmx wrapper + simulation
│   └── arduino_layer.py           # Arduino serial wrapper + simulation
│
├── devices/                       # I/O device modules (auto-discovered)
│   ├── base_device.py             # Abstract base class
│   ├── device_registry.py         # Auto-discovery & instance manager
│   ├── analog_input.py            # AI — NI or Arduino backend
│   ├── digital_io.py              # DIO — NI or Arduino backend
│   └── serial_device.py           # Generic serial / UART
│
├── core/
│   └── acquisition.py             # Threaded polling engine, buffers, CSV log
│
└── ui/
    ├── style.qss                  # Industrial dark theme
    ├── main_window.py             # Main window shell
    ├── device_panel.py            # Left sidebar — device cards
    ├── strip_chart.py             # Center — live pyqtgraph traces
    ├── readout_panel.py           # Right — numeric readouts
    ├── alarm_panel.py             # Right — alarm event log
    ├── config_dialog.py           # Per-device config dialog
    └── add_device_dialog.py       # Add device at runtime
```

---

## Backend Architecture

### Swappable API Layers

Each device module accepts a `backend` parameter:

```python
# NI-DAQmx (real hardware)
dev = AnalogInputDevice(
    device_id="ai_0",
    backend="nidaqmx",
    ni_device="Dev1",           # NI device name
    simulate=False,
)

# Arduino (real hardware)
dev = AnalogInputDevice(
    device_id="ard_0",
    backend="arduino",
    ard_port="COM3",            # or "/dev/ttyUSB0" on Linux/Mac
    ard_baud=115200,
    simulate=False,
)

# Simulation (no hardware)
dev = AnalogInputDevice(device_id="ai_sim", simulate=True)
```

You can hot-swap backends at runtime from the Configure dialog without restarting.

### NI-DAQmx Layer (`api_layers/nidaqmx_layer.py`)

- Auto-detects installed NI devices via `NidaqmxLayer.list_devices()`
- Falls back to simulation if `nidaqmx` package is not installed
- Configurable voltage range, sample rate, terminal configuration

### Arduino Layer (`api_layers/arduino_layer.py`)

- Serial protocol: `"A0:1.23,A1:4.56,D2:1\n"` (key:value CSV)
- Background read thread with latest-value cache
- Digital write: sends `"W:D13:1\n"` to Arduino
- Reference firmware included in `arduino_layer.py` as `ARDUINO_SKETCH`

---

## Adding a New Device Module

1. Create `devices/my_sensor.py`
2. Subclass `BaseDevice`
3. Implement: `connect()`, `disconnect()`, `read_channels()`, `write_channel()`, `get_config_widget()`
4. Drop the file in `devices/` — `DeviceRegistry` discovers it automatically

```python
from devices.base_device import BaseDevice, ChannelConfig, DeviceInfo, DeviceStatus

class MySensor(BaseDevice):
    def __init__(self, device_id="my_0", simulate=True):
        channels = [
            ChannelConfig(channel_id="ch0", name="Pressure", unit="Pa",
                          min_value=0, max_value=1e5, color="#00d4ff"),
        ]
        info = DeviceInfo(device_id=device_id, name="My Sensor",
                          device_type="custom", channels=channels)
        super().__init__(info)
        self.simulate = simulate

    def connect(self):
        self.status = DeviceStatus.SIMULATED if self.simulate else DeviceStatus.CONNECTED
        return True

    def disconnect(self):
        self.status = DeviceStatus.DISCONNECTED

    def read_channels(self):
        import random
        return {"ch0": random.uniform(0, 1e5)}

    def write_channel(self, channel_id, value):
        return False

    def get_config_widget(self):
        from PyQt6.QtWidgets import QLabel
        return QLabel("No configuration needed.")
```

---

## Data Logging

- Click **⬤ LOG** while acquisition is running
- CSV saved to `logs/daq_YYYYMMDD_HHMMSS.csv`
- Columns: `elapsed_s`, then one column per channel (`device_id/channel_id[unit]`)
- Stop logging with **⏹ LOGGING** — file is flushed and closed cleanly

---

## Alarm System

- Set per-channel `alarm_low` / `alarm_high` in the Configure → Channels & Alarms tab
- Alarms appear in the bottom-right panel with timestamp and direction (▲ HIGH / ▼ LOW)
- Alarm state is debounced — fires once on breach, resets when value recovers
- Channel readout highlights red while in alarm

---

## Strip Chart

- One plot row per device; all share a linked time axis
- Adjustable window (1 s → 3600 s)
- Auto or Fixed Y-scale
- Pause / Resume without stopping acquisition
- Powered by **pyqtgraph** for GPU-accelerated rendering

---

## Dependencies

| Package      | Purpose                    | Required |
|-------------|----------------------------|----------|
| PyQt6        | UI framework               | ✓        |
| pyqtgraph    | Real-time strip chart       | ✓        |
| numpy        | Array math for chart data   | ✓        |
| pyserial     | Arduino serial comms        | ✓        |
| nidaqmx      | NI-DAQmx hardware access    | Optional |