Initial commit

This commit is contained in:
Jono Targett 2026-03-14 23:19:44 +10:30
commit e82ad574ab
6 changed files with 419 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
__pycache__/
venv/
*.bak

64
assethandler.py Executable file
View File

@ -0,0 +1,64 @@
#! /usr/bin/env python3
import asyncio
import uuid
import aioserial
import aiomqtt
SERIAL_PORT = "/tmp/ttyV0"
BAUD = 115200
SERIAL_PUBLISH_TOPIC = "serial/data"
SERIAL_COMMAND_TOPIC = "serial/command"
async def serial_reader(ser, client):
while True:
data = await ser.read_async(64)
if data:
await client.publish(SERIAL_PUBLISH_TOPIC, data)
# Hand back to event loop
await asyncio.sleep(0)
async def mqtt_command_writer(ser, client):
async for message in client.messages:
topic = str(message.topic)
print(message.topic, message.payload)
if str(topic) == SERIAL_COMMAND_TOPIC:
await ser.write_async(message.payload)
async def main():
client_id = f"jono-test-{uuid.uuid4()}"
interval = 5
ser = aioserial.AioSerial(
port=SERIAL_PORT,
baudrate=BAUD,
timeout=0.05, # 50 ms
)
while True:
try:
async with aiomqtt.Client(
"127.0.0.1",
port=1883,
identifier=client_id,
) as client:
await client.subscribe(SERIAL_COMMAND_TOPIC)
await asyncio.gather(
serial_reader(ser, client),
mqtt_command_writer(ser, client),
)
except aiomqtt.MqttError as e:
print(e)
print(f"Connection lost; reconnecting in {interval} seconds...")
await asyncio.sleep(interval)
if __name__ == "__main__":
asyncio.run(main())

5
requirements.txt Normal file
View File

@ -0,0 +1,5 @@
aiomqtt
aioserial
black
pyubx2
pyubxutils

5
tunnel.sh Executable file
View File

@ -0,0 +1,5 @@
#! /bin/bash
socat -d -d \
pty,raw,echo=0,link=/tmp/ttyV0 \
pty,raw,echo=0,link=/tmp/ttyV1

42
ubx.py Executable file
View File

@ -0,0 +1,42 @@
#! /usr/bin/env python3
import asyncio
import aioserial
import pyubxutils
import pyubx2
SERIAL_PORT = "/tmp/ttyV1"
BAUD = 115200
async def simulator_to_serial(stream, ser):
ubr = pyubx2.UBXReader(stream)
for raw, parsed in ubr:
await ser.write_async(raw)
async def serial_to_simulator(stream, ser):
while True:
data = await ser.read_async(1024)
if data:
stream.write(data)
async def main():
ser = aioserial.AioSerial(port=SERIAL_PORT, baudrate=BAUD)
with pyubxutils.UBXSimulator(
configfile="./ubxsimulator.json",
interval=1000,
timeout=3,
) as stream:
await asyncio.gather(
simulator_to_serial(stream, ser),
serial_to_simulator(stream, ser),
)
if __name__ == "__main__":
asyncio.run(main())

300
ubxsimulator.json Normal file
View File

