diff --git a/firmware/application/apps/ui_subghzd.cpp b/firmware/application/apps/ui_subghzd.cpp index b041b04d2..ed9dfcf55 100644 --- a/firmware/application/apps/ui_subghzd.cpp +++ b/firmware/application/apps/ui_subghzd.cpp @@ -140,6 +140,8 @@ const char* SubGhzDView::getSensorTypeName(FPROTO_SUBGHZD_SENSOR type) { return "Airforce"; case FPS_CAMEATOMO: return "Came Atomo"; + case FPS_CAMETWEE: + return "Came Twee"; case FPS_Invalid: default: return "Unknown"; diff --git a/firmware/application/apps/ui_weatherstation.cpp b/firmware/application/apps/ui_weatherstation.cpp index aab140f91..a09ae7f23 100644 --- a/firmware/application/apps/ui_weatherstation.cpp +++ b/firmware/application/apps/ui_weatherstation.cpp @@ -96,7 +96,7 @@ WeatherView::WeatherView(NavigationView& nav) &button_clear_list, &recent_entries_view}); - baseband::run_image(portapack::spi_flash::image_tag_subghzd); + baseband::run_image(portapack::spi_flash::image_tag_weather); button_clear_list.on_select = [this](Button&) { recent.clear(); diff --git a/firmware/application/baseband_api.cpp b/firmware/application/baseband_api.cpp index 698b91aea..7317ab0c0 100644 --- a/firmware/application/baseband_api.cpp +++ b/firmware/application/baseband_api.cpp @@ -320,12 +320,12 @@ void set_spectrum_painter_config(const uint16_t width, const uint16_t height, bo } void set_weather() { - const SubGhzFPRxConfigureMessage message{0, 0}; + const SubGhzFPRxConfigureMessage message{0}; send_message(&message); } void set_subghzd(uint8_t modulation = 0) { - const SubGhzFPRxConfigureMessage message{1, modulation}; + const SubGhzFPRxConfigureMessage message{modulation}; send_message(&message); } diff --git a/firmware/baseband/CMakeLists.txt b/firmware/baseband/CMakeLists.txt index 45a9fc3f2..8d2d8f55c 100644 --- a/firmware/baseband/CMakeLists.txt +++ b/firmware/baseband/CMakeLists.txt @@ -550,12 +550,19 @@ set(MODE_CPPSRC ) DeclareTargets(PWFM wfm_audio) -### SubGhz Decoders + Weather Stations +### SubGhz Decoders set(MODE_CPPSRC proc_subghzd.cpp ) -DeclareTargets(PWTH subghzd) +DeclareTargets(PSGD subghzd) + + +### Weather Stations +set(MODE_CPPSRC + proc_weather.cpp +) +DeclareTargets(PWTH weather) ### Flash Utility diff --git a/firmware/baseband/fprotos/s-came_twee.hpp b/firmware/baseband/fprotos/s-came_twee.hpp new file mode 100644 index 000000000..6a3aaab00 --- /dev/null +++ b/firmware/baseband/fprotos/s-came_twee.hpp @@ -0,0 +1,151 @@ + +#ifndef __FPROTO_CAMETWEE_H__ +#define __FPROTO_CAMETWEE_H__ + +#include "subghzdbase.hpp" + +typedef enum { + CameTweeDecoderStepReset = 0, + CameTweeDecoderStepDecoderData, +} CameTweeDecoderStep; + +class FProtoSubGhzDCameTwee : public FProtoSubGhzDBase { + public: + FProtoSubGhzDCameTwee() { + sensorType = FPS_CAMETWEE; + } + + void feed(bool level, uint32_t duration) { + ManchesterEvent event = ManchesterEventReset; + switch (parser_step) { + case CameTweeDecoderStepReset: + if ((!level) && (DURATION_DIFF(duration, te_long * 51) < te_delta * 20)) { + // Found header CAME + parser_step = CameTweeDecoderStepDecoderData; + decode_data = 0; + decode_count_bit = 0; + FProtoGeneral::manchester_advance(manchester_saved_state, ManchesterEventLongLow, &manchester_saved_state, NULL); + FProtoGeneral::manchester_advance(manchester_saved_state, ManchesterEventLongHigh, &manchester_saved_state, NULL); + FProtoGeneral::manchester_advance(manchester_saved_state, ManchesterEventShortLow, &manchester_saved_state, NULL); + } + break; + case CameTweeDecoderStepDecoderData: + if (!level) { + 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 + te_delta)) { + if (decode_count_bit == min_count_bit_for_found) { + data = decode_data; + data_count_bit = decode_count_bit; + subghz_protocol_came_twee_remote_controller(); + if (callback) callback(this); + } + decode_data = 0; + decode_count_bit = 0; + FProtoGeneral::manchester_advance(manchester_saved_state, ManchesterEventLongLow, &manchester_saved_state, NULL); + FProtoGeneral::manchester_advance(manchester_saved_state, ManchesterEventLongHigh, &manchester_saved_state, NULL); + FProtoGeneral::manchester_advance(manchester_saved_state, ManchesterEventShortLow, &manchester_saved_state, NULL); + } else { + parser_step = CameTweeDecoderStepReset; + } + } else { + if (DURATION_DIFF(duration, te_short) < + te_delta) { + event = ManchesterEventShortHigh; + } else if ( + DURATION_DIFF(duration, te_long) < + te_delta) { + event = ManchesterEventLongHigh; + } else { + parser_step = CameTweeDecoderStepReset; + } + } + if (event != ManchesterEventReset) { + bool data; + bool data_ok = FProtoGeneral::manchester_advance(manchester_saved_state, event, &manchester_saved_state, &data); + if (data_ok) { + decode_data = (decode_data << 1) | !data; + decode_count_bit++; + } + } + break; + } + } + + protected: + uint32_t te_short = 500; + uint32_t te_long = 1000; + uint32_t te_delta = 250; + uint32_t min_count_bit_for_found = 54; + + void subghz_protocol_came_twee_remote_controller() { + /* Came Twee 54 bit, rolling code 15 parcels with + * a decreasing counter from 0xE to 0x0 + * with originally coded dip switches on the console 10 bit code + * + * 0x003FFF72E04A6FEE + * 0x003FFF72D17B5EDD + * 0x003FFF72C2684DCC + * 0x003FFF72B3193CBB + * 0x003FFF72A40E2BAA + * 0x003FFF72953F1A99 + * 0x003FFF72862C0988 + * 0x003FFF7277DDF877 + * 0x003FFF7268C2E766 + * 0x003FFF7259F3D655 + * 0x003FFF724AE0C544 + * 0x003FFF723B91B433 + * 0x003FFF722C86A322 + * 0x003FFF721DB79211 + * 0x003FFF720EA48100 + * + * decryption + * the last 32 bits, do XOR by the desired number, divide the result by 4, + * convert the first 16 bits of the resulting 32-bit number to bin and do + * bit-by-bit mirroring, adding up to 10 bits + * + * Example + * Step 1. 0x003FFF721DB79211 => 0x1DB79211 + * Step 4. 0x1DB79211 xor 0x1D1D1D11 => 0x00AA8F00 + * Step 4. 0x00AA8F00 / 4 => 0x002AA3C0 + * Step 5. 0x002AA3C0 => 0x002A + * Step 6. 0x002A bin => b101010 + * Step 7. b101010 => b0101010000 + * Step 8. b0101010000 => (Dip) Off ON Off ON Off ON Off Off Off Off + */ + + uint8_t cnt_parcel = (uint8_t)(data & 0xF); + uint32_t data = (uint32_t)(data & 0x0FFFFFFFF); + + data = (data ^ came_twee_magic_numbers_xor[cnt_parcel]); + serial = data; + data /= 4; + btn = (data >> 4) & 0x0F; + data >>= 16; + data = (uint16_t)FProtoGeneral::subghz_protocol_blocks_reverse_key(data, 16); + cnt = data >> 6; + } + inline static const uint32_t came_twee_magic_numbers_xor[15] = { + 0x0E0E0E00, + 0x1D1D1D11, + 0x2C2C2C22, + 0x3B3B3B33, + 0x4A4A4A44, + 0x59595955, + 0x68686866, + 0x77777777, + 0x86868688, + 0x95959599, + 0xA4A4A4AA, + 0xB3B3B3BB, + 0xC2C2C2CC, + 0xD1D1D1DD, + 0xE0E0E0EE, + }; +}; + +#endif diff --git a/firmware/baseband/fprotos/subghzdprotos.hpp b/firmware/baseband/fprotos/subghzdprotos.hpp index b02931a68..7edb5ea94 100644 --- a/firmware/baseband/fprotos/subghzdprotos.hpp +++ b/firmware/baseband/fprotos/subghzdprotos.hpp @@ -15,6 +15,7 @@ So include here the .hpp, and add a new element to the protos vector in the cons #include "s-bett.hpp" #include "s-came.hpp" #include "s-came_atomo.hpp" +#include "s-came_twee.hpp" #ifndef __FPROTO_PROTOLISTSGZ_H__ #define __FPROTO_PROTOLISTSGZ_H__ @@ -28,6 +29,7 @@ class SubGhzDProtos : public FProtoListGeneral { protos.push_back(std::make_unique()); // 3 protos.push_back(std::make_unique()); // 4, 5, 6 protos.push_back(std::make_unique()); // 7 + protos.push_back(std::make_unique()); // 8 // set callback for them for (const auto& obj : protos) { diff --git a/firmware/baseband/fprotos/subghztypes.hpp b/firmware/baseband/fprotos/subghztypes.hpp index f26f7c3e1..fb035d570 100644 --- a/firmware/baseband/fprotos/subghztypes.hpp +++ b/firmware/baseband/fprotos/subghztypes.hpp @@ -26,6 +26,7 @@ enum FPROTO_SUBGHZD_SENSOR { FPS_PRASTEL = 5, FPS_AIRFORCE = 6, FPS_CAMEATOMO = 7, + FPS_CAMETWEE = 8, }; #endif \ No newline at end of file diff --git a/firmware/baseband/proc_subghzd.cpp b/firmware/baseband/proc_subghzd.cpp index 2bbeb8dd5..e222bca06 100644 --- a/firmware/baseband/proc_subghzd.cpp +++ b/firmware/baseband/proc_subghzd.cpp @@ -24,7 +24,7 @@ #include "portapack_shared_memory.hpp" #include "event_m4.hpp" -void WeatherProcessor::execute(const buffer_c8_t& buffer) { +void SubGhzDProcessor::execute(const buffer_c8_t& buffer) { if (!configured) return; // SR = 4Mhz , and we are decimating by /8 in total , decim1_out clock 4Mhz /8= 500khz samples/sec. @@ -65,28 +65,18 @@ void WeatherProcessor::execute(const buffer_c8_t& buffer) { } } -void WeatherProcessor::on_message(const Message* const message) { +void SubGhzDProcessor::on_message(const Message* const message) { if (message->id == Message::ID::WeatherRxConfigure) configure(*reinterpret_cast(message)); } -void WeatherProcessor::configure(const SubGhzFPRxConfigureMessage& message) { +void SubGhzDProcessor::configure(const SubGhzFPRxConfigureMessage& message) { // constexpr size_t decim_0_output_fs = baseband_fs / decim_0.decimation_factor; //unused // constexpr size_t decim_1_output_fs = decim_0_output_fs / decim_1.decimation_factor; //unused decim_0.configure(taps_200k_wfm_decim_0.taps); decim_1.configure(taps_200k_wfm_decim_1.taps); - if (protoMode != message.protoMode) { - // change it. - FProtoListGeneral* tmp = protoList; - protoList = NULL; - protoMode = message.protoMode; - if (tmp) free(tmp); // takes some time - if (protoMode == 0) protoList = new WeatherProtos(); - if (protoMode == 1) protoList = new SubGhzDProtos(); - } - modulation = message.modulation; // NIY if (protoList != NULL) { @@ -97,7 +87,7 @@ void WeatherProcessor::configure(const SubGhzFPRxConfigureMessage& message) { } int main() { - EventDispatcher event_dispatcher{std::make_unique()}; + EventDispatcher event_dispatcher{std::make_unique()}; event_dispatcher.run(); return 0; } diff --git a/firmware/baseband/proc_subghzd.hpp b/firmware/baseband/proc_subghzd.hpp index 5b44c4d7d..c55fd0ebf 100644 --- a/firmware/baseband/proc_subghzd.hpp +++ b/firmware/baseband/proc_subghzd.hpp @@ -32,10 +32,9 @@ #include "message.hpp" #include "dsp_decimate.hpp" -#include "fprotos/weatherprotos.hpp" #include "fprotos/subghzdprotos.hpp" -class WeatherProcessor : public BasebandProcessor { +class SubGhzDProcessor : public BasebandProcessor { public: void execute(const buffer_c8_t& buffer) override; void on_message(const Message* const message) override; @@ -65,7 +64,7 @@ class WeatherProcessor : public BasebandProcessor { uint32_t cnt = 0; uint32_t tm = 0; - FProtoListGeneral* protoList = NULL; // holds all the protocols we can parse + FProtoListGeneral* protoList = new SubGhzDProtos(); // holds all the protocols we can parse void configure(const SubGhzFPRxConfigureMessage& message); /* NB: Threads should be the last members in the class definition. */ diff --git a/firmware/baseband/proc_weather.cpp b/firmware/baseband/proc_weather.cpp new file mode 100644 index 000000000..f6fabc009 --- /dev/null +++ b/firmware/baseband/proc_weather.cpp @@ -0,0 +1,88 @@ +/* + * 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; + + // SR = 4Mhz , and we are decimating by /8 in total , decim1_out clock 4Mhz /8= 500khz samples/sec. + // buffer has 2048 complex i8 I,Q signed samples + // decim0 out: 2048/4 = 512 complex i16 I,Q signed samples + // decim1 out: 512/2 = 256 complex i16 I,Q signed samples + // Regarding Filters, we are re-using existing FIR filters, @4Mhz, FIR decim1 ilter, BW =+-220Khz (at -3dB's). BW = 440kHZ. + + const auto decim_0_out = decim_0.execute(buffer, dst_buffer); // Input:2048 complex/4 (decim factor) = 512_output complex (1024 I/Q samples) + const auto decim_1_out = decim_1.execute(decim_0_out, dst_buffer); // Input:512 complex/2 (decim factor) = 256_output complex ( 512 I/Q samples) + + for (size_t i = 0; i < decim_1_out.count; i++) { + int16_t re = decim_1_out.p[i].real(); + int16_t im = decim_1_out.p[i].imag(); + uint32_t mag = ((uint32_t)re * (uint32_t)re) + ((uint32_t)im * (uint32_t)im); + + mag = (mag >> 12); // Decim samples are calculated with saturated gain . (we could also reduce that sat. param at configure time) + + bool meashl = (mag > threshold); + tm += mag; + if (meashl == currentHiLow && currentDuration < 10'000'000) // allow pass 'end' signal + { + if (currentDuration < UINT32_MAX) currentDuration += nsPerDecSamp; + } else { // called on change, so send the last duration and dir. + if (protoList) protoList->feed(currentHiLow, currentDuration / 1000); + currentDuration = nsPerDecSamp; + currentHiLow = meashl; + } + } + + cnt += decim_1_out.count; // TODO , check if it is necessary that xdecim factor. + 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(message)); +} + +void WeatherProcessor::configure(const SubGhzFPRxConfigureMessage& message) { + // constexpr size_t decim_0_output_fs = baseband_fs / decim_0.decimation_factor; //unused + // constexpr size_t decim_1_output_fs = decim_0_output_fs / decim_1.decimation_factor; //unused + (void)message; // unused + + decim_0.configure(taps_200k_wfm_decim_0.taps); + decim_1.configure(taps_200k_wfm_decim_1.taps); + + configured = true; +} + +int main() { + EventDispatcher event_dispatcher{std::make_unique()}; + event_dispatcher.run(); + return 0; +} diff --git a/firmware/baseband/proc_weather.hpp b/firmware/baseband/proc_weather.hpp new file mode 100644 index 000000000..835e6d6d5 --- /dev/null +++ b/firmware/baseband/proc_weather.hpp @@ -0,0 +1,75 @@ +/* + * 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 "dsp_decimate.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 size_t baseband_fs = 4'000'000; // it works, I think we need to write that master clock in the baseband_threat , even later we decimate it. + static constexpr uint32_t nsPerDecSamp = 1'000'000'000 / baseband_fs * 8; // 10 exp9/baseband_fs * 8 + + /* Array Buffer aux. used in decim0 and decim1 IQ c16 signed data ; (decim0 defines the max length of the array) */ + std::array dst{}; // decim0 /4 , 2048/4 = 512 complex I,Q + const buffer_c16_t dst_buffer{ + dst.data(), + dst.size()}; + + /* Decimates */ + dsp::decimate::FIRC8xR16x24FS4Decim4 decim_0{}; + dsp::decimate::FIRC16xR16x16Decim2 decim_1{}; + + uint32_t currentDuration = 0; + uint32_t threshold = 0x0630; // will overwrite after the first iteration + bool currentHiLow = false; + bool configured{false}; + + uint8_t modulation = 255; // 0 AM, 1 FM 255 = Not set + uint8_t protoMode = 255; // 0 weather, 1 subghzd, 255 = Not set + // for threshold + uint32_t cnt = 0; + uint32_t tm = 0; + + FProtoListGeneral* protoList = new WeatherProtos(); // holds all the protocols we can parse + void configure(const SubGhzFPRxConfigureMessage& 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__*/ diff --git a/firmware/common/message.hpp b/firmware/common/message.hpp index 590169b0c..e34c37a24 100644 --- a/firmware/common/message.hpp +++ b/firmware/common/message.hpp @@ -1241,11 +1241,9 @@ class SpectrumPainterBufferConfigureResponseMessage : public Message { class SubGhzFPRxConfigureMessage : public Message { public: - constexpr SubGhzFPRxConfigureMessage(uint8_t protoMode = 0, uint8_t modulation = 0) - : Message{ID::WeatherRxConfigure}, protoMode{protoMode}, modulation{modulation} { - // todoh give some more info + constexpr SubGhzFPRxConfigureMessage(uint8_t modulation = 0) + : Message{ID::WeatherRxConfigure}, modulation{modulation} { } - uint8_t protoMode = 0; // 0 weather, 1 subhgzd uint8_t modulation = 0; // 0 am, 1 fm }; diff --git a/firmware/common/spi_image.hpp b/firmware/common/spi_image.hpp index c6ff51818..27b6fda5c 100644 --- a/firmware/common/spi_image.hpp +++ b/firmware/common/spi_image.hpp @@ -112,7 +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_subghzd{'P', 'W', 'T', 'H'}; +constexpr image_tag_t image_tag_weather{'P', 'W', 'T', 'H'}; +constexpr image_tag_t image_tag_subghzd{'P', 'S', 'G', 'D'}; constexpr image_tag_t image_tag_noop{'P', 'N', 'O', 'P'};