mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-08-23 07:59:03 +00:00
Separate weather and sgd, bc of baseband size limit
This commit is contained in:
@@ -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";
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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<FProtoSubGhzDBett>()); // 3
|
||||
protos.push_back(std::make_unique<FProtoSubGhzDCame>()); // 4, 5, 6
|
||||
protos.push_back(std::make_unique<FProtoSubGhzDCameAtomo>()); // 7
|
||||
protos.push_back(std::make_unique<FProtoSubGhzDCameTwee>()); // 8
|
||||
|
||||
// set callback for them
|
||||
for (const auto& obj : protos) {
|
||||
|
||||
@@ -26,6 +26,7 @@ enum FPROTO_SUBGHZD_SENSOR {
|
||||
FPS_PRASTEL = 5,
|
||||
FPS_AIRFORCE = 6,
|
||||
FPS_CAMEATOMO = 7,
|
||||
FPS_CAMETWEE = 8,
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -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<const SubGhzFPRxConfigureMessage*>(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<WeatherProcessor>()};
|
||||
EventDispatcher event_dispatcher{std::make_unique<SubGhzDProcessor>()};
|
||||
event_dispatcher.run();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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<const SubGhzFPRxConfigureMessage*>(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<WeatherProcessor>()};
|
||||
event_dispatcher.run();
|
||||
return 0;
|
||||
}
|
||||
@@ -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<complex16_t, 512> 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__*/
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
@@ -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'};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user