· IoT· SIM-kort· 1NCE· 4G· LTE· dataforbrug· komprimering· gateway· NB-IoT· LTE-M
IoT SIM-kort og dataplan optimering — 10-årig budget med 1NCE
IoT SIM-kort til gateway med 4G LTE: 1NCE 500MB/10-årsplan, dataforbrug per dag, MessagePack+zlib komprimering, fallback-strategi og sammenligning med standard SIM.
Af M-Bus Gateway
M-Bus Gateway bruger 1NCE IoT SIM med 500MB over 10 år. Her er designet der gør det muligt at holde dataforbruget under 200MB over gateway-levetiden.
1NCE: Hvad er det og hvad koster det?
1NCE (onence.com):
Pris: ~20 USD engangs (lifetime)
Data: 500 MB over 10 år
SMS: 250 SMS over 10 år
Gyldighed: 10 år fra aktivering
Dækning:
→ Roaming i 100+ lande (inkl. hele EU/Norden)
→ LTE-M og NB-IoT (lavstrøm)
→ Standard 4G LTE fallback
APN: iot.1nce.net (1.8V nano-SIM)
Alternativ sammenligning:
Telia IoT: ~3 kr./MB → 200 MB over 10 år = 600 kr./SIM
1NCE: ~130 kr. engangs for 500 MB → 90% billigere
Dataforbrug-budget
Daglig payload (06:00 UTC):
Rå readings (50 målere × 10 felter JSON): ~25 KB
MessagePack-komprimeret: ~8 KB
zlib-komprimeret: ~3 KB (62% reduktion)
Status heartbeat (hvert 5. min):
JSON: {"gateway_id": "GW-0001", "ts": 1716508800, "online": true}
→ 80 bytes × 288 gange/dag = 23 KB/dag ukomprimeret
OTA-opdatering (kvartalvis, 5 MB/opdatering):
→ 4 opdateringer/år = 20 MB/år
→ 200 MB over 10 år (OTA alene!)
→ Optimeret: Binære diff-patches (typisk < 500 KB/patch)
Cloudflare Tunnel (teknikeradgang):
→ On-demand, max 30 min
→ Ca. 10 sessioner/år á 50 MB = 500 MB/år → 5 GB/10 år!
→ Begræns til absolut minimum: Kritisk fejlfinding kun
Data-budget (10-årig plan)
Optimeret dataforbrug:
Daglig payload (3 KB × 365 × 10 år): 10,9 MB
Status heartbeat:
→ Kun ved forbindelsesskift (ikke konstant)
→ 2 × 80 bytes/dag × 3.650 dage: 0,6 MB
OTA-patches (diff, 300 KB × 20 patches): 6,0 MB
Remote debug-sessioner (10/år × 2 MB): 20,0 MB
MQTT overhead (headers, ACK): 10,0 MB
Buffer: 152,5 MB
--------
Total: 200,0 MB
Resterende buffer: 300 MB (60% reserve)
1NCE limit: 500 MB over 10 år → Komfortabelt
MessagePack + zlib: Komprimerings-implementering
# gateway/src/mqtt/serializer.py
import msgpack
import zlib
from datetime import datetime
def serialize_payload(readings: list[dict], gateway_id: str) -> bytes:
"""
Komprimér daglig payload til minimal størrelse.
Reducer JSON overhead med MessagePack + zlib.
"""
payload = {
"v": 1, # Version for bakwards-compat
"gw": gateway_id,
"ts": int(datetime.utcnow().timestamp()),
"r": [
{
"id": r["meter_installation_id"],
"v": r["value"],
"u": r["unit"],
"t": int(r["timestamp"].timestamp()),
# RSSI og batteri kun medtaget hvis tilgængeligt:
**({"rs": r["rssi_dbm"]} if r.get("rssi_dbm") else {}),
**({"bat": r["battery_pct"]} if r.get("battery_pct") else {}),
}
for r in readings
],
}
# MessagePack (~40% af JSON):
packed = msgpack.packb(payload, use_bin_type=True)
# zlib komprimering (~35% af MessagePack):
compressed = zlib.compress(packed, level=6)
return compressed
def deserialize_payload(data: bytes) -> dict:
"""Server-side dekomprimering."""
decompressed = zlib.decompress(data)
return msgpack.unpackb(decompressed, raw=False)
Heartbeat-optimering
# Undgå konstante heartbeats — send kun ved tilstandsændring:
class HeartbeatManager:
def __init__(self, client: mqtt.Client, gateway_id: str):
self._last_status = None
self._client = client
self._gateway_id = gateway_id
def send_if_changed(self, online: bool, signal_dbm: int):
current_status = {
"online": online,
"sig": signal_dbm // 10, # Afrund til 10 dBm-bucket
}
if current_status != self._last_status:
# Status ændret → send update:
payload = msgpack.packb({
"gw": self._gateway_id,
"ts": int(time.time()),
**current_status,
})
self._client.publish(
f"meters/{self._gateway_id}/status",
payload,
qos=1,
retain=True, # MQTT retain = server kender seneste status
)
self._last_status = current_status
# Ellers: Send IKKE (spar data)
OTA diff-patches
# server/src/ota/patcher.py
import bsdiff4 # Binary delta patching
def create_patch(old_firmware: bytes, new_firmware: bytes) -> bytes:
"""
Opret binær diff-patch i stedet for fuld firmware-image.
Typisk: 4 MB → 300 KB patch (93% reduktion).
"""
patch = bsdiff4.diff(old_firmware, new_firmware)
compressed = zlib.compress(patch, level=9)
return compressed
def apply_patch(current_firmware: bytes, patch: bytes) -> bytes:
"""Gateway-side: Anvend patch."""
decompressed = zlib.decompress(patch)
return bsdiff4.patch(current_firmware, decompressed)
Fallback: Hvad sker ved SIM-problem?
# gateway/src/connectivity/fallback.py
class ConnectivityManager:
async def send_with_fallback(self, payload: bytes):
"""
Forsøgsrækkefølge:
1. 4G (SIM7080G) — primær
2. WiFi (hvis tilgængeligt på Pi) — fallback
3. Lokal buffer — gem til næste forbindelsesforsøg
"""
for attempt in range(3):
try:
await self._send_4g(payload)
return
except ConnectionError:
logger.warning(f"4G forsøg {attempt+1} fejlede")
await asyncio.sleep(60 * (2 ** attempt))
# WiFi fallback (sjælden):
try:
await self._send_wifi(payload)
return
except Exception:
pass
# Gem lokalt til næste forsøg:
await self._buffer_locally(payload)
logger.error("Kunne ikke sende — payload buffered")
Konklusion
1NCE IoT SIM med 500 MB/10 år er tilstrækkeligt for M-Bus Gateway med den rette data-optimering: MessagePack + zlib reducerer daglig payload til 3 KB, heartbeats sendes kun ved tilstandsændring og OTA bruger binære diff-patches. Cloudflare Tunnel er den største risikofaktor — brug det sparsomt og kun til fejlfinding.