Skip to content

About

An attempt to monitor radio transmissions from a Flume water monitor

Resources

Stars

4 stars

Watchers

2 watching

Forks

Latest commit

 

History

8 Commits

Folders and files

Repository files navigation

Flumewatch

Directly monitor a Flume water monitor by relaying and decrypting the MQTT traffic between your bridge and Flume's cloud server.

How It Works

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.

Prerequisites

  • 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)

Extracting Your Device Key

You need to read 32 bytes from your bridge's flash memory. This is a read-only operation that does not modify the device.

Hardware Connection

  1. Open the bridge enclosure
  2. Connect a 3.3V USB-to-serial adapter to the bridge's UART pins
  3. Connect GPIO0 to ground (puts the ESP8266 into UART download mode)
  4. Power on the bridge via USB

Reading the Key

Install esptool:

pip install esptool

Read the 32-byte device secret key from flash:

esptool read-flash 0x3f9010 0x20 device_sk.bin

Convert to hex:

xxd -p -c 32 device_sk.bin

This prints a 64-character hex string — that's your DEVICE_SK.

Reassembly

Remove the GPIO0 jumper and reassemble the bridge. No flash modifications are needed.

Relay Setup

cd relay
npm install

Create relay/.env.local with your device key:

DEVICE_SK=<your 64-character hex string>

Run the relay:

npm start

Network Setup

The 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.

Decrypted Message Types

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

Connection Handshake

When the bridge connects (or reconnects after an outage), it performs a registration handshake:

  1. Bridge sends type 7 on topic 2/7/12/<bridge_id>/0 with firmware info
  2. Flume responds on responses/<bridge_id>/1/12 with settings and sensor list
  3. Sensor sends type 7 on topic 2/7/12/<sensor_id>/<timestamp> with its own firmware info
  4. Flume responds on responses/<sensor_id>/1/12 with 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.

Example Messages

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).

Reporting Cadence

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).

Open Questions

  • 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 2048 mean? 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 type and level fields encode? Log type corresponds 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.

Disclaimer

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.

Technical Details

  • 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

About

An attempt to monitor radio transmissions from a Flume water monitor

Resources

Stars

4 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages