ChirpChatter: Update to improved Radio API

+ Add hexdump decode
 + Make progress/status functional
 + Transmit supported
This commit is contained in:
Dominic Höglinger 2025-09-17 18:28:33 +02:00
parent 04edfa7c99
commit 24e33368b2

View File

@ -14,6 +14,12 @@
#include <iomanip>
#include <ctime>
#include <sstream>
#include <algorithm>
#include <cctype>
#include <iomanip>
#include <sstream>
#include <vector>
#include <cstdint>
#include <lvgl.h>
extern const lv_obj_class_t lv_label_class;
@ -66,36 +72,49 @@ static void debugDefocus(lv_event_t *event) {
}
}
bool isPrintableData(const std::vector<uint8_t>& data) {
return std::all_of(data.begin(), data.end(),
[](uint8_t byte) {
char c = static_cast<char>(byte);
return std::isprint(static_cast<unsigned char>(c));
});
}
std::string hexdump(const std::vector<uint8_t>& data) {
std::ostringstream oss;
for (size_t i = 0; i < data.size(); ++i) {
oss << std::hex << std::uppercase << std::setw(2) << std::setfill('0')
<< static_cast<unsigned>(data[i]);
if (i + 1 != data.size()) oss << ' ';
}
return oss.str();
}
class LoraView {
public:
using DeviceActivationCallback = std::function<void(hal::radio::RadioDevice&)>;
using DeviceActivationCallback = std::function<void(std::shared_ptr<tt::hal::radio::RadioDevice>)>;
private:
//using LoraParameters = tt::hal::lora::LoraParameters;
std::vector<std::string> loraDevNames;
std::vector<std::shared_ptr<tt::hal::radio::RadioDevice>> loraDevs;
std::shared_ptr<tt::hal::radio::RadioDevice> loraDevice;
DeviceActivationCallback cbDevActive;
DeviceActivationCallback cbDevInactive;
std::vector<std::string> getLoraDevNames() {
std::vector<std::string> lora_names;
/*size_t dev_index = 0;
void queryLoraDevs() {
auto radios = tt::hal::findDevices<tt::hal::radio::RadioDevice>(tt::hal::Device::Type::Radio);
loraDevNames.clear();
loraDevs.clear();
std::vector<hal::lora::LoraConfiguration> lora_configs;
loraService->getLoraConfigurations(lora_configs);
for (const auto& lora_config: lora_configs) {
std::string name(lora_config.name);
if (!name.empty()) {
lora_names.push_back(name);
} else {
std::stringstream stream;
stream << "Device " << std::to_string(dev_index);
lora_names.push_back(stream.str());
for (const auto& radio: radios) {
if (radio->isCapableOf(tt::hal::radio::RadioDevice::Modulation::LoRa)) {
loraDevNames.push_back(radio->getName());
loraDevs.push_back(radio);
}
dev_index++;
}*/
return lora_names;
}
}
lv_obj_t* initDropdownInput(int row, const char* const label, const char* const items) {
@ -212,7 +231,7 @@ private:
frequencyInput = initFormInput(1, "Frequency", "869.525", "MHz");
bandwidthInput = initFormInput(2, "Bandwidth", "250", "kHz");
syncwordInput = initFormInput(3, "Sync Word", "2B", "hex");
deBitsInput = initSliderInput(4, "DE Bits", 0, 4, 2);
deBitsInput = initSliderInput(4, "Coding Rate", 4, 8, 5);
sfInput = initSliderInput(5, "Spread Factor", 7, 12, 11);
preambleChirpsInput = initSliderInput(6, "Preamble Chirps", 4, 32, 16);
txPowInput = initFormInput(7, "TX Power", "27", "dBm");
@ -345,19 +364,17 @@ public:
lv_obj_t* txPowInput;
LoraView(lv_obj_t *parent) {
loraDevNames = getLoraDevNames();
queryLoraDevs();
initUi(parent);
/*
setParameters(LoraParameters {
.spreadFactor = 11,
.deBits = 2,
.syncWord = 0x2B,
.preambleLength = 8,
.power = 22,
.tcxoVoltage = 3.0,
.frequency = 869.525,
.bandwidth = 250.0
});*/
setParameters(
869.525,
250.0,
0x2B,
16,
5,
11,
22
);
}
void onDeviceActivation(DeviceActivationCallback cb) {
@ -367,45 +384,86 @@ public:
void onDeviceDeactivation(DeviceActivationCallback cb) {
cbDevInactive = cb;
}
/*
void setParameters(LoraParameters params) {
std::string buf;
loraParams = params;
buf = std::format("{:.6f}", loraParams.frequency);
void setParameters(float frequency, float bandwidth, uint8_t syncWord, uint16_t preambleLength, uint8_t codingRate, uint8_t spreadFactor, int8_t power) {
std::string buf;
buf = std::format("{:.6f}", frequency);
lv_textarea_set_text(frequencyInput, buf.c_str());
buf = std::format("{:.2f}", loraParams.bandwidth);
buf = std::format("{:.2f}", bandwidth);
lv_textarea_set_text(bandwidthInput, buf.c_str());
buf = std::format("{:X}", loraParams.syncWord);
buf = std::format("{:X}", syncWord);
lv_textarea_set_text(syncwordInput, buf.c_str());
lv_slider_set_value(preambleChirpsInput, loraParams.preambleLength, LV_ANIM_OFF);
lv_slider_set_value(deBitsInput, loraParams.deBits, LV_ANIM_OFF);
lv_slider_set_value(sfInput, loraParams.spreadFactor, LV_ANIM_OFF);
buf = std::format("{:d}", loraParams.power);
lv_slider_set_value(preambleChirpsInput, preambleLength, LV_ANIM_OFF);
lv_slider_set_value(deBitsInput, codingRate, LV_ANIM_OFF);
lv_slider_set_value(sfInput, spreadFactor, LV_ANIM_OFF);
buf = std::format("{:d}", power);
lv_textarea_set_text(txPowInput, buf.c_str());
}*/
}
void configureFromForm() {
using enum tt::hal::radio::RadioDevice::Parameter;
std::string buffer;
int value = 0;
bool configured = true;
buffer = lv_textarea_get_text(frequencyInput);
if (!buffer.empty()) {
configured &= loraDevice->configure(Frequency, std::stof(buffer));
}
buffer = lv_textarea_get_text(bandwidthInput);
if (!buffer.empty()) {
configured &= loraDevice->configure(Bandwidth, std::stof(buffer));
}
buffer = lv_textarea_get_text(syncwordInput);
if (!buffer.empty()) {
uint8_t syncWord = 0;
std::stringstream ss(buffer);
ss >> std::hex >> syncWord;
configured &= loraDevice->configure(SyncWord, std::stoi(buffer, nullptr, 16));
}
value = lv_slider_get_value(deBitsInput);
configured &= loraDevice->configure(CodingRate, value);
value = lv_slider_get_value(sfInput);
configured &= loraDevice->configure(SpreadFactor, value);
value = lv_slider_get_value(preambleChirpsInput);
configured &= loraDevice->configure(PreambleLength, value);
buffer = lv_textarea_get_text(txPowInput);
if (!buffer.empty()) {
configured &= loraDevice->configure(Power, std::stof(buffer));
}
}
void enableDevice()
{
/*
loraDevice = loraService->getDevice(loraDevNames[lv_dropdown_get_selected(loraDeviceInput)]);
loraDevice = loraDevs[lv_dropdown_get_selected(loraDeviceInput)];
if (loraDevice) {
disableForm();
loraDevice->start(loraParams);
cbDevActive(*loraDevice);
configureFromForm();
loraDevice->start(tt::hal::radio::RadioDevice::Modulation::LoRa);
vTaskDelay(pdMS_TO_TICKS(500));
if (loraDevice->getState() != tt::hal::radio::RadioDevice::State::On) {
lv_obj_clear_state(loraDeviceOn, LV_STATE_CHECKED);
enableForm();
} else {
cbDevActive(loraDevice);
}
} else {
lv_obj_clear_state(loraDeviceOn, LV_STATE_CHECKED);
}*/
}
}
void disableDevice()
{/*
{
if (loraDevice) {
loraDevice->stop();
cbDevInactive(*loraDevice);
cbDevInactive(loraDevice);
}
enableForm();
*/
}
};
@ -414,6 +472,7 @@ class ChirpChatterApp : public App {
lv_obj_t* sidebar = nullptr;
lv_obj_t* mainView = nullptr;
lv_obj_t* progressBar = nullptr;
lv_obj_t* progressText = nullptr;
lv_obj_t* messageList = nullptr;
lv_obj_t* inputField = nullptr;
@ -422,6 +481,7 @@ class ChirpChatterApp : public App {
LoraView* loraView = nullptr;
hal::radio::RadioDevice::RxSubscriptionId rxSubId;
std::shared_ptr<tt::hal::radio::RadioDevice> loraDevice;
template<CCViews T>
lv_obj_t* createSidebarButton(lv_obj_t* parent, const char* image_file) {
@ -498,9 +558,14 @@ class ChirpChatterApp : public App {
lv_obj_t* msg_label = lv_label_create(msg_container);
std::string buf((char*)packet.data, packet.size);
lv_label_set_text(msg_label, buf.c_str());
std::vector<uint8_t> data(packet.data, packet.data + packet.size);
std::string messageBuf = "";
if (isPrintableData(data)) {
messageBuf = std::string((char*)packet.data, packet.size);
} else {
messageBuf = hexdump(data);
}
lv_label_set_text(msg_label, messageBuf.c_str());
lv_obj_set_width(msg_label, lv_pct(100));
lv_label_set_long_mode(msg_label, LV_LABEL_LONG_WRAP);
lv_obj_set_style_text_align(msg_label, LV_TEXT_ALIGN_LEFT, 0);
@ -565,13 +630,15 @@ public:
lv_obj_add_style(grid, &style_grid, LV_PART_MAIN);
// Create toolbar
auto* toolbar = tt::lvgl::toolbar_create(grid, "Transmitting message...");
auto* toolbar = tt::lvgl::toolbar_create(grid, "Welcome to ChirpChatter!");
lv_obj_set_size(toolbar, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
lv_obj_set_grid_cell(toolbar, LV_GRID_ALIGN_STRETCH, 0, 2,
LV_GRID_ALIGN_STRETCH, 0, 1);
auto* text = lv_obj_get_child_by_type(toolbar, 0, &lv_label_class);
lv_obj_set_style_text_font(text, &lv_font_montserrat_12, 0);
progressText = lv_obj_get_child_by_type(toolbar, 0, &lv_label_class);
lv_obj_align(progressText, LV_ALIGN_TOP_LEFT, 0, 0);
lv_obj_set_style_text_font(progressText, &lv_font_montserrat_12, 0);
lv_obj_set_size(progressText, lv_pct(75), LV_SIZE_CONTENT);
// Create sidebar
sidebar = lv_obj_create(grid);
@ -585,12 +652,15 @@ public:
lv_obj_set_flex_flow(sidebar, LV_FLEX_FLOW_COLUMN);
// Create progress bar
/*progressBar = lv_bar_create(toolbar);
lv_obj_align(progressBar, LV_ALIGN_BOTTOM_RIGHT, 0, 0);
lv_obj_set_size(progressBar, lv_pct(100), 20);
lv_obj_set_style_radius(progressBar, 0, LV_PART_MAIN);
lv_bar_set_start_value(progressBar, 50, LV_ANIM_ON);
*/
progressBar = lv_bar_create(toolbar);
//lv_obj_set_flex_flow(toolbar, LV_FLEX_FLOW_COLUMN);
lv_obj_align(progressBar, LV_ALIGN_RIGHT_MID, 0, 0);
lv_obj_set_size(progressBar, lv_pct(25), 36);
lv_obj_set_style_radius(progressBar, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_radius(progressBar, 0, LV_PART_INDICATOR | LV_STATE_DEFAULT);
lv_bar_set_range(progressBar, 0, 100);
lv_bar_set_value(progressBar, 100, LV_ANIM_OFF);
auto paths = context.getPaths();
auto icon_msgs_path = paths->getSystemPathLvgl("icon_msgs.png");
createSidebarButton<CCView_Msgs>(sidebar, icon_msgs_path.c_str());
@ -687,8 +757,14 @@ public:
//loraView->onDeviceActivation(std::bind(&ChirpChatterApp::onDeviceActivation, this));
//loraView->onDeviceDeactivation(std::bind(&ChirpChatterApp::onDeviceDeactivation, this));
//loraView->onDeviceActivation([this](hal::lora::LoraDevice& dev) { this->onDeviceActivation(dev); });
//loraView->onDeviceDeactivation([this](hal::lora::LoraDevice& dev) { this->onDeviceDeactivation(dev); });
loraView->onDeviceActivation([this](std::shared_ptr<tt::hal::radio::RadioDevice> dev) { this->onDeviceActivation(dev); });
loraView->onDeviceDeactivation([this](std::shared_ptr<tt::hal::radio::RadioDevice> dev) { this->onDeviceDeactivation(dev); });
lv_obj_add_event_cb(send_btn, [](lv_event_t * e) {
lv_obj_t* input = lv_event_get_target_obj(e);
ChirpChatterApp* self = (ChirpChatterApp*)lv_event_get_user_data(e);
self->sendMessage();
}, LV_EVENT_SHORT_CLICKED, (void*)this);
changeView(CCView_Msgs);
}
@ -697,15 +773,57 @@ public:
addPacketMessage(packet);
}
void onDeviceActivation(hal::radio::RadioDevice& dev) {
//rxSubId = dev.subscribeRx(std::bind(&ChirpChatterApp::onRxPacket, this));
rxSubId = dev.subscribeRx([this](hal::Device::Id id, const hal::radio::RxPacket& packet) {
void onDeviceActivation(std::shared_ptr<tt::hal::radio::RadioDevice> dev) {
rxSubId = dev->subscribeRx([this](hal::Device::Id id, const hal::radio::RxPacket& packet) {
this->onRxPacket(id, packet);
});
loraDevice = dev;
std::ostringstream oss;
oss << "Device \"" << dev->getName() << "\" online";
lv_label_set_text(progressText, oss.str().c_str());
}
void onDeviceDeactivation(std::shared_ptr<tt::hal::radio::RadioDevice> dev) {
dev->unsubscribeRx(rxSubId);
loraDevice = nullptr;
lv_label_set_text(progressText, "Offline");
}
void sendMessage() {
std::string message = lv_textarea_get_text(inputField);
std::vector<uint8_t> data(message.begin(), message.end());
loraDevice->transmit(data, [this](hal::radio::RadioDevice::TxId id, hal::radio::RadioDevice::TransmissionState state) {
this->onTxStatus(id, state);
});
}
void onDeviceDeactivation(hal::radio::RadioDevice& dev) {
dev.unsubscribeRx(rxSubId);
void onTxStatus(hal::radio::RadioDevice::TxId id, hal::radio::RadioDevice::TransmissionState state) {
using enum hal::radio::RadioDevice::TransmissionState;
switch (state) {
case Queued:
lv_label_set_text(progressText, "Message queued...");
lv_bar_set_value(progressBar, 25, LV_ANIM_ON);
break;
case PendingTransmit:
lv_label_set_text(progressText, "Message transmitting...");
lv_bar_set_value(progressBar, 50, LV_ANIM_ON);
break;
case Transmitted:
lv_label_set_text(progressText, "Message transmitted!\nReturn to receive.");
lv_bar_set_value(progressBar, 100, LV_ANIM_ON);
break;
case Timeout:
lv_label_set_text(progressText, "Message transmit timed out!");
lv_bar_set_value(progressBar, 100, LV_ANIM_ON);
break;
case Error:
lv_label_set_text(progressText, "Error transmitting message!");
lv_bar_set_value(progressBar, 100, LV_ANIM_ON);
break;
}
}
void changeView(const CCViews view) {