--- /dev/null
+/* Oil tank monitor using Si4320 framed FSK protocol
+ *
+ * Tested devices:
+ * Sensor Systems Watchman Sonic
+ *
+ * Copyright © 2015 David Woodhouse
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+#include "rtl_433.h"
+#include "pulse_demod.h"
+#include "util.h"
+
+
+// Start of frame preamble is 111000xx
+static const unsigned char preamble_pattern = 0xe0;
+
+// End of frame is 00xxxxxx or 11xxxxxx depending on final data bit
+static const unsigned char postamble_pattern[2] = { 0x00, 0xc0 };
+
+static int oil_watchman_callback(bitbuffer_t *bitbuffer) {
+ uint8_t *b;
+ uint32_t unit_id;
+ uint16_t depth = 0;
+ uint16_t binding_countdown = 0;
+ uint8_t flags;
+ uint8_t maybetemp;
+ double temperature;
+ char time_str[LOCAL_TIME_BUFLEN];
+ data_t *data;
+ unsigned bitpos = 0;
+ bitbuffer_t databits = {0};
+ int events = 0;
+
+ local_time_str(0, time_str);
+
+ // Find a preamble with enough bits after it that it could be a complete packet
+ while ((bitpos = bitbuffer_search(bitbuffer, 0, bitpos, &preamble_pattern, 6)) + 136 <=
+ bitbuffer->bits_per_row[0]) {
+
+ // Skip the matched preamble bits to point to the data
+ bitpos += 6;
+
+ bitpos = bitbuffer_manchester_decode(bitbuffer, 0, bitpos, &databits, 64);
+ if (databits.bits_per_row[0] != 64)
+ continue;
+
+ b = databits.bb[0];
+
+ // Check for postamble, depending on last data bit
+ if (bitbuffer_search(bitbuffer, 0, bitpos, &postamble_pattern[b[7] & 1], 2) != bitpos)
+ continue;
+
+ if (b[7] != crc8le(b, 7, 0x31, 0))
+ continue;
+
+ // The unit ID changes when you rebind by holding a magnet to the
+ // sensor for long enough; it seems to be time-based.
+ unit_id = (b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3];
+
+ // 0x01: Rebinding (magnet held to sensor)
+ // 0x08: Leak/theft alarm
+ // top three bits seem also to vary with temperature (independently of maybetemp)
+ flags = b[4];
+
+ // Not entirely sure what this is but it might be inversely
+ // proportional to temperature.
+ maybetemp = b[5] >> 2;
+ temperature = (double)(145.0 - 5.0 * maybetemp) / 3.0;
+ if (flags & 1)
+ // When binding, the countdown counts up from 0x51 to 0x5a
+ // (as long as you hold the magnet to it for long enough)
+ // before the device ID changes. The receiver unit needs
+ // to receive this *strongly* in order to change its
+ // allegiance.
+ binding_countdown = b[6];
+ else
+ // A depth reading of zero indicates no reading. Even with
+ // the sensor flat down on a table, it still reads about 13.
+ depth = ((b[5] & 3) << 8) | b[6];
+
+ data = data_make("time", "", DATA_STRING, time_str,
+ "model", "", DATA_STRING, "Oil Watchman",
+ "id", "", DATA_FORMAT, "%06x", DATA_INT, unit_id,
+ "flags", "", DATA_FORMAT, "%02x", DATA_INT, flags,
+ "maybetemp", "", DATA_INT, maybetemp,
+ "temperature_C", "", DATA_DOUBLE, temperature,
+ "binding_countdown", "", DATA_INT, binding_countdown,
+ "depth", "", DATA_INT, depth,
+ NULL);
+ data_acquired_handler(data);
+ events++;
+ }
+ return events;
+};
+
+static char *output_fields[] = {
+ "time",
+ "model",
+ "id",
+ "flags",
+ "maybetemp",
+ "temperature_C",
+ "binding_countdown",
+ "depth",
+ NULL
+};
+
+r_device oil_watchman = {
+ .name = "Watchman Sonic / Apollo Ultrasonic / Beckett Rocket oil tank monitor",
+ .modulation = FSK_PULSE_PCM,
+ .short_limit = 1000,
+ .long_limit = 1000, // NRZ
+ .reset_limit = 4000,
+ .json_callback = &oil_watchman_callback,
+ .disabled = 0,
+ .fields = output_fields,
+};