Directly monitor a Flume water monitor by relaying and decrypting the MQTT traffic between your bridge and Flume's cloud server.
The Flume bridge communicates with Flume's MQTT server on port 1883. All MQTT payloads are encrypted with libhydrogen's secretbox using a key stored in the bridge's flash memory.
This relay sits between your bridge and Flume's server, decrypting traffic in both directions so you can observe your water usage data locally. The relay forwards all messages untouched, so the Flume app continues to work normally.
- Node.js
- Network access to your Flume bridge (see Network Setup)
- A USB-to-serial adapter (3.3V TTL) to read the device key (one-time setup)
You need to read 32 bytes from your bridge's flash memory. This is a read-only operation that does not modify the device.
- Open the bridge enclosure
- Connect a 3.3V USB-to-serial adapter to the bridge's UART pins
- Connect GPIO0 to ground (puts the ESP8266 into UART download mode)
- Power on the bridge via USB
Install esptool:
pip install esptoolRead the 32-byte device secret key from flash:
esptool read-flash 0x3f9010 0x20 device_sk.binConvert to hex:
xxd -p -c 32 device_sk.binThis prints a 64-character hex string — that's your DEVICE_SK.
Remove the GPIO0 jumper and reassemble the bridge. No flash modifications are needed.
cd relay
npm installCreate relay/.env.local with your device key:
DEVICE_SK=<your 64-character hex string>
Run the relay:
npm startThe bridge connects to Flume's MQTT server on port 1883. You need to redirect this traffic to the machine running the relay.
Configure destination NAT on your router to redirect all TCP from the bridge to your relay machine's port 1883. If the bridge and relay are on the same subnet, you also need a masquerade/SNAT rule so that reply packets route back through the router (hairpin NAT).
Power cycle the bridge after applying NAT rules.
Messages are published on topics like 2/<type>/12/<device_id>/<counter>.
The system uses two device IDs: one for the bridge and one for the sensor. The
bridge subscribes to responses/<bridge_id>/# and responses/<sensor_id>/# for
downstream messages from Flume.
| Type | Topic Device | Direction | Content |
|---|---|---|---|
| 7 | Bridge | Bridge → Flume | Registration (firmware info, initiates handshake) |
| 7 | Sensor | Bridge → Flume | Sensor registration (firmware info) |
| 4 | Bridge | Bridge → Flume | Events (power-on, connection state) |
| 3 | Bridge | Bridge → Flume | Log messages |
| 3 | Sensor | Bridge → Flume | Sensor log (firmware SHA on connect) |
| 1 | Sensor | Bridge → Flume | Water flow data (cumulative pulses) |
| 2 | Sensor | Bridge → Flume | Sensor status (bitmask fields) |
| 2 | Bridge | Bridge → Flume | Bridge status (bitmask fields) |
| 6 | Bridge | Bridge → Flume | Heartbeat echo |
| 1 | Bridge/Sensor | Flume → Bridge | Handshake response (settings, last known state) |
| 4 | Bridge | Flume → Bridge | Heartbeat |
When the bridge connects (or reconnects after an outage), it performs a registration handshake:
- Bridge sends type 7 on topic
2/7/12/<bridge_id>/0with firmware info - Flume responds on
responses/<bridge_id>/1/12with settings and sensor list - Sensor sends type 7 on topic
2/7/12/<sensor_id>/<timestamp>with its own firmware info - Flume responds on
responses/<sensor_id>/1/12with last known state and sensor settings
Type 7 — Bridge registration (bridge → Flume):
{
"bridge_id": null,
"branch": "squidward",
"sha_full": "2f1c870a72eca7d7eb38cb145718a202fe1c4a86"
}Type 7 — Sensor registration (bridge → Flume):
{
"bridge_id": "61F6F57901765560",
"sha_full": "152eb0f620a1be2baa74742a55395599a0c5c107"
}Response type 1 — Bridge handshake response (Flume → bridge):
{
"code": 602,
"message": "Request OK",
"timestamp": 1785378214,
"sensors": ["62AC1323E5A55F6F"],
"devices": [{"uuid": "62AC1323E5A55F6F", "hardware_id": "ASY-00007"}],
"settings": {
"1": 1, "2": "/provisioning", "3": "/frames", "4": "/responses",
"5": "mqtt.flumewater.com:1883", "6": 30000, "7": 1200000,
"8": 4093, "9": 65281, "10": 14, "11": 261135, "12": 12,
"13": 60, "14": 0
}
}| Setting | Value | Meaning | Reasoning |
|---|---|---|---|
| 2 | "/provisioning" | Provisioning topic prefix | String path |
| 3 | "/frames" | Frames topic prefix | String path |
| 4 | "/responses" | Responses topic prefix | Matches observed subscription pattern |
| 5 | "mqtt.flumewater.com:1883" | MQTT broker address | Self-explanatory |
| 7 | 1200000 | Bridge status interval (ms) | 1200000ms = 1200s, matches observed 1200s bridge status cadence |
| 13 | 60 | Heartbeat timeout? (seconds) | Plausible keep-alive or timeout value |
Response type 1 — Sensor handshake response (Flume → sensor):
{
"code": 602,
"message": "Request OK",
"timestamp": 1785378222,
"last_sample_timestamp": 1785366129,
"sensors": [],
"peaks": 3842082,
"settings": {
"1": 16383, "2": 2097151, "3": 60000, "4": 14, "5": 511,
"6": 5, "7": 30, "8": 600, "9": 0, "10": 0, "11": 2,
"12": 150, "13": 0, "14": 0, "15": 0
}
}| Setting | Value | Meaning | Confidence | Reasoning |
|---|---|---|---|---|
| 6 | 5 | High-flow reporting interval (seconds) | High | Matches observed 5s interval during heavy flow |
| 7 | 30 | Low-flow reporting interval (seconds) | High | Matches observed 15–30s interval during light flow |
| 8 | 600 | Status reporting interval (seconds) | High | Matches observed ~600s sensor status cadence |
| 12 | 150 | ? | Low | Unknown |
| 1 | 16383 | Sensor status bitmask (keys to report) | Medium | 16383 = 0x3FFF = bits 0–13, could define which status keys are enabled |
| 2 | 2097151 | Extended status bitmask | Medium | 2097151 = 0x1FFFFF = bits 0–20, same pattern at larger scale |
| 3 | 60000 | ? (ms timeout?) | Low | Unknown |
The last_sample_timestamp and peaks fields tell the device what data the
server already has. On reconnection after a 3-hour outage, flow data that
occurred during the gap was delivered in rapid succession — the system buffers
readings and replays them starting after last_sample_timestamp. Whether
the buffer lives on the sensor or the bridge (or both) is not known from the
wire protocol alone.
Type 4 — Events (bridge → Flume):
[
{"timestamp": 1785105156, "type": 32768},
{"timestamp": 1785105163, "type": 16384}
]Event type codes are powers of 2. Observed values: 1, 256, 1024, 2048,
16384, 32768. Identified meanings:
| Code | Meaning | Confidence | Reasoning |
|---|---|---|---|
| 32768 | Boot/power-on | High | Always the first event in the sequence, present in every captured boot |
| 1024 | MQTT connected | High | A type 3 log message with type: 1024 fires at the exact same timestamp with the message "reconnected" |
| 1 | ? | Low | Appeared at boot alongside code 1024 in one reconnect sequence (both at same timestamp). Also appears in the type 7 handshake |
| 256 | Sensor registered | Medium | Fires at the same timestamp as the "client id: 3" log message (type 16384), appears only after sensor registration completes |
| 16384 | Sensor radio link | Medium | Always the last event in the boot sequence. Fires at the same timestamp as the "client id: 3" log message. Absent in MQTT-only reconnects (where the radio link was never lost) |
2048 appeared 599 seconds after boot in one capture but was absent in a fast
reconnect — possibly WiFi association, but not confirmed.
Type 3 — Log messages (bridge → Flume):
[
{"timestamp": 1785378214, "type": 1, "level": 4, "message": "RST REASON: SmartConfig OK - WiFi reset "},
{"timestamp": 1785378214, "type": 1, "level": 4, "message": "SDK reset #4: restart"},
{"timestamp": 1785378214, "type": 1, "level": 4, "message": "SHA: 2f1c870a72eca7d7eb38cb145718a202fe1c4a86 | BRANCH: squidward"}
][
{"timestamp": 1785105163, "type": 1024, "level": 4, "message": "reconnected"}
][
{"timestamp": 1785105260, "type": 16384, "level": 2, "message": "client id: 3"}
]The first log message batch after boot contains the reset reason, SDK info, and
firmware SHA/branch. The log type field corresponds to event type codes (type 1
= boot-related, type 1024 = MQTT subsystem, type 16384 = sensor radio subsystem).
Type 3 — Sensor log (sensor topic, bridge → Flume):
[
{"timestamp": 1785105263, "type": 1, "level": 4, "message": "<40-char hex SHA>"}
]The sensor's first log message contains its firmware commit hash.
Type 1 — Water flow data (sensor topic, bridge → Flume):
[
{"timestamp": 1785104646, "value": 3828906},
{"timestamp": 1785104676, "value": 3828907},
{"timestamp": 1785104691, "value": 3828908},
{"timestamp": 1785104716, "value": 3828909},
{"timestamp": 1785104731, "value": 3828910},
{"timestamp": 1785104767, "value": 3828911}
]The value field is a cumulative counter — magnetometer pulses from the water
meter's rotating element. The conversion factor is approximately 40
pulses/gallon, determined by correlating observed pulse counts with known
fixture volumes: toilet flushes consistently produce ~65 pulses (= 1.6 gal at
40 p/gal, matching the US 1.6 GPF standard), showers run at ~78 p/min (= 1.95
GPM, matching the 2.0 GPM federal showerhead max), and irrigation spray heads
produce ~150 p/min (= 3.75 GPM, typical residential).
The sensor samples every 5 seconds and only transmits when the counter has incremented — zero-delta intervals are never reported. During high flow (>1 pulse per 5s), reports arrive every 5 seconds; during low flow, reports arrive at variable multiples of ~5s depending on when the next pulse occurs. No messages are sent during idle periods.
Type 2 — Sensor status (sensor topic, bridge → Flume):
[
{
"1": 2, "2": 17, "4": 18, "8": 7,
"16": -79, "32": 3252, "128": 0,
"1024": 24, "2048": 7, "4096": 0, "8192": 1,
"16384": 3214, "131072": 3530, "262144": 50,
"timestamp": 1785105287
}
]Keys are bitmask-style (powers of 2). Not all keys appear in every message — some are reported on a slower cadence or only when changed.
| Key | Meaning | Confidence | Reasoning |
|---|---|---|---|
| 16 | RSSI (dBm) | High | Range -96 to -70 across 1097 messages. Consistent with sub-GHz radio signal strength |
| 32 | Battery (mV) | High | Range 3258–3280, extremely stable over 8 days. Consistent with a lithium primary cell under light load |
| 1024 | Temperature (°C) | High | Range 21–27, follows a diurnal cycle. The only plausible physical quantity with this range and behavior at a water meter |
| 262144 | Flow intensity (pulses per interval) | High | Baseline 49–50 when idle; spikes to 51–143 during flow with r=0.67 correlation to flow pulse count. 100% of spikes occur within 10 minutes of flow events. Clear dose-response: mean 39 pulses nearby at baseline, 757 pulses nearby at values >100 |
| 2 | ? | Low | Values 10–17, never repeats consecutively across 120 observations. Appears in ~11% of messages. The system uses 50 radio channels, so this is not a channel index despite the non-repeating pattern |
| 1 | Tx sequence/retry counter | Medium | Alternates 0/1 under normal conditions (alternating-bit protocol). Escalates to 2–3 during heavy flow congestion, indicating retransmissions |
| 2048 | Frame sequence bit | Medium | Alternates 6/7 with 373/374 perfect transitions across 375 observations. Only break was at a device reboot (restarted on 6 regardless of prior state) |
| 131072 | Signal measurement (ADC counts) | Medium | Range 2712–3783, high autocorrelation (lag-1 = 0.63), slowly drifting. No correlation with temperature, battery, RSSI, or flow. Likely ultrasonic transducer signal quality or coupling measurement |
| 8 | Reboot counter | Low-Medium | Values 7→12→13 observed across captures, incrementing. Only appears in full status reports at boot |
| 16384 | Reference voltage (mV) | Low-Medium | Value 3214 in every observation across multiple captures. Hardware-constant behavior suggests bandgap/ADC reference |
| 4 | Firmware/config version | Low | Values 18 and 96 observed, changes at reboot. Not a simple increment |
| 128 | ? | Low | Always 0. Only appears in full diagnostic dumps at boot |
| 4096 | ? | Low | Always 0. Only appears in full diagnostic dumps at boot |
| 8192 | ? | Low | Values 0 and 1. Only appears in full diagnostic dumps at boot |
Type 2 — Bridge status (bridge topic, bridge → Flume):
Typical report (subset of keys, sent every 1200s):
[
{
"4": 28496, "32": -87, "128": 13,
"256": 10, "512": -60,
"timestamp": 1785105496
}
]First report after boot includes extra keys (1, 16, 1024, 2048):
[
{
"1": 0, "4": 28504, "16": 20, "512": -75,
"1024": 6, "2048": 1,
"timestamp": 1785378223
}
]| Key | Meaning | Confidence | Reasoning |
|---|---|---|---|
| 32 | Sensor RSSI (dBm) | High | Range -100 to -74. Consistent with a 915MHz radio link at residential distances |
| 512 | WiFi RSSI (dBm) | High | Range -84 to -71. Consistent with 2.4GHz WiFi in a home environment |
| 128 | Sensor radio packet counter | High | Counts all radio packets received from the sensor (including keepalives). Idle rate: exactly 6/hr (1 keepalive every 10 minutes). Resets on full reboot |
| 256 | Data-bearing packet counter | Medium-High | Counts only packets carrying flow data (excludes keepalives). (key 128 − key 256) grows at exactly 6.0/hr, proving the difference is keepalive overhead. During idle: ratio to key 128 ≈ 0.50; during heavy flow: ratio approaches 0.95 |
| 4 | ? (firmware/config version?) | Low | Was 28496 across all observations in first capture; changed to 28504 after a full reboot. Stable within a session but not truly constant — possibly a firmware build number or config hash |
Type 4 — Heartbeat (Flume → bridge):
{
"timestamp": 1785105391038968,
"heartbeat": {
"id": "c4fc411648ca757e2f5210aa9af441f7",
"seq": 1
}
}The bridge echoes the heartbeat back verbatim as a type 6 message (within ~45ms). The timestamp is microsecond-precision. Heartbeats arrive every 600.0 seconds with sub-second precision — this is server-driven (213 observed intervals, all within ±0.2s of 600s).
Based on 35+ hours of captured data (1636 messages). These intervals are confirmed by the handshake response settings (sensor settings 6, 7, 8 and bridge setting 7):
| Message type | Interval | Handshake setting | Notes |
|---|---|---|---|
| Heartbeat (Flume → bridge) | 600s exactly | — | Server-controlled, microsecond-precision timestamps |
| Sensor status | ~600s average | sensor setting 8 = 600 | The sensor's clock drifts slightly: intervals alternate between ~581s and ~639s in a pattern that averages to 600s. This appears to be a clock correction mechanism, not jitter — the 639s interval appears every 3rd report |
| Bridge status | 1200s | bridge setting 7 = 1200000 (ms) | Consistent across observations |
| Flow data (high) | 5s | sensor setting 6 = 5 | During high flow (>1 pulse/sec) |
| Flow data (low) | 30s | sensor setting 7 = 30 | During low flow. No reports during idle periods |
The sensor status cadence is independent of the server heartbeat — there is no synchronization between them (offsets between sensor reports and heartbeats vary continuously).
- What is the pulse-to-volume conversion factor? Filling a container of known volume while watching the value counter would determine how many pulses equal one gallon (or liter). Rough estimates from household usage patterns suggest 65–100 pulses/gallon but this has not been measured.
- What do the remaining type 2 bitmask keys mean? Keys 131072, 262144, 4, 8, 128, 4096, 8192, 16384 on the sensor and keys 4, 256 on the bridge remain unidentified. Key 262144 spikes during heavy flow for unknown reasons. Key 131072 has voltage-like values (2836–3658) but varies too much to be a battery.
- What does event code
2048mean? Best guess is WiFi association, but the only evidence is its position in the boot sequence and its absence from fast reconnects. - What do the log
typeandlevelfields encode? Logtypecorresponds to event type codes / subsystem identifiers (1 = boot, 1024 = MQTT, 16384 = sensor radio — confirmed by matching log messages to subsystems). Level may be severity (2 = info, 4 = debug?). - Can the reporting cadence be changed? The handshake response includes settings that match observed intervals (sensor settings 6=5s, 7=30s, 8=600s). A crafted response with different values might increase resolution, but this has not been tested.
- What do the sensor handshake settings 1 and 2 control? Values 16383 (0x3FFF) and 2097151 (0x1FFFFF) — the bitmask pattern suggests they define which status keys to report, but this is unconfirmed.
- What does sensor key 2048 signify? It alternates perfectly between 6 and 7 with no exceptions across 67 observations. Could be a radio channel, protocol phase, or something else entirely.
- Where does flow buffering happen? During a 3-hour MQTT outage, flow data was replayed on reconnection. Whether the sensor or bridge (or both) maintains this buffer is not determined.
This project is provided for educational and personal use only. Use it at your own risk. The author is not responsible for any damage to your equipment, disruption of service, violation of terms of service, or any other consequences resulting from the use of this software. Interacting with your device's firmware and intercepting network traffic may void your warranty or violate your agreement with Flume.
- Encryption:
hydro_secretbox(key=device_sk, context="12345678", msg_id=0) - The device key is stored at flash address
0x3f9010 - The bridge uses the same key for both sending and receiving
- MQTT credentials are sent in plaintext during the CONNECT packet
- The firmware contains the hostname
mqtt.prod.flumetech.com