diff --git a/RFLink/7_Utils.cpp b/RFLink/7_Utils.cpp index 60f8c37..5a9d201 100644 --- a/RFLink/7_Utils.cpp +++ b/RFLink/7_Utils.cpp @@ -48,6 +48,14 @@ void reflect_nibbles(uint8_t message[], unsigned num_bytes) } } +void invert_bytes(uint8_t message[], unsigned num_bytes) +{ + for (unsigned i = 0; i < num_bytes; ++i) + { + message[i] = ~message[i]; + } +} + unsigned extract_nibbles_4b1s(uint8_t *message, unsigned offset_bits, unsigned num_bits, uint8_t *dst) { unsigned ret = 0; @@ -378,9 +386,9 @@ inline bool value_between(uint32_t value, uint32_t min, uint32_t max) return (value > min && value < max); } -bool decode_pwm(uint8_t frame[], uint8_t expectedBitCount, uint16_t const pulses[], const int pulsesCount, int pulseIndex, uint16_t shortPulseMinDuration, uint16_t shortPulseMaxDuration, uint16_t longPulseMinDuration, uint16_t longPulseMaxDuration) +bool decode_pwm(uint8_t frame[], uint8_t expectedBitCount, uint16_t const pulses[], const int pulsesCount, int pulseIndex, uint16_t shortPulseMinDuration, uint16_t shortPulseMaxDuration, uint16_t longPulseMinDuration, uint16_t longPulseMaxDuration, uint8_t bitOffset) { - if (pulseIndex + expectedBitCount * 2 > pulsesCount) + if (pulseIndex + (expectedBitCount - 1) * 2 > pulsesCount) { #ifdef PWM_DEBUG Serial.print(F("PWM: Not enough pulses: pulseIndex = ")); @@ -397,8 +405,9 @@ bool decode_pwm(uint8_t frame[], uint8_t expectedBitCount, uint16_t const pulses const uint8_t bitsPerByte = 8; //const uint8_t expectedByteCount = expectedBitCount / bitsPerByte; + const uint8_t endBitCount = expectedBitCount + bitOffset; - for(int8_t bitIndex = 0; bitIndex < expectedBitCount; bitIndex++) + for(uint8_t bitIndex = bitOffset; bitIndex < endBitCount; bitIndex++) { int currentFrameByteIndex = bitIndex / bitsPerByte; uint16_t bitDuration = pulses[pulseIndex]; diff --git a/RFLink/7_Utils.h b/RFLink/7_Utils.h index e4dcd4f..b74728b 100644 --- a/RFLink/7_Utils.h +++ b/RFLink/7_Utils.h @@ -63,6 +63,12 @@ uint8_t reflect4(uint8_t x); /// @param num_bytes number of bytes to reflect void reflect_nibbles(uint8_t message[], unsigned num_bytes); +/// Invert each bit in each byte of a number of bytes. +/// +/// @param message bytes of message data +/// @param num_bytes number of bytes to invert +void invert_bytes(uint8_t message[], unsigned num_bytes); + /// Unstuff nibbles with 1-bit separator (4B1S) to bytes, returns number of successfully unstuffed nibbles. /// /// @param message bytes of message data @@ -211,10 +217,12 @@ inline bool value_between(uint32_t value, uint32_t min, uint32_t max); * @param pulses the pulses to decode * @param pulsesCount the numnber of items inside @pulses * @param pulseIndex the index of the first pulse to be decoded - * @param shortPulseMinDuration the minimum duration of a half bit - * @param shortPulseMaxDuration the maximum duration of a half bit - * @param longPulseMinDuration the minimum duration of a half bit - * @param longPulseMaxDuration the maximum duration of a half bit + * @param shortPulseMinDuration the minimum duration of a short (low) bit + * @param shortPulseMaxDuration the maximum duration of a short (low) bit + * @param longPulseMinDuration the minimum duration of a long (high) bit + * @param longPulseMaxDuration the maximum duration of a long (high) bit + * @param bitOffset offset (in bits) in output buffer to read first bit into + * @param * @return true if pulses could be decoded, giving enough bits, false if not or pulses were not of valid lengths The PWM encoding uses pair of pulses like this: @@ -225,18 +233,26 @@ inline bool value_between(uint32_t value, uint32_t min, uint32_t max); So, for instance, we would receive this: ----__----__----__--____ This gives 4 pairs, hence 4 bits, long/short, long/short, long/short, short/long + Note that for efficiency reasons, this method does not consider the second pulse of the pair and thus does not test + it for duration validity. + + Because this method actually doesn't check the length of the second pulse of a PWM, it can also be used for devices + which have a constant-length gap between the data pulse and the next one, ie: + + Long then gap : ----___ + Short then gap : --___ + This method uses the following truth table Pair | Value ------------+-------- long/short | 1 short/long | 0 + long/gap | 1 + short/gap | 0 If you need the opposite, simply use the ~ operator on the frame bytes - Note that for efficiency reasons, this method does not consider the second pulse of the pair and thus does not test - it for duration validity. - Bits are placed in the frame in the order they appear, ie MSB first. To illustrate, consider the following set of pulses: --_-__--_--_--_--_-__--_--_-__-__--_--_--_-__-__-__--_-__--_--_--_-__-__ @@ -255,7 +271,7 @@ inline bool value_between(uint32_t value, uint32_t min, uint32_t max); But because the ESP32 is a little endian architecture, the bytes will be reversed two by two (0 - 3, 1 - 2) Using ntohs or ntohl from is thus recommended in that case */ -bool decode_pwm(uint8_t frame[], uint8_t expectedBitCount, uint16_t const pulses[], const int pulsesCount, int pulseIndex, uint16_t shortPulseMinDuration, uint16_t shortPulseMaxDuration, uint16_t longPulseMinDuration, uint16_t longPulseMaxDuration); +bool decode_pwm(uint8_t frame[], uint8_t expectedBitCount, uint16_t const pulses[], const int pulsesCount, int pulseIndex, uint16_t shortPulseMinDuration, uint16_t shortPulseMaxDuration, uint16_t longPulseMinDuration, uint16_t longPulseMaxDuration, uint8_t bitOffset = 0); /** * Decodes the pulses as a Manchester encoded series of pulses diff --git a/RFLink/Plugins/Plugin_050.c b/RFLink/Plugins/Plugin_050.c new file mode 100644 index 0000000..e99f230 --- /dev/null +++ b/RFLink/Plugins/Plugin_050.c @@ -0,0 +1,87 @@ +/** +Fine Offset Electronics WH2 Temperature/Humidity sensor protocol, +also Agimex Rosenborg 66796 (sold in Denmark), collides with WH5, +also ClimeMET CM9088 (Sold in UK), +also TFA Dostmann/Wertheim 30.3157 (Temperature only!) (sold in Germany). + +The sensor sends two identical packages of 48 bits each ~48s. The bits are PWM modulated with On Off Keying. + +The data is grouped in 6 bytes / 12 nibbles. + + [pre] [pre] [type] [id] [id] [temp] [temp] [temp] [humi] [humi] [crc] [crc] + +There is an extra, unidentified 7th byte in WH2A packages. + +- pre is always 0xFF +- type is always 0x4 (may be different for different sensor type?) +- id is a random id that is generated when the sensor starts +- temp is 12 bit signed magnitude scaled by 10 celsius +- humi is 8 bit relative humidity percentage + +Based on +http://lucsmall.com/2012/04/29/weather-station-hacking-part-2/ +and +https://github.com/lucsmall/WH2-Weather-Sensor-Library-for-Arduino + +Comment detailing protocol from rtl_433 project. +*/ +#define FINEOFFSET_PLUGIN_ID 050 +#define PLUGIN_DESC_050 "FineOffset Temp/Humidity sensors" + +#ifdef PLUGIN_050 +#include "../4_Display.h" +#include "../1_Radio.h" +#include "../7_Utils.h" + +#define PLUGIN_050_ID "FineOffset" + +#define FINEOFFSET_PULSE_COUNT 96 + +#ifdef PLUGIN_050_DEBUG +#define SerialDebugActivated +#endif + +boolean Plugin_050(byte function, const char *string) +{ + if (RawSignal.Number == FINEOFFSET_PULSE_COUNT) + { + const int CM_LongLowMinDuration = 1200 / RawSignal.Multiply; + const int CM_LongLowMaxDuration = 1600 / RawSignal.Multiply; + const int CM_ShortHighMinDuration = 300 / RawSignal.Multiply; + const int CM_ShortHighMaxDuration = 600 / RawSignal.Multiply; + byte data[6] = {0, 0, 0, 0, 0, 0}; + //Skip the first bit as the lengths tend to vary... + if(!decode_pwm(data, 47, RawSignal.Pulses,RawSignal.Number, 3, CM_ShortHighMinDuration, CM_ShortHighMaxDuration, CM_LongLowMinDuration, CM_LongLowMaxDuration, 1)) + return false; + invert_bytes(data, 6); + byte calculated_crc = crc8(data + 1, 4, 0x31, 0); +#ifdef PLUGIN_050_DEBUG + const size_t buflen = sizeof(PLUGIN_050_ID ": packet = ") + 32; + char printbuf[buflen]; + snprintf(printbuf, buflen, "%s%02x%02x%02x%02x%02x%02x, CRC=%02x", PLUGIN_088_ID ": packet = ", data[0], data[1], data[2], data[3],data[4], data[5], calculated_crc); + SerialDebugPrintln(printbuf); +#endif + if(calculated_crc != data[5]) + { +#ifdef PLUGIN_050_DEBUG + SerialDebugPrintln(PLUGIN_050_ID ": CRC Check Failed"); +#endif + return false; + } + + uint16_t unitid = (data[1] << 4) | (data[2] >> 4); + uint16_t temperature = ((data[2] & 0xF) << 8) | data[3]; + //MSB of 12-bit number actually indicates sign, this isn't standard two's complement + if(temperature & 0x800) + temperature = - (temperature & 0x7FF); + byte humidity = data[4]; + display_Header(); + display_Name(PLUGIN_050_ID); + display_IDn(unitid, 4); // unit id + display_TEMP(temperature); + display_HUM(humidity); + display_Footer(); + } + return false; +} +#endif diff --git a/RFLink/Plugins/_Plugin_Config_01.h b/RFLink/Plugins/_Plugin_Config_01.h index 3e19d23..ebaca13 100644 --- a/RFLink/Plugins/_Plugin_Config_01.h +++ b/RFLink/Plugins/_Plugin_Config_01.h @@ -64,6 +64,7 @@ #define PLUGIN_047 // Auriol v4 #define PLUGIN_048 // Oregon Weather #define PLUGIN_049 // Lacrosse TX141 +#define PLUGIN_050 // FineOffset WH2 / ClimeMet CM9088 Temperature/Humidity // ------------------- // Motion Sensors, include when needed // -------------------