Added weather station app with 18 protocol parsers (#1607)

* Added weather station app with 18 protocol parsers
* Fix button and formatting
* Set BW to 1.75m, changed to us in dsp part
This commit is contained in:
Totoo 2023-11-28 21:11:30 +01:00 committed by GitHub
parent c486572d3d
commit 02251eeeb4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 3428 additions and 0 deletions

View File

@ -307,6 +307,7 @@ set(CPPSRC
apps/ui_touch_calibration.cpp
apps/ui_touchtunes.cpp
apps/ui_view_wav.cpp
apps/ui_weatherstation.cpp
apps/ui_whipcalc.cpp
protocols/aprs.cpp
protocols/ax25.cpp

View File

@ -0,0 +1,205 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2017 Furrtek
*
* This file is part of PortaPack.
*
* 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, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "ui_weatherstation.hpp"
#include "modems.hpp"
#include "audio.hpp"
#include "baseband_api.hpp"
#include "string_format.hpp"
#include "portapack_persistent_memory.hpp"
using namespace portapack;
using namespace ui;
namespace ui {
void WeatherRecentEntryDetailView::update_data() {
// set text elements
text_type.set(WeatherView::getWeatherSensorTypeName((FPROTO_WEATHER_SENSOR)entry_.sensorType));
text_id.set("0x" + to_string_hex(entry_.id));
text_temp.set(to_string_decimal(entry_.temp, 2));
text_hum.set(to_string_dec_uint(entry_.humidity) + "%");
text_ch.set(to_string_dec_uint(entry_.channel));
text_batt.set(to_string_dec_uint(entry_.battery_low) + " " + ((entry_.battery_low == 0) ? "OK" : "LOW"));
}
void WeatherRecentEntryDetailView::set_entry(const WeatherRecentEntry& entry) {
entry_ = entry;
update_data();
set_dirty();
}
WeatherRecentEntryDetailView::WeatherRecentEntryDetailView(NavigationView& nav, const WeatherRecentEntry& entry)
: nav_{nav},
entry_{entry} {
add_children({&button_done,
&text_type,
&text_id,
&text_temp,
&text_hum,
&text_ch,
&text_batt,
&labels});
button_done.on_select = [&nav](const ui::Button&) {
nav.pop();
};
update_data();
}
void WeatherRecentEntryDetailView::focus() {
button_done.focus();
}
void WeatherView::focus() {
field_frequency.focus();
}
WeatherView::WeatherView(NavigationView& nav)
: nav_{nav} {
add_children({&rssi,
&field_rf_amp,
&field_lna,
&field_vga,
&field_frequency,
&button_clear_list,
&recent_entries_view});
baseband::run_image(portapack::spi_flash::image_tag_weather);
button_clear_list.on_select = [this](Button&) {
recent.clear();
recent_entries_view.set_dirty();
};
field_frequency.set_value(433920000);
field_frequency.set_step(1000);
const Rect content_rect{0, header_height, screen_width, screen_height - header_height};
recent_entries_view.set_parent_rect(content_rect);
recent_entries_view.on_select = [this](const WeatherRecentEntry& entry) {
nav_.push<WeatherRecentEntryDetailView>(entry);
};
receiver_model.set_target_frequency(433'920'000);
receiver_model.set_sampling_rate(2'000'000);
receiver_model.set_baseband_bandwidth(1'750'000);
receiver_model.set_modulation(ReceiverModel::Mode::AMAudio);
baseband::set_weather();
receiver_model.enable();
}
void WeatherView::on_data(const WeatherDataMessage* data) {
WeatherRecentEntry key{data->sensorType, data->id, data->temp, data->humidity, data->channel, data->battery_low};
auto matching_recent = find(recent, key.key());
if (matching_recent != std::end(recent)) {
// Found within. Move to front of list, increment counter.
recent.push_front(*matching_recent);
recent.erase(matching_recent);
} else {
recent.emplace_front(key);
truncate_entries(recent, 64);
}
recent_entries_view.set_dirty();
}
WeatherView::~WeatherView() {
receiver_model.disable();
baseband::shutdown();
}
const char* WeatherView::getWeatherSensorTypeName(FPROTO_WEATHER_SENSOR type) {
switch (type) {
case FPW_NexusTH:
return "NexusTH";
case FPW_Acurite592TXR:
return "Acurite592TXR";
case FPW_Acurite606TX:
return "Acurite606TX";
case FPW_Acurite609TX:
return "Acurite609TX";
case FPW_Ambient:
return "Ambient";
case FPW_AuriolAhfl:
return "AuriolAhfl";
case FPW_AuriolTH:
return "AuriolTH";
case FPW_GTWT02:
return "GT-WT02";
case FPW_GTWT03:
return "GT-WT03";
case FPW_INFACTORY:
return "InFactory";
case FPW_LACROSSETX:
return "LaCrosse TX";
case FPW_LACROSSETX141thbv2:
return "LaCrosse TX141THBv2";
case FPW_OREGON2:
return "Oregon2";
case FPW_OREGON3:
return "Oregon3";
case FPW_OREGONv1:
return "OregonV1";
case FPW_THERMOPROTX4:
return "ThermoPro TX4";
case FPW_TX_8300:
return "TX 8300";
case FPW_WENDOX_W6726:
return "Wendox W6726";
case FPW_Invalid:
default:
return "Unknown";
}
}
std::string WeatherView::pad_string_with_spaces(int snakes) {
std::string paddedStr(snakes, ' ');
return paddedStr;
}
template <>
void RecentEntriesTable<ui::WeatherRecentEntries>::draw(
const Entry& entry,
const Rect& target_rect,
Painter& painter,
const Style& style) {
std::string line{};
line.reserve(30);
line = WeatherView::getWeatherSensorTypeName((FPROTO_WEATHER_SENSOR)entry.sensorType);
if (line.length() < 13) {
line += WeatherView::pad_string_with_spaces(13 - line.length());
} else {
line = truncate(line, 13);
}
std::string temp = to_string_decimal(entry.temp, 2);
std::string humStr = to_string_dec_uint(entry.humidity) + "%";
std::string chStr = to_string_dec_uint(entry.channel);
line += WeatherView::pad_string_with_spaces(7 - temp.length()) + temp;
line += WeatherView::pad_string_with_spaces(5 - humStr.length()) + humStr;
line += WeatherView::pad_string_with_spaces(4 - chStr.length()) + chStr;
line.resize(target_rect.width() / 8, ' ');
painter.draw_string(target_rect.location(), style, line);
}
} // namespace ui

View File

@ -0,0 +1,172 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2017 Furrtek
*
* This file is part of PortaPack.
*
* 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, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifndef __UI_WEATHER_H__
#define __UI_WEATHER_H__
#include "ui.hpp"
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
#include "ui_freq_field.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "utility.hpp"
#include "recent_entries.hpp"
#include "../baseband/fprotos/weathertypes.hpp"
using namespace ui;
namespace ui {
struct WeatherRecentEntry {
using Key = uint64_t;
static constexpr Key invalid_key = 0x0fffffff; // todo calc the invalid all
uint8_t sensorType = FPW_Invalid;
uint32_t id = 0xFFFFFFFF;
float temp = -273.0f;
uint8_t humidity = 0xFF;
uint8_t battery_low = 0xFF;
uint8_t channel = 0xFF;
WeatherRecentEntry() {}
WeatherRecentEntry(
uint8_t sensorType,
uint32_t id,
float temp,
uint8_t humidity,
uint8_t channel,
uint8_t battery_low = 0xff)
: sensorType{sensorType},
id{id},
temp{temp},
humidity{humidity},
battery_low{battery_low},
channel{channel} {
}
Key key() const {
return (((static_cast<uint64_t>(temp * 10) & 0xFFFF) << 48) ^ static_cast<uint64_t>(id) << 24) |
(static_cast<uint64_t>(sensorType) & 0xFF) << 16 |
(static_cast<uint64_t>(humidity) & 0xFF) << 8 |
(static_cast<uint64_t>(battery_low) & 0xF) << 4 |
(static_cast<uint64_t>(channel) & 0xF);
}
};
using WeatherRecentEntries = RecentEntries<WeatherRecentEntry>;
using WeatherRecentEntriesView = RecentEntriesView<WeatherRecentEntries>;
class WeatherView : public View {
public:
WeatherView(NavigationView& nav);
~WeatherView();
void focus() override;
std::string title() const override { return "Weather"; };
static const char* getWeatherSensorTypeName(FPROTO_WEATHER_SENSOR type);
static std::string pad_string_with_spaces(int snakes);
private:
void on_data(const WeatherDataMessage* data);
NavigationView& nav_;
RxRadioState radio_state_{
433'920'000 /* frequency */,
1'750'000 /* bandwidth */,
2'000'000 /* sampling rate */,
ReceiverModel::Mode::SpectrumAnalysis};
app_settings::SettingsManager settings_{
"rx_weather", app_settings::Mode::RX};
WeatherRecentEntries recent{};
RFAmpField field_rf_amp{
{13 * 8, 0 * 16}};
LNAGainField field_lna{
{15 * 8, 0 * 16}};
VGAGainField field_vga{
{18 * 8, 0 * 16}};
RSSI rssi{
{21 * 8, 0, 6 * 8, 4}};
RxFrequencyField field_frequency{
{0 * 8, 0 * 16},
nav_};
Button button_clear_list{
{0, 16, 7 * 8, 32},
"Clear"};
static constexpr auto header_height = 3 * 16;
const RecentEntriesColumns columns{{
{"Type", 13},
{"Temp", 6},
{"Hum", 4},
{"Ch", 3},
}};
WeatherRecentEntriesView recent_entries_view{columns, recent};
MessageHandlerRegistration message_handler_packet{
Message::ID::WeatherData,
[this](Message* const p) {
const auto message = static_cast<const WeatherDataMessage*>(p);
this->on_data(message);
}};
};
class WeatherRecentEntryDetailView : public View {
public:
WeatherRecentEntryDetailView(NavigationView& nav, const WeatherRecentEntry& entry);
void set_entry(const WeatherRecentEntry& new_entry);
const WeatherRecentEntry& entry() const { return entry_; };
void update_data();
void focus() override;
private:
NavigationView& nav_;
WeatherRecentEntry entry_{};
Text text_type{{0 * 8, 1 * 16, 15 * 8, 16}, "?"};
Text text_id{{6 * 8, 2 * 16, 10 * 8, 16}, "?"};
Text text_temp{{6 * 8, 3 * 16, 8 * 8, 16}, "?"};
Text text_hum{{11 * 8, 4 * 16, 6 * 8, 16}, "?"};
Text text_ch{{11 * 8, 5 * 16, 6 * 8, 16}, "?"};
Text text_batt{{11 * 8, 6 * 16, 6 * 8, 16}, "?"};
Labels labels{
{{0 * 8, 0 * 16}, "Weather station type:", Color::light_grey()},
{{0 * 8, 2 * 16}, "Id: ", Color::light_grey()},
{{0 * 8, 3 * 16}, "Temp:", Color::light_grey()},
{{0 * 8, 4 * 16}, "Humidity:", Color::light_grey()},
{{0 * 8, 5 * 16}, "Channel:", Color::light_grey()},
{{0 * 8, 6 * 16}, "Battery:", Color::light_grey()},
};
Button button_done{
{screen_width - 96 - 4, screen_height - 32 - 12, 96, 32},
"Done"};
};
} // namespace ui
#endif /*__UI_WEATHER_H__*/

View File

@ -319,6 +319,11 @@ void set_spectrum_painter_config(const uint16_t width, const uint16_t height, bo
send_message(&message);
}
void set_weather() {
const WeatherRxConfigureMessage message{};
send_message(&message);
}
static bool baseband_image_running = false;
void run_image(const spi_flash::image_tag_t image_tag) {

View File

@ -88,6 +88,7 @@ void set_spectrum(const size_t sampling_rate, const size_t trigger);
void set_siggen_tone(const uint32_t tone);
void set_siggen_config(const uint32_t bw, const uint32_t shape, const uint32_t duration);
void set_spectrum_painter_config(const uint16_t width, const uint16_t height, bool update, int32_t bw);
void set_weather();
void request_beep();
void run_image(const portapack::spi_flash::image_tag_t image_tag);

View File

@ -79,6 +79,7 @@
#include "ui_tone_search.hpp"
#include "ui_touchtunes.hpp"
#include "ui_view_wav.hpp"
#include "ui_weatherstation.hpp"
#include "ui_whipcalc.hpp"
#include "ui_external_items_menu_loader.hpp"
@ -565,6 +566,7 @@ ReceiversMenuView::ReceiversMenuView(NavigationView& nav) {
{"Recon", Color::green(), &bitmap_icon_scanner, [&nav]() { nav.push<ReconView>(); }},
{"Search", Color::yellow(), &bitmap_icon_search, [&nav]() { nav.push<SearchView>(); }},
{"TPMS Cars", Color::green(), &bitmap_icon_tpms, [&nav]() { nav.push<TPMSAppView>(); }},
{"Weather", Color::green(), &bitmap_icon_lge, [&nav]() { nav.push<WeatherView>(); }},
// {"FSK RX", Color::yellow(), &bitmap_icon_remote, [&nav]() { nav.push<FskxRxMainView>(); }},
// {"DMR", Color::dark_grey(), &bitmap_icon_dmr, [&nav](){ nav.push<NotImplementedView>(); }},
// {"SIGFOX", Color::dark_grey(), &bitmap_icon_fox, [&nav](){ nav.push<NotImplementedView>(); }},

View File

@ -550,6 +550,14 @@ set(MODE_CPPSRC
)
DeclareTargets(PWFM wfm_audio)
### Weather Stations
set(MODE_CPPSRC
proc_weather.cpp
)
DeclareTargets(PWTH weather)
### Flash Utility
include(${CHIBIOS_PORTAPACK}/os/various/fatfs_bindings/fatfs.cmake)

View File

@ -0,0 +1,155 @@
#ifndef __FPROTO_Acurite_592TXR_H__
#define __FPROTO_Acurite_592TXR_H__
#include "weatherbase.hpp"
typedef enum {
Acurite_592TXRDecoderStepReset = 0,
Acurite_592TXRDecoderStepCheckPreambule,
Acurite_592TXRDecoderStepSaveDuration,
Acurite_592TXRDecoderStepCheckDuration,
} Acurite_592TXRDecoderStep;
class FProtoWeatherAcurite592TXR : public FProtoWeatherBase {
public:
FProtoWeatherAcurite592TXR() {
sensorType = FPW_Acurite592TXR;
}
void feed(bool level, uint32_t duration) {
switch (parser_step) {
case Acurite_592TXRDecoderStepReset:
if ((level) && (DURATION_DIFF(duration, te_short * 3) <
te_delta * 2)) {
parser_step = Acurite_592TXRDecoderStepCheckPreambule;
te_last = duration;
header_count = 0;
}
break;
case Acurite_592TXRDecoderStepCheckPreambule:
if (level) {
te_last = duration;
} else {
if ((DURATION_DIFF(
te_last, te_short * 3) <
te_delta * 2) &&
(DURATION_DIFF(duration, te_short * 3) <
te_delta * 2)) {
// Found preambule
header_count++;
} else if ((header_count > 2) && (header_count < 5)) {
if ((DURATION_DIFF(te_last, te_short) < te_delta) &&
(DURATION_DIFF(duration, te_long) < te_delta)) {
decode_data = 0;
decode_count_bit = 0;
subghz_protocol_blocks_add_bit(0);
parser_step = Acurite_592TXRDecoderStepSaveDuration;
} else if (
(DURATION_DIFF(
te_last, te_long) <
te_delta) &&
(DURATION_DIFF(duration, te_short) <
te_delta)) {
decode_data = 0;
decode_count_bit = 0;
subghz_protocol_blocks_add_bit(1);
parser_step = Acurite_592TXRDecoderStepSaveDuration;
} else {
parser_step = Acurite_592TXRDecoderStepReset;
}
} else {
parser_step = Acurite_592TXRDecoderStepReset;
}
}
break;
case Acurite_592TXRDecoderStepSaveDuration:
if (level) {
te_last = duration;
parser_step = Acurite_592TXRDecoderStepCheckDuration;
} else {
parser_step = Acurite_592TXRDecoderStepReset;
}
break;
case Acurite_592TXRDecoderStepCheckDuration:
if (!level) {
if (duration >= ((uint32_t)te_short * 5)) {
if ((decode_count_bit ==
min_count_bit_for_found) &&
ws_protocol_acurite_592txr_check_crc()) {
data = decode_data;
data_count_bit = decode_count_bit;
ws_protocol_acurite_592txr_remote_controller();
if (callback) callback(this);
}
decode_data = 0;
decode_count_bit = 0;
parser_step = Acurite_592TXRDecoderStepReset;
break;
} else if (
(DURATION_DIFF(
te_last, te_short) <
te_delta) &&
(DURATION_DIFF(duration, te_long) <
te_delta)) {
subghz_protocol_blocks_add_bit(0);
parser_step = Acurite_592TXRDecoderStepSaveDuration;
} else if (
(DURATION_DIFF(
te_last, te_long) <
te_delta) &&
(DURATION_DIFF(duration, te_short) <
te_delta)) {
subghz_protocol_blocks_add_bit(1);
parser_step = Acurite_592TXRDecoderStepSaveDuration;
} else {
parser_step = Acurite_592TXRDecoderStepReset;
}
} else {
parser_step = Acurite_592TXRDecoderStepReset;
}
break;
}
}
protected:
uint32_t te_short = 200;
uint32_t te_long = 400;
uint32_t te_delta = 90;
uint32_t min_count_bit_for_found = 56;
bool ws_protocol_acurite_592txr_check_crc() {
uint8_t msg[] = {
static_cast<uint8_t>(decode_data >> 48),
static_cast<uint8_t>(decode_data >> 40),
static_cast<uint8_t>(decode_data >> 32),
static_cast<uint8_t>(decode_data >> 24),
static_cast<uint8_t>(decode_data >> 16),
static_cast<uint8_t>(decode_data >> 8)};
if ((subghz_protocol_blocks_add_bytes(msg, 6) ==
(uint8_t)(decode_data & 0xFF)) &&
(!subghz_protocol_blocks_parity_bytes(&msg[2], 4))) {
return true;
} else {
return false;
}
}
void ws_protocol_acurite_592txr_remote_controller() {
uint8_t channel_[] = {3, 0, 2, 1};
uint8_t channel_raw = ((data >> 54) & 0x03);
channel = channel_[channel_raw];
id = (data >> 40) & 0x3FFF;
battery_low = !((data >> 38) & 1);
humidity = (data >> 24) & 0x7F;
uint16_t temp_raw = ((data >> 9) & 0xF80) | ((data >> 8) & 0x7F);
temp = ((float)(temp_raw)-1000) / 10.0f;
btn = WS_NO_BTN;
}
};
#endif

View File

@ -0,0 +1,110 @@
#ifndef __FPROTO_Acurite_606TX_H__
#define __FPROTO_Acurite_606TX_H__
#include "weatherbase.hpp"
typedef enum {
Acurite_606TXDecoderStepReset = 0,
Acurite_606TXDecoderStepSaveDuration,
Acurite_606TXDecoderStepCheckDuration,
} Acurite_606TXDecoderStep;
class FProtoWeatherAcurite606TX : public FProtoWeatherBase {
public:
FProtoWeatherAcurite606TX() {
sensorType = FPW_Acurite606TX;
}
void feed(bool level, uint32_t duration) {
switch (parser_step) {
case Acurite_606TXDecoderStepReset:
if ((!level) && (DURATION_DIFF(duration, te_short * 17) < te_delta * 8)) {
// Found syncPrefix
parser_step = Acurite_606TXDecoderStepSaveDuration;
decode_data = 0;
decode_count_bit = 0;
}
break;
case Acurite_606TXDecoderStepSaveDuration:
if (level) {
te_last = duration;
parser_step = Acurite_606TXDecoderStepCheckDuration;
} else {
parser_step = Acurite_606TXDecoderStepReset;
}
break;
case Acurite_606TXDecoderStepCheckDuration:
if (!level) {
if (DURATION_DIFF(te_last, te_short) <
te_delta) {
if ((DURATION_DIFF(duration, te_short) <
te_delta) ||
(duration > te_long * 3)) {
// Found syncPostfix
parser_step = Acurite_606TXDecoderStepReset;
if ((decode_count_bit ==
min_count_bit_for_found) &&
ws_protocol_acurite_606tx_check()) {
data = decode_data;
data_count_bit = decode_count_bit;
ws_protocol_acurite_606tx_remote_controller();
if (callback) callback(this);
}
decode_data = 0;
decode_count_bit = 0;
} else if (
DURATION_DIFF(duration, te_long) <
te_delta * 2) {
subghz_protocol_blocks_add_bit(0);
parser_step = Acurite_606TXDecoderStepSaveDuration;
} else if (
DURATION_DIFF(duration, te_long * 2) < te_delta * 4) {
subghz_protocol_blocks_add_bit(1);
parser_step = Acurite_606TXDecoderStepSaveDuration;
} else {
parser_step = Acurite_606TXDecoderStepReset;
}
} else {
parser_step = Acurite_606TXDecoderStepReset;
}
} else {
parser_step = Acurite_606TXDecoderStepReset;
}
break;
}
}
protected:
uint32_t te_short = 500;
uint32_t te_long = 2000;
uint32_t te_delta = 150;
uint32_t min_count_bit_for_found = 32;
void ws_protocol_acurite_606tx_remote_controller() {
id = (data >> 24) & 0xFF;
battery_low = (data >> 23) & 1;
channel = WS_NO_CHANNEL;
if (!((data >> 19) & 1)) {
temp = (float)((data >> 8) & 0x07FF) / 10.0f;
} else {
temp = (float)((~(data >> 8) & 0x07FF) + 1) / -10.0f;
}
btn = WS_NO_BTN;
humidity = WS_NO_HUMIDITY;
}
bool ws_protocol_acurite_606tx_check() {
if (!decode_data) return false;
uint8_t msg[] = {
static_cast<uint8_t>(decode_data >> 24),
static_cast<uint8_t>(decode_data >> 16),
static_cast<uint8_t>(decode_data >> 8)};
uint8_t crc = subghz_protocol_blocks_lfsr_digest8(msg, 3, 0x98, 0xF1);
return (crc == (decode_data & 0xFF));
}
};
#endif

View File

@ -0,0 +1,112 @@
#ifndef __FPROTO_Acurite_609TX_H__
#define __FPROTO_Acurite_609TX_H__
#include "weatherbase.hpp"
typedef enum {
Acurite_609TXCDecoderStepReset = 0,
Acurite_609TXCDecoderStepSaveDuration,
Acurite_609TXCDecoderStepCheckDuration,
} Acurite_609TXCDecoderStep;
class FProtoWeatherAcurite609TX : public FProtoWeatherBase {
public:
FProtoWeatherAcurite609TX() {
sensorType = FPW_Acurite609TX;
}
void feed(bool level, uint32_t duration) {
switch (parser_step) {
case Acurite_609TXCDecoderStepReset:
if ((!level) && (DURATION_DIFF(duration, te_short * 17) <
te_delta * 8)) {
// Found syncPrefix
parser_step = Acurite_609TXCDecoderStepSaveDuration;
decode_data = 0;
decode_count_bit = 0;
}
break;
case Acurite_609TXCDecoderStepSaveDuration:
if (level) {
te_last = duration;
parser_step = Acurite_609TXCDecoderStepCheckDuration;
} else {
parser_step = Acurite_609TXCDecoderStepReset;
}
break;
case Acurite_609TXCDecoderStepCheckDuration:
if (!level) {
if (DURATION_DIFF(te_last, te_short) <
te_delta) {
if ((DURATION_DIFF(duration, te_short) <
te_delta) ||
(duration > te_long * 3)) {
// Found syncPostfix
parser_step = Acurite_609TXCDecoderStepReset;
if ((decode_count_bit ==
min_count_bit_for_found) &&
ws_protocol_acurite_609txc_check()) {
data = decode_data;
data_count_bit = decode_count_bit;
ws_protocol_acurite_609txc_remote_controller();
if (callback) callback(this);
}
decode_data = 0;
decode_count_bit = 0;
} else if (
DURATION_DIFF(duration, te_long) <
te_delta * 2) {
subghz_protocol_blocks_add_bit(0);
parser_step = Acurite_609TXCDecoderStepSaveDuration;
} else if (
DURATION_DIFF(duration, te_long * 2) <
te_delta * 4) {
subghz_protocol_blocks_add_bit(1);
parser_step = Acurite_609TXCDecoderStepSaveDuration;
} else {
parser_step = Acurite_609TXCDecoderStepReset;
}
} else {
parser_step = Acurite_609TXCDecoderStepReset;
}
} else {
parser_step = Acurite_609TXCDecoderStepReset;
}
break;
}
}
protected:
uint32_t te_short = 500;
uint32_t te_long = 2000;
uint32_t te_delta = 150;
uint32_t min_count_bit_for_found = 32;
void ws_protocol_acurite_609txc_remote_controller() {
id = (data >> 32) & 0xFF;
battery_low = (data >> 31) & 1;
channel = WS_NO_CHANNEL;
// Temperature in Celsius is encoded as a 12 bit integer value
// multiplied by 10 using the 4th - 6th nybbles (bytes 1 & 2)
// negative values are recovered by sign extend from int16_t.
int16_t temp_raw =
(int16_t)(((data >> 12) & 0xf000) | ((data >> 16) << 4));
temp = (temp_raw >> 4) * 0.1f;
humidity = (data >> 8) & 0xff;
btn = WS_NO_BTN;
}
bool ws_protocol_acurite_609txc_check() {
if (!decode_data) return false;
uint8_t crc = (uint8_t)(decode_data >> 32) +
(uint8_t)(decode_data >> 24) +
(uint8_t)(decode_data >> 16) +
(uint8_t)(decode_data >> 8);
return (crc == (decode_data & 0xFF));
}
};
#endif

View File

@ -0,0 +1,88 @@
#ifndef __FPROTO_Ambient_H__
#define __FPROTO_Ambient_H__
#include "weatherbase.hpp"
#define AMBIENT_WEATHER_PACKET_HEADER_1 0xFFD440000000000 // 0xffd45 .. 0xffd46
#define AMBIENT_WEATHER_PACKET_HEADER_2 0x001440000000000 // 0x00145 .. 0x00146
#define AMBIENT_WEATHER_PACKET_HEADER_MASK 0xFFFFC0000000000
class FProtoWeatherAmbient : public FProtoWeatherBase {
public:
FProtoWeatherAmbient() {
sensorType = FPW_Ambient;
}
void feed(bool level, uint32_t duration) {
ManchesterEvent event = ManchesterEventReset;
if (!level) {
if (DURATION_DIFF(duration, te_short) < te_delta) {
event = ManchesterEventShortLow;
} else if (
DURATION_DIFF(duration, te_long) < te_delta * 2) {
event = ManchesterEventLongLow;
}
} else {
if (DURATION_DIFF(duration, te_short) < te_delta) {
event = ManchesterEventShortHigh;
} else if (
DURATION_DIFF(duration, te_long) < te_delta * 2) {
event = ManchesterEventLongHigh;
}
}
if (event != ManchesterEventReset) {
bool data;
bool data_ok = manchester_advance(manchester_saved_state, event, &manchester_saved_state, &data);
if (data_ok) {
decode_data = (decode_data << 1) | !data;
}
if (((decode_data & AMBIENT_WEATHER_PACKET_HEADER_MASK) == AMBIENT_WEATHER_PACKET_HEADER_1) ||
((decode_data & AMBIENT_WEATHER_PACKET_HEADER_MASK) == AMBIENT_WEATHER_PACKET_HEADER_2)) {
if (ws_protocol_ambient_weather_check_crc()) {
data = decode_data;
data_count_bit = min_count_bit_for_found;
ws_protocol_ambient_weather_remote_controller();
if (callback) callback(this);
decode_data = 0;
decode_count_bit = 0;
}
}
} else {
decode_data = 0;
decode_count_bit = 0;
manchester_advance(manchester_saved_state, ManchesterEventReset, &manchester_saved_state, NULL);
}
}
protected:
uint32_t te_short = 500;
uint32_t te_long = 1000;
uint32_t te_delta = 120;
uint32_t min_count_bit_for_found = 48;
bool ws_protocol_ambient_weather_check_crc() {
uint8_t msg[] = {
static_cast<uint8_t>(decode_data >> 40),
static_cast<uint8_t>(decode_data >> 32),
static_cast<uint8_t>(decode_data >> 24),
static_cast<uint8_t>(decode_data >> 16),
static_cast<uint8_t>(decode_data >> 8)};
uint8_t crc = subghz_protocol_blocks_lfsr_digest8(msg, 5, 0x98, 0x3e) ^ 0x64;
return (crc == (uint8_t)(decode_data & 0xFF));
}
void ws_protocol_ambient_weather_remote_controller() {
id = (data >> 32) & 0xFF;
battery_low = (data >> 31) & 1;
channel = ((data >> 28) & 0x07) + 1;
temp = locale_fahrenheit_to_celsius(((float)((data >> 16) & 0x0FFF) - 400.0f) / 10.0f);
humidity = (data >> 8) & 0xFF;
btn = WS_NO_BTN;
}
};
#endif

View File

@ -0,0 +1,121 @@
#ifndef __FPROTO_AuriolAhfl_H__
#define __FPROTO_AuriolAhfl_H__
#include "weatherbase.hpp"
#define AURIOL_AHFL_CONST_DATA 0b0100
typedef enum {
auriol_AHFLDecoderStepReset = 0,
auriol_AHFLDecoderStepSaveDuration,
auriol_AHFLDecoderStepCheckDuration,
} auriol_AHFLDecoderStep;
class FProtoWeatherAuriolAhfl : public FProtoWeatherBase {
public:
FProtoWeatherAuriolAhfl() {
sensorType = FPW_AuriolAhfl;
}
void feed(bool level, uint32_t duration) {
switch (parser_step) {
case auriol_AHFLDecoderStepReset:
if ((!level) && (DURATION_DIFF(duration, te_short * 18) <
te_delta)) {
// Found syncPrefix
parser_step = auriol_AHFLDecoderStepSaveDuration;
decode_data = 0;
decode_count_bit = 0;
}
break;
case auriol_AHFLDecoderStepSaveDuration:
if (level) {
te_last = duration;
parser_step = auriol_AHFLDecoderStepCheckDuration;
} else {
parser_step = auriol_AHFLDecoderStepReset;
}
break;
case auriol_AHFLDecoderStepCheckDuration:
if (!level) {
if (DURATION_DIFF(te_last, te_short) <
te_delta) {
if (DURATION_DIFF(duration, te_short * 18) <
te_delta * 8) {
// Found syncPostfix
parser_step = auriol_AHFLDecoderStepReset;
if ((decode_count_bit ==
min_count_bit_for_found) &&
ws_protocol_auriol_ahfl_check()) {
data = decode_data;
data_count_bit = decode_count_bit;
ws_protocol_auriol_ahfl_remote_controller();
if (callback) {
callback(this);
}
} else if (decode_count_bit == 1) {
parser_step = auriol_AHFLDecoderStepSaveDuration;
}
decode_data = 0;
decode_count_bit = 0;
} else if (
DURATION_DIFF(duration, te_long) <
te_delta * 2) {
subghz_protocol_blocks_add_bit(0);
parser_step = auriol_AHFLDecoderStepSaveDuration;
} else if (
DURATION_DIFF(duration, te_long * 2) <
te_delta * 4) {
subghz_protocol_blocks_add_bit(1);
parser_step = auriol_AHFLDecoderStepSaveDuration;
} else {
parser_step = auriol_AHFLDecoderStepReset;
}
} else {
parser_step = auriol_AHFLDecoderStepReset;
}
} else {
parser_step = auriol_AHFLDecoderStepReset;
}
break;
}
}
protected:
uint32_t te_short = 500;
uint32_t te_long = 2000;
uint32_t te_delta = 150;
uint32_t min_count_bit_for_found = 42;
void ws_protocol_auriol_ahfl_remote_controller() {
id = data >> 34;
battery_low = (data >> 33) & 1;
btn = (data >> 32) & 1;
channel = ((data >> 30) & 0x3) + 1;
if (!((data >> 29) & 1)) {
temp = (float)((data >> 18) & 0x07FF) / 10.0f;
} else {
temp = (float)((~(data >> 18) & 0x07FF) + 1) / -10.0f;
}
humidity = (data >> 11) & 0x7F;
}
bool ws_protocol_auriol_ahfl_check() {
uint8_t type = (decode_data >> 6) & 0x0F;
if (type != AURIOL_AHFL_CONST_DATA) {
// Fail const data check
return false;
}
uint64_t payload = decode_data >> 6;
// Checksum is the last 6 bits of data
uint8_t checksum_received = decode_data & 0x3F;
uint8_t checksum_calculated = 0;
for (uint8_t i = 0; i < 9; i++) {
checksum_calculated += (payload >> (i * 4)) & 0xF;
}
return checksum_received == checksum_calculated;
}
};
#endif

View File

@ -0,0 +1,115 @@
#ifndef __FPROTO_AuriolTH_H__
#define __FPROTO_AuriolTH_H__
#include "weatherbase.hpp"
#define AURIOL_TH_CONST_DATA 0b1110
typedef enum {
auriol_THDecoderStepReset = 0,
auriol_THDecoderStepSaveDuration,
auriol_THDecoderStepCheckDuration,
} auriol_THDecoderStep;
class FProtoWeatherAuriolTh : public FProtoWeatherBase {
public:
FProtoWeatherAuriolTh() {
sensorType = FPW_AuriolTH;
}
void feed(bool level, uint32_t duration) {
switch (parser_step) {
case auriol_THDecoderStepReset:
if ((!level) && (DURATION_DIFF(duration, te_short * 8) < te_delta)) {
// Found sync
parser_step = auriol_THDecoderStepSaveDuration;
decode_data = 0;
decode_count_bit = 0;
}
break;
case auriol_THDecoderStepSaveDuration:
if (level) {
te_last = duration;
parser_step = auriol_THDecoderStepCheckDuration;
} else {
parser_step = auriol_THDecoderStepReset;
}
break;
case auriol_THDecoderStepCheckDuration:
if (!level) {
if (DURATION_DIFF(duration, te_short * 8) <
te_delta) {
// Found sync
parser_step = auriol_THDecoderStepReset;
if ((decode_count_bit ==
min_count_bit_for_found) &&
ws_protocol_auriol_th_check()) {
data = decode_data;
data_count_bit = decode_count_bit;
ws_protocol_auriol_th_remote_controller();
if (callback) callback(this);
parser_step = auriol_THDecoderStepCheckDuration;
}
decode_data = 0;
decode_count_bit = 0;
break;
} else if (
(DURATION_DIFF(te_last, te_short) <
te_delta) &&
(DURATION_DIFF(duration, te_short * 2) <
te_delta)) {
subghz_protocol_blocks_add_bit(0);
parser_step = auriol_THDecoderStepSaveDuration;
} else if (
(DURATION_DIFF(te_last, te_short) <
te_delta) &&
(DURATION_DIFF(duration, te_short * 4) <
te_delta * 2)) {
subghz_protocol_blocks_add_bit(1);
parser_step = auriol_THDecoderStepSaveDuration;
} else {
parser_step = auriol_THDecoderStepReset;
}
} else {
parser_step = auriol_THDecoderStepReset;
}
break;
}
}
protected:
uint32_t te_short = 500;
uint32_t te_long = 2000;
uint32_t te_delta = 150;
uint32_t min_count_bit_for_found = 37;
void ws_protocol_auriol_th_remote_controller() {
id = (data >> 31) & 0xFF;
battery_low = ((data >> 30) & 1);
channel = ((data >> 25) & 0x03) + 1;
btn = WS_NO_BTN;
if (!((data >> 23) & 1)) {
temp = (float)((data >> 13) & 0x07FF) / 10.0f;
} else {
temp = (float)((~(data >> 13) & 0x07FF) + 1) / -10.0f;
}
humidity = (data >> 1) & 0x7F;
}
bool ws_protocol_auriol_th_check() {
uint8_t type = (decode_data >> 8) & 0x0F;
if ((type == AURIOL_TH_CONST_DATA) && ((decode_data >> 4) != 0xffffffff)) {
return true;
} else {
return false;
}
return true;
}
};
#endif

View File

@ -0,0 +1,121 @@
#ifndef __FPROTO_GTWT02_H__
#define __FPROTO_GTWT02_H__
#include "weatherbase.hpp"
#define AURIOL_AHFL_CONST_DATA 0b0100
typedef enum {
GT_WT02DecoderStepReset = 0,
GT_WT02DecoderStepSaveDuration,
GT_WT02DecoderStepCheckDuration,
} GT_WT02DecoderStep;
class FProtoWeatherGTWT02 : public FProtoWeatherBase {
public:
FProtoWeatherGTWT02() {
sensorType = FPW_GTWT02;
}
void feed(bool level, uint32_t duration) {
switch (parser_step) {
case GT_WT02DecoderStepReset:
if ((!level) && (DURATION_DIFF(duration, te_short * 18) <
te_delta * 8)) {
// Found syncPrefix
parser_step = GT_WT02DecoderStepSaveDuration;
decode_data = 0;
decode_count_bit = 0;
}
break;
case GT_WT02DecoderStepSaveDuration:
if (level) {
te_last = duration;
parser_step = GT_WT02DecoderStepCheckDuration;
} else {
parser_step = GT_WT02DecoderStepReset;
}
break;
case GT_WT02DecoderStepCheckDuration:
if (!level) {
if (DURATION_DIFF(te_last, te_short) <
te_delta) {
if (DURATION_DIFF(duration, te_short * 18) <
te_delta * 8) {
// Found syncPostfix
parser_step = GT_WT02DecoderStepReset;
if ((decode_count_bit ==
min_count_bit_for_found) &&
ws_protocol_gt_wt_02_check()) {
data = decode_data;
data_count_bit = decode_count_bit;
ws_protocol_gt_wt_02_remote_controller();
if (callback) callback(this);
} else if (decode_count_bit == 1) {
parser_step = GT_WT02DecoderStepSaveDuration;
}
decode_data = 0;
decode_count_bit = 0;
} else if (
DURATION_DIFF(duration, te_long) <
te_delta * 2) {
subghz_protocol_blocks_add_bit(0);
parser_step = GT_WT02DecoderStepSaveDuration;
} else if (
DURATION_DIFF(duration, te_long * 2) <
te_delta * 4) {
subghz_protocol_blocks_add_bit(1);
parser_step = GT_WT02DecoderStepSaveDuration;
} else {
parser_step = GT_WT02DecoderStepReset;
}
} else {
parser_step = GT_WT02DecoderStepReset;
}
} else {
parser_step = GT_WT02DecoderStepReset;
}
break;
}
}
protected:
uint32_t te_short = 500;
uint32_t te_long = 2000;
uint32_t te_delta = 150;
uint32_t min_count_bit_for_found = 37;
void ws_protocol_gt_wt_02_remote_controller() {
id = (data >> 29) & 0xFF;
battery_low = (data >> 28) & 1;
btn = (data >> 27) & 1;
channel = ((data >> 25) & 0x3) + 1;
if (!((data >> 24) & 1)) {
temp = (float)((data >> 13) & 0x07FF) / 10.0f;
} else {
temp = (float)((~(data >> 13) & 0x07FF) + 1) / -10.0f;
}
humidity = (data >> 6) & 0x7F;
if (humidity <= 10) // actually the sensors sends 10 below working range of 20%
humidity = 0;
else if (humidity > 90) // actually the sensors sends 110 above working range of 90%
humidity = 100;
}
bool ws_protocol_gt_wt_02_check() {
if (!decode_data) return false;
uint8_t sum = (decode_data >> 5) & 0xe;
uint64_t temp_data = decode_data >> 9;
for (uint8_t i = 0; i < 7; i++) {
sum += (temp_data >> (i * 4)) & 0xF;
}
return ((uint8_t)(decode_data & 0x3F) == (sum & 0x3F));
}
};
#endif

View File

@ -0,0 +1,159 @@
#ifndef __FPROTO_GTWT03_H__
#define __FPROTO_GTWT03_H__
#include "weatherbase.hpp"
#define AURIOL_AHFL_CONST_DATA 0b0100
typedef enum {
GT_WT03DecoderStepReset = 0,
GT_WT03DecoderStepCheckPreambule,
GT_WT03DecoderStepSaveDuration,
GT_WT03DecoderStepCheckDuration,
} GT_WT03DecoderStep;
class FProtoWeatherGTWT03 : public FProtoWeatherBase {
public:
FProtoWeatherGTWT03() {
sensorType = FPW_GTWT03;
}
void feed(bool level, uint32_t duration) {
switch (parser_step) {
case GT_WT03DecoderStepReset:
if ((level) && (DURATION_DIFF(duration, te_short * 3) <
te_delta * 2)) {
parser_step = GT_WT03DecoderStepCheckPreambule;
te_last = duration;
header_count = 0;
}
break;
case GT_WT03DecoderStepCheckPreambule:
if (level) {
te_last = duration;
} else {
if ((DURATION_DIFF(te_last, te_short * 3) < te_delta * 2) &&
(DURATION_DIFF(duration, te_short * 3) < te_delta * 2)) {
// Found preambule
header_count++;
} else if (header_count == 4) {
if ((DURATION_DIFF(te_last, te_short) < te_delta) &&
(DURATION_DIFF(duration, te_long) < te_delta)) {
decode_data = 0;
decode_count_bit = 0;
subghz_protocol_blocks_add_bit(0);
parser_step = GT_WT03DecoderStepSaveDuration;
} else if (
(DURATION_DIFF(te_last, te_long) < te_delta) &&
(DURATION_DIFF(duration, te_short) < te_delta)) {
decode_data = 0;
decode_count_bit = 0;
subghz_protocol_blocks_add_bit(1);
parser_step = GT_WT03DecoderStepSaveDuration;
} else {
parser_step = GT_WT03DecoderStepReset;
}
} else {
parser_step = GT_WT03DecoderStepReset;
}
}
break;
case GT_WT03DecoderStepSaveDuration:
if (level) {
te_last = duration;
parser_step = GT_WT03DecoderStepCheckDuration;
} else {
parser_step = GT_WT03DecoderStepReset;
}
break;
case GT_WT03DecoderStepCheckDuration:
if (!level) {
if (((DURATION_DIFF(te_last, te_short * 3) < te_delta * 2) &&
(DURATION_DIFF(duration, te_short * 3) < te_delta * 2))) {
if ((decode_count_bit ==
min_count_bit_for_found) &&
ws_protocol_gt_wt_03_check_crc()) {
data = decode_data;
data_count_bit = decode_count_bit;
ws_protocol_gt_wt_03_remote_controller();
if (callback) callback(this);
}
decode_data = 0;
decode_count_bit = 0;
header_count = 1;
parser_step = GT_WT03DecoderStepCheckPreambule;
break;
} else if (
(DURATION_DIFF(te_last, te_short) < te_delta) &&
(DURATION_DIFF(duration, te_long) < te_delta)) {
subghz_protocol_blocks_add_bit(0);
parser_step = GT_WT03DecoderStepSaveDuration;
} else if (
(DURATION_DIFF(te_last, te_long) < te_delta) &&
(DURATION_DIFF(duration, te_short) < te_delta)) {
subghz_protocol_blocks_add_bit(1);
parser_step = GT_WT03DecoderStepSaveDuration;
} else {
parser_step = GT_WT03DecoderStepReset;
}
} else {
parser_step = GT_WT03DecoderStepReset;
}
break;
}
}
protected:
uint32_t te_short = 285;
uint32_t te_long = 570;
uint32_t te_delta = 120;
uint32_t min_count_bit_for_found = 41;
void ws_protocol_gt_wt_03_remote_controller() {
id = data >> 33;
humidity = (data >> 25) & 0xFF;
if (humidity <= 10) { // actually the sensors sends 10 below working range of 20%
humidity = 0;
} else if (humidity > 95) { // actually the sensors sends 110 above working range of 90%
humidity = 100;
}
battery_low = (data >> 24) & 1;
btn = (data >> 23) & 1;
channel = ((data >> 21) & 0x03) + 1;
if (!((data >> 20) & 1)) {
temp = (float)((data >> 9) & 0x07FF) / 10.0f;
} else {
temp = (float)((~(data >> 9) & 0x07FF) + 1) / -10.0f;
}
}
bool ws_protocol_gt_wt_03_check_crc() {
uint8_t msg[] = {
static_cast<uint8_t>(decode_data >> 33),
static_cast<uint8_t>(decode_data >> 25),
static_cast<uint8_t>(decode_data >> 17),
static_cast<uint8_t>(decode_data >> 9)};
uint8_t sum = 0;
for (unsigned k = 0; k < sizeof(msg); ++k) {
uint8_t data = msg[k];
uint16_t key = 0x3100;
for (int i = 7; i >= 0; --i) {
// XOR key into sum if data bit is set
if ((data >> i) & 1) sum ^= key & 0xff;
// roll the key right
key = (key >> 1);
}
}
return ((sum ^ (uint8_t)((decode_data >> 1) & 0xFF)) == 0x2D);
}
};
#endif

View File

@ -0,0 +1,138 @@
#ifndef __FPROTO_Infactory_H__
#define __FPROTO_Infactory_H__
#include "weatherbase.hpp"
typedef enum {
InfactoryDecoderStepReset = 0,
InfactoryDecoderStepCheckPreambule,
InfactoryDecoderStepSaveDuration,
InfactoryDecoderStepCheckDuration,
} InfactoryDecoderStep;
class FProtoWeatherInfactory : public FProtoWeatherBase {
public:
FProtoWeatherInfactory() {
sensorType = FPW_INFACTORY;
}
void feed(bool level, uint32_t duration) {
switch (parser_step) {
case InfactoryDecoderStepReset:
if ((level) && (DURATION_DIFF(duration, te_short * 2) <
te_delta * 2)) {
parser_step = InfactoryDecoderStepCheckPreambule;
te_last = duration;
header_count = 0;
}
break;
case InfactoryDecoderStepCheckPreambule:
if (level) {
te_last = duration;
} else {
if ((DURATION_DIFF(te_last, te_short * 2) <
te_delta * 2) &&
(DURATION_DIFF(duration, te_short * 2) <
te_delta * 2)) {
// Found preambule
header_count++;
} else if (
(DURATION_DIFF(te_last, te_short) <
te_delta) &&
(DURATION_DIFF(duration, te_short * 16) <
te_delta * 8)) {
// Found syncPrefix
if (header_count > 3) {
parser_step = InfactoryDecoderStepSaveDuration;
decode_data = 0;
decode_count_bit = 0;
}
} else {
parser_step = InfactoryDecoderStepReset;
}
}
break;
case InfactoryDecoderStepSaveDuration:
if (level) {
te_last = duration;
parser_step = InfactoryDecoderStepCheckDuration;
} else {
parser_step = InfactoryDecoderStepReset;
}
break;
case InfactoryDecoderStepCheckDuration:
if (!level) {
if (duration >= ((uint32_t)te_short * 30)) {
// Found syncPostfix
if ((decode_count_bit ==
min_count_bit_for_found) &&
ws_protocol_infactory_check_crc()) {
data = decode_data;
data_count_bit = decode_count_bit;
ws_protocol_infactory_remote_controller();
if (callback) callback(this);
}
decode_data = 0;
decode_count_bit = 0;
parser_step = InfactoryDecoderStepReset;
break;
} else if (
(DURATION_DIFF(te_last, te_short) <
te_delta) &&
(DURATION_DIFF(duration, te_long) <
te_delta * 2)) {
subghz_protocol_blocks_add_bit(0);
parser_step = InfactoryDecoderStepSaveDuration;
} else if (
(DURATION_DIFF(te_last, te_short) <
te_delta) &&
(DURATION_DIFF(duration, te_long * 2) <
te_delta * 4)) {
subghz_protocol_blocks_add_bit(1);
parser_step = InfactoryDecoderStepSaveDuration;
} else {
parser_step = InfactoryDecoderStepReset;
}
} else {
parser_step = InfactoryDecoderStepReset;
}
break;
}
}
protected:
uint32_t te_short = 500;
uint32_t te_long = 2000;
uint32_t te_delta = 150;
uint32_t min_count_bit_for_found = 40;
bool ws_protocol_infactory_check_crc() {
uint8_t msg[] = {
static_cast<uint8_t>(decode_data >> 32),
static_cast<uint8_t>(((decode_data >> 24) & 0x0F) | (decode_data & 0x0F) << 4),
static_cast<uint8_t>(decode_data >> 16),
static_cast<uint8_t>(decode_data >> 8),
static_cast<uint8_t>(decode_data)};
uint8_t crc =
subghz_protocol_blocks_crc4(msg, 4, 0x13, 0); // Koopmann 0x9, CCITT-4; FP-4; ITU-T G.704
crc ^= msg[4] >> 4; // last nibble is only XORed
return (crc == ((decode_data >> 28) & 0x0F));
}
void ws_protocol_infactory_remote_controller() {
id = data >> 32;
battery_low = (data >> 26) & 1;
btn = WS_NO_BTN;
temp =
locale_fahrenheit_to_celsius(((float)((data >> 12) & 0x0FFF) - 900.0f) / 10.0f);
humidity =
(((data >> 8) & 0x0F) * 10) + ((data >> 4) & 0x0F); // BCD, 'A0'=100%rH
channel = data & 0x03;
}
};
#endif

View File

@ -0,0 +1,163 @@
#ifndef __FPROTO_LACROSSE_TX_H__
#define __FPROTO_LACROSSE_TX_H__
#include "weatherbase.hpp"
#define LACROSSE_TX_GAP 1000
#define LACROSSE_TX_BIT_SIZE 44
#define LACROSSE_TX_SUNC_PATTERN 0x0A000000000
#define LACROSSE_TX_SUNC_MASK 0x0F000000000
#define LACROSSE_TX_MSG_TYPE_TEMP 0x00
#define LACROSSE_TX_MSG_TYPE_HUM 0x0E
typedef enum {
LaCrosse_TXDecoderStepReset = 0,
LaCrosse_TXDecoderStepCheckPreambule,
LaCrosse_TXDecoderStepSaveDuration,
LaCrosse_TXDecoderStepCheckDuration,
} LaCrosse_TXDecoderStep;
class FProtoWeatherLaCrosseTx : public FProtoWeatherBase {
public:
FProtoWeatherLaCrosseTx() {
sensorType = FPW_LACROSSETX;
}
void feed(bool level, uint32_t duration) {
switch (parser_step) {
case LaCrosse_TXDecoderStepReset:
if ((!level) && (DURATION_DIFF(duration, LACROSSE_TX_GAP) < te_delta * 2)) {
parser_step = LaCrosse_TXDecoderStepCheckPreambule;
header_count = 0;
}
break;
case LaCrosse_TXDecoderStepCheckPreambule:
if (level) {
if ((DURATION_DIFF(duration, te_short) < te_delta) &&
(header_count > 1)) {
parser_step = LaCrosse_TXDecoderStepCheckDuration;
decode_data = 0;
decode_count_bit = 0;
te_last = duration;
} else if (duration > (te_long * 2)) {
parser_step = LaCrosse_TXDecoderStepReset;
}
} else {
if (DURATION_DIFF(duration, LACROSSE_TX_GAP) < te_delta * 2) {
te_last = duration;
header_count++;
} else {
parser_step = LaCrosse_TXDecoderStepReset;
}
}
break;
case LaCrosse_TXDecoderStepSaveDuration:
if (level) {
te_last = duration;
parser_step = LaCrosse_TXDecoderStepCheckDuration;
} else {
parser_step = LaCrosse_TXDecoderStepReset;
}
break;
case LaCrosse_TXDecoderStepCheckDuration:
if (!level) {
if (duration > LACROSSE_TX_GAP * 3) {
if (DURATION_DIFF(te_last, te_short) < te_delta) {
subghz_protocol_blocks_add_bit(1);
parser_step = LaCrosse_TXDecoderStepSaveDuration;
} else if (
DURATION_DIFF(te_last, te_long) < te_delta) {
subghz_protocol_blocks_add_bit(0);
parser_step = LaCrosse_TXDecoderStepSaveDuration;
}
if ((decode_data & LACROSSE_TX_SUNC_MASK) ==
LACROSSE_TX_SUNC_PATTERN) {
if (ws_protocol_lacrosse_tx_check_crc()) {
data = decode_data;
data_count_bit = LACROSSE_TX_BIT_SIZE;
ws_protocol_lacrosse_tx_remote_controller();
if (callback) callback(this);
}
}
decode_data = 0;
decode_count_bit = 0;
header_count = 0;
parser_step = LaCrosse_TXDecoderStepReset;
break;
} else if (
(DURATION_DIFF(te_last, te_short) < te_delta) &&
(DURATION_DIFF(duration, LACROSSE_TX_GAP) < te_delta * 2)) {
subghz_protocol_blocks_add_bit(1);
parser_step = LaCrosse_TXDecoderStepSaveDuration;
} else if (
(DURATION_DIFF(te_last, te_long) < te_delta) &&
(DURATION_DIFF(duration, LACROSSE_TX_GAP) < te_delta * 2)) {
subghz_protocol_blocks_add_bit(0);
parser_step = LaCrosse_TXDecoderStepSaveDuration;
} else {
parser_step = LaCrosse_TXDecoderStepReset;
}
} else {
parser_step = LaCrosse_TXDecoderStepReset;
}
break;
}
}
protected:
uint32_t te_short = 550;
uint32_t te_long = 1300;
uint32_t te_delta = 120;
uint32_t min_count_bit_for_found = 40;
void ws_protocol_lacrosse_tx_remote_controller() {
uint8_t msg_type = (data >> 32) & 0x0F;
id = (((data >> 28) & 0x0F) << 3) | (((data >> 24) & 0x0F) >> 1);
float msg_value = (float)((data >> 20) & 0x0F) * 10.0f +
(float)((data >> 16) & 0x0F) +
(float)((data >> 12) & 0x0F) * 0.1f;
if (msg_type == LACROSSE_TX_MSG_TYPE_TEMP) { //-V1051
temp = msg_value - 50.0f;
humidity = WS_NO_HUMIDITY;
} else if (msg_type == LACROSSE_TX_MSG_TYPE_HUM) {
// ToDo for verification, records are needed with sensors maintaining temperature and temperature for this standard
humidity = (uint8_t)msg_value;
} else {
// furi_crash("WS: WSProtocolLaCrosse_TX incorrect msg_type.");
}
btn = WS_NO_BTN;
battery_low = WS_NO_BATT;
channel = WS_NO_CHANNEL;
}
bool ws_protocol_lacrosse_tx_check_crc() {
if (!decode_data) return false;
uint8_t msg[] = {
static_cast<uint8_t>((decode_data >> 36) & 0x0F),
static_cast<uint8_t>((decode_data >> 32) & 0x0F),
static_cast<uint8_t>((decode_data >> 28) & 0x0F),
static_cast<uint8_t>((decode_data >> 24) & 0x0F),
static_cast<uint8_t>((decode_data >> 20) & 0x0F),
static_cast<uint8_t>((decode_data >> 16) & 0x0F),
static_cast<uint8_t>((decode_data >> 12) & 0x0F),
static_cast<uint8_t>((decode_data >> 8) & 0x0F),
static_cast<uint8_t>((decode_data >> 4) & 0x0F)};
uint8_t crc = subghz_protocol_blocks_add_bytes(msg, 9);
return ((crc & 0x0F) == (decode_data & 0x0F));
}
};
#endif

View File

@ -0,0 +1,135 @@
#ifndef __FPROTO_LACROSSE_TX141thbv2_H__
#define __FPROTO_LACROSSE_TX141thbv2_H__
#include "weatherbase.hpp"
#define LACROSSE_TX141TH_BV2_BIT_COUNT 41
typedef enum {
LaCrosse_TX141THBv2DecoderStepReset = 0,
LaCrosse_TX141THBv2DecoderStepCheckPreambule,
LaCrosse_TX141THBv2DecoderStepSaveDuration,
LaCrosse_TX141THBv2DecoderStepCheckDuration,
} LaCrosse_TX141THBv2DecoderStep;
class FProtoWeatherLaCrosseTx141thbv2 : public FProtoWeatherBase {
public:
FProtoWeatherLaCrosseTx141thbv2() {
sensorType = FPW_LACROSSETX141thbv2;
}
void feed(bool level, uint32_t duration) {
switch (parser_step) {
case LaCrosse_TX141THBv2DecoderStepReset:
if ((level) && (DURATION_DIFF(duration, te_short * 4) < te_delta * 2)) {
parser_step = LaCrosse_TX141THBv2DecoderStepCheckPreambule;
te_last = duration;
header_count = 0;
}
break;
case LaCrosse_TX141THBv2DecoderStepCheckPreambule:
if (level) {
te_last = duration;
} else {
if ((DURATION_DIFF(te_last, te_short * 4) < te_delta * 2) &&
(DURATION_DIFF(duration, te_short * 4) < te_delta * 2)) {
// Found preambule
header_count++;
} else if (header_count == 4) {
if (ws_protocol_decoder_lacrosse_tx141thbv2_add_bit(te_last, duration)) {
decode_data = decode_data & 1;
decode_count_bit = 1;
parser_step = LaCrosse_TX141THBv2DecoderStepSaveDuration;
} else {
parser_step = LaCrosse_TX141THBv2DecoderStepReset;
}
} else {
parser_step = LaCrosse_TX141THBv2DecoderStepReset;
}
}
break;
case LaCrosse_TX141THBv2DecoderStepSaveDuration:
if (level) {
te_last = duration;
parser_step = LaCrosse_TX141THBv2DecoderStepCheckDuration;
} else {
parser_step = LaCrosse_TX141THBv2DecoderStepReset;
}
break;
case LaCrosse_TX141THBv2DecoderStepCheckDuration:
if (!level) {
if (((DURATION_DIFF(te_last, te_short * 3) < te_delta * 2) &&
(DURATION_DIFF(duration, te_short * 4) < te_delta * 2))) {
if ((decode_count_bit == min_count_bit_for_found) ||
(decode_count_bit == LACROSSE_TX141TH_BV2_BIT_COUNT)) {
if (ws_protocol_lacrosse_tx141thbv2_check_crc()) {
data = decode_data;
data_count_bit = decode_count_bit;
ws_protocol_lacrosse_tx141thbv2_remote_controller();
if (callback) callback(this);
}
decode_data = 0;
decode_count_bit = 0;
header_count = 1;
parser_step = LaCrosse_TX141THBv2DecoderStepCheckPreambule;
break;
}
} else if (ws_protocol_decoder_lacrosse_tx141thbv2_add_bit(te_last, duration)) {
parser_step = LaCrosse_TX141THBv2DecoderStepSaveDuration;
} else {
parser_step = LaCrosse_TX141THBv2DecoderStepReset;
}
} else {
parser_step = LaCrosse_TX141THBv2DecoderStepReset;
}
break;
}
}
protected:
uint32_t te_short = 208;
uint32_t te_long = 417;
uint32_t te_delta = 120;
uint32_t min_count_bit_for_found = 40;
bool ws_protocol_lacrosse_tx141thbv2_check_crc() {
if (!decode_data) return false;
uint64_t data = decode_data;
if (decode_count_bit == LACROSSE_TX141TH_BV2_BIT_COUNT) {
data >>= 1;
}
uint8_t msg[] = {static_cast<uint8_t>(data >> 32), static_cast<uint8_t>(data >> 24), static_cast<uint8_t>(data >> 16), static_cast<uint8_t>(data >> 8)};
uint8_t crc = subghz_protocol_blocks_lfsr_digest8_reflect(msg, 4, 0x31, 0xF4);
return (crc == (data & 0xFF));
}
void ws_protocol_lacrosse_tx141thbv2_remote_controller() {
if (data_count_bit == LACROSSE_TX141TH_BV2_BIT_COUNT) {
data >>= 1;
}
id = data >> 32;
battery_low = (data >> 31) & 1;
btn = (data >> 30) & 1;
channel = ((data >> 28) & 0x03) + 1;
temp = ((float)((data >> 16) & 0x0FFF) - 500.0f) / 10.0f;
humidity = (data >> 8) & 0xFF;
}
bool ws_protocol_decoder_lacrosse_tx141thbv2_add_bit(uint32_t te_last, uint32_t te_current) {
bool ret = false;
if (DURATION_DIFF(te_last + te_current, te_short + te_long) < te_delta * 2) {
if (te_last > te_current) {
subghz_protocol_blocks_add_bit(1);
} else {
subghz_protocol_blocks_add_bit(0);
}
ret = true;
}
return ret;
}
};
#endif

View File

@ -0,0 +1,121 @@
#ifndef __FPROTO_NEXUSTH_H__
#define __FPROTO_NEXUSTH_H__
#include "weatherbase.hpp"
// For the state machine
typedef enum {
Nexus_THDecoderStepReset = 0,
Nexus_THDecoderStepSaveDuration,
Nexus_THDecoderStepCheckDuration,
} Nexus_THDecoderStep;
// we will look for this value
#define NEXUS_TH_CONST_DATA 0b1111
class FProtoWeatherNexusTH : public FProtoWeatherBase {
public:
FProtoWeatherNexusTH() {
// must set it's value from the "weathertypes.hpp". getWeatherSensorTypeName() will work with this.
sensorType = FPW_NexusTH;
}
// Here we will got a level and duration. eg HIGH (true) for 500. This function must be as fast as possible, to keep the core happy.
// From this function we will call the "callback", that is responsible for transmitting the parsed data to the ui. Before calling it, we must populate the base class's variables, like temp, humidity, ...
void feed(bool level, uint32_t duration) override {
switch (parser_step) {
case Nexus_THDecoderStepReset:
if ((!level) && (DURATION_DIFF(duration, te_short * 8) <
te_delta * 4)) {
// Found sync
parser_step = Nexus_THDecoderStepSaveDuration;
decode_data = 0;
decode_count_bit = 0;
}
break;
case Nexus_THDecoderStepSaveDuration:
if (level) {
te_last = duration;
parser_step = Nexus_THDecoderStepCheckDuration;
} else {
parser_step = Nexus_THDecoderStepReset;
}
break;
case Nexus_THDecoderStepCheckDuration:
if (!level) {
if (DURATION_DIFF(duration, te_short * 8) < te_delta * 4) {
// Found sync
parser_step = Nexus_THDecoderStepReset;
if ((decode_count_bit == min_count_bit_for_found) &&
ws_protocol_nexus_th_check()) {
data = decode_data;
data_count_bit = decode_count_bit;
ws_protocol_nexus_th_remote_controller();
if (callback) callback((FProtoWeatherBase*)this);
parser_step = Nexus_THDecoderStepCheckDuration;
}
decode_data = 0;
decode_count_bit = 0;
break;
} else if ((DURATION_DIFF(te_last, te_short) <
te_delta) &&
(DURATION_DIFF(duration, te_short * 2) < te_delta * 2)) {
subghz_protocol_blocks_add_bit(0);
parser_step = Nexus_THDecoderStepSaveDuration;
} else if (
(DURATION_DIFF(te_last, te_short) < te_delta) &&
(DURATION_DIFF(duration, te_short * 4) < te_delta * 4)) {
subghz_protocol_blocks_add_bit(1);
parser_step = Nexus_THDecoderStepSaveDuration;
} else {
parser_step = Nexus_THDecoderStepReset;
}
} else {
parser_step = Nexus_THDecoderStepReset;
}
break;
}
}
protected:
// timing values
uint32_t te_short = 490;
uint32_t te_long = 1980;
uint32_t te_delta = 150;
uint32_t min_count_bit_for_found = 36;
// sanity check
bool ws_protocol_nexus_th_check() {
uint8_t type = (decode_data >> 8) & 0x0F;
if ((type == NEXUS_TH_CONST_DATA) && ((decode_data >> 4) != 0xffffffff)) {
return true;
} else {
return false;
}
return true;
}
// fill the base class's variables before calling the callback
void ws_protocol_nexus_th_remote_controller() {
id = (data >> 28) & 0xFF;
battery_low = !((data >> 27) & 1);
channel = ((data >> 24) & 0x03) + 1;
btn = WS_NO_BTN;
if (!((data >> 23) & 1)) {
temp = (float)((data >> 12) & 0x07FF) / 10.0f;
} else {
temp = (float)((~(data >> 12) & 0x07FF) + 1) / -10.0f;
}
humidity = data & 0xFF;
if (humidity > 95)
humidity = 95;
else if (humidity < 20)
humidity = 20;
}
};
#endif

View File

@ -0,0 +1,248 @@
#ifndef __FPROTO_OREGON2_H__
#define __FPROTO_OREGON2_H__
#include "weatherbase.hpp"
#define OREGON2_PREAMBLE_BITS 19
#define OREGON2_PREAMBLE_MASK 0b1111111111111111111
#define OREGON2_SENSOR_ID(d) (((d) >> 16) & 0xFFFF)
#define OREGON2_CHECKSUM_BITS 8
// 15 ones + 0101 (inverted A)
#define OREGON2_PREAMBLE 0b1111111111111110101
// bit indicating the low battery
#define OREGON2_FLAG_BAT_LOW 0x4
/// Documentation for Oregon Scientific protocols can be found here:
/// http://wmrx00.sourceforge.net/Arduino/OregonScientific-RF-Protocols.pdf
// Sensors ID
#define ID_THGR122N 0x1d20
#define ID_THGR968 0x1d30
#define ID_BTHR918 0x5d50
#define ID_BHTR968 0x5d60
#define ID_RGR968 0x2d10
#define ID_THR228N 0xec40
#define ID_THN132N 0xec40 // same as THR228N but different packet size
#define ID_RTGN318 0x0cc3 // warning: id is from 0x0cc3 and 0xfcc3
#define ID_RTGN129 0x0cc3 // same as RTGN318 but different packet size
#define ID_THGR810 0xf824 // This might be ID_THGR81, but what's true is lost in (git) history
#define ID_THGR810a 0xf8b4 // unconfirmed version
#define ID_THN802 0xc844
#define ID_PCR800 0x2914
#define ID_PCR800a 0x2d14 // Different PCR800 ID - AU version I think
#define ID_WGR800 0x1984
#define ID_WGR800a 0x1994 // unconfirmed version
#define ID_WGR968 0x3d00
#define ID_UV800 0xd874
#define ID_THN129 0xcc43 // THN129 Temp only
#define ID_RTHN129 0x0cd3 // RTHN129 Temp, clock sensors
#define ID_RTHN129_1 0x9cd3
#define ID_RTHN129_2 0xacd3
#define ID_RTHN129_3 0xbcd3
#define ID_RTHN129_4 0xccd3
#define ID_RTHN129_5 0xdcd3
#define ID_BTHGN129 0x5d53 // Baro, Temp, Hygro sensor
#define ID_UVR128 0xec70
#define ID_THGR328N 0xcc23 // Temp & Hygro sensor similar to THR228N with 5 channel instead of 3
#define ID_RTGR328N_1 0xdcc3 // RTGR328N_[1-5] RFclock(date &time)&Temp&Hygro sensor
#define ID_RTGR328N_2 0xccc3
#define ID_RTGR328N_3 0xbcc3
#define ID_RTGR328N_4 0xacc3
#define ID_RTGR328N_5 0x9cc3
#define ID_RTGR328N_6 0x8ce3 // RTGR328N_6&7 RFclock(date &time)&Temp&Hygro sensor like THGR328N
#define ID_RTGR328N_7 0x8ae3
typedef enum {
Oregon2DecoderStepReset = 0,
Oregon2DecoderStepFoundPreamble,
Oregon2DecoderStepVarData,
} Oregon2DecoderStep;
class FProtoWeatherOregon2 : public FProtoWeatherBase {
public:
FProtoWeatherOregon2() {
sensorType = FPW_OREGON2;
}
void feed(bool level, uint32_t duration) override {
// oregon v2.1 signal is inverted
ManchesterEvent event = level_and_duration_to_event(!level, duration);
bool data;
// low-level bit sequence decoding
if (event == ManchesterEventReset) {
parser_step = Oregon2DecoderStepReset;
have_bit = false;
decode_data = 0UL;
decode_count_bit = 0;
}
if (manchester_advance(manchester_saved_state, event, &manchester_saved_state, &data)) {
if (have_bit) {
if (!prev_bit && data) {
subghz_protocol_blocks_add_bit(1);
} else if (prev_bit && !data) {
subghz_protocol_blocks_add_bit(0);
} else {
ws_protocol_decoder_oregon2_reset();
}
have_bit = false;
} else {
prev_bit = data;
have_bit = true;
}
}
switch (parser_step) {
case Oregon2DecoderStepReset:
// waiting for fixed oregon2 preamble
if (decode_count_bit >= OREGON2_PREAMBLE_BITS &&
((decode_data & OREGON2_PREAMBLE_MASK) == OREGON2_PREAMBLE)) {
parser_step = Oregon2DecoderStepFoundPreamble;
decode_count_bit = 0;
decode_data = 0UL;
}
break;
case Oregon2DecoderStepFoundPreamble:
// waiting for fixed oregon2 data
if (decode_count_bit == 32) {
data = decode_data;
data_count_bit = decode_count_bit;
decode_data = 0UL;
decode_count_bit = 0;
// reverse nibbles in decoded data
data = (data & 0x55555555) << 1 |
(data & 0xAAAAAAAA) >> 1;
data = (data & 0x33333333) << 2 |
(data & 0xCCCCCCCC) >> 2;
ws_oregon2_decode_const_data();
var_bits =
oregon2_sensor_id_var_bits(OREGON2_SENSOR_ID(data));
if (!var_bits) {
// sensor is not supported, stop decoding, but showing the decoded fixed part
parser_step = Oregon2DecoderStepReset;
if (callback) callback(this);
} else {
parser_step = Oregon2DecoderStepVarData;
}
}
break;
case Oregon2DecoderStepVarData:
// waiting for variable (sensor-specific data)
if (decode_count_bit == (uint32_t)var_bits + OREGON2_CHECKSUM_BITS) {
var_data = decode_data & 0xFFFFFFFF;
// reverse nibbles in var data
var_data = (var_data & 0x55555555) << 1 |
(var_data & 0xAAAAAAAA) >> 1;
var_data = (var_data & 0x33333333) << 2 |
(var_data & 0xCCCCCCCC) >> 2;
ws_oregon2_decode_var_data(OREGON2_SENSOR_ID(data), var_data >> OREGON2_CHECKSUM_BITS);
parser_step = Oregon2DecoderStepReset;
if (callback) callback(this);
}
break;
}
}
protected:
// timing values
uint32_t te_short = 500;
uint32_t te_long = 1000;
uint32_t te_delta = 200;
uint32_t min_count_bit_for_found = 32;
bool have_bit = false;
bool prev_bit = 0;
uint8_t var_bits{0};
uint32_t var_data{0};
void ws_protocol_decoder_oregon2_reset() {
parser_step = Oregon2DecoderStepReset;
decode_data = 0UL;
decode_count_bit = 0;
manchester_advance(manchester_saved_state, ManchesterEventReset, &manchester_saved_state, NULL);
have_bit = false;
var_data = 0;
var_bits = 0;
}
ManchesterEvent level_and_duration_to_event(bool level, uint32_t duration) {
bool is_long = false;
if (DURATION_DIFF(duration, te_long) < te_delta) {
is_long = true;
} else if (DURATION_DIFF(duration, te_short) < te_delta) {
is_long = false;
} else {
return ManchesterEventReset;
}
if (level)
return is_long ? ManchesterEventLongHigh : ManchesterEventShortHigh;
else
return is_long ? ManchesterEventLongLow : ManchesterEventShortLow;
}
uint8_t oregon2_sensor_id_var_bits(uint16_t sensor_id) {
switch (sensor_id) {
case ID_THR228N:
case ID_RTHN129_1:
case ID_RTHN129_2:
case ID_RTHN129_3:
case ID_RTHN129_4:
case ID_RTHN129_5:
return 16;
case ID_THGR122N:
return 24;
default:
return 0;
}
}
void ws_oregon2_decode_const_data() {
id = OREGON2_SENSOR_ID(data);
uint8_t ch_bits = (data >> 12) & 0xF;
channel = 1;
while (ch_bits > 1) {
channel++;
ch_bits >>= 1;
}
battery_low = (data & OREGON2_FLAG_BAT_LOW) ? 1 : 0;
}
uint16_t bcd_decode_short(uint32_t data) {
return (data & 0xF) * 10 + ((data >> 4) & 0xF);
}
float ws_oregon2_decode_temp(uint32_t data) {
int32_t temp_val;
temp_val = bcd_decode_short(data >> 4);
temp_val *= 10;
temp_val += (data >> 12) & 0xF;
if (data & 0xF) temp_val = -temp_val;
return (float)temp_val / 10.0;
}
void ws_oregon2_decode_var_data(uint16_t sensor_id, uint32_t data) {
switch (sensor_id) {
case ID_THR228N:
case ID_RTHN129_1:
case ID_RTHN129_2:
case ID_RTHN129_3:
case ID_RTHN129_4:
case ID_RTHN129_5:
temp = ws_oregon2_decode_temp(data);
humidity = WS_NO_HUMIDITY;
return;
case ID_THGR122N:
humidity = bcd_decode_short(data);
temp = ws_oregon2_decode_temp(data >> 8);
return;
default:
break;
}
}
};
#endif

View File

@ -0,0 +1,173 @@
#ifndef __FPROTO_OREGON3_H__
#define __FPROTO_OREGON3_H__
#include "weatherbase.hpp"
#define OREGON3_PREAMBLE_BITS 28
#define OREGON3_PREAMBLE_MASK 0b1111111111111111111111111111
// 24 ones + 0101 (inverted A)
#define OREGON3_PREAMBLE 0b1111111111111111111111110101
// Fixed part contains:
// - Sensor type: 16 bits
// - Channel: 4 bits
// - ID (changes when batteries are changed): 8 bits
// - Battery status: 4 bits
#define OREGON3_FIXED_PART_BITS (16 + 4 + 8 + 4)
#define OREGON3_SENSOR_ID(d) (((d) >> 16) & 0xFFFF)
#define OREGON3_CHECKSUM_BITS 8
// bit indicating the low battery
#define OREGON3_FLAG_BAT_LOW 0x4
/// Documentation for Oregon Scientific protocols can be found here:
/// https://www.osengr.org/Articles/OS-RF-Protocols-IV.pdf
// Sensors ID
#define ID_THGR221 0xf824
typedef enum {
Oregon3DecoderStepReset = 0,
Oregon3DecoderStepFoundPreamble,
Oregon3DecoderStepVarData,
} Oregon3DecoderStep;
class FProtoWeatherOregon3 : public FProtoWeatherBase {
public:
FProtoWeatherOregon3() {
sensorType = FPW_OREGON3;
}
void feed(bool level, uint32_t duration) override {
ManchesterEvent event = level_and_duration_to_event(!level, duration);
// low-level bit sequence decoding
if (event == ManchesterEventReset) {
parser_step = Oregon3DecoderStepReset;
prev_bit = false;
decode_data = 0UL;
decode_count_bit = 0;
}
if (manchester_advance(
manchester_saved_state, event, &manchester_saved_state, &prev_bit)) {
subghz_protocol_blocks_add_bit(prev_bit);
}
switch (parser_step) {
case Oregon3DecoderStepReset:
// waiting for fixed oregon3 preamble
if (decode_count_bit >= OREGON3_PREAMBLE_BITS &&
((decode_data & OREGON3_PREAMBLE_MASK) == OREGON3_PREAMBLE)) {
parser_step = Oregon3DecoderStepFoundPreamble;
decode_count_bit = 0;
decode_data = 0UL;
}
break;
case Oregon3DecoderStepFoundPreamble:
// waiting for fixed oregon3 data
if (decode_count_bit == OREGON3_FIXED_PART_BITS) {
data = decode_data;
data_count_bit = decode_count_bit;
decode_data = 0UL;
decode_count_bit = 0;
// reverse nibbles in decoded data as oregon v3.0 is LSB first
data = (data & 0x55555555) << 1 |
(data & 0xAAAAAAAA) >> 1;
data = (data & 0x33333333) << 2 |
(data & 0xCCCCCCCC) >> 2;
ws_oregon3_decode_const_data();
var_bits =
oregon3_sensor_id_var_bits(OREGON3_SENSOR_ID(data));
if (!var_bits) {
// sensor is not supported, stop decoding
parser_step = Oregon3DecoderStepReset;
} else {
parser_step = Oregon3DecoderStepVarData;
}
}
break;
case Oregon3DecoderStepVarData:
// waiting for variable (sensor-specific data)
if (decode_count_bit == (uint32_t)var_bits + OREGON3_CHECKSUM_BITS) {
var_data = decode_data & 0xFFFFFFFFFFFFFFFF;
// reverse nibbles in var data
var_data = (var_data & 0x5555555555555555) << 1 |
(var_data & 0xAAAAAAAAAAAAAAAA) >> 1;
var_data = (var_data & 0x3333333333333333) << 2 |
(var_data & 0xCCCCCCCCCCCCCCCC) >> 2;
ws_oregon3_decode_var_data(OREGON3_SENSOR_ID(data), var_data >> OREGON3_CHECKSUM_BITS);
parser_step = Oregon3DecoderStepReset;
if (callback) callback(this);
}
break;
}
}
protected:
// timing values
uint32_t te_short = 500;
uint32_t te_long = 1100;
uint32_t te_delta = 300;
uint32_t min_count_bit_for_found = 32;
bool prev_bit = false;
uint8_t var_bits{0};
uint64_t var_data{0};
ManchesterEvent level_and_duration_to_event(bool level, uint32_t duration) {
bool is_long = false;
if (DURATION_DIFF(duration, te_long) < te_delta) {
is_long = true;
} else if (DURATION_DIFF(duration, te_short) < te_delta) {
is_long = false;
} else {
return ManchesterEventReset;
}
if (level)
return is_long ? ManchesterEventLongHigh : ManchesterEventShortHigh;
else
return is_long ? ManchesterEventLongLow : ManchesterEventShortLow;
}
uint8_t oregon3_sensor_id_var_bits(uint16_t sensor_id) {
switch (sensor_id) {
case ID_THGR221:
// nibbles: temp + hum + '0'
return (4 + 2 + 1) * 4;
default:
return 0;
}
}
void ws_oregon3_decode_const_data() {
id = OREGON3_SENSOR_ID(data);
channel = (data >> 12) & 0xF;
battery_low = (data & OREGON3_FLAG_BAT_LOW) ? 1 : 0;
}
uint16_t ws_oregon3_bcd_decode_short(uint32_t data) {
return (data & 0xF) * 10 + ((data >> 4) & 0xF);
}
float ws_oregon3_decode_temp(uint32_t data) {
int32_t temp_val;
temp_val = ws_oregon3_bcd_decode_short(data >> 4);
temp_val *= 10;
temp_val += (data >> 12) & 0xF;
if (data & 0xF) temp_val = -temp_val;
return (float)temp_val / 10.0;
}
void ws_oregon3_decode_var_data(uint16_t sensor_id, uint32_t data) {
switch (sensor_id) {
case ID_THGR221:
default:
humidity = ws_oregon3_bcd_decode_short(data >> 4);
temp = ws_oregon3_decode_temp(data >> 12);
break;
}
}
};
#endif

View File

@ -0,0 +1,176 @@
#ifndef __FPROTO_OREGONv1_H__
#define __FPROTO_OREGONv1_H__
#include "weatherbase.hpp"
typedef enum {
Oregon_V1DecoderStepReset = 0,
Oregon_V1DecoderStepFoundPreamble,
Oregon_V1DecoderStepParse,
} Oregon_V1DecoderStep;
#define OREGON_V1_HEADER_OK 0xFF
class FProtoWeatherOregonV1 : public FProtoWeatherBase {
public:
FProtoWeatherOregonV1() {
sensorType = FPW_OREGONv1;
}
void feed(bool level, uint32_t duration) override {
ManchesterEvent event = ManchesterEventReset;
switch (parser_step) {
case Oregon_V1DecoderStepReset:
if ((level) && (DURATION_DIFF(duration, te_short) <
te_delta)) {
parser_step = Oregon_V1DecoderStepFoundPreamble;
te_last = duration;
header_count = 0;
}
break;
case Oregon_V1DecoderStepFoundPreamble:
if (level) {
// keep high levels, if they suit our durations
if ((DURATION_DIFF(duration, te_short) <
te_delta) ||
(DURATION_DIFF(duration, te_short * 4) <
te_delta)) {
te_last = duration;
} else {
parser_step = Oregon_V1DecoderStepReset;
}
} else if (
// checking low levels
(DURATION_DIFF(duration, te_short) <
te_delta) &&
(DURATION_DIFF(te_last, te_short) <
te_delta)) {
// Found header
header_count++;
} else if (
(DURATION_DIFF(duration, te_short * 3) <
te_delta) &&
(DURATION_DIFF(te_last, te_short) <
te_delta)) {
// check header
if (header_count > 7) {
header_count = OREGON_V1_HEADER_OK;
}
} else if (
(header_count == OREGON_V1_HEADER_OK) &&
(DURATION_DIFF(te_last, te_short * 4) <
te_delta)) {
// found all the necessary patterns
decode_data = 0;
decode_count_bit = 1;
manchester_advance(
manchester_saved_state,
ManchesterEventReset,
&manchester_saved_state,
NULL);
parser_step = Oregon_V1DecoderStepParse;
if (duration < te_short * 4) {
first_bit = 1;
} else {
first_bit = 0;
}
} else {
parser_step = Oregon_V1DecoderStepReset;
}
break;
case Oregon_V1DecoderStepParse:
if (level) {
if (DURATION_DIFF(duration, te_short) <
te_delta) {
event = ManchesterEventShortHigh;
} else if (
DURATION_DIFF(duration, te_long) <
te_delta) {
event = ManchesterEventLongHigh;
} else {
parser_step = Oregon_V1DecoderStepReset;
}
} else {
if (DURATION_DIFF(duration, te_short) <
te_delta) {
event = ManchesterEventShortLow;
} else if (
DURATION_DIFF(duration, te_long) <
te_delta) {
event = ManchesterEventLongLow;
} else if (duration >= ((uint32_t)te_long * 2)) {
if (decode_count_bit ==
min_count_bit_for_found) {
if (first_bit) {
decode_data = ~decode_data | (1 << 31);
}
if (ws_protocol_oregon_v1_check()) {
data = decode_data;
data_count_bit = decode_count_bit;
ws_protocol_oregon_v1_remote_controller();
if (callback) callback(this);
}
}
decode_data = 0;
decode_count_bit = 0;
manchester_advance(
manchester_saved_state,
ManchesterEventReset,
&manchester_saved_state,
NULL);
} else {
parser_step = Oregon_V1DecoderStepReset;
}
}
if (event != ManchesterEventReset) {
bool data;
bool data_ok = manchester_advance(
manchester_saved_state, event, &manchester_saved_state, &data);
if (data_ok) {
decode_data = (decode_data << 1) | !data;
decode_count_bit++;
}
}
break;
}
}
protected:
// timing values
uint32_t te_short = 1465;
uint32_t te_long = 2930;
uint32_t te_delta = 350;
uint32_t min_count_bit_for_found = 32;
uint8_t first_bit{0};
bool ws_protocol_oregon_v1_check() {
if (!decode_data) return false;
uint64_t data = subghz_protocol_blocks_reverse_key(decode_data, 32);
uint16_t crc = (data & 0xff) + ((data >> 8) & 0xff) + ((data >> 16) & 0xff);
crc = (crc & 0xff) + ((crc >> 8) & 0xff);
return (crc == ((data >> 24) & 0xFF));
}
void ws_protocol_oregon_v1_remote_controller() {
uint64_t data2 = subghz_protocol_blocks_reverse_key(data, 32);
id = data2 & 0xFF;
channel = ((data2 >> 6) & 0x03) + 1;
float temp_raw =
((data2 >> 8) & 0x0F) * 0.1f + ((data2 >> 12) & 0x0F) + ((data2 >> 16) & 0x0F) * 10.0f;
if (!((data2 >> 21) & 1)) {
temp = temp_raw;
} else {
temp = -temp_raw;
}
battery_low = !((data2 >> 23) & 1ULL);
btn = WS_NO_BTN;
humidity = WS_NO_HUMIDITY;
}
};
#endif

View File

@ -0,0 +1,110 @@
#ifndef __FPROTO_THERMOPROTX4_H__
#define __FPROTO_THERMOPROTX4_H__
#include "weatherbase.hpp"
#define THERMO_PRO_TX4_TYPE_1 0b1001
#define THERMO_PRO_TX4_TYPE_2 0b0110
typedef enum {
ThermoPRO_TX4DecoderStepReset = 0,
ThermoPRO_TX4DecoderStepSaveDuration,
ThermoPRO_TX4DecoderStepCheckDuration,
} ThermoPRO_TX4DecoderStep;
class FProtoWeatherThermoProTx4 : public FProtoWeatherBase {
public:
FProtoWeatherThermoProTx4() {
sensorType = FPW_THERMOPROTX4;
}
void feed(bool level, uint32_t duration) override {
switch (parser_step) {
case ThermoPRO_TX4DecoderStepReset:
if ((!level) && (DURATION_DIFF(duration, te_short * 18) < te_delta * 10)) {
// Found sync
parser_step = ThermoPRO_TX4DecoderStepSaveDuration;
decode_data = 0;
decode_count_bit = 0;
}
break;
case ThermoPRO_TX4DecoderStepSaveDuration:
if (level) {
te_last = duration;
parser_step = ThermoPRO_TX4DecoderStepCheckDuration;
} else {
parser_step = ThermoPRO_TX4DecoderStepReset;
}
break;
case ThermoPRO_TX4DecoderStepCheckDuration:
if (!level) {
if (DURATION_DIFF(duration, te_short * 18) < te_delta * 10) {
// Found sync
parser_step = ThermoPRO_TX4DecoderStepReset;
if ((decode_count_bit == min_count_bit_for_found) &&
ws_protocol_thermopro_tx4_check()) {
data = decode_data;
data_count_bit = decode_count_bit;
ws_protocol_thermopro_tx4_remote_controller();
if (callback) callback(this);
parser_step = ThermoPRO_TX4DecoderStepCheckDuration;
}
decode_data = 0;
decode_count_bit = 0;
break;
} else if (
(DURATION_DIFF(
te_last, te_short) < te_delta) &&
(DURATION_DIFF(duration, te_long) < te_delta * 2)) {
subghz_protocol_blocks_add_bit(0);
parser_step = ThermoPRO_TX4DecoderStepSaveDuration;
} else if (
(DURATION_DIFF(
te_last, te_short) < te_delta) &&
(DURATION_DIFF(duration, te_long * 2) < te_delta * 4)) {
subghz_protocol_blocks_add_bit(1);
parser_step = ThermoPRO_TX4DecoderStepSaveDuration;
} else {
parser_step = ThermoPRO_TX4DecoderStepReset;
}
} else {
parser_step = ThermoPRO_TX4DecoderStepReset;
}
break;
}
}
protected:
// timing values
uint32_t te_short = 500;
uint32_t te_long = 2000;
uint32_t te_delta = 150;
uint32_t min_count_bit_for_found = 37;
bool ws_protocol_thermopro_tx4_check() {
uint8_t type = decode_data >> 33;
if ((type == THERMO_PRO_TX4_TYPE_1) || (type == THERMO_PRO_TX4_TYPE_2)) {
return true;
} else {
return false;
}
}
void ws_protocol_thermopro_tx4_remote_controller() {
id = (data >> 25) & 0xFF;
battery_low = (data >> 24) & 1;
btn = (data >> 23) & 1;
channel = ((data >> 21) & 0x03) + 1;
if (!((data >> 20) & 1)) {
temp = (float)((data >> 9) & 0x07FF) / 10.0f;
} else {
temp = (float)((~(data >> 9) & 0x07FF) + 1) / -10.0f;
}
humidity = (data >> 1) & 0xFF;
}
};
#endif

View File

@ -0,0 +1,141 @@
#ifndef __FPROTO_TX8300_H__
#define __FPROTO_TX8300_H__
#include "weatherbase.hpp"
typedef enum {
TX_8300DecoderStepReset = 0,
TX_8300DecoderStepCheckPreambule,
TX_8300DecoderStepSaveDuration,
TX_8300DecoderStepCheckDuration,
} TX_8300DecoderStep;
#define TX_8300_PACKAGE_SIZE 32
class FProtoWeatherTX8300 : public FProtoWeatherBase {
public:
FProtoWeatherTX8300() {
sensorType = FPW_TX_8300;
}
void feed(bool level, uint32_t duration) override {
switch (parser_step) {
case TX_8300DecoderStepReset:
if ((level) && (DURATION_DIFF(duration, te_short * 2) < te_delta)) {
parser_step = TX_8300DecoderStepCheckPreambule;
}
break;
case TX_8300DecoderStepCheckPreambule:
if ((!level) && ((DURATION_DIFF(duration, te_short * 2) < te_delta) ||
(DURATION_DIFF(duration, te_short * 3) < te_delta))) {
parser_step = TX_8300DecoderStepSaveDuration;
decode_data = 0;
decode_count_bit = 1;
package_1 = 0;
package_2 = 0;
} else {
parser_step = TX_8300DecoderStepReset;
}
break;
case TX_8300DecoderStepSaveDuration:
if (level) {
te_last = duration;
parser_step = TX_8300DecoderStepCheckDuration;
} else {
parser_step = TX_8300DecoderStepReset;
}
break;
case TX_8300DecoderStepCheckDuration:
if (!level) {
if (duration >= ((uint32_t)te_short * 5)) {
// Found syncPostfix
if ((decode_count_bit ==
min_count_bit_for_found) &&
ws_protocol_tx_8300_check_crc()) {
data = package_1;
data_count_bit = decode_count_bit;
ws_protocol_tx_8300_remote_controller();
if (callback) callback(this);
}
decode_data = 0;
decode_count_bit = 1;
parser_step = TX_8300DecoderStepReset;
break;
} else if (
(DURATION_DIFF(te_last, te_short) <
te_delta) &&
(DURATION_DIFF(duration, te_long) <
te_delta * 2)) {
subghz_protocol_blocks_add_bit(1);
parser_step = TX_8300DecoderStepSaveDuration;
} else if (
(DURATION_DIFF(te_last, te_short) <
te_delta) &&
(DURATION_DIFF(duration, te_short) <
te_delta)) {
subghz_protocol_blocks_add_bit(0);
parser_step = TX_8300DecoderStepSaveDuration;
} else {
parser_step = TX_8300DecoderStepReset;
}
if (decode_count_bit == TX_8300_PACKAGE_SIZE) {
package_1 = decode_data;
decode_data = 0;
} else if (decode_count_bit == TX_8300_PACKAGE_SIZE * 2) {
package_2 = decode_data;
decode_data = 0;
}
} else {
parser_step = TX_8300DecoderStepReset;
}
break;
}
}
protected:
// timing values
uint32_t te_short = 1940;
uint32_t te_long = 3880;
uint32_t te_delta = 250;
uint32_t min_count_bit_for_found = 72;
uint32_t package_1{0};
uint32_t package_2{0};
bool ws_protocol_tx_8300_check_crc() {
if (!package_2) return false;
if (package_1 != ~package_2) return false;
uint16_t x = 0;
uint16_t y = 0;
for (int i = 0; i < 32; i += 4) {
x += (package_1 >> i) & 0x0F;
y += (package_1 >> i) & 0x05;
}
uint8_t crc = (~x & 0xF) << 4 | (~y & 0xF);
return (crc == (decode_data & 0xFF));
}
void ws_protocol_tx_8300_remote_controller() {
humidity = (((data >> 28) & 0x0F) * 10) + ((data >> 24) & 0x0F);
btn = WS_NO_BTN;
if (!((data >> 22) & 0x03))
battery_low = 0;
else
battery_low = 1;
channel = (data >> 20) & 0x03;
id = (data >> 12) & 0x7F;
float temp_raw = ((data >> 8) & 0x0F) * 10.0f + ((data >> 4) & 0x0F) +
(data & 0x0F) * 0.1f;
if (!((data >> 19) & 1)) {
temp = temp_raw;
} else {
temp = -temp_raw;
}
}
};
#endif

View File

@ -0,0 +1,156 @@
#ifndef __FPROTO_WENDOX_W6726_H__
#define __FPROTO_WENDOX_W6726_H__
#include "weatherbase.hpp"
typedef enum {
WendoxW6726DecoderStepReset = 0,
WendoxW6726DecoderStepCheckPreambule,
WendoxW6726DecoderStepSaveDuration,
WendoxW6726DecoderStepCheckDuration,
} WendoxW6726DecoderStep;
class FProtoWeatherWendoxW6726 : public FProtoWeatherBase {
public:
FProtoWeatherWendoxW6726() {
sensorType = FPW_WENDOX_W6726;
}
void feed(bool level, uint32_t duration) override {
switch (parser_step) {
case WendoxW6726DecoderStepReset:
if ((level) && (DURATION_DIFF(duration, te_short) <
te_delta)) {
parser_step = WendoxW6726DecoderStepCheckPreambule;
te_last = duration;
header_count = 0;
}
break;
case WendoxW6726DecoderStepCheckPreambule:
if (level) {
te_last = duration;
} else {
if ((DURATION_DIFF(te_last, te_short) <
te_delta * 1) &&
(DURATION_DIFF(duration, te_long) <
te_delta * 2)) {
header_count++;
} else if ((header_count > 4) && (header_count < 12)) {
if ((DURATION_DIFF(
te_last, te_long) <
te_delta * 2) &&
(DURATION_DIFF(duration, te_short) <
te_delta)) {
decode_data = 0;
decode_count_bit = 0;
subghz_protocol_blocks_add_bit(1);
parser_step = WendoxW6726DecoderStepSaveDuration;
} else {
parser_step = WendoxW6726DecoderStepReset;
}
} else {
parser_step = WendoxW6726DecoderStepReset;
}
}
break;
case WendoxW6726DecoderStepSaveDuration:
if (level) {
te_last = duration;
parser_step = WendoxW6726DecoderStepCheckDuration;
} else {
parser_step = WendoxW6726DecoderStepReset;
}
break;
case WendoxW6726DecoderStepCheckDuration:
if (!level) {
if (duration >
te_short + te_long) {
if (DURATION_DIFF(te_last, te_short) < te_delta) {
subghz_protocol_blocks_add_bit(0);
parser_step = WendoxW6726DecoderStepSaveDuration;
} else if (
DURATION_DIFF(te_last, te_long) < te_delta * 2) {
subghz_protocol_blocks_add_bit(1);
parser_step = WendoxW6726DecoderStepSaveDuration;
} else {
parser_step = WendoxW6726DecoderStepReset;
}
if ((decode_count_bit ==
min_count_bit_for_found) &&
ws_protocol_wendox_w6726_check()) {
data = decode_data;
data_count_bit = decode_count_bit;
ws_protocol_wendox_w6726_remote_controller();
if (callback) callback(this);
}
parser_step = WendoxW6726DecoderStepReset;
} else if (
(DURATION_DIFF(te_last, te_short) <
te_delta) &&
(DURATION_DIFF(duration, te_long) <
te_delta * 3)) {
subghz_protocol_blocks_add_bit(0);
parser_step = WendoxW6726DecoderStepSaveDuration;
} else if (
(DURATION_DIFF(te_last, te_long) <
te_delta * 2) &&
(DURATION_DIFF(duration, te_short) <
te_delta)) {
subghz_protocol_blocks_add_bit(1);
parser_step = WendoxW6726DecoderStepSaveDuration;
} else {
parser_step = WendoxW6726DecoderStepReset;
}
} else {
parser_step = WendoxW6726DecoderStepReset;
}
break;
}
}
protected:
// timing values
uint32_t te_short = 1955;
uint32_t te_long = 5865;
uint32_t te_delta = 300;
uint32_t min_count_bit_for_found = 29;
bool ws_protocol_wendox_w6726_check() {
if (!decode_data) return false;
uint8_t msg[] = {
static_cast<uint8_t>(decode_data >> 28),
static_cast<uint8_t>(decode_data >> 20),
static_cast<uint8_t>(decode_data >> 12),
static_cast<uint8_t>(decode_data >> 4)};
uint8_t crc = subghz_protocol_blocks_crc4(msg, 4, 0x9, 0xD);
return (crc == (decode_data & 0x0F));
}
void ws_protocol_wendox_w6726_remote_controller() {
id = (data >> 24) & 0xFF;
battery_low = (data >> 6) & 1;
channel = WS_NO_CHANNEL;
if (((data >> 23) & 1)) {
temp = (float)(((data >> 14) & 0x1FF) + 12) / 10.0f;
} else {
temp = (float)((~(data >> 14) & 0x1FF) + 1 - 12) / -10.0f;
}
if (temp < -50.0f) {
temp = -50.0f;
} else if (temp > 70.0f) {
temp = 70.0f;
}
btn = WS_NO_BTN;
humidity = WS_NO_HUMIDITY;
}
};
#endif

View File

@ -0,0 +1,210 @@
/*
Base class for all weather protocols.
This and most of the weather protocols uses code from Flipper XTreme codebase ( https://github.com/Flipper-XFW/Xtreme-Firmware/tree/dev/lib/subghz ). Thanks for their work!
For comments in a protocol implementation check w-nexus-th.hpp
*/
#ifndef __FPROTO_BASE_H__
#define __FPROTO_BASE_H__
#define bit_read(value, bit) (((value) >> (bit)) & 0x01)
#include "weathertypes.hpp"
#include <string>
// default walues to indicate 'no value'
#define WS_NO_ID 0xFFFFFFFF
#define WS_NO_BATT 0xFF
#define WS_NO_HUMIDITY 0xFF
#define WS_NO_CHANNEL 0xFF
#define WS_NO_BTN 0xFF
#define WS_NO_TEMPERATURE -273.0f
#define DURATION_DIFF(x, y) (((x) < (y)) ? ((y) - (x)) : ((x) - (y)))
typedef enum {
ManchesterStateStart1 = 0,
ManchesterStateMid1 = 1,
ManchesterStateMid0 = 2,
ManchesterStateStart0 = 3
} ManchesterState;
typedef enum {
ManchesterEventShortLow = 0,
ManchesterEventShortHigh = 2,
ManchesterEventLongLow = 4,
ManchesterEventLongHigh = 6,
ManchesterEventReset = 8
} ManchesterEvent;
class FProtoWeatherBase;
typedef void (*SubGhzProtocolDecoderBaseRxCallback)(FProtoWeatherBase* instance);
class FProtoWeatherBase {
public:
FProtoWeatherBase() {}
virtual ~FProtoWeatherBase() {}
virtual void feed(bool level, uint32_t duration) = 0; // need to be implemented on each protocol handler.
void setCallback(SubGhzProtocolDecoderBaseRxCallback cb) { callback = cb; } // this is called when there is a hit.
uint8_t getSensorType() { return sensorType; }
uint32_t getSensorId() { return id; }
float getTemp() { return temp; }
uint8_t getHumidity() { return humidity; }
uint8_t getBattLow() { return battery_low; }
uint32_t getTimestamp() { return timestamp; }
uint8_t getChannel() { return channel; }
uint8_t getButton() { return btn; }
protected:
// Helper functions to keep it as compatible with flipper as we can, so adding new protos will be easy.
void subghz_protocol_blocks_add_bit(uint8_t bit) {
decode_data = decode_data << 1 | bit;
decode_count_bit++;
}
uint8_t subghz_protocol_blocks_add_bytes(uint8_t const message[], size_t size) {
uint32_t result = 0;
for (size_t i = 0; i < size; ++i) {
result += message[i];
}
return (uint8_t)result;
}
uint8_t subghz_protocol_blocks_parity8(uint8_t byte) {
byte ^= byte >> 4;
byte &= 0xf;
return (0x6996 >> byte) & 1;
}
uint8_t subghz_protocol_blocks_parity_bytes(uint8_t const message[], size_t size) {
uint8_t result = 0;
for (size_t i = 0; i < size; ++i) {
result ^= subghz_protocol_blocks_parity8(message[i]);
}
return result;
}
uint8_t subghz_protocol_blocks_lfsr_digest8(
uint8_t const message[],
size_t size,
uint8_t gen,
uint8_t key) {
uint8_t sum = 0;
for (size_t byte = 0; byte < size; ++byte) {
uint8_t data = message[byte];
for (int i = 7; i >= 0; --i) {
// XOR key into sum if data bit is set
if ((data >> i) & 1) sum ^= key;
// roll the key right (actually the LSB is dropped here)
// and apply the gen (needs to include the dropped LSB as MSB)
if (key & 1)
key = (key >> 1) ^ gen;
else
key = (key >> 1);
}
}
return sum;
}
float locale_fahrenheit_to_celsius(float temp_f) {
return (temp_f - 32.f) / 1.8f;
}
bool manchester_advance(
ManchesterState state,
ManchesterEvent event,
ManchesterState* next_state,
bool* data) {
bool result = false;
ManchesterState new_state;
if (event == ManchesterEventReset) {
new_state = manchester_reset_state;
} else {
new_state = (ManchesterState)(transitions[state] >> event & 0x3);
if (new_state == state) {
new_state = manchester_reset_state;
} else {
if (new_state == ManchesterStateMid0) {
if (data) *data = false;
result = true;
} else if (new_state == ManchesterStateMid1) {
if (data) *data = true;
result = true;
}
}
}
*next_state = new_state;
return result;
}
uint8_t subghz_protocol_blocks_crc4(
uint8_t const message[],
size_t size,
uint8_t polynomial,
uint8_t init) {
uint8_t remainder = init << 4; // LSBs are unused
uint8_t poly = polynomial << 4;
uint8_t bit;
while (size--) {
remainder ^= *message++;
for (bit = 0; bit < 8; bit++) {
if (remainder & 0x80) {
remainder = (remainder << 1) ^ poly;
} else {
remainder = (remainder << 1);
}
}
}
return remainder >> 4 & 0x0f; // discard the LSBs
}
uint8_t subghz_protocol_blocks_lfsr_digest8_reflect(
uint8_t const message[],
size_t size,
uint8_t gen,
uint8_t key) {
uint8_t sum = 0;
// Process message from last byte to first byte (reflected)
for (int byte = size - 1; byte >= 0; --byte) {
uint8_t data = message[byte];
// Process individual bits of each byte (reflected)
for (uint8_t i = 0; i < 8; ++i) {
// XOR key into sum if data bit is set
if ((data >> i) & 1) {
sum ^= key;
}
// roll the key left (actually the LSB is dropped here)
// and apply the gen (needs to include the dropped lsb as MSB)
if (key & 0x80)
key = (key << 1) ^ gen;
else
key = (key << 1);
}
}
return sum;
}
uint64_t subghz_protocol_blocks_reverse_key(uint64_t key, uint8_t bit_count) {
uint64_t reverse_key = 0;
for (uint8_t i = 0; i < bit_count; i++) {
reverse_key = reverse_key << 1 | bit_read(key, i);
}
return reverse_key;
}
// General weather data holder
uint8_t sensorType = FPW_Invalid;
uint32_t id = WS_NO_ID;
float temp = WS_NO_TEMPERATURE;
uint8_t humidity = WS_NO_HUMIDITY;
uint8_t battery_low = WS_NO_BATT;
uint32_t timestamp = 0;
uint8_t channel = WS_NO_CHANNEL;
uint8_t btn = WS_NO_BTN;
// inner logic stuff, also for flipper compatibility. //todo revork a bit, so won't have dupes (decode_data + data, ..), but check if any of the protos uses it in the same time or not. (shouldn't)
SubGhzProtocolDecoderBaseRxCallback callback = NULL;
uint16_t header_count = 0;
uint8_t parser_step = 0;
uint32_t te_last = 0;
uint64_t data = 0;
uint32_t data_count_bit = 0;
uint64_t decode_data = 0;
uint32_t decode_count_bit = 0;
ManchesterState manchester_saved_state = ManchesterStateMid1;
static const ManchesterState manchester_reset_state = ManchesterStateMid1;
static inline const uint8_t transitions[] = {0b00000001, 0b10010001, 0b10011011, 0b11111011};
};
#endif

View File

@ -0,0 +1,78 @@
/*
This is the protocol list handler. It holds an instance of all known protocols.
So include here the .hpp, and add a new element to the protos vector in the constructor. That's all you need to do here if you wanna add a new proto.
@htotoo
*/
#include "w-nexus-th.hpp"
#include "w-acurite592txr.hpp"
#include "w-acurite606tx.hpp"
#include "w-acurite609tx.hpp"
#include "w-ambient.hpp"
#include "w-auriol-ahfl.hpp"
#include "w-auriol-th.hpp"
#include "w-gt-wt-02.hpp"
#include "w-gt-wt-03.hpp"
#include "w-infactory.hpp"
#include "w-lacrosse-tx.hpp"
#include "w-lacrosse-tx141thbv2.hpp"
#include "w-oregon2.hpp"
#include "w-oregon3.hpp"
#include "w-oregonv1.hpp"
#include "w-thermoprotx4.hpp"
#include "w-tx8300.hpp"
#include "w-wendox-w6726.hpp"
#include <vector>
#include <memory>
#include "portapack_shared_memory.hpp"
#ifndef __FPROTO_PROTOLIST_H__
#define __FPROTO_PROTOLIST_H__
class WeatherProtos {
public:
WeatherProtos() {
// add protos
protos.push_back(std::make_unique<FProtoWeatherNexusTH>()); // 1
protos.push_back(std::make_unique<FProtoWeatherAcurite592TXR>()); // 2
protos.push_back(std::make_unique<FProtoWeatherAcurite606TX>()); // 3
protos.push_back(std::make_unique<FProtoWeatherAcurite609TX>()); // 4
protos.push_back(std::make_unique<FProtoWeatherAmbient>()); // 5
protos.push_back(std::make_unique<FProtoWeatherAuriolAhfl>()); // 6
protos.push_back(std::make_unique<FProtoWeatherAuriolTh>()); // 7
protos.push_back(std::make_unique<FProtoWeatherGTWT02>()); // 8
protos.push_back(std::make_unique<FProtoWeatherGTWT03>()); // 9
protos.push_back(std::make_unique<FProtoWeatherInfactory>()); // 10
protos.push_back(std::make_unique<FProtoWeatherLaCrosseTx>()); // 11
protos.push_back(std::make_unique<FProtoWeatherLaCrosseTx141thbv2>()); // 12
protos.push_back(std::make_unique<FProtoWeatherOregon2>()); // 13
protos.push_back(std::make_unique<FProtoWeatherOregon3>()); // 14
protos.push_back(std::make_unique<FProtoWeatherOregonV1>()); // 15
protos.push_back(std::make_unique<FProtoWeatherThermoProTx4>()); // 16
protos.push_back(std::make_unique<FProtoWeatherTX8300>()); // 17
protos.push_back(std::make_unique<FProtoWeatherWendoxW6726>()); // 18
// set callback for them
for (const auto& obj : protos) {
obj->setCallback(callbackTarget);
}
}
static void callbackTarget(FProtoWeatherBase* instance) {
WeatherDataMessage packet_message{instance->getSensorType(), instance->getSensorId(),
instance->getTemp(), instance->getHumidity(), instance->getBattLow(),
instance->getChannel(), instance->getButton()};
shared_memory.application_queue.push(packet_message);
}
void feed(bool level, uint32_t duration) {
for (const auto& obj : protos) {
obj->feed(level, duration);
}
}
protected:
std::vector<std::unique_ptr<FProtoWeatherBase>> protos{};
};
#endif

View File

@ -0,0 +1,34 @@
#ifndef __FPROTO_WEATHERTYPES_H__
#define __FPROTO_WEATHERTYPES_H__
/*
Define known protocols.
These values must be present on the protocol's constructor, like FProtoWeatherAcurite592TXR() { sensorType = FPW_Acurite592TXR; }
Also it must have a switch-case element in the getWeatherSensorTypeName() function, to display it's name.
*/
enum FPROTO_WEATHER_SENSOR {
FPW_Invalid = 0,
FPW_NexusTH = 1,
FPW_Acurite592TXR = 2,
FPW_Acurite606TX = 3,
FPW_Acurite609TX = 4,
FPW_Ambient = 5,
FPW_AuriolAhfl = 6,
FPW_AuriolTH = 7,
FPW_GTWT02 = 8,
FPW_GTWT03 = 9,
FPW_INFACTORY = 10,
FPW_LACROSSETX = 11,
FPW_LACROSSETX141thbv2 = 12,
FPW_OREGON2 = 13,
FPW_OREGON3 = 14,
FPW_OREGONv1 = 15,
FPW_THERMOPROTX4 = 16,
FPW_TX_8300 = 17,
FPW_WENDOX_W6726 = 18
};
#endif

View File

@ -0,0 +1,69 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
*
* This file is part of PortaPack.
*
* 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, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "proc_weather.hpp"
#include "portapack_shared_memory.hpp"
#include "event_m4.hpp"
void WeatherProcessor::execute(const buffer_c8_t& buffer) {
if (!configured) return;
for (size_t i = 0; i < buffer.count; i++) {
int8_t re = buffer.p[i].real();
int8_t im = buffer.p[i].imag();
uint32_t mag = ((uint32_t)re * (uint32_t)re) + ((uint32_t)im * (uint32_t)im);
bool meashl = (mag > threshold);
tm += mag;
if (meashl == currentHiLow && currentDuration < 10'000'000) // allow pass 'end' signal
{
if (currentDuration < UINT32_MAX) currentDuration += usperTick;
} else { // called on change, so send the last duration and dir.
protoList.feed(currentHiLow, currentDuration / 1000);
currentDuration = usperTick;
currentHiLow = meashl;
}
}
cnt += buffer.count;
if (cnt > 30'000) {
threshold = (tm / cnt) / 2;
cnt = 0;
tm = 0;
if (threshold < 50) threshold = 50;
if (threshold > 1700) threshold = 1700;
}
}
void WeatherProcessor::on_message(const Message* const message) {
if (message->id == Message::ID::WeatherRxConfigure)
configure(*reinterpret_cast<const WeatherRxConfigureMessage*>(message));
}
void WeatherProcessor::configure(const WeatherRxConfigureMessage& message) {
(void)message;
configured = true;
}
int main() {
EventDispatcher event_dispatcher{std::make_unique<WeatherProcessor>()};
event_dispatcher.run();
return 0;
}

View File

@ -0,0 +1,61 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
*
* This file is part of PortaPack.
*
* 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, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
/*
Creator: @htotoo
*/
#ifndef __PROC_WEATHER_H__
#define __PROC_WEATHER_H__
#include "baseband_processor.hpp"
#include "baseband_thread.hpp"
#include "rssi_thread.hpp"
#include "message.hpp"
#include "fprotos/weatherprotos.hpp"
class WeatherProcessor : public BasebandProcessor {
public:
void execute(const buffer_c8_t& buffer) override;
void on_message(const Message* const message) override;
private:
static constexpr uint32_t usperTick = 500; // we nees ms to has to divide by 1000
static constexpr size_t baseband_fs = 1'750'000;
uint32_t currentDuration = 0;
uint32_t threshold = 0x0630; // will overwrite after the first iteration
bool currentHiLow = false;
bool configured{false};
// for debug
uint32_t cnt = 0;
uint32_t tm = 0;
WeatherProtos protoList{}; // holds all the protocols we can parse
void configure(const WeatherRxConfigureMessage& message);
/* NB: Threads should be the last members in the class definition. */
BasebandThread baseband_thread{baseband_fs, this, baseband::Direction::Receive};
RSSIThread rssi_thread{};
};
#endif /*__PROC_WEATHER_H__*/

View File

@ -115,6 +115,8 @@ class Message {
FSKRxConfigure = 58,
BlePacket = 58,
BTLETxConfigure = 59,
WeatherRxConfigure = 60,
WeatherData = 61,
MAX
};
@ -1236,4 +1238,40 @@ class SpectrumPainterBufferConfigureResponseMessage : public Message {
SpectrumPainterFIFO* fifo{nullptr};
};
class WeatherRxConfigureMessage : public Message {
public:
constexpr WeatherRxConfigureMessage()
: Message{ID::WeatherRxConfigure} {
// todoh give some more info
}
};
class WeatherDataMessage : public Message {
public:
constexpr WeatherDataMessage(
uint8_t sensorType = 0,
uint32_t id = 0xFFFFFFFF,
float temp = -273.0f,
uint8_t humidity = 0xFF,
uint8_t battery_low = 0xFF,
uint8_t channel = 0xFF,
uint8_t btn = 0xFF)
: Message{ID::WeatherData},
sensorType{sensorType},
id{id},
temp{temp},
humidity{humidity},
battery_low{battery_low},
channel{channel},
btn{btn} {
}
uint8_t sensorType = 0;
uint32_t id = 0xFFFFFFFF;
float temp = -273.0f;
uint8_t humidity = 0xFF;
uint8_t battery_low = 0xFF;
uint8_t channel = 0xFF;
uint8_t btn = 0xFF;
};
#endif /*__MESSAGE_H__*/

View File

@ -112,6 +112,8 @@ constexpr image_tag_t image_tag_tones{'P', 'T', 'O', 'N'};
constexpr image_tag_t image_tag_flash_utility{'P', 'F', 'U', 'T'};
constexpr image_tag_t image_tag_usb_sd{'P', 'U', 'S', 'B'};
constexpr image_tag_t image_tag_weather{'P', 'W', 'T', 'H'};
constexpr image_tag_t image_tag_noop{'P', 'N', 'O', 'P'};
constexpr image_tag_t image_tag_hackrf{'H', 'R', 'F', '1'};