MoasureONE E222
A comprehensive technical reference documenting the reverse engineering of the Moasure measurement device—BLE protocol, firmware update system, production API, calibration data—and the design of an open-source measurement platform.
1. Project Overview
What is the MoasureONE?
The MoasureONE E222 is a handheld IMU-based measurement device that retails for around $150. It uses inertial sensors (accelerometers and gyroscopes) to measure distances, areas, and elevations by tracking its own motion through space. The device communicates via Bluetooth Low Energy with a companion mobile app that performs all computation—the device itself is essentially a dumb IMU sensor with a simple state machine.
Why this project?
Moasure's ecosystem is locked down. The mobile app validates devices against a server-side database (checking serial number and MAC address), firmware images are AES-encrypted, and there's no public API or SDK. This project aims to build open-source measurement software that talks directly to the device over BLE, bypassing the proprietary app entirely.
What we've accomplished
- Fully mapped the BLE GATT service—all 9 characteristics, their behaviors, and the cmd1 command set
- Decoded the IMU data format (124 bytes per packet, 5 samples at 100Hz)
- Reverse-engineered the firmware updater app (.NET MAUI) and its USB serial protocol
- Discovered and documented the production API (factory test data, calibration matrices, device metadata)
- Downloaded the full 138KB calibration dataset (48×24 matrix for sensor correction)
- Built a working Electron BLE explorer with real-time IMU visualization (Madgwick AHRS + dead reckoning)
- Wrote 12 Python research scripts for protocol probing, MITM proxying, and data capture
- Designed a next-generation hardware concept (UWB + RTK GPS measurement puck system)
2. Device Hardware
Dual Processor Architecture
The MoasureONE runs two separate processors on a single board, each with independent firmware:
v0.13v0.1v1.272Device Identity
18S1FR52103502322:E2:0F:8D:83:E627544Manufacturing History
The device went through 5 manufacturing phases with 41 factory tests, all passing. Data retrieved from the Moasure production API.
Factory Test Results (41 tests, all passed)
| Test | Value | Range | Unit |
|---|---|---|---|
| PCBA Present / Available | 1 | 0 – 1 | — |
| USB Supply Current | 40.66 | 2.5 – 55 | mA |
| Vdd Main PCBA Voltage | 3.17 | 3.1 – 3.26 | V |
| Low Power Tap Sensor Voltage | 2.994 | 2.9 – 3.4 | V |
| Battery Charging Voltage (Off Load) | 4.096 | 4.05 – 4.25 | V |
| Battery Charging Voltage (On Load) | 4.047 | 3.85 – 4.14 | V |
| Battery Charging Current | -369.28 | -448 – -240 | mA |
| Battery Operation Voltage | 4.464 | 4.3 – 5.2 | V |
| Battery Operation Current | 43.23 | 5 – 55 | mA |
| Battery Standby Current (No Tap) | 0.57 | 0 – 2.5 | mA |
| Battery Operation (Voltage) | 5.039 | 4.5 – 5.2 | V |
| Battery Charging Current (Moasure-One) | -315.22 | -426 – -196 | mA |
| Battery Voltage Monitor Accuracy | -0.123 | -0.45 – 0.2 | — |
| USB Supply Current (Moasure-ONE) | 103.6 | 24 – 115 | mA |
| Programming Atmel S70 | 1 | 1 – 1 | — |
| Programming BLE | 1 | 1 – 1 | — |
| 16 Sensors Active ONE | 16 | 16 – 16 | — |
| Sensors Tested Count | 16 | 16 – 16 | — |
| BLE Signal Level | 56 | 17 – 99 | — |
| BT Debug Communication | 55 | 1 – 99 | — |
| BLE MAC Serial Number | 1 | 1 – 1 | — |
| LED Colour & Brightness | 1 | 1 – 1 | — |
| Double Tap Wake Up On Batt | 3.17 | 3.1 – 3.26 | V |
Manufacturing Phases
| Phase | Date | Details |
|---|---|---|
| Phase 1 | 2022-04-23 | PCB assembly, 41 electrical tests |
| Phase 2 | 2022-04-23 | Firmware flash (PRO-ONE-260_v011B0010.img), Nordic 0A |
| Phase 3 | 2022-05-06 | Enclosure assembly |
| Phase 4 | 2022-05-07 | Calibration (AWS EC2, MCC ver 2.1.4) |
| Phase 5 | 2022-05-11 | Distance accuracy validation (1m target, 4 measurements) |
Phase 5 — Distance Accuracy Measurements
Phase 5 validation tested distance measurement accuracy against a 1-meter target. The device was iterated twice (May 7 and May 11, 2022), each time performing 4 measurements.
| # | X (m) | Y (m) | Z (m) | Distance | Error | Flags |
|---|---|---|---|---|---|---|
| 0 | -0.031 | 0.998 | 0.002 | 0.998m | -0.2% | — |
| 1 | 0.188 | 0.991 | 0.003 | 1.008m | +0.8% | — |
| 2 | -0.055 | 1.004 | 0.004 | 1.006m | +0.6% | — |
| 3 | 0.285 | 0.966 | 0.002 | 1.007m | +0.7% | — |
3. BLE Protocol
GATT Service
The device exposes a single BLE service with UUID 81f61400-3834-4eca-ba7f-845c28a2e453 containing 9 characteristics. All characteristic UUIDs share the same base with only the 16-bit suffix differing.
| Name | UUID Suffix | Properties | Description |
|---|---|---|---|
data | 0x1401 | Read / Write / Notify | IMU data buffer. 124 bytes. Writable—first N bytes replaced on write. Notifications deliver IMU packets during streaming. |
firmware | 0x1402 | Read | Live counter (~5180–5216 range, uint16 LE). Not firmware version. Likely battery voltage or temperature ADC. |
sensor | 0x1403 | Read | 96 bytes. Slowly changing calibration/offset data. Pairs of uint16 LE interleaved with 005c spacers. |
cmd1 | 0x1404 | Write | Command register. Single byte only—multi-byte writes are ignored silently. |
serial | 0x1405 | Read | Device serial number. First byte 0x00, then ASCII. |
config | 0x1406 | Read | Always a0 01 1b. Static. Useful for keepalive reads. |
status | 0x1407 | Read / Notify | 2-byte device state. Sends notifications on state changes. |
cmd2 | 0x1408 | Write | Completely inert. No effect from any write in any state. |
name | 0x1409 | Read / Write | Device name as null-terminated ASCII. Default: MoasureONE E222\0 |
Status Codes
| Status | Meaning | Description |
|---|---|---|
0x0001 | Idle | Device is connected but not streaming or measuring |
0x0002 | Streaming | 100Hz IMU data notifications active on data characteristic |
0x0003 | Measure | Measurement mode active (no IMU streaming) |
0x0103 | Measure + Marked | Measurement point marked (~1.7s after entering measure) |
State Machine
0x0001
0x0002
0x0001
0x0003
0x0103
cmd1 Command Map
Commands from IDLE (0x0001)
| Command | Effect | Notes |
|---|---|---|
0x00 | No change | Stay idle |
0x01 | → 0x0002 Streaming | Starts 100Hz IMU data notifications |
0x02 | → 0x0003 Measure | Enters measurement mode |
0x0f | DANGER Hard-kill | Device becomes unresponsive, requires power cycle |
0x30 | One-shot data | Returns one IMU notification, stays idle |
0x6d | DANGER Disconnect | Causes immediate BLE disconnect |
Commands from STREAMING (0x0002)
| Command | Effect | Notes |
|---|---|---|
0x00 | → 0x0001 Idle | Stops streaming |
0x02 | → 0x0003 Measure | Stops stream, enters measure |
0x22, 0x44, 0x6e, 0xb0 | Stay streaming | Re-emits status notification only |
0xc0 | → 0x0001 Idle | Alt stop streaming |
Commands from MEASURE (0x0003)
| Command | Effect | Notes |
|---|---|---|
0x00 | No effect | Unlike from streaming—does not exit measure |
0x01 | → 0x0002 Streaming | Caution Exits measure mode, starts streaming |
0x04 | Stay / → 0x0103 | Measurement sub-command (may control LED) |
0x08 | Stay / → 0x0103 | Measurement sub-command (may control LED) |
0x0a | → 0x0003 | Resets/clears measurement point |
0x0d | Stay / → 0x0103 | Measurement sub-command (may control LED) |
0x30 | Stay 0x0003 | Returns sentinel cfcfcfcf... (not real IMU data) |
0x60 | → 0x0001 Idle | Exit measurement mode |
0x63 | → 0x0001 Idle | Alt exit measurement mode |
0xc0 | Stay 0x0003 | Re-emits status notification only |
IMU Data Format
During streaming, the data characteristic delivers 124-byte packets via notifications at ~20 packets/second (100Hz effective sample rate):
Offset Size Type Description
0 4 uint32 LE Packet counter (increments by 50 per packet)
4 24 6×float32 Sample 0: ax, ay, az, gx, gy, gz
28 24 6×float32 Sample 1: ax, ay, az, gx, gy, gz
52 24 6×float32 Sample 2: ax, ay, az, gx, gy, gz
76 24 6×float32 Sample 3: ax, ay, az, gx, gy, gz
100 24 6×float32 Sample 4: ax, ay, az, gx, gy, gz
5 samples × 6 floats × 4 bytes = 120 + 4 counter = 124 bytes
Accelerometer: m/s² (gravity ≈ 9.81 on vertical axis)
Gyroscope: rad/s (IEEE 754 LE float32)
Key Findings
0x0103 (measurement marked) status transition fires approximately 1.7 seconds after entering measurement mode via 0x02. It is not triggered by any specific command—whichever command happens to land at the ~1.7s mark appears to get "credit" for the transition.
- Firmware characteristic is a live counter, not a version number. Values fluctuate in the ~5180–5216 range (uint16 LE). Likely battery voltage ADC or temperature.
- Sensor characteristic slowly drifts in all states. 96 bytes of what appears to be live calibration offsets or bias readings.
- Streaming and measure are mutually exclusive. Sending
0x01from measure mode exits to streaming; the device does not return to measure when streaming stops. - The phone app does all computation. The device is just a sensor + state machine. All IMU integration, dead reckoning, and measurement calculation happens on the phone.
Connection Notes
- Keepalive: Read the config characteristic (
0x1406) every 2–5 seconds to prevent disconnect - Write mode: Use write-with-response (not withoutResponse) for cmd1
- Auto-sleep: Device sleeps if not physically moved or tapped. Wake with double-tap.
- Re-advertise: After disconnect, device takes 5–15 seconds to begin advertising again
- Single connection: Only one BLE connection at a time, but device continues advertising while connected
- Noble: Use
@stoprocent/noblefor macOS Electron compatibility
4. Firmware Update System
Firmware updates use USB (Micro USB cable), not BLE. A dedicated .NET MAUI app communicates with Moasure API servers and flashes firmware over USB serial.
USB Serial Protocol
.CMD\r\n/dev/tty.usbmodem*Command Set
All commands use the .CMD\r\n format—a dot prefix, 3-letter command code, and CRLF terminator. Data payloads are hex-encoded.
USB Serial Commands (31 total)
| Command | Name | Direction | Description |
|---|---|---|---|
.BSY | Busy | ← Device | Device is busy, wait and retry |
.RCF | Read Config | → Device | Request device configuration |
.DID | Device ID | → Device | Request device identifier |
.BLI | Bootloader Info | → Device | Request bootloader information |
.ULC | Unlock | → Device | Unlock device (requires password) |
.UL2 | Unlock Stage 2 | → Device | Second unlock stage |
.RBL | Request Bootloader | → Device | Enter bootloader mode |
.FER | Flash Erase | → Device | Erase flash memory (requires password) |
.FAD | Flash Address | → Device | Select flash address for writing |
.DTA | Data | → Device | Send data chunk to device |
.DTP | Data Page | → Device | Send firmware data page |
.CAL | Calibration | → Device | Send/receive calibration data |
.FCL | Flash Calibration | → Device | Flash calibration data to device |
.RCL | Read Calibration | → Device | Read calibration from device |
.GHS | Get Hash | → Device | Get firmware/calibration SHA hash |
.NRF | NRF Update | → Device | Trigger nRF52 firmware update |
.RST | Reset | → Device | Reset device |
.SHR | Set High Range | → Device | Configure high measurement range |
.SLR | Set Low Range | → Device | Configure low measurement range |
.NAM | Name | → Device | Set/get device name |
.SHP | Shutdown | → Device | Stop current operation (sent 3×) |
.MRU | Unknown | ? | — |
.XCU | Unknown | ? | Possibly calibration-related |
.XCX | Unknown | ? | — |
.XCT | Unknown | ? | — |
.XCS | Unknown | ? | — |
.XCD | Unknown | ? | — |
.XCP | Unknown | ? | — |
.DBS | Unknown | ? | — |
.RDT | Read Data | ? | — |
.RSL | Unknown | ? | — |
Flash Update State Machine
ConnectingToModule
→ AcceptModuleConfig (get serial, type, firmware version)
→ AcceptUnlock / Unlocked (with UnlockPassword)
→ ConnectToBootloader (enter bootloader mode)
→ AcceptBootLoaderInfo
→ FlashErase (with FlashErasePassword)
→ AcceptFlashDataPage (repeated for each firmware page)
→ RebootComplete
→ AcceptNrfUpdate (optional: nRF52 BLE processor)
→ CalibrationUpdateReboot (optional: calibration update)
UnlockPassword to unlock the device and FlashErasePassword to erase flash. These are likely stored server-side or derived per-device. A watchdog timer must be periodically poked during the update.
Firmware Images
Five encrypted firmware images were extracted from the updater app bundle:
| Filename | Size | Type | Version |
|---|---|---|---|
PRO-ONE-260_v010B0009.img | 119,280 B | M1 (ancient) | — |
PRO-ONE-260_v011C0011.img | 124,880 B | M1 | — |
PRO-ONE-260_v011D0011.img | 124,880 B | M1 (latest) | 1.29.0.11 |
AppImg_External_1600Hz_M2P_INT_2.18.img | 132,160 B | M2 | — |
AppImg_External_1600Hz_M2P_INT_2.21.img | 133,280 B | M2 (latest) | 2.21 |
- M1 = PRO-ONE-260 (original MoasureONE, S70 processor)
- M2 = M2P 1600Hz (newer model, higher internal sample rate)
- All files are AES encrypted (entropy ~7.999 bits/byte, all sizes multiples of 16)
- Decryption happens on-device in the bootloader (key stored in device flash)
- No recognizable headers, strings, or magic bytes in the encrypted data
5. Moasure Production API
Overview
The Moasure production API provides access to factory test data, device metadata, and calibration matrices. All endpoints are unauthenticated—only the device serial number and MAC address are required.
https://production2.moasure.net/production/application/json; v=2.0Endpoints
get_tracker_info
GET /production/get_tracker_info?serial_no=18S1FR521035023
Accept: application/json; v=2.0
Returns the full manufacturing history: all 5 phases, 41 factory test results with actual/min/max values, distance accuracy measurements, and user ownership history.
get_the_module
GET /production/get_the_module?serial_no=18S1FR521035023&mac_address=22:E2:0F:8D:83:E6
Accept: application/json; v=2.0
Returns device metadata: serial, MAC, model ID, firmware versions (S70 and nRF52), sensor IDs, calibration state, validation status, and SHA hash for calibration verification.
return_the_calibration
GET /production/return_the_calibration?serial_no=18S1FR521035023&mac_address=22:E2:0F:8D:83:E6
Accept: application/json; v=2.0
Returns the full 138KB calibration dataset including the 48×24 calibration matrix, raw accel/gyro data (20 and 30 orientation captures respectively), and processing metadata.
Calibration Data Structure
The calibration result is a 48×24 matrix representing 8 orientation groups. Each group contains correction parameters for both accelerometer and gyroscope:
- 48 rows × 24 columns — stored as a JSON-encoded nested array string
- 8 orientation groups (6 rows each): each with a 3×3 rotation matrix + 3-element bias vector, for both accel and gyro
- Raw data: 20 accelerometer captures (576 values each) + 30 gyroscope captures (577 values each)
- Processed on: AWS EC2 instance
EC2AMAZ-PFH7AJErunning "MCC ver 2.1.4" - Processing time: ~0.97s (accel) + ~0.67s (gyro)
- Scores: accel
-13.96, gyro2.51
API Response Structure
Module metadata fields
serial_no,mac_address— Device identifiersmodule_id(27544),model_id(2) — Internal IDss70_major_version/s70_minor_version— S70 firmware (0.13)nrf52_major_version/nrf52_minor_version— nRF52 firmware (0.1)passed_all_validation(true) — Factory QA passedprimary_sensor_id/secondary_sensor_id— Both1is_blocked(false) — Device not blacklistedCalibrationState,ShaString— Calibration verificationDblTap— Double-tap wake sensitivitymeasurement_number— Lifetime measurement count
6. Existing Software Architecture
Stack
The current explorer is an Electron desktop application with a BLE manager in the main process, an IPC bridge via preload, and a Canvas 2D renderer for real-time IMU visualization.
Source Files
| File | Lines | Role |
|---|---|---|
src/main/ble.js | 478 | BLE manager—scanning, connection, streaming, command dispatch |
src/main/index.js | 35 | Electron entry point, window creation |
src/main/preload.js | 18 | IPC bridge—exposes BLE API to renderer via contextBridge |
src/renderer/app.js | 736 | UI controller—command history, device panel, IMU data decode |
src/renderer/imu.js | 200 | Madgwick AHRS filter, dead reckoning, zero-velocity updates (ZUPT) |
src/renderer/visualizer.js | 307 | 3D trail visualization + orientation cube (split Canvas 2D) |
src/renderer/styles.css | 486 | Dark theme UI styles |
src/renderer/index.html | 148 | Explorer UI layout |
Architecture Diagram
┌─────────────────────────────────────────────┐
│ Main Process │
│ ┌────────────┐ ┌──────────────────┐ │
│ │ index.js │─────▶│ ble.js │ │
│ │ (Electron) │ │ (noble + GATT) │ │
│ └────────────┘ └───────┬──────────┘ │
│ │ IPC │
│ ┌────────────┐ │ │
│ │ preload.js │◀────────────┘ │
│ │ (bridge) │ │
│ └─────┬──────┘ │
├────────┼────────────────────────────────────┤
│ Renderer Process │
│ │ contextBridge │
│ ┌─────▼──────┐ ┌──────┐ ┌────────────┐ │
│ │ app.js │──│imu.js│──│visualizer.js│ │
│ │ (UI ctrl) │ │(AHRS)│ │ (Canvas) │ │
│ └────────────┘ └──────┘ └────────────┘ │
└─────────────────────────────────────────────┘
Research Scripts
12 Python scripts were written for protocol research and reverse engineering:
Current Status
The Electron app is a fully functional BLE explorer and IMU visualizer. It can scan for and connect to the device, send arbitrary commands, stream IMU data, and render real-time 3D orientation and motion trails. It is not yet a measurement application—that's the next phase.
7. Hardware Design Concept
A next-generation measurement system designed for contractors: a universal puck form factor with UWB mesh positioning, RTK GPS, and inertial dead reckoning.
Product Line
Two SKUs sharing a single ~60mm hockey puck enclosure (glass-filled polycarbonate + TPU overmold, IP67, MIL-STD-810H):
Indoor Puck
- ESP32-S3 (dual-core, BLE + Wi-Fi)
- ICM-42688-P (6-axis IMU)
- MMC5983MA (magnetometer)
- BMP390 (barometric altimeter)
- DW3220 (UWB transceiver)
- Piezo buzzer (Find My + alerts)
Outdoor Puck
- Everything in Indoor, plus:
- u-blox ZED-F9P (RTK GNSS)
- Ceramic patch antenna
- RTK corrections via NTRIP
- Multi-constellation (GPS, GLONASS, Galileo, BeiDou)
Positioning System
The puck system combines multiple positioning technologies for centimeter-level accuracy across all environments:
| Technology | Accuracy | Environment | Role |
|---|---|---|---|
| RTK GPS | ±2 cm horizontal | Outdoor, open sky | Primary outdoor positioning |
| UWB Mesh | ±5–10 cm | Indoor & outdoor | Relative positioning between pucks |
| Barometric | ±10–20 cm | All environments | Z-axis altitude (complements RTK & UWB) |
| IMU Dead Reckoning | ±10–30 cm (short term) | All environments | Fallback, gap-filling between fixes |
Z-Axis Accuracy Targets
- Outdoor (RTK + baro): ±2–3 cm
- Holes / Indoor (UWB + baro): ±2–5 cm
- IMU-only fallback: ±10–30 cm
Multi-Puck Operation
Any puck can be a beacon (fixed reference) or rover (moving measurement point), with roles set in software. Multi-puck modes enable:
- Distance — point-to-point between any two pucks
- Area & Perimeter — walk the boundary with rover puck(s)
- Elevation Profiles — traverse terrain with altitude tracking
- Volume — walk the surface of dirt piles, excavations
- Retaining Walls — measure along vertical faces
Charging & Accessories
Each puck has USB-C plus a magnetic pogo-pin interface for station charging:
- 2-slot station — $69
- 4-slot station — $99
- 8-slot crew case — $149 (rugged carry case with integrated charger)
Retail Bundles
- Single Indoor Puck — $149
- 2-Pack Indoor + 2-Slot Station — $367
- 4-Pack Indoor + 4-Slot Station — $695
- Pro Kit (1 Outdoor + 3 Indoor + 4-Slot) — $945
Find My Integration
Every puck supports Apple Find My and Google Find My Device networks via BLE, UWB precision finding (point-and-locate), and piezo buzzer for audible location.
RTK Corrections
The Outdoor Puck uses the u-blox ZED-F9P GNSS receiver with NTRIP correction sources:
- Free: State DOT CORS networks, RTK2go community casters
- Paid: Swift Navigation Skylark, PointOne Navigation
- Antenna: u-blox ANN-MB-00 (integrated ceramic patch in puck lid)
Comparison: Moasure IMU-Only vs. Puck System
| Capability | Moasure (IMU) | Puck System (RTK+UWB+IMU) |
|---|---|---|
| Distance Accuracy | ±1–2% (drift accumulates) | ±2 cm (absolute) |
| Max Reliable Range | ~30 m (drift limit) | Unlimited (outdoor GPS) |
| Z-Axis | Poor (IMU drift) | ±2–3 cm (RTK + baro) |
| Multi-Point | Sequential only | Simultaneous (mesh) |
| Indoor | Works everywhere | UWB mesh + IMU |
| Outdoor | Works but drifts | RTK GPS + baro |
| Price | ~$150 device | $149–$349 per puck |
8. Implementation Roadmap
The path from BLE explorer to measurement application, broken into four phases:
Phase 1 — Calibration Module
Apply the factory calibration matrix to raw IMU data. Load the 48×24 matrix from the API, transform accelerometer and gyroscope readings to correct for per-device sensor bias and orientation errors.
New file: calibration.js
Phase 2 — Measurement Engine
Core measurement algorithms: distance (double-integrated acceleration), area (shoelace formula on path), elevation (barometric or Z-integration), and point-to-point. Handle ZUPT detection, drift correction, and coordinate transforms.
New file: measurement.js
Phase 3 — Measurement UI
Dual-mode application: Measure (streamlined measurement workflow with results display) and Explorer (existing debug/protocol interface). Split-view or tab-based, sharing the same BLE connection.
New files: measure-app.js, measure-viz.js, storage.js, export.js
Phase 4 — Polish
Auto-connect to known devices, live measurement overlay during capture, shape detection (rectangle, circle), keyboard shortcuts, measurement history with local storage, CSV/JSON export.
Modified files: imu.js, app.js, index.html, styles.css, index.js, preload.js