New variant variants/m5stack_cardputer_adv for the Cardputer-Adv (ESP32-S3, 8 MB) with the Cap LoRa-1262 (SX1262 + GNSS): - Envs: companion_radio_ble, companion_radio_usb, repeater, room_server. - Board: enables the Cap's antenna path via the PI4IOE5V6408 on the internal I2C bus (Wire1, G8/G9); Wire stays on the Grove port. LoRa and microSD share FSPI because the display owns SPI3. TCXO 1.8 V. - Companion UI adapted from MultiMote/meshcore-cardputer-adv (MIT, LICENSE.MultiMote), running on the standard companion so the USB terminal CLI stays available. Contacts/identity stay in internal SPIFFS; the microSD card is optional (chat history, keyboard layouts). - Sticky Shift, one-shot Fn with Fn+letter shortcuts, top button (G0) sends a flood advert, brightness control. - Text size setting (Normal/Large/Extra large) with DejaVu Sans fonts generated by tools/gen_vlw_font.py; layouts derive from the font size. - Power save maps to the companion's powersaving pref; the UI vetoes light sleep while the screen is on. Low-battery power-off at 3.4 V. - win_long_cmdline.py: Windows-only -iprefix include shortening so the ESP32 compile command stays under the 32,767-character limit. Shared companion changes are guarded by CARDPUTER_ADV_UI: main.cpp selects CardputerDataStore/CardputerUITask/CardputerMesh, and MyMesh.h maps the_mesh to the subclass like the PSRAM storage path does. Tested on hardware (910.525 MHz mesh): radio RX/TX, USB CLI, BLE, UI. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
80 lines
2.1 KiB
C++
80 lines
2.1 KiB
C++
#pragma once
|
|
#include "globals.h"
|
|
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
|
|
struct UnreadCounterItem {
|
|
bool is_channel;
|
|
uint8_t pkey[CONTACT_LOOKUP_BYTES]; // todo: maybe replace to index, but what should we do if it gets deleted?
|
|
int count;
|
|
};
|
|
|
|
class UnreadCounter {
|
|
|
|
private:
|
|
UnreadCounterItem _items[UNREAD_COUNTER_MAX_ITEMS];
|
|
uint16_t _count = 0;
|
|
using LookupPkey = uint8_t[CONTACT_LOOKUP_BYTES];
|
|
|
|
public:
|
|
UnreadCounter() {}
|
|
|
|
const UnreadCounterItem *get(uint16_t index) const {
|
|
if (index >= _count) {
|
|
return nullptr;
|
|
}
|
|
return &_items[index];
|
|
}
|
|
|
|
inline const uint16_t countChats() const { return _count; }
|
|
const uint16_t countMessages() const {
|
|
uint16_t total = 0;
|
|
for (uint16_t i = 0; i < _count; i++) {
|
|
total += _items[i].count;
|
|
}
|
|
return total;
|
|
}
|
|
|
|
bool add(bool is_channel, const LookupPkey pkey, int count) {
|
|
for (uint16_t i = 0; i < _count; i++) {
|
|
if (_items[i].is_channel == is_channel && memcmp(_items[i].pkey, pkey, CONTACT_LOOKUP_BYTES) == 0) {
|
|
_items[i].count += count;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (_count < UNREAD_COUNTER_MAX_ITEMS) {
|
|
_items[_count].is_channel = is_channel;
|
|
_items[_count].count = count;
|
|
memcpy(_items[_count].pkey, pkey, CONTACT_LOOKUP_BYTES);
|
|
_count++;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void reset(bool is_channel, const LookupPkey pkey) {
|
|
for (uint16_t i = 0; i < _count; i++) {
|
|
if (_items[i].is_channel == is_channel && memcmp(_items[i].pkey, pkey, CONTACT_LOOKUP_BYTES) == 0) {
|
|
// Swap the target item with the last active item
|
|
if (i < _count - 1) {
|
|
_items[i] = _items[_count - 1];
|
|
}
|
|
|
|
_count--;
|
|
_items[_count] = { false, 0, 0 };
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
inline void addChannel(const LookupPkey pkey, int count) { add(true, pkey, count); }
|
|
|
|
inline void addContact(const LookupPkey pkey, int count) { add(false, pkey, count); }
|
|
|
|
inline void resetChannel(const LookupPkey pkey) { reset(true, pkey); }
|
|
|
|
inline void resetContact(const LookupPkey pkey) { reset(false, pkey); }
|
|
};
|