mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-09-15 19:13:00 +00:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c9863187d6 | |||
| 13e4f225ea | |||
| 0df2c085a1 | |||
| a4c2e155e5 | |||
| c90f0944b1 | |||
| 7a38b04192 | |||
| 9f84ccbe1f | |||
| cb3774ad81 |
@@ -23,6 +23,10 @@ cmake_policy(SET CMP0005 NEW)
|
||||
|
||||
set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_LIST_DIR}/firmware/toolchain-arm-cortex-m.cmake)
|
||||
|
||||
set(CMAKE_COLOR_MAKEFILE ON)
|
||||
|
||||
add_compile_options(-fdiagnostics-color=always)
|
||||
|
||||
option(USE_CCACHE "Enable ccache, please keep an eye on this because sometimes it's not quite stable and generated unusable binary" OFF)
|
||||
|
||||
project(portapack-h1)
|
||||
|
||||
@@ -75,7 +75,19 @@ void BattinfoView::update_result() {
|
||||
text_current.hidden(false);
|
||||
text_charge.hidden(false);
|
||||
text_current.set(to_string_dec_int(current) + " mA");
|
||||
text_charge.set(current >= 0 ? "Charging" : "Discharging");
|
||||
if (current >= 25) // when >25mA it is charging in any scenario
|
||||
text_charge.set("Charging");
|
||||
else {
|
||||
if (current > -25) { // between -25 and 25
|
||||
if (voltage > 4060 || percent == 100) { // the voltage is high enough, so it is full, that's why no mA
|
||||
text_charge.set("Full");
|
||||
} else {
|
||||
text_charge.set("Holding"); // not enough voltage, so should charge. maybe the batttery is off, or USB power is not enough
|
||||
}
|
||||
} else {
|
||||
text_charge.set("Discharging"); // less then -25mA, so it is discharging
|
||||
}
|
||||
}
|
||||
labels_opt.hidden(false);
|
||||
|
||||
text_ttef.hidden(false);
|
||||
@@ -103,7 +115,7 @@ void BattinfoView::update_result() {
|
||||
if ((valid_mask & battery::BatteryManagement::BATT_VALID_TTEF) == battery::BatteryManagement::BATT_VALID_TTEF) {
|
||||
text_ttef.hidden(false);
|
||||
float ttef = 0;
|
||||
if (current <= 0) {
|
||||
if (current <= 0) { // we keep this yet
|
||||
ttef = battery::BatteryManagement::get_tte();
|
||||
} else {
|
||||
ttef = battery::BatteryManagement::get_ttf();
|
||||
@@ -133,7 +145,7 @@ void BattinfoView::update_result() {
|
||||
}
|
||||
if (uichg) set_dirty();
|
||||
// to update status bar too, send message in behalf of batt manager
|
||||
BatteryStateMessage msg{valid_mask, percent, current >= 0, voltage};
|
||||
BatteryStateMessage msg{valid_mask, percent, current >= 25, voltage};
|
||||
EventDispatcher::send_message(msg);
|
||||
}
|
||||
|
||||
|
||||
@@ -213,6 +213,11 @@ const char* WeatherView::getWeatherSensorTypeName(FPROTO_WEATHER_SENSOR type) {
|
||||
return "EmosE601x";
|
||||
case FPW_SolightTE44:
|
||||
return "SolightTE44";
|
||||
case FPW_Bresser3CH:
|
||||
case FPW_Bresser3CH_V1:
|
||||
return "Bresser3CH";
|
||||
case FPW_Vauno_EN8822:
|
||||
return "Vauno EN8822";
|
||||
case FPW_Invalid:
|
||||
default:
|
||||
return "Unknown";
|
||||
@@ -532,6 +537,42 @@ WeatherRecentEntry WeatherView::process_data(const WeatherDataMessage* data) {
|
||||
i16 |= 0xf000;
|
||||
}
|
||||
ret.temp = (float)i16 / 10.0;
|
||||
break;
|
||||
case FPW_Bresser3CH:
|
||||
ret.id = (data->decode_data >> 28) & 0xff;
|
||||
ret.channel = ((data->decode_data >> 27) & 0x01) | (((data->decode_data >> 26) & 0x01) << 1);
|
||||
// ret.btn = ((data->decode_data >> 25) & 0x1);
|
||||
ret.battery_low = ((data->decode_data >> 24) & 0x1);
|
||||
i16 = (data->decode_data >> 12) & 0x0fff;
|
||||
/* Handle signed data */
|
||||
if (i16 & 0x0800) {
|
||||
i16 |= 0xf000;
|
||||
}
|
||||
ret.temp = (float)i16 / 10.0;
|
||||
ret.humidity = data->decode_data & 0xff;
|
||||
break;
|
||||
case FPW_Bresser3CH_V1:
|
||||
ret.id = (data->decode_data >> 32) & 0xff;
|
||||
ret.battery_low = ((data->decode_data >> 31) & 0x1);
|
||||
// ret.btn = (data->decode_data >> 30) & 0x1;
|
||||
ret.channel = (data->decode_data >> 28) & 0x3;
|
||||
ret.temp = (data->decode_data >> 16) & 0xfff;
|
||||
ret.temp = FProtoGeneral::locale_fahrenheit_to_celsius((float)(ret.temp - 900) / 10.0);
|
||||
ret.humidity = (data->decode_data >> 8) & 0xff;
|
||||
break;
|
||||
|
||||
case FPW_Vauno_EN8822:
|
||||
ret.id = (data->decode_data >> 34) & 0xff;
|
||||
ret.battery_low = (data->decode_data >> 33) & 0x01;
|
||||
ret.channel = ((data->decode_data >> 30) & 0x03);
|
||||
i16 = (data->decode_data >> 18) & 0x0fff;
|
||||
/* Handle signed data */
|
||||
if (i16 & 0x0800) {
|
||||
i16 |= 0xf000;
|
||||
}
|
||||
ret.temp = (float)i16 / 10.0;
|
||||
ret.humidity = (data->decode_data >> 11) & 0x7f;
|
||||
|
||||
break;
|
||||
case FPW_Invalid:
|
||||
default:
|
||||
|
||||
@@ -149,6 +149,7 @@ const NavigationView::AppList NavigationView::appList = {
|
||||
{"search", "Search", RX, Color::yellow(), &bitmap_icon_search, new ViewFactory<SearchView>()},
|
||||
{"subghzd", "SubGhzD", RX, Color::yellow(), &bitmap_icon_remote, new ViewFactory<SubGhzDView>()},
|
||||
{"weather", "Weather", RX, Color::green(), &bitmap_icon_thermometer, new ViewFactory<WeatherView>()},
|
||||
//{"fskrx", "FSK RX", RX, Color::yellow(), &bitmap_icon_remote, new ViewFactory<FskxRxMainView>()}, //for JT
|
||||
//{"dmr", "DMR", RX, Color::dark_grey(), &bitmap_icon_dmr, new ViewFactory<NotImplementedView>()},
|
||||
//{"sigfox", "SIGFOX", RX, Color::dark_grey(), &bitmap_icon_fox, new ViewFactory<NotImplementedView>()},
|
||||
//{"lora", "LoRa", RX, Color::dark_grey(), &bitmap_icon_lora, new ViewFactory<NotImplementedView>()},
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
|
||||
#ifndef __FPROTO_Bresser3ch_H__
|
||||
#define __FPROTO_Bresser3ch_H__
|
||||
|
||||
#include "weatherbase.hpp"
|
||||
|
||||
#define BRESSER_V0_DATA 36
|
||||
#define BRESSER_V0_DATA_AND_TAIL 52
|
||||
#define BRESSER_V1_DATA 40
|
||||
|
||||
typedef enum {
|
||||
Bresser3chDecoderStepReset = 0,
|
||||
Bresser3chDecoderStepV0SaveDuration,
|
||||
Bresser3chDecoderStepV0CheckDuration,
|
||||
Bresser3chDecoderStepV0TailCheckDuration,
|
||||
Bresser3chDecoderStepV1PreambleDn,
|
||||
Bresser3chDecoderStepV1PreambleUp,
|
||||
Bresser3chDecoderStepV1SaveDuration,
|
||||
Bresser3chDecoderStepV1CheckDuration,
|
||||
} Bresser3chDecoderStepV1;
|
||||
|
||||
class FProtoWeatheBresser3CH : public FProtoWeatherBase {
|
||||
public:
|
||||
FProtoWeatheBresser3CH() {
|
||||
sensorType = FPW_Bresser3CH;
|
||||
}
|
||||
|
||||
void feed(bool level, uint32_t duration) {
|
||||
switch (parser_step) {
|
||||
case Bresser3chDecoderStepReset:
|
||||
if (level && DURATION_DIFF(duration, te_short * 3) < te_delta) {
|
||||
te_last = duration;
|
||||
parser_step = Bresser3chDecoderStepV1PreambleDn;
|
||||
decode_data = 0;
|
||||
decode_count_bit = 0;
|
||||
} else if ((!level) && duration >= te_long) {
|
||||
parser_step = Bresser3chDecoderStepV0SaveDuration;
|
||||
decode_data = 0;
|
||||
decode_count_bit = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case Bresser3chDecoderStepV0SaveDuration:
|
||||
if (level) {
|
||||
te_last = duration;
|
||||
if (decode_count_bit < BRESSER_V0_DATA) {
|
||||
parser_step = Bresser3chDecoderStepV0CheckDuration;
|
||||
} else {
|
||||
parser_step = Bresser3chDecoderStepV0TailCheckDuration;
|
||||
}
|
||||
} else {
|
||||
parser_step = Bresser3chDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case Bresser3chDecoderStepV0CheckDuration:
|
||||
if (!level) {
|
||||
if (DURATION_DIFF(te_last, te_short) < te_delta) {
|
||||
if (DURATION_DIFF(duration, te_short * 2) < te_delta) {
|
||||
subghz_protocol_blocks_add_bit(0);
|
||||
parser_step = Bresser3chDecoderStepV0SaveDuration;
|
||||
} else if (
|
||||
DURATION_DIFF(duration, te_short * 4) < te_delta) {
|
||||
subghz_protocol_blocks_add_bit(1);
|
||||
parser_step = Bresser3chDecoderStepV0SaveDuration;
|
||||
} else
|
||||
parser_step = Bresser3chDecoderStepReset;
|
||||
} else
|
||||
parser_step = Bresser3chDecoderStepReset;
|
||||
} else
|
||||
parser_step = Bresser3chDecoderStepReset;
|
||||
break;
|
||||
|
||||
case Bresser3chDecoderStepV0TailCheckDuration:
|
||||
if (!level) {
|
||||
if (duration >= te_long) {
|
||||
if (decode_count_bit == BRESSER_V0_DATA_AND_TAIL &&
|
||||
ws_protocol_bresser_3ch_check_v0()) {
|
||||
decode_count_bit = BRESSER_V0_DATA;
|
||||
sensorType = FPW_Bresser3CH;
|
||||
// ws_protocol_bresser_3ch_extract_data_v0();
|
||||
if (callback) {
|
||||
callback(this);
|
||||
}
|
||||
}
|
||||
decode_data = 0;
|
||||
decode_count_bit = 0;
|
||||
parser_step = Bresser3chDecoderStepReset;
|
||||
} else if (
|
||||
decode_count_bit < BRESSER_V0_DATA_AND_TAIL &&
|
||||
DURATION_DIFF(te_last, te_short) < te_delta &&
|
||||
DURATION_DIFF(duration, te_short * 2) < te_delta) {
|
||||
decode_count_bit++;
|
||||
parser_step = Bresser3chDecoderStepV0SaveDuration;
|
||||
} else
|
||||
parser_step = Bresser3chDecoderStepReset;
|
||||
} else
|
||||
parser_step = Bresser3chDecoderStepReset;
|
||||
break;
|
||||
|
||||
case Bresser3chDecoderStepV1PreambleDn:
|
||||
if ((!level) && DURATION_DIFF(duration, te_short_v1 * 3) < te_delta) {
|
||||
if (DURATION_DIFF(te_last, te_short_v1 * 12) < te_delta * 2) {
|
||||
// End of sync after 4*750 (12*250) high values, start reading the message
|
||||
parser_step = Bresser3chDecoderStepV1SaveDuration;
|
||||
} else {
|
||||
parser_step = Bresser3chDecoderStepV1PreambleUp;
|
||||
}
|
||||
} else {
|
||||
parser_step = Bresser3chDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case Bresser3chDecoderStepV1PreambleUp:
|
||||
if (level && DURATION_DIFF(duration, te_short_v1 * 3) < te_delta) {
|
||||
te_last = te_last + duration;
|
||||
parser_step = Bresser3chDecoderStepV1PreambleDn;
|
||||
} else {
|
||||
parser_step = Bresser3chDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case Bresser3chDecoderStepV1SaveDuration:
|
||||
if (decode_count_bit == BRESSER_V1_DATA) {
|
||||
if (ws_protocol_bresser_3ch_check_v1()) {
|
||||
// ws_protocol_bresser_3ch_extract_data_v1(&instance->generic);
|
||||
sensorType = FPW_Bresser3CH_V1;
|
||||
if (callback) {
|
||||
callback(this);
|
||||
}
|
||||
}
|
||||
decode_data = 0;
|
||||
decode_count_bit = 0;
|
||||
parser_step = Bresser3chDecoderStepReset;
|
||||
} else if (level) {
|
||||
te_last = duration;
|
||||
parser_step = Bresser3chDecoderStepV1CheckDuration;
|
||||
} else {
|
||||
parser_step = Bresser3chDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case Bresser3chDecoderStepV1CheckDuration:
|
||||
if (!level) {
|
||||
if (DURATION_DIFF(te_last, te_short_v1) < te_delta && DURATION_DIFF(duration, te_long_v1) < te_delta) {
|
||||
subghz_protocol_blocks_add_bit(0);
|
||||
parser_step = Bresser3chDecoderStepV1SaveDuration;
|
||||
} else if (
|
||||
DURATION_DIFF(te_last, te_long_v1) < te_delta && DURATION_DIFF(duration, te_short_v1) < te_delta) {
|
||||
subghz_protocol_blocks_add_bit(1);
|
||||
parser_step = Bresser3chDecoderStepV1SaveDuration;
|
||||
} else
|
||||
parser_step = Bresser3chDecoderStepReset;
|
||||
} else
|
||||
parser_step = Bresser3chDecoderStepReset;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
uint32_t te_short = 475;
|
||||
uint32_t te_long = 3900;
|
||||
uint32_t te_delta = 150;
|
||||
|
||||
uint32_t te_short_v1 = 250;
|
||||
uint32_t te_long_v1 = 500;
|
||||
|
||||
bool ws_protocol_bresser_3ch_check_v0() {
|
||||
if (!decode_data) return false;
|
||||
// No CRC, so better sanity checks here
|
||||
if (((decode_data >> 8) & 0x0f) != 0x0f) return false; // separator not 0xf
|
||||
if (((decode_data >> 28) & 0xff) == 0xff) return false; // ID only ones?
|
||||
if (((decode_data >> 28) & 0xff) == 0x00) return false; // ID only zeroes?
|
||||
if (((decode_data >> 25) & 0x0f) == 0x0f) return false; // flags only ones?
|
||||
if (((decode_data >> 25) & 0x0f) == 0x00) return false; // flags only zeroes?
|
||||
if (((decode_data >> 12) & 0x0fff) == 0x0fff)
|
||||
return false; // temperature maxed out?
|
||||
if ((decode_data & 0xff) < 20)
|
||||
return false; // humidity percentage less than 20?
|
||||
if ((decode_data & 0xff) > 95)
|
||||
return false; // humidity percentage more than 95?
|
||||
return true;
|
||||
}
|
||||
bool ws_protocol_bresser_3ch_check_v1() {
|
||||
if (!decode_data) return false;
|
||||
|
||||
uint8_t sum = (((decode_data >> 32) & 0xff) +
|
||||
((decode_data >> 24) & 0xff) +
|
||||
((decode_data >> 16) & 0xff) +
|
||||
((decode_data >> 8) & 0xff)) &
|
||||
0xff;
|
||||
|
||||
return (decode_data & 0xff) == sum;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,88 @@
|
||||
|
||||
#ifndef __FPROTO_VAUNO_EN8822_H__
|
||||
#define __FPROTO_VAUNO_EN8822_H__
|
||||
|
||||
#include "weatherbase.hpp"
|
||||
|
||||
typedef enum {
|
||||
VaunoEN8822CDecoderStepReset = 0,
|
||||
VaunoEN8822CDecoderStepSaveDuration,
|
||||
VaunoEN8822CDecoderStepCheckDuration,
|
||||
} VaunoEN8822CDecoderStep;
|
||||
|
||||
class FProtoWeatherVaunoEN8822 : public FProtoWeatherBase {
|
||||
public:
|
||||
FProtoWeatherVaunoEN8822() {
|
||||
sensorType = FPW_Vauno_EN8822;
|
||||
}
|
||||
|
||||
void feed(bool level, uint32_t duration) override {
|
||||
switch (parser_step) {
|
||||
case VaunoEN8822CDecoderStepReset:
|
||||
if ((!level) && DURATION_DIFF(duration, te_long * 4) < te_delta) {
|
||||
parser_step = VaunoEN8822CDecoderStepSaveDuration;
|
||||
decode_data = 0;
|
||||
decode_count_bit = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case VaunoEN8822CDecoderStepSaveDuration:
|
||||
if (level) {
|
||||
te_last = duration;
|
||||
parser_step = VaunoEN8822CDecoderStepCheckDuration;
|
||||
} else {
|
||||
parser_step = VaunoEN8822CDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case VaunoEN8822CDecoderStepCheckDuration:
|
||||
if (!level) {
|
||||
if (DURATION_DIFF(te_last, te_short) < te_delta) {
|
||||
if (DURATION_DIFF(duration, te_long * 2) < te_delta) {
|
||||
subghz_protocol_blocks_add_bit(1);
|
||||
parser_step = VaunoEN8822CDecoderStepSaveDuration;
|
||||
} else if (DURATION_DIFF(duration, te_long) < te_delta) {
|
||||
subghz_protocol_blocks_add_bit(0);
|
||||
parser_step = VaunoEN8822CDecoderStepSaveDuration;
|
||||
} else if (DURATION_DIFF(duration, te_long * 4) < te_delta) {
|
||||
parser_step = VaunoEN8822CDecoderStepReset;
|
||||
if (decode_count_bit == min_count_bit_for_found && ws_protocol_vauno_en8822c_check()) {
|
||||
// ws_protocol_vauno_en8822c_extract_data(&instance->generic);
|
||||
|
||||
if (callback) {
|
||||
callback(this);
|
||||
}
|
||||
} else if (decode_count_bit == 1) {
|
||||
parser_step = VaunoEN8822CDecoderStepSaveDuration;
|
||||
}
|
||||
decode_data = 0;
|
||||
decode_count_bit = 0;
|
||||
} else
|
||||
parser_step = VaunoEN8822CDecoderStepReset;
|
||||
} else
|
||||
parser_step = VaunoEN8822CDecoderStepReset;
|
||||
} else
|
||||
parser_step = VaunoEN8822CDecoderStepReset;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
// timing values
|
||||
uint32_t te_short = 500;
|
||||
uint32_t te_long = 1940;
|
||||
uint32_t te_delta = 150;
|
||||
uint32_t min_count_bit_for_found = 42;
|
||||
|
||||
bool ws_protocol_vauno_en8822c_check() {
|
||||
if (!decode_data) return false;
|
||||
// The sum of all nibbles should match the last 6 bits
|
||||
uint8_t sum = 0;
|
||||
for (uint8_t i = 6; i <= 38; i += 4) {
|
||||
sum += ((decode_data >> i) & 0x0f);
|
||||
}
|
||||
return sum != 0 && (sum & 0x3f) == (decode_data & 0x3f);
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -29,6 +29,8 @@ So include here the .hpp, and add a new element to the protos vector in the cons
|
||||
#include "w-acurite5in1.hpp"
|
||||
#include "w-emose601x.hpp"
|
||||
#include "w-solight_te44.hpp"
|
||||
#include "w-bresser_3ch.hpp"
|
||||
#include "w-vauno_en8822.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
@@ -66,10 +68,13 @@ class WeatherProtos : public FProtoListGeneral {
|
||||
protos[FPW_Acurite5in1] = new FProtoWeatherAcurite5in1();
|
||||
protos[FPW_EmosE601x] = new FProtoWeatherEmosE601x();
|
||||
protos[FPW_SolightTE44] = new FProtoWeatherSolightTE44();
|
||||
protos[FPW_Bresser3CH] = new FProtoWeatheBresser3CH();
|
||||
protos[FPW_Bresser3CH_V1] = nullptr; // done by FProtoWeatheBresser3CH
|
||||
protos[FPW_Vauno_EN8822] = new FProtoWeatherVaunoEN8822();
|
||||
|
||||
// set callback for them
|
||||
for (uint8_t i = 0; i < FPW_COUNT; ++i) {
|
||||
protos[i]->setCallback(callbackTarget);
|
||||
if (protos[i] != NULL) protos[i]->setCallback(callbackTarget);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,9 @@ enum FPROTO_WEATHER_SENSOR {
|
||||
FPW_Acurite5in1 = 21,
|
||||
FPW_EmosE601x = 22,
|
||||
FPW_SolightTE44 = 23,
|
||||
FPW_Bresser3CH = 24,
|
||||
FPW_Bresser3CH_V1 = 25,
|
||||
FPW_Vauno_EN8822 = 26,
|
||||
FPW_COUNT // this must be the last
|
||||
};
|
||||
|
||||
|
||||
@@ -38,32 +38,79 @@ void SubGhzDProcessor::execute(const buffer_c8_t& buffer) {
|
||||
feed_channel_stats(decim_1_out);
|
||||
|
||||
for (size_t i = 0; i < decim_1_out.count; i++) {
|
||||
threshold = (low_estimate + high_estimate) / 2;
|
||||
int32_t const hysteresis = threshold / 8; // +-12%
|
||||
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)
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
bool meashl = (mag > threshold);
|
||||
tm += mag;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
cnt += decim_1_out.count; // TODO , check if it is necessary that xdecim factor.
|
||||
if (cnt > 90'000) {
|
||||
threshold = (tm / cnt) / 2;
|
||||
cnt = 0;
|
||||
tm = 0;
|
||||
if (threshold < 50) threshold = 50;
|
||||
if (threshold > 1700) threshold = 1700;
|
||||
}
|
||||
}
|
||||
|
||||
void SubGhzDProcessor::on_message(const Message* const message) {
|
||||
@@ -75,8 +122,6 @@ 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
|
||||
|
||||
modulation = message.modulation; // TODO: add support for FM (currently AM only)
|
||||
|
||||
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
|
||||
|
||||
@@ -32,7 +32,14 @@
|
||||
#include "message.hpp"
|
||||
#include "dsp_decimate.hpp"
|
||||
|
||||
#pragma GCC push_options
|
||||
#pragma GCC optimize("Os")
|
||||
#include "fprotos/subghzdprotos.hpp"
|
||||
#pragma GCC pop_options
|
||||
|
||||
#define OOK_EST_HIGH_RATIO 3 // Constant for slowness of OOK high level estimator
|
||||
#define OOK_EST_LOW_RATIO 5 // Constant for slowness of OOK low level (noise) estimator (very slow)
|
||||
#define OOK_MAX_HIGH_LEVEL 450000
|
||||
|
||||
class SubGhzDProcessor : public BasebandProcessor {
|
||||
public:
|
||||
@@ -40,9 +47,18 @@ class SubGhzDProcessor : public BasebandProcessor {
|
||||
void on_message(const Message* const message) override;
|
||||
|
||||
private:
|
||||
enum {
|
||||
STATE_IDLE = 0,
|
||||
STATE_PULSE = 1,
|
||||
STATE_GAP_START = 2,
|
||||
STATE_GAP = 3,
|
||||
} sig_state = STATE_IDLE;
|
||||
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
|
||||
uint32_t nsPerDecSamp = 0;
|
||||
uint8_t modulation = 0;
|
||||
|
||||
/* 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
|
||||
@@ -55,14 +71,10 @@ class SubGhzDProcessor : public BasebandProcessor {
|
||||
dsp::decimate::FIRC16xR16x16Decim2 decim_1{};
|
||||
|
||||
uint32_t currentDuration = 0;
|
||||
uint32_t threshold = 0x0630; // will overwrite after the first iteration
|
||||
uint32_t threshold = 0x0630;
|
||||
bool currentHiLow = false;
|
||||
bool configured{false};
|
||||
|
||||
// for threshold
|
||||
uint32_t cnt = 0;
|
||||
uint32_t tm = 0;
|
||||
|
||||
FProtoListGeneral* protoList = new SubGhzDProtos(); // holds all the protocols we can parse
|
||||
void configure(const SubGhzFPRxConfigureMessage& message);
|
||||
|
||||
|
||||
@@ -39,32 +39,79 @@ void WeatherProcessor::execute(const buffer_c8_t& buffer) {
|
||||
feed_channel_stats(decim_1_out);
|
||||
|
||||
for (size_t i = 0; i < decim_1_out.count; i++) {
|
||||
threshold = (low_estimate + high_estimate) / 2;
|
||||
int32_t const hysteresis = threshold / 8; // +-12%
|
||||
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)
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
bool meashl = (mag > threshold);
|
||||
tm += mag;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
cnt += decim_1_out.count; // TODO , check if it is necessary that xdecim factor.
|
||||
if (cnt > 90'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) {
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#ifndef __PROC_WEATHER_H__
|
||||
#define __PROC_WEATHER_H__
|
||||
|
||||
#include <algorithm>
|
||||
#include "baseband_processor.hpp"
|
||||
#include "baseband_thread.hpp"
|
||||
#include "rssi_thread.hpp"
|
||||
@@ -34,15 +35,28 @@
|
||||
|
||||
#include "fprotos/weatherprotos.hpp"
|
||||
|
||||
#define OOK_EST_HIGH_RATIO 3 // Constant for slowness of OOK high level estimator
|
||||
#define OOK_EST_LOW_RATIO 5 // Constant for slowness of OOK low level (noise) estimator (very slow)
|
||||
#define OOK_MAX_HIGH_LEVEL 450000
|
||||
|
||||
class WeatherProcessor : public BasebandProcessor {
|
||||
public:
|
||||
void execute(const buffer_c8_t& buffer) override;
|
||||
void on_message(const Message* const message) override;
|
||||
|
||||
private:
|
||||
enum {
|
||||
STATE_IDLE = 0,
|
||||
STATE_PULSE = 1,
|
||||
STATE_GAP_START = 2,
|
||||
STATE_GAP = 3,
|
||||
} sig_state = STATE_IDLE;
|
||||
uint32_t low_estimate = 100;
|
||||
uint32_t high_estimate = 12000;
|
||||
uint32_t min_high_level = 10;
|
||||
size_t baseband_fs = 0; // will be set later by configure message.
|
||||
uint32_t nsPerDecSamp = 0;
|
||||
|
||||
uint8_t numg = 0; // count of matched signals to filter spikes
|
||||
/* 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{
|
||||
@@ -58,10 +72,6 @@ class WeatherProcessor : public BasebandProcessor {
|
||||
bool currentHiLow = false;
|
||||
bool configured{false};
|
||||
|
||||
// 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);
|
||||
void on_beep_message(const AudioBeepMessage& message);
|
||||
|
||||
@@ -192,7 +192,7 @@ void I2cDev_MAX17055::update() {
|
||||
getBatteryInfo(validity, batteryPercentage, voltage, current);
|
||||
|
||||
// send local message
|
||||
BatteryStateMessage msg{validity, batteryPercentage, current >= 0, voltage};
|
||||
BatteryStateMessage msg{validity, batteryPercentage, current >= 25, voltage};
|
||||
EventDispatcher::send_message(msg);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
|
||||
#include "i2cdev_ppmod.hpp"
|
||||
#include "portapack.hpp"
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace i2cdev {
|
||||
@@ -30,11 +29,95 @@ bool I2cDev_PPmod::init(uint8_t addr_) {
|
||||
if (addr_ != I2CDEV_PPMOD_ADDR_1) return false;
|
||||
addr = addr_;
|
||||
model = I2CDECMDL_PPMOD;
|
||||
query_interval = 10;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void I2cDev_PPmod::update() {
|
||||
auto mask = get_features_mask();
|
||||
if (mask & (uint64_t)SupportedFeatures::FEAT_GPS) {
|
||||
auto data = get_gps_data();
|
||||
if (data.has_value()) {
|
||||
GPSPosDataMessage msg{data.value().latitude, data.value().longitude, (int32_t)data.value().altitude, (int32_t)data.value().speed, data.value().sats_in_use};
|
||||
EventDispatcher::send_message(msg);
|
||||
}
|
||||
}
|
||||
if (mask & (uint64_t)SupportedFeatures::FEAT_ORIENTATION) {
|
||||
auto data = get_orientation_data();
|
||||
if (data.has_value()) {
|
||||
OrientationDataMessage msg{(uint16_t)data.value().angle, (int16_t)data.value().tilt};
|
||||
EventDispatcher::send_message(msg);
|
||||
}
|
||||
}
|
||||
if (mask & (uint64_t)SupportedFeatures::FEAT_ENVIRONMENT) {
|
||||
auto data = get_environment_data();
|
||||
if (data.has_value()) {
|
||||
EnvironmentDataMessage msg{data.value().temperature, data.value().humidity, data.value().pressure};
|
||||
EventDispatcher::send_message(msg);
|
||||
}
|
||||
}
|
||||
if (mask & (uint64_t)SupportedFeatures::FEAT_LIGHT) {
|
||||
auto data = get_light_data();
|
||||
if (data.has_value()) {
|
||||
LightDataMessage msg{data.value()};
|
||||
EventDispatcher::send_message(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<orientation_t> I2cDev_PPmod::get_orientation_data() {
|
||||
Command cmd = Command::COMMAND_GETFEAT_DATA_ORIENTATION;
|
||||
orientation_t data;
|
||||
bool success = i2c_read((uint8_t*)&cmd, 2, (uint8_t*)&data, sizeof(orientation_t));
|
||||
if (success == false) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
std::optional<gpssmall_t> I2cDev_PPmod::get_gps_data() {
|
||||
Command cmd = Command::COMMAND_GETFEAT_DATA_GPS;
|
||||
gpssmall_t data;
|
||||
bool success = i2c_read((uint8_t*)&cmd, 2, (uint8_t*)&data, sizeof(gpssmall_t));
|
||||
if (success == false) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
std::optional<environment_t> I2cDev_PPmod::get_environment_data() {
|
||||
Command cmd = Command::COMMAND_GETFEAT_DATA_ENVIRONMENT;
|
||||
environment_t data;
|
||||
bool success = i2c_read((uint8_t*)&cmd, 2, (uint8_t*)&data, sizeof(environment_t));
|
||||
if (success == false) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
std::optional<uint16_t> I2cDev_PPmod::get_light_data() {
|
||||
Command cmd = Command::COMMAND_GETFEAT_DATA_LIGHT;
|
||||
uint16_t data;
|
||||
bool success = i2c_read((uint8_t*)&cmd, 2, (uint8_t*)&data, sizeof(uint16_t));
|
||||
if (success == false) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
uint64_t I2cDev_PPmod::get_features_mask() {
|
||||
uint64_t mask = 0;
|
||||
Command cmd = Command::COMMAND_GETFEATURE_MASK;
|
||||
bool success = i2c_read((uint8_t*)&cmd, 2, (uint8_t*)&mask, sizeof(mask));
|
||||
if (success == false) {
|
||||
return 0;
|
||||
}
|
||||
// sanity check
|
||||
if (mask == UINT64_MAX) {
|
||||
return 0;
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
std::optional<I2cDev_PPmod::device_info> I2cDev_PPmod::readDeviceInfo() {
|
||||
@@ -45,7 +128,10 @@ std::optional<I2cDev_PPmod::device_info> I2cDev_PPmod::readDeviceInfo() {
|
||||
if (success == false) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// sanity check
|
||||
if (info.application_count > 1000) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
@@ -58,6 +144,10 @@ std::optional<I2cDev_PPmod::standalone_app_info> I2cDev_PPmod::getStandaloneAppI
|
||||
if (success == false) {
|
||||
return std::nullopt;
|
||||
}
|
||||
// sanity check
|
||||
if (info.binary_size == UINT32_MAX) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
@@ -30,6 +30,8 @@
|
||||
#include "standalone_app.hpp"
|
||||
#include "i2cdevmanager.hpp"
|
||||
|
||||
#include "i2cdev_ppmod_helper.hpp"
|
||||
|
||||
namespace i2cdev {
|
||||
|
||||
class I2cDev_PPmod : public I2cDev {
|
||||
@@ -38,13 +40,22 @@ class I2cDev_PPmod : public I2cDev {
|
||||
COMMAND_NONE = 0,
|
||||
|
||||
// will respond with device_info
|
||||
COMMAND_INFO = 0x18F0,
|
||||
COMMAND_INFO = 1,
|
||||
|
||||
// will respond with info of application
|
||||
COMMAND_APP_INFO = 0xA90B,
|
||||
COMMAND_APP_INFO,
|
||||
|
||||
// will respond with application data
|
||||
COMMAND_APP_TRANSFER = 0x4183,
|
||||
COMMAND_APP_TRANSFER,
|
||||
|
||||
// Feature specific commands
|
||||
COMMAND_GETFEATURE_MASK,
|
||||
// Feature data getter commands
|
||||
COMMAND_GETFEAT_DATA_GPS,
|
||||
COMMAND_GETFEAT_DATA_ORIENTATION,
|
||||
COMMAND_GETFEAT_DATA_ENVIRONMENT,
|
||||
COMMAND_GETFEAT_DATA_LIGHT,
|
||||
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
@@ -66,9 +77,14 @@ class I2cDev_PPmod : public I2cDev {
|
||||
bool init(uint8_t addr_) override;
|
||||
void update() override;
|
||||
|
||||
std::optional<device_info> readDeviceInfo();
|
||||
std::optional<standalone_app_info> getStandaloneAppInfo(uint32_t index);
|
||||
std::vector<uint8_t> downloadStandaloneApp(uint32_t index, size_t offset);
|
||||
uint64_t get_features_mask();
|
||||
std::optional<device_info> readDeviceInfo();
|
||||
std::optional<gpssmall_t> get_gps_data();
|
||||
std::optional<orientation_t> get_orientation_data();
|
||||
std::optional<environment_t> get_environment_data();
|
||||
std::optional<uint16_t> get_light_data();
|
||||
};
|
||||
|
||||
} /* namespace i2cdev */
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
#ifndef I2CDEV_PPMOD_HELPER_H
|
||||
#define I2CDEV_PPMOD_HELPER_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
enum class SupportedFeatures : uint64_t {
|
||||
FEAT_NONE = 0,
|
||||
FEAT_EXT_APP = 1 << 0,
|
||||
FEAT_UART = 1 << 1,
|
||||
FEAT_GPS = 1 << 2,
|
||||
FEAT_ORIENTATION = 1 << 3,
|
||||
FEAT_ENVIRONMENT = 1 << 4,
|
||||
FEAT_LIGHT = 1 << 5,
|
||||
FEAT_DISPLAY = 1 << 6
|
||||
};
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint8_t hour; /*!< Hour */
|
||||
uint8_t minute; /*!< Minute */
|
||||
uint8_t second; /*!< Second */
|
||||
uint16_t thousand; /*!< Thousand */
|
||||
} gps_time_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint8_t day; /*!< Day (start from 1) */
|
||||
uint8_t month; /*!< Month (start from 1) */
|
||||
uint16_t year; /*!< Year (start from 2000) */
|
||||
} gps_date_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
float latitude; /*!< Latitude (degrees) */
|
||||
float longitude; /*!< Longitude (degrees) */
|
||||
float altitude; /*!< Altitude (meters) */
|
||||
uint8_t sats_in_use; /*!< Number of satellites in use */
|
||||
uint8_t sats_in_view; /*!< Number of satellites in view */
|
||||
float speed; /*!< Ground speed, unit: m/s */
|
||||
gps_date_t date; /*!< Fix date */
|
||||
gps_time_t tim; /*!< time in UTC */
|
||||
} gpssmall_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
float angle;
|
||||
float tilt;
|
||||
} orientation_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
float temperature;
|
||||
float humidity;
|
||||
float pressure;
|
||||
} environment_t;
|
||||
|
||||
// light is uint16_t
|
||||
|
||||
#endif
|
||||
@@ -131,8 +131,13 @@ void I2cDev::got_success() {
|
||||
|
||||
bool I2cDev::i2c_read(uint8_t* reg, uint8_t reg_size, uint8_t* data, uint8_t bytes) {
|
||||
if (bytes == 0) return false;
|
||||
if (reg_size > 0 && reg) i2cbus.transmit(addr, reg, reg_size);
|
||||
bool ret = i2cbus.receive(addr, data, bytes);
|
||||
bool ret = true;
|
||||
if (reg_size > 0 && reg) ret = i2cbus.transmit(addr, reg, reg_size, 150);
|
||||
if (!ret) {
|
||||
got_error();
|
||||
return false;
|
||||
}
|
||||
ret = i2cbus.receive(addr, data, bytes, 150);
|
||||
if (!ret)
|
||||
got_error();
|
||||
else
|
||||
@@ -153,7 +158,7 @@ bool I2cDev::i2c_write(uint8_t* reg, uint8_t reg_size, uint8_t* data, uint8_t by
|
||||
// Copy the data into the buffer after the register data
|
||||
memcpy(buffer + reg_size, data, bytes);
|
||||
// Transmit the combined data
|
||||
bool result = i2cbus.transmit(addr, buffer, total_size);
|
||||
bool result = i2cbus.transmit(addr, buffer, total_size, 150);
|
||||
// Clean up the dynamically allocated buffer
|
||||
delete[] buffer;
|
||||
if (!result)
|
||||
@@ -304,7 +309,7 @@ msg_t I2CDevManager::timer_fn(void* arg) {
|
||||
force_scan = false;
|
||||
}
|
||||
for (size_t i = 0; i < devlist.size(); i++) {
|
||||
if (devlist[i].addr != 0 && devlist[i].dev) {
|
||||
if (devlist[i].addr != 0 && devlist[i].dev && devlist[i].dev->query_interval != 0) {
|
||||
if ((curr_timer % devlist[i].dev->query_interval) == 0) { // only if it is device's interval
|
||||
devlist[i].dev->update(); // updates it's data, and broadcasts it. if there is any error it will handle in it, and later we can remove it
|
||||
}
|
||||
|
||||
@@ -416,8 +416,12 @@ class IO {
|
||||
const auto value_low = data_read();
|
||||
uint32_t original_value = (value_high << 8) | value_low;
|
||||
|
||||
if (get_is_inverted()) return original_value;
|
||||
|
||||
if (get_dark_cover()) {
|
||||
original_value = DARKENED_PIXEL(original_value, get_brightness());
|
||||
// this is read data, so if the fake brightness is enabled AKA get_dark_cover() == true,
|
||||
// then shift to back side AKA UNDARKENED_PIXEL, to prevent read shifted darkern info
|
||||
original_value = UNDARKENED_PIXEL(original_value, get_brightness());
|
||||
}
|
||||
return original_value;
|
||||
}
|
||||
|
||||
@@ -1 +1,25 @@
|
||||
bitmap is for icons, it's colorless and headless; splash and modal isn't using bitmap
|
||||
bitmap is for icons, it's colorless and headless; splash and modal isn't
|
||||
using bitmap
|
||||
|
||||
## Bitmap helper folder
|
||||
The folder `bitmap_tools` contains scripts to convert icons in png format to
|
||||
the PortaPack readable format in `bitmap.hpp`.
|
||||
|
||||
### Convert a folder contains one or more icon.png to one bitmap.hpp
|
||||
The `make_bitmap.py` is the traditional helper, well tested. The folder with
|
||||
the icons is given as argument and generates the `./bitmap.hpp`. This file
|
||||
needs to be copied to `mayhem-firmware/firmware/application/`.
|
||||
The icon size needs to be a multiple of 8. The generated icon is black/white.
|
||||
|
||||
### Convert bitmap array to icon.png
|
||||
The `bitmap_arr_reverse_decode.py` takes an array from the bitmap.hpp and
|
||||
convert it back to a png.
|
||||
|
||||
### Convert both ways
|
||||
The `pp_png2hpp.py` is based on the privious scripts, as all in one solution.
|
||||
With the `--hpp bitmap.hpp` file and the `--graphics /folder_to/png_icons/`
|
||||
arguments, theis script will generate a `bitmap.hpp`.
|
||||
Add the `--reverse` argument to generate png icons from the given `bitmap.hpp`.
|
||||
|
||||
Especially the reverse function got a parser, to automatic get the filename
|
||||
and size of the image.
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
## Note: This helper was just a POC for pp_png3hpp.py to test the regex.
|
||||
|
||||
import argparse
|
||||
import numpy as np
|
||||
import re
|
||||
from PIL import Image
|
||||
|
||||
|
||||
def parse_bitmaphpp(bitmaphpp_file, icon_name):
|
||||
ico_pattern = re.compile(r"static constexpr uint8_t bitmap_(.*)_data\[\] = {\n((?:\s+(?:.*)\n)+)};\nstatic constexpr Bitmap bitmap_.*\{\n\s+\{(.*)\},", re.MULTILINE)
|
||||
ico_data = []
|
||||
|
||||
# read file to buffer, to find multiline regex
|
||||
readfile = open(bitmaphpp_file,'r')
|
||||
buff = readfile.read()
|
||||
readfile.close()
|
||||
|
||||
if icon_name == 'all':
|
||||
for match in ico_pattern.finditer(buff):
|
||||
ico_data.append([match.group(1), match.group(2), match.group(3)])
|
||||
else:
|
||||
for match in ico_pattern.finditer(buff):
|
||||
if match.group(1) in icon_name:
|
||||
ico_data.append([match.group(1), match.group(2), match.group(3)])
|
||||
|
||||
return (ico_data)
|
||||
|
||||
def convert_hpp(icon_name,bitmap_array,iconsize_str):
|
||||
iconsize = iconsize_str.split(", ")
|
||||
bitmap_size = (int(iconsize[0]),int(iconsize[1]))
|
||||
bitmap_data=[]
|
||||
|
||||
image_data = np.zeros((bitmap_size[1], bitmap_size[0]), dtype=np.uint8)
|
||||
|
||||
#print(bitmap_array)
|
||||
for value in bitmap_array.split(",\n"):
|
||||
if (value):
|
||||
#print(int(value, 0))
|
||||
bitmap_data.append(int(value, 0))
|
||||
|
||||
print(f"Count {len(bitmap_data)} Size: {bitmap_size[0]}x{bitmap_size[1]} ({bitmap_size[0]*bitmap_size[1]})")
|
||||
|
||||
for y in range(bitmap_size[1]):
|
||||
for x in range(bitmap_size[0]):
|
||||
byte_index = (y * bitmap_size[0] + x) // 8
|
||||
bit_index = x % 8
|
||||
# bit_index = 7 - (x % 8)
|
||||
pixel_value = (bitmap_data[byte_index] >> bit_index) & 1
|
||||
image_data[y, x] = pixel_value * 255
|
||||
|
||||
image = Image.fromarray(image_data, 'L')
|
||||
imagea = image.copy()
|
||||
imagea.putalpha(image)
|
||||
imagea.save(icon_name+".png")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("hpp", help="Path for bitmap.hpp")
|
||||
parser.add_argument("--icon", help="Name of the icon from bitmap.hpp, Use 'All' for all icons in file", default = 'titlebar_image')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.icon:
|
||||
icon_name = args.icon
|
||||
else:
|
||||
icon_name = 'titlebar_image'
|
||||
|
||||
print("parse", icon_name)
|
||||
icons = parse_bitmaphpp(args.hpp, icon_name)
|
||||
|
||||
for icon in icons:
|
||||
convert_hpp(icon[0],icon[1],icon[2])
|
||||
|
||||
|
||||
Executable
+237
@@ -0,0 +1,237 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Convert png icons to bitmap.hpp inspired by
|
||||
# make_bitmap.py - Copyright (C) 2016 Furrtek
|
||||
# Convert bitmap.hpp to icons inspyred by
|
||||
# bitmap_arr_reverse_decode.py - Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||
# bitmap_arr_reverse_decode.py - Copyleft (ɔ) 2024 zxkmm with the GPL license
|
||||
# Copysomething (c) 2024 LupusE with the license, needed by the PortaPack project
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
import argparse
|
||||
import numpy as np
|
||||
import re
|
||||
import sys
|
||||
import os
|
||||
from PIL import Image
|
||||
|
||||
### Convert a directory of icons in png format to one bitmap.hpp file.
|
||||
######################################################################
|
||||
|
||||
def pp_bitmaphpp_header():
|
||||
return ("""/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// This file was generated by make_bitmap.py
|
||||
|
||||
#ifndef __BITMAP_HPP__
|
||||
#define __BITMAP_HPP__
|
||||
|
||||
#include \"ui.hpp\"
|
||||
|
||||
namespace ui {
|
||||
""")
|
||||
|
||||
|
||||
def convert_png(file):
|
||||
bmp_data = []
|
||||
data = 0
|
||||
|
||||
rgb_im = Image.open(file).convert('RGBA')
|
||||
|
||||
if rgb_im.size[0] % 8 or rgb_im.size[1] % 8:
|
||||
print((file + ": Size is not a multiple of 8. Image is not included in bitmap.hpp."))
|
||||
sys.exit(-1)
|
||||
|
||||
name = os.path.basename(file).split(".")[0].lower()
|
||||
|
||||
bmp_data.append(f"\nstatic constexpr uint8_t bitmap_{name}_data[] = {{\n")
|
||||
|
||||
for i in range(rgb_im.size[1]):
|
||||
for j in range(rgb_im.size[0]):
|
||||
r, g, b, a = rgb_im.getpixel((j, i))
|
||||
|
||||
data >>= 1
|
||||
|
||||
if r > 127 and g > 127 and b > 127 and a > 127:
|
||||
data += 128
|
||||
|
||||
if j % 8 == 7:
|
||||
bmp_data.append(" 0x%0.2X,\n" % data)
|
||||
data = 0
|
||||
|
||||
bmp_data.append(f"""}};
|
||||
static constexpr Bitmap bitmap_{name}{{
|
||||
{{{str(rgb_im.size[0])}, {str(rgb_im.size[1])}}},
|
||||
bitmap_{name}_data}};
|
||||
""")
|
||||
|
||||
out_bmpdata = ''.join(map(str, bmp_data))
|
||||
|
||||
return (out_bmpdata)
|
||||
|
||||
def pp_bitmaphpp_data(pngicons_path):
|
||||
count = 0
|
||||
bitmaphpp_data = []
|
||||
for file in os.listdir(pngicons_path):
|
||||
if os.path.isfile(pngicons_path + file) and file.endswith(".png"):
|
||||
bitmaphpp_data.append(convert_png(pngicons_path + file))
|
||||
count += 1
|
||||
|
||||
return bitmaphpp_data
|
||||
|
||||
|
||||
def pp_bitmaphpp_footer():
|
||||
return("""
|
||||
} /* namespace ui */
|
||||
|
||||
#endif /*__BITMAP_HPP__*/
|
||||
""")
|
||||
|
||||
|
||||
def pp_write_bitmaphpp(pngicons_path, hpp_outpath):
|
||||
bitmaphpp_file = []
|
||||
|
||||
bitmaphpp_file.append(pp_bitmaphpp_header())
|
||||
bitmaphpp_file.append("".join(str(x) for x in pp_bitmaphpp_data(pngicons_path)))
|
||||
bitmaphpp_file.append(pp_bitmaphpp_footer())
|
||||
|
||||
out_file = "".join(str(x) for x in bitmaphpp_file)
|
||||
|
||||
with open(hpp_outpath, "w", encoding="utf-8") as fd:
|
||||
fd.writelines(out_file)
|
||||
|
||||
print("Find your bitmap.hpp at", hpp_outpath)
|
||||
|
||||
|
||||
### Convert from a bitmap.hpp file one or all icons in png.
|
||||
###########################################################
|
||||
|
||||
def parse_bitmaphpp(bitmaphpp_file,icon_name):
|
||||
ico_pattern = re.compile(r"static constexpr uint8_t bitmap_(.*)_data\[\] = {\n((?:\s+(?:.*)\n)+)};\nstatic constexpr Bitmap bitmap_.*\{\n\s+\{(.*)\},", re.MULTILINE)
|
||||
ico_data = []
|
||||
|
||||
# read file to buffer, to find multiline regex
|
||||
readfile = open(bitmaphpp_file,'r')
|
||||
buff = readfile.read()
|
||||
readfile.close()
|
||||
|
||||
if icon_name == 'all':
|
||||
for match in ico_pattern.finditer(buff):
|
||||
ico_data.append([match.group(1), match.group(2), match.group(3)])
|
||||
else:
|
||||
for match in ico_pattern.finditer(buff):
|
||||
if match.group(1) in icon_name:
|
||||
ico_data.append([match.group(1), match.group(2), match.group(3)])
|
||||
|
||||
return (ico_data)
|
||||
|
||||
|
||||
def convert_hpp(icon_name,bitmap_array,iconsize_str,png_outdir):
|
||||
iconsize = iconsize_str.split(", ")
|
||||
bitmap_size = (int(iconsize[0]),int(iconsize[1]))
|
||||
bitmap_data=[]
|
||||
|
||||
image_data = np.zeros((bitmap_size[1], bitmap_size[0]), dtype=np.uint8)
|
||||
|
||||
for value in bitmap_array.split(",\n"):
|
||||
if (value):
|
||||
bitmap_data.append(int(value, 0))
|
||||
|
||||
for y in range(bitmap_size[1]):
|
||||
for x in range(bitmap_size[0]):
|
||||
byte_index = (y * bitmap_size[0] + x) // 8
|
||||
bit_index = x % 8
|
||||
# bit_index = 7 - (x % 8)
|
||||
pixel_value = (bitmap_data[byte_index] >> bit_index) & 1
|
||||
image_data[y, x] = pixel_value * 255
|
||||
|
||||
image = Image.fromarray(image_data, 'L')
|
||||
icon_out = os.path.join(png_outdir,icon_name+".png")
|
||||
image.save(icon_out)
|
||||
|
||||
|
||||
### Processing
|
||||
##############
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("hpp", help="Path of the bitmap.hpp")
|
||||
parser.add_argument("graphics", help="Path of <icon>.png files to convert", default=os.path.join(os.getcwd(),"graphics"))
|
||||
parser.add_argument("--icon","-i", help="Name of the icon to convert in reverse. Use 'all' to convert all. More names can be comma seperated.")
|
||||
parser.add_argument("--reverse","-r", help="Convert icon from bitmap.hpp to <name>.png", action="store_true")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.reverse:
|
||||
print("Reverse: Converting from hpp to png")
|
||||
|
||||
# filter hpp arg is a file
|
||||
hpp_file_path = args.hpp
|
||||
if not os.path.isfile(hpp_file_path):
|
||||
print(f"Error: {hpp_file_path} is not a valid file.")
|
||||
sys.exit(1)
|
||||
|
||||
# filter graph arg is a path
|
||||
if args.graphics:
|
||||
graphics_path = os.path.join(args.graphics, '')
|
||||
else:
|
||||
graphics_path = os.path.join(os.getcwd(),"graphics", '')
|
||||
if not os.path.exists(graphics_path):
|
||||
os.makedirs(graphics_path) # create if not exist
|
||||
|
||||
# define icons to convert
|
||||
if args.icon:
|
||||
icon_name = args.icon
|
||||
else:
|
||||
icon_name = 'titlebar_image'
|
||||
|
||||
icons = parse_bitmaphpp(hpp_file_path, icon_name)
|
||||
|
||||
for icon in icons:
|
||||
print("Converting icon", icon[0])
|
||||
convert_hpp(icon[0], icon[1], icon[2], graphics_path)
|
||||
sys.exit()
|
||||
else:
|
||||
print("Converting from png to hpp")
|
||||
if args.graphics:
|
||||
graphics_path = os.path.join(args.graphics, '')
|
||||
else:
|
||||
graphics_path = os.path.join(os.getcwd(),"graphics", '')
|
||||
print("From path", graphics_path, "to file", args.hpp)
|
||||
pp_write_bitmaphpp(graphics_path, args.hpp)
|
||||
sys.exit()
|
||||
Regular → Executable
BIN
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user