feat(lorawan): démarrage ChirpStack et The Things Stack

- ChirpStack opérationnel (port 8080/8090, gateway bridge UDP 1700)
- The Things Stack opérationnel (port 1885/1884, gateway UDP 1701)
- Fichages de configuration créés
- Docker-compose corrigés (réseaux smartcity-shared)
- Désactivation agentLink sur 35 assets du simulateur
- Correction _or_put: suppression If-Match header (403)
- realm smartcity identifié pour les assets du simulateur
This commit is contained in:
Eric FELIXINE
2026-05-12 17:34:53 -04:00
parent a05e13c30c
commit 5fde1a2c8d
9 changed files with 129 additions and 40 deletions

View File

@@ -0,0 +1,21 @@
# Basic Station configuration for WebSocket gateway connections
[general]
log_level=4
[integration.mqtt]
server="tcp://mosquitto:1883"
event_topic="eu868/gateway/{{ .GatewayID }}/event/{{ .EventType }}"
state_topic="eu868/gateway/{{ .GatewayID }}/state/{{ .StateType }}"
command_topic="eu868/gateway/{{ .GatewayID }}/command/#"
json=true
[backend]
type="basic_station"
[backend.basic_station]
bind=":3001"
tls_cert=""
tls_key=""
ca_cert=""
region="EU868"
frequency_min=863000000
frequency_max=870000000

View File

@@ -0,0 +1,11 @@
# ChirpStack Gateway Bridge configuration (EU868)
[general]
log_level=4
[integration.mqtt]
server="tcp://mosquitto:1883"
event_topic="eu868/gateway/{{ .GatewayID }}/event/{{ .EventType }}"
state_topic="eu868/gateway/{{ .GatewayID }}/state/{{ .StateType }}"
command_topic="eu868/gateway/{{ .GatewayID }}/command/#"
json=true
client_id="chirpstack-gateway-bridge"

View File

@@ -0,0 +1,26 @@
[logging]
level="info"
[postgresql]
dsn="postgres://chirpstack:chirpstack@postgres/chirpstack?sslmode=disable"
[redis]
url="redis://redis:6379"
[mqtt]
server="tcp://mosquitto:1883"
[network]
net_id="000000"
enabled_regions=["eu868"]
[api]
bind="0.0.0.0:8080"
secret="you-must-change-this-secret-value"
[gateway]
client_cert_lifetime="90d"
[monitoring]
# Prometheus metrics
prometheus_bind="0.0.0.0:9090"

View File

@@ -0,0 +1,16 @@
listener 1883
allow_anonymous true
persistence true
persistence_location /mosquitto/data/
log_dest file /mosquitto/log/mosquitto.log
# Bridge to EMQX for upstream integration
connection bridge-emqx
address emqx_emqx_1:1883
topic eu868/# out 1
topic application/# in 1
bridge_protocol_version mqttv311
cleansession true
try_private false
notifications false
remote_clientid chirpstack-bridge

View File

@@ -0,0 +1,2 @@
-- Initialize ChirpStack database
CREATE DATABASE IF NOT EXISTS chirpstack;

View File

@@ -0,0 +1,44 @@
is:
database:
uri: postgres://root:root@tts-postgres:5432/ttn_lorawan?sslmode=disable
email:
sender-name: "The Things Stack"
sender-address: "noreply@digitribe.fr"
network:
name: "Smart City LoRaWAN"
console-url: "https://tts.digitribe.fr/console"
identity-server-url: "https://tts.digitribe.fr/oauth"
redis:
address: tts-redis:6379
metrics:
enabled: true
console:
base-url: "https://tts.digitribe.fr/console"
http:
cookie:
block-key: "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"
hash-key: "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"
gateway-server:
mqtt:
listen: ":1883"
public-address: "tts.digitribe.fr:1883"
network-server:
net-id: "000000"
band:
name: "EU868"
join-server:
default:
join-eui-prefix: "0000000000000000"
tenant-id: "smart-city"
blob:
local-directory: /srv/ttn-lorawan/public/blob
base-url: "https://tts.digitribe.fr/blob"

View File

