Skip to content

OpenCollar Edge

The first comprehensive device driver (shared/device_drivers/opencollar/). It decodes the LoRaWAN uplinks of every OpenCollar Edge device (RangerEdge, RhinoEdge, CollarEdge, ElephantEdge, WisentEdge, FreeEdge, Fence Monitor) built from the public firmware. The complete protocol study, with byte layouts, the settings and command tables and the public decoder copied verbatim, is in the protocol research. This page is the reference example for driver documentation (architecture 28.12).

Frame

Every uplink is [msg_id][len][data] on an FPort that selects the message type; integers are little-endian. FPort 29 has no header: it is a concatenation of stored records [port][msg_id][len][data][store timestamp u32]. FPorts 3 and 30 are id, len, value lists.

The same messages arrive over other paths (architecture 25, phase 11) and the driver reads them by the delivery's acquisition channel: over Web Bluetooth and in raw log files a frame carries the port in front, [port][msg_id][len][data], and on port 29 the rest of the frame is the stored record stream; over Iridium the payload is the satellite buffer, a stored record stream in the flash storage format. A record's own timestamp stays canonical whichever path delivered it, which is what makes the same fix over LoRaWAN, from a file and over Bluetooth one position with three deliveries. See OpenCollar over Web Bluetooth, raw log files and Cloudloop.

The protocol catalogue (settings, commands and readable values of firmware 7.3.0, research sections 4.2 to 4.4) ships with the driver as catalog.json, generated from the research document, and feeds the Web Bluetooth settings editor through GET /devices/{id}/driver-catalog.

What the driver produces

FPort Message Canonical records Canonical time
2 GNSS position (u-blox) Position with altitude, accuracy, satellites, fix type and PDOP; measurements gnss_fix, gnss_time_to_fix, gnss_satellites, gnss_accuracy, gnss_pdop; speed and heading when active tracking is on Fix timestamp from the receiver
13, 16 Short position, and its periodic resend Position with accuracy. A port 16 resend of the same fix has the same canonical key and becomes a second delivery, not a second position Fix timestamp
4 Status Measurements battery_voltage (V), charging_voltage (V, when charging), device_temperature (°C), acceleration_x/y/z (m/s², one sample of gravity plus motion at the moment of the message) and, derived by the decoder, activity (the change of that vector against the status message before it, m/s²; last_movement_at on the device's current state moves on one change of 2.0 m/s² or on two of 1.4 m/s² running; a still device's accelerometer drifts by up to 1.36 m/s² of its own accord, which is the sensor's 8-bit resolution and not movement), uptime (s, shown as days; the byte counts days since firmware 4.0.1 and hours before, read from the firmware version inside the message, and wraps at 255 either way, which is not a reboot; an uptime lower than the one before it is a reboot: a device_reset event with the reset reason and last_reset_at on the device's current state), lr_satellites; a device state with reset reason, error flags, firmware and hardware version and type; a device_error event when an error flag other than ublox_fix is set (a GPS attempt that got no fix is routine and raises no event, Tim 2026-09-19; the flag stays in the state, on the health card and in the device performance figures) Network receive time (the message has no clock); the store timestamp when it comes from a flash log
12 Electric fence measurement fence_voltage (V, shown as kV), fence_pulse_count, fence_energy; a fence_measurement_failed event when the measurement did not succeed; on a device whose entity stands on a fence line, the line's sections are recomputed and a FENCE_STATUS event raised when one changed (phase 32, Fences and traps) Network receive time
14 Flash status flash_used_percent, flash_messages Network receive time
18 Device timestamp State device_time; clock_offset (device clock minus network time, s) Network receive time
19, 20 External switch change and status switch_active, switch_count; switch_activated and switch_deactivated events; on a device whose entity is a Trap, a trap_triggered reading and a TRAP_CLOSED or TRAP_OPENED event on a change (phase 32, Fences and traps) Network receive time
29 Flash log Each stored record is decoded as if it had arrived on its own port; positions keep their fix time, clockless messages take the store timestamp Per record
31 Command confirmation, BLE MAC, requested last position State last_command, state ble_mac, Position (firmware byte order: longitude first) Fix timestamp for the position
3, 30 Settings and values readback State with the raw id: hex list Network receive time
7, 11 BLE scan, aggregated over a window and one at a time One contact per sighting, with the three octets the scan carries, the signal strength and how the address resolved; under the phone filter also a human_presence event and measurement, and from a device with a place set, a proximity position for what it heard. See Bluetooth contacts The scan's own timestamp, unless it is implausibly far behind its delivery (decision D259)
15 Cardiac monitor (CMDQ) One reading per sighting of the LINQII tag the device follows: heart_rate (bpm), heart_rate_variability (ms), cmdq_temperature (°C), cmdq_success and the raw fields. See Cardiac monitoring The sighting's own timestamp, from the device's clock
1, 5, 6, 9, 10, 21, 27, 28 LR11xx NAV, satellite lists, Wi-Fi scans, air quality, Memfault, messaging Accepted, no canonical rows yet (the LR11xx NAV needs an external solver)

