Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support to Ecowitt WH46 (device 262) #3010

Merged
merged 3 commits into from
Aug 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions include/rtl_433_devices.h
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@
DECL(thermopro_tp829b) \
DECL(arad_ms_meter) \
DECL(geevon) \
DECL(fineoffset_wh46) \

/* Add new decoders here. */

Expand Down
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ add_library(r_433 STATIC
devices/fineoffset_wh1080.c
devices/fineoffset_wh31l.c
devices/fineoffset_wh45.c
devices/fineoffset_wh46.c
devices/fineoffset_wh55.c
devices/fineoffset_wn34.c
devices/fineoffset_ws80.c
Expand Down
156 changes: 156 additions & 0 deletions src/devices/fineoffset_wh46.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/** @file
Fine Offset Electronics WH46 air quality sensor.

Based on fineoffset_wh45 from \@anthyz
Copyright (C) 2024 \@joanma747

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 "decoder.h"

/**
Fine Offset Electronics WH46 air quality sensor,

- also Ecowitt WH46

Preamble is aaaa aaaa, sync word is 2dd4.

Packet layout:

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
YY II II II 0T TT HH Bp pp BP PP CC CC qq qq QQ QQ ?? ?? XX AA
46 00 27 f1 02 b5 33 40 32 40 39 03 0b 00 2a 00 36 01 90 e4 16

- Y: 8 bit fixed sensor type 0x46
- I: 24 bit device ID
- T: 11 bit temperature, offset 40, scale 10
- H: 8 bit humidity
- B: 1 bit MSB of battery bars out of 5 (a value of 6 indicates external power via USB)
- p: 14 bit PM2.5 reading in ug/m3 * 10
- B: 2 bits LSBs of battery bars out of 5
- P: 14 bit PM10 reading in ug/m3 * 10
- C: 16 bit CO2 reading in ppm
- q: 14 bit PM1 reading in ug/m3 * 10
- Q: 14 bit PM4 reading in ug/m3 * 10
- ?: Constant value 0190. Might be version of a firmware or so.
- X: 8 bit CRC
- A: 8 bit checksum

The WH46 uses a Sensirion SPS30 sensor for PM1/PM2.5/PM4/PM10 and a
Sensirion SCD30 for CO2.

Technical documents for the SPS30 are here:

https://sensirion.com/products/catalog/SPS30

The sensor specification statement states that PM10 values are estimated
from distribution profiles of PM0.5, PM1.0, and PM2.5 measurements, but
the datasheet does a specify a degree of accuracy for the values unlike
the Honeywell sensor.

Technical documents for the SCD30 are here:

https://sensirion.com/products/catalog/SCD30/
*/

static int fineoffset_wh46_decode(r_device *decoder, bitbuffer_t *bitbuffer)
{
uint8_t const preamble[] = {0xaa, 0x2d, 0xd4}; // 24 bit, part of preamble and sync word
uint8_t b[21];

// Find a data package and extract data buffer
unsigned bit_offset = bitbuffer_search(bitbuffer, 0, 0, preamble, sizeof(preamble) * 8) + sizeof(preamble) * 8;

if (bit_offset + sizeof(b) * 8 > bitbuffer->bits_per_row[0]) { // Did not find a big enough package
decoder_logf_bitbuffer(decoder, 2, __func__, bitbuffer, "short package at %u", bit_offset);
return DECODE_ABORT_LENGTH;
}

// Extract package data
bitbuffer_extract_bytes(bitbuffer, 0, bit_offset, b, sizeof(b) * 8);

if (b[0] != 0x46) // Check for family code 0x46
return DECODE_ABORT_EARLY;

decoder_log_bitrow(decoder, 1, __func__, b, sizeof (b) * 8, "");

// Verify checksum and CRC
uint8_t crc = crc8(b, 19, 0x31, 0x00);
uint8_t chk = add_bytes(b, 20);
if (crc != b[19] || chk != b[20]) {
decoder_logf(decoder, 1, __func__, "Checksum error: %02x %02x", crc, chk);
return DECODE_FAIL_MIC;
}

int id = (b[1] << 16) | (b[2] << 8) | (b[3]);
int temp_raw = (b[4] & 0x7) << 8 | b[5];
float temp_c = (temp_raw - 400) * 0.1f; // range -40.0-60.0 C
int humidity = b[6];
int battery_bars = (b[7] & 0x40) >> 4 | (b[9] & 0xC0) >> 6;
// A battery bars value of 6 means the sensor is powered via USB (the Ecowitt WS View app shows 'DC')
int ext_power = battery_bars == 6 ? 1 : 0;
// Battery level is indicated with 5 bars. Convert to 0 (0 bars) to 1 (5 or 6 bars)
float battery_ok = MIN(battery_bars * 0.2f, 1.0f);
int pm2_5_raw = (b[7] & 0x3f) << 8 | b[8];
float pm2_5 = pm2_5_raw * 0.1f;
int pm10_raw = (b[9] & 0x3f) << 8 | b[10];
float pm10 = pm10_raw * 0.1f;
int co2 = (b[11] << 8) | b[12];
int pm1_raw = (b[13] << 8) | b[14];
float pm1 = pm1_raw * 0.1f;
int pm4_raw = (b[15] << 8) | b[16];
float pm4 = pm4_raw * 0.1f;
int unknown = (b[17] << 8) | b[18];

/* clang-format off */
data_t *data = data_make(
"model", "", DATA_STRING, "Fineoffset-WH46",
"id", "ID", DATA_FORMAT, "%06x", DATA_INT, id,
"battery_ok", "Battery Level", DATA_FORMAT, "%.1f", DATA_DOUBLE, battery_ok,
"temperature_C", "Temperature", DATA_FORMAT, "%.1f C", DATA_DOUBLE, temp_c,
"humidity", "Humidity", DATA_FORMAT, "%u %%", DATA_INT, humidity,
"pm1_ug_m3", "1um Fine PM", DATA_FORMAT, "%.1f ug/m3", DATA_DOUBLE, pm1,
"pm2_5_ug_m3", "2.5um Fine PM", DATA_FORMAT, "%.1f ug/m3", DATA_DOUBLE, pm2_5,
"pm4_ug_m3", "4um Coarse PM", DATA_FORMAT, "%.1f ug/m3", DATA_DOUBLE, pm4,
"pm10_ug_m3", "10um Coarse PM", DATA_FORMAT, "%.1f ug/m3", DATA_DOUBLE, pm10,
"co2_ppm", "Carbon Dioxide", DATA_FORMAT, "%d ppm", DATA_INT, co2,
"unknown", "Do not know", DATA_FORMAT, "%d ?", DATA_INT, unknown,
"ext_power", "External Power", DATA_INT, ext_power,
"mic", "Integrity", DATA_STRING, "CRC",
NULL);
/* clang-format on */

decoder_output_data(decoder, data);
return 1;
}

static char const *const output_fields[] = {
"model",
"id",
"battery_ok",
"temperature_C",
"humidity",
"pm1_ug_m3",
"pm2_5_ug_m3",
"pm4_ug_m3",
"pm10_ug_m3",
"co2_ppm",
"unknown",
"ext_power",
"mic",
NULL,
};

r_device const fineoffset_wh46 = {
.name = "Fine Offset Electronics WH46 air quality sensor",
.modulation = FSK_PULSE_PCM,
.short_width = 58,
.long_width = 58,
.reset_limit = 2500,
.decode_fn = &fineoffset_wh46_decode,
.fields = output_fields,
};
Loading