I know what some of the people think about AI but, i had AI summarize what i did to get this running. i hope this helps
Title:
UNO Q App Lab + Modbus TCP + qModMaster working example for reading A0, A1, A2 over network
I wanted to share a working setup because I struggled quite a bit getting this running, and I did not find a complete example.
My goal was to use an Arduino UNO Q running App Lab as a Modbus TCP server so that qModMaster could read the first 3 analog inputs over the network.
What I wanted in the end was:
-
A0, A1, A2 read on the UNO Q
-
values converted to mV
-
exposed as Modbus registers
-
readable from qModMaster over Ethernet/Wi-Fi
For example:
A few important things I learned:
-
On UNO Q App Lab, I had to use Arduino_RouterBridge.h in the sketch, not Bridge.h. Arduino’s UNO Q App Lab examples use RouterBridge for the sketch/Python communication.
-
My Python app would immediately crash until I added python/requirements.txt with pymodbus==3.8.1. App Lab uses python/main.py as the entry point and python/requirements.txt for Python dependencies.
-
I also had to expose the Modbus TCP port in app.yaml using ports: [1502]. In my case app.yaml was view-only in the App Lab editor, so I had to SSH into the board and edit the file there. Arduino documents that App Lab apps live on the board and that SSH access is supported.
-
qModMaster could connect after that, but Function 03/04 initially gave illegal data address. The fix was changing the PyModbus datastore blocks to start at address 1 instead of 0, then mapping the first 3 channels there. That resolved the addressing issue in my setup.
Below is the working setup I ended with.
App structure
app.yaml
python/
main.py
requirements.txt
sketch/
sketch.ino
sketch.yaml
python/requirements.txt
pymodbus==3.8.1
python/main.py
import traceback
from threading import Thread, Lock
from arduino.app_utils import App, Bridge
print("main.py starting")
try:
import pymodbus
print("pymodbus version:", getattr(pymodbus, "__version__", "unknown"))
from pymodbus.server import StartTcpServer
from pymodbus.datastore import (
ModbusSequentialDataBlock,
ModbusSlaveContext,
ModbusServerContext,
)
except Exception:
print("PyModbus import failed:")
print(traceback.format_exc())
raise
MODBUS_PORT = 1502
# IMPORTANT:
# In this working setup, using address 1 fixed qModMaster "illegal data address"
# for FC03/FC04 reads.
hr_block = ModbusSequentialDataBlock(1, [0, 0, 0])
ir_block = ModbusSequentialDataBlock(1, [0, 0, 0])
store = ModbusSlaveContext(
hr=hr_block,
ir=ir_block,
)
context = ModbusServerContext(slaves=store, single=True)
lock = Lock()
def raw_to_mv(raw: int) -> int:
raw = max(0, min(16383, int(raw)))
return int(round(raw * 3300 / 16383))
def update_channel(index: int, raw_value: int):
mv = raw_to_mv(raw_value)
# index 0 -> register 1
# index 1 -> register 2
# index 2 -> register 3
reg_addr = index + 1
with lock:
hr_block.setValues(reg_addr, [mv])
ir_block.setValues(reg_addr, [mv])
print(f"A{index}: raw={raw_value} -> {mv} mV (reg {reg_addr})")
def ai0_value_callback(raw_value: int):
update_channel(0, raw_value)
def ai1_value_callback(raw_value: int):
update_channel(1, raw_value)
def ai2_value_callback(raw_value: int):
update_channel(2, raw_value)
Bridge.provide("ai0_value", ai0_value_callback)
Bridge.provide("ai1_value", ai1_value_callback)
Bridge.provide("ai2_value", ai2_value_callback)
def modbus_server_thread():
try:
print(f"Starting Modbus TCP server on 0.0.0.0:{MODBUS_PORT}")
print("FC03/FC04 registers 0..2 map to A0..A2 in mV")
StartTcpServer(
context=context,
address=("0.0.0.0", MODBUS_PORT),
)
except Exception:
print("Modbus server thread crashed:")
print(traceback.format_exc())
raise
Thread(target=modbus_server_thread, daemon=True).start()
print("Calling App.run()")
App.run()
sketch/sketch.ino
#include <Arduino_RouterBridge.h>
void setup() {
Bridge.begin();
analogReadResolution(14);
}
void loop() {
int a0 = analogRead(A0);
int a1 = analogRead(A1);
int a2 = analogRead(A2);
Bridge.call("ai0_value", a0);
Bridge.call("ai1_value", a1);
Bridge.call("ai2_value", a2);
delay(100);
}
app.yaml
In my case, App Lab showed this file as view-only in the UI, so I had to edit it on the board over SSH.
The important part was:
ports: [1502]
My file ended up looking like this:
name: Corvac Chiller Pressure
icon: 😀
ports: [1502]
bricks: []
qModMaster settings
These are the settings that worked for me:
Function 04 Read Input Registers should also work since I mirrored the values into input registers too.
Register mapping
This is how the final mapping worked:
-
Register 0 = A0 in mV
-
Register 1 = A1 in mV
-
Register 2 = A2 in mV
So if A0 is 1.513 V, qModMaster shows about:
1513
Problems I hit and fixes
Problem 1
#include <Bridge.h> failed with:
fatal error: Bridge.h: No such file or directory
Fix
Use:
#include <Arduino_RouterBridge.h>
Problem 2
Python app started for about 1 second and stopped.
Fix
Create:
python/requirements.txt
with:
pymodbus==3.8.1
Problem 3
qModMaster would not connect.
Fix
Expose the port in app.yaml:
ports: [1502]
Problem 4
qModMaster connected, but FC03/FC04 returned:
illegal data address
Fix
Change the PyModbus data blocks to start at address 1:
hr_block = ModbusSequentialDataBlock(1, [0, 0, 0])
ir_block = ModbusSequentialDataBlock(1, [0, 0, 0])
That was the piece that finally made the registers readable.
Final result
This now lets me feed 0–3.3 VDC into A0, A1, and A2 on the UNO Q and read those 3 analog inputs over the network from qModMaster as Modbus registers in mV.
Hopefully this saves someone else some time.