IoT Connectivity

FiWorks Multi-Agent

One binary, every protocol — from 6 Node.js services to 1 C binary with plugins

6 Agents, 1 Library, 6 × Node.js

Today, FIWARE has 6 active IoT Agents, each a separate Node.js application, each consuming ~80–150 MB of RAM, each running its own Express HTTP server. They all depend on a shared base library (iotagent-node-lib) that handles 80% of the work: device provisioning, NGSI broker communication, configuration, and the northbound API.

AgentProtocolTransportProtocol-Specific Code
iotagent-jsonJSON payloadsMQTT, HTTP, AMQPJSON decode (trivial — it's already JSON)
iotagent-ulUltralight 2.0MQTT, HTTP, AMQPText parsing: key|value|key|value
iotagent-opcuaOPC UAOPC UA TCPOPC UA client (node-opcua library)
iotagent-lorawanLoRaWANHTTP callbacksCayenneLpp / CBOR decode
lightweightm2mLWM2MCoAPCoAP server + LWM2M object model
sigfox-iotagentSigfoxHTTP callbacksSigfox callback parsing (12 bytes)

A typical FIWARE IoT deployment with 3 protocols needs 3 separate Node.js processes (~300–450 MB RAM) plus the shared library duplicated in each process. They don't share device registries, don't share broker connections, and can't be managed as a single unit.

The iotagent-node-lib Architecture

Southbound (device) Protocol plugin iotagent-node-lib Device registry Attribute mapping NGSI broker update
NGSI broker command iotagent-node-lib Command mapping Protocol plugin Southbound (device)

The purple boxes are protocol-specific. Everything else is shared. In practice, the protocol plugin is 5–15% of the code; the library is 85–95%.

One Binary, Pluggable Protocols

Instead of 6 separate Node.js applications, the FiWorks Multi-Agent is a single C binary with protocol support via shared libraries (.so plugins) or compile-time flags:

# Run as JSON agent (MQTT + HTTP)
fiworks-agent --protocol json --mqtt localhost:1883 --broker localhost:1026

# Run as Ultralight agent
fiworks-agent --protocol ul --mqtt localhost:1883 --broker localhost:1026

# Run as OPC UA agent
fiworks-agent --protocol opcua --opcua-server opc.tcp://plc:4840 --broker localhost:1026

# Run as LoRaWAN agent (HTTP callback receiver)
fiworks-agent --protocol lorawan --port 4061 --broker localhost:1026

# Run as LWM2M agent (CoAP server)
fiworks-agent --protocol lwm2m --coap-port 5683 --broker localhost:1026

# Run as multi-protocol agent (why not?)
fiworks-agent --protocol json,ul,lorawan --mqtt localhost:1883 --broker localhost:1026

Architecture

// FiWorks Multi-Agent core (the new "iotagent-node-lib")

typedef struct FwProtocol {
    const char*     name;           // "json", "ul", "opcua", "lorawan", "lwm2m"
    KhStatus       (*init)(FwAgent* agent, FwConfig* config);
    KhStatus       (*decode)(FwAgent* agent, char* payload, int len, FtNode** measures);
    KhStatus       (*encode)(FwAgent* agent, FtNode* command, char** payload, int* len);
    void            (*shutdown)(FwAgent* agent);
} FwProtocol;

// The core handles everything else:
// - fwHttp: northbound API (device provisioning, config, health)
// - fwHttp client: NGSI-LD broker communication
// - fwHash: device registry (device ID -> entity mapping)
// - fwHash: device group management
// - fwJson: NGSI-LD entity building from decoded measures
// - fwAlloc: per-message bump allocation
// - fwTrace: structured logging
// - fwProm: Prometheus metrics

fw-libs Coverage: iotagent-node-lib → FiWorks Multi-Agent Core

iotagent-node-lib Featurefw-libStatus
Northbound REST API (provisioning, config)fwHttpDone
NGSI-LD broker communicationfwHttp client + fwJsonDone
JSON parsing (device payloads, API requests)fwJsonDone
Device registry (in-memory + persistence)fwHashDone
Device group managementfwHashDone
Memory managementfwAllocDone
LoggingfwTraceDone
Metrics (Prometheus)fwPromDone
MQTT client (for JSON, UL agents)To build (or use libmosquitto)
AMQP clientTo build (or use librabbitmq)
CoAP server (for LWM2M)To build (or use libcoap)
OPC UA clientTo build (or use open62541)

The core library functionality is ~80% covered by existing fw-libs. The remaining 20% is transport-specific clients (MQTT, CoAP, OPC UA) which can use mature C libraries.

Protocol Plugins

PluginComplexityWhat It DoesExternal Lib
json Trivial Decode: fwJson parse. Encode: fwJson render. Done. None — fwJson IS the protocol
ul Easy Decode: split on |, pair keys with values. Encode: join with |. None — ~50 lines of C
lorawan Easy Receive HTTP callbacks from LoRa network server, decode CayenneLpp/CBOR payloads. tinycbor (small, embeddable)
sigfox Trivial Receive HTTP callbacks, decode 12-byte binary payload. None
opcua Medium OPC UA client: connect to server, subscribe to nodes, map to NGSI entities. open62541 (C, MIT, mature)
lwm2m Medium CoAP server + LWM2M Registration/Read/Write/Observe/Execute handlers. wakaama (C, EPL/EDL) or libcoap

Node.js vs C — The IoT Agent Hot Path

The IoT Agent hot path is: receive device message → decode → map to NGSI entity → POST/PATCH to broker. For a sensor sending a temperature reading every 10 seconds, this happens millions of times per day across a fleet.

Per-Message Processing

PhaseNode.js (iotagent-node-lib)C (FiWorks Multi-Agent)
Receive MQTT message ~0.1ms (event loop callback) ~0.01ms (libmosquitto callback)
Decode payload (JSON) ~0.05–0.5ms (JSON.parse, allocates objects) ~0.005ms (fwJson in-place, zero alloc)
Device registry lookup ~0.1–1ms (MongoDB query or in-memory cache) ~0.001ms (fwHash O(1) lookup)
Attribute mapping ~0.05–0.2ms (JS object manipulation) ~0.005ms (FtNode tree building)
Build NGSI-LD entity ~0.1–0.5ms (JSON.stringify) ~0.01ms (fwJson render)
HTTP POST to broker ~1–5ms (new HTTP connection per update) ~0.2–1ms (pooled connection)
Total per message ~2–8ms ~0.2–1.1ms

System-Level Comparison

Metric6 × Node.js Agents1 × FiWorks Multi-AgentImprovement
Messages/sec (single core) ~5,000–10,000 ~50,000–200,000 ~10–20×
RAM (3 protocols active) ~300–450 MB (3 Node.js processes) ~5–15 MB (1 process, 3 plugins) ~30–60× less
RAM (all 6 protocols) ~600–900 MB ~10–25 MB ~40–60× less
p99 latency (decode + forward) ~10–30ms (GC spikes) ~0.5–2ms ~10–20×
Startup time ~2–5s per agent <50ms ~50–100×
Broker connections 3–6 separate connection pools 1 shared connection pool Fewer sockets, less broker load
Device registry 3–6 separate MongoDB instances or caches 1 shared fwHash registry Single source of truth
Deployment 6 Docker containers, 6 configs 1 binary, 1 config Dramatically simpler ops

The Multi-Protocol Advantage

The unified design isn't just about performance — it's about operational simplicity:

Today (6 separate agents)

  • 6 Docker images to maintain and update
  • 6 separate configurations (each with broker URL, auth, etc.)
  • 6 separate device registries (or 6 MongoDB connections)
  • 6 separate log streams to aggregate
  • 6 separate health endpoints to monitor
  • If a device switches protocol, it must be re-provisioned in a different agent

FiWorks Multi-Agent (1 unified agent)

  • 1 binary, 1 config file
  • 1 device registry shared across all protocols
  • 1 broker connection pool shared across all protocols
  • 1 log stream, 1 health endpoint, 1 metrics endpoint
  • Protocol switch = config change, no re-provisioning
  • Add a protocol = --protocol json,ul,lorawan

Why One Binary for All Protocols?

The current FIWARE IoT Agent architecture already proves the concept of a shared core with thin protocol layers. The iotagent-node-lib base library provides 85–95% of the functionality — device provisioning, northbound API, broker communication, attribute mapping, command handling — while each protocol agent contributes only 5–15% of protocol-specific code. Running 6 separate Node.js processes that each bundle that same 85–95% is pure waste.

What the Single Binary Gives You

Fault Isolation

The natural question: if one protocol plugin crashes, does it take down the others? The answer depends on the protocol:

Protocol CategoryCrash RiskWhy
JSON, Ultralight, Sigfox Negligible Pure parsing code — splits strings, reads JSON. No external C libraries, no complex state. These cannot realistically crash.
LoRaWAN Very low HTTP callback receiver with CayenneLpp/CBOR decode. tinycbor is small and well-tested.
OPC-UA Low–medium Wraps open62541 — a mature C library, but any external C library could theoretically segfault under unexpected conditions.
LWM2M Low–medium Wraps wakaama or libcoap — same consideration as OPC-UA.

For the simple text-based protocols (JSON, UL, LoRaWAN, Sigfox), there is no practical fault isolation concern — they are pure parsing code running in the process space with no external library dependencies that could cause unexpected crashes.

For the complex protocols (OPC-UA, LWM2M) that wrap external C libraries, a segfault in the library would take down the entire process. However, with restart: always and <50ms startup time, recovery is near-instant — all protocols come back online in the blink of an eye.

User's Choice: Isolate When You Want To

The --protocol flag already gives the operator full control. Want all protocols in one process? Use --protocol json,ul,lorawan,opcua. Want to isolate OPC-UA because it talks to a flaky PLC? Run two instances:

# Instance 1: simple protocols (shared)
fiworks-agent --protocol json,ul,lorawan --mqtt localhost:1883 --broker localhost:1026

# Instance 2: OPC-UA isolated
fiworks-agent --protocol opcua --opcua-server opc.tcp://plc:4840 --broker localhost:1026

Each instance is a fully independent process with its own device registry and broker connections. This is not a special "isolation mode" — it is simply running two instances of the same binary with different protocol flags. The operator decides the trade-off between operational simplicity (one instance) and fault isolation (multiple instances) based on their specific deployment needs.

Effort Estimate with Claude Max

Phase 1: Core + JSON + Ultralight (the quick wins)

ComponentWorkEstimate
Multi-Agent core Northbound API (fwHttp), device registry (fwHash), NGSI-LD client (fwHttp+fwJson), device group management, attribute mapping engine, command handling 2–3 weeks
MQTT transport libmosquitto integration, topic-based device routing, QoS handling 1 week
JSON plugin Trivial: fwJson parse/render. ~100 lines. 1–2 days
UL plugin Text parsing: split on |. ~50 lines. 1–2 days
HTTP transport (southbound) fwHttp server for device HTTP push 3–4 days
Configuration + CLI Config file, --protocol flag, env var overrides 2–3 days
Testing Unit tests, integration tests with broker, MQTT broker 1–2 weeks
Phase 1 total 5–7 weeks

Phase 2: LoRaWAN + Sigfox (HTTP callback protocols)

ComponentWorkEstimate
LoRaWAN plugin HTTP callback receiver, CayenneLpp decode, CBOR decode (tinycbor) 1 week
Sigfox plugin HTTP callback receiver, 12-byte binary decode. Trivial. 2–3 days
Phase 2 total 1.5–2 weeks

Phase 3: OPC UA + LWM2M (complex protocols)

ComponentWorkEstimate
OPC UA plugin open62541 integration, node subscription, browse, monitoring, command execution 2–3 weeks
LWM2M plugin wakaama or libcoap integration, LWM2M Registration/Read/Write/Observe/Execute, object model mapping 2–3 weeks
Testing + hardening OPC UA server simulation, LWM2M device simulation, load testing 1–2 weeks
Phase 3 total 5–8 weeks
PhaseDeliverableCumulative Time
Phase 1JSON + UL over MQTT/HTTP — covers 80% of FIWARE IoT deployments5–7 weeks
Phase 2+ LoRaWAN + Sigfox — all callback-based LPWAN protocols7–9 weeks
Phase 3+ OPC UA + LWM2M — full protocol coverage12–17 weeks

Verdict: One Agent to Rule Them All

The FiWorks Multi-Agent delivers 10–20× throughput, 30–60× less RAM, and dramatically simpler operations compared to 6 separate Node.js agents. Phase 1 (5–7 weeks) covers JSON + Ultralight — the two most common protocols — and establishes the plugin architecture for easy protocol addition.

The real innovation isn't the performance — it's the unified design. One device registry, one broker connection pool, one config, one binary. Add a protocol with a CLI flag. A multi-protocol IoT gateway that runs on a Raspberry Pi with room to spare.

Development effort: 3–4 months for all 6 protocols. Phase 1 alone (5–7 weeks) replaces the two most-used agents and the entire iotagent-node-lib.

Full Analysis: Protocol Support →