fix: OpenRemote PUT 403/409, MQTTv5 callback, geojson-proxy API REST

- simulator.py: Fix MQTTv5 callback crash (5th arg *args)
- simulator.py: Fix _or_put() - GET version+realm before PUT, inject version in payload
- simulator.py: Fix token TTL (min 30s cache)
- simulator.py: Round-robin OR updates (~5 assets/iteration instead of 60)
- geojson-proxy: Rewrite using REST API instead of psycopg2 (PG auth issue)
- geojson-proxy: Add sensorType + attributes in properties for map styling
- docker-compose.yml: Add openremote_default network + DB vars for proxy
- docker-compose.yml: Add OR_REALM=master for geojson-proxy

Resolves: OpenRemote 403 (wrong realm in payload), 409 (missing version),
MQTTv5 callback crash, geojson-proxy DB connection failure
This commit is contained in:
Eric FELIXINE
2026-05-18 10:04:12 -04:00
parent 7937e2bb43
commit 47746b584c
3 changed files with 125 additions and 76 deletions

View File

@@ -506,8 +506,8 @@ class MultiMQTT:
c.tls_insecure_set(True)
if ws:
c.ws_set(b"/mqtt")
c.on_connect = lambda _c, _, __, rc: self._on_connect(name, rc)
c.on_disconnect = lambda _c, _, __: self._on_disconnect(name)
c.on_connect = lambda _c, _, __, rc, *args: self._on_connect(name, rc)
c.on_disconnect = lambda _c, _, __, *args: self._on_disconnect(name)
try:
c.connect(host, port, keepalive=120)
c.loop_start()
@@ -791,21 +791,41 @@ def _get_or_token() -> str:
with urllib.request.urlopen(req, timeout=5) as r:
token_data = json.loads(r.read().decode())
_or_token_cache["token"] = token_data["access_token"]
_or_token_cache["expires"] = time.time() + token_data.get("expires_in", 300) - 60
_or_token_cache["expires"] = time.time() + max(token_data.get("expires_in", 300) - 60, 30)
return _or_token_cache["token"]
except Exception as e:
print(f" ⚠️ OpenRemote token → {e}")
return ""
def _or_put(asset_id: str, payload: dict) -> bool:
"""PUT update sur un asset OpenRemote (sans If-Match pour éviter 403)."""
"""PUT update sur un asset OpenRemote (avec version pour éviter 409 Conflict).
Always uses OR_REALM (master) for the API URL, which has cross-realm access.
The payload realm is corrected to match the asset's actual realm."""
token = _get_or_token()
if not token:
return False
try:
# GET current asset to obtain version and actual realm
get_url = f"{OR_URL}/api/{OR_REALM}/asset/{asset_id}"
get_req = urllib.request.Request(get_url, headers={"Authorization": f"Bearer {token}"})
version = None
try:
with urllib.request.urlopen(get_req, timeout=5) as get_resp:
current = json.loads(get_resp.read().decode())
version = current.get("version")
# Correct the payload realm to match the asset's actual realm
payload["realm"] = current.get("realm", OR_REALM)
except Exception:
pass
# Inject version into payload to avoid 409 Conflict
if version is not None:
payload["version"] = version
body = json.dumps(payload).encode()
url = f"{OR_URL}/api/{OR_REALM}/asset/{asset_id}"
req = urllib.request.Request(url, data=body,
# Always use OR_REALM for PUT URL (master has cross-realm access)
put_url = f"{OR_URL}/api/{OR_REALM}/asset/{asset_id}"
req = urllib.request.Request(put_url, data=body,
headers={
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
@@ -1150,10 +1170,14 @@ def main():
if isinstance(lo, (int, float)):
or_values[field] = round(random.uniform(lo, hi), 1)
# --- OpenRemote REST ---
# --- OpenRemote REST (round-robin: seulement quelques assets/itération) ---
if ENABLE_OPENREMOTE:
ok_or = publish_openremote(sid, sensor, or_values)
print(f" 🏠 OpenRemote: {'' if ok_or else '⚠️ skipped'}")
# Mettre à jour seulement ~5 assets par itération pour éviter timeups
# sensor_num % 12 == iteration % 12 → chaque asset est mis à jour toutes les ~60s
if sensor_num % 12 == iteration % 12:
ok_or = publish_openremote(sid, sensor, or_values)
print(f" 🏠 OpenRemote: {'' if ok_or else '⚠️ skipped'}")
# else: skip OR update this iteration (asset updated in previous cycle)
# # --- Orion-LD --- (DÉSACTIVÉ: tout passe par les IoT-Agents MQTT)
# # if ENABLE_ORION: