The typical first integration between a reader estate and an ERP is HTTP: the gateway posts each batch of reads to an endpoint, Odoo receives it, and everyone goes home. Three months later the endpoint is timing out under peak load, the gateway is retrying into a wall, and the events that were lost are exactly the ones that would have explained the stock discrepancy.
MQTT is the usual fix, and it is a good one — but only if it is understood as a transport with specific guarantees rather than as a queue that magically makes the problem go away.
Why HTTP polling fails at RFID event rates
The failure is not a matter of tuning; it is structural.
- Polling means the client asks; at ten thousand tags per hour the asking itself becomes the load.
- A slow ERP response blocks the gateway, so readers either buffer in memory or drop events at the edge.
- Network jitter turns a synchronous call into a timeout, and a timeout into an unrecorded read.
- There is no natural back-channel: Odoo cannot tell the gateway to slow down, change mode or re-read a zone.
- Retry storms amplify the original problem, because every failed request is repeated while new events keep arriving.
What publish and subscribe changes
The broker inserts a durable, addressable buffer between the two sides, and that changes the failure modes.
- The reader publishes and forgets; delivery becomes the broker's problem rather than the reader's.
- The business side subscribes at its own pace, so a slow ERP slows the bridge, not the radio.
- Topics give a routing vocabulary: a site, a zone, a reader and an event type can each be addressed.
- The broker can buffer while the consumer restarts, which turns an outage into a delay.
- Command topics allow Odoo or an operator to push configuration back down to readers.
The topic tree is the contract
Most MQTT integrations age badly because the topic structure was improvised, and every consumer now parses strings.
- Design the hierarchy once: site, zone, reader, event type, and keep the order fixed forever.
- Publish raw reads on one branch and resolved business events on another; they have different consumers and different retention needs.
- Use a separate branch for status and heartbeat, so a dead reader is visible without scanning the event stream.
- Wildcards are convenient for tooling and dangerous for production consumers, because a subscription can silently widen.
- Version the tree, not the payload alone; a renamed level breaks more than a renamed field.
Payload design: small, versioned, self-describing
Payloads are contracts too, and the cost of getting them wrong arrives late.
- Include the essentials per event: event time from the reader, reader identifier, antenna, EPC, signal strength and a monotonic sequence.
- Prefer numeric timestamps in milliseconds from the device, with the gateway's receive time in a separate field.
- Keep the schema flat where possible; nested structures are harder to validate and harder to index.
- Carry an explicit schema version and firmware version, because field behaviour changes with firmware more often than with schema.
- Compress only when bandwidth is genuinely constrained; a broker full of binary blobs is a broker nobody can debug.
QoS, retained messages and what they actually guarantee
The three QoS levels are widely described and widely misunderstood.
- QoS zero is fire and forget: fine for high-rate raw reads where the next read a second later will replace it.
- QoS one guarantees at least one delivery, which means duplicates, and duplicates mean the bridge must be idempotent.
- QoS two guarantees exactly one delivery at the protocol level, at the cost of a four-way handshake per message.
- Retained messages are for state, not events: the last known reader status belongs there, a tag read does not.
- A last will and testament is what makes a dead reader visible within seconds instead of at the next audit.
The bridge: where protocol becomes business event
The bridge is a real application with state, and it is where the value is created or destroyed.
- Subscribe to raw reads, deduplicate by EPC within a time window, and aggregate into signed deltas.
- Resolve EPC to product and lot from a cached mapping, and route unknown tags to a quarantine topic.
- Apply to Odoo through a single writer with an idempotency key, never from multiple bridge instances at once.
- Acknowledge the MQTT message only after the business write commits, or you will lose events at the worst moment.
- Publish a resolved event back on the business branch so that other consumers do not have to repeat this work.
Backpressure, replay and the poison message
Every event pipeline eventually meets a message it cannot process, and the design question is what happens then.
- Cap in-flight messages; an unbounded consumer is just a slower way to run out of memory.
- When the spool grows beyond a limit, drop with a counter rather than blocking the reader, and alert loudly.
- Send unprocessable messages to a dead-letter topic with the reason attached, and never let one message stall the queue.
- Store the monotonic sequence so a replay can start from a known point instead of guessing.
- Test the replay path deliberately; an untested replay is a belief, not a capability.
Security: TLS, credentials and topic ACLs
An MQTT broker on a warehouse network without access control is an inventory manipulation interface.
- Use TLS on the standard secure port and disable the plaintext listener entirely.
- Give each reader its own credential or client certificate, so a compromised device can be revoked alone.
- Restrict publish and subscribe rights per topic branch, so a reader cannot publish on another site's branch.
- Disable anonymous access and the default administrative account; both are still the most common findings in audits.
- Log connection and authorisation failures, because they are the earliest signal of a misconfigured or hostile device.
Operations: running a broker you now own
Adding a broker means adding a system that must be monitored like any other.
- Track connected clients, message rate, dropped messages and the depth of persistent sessions.
- Alert on bridge lag, on the age of the oldest unprocessed message, and on dead-letter growth.
- Capacity-plan for the peak hour, not the daily average, and test a broker restart during a busy shift.
- Back up the retained state and the bridge's mapping tables; they are configuration, not cache.
- Document the topic tree and the payload schema in the repository, because the next integration will be written by someone else.






