486 lines
16 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "TermView.h"
#include <tt_kernel.h>
#include <time.h>
#include "Str.h"
#include "Utils.h"
extern lv_font_t lv_font_unscii_8;
extern lv_font_t lv_font_ptmono_12;
class Message {
public:
uint8_t* data;
size_t size;
Message(const uint8_t* packetData, const size_t packetSize) {
size = packetSize;
data = new uint8_t[size];
memcpy(data, packetData, size);
}
virtual lv_obj_t* makeMessageBox(lv_obj_t* parent) = 0;
virtual void refresh() = 0;
virtual ~Message() {
delete[] data;
}
};
class RxMessage : public Message {
public:
RadioHandle radio;
float rssi;
float snr;
lv_obj_t* msgBox = nullptr;
RxMessage(RadioHandle radio, const RadioRxPacket* packet)
: Message(packet->data, packet->size)
, radio(radio) {
rssi = packet->rssi;
snr = packet->snr;
}
static void applyStyle(lv_obj_t* obj) {
static lv_style_t style;
static bool init = false;
if (!init) {
lv_style_init(&style);
lv_style_set_border_color(&style, lv_color_make(0x0A, 0x5C, 0x36));
lv_style_set_border_side(&style, LV_BORDER_SIDE_LEFT);
lv_style_set_border_width(&style, 5);
lv_style_set_pad_all(&style, 1);
lv_style_set_margin_bottom(&style, 2);
lv_style_set_radius(&style, 0);
}
lv_obj_add_style(obj, &style, LV_PART_MAIN);
}
virtual lv_obj_t* makeMessageBox(lv_obj_t* parent) {
char timebuffer[32] = {0};
Str info;
Str message;
bool is_txt = false;
auto* msg_container = lv_obj_create(parent);
lv_obj_set_flex_flow(msg_container, LV_FLEX_FLOW_COLUMN);
//lv_obj_set_flex_grow(msg_container, 0);
lv_obj_set_style_pad_all(msg_container, 1, 0);
//lv_obj_add_flag(msg_container, LV_OBJ_FLAG_CLICKABLE);
lv_obj_t* msg_label = lv_label_create(msg_container);
is_txt = isPrintable(data, size);
if (is_txt) {
message.set((const char*)data, ((const char*)data + size));
} else {
hexdump(message, data, size);
}
lv_label_set_text(msg_label, message.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);
lv_obj_set_style_pad_all(msg_label, 0, 0);
lv_obj_set_style_text_font(msg_label, &lv_font_ptmono_12, 0);
lv_obj_t* msg_info = lv_label_create(msg_container);
// Get time
time_t now = time(nullptr);
struct tm* tm_info = localtime(&now);
strftime(timebuffer, sizeof(timebuffer), "%H:%M:%S", tm_info);
info.append("RX/");
// Get device name and modulation
auto modulation = MODULATION_NONE;
if (radio) {
info.append(tt_hal_radio_get_name(radio));
modulation = tt_hal_radio_get_modulation(radio);
info.append(" ");
} else {
info.append("?");
}
info.append(toString(modulation));
info.append(is_txt ? "/TXT " : "/BIN ");
info.append(timebuffer);
info.appendf(" SI:%.2f SN:%.2f PS:%d", rssi, snr, size);
lv_label_set_text(msg_info, info.c_str());
lv_obj_set_width(msg_info, lv_pct(100));
lv_label_set_long_mode(msg_info, LV_LABEL_LONG_WRAP);
lv_obj_set_style_text_align(msg_info, LV_TEXT_ALIGN_LEFT, 0);
lv_obj_set_style_pad_all(msg_info, 0, 0);
lv_obj_set_style_text_font(msg_info, &lv_font_unscii_8, 0);
lv_obj_set_width(msg_container, lv_pct(100));
lv_obj_update_layout(msg_label);
lv_obj_update_layout(msg_info);
lv_obj_set_height(msg_container, LV_SIZE_CONTENT);
applyStyle(msg_container);
msgBox = msg_container;
//clownvomit(msg_container);
return msg_container;
}
virtual void refresh() {}
virtual ~RxMessage() {
if (msgBox) {
lv_obj_del(msgBox);
}
}
};
class TxMessage : public Message {
public:
RadioHandle radio;
RadioTxState lastState;
uint32_t address;
lv_obj_t* msgBox = nullptr;
lv_obj_t* statusLabel = nullptr;
TxMessage(RadioHandle radio, const RadioTxPacket* packet)
: Message(packet->data, packet->size)
, radio(radio) {
address = packet->address;
}
static void updateStatusFor(RadioTxId id, RadioTxState state, void* ctx) {
static_cast<TxMessage*>(ctx)->updateStatus(id, state);
}
static void applyStyle(lv_obj_t* obj) {
static lv_style_t style;
static bool init = false;
if (!init) {
lv_style_init(&style);
lv_style_set_border_color(&style, lv_color_make(0xFF, 0xBF, 0x00));
lv_style_set_border_side(&style, LV_BORDER_SIDE_LEFT);
lv_style_set_border_width(&style, 5);
lv_style_set_pad_all(&style, 1);
lv_style_set_margin_bottom(&style, 2);
lv_style_set_radius(&style, 0);
}
lv_obj_add_style(obj, &style, LV_PART_MAIN);
}
virtual lv_obj_t* makeMessageBox(lv_obj_t* parent) {
char timebuffer[32] = {0};
Str info;
Str message;
auto* msg_container = lv_obj_create(parent);
static lv_coord_t col_dsc[] = {LV_GRID_FR(1), LV_GRID_TEMPLATE_LAST};
static lv_coord_t row_dsc[] = {LV_GRID_CONTENT, /* row 0 label 0 */
LV_GRID_CONTENT, /* row 1 labels 1+2 */
LV_GRID_TEMPLATE_LAST};
lv_obj_set_grid_dsc_array(msg_container, col_dsc, row_dsc);
lv_obj_set_style_pad_all(msg_container, 1, 0);
lv_obj_t* msg_label = lv_label_create(msg_container);
if (isPrintable(data, size)) {
message.set((const char*)data, ((const char*)data + size));
} else {
hexdump(message, data, size);
}
lv_label_set_text(msg_label, message.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);
lv_obj_set_style_pad_all(msg_label, 0, 0);
lv_obj_set_style_text_font(msg_label, &lv_font_ptmono_12, 0);
lv_obj_t* msg_info = lv_label_create(msg_container);
// Get time
time_t now = time(nullptr);
struct tm* tm_info = localtime(&now);
strftime(timebuffer, sizeof(timebuffer), "%H:%M:%S", tm_info);
info.append("TX/");
// Get device name and modulation
auto modulation = MODULATION_NONE;
if (radio) {
info.append(tt_hal_radio_get_name(radio));
modulation = tt_hal_radio_get_modulation(radio);
info.append(" ");
} else {
info.append("?");
}
bool is_txt = isPrintable(data, size);
info.append(toString(modulation));
info.append(is_txt ? "/TXT " : "/BIN ");
info.append(timebuffer);
info.appendf(" PS:%d", size);
lv_label_set_text(msg_info, info.c_str());
lv_obj_set_width(msg_info, LV_SIZE_CONTENT);
lv_label_set_long_mode(msg_info, LV_LABEL_LONG_WRAP);
lv_obj_set_style_text_align(msg_info, LV_TEXT_ALIGN_LEFT, 0);
lv_obj_set_style_pad_all(msg_info, 0, 0);
lv_obj_set_style_text_font(msg_info, &lv_font_unscii_8, 0);
statusLabel = lv_label_create(msg_container);
lv_label_set_text(statusLabel, "NEW");
lv_obj_set_width(statusLabel, LV_SIZE_CONTENT);
lv_label_set_long_mode(statusLabel, LV_LABEL_LONG_WRAP);
lv_obj_set_style_text_align(statusLabel, LV_TEXT_ALIGN_RIGHT, 0);
lv_obj_set_style_pad_all(statusLabel, 0, 0);
lv_obj_set_style_text_font(statusLabel, &lv_font_unscii_8, 0);
lv_obj_update_layout(msg_label);
lv_obj_update_layout(msg_info);
lv_obj_update_layout(statusLabel);
/* attach items (child index, column, row, column-span, row-span) */
lv_obj_set_grid_cell(msg_label, LV_GRID_ALIGN_STRETCH, 0, 1,
LV_GRID_ALIGN_CENTER, 0, 1);
lv_obj_set_grid_cell(msg_info, LV_GRID_ALIGN_START, 0, 1,
LV_GRID_ALIGN_CENTER, 1, 1);
lv_obj_set_grid_cell(statusLabel, LV_GRID_ALIGN_END, 0, 1,
LV_GRID_ALIGN_CENTER, 1, 1);
lv_obj_set_width(msg_container, lv_pct(100));
lv_obj_set_height(msg_container, LV_SIZE_CONTENT);
applyStyle(msg_container);
msgBox = msg_container;
return msg_container;
}
void updateStatus(RadioTxId id, RadioTxState state) {
lastState = state;
}
virtual void refresh() {
const auto* status_txt = toString(lastState);
if (statusLabel) {
auto indicatorColor = lv_color_make(0xFF, 0x00, 0x00);
switch (lastState) {
case RADIO_TX_QUEUED:
// Dark Amber
indicatorColor = lv_color_make(0xD5, 0x36, 0x00);
break;
case RADIO_TX_PENDING_TRANSMIT:
// Light Amber
indicatorColor = lv_color_make(0xFF, 0xD2, 0x2B);
break;
case RADIO_TX_TRANSMITTED:
// Green
indicatorColor = lv_color_make(0x8F, 0xCE, 0x00);
break;
case RADIO_TX_TIMEOUT:
// Purple
indicatorColor = lv_color_make(0xC9, 0x00, 0x76);
break;
case RADIO_TX_ERROR:
// Full Red
default:
break;
}
lv_obj_set_style_bg_opa(statusLabel, LV_OPA_COVER, 0);
lv_obj_set_style_bg_color(statusLabel, indicatorColor, 0);
lv_obj_set_style_text_color(statusLabel, lv_color_make(0x00, 0x00, 0x00), 0);
lv_obj_set_style_pad_all(statusLabel, 1, 0);
lv_label_set_text(statusLabel, status_txt);
}
}
virtual ~TxMessage() {
if (msgBox) {
lv_obj_del(msgBox);
}
}
};
void TermView::initUi(lv_obj_t* parent) {
mainPanel = lv_obj_create(parent);
lv_obj_set_size(mainPanel, lv_pct(100), lv_pct(100));
lv_obj_set_flex_flow(mainPanel, LV_FLEX_FLOW_COLUMN);
lv_obj_align(mainPanel, LV_ALIGN_TOP_MID, 0, 0);
lv_obj_set_style_border_width(mainPanel, 0, 0);
lv_obj_set_style_pad_all(mainPanel, 0, 0);
messageList = lv_obj_create(mainPanel);
lv_obj_set_size(messageList, lv_pct(100), lv_pct(100));
lv_obj_set_flex_flow(messageList, LV_FLEX_FLOW_COLUMN);
lv_obj_align(messageList, LV_ALIGN_TOP_MID, 0, 0);
lv_obj_set_style_border_width(messageList, 0, 0);
lv_obj_set_style_pad_all(messageList, 0, 0);
make_scrollable(messageList);
// Input panel
auto* input_panel = lv_obj_create(mainPanel);
lv_obj_set_flex_flow(input_panel, LV_FLEX_FLOW_ROW);
lv_obj_set_size(input_panel, lv_pct(100), LV_SIZE_CONTENT);
lv_obj_align(input_panel, LV_ALIGN_BOTTOM_MID, 0, 0);
lv_obj_set_flex_align(input_panel, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
lv_obj_set_style_pad_all(input_panel, 5, 0);
// Input field
inputField = lv_textarea_create(input_panel);
lv_obj_set_flex_grow(inputField, 1);
lv_obj_set_height(inputField, LV_PCT(100));
lv_textarea_set_placeholder_text(inputField, "Type a message...");
lv_textarea_set_one_line(inputField, true);
// Send button
sendButton = lv_btn_create(input_panel);
lv_obj_set_size(sendButton, 50, LV_SIZE_CONTENT);
//lv_obj_add_event_cb(send_btn, onSendClicked, LV_EVENT_CLICKED, this);
auto* btn_label = lv_label_create(sendButton);
lv_label_set_text(btn_label, "SEND");
lv_obj_center(btn_label);
lv_obj_set_flex_grow(messageList, 1);
lv_obj_set_flex_grow(input_panel, 0);
lv_obj_add_event_cb(sendButton, [](lv_event_t * e) {
lv_obj_t* input = lv_event_get_target_obj(e);
auto* self = (TermView*)lv_event_get_user_data(e);
self->sendMessage();
}, LV_EVENT_SHORT_CLICKED, (void*)this);
renderMessagesTimer = lv_timer_create([](lv_timer_t* timer) {
auto self = reinterpret_cast<TermView*>(lv_timer_get_user_data(timer));
self->updateMessages();
}, 500, this);
}
void TermView::setVisible(bool visible) {
if (visible) {
lv_obj_clear_flag(mainPanel, LV_OBJ_FLAG_HIDDEN);
} else {
lv_obj_add_flag(mainPanel, LV_OBJ_FLAG_HIDDEN);
}
}
void TermView::addRadio(RadioHandle radio) {
auto sub_id = tt_hal_radio_subscribe_receive(radio, [](DeviceId id, const RadioRxPacket* packet, void* ctx) {
auto* self = (TermView*)ctx;
self->pushRxMessage(id, packet);
}, this);
radios.pushBack(RadioItem{
.handle = radio,
.rxSubId = sub_id
});
// TODO: Remove once radio select dropdown is there
selectedRadio = radio;
}
void TermView::cleanupRadios() {
for (auto iter = radios.begin(); iter != radios.end(); iter++) {
tt_hal_radio_unsubscribe_receive(iter->handle, iter->rxSubId);
}
}
void TermView::sendMessage() {
auto* message = lv_textarea_get_text(inputField);
auto txPacket = RadioTxPacket {
.data = (uint8_t*)message, // I am NOT allocating for this
.size = strlen(message),
.address = 0
};
auto* txBox = new TxMessage(selectedRadio, &txPacket);
tt_mutex_lock(mutex, portMAX_DELAY);
inMsgQueue.pushBack(txBox);
signalHasMsgs = true;
tt_mutex_unlock(mutex);
tt_hal_radio_transmit(selectedRadio, txPacket, TxMessage::updateStatusFor, txBox);
}
void TermView::pushRxMessage(DeviceId device, const RadioRxPacket* packet) {
auto radio = getRadioForDeviceId(device);
tt_mutex_lock(mutex, portMAX_DELAY);
auto* rxBox = new RxMessage(radio, packet);
inMsgQueue.pushBack(rxBox);
signalHasMsgs = true;
tt_mutex_unlock(mutex);
}
void TermView::run() {
while(!getSignalStop()) {
while(!getSignalHasMsg() && !getSignalStop()) {
tt_kernel_delay_millis(100);
}
if (getSignalStop()) {
break;
}
tt_mutex_lock(lvglMutex, portMAX_DELAY);
while (!inMsgQueue.empty()) {
auto* message = inMsgQueue.front();
inMsgQueue.popFront();
if (messages.size() >= MESSAGE_LIMIT) {
delete messages.back();
messages.popBack();
}
message->makeMessageBox(messageList);
messages.pushFront(message);
}
lv_obj_update_layout(messageList);
tt_mutex_unlock(lvglMutex);
clearSignalHasMsg();
}
}
void TermView::updateMessages() {
bool at_bottom = (lv_obj_get_scroll_bottom(messageList) <= 0);
bool focused = lv_obj_has_state(messageList, LV_STATE_FOCUSED);
tt_mutex_lock(lvglMutex, portMAX_DELAY);
while (!inMsgQueue.empty()) {
auto* message = inMsgQueue.front();
inMsgQueue.popFront();
if (messages.size() >= MESSAGE_LIMIT) {
delete messages.back();
messages.popBack();
}
message->makeMessageBox(messageList);
messages.pushFront(message);
}
lv_obj_update_layout(messageList);
tt_mutex_unlock(lvglMutex);
// demorgan spinning in his grave for his teachings are for naught
//if (!(!at_bottom || focused)) {
if (at_bottom) {
lv_obj_scroll_to_y(messageList, LV_COORD_MAX, LV_ANIM_ON);
}
for (auto iter = messages.begin(); iter != messages.end(); iter++) {
(*iter)->refresh();
}
}