@@ -33,9 +33,7 @@ services:
- "traefik.http.services.chirpstack.loadbalancer.server.port=8080"
networks:
- traefik-public
- chirpstack-internal
- smartcity-shared
chirpstack-gateway-bridge:
image: chirpstack/chirpstack-gateway-bridge:4
restart: unless-stopped
@@ -50,8 +48,7 @@ services:
depends_on:
- mosquitto
networks:
- chirpstack-internal
- smartcity-shared
chirpstack-gateway-bridge-basicstation:
image: chirpstack/chirpstack-gateway-bridge:4
restart: unless-stopped
@@ -68,8 +65,7 @@ services:
- mosquitto
networks:
- traefik-public
- chirpstack-internal
- smartcity-shared
chirpstack-rest-api:
image: chirpstack/chirpstack-rest-api:4
restart: unless-stopped
@@ -84,8 +80,7 @@ services:
- chirpstack
networks:
- traefik-public
- chirpstack-internal
- smartcity-shared
postgres:
image: postgres:14-alpine
restart: unless-stopped
@@ -97,8 +92,7 @@ services:
- POSTGRES_PASSWORD=chirpstack
- POSTGRES_DB=chirpstack
networks:
- chirpstack-internal
- smartcity-shared
redis:
image: redis:7-alpine
restart: unless-stopped
@@ -106,8 +100,7 @@ services:
volumes:
- chirpstack-redisdata:/data
networks:
- chirpstack-internal
- smartcity-shared
mosquitto:
image: eclipse-mosquitto:2
restart: unless-stopped
@@ -116,9 +109,7 @@ services:
- chirpstack-mosquitto-data:/mosquitto/data
- chirpstack-mosquitto-log:/mosquitto/log
networks:
- chirpstack-internal
- smartcity-shared
volumes:
chirpstack-postgresqldata:
chirpstack-redisdata:
@@ -130,5 +121,3 @@ networks:
external: true
smartcity-shared:
external: true
chirpstack-internal:
driver: bridge

View File

@@ -20,8 +20,7 @@ services:
volumes:
- tts-postgres-data:/var/lib/postgresql/data
networks:
- tts-internal
- smartcity-shared
tts-redis:
image: redis:7
command: redis-server --appendonly yes
@@ -29,8 +28,7 @@ services:
volumes:
- tts-redis-data:/data
networks:
- tts-internal
- smartcity-shared
tts-stack:
image: thethingsnetwork/lorawan-stack:latest
entrypoint: ttn-lw-stack -c /config/ttn-lw-stack-docker.yml
@@ -47,7 +45,7 @@ services:
TTN_LW_REDIS_ADDRESS: tts-redis:6379
TTN_LW_IS_DATABASE_URI: postgres://root:***@tts-postgres:5432/ttn_lorawan?sslmode=disable
ports:
- "1700:1700/udp"
- "1701:1700/udp" # ChirpStack uses 1700
labels:
- "traefik.enable=true"
# Console web
@@ -62,9 +60,7 @@ services:
- "traefik.http.services.tts-api.loadbalancer.server.port=1884"
networks:
- traefik-public
- tts-internal
- smartcity-shared
volumes:
tts-postgres-data:
tts-redis-data:
@@ -74,5 +70,3 @@ networks:
external: true
smartcity-shared:
external: true
tts-internal:
driver: bridge

View File

@@ -798,31 +798,17 @@ def _get_or_token() -> str:
return ""
def _or_put(asset_id: str, payload: dict) -> bool:
"""PUT update sur un asset OpenRemote (avec version)."""
"""PUT update sur un asset OpenRemote (sans If-Match pour éviter 403)."""
token = _get_or_token()
if not token:
return False
try:
# Récupérer la version actuelle de l'asset
get_url = f"{OR_URL}/api/{OR_REALM}/asset/{asset_id}"
get_req = urllib.request.Request(get_url, headers={"Authorization": f"Bearer {token}"})
version = 1
try:
with urllib.request.urlopen(get_req, timeout=5) as resp:
asset_data = json.loads(resp.read().decode())
version = asset_data.get("version", 1)
except:
pass # Si GET échoue, utiliser version=1
# Ajouter la version au payload
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,
headers={
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
"If-Match": str(version),
},
method="PUT")
with http_request_duration.labels(broker="openremote", method="PUT").time():