Skip to content

Commit 96fc2f8

Browse files
committed
http proxy for meshtastic models
1 parent 4e66ec7 commit 96fc2f8

File tree

7 files changed

+158
-40
lines changed

7 files changed

+158
-40
lines changed

.vscode/launch.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@
1010
"request": "launch",
1111
"module": "bridger.bot"
1212
},
13+
{
14+
"name": "HTTP Server",
15+
"type": "debugpy",
16+
"request": "launch",
17+
"module": "bridger.http"
18+
},
1319
{
1420
"name": "Bridger",
1521
"type": "debugpy",

bridger/http.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import os
2+
3+
from aiohttp import ClientSession, web
4+
5+
from bridger.meshtastic import DeviceModel
6+
7+
VERSION = os.getenv("VERSION", "development")
8+
9+
app = web.Application()
10+
routes = web.RouteTableDef()
11+
device = None
12+
session = None
13+
14+
15+
async def on_startup(app):
16+
global device, session
17+
session = ClientSession()
18+
device = DeviceModel(session=session)
19+
20+
21+
async def on_cleanup(app):
22+
await session.close()
23+
24+
25+
@routes.get("/")
26+
async def index_view(request):
27+
# Get list of routes and their methods
28+
routes_list = [(route.method, route.resource.canonical) for route in app.router.routes()]
29+
# Create HTML response
30+
html_response = "<h1>Bridger API</h1><ul>"
31+
for method, path in routes_list:
32+
html_response += f"<li>{method} {path}</li>"
33+
html_response += "</ul>"
34+
html_response += f"<p>Version: {VERSION}</p>"
35+
36+
return web.Response(text=html_response, content_type="text/html")
37+
38+
39+
@routes.get("/model/displaynames/{model_id}")
40+
async def get_displaynames(request):
41+
model_id = int(request.match_info["model_id"])
42+
displaynames = await device.get_displaynames(model_id)
43+
return web.json_response(displaynames)
44+
45+
46+
@routes.get("/model/displaynames")
47+
async def get_displaynames_all(request):
48+
displaynames = await device.get_all_displaynames()
49+
return web.json_response(displaynames)
50+
51+
52+
app.on_startup.append(on_startup)
53+
app.on_cleanup.append(on_cleanup)
54+
app.add_routes(routes)
55+
56+
if __name__ == "__main__":
57+
web.run_app(app)

bridger/meshtastic/__init__.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from collections import defaultdict
2+
3+
from aiocache import cached
4+
from aiohttp import ClientSession
5+
6+
BASE_URL = "https://api.meshtastic.org"
7+
8+
9+
class DeviceModel:
10+
device_hardware_path = "/device/hardware"
11+
12+
def __init__(self, session: ClientSession = None):
13+
self.session = session
14+
15+
@cached(ttl=3600) # Cache the response for 1 hour
16+
async def make_request(self) -> list:
17+
async with self.session.get(BASE_URL + self.device_hardware_path) as response:
18+
return await response.json()
19+
20+
async def get_models(self, model_id: int = None) -> list:
21+
response = await self.make_request()
22+
return [model for model in response if model["hwModel"] == model_id]
23+
24+
async def get_displaynames(self, model_id: int) -> list:
25+
models = await self.get_models(model_id)
26+
return [model["displayName"] for model in models]
27+
28+
async def get_all_displaynames(self, names_as_list=False) -> list:
29+
models_dict = defaultdict(list)
30+
response = await self.make_request()
31+
for model in response:
32+
models_dict[int(model["hwModel"])].append(model["displayName"])
33+
34+
if names_as_list:
35+
result = [{"hw_model": hwModel, "names": names} for hwModel, names in models_dict.items()]
36+
else:
37+
result = [{"hw_model": hwModel, "names": ", ".join(names)} for hwModel, names in models_dict.items()]
38+
39+
return result

config/deploy.sh

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ mkdir -p "$UNIT_LOCATION" "$CONFIG_LOCATION"
1313
cp -r config/loki/* "$CONFIG_LOCATION"
1414
cp -r $QUADLET_LOCATION/* "$UNIT_LOCATION"
1515

16-
envsubst < "$QUADLET_LOCATION/bridger.container" > "$UNIT_LOCATION/bridger.container"
17-
envsubst < "$QUADLET_LOCATION/bot.container" > "$UNIT_LOCATION/bot.container"
16+
for service in bridger bot http; do
17+
envsubst < "$QUADLET_LOCATION/$service.container" > "$UNIT_LOCATION/$service.container"
18+
done
1819

1920
systemctl --user daemon-reload
2021
systemctl --user reload-or-restart "${SERVICES[@]}"

config/quadlet/http.container

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[Unit]
2+
StartLimitInterval = 200
3+
StartLimitBurst = 5
4+
5+
[Container]
6+
Image = ${IMAGE}
7+
Exec = python -m bridger.http
8+
Environment = INFLUXDB_V2_URL=http://influxdb:8086
9+
Environment = MQTT_BROKER=emqx
10+
Environment = LOG_PATH=/var/lib/bridger/logs/http.log
11+
EnvironmentFile = /home/andy/.config/bridger.env
12+
Network = bridger.network
13+
HostName = http
14+
Volume = bridger-data.volume:/var/lib/bridger
15+
16+
[Install]
17+
WantedBy = default.target
18+
19+
[Service]
20+
Restart = always
21+
RestartSec = 30

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ authors = [
1717
]
1818

1919
dependencies = [
20+
"aiohttp",
21+
"aiocache",
2022
"arrow",
2123
"cryptography",
2224
"dataclasses-json",

requirements.txt

Lines changed: 30 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -4,39 +4,41 @@
44
#
55
# pip-compile --strip-extras
66
#
7-
aiohappyeyeballs==2.4.3
7+
aiocache==0.12.3
8+
# via bridger (pyproject.toml)
9+
aiohappyeyeballs==2.4.4
810
# via aiohttp
9-
aiohttp==3.10.10
10-
# via discord-py
11-
aiosignal==1.3.1
11+
aiohttp==3.11.11
12+
# via
13+
# bridger (pyproject.toml)
14+
# discord-py
15+
aiosignal==1.3.2
1216
# via aiohttp
1317
arrow==1.3.0
1418
# via bridger (pyproject.toml)
15-
attrs==24.2.0
19+
attrs==25.1.0
1620
# via aiohttp
1721
bleak==0.22.3
1822
# via meshtastic
19-
certifi==2024.8.30
23+
certifi==2025.1.31
2024
# via
2125
# influxdb-client
2226
# requests
2327
# sentry-sdk
2428
cffi==1.17.1
2529
# via cryptography
26-
charset-normalizer==3.4.0
30+
charset-normalizer==3.4.1
2731
# via requests
28-
coverage==7.6.4
32+
coverage==7.6.10
2933
# via pytest-cov
30-
cryptography==43.0.3
34+
cryptography==44.0.0
3135
# via bridger (pyproject.toml)
3236
dataclasses-json==0.6.7
3337
# via bridger (pyproject.toml)
34-
dbus-fast==2.24.3
38+
dbus-fast==2.31.0
3539
# via bleak
3640
discord-py==2.4.0
3741
# via bridger (pyproject.toml)
38-
dotmap==1.3.30
39-
# via meshtastic
4042
frozenlist==1.5.0
4143
# via
4244
# aiohttp
@@ -45,54 +47,46 @@ idna==3.10
4547
# via
4648
# requests
4749
# yarl
48-
influxdb-client==1.47.0
50+
influxdb-client==1.48.0
4951
# via bridger (pyproject.toml)
5052
iniconfig==2.0.0
5153
# via pytest
52-
loguru==0.7.2
54+
loguru==0.7.3
5355
# via bridger (pyproject.toml)
54-
marshmallow==3.23.1
56+
marshmallow==3.26.0
5557
# via dataclasses-json
56-
meshtastic==2.5.4
58+
meshtastic==2.5.11
5759
# via bridger (pyproject.toml)
5860
multidict==6.1.0
5961
# via
6062
# aiohttp
6163
# yarl
6264
mypy-extensions==1.0.0
6365
# via typing-inspect
64-
packaging==24.1
66+
packaging==24.2
6567
# via
6668
# marshmallow
6769
# meshtastic
6870
# pytest
6971
paho-mqtt==2.1.0
7072
# via bridger (pyproject.toml)
71-
pexpect==4.9.0
72-
# via meshtastic
7373
pluggy==1.5.0
7474
# via pytest
75-
print-color==0.4.6
76-
# via meshtastic
77-
propcache==0.2.0
78-
# via yarl
79-
protobuf==5.28.3
75+
propcache==0.2.1
76+
# via
77+
# aiohttp
78+
# yarl
79+
protobuf==5.29.3
8080
# via
8181
# bridger (pyproject.toml)
8282
# meshtastic
83-
ptyprocess==0.7.0
84-
# via pexpect
8583
pycparser==2.22
8684
# via cffi
87-
pyparsing==3.2.0
88-
# via meshtastic
8985
pypubsub==4.0.3
9086
# via meshtastic
91-
pyqrcode==1.2.1
92-
# via meshtastic
9387
pyserial==3.5
9488
# via meshtastic
95-
pytest==8.3.3
89+
pytest==8.3.4
9690
# via
9791
# bridger (pyproject.toml)
9892
# pytest-cov
@@ -115,28 +109,26 @@ requests==2.32.3
115109
# requests-mock
116110
requests-mock==1.12.1
117111
# via bridger (pyproject.toml)
118-
sentry-sdk==2.17.0
112+
sentry-sdk==2.20.0
119113
# via bridger (pyproject.toml)
120-
six==1.16.0
114+
six==1.17.0
121115
# via python-dateutil
122116
tabulate==0.9.0
123117
# via meshtastic
124-
types-python-dateutil==2.9.0.20241003
118+
types-python-dateutil==2.9.0.20241206
125119
# via arrow
126120
typing-extensions==4.12.2
127121
# via
128122
# reactivex
129123
# typing-inspect
130124
typing-inspect==0.9.0
131125
# via dataclasses-json
132-
urllib3==2.2.3
126+
urllib3==2.3.0
133127
# via
134128
# influxdb-client
135129
# requests
136130
# sentry-sdk
137-
webencodings==0.5.1
138-
# via meshtastic
139-
yarl==1.17.1
131+
yarl==1.18.3
140132
# via aiohttp
141133

142134
# The following packages are considered to be unsafe in a requirements file:

0 commit comments

Comments
 (0)