diff --git a/Tactility/Source/app/chirpchatter/ChirpChatterApp.cpp b/Tactility/Source/app/chirpchatter/ChirpChatterApp.cpp index e759db08..5a064a66 100644 --- a/Tactility/Source/app/chirpchatter/ChirpChatterApp.cpp +++ b/Tactility/Source/app/chirpchatter/ChirpChatterApp.cpp @@ -14,6 +14,12 @@ #include #include #include +#include +#include +#include +#include +#include +#include #include 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& data) { + return std::all_of(data.begin(), data.end(), + [](uint8_t byte) { + char c = static_cast(byte); + return std::isprint(static_cast(c)); + }); +} + +std::string hexdump(const std::vector& 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(data[i]); + if (i + 1 != data.size()) oss << ' '; + } + return oss.str(); +} + + class LoraView { public: - using DeviceActivationCallback = std::function; + using DeviceActivationCallback = std::function)>; private: //using LoraParameters = tt::hal::lora::LoraParameters; std::vector loraDevNames; + std::vector> loraDevs; + std::shared_ptr loraDevice; DeviceActivationCallback cbDevActive; DeviceActivationCallback cbDevInactive; - std::vector getLoraDevNames() { - std::vector lora_names; - /*size_t dev_index = 0; + void queryLoraDevs() { + auto radios = tt::hal::findDevices(tt::hal::Device::Type::Radio); + loraDevNames.clear(); + loraDevs.clear(); - std::vector 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 loraDevice; template 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 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(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 dev) { this->onDeviceActivation(dev); }); + loraView->onDeviceDeactivation([this](std::shared_ptr 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 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 dev) { + dev->unsubscribeRx(rxSubId); + loraDevice = nullptr; + + lv_label_set_text(progressText, "Offline"); + } + + void sendMessage() { + std::string message = lv_textarea_get_text(inputField); + std::vector 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) {