Unknown ports, wrong message ids and length mismatches raise PAYLOAD_DECODE_FAILED and land in Needs Attention.

Deduplication

The canonical key of a position is device, fix time and record type. The same fix arrives up to three times (port 2 or 13 on the air, port 16 resend, port 29 flash log, and later WebBLE and raw log files) and is stored once with every delivery linked (ADR 0008). Positions without a valid fix (success bit clear, zero coordinates or a fix time before 2001) produce a gnss_fix = false measurement and nothing else.

Metrics

All metric keys used by the driver are in the registry seeds (shared/metrics/seeds.py), with canonical units. Battery is reported in volts, temperature in degrees Celsius, acceleration in m/s², uptime in seconds, fence voltage in volts as the device reports it.

Versions

The status message carries firmware and hardware version as major.minor nibbles; the patch version never reaches the air and minor versions of 16 and above wrap. The driver's decoder_version is fw7.3.0, the firmware whose protocol definition it follows. Firmware 6.x and 4.x devices speak the same layouts for the ports above; ports 8 and 17 (RF scan, open sky) of firmware up to 6.16 are not decoded.

Known discrepancies

Documented in section 8 of the research: the course over ground byte order in port 2 (the driver follows the firmware, little-endian), the longitude and latitude order in port 31 message 0xFE (the driver follows the firmware), and the wiki's big-endian count in flash read examples.

Control

Downlinks are settings on FPort 3 (id len value) and commands on FPort 32; cmd_reset is 0xA1, cmd_send_status 0xA4, cmd_send_position 0xA5. The complete command and settings tables are in the research document; the encoders and the command lifecycle are described under device control.

Testing

tests/shared/test_opencollar_driver.py runs golden tests over tests/fixtures/payloads/opencollar/uplinks.jsonl, the wiki examples with the values the public decoder produces. Recorded uplinks from live collars are added to the same file with their origin noted in the README next to it. The ChirpStack device profile codec for the local setup is the public decoder, shared/device_drivers/opencollar/codec.js, passed to scripts/dev.sh chirpstack-bootstrap --codec.

Health

The status message on port 4 is the collar's heartbeat: every status interval it reports the battery, charging, temperature, uptime, reset reason, error flags, firmware and hardware version and the LoRa satellite count. Protect keeps the newest of each on the device's current state and shows them as the Health card on the device page, as a compact line in the devices list and as a level on the map (decision D104): battery under 3.6 V warns and under 3.45 V is critical, temperature above 50 °C warns, any error flag warns, a fix with an accuracy above 30 m or more than two minutes to fix warns, flash use above 80 percent warns. The firmware version from the status is written on the device.

Firmware versions

The driver keeps one layout per firmware range, named after the firmware's own reference decoder: fw7.2.0 (7.1 to 7.3), fw6.15.1 (6.15 and 6.16), fw6.11.2 (6.9 to 6.14), fw6.5.0 (6.1 to 6.8) and fw4.4.3 (older). It picks the layout from the version the collar last reported in its status message (a status writes the firmware version on the device; 6.16 reports as 6.0 and is read as 6.16), and inside a raw log or Bluetooth download from the status records in the stream. The layout is the decoder_version of every record and of a log file. Ports 8 and 17 (RF scanner, open sky detection) decode into state records on firmware up to 6.16, port 21 (air quality, BME690 and BMV080) into measurements from 7.2, and the status message's feature bit 2 is the RF scanner only before 7.1. A message from a port the layout lacks is a note on the trace that says which firmware sends it. tests/fixtures/payloads/opencollar/golden.json holds the output of the three reference decoders for every recorded frame; scripts/opencollar_golden.py regenerates it (decision D100, ADR 0021).

Control actions

Downlinks follow protocol research section 4: commands on FPort 32 as cmd_id length argument, settings on FPort 3 as id length value. See device control for the path and the lifecycle.

Action Payload Permission Confirmed by
REQUEST_STATUS port 32, A4 00 (cmd_send_status) devices:control the next status uplink on port 4
REQUEST_POSITION port 32, B8 00 (cmd_get_ublox_fix) devices:control the next port 2 position
SET_GNSS_INTERVAL port 3, 02 04 <u32 LE seconds> (ublox_send_interval, 0 to 172800) devices:control_high_impact no answer; ends at the network stage
RESET port 32, A1 00 (cmd_reset) devices:control_high_impact a rejoin, or a status uplink with the software reset reason

Commands and settings must not be mixed in one downlink; each action produces one.