mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-09-14 10:39:30 +00:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1bf0d2b3e6 | |||
| ea02d98a59 | |||
| 9624d7b429 | |||
| 4f440055cb | |||
| 7354ba8fd5 | |||
| b7b4a10485 | |||
| cca0e18f5a | |||
| 8846926b68 | |||
| 7ceefb1b81 | |||
| 861f684d00 | |||
| 17b146b960 | |||
| 02251eeeb4 | |||
| c486572d3d | |||
| a6ed6e3099 | |||
| 175e5e2e8c | |||
| 7bc27038be | |||
| 609bf3219f | |||
| bdbd616a84 |
@@ -1,6 +1,6 @@
|
||||
# PortaPack Mayhem
|
||||
|
||||
[](https://travis-ci.com/eried/portapack-mayhem) [](https://github.com/eried/portapack-mayhem/actions/workflows/create_nightly_release.yml) [](https://codescene.io/projects/8381) [](https://github.com/eried/portapack-mayhem/releases) [](https://github.com/eried/portapack-mayhem/releases/latest) [](https://hub.docker.com/r/eried/portapack) [](https://discord.gg/tuwVMv3) [](https://www.bountysource.com/teams/portapack-mayhem/issues)
|
||||
[](https://travis-ci.com/eried/portapack-mayhem) [](https://github.com/eried/portapack-mayhem/actions/workflows/create_nightly_release.yml) [](https://codescene.io/projects/8381) [](https://github.com/eried/portapack-mayhem/releases) [](https://github.com/eried/portapack-mayhem/releases/latest) [](https://hub.docker.com/r/eried/portapack) [](https://discord.gg/tuwVMv3)
|
||||
|
||||
This is a fork of the [Havoc](https://github.com/furrtek/portapack-havoc/) firmware, which itself was a fork of the [PortaPack](https://github.com/sharebrained/portapack-hackrf) firmware, an add-on for the [HackRF](http://greatscottgadgets.com/hackrf/). A fork is a derivate, in this case one that has extra features and fixes when compared to the older versions.
|
||||
|
||||
@@ -22,7 +22,7 @@ This repository expands upon the previous work by many people and aims to consta
|
||||
|
||||
:heavy_check_mark: A recommended one is this [PortaPack H2](https://s.click.aliexpress.com/e/_DmU7GQX), that includes everything you need with the plastic case "inspired" on [this](https://github.com/eried/portapack-mayhem/wiki/H2-Enclosure).
|
||||
|
||||
:heavy_check_mark: Our friends at OpenSourceSDRLab give away five units every three months in [our discord](https://discord.com/channels/719669764804444213/1165624357129834547) of this [PortaPack H2](https://www.aliexpress.com/item/4000247041639.html?gatewayAdapt=4itemAdapt), you can support them too.
|
||||
:heavy_check_mark: Our friends at OpenSourceSDRLab give away five units every three months in our discord (check the badge on top) of their [PortaPack H2](https://www.aliexpress.com/item/4000247041639.html?gatewayAdapt=4itemAdapt), you can support them too by ordering.
|
||||
|
||||
:heavy_check_mark: Another popular option is the clone of the [PortaPack H1](https://s.click.aliexpress.com/e/_Dkbqs2X).
|
||||
|
||||
@@ -50,9 +50,11 @@ To support the people behind the hardware, please buy a genuine [HackRF](https:/
|
||||
## What if I really want something specific?
|
||||
If what you need can be relevant in general, you can [request a feature](https://github.com/eried/portapack-mayhem/issues/new?labels=enhancement&template=feature_request.md).
|
||||
|
||||
You can create a bounty and invite people to your own bounty. This will incentivize coders to work on a new feature, solving a bug or even writting documentation. Start a bounty by [creating](https://github.com/eried/portapack-mayhem/issues/new/choose) or [choosing](https://github.com/eried/portapack-mayhem/issues/) an existing issue. Then, go to [Bountysource](https://www.bountysource.com/) and post a bounty using the link to that specific [issue](https://www.bountysource.com/teams/portapack-mayhem/issues).
|
||||
<del>You can create a bounty and invite people to your own bounty. This will incentivize coders to work on a new feature, solving a bug or even writting documentation. Start a bounty by [creating](https://github.com/eried/portapack-mayhem/issues/new/choose) or [choosing](https://github.com/eried/portapack-mayhem/issues/) an existing issue. Then, go to [Bountysource](https://www.bountysource.com/) and post a bounty using the link to that specific [issue](https://www.bountysource.com/teams/portapack-mayhem/issues).</del>
|
||||
|
||||
Promote your bounty over our Discord by clicking the chat badge on [top](#portapack-mayhem).
|
||||
<del>Promote your bounty over our Discord by clicking the chat badge on [top](#portapack-mayhem).</del>
|
||||
|
||||
Bountysource has not been reliable lately, so until this changes, please **DO NOT** post a bounty there. Go to our Discord by clicking the chat badge on [top](#portapack-mayhem) and discuss there.
|
||||
|
||||
## What if I need help?
|
||||
First, check the [documentation](https://github.com/eried/portapack-mayhem/wiki). If you find a bug or you think the problem is related to the current repository, please open an [issue](https://github.com/eried/portapack-mayhem/issues/new/choose).
|
||||
|
||||
@@ -307,6 +307,7 @@ set(CPPSRC
|
||||
apps/ui_touch_calibration.cpp
|
||||
apps/ui_touchtunes.cpp
|
||||
apps/ui_view_wav.cpp
|
||||
apps/ui_weatherstation.cpp
|
||||
apps/ui_whipcalc.cpp
|
||||
protocols/aprs.cpp
|
||||
protocols/ax25.cpp
|
||||
|
||||
@@ -428,6 +428,8 @@ BLERxView::BLERxView(NavigationView& nav)
|
||||
nav_.push<BleRecentEntryDetailView>(entry);
|
||||
};
|
||||
|
||||
filterBuffer = filter;
|
||||
|
||||
button_filter.on_select = [this](Button&) {
|
||||
text_prompt(
|
||||
nav_,
|
||||
@@ -469,34 +471,38 @@ BLERxView::BLERxView(NavigationView& nav)
|
||||
logger->append(LOG_ROOT_DIR "/BLELOG_" + to_string_timestamp(rtc_time::now()) + ".TXT");
|
||||
};
|
||||
|
||||
check_name.set_value(true);
|
||||
check_name.set_value(name_enable);
|
||||
|
||||
check_name.on_select = [this](Checkbox&, bool v) {
|
||||
name_enable = v;
|
||||
setAllMembersToValue(recent, &BleRecentEntry::include_name, v);
|
||||
recent_entries_view.set_dirty();
|
||||
};
|
||||
|
||||
options_channel.on_change = [this](size_t, int32_t i) {
|
||||
options_channel.on_change = [this](size_t index, int32_t v) {
|
||||
channel_index = (uint8_t)index;
|
||||
|
||||
// If we selected Auto don't do anything and Auto will handle changing.
|
||||
if (i == 40) {
|
||||
if (v == 40) {
|
||||
auto_channel = true;
|
||||
return;
|
||||
} else {
|
||||
auto_channel = false;
|
||||
}
|
||||
|
||||
field_frequency.set_value(get_freq_by_channel_number(i));
|
||||
channel_number = i;
|
||||
field_frequency.set_value(get_freq_by_channel_number(v));
|
||||
channel_number = v;
|
||||
|
||||
baseband::set_btlerx(channel_number);
|
||||
};
|
||||
|
||||
options_sort.on_change = [this](size_t, int32_t index) {
|
||||
handle_entries_sort(index);
|
||||
options_sort.on_change = [this](size_t index, int32_t v) {
|
||||
sort_index = (uint8_t)index;
|
||||
handle_entries_sort(v);
|
||||
};
|
||||
|
||||
options_channel.set_selected_index(0, true);
|
||||
options_sort.set_selected_index(0, true);
|
||||
options_channel.set_selected_index(channel_index, true);
|
||||
options_sort.set_selected_index(sort_index, true);
|
||||
|
||||
logger = std::make_unique<BLELogger>();
|
||||
|
||||
@@ -657,16 +663,6 @@ bool BLERxView::saveFile(const std::filesystem::path& path) {
|
||||
}
|
||||
|
||||
void BLERxView::on_data(BlePacketData* packet) {
|
||||
if (auto_channel) {
|
||||
int min = 37;
|
||||
int max = 39;
|
||||
|
||||
int randomChannel = min + std::rand() % (max - min + 1);
|
||||
|
||||
field_frequency.set_value(get_freq_by_channel_number(randomChannel));
|
||||
baseband::set_btlerx(randomChannel);
|
||||
}
|
||||
|
||||
std::string str_console = "";
|
||||
|
||||
if (!logging) {
|
||||
@@ -726,6 +722,23 @@ void BLERxView::on_filter_change(std::string value) {
|
||||
filter = value;
|
||||
}
|
||||
|
||||
// called each 1/60th of second, so 6 = 100ms
|
||||
void BLERxView::on_timer() {
|
||||
if (++timer_count == timer_period) {
|
||||
timer_count = 0;
|
||||
|
||||
if (auto_channel) {
|
||||
int min = 37;
|
||||
int max = 39;
|
||||
|
||||
int randomChannel = min + std::rand() % (max - min + 1);
|
||||
|
||||
field_frequency.set_value(get_freq_by_channel_number(randomChannel));
|
||||
baseband::set_btlerx(randomChannel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BLERxView::handle_entries_sort(uint8_t index) {
|
||||
switch (index) {
|
||||
case 0:
|
||||
|
||||
@@ -195,6 +195,7 @@ class BLERxView : public View {
|
||||
bool saveFile(const std::filesystem::path& path);
|
||||
void on_data(BlePacketData* packetData);
|
||||
void on_filter_change(std::string value);
|
||||
void on_timer();
|
||||
void handle_entries_sort(uint8_t index);
|
||||
void updateEntry(const BlePacketData* packet, BleRecentEntry& entry, ADV_PDU_TYPE pdu_type);
|
||||
|
||||
@@ -204,16 +205,32 @@ class BLERxView : public View {
|
||||
4000000 /* bandwidth */,
|
||||
4000000 /* sampling rate */,
|
||||
ReceiverModel::Mode::WidebandFMAudio};
|
||||
|
||||
uint8_t channel_index{0};
|
||||
uint8_t sort_index{0};
|
||||
std::string filter{};
|
||||
bool logging{false};
|
||||
bool name_enable{true};
|
||||
app_settings::SettingsManager settings_{
|
||||
"BLE Rx", app_settings::Mode::RX};
|
||||
"rx_ble",
|
||||
app_settings::Mode::RX,
|
||||
{
|
||||
{"channel_index"sv, &channel_index},
|
||||
{"sort_index"sv, &sort_index},
|
||||
{"filter"sv, &filter},
|
||||
{"log"sv, &logging},
|
||||
{"name"sv, &name_enable},
|
||||
}};
|
||||
|
||||
uint8_t console_color{0};
|
||||
uint32_t prev_value{0};
|
||||
uint8_t channel_number = 37;
|
||||
bool auto_channel = false;
|
||||
|
||||
int16_t timer_count{0};
|
||||
int16_t timer_period{6}; // 100ms
|
||||
|
||||
std::string filterBuffer{};
|
||||
std::string filter{};
|
||||
std::string listFileBuffer{};
|
||||
std::string headerStr = "Timestamp, MAC Address, Name, Packet Type, Data, Hits, dB, Channel";
|
||||
uint16_t maxLineLength = 140;
|
||||
@@ -292,7 +309,6 @@ class BLERxView : public View {
|
||||
"Tx"};
|
||||
|
||||
std::string str_log{""};
|
||||
bool logging{false};
|
||||
|
||||
std::unique_ptr<BLELogger> logger{};
|
||||
|
||||
@@ -313,6 +329,12 @@ class BLERxView : public View {
|
||||
const auto message = static_cast<const BLEPacketMessage*>(p);
|
||||
this->on_data(message->packet);
|
||||
}};
|
||||
|
||||
MessageHandlerRegistration message_handler_frame_sync{
|
||||
Message::ID::DisplayFrameSync,
|
||||
[this](const Message* const) {
|
||||
this->on_timer();
|
||||
}};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -67,7 +67,7 @@ std::vector<std::string> splitIntoStrings(const char* input) {
|
||||
|
||||
while (start < length) {
|
||||
int remaining = length - start;
|
||||
int chunkSize = (remaining > 30) ? 30 : remaining;
|
||||
int chunkSize = (remaining > 29) ? 29 : remaining;
|
||||
result.push_back(std::string(input + start, chunkSize));
|
||||
start += chunkSize;
|
||||
}
|
||||
@@ -167,16 +167,8 @@ void BLETxView::toggle() {
|
||||
}
|
||||
|
||||
void BLETxView::start() {
|
||||
int randomChannel = channel_number;
|
||||
|
||||
if (auto_channel) {
|
||||
int min = 37;
|
||||
int max = 39;
|
||||
|
||||
randomChannel = min + std::rand() % (max - min + 1);
|
||||
|
||||
field_frequency.set_value(get_freq_by_channel_number(randomChannel));
|
||||
}
|
||||
baseband::run_image(portapack::spi_flash::image_tag_btle_tx);
|
||||
transmitter_model.enable();
|
||||
|
||||
// Generate new random Mac Address.
|
||||
generateRandomMacAddress(randomMac);
|
||||
@@ -192,27 +184,65 @@ void BLETxView::start() {
|
||||
return;
|
||||
}
|
||||
|
||||
transmitter_model.enable();
|
||||
button_play.set_bitmap(&bitmap_stop);
|
||||
is_running = true;
|
||||
}
|
||||
|
||||
// Send next packet.
|
||||
progressbar.set_max(packets[current_packet].packet_count);
|
||||
baseband::set_btletx(randomChannel, random_mac ? randomMac : packets[current_packet].macAddress, packets[current_packet].advertisementData, pduType);
|
||||
char advertisementData[63] = {0};
|
||||
strcpy(advertisementData, packets[current_packet].advertisementData);
|
||||
|
||||
is_sending = true;
|
||||
// TODO: Make this a checkbox.
|
||||
if (!markedBytes.empty()) {
|
||||
for (size_t i = 0; i < strlen(advertisementData); i++) {
|
||||
bool found = false;
|
||||
|
||||
if ((packet_counter % 10) == 0) {
|
||||
text_packets_sent.set(to_string_dec_uint(packet_counter));
|
||||
auto it = std::find(markedBytes.begin(), markedBytes.end(), i);
|
||||
|
||||
if (it != markedBytes.end()) {
|
||||
found = true;
|
||||
}
|
||||
|
||||
if (found) {
|
||||
uint8_t hexDigit;
|
||||
switch (marked_data_sequence.selected_index_value()) {
|
||||
case 0:
|
||||
hexDigit = marked_counter++;
|
||||
break;
|
||||
case 1:
|
||||
hexDigit = marked_counter--;
|
||||
break;
|
||||
case 2: {
|
||||
uint8_t min = 0x00;
|
||||
uint8_t max = 0x0F;
|
||||
|
||||
hexDigit = min + std::rand() % (max - min + 1);
|
||||
} break;
|
||||
default:
|
||||
hexDigit = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
advertisementData[i] = uint_to_char(hexDigit, 16);
|
||||
|
||||
// Bounding to Hex.
|
||||
if (marked_counter == 16) {
|
||||
marked_counter = 0;
|
||||
} else if (marked_counter == 255) {
|
||||
marked_counter = 15;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
packet_counter--;
|
||||
progressbar.set_value(packets[current_packet].packet_count - packet_counter);
|
||||
// Setup next packet configuration.
|
||||
progressbar.set_max(packets[current_packet].packet_count);
|
||||
baseband::set_btletx(channel_number, random_mac ? randomMac : packets[current_packet].macAddress, advertisementData, pduType);
|
||||
}
|
||||
|
||||
void BLETxView::stop() {
|
||||
transmitter_model.disable();
|
||||
baseband::shutdown();
|
||||
|
||||
progressbar.set_value(0);
|
||||
button_play.set_bitmap(&bitmap_play);
|
||||
check_loop.set_value(false);
|
||||
@@ -222,47 +252,72 @@ void BLETxView::stop() {
|
||||
is_running = false;
|
||||
}
|
||||
|
||||
void BLETxView::reset() {
|
||||
transmitter_model.disable();
|
||||
baseband::shutdown();
|
||||
|
||||
start();
|
||||
}
|
||||
|
||||
// called each 1/60th of second, so 6 = 100ms
|
||||
void BLETxView::on_timer() {
|
||||
if (++mscounter == timer_period) {
|
||||
mscounter = 0;
|
||||
if (++timer_count == timer_period) {
|
||||
timer_count = 0;
|
||||
|
||||
if (is_active() && !is_sending) {
|
||||
if (is_active()) {
|
||||
// Reached end of current packet repeats.
|
||||
if (packet_counter == 0) {
|
||||
// Done sending all packets.
|
||||
if (current_packet == (num_packets - 1)) {
|
||||
current_packet = 0;
|
||||
|
||||
// If looping, restart from beginning.
|
||||
if (check_loop.value()) {
|
||||
update_current_packet(packets[current_packet], 0);
|
||||
start();
|
||||
update_current_packet(packets[current_packet], current_packet);
|
||||
reset();
|
||||
} else {
|
||||
stop();
|
||||
}
|
||||
} else {
|
||||
current_packet++;
|
||||
update_current_packet(packets[current_packet], current_packet);
|
||||
start();
|
||||
reset();
|
||||
}
|
||||
} else {
|
||||
start();
|
||||
reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (++auto_channel_counter == auto_channel_period) {
|
||||
auto_channel_counter = 0;
|
||||
|
||||
if (auto_channel) {
|
||||
int min = 37;
|
||||
int max = 39;
|
||||
|
||||
channel_number = min + std::rand() % (max - min + 1);
|
||||
|
||||
field_frequency.set_value(get_freq_by_channel_number(channel_number));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BLETxView::on_tx_progress(const bool done) {
|
||||
if (done) {
|
||||
if (is_active()) {
|
||||
is_sending = false;
|
||||
if ((packet_counter % 10) == 0) {
|
||||
text_packets_sent.set(to_string_dec_uint(packet_counter));
|
||||
}
|
||||
|
||||
packet_counter--;
|
||||
progressbar.set_value(packets[current_packet].packet_count - packet_counter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BLETxView::BLETxView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
baseband::run_image(portapack::spi_flash::image_tag_btle_tx);
|
||||
|
||||
add_children({&button_open,
|
||||
&text_filename,
|
||||
&progressbar,
|
||||
@@ -275,6 +330,8 @@ BLETxView::BLETxView(NavigationView& nav)
|
||||
&options_speed,
|
||||
&options_channel,
|
||||
&options_adv_type,
|
||||
&label_marked_data,
|
||||
&marked_data_sequence,
|
||||
&label_packet_index,
|
||||
&text_packet_index,
|
||||
&label_packets_sent,
|
||||
@@ -282,9 +339,10 @@ BLETxView::BLETxView(NavigationView& nav)
|
||||
&label_mac_address,
|
||||
&text_mac_address,
|
||||
&label_data_packet,
|
||||
&dataEditView,
|
||||
&button_clear_marked,
|
||||
&button_save_packet,
|
||||
&button_switch,
|
||||
&console});
|
||||
&button_switch});
|
||||
|
||||
field_frequency.set_step(0);
|
||||
|
||||
@@ -309,7 +367,7 @@ BLETxView::BLETxView(NavigationView& nav)
|
||||
|
||||
options_speed.on_change = [this](size_t, int32_t i) {
|
||||
timer_period = i;
|
||||
mscounter = 0;
|
||||
timer_count = 0;
|
||||
};
|
||||
|
||||
options_adv_type.on_change = [this](size_t, int32_t i) {
|
||||
@@ -348,6 +406,33 @@ BLETxView::BLETxView(NavigationView& nav)
|
||||
button_switch.on_select = [&nav](Button&) {
|
||||
nav.replace<BLERxView>();
|
||||
};
|
||||
|
||||
dataEditView.on_select = [this] {
|
||||
// Save last selected cursor.
|
||||
cursor_pos.line = dataEditView.line();
|
||||
cursor_pos.col = dataEditView.col();
|
||||
|
||||
// Reject setting newline at index 29.
|
||||
if (cursor_pos.col != 29) {
|
||||
uint16_t dataBytePos = (cursor_pos.line * 29) + cursor_pos.col;
|
||||
|
||||
auto it = std::find(markedBytes.begin(), markedBytes.end(), dataBytePos);
|
||||
|
||||
if (it != markedBytes.end()) {
|
||||
markedBytes.erase(it);
|
||||
} else {
|
||||
markedBytes.push_back(dataBytePos);
|
||||
}
|
||||
|
||||
dataEditView.cursor_mark_selected();
|
||||
}
|
||||
};
|
||||
|
||||
button_clear_marked.on_select = [this](Button&) {
|
||||
marked_counter = 0;
|
||||
markedBytes.clear();
|
||||
dataEditView.cursor_clear_marked();
|
||||
};
|
||||
}
|
||||
|
||||
BLETxView::BLETxView(
|
||||
@@ -423,8 +508,6 @@ void BLETxView::on_data(uint32_t value, bool is_data) {
|
||||
if (is_data) {
|
||||
str_console += (char)(value);
|
||||
}
|
||||
|
||||
console.write(str_console);
|
||||
}
|
||||
|
||||
void BLETxView::update_current_packet(BLETxPacket packet, uint32_t currentIndex) {
|
||||
@@ -438,23 +521,39 @@ void BLETxView::update_current_packet(BLETxPacket packet, uint32_t currentIndex)
|
||||
|
||||
text_mac_address.set(formattedMacAddress);
|
||||
|
||||
console.clear(true);
|
||||
|
||||
for (const std::string& str : strings) {
|
||||
console.writeln(str);
|
||||
}
|
||||
|
||||
packet_counter = packet.packet_count;
|
||||
current_packet = currentIndex;
|
||||
|
||||
dataFile.create(dataTempFilePath);
|
||||
|
||||
for (const std::string& str : strings) {
|
||||
dataFile.write(str.c_str(), str.size());
|
||||
dataFile.write("\n", 1);
|
||||
;
|
||||
}
|
||||
|
||||
dataFile.~File();
|
||||
|
||||
auto result = FileWrapper::open(dataTempFilePath);
|
||||
|
||||
if (!result)
|
||||
return;
|
||||
|
||||
dataFileWrapper = *std::move(result);
|
||||
|
||||
dataEditView.set_font_zoom(true);
|
||||
dataEditView.set_file(*dataFileWrapper);
|
||||
dataEditView.redraw(true, true);
|
||||
}
|
||||
|
||||
void BLETxView::set_parent_rect(const Rect new_parent_rect) {
|
||||
View::set_parent_rect(new_parent_rect);
|
||||
const Rect content_rect{0, header_height, new_parent_rect.width(), new_parent_rect.height() - header_height - switch_button_height};
|
||||
console.set_parent_rect(content_rect);
|
||||
dataEditView.set_parent_rect(content_rect);
|
||||
}
|
||||
|
||||
BLETxView::~BLETxView() {
|
||||
delete_file(dataTempFilePath);
|
||||
transmitter_model.disable();
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_transmitter.hpp"
|
||||
#include "ui_text_editor.hpp"
|
||||
#include "ui_freq_field.hpp"
|
||||
#include "ui_record_view.hpp"
|
||||
#include "app_settings.hpp"
|
||||
@@ -108,6 +109,7 @@ class BLETxView : public View {
|
||||
void toggle();
|
||||
void start();
|
||||
void stop();
|
||||
void reset();
|
||||
void handle_replay_thread_done(const uint32_t return_code);
|
||||
void file_error();
|
||||
bool saveFile(const std::filesystem::path& path);
|
||||
@@ -120,6 +122,7 @@ class BLETxView : public View {
|
||||
void on_file_changed(const std::filesystem::path& new_file_path);
|
||||
void on_save_file(const std::string value);
|
||||
void on_tx_progress(const bool done);
|
||||
void on_random_data_change(std::string value);
|
||||
void update_current_packet(BLETxPacket packet, uint32_t currentIndex);
|
||||
|
||||
NavigationView& nav_;
|
||||
@@ -129,7 +132,7 @@ class BLETxView : public View {
|
||||
4'000'000 /* sampling rate */
|
||||
};
|
||||
app_settings::SettingsManager settings_{
|
||||
"ble_tx_app", app_settings::Mode::TX};
|
||||
"tx_ble", app_settings::Mode::TX};
|
||||
|
||||
uint8_t console_color{0};
|
||||
uint32_t prev_value{0};
|
||||
@@ -142,9 +145,12 @@ class BLETxView : public View {
|
||||
char randomMac[13] = "010203040506";
|
||||
|
||||
bool is_running = false;
|
||||
bool is_sending = false;
|
||||
uint64_t timer_count{0};
|
||||
|
||||
int16_t timer_count{0};
|
||||
int16_t timer_period{1};
|
||||
int16_t auto_channel_counter = 0;
|
||||
int16_t auto_channel_period{6};
|
||||
|
||||
bool repeatLoop = false;
|
||||
uint32_t packet_counter{0};
|
||||
uint32_t num_packets{0};
|
||||
@@ -152,19 +158,30 @@ class BLETxView : public View {
|
||||
bool random_mac = false;
|
||||
bool file_override = false;
|
||||
|
||||
typedef struct {
|
||||
uint16_t line;
|
||||
uint16_t col;
|
||||
} CursorPos;
|
||||
|
||||
std::unique_ptr<FileWrapper> dataFileWrapper{};
|
||||
File dataFile{};
|
||||
std::filesystem::path dataTempFilePath{u"BLETX/dataFileTemp.TXT"};
|
||||
std::vector<uint16_t> markedBytes{};
|
||||
CursorPos cursor_pos{};
|
||||
uint8_t marked_counter = 0;
|
||||
|
||||
static constexpr uint8_t mac_address_size_str{12};
|
||||
static constexpr uint8_t max_packet_size_str{62};
|
||||
static constexpr uint8_t max_packet_repeat_str{10};
|
||||
static constexpr uint32_t max_packet_repeat_count{UINT32_MAX};
|
||||
static constexpr uint32_t max_num_packets{32};
|
||||
int16_t mscounter = 0;
|
||||
|
||||
BLETxPacket packets[max_num_packets];
|
||||
|
||||
PKT_TYPE pduType = {PKT_TYPE_DISCOVERY};
|
||||
|
||||
static constexpr auto header_height = 9 * 16;
|
||||
static constexpr auto switch_button_height = 3 * 16;
|
||||
static constexpr auto header_height = 10 * 16;
|
||||
static constexpr auto switch_button_height = 6 * 16;
|
||||
|
||||
Button button_open{
|
||||
{0 * 8, 0 * 16, 10 * 8, 2 * 16},
|
||||
@@ -180,7 +197,7 @@ class BLETxView : public View {
|
||||
Checkbox check_rand_mac{
|
||||
{21 * 8, 1 * 16},
|
||||
6,
|
||||
"Random",
|
||||
"?? Mac",
|
||||
true};
|
||||
|
||||
TxFrequencyField field_frequency{
|
||||
@@ -235,32 +252,49 @@ class BLETxView : public View {
|
||||
{"SCAN_RSP", PKT_TYPE_SCAN_RSP},
|
||||
{"CONNECT_REQ", PKT_TYPE_CONNECT_REQ}}};
|
||||
|
||||
Labels label_marked_data{
|
||||
{{0 * 8, 4 * 16}, "Marked Data:", Color::light_grey()}};
|
||||
|
||||
OptionsField marked_data_sequence{
|
||||
{12 * 8, 8 * 8},
|
||||
8,
|
||||
{{"Ascend", 0},
|
||||
{"Descend", 1},
|
||||
{"Random", 2}}};
|
||||
|
||||
Labels label_packet_index{
|
||||
{{0 * 8, 10 * 8}, "Packet Index:", Color::light_grey()}};
|
||||
{{0 * 8, 12 * 8}, "Packet Index:", Color::light_grey()}};
|
||||
|
||||
Text text_packet_index{
|
||||
{13 * 8, 5 * 16, 12 * 8, 16},
|
||||
"-"};
|
||||
|
||||
Labels label_packets_sent{
|
||||
{{0 * 8, 12 * 8}, "Packets Left:", Color::light_grey()}};
|
||||
|
||||
Text text_packets_sent{
|
||||
{13 * 8, 6 * 16, 12 * 8, 16},
|
||||
"-"};
|
||||
|
||||
Labels label_packets_sent{
|
||||
{{0 * 8, 14 * 8}, "Repeat Count:", Color::light_grey()}};
|
||||
|
||||
Text text_packets_sent{
|
||||
{13 * 8, 7 * 16, 12 * 8, 16},
|
||||
"-"};
|
||||
|
||||
Labels label_mac_address{
|
||||
{{0 * 8, 14 * 8}, "Mac Address:", Color::light_grey()}};
|
||||
{{0 * 8, 16 * 8}, "Mac Address:", Color::light_grey()}};
|
||||
|
||||
Text text_mac_address{
|
||||
{12 * 8, 7 * 16, 20 * 8, 16},
|
||||
{12 * 8, 8 * 16, 20 * 8, 16},
|
||||
"-"};
|
||||
|
||||
Labels label_data_packet{
|
||||
{{0 * 8, 16 * 8}, "Packet Data:", Color::light_grey()}};
|
||||
{{0 * 8, 9 * 16}, "Packet Data:", Color::light_grey()}};
|
||||
|
||||
Console console{
|
||||
{0, 8 * 16, 240, 240}};
|
||||
{0, 9 * 18, 240, 240}};
|
||||
|
||||
TextViewer dataEditView{
|
||||
{0, 9 * 18, 240, 240}};
|
||||
|
||||
Button button_clear_marked{
|
||||
{1 * 8, 14 * 16, 13 * 8, 3 * 8},
|
||||
"Clear Marked"};
|
||||
|
||||
Button button_save_packet{
|
||||
{1 * 8, 16 * 16, 13 * 8, 2 * 16},
|
||||
|
||||
@@ -65,9 +65,13 @@ std::string commodity_type(CommodityType value) {
|
||||
|
||||
} /* namespace ert */
|
||||
|
||||
void ERTLogger::on_packet(const ert::Packet& packet) {
|
||||
void ERTLogger::on_packet(const ert::Packet& packet, const uint32_t target_frequency) {
|
||||
const auto formatted = packet.symbols_formatted();
|
||||
std::string entry = ert::format::type(packet.type()) + " " + formatted.data + "/" + formatted.errors;
|
||||
|
||||
// TODO: function doesn't take uint64_t, so when >= 1<<32, weirdness will ensue!
|
||||
const auto target_frequency_str = to_string_dec_uint(target_frequency, 10);
|
||||
|
||||
std::string entry = target_frequency_str + " " + ert::format::type(packet.type()) + " " + formatted.data + "/" + formatted.errors;
|
||||
log_file.write_entry(packet.received_at(), entry);
|
||||
}
|
||||
|
||||
@@ -99,10 +103,12 @@ void RecentEntriesTable<ERTRecentEntries>::draw(
|
||||
painter.draw_string(target_rect.location(), style, line);
|
||||
}
|
||||
|
||||
ERTAppView::ERTAppView(NavigationView&) {
|
||||
ERTAppView::ERTAppView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
baseband::run_image(portapack::spi_flash::image_tag_ert);
|
||||
|
||||
add_children({
|
||||
&field_frequency,
|
||||
&field_rf_amp,
|
||||
&field_lna,
|
||||
&field_vga,
|
||||
@@ -110,6 +116,8 @@ ERTAppView::ERTAppView(NavigationView&) {
|
||||
&recent_entries_view,
|
||||
});
|
||||
|
||||
field_frequency.set_step(1000000);
|
||||
|
||||
receiver_model.enable();
|
||||
|
||||
logger = std::make_unique<ERTLogger>();
|
||||
@@ -134,7 +142,7 @@ void ERTAppView::set_parent_rect(const Rect new_parent_rect) {
|
||||
|
||||
void ERTAppView::on_packet(const ert::Packet& packet) {
|
||||
if (logger) {
|
||||
logger->on_packet(packet);
|
||||
logger->on_packet(packet, receiver_model.target_frequency());
|
||||
}
|
||||
|
||||
if (packet.crc_ok()) {
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_freq_field.hpp"
|
||||
#include "ui_rssi.hpp"
|
||||
#include "ui_channel.hpp"
|
||||
|
||||
@@ -95,7 +96,7 @@ class ERTLogger {
|
||||
return log_file.append(filename);
|
||||
}
|
||||
|
||||
void on_packet(const ert::Packet& packet);
|
||||
void on_packet(const ert::Packet& packet, const uint32_t target_frequency);
|
||||
|
||||
private:
|
||||
LogFile log_file{};
|
||||
@@ -126,6 +127,7 @@ class ERTAppView : public View {
|
||||
ERTRecentEntries recent{};
|
||||
std::unique_ptr<ERTLogger> logger{};
|
||||
|
||||
NavigationView& nav_;
|
||||
RxRadioState radio_state_{
|
||||
911600000 /* frequency */,
|
||||
2500000 /* bandwidth */,
|
||||
@@ -143,6 +145,10 @@ class ERTAppView : public View {
|
||||
|
||||
static constexpr auto header_height = 1 * 16;
|
||||
|
||||
RxFrequencyField field_frequency{
|
||||
{5 * 8, 0 * 16},
|
||||
nav_};
|
||||
|
||||
RFAmpField field_rf_amp{
|
||||
{13 * 8, 0 * 16}};
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ void AboutView::update() {
|
||||
break;
|
||||
case 2:
|
||||
console.writeln("NotherNgineer,zxkmm,u-foka");
|
||||
console.writeln("Netro");
|
||||
console.writeln("Netro,HTotoo");
|
||||
console.writeln("");
|
||||
break;
|
||||
|
||||
|
||||
@@ -56,13 +56,23 @@ static msg_t ookthread_fn(void* arg) {
|
||||
v = (symbol < 2) ? 1 : 0; // TX on for dot or dash, off for pause
|
||||
delay = morse_symbols[symbol];
|
||||
|
||||
gpio_og_tx.write(v);
|
||||
if (hackrf_r9) { // r9 has a common PIN 54 for MODE CONTROL TX / RX, <=r6 has two independent MODE CONTROL pins (TX and RX)
|
||||
gpio_r9_rx.write(!v); // in hackrf r9 opposite logic "0" means tx , "1" rx = no tx)
|
||||
} else {
|
||||
gpio_og_tx.write(v); // in hackrf <=r6, "1" means tx , "0" no tx.
|
||||
}
|
||||
|
||||
arg_c->on_tx_progress(i, false);
|
||||
|
||||
chThdSleepMilliseconds(delay * arg_c->time_unit_ms);
|
||||
}
|
||||
|
||||
gpio_og_tx.write(0); // Ensure TX is off
|
||||
if (hackrf_r9) { // r9 has a common PIN 54 for MODE CONTROL TX / RX, <=r6 has two independent MODE CONTROL pins (TX and RX)
|
||||
gpio_r9_rx.write(1); // Hackrf_r9 , common pin, rx="1" (we ensure TX is off) / tx="0"
|
||||
} else {
|
||||
gpio_og_tx.write(0); // Hackrf <=r6 independent TX / RX pins ,now touching the tx pin ==> Ensure TX is off
|
||||
}
|
||||
|
||||
arg_c->on_tx_progress(0, true);
|
||||
chThdExit(0);
|
||||
|
||||
@@ -121,9 +131,9 @@ bool MorseView::start_tx() {
|
||||
transmitter_model.set_baseband_bandwidth(1'750'000); // Min TX LPF .already tested in FM morse max tone 9,999k , max dev 150khz
|
||||
transmitter_model.enable();
|
||||
|
||||
if (modulation == CW) {
|
||||
if (mode_cw) {
|
||||
ookthread = chThdCreateStatic(ookthread_wa, sizeof(ookthread_wa), NORMALPRIO + 10, ookthread_fn, this);
|
||||
} else if (modulation == FM) {
|
||||
} else {
|
||||
baseband::set_tones_config(transmitter_model.channel_bandwidth(), 0, symbol_count, false, false);
|
||||
}
|
||||
|
||||
@@ -133,8 +143,8 @@ bool MorseView::start_tx() {
|
||||
void MorseView::update_tx_duration() {
|
||||
uint32_t duration_ms;
|
||||
|
||||
time_unit_ms = 1200 / field_speed.value();
|
||||
symbol_count = morse_encode(message, time_unit_ms, field_tone.value(), &time_units);
|
||||
time_unit_ms = 1200 / speed;
|
||||
symbol_count = morse_encode(message, time_unit_ms, tone, &time_units);
|
||||
|
||||
if (symbol_count) {
|
||||
duration_ms = time_units * time_unit_ms;
|
||||
@@ -175,7 +185,7 @@ void MorseView::on_loop_progress(const uint32_t progress, const bool done) {
|
||||
}
|
||||
|
||||
void MorseView::set_foxhunt(size_t i) {
|
||||
message = foxhunt_codes[i];
|
||||
message = foxhunt_codes[i - 1];
|
||||
buffer = message.c_str();
|
||||
text_message.set(message);
|
||||
update_tx_duration();
|
||||
@@ -188,7 +198,7 @@ MorseView::MorseView(
|
||||
|
||||
add_children({&labels,
|
||||
&checkbox_foxhunt,
|
||||
&options_foxhunt,
|
||||
&field_foxhunt,
|
||||
&field_speed,
|
||||
&field_tone,
|
||||
&options_modulation,
|
||||
@@ -200,36 +210,46 @@ MorseView::MorseView(
|
||||
&tx_view});
|
||||
|
||||
// Default settings
|
||||
field_speed.set_value(15); // 15wps
|
||||
field_tone.set_value(700); // 700Hz FM tone
|
||||
options_modulation.set_selected_index(0); // CW mode
|
||||
options_loop.set_selected_index(0); // Off
|
||||
field_speed.set_value(speed);
|
||||
field_tone.set_value(tone);
|
||||
options_modulation.set_by_value(mode_cw);
|
||||
options_loop.set_by_value(loop);
|
||||
checkbox_foxhunt.set_value(foxhunt_mode);
|
||||
field_foxhunt.set_value(foxhunt_code);
|
||||
|
||||
checkbox_foxhunt.on_select = [this](Checkbox&, bool value) {
|
||||
foxhunt_mode = value;
|
||||
|
||||
if (foxhunt_mode)
|
||||
set_foxhunt(options_foxhunt.selected_index_value());
|
||||
set_foxhunt(foxhunt_code);
|
||||
};
|
||||
|
||||
options_foxhunt.on_change = [this](size_t i, int32_t) {
|
||||
field_foxhunt.on_change = [this](int32_t value) {
|
||||
foxhunt_code = value;
|
||||
|
||||
if (foxhunt_mode)
|
||||
set_foxhunt(i);
|
||||
set_foxhunt(foxhunt_code);
|
||||
};
|
||||
|
||||
options_modulation.on_change = [this](size_t i, int32_t) {
|
||||
modulation = (modulation_t)i;
|
||||
};
|
||||
|
||||
options_loop.on_change = [this](size_t i, uint32_t n) {
|
||||
options_modulation.on_change = [this](size_t i, int32_t value) {
|
||||
(void)i; // avoid unused warning
|
||||
loop = n;
|
||||
mode_cw = (bool)value;
|
||||
};
|
||||
|
||||
field_speed.on_change = [this](int32_t) {
|
||||
options_loop.on_change = [this](size_t i, uint32_t value) {
|
||||
(void)i; // avoid unused warning
|
||||
loop = value;
|
||||
};
|
||||
|
||||
field_speed.on_change = [this](int32_t value) {
|
||||
speed = value;
|
||||
update_tx_duration();
|
||||
};
|
||||
|
||||
field_tone.on_change = [this](int32_t value) {
|
||||
tone = value;
|
||||
};
|
||||
|
||||
button_message.on_select = [this, &nav](Button&) {
|
||||
this->on_set_text(nav);
|
||||
};
|
||||
|
||||
@@ -65,7 +65,6 @@ class MorseView : public View {
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
std::string buffer{"PORTAPACK"};
|
||||
std::string message{};
|
||||
uint32_t time_units{0};
|
||||
|
||||
@@ -74,14 +73,25 @@ class MorseView : public View {
|
||||
1750000 /* bandwidth */,
|
||||
1536000 /* sampling rate */
|
||||
};
|
||||
app_settings::SettingsManager settings_{
|
||||
"tx_morse", app_settings::Mode::TX};
|
||||
|
||||
enum modulation_t {
|
||||
CW = 0,
|
||||
FM = 1
|
||||
};
|
||||
modulation_t modulation{CW};
|
||||
std::string buffer{"PORTAPACK"};
|
||||
bool mode_cw{true};
|
||||
bool foxhunt_mode{false};
|
||||
uint32_t foxhunt_code{1};
|
||||
uint32_t speed{15};
|
||||
uint32_t tone{700};
|
||||
app_settings::SettingsManager settings_{
|
||||
"tx_morse",
|
||||
app_settings::Mode::TX,
|
||||
{
|
||||
{"message"sv, &buffer},
|
||||
{"foxhunt"sv, &foxhunt_mode},
|
||||
{"foxhunt_code"sv, &foxhunt_code},
|
||||
{"speed"sv, &speed},
|
||||
{"tone"sv, &tone},
|
||||
{"mode_cw"sv, &mode_cw},
|
||||
{"loop"sv, &loop},
|
||||
}};
|
||||
|
||||
bool start_tx();
|
||||
void update_tx_duration();
|
||||
@@ -90,7 +100,6 @@ class MorseView : public View {
|
||||
|
||||
Thread* ookthread{nullptr};
|
||||
Thread* loopthread{nullptr};
|
||||
bool foxhunt_mode{false};
|
||||
bool run{false};
|
||||
|
||||
Labels labels{
|
||||
@@ -104,20 +113,12 @@ class MorseView : public View {
|
||||
{4 * 8, 16},
|
||||
8,
|
||||
"Foxhunt:"};
|
||||
OptionsField options_foxhunt{
|
||||
NumberField field_foxhunt{
|
||||
{17 * 8, 16 + 4},
|
||||
7,
|
||||
{{"1 (MOE)", 0},
|
||||
{"2 (MOI)", 1},
|
||||
{"3 (MOS)", 2},
|
||||
{"4 (MOH)", 3},
|
||||
{"5 (MO5)", 4},
|
||||
{"6 (MON)", 5},
|
||||
{"7 (MOD)", 6},
|
||||
{"8 (MOB)", 7},
|
||||
{"9 (MO6)", 8},
|
||||
{"X (MO) ", 9},
|
||||
{"T (S) ", 10}}};
|
||||
2,
|
||||
{1, 11},
|
||||
1,
|
||||
' '};
|
||||
|
||||
NumberField field_speed{
|
||||
{10 * 8, 6 * 8},
|
||||
@@ -136,8 +137,8 @@ class MorseView : public View {
|
||||
OptionsField options_modulation{
|
||||
{15 * 8, 10 * 8},
|
||||
2,
|
||||
{{"CW", 0},
|
||||
{"FM", 1}}};
|
||||
{{"CW", true},
|
||||
{"FM", false}}};
|
||||
|
||||
OptionsField options_loop{
|
||||
{9 * 8, 12 * 8},
|
||||
|
||||
@@ -72,6 +72,11 @@ void TextViewer::paint(Painter& painter) {
|
||||
paint_state_.redraw_text = false;
|
||||
}
|
||||
|
||||
if (paint_state_.redraw_marked) {
|
||||
paint_marked(painter);
|
||||
paint_state_.redraw_marked = false;
|
||||
}
|
||||
|
||||
paint_cursor(painter);
|
||||
}
|
||||
|
||||
@@ -118,8 +123,9 @@ bool TextViewer::on_encoder(EncoderEvent delta) {
|
||||
return updated;
|
||||
}
|
||||
|
||||
void TextViewer::redraw(bool redraw_text) {
|
||||
void TextViewer::redraw(bool redraw_text, bool redraw_marked) {
|
||||
paint_state_.redraw_text = redraw_text;
|
||||
paint_state_.redraw_marked = redraw_marked;
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
@@ -140,6 +146,32 @@ void TextViewer::cursor_end() {
|
||||
redraw();
|
||||
}
|
||||
|
||||
void TextViewer::cursor_set(uint16_t line, uint16_t col) {
|
||||
cursor_.line = line;
|
||||
cursor_.col = col;
|
||||
}
|
||||
|
||||
void TextViewer::cursor_mark_selected() {
|
||||
LineColPair newMarker = std::make_pair(cursor_.line, cursor_.col);
|
||||
auto it = std::find(lineColPair.begin(), lineColPair.end(), newMarker);
|
||||
|
||||
if (it != lineColPair.end()) {
|
||||
lineColPair.erase(it);
|
||||
} else {
|
||||
lineColPair.push_back(newMarker);
|
||||
}
|
||||
|
||||
// Mark pending change.
|
||||
cursor_.mark_change = false;
|
||||
|
||||
redraw();
|
||||
}
|
||||
|
||||
void TextViewer::cursor_clear_marked() {
|
||||
lineColPair.clear();
|
||||
redraw(true, true);
|
||||
}
|
||||
|
||||
uint16_t TextViewer::line_length() {
|
||||
return file_->line_length(cursor_.line);
|
||||
}
|
||||
@@ -247,12 +279,52 @@ void TextViewer::paint_cursor(Painter& painter) {
|
||||
};
|
||||
|
||||
if (paint_state_.line != UINT32_MAX) // only XOR old cursor if it still appears on the screen
|
||||
xor_cursor(paint_state_.line, paint_state_.col);
|
||||
{
|
||||
// Only reset previous cursor if we aren't marking.
|
||||
if (paint_state_.mark_change) {
|
||||
xor_cursor(paint_state_.line, paint_state_.col);
|
||||
}
|
||||
}
|
||||
|
||||
xor_cursor(cursor_.line, cursor_.col);
|
||||
|
||||
paint_state_.line = cursor_.line;
|
||||
paint_state_.col = cursor_.col;
|
||||
paint_state_.mark_change = cursor_.mark_change;
|
||||
|
||||
// Reset marking and wait for new change.
|
||||
cursor_.mark_change = true;
|
||||
}
|
||||
|
||||
void TextViewer::paint_marked(Painter& painter) {
|
||||
auto xor_cursor = [this, &painter](int32_t line, uint16_t col) {
|
||||
int cursor_width = char_width + 1;
|
||||
int x = (col - paint_state_.first_col) * char_width - 1;
|
||||
if (x < 0) { // cursor is one pixel narrower when in left column
|
||||
cursor_width--;
|
||||
x = 0;
|
||||
}
|
||||
int y = screen_rect().top() + (line - paint_state_.first_line) * char_height;
|
||||
|
||||
// Converting one row at a time to reduce buffer size
|
||||
auto pbuf8 = cursor_.pixel_buffer8;
|
||||
auto pbuf = cursor_.pixel_buffer;
|
||||
for (auto col = 0; col < char_height; col++) {
|
||||
// Annoyingly, read_pixels uses a 24-bit pixel format vs draw_pixels which uses 16-bit
|
||||
portapack::display.read_pixels({x, y + col, cursor_width, 1}, pbuf8, cursor_width);
|
||||
for (auto i = 0; i < cursor_width; i++)
|
||||
pbuf[i] = Color(pbuf8[i].r, pbuf8[i].g, pbuf8[i].b).v ^ 0xFFFF;
|
||||
portapack::display.draw_pixels({x, y + col, cursor_width, 1}, pbuf, cursor_width);
|
||||
}
|
||||
};
|
||||
|
||||
auto it = lineColPair.begin();
|
||||
|
||||
while (it != lineColPair.end()) {
|
||||
LineColPair entry = (LineColPair)*it;
|
||||
xor_cursor(entry.first, entry.second);
|
||||
it++;
|
||||
}
|
||||
}
|
||||
|
||||
void TextViewer::reset_file(FileWrapper* file) {
|
||||
|
||||
@@ -59,7 +59,7 @@ class TextViewer : public Widget {
|
||||
bool on_key(KeyEvent key) override;
|
||||
bool on_encoder(EncoderEvent delta) override;
|
||||
|
||||
void redraw(bool redraw_text = false);
|
||||
void redraw(bool redraw_text = false, bool redraw_marked = false);
|
||||
|
||||
void set_file(FileWrapper& file) { reset_file(&file); }
|
||||
void clear_file() { reset_file(); }
|
||||
@@ -71,6 +71,12 @@ class TextViewer : public Widget {
|
||||
|
||||
void cursor_home();
|
||||
void cursor_end();
|
||||
void cursor_set(uint16_t line, uint16_t col);
|
||||
void cursor_mark_selected();
|
||||
void cursor_clear_marked();
|
||||
|
||||
typedef std::pair<uint16_t, uint16_t> LineColPair;
|
||||
std::vector<LineColPair> lineColPair{};
|
||||
|
||||
// Gets the length of the current line.
|
||||
uint16_t line_length();
|
||||
@@ -97,6 +103,7 @@ class TextViewer : public Widget {
|
||||
|
||||
void paint_text(Painter& painter, uint32_t line, uint16_t col);
|
||||
void paint_cursor(Painter& painter);
|
||||
void paint_marked(Painter& painter);
|
||||
|
||||
void reset_file(FileWrapper* file = nullptr);
|
||||
|
||||
@@ -111,6 +118,8 @@ class TextViewer : public Widget {
|
||||
uint32_t first_line{};
|
||||
uint16_t first_col{};
|
||||
bool redraw_text{true};
|
||||
bool redraw_marked{false};
|
||||
bool mark_change{true};
|
||||
} paint_state_{};
|
||||
|
||||
struct {
|
||||
@@ -121,6 +130,8 @@ class TextViewer : public Widget {
|
||||
// Pixel buffer used for cursor XOR'ing - Max cursor width = Max char width + 1
|
||||
ColorRGB888 pixel_buffer8[ui::char_width + 1]{};
|
||||
Color pixel_buffer[ui::char_width + 1]{};
|
||||
|
||||
bool mark_change{true};
|
||||
} cursor_{};
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2017 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 "ui_weatherstation.hpp"
|
||||
#include "modems.hpp"
|
||||
#include "audio.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
using namespace ui;
|
||||
|
||||
namespace ui {
|
||||
|
||||
void WeatherRecentEntryDetailView::update_data() {
|
||||
// set text elements
|
||||
text_type.set(WeatherView::getWeatherSensorTypeName((FPROTO_WEATHER_SENSOR)entry_.sensorType));
|
||||
text_id.set("0x" + to_string_hex(entry_.id));
|
||||
text_temp.set(weather_units_fahr ? to_string_decimal((entry_.temp * 9 / 5) + 32, 1) + STR_DEGREES_F : to_string_decimal(entry_.temp, 2) + STR_DEGREES_C);
|
||||
text_hum.set(to_string_dec_uint(entry_.humidity) + "%");
|
||||
text_ch.set(to_string_dec_uint(entry_.channel));
|
||||
text_batt.set(to_string_dec_uint(entry_.battery_low) + " " + ((entry_.battery_low == 0) ? "OK" : "LOW"));
|
||||
text_age.set(to_string_dec_uint(entry_.age) + " sec");
|
||||
}
|
||||
|
||||
WeatherRecentEntryDetailView::WeatherRecentEntryDetailView(NavigationView& nav, const WeatherRecentEntry& entry)
|
||||
: nav_{nav},
|
||||
entry_{entry} {
|
||||
add_children({&button_done,
|
||||
&text_type,
|
||||
&text_id,
|
||||
&text_temp,
|
||||
&text_hum,
|
||||
&text_ch,
|
||||
&text_batt,
|
||||
&text_age,
|
||||
&labels});
|
||||
|
||||
button_done.on_select = [&nav](const ui::Button&) {
|
||||
nav.pop();
|
||||
};
|
||||
update_data();
|
||||
}
|
||||
|
||||
void WeatherRecentEntryDetailView::focus() {
|
||||
button_done.focus();
|
||||
}
|
||||
|
||||
void WeatherView::focus() {
|
||||
field_frequency.focus();
|
||||
}
|
||||
|
||||
WeatherView::WeatherView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
add_children({&rssi,
|
||||
&field_rf_amp,
|
||||
&field_lna,
|
||||
&field_vga,
|
||||
&field_frequency,
|
||||
&options_temperature,
|
||||
&button_clear_list,
|
||||
&recent_entries_view});
|
||||
|
||||
baseband::run_image(portapack::spi_flash::image_tag_weather);
|
||||
|
||||
button_clear_list.on_select = [this](Button&) {
|
||||
recent.clear();
|
||||
recent_entries_view.set_dirty();
|
||||
};
|
||||
field_frequency.set_step(100000);
|
||||
|
||||
options_temperature.on_change = [this](size_t, int32_t i) {
|
||||
weather_units_fahr = (bool)i;
|
||||
recent_entries_view.set_dirty();
|
||||
};
|
||||
options_temperature.set_selected_index(weather_units_fahr, false);
|
||||
|
||||
const Rect content_rect{0, header_height, screen_width, screen_height - header_height};
|
||||
recent_entries_view.set_parent_rect(content_rect);
|
||||
recent_entries_view.on_select = [this](const WeatherRecentEntry& entry) {
|
||||
nav_.push<WeatherRecentEntryDetailView>(entry);
|
||||
};
|
||||
baseband::set_weather();
|
||||
receiver_model.enable();
|
||||
signal_token_tick_second = rtc_time::signal_tick_second += [this]() {
|
||||
on_tick_second();
|
||||
};
|
||||
}
|
||||
|
||||
void WeatherView::on_tick_second() {
|
||||
for (auto& entry : recent) {
|
||||
entry.inc_age(1);
|
||||
}
|
||||
recent_entries_view.set_dirty();
|
||||
}
|
||||
|
||||
void WeatherView::on_data(const WeatherDataMessage* data) {
|
||||
WeatherRecentEntry key{data->sensorType, data->id, data->temp, data->humidity, data->channel, data->battery_low};
|
||||
auto matching_recent = find(recent, key.key());
|
||||
if (matching_recent != std::end(recent)) {
|
||||
// Found within. Move to front of list, increment counter.
|
||||
(*matching_recent).reset_age();
|
||||
recent.push_front(*matching_recent);
|
||||
recent.erase(matching_recent);
|
||||
} else {
|
||||
recent.emplace_front(key);
|
||||
truncate_entries(recent, 64);
|
||||
}
|
||||
recent_entries_view.set_dirty();
|
||||
}
|
||||
|
||||
WeatherView::~WeatherView() {
|
||||
rtc_time::signal_tick_second -= signal_token_tick_second;
|
||||
receiver_model.disable();
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
const char* WeatherView::getWeatherSensorTypeName(FPROTO_WEATHER_SENSOR type) {
|
||||
switch (type) {
|
||||
case FPW_NexusTH:
|
||||
return "NexusTH";
|
||||
case FPW_Acurite592TXR:
|
||||
return "Acurite592TXR";
|
||||
case FPW_Acurite606TX:
|
||||
return "Acurite606TX";
|
||||
case FPW_Acurite609TX:
|
||||
return "Acurite609TX";
|
||||
case FPW_Ambient:
|
||||
return "Ambient";
|
||||
case FPW_AuriolAhfl:
|
||||
return "AuriolAhfl";
|
||||
case FPW_AuriolTH:
|
||||
return "AuriolTH";
|
||||
case FPW_GTWT02:
|
||||
return "GT-WT02";
|
||||
case FPW_GTWT03:
|
||||
return "GT-WT03";
|
||||
case FPW_INFACTORY:
|
||||
return "InFactory";
|
||||
case FPW_LACROSSETX:
|
||||
return "LaCrosse TX";
|
||||
case FPW_LACROSSETX141thbv2:
|
||||
return "LaCrosse TX141THBv2";
|
||||
case FPW_OREGON2:
|
||||
return "Oregon2";
|
||||
case FPW_OREGON3:
|
||||
return "Oregon3";
|
||||
case FPW_OREGONv1:
|
||||
return "OregonV1";
|
||||
case FPW_THERMOPROTX4:
|
||||
return "ThermoPro TX4";
|
||||
case FPW_TX_8300:
|
||||
return "TX 8300";
|
||||
case FPW_WENDOX_W6726:
|
||||
return "Wendox W6726";
|
||||
case FPW_Acurite986:
|
||||
return "Acurite986";
|
||||
|
||||
case FPW_Invalid:
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
std::string WeatherView::pad_string_with_spaces(int snakes) {
|
||||
std::string paddedStr(snakes, ' ');
|
||||
return paddedStr;
|
||||
}
|
||||
|
||||
template <>
|
||||
void RecentEntriesTable<ui::WeatherRecentEntries>::draw(
|
||||
const Entry& entry,
|
||||
const Rect& target_rect,
|
||||
Painter& painter,
|
||||
const Style& style) {
|
||||
std::string line{};
|
||||
line.reserve(30);
|
||||
|
||||
line = WeatherView::getWeatherSensorTypeName((FPROTO_WEATHER_SENSOR)entry.sensorType);
|
||||
if (line.length() < 10) {
|
||||
line += WeatherView::pad_string_with_spaces(10 - line.length());
|
||||
} else {
|
||||
line = truncate(line, 10);
|
||||
}
|
||||
|
||||
std::string temp = (weather_units_fahr ? to_string_decimal((entry.temp * 9 / 5) + 32, 1) : to_string_decimal(entry.temp, 1));
|
||||
std::string humStr = to_string_dec_uint(entry.humidity) + "%";
|
||||
std::string chStr = to_string_dec_uint(entry.channel);
|
||||
std::string ageStr = to_string_dec_uint(entry.age);
|
||||
|
||||
line += WeatherView::pad_string_with_spaces(6 - temp.length()) + temp;
|
||||
line += WeatherView::pad_string_with_spaces(5 - humStr.length()) + humStr;
|
||||
line += WeatherView::pad_string_with_spaces(4 - chStr.length()) + chStr;
|
||||
line += WeatherView::pad_string_with_spaces(4 - ageStr.length()) + ageStr;
|
||||
|
||||
line.resize(target_rect.width() / 8, ' ');
|
||||
painter.draw_string(target_rect.location(), style, line);
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2017 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.
|
||||
*/
|
||||
|
||||
#ifndef __UI_WEATHER_H__
|
||||
#define __UI_WEATHER_H__
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_freq_field.hpp"
|
||||
#include "app_settings.hpp"
|
||||
#include "radio_state.hpp"
|
||||
#include "utility.hpp"
|
||||
#include "recent_entries.hpp"
|
||||
|
||||
#include "../baseband/fprotos/weathertypes.hpp"
|
||||
using namespace ui;
|
||||
|
||||
namespace ui {
|
||||
|
||||
static bool weather_units_fahr{false};
|
||||
|
||||
struct WeatherRecentEntry {
|
||||
using Key = uint64_t;
|
||||
static constexpr Key invalid_key = 0x0fffffff; // todo calc the invalid all
|
||||
uint8_t sensorType = FPW_Invalid;
|
||||
uint32_t id = 0xFFFFFFFF;
|
||||
float temp = -273.0f;
|
||||
uint8_t humidity = 0xFF;
|
||||
uint8_t battery_low = 0xFF;
|
||||
uint8_t channel = 0xFF;
|
||||
uint16_t age = 0; // updated on each seconds, show how long the signal was last seen
|
||||
|
||||
WeatherRecentEntry() {}
|
||||
WeatherRecentEntry(
|
||||
uint8_t sensorType,
|
||||
uint32_t id,
|
||||
float temp,
|
||||
uint8_t humidity,
|
||||
uint8_t channel,
|
||||
uint8_t battery_low = 0xff)
|
||||
: sensorType{sensorType},
|
||||
id{id},
|
||||
temp{temp},
|
||||
humidity{humidity},
|
||||
battery_low{battery_low},
|
||||
channel{channel} {
|
||||
}
|
||||
Key key() const {
|
||||
return (((static_cast<uint64_t>(temp * 10) & 0xFFFF) << 48) ^ static_cast<uint64_t>(id) << 24) |
|
||||
(static_cast<uint64_t>(sensorType) & 0xFF) << 16 |
|
||||
(static_cast<uint64_t>(humidity) & 0xFF) << 8 |
|
||||
(static_cast<uint64_t>(battery_low) & 0xF) << 4 |
|
||||
(static_cast<uint64_t>(channel) & 0xF);
|
||||
}
|
||||
void inc_age(int delta) {
|
||||
if (UINT16_MAX - delta > age) age += delta;
|
||||
}
|
||||
void reset_age() {
|
||||
age = 0;
|
||||
}
|
||||
};
|
||||
using WeatherRecentEntries = RecentEntries<WeatherRecentEntry>;
|
||||
using WeatherRecentEntriesView = RecentEntriesView<WeatherRecentEntries>;
|
||||
|
||||
class WeatherView : public View {
|
||||
public:
|
||||
WeatherView(NavigationView& nav);
|
||||
~WeatherView();
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return "Weather"; };
|
||||
static const char* getWeatherSensorTypeName(FPROTO_WEATHER_SENSOR type);
|
||||
static std::string pad_string_with_spaces(int snakes);
|
||||
|
||||
private:
|
||||
void on_tick_second();
|
||||
void on_data(const WeatherDataMessage* data);
|
||||
|
||||
NavigationView& nav_;
|
||||
RxRadioState radio_state_{
|
||||
433'920'000 /* frequency */,
|
||||
1'750'000 /* bandwidth */,
|
||||
2'000'000 /* sampling rate */,
|
||||
ReceiverModel::Mode::AMAudio};
|
||||
app_settings::SettingsManager settings_{
|
||||
"rx_weather",
|
||||
app_settings::Mode::RX,
|
||||
{
|
||||
{"units_fahr"sv, &weather_units_fahr},
|
||||
}};
|
||||
|
||||
WeatherRecentEntries recent{};
|
||||
|
||||
OptionsField options_temperature{
|
||||
{10 * 8, 0 * 16},
|
||||
2,
|
||||
{{STR_DEGREES_C, 0},
|
||||
{STR_DEGREES_F, 1}}};
|
||||
|
||||
RFAmpField field_rf_amp{
|
||||
{13 * 8, 0 * 16}};
|
||||
LNAGainField field_lna{
|
||||
{15 * 8, 0 * 16}};
|
||||
VGAGainField field_vga{
|
||||
{18 * 8, 0 * 16}};
|
||||
RSSI rssi{
|
||||
{21 * 8, 0, 6 * 8, 4}};
|
||||
RxFrequencyField field_frequency{
|
||||
{0 * 8, 0 * 16},
|
||||
nav_};
|
||||
|
||||
SignalToken signal_token_tick_second{};
|
||||
|
||||
Button button_clear_list{
|
||||
{0, 16, 7 * 8, 32},
|
||||
"Clear"};
|
||||
|
||||
static constexpr auto header_height = 3 * 16;
|
||||
|
||||
const RecentEntriesColumns columns{{
|
||||
{"Type", 10},
|
||||
{"Temp", 5},
|
||||
{"Hum", 4},
|
||||
{"Ch", 3},
|
||||
{"Age", 3},
|
||||
}};
|
||||
WeatherRecentEntriesView recent_entries_view{columns, recent};
|
||||
|
||||
MessageHandlerRegistration message_handler_packet{
|
||||
Message::ID::WeatherData,
|
||||
[this](Message* const p) {
|
||||
const auto message = static_cast<const WeatherDataMessage*>(p);
|
||||
this->on_data(message);
|
||||
}};
|
||||
};
|
||||
|
||||
class WeatherRecentEntryDetailView : public View {
|
||||
public:
|
||||
WeatherRecentEntryDetailView(NavigationView& nav, const WeatherRecentEntry& entry);
|
||||
|
||||
void update_data();
|
||||
void focus() override;
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
WeatherRecentEntry entry_{};
|
||||
Text text_type{{0 * 8, 1 * 16, 15 * 8, 16}, "?"};
|
||||
Text text_id{{6 * 8, 2 * 16, 10 * 8, 16}, "?"};
|
||||
Text text_temp{{6 * 8, 3 * 16, 8 * 8, 16}, "?"};
|
||||
Text text_hum{{11 * 8, 4 * 16, 6 * 8, 16}, "?"};
|
||||
Text text_ch{{11 * 8, 5 * 16, 6 * 8, 16}, "?"};
|
||||
Text text_batt{{11 * 8, 6 * 16, 6 * 8, 16}, "?"};
|
||||
Text text_age{{11 * 8, 7 * 16, 6 * 8, 16}, "?"};
|
||||
|
||||
Labels labels{
|
||||
{{0 * 8, 0 * 16}, "Weather station type:", Color::light_grey()},
|
||||
{{0 * 8, 2 * 16}, "Id: ", Color::light_grey()},
|
||||
{{0 * 8, 3 * 16}, "Temp:", Color::light_grey()},
|
||||
{{0 * 8, 4 * 16}, "Humidity:", Color::light_grey()},
|
||||
{{0 * 8, 5 * 16}, "Channel:", Color::light_grey()},
|
||||
{{0 * 8, 6 * 16}, "Battery:", Color::light_grey()},
|
||||
{{0 * 8, 7 * 16}, "Age:", Color::light_grey()},
|
||||
};
|
||||
|
||||
Button button_done{
|
||||
{screen_width - 96 - 4, screen_height - 32 - 12, 96, 32},
|
||||
"Done"};
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
|
||||
#endif /*__UI_WEATHER_H__*/
|
||||
@@ -319,6 +319,11 @@ void set_spectrum_painter_config(const uint16_t width, const uint16_t height, bo
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
void set_weather() {
|
||||
const WeatherRxConfigureMessage message{};
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
static bool baseband_image_running = false;
|
||||
|
||||
void run_image(const spi_flash::image_tag_t image_tag) {
|
||||
|
||||
@@ -88,6 +88,7 @@ void set_spectrum(const size_t sampling_rate, const size_t trigger);
|
||||
void set_siggen_tone(const uint32_t tone);
|
||||
void set_siggen_config(const uint32_t bw, const uint32_t shape, const uint32_t duration);
|
||||
void set_spectrum_painter_config(const uint16_t width, const uint16_t height, bool update, int32_t bw);
|
||||
void set_weather();
|
||||
void request_beep();
|
||||
|
||||
void run_image(const portapack::spi_flash::image_tag_t image_tag);
|
||||
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Bernd Herzog
|
||||
*
|
||||
* 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 "ui.hpp"
|
||||
#include "ui_blespam.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "external_app.hpp"
|
||||
|
||||
namespace ui::external_app::blespam {
|
||||
void initialize_app(ui::NavigationView& nav) {
|
||||
nav.push<BLESpamView>();
|
||||
}
|
||||
} // namespace ui::external_app::blespam
|
||||
|
||||
extern "C" {
|
||||
|
||||
__attribute__((section(".external_app.app_blespam.application_information"), used)) application_information_t _application_information_blespam = {
|
||||
/*.memory_location = */ (uint8_t*)0x00000000,
|
||||
/*.externalAppEntry = */ ui::external_app::blespam::initialize_app,
|
||||
/*.header_version = */ CURRENT_HEADER_VERSION,
|
||||
/*.app_version = */ VERSION_MD5,
|
||||
|
||||
/*.app_name = */ "BLESpam",
|
||||
/*.bitmap_data = */ {
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0xF8,
|
||||
0x1F,
|
||||
0x04,
|
||||
0x20,
|
||||
0x02,
|
||||
0x40,
|
||||
0xFF,
|
||||
0xFF,
|
||||
0xFF,
|
||||
0xFF,
|
||||
0xAB,
|
||||
0xDF,
|
||||
0xAB,
|
||||
0xDF,
|
||||
0xFF,
|
||||
0xFF,
|
||||
0xFF,
|
||||
0xFF,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
},
|
||||
/*.icon_color = */ ui::Color::yellow().v,
|
||||
/*.menu_location = */ app_location_t::TX,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_afsk_rx */ {'P', 'B', 'T', 'T'},
|
||||
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
|
||||
};
|
||||
}
|
||||
+1292
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,250 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
|
||||
* Copyright (C) 2017 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.
|
||||
*/
|
||||
|
||||
// Code from https://github.com/Flipper-XFW/Xtreme-Apps/tree/04c3a60093e2c2378e79498b4505aa8072980a42/ble_spam/protocols
|
||||
// Thanks for the work of the original creators!
|
||||
// Saying thanks in the main view!
|
||||
|
||||
#ifndef __UI_BLESPAM_H__
|
||||
#define __UI_BLESPAM_H__
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_transmitter.hpp"
|
||||
#include "ui_freq_field.hpp"
|
||||
#include "ui_record_view.hpp"
|
||||
#include "app_settings.hpp"
|
||||
#include "radio_state.hpp"
|
||||
#include "log_file.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
using namespace ui;
|
||||
|
||||
namespace ui::external_app::blespam {
|
||||
|
||||
enum ATK_TYPE {
|
||||
ATK_ANDROID,
|
||||
ATK_IOS,
|
||||
ATK_IOS_CRASH,
|
||||
ATK_WINDOWS,
|
||||
ATK_SAMSUNG
|
||||
};
|
||||
enum PKT_TYPE {
|
||||
PKT_TYPE_INVALID_TYPE,
|
||||
PKT_TYPE_RAW,
|
||||
PKT_TYPE_DISCOVERY,
|
||||
PKT_TYPE_IBEACON,
|
||||
PKT_TYPE_ADV_IND,
|
||||
PKT_TYPE_ADV_DIRECT_IND,
|
||||
PKT_TYPE_ADV_NONCONN_IND,
|
||||
PKT_TYPE_ADV_SCAN_IND,
|
||||
PKT_TYPE_SCAN_REQ,
|
||||
PKT_TYPE_SCAN_RSP,
|
||||
PKT_TYPE_CONNECT_REQ,
|
||||
PKT_TYPE_LL_DATA,
|
||||
PKT_TYPE_LL_CONNECTION_UPDATE_REQ,
|
||||
PKT_TYPE_LL_CHANNEL_MAP_REQ,
|
||||
PKT_TYPE_LL_TERMINATE_IND,
|
||||
PKT_TYPE_LL_ENC_REQ,
|
||||
PKT_TYPE_LL_ENC_RSP,
|
||||
PKT_TYPE_LL_START_ENC_REQ,
|
||||
PKT_TYPE_LL_START_ENC_RSP,
|
||||
PKT_TYPE_LL_UNKNOWN_RSP,
|
||||
PKT_TYPE_LL_FEATURE_REQ,
|
||||
PKT_TYPE_LL_FEATURE_RSP,
|
||||
PKT_TYPE_LL_PAUSE_ENC_REQ,
|
||||
PKT_TYPE_LL_PAUSE_ENC_RSP,
|
||||
PKT_TYPE_LL_VERSION_IND,
|
||||
PKT_TYPE_LL_REJECT_IND,
|
||||
PKT_TYPE_NUM_PKT_TYPE
|
||||
};
|
||||
|
||||
class BLESpamView : public View {
|
||||
public:
|
||||
BLESpamView(NavigationView& nav);
|
||||
~BLESpamView();
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override {
|
||||
return "BLESpam";
|
||||
};
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
TxRadioState radio_state_{
|
||||
2'402'000'000 /* frequency */,
|
||||
4'000'000 /* bandwidth */,
|
||||
4'000'000 /* sampling rate */
|
||||
};
|
||||
TxFrequencyField field_frequency{
|
||||
{0 * 8, 0 * 16},
|
||||
nav_};
|
||||
TransmitterView2 tx_view{
|
||||
{11 * 8, 0 * 16},
|
||||
/*short_ui*/ true};
|
||||
app_settings::SettingsManager settings_{
|
||||
"tx_blespam", app_settings::Mode::TX};
|
||||
|
||||
Button button_startstop{
|
||||
{0, 3 * 16, 96, 24},
|
||||
"Start"};
|
||||
Checkbox chk_randdev{{100, 16}, 10, "Rnd device", true};
|
||||
|
||||
Console console{
|
||||
{0, 70, 240, 220}};
|
||||
|
||||
OptionsField options_atkmode{
|
||||
{0 * 8, 2 * 8},
|
||||
10,
|
||||
{{"Android", 0},
|
||||
{"iOs", 1},
|
||||
{"iOs crash", 2},
|
||||
{"Windows", 3},
|
||||
{"Samsung", 4}}};
|
||||
|
||||
bool is_running{false};
|
||||
|
||||
uint8_t counter = 0; // for packet change
|
||||
uint8_t displayCounter = 0; // for packet display
|
||||
|
||||
ATK_TYPE attackType = ATK_ANDROID;
|
||||
|
||||
bool randomMac{true};
|
||||
bool randomDev{true};
|
||||
|
||||
uint8_t channel_number = 37;
|
||||
char mac[13] = "010203040507";
|
||||
char advertisementData[63] = {"03032CFE06162CFED5A59E020AB4\0"};
|
||||
PKT_TYPE pduType = {PKT_TYPE_DISCOVERY};
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
void reset();
|
||||
void createFastPairPacket();
|
||||
void createIosPacket(bool crash);
|
||||
void createSamsungPacket();
|
||||
void createWindowsPacket();
|
||||
void changePacket(bool forced);
|
||||
void on_timer();
|
||||
uint64_t get_freq_by_channel_number(uint8_t channel_number);
|
||||
void randomizeMac();
|
||||
void randomChn();
|
||||
|
||||
void furi_hal_random_fill_buf(uint8_t* buf, uint32_t len);
|
||||
|
||||
MessageHandlerRegistration message_handler_frame_sync{
|
||||
Message::ID::DisplayFrameSync,
|
||||
[this](const Message* const) {
|
||||
this->on_timer();
|
||||
}};
|
||||
|
||||
// continuity
|
||||
|
||||
typedef enum {
|
||||
PayloadModeRandom,
|
||||
PayloadModeValue,
|
||||
PayloadModeBruteforce,
|
||||
} PayloadMode;
|
||||
|
||||
typedef enum {
|
||||
ContinuityTypeAirDrop = 0x05,
|
||||
ContinuityTypeProximityPair = 0x07,
|
||||
ContinuityTypeAirplayTarget = 0x09,
|
||||
ContinuityTypeHandoff = 0x0C,
|
||||
ContinuityTypeTetheringSource = 0x0E,
|
||||
ContinuityTypeNearbyAction = 0x0F,
|
||||
ContinuityTypeNearbyInfo = 0x10,
|
||||
|
||||
ContinuityTypeCustomCrash,
|
||||
ContinuityTypeCOUNT
|
||||
} ContinuityType;
|
||||
|
||||
typedef enum {
|
||||
ContinuityPpBruteforceModel,
|
||||
ContinuityPpBruteforceColor,
|
||||
} ContinuityPpBruteforce;
|
||||
|
||||
typedef struct {
|
||||
uint8_t value;
|
||||
// const char* name;
|
||||
} ContinuityColor;
|
||||
|
||||
static const uint8_t pp_prefixes_count;
|
||||
|
||||
static const uint8_t na_actions_count;
|
||||
static const uint8_t pp_models_count;
|
||||
static const ContinuityColor colors_beats_studio_buds_[];
|
||||
static const ContinuityColor colors_beats_fit_pro[];
|
||||
static const ContinuityColor colors_beats_studio_pro[];
|
||||
static const ContinuityColor colors_beats_studio_3[];
|
||||
static const ContinuityColor colors_beats_x[];
|
||||
static const ContinuityColor colors_beats_studio_buds[];
|
||||
static const ContinuityColor colors_beats_solo_pro[];
|
||||
static const ContinuityColor colors_powerbeats_pro[];
|
||||
static const ContinuityColor colors_powerbeats_3[];
|
||||
static const ContinuityColor colors_beats_solo_3[];
|
||||
static const ContinuityColor colors_beats_flex[];
|
||||
static const ContinuityColor colors_airpods_max[];
|
||||
static const ContinuityColor colors_white[];
|
||||
typedef struct {
|
||||
uint16_t value;
|
||||
// const char* name;
|
||||
const ContinuityColor* colors;
|
||||
const uint8_t colors_count;
|
||||
} contiModels;
|
||||
static const contiModels pp_models[];
|
||||
typedef struct {
|
||||
uint8_t value;
|
||||
} contiU8;
|
||||
static const contiU8 pp_prefixes[];
|
||||
static const contiU8 na_actions[];
|
||||
|
||||
// fastpair:
|
||||
|
||||
static const uint16_t fastpairModels_count;
|
||||
typedef struct {
|
||||
uint32_t value;
|
||||
const char* name; // could be moved too
|
||||
} fpUi32;
|
||||
static const fpUi32 fastpairModels[];
|
||||
|
||||
// easysetup:
|
||||
static const uint8_t watch_models_count;
|
||||
typedef struct {
|
||||
uint8_t value;
|
||||
} easyU8;
|
||||
typedef struct {
|
||||
uint32_t value;
|
||||
} easyU32;
|
||||
typedef enum {
|
||||
EasysetupTypeBuds = 0x01, // Skip 0 as it means unset
|
||||
EasysetupTypeWatch,
|
||||
EasysetupTypeCOUNT,
|
||||
} EasysetupType;
|
||||
static const easyU8 watch_models[];
|
||||
static const uint8_t buds_models_count;
|
||||
static const easyU32 buds_models[];
|
||||
};
|
||||
}; // namespace ui::external_app::blespam
|
||||
|
||||
#endif /*__UI_BLESPAM_H__*/
|
||||
@@ -15,6 +15,11 @@ set(EXTCPPSRC
|
||||
#font_viewer
|
||||
external/font_viewer/main.cpp
|
||||
external/font_viewer/ui_font_viewer.cpp
|
||||
|
||||
#blespam
|
||||
external/blespam/main.cpp
|
||||
external/blespam/ui_blespam.cpp
|
||||
|
||||
)
|
||||
|
||||
set(EXTAPPLIST
|
||||
@@ -22,4 +27,5 @@ set(EXTAPPLIST
|
||||
afsk_rx
|
||||
calculator
|
||||
font_viewer
|
||||
blespam
|
||||
)
|
||||
|
||||
+8
@@ -21,6 +21,7 @@ MEMORY
|
||||
ram_external_app_afsk_rx (rwx) : org = 0xEEEA0000, len = 32k
|
||||
ram_external_app_calculator (rwx) : org = 0xEEEB0000, len = 32k
|
||||
ram_external_app_font_viewer(rwx) : org = 0xEEEC0000, len = 32k
|
||||
ram_external_app_blespam(rwx) : org = 0xEEED0000, len = 32k
|
||||
}
|
||||
|
||||
SECTIONS
|
||||
@@ -48,4 +49,11 @@ SECTIONS
|
||||
KEEP(*(.external_app.app_font_viewer.application_information));
|
||||
*(*ui*external_app*font_viewer*);
|
||||
} > ram_external_app_font_viewer
|
||||
|
||||
|
||||
.external_app_blespam : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_blespam.application_information));
|
||||
*(*ui*external_app*blespam*);
|
||||
} > ram_external_app_blespam
|
||||
}
|
||||
|
||||
@@ -253,7 +253,7 @@ std::string to_string_hex(uint64_t value, int32_t length) {
|
||||
|
||||
std::string to_string_hex_array(uint8_t* array, int32_t length) {
|
||||
std::string str_return;
|
||||
str_return.reserve(length);
|
||||
str_return.reserve(length * 2);
|
||||
|
||||
for (uint8_t i = 0; i < length; i++)
|
||||
str_return += to_string_hex(array[i], 2);
|
||||
|
||||
@@ -79,6 +79,7 @@
|
||||
#include "ui_tone_search.hpp"
|
||||
#include "ui_touchtunes.hpp"
|
||||
#include "ui_view_wav.hpp"
|
||||
#include "ui_weatherstation.hpp"
|
||||
#include "ui_whipcalc.hpp"
|
||||
#include "ui_external_items_menu_loader.hpp"
|
||||
|
||||
@@ -565,6 +566,7 @@ ReceiversMenuView::ReceiversMenuView(NavigationView& nav) {
|
||||
{"Recon", Color::green(), &bitmap_icon_scanner, [&nav]() { nav.push<ReconView>(); }},
|
||||
{"Search", Color::yellow(), &bitmap_icon_search, [&nav]() { nav.push<SearchView>(); }},
|
||||
{"TPMS Cars", Color::green(), &bitmap_icon_tpms, [&nav]() { nav.push<TPMSAppView>(); }},
|
||||
{"Weather", Color::yellow(), &bitmap_icon_lge, [&nav]() { nav.push<WeatherView>(); }},
|
||||
// {"FSK RX", Color::yellow(), &bitmap_icon_remote, [&nav]() { nav.push<FskxRxMainView>(); }},
|
||||
// {"DMR", Color::dark_grey(), &bitmap_icon_dmr, [&nav](){ nav.push<NotImplementedView>(); }},
|
||||
// {"SIGFOX", Color::dark_grey(), &bitmap_icon_fox, [&nav](){ nav.push<NotImplementedView>(); }},
|
||||
|
||||
@@ -550,6 +550,14 @@ set(MODE_CPPSRC
|
||||
)
|
||||
DeclareTargets(PWFM wfm_audio)
|
||||
|
||||
### Weather Stations
|
||||
|
||||
set(MODE_CPPSRC
|
||||
proc_weather.cpp
|
||||
)
|
||||
DeclareTargets(PWTH weather)
|
||||
|
||||
|
||||
### Flash Utility
|
||||
|
||||
include(${CHIBIOS_PORTAPACK}/os/various/fatfs_bindings/fatfs.cmake)
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
|
||||
#ifndef __FPROTO_Acurite_592TXR_H__
|
||||
#define __FPROTO_Acurite_592TXR_H__
|
||||
|
||||
#include "weatherbase.hpp"
|
||||
|
||||
typedef enum {
|
||||
Acurite_592TXRDecoderStepReset = 0,
|
||||
Acurite_592TXRDecoderStepCheckPreambule,
|
||||
Acurite_592TXRDecoderStepSaveDuration,
|
||||
Acurite_592TXRDecoderStepCheckDuration,
|
||||
} Acurite_592TXRDecoderStep;
|
||||
|
||||
class FProtoWeatherAcurite592TXR : public FProtoWeatherBase {
|
||||
public:
|
||||
FProtoWeatherAcurite592TXR() {
|
||||
sensorType = FPW_Acurite592TXR;
|
||||
}
|
||||
|
||||
void feed(bool level, uint32_t duration) {
|
||||
switch (parser_step) {
|
||||
case Acurite_592TXRDecoderStepReset:
|
||||
if ((level) && (DURATION_DIFF(duration, te_short * 3) <
|
||||
te_delta * 2)) {
|
||||
parser_step = Acurite_592TXRDecoderStepCheckPreambule;
|
||||
te_last = duration;
|
||||
header_count = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case Acurite_592TXRDecoderStepCheckPreambule:
|
||||
if (level) {
|
||||
te_last = duration;
|
||||
} else {
|
||||
if ((DURATION_DIFF(
|
||||
te_last, te_short * 3) <
|
||||
te_delta * 2) &&
|
||||
(DURATION_DIFF(duration, te_short * 3) <
|
||||
te_delta * 2)) {
|
||||
// Found preambule
|
||||
header_count++;
|
||||
} else if ((header_count > 2) && (header_count < 5)) {
|
||||
if ((DURATION_DIFF(te_last, te_short) < te_delta) &&
|
||||
(DURATION_DIFF(duration, te_long) < te_delta)) {
|
||||
decode_data = 0;
|
||||
decode_count_bit = 0;
|
||||
subghz_protocol_blocks_add_bit(0);
|
||||
parser_step = Acurite_592TXRDecoderStepSaveDuration;
|
||||
} else if (
|
||||
(DURATION_DIFF(
|
||||
te_last, te_long) <
|
||||
te_delta) &&
|
||||
(DURATION_DIFF(duration, te_short) <
|
||||
te_delta)) {
|
||||
decode_data = 0;
|
||||
decode_count_bit = 0;
|
||||
subghz_protocol_blocks_add_bit(1);
|
||||
parser_step = Acurite_592TXRDecoderStepSaveDuration;
|
||||
} else {
|
||||
parser_step = Acurite_592TXRDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
parser_step = Acurite_592TXRDecoderStepReset;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case Acurite_592TXRDecoderStepSaveDuration:
|
||||
if (level) {
|
||||
te_last = duration;
|
||||
parser_step = Acurite_592TXRDecoderStepCheckDuration;
|
||||
} else {
|
||||
parser_step = Acurite_592TXRDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case Acurite_592TXRDecoderStepCheckDuration:
|
||||
if (!level) {
|
||||
if (duration >= ((uint32_t)te_short * 5)) {
|
||||
if ((decode_count_bit ==
|
||||
min_count_bit_for_found) &&
|
||||
ws_protocol_acurite_592txr_check_crc()) {
|
||||
data = decode_data;
|
||||
data_count_bit = decode_count_bit;
|
||||
ws_protocol_acurite_592txr_remote_controller();
|
||||
if (callback) callback(this);
|
||||
}
|
||||
decode_data = 0;
|
||||
decode_count_bit = 0;
|
||||
parser_step = Acurite_592TXRDecoderStepReset;
|
||||
break;
|
||||
} else if (
|
||||
(DURATION_DIFF(
|
||||
te_last, te_short) <
|
||||
te_delta) &&
|
||||
(DURATION_DIFF(duration, te_long) <
|
||||
te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(0);
|
||||
parser_step = Acurite_592TXRDecoderStepSaveDuration;
|
||||
} else if (
|
||||
(DURATION_DIFF(
|
||||
te_last, te_long) <
|
||||
te_delta) &&
|
||||
(DURATION_DIFF(duration, te_short) <
|
||||
te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(1);
|
||||
parser_step = Acurite_592TXRDecoderStepSaveDuration;
|
||||
} else {
|
||||
parser_step = Acurite_592TXRDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
parser_step = Acurite_592TXRDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
uint32_t te_short = 200;
|
||||
uint32_t te_long = 400;
|
||||
uint32_t te_delta = 90;
|
||||
uint32_t min_count_bit_for_found = 56;
|
||||
|
||||
bool ws_protocol_acurite_592txr_check_crc() {
|
||||
uint8_t msg[] = {
|
||||
static_cast<uint8_t>(decode_data >> 48),
|
||||
static_cast<uint8_t>(decode_data >> 40),
|
||||
static_cast<uint8_t>(decode_data >> 32),
|
||||
static_cast<uint8_t>(decode_data >> 24),
|
||||
static_cast<uint8_t>(decode_data >> 16),
|
||||
static_cast<uint8_t>(decode_data >> 8)};
|
||||
|
||||
if ((subghz_protocol_blocks_add_bytes(msg, 6) ==
|
||||
(uint8_t)(decode_data & 0xFF)) &&
|
||||
(!subghz_protocol_blocks_parity_bytes(&msg[2], 4))) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void ws_protocol_acurite_592txr_remote_controller() {
|
||||
uint8_t channel_[] = {3, 0, 2, 1};
|
||||
uint8_t channel_raw = ((data >> 54) & 0x03);
|
||||
channel = channel_[channel_raw];
|
||||
id = (data >> 40) & 0x3FFF;
|
||||
battery_low = !((data >> 38) & 1);
|
||||
humidity = (data >> 24) & 0x7F;
|
||||
uint16_t temp_raw = ((data >> 9) & 0xF80) | ((data >> 8) & 0x7F);
|
||||
temp = ((float)(temp_raw)-1000) / 10.0f;
|
||||
btn = WS_NO_BTN;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,110 @@
|
||||
|
||||
#ifndef __FPROTO_Acurite_606TX_H__
|
||||
#define __FPROTO_Acurite_606TX_H__
|
||||
|
||||
#include "weatherbase.hpp"
|
||||
|
||||
typedef enum {
|
||||
Acurite_606TXDecoderStepReset = 0,
|
||||
Acurite_606TXDecoderStepSaveDuration,
|
||||
Acurite_606TXDecoderStepCheckDuration,
|
||||
} Acurite_606TXDecoderStep;
|
||||
|
||||
class FProtoWeatherAcurite606TX : public FProtoWeatherBase {
|
||||
public:
|
||||
FProtoWeatherAcurite606TX() {
|
||||
sensorType = FPW_Acurite606TX;
|
||||
}
|
||||
|
||||
void feed(bool level, uint32_t duration) {
|
||||
switch (parser_step) {
|
||||
case Acurite_606TXDecoderStepReset:
|
||||
if ((!level) && (DURATION_DIFF(duration, te_short * 17) < te_delta * 8)) {
|
||||
// Found syncPrefix
|
||||
parser_step = Acurite_606TXDecoderStepSaveDuration;
|
||||
decode_data = 0;
|
||||
decode_count_bit = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case Acurite_606TXDecoderStepSaveDuration:
|
||||
if (level) {
|
||||
te_last = duration;
|
||||
parser_step = Acurite_606TXDecoderStepCheckDuration;
|
||||
} else {
|
||||
parser_step = Acurite_606TXDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case Acurite_606TXDecoderStepCheckDuration:
|
||||
if (!level) {
|
||||
if (DURATION_DIFF(te_last, te_short) <
|
||||
te_delta) {
|
||||
if ((DURATION_DIFF(duration, te_short) <
|
||||
te_delta) ||
|
||||
(duration > te_long * 3)) {
|
||||
// Found syncPostfix
|
||||
parser_step = Acurite_606TXDecoderStepReset;
|
||||
if ((decode_count_bit ==
|
||||
min_count_bit_for_found) &&
|
||||
ws_protocol_acurite_606tx_check()) {
|
||||
data = decode_data;
|
||||
data_count_bit = decode_count_bit;
|
||||
ws_protocol_acurite_606tx_remote_controller();
|
||||
if (callback) callback(this);
|
||||
}
|
||||
decode_data = 0;
|
||||
decode_count_bit = 0;
|
||||
} else if (
|
||||
DURATION_DIFF(duration, te_long) <
|
||||
te_delta * 2) {
|
||||
subghz_protocol_blocks_add_bit(0);
|
||||
parser_step = Acurite_606TXDecoderStepSaveDuration;
|
||||
} else if (
|
||||
DURATION_DIFF(duration, te_long * 2) < te_delta * 4) {
|
||||
subghz_protocol_blocks_add_bit(1);
|
||||
parser_step = Acurite_606TXDecoderStepSaveDuration;
|
||||
} else {
|
||||
parser_step = Acurite_606TXDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
parser_step = Acurite_606TXDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
parser_step = Acurite_606TXDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
uint32_t te_short = 500;
|
||||
uint32_t te_long = 2000;
|
||||
uint32_t te_delta = 150;
|
||||
uint32_t min_count_bit_for_found = 32;
|
||||
|
||||
void ws_protocol_acurite_606tx_remote_controller() {
|
||||
id = (data >> 24) & 0xFF;
|
||||
battery_low = (data >> 23) & 1;
|
||||
channel = WS_NO_CHANNEL;
|
||||
if (!((data >> 19) & 1)) {
|
||||
temp = (float)((data >> 8) & 0x07FF) / 10.0f;
|
||||
} else {
|
||||
temp = (float)((~(data >> 8) & 0x07FF) + 1) / -10.0f;
|
||||
}
|
||||
btn = WS_NO_BTN;
|
||||
humidity = WS_NO_HUMIDITY;
|
||||
}
|
||||
bool ws_protocol_acurite_606tx_check() {
|
||||
if (!decode_data) return false;
|
||||
uint8_t msg[] = {
|
||||
static_cast<uint8_t>(decode_data >> 24),
|
||||
static_cast<uint8_t>(decode_data >> 16),
|
||||
static_cast<uint8_t>(decode_data >> 8)};
|
||||
|
||||
uint8_t crc = subghz_protocol_blocks_lfsr_digest8(msg, 3, 0x98, 0xF1);
|
||||
return (crc == (decode_data & 0xFF));
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,112 @@
|
||||
|
||||
#ifndef __FPROTO_Acurite_609TX_H__
|
||||
#define __FPROTO_Acurite_609TX_H__
|
||||
|
||||
#include "weatherbase.hpp"
|
||||
|
||||
typedef enum {
|
||||
Acurite_609TXCDecoderStepReset = 0,
|
||||
Acurite_609TXCDecoderStepSaveDuration,
|
||||
Acurite_609TXCDecoderStepCheckDuration,
|
||||
} Acurite_609TXCDecoderStep;
|
||||
|
||||
class FProtoWeatherAcurite609TX : public FProtoWeatherBase {
|
||||
public:
|
||||
FProtoWeatherAcurite609TX() {
|
||||
sensorType = FPW_Acurite609TX;
|
||||
}
|
||||
|
||||
void feed(bool level, uint32_t duration) {
|
||||
switch (parser_step) {
|
||||
case Acurite_609TXCDecoderStepReset:
|
||||
if ((!level) && (DURATION_DIFF(duration, te_short * 17) <
|
||||
te_delta * 8)) {
|
||||
// Found syncPrefix
|
||||
parser_step = Acurite_609TXCDecoderStepSaveDuration;
|
||||
decode_data = 0;
|
||||
decode_count_bit = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case Acurite_609TXCDecoderStepSaveDuration:
|
||||
if (level) {
|
||||
te_last = duration;
|
||||
parser_step = Acurite_609TXCDecoderStepCheckDuration;
|
||||
} else {
|
||||
parser_step = Acurite_609TXCDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case Acurite_609TXCDecoderStepCheckDuration:
|
||||
if (!level) {
|
||||
if (DURATION_DIFF(te_last, te_short) <
|
||||
te_delta) {
|
||||
if ((DURATION_DIFF(duration, te_short) <
|
||||
te_delta) ||
|
||||
(duration > te_long * 3)) {
|
||||
// Found syncPostfix
|
||||
parser_step = Acurite_609TXCDecoderStepReset;
|
||||
if ((decode_count_bit ==
|
||||
min_count_bit_for_found) &&
|
||||
ws_protocol_acurite_609txc_check()) {
|
||||
data = decode_data;
|
||||
data_count_bit = decode_count_bit;
|
||||
ws_protocol_acurite_609txc_remote_controller();
|
||||
if (callback) callback(this);
|
||||
}
|
||||
decode_data = 0;
|
||||
decode_count_bit = 0;
|
||||
} else if (
|
||||
DURATION_DIFF(duration, te_long) <
|
||||
te_delta * 2) {
|
||||
subghz_protocol_blocks_add_bit(0);
|
||||
parser_step = Acurite_609TXCDecoderStepSaveDuration;
|
||||
} else if (
|
||||
DURATION_DIFF(duration, te_long * 2) <
|
||||
te_delta * 4) {
|
||||
subghz_protocol_blocks_add_bit(1);
|
||||
parser_step = Acurite_609TXCDecoderStepSaveDuration;
|
||||
} else {
|
||||
parser_step = Acurite_609TXCDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
parser_step = Acurite_609TXCDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
parser_step = Acurite_609TXCDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
uint32_t te_short = 500;
|
||||
uint32_t te_long = 2000;
|
||||
uint32_t te_delta = 150;
|
||||
uint32_t min_count_bit_for_found = 32;
|
||||
|
||||
void ws_protocol_acurite_609txc_remote_controller() {
|
||||
id = (data >> 32) & 0xFF;
|
||||
battery_low = (data >> 31) & 1;
|
||||
channel = WS_NO_CHANNEL;
|
||||
|
||||
// Temperature in Celsius is encoded as a 12 bit integer value
|
||||
// multiplied by 10 using the 4th - 6th nybbles (bytes 1 & 2)
|
||||
// negative values are recovered by sign extend from int16_t.
|
||||
int16_t temp_raw =
|
||||
(int16_t)(((data >> 12) & 0xf000) | ((data >> 16) << 4));
|
||||
temp = (temp_raw >> 4) * 0.1f;
|
||||
humidity = (data >> 8) & 0xff;
|
||||
btn = WS_NO_BTN;
|
||||
}
|
||||
bool ws_protocol_acurite_609txc_check() {
|
||||
if (!decode_data) return false;
|
||||
uint8_t crc = (uint8_t)(decode_data >> 32) +
|
||||
(uint8_t)(decode_data >> 24) +
|
||||
(uint8_t)(decode_data >> 16) +
|
||||
(uint8_t)(decode_data >> 8);
|
||||
return (crc == (decode_data & 0xFF));
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,138 @@
|
||||
|
||||
#ifndef __FPROTO_Acurite_986_H__
|
||||
#define __FPROTO_Acurite_986_H__
|
||||
|
||||
#include "weatherbase.hpp"
|
||||
|
||||
typedef enum {
|
||||
Acurite_986DecoderStepReset = 0,
|
||||
Acurite_986DecoderStepSync1,
|
||||
Acurite_986DecoderStepSync2,
|
||||
Acurite_986DecoderStepSync3,
|
||||
Acurite_986DecoderStepSaveDuration,
|
||||
Acurite_986DecoderStepCheckDuration,
|
||||
} Acurite_986DecoderStep;
|
||||
|
||||
class FProtoWeatherAcurite986 : public FProtoWeatherBase {
|
||||
public:
|
||||
FProtoWeatherAcurite986() {
|
||||
sensorType = FPW_Acurite986;
|
||||
}
|
||||
|
||||
void feed(bool level, uint32_t duration) {
|
||||
switch (parser_step) {
|
||||
case Acurite_986DecoderStepReset:
|
||||
if ((!level) && (DURATION_DIFF(duration, te_long) < te_delta * 15)) {
|
||||
// Found 1st sync bit
|
||||
parser_step = Acurite_986DecoderStepSync1;
|
||||
decode_data = 0;
|
||||
decode_count_bit = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case Acurite_986DecoderStepSync1:
|
||||
if (DURATION_DIFF(duration, te_long) < te_delta * 15) {
|
||||
if (!level) {
|
||||
parser_step = Acurite_986DecoderStepSync2;
|
||||
}
|
||||
} else {
|
||||
parser_step = Acurite_986DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case Acurite_986DecoderStepSync2:
|
||||
if (DURATION_DIFF(duration, te_long) < te_delta * 15) {
|
||||
if (!level) {
|
||||
parser_step = Acurite_986DecoderStepSync3;
|
||||
}
|
||||
} else {
|
||||
parser_step = Acurite_986DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case Acurite_986DecoderStepSync3:
|
||||
if (DURATION_DIFF(duration, te_long) < te_delta * 15) {
|
||||
if (!level) {
|
||||
parser_step = Acurite_986DecoderStepSaveDuration;
|
||||
}
|
||||
} else {
|
||||
parser_step = Acurite_986DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case Acurite_986DecoderStepSaveDuration:
|
||||
if (level) {
|
||||
te_last = duration;
|
||||
parser_step = Acurite_986DecoderStepCheckDuration;
|
||||
} else {
|
||||
parser_step = Acurite_986DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case Acurite_986DecoderStepCheckDuration:
|
||||
if (!level) {
|
||||
if (DURATION_DIFF(duration, te_short) <
|
||||
te_delta * 10) {
|
||||
if (duration < te_short) {
|
||||
subghz_protocol_blocks_add_bit(0);
|
||||
parser_step = Acurite_986DecoderStepSaveDuration;
|
||||
} else {
|
||||
subghz_protocol_blocks_add_bit(1);
|
||||
parser_step = Acurite_986DecoderStepSaveDuration;
|
||||
}
|
||||
} else {
|
||||
// Found syncPostfix
|
||||
parser_step = Acurite_986DecoderStepReset;
|
||||
if ((decode_count_bit == min_count_bit_for_found) && ws_protocol_acurite_986_check()) {
|
||||
data = decode_data;
|
||||
data_count_bit = decode_count_bit;
|
||||
ws_protocol_acurite_986_remote_controller();
|
||||
if (callback) callback(this);
|
||||
}
|
||||
decode_data = 0;
|
||||
decode_count_bit = 0;
|
||||
}
|
||||
} else {
|
||||
parser_step = Acurite_986DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
uint32_t te_short = 800;
|
||||
uint32_t te_long = 1750;
|
||||
uint32_t te_delta = 50;
|
||||
uint32_t min_count_bit_for_found = 40;
|
||||
|
||||
void ws_protocol_acurite_986_remote_controller() {
|
||||
int temp;
|
||||
|
||||
id = subghz_protocol_blocks_reverse_key(data >> 24, 8);
|
||||
id = (id << 8) | subghz_protocol_blocks_reverse_key(data >> 16, 8);
|
||||
battery_low = (data >> 14) & 1;
|
||||
channel = ((data >> 15) & 1) + 1;
|
||||
|
||||
temp = subghz_protocol_blocks_reverse_key(data >> 32, 8);
|
||||
if (temp & 0x80) {
|
||||
temp = -(temp & 0x7F);
|
||||
}
|
||||
temp = locale_fahrenheit_to_celsius((float)temp);
|
||||
btn = WS_NO_BTN;
|
||||
humidity = WS_NO_HUMIDITY;
|
||||
}
|
||||
|
||||
bool ws_protocol_acurite_986_check() {
|
||||
if (!decode_data) return false;
|
||||
uint8_t msg[] = {
|
||||
(uint8_t)(decode_data >> 32),
|
||||
(uint8_t)(decode_data >> 24),
|
||||
(uint8_t)(decode_data >> 16),
|
||||
(uint8_t)(decode_data >> 8)};
|
||||
|
||||
uint8_t crc = subghz_protocol_blocks_crc8(msg, 4, 0x07, 0x00);
|
||||
return (crc == (decode_data & 0xFF));
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,88 @@
|
||||
|
||||
#ifndef __FPROTO_Ambient_H__
|
||||
#define __FPROTO_Ambient_H__
|
||||
|
||||
#include "weatherbase.hpp"
|
||||
|
||||
#define AMBIENT_WEATHER_PACKET_HEADER_1 0xFFD440000000000 // 0xffd45 .. 0xffd46
|
||||
#define AMBIENT_WEATHER_PACKET_HEADER_2 0x001440000000000 // 0x00145 .. 0x00146
|
||||
#define AMBIENT_WEATHER_PACKET_HEADER_MASK 0xFFFFC0000000000
|
||||
|
||||
class FProtoWeatherAmbient : public FProtoWeatherBase {
|
||||
public:
|
||||
FProtoWeatherAmbient() {
|
||||
sensorType = FPW_Ambient;
|
||||
}
|
||||
|
||||
void feed(bool level, uint32_t duration) {
|
||||
ManchesterEvent event = ManchesterEventReset;
|
||||
if (!level) {
|
||||
if (DURATION_DIFF(duration, te_short) < te_delta) {
|
||||
event = ManchesterEventShortLow;
|
||||
} else if (
|
||||
DURATION_DIFF(duration, te_long) < te_delta * 2) {
|
||||
event = ManchesterEventLongLow;
|
||||
}
|
||||
} else {
|
||||
if (DURATION_DIFF(duration, te_short) < te_delta) {
|
||||
event = ManchesterEventShortHigh;
|
||||
} else if (
|
||||
DURATION_DIFF(duration, te_long) < te_delta * 2) {
|
||||
event = ManchesterEventLongHigh;
|
||||
}
|
||||
}
|
||||
if (event != ManchesterEventReset) {
|
||||
bool data;
|
||||
bool data_ok = manchester_advance(manchester_saved_state, event, &manchester_saved_state, &data);
|
||||
|
||||
if (data_ok) {
|
||||
decode_data = (decode_data << 1) | !data;
|
||||
}
|
||||
|
||||
if (((decode_data & AMBIENT_WEATHER_PACKET_HEADER_MASK) == AMBIENT_WEATHER_PACKET_HEADER_1) ||
|
||||
((decode_data & AMBIENT_WEATHER_PACKET_HEADER_MASK) == AMBIENT_WEATHER_PACKET_HEADER_2)) {
|
||||
if (ws_protocol_ambient_weather_check_crc()) {
|
||||
data = decode_data;
|
||||
data_count_bit = min_count_bit_for_found;
|
||||
ws_protocol_ambient_weather_remote_controller();
|
||||
if (callback) callback(this);
|
||||
decode_data = 0;
|
||||
decode_count_bit = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
decode_data = 0;
|
||||
decode_count_bit = 0;
|
||||
manchester_advance(manchester_saved_state, ManchesterEventReset, &manchester_saved_state, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
uint32_t te_short = 500;
|
||||
uint32_t te_long = 1000;
|
||||
uint32_t te_delta = 120;
|
||||
uint32_t min_count_bit_for_found = 48;
|
||||
|
||||
bool ws_protocol_ambient_weather_check_crc() {
|
||||
uint8_t msg[] = {
|
||||
static_cast<uint8_t>(decode_data >> 40),
|
||||
static_cast<uint8_t>(decode_data >> 32),
|
||||
static_cast<uint8_t>(decode_data >> 24),
|
||||
static_cast<uint8_t>(decode_data >> 16),
|
||||
static_cast<uint8_t>(decode_data >> 8)};
|
||||
|
||||
uint8_t crc = subghz_protocol_blocks_lfsr_digest8(msg, 5, 0x98, 0x3e) ^ 0x64;
|
||||
return (crc == (uint8_t)(decode_data & 0xFF));
|
||||
}
|
||||
|
||||
void ws_protocol_ambient_weather_remote_controller() {
|
||||
id = (data >> 32) & 0xFF;
|
||||
battery_low = (data >> 31) & 1;
|
||||
channel = ((data >> 28) & 0x07) + 1;
|
||||
temp = locale_fahrenheit_to_celsius(((float)((data >> 16) & 0x0FFF) - 400.0f) / 10.0f);
|
||||
humidity = (data >> 8) & 0xFF;
|
||||
btn = WS_NO_BTN;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,121 @@
|
||||
|
||||
#ifndef __FPROTO_AuriolAhfl_H__
|
||||
#define __FPROTO_AuriolAhfl_H__
|
||||
|
||||
#include "weatherbase.hpp"
|
||||
|
||||
#define AURIOL_AHFL_CONST_DATA 0b0100
|
||||
|
||||
typedef enum {
|
||||
auriol_AHFLDecoderStepReset = 0,
|
||||
auriol_AHFLDecoderStepSaveDuration,
|
||||
auriol_AHFLDecoderStepCheckDuration,
|
||||
} auriol_AHFLDecoderStep;
|
||||
|
||||
class FProtoWeatherAuriolAhfl : public FProtoWeatherBase {
|
||||
public:
|
||||
FProtoWeatherAuriolAhfl() {
|
||||
sensorType = FPW_AuriolAhfl;
|
||||
}
|
||||
|
||||
void feed(bool level, uint32_t duration) {
|
||||
switch (parser_step) {
|
||||
case auriol_AHFLDecoderStepReset:
|
||||
if ((!level) && (DURATION_DIFF(duration, te_short * 18) <
|
||||
te_delta)) {
|
||||
// Found syncPrefix
|
||||
parser_step = auriol_AHFLDecoderStepSaveDuration;
|
||||
decode_data = 0;
|
||||
decode_count_bit = 0;
|
||||
}
|
||||
break;
|
||||
case auriol_AHFLDecoderStepSaveDuration:
|
||||
if (level) {
|
||||
te_last = duration;
|
||||
parser_step = auriol_AHFLDecoderStepCheckDuration;
|
||||
} else {
|
||||
parser_step = auriol_AHFLDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
case auriol_AHFLDecoderStepCheckDuration:
|
||||
if (!level) {
|
||||
if (DURATION_DIFF(te_last, te_short) <
|
||||
te_delta) {
|
||||
if (DURATION_DIFF(duration, te_short * 18) <
|
||||
te_delta * 8) {
|
||||
// Found syncPostfix
|
||||
parser_step = auriol_AHFLDecoderStepReset;
|
||||
if ((decode_count_bit ==
|
||||
min_count_bit_for_found) &&
|
||||
ws_protocol_auriol_ahfl_check()) {
|
||||
data = decode_data;
|
||||
data_count_bit = decode_count_bit;
|
||||
ws_protocol_auriol_ahfl_remote_controller();
|
||||
if (callback) {
|
||||
callback(this);
|
||||
}
|
||||
} else if (decode_count_bit == 1) {
|
||||
parser_step = auriol_AHFLDecoderStepSaveDuration;
|
||||
}
|
||||
decode_data = 0;
|
||||
decode_count_bit = 0;
|
||||
} else if (
|
||||
DURATION_DIFF(duration, te_long) <
|
||||
te_delta * 2) {
|
||||
subghz_protocol_blocks_add_bit(0);
|
||||
parser_step = auriol_AHFLDecoderStepSaveDuration;
|
||||
} else if (
|
||||
DURATION_DIFF(duration, te_long * 2) <
|
||||
te_delta * 4) {
|
||||
subghz_protocol_blocks_add_bit(1);
|
||||
parser_step = auriol_AHFLDecoderStepSaveDuration;
|
||||
} else {
|
||||
parser_step = auriol_AHFLDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
parser_step = auriol_AHFLDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
parser_step = auriol_AHFLDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
uint32_t te_short = 500;
|
||||
uint32_t te_long = 2000;
|
||||
uint32_t te_delta = 150;
|
||||
uint32_t min_count_bit_for_found = 42;
|
||||
|
||||
void ws_protocol_auriol_ahfl_remote_controller() {
|
||||
id = data >> 34;
|
||||
battery_low = (data >> 33) & 1;
|
||||
btn = (data >> 32) & 1;
|
||||
channel = ((data >> 30) & 0x3) + 1;
|
||||
if (!((data >> 29) & 1)) {
|
||||
temp = (float)((data >> 18) & 0x07FF) / 10.0f;
|
||||
} else {
|
||||
temp = (float)((~(data >> 18) & 0x07FF) + 1) / -10.0f;
|
||||
}
|
||||
humidity = (data >> 11) & 0x7F;
|
||||
}
|
||||
|
||||
bool ws_protocol_auriol_ahfl_check() {
|
||||
uint8_t type = (decode_data >> 6) & 0x0F;
|
||||
if (type != AURIOL_AHFL_CONST_DATA) {
|
||||
// Fail const data check
|
||||
return false;
|
||||
}
|
||||
uint64_t payload = decode_data >> 6;
|
||||
// Checksum is the last 6 bits of data
|
||||
uint8_t checksum_received = decode_data & 0x3F;
|
||||
uint8_t checksum_calculated = 0;
|
||||
for (uint8_t i = 0; i < 9; i++) {
|
||||
checksum_calculated += (payload >> (i * 4)) & 0xF;
|
||||
}
|
||||
return checksum_received == checksum_calculated;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,115 @@
|
||||
|
||||
#ifndef __FPROTO_AuriolTH_H__
|
||||
#define __FPROTO_AuriolTH_H__
|
||||
|
||||
#include "weatherbase.hpp"
|
||||
|
||||
#define AURIOL_TH_CONST_DATA 0b1110
|
||||
|
||||
typedef enum {
|
||||
auriol_THDecoderStepReset = 0,
|
||||
auriol_THDecoderStepSaveDuration,
|
||||
auriol_THDecoderStepCheckDuration,
|
||||
} auriol_THDecoderStep;
|
||||
|
||||
class FProtoWeatherAuriolTh : public FProtoWeatherBase {
|
||||
public:
|
||||
FProtoWeatherAuriolTh() {
|
||||
sensorType = FPW_AuriolTH;
|
||||
}
|
||||
|
||||
void feed(bool level, uint32_t duration) {
|
||||
switch (parser_step) {
|
||||
case auriol_THDecoderStepReset:
|
||||
if ((!level) && (DURATION_DIFF(duration, te_short * 8) < te_delta)) {
|
||||
// Found sync
|
||||
parser_step = auriol_THDecoderStepSaveDuration;
|
||||
decode_data = 0;
|
||||
decode_count_bit = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case auriol_THDecoderStepSaveDuration:
|
||||
if (level) {
|
||||
te_last = duration;
|
||||
parser_step = auriol_THDecoderStepCheckDuration;
|
||||
} else {
|
||||
parser_step = auriol_THDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case auriol_THDecoderStepCheckDuration:
|
||||
if (!level) {
|
||||
if (DURATION_DIFF(duration, te_short * 8) <
|
||||
te_delta) {
|
||||
// Found sync
|
||||
parser_step = auriol_THDecoderStepReset;
|
||||
if ((decode_count_bit ==
|
||||
min_count_bit_for_found) &&
|
||||
ws_protocol_auriol_th_check()) {
|
||||
data = decode_data;
|
||||
data_count_bit = decode_count_bit;
|
||||
ws_protocol_auriol_th_remote_controller();
|
||||
if (callback) callback(this);
|
||||
parser_step = auriol_THDecoderStepCheckDuration;
|
||||
}
|
||||
decode_data = 0;
|
||||
decode_count_bit = 0;
|
||||
|
||||
break;
|
||||
} else if (
|
||||
(DURATION_DIFF(te_last, te_short) <
|
||||
te_delta) &&
|
||||
(DURATION_DIFF(duration, te_short * 2) <
|
||||
te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(0);
|
||||
parser_step = auriol_THDecoderStepSaveDuration;
|
||||
} else if (
|
||||
(DURATION_DIFF(te_last, te_short) <
|
||||
te_delta) &&
|
||||
(DURATION_DIFF(duration, te_short * 4) <
|
||||
te_delta * 2)) {
|
||||
subghz_protocol_blocks_add_bit(1);
|
||||
parser_step = auriol_THDecoderStepSaveDuration;
|
||||
} else {
|
||||
parser_step = auriol_THDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
parser_step = auriol_THDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
uint32_t te_short = 500;
|
||||
uint32_t te_long = 2000;
|
||||
uint32_t te_delta = 150;
|
||||
uint32_t min_count_bit_for_found = 37;
|
||||
|
||||
void ws_protocol_auriol_th_remote_controller() {
|
||||
id = (data >> 31) & 0xFF;
|
||||
battery_low = ((data >> 30) & 1);
|
||||
channel = ((data >> 25) & 0x03) + 1;
|
||||
btn = WS_NO_BTN;
|
||||
if (!((data >> 23) & 1)) {
|
||||
temp = (float)((data >> 13) & 0x07FF) / 10.0f;
|
||||
} else {
|
||||
temp = (float)((~(data >> 13) & 0x07FF) + 1) / -10.0f;
|
||||
}
|
||||
|
||||
humidity = (data >> 1) & 0x7F;
|
||||
}
|
||||
bool ws_protocol_auriol_th_check() {
|
||||
uint8_t type = (decode_data >> 8) & 0x0F;
|
||||
|
||||
if ((type == AURIOL_TH_CONST_DATA) && ((decode_data >> 4) != 0xffffffff)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,121 @@
|
||||
|
||||
#ifndef __FPROTO_GTWT02_H__
|
||||
#define __FPROTO_GTWT02_H__
|
||||
|
||||
#include "weatherbase.hpp"
|
||||
|
||||
#define AURIOL_AHFL_CONST_DATA 0b0100
|
||||
|
||||
typedef enum {
|
||||
GT_WT02DecoderStepReset = 0,
|
||||
GT_WT02DecoderStepSaveDuration,
|
||||
GT_WT02DecoderStepCheckDuration,
|
||||
} GT_WT02DecoderStep;
|
||||
|
||||
class FProtoWeatherGTWT02 : public FProtoWeatherBase {
|
||||
public:
|
||||
FProtoWeatherGTWT02() {
|
||||
sensorType = FPW_GTWT02;
|
||||
}
|
||||
|
||||
void feed(bool level, uint32_t duration) {
|
||||
switch (parser_step) {
|
||||
case GT_WT02DecoderStepReset:
|
||||
if ((!level) && (DURATION_DIFF(duration, te_short * 18) <
|
||||
te_delta * 8)) {
|
||||
// Found syncPrefix
|
||||
parser_step = GT_WT02DecoderStepSaveDuration;
|
||||
decode_data = 0;
|
||||
decode_count_bit = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case GT_WT02DecoderStepSaveDuration:
|
||||
if (level) {
|
||||
te_last = duration;
|
||||
parser_step = GT_WT02DecoderStepCheckDuration;
|
||||
} else {
|
||||
parser_step = GT_WT02DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case GT_WT02DecoderStepCheckDuration:
|
||||
if (!level) {
|
||||
if (DURATION_DIFF(te_last, te_short) <
|
||||
te_delta) {
|
||||
if (DURATION_DIFF(duration, te_short * 18) <
|
||||
te_delta * 8) {
|
||||
// Found syncPostfix
|
||||
parser_step = GT_WT02DecoderStepReset;
|
||||
if ((decode_count_bit ==
|
||||
min_count_bit_for_found) &&
|
||||
ws_protocol_gt_wt_02_check()) {
|
||||
data = decode_data;
|
||||
data_count_bit = decode_count_bit;
|
||||
ws_protocol_gt_wt_02_remote_controller();
|
||||
if (callback) callback(this);
|
||||
} else if (decode_count_bit == 1) {
|
||||
parser_step = GT_WT02DecoderStepSaveDuration;
|
||||
}
|
||||
decode_data = 0;
|
||||
decode_count_bit = 0;
|
||||
} else if (
|
||||
DURATION_DIFF(duration, te_long) <
|
||||
te_delta * 2) {
|
||||
subghz_protocol_blocks_add_bit(0);
|
||||
parser_step = GT_WT02DecoderStepSaveDuration;
|
||||
} else if (
|
||||
DURATION_DIFF(duration, te_long * 2) <
|
||||
te_delta * 4) {
|
||||
subghz_protocol_blocks_add_bit(1);
|
||||
parser_step = GT_WT02DecoderStepSaveDuration;
|
||||
} else {
|
||||
parser_step = GT_WT02DecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
parser_step = GT_WT02DecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
parser_step = GT_WT02DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
uint32_t te_short = 500;
|
||||
uint32_t te_long = 2000;
|
||||
uint32_t te_delta = 150;
|
||||
uint32_t min_count_bit_for_found = 37;
|
||||
|
||||
void ws_protocol_gt_wt_02_remote_controller() {
|
||||
id = (data >> 29) & 0xFF;
|
||||
battery_low = (data >> 28) & 1;
|
||||
btn = (data >> 27) & 1;
|
||||
channel = ((data >> 25) & 0x3) + 1;
|
||||
|
||||
if (!((data >> 24) & 1)) {
|
||||
temp = (float)((data >> 13) & 0x07FF) / 10.0f;
|
||||
} else {
|
||||
temp = (float)((~(data >> 13) & 0x07FF) + 1) / -10.0f;
|
||||
}
|
||||
|
||||
humidity = (data >> 6) & 0x7F;
|
||||
if (humidity <= 10) // actually the sensors sends 10 below working range of 20%
|
||||
humidity = 0;
|
||||
else if (humidity > 90) // actually the sensors sends 110 above working range of 90%
|
||||
humidity = 100;
|
||||
}
|
||||
|
||||
bool ws_protocol_gt_wt_02_check() {
|
||||
if (!decode_data) return false;
|
||||
uint8_t sum = (decode_data >> 5) & 0xe;
|
||||
uint64_t temp_data = decode_data >> 9;
|
||||
for (uint8_t i = 0; i < 7; i++) {
|
||||
sum += (temp_data >> (i * 4)) & 0xF;
|
||||
}
|
||||
return ((uint8_t)(decode_data & 0x3F) == (sum & 0x3F));
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,159 @@
|
||||
|
||||
#ifndef __FPROTO_GTWT03_H__
|
||||
#define __FPROTO_GTWT03_H__
|
||||
|
||||
#include "weatherbase.hpp"
|
||||
|
||||
#define AURIOL_AHFL_CONST_DATA 0b0100
|
||||
|
||||
typedef enum {
|
||||
GT_WT03DecoderStepReset = 0,
|
||||
GT_WT03DecoderStepCheckPreambule,
|
||||
GT_WT03DecoderStepSaveDuration,
|
||||
GT_WT03DecoderStepCheckDuration,
|
||||
} GT_WT03DecoderStep;
|
||||
|
||||
class FProtoWeatherGTWT03 : public FProtoWeatherBase {
|
||||
public:
|
||||
FProtoWeatherGTWT03() {
|
||||
sensorType = FPW_GTWT03;
|
||||
}
|
||||
|
||||
void feed(bool level, uint32_t duration) {
|
||||
switch (parser_step) {
|
||||
case GT_WT03DecoderStepReset:
|
||||
if ((level) && (DURATION_DIFF(duration, te_short * 3) <
|
||||
te_delta * 2)) {
|
||||
parser_step = GT_WT03DecoderStepCheckPreambule;
|
||||
te_last = duration;
|
||||
header_count = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case GT_WT03DecoderStepCheckPreambule:
|
||||
if (level) {
|
||||
te_last = duration;
|
||||
} else {
|
||||
if ((DURATION_DIFF(te_last, te_short * 3) < te_delta * 2) &&
|
||||
(DURATION_DIFF(duration, te_short * 3) < te_delta * 2)) {
|
||||
// Found preambule
|
||||
header_count++;
|
||||
} else if (header_count == 4) {
|
||||
if ((DURATION_DIFF(te_last, te_short) < te_delta) &&
|
||||
(DURATION_DIFF(duration, te_long) < te_delta)) {
|
||||
decode_data = 0;
|
||||
decode_count_bit = 0;
|
||||
subghz_protocol_blocks_add_bit(0);
|
||||
parser_step = GT_WT03DecoderStepSaveDuration;
|
||||
} else if (
|
||||
(DURATION_DIFF(te_last, te_long) < te_delta) &&
|
||||
(DURATION_DIFF(duration, te_short) < te_delta)) {
|
||||
decode_data = 0;
|
||||
decode_count_bit = 0;
|
||||
subghz_protocol_blocks_add_bit(1);
|
||||
parser_step = GT_WT03DecoderStepSaveDuration;
|
||||
} else {
|
||||
parser_step = GT_WT03DecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
parser_step = GT_WT03DecoderStepReset;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case GT_WT03DecoderStepSaveDuration:
|
||||
if (level) {
|
||||
te_last = duration;
|
||||
parser_step = GT_WT03DecoderStepCheckDuration;
|
||||
} else {
|
||||
parser_step = GT_WT03DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case GT_WT03DecoderStepCheckDuration:
|
||||
if (!level) {
|
||||
if (((DURATION_DIFF(te_last, te_short * 3) < te_delta * 2) &&
|
||||
(DURATION_DIFF(duration, te_short * 3) < te_delta * 2))) {
|
||||
if ((decode_count_bit ==
|
||||
min_count_bit_for_found) &&
|
||||
ws_protocol_gt_wt_03_check_crc()) {
|
||||
data = decode_data;
|
||||
data_count_bit = decode_count_bit;
|
||||
ws_protocol_gt_wt_03_remote_controller();
|
||||
if (callback) callback(this);
|
||||
}
|
||||
decode_data = 0;
|
||||
decode_count_bit = 0;
|
||||
header_count = 1;
|
||||
parser_step = GT_WT03DecoderStepCheckPreambule;
|
||||
break;
|
||||
} else if (
|
||||
(DURATION_DIFF(te_last, te_short) < te_delta) &&
|
||||
(DURATION_DIFF(duration, te_long) < te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(0);
|
||||
parser_step = GT_WT03DecoderStepSaveDuration;
|
||||
} else if (
|
||||
(DURATION_DIFF(te_last, te_long) < te_delta) &&
|
||||
(DURATION_DIFF(duration, te_short) < te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(1);
|
||||
parser_step = GT_WT03DecoderStepSaveDuration;
|
||||
} else {
|
||||
parser_step = GT_WT03DecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
parser_step = GT_WT03DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
uint32_t te_short = 285;
|
||||
uint32_t te_long = 570;
|
||||
uint32_t te_delta = 120;
|
||||
uint32_t min_count_bit_for_found = 41;
|
||||
|
||||
void ws_protocol_gt_wt_03_remote_controller() {
|
||||
id = data >> 33;
|
||||
humidity = (data >> 25) & 0xFF;
|
||||
|
||||
if (humidity <= 10) { // actually the sensors sends 10 below working range of 20%
|
||||
humidity = 0;
|
||||
} else if (humidity > 95) { // actually the sensors sends 110 above working range of 90%
|
||||
humidity = 100;
|
||||
}
|
||||
|
||||
battery_low = (data >> 24) & 1;
|
||||
btn = (data >> 23) & 1;
|
||||
channel = ((data >> 21) & 0x03) + 1;
|
||||
|
||||
if (!((data >> 20) & 1)) {
|
||||
temp = (float)((data >> 9) & 0x07FF) / 10.0f;
|
||||
} else {
|
||||
temp = (float)((~(data >> 9) & 0x07FF) + 1) / -10.0f;
|
||||
}
|
||||
}
|
||||
|
||||
bool ws_protocol_gt_wt_03_check_crc() {
|
||||
uint8_t msg[] = {
|
||||
static_cast<uint8_t>(decode_data >> 33),
|
||||
static_cast<uint8_t>(decode_data >> 25),
|
||||
static_cast<uint8_t>(decode_data >> 17),
|
||||
static_cast<uint8_t>(decode_data >> 9)};
|
||||
|
||||
uint8_t sum = 0;
|
||||
for (unsigned k = 0; k < sizeof(msg); ++k) {
|
||||
uint8_t data = msg[k];
|
||||
uint16_t key = 0x3100;
|
||||
for (int i = 7; i >= 0; --i) {
|
||||
// XOR key into sum if data bit is set
|
||||
if ((data >> i) & 1) sum ^= key & 0xff;
|
||||
// roll the key right
|
||||
key = (key >> 1);
|
||||
}
|
||||
}
|
||||
return ((sum ^ (uint8_t)((decode_data >> 1) & 0xFF)) == 0x2D);
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,138 @@
|
||||
|
||||
#ifndef __FPROTO_Infactory_H__
|
||||
#define __FPROTO_Infactory_H__
|
||||
|
||||
#include "weatherbase.hpp"
|
||||
|
||||
typedef enum {
|
||||
InfactoryDecoderStepReset = 0,
|
||||
InfactoryDecoderStepCheckPreambule,
|
||||
InfactoryDecoderStepSaveDuration,
|
||||
InfactoryDecoderStepCheckDuration,
|
||||
} InfactoryDecoderStep;
|
||||
|
||||
class FProtoWeatherInfactory : public FProtoWeatherBase {
|
||||
public:
|
||||
FProtoWeatherInfactory() {
|
||||
sensorType = FPW_INFACTORY;
|
||||
}
|
||||
|
||||
void feed(bool level, uint32_t duration) {
|
||||
switch (parser_step) {
|
||||
case InfactoryDecoderStepReset:
|
||||
if ((level) && (DURATION_DIFF(duration, te_short * 2) <
|
||||
te_delta * 2)) {
|
||||
parser_step = InfactoryDecoderStepCheckPreambule;
|
||||
te_last = duration;
|
||||
header_count = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case InfactoryDecoderStepCheckPreambule:
|
||||
if (level) {
|
||||
te_last = duration;
|
||||
} else {
|
||||
if ((DURATION_DIFF(te_last, te_short * 2) <
|
||||
te_delta * 2) &&
|
||||
(DURATION_DIFF(duration, te_short * 2) <
|
||||
te_delta * 2)) {
|
||||
// Found preambule
|
||||
header_count++;
|
||||
} else if (
|
||||
(DURATION_DIFF(te_last, te_short) <
|
||||
te_delta) &&
|
||||
(DURATION_DIFF(duration, te_short * 16) <
|
||||
te_delta * 8)) {
|
||||
// Found syncPrefix
|
||||
if (header_count > 3) {
|
||||
parser_step = InfactoryDecoderStepSaveDuration;
|
||||
decode_data = 0;
|
||||
decode_count_bit = 0;
|
||||
}
|
||||
} else {
|
||||
parser_step = InfactoryDecoderStepReset;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case InfactoryDecoderStepSaveDuration:
|
||||
if (level) {
|
||||
te_last = duration;
|
||||
parser_step = InfactoryDecoderStepCheckDuration;
|
||||
} else {
|
||||
parser_step = InfactoryDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case InfactoryDecoderStepCheckDuration:
|
||||
if (!level) {
|
||||
if (duration >= ((uint32_t)te_short * 30)) {
|
||||
// Found syncPostfix
|
||||
if ((decode_count_bit ==
|
||||
min_count_bit_for_found) &&
|
||||
ws_protocol_infactory_check_crc()) {
|
||||
data = decode_data;
|
||||
data_count_bit = decode_count_bit;
|
||||
ws_protocol_infactory_remote_controller();
|
||||
if (callback) callback(this);
|
||||
}
|
||||
decode_data = 0;
|
||||
decode_count_bit = 0;
|
||||
parser_step = InfactoryDecoderStepReset;
|
||||
break;
|
||||
} else if (
|
||||
(DURATION_DIFF(te_last, te_short) <
|
||||
te_delta) &&
|
||||
(DURATION_DIFF(duration, te_long) <
|
||||
te_delta * 2)) {
|
||||
subghz_protocol_blocks_add_bit(0);
|
||||
parser_step = InfactoryDecoderStepSaveDuration;
|
||||
} else if (
|
||||
(DURATION_DIFF(te_last, te_short) <
|
||||
te_delta) &&
|
||||
(DURATION_DIFF(duration, te_long * 2) <
|
||||
te_delta * 4)) {
|
||||
subghz_protocol_blocks_add_bit(1);
|
||||
parser_step = InfactoryDecoderStepSaveDuration;
|
||||
} else {
|
||||
parser_step = InfactoryDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
parser_step = InfactoryDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
uint32_t te_short = 500;
|
||||
uint32_t te_long = 2000;
|
||||
uint32_t te_delta = 150;
|
||||
uint32_t min_count_bit_for_found = 40;
|
||||
|
||||
bool ws_protocol_infactory_check_crc() {
|
||||
uint8_t msg[] = {
|
||||
static_cast<uint8_t>(decode_data >> 32),
|
||||
static_cast<uint8_t>(((decode_data >> 24) & 0x0F) | (decode_data & 0x0F) << 4),
|
||||
static_cast<uint8_t>(decode_data >> 16),
|
||||
static_cast<uint8_t>(decode_data >> 8),
|
||||
static_cast<uint8_t>(decode_data)};
|
||||
|
||||
uint8_t crc =
|
||||
subghz_protocol_blocks_crc4(msg, 4, 0x13, 0); // Koopmann 0x9, CCITT-4; FP-4; ITU-T G.704
|
||||
crc ^= msg[4] >> 4; // last nibble is only XORed
|
||||
return (crc == ((decode_data >> 28) & 0x0F));
|
||||
}
|
||||
void ws_protocol_infactory_remote_controller() {
|
||||
id = data >> 32;
|
||||
battery_low = (data >> 26) & 1;
|
||||
btn = WS_NO_BTN;
|
||||
temp =
|
||||
locale_fahrenheit_to_celsius(((float)((data >> 12) & 0x0FFF) - 900.0f) / 10.0f);
|
||||
humidity =
|
||||
(((data >> 8) & 0x0F) * 10) + ((data >> 4) & 0x0F); // BCD, 'A0'=100%rH
|
||||
channel = data & 0x03;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,163 @@
|
||||
|
||||
#ifndef __FPROTO_LACROSSE_TX_H__
|
||||
#define __FPROTO_LACROSSE_TX_H__
|
||||
|
||||
#include "weatherbase.hpp"
|
||||
|
||||
#define LACROSSE_TX_GAP 1000
|
||||
#define LACROSSE_TX_BIT_SIZE 44
|
||||
#define LACROSSE_TX_SUNC_PATTERN 0x0A000000000
|
||||
#define LACROSSE_TX_SUNC_MASK 0x0F000000000
|
||||
#define LACROSSE_TX_MSG_TYPE_TEMP 0x00
|
||||
#define LACROSSE_TX_MSG_TYPE_HUM 0x0E
|
||||
typedef enum {
|
||||
LaCrosse_TXDecoderStepReset = 0,
|
||||
LaCrosse_TXDecoderStepCheckPreambule,
|
||||
LaCrosse_TXDecoderStepSaveDuration,
|
||||
LaCrosse_TXDecoderStepCheckDuration,
|
||||
} LaCrosse_TXDecoderStep;
|
||||
|
||||
class FProtoWeatherLaCrosseTx : public FProtoWeatherBase {
|
||||
public:
|
||||
FProtoWeatherLaCrosseTx() {
|
||||
sensorType = FPW_LACROSSETX;
|
||||
}
|
||||
|
||||
void feed(bool level, uint32_t duration) {
|
||||
switch (parser_step) {
|
||||
case LaCrosse_TXDecoderStepReset:
|
||||
if ((!level) && (DURATION_DIFF(duration, LACROSSE_TX_GAP) < te_delta * 2)) {
|
||||
parser_step = LaCrosse_TXDecoderStepCheckPreambule;
|
||||
header_count = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case LaCrosse_TXDecoderStepCheckPreambule:
|
||||
|
||||
if (level) {
|
||||
if ((DURATION_DIFF(duration, te_short) < te_delta) &&
|
||||
(header_count > 1)) {
|
||||
parser_step = LaCrosse_TXDecoderStepCheckDuration;
|
||||
decode_data = 0;
|
||||
decode_count_bit = 0;
|
||||
te_last = duration;
|
||||
} else if (duration > (te_long * 2)) {
|
||||
parser_step = LaCrosse_TXDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
if (DURATION_DIFF(duration, LACROSSE_TX_GAP) < te_delta * 2) {
|
||||
te_last = duration;
|
||||
header_count++;
|
||||
} else {
|
||||
parser_step = LaCrosse_TXDecoderStepReset;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case LaCrosse_TXDecoderStepSaveDuration:
|
||||
if (level) {
|
||||
te_last = duration;
|
||||
parser_step = LaCrosse_TXDecoderStepCheckDuration;
|
||||
} else {
|
||||
parser_step = LaCrosse_TXDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case LaCrosse_TXDecoderStepCheckDuration:
|
||||
|
||||
if (!level) {
|
||||
if (duration > LACROSSE_TX_GAP * 3) {
|
||||
if (DURATION_DIFF(te_last, te_short) < te_delta) {
|
||||
subghz_protocol_blocks_add_bit(1);
|
||||
parser_step = LaCrosse_TXDecoderStepSaveDuration;
|
||||
} else if (
|
||||
DURATION_DIFF(te_last, te_long) < te_delta) {
|
||||
subghz_protocol_blocks_add_bit(0);
|
||||
parser_step = LaCrosse_TXDecoderStepSaveDuration;
|
||||
}
|
||||
if ((decode_data & LACROSSE_TX_SUNC_MASK) ==
|
||||
LACROSSE_TX_SUNC_PATTERN) {
|
||||
if (ws_protocol_lacrosse_tx_check_crc()) {
|
||||
data = decode_data;
|
||||
data_count_bit = LACROSSE_TX_BIT_SIZE;
|
||||
ws_protocol_lacrosse_tx_remote_controller();
|
||||
if (callback) callback(this);
|
||||
}
|
||||
}
|
||||
|
||||
decode_data = 0;
|
||||
decode_count_bit = 0;
|
||||
header_count = 0;
|
||||
parser_step = LaCrosse_TXDecoderStepReset;
|
||||
break;
|
||||
} else if (
|
||||
(DURATION_DIFF(te_last, te_short) < te_delta) &&
|
||||
(DURATION_DIFF(duration, LACROSSE_TX_GAP) < te_delta * 2)) {
|
||||
subghz_protocol_blocks_add_bit(1);
|
||||
parser_step = LaCrosse_TXDecoderStepSaveDuration;
|
||||
} else if (
|
||||
(DURATION_DIFF(te_last, te_long) < te_delta) &&
|
||||
(DURATION_DIFF(duration, LACROSSE_TX_GAP) < te_delta * 2)) {
|
||||
subghz_protocol_blocks_add_bit(0);
|
||||
parser_step = LaCrosse_TXDecoderStepSaveDuration;
|
||||
} else {
|
||||
parser_step = LaCrosse_TXDecoderStepReset;
|
||||
}
|
||||
|
||||
} else {
|
||||
parser_step = LaCrosse_TXDecoderStepReset;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
uint32_t te_short = 550;
|
||||
uint32_t te_long = 1300;
|
||||
uint32_t te_delta = 120;
|
||||
uint32_t min_count_bit_for_found = 40;
|
||||
|
||||
void ws_protocol_lacrosse_tx_remote_controller() {
|
||||
uint8_t msg_type = (data >> 32) & 0x0F;
|
||||
id = (((data >> 28) & 0x0F) << 3) | (((data >> 24) & 0x0F) >> 1);
|
||||
|
||||
float msg_value = (float)((data >> 20) & 0x0F) * 10.0f +
|
||||
(float)((data >> 16) & 0x0F) +
|
||||
(float)((data >> 12) & 0x0F) * 0.1f;
|
||||
|
||||
if (msg_type == LACROSSE_TX_MSG_TYPE_TEMP) { //-V1051
|
||||
temp = msg_value - 50.0f;
|
||||
humidity = WS_NO_HUMIDITY;
|
||||
} else if (msg_type == LACROSSE_TX_MSG_TYPE_HUM) {
|
||||
// ToDo for verification, records are needed with sensors maintaining temperature and temperature for this standard
|
||||
humidity = (uint8_t)msg_value;
|
||||
} else {
|
||||
// furi_crash("WS: WSProtocolLaCrosse_TX incorrect msg_type.");
|
||||
}
|
||||
|
||||
btn = WS_NO_BTN;
|
||||
battery_low = WS_NO_BATT;
|
||||
channel = WS_NO_CHANNEL;
|
||||
}
|
||||
|
||||
bool ws_protocol_lacrosse_tx_check_crc() {
|
||||
if (!decode_data) return false;
|
||||
uint8_t msg[] = {
|
||||
static_cast<uint8_t>((decode_data >> 36) & 0x0F),
|
||||
static_cast<uint8_t>((decode_data >> 32) & 0x0F),
|
||||
static_cast<uint8_t>((decode_data >> 28) & 0x0F),
|
||||
static_cast<uint8_t>((decode_data >> 24) & 0x0F),
|
||||
static_cast<uint8_t>((decode_data >> 20) & 0x0F),
|
||||
static_cast<uint8_t>((decode_data >> 16) & 0x0F),
|
||||
static_cast<uint8_t>((decode_data >> 12) & 0x0F),
|
||||
static_cast<uint8_t>((decode_data >> 8) & 0x0F),
|
||||
static_cast<uint8_t>((decode_data >> 4) & 0x0F)};
|
||||
|
||||
uint8_t crc = subghz_protocol_blocks_add_bytes(msg, 9);
|
||||
return ((crc & 0x0F) == (decode_data & 0x0F));
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,135 @@
|
||||
|
||||
#ifndef __FPROTO_LACROSSE_TX141thbv2_H__
|
||||
#define __FPROTO_LACROSSE_TX141thbv2_H__
|
||||
|
||||
#include "weatherbase.hpp"
|
||||
|
||||
#define LACROSSE_TX141TH_BV2_BIT_COUNT 41
|
||||
|
||||
typedef enum {
|
||||
LaCrosse_TX141THBv2DecoderStepReset = 0,
|
||||
LaCrosse_TX141THBv2DecoderStepCheckPreambule,
|
||||
LaCrosse_TX141THBv2DecoderStepSaveDuration,
|
||||
LaCrosse_TX141THBv2DecoderStepCheckDuration,
|
||||
} LaCrosse_TX141THBv2DecoderStep;
|
||||
|
||||
class FProtoWeatherLaCrosseTx141thbv2 : public FProtoWeatherBase {
|
||||
public:
|
||||
FProtoWeatherLaCrosseTx141thbv2() {
|
||||
sensorType = FPW_LACROSSETX141thbv2;
|
||||
}
|
||||
|
||||
void feed(bool level, uint32_t duration) {
|
||||
switch (parser_step) {
|
||||
case LaCrosse_TX141THBv2DecoderStepReset:
|
||||
if ((level) && (DURATION_DIFF(duration, te_short * 4) < te_delta * 2)) {
|
||||
parser_step = LaCrosse_TX141THBv2DecoderStepCheckPreambule;
|
||||
te_last = duration;
|
||||
header_count = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case LaCrosse_TX141THBv2DecoderStepCheckPreambule:
|
||||
if (level) {
|
||||
te_last = duration;
|
||||
} else {
|
||||
if ((DURATION_DIFF(te_last, te_short * 4) < te_delta * 2) &&
|
||||
(DURATION_DIFF(duration, te_short * 4) < te_delta * 2)) {
|
||||
// Found preambule
|
||||
header_count++;
|
||||
} else if (header_count == 4) {
|
||||
if (ws_protocol_decoder_lacrosse_tx141thbv2_add_bit(te_last, duration)) {
|
||||
decode_data = decode_data & 1;
|
||||
decode_count_bit = 1;
|
||||
parser_step = LaCrosse_TX141THBv2DecoderStepSaveDuration;
|
||||
} else {
|
||||
parser_step = LaCrosse_TX141THBv2DecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
parser_step = LaCrosse_TX141THBv2DecoderStepReset;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case LaCrosse_TX141THBv2DecoderStepSaveDuration:
|
||||
if (level) {
|
||||
te_last = duration;
|
||||
parser_step = LaCrosse_TX141THBv2DecoderStepCheckDuration;
|
||||
} else {
|
||||
parser_step = LaCrosse_TX141THBv2DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case LaCrosse_TX141THBv2DecoderStepCheckDuration:
|
||||
if (!level) {
|
||||
if (((DURATION_DIFF(te_last, te_short * 3) < te_delta * 2) &&
|
||||
(DURATION_DIFF(duration, te_short * 4) < te_delta * 2))) {
|
||||
if ((decode_count_bit == min_count_bit_for_found) ||
|
||||
(decode_count_bit == LACROSSE_TX141TH_BV2_BIT_COUNT)) {
|
||||
if (ws_protocol_lacrosse_tx141thbv2_check_crc()) {
|
||||
data = decode_data;
|
||||
data_count_bit = decode_count_bit;
|
||||
ws_protocol_lacrosse_tx141thbv2_remote_controller();
|
||||
if (callback) callback(this);
|
||||
}
|
||||
decode_data = 0;
|
||||
decode_count_bit = 0;
|
||||
header_count = 1;
|
||||
parser_step = LaCrosse_TX141THBv2DecoderStepCheckPreambule;
|
||||
break;
|
||||
}
|
||||
} else if (ws_protocol_decoder_lacrosse_tx141thbv2_add_bit(te_last, duration)) {
|
||||
parser_step = LaCrosse_TX141THBv2DecoderStepSaveDuration;
|
||||
} else {
|
||||
parser_step = LaCrosse_TX141THBv2DecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
parser_step = LaCrosse_TX141THBv2DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
uint32_t te_short = 208;
|
||||
uint32_t te_long = 417;
|
||||
uint32_t te_delta = 120;
|
||||
uint32_t min_count_bit_for_found = 40;
|
||||
|
||||
bool ws_protocol_lacrosse_tx141thbv2_check_crc() {
|
||||
if (!decode_data) return false;
|
||||
uint64_t data = decode_data;
|
||||
if (decode_count_bit == LACROSSE_TX141TH_BV2_BIT_COUNT) {
|
||||
data >>= 1;
|
||||
}
|
||||
uint8_t msg[] = {static_cast<uint8_t>(data >> 32), static_cast<uint8_t>(data >> 24), static_cast<uint8_t>(data >> 16), static_cast<uint8_t>(data >> 8)};
|
||||
|
||||
uint8_t crc = subghz_protocol_blocks_lfsr_digest8_reflect(msg, 4, 0x31, 0xF4);
|
||||
return (crc == (data & 0xFF));
|
||||
}
|
||||
void ws_protocol_lacrosse_tx141thbv2_remote_controller() {
|
||||
if (data_count_bit == LACROSSE_TX141TH_BV2_BIT_COUNT) {
|
||||
data >>= 1;
|
||||
}
|
||||
id = data >> 32;
|
||||
battery_low = (data >> 31) & 1;
|
||||
btn = (data >> 30) & 1;
|
||||
channel = ((data >> 28) & 0x03) + 1;
|
||||
temp = ((float)((data >> 16) & 0x0FFF) - 500.0f) / 10.0f;
|
||||
humidity = (data >> 8) & 0xFF;
|
||||
}
|
||||
bool ws_protocol_decoder_lacrosse_tx141thbv2_add_bit(uint32_t te_last, uint32_t te_current) {
|
||||
bool ret = false;
|
||||
if (DURATION_DIFF(te_last + te_current, te_short + te_long) < te_delta * 2) {
|
||||
if (te_last > te_current) {
|
||||
subghz_protocol_blocks_add_bit(1);
|
||||
} else {
|
||||
subghz_protocol_blocks_add_bit(0);
|
||||
}
|
||||
ret = true;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,121 @@
|
||||
|
||||
#ifndef __FPROTO_NEXUSTH_H__
|
||||
#define __FPROTO_NEXUSTH_H__
|
||||
|
||||
#include "weatherbase.hpp"
|
||||
|
||||
// For the state machine
|
||||
typedef enum {
|
||||
Nexus_THDecoderStepReset = 0,
|
||||
Nexus_THDecoderStepSaveDuration,
|
||||
Nexus_THDecoderStepCheckDuration,
|
||||
} Nexus_THDecoderStep;
|
||||
|
||||
// we will look for this value
|
||||
#define NEXUS_TH_CONST_DATA 0b1111
|
||||
|
||||
class FProtoWeatherNexusTH : public FProtoWeatherBase {
|
||||
public:
|
||||
FProtoWeatherNexusTH() {
|
||||
// must set it's value from the "weathertypes.hpp". getWeatherSensorTypeName() will work with this.
|
||||
sensorType = FPW_NexusTH;
|
||||
}
|
||||
|
||||
// Here we will got a level and duration. eg HIGH (true) for 500. This function must be as fast as possible, to keep the core happy.
|
||||
// From this function we will call the "callback", that is responsible for transmitting the parsed data to the ui. Before calling it, we must populate the base class's variables, like temp, humidity, ...
|
||||
void feed(bool level, uint32_t duration) override {
|
||||
switch (parser_step) {
|
||||
case Nexus_THDecoderStepReset:
|
||||
if ((!level) && (DURATION_DIFF(duration, te_short * 8) <
|
||||
te_delta * 4)) {
|
||||
// Found sync
|
||||
parser_step = Nexus_THDecoderStepSaveDuration;
|
||||
decode_data = 0;
|
||||
decode_count_bit = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case Nexus_THDecoderStepSaveDuration:
|
||||
if (level) {
|
||||
te_last = duration;
|
||||
parser_step = Nexus_THDecoderStepCheckDuration;
|
||||
} else {
|
||||
parser_step = Nexus_THDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case Nexus_THDecoderStepCheckDuration:
|
||||
if (!level) {
|
||||
if (DURATION_DIFF(duration, te_short * 8) < te_delta * 4) {
|
||||
// Found sync
|
||||
parser_step = Nexus_THDecoderStepReset;
|
||||
if ((decode_count_bit == min_count_bit_for_found) &&
|
||||
ws_protocol_nexus_th_check()) {
|
||||
data = decode_data;
|
||||
data_count_bit = decode_count_bit;
|
||||
ws_protocol_nexus_th_remote_controller();
|
||||
if (callback) callback((FProtoWeatherBase*)this);
|
||||
parser_step = Nexus_THDecoderStepCheckDuration;
|
||||
}
|
||||
decode_data = 0;
|
||||
decode_count_bit = 0;
|
||||
|
||||
break;
|
||||
} else if ((DURATION_DIFF(te_last, te_short) <
|
||||
te_delta) &&
|
||||
(DURATION_DIFF(duration, te_short * 2) < te_delta * 2)) {
|
||||
subghz_protocol_blocks_add_bit(0);
|
||||
parser_step = Nexus_THDecoderStepSaveDuration;
|
||||
} else if (
|
||||
(DURATION_DIFF(te_last, te_short) < te_delta) &&
|
||||
(DURATION_DIFF(duration, te_short * 4) < te_delta * 4)) {
|
||||
subghz_protocol_blocks_add_bit(1);
|
||||
parser_step = Nexus_THDecoderStepSaveDuration;
|
||||
} else {
|
||||
parser_step = Nexus_THDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
parser_step = Nexus_THDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
// timing values
|
||||
uint32_t te_short = 490;
|
||||
uint32_t te_long = 1980;
|
||||
uint32_t te_delta = 150;
|
||||
uint32_t min_count_bit_for_found = 36;
|
||||
|
||||
// sanity check
|
||||
bool ws_protocol_nexus_th_check() {
|
||||
uint8_t type = (decode_data >> 8) & 0x0F;
|
||||
if ((type == NEXUS_TH_CONST_DATA) && ((decode_data >> 4) != 0xffffffff)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// fill the base class's variables before calling the callback
|
||||
void ws_protocol_nexus_th_remote_controller() {
|
||||
id = (data >> 28) & 0xFF;
|
||||
battery_low = !((data >> 27) & 1);
|
||||
channel = ((data >> 24) & 0x03) + 1;
|
||||
btn = WS_NO_BTN;
|
||||
if (!((data >> 23) & 1)) {
|
||||
temp = (float)((data >> 12) & 0x07FF) / 10.0f;
|
||||
} else {
|
||||
temp = (float)((~(data >> 12) & 0x07FF) + 1) / -10.0f;
|
||||
}
|
||||
humidity = data & 0xFF;
|
||||
if (humidity > 95)
|
||||
humidity = 95;
|
||||
else if (humidity < 20)
|
||||
humidity = 20;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,248 @@
|
||||
|
||||
#ifndef __FPROTO_OREGON2_H__
|
||||
#define __FPROTO_OREGON2_H__
|
||||
|
||||
#include "weatherbase.hpp"
|
||||
|
||||
#define OREGON2_PREAMBLE_BITS 19
|
||||
#define OREGON2_PREAMBLE_MASK 0b1111111111111111111
|
||||
#define OREGON2_SENSOR_ID(d) (((d) >> 16) & 0xFFFF)
|
||||
#define OREGON2_CHECKSUM_BITS 8
|
||||
|
||||
// 15 ones + 0101 (inverted A)
|
||||
#define OREGON2_PREAMBLE 0b1111111111111110101
|
||||
|
||||
// bit indicating the low battery
|
||||
#define OREGON2_FLAG_BAT_LOW 0x4
|
||||
|
||||
/// Documentation for Oregon Scientific protocols can be found here:
|
||||
/// http://wmrx00.sourceforge.net/Arduino/OregonScientific-RF-Protocols.pdf
|
||||
// Sensors ID
|
||||
#define ID_THGR122N 0x1d20
|
||||
#define ID_THGR968 0x1d30
|
||||
#define ID_BTHR918 0x5d50
|
||||
#define ID_BHTR968 0x5d60
|
||||
#define ID_RGR968 0x2d10
|
||||
#define ID_THR228N 0xec40
|
||||
#define ID_THN132N 0xec40 // same as THR228N but different packet size
|
||||
#define ID_RTGN318 0x0cc3 // warning: id is from 0x0cc3 and 0xfcc3
|
||||
#define ID_RTGN129 0x0cc3 // same as RTGN318 but different packet size
|
||||
#define ID_THGR810 0xf824 // This might be ID_THGR81, but what's true is lost in (git) history
|
||||
#define ID_THGR810a 0xf8b4 // unconfirmed version
|
||||
#define ID_THN802 0xc844
|
||||
#define ID_PCR800 0x2914
|
||||
#define ID_PCR800a 0x2d14 // Different PCR800 ID - AU version I think
|
||||
#define ID_WGR800 0x1984
|
||||
#define ID_WGR800a 0x1994 // unconfirmed version
|
||||
#define ID_WGR968 0x3d00
|
||||
#define ID_UV800 0xd874
|
||||
#define ID_THN129 0xcc43 // THN129 Temp only
|
||||
#define ID_RTHN129 0x0cd3 // RTHN129 Temp, clock sensors
|
||||
#define ID_RTHN129_1 0x9cd3
|
||||
#define ID_RTHN129_2 0xacd3
|
||||
#define ID_RTHN129_3 0xbcd3
|
||||
#define ID_RTHN129_4 0xccd3
|
||||
#define ID_RTHN129_5 0xdcd3
|
||||
#define ID_BTHGN129 0x5d53 // Baro, Temp, Hygro sensor
|
||||
#define ID_UVR128 0xec70
|
||||
#define ID_THGR328N 0xcc23 // Temp & Hygro sensor similar to THR228N with 5 channel instead of 3
|
||||
#define ID_RTGR328N_1 0xdcc3 // RTGR328N_[1-5] RFclock(date &time)&Temp&Hygro sensor
|
||||
#define ID_RTGR328N_2 0xccc3
|
||||
#define ID_RTGR328N_3 0xbcc3
|
||||
#define ID_RTGR328N_4 0xacc3
|
||||
#define ID_RTGR328N_5 0x9cc3
|
||||
#define ID_RTGR328N_6 0x8ce3 // RTGR328N_6&7 RFclock(date &time)&Temp&Hygro sensor like THGR328N
|
||||
#define ID_RTGR328N_7 0x8ae3
|
||||
typedef enum {
|
||||
Oregon2DecoderStepReset = 0,
|
||||
Oregon2DecoderStepFoundPreamble,
|
||||
Oregon2DecoderStepVarData,
|
||||
} Oregon2DecoderStep;
|
||||
|
||||
class FProtoWeatherOregon2 : public FProtoWeatherBase {
|
||||
public:
|
||||
FProtoWeatherOregon2() {
|
||||
sensorType = FPW_OREGON2;
|
||||
}
|
||||
|
||||
void feed(bool level, uint32_t duration) override {
|
||||
// oregon v2.1 signal is inverted
|
||||
ManchesterEvent event = level_and_duration_to_event(!level, duration);
|
||||
bool data;
|
||||
|
||||
// low-level bit sequence decoding
|
||||
if (event == ManchesterEventReset) {
|
||||
parser_step = Oregon2DecoderStepReset;
|
||||
have_bit = false;
|
||||
decode_data = 0UL;
|
||||
decode_count_bit = 0;
|
||||
}
|
||||
if (manchester_advance(manchester_saved_state, event, &manchester_saved_state, &data)) {
|
||||
if (have_bit) {
|
||||
if (!prev_bit && data) {
|
||||
subghz_protocol_blocks_add_bit(1);
|
||||
} else if (prev_bit && !data) {
|
||||
subghz_protocol_blocks_add_bit(0);
|
||||
} else {
|
||||
ws_protocol_decoder_oregon2_reset();
|
||||
}
|
||||
have_bit = false;
|
||||
} else {
|
||||
prev_bit = data;
|
||||
have_bit = true;
|
||||
}
|
||||
}
|
||||
|
||||
switch (parser_step) {
|
||||
case Oregon2DecoderStepReset:
|
||||
// waiting for fixed oregon2 preamble
|
||||
if (decode_count_bit >= OREGON2_PREAMBLE_BITS &&
|
||||
((decode_data & OREGON2_PREAMBLE_MASK) == OREGON2_PREAMBLE)) {
|
||||
parser_step = Oregon2DecoderStepFoundPreamble;
|
||||
decode_count_bit = 0;
|
||||
decode_data = 0UL;
|
||||
}
|
||||
break;
|
||||
case Oregon2DecoderStepFoundPreamble:
|
||||
// waiting for fixed oregon2 data
|
||||
if (decode_count_bit == 32) {
|
||||
data = decode_data;
|
||||
data_count_bit = decode_count_bit;
|
||||
decode_data = 0UL;
|
||||
decode_count_bit = 0;
|
||||
|
||||
// reverse nibbles in decoded data
|
||||
data = (data & 0x55555555) << 1 |
|
||||
(data & 0xAAAAAAAA) >> 1;
|
||||
data = (data & 0x33333333) << 2 |
|
||||
(data & 0xCCCCCCCC) >> 2;
|
||||
|
||||
ws_oregon2_decode_const_data();
|
||||
var_bits =
|
||||
oregon2_sensor_id_var_bits(OREGON2_SENSOR_ID(data));
|
||||
|
||||
if (!var_bits) {
|
||||
// sensor is not supported, stop decoding, but showing the decoded fixed part
|
||||
parser_step = Oregon2DecoderStepReset;
|
||||
if (callback) callback(this);
|
||||
} else {
|
||||
parser_step = Oregon2DecoderStepVarData;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Oregon2DecoderStepVarData:
|
||||
// waiting for variable (sensor-specific data)
|
||||
if (decode_count_bit == (uint32_t)var_bits + OREGON2_CHECKSUM_BITS) {
|
||||
var_data = decode_data & 0xFFFFFFFF;
|
||||
|
||||
// reverse nibbles in var data
|
||||
var_data = (var_data & 0x55555555) << 1 |
|
||||
(var_data & 0xAAAAAAAA) >> 1;
|
||||
var_data = (var_data & 0x33333333) << 2 |
|
||||
(var_data & 0xCCCCCCCC) >> 2;
|
||||
|
||||
ws_oregon2_decode_var_data(OREGON2_SENSOR_ID(data), var_data >> OREGON2_CHECKSUM_BITS);
|
||||
|
||||
parser_step = Oregon2DecoderStepReset;
|
||||
if (callback) callback(this);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
// timing values
|
||||
uint32_t te_short = 500;
|
||||
uint32_t te_long = 1000;
|
||||
uint32_t te_delta = 200;
|
||||
uint32_t min_count_bit_for_found = 32;
|
||||
|
||||
bool have_bit = false;
|
||||
bool prev_bit = 0;
|
||||
uint8_t var_bits{0};
|
||||
uint32_t var_data{0};
|
||||
|
||||
void ws_protocol_decoder_oregon2_reset() {
|
||||
parser_step = Oregon2DecoderStepReset;
|
||||
decode_data = 0UL;
|
||||
decode_count_bit = 0;
|
||||
manchester_advance(manchester_saved_state, ManchesterEventReset, &manchester_saved_state, NULL);
|
||||
have_bit = false;
|
||||
var_data = 0;
|
||||
var_bits = 0;
|
||||
}
|
||||
|
||||
ManchesterEvent level_and_duration_to_event(bool level, uint32_t duration) {
|
||||
bool is_long = false;
|
||||
if (DURATION_DIFF(duration, te_long) < te_delta) {
|
||||
is_long = true;
|
||||
} else if (DURATION_DIFF(duration, te_short) < te_delta) {
|
||||
is_long = false;
|
||||
} else {
|
||||
return ManchesterEventReset;
|
||||
}
|
||||
|
||||
if (level)
|
||||
return is_long ? ManchesterEventLongHigh : ManchesterEventShortHigh;
|
||||
else
|
||||
return is_long ? ManchesterEventLongLow : ManchesterEventShortLow;
|
||||
}
|
||||
|
||||
uint8_t oregon2_sensor_id_var_bits(uint16_t sensor_id) {
|
||||
switch (sensor_id) {
|
||||
case ID_THR228N:
|
||||
case ID_RTHN129_1:
|
||||
case ID_RTHN129_2:
|
||||
case ID_RTHN129_3:
|
||||
case ID_RTHN129_4:
|
||||
case ID_RTHN129_5:
|
||||
return 16;
|
||||
case ID_THGR122N:
|
||||
return 24;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
void ws_oregon2_decode_const_data() {
|
||||
id = OREGON2_SENSOR_ID(data);
|
||||
uint8_t ch_bits = (data >> 12) & 0xF;
|
||||
channel = 1;
|
||||
while (ch_bits > 1) {
|
||||
channel++;
|
||||
ch_bits >>= 1;
|
||||
}
|
||||
battery_low = (data & OREGON2_FLAG_BAT_LOW) ? 1 : 0;
|
||||
}
|
||||
uint16_t bcd_decode_short(uint32_t data) {
|
||||
return (data & 0xF) * 10 + ((data >> 4) & 0xF);
|
||||
}
|
||||
float ws_oregon2_decode_temp(uint32_t data) {
|
||||
int32_t temp_val;
|
||||
temp_val = bcd_decode_short(data >> 4);
|
||||
temp_val *= 10;
|
||||
temp_val += (data >> 12) & 0xF;
|
||||
if (data & 0xF) temp_val = -temp_val;
|
||||
return (float)temp_val / 10.0;
|
||||
}
|
||||
void ws_oregon2_decode_var_data(uint16_t sensor_id, uint32_t data) {
|
||||
switch (sensor_id) {
|
||||
case ID_THR228N:
|
||||
case ID_RTHN129_1:
|
||||
case ID_RTHN129_2:
|
||||
case ID_RTHN129_3:
|
||||
case ID_RTHN129_4:
|
||||
case ID_RTHN129_5:
|
||||
temp = ws_oregon2_decode_temp(data);
|
||||
humidity = WS_NO_HUMIDITY;
|
||||
return;
|
||||
case ID_THGR122N:
|
||||
humidity = bcd_decode_short(data);
|
||||
temp = ws_oregon2_decode_temp(data >> 8);
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,173 @@
|
||||
|
||||
#ifndef __FPROTO_OREGON3_H__
|
||||
#define __FPROTO_OREGON3_H__
|
||||
|
||||
#include "weatherbase.hpp"
|
||||
|
||||
#define OREGON3_PREAMBLE_BITS 28
|
||||
#define OREGON3_PREAMBLE_MASK 0b1111111111111111111111111111
|
||||
// 24 ones + 0101 (inverted A)
|
||||
#define OREGON3_PREAMBLE 0b1111111111111111111111110101
|
||||
|
||||
// Fixed part contains:
|
||||
// - Sensor type: 16 bits
|
||||
// - Channel: 4 bits
|
||||
// - ID (changes when batteries are changed): 8 bits
|
||||
// - Battery status: 4 bits
|
||||
#define OREGON3_FIXED_PART_BITS (16 + 4 + 8 + 4)
|
||||
#define OREGON3_SENSOR_ID(d) (((d) >> 16) & 0xFFFF)
|
||||
#define OREGON3_CHECKSUM_BITS 8
|
||||
|
||||
// bit indicating the low battery
|
||||
#define OREGON3_FLAG_BAT_LOW 0x4
|
||||
|
||||
/// Documentation for Oregon Scientific protocols can be found here:
|
||||
/// https://www.osengr.org/Articles/OS-RF-Protocols-IV.pdf
|
||||
// Sensors ID
|
||||
#define ID_THGR221 0xf824
|
||||
typedef enum {
|
||||
Oregon3DecoderStepReset = 0,
|
||||
Oregon3DecoderStepFoundPreamble,
|
||||
Oregon3DecoderStepVarData,
|
||||
} Oregon3DecoderStep;
|
||||
|
||||
class FProtoWeatherOregon3 : public FProtoWeatherBase {
|
||||
public:
|
||||
FProtoWeatherOregon3() {
|
||||
sensorType = FPW_OREGON3;
|
||||
}
|
||||
|
||||
void feed(bool level, uint32_t duration) override {
|
||||
ManchesterEvent event = level_and_duration_to_event(!level, duration);
|
||||
|
||||
// low-level bit sequence decoding
|
||||
if (event == ManchesterEventReset) {
|
||||
parser_step = Oregon3DecoderStepReset;
|
||||
prev_bit = false;
|
||||
decode_data = 0UL;
|
||||
decode_count_bit = 0;
|
||||
}
|
||||
if (manchester_advance(
|
||||
manchester_saved_state, event, &manchester_saved_state, &prev_bit)) {
|
||||
subghz_protocol_blocks_add_bit(prev_bit);
|
||||
}
|
||||
|
||||
switch (parser_step) {
|
||||
case Oregon3DecoderStepReset:
|
||||
// waiting for fixed oregon3 preamble
|
||||
if (decode_count_bit >= OREGON3_PREAMBLE_BITS &&
|
||||
((decode_data & OREGON3_PREAMBLE_MASK) == OREGON3_PREAMBLE)) {
|
||||
parser_step = Oregon3DecoderStepFoundPreamble;
|
||||
decode_count_bit = 0;
|
||||
decode_data = 0UL;
|
||||
}
|
||||
break;
|
||||
case Oregon3DecoderStepFoundPreamble:
|
||||
// waiting for fixed oregon3 data
|
||||
if (decode_count_bit == OREGON3_FIXED_PART_BITS) {
|
||||
data = decode_data;
|
||||
data_count_bit = decode_count_bit;
|
||||
decode_data = 0UL;
|
||||
decode_count_bit = 0;
|
||||
|
||||
// reverse nibbles in decoded data as oregon v3.0 is LSB first
|
||||
data = (data & 0x55555555) << 1 |
|
||||
(data & 0xAAAAAAAA) >> 1;
|
||||
data = (data & 0x33333333) << 2 |
|
||||
(data & 0xCCCCCCCC) >> 2;
|
||||
|
||||
ws_oregon3_decode_const_data();
|
||||
var_bits =
|
||||
oregon3_sensor_id_var_bits(OREGON3_SENSOR_ID(data));
|
||||
|
||||
if (!var_bits) {
|
||||
// sensor is not supported, stop decoding
|
||||
parser_step = Oregon3DecoderStepReset;
|
||||
} else {
|
||||
parser_step = Oregon3DecoderStepVarData;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Oregon3DecoderStepVarData:
|
||||
// waiting for variable (sensor-specific data)
|
||||
if (decode_count_bit == (uint32_t)var_bits + OREGON3_CHECKSUM_BITS) {
|
||||
var_data = decode_data & 0xFFFFFFFFFFFFFFFF;
|
||||
|
||||
// reverse nibbles in var data
|
||||
var_data = (var_data & 0x5555555555555555) << 1 |
|
||||
(var_data & 0xAAAAAAAAAAAAAAAA) >> 1;
|
||||
var_data = (var_data & 0x3333333333333333) << 2 |
|
||||
(var_data & 0xCCCCCCCCCCCCCCCC) >> 2;
|
||||
|
||||
ws_oregon3_decode_var_data(OREGON3_SENSOR_ID(data), var_data >> OREGON3_CHECKSUM_BITS);
|
||||
parser_step = Oregon3DecoderStepReset;
|
||||
if (callback) callback(this);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
// timing values
|
||||
uint32_t te_short = 500;
|
||||
uint32_t te_long = 1100;
|
||||
uint32_t te_delta = 300;
|
||||
uint32_t min_count_bit_for_found = 32;
|
||||
|
||||
bool prev_bit = false;
|
||||
uint8_t var_bits{0};
|
||||
uint64_t var_data{0};
|
||||
|
||||
ManchesterEvent level_and_duration_to_event(bool level, uint32_t duration) {
|
||||
bool is_long = false;
|
||||
|
||||
if (DURATION_DIFF(duration, te_long) < te_delta) {
|
||||
is_long = true;
|
||||
} else if (DURATION_DIFF(duration, te_short) < te_delta) {
|
||||
is_long = false;
|
||||
} else {
|
||||
return ManchesterEventReset;
|
||||
}
|
||||
|
||||
if (level)
|
||||
return is_long ? ManchesterEventLongHigh : ManchesterEventShortHigh;
|
||||
else
|
||||
return is_long ? ManchesterEventLongLow : ManchesterEventShortLow;
|
||||
}
|
||||
uint8_t oregon3_sensor_id_var_bits(uint16_t sensor_id) {
|
||||
switch (sensor_id) {
|
||||
case ID_THGR221:
|
||||
// nibbles: temp + hum + '0'
|
||||
return (4 + 2 + 1) * 4;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
void ws_oregon3_decode_const_data() {
|
||||
id = OREGON3_SENSOR_ID(data);
|
||||
channel = (data >> 12) & 0xF;
|
||||
battery_low = (data & OREGON3_FLAG_BAT_LOW) ? 1 : 0;
|
||||
}
|
||||
uint16_t ws_oregon3_bcd_decode_short(uint32_t data) {
|
||||
return (data & 0xF) * 10 + ((data >> 4) & 0xF);
|
||||
}
|
||||
float ws_oregon3_decode_temp(uint32_t data) {
|
||||
int32_t temp_val;
|
||||
temp_val = ws_oregon3_bcd_decode_short(data >> 4);
|
||||
temp_val *= 10;
|
||||
temp_val += (data >> 12) & 0xF;
|
||||
if (data & 0xF) temp_val = -temp_val;
|
||||
return (float)temp_val / 10.0;
|
||||
}
|
||||
void ws_oregon3_decode_var_data(uint16_t sensor_id, uint32_t data) {
|
||||
switch (sensor_id) {
|
||||
case ID_THGR221:
|
||||
default:
|
||||
humidity = ws_oregon3_bcd_decode_short(data >> 4);
|
||||
temp = ws_oregon3_decode_temp(data >> 12);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,176 @@
|
||||
|
||||
#ifndef __FPROTO_OREGONv1_H__
|
||||
#define __FPROTO_OREGONv1_H__
|
||||
|
||||
#include "weatherbase.hpp"
|
||||
|
||||
typedef enum {
|
||||
Oregon_V1DecoderStepReset = 0,
|
||||
Oregon_V1DecoderStepFoundPreamble,
|
||||
Oregon_V1DecoderStepParse,
|
||||
} Oregon_V1DecoderStep;
|
||||
|
||||
#define OREGON_V1_HEADER_OK 0xFF
|
||||
|
||||
class FProtoWeatherOregonV1 : public FProtoWeatherBase {
|
||||
public:
|
||||
FProtoWeatherOregonV1() {
|
||||
sensorType = FPW_OREGONv1;
|
||||
}
|
||||
|
||||
void feed(bool level, uint32_t duration) override {
|
||||
ManchesterEvent event = ManchesterEventReset;
|
||||
switch (parser_step) {
|
||||
case Oregon_V1DecoderStepReset:
|
||||
if ((level) && (DURATION_DIFF(duration, te_short) <
|
||||
te_delta)) {
|
||||
parser_step = Oregon_V1DecoderStepFoundPreamble;
|
||||
te_last = duration;
|
||||
header_count = 0;
|
||||
}
|
||||
break;
|
||||
case Oregon_V1DecoderStepFoundPreamble:
|
||||
if (level) {
|
||||
// keep high levels, if they suit our durations
|
||||
if ((DURATION_DIFF(duration, te_short) <
|
||||
te_delta) ||
|
||||
(DURATION_DIFF(duration, te_short * 4) <
|
||||
te_delta)) {
|
||||
te_last = duration;
|
||||
} else {
|
||||
parser_step = Oregon_V1DecoderStepReset;
|
||||
}
|
||||
} else if (
|
||||
// checking low levels
|
||||
(DURATION_DIFF(duration, te_short) <
|
||||
te_delta) &&
|
||||
(DURATION_DIFF(te_last, te_short) <
|
||||
te_delta)) {
|
||||
// Found header
|
||||
header_count++;
|
||||
} else if (
|
||||
(DURATION_DIFF(duration, te_short * 3) <
|
||||
te_delta) &&
|
||||
(DURATION_DIFF(te_last, te_short) <
|
||||
te_delta)) {
|
||||
// check header
|
||||
if (header_count > 7) {
|
||||
header_count = OREGON_V1_HEADER_OK;
|
||||
}
|
||||
} else if (
|
||||
(header_count == OREGON_V1_HEADER_OK) &&
|
||||
(DURATION_DIFF(te_last, te_short * 4) <
|
||||
te_delta)) {
|
||||
// found all the necessary patterns
|
||||
decode_data = 0;
|
||||
decode_count_bit = 1;
|
||||
manchester_advance(
|
||||
manchester_saved_state,
|
||||
ManchesterEventReset,
|
||||
&manchester_saved_state,
|
||||
NULL);
|
||||
parser_step = Oregon_V1DecoderStepParse;
|
||||
if (duration < te_short * 4) {
|
||||
first_bit = 1;
|
||||
} else {
|
||||
first_bit = 0;
|
||||
}
|
||||
} else {
|
||||
parser_step = Oregon_V1DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
case Oregon_V1DecoderStepParse:
|
||||
if (level) {
|
||||
if (DURATION_DIFF(duration, te_short) <
|
||||
te_delta) {
|
||||
event = ManchesterEventShortHigh;
|
||||
} else if (
|
||||
DURATION_DIFF(duration, te_long) <
|
||||
te_delta) {
|
||||
event = ManchesterEventLongHigh;
|
||||
} else {
|
||||
parser_step = Oregon_V1DecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
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)) {
|
||||
if (decode_count_bit ==
|
||||
min_count_bit_for_found) {
|
||||
if (first_bit) {
|
||||
decode_data = ~decode_data | (1 << 31);
|
||||
}
|
||||
if (ws_protocol_oregon_v1_check()) {
|
||||
data = decode_data;
|
||||
data_count_bit = decode_count_bit;
|
||||
ws_protocol_oregon_v1_remote_controller();
|
||||
if (callback) callback(this);
|
||||
}
|
||||
}
|
||||
decode_data = 0;
|
||||
decode_count_bit = 0;
|
||||
manchester_advance(
|
||||
manchester_saved_state,
|
||||
ManchesterEventReset,
|
||||
&manchester_saved_state,
|
||||
NULL);
|
||||
} else {
|
||||
parser_step = Oregon_V1DecoderStepReset;
|
||||
}
|
||||
}
|
||||
if (event != ManchesterEventReset) {
|
||||
bool data;
|
||||
bool data_ok = manchester_advance(
|
||||
manchester_saved_state, event, &manchester_saved_state, &data);
|
||||
|
||||
if (data_ok) {
|
||||
decode_data = (decode_data << 1) | !data;
|
||||
decode_count_bit++;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
// timing values
|
||||
uint32_t te_short = 1465;
|
||||
uint32_t te_long = 2930;
|
||||
uint32_t te_delta = 350;
|
||||
uint32_t min_count_bit_for_found = 32;
|
||||
|
||||
uint8_t first_bit{0};
|
||||
|
||||
bool ws_protocol_oregon_v1_check() {
|
||||
if (!decode_data) return false;
|
||||
uint64_t data = subghz_protocol_blocks_reverse_key(decode_data, 32);
|
||||
uint16_t crc = (data & 0xff) + ((data >> 8) & 0xff) + ((data >> 16) & 0xff);
|
||||
crc = (crc & 0xff) + ((crc >> 8) & 0xff);
|
||||
return (crc == ((data >> 24) & 0xFF));
|
||||
}
|
||||
void ws_protocol_oregon_v1_remote_controller() {
|
||||
uint64_t data2 = subghz_protocol_blocks_reverse_key(data, 32);
|
||||
|
||||
id = data2 & 0xFF;
|
||||
channel = ((data2 >> 6) & 0x03) + 1;
|
||||
|
||||
float temp_raw =
|
||||
((data2 >> 8) & 0x0F) * 0.1f + ((data2 >> 12) & 0x0F) + ((data2 >> 16) & 0x0F) * 10.0f;
|
||||
if (!((data2 >> 21) & 1)) {
|
||||
temp = temp_raw;
|
||||
} else {
|
||||
temp = -temp_raw;
|
||||
}
|
||||
battery_low = !((data2 >> 23) & 1ULL);
|
||||
btn = WS_NO_BTN;
|
||||
humidity = WS_NO_HUMIDITY;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,110 @@
|
||||
|
||||
#ifndef __FPROTO_THERMOPROTX4_H__
|
||||
#define __FPROTO_THERMOPROTX4_H__
|
||||
|
||||
#include "weatherbase.hpp"
|
||||
#define THERMO_PRO_TX4_TYPE_1 0b1001
|
||||
#define THERMO_PRO_TX4_TYPE_2 0b0110
|
||||
|
||||
typedef enum {
|
||||
ThermoPRO_TX4DecoderStepReset = 0,
|
||||
ThermoPRO_TX4DecoderStepSaveDuration,
|
||||
ThermoPRO_TX4DecoderStepCheckDuration,
|
||||
} ThermoPRO_TX4DecoderStep;
|
||||
|
||||
class FProtoWeatherThermoProTx4 : public FProtoWeatherBase {
|
||||
public:
|
||||
FProtoWeatherThermoProTx4() {
|
||||
sensorType = FPW_THERMOPROTX4;
|
||||
}
|
||||
|
||||
void feed(bool level, uint32_t duration) override {
|
||||
switch (parser_step) {
|
||||
case ThermoPRO_TX4DecoderStepReset:
|
||||
if ((!level) && (DURATION_DIFF(duration, te_short * 18) < te_delta * 10)) {
|
||||
// Found sync
|
||||
parser_step = ThermoPRO_TX4DecoderStepSaveDuration;
|
||||
decode_data = 0;
|
||||
decode_count_bit = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case ThermoPRO_TX4DecoderStepSaveDuration:
|
||||
if (level) {
|
||||
te_last = duration;
|
||||
parser_step = ThermoPRO_TX4DecoderStepCheckDuration;
|
||||
} else {
|
||||
parser_step = ThermoPRO_TX4DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case ThermoPRO_TX4DecoderStepCheckDuration:
|
||||
if (!level) {
|
||||
if (DURATION_DIFF(duration, te_short * 18) < te_delta * 10) {
|
||||
// Found sync
|
||||
parser_step = ThermoPRO_TX4DecoderStepReset;
|
||||
if ((decode_count_bit == min_count_bit_for_found) &&
|
||||
ws_protocol_thermopro_tx4_check()) {
|
||||
data = decode_data;
|
||||
data_count_bit = decode_count_bit;
|
||||
ws_protocol_thermopro_tx4_remote_controller();
|
||||
if (callback) callback(this);
|
||||
parser_step = ThermoPRO_TX4DecoderStepCheckDuration;
|
||||
}
|
||||
decode_data = 0;
|
||||
decode_count_bit = 0;
|
||||
|
||||
break;
|
||||
} else if (
|
||||
(DURATION_DIFF(
|
||||
te_last, te_short) < te_delta) &&
|
||||
(DURATION_DIFF(duration, te_long) < te_delta * 2)) {
|
||||
subghz_protocol_blocks_add_bit(0);
|
||||
parser_step = ThermoPRO_TX4DecoderStepSaveDuration;
|
||||
} else if (
|
||||
(DURATION_DIFF(
|
||||
te_last, te_short) < te_delta) &&
|
||||
(DURATION_DIFF(duration, te_long * 2) < te_delta * 4)) {
|
||||
subghz_protocol_blocks_add_bit(1);
|
||||
parser_step = ThermoPRO_TX4DecoderStepSaveDuration;
|
||||
} else {
|
||||
parser_step = ThermoPRO_TX4DecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
parser_step = ThermoPRO_TX4DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
// timing values
|
||||
uint32_t te_short = 500;
|
||||
uint32_t te_long = 2000;
|
||||
uint32_t te_delta = 150;
|
||||
uint32_t min_count_bit_for_found = 37;
|
||||
|
||||
bool ws_protocol_thermopro_tx4_check() {
|
||||
uint8_t type = decode_data >> 33;
|
||||
if ((type == THERMO_PRO_TX4_TYPE_1) || (type == THERMO_PRO_TX4_TYPE_2)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void ws_protocol_thermopro_tx4_remote_controller() {
|
||||
id = (data >> 25) & 0xFF;
|
||||
battery_low = (data >> 24) & 1;
|
||||
btn = (data >> 23) & 1;
|
||||
channel = ((data >> 21) & 0x03) + 1;
|
||||
if (!((data >> 20) & 1)) {
|
||||
temp = (float)((data >> 9) & 0x07FF) / 10.0f;
|
||||
} else {
|
||||
temp = (float)((~(data >> 9) & 0x07FF) + 1) / -10.0f;
|
||||
}
|
||||
humidity = (data >> 1) & 0xFF;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,141 @@
|
||||
|
||||
#ifndef __FPROTO_TX8300_H__
|
||||
#define __FPROTO_TX8300_H__
|
||||
|
||||
#include "weatherbase.hpp"
|
||||
typedef enum {
|
||||
TX_8300DecoderStepReset = 0,
|
||||
TX_8300DecoderStepCheckPreambule,
|
||||
TX_8300DecoderStepSaveDuration,
|
||||
TX_8300DecoderStepCheckDuration,
|
||||
} TX_8300DecoderStep;
|
||||
#define TX_8300_PACKAGE_SIZE 32
|
||||
|
||||
class FProtoWeatherTX8300 : public FProtoWeatherBase {
|
||||
public:
|
||||
FProtoWeatherTX8300() {
|
||||
sensorType = FPW_TX_8300;
|
||||
}
|
||||
|
||||
void feed(bool level, uint32_t duration) override {
|
||||
switch (parser_step) {
|
||||
case TX_8300DecoderStepReset:
|
||||
if ((level) && (DURATION_DIFF(duration, te_short * 2) < te_delta)) {
|
||||
parser_step = TX_8300DecoderStepCheckPreambule;
|
||||
}
|
||||
break;
|
||||
|
||||
case TX_8300DecoderStepCheckPreambule:
|
||||
if ((!level) && ((DURATION_DIFF(duration, te_short * 2) < te_delta) ||
|
||||
(DURATION_DIFF(duration, te_short * 3) < te_delta))) {
|
||||
parser_step = TX_8300DecoderStepSaveDuration;
|
||||
decode_data = 0;
|
||||
decode_count_bit = 1;
|
||||
package_1 = 0;
|
||||
package_2 = 0;
|
||||
} else {
|
||||
parser_step = TX_8300DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case TX_8300DecoderStepSaveDuration:
|
||||
if (level) {
|
||||
te_last = duration;
|
||||
parser_step = TX_8300DecoderStepCheckDuration;
|
||||
} else {
|
||||
parser_step = TX_8300DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case TX_8300DecoderStepCheckDuration:
|
||||
if (!level) {
|
||||
if (duration >= ((uint32_t)te_short * 5)) {
|
||||
// Found syncPostfix
|
||||
if ((decode_count_bit ==
|
||||
min_count_bit_for_found) &&
|
||||
ws_protocol_tx_8300_check_crc()) {
|
||||
data = package_1;
|
||||
data_count_bit = decode_count_bit;
|
||||
ws_protocol_tx_8300_remote_controller();
|
||||
if (callback) callback(this);
|
||||
}
|
||||
decode_data = 0;
|
||||
decode_count_bit = 1;
|
||||
parser_step = TX_8300DecoderStepReset;
|
||||
break;
|
||||
} else if (
|
||||
(DURATION_DIFF(te_last, te_short) <
|
||||
te_delta) &&
|
||||
(DURATION_DIFF(duration, te_long) <
|
||||
te_delta * 2)) {
|
||||
subghz_protocol_blocks_add_bit(1);
|
||||
parser_step = TX_8300DecoderStepSaveDuration;
|
||||
} else if (
|
||||
(DURATION_DIFF(te_last, te_short) <
|
||||
te_delta) &&
|
||||
(DURATION_DIFF(duration, te_short) <
|
||||
te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(0);
|
||||
parser_step = TX_8300DecoderStepSaveDuration;
|
||||
} else {
|
||||
parser_step = TX_8300DecoderStepReset;
|
||||
}
|
||||
|
||||
if (decode_count_bit == TX_8300_PACKAGE_SIZE) {
|
||||
package_1 = decode_data;
|
||||
decode_data = 0;
|
||||
} else if (decode_count_bit == TX_8300_PACKAGE_SIZE * 2) {
|
||||
package_2 = decode_data;
|
||||
decode_data = 0;
|
||||
}
|
||||
|
||||
} else {
|
||||
parser_step = TX_8300DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
// timing values
|
||||
uint32_t te_short = 1940;
|
||||
uint32_t te_long = 3880;
|
||||
uint32_t te_delta = 250;
|
||||
uint32_t min_count_bit_for_found = 72;
|
||||
|
||||
uint32_t package_1{0};
|
||||
uint32_t package_2{0};
|
||||
bool ws_protocol_tx_8300_check_crc() {
|
||||
if (!package_2) return false;
|
||||
if (package_1 != ~package_2) return false;
|
||||
|
||||
uint16_t x = 0;
|
||||
uint16_t y = 0;
|
||||
for (int i = 0; i < 32; i += 4) {
|
||||
x += (package_1 >> i) & 0x0F;
|
||||
y += (package_1 >> i) & 0x05;
|
||||
}
|
||||
uint8_t crc = (~x & 0xF) << 4 | (~y & 0xF);
|
||||
return (crc == (decode_data & 0xFF));
|
||||
}
|
||||
void ws_protocol_tx_8300_remote_controller() {
|
||||
humidity = (((data >> 28) & 0x0F) * 10) + ((data >> 24) & 0x0F);
|
||||
btn = WS_NO_BTN;
|
||||
if (!((data >> 22) & 0x03))
|
||||
battery_low = 0;
|
||||
else
|
||||
battery_low = 1;
|
||||
channel = (data >> 20) & 0x03;
|
||||
id = (data >> 12) & 0x7F;
|
||||
|
||||
float temp_raw = ((data >> 8) & 0x0F) * 10.0f + ((data >> 4) & 0x0F) +
|
||||
(data & 0x0F) * 0.1f;
|
||||
if (!((data >> 19) & 1)) {
|
||||
temp = temp_raw;
|
||||
} else {
|
||||
temp = -temp_raw;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,156 @@
|
||||
|
||||
#ifndef __FPROTO_WENDOX_W6726_H__
|
||||
#define __FPROTO_WENDOX_W6726_H__
|
||||
|
||||
#include "weatherbase.hpp"
|
||||
typedef enum {
|
||||
WendoxW6726DecoderStepReset = 0,
|
||||
WendoxW6726DecoderStepCheckPreambule,
|
||||
WendoxW6726DecoderStepSaveDuration,
|
||||
WendoxW6726DecoderStepCheckDuration,
|
||||
} WendoxW6726DecoderStep;
|
||||
|
||||
class FProtoWeatherWendoxW6726 : public FProtoWeatherBase {
|
||||
public:
|
||||
FProtoWeatherWendoxW6726() {
|
||||
sensorType = FPW_WENDOX_W6726;
|
||||
}
|
||||
|
||||
void feed(bool level, uint32_t duration) override {
|
||||
switch (parser_step) {
|
||||
case WendoxW6726DecoderStepReset:
|
||||
if ((level) && (DURATION_DIFF(duration, te_short) <
|
||||
te_delta)) {
|
||||
parser_step = WendoxW6726DecoderStepCheckPreambule;
|
||||
te_last = duration;
|
||||
header_count = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case WendoxW6726DecoderStepCheckPreambule:
|
||||
if (level) {
|
||||
te_last = duration;
|
||||
} else {
|
||||
if ((DURATION_DIFF(te_last, te_short) <
|
||||
te_delta * 1) &&
|
||||
(DURATION_DIFF(duration, te_long) <
|
||||
te_delta * 2)) {
|
||||
header_count++;
|
||||
} else if ((header_count > 4) && (header_count < 12)) {
|
||||
if ((DURATION_DIFF(
|
||||
te_last, te_long) <
|
||||
te_delta * 2) &&
|
||||
(DURATION_DIFF(duration, te_short) <
|
||||
te_delta)) {
|
||||
decode_data = 0;
|
||||
decode_count_bit = 0;
|
||||
subghz_protocol_blocks_add_bit(1);
|
||||
parser_step = WendoxW6726DecoderStepSaveDuration;
|
||||
} else {
|
||||
parser_step = WendoxW6726DecoderStepReset;
|
||||
}
|
||||
|
||||
} else {
|
||||
parser_step = WendoxW6726DecoderStepReset;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case WendoxW6726DecoderStepSaveDuration:
|
||||
if (level) {
|
||||
te_last = duration;
|
||||
parser_step = WendoxW6726DecoderStepCheckDuration;
|
||||
} else {
|
||||
parser_step = WendoxW6726DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case WendoxW6726DecoderStepCheckDuration:
|
||||
if (!level) {
|
||||
if (duration >
|
||||
te_short + te_long) {
|
||||
if (DURATION_DIFF(te_last, te_short) < te_delta) {
|
||||
subghz_protocol_blocks_add_bit(0);
|
||||
parser_step = WendoxW6726DecoderStepSaveDuration;
|
||||
} else if (
|
||||
DURATION_DIFF(te_last, te_long) < te_delta * 2) {
|
||||
subghz_protocol_blocks_add_bit(1);
|
||||
parser_step = WendoxW6726DecoderStepSaveDuration;
|
||||
} else {
|
||||
parser_step = WendoxW6726DecoderStepReset;
|
||||
}
|
||||
if ((decode_count_bit ==
|
||||
min_count_bit_for_found) &&
|
||||
ws_protocol_wendox_w6726_check()) {
|
||||
data = decode_data;
|
||||
data_count_bit = decode_count_bit;
|
||||
ws_protocol_wendox_w6726_remote_controller();
|
||||
if (callback) callback(this);
|
||||
}
|
||||
|
||||
parser_step = WendoxW6726DecoderStepReset;
|
||||
} else if (
|
||||
(DURATION_DIFF(te_last, te_short) <
|
||||
te_delta) &&
|
||||
(DURATION_DIFF(duration, te_long) <
|
||||
te_delta * 3)) {
|
||||
subghz_protocol_blocks_add_bit(0);
|
||||
parser_step = WendoxW6726DecoderStepSaveDuration;
|
||||
} else if (
|
||||
(DURATION_DIFF(te_last, te_long) <
|
||||
te_delta * 2) &&
|
||||
(DURATION_DIFF(duration, te_short) <
|
||||
te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(1);
|
||||
parser_step = WendoxW6726DecoderStepSaveDuration;
|
||||
} else {
|
||||
parser_step = WendoxW6726DecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
parser_step = WendoxW6726DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
// timing values
|
||||
uint32_t te_short = 1955;
|
||||
uint32_t te_long = 5865;
|
||||
uint32_t te_delta = 300;
|
||||
uint32_t min_count_bit_for_found = 29;
|
||||
|
||||
bool ws_protocol_wendox_w6726_check() {
|
||||
if (!decode_data) return false;
|
||||
uint8_t msg[] = {
|
||||
static_cast<uint8_t>(decode_data >> 28),
|
||||
static_cast<uint8_t>(decode_data >> 20),
|
||||
static_cast<uint8_t>(decode_data >> 12),
|
||||
static_cast<uint8_t>(decode_data >> 4)};
|
||||
|
||||
uint8_t crc = subghz_protocol_blocks_crc4(msg, 4, 0x9, 0xD);
|
||||
return (crc == (decode_data & 0x0F));
|
||||
}
|
||||
void ws_protocol_wendox_w6726_remote_controller() {
|
||||
id = (data >> 24) & 0xFF;
|
||||
battery_low = (data >> 6) & 1;
|
||||
channel = WS_NO_CHANNEL;
|
||||
|
||||
if (((data >> 23) & 1)) {
|
||||
temp = (float)(((data >> 14) & 0x1FF) + 12) / 10.0f;
|
||||
} else {
|
||||
temp = (float)((~(data >> 14) & 0x1FF) + 1 - 12) / -10.0f;
|
||||
}
|
||||
|
||||
if (temp < -50.0f) {
|
||||
temp = -50.0f;
|
||||
} else if (temp > 70.0f) {
|
||||
temp = 70.0f;
|
||||
}
|
||||
|
||||
btn = WS_NO_BTN;
|
||||
humidity = WS_NO_HUMIDITY;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,230 @@
|
||||
/*
|
||||
Base class for all weather protocols.
|
||||
This and most of the weather protocols uses code from Flipper XTreme codebase ( https://github.com/Flipper-XFW/Xtreme-Firmware/tree/dev/lib/subghz ). Thanks for their work!
|
||||
For comments in a protocol implementation check w-nexus-th.hpp
|
||||
*/
|
||||
|
||||
#ifndef __FPROTO_BASE_H__
|
||||
#define __FPROTO_BASE_H__
|
||||
|
||||
#define bit_read(value, bit) (((value) >> (bit)) & 0x01)
|
||||
|
||||
#include "weathertypes.hpp"
|
||||
|
||||
#include <string>
|
||||
// default walues to indicate 'no value'
|
||||
#define WS_NO_ID 0xFFFFFFFF
|
||||
#define WS_NO_BATT 0xFF
|
||||
#define WS_NO_HUMIDITY 0xFF
|
||||
#define WS_NO_CHANNEL 0xFF
|
||||
#define WS_NO_BTN 0xFF
|
||||
#define WS_NO_TEMPERATURE -273.0f
|
||||
|
||||
#define DURATION_DIFF(x, y) (((x) < (y)) ? ((y) - (x)) : ((x) - (y)))
|
||||
typedef enum {
|
||||
ManchesterStateStart1 = 0,
|
||||
ManchesterStateMid1 = 1,
|
||||
ManchesterStateMid0 = 2,
|
||||
ManchesterStateStart0 = 3
|
||||
} ManchesterState;
|
||||
typedef enum {
|
||||
ManchesterEventShortLow = 0,
|
||||
ManchesterEventShortHigh = 2,
|
||||
ManchesterEventLongLow = 4,
|
||||
ManchesterEventLongHigh = 6,
|
||||
ManchesterEventReset = 8
|
||||
} ManchesterEvent;
|
||||
class FProtoWeatherBase;
|
||||
typedef void (*SubGhzProtocolDecoderBaseRxCallback)(FProtoWeatherBase* instance);
|
||||
|
||||
class FProtoWeatherBase {
|
||||
public:
|
||||
FProtoWeatherBase() {}
|
||||
virtual ~FProtoWeatherBase() {}
|
||||
virtual void feed(bool level, uint32_t duration) = 0; // need to be implemented on each protocol handler.
|
||||
void setCallback(SubGhzProtocolDecoderBaseRxCallback cb) { callback = cb; } // this is called when there is a hit.
|
||||
|
||||
uint8_t getSensorType() { return sensorType; }
|
||||
uint32_t getSensorId() { return id; }
|
||||
float getTemp() { return temp; }
|
||||
uint8_t getHumidity() { return humidity; }
|
||||
uint8_t getBattLow() { return battery_low; }
|
||||
uint32_t getTimestamp() { return timestamp; }
|
||||
uint8_t getChannel() { return channel; }
|
||||
uint8_t getButton() { return btn; }
|
||||
|
||||
protected:
|
||||
// Helper functions to keep it as compatible with flipper as we can, so adding new protos will be easy.
|
||||
void subghz_protocol_blocks_add_bit(uint8_t bit) {
|
||||
decode_data = decode_data << 1 | bit;
|
||||
decode_count_bit++;
|
||||
}
|
||||
uint8_t subghz_protocol_blocks_add_bytes(uint8_t const message[], size_t size) {
|
||||
uint32_t result = 0;
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
result += message[i];
|
||||
}
|
||||
return (uint8_t)result;
|
||||
}
|
||||
uint8_t subghz_protocol_blocks_parity8(uint8_t byte) {
|
||||
byte ^= byte >> 4;
|
||||
byte &= 0xf;
|
||||
return (0x6996 >> byte) & 1;
|
||||
}
|
||||
uint8_t subghz_protocol_blocks_parity_bytes(uint8_t const message[], size_t size) {
|
||||
uint8_t result = 0;
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
result ^= subghz_protocol_blocks_parity8(message[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
uint8_t subghz_protocol_blocks_lfsr_digest8(
|
||||
uint8_t const message[],
|
||||
size_t size,
|
||||
uint8_t gen,
|
||||
uint8_t key) {
|
||||
uint8_t sum = 0;
|
||||
for (size_t byte = 0; byte < size; ++byte) {
|
||||
uint8_t data = message[byte];
|
||||
for (int i = 7; i >= 0; --i) {
|
||||
// XOR key into sum if data bit is set
|
||||
if ((data >> i) & 1) sum ^= key;
|
||||
// roll the key right (actually the LSB is dropped here)
|
||||
// and apply the gen (needs to include the dropped LSB as MSB)
|
||||
if (key & 1)
|
||||
key = (key >> 1) ^ gen;
|
||||
else
|
||||
key = (key >> 1);
|
||||
}
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
float locale_fahrenheit_to_celsius(float temp_f) {
|
||||
return (temp_f - 32.f) / 1.8f;
|
||||
}
|
||||
bool manchester_advance(
|
||||
ManchesterState state,
|
||||
ManchesterEvent event,
|
||||
ManchesterState* next_state,
|
||||
bool* data) {
|
||||
bool result = false;
|
||||
ManchesterState new_state;
|
||||
|
||||
if (event == ManchesterEventReset) {
|
||||
new_state = manchester_reset_state;
|
||||
} else {
|
||||
new_state = (ManchesterState)(transitions[state] >> event & 0x3);
|
||||
if (new_state == state) {
|
||||
new_state = manchester_reset_state;
|
||||
} else {
|
||||
if (new_state == ManchesterStateMid0) {
|
||||
if (data) *data = false;
|
||||
result = true;
|
||||
} else if (new_state == ManchesterStateMid1) {
|
||||
if (data) *data = true;
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*next_state = new_state;
|
||||
return result;
|
||||
}
|
||||
uint8_t subghz_protocol_blocks_crc4(
|
||||
uint8_t const message[],
|
||||
size_t size,
|
||||
uint8_t polynomial,
|
||||
uint8_t init) {
|
||||
uint8_t remainder = init << 4; // LSBs are unused
|
||||
uint8_t poly = polynomial << 4;
|
||||
uint8_t bit;
|
||||
|
||||
while (size--) {
|
||||
remainder ^= *message++;
|
||||
for (bit = 0; bit < 8; bit++) {
|
||||
if (remainder & 0x80) {
|
||||
remainder = (remainder << 1) ^ poly;
|
||||
} else {
|
||||
remainder = (remainder << 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return remainder >> 4 & 0x0f; // discard the LSBs
|
||||
}
|
||||
uint8_t subghz_protocol_blocks_lfsr_digest8_reflect(
|
||||
uint8_t const message[],
|
||||
size_t size,
|
||||
uint8_t gen,
|
||||
uint8_t key) {
|
||||
uint8_t sum = 0;
|
||||
// Process message from last byte to first byte (reflected)
|
||||
for (int byte = size - 1; byte >= 0; --byte) {
|
||||
uint8_t data = message[byte];
|
||||
// Process individual bits of each byte (reflected)
|
||||
for (uint8_t i = 0; i < 8; ++i) {
|
||||
// XOR key into sum if data bit is set
|
||||
if ((data >> i) & 1) {
|
||||
sum ^= key;
|
||||
}
|
||||
// roll the key left (actually the LSB is dropped here)
|
||||
// and apply the gen (needs to include the dropped lsb as MSB)
|
||||
if (key & 0x80)
|
||||
key = (key << 1) ^ gen;
|
||||
else
|
||||
key = (key << 1);
|
||||
}
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
uint64_t subghz_protocol_blocks_reverse_key(uint64_t key, uint8_t bit_count) {
|
||||
uint64_t reverse_key = 0;
|
||||
for (uint8_t i = 0; i < bit_count; i++) {
|
||||
reverse_key = reverse_key << 1 | bit_read(key, i);
|
||||
}
|
||||
return reverse_key;
|
||||
}
|
||||
uint8_t subghz_protocol_blocks_crc8(
|
||||
uint8_t const message[],
|
||||
size_t size,
|
||||
uint8_t polynomial,
|
||||
uint8_t init) {
|
||||
uint8_t remainder = init;
|
||||
|
||||
for (size_t byte = 0; byte < size; ++byte) {
|
||||
remainder ^= message[byte];
|
||||
for (uint8_t bit = 0; bit < 8; ++bit) {
|
||||
if (remainder & 0x80) {
|
||||
remainder = (remainder << 1) ^ polynomial;
|
||||
} else {
|
||||
remainder = (remainder << 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return remainder;
|
||||
}
|
||||
|
||||
// General weather data holder
|
||||
uint8_t sensorType = FPW_Invalid;
|
||||
uint32_t id = WS_NO_ID;
|
||||
float temp = WS_NO_TEMPERATURE;
|
||||
uint8_t humidity = WS_NO_HUMIDITY;
|
||||
uint8_t battery_low = WS_NO_BATT;
|
||||
uint32_t timestamp = 0;
|
||||
uint8_t channel = WS_NO_CHANNEL;
|
||||
uint8_t btn = WS_NO_BTN;
|
||||
|
||||
// inner logic stuff, also for flipper compatibility. //todo revork a bit, so won't have dupes (decode_data + data, ..), but check if any of the protos uses it in the same time or not. (shouldn't)
|
||||
SubGhzProtocolDecoderBaseRxCallback callback = NULL;
|
||||
uint16_t header_count = 0;
|
||||
uint8_t parser_step = 0;
|
||||
uint32_t te_last = 0;
|
||||
uint64_t data = 0;
|
||||
uint32_t data_count_bit = 0;
|
||||
uint64_t decode_data = 0;
|
||||
uint32_t decode_count_bit = 0;
|
||||
ManchesterState manchester_saved_state = ManchesterStateMid1;
|
||||
static const ManchesterState manchester_reset_state = ManchesterStateMid1;
|
||||
static inline const uint8_t transitions[] = {0b00000001, 0b10010001, 0b10011011, 0b11111011};
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
This is the protocol list handler. It holds an instance of all known protocols.
|
||||
So include here the .hpp, and add a new element to the protos vector in the constructor. That's all you need to do here if you wanna add a new proto.
|
||||
@htotoo
|
||||
*/
|
||||
#include "w-nexus-th.hpp"
|
||||
#include "w-acurite592txr.hpp"
|
||||
#include "w-acurite606tx.hpp"
|
||||
#include "w-acurite609tx.hpp"
|
||||
#include "w-ambient.hpp"
|
||||
#include "w-auriol-ahfl.hpp"
|
||||
#include "w-auriol-th.hpp"
|
||||
#include "w-gt-wt-02.hpp"
|
||||
#include "w-gt-wt-03.hpp"
|
||||
#include "w-infactory.hpp"
|
||||
#include "w-lacrosse-tx.hpp"
|
||||
#include "w-lacrosse-tx141thbv2.hpp"
|
||||
#include "w-oregon2.hpp"
|
||||
#include "w-oregon3.hpp"
|
||||
#include "w-oregonv1.hpp"
|
||||
#include "w-thermoprotx4.hpp"
|
||||
#include "w-tx8300.hpp"
|
||||
#include "w-wendox-w6726.hpp"
|
||||
#include "w-acurite986.hpp"
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include "portapack_shared_memory.hpp"
|
||||
|
||||
#ifndef __FPROTO_PROTOLIST_H__
|
||||
#define __FPROTO_PROTOLIST_H__
|
||||
|
||||
class WeatherProtos {
|
||||
public:
|
||||
WeatherProtos() {
|
||||
// add protos
|
||||
protos.push_back(std::make_unique<FProtoWeatherNexusTH>()); // 1
|
||||
protos.push_back(std::make_unique<FProtoWeatherAcurite592TXR>()); // 2
|
||||
protos.push_back(std::make_unique<FProtoWeatherAcurite606TX>()); // 3
|
||||
protos.push_back(std::make_unique<FProtoWeatherAcurite609TX>()); // 4
|
||||
protos.push_back(std::make_unique<FProtoWeatherAmbient>()); // 5
|
||||
protos.push_back(std::make_unique<FProtoWeatherAuriolAhfl>()); // 6
|
||||
protos.push_back(std::make_unique<FProtoWeatherAuriolTh>()); // 7
|
||||
protos.push_back(std::make_unique<FProtoWeatherGTWT02>()); // 8
|
||||
protos.push_back(std::make_unique<FProtoWeatherGTWT03>()); // 9
|
||||
protos.push_back(std::make_unique<FProtoWeatherInfactory>()); // 10
|
||||
protos.push_back(std::make_unique<FProtoWeatherLaCrosseTx>()); // 11
|
||||
protos.push_back(std::make_unique<FProtoWeatherLaCrosseTx141thbv2>()); // 12
|
||||
protos.push_back(std::make_unique<FProtoWeatherOregon2>()); // 13
|
||||
protos.push_back(std::make_unique<FProtoWeatherOregon3>()); // 14
|
||||
protos.push_back(std::make_unique<FProtoWeatherOregonV1>()); // 15
|
||||
protos.push_back(std::make_unique<FProtoWeatherThermoProTx4>()); // 16
|
||||
protos.push_back(std::make_unique<FProtoWeatherTX8300>()); // 17
|
||||
protos.push_back(std::make_unique<FProtoWeatherWendoxW6726>()); // 18
|
||||
protos.push_back(std::make_unique<FProtoWeatherAcurite986>()); // 19
|
||||
|
||||
// set callback for them
|
||||
for (const auto& obj : protos) {
|
||||
obj->setCallback(callbackTarget);
|
||||
}
|
||||
}
|
||||
|
||||
static void callbackTarget(FProtoWeatherBase* instance) {
|
||||
WeatherDataMessage packet_message{instance->getSensorType(), instance->getSensorId(),
|
||||
instance->getTemp(), instance->getHumidity(), instance->getBattLow(),
|
||||
instance->getChannel(), instance->getButton()};
|
||||
shared_memory.application_queue.push(packet_message);
|
||||
}
|
||||
|
||||
void feed(bool level, uint32_t duration) {
|
||||
for (const auto& obj : protos) {
|
||||
obj->feed(level, duration);
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
std::vector<std::unique_ptr<FProtoWeatherBase>> protos{};
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,34 @@
|
||||
|
||||
#ifndef __FPROTO_WEATHERTYPES_H__
|
||||
#define __FPROTO_WEATHERTYPES_H__
|
||||
|
||||
/*
|
||||
Define known protocols.
|
||||
These values must be present on the protocol's constructor, like FProtoWeatherAcurite592TXR() { sensorType = FPW_Acurite592TXR; }
|
||||
Also it must have a switch-case element in the getWeatherSensorTypeName() function, to display it's name.
|
||||
*/
|
||||
|
||||
enum FPROTO_WEATHER_SENSOR {
|
||||
FPW_Invalid = 0,
|
||||
FPW_NexusTH = 1,
|
||||
FPW_Acurite592TXR = 2,
|
||||
FPW_Acurite606TX = 3,
|
||||
FPW_Acurite609TX = 4,
|
||||
FPW_Ambient = 5,
|
||||
FPW_AuriolAhfl = 6,
|
||||
FPW_AuriolTH = 7,
|
||||
FPW_GTWT02 = 8,
|
||||
FPW_GTWT03 = 9,
|
||||
FPW_INFACTORY = 10,
|
||||
FPW_LACROSSETX = 11,
|
||||
FPW_LACROSSETX141thbv2 = 12,
|
||||
FPW_OREGON2 = 13,
|
||||
FPW_OREGON3 = 14,
|
||||
FPW_OREGONv1 = 15,
|
||||
FPW_THERMOPROTX4 = 16,
|
||||
FPW_TX_8300 = 17,
|
||||
FPW_WENDOX_W6726 = 18,
|
||||
FPW_Acurite986 = 19
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
for (size_t i = 0; i < buffer.count; i++) {
|
||||
int8_t re = buffer.p[i].real();
|
||||
int8_t im = buffer.p[i].imag();
|
||||
uint32_t mag = ((uint32_t)re * (uint32_t)re) + ((uint32_t)im * (uint32_t)im);
|
||||
bool meashl = (mag > threshold);
|
||||
tm += mag;
|
||||
if (meashl == currentHiLow && currentDuration < 10'000'000) // allow pass 'end' signal
|
||||
{
|
||||
if (currentDuration < UINT32_MAX) currentDuration += usperTick;
|
||||
} else { // called on change, so send the last duration and dir.
|
||||
protoList.feed(currentHiLow, currentDuration / 1000);
|
||||
currentDuration = usperTick;
|
||||
currentHiLow = meashl;
|
||||
}
|
||||
}
|
||||
cnt += buffer.count;
|
||||
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 WeatherRxConfigureMessage*>(message));
|
||||
}
|
||||
|
||||
void WeatherProcessor::configure(const WeatherRxConfigureMessage& message) {
|
||||
(void)message;
|
||||
configured = true;
|
||||
}
|
||||
|
||||
int main() {
|
||||
EventDispatcher event_dispatcher{std::make_unique<WeatherProcessor>()};
|
||||
event_dispatcher.run();
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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 "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 uint32_t usperTick = 500; // we nees ms to has to divide by 1000
|
||||
static constexpr size_t baseband_fs = 1'750'000;
|
||||
uint32_t currentDuration = 0;
|
||||
uint32_t threshold = 0x0630; // will overwrite after the first iteration
|
||||
bool currentHiLow = false;
|
||||
bool configured{false};
|
||||
|
||||
// for debug
|
||||
uint32_t cnt = 0;
|
||||
uint32_t tm = 0;
|
||||
|
||||
WeatherProtos protoList{}; // holds all the protocols we can parse
|
||||
void configure(const WeatherRxConfigureMessage& 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__*/
|
||||
@@ -115,6 +115,8 @@ class Message {
|
||||
FSKRxConfigure = 58,
|
||||
BlePacket = 58,
|
||||
BTLETxConfigure = 59,
|
||||
WeatherRxConfigure = 60,
|
||||
WeatherData = 61,
|
||||
MAX
|
||||
};
|
||||
|
||||
@@ -1236,4 +1238,40 @@ class SpectrumPainterBufferConfigureResponseMessage : public Message {
|
||||
SpectrumPainterFIFO* fifo{nullptr};
|
||||
};
|
||||
|
||||
class WeatherRxConfigureMessage : public Message {
|
||||
public:
|
||||
constexpr WeatherRxConfigureMessage()
|
||||
: Message{ID::WeatherRxConfigure} {
|
||||
// todoh give some more info
|
||||
}
|
||||
};
|
||||
|
||||
class WeatherDataMessage : public Message {
|
||||
public:
|
||||
constexpr WeatherDataMessage(
|
||||
uint8_t sensorType = 0,
|
||||
uint32_t id = 0xFFFFFFFF,
|
||||
float temp = -273.0f,
|
||||
uint8_t humidity = 0xFF,
|
||||
uint8_t battery_low = 0xFF,
|
||||
uint8_t channel = 0xFF,
|
||||
uint8_t btn = 0xFF)
|
||||
: Message{ID::WeatherData},
|
||||
sensorType{sensorType},
|
||||
id{id},
|
||||
temp{temp},
|
||||
humidity{humidity},
|
||||
battery_low{battery_low},
|
||||
channel{channel},
|
||||
btn{btn} {
|
||||
}
|
||||
uint8_t sensorType = 0;
|
||||
uint32_t id = 0xFFFFFFFF;
|
||||
float temp = -273.0f;
|
||||
uint8_t humidity = 0xFF;
|
||||
uint8_t battery_low = 0xFF;
|
||||
uint8_t channel = 0xFF;
|
||||
uint8_t btn = 0xFF;
|
||||
};
|
||||
|
||||
#endif /*__MESSAGE_H__*/
|
||||
|
||||
@@ -112,6 +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_weather{'P', 'W', 'T', 'H'};
|
||||
|
||||
constexpr image_tag_t image_tag_noop{'P', 'N', 'O', 'P'};
|
||||
|
||||
constexpr image_tag_t image_tag_hackrf{'H', 'R', 'F', '1'};
|
||||
|
||||
Reference in New Issue
Block a user