@ -0,0 +1,300 @@
{
"interval": 1000,
"timeout": 3,
"logfile": "./ubxsimulator.log",
"simVector": 1,
"global": {
"lat": 52.477311,
"lon": 13.391426,
"alt": 50.034,
"height": 47494,
"sep": 2.54,
"hMSL": 50034,
"NS": "N",
"EW": "E",
"gSpeed": 100000,
"headMot": 135,
"spd": 194.3844,
"cog": 126
},
"ubxmessages": [
{
"msgCls": 1,
"msgId": 7,
"rate": 1,
"attrs": {
"validDate": 1,
"validTime": 1,
"validMag": 1,
"fixType": 3,
"numSV": 12,
"gnssFixOk": 1,
"pDOP": 0.843,
"hAcc": 585,
"vAcc": 543,
"diffSoln": 1,
"carrSoln": 1,
"lastCorrectionAge": 5
}
},
{
"msgCls": 1,
"msgId": 4,
"rate": 1,
"attrs": {
"gDOP": 0.45,
"pDOP": 0.356,
"tDOP": 0.3864,
"vDOP": 0.98733,
"hDOP": 0.96763,
"nDOP": 0.9873476,
"eDOP": 0.834
}
},
{
"msgCls": 1,
"msgId": 53,
"rate": 4,
"attrs": {
"version": 1,
"numSvs": 13,
"reserved0": 0,
"gnssId_01": 0,
"svId_01": 4,
"cno_01": 0,
"elev_01": 16,
"azim_01": 302,
"prRes_01": 0.0,
"qualityInd_01": 1,
"svUsed_01": 0,
"health_01": 1,
"diffCorr_01": 0,
"smoothed_01": 0,
"orbitSource_01": 2,
"ephAvail_01": 0,
"almAvail_01": 1,
"gnssId_02": 0,
"svId_02": 5,
"cno_02": 48,
"elev_02": 15,
"azim_02": 73,
"prRes_02": 0.0,
"qualityInd_02": 4,
"svUsed_02": 0,
"health_02": 1,
"diffCorr_02": 0,
"smoothed_02": 0,
"orbitSource_02": 2,
"ephAvail_02": 0,
"almAvail_02": 1,
"gnssId_03": 0,
"svId_03": 14,
"cno_03": 0,
"elev_03": -91,
"azim_03": 0,
"prRes_03": 0.0,
"qualityInd_03": 1,
"svUsed_03": 0,
"health_03": 1,
"diffCorr_03": 0,
"smoothed_03": 0,
"orbitSource_03": 0,
"gnssId_04": 0,
"svId_04": 18,
"cno_04": 50,
"elev_04": -91,
"azim_04": 0,
"prRes_04": 0.0,
"qualityInd_04": 4,
"svUsed_04": 0,
"health_04": 1,
"diffCorr_04": 0,
"smoothed_04": 0,
"orbitSource_04": 0,
"gnssId_05": 0,
"svId_05": 25,
"cno_05": 0,
"elev_05": 19,
"azim_05": 116,
"prRes_05": 0.0,
"qualityInd_05": 1,
"svUsed_05": 0,
"health_05": 1,
"diffCorr_05": 0,
"smoothed_05": 0,
"orbitSource_05": 1,
"ephAvail_05": 1,
"almAvail_05": 1,
"gnssId_06": 0,
"svId_06": 26,
"cno_06": 45,
"elev_06": 61,
"azim_06": 285,
"prRes_06": 3.5,
"qualityInd_06": 7,
"svUsed_06": 1,
"health_06": 1,
"diffCorr_06": 0,
"smoothed_06": 0,
"orbitSource_06": 1,
"ephAvail_06": 1,
"almAvail_06": 1,
"gnssId_07": 0,
"svId_07": 27,
"cno_07": 0,
"elev_07": 4,
"azim_07": 245,
"prRes_07": 0.0,
"qualityInd_07": 1,
"svUsed_07": 0,
"health_07": 1,
"diffCorr_07": 0,
"smoothed_07": 0,
"orbitSource_07": 2,
"ephAvail_07": 0,
"almAvail_07": 1,
"gnssId_08": 0,
"svId_08": 28,
"cno_08": 52,
"elev_08": 39,
"azim_08": 196,
"prRes_08": 2.1,
"qualityInd_08": 7,
"svUsed_08": 1,
"health_08": 1,
"diffCorr_08": 0,
"smoothed_08": 0,
"orbitSource_08": 1,
"ephAvail_08": 1,
"almAvail_08": 1,
"gnssId_09": 0,
"svId_09": 29,
"cno_09": 43,
"elev_09": 56,
"azim_09": 67,
"prRes_09": 1.8,
"qualityInd_09": 7,
"svUsed_09": 1,
"health_09": 1,
"diffCorr_09": 0,
"smoothed_09": 0,
"orbitSource_09": 1,
"ephAvail_09": 1,
"almAvail_09": 1,
"gnssId_10": 0,
"svId_10": 31,
"cno_10": 54,
"elev_10": 55,
"azim_10": 230,
"prRes_10": 0.3,
"qualityInd_10": 7,
"svUsed_10": 1,
"health_10": 1,
"diffCorr_10": 0,
"smoothed_10": 0,
"orbitSource_10": 1,
"ephAvail_10": 1,
"gnssId_11": 2,
"svId_11": 120,
"cno_11": 0,
"elev_11": 28,
"azim_11": 196,
"prRes_11": 0.0,
"qualityInd_11": 1,
"svUsed_11": 0,
"health_11": 0,
"diffCorr_11": 0,
"smoothed_11": 0,
"orbitSource_11": 7,
"gnssId_12": 2,
"svId_12": 123,
"cno_12": 0,
"elev_12": 22,
"azim_12": 140,
"prRes_12": 0.0,
"qualityInd_12": 1,
"svUsed_12": 0,
"health_12": 0,
"diffCorr_12": 0,
"smoothed_12": 0,
"orbitSource_12": 7,
"gnssId_13": 2,
"svId_13": 136,
"cno_13": 0,
"elev_13": 29,
"azim_13": 171,
"prRes_13": 0.0,
"qualityInd_13": 1,
"svUsed_13": 0,
"health_13": 0,
"diffCorr_13": 0,
"smoothed_13": 0,
"orbitSource_13": 7
}
}
],
"nmeamessages": [
{
"talker": "GN",
"msgId": "GGA",
"rate": 1,
"attrs": {
"status": "A",
"posMode": "A",
"navStatus": "V",
"sepUnit": "M",
"altUnit": "M",
"quality": 5,
"numSV": 12,
"HDOP": 0.9873476,
"diffAge": 10,
"diffStation": 2746
}
},
{
"talker": "GN",
"msgId": "RMC",
"rate": 1,
"attrs": {
"status": "A",
"posMode": "A",
"navStatus": "V"
}
},
{
"talker": "P",
"msgId": "QTMVERNO",
"rate": 1,
"attrs": {
"verstr": "LG290P03AANR01A03S",
"builddate": "2025/03/23",
"buildtime": "12:02:34"
}
},
{
"talker": "P",
"msgId": "QTMPVT",
"rate": 1,
"attrs": {
"msgver": 1,
"tow": 1000,
"date": "20221225",
"time": 83737.000,
"fixtype": 3,
"numsv": 22,
"leaps": 3,
"lat": 52.477311,
"lon": 13.391426,
"alt": 45.287,
"sep": 31.075,
"veln": 0.034,
"vele": 0.016,
"veld": 0.065,
"spd": 23.48,
"hdg": 182.12,
"hdop": 1.23,
"pdop": 1.18
}
}
]
}