mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-08-19 06:03:59 +00:00
SubCar fm mode (#2965)
This commit is contained in:
+18
-2
@@ -91,6 +91,7 @@ SubCarView::SubCarView(NavigationView& nav)
|
||||
&button_clear_list,
|
||||
&check_log,
|
||||
&labels,
|
||||
&options_mode,
|
||||
&recent_entries_view});
|
||||
|
||||
baseband::run_prepared_image(portapack::memory::map::m4_code.base());
|
||||
@@ -114,11 +115,17 @@ SubCarView::SubCarView(NavigationView& nav)
|
||||
recent_entries_view.on_select = [this](const SubCarRecentEntry& entry) {
|
||||
nav_.push<SubCarRecentEntryDetailView>(entry);
|
||||
};
|
||||
baseband::set_subghzd_config(0, receiver_model.sampling_rate()); // 0=am
|
||||
receiver_model.enable();
|
||||
|
||||
options_mode.on_change = [this](size_t, int32_t v) {
|
||||
modulation = v;
|
||||
chThdSleepMilliseconds(100); // wait for the baseband thread to process the previous config, to avoid glitchy output when switching modes
|
||||
baseband::set_subghzd_config(modulation, receiver_model.sampling_rate());
|
||||
};
|
||||
signal_token_tick_second = rtc_time::signal_tick_second += [this]() {
|
||||
on_tick_second();
|
||||
};
|
||||
options_mode.set_selected_index(modulation, true);
|
||||
receiver_model.enable();
|
||||
}
|
||||
|
||||
void SubCarView::on_tick_second() {
|
||||
@@ -176,6 +183,8 @@ const char* SubCarView::getSensorTypeName(FPROTO_SUBCAR_SENSOR type) {
|
||||
return "Fiat V0";
|
||||
case FPC_BMWV0:
|
||||
return "BMW V0";
|
||||
/* case FPC_KIAV6:
|
||||
return "Kia V6";*/
|
||||
|
||||
case FPC_Invalid:
|
||||
default:
|
||||
@@ -491,6 +500,13 @@ void SubCarRecentEntryDetailView::parseProtocol() {
|
||||
btn = to_string_dec_uint(button);
|
||||
}
|
||||
|
||||
/*if (entry_.sensorType == FPC_KIAV6) {
|
||||
// not decrypted!
|
||||
serial = 0;
|
||||
btn = "?";
|
||||
cnt = 0;
|
||||
}*/
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+19
-13
@@ -116,25 +116,27 @@ class SubCarView : public View {
|
||||
4'000'000 /* sampling rate */,
|
||||
ReceiverModel::Mode::AMAudio};
|
||||
bool logging = false;
|
||||
uint8_t modulation = 0;
|
||||
app_settings::SettingsManager settings_{
|
||||
"rx_subcar",
|
||||
app_settings::Mode::RX,
|
||||
{
|
||||
{"log"sv, &logging},
|
||||
{"modulationmode"sv, &modulation},
|
||||
}};
|
||||
|
||||
SubCarRecentEntries recent{};
|
||||
|
||||
RFAmpField field_rf_amp{
|
||||
{13 * 8, UI_POS_Y(0)}};
|
||||
{UI_POS_X(13), UI_POS_Y(0)}};
|
||||
LNAGainField field_lna{
|
||||
{15 * 8, UI_POS_Y(0)}};
|
||||
{UI_POS_X(15), UI_POS_Y(0)}};
|
||||
VGAGainField field_vga{
|
||||
{18 * 8, UI_POS_Y(0)}};
|
||||
{UI_POS_X(18), UI_POS_Y(0)}};
|
||||
RSSI rssi{
|
||||
{21 * 8, 0, UI_POS_WIDTH_REMAINING(24), 4}};
|
||||
{UI_POS_X(21), 0, UI_POS_WIDTH_REMAINING(24), 4}};
|
||||
Channel channel{
|
||||
{21 * 8, 5, UI_POS_WIDTH_REMAINING(24), 4},
|
||||
{UI_POS_X(21), 5, UI_POS_WIDTH_REMAINING(24), 4},
|
||||
};
|
||||
RxFrequencyField field_frequency{
|
||||
{UI_POS_X(0), UI_POS_Y(0)},
|
||||
@@ -143,18 +145,22 @@ class SubCarView : public View {
|
||||
SignalToken signal_token_tick_second{};
|
||||
|
||||
Button button_clear_list{
|
||||
{0, 16, 7 * 8, 32},
|
||||
{UI_POS_X(0), UI_POS_Y(1), UI_POS_WIDTH(7), UI_POS_HEIGHT(2)},
|
||||
"Clear"};
|
||||
|
||||
Checkbox check_log{
|
||||
{10 * 8, 18},
|
||||
{UI_POS_X(8), UI_POS_Y(1)},
|
||||
3,
|
||||
"Log",
|
||||
true};
|
||||
|
||||
Labels labels{
|
||||
{{UI_POS_X_RIGHT(14), UI_POS_Y(1)}, "no fm yet :(", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(15), UI_POS_Y(1)}, "Mode:", Theme::getInstance()->fg_light->foreground},
|
||||
};
|
||||
ui::OptionsField options_mode{
|
||||
{UI_POS_X(22), UI_POS_Y(1)},
|
||||
3,
|
||||
{{"AM", 0}, {"FM", 1}}};
|
||||
|
||||
static constexpr auto header_height = 3 * 16;
|
||||
|
||||
@@ -198,16 +204,16 @@ class SubCarRecentEntryDetailView : public View {
|
||||
std::string btn = "";
|
||||
uint32_t cnt = SD_NO_CNT;
|
||||
|
||||
Text text_type{{UI_POS_X(0), 1 * 16, 15 * 8, 16}, "?"};
|
||||
Text text_id{{6 * 8, 2 * 16, 10 * 8, 16}, "?"};
|
||||
Text text_type{{UI_POS_X(0), UI_POS_Y(1), UI_POS_WIDTH(15), UI_POS_HEIGHT(1)}, "?"};
|
||||
Text text_id{{UI_POS_X(6), UI_POS_Y(2), UI_POS_WIDTH(10), UI_POS_HEIGHT(1)}, "?"};
|
||||
|
||||
Console console{
|
||||
{0, 4 * 16, screen_width, screen_height - (4 * 16) - 36}};
|
||||
{UI_POS_X(0), UI_POS_Y(4), UI_POS_MAXWIDTH, screen_height - (4 * 16) - 36}};
|
||||
|
||||
Labels labels{
|
||||
{{UI_POS_X(0), UI_POS_Y(0)}, "Type:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), 2 * 16}, "Serial: ", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), 3 * 16}, "Data:", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), UI_POS_Y(2)}, "Serial: ", Theme::getInstance()->fg_light->foreground},
|
||||
{{UI_POS_X(0), UI_POS_Y(3)}, "Data:", Theme::getInstance()->fg_light->foreground},
|
||||
};
|
||||
|
||||
Button button_done{
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
#pragma once
|
||||
#include "subcarbase.hpp"
|
||||
#include <cstring>
|
||||
|
||||
typedef enum {
|
||||
KiaV6DecoderStepReset = 0,
|
||||
KiaV6DecoderStepWaitFirstHigh,
|
||||
KiaV6DecoderStepCountPreamble,
|
||||
KiaV6DecoderStepWaitLongHigh,
|
||||
KiaV6DecoderStepData,
|
||||
} KiaV6DecoderStep;
|
||||
|
||||
#define KIA_V6_XOR_MASK_LOW 0x84AF25FB
|
||||
#define KIA_V6_XOR_MASK_HIGH 0x638766AB
|
||||
|
||||
class FProtoSubCarKiaV6 : public FProtoSubCarBase {
|
||||
public:
|
||||
FProtoSubCarKiaV6() {
|
||||
sensorType = FPC_KIAV6;
|
||||
te_short = 200;
|
||||
te_long = 400;
|
||||
te_delta = 100;
|
||||
min_count_bit_for_found = 144;
|
||||
}
|
||||
|
||||
void feed(bool level, uint32_t duration) {
|
||||
uint32_t uVar4, uVar5;
|
||||
ManchesterEvent event;
|
||||
bool data_bit;
|
||||
uint8_t bit_count_inc;
|
||||
uint32_t step_value;
|
||||
|
||||
switch (parser_step) {
|
||||
case KiaV6DecoderStepReset: // case 0
|
||||
if (level == 0) {
|
||||
return;
|
||||
}
|
||||
if (DURATION_DIFF(duration, te_short) <
|
||||
te_delta) {
|
||||
parser_step = KiaV6DecoderStepWaitFirstHigh;
|
||||
te_last = duration;
|
||||
header_count = 0;
|
||||
FProtoGeneral::manchester_advance(
|
||||
manchester_state,
|
||||
ManchesterEventReset,
|
||||
&manchester_state,
|
||||
NULL);
|
||||
}
|
||||
return;
|
||||
|
||||
case KiaV6DecoderStepWaitFirstHigh: { // case 1
|
||||
if (level != 0) {
|
||||
return;
|
||||
}
|
||||
uint32_t diff_short = DURATION_DIFF(duration, te_short);
|
||||
uint32_t diff_long = DURATION_DIFF(duration, te_long);
|
||||
|
||||
uint32_t diff = (diff_long < diff_short) ? diff_long : diff_short;
|
||||
|
||||
if (diff_long < te_delta && diff_long < diff_short) {
|
||||
if (header_count >= 0x259) { // 601 decimal
|
||||
header_count = 0;
|
||||
te_last = duration;
|
||||
parser_step = KiaV6DecoderStepWaitLongHigh;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (diff >= te_delta) {
|
||||
step_value = KiaV6DecoderStepReset;
|
||||
goto LAB_reset;
|
||||
}
|
||||
|
||||
if (DURATION_DIFF(te_last, te_short) <
|
||||
te_delta) {
|
||||
te_last = duration;
|
||||
header_count++;
|
||||
return;
|
||||
} else {
|
||||
step_value = KiaV6DecoderStepReset;
|
||||
goto LAB_reset;
|
||||
}
|
||||
}
|
||||
case KiaV6DecoderStepWaitLongHigh: { // case 2
|
||||
if (level == 0) {
|
||||
step_value = KiaV6DecoderStepReset;
|
||||
goto LAB_reset;
|
||||
}
|
||||
uint32_t diff_long_check = DURATION_DIFF(duration, te_long);
|
||||
uint32_t diff_short_check = DURATION_DIFF(duration, te_short);
|
||||
|
||||
if (diff_long_check >= te_delta) {
|
||||
if (diff_short_check >= te_delta) {
|
||||
step_value = KiaV6DecoderStepReset;
|
||||
goto LAB_reset;
|
||||
}
|
||||
}
|
||||
|
||||
if (DURATION_DIFF(te_last, te_long) >=
|
||||
te_delta) {
|
||||
step_value = KiaV6DecoderStepReset;
|
||||
goto LAB_reset;
|
||||
}
|
||||
decode_data = 0;
|
||||
decode_count_bit = 0;
|
||||
|
||||
subghz_protocol_blocks_add_bit(1);
|
||||
subghz_protocol_blocks_add_bit(1);
|
||||
subghz_protocol_blocks_add_bit(0);
|
||||
subghz_protocol_blocks_add_bit(1);
|
||||
|
||||
data_part1_low = (uint32_t)(decode_data & 0xFFFFFFFF);
|
||||
data_part1_high = (uint32_t)((decode_data >> 32) & 0xFFFFFFFF);
|
||||
bit_count = decode_count_bit;
|
||||
|
||||
parser_step = KiaV6DecoderStepData;
|
||||
return;
|
||||
}
|
||||
case KiaV6DecoderStepData: // case 3
|
||||
if (DURATION_DIFF(duration, te_short) <
|
||||
te_delta) {
|
||||
event = (ManchesterEvent)((level & 0x7F) << 1);
|
||||
goto manchester_process;
|
||||
} else if (
|
||||
DURATION_DIFF(duration, te_long) <
|
||||
te_delta) {
|
||||
event = (ManchesterEvent)(level ? 6 : 4);
|
||||
goto manchester_process;
|
||||
}
|
||||
step_value = KiaV6DecoderStepReset;
|
||||
goto LAB_reset;
|
||||
|
||||
manchester_process:
|
||||
if (FProtoGeneral::manchester_advance(
|
||||
manchester_state, event, &manchester_state, &data_bit)) {
|
||||
uVar4 = data_part1_low;
|
||||
uVar5 = (uVar4 << 1) | (data_bit ? 1 : 0);
|
||||
|
||||
uint32_t carry = (uVar4 >> 31) & 1;
|
||||
uVar4 = (data_part1_high << 1) | carry;
|
||||
|
||||
data_part1_low = uVar5;
|
||||
data_part1_high = uVar4;
|
||||
|
||||
decode_data = ((uint64_t)uVar4 << 32) | uVar5;
|
||||
|
||||
bit_count_inc = bit_count + 1;
|
||||
bit_count = bit_count_inc;
|
||||
|
||||
if (bit_count_inc == 0x40) {
|
||||
// stored_part1_low = ~uVar5;
|
||||
// stored_part1_high = ~uVar4;
|
||||
data_part1_low = 0;
|
||||
data_part1_high = 0;
|
||||
} else if (bit_count_inc == 0x80) {
|
||||
// stored_part2_low = ~uVar5;
|
||||
// stored_part2_high = ~uVar4;
|
||||
data_part1_low = 0;
|
||||
data_part1_high = 0;
|
||||
}
|
||||
}
|
||||
|
||||
te_last = duration;
|
||||
|
||||
if (bit_count != min_count_bit_for_found) {
|
||||
return;
|
||||
}
|
||||
data_count_bit = min_count_bit_for_found;
|
||||
// data_part3 = ~((uint16_t)data_part1_low);
|
||||
|
||||
// kia_v6_decrypt(); --won't
|
||||
decode_data = data_part1_low | ((uint64_t)data_part1_high << 32);
|
||||
if (callback) {
|
||||
callback(this);
|
||||
}
|
||||
|
||||
data_part1_low = 0;
|
||||
data_part1_high = 0;
|
||||
bit_count = 0;
|
||||
step_value = KiaV6DecoderStepReset;
|
||||
goto LAB_reset;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
LAB_reset:
|
||||
parser_step = step_value;
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t bit_count = 0;
|
||||
|
||||
uint16_t header_count = 0;
|
||||
ManchesterState manchester_state = ManchesterStateMid1;
|
||||
uint32_t data_part1_low = 0;
|
||||
uint32_t data_part1_high = 0;
|
||||
|
||||
// uint32_t stored_part1_low = 0;
|
||||
// uint32_t stored_part1_high = 0;
|
||||
// uint32_t stored_part2_low = 0;
|
||||
// uint32_t stored_part2_high = 0;
|
||||
// uint16_t data_part3 = 0;
|
||||
};
|
||||
@@ -76,7 +76,7 @@ class FProtoSubGhzDSecPlusV1 : public FProtoSubGhzDBase {
|
||||
packet_accepted |= SECPLUS_V1_PACKET_2_ACCEPTED;
|
||||
|
||||
if (packet_accepted == (SECPLUS_V1_PACKET_1_ACCEPTED | SECPLUS_V1_PACKET_2_ACCEPTED)) {
|
||||
// subghz_protocol_secplus_v1_decode(); // disabled doe to lack of flash
|
||||
// subghz_protocol_secplus_v1_decode(); // disabled due to lack of flash
|
||||
// controller
|
||||
// uint32_t fixed = (data >> 32) & 0xFFFFFFFF;
|
||||
// cnt = data & 0xFFFFFFFF;
|
||||
|
||||
@@ -21,6 +21,7 @@ So include here the .hpp, and add a new element to the protos vector in the cons
|
||||
#include "c-ford_v0.hpp"
|
||||
#include "c-fiat_v0.hpp"
|
||||
#include "c-bmw_v0.hpp"
|
||||
// #include "c-kia_v6.hpp"
|
||||
|
||||
#ifndef __FPROTO_PROTOLISTCAR_H__
|
||||
#define __FPROTO_PROTOLISTCAR_H__
|
||||
@@ -42,6 +43,7 @@ class SubCarProtos : public FProtoListGeneral {
|
||||
protos[FPC_FORDV0] = new FProtoSubCarFordV0();
|
||||
protos[FPC_FIATV0] = new FProtoSubCarFiatV0();
|
||||
protos[FPC_BMWV0] = new FProtoSubCarBMWV0();
|
||||
// protos[FPC_KIAV6] = new FProtoSubCarKiaV6(); //-- disabled, due to whole encrypted.
|
||||
|
||||
for (uint8_t i = 0; i < FPC_COUNT; ++i) {
|
||||
if (protos[i] != NULL) protos[i]->setCallback(callbackTarget);
|
||||
|
||||
@@ -8,9 +8,6 @@ These values must be present on the protocol's constructor, like FProtoWeatherAc
|
||||
Also it must have a switch-case element in the getSubGhzDSensorTypeName() function, to display it's name.
|
||||
*/
|
||||
|
||||
#define FPM_AM 0
|
||||
#define FPM_FM 1
|
||||
|
||||
enum FPROTO_SUBCAR_SENSOR : uint8_t {
|
||||
FPC_Invalid = 0,
|
||||
FPC_SUZUKI = 1,
|
||||
@@ -24,6 +21,8 @@ enum FPROTO_SUBCAR_SENSOR : uint8_t {
|
||||
FPC_FORDV0 = 9,
|
||||
FPC_FIATV0 = 10,
|
||||
FPC_BMWV0 = 11,
|
||||
// FPC_KIAV6 = 12, //disabled, due to whole encrypted.
|
||||
// FPC_PSA = 13, // IS whole encrypted
|
||||
FPC_COUNT
|
||||
};
|
||||
|
||||
|
||||
@@ -25,14 +25,6 @@
|
||||
#include "portapack_shared_memory.hpp"
|
||||
#include "event_m4.hpp"
|
||||
|
||||
static inline int get_quadrant(int16_t i, int16_t q) {
|
||||
if (i >= 0) {
|
||||
return (q >= 0) ? 0 : 3;
|
||||
} else {
|
||||
return (q >= 0) ? 1 : 2;
|
||||
}
|
||||
}
|
||||
|
||||
void SubCarProcessor::execute(const buffer_c8_t& buffer) {
|
||||
if (!configured) return;
|
||||
|
||||
@@ -46,10 +38,6 @@ void SubCarProcessor::execute(const buffer_c8_t& buffer) {
|
||||
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)
|
||||
feed_channel_stats(decim_1_out);
|
||||
|
||||
// for fm
|
||||
const int32_t DC_ALPHA = 5; // Auto-centering speed
|
||||
int32_t buffer_rotation_sum = 0;
|
||||
|
||||
for (size_t i = 0; i < decim_1_out.count; i++) {
|
||||
// am
|
||||
threshold = (low_estimate + high_estimate) / 2;
|
||||
@@ -57,126 +45,103 @@ void SubCarProcessor::execute(const buffer_c8_t& buffer) {
|
||||
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 >> 10);
|
||||
int32_t const ook_low_delta = mag - low_estimate;
|
||||
bool meashl = currentHiLow;
|
||||
if (sig_state == STATE_IDLE) {
|
||||
if (mag > (threshold + hysteresis)) { // just become high
|
||||
meashl = true;
|
||||
sig_state = STATE_PULSE;
|
||||
numg = 0;
|
||||
} else {
|
||||
meashl = false; // still low
|
||||
low_estimate += ook_low_delta / OOK_EST_LOW_RATIO;
|
||||
low_estimate += ((ook_low_delta > 0) ? 1 : -1); // Hack to compensate for lack of fixed-point scaling
|
||||
// Calculate default OOK high level estimate
|
||||
high_estimate = 1.35 * low_estimate; // Default is a ratio of low level
|
||||
high_estimate = std::max(high_estimate, min_high_level);
|
||||
high_estimate = std::min(high_estimate, (uint32_t)OOK_MAX_HIGH_LEVEL);
|
||||
}
|
||||
|
||||
} else if (sig_state == STATE_PULSE) {
|
||||
++numg;
|
||||
if (numg > 100) numg = 100;
|
||||
if (mag < (threshold - hysteresis)) {
|
||||
// check if really a bad value
|
||||
if (numg < 3) {
|
||||
// susp
|
||||
sig_state = STATE_GAP;
|
||||
} else {
|
||||
if (modulation == 0) {
|
||||
int32_t const ook_low_delta = mag - low_estimate;
|
||||
bool meashl = currentHiLow;
|
||||
if (sig_state == STATE_IDLE) {
|
||||
if (mag > (threshold + hysteresis)) { // just become high
|
||||
meashl = true;
|
||||
sig_state = STATE_PULSE;
|
||||
numg = 0;
|
||||
sig_state = STATE_GAP_START;
|
||||
} else {
|
||||
meashl = false; // still low
|
||||
low_estimate += ook_low_delta / OOK_EST_LOW_RATIO;
|
||||
low_estimate += ((ook_low_delta > 0) ? 1 : -1); // Hack to compensate for lack of fixed-point scaling
|
||||
// Calculate default OOK high level estimate
|
||||
high_estimate = 1.35 * low_estimate; // Default is a ratio of low level
|
||||
high_estimate = std::max(high_estimate, min_high_level);
|
||||
high_estimate = std::min(high_estimate, (uint32_t)OOK_MAX_HIGH_LEVEL);
|
||||
}
|
||||
|
||||
} else if (sig_state == STATE_PULSE) {
|
||||
++numg;
|
||||
if (numg > 100) numg = 100;
|
||||
if (mag < (threshold - hysteresis)) {
|
||||
// check if really a bad value
|
||||
if (numg < 3) {
|
||||
// susp
|
||||
sig_state = STATE_GAP;
|
||||
} else {
|
||||
numg = 0;
|
||||
sig_state = STATE_GAP_START;
|
||||
}
|
||||
meashl = false; // low
|
||||
} else {
|
||||
high_estimate += mag / OOK_EST_HIGH_RATIO - high_estimate / OOK_EST_HIGH_RATIO;
|
||||
high_estimate = std::max(high_estimate, min_high_level);
|
||||
high_estimate = std::min(high_estimate, (uint32_t)OOK_MAX_HIGH_LEVEL);
|
||||
meashl = true; // still high
|
||||
}
|
||||
} else if (sig_state == STATE_GAP_START) {
|
||||
++numg;
|
||||
if (mag > (threshold + hysteresis)) { // New pulse?
|
||||
sig_state = STATE_PULSE;
|
||||
meashl = true;
|
||||
} else if (numg >= 3) {
|
||||
sig_state = STATE_GAP;
|
||||
meashl = false; // gap
|
||||
}
|
||||
} else if (sig_state == STATE_GAP) {
|
||||
++numg;
|
||||
if (mag > (threshold + hysteresis)) { // New pulse?
|
||||
numg = 0;
|
||||
sig_state = STATE_PULSE;
|
||||
meashl = true;
|
||||
} else {
|
||||
meashl = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (meashl == currentHiLow && currentDuration < 30'000'000) // allow pass 'end' signal
|
||||
{
|
||||
currentDuration += nsPerDecSamp;
|
||||
} else { // called on change, so send the last duration and dir.
|
||||
if (currentDuration >= 30'000'000) sig_state = STATE_IDLE;
|
||||
if (protoList) protoList->feed(currentHiLow, currentDuration / 1000);
|
||||
currentDuration = nsPerDecSamp;
|
||||
currentHiLow = meashl;
|
||||
}
|
||||
}
|
||||
if (modulation == 1) {
|
||||
int32_t discrim = ((int32_t)im * fm_state.last_re) - ((int32_t)re * fm_state.last_im);
|
||||
fm_state.last_re = re;
|
||||
fm_state.last_im = im;
|
||||
fm_state.smoothed_discrim += (discrim - fm_state.smoothed_discrim) >> 4;
|
||||
|
||||
// --- FM Part (Simple 2-FSK) ---
|
||||
if (mag > (threshold / 2)) {
|
||||
const int32_t fm_hysteresis = 2000;
|
||||
bool new_level = fm_state.current_logic_level;
|
||||
if (fm_state.smoothed_discrim > fm_hysteresis) {
|
||||
new_level = true;
|
||||
} else if (fm_state.smoothed_discrim < -fm_hysteresis) {
|
||||
new_level = false;
|
||||
}
|
||||
if (new_level == fm_state.current_logic_level) {
|
||||
fm_state.buffer_count++;
|
||||
} else {
|
||||
int32_t duration_us = (fm_state.buffer_count * nsPerDecSamp) / 1000;
|
||||
if (duration_us > 15) {
|
||||
if (protoList) protoList->feed(fm_state.current_logic_level, duration_us);
|
||||
}
|
||||
fm_state.current_logic_level = new_level;
|
||||
fm_state.buffer_count = 1;
|
||||
}
|
||||
meashl = false; // low
|
||||
} else {
|
||||
high_estimate += mag / OOK_EST_HIGH_RATIO - high_estimate / OOK_EST_HIGH_RATIO;
|
||||
high_estimate = std::max(high_estimate, min_high_level);
|
||||
high_estimate = std::min(high_estimate, (uint32_t)OOK_MAX_HIGH_LEVEL);
|
||||
meashl = true; // still high
|
||||
}
|
||||
} else if (sig_state == STATE_GAP_START) {
|
||||
++numg;
|
||||
if (mag > (threshold + hysteresis)) { // New pulse?
|
||||
sig_state = STATE_PULSE;
|
||||
meashl = true;
|
||||
} else if (numg >= 3) {
|
||||
sig_state = STATE_GAP;
|
||||
meashl = false; // gap
|
||||
}
|
||||
} else if (sig_state == STATE_GAP) {
|
||||
++numg;
|
||||
if (mag > (threshold + hysteresis)) { // New pulse?
|
||||
numg = 0;
|
||||
sig_state = STATE_PULSE;
|
||||
meashl = true;
|
||||
} else {
|
||||
meashl = false;
|
||||
fm_state.buffer_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (meashl == currentHiLow && currentDuration < 30'000'000) // allow pass 'end' signal
|
||||
{
|
||||
currentDuration += nsPerDecSamp;
|
||||
} else { // called on change, so send the last duration and dir.
|
||||
if (currentDuration >= 30'000'000) sig_state = STATE_IDLE;
|
||||
if (protoList) protoList->feed(currentHiLow, currentDuration / 1000);
|
||||
currentDuration = nsPerDecSamp;
|
||||
currentHiLow = meashl;
|
||||
}
|
||||
|
||||
// fm part: -- NOT WORKING!!!! TODO FIX. AI code ;)
|
||||
int current_quad = get_quadrant(re, im);
|
||||
// Calculate Step (Current - Previous)
|
||||
int diff = current_quad - fm_state.prev_quad;
|
||||
// Handle Wrap-Around (crossing from Q3 to Q0 or Q0 to Q3)
|
||||
// 3 -> 0 should be +1 (CCW)
|
||||
// 0 -> 3 should be -1 (CW)
|
||||
if (diff == -3)
|
||||
diff = 1;
|
||||
else if (diff == 3)
|
||||
diff = -1;
|
||||
// Update History
|
||||
fm_state.prev_quad = current_quad;
|
||||
// Accumulate Rotation
|
||||
buffer_rotation_sum += diff;
|
||||
}
|
||||
|
||||
// fm finish:
|
||||
// 3. AUTO-CENTERING (DC BLOCKER)
|
||||
// Even with quadrant counting, "drift" (hand effect) makes the wheel spin
|
||||
// faster or slower. We need to subtract the average speed.
|
||||
// Update our "Average Speed" estimate
|
||||
// Note: buffer_rotation_sum is roughly proportional to frequency.
|
||||
fm_state.dc_offset = (fm_state.dc_offset * ((1 << DC_ALPHA) - 1) + buffer_rotation_sum) >> DC_ALPHA;
|
||||
// Remove the drift
|
||||
int32_t centered_rotation = buffer_rotation_sum - fm_state.dc_offset;
|
||||
// 4. LOW PASS FILTER
|
||||
const int32_t LPF_ALPHA = 4;
|
||||
fm_state.smoothed_error = (fm_state.smoothed_error * (LPF_ALPHA - 1) + centered_rotation) / LPF_ALPHA;
|
||||
// 5. DECISION LOGIC
|
||||
// Threshold is small now because we are counting quadrant steps.
|
||||
// Max steps per buffer (256 samples) is 256.
|
||||
// Typical FSK deviation might give you +/- 10 to 50 steps per buffer.
|
||||
const int32_t THRESHOLD = 3;
|
||||
bool new_level = fm_state.current_logic_level;
|
||||
if (fm_state.smoothed_error > THRESHOLD) {
|
||||
new_level = true;
|
||||
} else if (fm_state.smoothed_error < -THRESHOLD) {
|
||||
new_level = false;
|
||||
}
|
||||
// 6. TIMING OUTPUT
|
||||
if (new_level == fm_state.current_logic_level) {
|
||||
fm_state.buffer_count++;
|
||||
} else {
|
||||
// Output pulse duration
|
||||
int32_t duration_us = fm_state.buffer_count * 512;
|
||||
|
||||
if (duration_us > 250) {
|
||||
if (protoListFm) protoListFm->feed(fm_state.current_logic_level, duration_us);
|
||||
}
|
||||
fm_state.current_logic_level = new_level;
|
||||
fm_state.buffer_count = 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,6 +154,14 @@ void SubCarProcessor::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
|
||||
|
||||
if (modulation != message.modulation) {
|
||||
// reload protos to reset them all
|
||||
if (protoList) {
|
||||
delete protoList;
|
||||
}
|
||||
protoList = new SubCarProtos();
|
||||
}
|
||||
modulation = message.modulation;
|
||||
baseband_fs = message.sampling_rate;
|
||||
baseband_thread.set_sampling_rate(baseband_fs);
|
||||
nsPerDecSamp = 1'000'000'000 / baseband_fs * 8; // Scaled it due to less array buffer sampes due to /8 decimation. 250 nseg (4Mhz) * 8
|
||||
|
||||
@@ -56,8 +56,8 @@ class SubCarProcessor : public BasebandProcessor {
|
||||
uint32_t low_estimate = 100;
|
||||
uint32_t high_estimate = 12000;
|
||||
uint32_t min_high_level = 10;
|
||||
uint8_t numg = 0; // count of matched signals to filter spikes
|
||||
size_t baseband_fs = 0; // will be set later by configure message
|
||||
uint8_t numg = 0; // count of matched signals to filter spikes
|
||||
size_t baseband_fs = 4'000'000; // will be set later by configure message
|
||||
uint32_t nsPerDecSamp = 0;
|
||||
|
||||
/* Array Buffer aux. used in decim0 and decim1 IQ c16 signed data ; (decim0 defines the max length of the array) */
|
||||
@@ -74,20 +74,20 @@ class SubCarProcessor : public BasebandProcessor {
|
||||
uint32_t threshold = 0x0630;
|
||||
bool currentHiLow = false;
|
||||
bool configured{false};
|
||||
uint8_t mode = 0; // 0 = am, 1 = fm
|
||||
|
||||
// fm part:
|
||||
struct DemodFMState {
|
||||
int prev_quad = 0; // Stores 0, 1, 2, or 3
|
||||
int32_t dc_offset = 0;
|
||||
int32_t smoothed_error = 0;
|
||||
bool current_logic_level = false;
|
||||
uint32_t buffer_count = 0;
|
||||
int16_t last_re = 0; // Store previous Real sample
|
||||
int16_t last_im = 0;
|
||||
int32_t smoothed_discrim = 0;
|
||||
};
|
||||
DemodFMState fm_state{};
|
||||
|
||||
FProtoListGeneral* protoList = new SubCarProtos(); // holds all the protocols we can parse
|
||||
FProtoListGeneral* protoListFm = new SubCarProtos(); // holds all the protocols we can parse, but for fm (dupe, bc most of it is dual)
|
||||
uint8_t modulation = 0; // 0 am, 1 fm
|
||||
|
||||
FProtoListGeneral* protoList = new SubCarProtos(); // holds all the protocols we can parse
|
||||
void configure(const SubGhzFPRxConfigureMessage& message);
|
||||
|
||||
/* NB: Threads should be the last members in the class definition. */
|
||||
|
||||
Reference in New Issue
Block a user