diff --git a/firmware/application/apps/ui_about_simple.cpp b/firmware/application/apps/ui_about_simple.cpp index 47026703..f5586a59 100644 --- a/firmware/application/apps/ui_about_simple.cpp +++ b/firmware/application/apps/ui_about_simple.cpp @@ -37,7 +37,7 @@ void AboutView::update() { break; case 2: console.writeln("NotherNgineer,zxkmm,u-foka"); - console.writeln("Netro"); + console.writeln("Netro,HTotoo"); console.writeln(""); break; diff --git a/firmware/application/apps/ui_weatherstation.cpp b/firmware/application/apps/ui_weatherstation.cpp index 60c75239..1909c04e 100644 --- a/firmware/application/apps/ui_weatherstation.cpp +++ b/firmware/application/apps/ui_weatherstation.cpp @@ -40,12 +40,7 @@ void WeatherRecentEntryDetailView::update_data() { 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(); + text_age.set(to_string_dec_uint(entry_.age) + " sec"); } WeatherRecentEntryDetailView::WeatherRecentEntryDetailView(NavigationView& nav, const WeatherRecentEntry& entry) @@ -58,6 +53,7 @@ WeatherRecentEntryDetailView::WeatherRecentEntryDetailView(NavigationView& nav, &text_hum, &text_ch, &text_batt, + &text_age, &labels}); button_done.on_select = [&nav](const ui::Button&) { @@ -106,6 +102,16 @@ WeatherView::WeatherView(NavigationView& nav) }; baseband::set_weather(); receiver_model.enable(); + signal_token_tick_second = rtc_time::signal_tick_second += [this]() { + on_tick_second(); + }; +} + +void WeatherView::on_tick_second() { + for (auto& entry : recent) { + entry.inc_age(1); + } + recent_entries_view.set_dirty(); } void WeatherView::on_data(const WeatherDataMessage* data) { @@ -113,6 +119,7 @@ void WeatherView::on_data(const WeatherDataMessage* data) { auto matching_recent = find(recent, key.key()); if (matching_recent != std::end(recent)) { // Found within. Move to front of list, increment counter. + (*matching_recent).reset_age(); recent.push_front(*matching_recent); recent.erase(matching_recent); } else { @@ -123,6 +130,7 @@ void WeatherView::on_data(const WeatherDataMessage* data) { } WeatherView::~WeatherView() { + rtc_time::signal_tick_second -= signal_token_tick_second; receiver_model.disable(); baseband::shutdown(); } @@ -165,6 +173,8 @@ const char* WeatherView::getWeatherSensorTypeName(FPROTO_WEATHER_SENSOR type) { return "TX 8300"; case FPW_WENDOX_W6726: return "Wendox W6726"; + case FPW_Acurite986: + return "Acurite986"; case FPW_Invalid: default: @@ -187,19 +197,21 @@ void RecentEntriesTable::draw( line.reserve(30); line = WeatherView::getWeatherSensorTypeName((FPROTO_WEATHER_SENSOR)entry.sensorType); - if (line.length() < 13) { - line += WeatherView::pad_string_with_spaces(13 - line.length()); + if (line.length() < 10) { + line += WeatherView::pad_string_with_spaces(10 - line.length()); } else { - line = truncate(line, 13); + line = truncate(line, 10); } - std::string temp = (weather_units_fahr ? to_string_decimal((entry.temp * 9 / 5) + 32, 1) : to_string_decimal(entry.temp, 2)); + std::string temp = (weather_units_fahr ? to_string_decimal((entry.temp * 9 / 5) + 32, 1) : to_string_decimal(entry.temp, 1)); std::string humStr = to_string_dec_uint(entry.humidity) + "%"; std::string chStr = to_string_dec_uint(entry.channel); + std::string ageStr = to_string_dec_uint(entry.age); - line += WeatherView::pad_string_with_spaces(7 - temp.length()) + temp; + line += WeatherView::pad_string_with_spaces(6 - temp.length()) + temp; line += WeatherView::pad_string_with_spaces(5 - humStr.length()) + humStr; line += WeatherView::pad_string_with_spaces(4 - chStr.length()) + chStr; + line += WeatherView::pad_string_with_spaces(4 - ageStr.length()) + ageStr; line.resize(target_rect.width() / 8, ' '); painter.draw_string(target_rect.location(), style, line); diff --git a/firmware/application/apps/ui_weatherstation.hpp b/firmware/application/apps/ui_weatherstation.hpp index ef964a24..52cc77b5 100644 --- a/firmware/application/apps/ui_weatherstation.hpp +++ b/firmware/application/apps/ui_weatherstation.hpp @@ -48,6 +48,7 @@ struct WeatherRecentEntry { uint8_t humidity = 0xFF; uint8_t battery_low = 0xFF; uint8_t channel = 0xFF; + uint16_t age = 0; // updated on each seconds, show how long the signal was last seen WeatherRecentEntry() {} WeatherRecentEntry( @@ -71,6 +72,12 @@ struct WeatherRecentEntry { (static_cast(battery_low) & 0xF) << 4 | (static_cast(channel) & 0xF); } + void inc_age(int delta) { + if (UINT16_MAX - delta > age) age += delta; + } + void reset_age() { + age = 0; + } }; using WeatherRecentEntries = RecentEntries; using WeatherRecentEntriesView = RecentEntriesView; @@ -87,6 +94,7 @@ class WeatherView : public View { static std::string pad_string_with_spaces(int snakes); private: + void on_tick_second(); void on_data(const WeatherDataMessage* data); NavigationView& nav_; @@ -122,6 +130,8 @@ class WeatherView : public View { {0 * 8, 0 * 16}, nav_}; + SignalToken signal_token_tick_second{}; + Button button_clear_list{ {0, 16, 7 * 8, 32}, "Clear"}; @@ -129,11 +139,11 @@ class WeatherView : public View { static constexpr auto header_height = 3 * 16; const RecentEntriesColumns columns{{ - {"Type", 13}, - {"Temp", 6}, + {"Type", 10}, + {"Temp", 5}, {"Hum", 4}, {"Ch", 3}, - + {"Age", 3}, }}; WeatherRecentEntriesView recent_entries_view{columns, recent}; @@ -149,9 +159,6 @@ 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; @@ -164,6 +171,7 @@ class WeatherRecentEntryDetailView : public View { 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}, "?"}; + Text text_age{{11 * 8, 7 * 16, 6 * 8, 16}, "?"}; Labels labels{ {{0 * 8, 0 * 16}, "Weather station type:", Color::light_grey()}, @@ -172,6 +180,7 @@ class WeatherRecentEntryDetailView : public View { {{0 * 8, 4 * 16}, "Humidity:", Color::light_grey()}, {{0 * 8, 5 * 16}, "Channel:", Color::light_grey()}, {{0 * 8, 6 * 16}, "Battery:", Color::light_grey()}, + {{0 * 8, 7 * 16}, "Age:", Color::light_grey()}, }; Button button_done{ diff --git a/firmware/baseband/fprotos/w-acurite986.hpp b/firmware/baseband/fprotos/w-acurite986.hpp new file mode 100644 index 00000000..24dce719 --- /dev/null +++ b/firmware/baseband/fprotos/w-acurite986.hpp @@ -0,0 +1,138 @@ + +#ifndef __FPROTO_Acurite_986_H__ +#define __FPROTO_Acurite_986_H__ + +#include "weatherbase.hpp" + +typedef enum { + Acurite_986DecoderStepReset = 0, + Acurite_986DecoderStepSync1, + Acurite_986DecoderStepSync2, + Acurite_986DecoderStepSync3, + Acurite_986DecoderStepSaveDuration, + Acurite_986DecoderStepCheckDuration, +} Acurite_986DecoderStep; + +class FProtoWeatherAcurite986 : public FProtoWeatherBase { + public: + FProtoWeatherAcurite986() { + sensorType = FPW_Acurite986; + } + + void feed(bool level, uint32_t duration) { + switch (parser_step) { + case Acurite_986DecoderStepReset: + if ((!level) && (DURATION_DIFF(duration, te_long) < te_delta * 15)) { + // Found 1st sync bit + parser_step = Acurite_986DecoderStepSync1; + decode_data = 0; + decode_count_bit = 0; + } + break; + + case Acurite_986DecoderStepSync1: + if (DURATION_DIFF(duration, te_long) < te_delta * 15) { + if (!level) { + parser_step = Acurite_986DecoderStepSync2; + } + } else { + parser_step = Acurite_986DecoderStepReset; + } + break; + + case Acurite_986DecoderStepSync2: + if (DURATION_DIFF(duration, te_long) < te_delta * 15) { + if (!level) { + parser_step = Acurite_986DecoderStepSync3; + } + } else { + parser_step = Acurite_986DecoderStepReset; + } + break; + + case Acurite_986DecoderStepSync3: + if (DURATION_DIFF(duration, te_long) < te_delta * 15) { + if (!level) { + parser_step = Acurite_986DecoderStepSaveDuration; + } + } else { + parser_step = Acurite_986DecoderStepReset; + } + break; + + case Acurite_986DecoderStepSaveDuration: + if (level) { + te_last = duration; + parser_step = Acurite_986DecoderStepCheckDuration; + } else { + parser_step = Acurite_986DecoderStepReset; + } + break; + + case Acurite_986DecoderStepCheckDuration: + if (!level) { + if (DURATION_DIFF(duration, te_short) < + te_delta * 10) { + if (duration < te_short) { + subghz_protocol_blocks_add_bit(0); + parser_step = Acurite_986DecoderStepSaveDuration; + } else { + subghz_protocol_blocks_add_bit(1); + parser_step = Acurite_986DecoderStepSaveDuration; + } + } else { + // Found syncPostfix + parser_step = Acurite_986DecoderStepReset; + if ((decode_count_bit == min_count_bit_for_found) && ws_protocol_acurite_986_check()) { + data = decode_data; + data_count_bit = decode_count_bit; + ws_protocol_acurite_986_remote_controller(); + if (callback) callback(this); + } + decode_data = 0; + decode_count_bit = 0; + } + } else { + parser_step = Acurite_986DecoderStepReset; + } + break; + } + } + + protected: + uint32_t te_short = 800; + uint32_t te_long = 1750; + uint32_t te_delta = 50; + uint32_t min_count_bit_for_found = 40; + + void ws_protocol_acurite_986_remote_controller() { + int temp; + + id = subghz_protocol_blocks_reverse_key(data >> 24, 8); + id = (id << 8) | subghz_protocol_blocks_reverse_key(data >> 16, 8); + battery_low = (data >> 14) & 1; + channel = ((data >> 15) & 1) + 1; + + temp = subghz_protocol_blocks_reverse_key(data >> 32, 8); + if (temp & 0x80) { + temp = -(temp & 0x7F); + } + temp = locale_fahrenheit_to_celsius((float)temp); + btn = WS_NO_BTN; + humidity = WS_NO_HUMIDITY; + } + + bool ws_protocol_acurite_986_check() { + if (!decode_data) return false; + uint8_t msg[] = { + (uint8_t)(decode_data >> 32), + (uint8_t)(decode_data >> 24), + (uint8_t)(decode_data >> 16), + (uint8_t)(decode_data >> 8)}; + + uint8_t crc = subghz_protocol_blocks_crc8(msg, 4, 0x07, 0x00); + return (crc == (decode_data & 0xFF)); + } +}; + +#endif diff --git a/firmware/baseband/fprotos/weatherbase.hpp b/firmware/baseband/fprotos/weatherbase.hpp index a4366d79..aa65940d 100644 --- a/firmware/baseband/fprotos/weatherbase.hpp +++ b/firmware/baseband/fprotos/weatherbase.hpp @@ -183,6 +183,26 @@ class FProtoWeatherBase { } return reverse_key; } + uint8_t subghz_protocol_blocks_crc8( + uint8_t const message[], + size_t size, + uint8_t polynomial, + uint8_t init) { + uint8_t remainder = init; + + for (size_t byte = 0; byte < size; ++byte) { + remainder ^= message[byte]; + for (uint8_t bit = 0; bit < 8; ++bit) { + if (remainder & 0x80) { + remainder = (remainder << 1) ^ polynomial; + } else { + remainder = (remainder << 1); + } + } + } + return remainder; + } + // General weather data holder uint8_t sensorType = FPW_Invalid; uint32_t id = WS_NO_ID; diff --git a/firmware/baseband/fprotos/weatherprotos.hpp b/firmware/baseband/fprotos/weatherprotos.hpp index f9e80074..dcbccf54 100644 --- a/firmware/baseband/fprotos/weatherprotos.hpp +++ b/firmware/baseband/fprotos/weatherprotos.hpp @@ -21,7 +21,7 @@ So include here the .hpp, and add a new element to the protos vector in the cons #include "w-thermoprotx4.hpp" #include "w-tx8300.hpp" #include "w-wendox-w6726.hpp" - +#include "w-acurite986.hpp" #include #include #include "portapack_shared_memory.hpp" @@ -51,6 +51,7 @@ class WeatherProtos { protos.push_back(std::make_unique()); // 16 protos.push_back(std::make_unique()); // 17 protos.push_back(std::make_unique()); // 18 + protos.push_back(std::make_unique()); // 19 // set callback for them for (const auto& obj : protos) { diff --git a/firmware/baseband/fprotos/weathertypes.hpp b/firmware/baseband/fprotos/weathertypes.hpp index 5a3eec52..bf1798c8 100644 --- a/firmware/baseband/fprotos/weathertypes.hpp +++ b/firmware/baseband/fprotos/weathertypes.hpp @@ -27,8 +27,8 @@ enum FPROTO_WEATHER_SENSOR { FPW_OREGONv1 = 15, FPW_THERMOPROTX4 = 16, FPW_TX_8300 = 17, - FPW_WENDOX_W6726 = 18 - + FPW_WENDOX_W6726 = 18, + FPW_Acurite986 = 19 }; #endif \ No newline at end of file