Merge branch 'main' into main

This commit is contained in:
hank 2025-05-26 17:11:23 -07:00 committed by GitHub
commit 468ccf02cf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
185 changed files with 14023 additions and 754 deletions

View file

@ -8,8 +8,14 @@
memcpy(&app_data[i], &_lat, 4); i += 4;
memcpy(&app_data[i], &_lon, 4); i += 4;
}
// TODO: BATTERY encoding
// TODO: TEMPERATURE encoding
if (_extra1) {
app_data[0] |= ADV_FEAT1_MASK;
memcpy(&app_data[i], &_extra1, 2); i += 2;
}
if (_extra2) {
app_data[0] |= ADV_FEAT2_MASK;
memcpy(&app_data[i], &_extra2, 2); i += 2;
}
if (_name && *_name != 0) {
app_data[0] |= ADV_NAME_MASK;
const char* sp = _name;
@ -25,17 +31,18 @@
_lat = _lon = 0;
_flags = app_data[0];
_valid = false;
_extra1 = _extra2 = 0;
int i = 1;
if (_flags & ADV_LATLON_MASK) {
memcpy(&_lat, &app_data[i], 4); i += 4;
memcpy(&_lon, &app_data[i], 4); i += 4;
}
if (_flags & ADV_BATTERY_MASK) {
/* TODO: somewhere to store battery volts? */ i += 2;
if (_flags & ADV_FEAT1_MASK) {
memcpy(&_extra1, &app_data[i], 2); i += 2;
}
if (_flags & ADV_TEMPERATURE_MASK) {
/* TODO: somewhere to store temperature? */ i += 2;
if (_flags & ADV_FEAT2_MASK) {
memcpy(&_extra2, &app_data[i], 2); i += 2;
}
if (app_data_len >= i) {

View file

@ -11,20 +11,25 @@
//FUTURE: 4..15
#define ADV_LATLON_MASK 0x10
#define ADV_BATTERY_MASK 0x20
#define ADV_TEMPERATURE_MASK 0x40
#define ADV_FEAT1_MASK 0x20 // FUTURE
#define ADV_FEAT2_MASK 0x40 // FUTURE
#define ADV_NAME_MASK 0x80
class AdvertDataBuilder {
uint8_t _type;
const char* _name;
int32_t _lat, _lon;
uint16_t _extra1 = 0;
uint16_t _extra2 = 0;
public:
AdvertDataBuilder(uint8_t adv_type) : _type(adv_type), _name(NULL), _lat(0), _lon(0) { }
AdvertDataBuilder(uint8_t adv_type, const char* name) : _type(adv_type), _name(name), _lat(0), _lon(0) { }
AdvertDataBuilder(uint8_t adv_type, const char* name, double lat, double lon) :
_type(adv_type), _name(name), _lat(lat * 1E6), _lon(lon * 1E6) { }
void setFeat1(uint16_t extra) { _extra1 = extra; }
void setFeat2(uint16_t extra) { _extra2 = extra; }
/**
* \brief encode the given advertisement data.
* \param app_data dest array, must be MAX_ADVERT_DATA_SIZE
@ -38,11 +43,15 @@ class AdvertDataParser {
bool _valid;
char _name[MAX_ADVERT_DATA_SIZE];
int32_t _lat, _lon;
uint16_t _extra1;
uint16_t _extra2;
public:
AdvertDataParser(const uint8_t app_data[], uint8_t app_data_len);
bool isValid() const { return _valid; }
uint8_t getType() const { return _flags & 0x0F; }
uint16_t getFeat1() const { return _extra1; }
uint16_t getFeat2() const { return _extra2; }
bool hasName() const { return _name[0] != 0; }
const char* getName() const { return _name; }

View file

@ -8,30 +8,18 @@ class ArduinoSerialInterface : public BaseSerialInterface {
uint8_t _state;
uint16_t _frame_len;
uint16_t rx_len;
#ifdef LILYGO_T3S3
HWCDC* _serial;
#elif defined(NRF52_PLATFORM)
Adafruit_USBD_CDC* _serial;
#else
HardwareSerial* _serial;
#endif
Stream* _serial;
uint8_t rx_buf[MAX_FRAME_SIZE];
public:
ArduinoSerialInterface() { _isEnabled = false; _state = 0; }
#ifdef LILYGO_T3S3
void begin(HWCDC& serial) { _serial = &serial; }
#elif defined(NRF52_PLATFORM)
void begin(Adafruit_USBD_CDC& serial) {
_serial = &serial;
void begin(Stream& serial) {
_serial = &serial;
#ifdef RAK_4631
pinMode(WB_IO2, OUTPUT);
#endif
#endif
}
#else
void begin(HardwareSerial& serial) { _serial = &serial; }
#endif
// BaseSerialInterface methods
void enable() override;

View file

@ -178,6 +178,27 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender
} else {
MESH_DEBUG_PRINTLN("onPeerDataRecv: unsupported message type: %u", (uint32_t) flags);
}
} else if (type == PAYLOAD_TYPE_REQ && len > 4) {
uint32_t sender_timestamp;
memcpy(&sender_timestamp, data, 4);
uint8_t reply_len = onContactRequest(from, sender_timestamp, &data[4], len - 4, temp_buf);
if (reply_len > 0) {
if (packet->isRouteFlood()) {
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response
mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len,
PAYLOAD_TYPE_RESPONSE, temp_buf, reply_len);
if (path) sendFlood(path);
} else {
mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, from.id, secret, temp_buf, reply_len);
if (reply) {
if (from.out_path_len >= 0) { // we have an out_path, so send DIRECT
sendDirect(reply, from.out_path, from.out_path_len);
} else {
sendFlood(reply);
}
}
}
}
} else if (type == PAYLOAD_TYPE_RESPONSE && len > 0) {
onContactResponse(from, data, len);
}
@ -352,6 +373,7 @@ bool BaseChatMesh::importContact(const uint8_t src_buf[], uint8_t len) {
if (pkt) {
if (pkt->readFrom(src_buf, len) && pkt->getPayloadType() == PAYLOAD_TYPE_ADVERT) {
pkt->header |= ROUTE_TYPE_FLOOD; // simulate it being received flood-mode
getTables()->clear(pkt); // remove packet hash from table, so we can receive/process it again
_pendingLoopback = pkt; // loop-back, as if received over radio
return true; // success
} else {
@ -393,11 +415,11 @@ int BaseChatMesh::sendLogin(const ContactInfo& recipient, const char* password,
return MSG_SEND_FAILED;
}
int BaseChatMesh::sendStatusRequest(const ContactInfo& recipient, uint32_t& est_timeout) {
int BaseChatMesh::sendRequest(const ContactInfo& recipient, uint8_t req_type, uint32_t& tag, uint32_t& est_timeout) {
uint8_t temp[13];
uint32_t now = getRTCClock()->getCurrentTimeUnique();
memcpy(temp, &now, 4); // mostly an extra blob to help make packet_hash unique
temp[4] = REQ_TYPE_GET_STATUS;
tag = getRTCClock()->getCurrentTimeUnique();
memcpy(temp, &tag, 4); // mostly an extra blob to help make packet_hash unique
temp[4] = req_type;
memset(&temp[5], 0, 4); // reserved (possibly for 'since' param)
getRNG()->random(&temp[9], 4); // random blob to help make packet-hash unique

View file

@ -114,6 +114,7 @@ protected:
virtual uint32_t calcDirectTimeoutMillisFor(uint32_t pkt_airtime_millis, uint8_t path_len) const = 0;
virtual void onSendTimeout() = 0;
virtual void onChannelMessageRecv(const mesh::GroupChannel& channel, mesh::Packet* pkt, uint32_t timestamp, const char *text) = 0;
virtual uint8_t onContactRequest(const ContactInfo& contact, uint32_t sender_timestamp, const uint8_t* data, uint8_t len, uint8_t* reply) = 0;
virtual void onContactResponse(const ContactInfo& contact, const uint8_t* data, uint8_t len) = 0;
// storage concepts, for sub-classes to override/implement
@ -146,7 +147,7 @@ public:
int sendCommandData(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char* text, uint32_t& est_timeout);
bool sendGroupMessage(uint32_t timestamp, mesh::GroupChannel& channel, const char* sender_name, const char* text, int text_len);
int sendLogin(const ContactInfo& recipient, const char* password, uint32_t& est_timeout);
int sendStatusRequest(const ContactInfo& recipient, uint32_t& est_timeout);
int sendRequest(const ContactInfo& recipient, uint8_t req_type, uint32_t& tag, uint32_t& est_timeout);
bool shareContactZeroHop(const ContactInfo& contact);
uint8_t exportContact(const ContactInfo& contact, uint8_t dest_buf[]);
bool importContact(const uint8_t src_buf[], uint8_t len);

View file

@ -24,7 +24,11 @@ void CommonCLI::loadPrefs(FILESYSTEM* fs) {
}
void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
#if defined(RP2040_PLATFORM)
File file = fs->open(filename, "r");
#else
File file = fs->open(filename);
#endif
if (file) {
uint8_t pad[8];
@ -69,9 +73,11 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
}
void CommonCLI::savePrefs(FILESYSTEM* fs) {
#if defined(NRF52_PLATFORM)
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
fs->remove("/com_prefs");
File file = fs->open("/com_prefs", FILE_O_WRITE);
if (file) { file.seek(0); file.truncate(); }
#elif defined(RP2040_PLATFORM)
File file = fs->open("/com_prefs", "w");
#else
File file = fs->open("/com_prefs", "w", true);
#endif
@ -155,12 +161,17 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
} else {
strcpy(reply, "(ERR: clock cannot go backwards)");
}
} else if (memcmp(command, "neighbors", 9) == 0) {
_callbacks->formatNeighborsReply(reply);
} else if (memcmp(command, "password ", 9) == 0) {
// change admin password
StrHelper::strncpy(_prefs->password, &command[9], sizeof(_prefs->password));
checkAdvertInterval();
savePrefs();
sprintf(reply, "password now: %s", _prefs->password); // echo back just to let admin know for sure!!
} else if (memcmp(command, "clear stats", 11) == 0) {
_callbacks->clearStats();
strcpy(reply, "(OK - stats reset)");
} else if (memcmp(command, "get ", 4) == 0) {
const char* config = &command[4];
if (memcmp(config, "af", 2) == 0) {
@ -200,7 +211,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
sprintf(reply, "> %s", StrHelper::ftoa(_prefs->freq));
} else if (memcmp(config, "public.key", 10) == 0) {
strcpy(reply, "> ");
mesh::Utils::toHex(&reply[2], _mesh->self_id.pub_key, PUB_KEY_SIZE);
mesh::Utils::toHex(&reply[2], _callbacks->getSelfIdPubKey(), PUB_KEY_SIZE);
} else if (memcmp(config, "role", 4) == 0) {
sprintf(reply, "> %s", _callbacks->getRole());
} else {
@ -347,6 +358,6 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
_callbacks->dumpLogFile();
strcpy(reply, " EOF");
} else {
sprintf(reply, "Unknown: %s", command);
strcpy(reply, "Unknown command");
}
}

View file

@ -40,16 +40,19 @@ public:
virtual void eraseLogFile() = 0;
virtual void dumpLogFile() = 0;
virtual void setTxPower(uint8_t power_dbm) = 0;
virtual void formatNeighborsReply(char *reply) = 0;
virtual const uint8_t* getSelfIdPubKey() = 0;
virtual void clearStats() = 0;
};
class CommonCLI {
mesh::Mesh* _mesh;
mesh::RTCClock* _rtc;
NodePrefs* _prefs;
CommonCLICallbacks* _callbacks;
mesh::MainBoard* _board;
char tmp[80];
mesh::RTCClock* getRTCClock() { return _mesh->getRTCClock(); }
mesh::RTCClock* getRTCClock() { return _rtc; }
void savePrefs() { _callbacks->savePrefs(); }
void checkAdvertInterval();
@ -57,8 +60,8 @@ class CommonCLI {
void loadPrefsInt(FILESYSTEM* _fs, const char* filename);
public:
CommonCLI(mesh::MainBoard& board, mesh::Mesh* mesh, NodePrefs* prefs, CommonCLICallbacks* callbacks)
: _board(&board), _mesh(mesh), _prefs(prefs), _callbacks(callbacks) { }
CommonCLI(mesh::MainBoard& board, mesh::RTCClock& rtc, NodePrefs* prefs, CommonCLICallbacks* callbacks)
: _board(&board), _rtc(&rtc), _prefs(prefs), _callbacks(callbacks) { }
void loadPrefs(FILESYSTEM* _fs);
void savePrefs(FILESYSTEM* _fs);

View file

@ -0,0 +1,17 @@
#pragma once
#include <RadioLib.h>
#define SX126X_IRQ_HEADER_VALID 0b0000010000 // 4 4 valid LoRa header received
#define SX126X_IRQ_PREAMBLE_DETECTED 0x04
class CustomSTM32WLx : public STM32WLx {
public:
CustomSTM32WLx(STM32WLx_Module *mod) : STM32WLx(mod) { }
bool isReceiving() {
uint16_t irq = getIrqFlags();
bool detected = (irq & SX126X_IRQ_HEADER_VALID) || (irq & SX126X_IRQ_PREAMBLE_DETECTED);
return detected;
}
};

View file

@ -0,0 +1,30 @@
#pragma once
#include "CustomSTM32WLx.h"
#include "RadioLibWrappers.h"
#include <math.h>
class CustomSTM32WLxWrapper : public RadioLibWrapper {
public:
CustomSTM32WLxWrapper(CustomSTM32WLx& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { }
bool isReceiving() override {
if (((CustomSTM32WLx *)_radio)->isReceiving()) return true;
idle(); // put sx126x into standby
// do some basic CAD (blocks for ~12780 micros (on SF 10)!)
bool activity = (((CustomSTM32WLx *)_radio)->scanChannel() == RADIOLIB_LORA_DETECTED);
if (activity) {
startRecv();
} else {
idle();
}
return activity;
}
float getLastRSSI() const override { return ((CustomSTM32WLx *)_radio)->getRSSI(); }
float getLastSNR() const override { return ((CustomSTM32WLx *)_radio)->getSNR(); }
float packetScore(float snr, int packet_len) override {
int sf = ((CustomSTM32WLx *)_radio)->spreadingFactor;
return packetScoreInt(snr, sf, packet_len);
}
};

View file

@ -8,6 +8,8 @@
#include <ESPAsyncWebServer.h>
#include <AsyncElegantOTA.h>
#include <SPIFFS.h>
bool ESP32Board::startOTAUpdate(const char* id, char reply[]) {
WiFi.softAP("MeshCore-OTA", NULL);
@ -24,6 +26,9 @@ bool ESP32Board::startOTAUpdate(const char* id, char reply[]) {
server->on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/html", home_buf);
});
server->on("/log", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(SPIFFS, "/packet_log", "text/plain");
});
AsyncElegantOTA.setID(id_buf);
AsyncElegantOTA.begin(server); // Start ElegantOTA

View file

@ -1,8 +1,10 @@
#pragma once
#include <Arduino.h>
#include <helpers/RefCountedDigitalPin.h>
// LoRa radio module pins for Heltec V3
// Also for Heltec Wireless Tracker
#define P_LORA_DIO_1 14
#define P_LORA_NSS 8
#define P_LORA_RESET RADIOLIB_NC
@ -13,11 +15,12 @@
// built-ins
#define PIN_VBAT_READ 1
#define PIN_ADC_CTRL 37
#ifndef PIN_ADC_CTRL // set in platformio.ini for Heltec Wireless Tracker (2)
#define PIN_ADC_CTRL 37
#endif
#define PIN_ADC_CTRL_ACTIVE LOW
#define PIN_ADC_CTRL_INACTIVE HIGH
#define PIN_LED_BUILTIN 35
#define PIN_VEXT_EN 36
//#define PIN_LED_BUILTIN 35
#include "ESP32Board.h"
@ -28,6 +31,10 @@ private:
bool adc_active_state;
public:
RefCountedDigitalPin periph_power;
HeltecV3Board() : periph_power(PIN_VEXT_EN) { }
void begin() {
ESP32Board::begin();
@ -38,8 +45,7 @@ public:
pinMode(PIN_ADC_CTRL, OUTPUT);
digitalWrite(PIN_ADC_CTRL, !adc_active_state); // Initially inactive
pinMode(PIN_VEXT_EN, OUTPUT);
digitalWrite(PIN_VEXT_EN, LOW); // for V3.2 boards
periph_power.begin();
esp_reset_reason_t reason = esp_reset_reason();
if (reason == ESP_RST_DEEPSLEEP) {

View file

@ -5,7 +5,11 @@ bool IdentityStore::load(const char *name, mesh::LocalIdentity& id) {
char filename[40];
sprintf(filename, "%s/%s.id", _dir, name);
if (_fs->exists(filename)) {
#if defined(RP2040_PLATFORM)
File file = _fs->open(filename, "r");
#else
File file = _fs->open(filename);
#endif
if (file) {
loaded = id.readFrom(file);
file.close();
@ -19,7 +23,11 @@ bool IdentityStore::load(const char *name, mesh::LocalIdentity& id, char display
char filename[40];
sprintf(filename, "%s/%s.id", _dir, name);
if (_fs->exists(filename)) {
#if defined(RP2040_PLATFORM)
File file = _fs->open(filename, "r");
#else
File file = _fs->open(filename);
#endif
if (file) {
loaded = id.readFrom(file);
@ -38,9 +46,11 @@ bool IdentityStore::save(const char *name, const mesh::LocalIdentity& id) {
char filename[40];
sprintf(filename, "%s/%s.id", _dir, name);
#if defined(NRF52_PLATFORM)
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
_fs->remove(filename);
File file = _fs->open(filename, FILE_O_WRITE);
if (file) { file.seek(0); file.truncate(); }
#elif defined(RP2040_PLATFORM)
File file = _fs->open(filename, "w");
#else
File file = _fs->open(filename, "w", true);
#endif
@ -58,9 +68,11 @@ bool IdentityStore::save(const char *name, const mesh::LocalIdentity& id, const
char filename[40];
sprintf(filename, "%s/%s.id", _dir, name);
#if defined(NRF52_PLATFORM)
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
_fs->remove(filename);
File file = _fs->open(filename, FILE_O_WRITE);
if (file) { file.seek(0); file.truncate(); }
#elif defined(RP2040_PLATFORM)
File file = _fs->open(filename, "w");
#else
File file = _fs->open(filename, "w", true);
#endif

View file

@ -1,9 +1,9 @@
#pragma once
#if defined(ESP32)
#if defined(ESP32) || defined(RP2040_PLATFORM)
#include <FS.h>
#define FILESYSTEM fs::FS
#elif defined(NRF52_PLATFORM)
#elif defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
#include <Adafruit_LittleFS.h>
#define FILESYSTEM Adafruit_LittleFS
@ -18,7 +18,7 @@ class IdentityStore {
public:
IdentityStore(FILESYSTEM& fs, const char* dir): _fs(&fs), _dir(dir) { }
void begin() { _fs->mkdir(_dir); }
void begin() { if (_dir && _dir[0] == '/') { _fs->mkdir(_dir); } }
bool load(const char *name, mesh::LocalIdentity& id);
bool load(const char *name, mesh::LocalIdentity& id, char display_name[], int max_name_sz);
bool save(const char *name, const mesh::LocalIdentity& id);

View file

@ -44,6 +44,10 @@ void RadioLibWrapper::startRecv() {
}
}
bool RadioLibWrapper::isInRecvMode() const {
return (state & ~STATE_INT_READY) == STATE_RX;
}
int RadioLibWrapper::recvRaw(uint8_t* bytes, int sz) {
if (state & STATE_INT_READY) {
int len = _radio->getPacketLength();
@ -77,13 +81,16 @@ uint32_t RadioLibWrapper::getEstAirtimeFor(int len_bytes) {
return _radio->getTimeOnAir(len_bytes) / 1000;
}
void RadioLibWrapper::startSendRaw(const uint8_t* bytes, int len) {
state = STATE_TX_WAIT;
bool RadioLibWrapper::startSendRaw(const uint8_t* bytes, int len) {
_board->onBeforeTransmit();
int err = _radio->startTransmit((uint8_t *) bytes, len);
if (err != RADIOLIB_ERR_NONE) {
MESH_DEBUG_PRINTLN("RadioLibWrapper: error: startTransmit(%d)", err);
if (err == RADIOLIB_ERR_NONE) {
state = STATE_TX_WAIT;
return true;
}
MESH_DEBUG_PRINTLN("RadioLibWrapper: error: startTransmit(%d)", err);
idle(); // trigger another startRecv()
return false;
}
bool RadioLibWrapper::isSendComplete() {

View file

@ -19,12 +19,15 @@ public:
void begin() override;
int recvRaw(uint8_t* bytes, int sz) override;
uint32_t getEstAirtimeFor(int len_bytes) override;
void startSendRaw(const uint8_t* bytes, int len) override;
bool startSendRaw(const uint8_t* bytes, int len) override;
bool isSendComplete() override;
void onSendFinished() override;
bool isInRecvMode() const override;
uint32_t getPacketsRecv() const { return n_recv; }
uint32_t getPacketsSent() const { return n_sent; }
void resetStats() { n_recv = n_sent = 0; }
virtual float getLastRSSI() const override;
virtual float getLastSNR() const override;

View file

@ -0,0 +1,29 @@
#pragma once
#include <Arduino.h>
class RefCountedDigitalPin {
uint8_t _pin;
int8_t _claims = 0;
public:
RefCountedDigitalPin(uint8_t pin): _pin(pin) { }
void begin() {
pinMode(_pin, OUTPUT);
digitalWrite(_pin, LOW); // initial state
}
void claim() {
_claims++;
if (_claims > 0) {
digitalWrite(_pin, HIGH);
}
}
void release() {
_claims--;
if (_claims == 0) {
digitalWrite(_pin, LOW);
}
}
};

View file

@ -0,0 +1,24 @@
#pragma once
#include <CayenneLPP.h>
#define TELEM_PERM_BASE 0x01 // 'base' permission includes battery
#define TELEM_PERM_LOCATION 0x02
#define TELEM_PERM_ENVIRONMENT 0x04 // permission to access environment sensors
#define TELEM_CHANNEL_SELF 1 // LPP data channel for 'self' device
class SensorManager {
public:
double node_lat, node_lon; // modify these, if you want to affect Advert location
double node_altitude; // altitude in meters
SensorManager() { node_lat = 0; node_lon = 0; node_altitude = 0; }
virtual bool begin() { return false; }
virtual bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) { return false; }
virtual void loop() { }
virtual int getNumSettings() const { return 0; }
virtual const char* getSettingName(int i) const { return NULL; }
virtual const char* getSettingValue(int i) const { return NULL; }
virtual bool setSettingValue(const char* name, const char* value) { return false; }
};

View file

@ -80,7 +80,32 @@ public:
return false;
}
void clear(const mesh::Packet* packet) override {
if (packet->getPayloadType() == PAYLOAD_TYPE_ACK) {
uint32_t ack;
memcpy(&ack, packet->payload, 4);
for (int i = 0; i < MAX_PACKET_ACKS; i++) {
if (ack == _acks[i]) {
_acks[i] = 0;
break;
}
}
} else {
uint8_t hash[MAX_HASH_SIZE];
packet->calculatePacketHash(hash);
uint8_t* sp = _hashes;
for (int i = 0; i < MAX_PACKET_HASHES; i++, sp += MAX_HASH_SIZE) {
if (memcmp(hash, sp, MAX_HASH_SIZE) == 0) {
memset(sp, 0, MAX_HASH_SIZE);
break;
}
}
}
}
uint32_t getNumDirectDups() const { return _direct_dups; }
uint32_t getNumFloodDups() const { return _flood_dups; }
void resetStats() { _direct_dups = _flood_dups = 0; }
};

View file

@ -8,6 +8,15 @@ PacketQueue::PacketQueue(int max_entries) {
_num = 0;
}
int PacketQueue::countBefore(uint32_t now) const {
int n = 0;
for (int j = 0; j < _num; j++) {
if (_schedule_table[j] > now) continue; // scheduled for future... ignore for now
n++;
}
return n;
}
mesh::Packet* PacketQueue::get(uint32_t now) {
uint8_t min_pri = 0xFF;
int best_idx = -1;
@ -81,8 +90,8 @@ mesh::Packet* StaticPoolPacketManager::getNextOutbound(uint32_t now) {
return send_queue.get(now);
}
int StaticPoolPacketManager::getOutboundCount() const {
return send_queue.count();
int StaticPoolPacketManager::getOutboundCount(uint32_t now) const {
return send_queue.countBefore(now);
}
int StaticPoolPacketManager::getFreeCount() const {

View file

@ -13,6 +13,7 @@ public:
mesh::Packet* get(uint32_t now);
void add(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for);
int count() const { return _num; }
int countBefore(uint32_t now) const;
mesh::Packet* itemAt(int i) const { return _table[i]; }
mesh::Packet* removeByIdx(int i);
};
@ -27,7 +28,7 @@ public:
void free(mesh::Packet* packet) override;
void queueOutbound(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for) override;
mesh::Packet* getNextOutbound(uint32_t now) override;
int getOutboundCount() const override;
int getOutboundCount(uint32_t now) const override;
int getFreeCount() const override;
mesh::Packet* getOutboundByIdx(int i) override;
mesh::Packet* removeOutboundByIdx(int i) override;

View file

@ -0,0 +1,79 @@
#pragma once
#include <Wire.h>
#include <Arduino.h>
#include "XPowersLib.h"
#define XPOWERS_CHIP_AXP192
// LoRa radio module pins for TBeam
#define P_LORA_DIO_1 33 // SX1262 IRQ pin
#define P_LORA_NSS 18
#define P_LORA_RESET 23
#define P_LORA_BUSY 32 // SX1262 Busy pin
#define P_LORA_SCLK 5
#define P_LORA_MISO 19
#define P_LORA_MOSI 27
#include "ESP32Board.h"
#include <driver/rtc_io.h>
class TBeamBoardSX1262 : public ESP32Board {
XPowersAXP192 power;
public:
void begin() {
ESP32Board::begin();
power.setLDO2Voltage(3300);
power.enableLDO2();
power.enableBattVoltageMeasure();
pinMode(38, INPUT_PULLUP);
esp_reset_reason_t reason = esp_reset_reason();
if (reason == ESP_RST_DEEPSLEEP) {
long wakeup_source = esp_sleep_get_ext1_wakeup_status();
if (wakeup_source & (1 << P_LORA_DIO_1)) { // received a LoRa packet (while in deep sleep)
startup_reason = BD_STARTUP_RX_PACKET;
}
rtc_gpio_hold_dis((gpio_num_t)P_LORA_NSS);
rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_1);
}
}
void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1) {
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
// Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep
rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY);
rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1);
rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS);
if (pin_wake_btn < 0) {
esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet
} else {
esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn
}
if (secs > 0) {
esp_sleep_enable_timer_wakeup(secs * 1000000);
}
// Finally set ESP32 into sleep
esp_deep_sleep_start(); // CPU halts here and never returns!
}
uint16_t getBattMilliVolts() override {
return power.getBattVoltage();
}
const char* getManufacturerName() const override {
return "LilyGo T-Beam SX1262";
}
};

View file

@ -15,8 +15,8 @@
#define P_LORA_MISO 13 //SX1262 MISO pin
#define P_LORA_MOSI 11 //SX1262 MOSI pin
#define PIN_BOARD_SDA 17 //SDA for OLED, BME280, and QMC6310U (0x1C)
#define PIN_BOARD_SCL 18 //SCL for OLED, BME280, and QMC6310U (0x1C)
//#define PIN_BOARD_SDA 17 //SDA for OLED, BME280, and QMC6310U (0x1C)
//#define PIN_BOARD_SCL 18 //SCL for OLED, BME280, and QMC6310U (0x1C)
#define PIN_BOARD_SDA1 42 //SDA for PMU and PFC8563 (RTC)
#define PIN_BOARD_SCL1 41 //SCL for PMU and PFC8563 (RTC)
@ -27,7 +27,7 @@
#define P_BOARD_SPI_MOSI 35 //SPI for SD Card and QMI8653 (IMU)
#define P_BOARD_SPI_MISO 37 //SPI for SD Card and QMI8653 (IMU)
#define P_BOARD_SPI_SCK 36 //SPI for SD Card and QMI8653 (IMU)
#define P_BPARD_SPI_CS 47 //SPI for SD Card and QMI8653 (IMU)
#define P_BPARD_SPI_CS 47 //Pin for SD Card CS
#define P_BOARD_IMU_CS 34 //Pin for QMI8653 (IMU) CS
#define P_BOARD_IMU_INT 33 //IMU Int pin
@ -36,7 +36,8 @@
#define P_GPS_RX 9 //GPS RX pin
#define P_GPS_TX 8 //GPS TX pin
#define P_GPS_WAKE 7 //GPS Wakeup pin
#define P_GPS_1PPS 6 //GPS 1PPS pin
//#define P_GPS_1PPS 6 //GPS 1PPS pin - repurposed for lora tx led
#define GPS_BAUD_RATE 9600
//I2C Wire addresses
#define I2C_BME280_ADD 0x76 //BME280 sensor I2C address on Wire
@ -47,15 +48,21 @@
#define I2C_RTC_ADD 0x51 //RTC I2C address on Wire1
#define I2C_PMU_ADD 0x34 //AXP2101 I2C address on Wire1
#define PMU_WIRE_PORT Wire1
#define XPOWERS_CHIP_AXP2101
class TBeamS3SupremeBoard : public ESP32Board {
XPowersAXP2101 PMU;
public:
#ifdef MESH_DEBUG
void printPMU();
#endif
bool power_init();
void begin() {
bool power_init();
power_init();
ESP32Board::begin();
esp_reset_reason_t reason = esp_reset_reason();
@ -94,12 +101,14 @@ public:
}
uint16_t getBattMilliVolts() override {
return 0;
return PMU.getBattVoltage();
}
uint16_t getBattPercent();
uint16_t getBattPercent() {
//Read the PMU fuel guage for battery %
uint16_t battPercent = PMU.getBatteryPercent();
return battPercent;
}
const char* getManufacturerName() const override {
return "LilyGo T-Beam S3 Supreme SX1262";
}

View file

@ -5,7 +5,7 @@
static uint8_t broadcastAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
static esp_now_peer_info_t peerInfo;
static bool is_send_complete = false;
static volatile bool is_send_complete = false;
static esp_err_t last_send_result;
static uint8_t rx_buf[256];
static uint8_t last_rx_len = 0;
@ -22,7 +22,7 @@ static void OnDataRecv(const uint8_t *mac, const uint8_t *data, int len) {
last_rx_len = len;
}
void ESPNOWRadio::begin() {
void ESPNOWRadio::init() {
// Set device as a Wi-Fi Station
WiFi.mode(WIFI_STA);
// Long Range mode
@ -44,6 +44,8 @@ void ESPNOWRadio::begin() {
peerInfo.channel = 0;
peerInfo.encrypt = false;
is_send_complete = true;
// Add peer
if (esp_now_add_peer(&peerInfo) == ESP_OK) {
ESPNOW_DEBUG_PRINTLN("init success");
@ -67,23 +69,30 @@ uint32_t ESPNOWRadio::intID() {
return n + m;
}
void ESPNOWRadio::startSendRaw(const uint8_t* bytes, int len) {
bool ESPNOWRadio::startSendRaw(const uint8_t* bytes, int len) {
// Send message via ESP-NOW
is_send_complete = false;
esp_err_t result = esp_now_send(broadcastAddress, bytes, len);
if (result == ESP_OK) {
n_sent++;
ESPNOW_DEBUG_PRINTLN("Send success");
} else {
last_send_result = result;
is_send_complete = true;
ESPNOW_DEBUG_PRINTLN("Send failed: %d", result);
return true;
}
last_send_result = result;
is_send_complete = true;
ESPNOW_DEBUG_PRINTLN("Send failed: %d", result);
return false;
}
bool ESPNOWRadio::isSendComplete() {
return is_send_complete;
}
void ESPNOWRadio::onSendFinished() {
is_send_complete = true;
}
bool ESPNOWRadio::isInRecvMode() const {
return is_send_complete; // if NO send in progress, then we're in Rx mode
}
float ESPNOWRadio::getLastRSSI() const { return 0; }
@ -94,6 +103,7 @@ int ESPNOWRadio::recvRaw(uint8_t* bytes, int sz) {
if (last_rx_len > 0) {
memcpy(bytes, rx_buf, last_rx_len);
last_rx_len = 0;
n_recv++;
}
return len;
}

View file

@ -9,15 +9,18 @@ protected:
public:
ESPNOWRadio() { n_recv = n_sent = 0; }
void begin() override;
void init();
int recvRaw(uint8_t* bytes, int sz) override;
uint32_t getEstAirtimeFor(int len_bytes) override;
void startSendRaw(const uint8_t* bytes, int len) override;
bool startSendRaw(const uint8_t* bytes, int len) override;
bool isSendComplete() override;
void onSendFinished() override;
bool isInRecvMode() const override;
uint32_t getPacketsRecv() const { return n_recv; }
uint32_t getPacketsSent() const { return n_sent; }
void resetStats() { n_recv = n_sent = 0; }
virtual float getLastRSSI() const override;
virtual float getLastSNR() const override;

View file

@ -169,7 +169,7 @@ size_t SerialBLEInterface::writeFrame(const uint8_t src[], size_t len) {
return 0;
}
#define BLE_WRITE_MIN_INTERVAL 20
#define BLE_WRITE_MIN_INTERVAL 60
bool SerialBLEInterface::isWriteBusy() const {
return millis() < _last_write + BLE_WRITE_MIN_INTERVAL; // still too soon to start another write?

View file

@ -26,6 +26,10 @@ void RAK4631Board::begin() {
pinMode(PIN_USER_BTN, INPUT_PULLUP);
#endif
#ifdef PIN_USER_BTN_ANA
pinMode(PIN_USER_BTN_ANA, INPUT_PULLUP);
#endif
#if defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL)
Wire.setPins(PIN_BOARD_SDA, PIN_BOARD_SCL);
#endif

View file

@ -94,7 +94,7 @@ size_t SerialBLEInterface::writeFrame(const uint8_t src[], size_t len) {
return 0;
}
#define BLE_WRITE_MIN_INTERVAL 20
#define BLE_WRITE_MIN_INTERVAL 60
bool SerialBLEInterface::isWriteBusy() const {
return millis() < _last_write + BLE_WRITE_MIN_INTERVAL; // still too soon to start another write?

View file

@ -0,0 +1,90 @@
#include <Arduino.h>
#include "ThinkNodeM1Board.h"
#ifdef THINKNODE_M1
#include <bluefruit.h>
#include <Wire.h>
static BLEDfu bledfu;
static void connect_callback(uint16_t conn_handle) {
(void)conn_handle;
MESH_DEBUG_PRINTLN("BLE client connected");
}
static void disconnect_callback(uint16_t conn_handle, uint8_t reason) {
(void)conn_handle;
(void)reason;
MESH_DEBUG_PRINTLN("BLE client disconnected");
}
void ThinkNodeM1Board::begin() {
// for future use, sub-classes SHOULD call this from their begin()
startup_reason = BD_STARTUP_NORMAL;
Wire.begin();
pinMode(SX126X_POWER_EN, OUTPUT);
digitalWrite(SX126X_POWER_EN, HIGH);
delay(10); // give sx1262 some time to power up
}
uint16_t ThinkNodeM1Board::getBattMilliVolts() {
int adcvalue = 0;
analogReference(AR_INTERNAL_3_0);
analogReadResolution(12);
delay(10);
// ADC range is 0..3000mV and resolution is 12-bit (0..4095)
adcvalue = analogRead(PIN_VBAT_READ);
// Convert the raw value to compensated mv, taking the resistor-
// divider into account (providing the actual LIPO voltage)
return (uint16_t)((float)adcvalue * REAL_VBAT_MV_PER_LSB);
}
bool ThinkNodeM1Board::startOTAUpdate(const char* id, char reply[]) {
// Config the peripheral connection with maximum bandwidth
// more SRAM required by SoftDevice
// Note: All config***() function must be called before begin()
Bluefruit.configPrphBandwidth(BANDWIDTH_MAX);
Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16);
Bluefruit.begin(1, 0);
// Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4
Bluefruit.setTxPower(4);
// Set the BLE device name
Bluefruit.setName("THINKNODE_M1_OTA");
Bluefruit.Periph.setConnectCallback(connect_callback);
Bluefruit.Periph.setDisconnectCallback(disconnect_callback);
// To be consistent OTA DFU should be added first if it exists
bledfu.begin();
// Set up and start advertising
// Advertising packet
Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
Bluefruit.Advertising.addTxPower();
Bluefruit.Advertising.addName();
/* Start Advertising
- Enable auto advertising if disconnected
- Interval: fast mode = 20 ms, slow mode = 152.5 ms
- Timeout for fast mode is 30 seconds
- Start(timeout) with timeout = 0 will advertise forever (until connected)
For recommended advertising interval
https://developer.apple.com/library/content/qa/qa1931/_index.html
*/
Bluefruit.Advertising.restartOnDisconnect(true);
Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms
Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode
Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds
strcpy(reply, "OK - started");
return true;
}
#endif

View file

@ -0,0 +1,49 @@
#pragma once
#include <MeshCore.h>
#include <Arduino.h>
// LoRa radio module pins for Elecrow ThinkNode M1
#define P_LORA_DIO_1 20
#define P_LORA_NSS 24
#define P_LORA_RESET 25
#define P_LORA_BUSY 17
#define P_LORA_SCLK 19
#define P_LORA_MISO 23
#define P_LORA_MOSI 22
#define SX126X_POWER_EN 37
#define SX126X_DIO2_AS_RF_SWITCH true
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
// built-ins
#define VBAT_MV_PER_LSB (0.73242188F) // 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096
#define VBAT_DIVIDER (0.5F) // 150K + 150K voltage divider on VBAT
#define VBAT_DIVIDER_COMP (2.0F) // Compensation factor for the VBAT divider
#define PIN_VBAT_READ (4)
#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB)
class ThinkNodeM1Board : public mesh::MainBoard {
protected:
uint8_t startup_reason;
public:
void begin();
uint16_t getBattMilliVolts() override;
bool startOTAUpdate(const char* id, char reply[]) override;
uint8_t getStartupReason() const override {
return startup_reason;
}
const char* getManufacturerName() const override {
return "Elecrow ThinkNode-M1";
}
void reboot() override {
NVIC_SystemReset();
}
};

View file

@ -7,25 +7,17 @@
// LoRa radio module pins for Seeed Xiao-nrf52
#ifdef SX1262_XIAO_S3_VARIANT
#undef P_LORA_DIO_1
#undef P_LORA_BUSY
#undef P_LORA_RESET
#undef P_LORA_NSS
#define P_LORA_DIO_1 D0
#define P_LORA_BUSY D1
#define P_LORA_RESET D2
#define P_LORA_NSS D3
#define LORA_TX_BOOST_PIN D4
#else
#define P_LORA_DIO_1 D1
#define P_LORA_BUSY D3
#define P_LORA_RESET D2
#define P_LORA_NSS D4
#define LORA_TX_BOOST_PIN D5
#endif
#define P_LORA_SCLK PIN_SPI_SCK
#define P_LORA_MISO PIN_SPI_MISO
#define P_LORA_MOSI PIN_SPI_MOSI
//#define SX126X_POWER_EN 37
#define SX126X_DIO2_AS_RF_SWITCH true
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
class XiaoNrf52Board : public mesh::MainBoard {

View file

@ -0,0 +1,42 @@
#include <Arduino.h>
#include "PicoWBoard.h"
//#include <bluefruit.h>
#include <Wire.h>
//static BLEDfu bledfu;
static void connect_callback(uint16_t conn_handle) {
(void)conn_handle;
MESH_DEBUG_PRINTLN("BLE client connected");
}
static void disconnect_callback(uint16_t conn_handle, uint8_t reason) {
(void)conn_handle;
(void)reason;
MESH_DEBUG_PRINTLN("BLE client disconnected");
}
void PicoWBoard::begin() {
// for future use, sub-classes SHOULD call this from their begin()
startup_reason = BD_STARTUP_NORMAL;
pinMode(PIN_VBAT_READ, INPUT);
#ifdef PIN_USER_BTN
pinMode(PIN_USER_BTN, INPUT_PULLUP);
#endif
#if defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL)
Wire.setPins(PIN_BOARD_SDA, PIN_BOARD_SCL);
#endif
Wire.begin();
//pinMode(SX126X_POWER_EN, OUTPUT);
//digitalWrite(SX126X_POWER_EN, HIGH);
delay(10); // give sx1262 some time to power up
}
bool PicoWBoard::startOTAUpdate(const char* id, char reply[]) {
return false;
}

View file

@ -0,0 +1,64 @@
#pragma once
#include <MeshCore.h>
#include <Arduino.h>
// LoRa radio module pins for PicoW
#define P_LORA_DIO_1 20
#define P_LORA_NSS 3
#define P_LORA_RESET 15
#define P_LORA_BUSY 2
#define P_LORA_SCLK 10
#define P_LORA_MISO 12
#define P_LORA_MOSI 11
//#define SX126X_POWER_EN ??? // Not Sure
#define SX126X_DIO2_AS_RF_SWITCH true
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
// built-ins
#define PIN_VBAT_READ 26
#define ADC_MULTIPLIER (3.1 * 3.3 * 1000) // MT Uses 3.1
#define PIN_LED_BUILTIN LED_BUILTIN
class PicoWBoard : public mesh::MainBoard {
protected:
uint8_t startup_reason;
public:
void begin();
uint8_t getStartupReason() const override { return startup_reason; }
void onBeforeTransmit() override {
digitalWrite(LED_BUILTIN, HIGH); // turn TX LED on
}
void onAfterTransmit() override {
digitalWrite(LED_BUILTIN, LOW); // turn TX LED off
}
#define BATTERY_SAMPLES 8
uint16_t getBattMilliVolts() override {
analogReadResolution(12);
uint32_t raw = 0;
for (int i = 0; i < BATTERY_SAMPLES; i++) {
raw += analogRead(PIN_VBAT_READ);
}
raw = raw / BATTERY_SAMPLES;
return (ADC_MULTIPLIER * raw) / 4096;
}
const char* getManufacturerName() const override {
return "Pico W";
}
void reboot() override {
//NVIC_SystemReset();
rp2040.reboot();
}
bool startOTAUpdate(const char* id, char reply[]) override;
};

View file

@ -0,0 +1,231 @@
#include "EnvironmentSensorManager.h"
#if ENV_INCLUDE_AHTX0
#define TELEM_AHTX_ADDRESS 0x38 // AHT10, AHT20 temperature and humidity sensor I2C address
#include <Adafruit_AHTX0.h>
static Adafruit_AHTX0 AHTX0;
#endif
#if ENV_INCLUDE_BME280
#define TELEM_BME280_ADDRESS 0x76 // BME280 environmental sensor I2C address
#define TELEM_BME280_SEALEVELPRESSURE_HPA (1013.25) // Athmospheric pressure at sea level
#include <Adafruit_BME280.h>
static Adafruit_BME280 BME280;
#endif
#if ENV_INCLUDE_INA3221
#define TELEM_INA3221_ADDRESS 0x42 // INA3221 3 channel current sensor I2C address
#define TELEM_INA3221_SHUNT_VALUE 0.100 // most variants will have a 0.1 ohm shunts
#define TELEM_INA3221_NUM_CHANNELS 3
#include <Adafruit_INA3221.h>
static Adafruit_INA3221 INA3221;
#endif
#if ENV_INCLUDE_INA219
#define TELEM_INA219_ADDRESS 0x40 // INA219 single channel current sensor I2C address
#include <Adafruit_INA219.h>
static Adafruit_INA219 INA219(TELEM_INA219_ADDRESS);
#endif
bool EnvironmentSensorManager::begin() {
#if ENV_INCLUDE_GPS
initBasicGPS();
#endif
#if ENV_INCLUDE_AHTX0
if (AHTX0.begin(&Wire, 0, TELEM_AHTX_ADDRESS)) {
MESH_DEBUG_PRINTLN("Found AHT10/AHT20 at address: %02X", TELEM_AHTX_ADDRESS);
AHTX0_initialized = true;
} else {
AHTX0_initialized = false;
MESH_DEBUG_PRINTLN("AHT10/AHT20 was not found at I2C address %02X", TELEM_AHTX_ADDRESS);
}
#endif
#if ENV_INCLUDE_BME280
if (BME280.begin(TELEM_BME280_ADDRESS, &Wire)) {
MESH_DEBUG_PRINTLN("Found BME280 at address: %02X", TELEM_BME280_ADDRESS);
MESH_DEBUG_PRINTLN("BME sensor ID: %02X", BME280.sensorID());
BME280_initialized = true;
} else {
BME280_initialized = false;
MESH_DEBUG_PRINTLN("BME280 was not found at I2C address %02X", TELEM_BME280_ADDRESS);
}
#endif
#if ENV_INCLUDE_INA3221
if (INA3221.begin(TELEM_INA3221_ADDRESS, &Wire)) {
MESH_DEBUG_PRINTLN("Found INA3221 at address: %02X", TELEM_INA3221_ADDRESS);
MESH_DEBUG_PRINTLN("%04X %04X", INA3221.getDieID(), INA3221.getManufacturerID());
for(int i = 0; i < 3; i++) {
INA3221.setShuntResistance(i, TELEM_INA3221_SHUNT_VALUE);
}
INA3221_initialized = true;
} else {
INA3221_initialized = false;
MESH_DEBUG_PRINTLN("INA3221 was not found at I2C address %02X", TELEM_INA3221_ADDRESS);
}
#endif
#if ENV_INCLUDE_INA219
if (INA219.begin(&Wire)) {
MESH_DEBUG_PRINTLN("Found INA219 at address: %02X", TELEM_INA219_ADDRESS);
INA219_initialized = true;
} else {
INA219_initialized = false;
MESH_DEBUG_PRINTLN("INA219 was not found at I2C address %02X", TELEM_INA219_ADDRESS);
}
#endif
return true;
}
bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) {
next_available_channel = TELEM_CHANNEL_SELF + 1;
if (requester_permissions & TELEM_PERM_LOCATION) {
telemetry.addGPS(TELEM_CHANNEL_SELF, node_lat, node_lon, 0.0f); // allow lat/lon via telemetry even if no GPS is detected
}
if (requester_permissions & TELEM_PERM_ENVIRONMENT) {
#if ENV_INCLUDE_AHTX0
if (AHTX0_initialized) {
sensors_event_t humidity, temp;
AHTX0.getEvent(&humidity, &temp);
telemetry.addTemperature(TELEM_CHANNEL_SELF, temp.temperature);
telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, humidity.relative_humidity);
}
#endif
#if ENV_INCLUDE_BME280
if (BME280_initialized) {
telemetry.addTemperature(TELEM_CHANNEL_SELF, BME280.readTemperature());
telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, BME280.readHumidity());
telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BME280.readPressure());
telemetry.addAltitude(TELEM_CHANNEL_SELF, BME280.readAltitude(TELEM_BME280_SEALEVELPRESSURE_HPA));
}
#endif
#if ENV_INCLUDE_INA3221
if (INA3221_initialized) {
for(int i = 0; i < TELEM_INA3221_NUM_CHANNELS; i++) {
// add only enabled INA3221 channels to telemetry
if (INA3221.isChannelEnabled(i)) {
float voltage = INA3221.getBusVoltage(i);
float current = INA3221.getCurrentAmps(i);
telemetry.addVoltage(next_available_channel, voltage);
telemetry.addCurrent(next_available_channel, current);
telemetry.addPower(next_available_channel, voltage * current);
next_available_channel++;
}
}
}
#endif
#if ENV_INCLUDE_INA219
if (INA219_initialized) {
telemetry.addVoltage(next_available_channel, INA219.getBusVoltage_V());
telemetry.addCurrent(next_available_channel, INA219.getCurrent_mA() / 1000);
telemetry.addPower(next_available_channel, INA219.getPower_mW() / 1000);
next_available_channel++;
}
#endif
}
return true;
}
int EnvironmentSensorManager::getNumSettings() const {
#if ENV_INCLUDE_GPS
return gps_detected ? 1 : 0; // only show GPS setting if GPS is detected
#else
return 0;
#endif
}
const char* EnvironmentSensorManager::getSettingName(int i) const {
#if ENV_INCLUDE_GPS
return (gps_detected && i == 0) ? "gps" : NULL;
#else
return NULL;
#endif
}
const char* EnvironmentSensorManager::getSettingValue(int i) const {
#if ENV_INCLUDE_GPS
if (gps_detected && i == 0) {
return gps_active ? "1" : "0";
}
#endif
return NULL;
}
bool EnvironmentSensorManager::setSettingValue(const char* name, const char* value) {
#if ENV_INCLUDE_GPS
if (gps_detected && strcmp(name, "gps") == 0) {
if (strcmp(value, "0") == 0) {
stop_gps();
} else {
start_gps();
}
return true;
}
#endif
return false; // not supported
}
#if ENV_INCLUDE_GPS
void EnvironmentSensorManager::initBasicGPS() {
Serial1.setPins(PIN_GPS_TX, PIN_GPS_RX);
Serial1.begin(9600);
// Try to detect if GPS is physically connected to determine if we should expose the setting
pinMode(PIN_GPS_EN, OUTPUT);
digitalWrite(PIN_GPS_EN, HIGH); // Power on GPS
// Give GPS a moment to power up and send data
delay(1000);
// We'll consider GPS detected if we see any data on Serial1
gps_detected = (Serial1.available() > 0);
if (gps_detected) {
MESH_DEBUG_PRINTLN("GPS detected");
digitalWrite(PIN_GPS_EN, LOW); // Power off GPS until the setting is changed
} else {
MESH_DEBUG_PRINTLN("No GPS detected");
digitalWrite(PIN_GPS_EN, LOW);
}
}
void EnvironmentSensorManager::start_gps() {
gps_active = true;
pinMode(PIN_GPS_EN, OUTPUT);
digitalWrite(PIN_GPS_EN, HIGH);
}
void EnvironmentSensorManager::stop_gps() {
gps_active = false;
pinMode(PIN_GPS_EN, OUTPUT);
digitalWrite(PIN_GPS_EN, LOW);
}
void EnvironmentSensorManager::loop() {
static long next_gps_update = 0;
_location->loop();
if (millis() > next_gps_update) {
if (_location->isValid()) {
node_lat = ((double)_location->getLatitude())/1000000.;
node_lon = ((double)_location->getLongitude())/1000000.;
MESH_DEBUG_PRINTLN("lat %f lon %f", node_lat, node_lon);
}
next_gps_update = millis() + 1000;
}
}
#endif

View file

@ -0,0 +1,42 @@
#pragma once
#include <Mesh.h>
#include <helpers/SensorManager.h>
#include <helpers/sensors/LocationProvider.h>
class EnvironmentSensorManager : public SensorManager {
protected:
int next_available_channel = TELEM_CHANNEL_SELF + 1;
bool AHTX0_initialized = false;
bool BME280_initialized = false;
bool INA3221_initialized = false;
bool INA219_initialized = false;
bool gps_detected = false;
bool gps_active = false;
#if ENV_INCLUDE_GPS
LocationProvider* _location;
void start_gps();
void stop_gps();
void initBasicGPS();
#endif
public:
#if ENV_INCLUDE_GPS
EnvironmentSensorManager(LocationProvider &location): _location(&location){};
#else
EnvironmentSensorManager(){};
#endif
bool begin() override;
bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) override;
#if ENV_INCLUDE_GPS
void loop() override;
#endif
int getNumSettings() const override;
const char* getSettingName(int i) const override;
const char* getSettingValue(int i) const override;
bool setSettingValue(const char* name, const char* value) override;
};

View file

@ -0,0 +1,18 @@
#pragma once
#include "Mesh.h"
class LocationProvider {
public:
virtual long getLatitude() = 0;
virtual long getLongitude() = 0;
virtual long getAltitude() = 0;
virtual bool isValid() = 0;
virtual long getTimestamp() = 0;
virtual void reset();
virtual void begin();
virtual void stop();
virtual void loop();
};

View file

@ -0,0 +1,85 @@
#pragma once
#include "LocationProvider.h"
#include <MicroNMEA.h>
#include <RTClib.h>
#ifndef GPS_EN
#define GPS_EN (-1)
#endif
#ifndef GPS_RESET
#define GPS_RESET (-1)
#endif
#ifndef GPS_RESET_FORCE
#define GPS_RESET_FORCE LOW
#endif
class MicroNMEALocationProvider : public LocationProvider {
char _nmeaBuffer[100];
MicroNMEA nmea;
Stream* _gps_serial;
int _pin_reset;
int _pin_en;
public :
MicroNMEALocationProvider(Stream& ser, int pin_reset = GPS_RESET, int pin_en = GPS_EN) :
_gps_serial(&ser), nmea(_nmeaBuffer, sizeof(_nmeaBuffer)), _pin_reset(pin_reset), _pin_en(pin_en) {
if (_pin_reset != -1) {
pinMode(_pin_reset, OUTPUT);
digitalWrite(_pin_reset, GPS_RESET_FORCE);
}
if (_pin_en != -1) {
pinMode(_pin_en, OUTPUT);
digitalWrite(_pin_en, LOW);
}
}
void begin() override {
if (_pin_reset != -1) {
digitalWrite(_pin_reset, !GPS_RESET_FORCE);
}
if (_pin_en != -1) {
digitalWrite(_pin_en, HIGH);
}
}
void reset() override {
if (_pin_reset != -1) {
digitalWrite(_pin_reset, GPS_RESET_FORCE);
delay(100);
digitalWrite(_pin_reset, !GPS_RESET_FORCE);
}
}
void stop() override {
if (_pin_en != -1) {
digitalWrite(_pin_en, LOW);
}
}
long getLatitude() override { return nmea.getLatitude(); }
long getLongitude() override { return nmea.getLongitude(); }
long getAltitude() override {
long alt = 0;
nmea.getAltitude(alt);
return alt;
}
bool isValid() override { return nmea.isValid(); }
long getTimestamp() override {
DateTime dt(nmea.getYear(), nmea.getMonth(),nmea.getDay(),nmea.getHour(),nmea.getMinute(),nmea.getSecond());
return dt.unixtime();
}
void loop() override {
while (_gps_serial->available()) {
char c = _gps_serial->read();
#ifdef GPS_NMEA_DEBUG
Serial.print(c);
#endif
nmea.process(c);
}
}
};

View file

@ -0,0 +1,142 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2019 hathach for Adafruit Industries
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <Arduino.h>
#include "InternalFileSystem.h"
//--------------------------------------------------------------------+
// LFS Disk IO
//--------------------------------------------------------------------+
static int _internal_flash_read(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size)
{
if (!buffer || !size) return LFS_ERR_INVAL;
lfs_block_t address = LFS_FLASH_ADDR_BASE + (block * FLASH_PAGE_SIZE + off);
memcpy(buffer, (void *)address, size);
return LFS_ERR_OK;
}
// Program a region in a block. The block must have previously
// been erased. Negative error codes are propogated to the user.
// May return LFS_ERR_CORRUPT if the block should be considered bad.
static int _internal_flash_prog(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size)
{
HAL_StatusTypeDef hal_rc = HAL_OK;
lfs_block_t addr = LFS_FLASH_ADDR_BASE + (block * FLASH_PAGE_SIZE + off);
uint64_t *bufp = (uint64_t *) buffer;
if (HAL_FLASH_Unlock() != HAL_OK) return LFS_ERR_IO;
for (uint32_t i = 0; i < size/8; i++) {
if ((addr < LFS_FLASH_ADDR_BASE) || (addr > FLASH_END_ADDR)) {
HAL_FLASH_Lock();
return LFS_ERR_INVAL;
}
hal_rc = HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, addr, *bufp);
addr += 8;
bufp += 1;
}
if (HAL_FLASH_Lock() != HAL_OK) return LFS_ERR_IO;
return hal_rc == HAL_OK ? LFS_ERR_OK : LFS_ERR_IO;
}
// Erase a block. A block must be erased before being programmed.
// The state of an erased block is undefined. Negative error codes
// are propogated to the user.
// May return LFS_ERR_CORRUPT if the block should be considered bad.
static int _internal_flash_erase(const struct lfs_config *c, lfs_block_t block)
{
HAL_StatusTypeDef hal_rc;
lfs_block_t address = LFS_FLASH_ADDR_BASE + (block * FLASH_PAGE_SIZE);
uint32_t pageError = 0;
FLASH_EraseInitTypeDef EraseInitStruct = {
.TypeErase = FLASH_TYPEERASE_PAGES,
.Page = 0,
.NbPages = 1
};
if ((address < LFS_FLASH_ADDR_BASE) || (address > FLASH_END_ADDR)) {
return LFS_ERR_INVAL;
}
EraseInitStruct.Page = (address - FLASH_BASE) / FLASH_PAGE_SIZE;
HAL_FLASH_Unlock();
hal_rc = HAL_FLASHEx_Erase(&EraseInitStruct, &pageError);
HAL_FLASH_Lock();
return hal_rc == HAL_OK ? LFS_ERR_OK : LFS_ERR_IO;
}
// Sync the state of the underlying block device. Negative error codes
// are propogated to the user.
static int _internal_flash_sync(const struct lfs_config *c)
{
return LFS_ERR_OK; // don't need sync
}
struct lfs_config _InternalFSConfig = {
.context = NULL,
.read = _internal_flash_read,
.prog = _internal_flash_prog,
.erase = _internal_flash_erase,
.sync = _internal_flash_sync,
.read_size = LFS_BLOCK_SIZE,
.prog_size = LFS_BLOCK_SIZE,
.block_size = LFS_BLOCK_SIZE,
.block_count = LFS_FLASH_TOTAL_SIZE / LFS_BLOCK_SIZE,
.lookahead = 128,
.read_buffer = NULL,
.prog_buffer = NULL,
.lookahead_buffer = NULL,
.file_buffer = NULL
};
InternalFileSystem InternalFS;
//--------------------------------------------------------------------+
//
//--------------------------------------------------------------------+
InternalFileSystem::InternalFileSystem(void)
: Adafruit_LittleFS(&_InternalFSConfig)
{
}
bool InternalFileSystem::begin(void)
{
#ifdef FORMAT_FS
this->format();
#endif
// failed to mount, erase all sector then format and mount again
if ( !Adafruit_LittleFS::begin() )
{
// lfs format
this->format();
// mount again if still failed, give up
if ( !Adafruit_LittleFS::begin() ) return false;
}
return true;
}

View file

@ -0,0 +1,48 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2019 hathach for Adafruit Industries
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef INTERNALFILESYSTEM_H_
#define INTERNALFILESYSTEM_H_
#include "Adafruit_LittleFS.h"
#ifndef LFS_FLASH_TOTAL_SIZE /* Flash size can be configured in platformio.ini */
#define LFS_FLASH_TOTAL_SIZE (16 * 2048) /* defaults to 32k flash */
#endif
#define LFS_BLOCK_SIZE (2048)
#define LFS_FLASH_ADDR_BASE (FLASH_END_ADDR - LFS_FLASH_TOTAL_SIZE + 1)
class InternalFileSystem : public Adafruit_LittleFS
{
public:
InternalFileSystem(void);
// overwrite to also perform low level format (sector erase of whole flash region)
bool begin(void);
};
extern InternalFileSystem InternalFS;
#endif /* INTERNALFILESYSTEM_H_ */

View file

@ -0,0 +1,29 @@
#pragma once
#include <MeshCore.h>
#include <Arduino.h>
class STM32Board : public mesh::MainBoard {
protected:
uint8_t startup_reason;
public:
void begin() {
startup_reason = BD_STARTUP_NORMAL;
}
uint8_t getStartupReason() const override { return startup_reason; }
uint16_t getBattMilliVolts() override {
return 0; // not supported
}
const char* getManufacturerName() const override {
return "Generic STM32";
}
void reboot() override {
}
bool startOTAUpdate(const char* id, char reply[]) override { return false; };
};

View file

@ -24,5 +24,6 @@ public:
virtual void fillRect(int x, int y, int w, int h) = 0;
virtual void drawRect(int x, int y, int w, int h) = 0;
virtual void drawXbm(int x, int y, const uint8_t* bits, int w, int h) = 0;
virtual uint16_t getTextWidth(const char* str) = 0;
virtual void endFrame() = 0;
};

View file

@ -1,11 +1,23 @@
#include "GxEPDDisplay.h"
#ifndef DISPLAY_ROTATION
#define DISPLAY_ROTATION 3
#endif
#ifdef TECHO_ZOOM
#define SCALE_X (1.5625f * 1.5f) // 200 / 128 (with 1.5 scale)
#define SCALE_Y (1.5625f * 1.5f) // 200 / 128 (with 1.5 scale)
#else
#define SCALE_X 1.5625f // 200 / 128
#define SCALE_Y 1.5625f // 200 / 128
#endif
bool GxEPDDisplay::begin() {
display.epd2.selectSPI(SPI1, SPISettings(4000000, MSBFIRST, SPI_MODE0));
SPI1.begin();
display.init(115200, true, 2, false);
display.setRotation(3);
display.setRotation(DISPLAY_ROTATION);
#ifdef TECHO_ZOOM
display.setFont(&FreeMono9pt7b);
#endif
@ -53,11 +65,7 @@ void GxEPDDisplay::setColor(Color c) {
}
void GxEPDDisplay::setCursor(int x, int y) {
#ifdef TECHO_ZOOM
x = x + (x >> 1);
y = y + (y >> 1);
#endif
display.setCursor(x, (y+10));
display.setCursor(x*SCALE_X, (y+10)*SCALE_Y);
}
void GxEPDDisplay::print(const char* str) {
@ -65,33 +73,22 @@ void GxEPDDisplay::print(const char* str) {
}
void GxEPDDisplay::fillRect(int x, int y, int w, int h) {
#ifdef TECHO_ZOOM
x = x + (x >> 1);
y = y + (y >> 1);
w = w + (w >> 1);
h = h + (h >> 1);
#endif
display.fillRect(x, y, w, h, GxEPD_BLACK);
display.fillRect(x*SCALE_X, y*SCALE_Y, w*SCALE_X, h*SCALE_Y, GxEPD_BLACK);
}
void GxEPDDisplay::drawRect(int x, int y, int w, int h) {
#ifdef TECHO_ZOOM
x = x + (x >> 1);
y = y + (y >> 1);
w = w + (w >> 1);
h = h + (h >> 1);
#endif
display.drawRect(x, y, w, h, GxEPD_BLACK);
display.drawRect(x*SCALE_X, y*SCALE_Y, w*SCALE_X, h*SCALE_Y, GxEPD_BLACK);
}
void GxEPDDisplay::drawXbm(int x, int y, const uint8_t* bits, int w, int h) {
#ifdef TECHO_ZOOM
x = x + (x >> 1);
y = y + (y >> 1);
w = w + (w >> 1);
h = h + (h >> 1);
#endif
display.drawBitmap(x*1.5, (y*1.5) + 10, bits, w, h, GxEPD_BLACK);
display.drawBitmap(x*SCALE_X, (y*SCALE_Y) + 10, bits, w, h, GxEPD_BLACK);
}
uint16_t GxEPDDisplay::getTextWidth(const char* str) {
int16_t x1, y1;
uint16_t w, h;
display.getTextBounds(str, 0, 0, &x1, &y1, &w, &h);
return w / SCALE_X;
}
void GxEPDDisplay::endFrame() {

View file

@ -14,11 +14,11 @@
#define GxEPD2_DISPLAY_CLASS GxEPD2_BW
#define GxEPD2_DRIVER_CLASS GxEPD2_150_BN // DEPG0150BN 200x200, SSD1681, (FPC8101), TTGO T5 V2.4.1
#include <epd/GxEPD2_150_BN.h> // 1.54" b/w
#include <epd/GxEPD2_150_BN.h> // 1.54" b/w
#include "DisplayDriver.h"
//GxEPD2_BW<GxEPD2_150_BN, 200> display(GxEPD2_150_BN(DISP_CS, DISP_DC, DISP_RST, DISP_BUSY)); // DEPG0150BN 200x200, SSD1681, TTGO T5 V2.4.1
//GxEPD2_BW<GxEPD2_150_BN, 200> display(GxEPD2_150_BN(DISP_CS, DISP_DC, DISP_RST, DISP_BUSY)); // DEPG0150BN 200x200, SSD1681, TTGO T5 V2.4.1
class GxEPDDisplay : public DisplayDriver {
@ -29,13 +29,12 @@ class GxEPDDisplay : public DisplayDriver {
public:
// there is a margin in y...
GxEPDDisplay() : DisplayDriver(200, 200-10), display(GxEPD2_150_BN(DISP_CS, DISP_DC, DISP_RST, DISP_BUSY)) {
GxEPDDisplay() : DisplayDriver(128, 128), display(GxEPD2_150_BN(DISP_CS, DISP_DC, DISP_RST, DISP_BUSY)) {
}
bool begin();
bool isOn() override {return _isOn;};
bool isOn() override {return _isOn;};
void turnOn() override;
void turnOff() override;
void clear() override;
@ -47,5 +46,6 @@ public:
void fillRect(int x, int y, int w, int h) override;
void drawRect(int x, int y, int w, int h) override;
void drawXbm(int x, int y, const uint8_t* bits, int w, int h) override;
uint16_t getTextWidth(const char* str) override;
void endFrame() override;
};

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,395 @@
/**
* The MIT License (MIT)
*
* Copyright (c) 2018 by ThingPulse, Daniel Eichhorn
* Copyright (c) 2018 by Fabrice Weinberg
* Copyright (c) 2019 by Helmut Tschemernjak - www.radioshuttle.de
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* ThingPulse invests considerable time and money to develop these open source libraries.
* Please support us by buying our products (and not the clones) from
* https://thingpulse.com
*
*/
#ifndef OLEDDISPLAY_h
#define OLEDDISPLAY_h
#include <cstdarg>
#ifdef ARDUINO
#include <Arduino.h>
#elif __MBED__
#define pgm_read_byte(addr) (*(const unsigned char *)(addr))
#include <mbed.h>
#define delay(x) wait_ms(x)
#define yield() void()
/*
* This is a little Arduino String emulation to keep the OLEDDisplay
* library code in common between Arduino and mbed-os
*/
class String {
public:
String(const char *s) { _str = s; };
int length() { return strlen(_str); };
const char *c_str() { return _str; };
void toCharArray(char *buf, unsigned int bufsize, unsigned int index = 0) const {
memcpy(buf, _str + index, std::min(bufsize, strlen(_str)));
};
private:
const char *_str;
};
#else
#error "Unkown operating system"
#endif
#include "OLEDDisplayFonts.h"
//#define DEBUG_OLEDDISPLAY(...) Serial.printf( __VA_ARGS__ )
//#define DEBUG_OLEDDISPLAY(...) dprintf("%s", __VA_ARGS__ )
#ifndef DEBUG_OLEDDISPLAY
#define DEBUG_OLEDDISPLAY(...)
#endif
// Use DOUBLE BUFFERING by default
#ifndef OLEDDISPLAY_REDUCE_MEMORY
#define OLEDDISPLAY_DOUBLE_BUFFER
#endif
// Header Values
#define JUMPTABLE_BYTES 4
#define JUMPTABLE_LSB 1
#define JUMPTABLE_SIZE 2
#define JUMPTABLE_WIDTH 3
#define JUMPTABLE_START 4
#define WIDTH_POS 0
#define HEIGHT_POS 1
#define FIRST_CHAR_POS 2
#define CHAR_NUM_POS 3
// Display commands
#define CHARGEPUMP 0x8D
#define COLUMNADDR 0x21
#define COMSCANDEC 0xC8
#define COMSCANINC 0xC0
#define DISPLAYALLON 0xA5
#define DISPLAYALLON_RESUME 0xA4
#define DISPLAYOFF 0xAE
#define DISPLAYON 0xAF
#define EXTERNALVCC 0x1
#define INVERTDISPLAY 0xA7
#define MEMORYMODE 0x20
#define NORMALDISPLAY 0xA6
#define PAGEADDR 0x22
#define SEGREMAP 0xA0
#define SETCOMPINS 0xDA
#define SETCONTRAST 0x81
#define SETDISPLAYCLOCKDIV 0xD5
#define SETDISPLAYOFFSET 0xD3
#define SETHIGHCOLUMN 0x10
#define SETLOWCOLUMN 0x00
#define SETMULTIPLEX 0xA8
#define SETPRECHARGE 0xD9
#define SETSEGMENTREMAP 0xA1
#define SETSTARTLINE 0x40
#define SETVCOMDETECT 0xDB
#define SWITCHCAPVCC 0x2
#ifndef _swap_int16_t
#define _swap_int16_t(a, b) { int16_t t = a; a = b; b = t; }
#endif
enum OLEDDISPLAY_COLOR {
BLACK = 0,
WHITE = 1,
INVERSE = 2
};
enum OLEDDISPLAY_TEXT_ALIGNMENT {
TEXT_ALIGN_LEFT = 0,
TEXT_ALIGN_RIGHT = 1,
TEXT_ALIGN_CENTER = 2,
TEXT_ALIGN_CENTER_BOTH = 3
};
enum OLEDDISPLAY_GEOMETRY {
GEOMETRY_128_64 = 0,
GEOMETRY_128_32 = 1,
GEOMETRY_64_48 = 2,
GEOMETRY_64_32 = 3,
GEOMETRY_RAWMODE = 4,
GEOMETRY_128_128 = 5
};
enum HW_I2C {
I2C_ONE,
I2C_TWO
};
typedef char (*FontTableLookupFunction)(const uint8_t ch);
char DefaultFontTableLookup(const uint8_t ch);
#ifdef ARDUINO
class OLEDDisplay : public Print {
#elif __MBED__
class OLEDDisplay : public Stream {
#else
#error "Unkown operating system"
#endif
public:
OLEDDisplay();
virtual ~OLEDDisplay();
uint16_t width(void) const { return displayWidth; };
uint16_t height(void) const { return displayHeight; };
// Use this to resume after a deep sleep without resetting the display (what init() would do).
// Returns true if connection to the display was established and the buffer allocated, false otherwise.
bool allocateBuffer();
// Allocates the buffer and initializes the driver & display. Resets the display!
// Returns false if buffer allocation failed, true otherwise.
bool init();
// Free the memory used by the display
void end();
// Cycle through the initialization
void resetDisplay(void);
/* Drawing functions */
// Sets the color of all pixel operations
void setColor(OLEDDISPLAY_COLOR color);
// Returns the current color.
OLEDDISPLAY_COLOR getColor();
// Draw a pixel at given position
void setPixel(int16_t x, int16_t y);
// Draw a pixel at given position and color
void setPixelColor(int16_t x, int16_t y, OLEDDISPLAY_COLOR color);
// Clear a pixel at given position FIXME: INVERSE is untested with this function
void clearPixel(int16_t x, int16_t y);
// Draw a line from position 0 to position 1
void drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1);
// Draw the border of a rectangle at the given location
void drawRect(int16_t x, int16_t y, int16_t width, int16_t height);
// Fill the rectangle
void fillRect(int16_t x, int16_t y, int16_t width, int16_t height);
// Draw the border of a circle
void drawCircle(int16_t x, int16_t y, int16_t radius);
// Draw all Quadrants specified in the quads bit mask
void drawCircleQuads(int16_t x0, int16_t y0, int16_t radius, uint8_t quads);
// Fill circle
void fillCircle(int16_t x, int16_t y, int16_t radius);
// Draw an empty triangle i.e. only the outline
void drawTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2, int16_t y2);
// Draw a solid triangle i.e. filled
void fillTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2, int16_t y2);
// Draw a line horizontally
void drawHorizontalLine(int16_t x, int16_t y, int16_t length);
// Draw a line vertically
void drawVerticalLine(int16_t x, int16_t y, int16_t length);
// Draws a rounded progress bar with the outer dimensions given by width and height. Progress is
// a unsigned byte value between 0 and 100
void drawProgressBar(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t progress);
// Draw a bitmap in the internal image format
void drawFastImage(int16_t x, int16_t y, int16_t width, int16_t height, const uint8_t *image);
// Draw a XBM
void drawXbm(int16_t x, int16_t y, int16_t width, int16_t height, const uint8_t *xbm);
// Draw icon 16x16 xbm format
void drawIco16x16(int16_t x, int16_t y, const uint8_t *ico, bool inverse = false);
/* Text functions */
// Draws a string at the given location, returns how many chars have been written
uint16_t drawString(int16_t x, int16_t y, const String &text);
// Draws a formatted string (like printf) at the given location
void drawStringf(int16_t x, int16_t y, char* buffer, String format, ... );
// Draws a String with a maximum width at the given location.
// If the given String is wider than the specified width
// The text will be wrapped to the next line at a space or dash
// returns 0 if everything fits on the screen or the numbers of characters in the
// first line if not
uint16_t drawStringMaxWidth(int16_t x, int16_t y, uint16_t maxLineWidth, const String &text);
// Returns the width of the const char* with the current
// font settings
uint16_t getStringWidth(const char* text, uint16_t length, bool utf8 = false);
// Convencience method for the const char version
uint16_t getStringWidth(const String &text);
// Specifies relative to which anchor point
// the text is rendered. Available constants:
// TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER_BOTH
void setTextAlignment(OLEDDISPLAY_TEXT_ALIGNMENT textAlignment);
// Sets the current font. Available default fonts
// ArialMT_Plain_10, ArialMT_Plain_16, ArialMT_Plain_24
void setFont(const uint8_t *fontData);
// Set the function that will convert utf-8 to font table index
void setFontTableLookupFunction(FontTableLookupFunction function);
/* Display functions */
// Turn the display on
void displayOn(void);
// Turn the display offs
void displayOff(void);
// Inverted display mode
void invertDisplay(void);
// Normal display mode
void normalDisplay(void);
// Set display contrast
// really low brightness & contrast: contrast = 10, precharge = 5, comdetect = 0
// normal brightness & contrast: contrast = 100
void setContrast(uint8_t contrast, uint8_t precharge = 241, uint8_t comdetect = 64);
// Convenience method to access
virtual void setBrightness(uint8_t);
// Reset display rotation or mirroring
void resetOrientation();
// Turn the display upside down
void flipScreenVertically();
// Mirror the display (to be used in a mirror or as a projector)
void mirrorScreen();
// Write the buffer to the display memory
virtual void display(void) = 0;
// Clear the local pixel buffer
void clear(void);
// Log buffer implementation
// This will define the lines and characters you can
// print to the screen. When you exeed the buffer size (lines * chars)
// the output may be truncated due to the size constraint.
bool setLogBuffer(uint16_t lines, uint16_t chars);
// Draw the log buffer at position (x, y)
void drawLogBuffer(uint16_t x, uint16_t y);
// Get screen geometry
uint16_t getWidth(void);
uint16_t getHeight(void);
// Implement needed function to be compatible with Print class
size_t write(uint8_t c);
size_t write(const char* s);
// Implement needed function to be compatible with Stream class
#ifdef __MBED__
int _putc(int c);
int _getc() { return -1; };
#endif
uint8_t *buffer;
#ifdef OLEDDISPLAY_DOUBLE_BUFFER
uint8_t *buffer_back;
#endif
// Set the correct height, width and buffer for the geometry
void setGeometry(OLEDDISPLAY_GEOMETRY g, uint16_t width = 0, uint16_t height = 0);
protected:
OLEDDISPLAY_GEOMETRY geometry;
uint16_t displayWidth;
uint16_t displayHeight;
uint16_t displayBufferSize;
OLEDDISPLAY_TEXT_ALIGNMENT textAlignment;
OLEDDISPLAY_COLOR color;
const uint8_t *fontData;
// State values for logBuffer
uint16_t logBufferSize;
uint16_t logBufferFilled;
uint16_t logBufferLine;
uint16_t logBufferMaxLines;
char *logBuffer;
// the header size of the buffer used, e.g. for the SPI command header
int BufferOffset;
virtual int getBufferOffset(void) = 0;
// Send a command to the display (low level function)
virtual void sendCommand(uint8_t com) {(void)com;};
// Connect to the display
virtual bool connect() { return false; };
// Send all the init commands
virtual void sendInitCommands();
// converts utf8 characters to extended ascii
char* utf8ascii(const String &s);
void inline drawInternal(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const uint8_t *data, uint16_t offset, uint16_t bytesInData) __attribute__((always_inline));
uint16_t drawStringInternal(int16_t xMove, int16_t yMove, const char* text, uint16_t textLength, uint16_t textWidth, bool utf8);
FontTableLookupFunction fontTableLookupFunction;
};
#endif

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,13 @@
#ifndef OLEDDISPLAYFONTS_h
#define OLEDDISPLAYFONTS_h
#ifdef ARDUINO
#include <Arduino.h>
#elif __MBED__
#define PROGMEM
#endif
extern const uint8_t ArialMT_Plain_10[] PROGMEM;
extern const uint8_t ArialMT_Plain_16[] PROGMEM;
extern const uint8_t ArialMT_Plain_24[] PROGMEM;
#endif

View file

@ -0,0 +1,91 @@
#include "SH1106Display.h"
#include <Adafruit_GrayOLED.h>
#include "Adafruit_SH110X.h"
bool SH1106Display::i2c_probe(TwoWire &wire, uint8_t addr)
{
wire.beginTransmission(addr);
uint8_t error = wire.endTransmission();
return (error == 0);
}
bool SH1106Display::begin()
{
return display.begin(DISPLAY_ADDRESS, true) && i2c_probe(Wire, DISPLAY_ADDRESS);
}
void SH1106Display::turnOn()
{
display.oled_command(SH110X_DISPLAYON);
_isOn = true;
}
void SH1106Display::turnOff()
{
display.oled_command(SH110X_DISPLAYOFF);
_isOn = false;
}
void SH1106Display::clear()
{
display.clearDisplay();
display.display();
}
void SH1106Display::startFrame(Color bkg)
{
display.clearDisplay(); // TODO: apply 'bkg'
_color = SH110X_WHITE;
display.setTextColor(_color);
display.setTextSize(1);
display.cp437(true); // Use full 256 char 'Code Page 437' font
}
void SH1106Display::setTextSize(int sz)
{
display.setTextSize(sz);
}
void SH1106Display::setColor(Color c)
{
_color = (c != 0) ? SH110X_WHITE : SH110X_BLACK;
display.setTextColor(_color);
}
void SH1106Display::setCursor(int x, int y)
{
display.setCursor(x, y);
}
void SH1106Display::print(const char *str)
{
display.print(str);
}
void SH1106Display::fillRect(int x, int y, int w, int h)
{
display.fillRect(x, y, w, h, _color);
}
void SH1106Display::drawRect(int x, int y, int w, int h)
{
display.drawRect(x, y, w, h, _color);
}
void SH1106Display::drawXbm(int x, int y, const uint8_t *bits, int w, int h)
{
display.drawBitmap(x, y, bits, w, h, SH110X_WHITE);
}
uint16_t SH1106Display::getTextWidth(const char *str)
{
int16_t x1, y1;
uint16_t w, h;
display.getTextBounds(str, 0, 0, &x1, &y1, &w, &h);
return w;
}
void SH1106Display::endFrame()
{
display.display();
}

View file

@ -0,0 +1,43 @@
#pragma once
#include "DisplayDriver.h"
#include <Wire.h>
#include <Adafruit_GFX.h>
#define SH110X_NO_SPLASH
#include <Adafruit_SH110X.h>
#ifndef PIN_OLED_RESET
#define PIN_OLED_RESET -1
#endif
#ifndef DISPLAY_ADDRESS
#define DISPLAY_ADDRESS 0x3C
#endif
class SH1106Display : public DisplayDriver
{
Adafruit_SH1106G display;
bool _isOn;
uint8_t _color;
bool i2c_probe(TwoWire &wire, uint8_t addr);
public:
SH1106Display() : DisplayDriver(128, 64), display(128, 64, &Wire, PIN_OLED_RESET) { _isOn = false; }
bool begin();
bool isOn() override { return _isOn; }
void turnOn() override;
void turnOff() override;
void clear() override;
void startFrame(Color bkg = DARK) override;
void setTextSize(int sz) override;
void setColor(Color c) override;
void setCursor(int x, int y) override;
void print(const char *str) override;
void fillRect(int x, int y, int w, int h) override;
void drawRect(int x, int y, int w, int h) override;
void drawXbm(int x, int y, const uint8_t *bits, int w, int h) override;
uint16_t getTextWidth(const char *str) override;
void endFrame() override;
};

View file

@ -62,6 +62,13 @@ void SSD1306Display::drawXbm(int x, int y, const uint8_t* bits, int w, int h) {
display.drawBitmap(x, y, bits, w, h, SSD1306_WHITE);
}
uint16_t SSD1306Display::getTextWidth(const char* str) {
int16_t x1, y1;
uint16_t w, h;
display.getTextBounds(str, 0, 0, &x1, &y1, &w, &h);
return w;
}
void SSD1306Display::endFrame() {
display.display();
}

View file

@ -36,5 +36,6 @@ public:
void fillRect(int x, int y, int w, int h) override;
void drawRect(int x, int y, int w, int h) override;
void drawXbm(int x, int y, const uint8_t* bits, int w, int h) override;
uint16_t getTextWidth(const char* str) override;
void endFrame() override;
};

View file

@ -0,0 +1,130 @@
#include "ST7735Display.h"
#ifndef DISPLAY_ROTATION
#define DISPLAY_ROTATION 2
#endif
#define SCALE_X 1.25f // 160 / 128
#define SCALE_Y 1.25f // 80 / 64
bool ST7735Display::i2c_probe(TwoWire& wire, uint8_t addr) {
return true;
/*
wire.beginTransmission(addr);
uint8_t error = wire.endTransmission();
return (error == 0);
*/
}
bool ST7735Display::begin() {
if (!_isOn) {
if (_peripher_power) _peripher_power->claim();
pinMode(PIN_TFT_LEDA_CTL, OUTPUT);
digitalWrite(PIN_TFT_LEDA_CTL, HIGH);
digitalWrite(PIN_TFT_RST, HIGH);
display.initR(INITR_MINI160x80_PLUGIN);
display.setRotation(DISPLAY_ROTATION);
display.setSPISpeed(40000000);
display.fillScreen(ST77XX_BLACK);
display.setTextColor(ST77XX_WHITE);
display.setTextSize(2);
display.cp437(true); // Use full 256 char 'Code Page 437' font
_isOn = true;
}
return true;
}
void ST7735Display::turnOn() {
ST7735Display::begin();
}
void ST7735Display::turnOff() {
if (_isOn) {
digitalWrite(PIN_TFT_LEDA_CTL, HIGH);
digitalWrite(PIN_TFT_RST, LOW);
digitalWrite(PIN_TFT_LEDA_CTL, LOW);
_isOn = false;
if (_peripher_power) _peripher_power->release();
}
}
void ST7735Display::clear() {
//Serial.println("DBG: display.Clear");
display.fillScreen(ST77XX_BLACK);
}
void ST7735Display::startFrame(Color bkg) {
display.fillScreen(0x00);
display.setTextColor(ST77XX_WHITE);
display.setTextSize(1); // This one affects size of Please wait... message
display.cp437(true); // Use full 256 char 'Code Page 437' font
}
void ST7735Display::setTextSize(int sz) {
display.setTextSize(sz);
}
void ST7735Display::setColor(Color c) {
switch (c) {
case DisplayDriver::DARK :
_color = ST77XX_BLACK;
break;
case DisplayDriver::LIGHT :
_color = ST77XX_WHITE;
break;
case DisplayDriver::RED :
_color = ST77XX_RED;
break;
case DisplayDriver::GREEN :
_color = ST77XX_GREEN;
break;
case DisplayDriver::BLUE :
_color = ST77XX_BLUE;
break;
case DisplayDriver::YELLOW :
_color = ST77XX_YELLOW;
break;
case DisplayDriver::ORANGE :
_color = ST77XX_ORANGE;
break;
default:
_color = ST77XX_WHITE;
break;
}
display.setTextColor(_color);
}
void ST7735Display::setCursor(int x, int y) {
display.setCursor(x*SCALE_X, y*SCALE_Y);
}
void ST7735Display::print(const char* str) {
display.print(str);
}
void ST7735Display::fillRect(int x, int y, int w, int h) {
display.fillRect(x*SCALE_X, y*SCALE_Y, w*SCALE_X, h*SCALE_Y, _color);
}
void ST7735Display::drawRect(int x, int y, int w, int h) {
display.drawRect(x*SCALE_X, y*SCALE_Y, w*SCALE_X, h*SCALE_Y, _color);
}
void ST7735Display::drawXbm(int x, int y, const uint8_t* bits, int w, int h) {
display.drawBitmap(x*SCALE_X, y*SCALE_Y, bits, w, h, _color);
}
uint16_t ST7735Display::getTextWidth(const char* str) {
int16_t x1, y1;
uint16_t w, h;
display.getTextBounds(str, 0, 0, &x1, &y1, &w, &h);
return w / SCALE_X;
}
void ST7735Display::endFrame() {
// display.display();
}

View file

@ -0,0 +1,49 @@
#pragma once
#include "DisplayDriver.h"
#include <Wire.h>
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include <helpers/RefCountedDigitalPin.h>
class ST7735Display : public DisplayDriver {
Adafruit_ST7735 display;
bool _isOn;
uint16_t _color;
RefCountedDigitalPin* _peripher_power;
bool i2c_probe(TwoWire& wire, uint8_t addr);
public:
#ifdef USE_PIN_TFT
ST7735Display(RefCountedDigitalPin* peripher_power=NULL) : DisplayDriver(128, 64),
display(PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_SDA, PIN_TFT_SCL, PIN_TFT_RST),
_peripher_power(peripher_power)
{
_isOn = false;
}
#else
ST7735Display(RefCountedDigitalPin* peripher_power=NULL) : DisplayDriver(128, 64),
display(&SPI1, PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_RST),
_peripher_power(peripher_power)
{
_isOn = false;
}
#endif
bool begin();
bool isOn() override { return _isOn; }
void turnOn() override;
void turnOff() override;
void clear() override;
void startFrame(Color bkg = DARK) override;
void setTextSize(int sz) override;
void setColor(Color c) override;
void setCursor(int x, int y) override;
void print(const char* str) override;
void fillRect(int x, int y, int w, int h) override;
void drawRect(int x, int y, int w, int h) override;
void drawXbm(int x, int y, const uint8_t* bits, int w, int h) override;
uint16_t getTextWidth(const char* str) override;
void endFrame() override;
};

View file

@ -2,14 +2,16 @@
#include "ST7789Display.h"
bool ST7789Display::i2c_probe(TwoWire& wire, uint8_t addr) {
return true;
/*
wire.beginTransmission(addr);
uint8_t error = wire.endTransmission();
return (error == 0);
*/
}
#ifndef X_OFFSET
#define X_OFFSET 0 // No offset needed for landscape
#endif
#ifndef Y_OFFSET
#define Y_OFFSET 1 // Vertical offset to prevent top row cutoff
#endif
#define SCALE_X 1.875f // 240 / 128
#define SCALE_Y 2.109375f // 135 / 64
bool ST7789Display::begin() {
if(!_isOn) {
@ -19,14 +21,11 @@ bool ST7789Display::begin() {
digitalWrite(PIN_TFT_LEDA_CTL, LOW);
digitalWrite(PIN_TFT_RST, HIGH);
display.init(135, 240);
display.setRotation(2);
display.setSPISpeed(40000000);
display.fillScreen(ST77XX_BLACK);
display.setTextColor(ST77XX_WHITE);
display.setTextSize(2);
display.cp437(true); // Use full 256 char 'Code Page 437' font
display.init();
display.landscapeScreen();
display.displayOn();
setCursor(0,0);
_isOn = true;
}
return true;
@ -40,24 +39,28 @@ void ST7789Display::turnOff() {
digitalWrite(PIN_TFT_VDD_CTL, HIGH);
digitalWrite(PIN_TFT_LEDA_CTL, HIGH);
digitalWrite(PIN_TFT_RST, LOW);
// digitalWrite(PIN_TFT_VDD_CTL, LOW);
// digitalWrite(PIN_TFT_LEDA_CTL, LOW);
_isOn = false;
}
void ST7789Display::clear() {
display.fillScreen(ST77XX_BLACK);
display.clear();
}
void ST7789Display::startFrame(Color bkg) {
display.fillScreen(0x00);
display.setTextColor(ST77XX_WHITE);
display.setTextSize(2);
display.cp437(true); // Use full 256 char 'Code Page 437' font
display.clear();
}
void ST7789Display::setTextSize(int sz) {
display.setTextSize(sz);
switch(sz) {
case 1 :
display.setFont(ArialMT_Plain_16);
break;
case 2 :
display.setFont(ArialMT_Plain_24);
break;
default:
display.setFont(ArialMT_Plain_16);
}
}
void ST7789Display::setColor(Color c) {
@ -87,31 +90,68 @@ void ST7789Display::setColor(Color c) {
_color = ST77XX_WHITE;
break;
}
display.setTextColor(_color);
display.setRGB(_color);
}
void ST7789Display::setCursor(int x, int y) {
display.setCursor(x, y);
_x = x*SCALE_X + X_OFFSET;
_y = y*SCALE_Y + Y_OFFSET;
}
void ST7789Display::print(const char* str) {
display.print(str);
display.drawString(_x, _y, str);
}
void ST7789Display::fillRect(int x, int y, int w, int h) {
display.fillRect(x, y, w, h, _color);
display.fillRect(x*SCALE_X + X_OFFSET, y*SCALE_Y + Y_OFFSET, w*SCALE_X, h*SCALE_Y);
}
void ST7789Display::drawRect(int x, int y, int w, int h) {
display.drawRect(x, y, w, h, _color);
display.drawRect(x*SCALE_X + X_OFFSET, y*SCALE_Y + Y_OFFSET, w*SCALE_X, h*SCALE_Y);
}
void ST7789Display::drawXbm(int x, int y, const uint8_t* bits, int w, int h) {
display.drawBitmap(x, y, bits, w, h, _color);
// Calculate the base position in display coordinates
uint16_t startX = x * SCALE_X + X_OFFSET;
uint16_t startY = y * SCALE_Y + Y_OFFSET;
// Width in bytes for bitmap processing
uint16_t widthInBytes = (w + 7) / 8;
// Process the bitmap row by row
for (uint16_t by = 0; by < h; by++) {
// Calculate the target y-coordinates for this logical row
int y1 = startY + (int)(by * SCALE_Y);
int y2 = startY + (int)((by + 1) * SCALE_Y);
int block_h = y2 - y1;
// Scan across the row bit by bit
for (uint16_t bx = 0; bx < w; bx++) {
// Calculate the target x-coordinates for this logical column
int x1 = startX + (int)(bx * SCALE_X);
int x2 = startX + (int)((bx + 1) * SCALE_X);
int block_w = x2 - x1;
// Get the current bit
uint16_t byteOffset = (by * widthInBytes) + (bx / 8);
uint8_t bitMask = 0x80 >> (bx & 7);
bool bitSet = pgm_read_byte(bits + byteOffset) & bitMask;
// If the bit is set, draw a block of pixels
if (bitSet) {
// Draw the block as a filled rectangle
display.fillRect(x1, y1, block_w, block_h);
}
}
}
}
uint16_t ST7789Display::getTextWidth(const char* str) {
return display.getStringWidth(str) / SCALE_X;
}
void ST7789Display::endFrame() {
// display.display();
display.display();
}
#endif

View file

@ -4,17 +4,18 @@
#include <Wire.h>
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ST7789.h>
#include <ST7789Spi.h>
class ST7789Display : public DisplayDriver {
Adafruit_ST7789 display;
ST7789Spi display;
bool _isOn;
uint16_t _color;
int _x=0, _y=0;
bool i2c_probe(TwoWire& wire, uint8_t addr);
public:
ST7789Display() : DisplayDriver(135, 240), display(&SPI1, PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_RST) { _isOn = false; }
// ST7789Display() : DisplayDriver(135, 240), display(PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_SDA, PIN_TFT_SCL, PIN_TFT_RST) { _isOn = false; }
ST7789Display() : DisplayDriver(128, 64), display(&SPI1, PIN_TFT_RST, PIN_TFT_DC, PIN_TFT_CS, GEOMETRY_RAWMODE, 240, 135) {_isOn = false;}
bool begin();
bool isOn() override { return _isOn; }
@ -29,5 +30,6 @@ public:
void fillRect(int x, int y, int w, int h) override;
void drawRect(int x, int y, int w, int h) override;
void drawXbm(int x, int y, const uint8_t* bits, int w, int h) override;
uint16_t getTextWidth(const char* str) override;
void endFrame() override;
};

464
src/helpers/ui/ST7789Spi.h Normal file
View file

@ -0,0 +1,464 @@
/**
* The MIT License (MIT)
*
* Copyright (c) 2018 by ThingPulse, Daniel Eichhorn
* Copyright (c) 2018 by Fabrice Weinberg
* Copyright (c) 2024 by Heltec AutoMation
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* ThingPulse invests considerable time and money to develop these open source libraries.
* Please support us by buying our products (and not the clones) from
* https://thingpulse.com
*
*/
#ifndef ST7789Spi_h
#define ST7789Spi_h
#include "OLEDDisplay.h"
#include <SPI.h>
#define ST_CMD_DELAY 0x80 // special signifier for command lists
#define ST77XX_NOP 0x00
#define ST77XX_SWRESET 0x01
#define ST77XX_RDDID 0x04
#define ST77XX_RDDST 0x09
#define ST77XX_SLPIN 0x10
#define ST77XX_SLPOUT 0x11
#define ST77XX_PTLON 0x12
#define ST77XX_NORON 0x13
#define ST77XX_INVOFF 0x20
#define ST77XX_INVON 0x21
#define ST77XX_DISPOFF 0x28
#define ST77XX_DISPON 0x29
#define ST77XX_CASET 0x2A
#define ST77XX_RASET 0x2B
#define ST77XX_RAMWR 0x2C
#define ST77XX_RAMRD 0x2E
#define ST77XX_PTLAR 0x30
#define ST77XX_TEOFF 0x34
#define ST77XX_TEON 0x35
#define ST77XX_MADCTL 0x36
#define ST77XX_COLMOD 0x3A
#define ST77XX_MADCTL_MY 0x80
#define ST77XX_MADCTL_MX 0x40
#define ST77XX_MADCTL_MV 0x20
#define ST77XX_MADCTL_ML 0x10
#define ST77XX_MADCTL_RGB 0x00
#define ST77XX_RDID1 0xDA
#define ST77XX_RDID2 0xDB
#define ST77XX_RDID3 0xDC
#define ST77XX_RDID4 0xDD
// Some ready-made 16-bit ('565') color settings:
#define ST77XX_BLACK 0x0000
#define ST77XX_WHITE 0xFFFF
#define ST77XX_RED 0xF800
#define ST77XX_GREEN 0x07E0
#define ST77XX_BLUE 0x001F
#define ST77XX_CYAN 0x07FF
#define ST77XX_MAGENTA 0xF81F
#define ST77XX_YELLOW 0xFFE0
#define ST77XX_ORANGE 0xFC00
#define LED_A_ON LOW
#ifdef ESP_PLATFORM
#undef LED_A_ON
#define LED_A_ON HIGH
#define rtos_free free
#define rtos_malloc malloc
//SPIClass SPI1(HSPI);
#endif
class ST7789Spi : public OLEDDisplay {
private:
uint8_t _rst;
uint8_t _dc;
uint8_t _cs;
uint8_t _ledA;
int _miso;
int _mosi;
int _clk;
SPIClass * _spi;
SPISettings _spiSettings;
uint16_t _RGB=0xFFFF;
uint8_t _buffheight;
public:
/* pass _cs as -1 to indicate "do not use CS pin", for cases where it is hard wired low */
ST7789Spi(SPIClass *spiClass,uint8_t _rst, uint8_t _dc, uint8_t _cs, OLEDDISPLAY_GEOMETRY g = GEOMETRY_RAWMODE,uint16_t width=240,uint16_t height=135,int mosi=-1,int miso=-1,int clk=-1) {
this->_spi = spiClass;
this->_rst = _rst;
this->_dc = _dc;
this->_cs = _cs;
this->_mosi=mosi;
this->_miso=miso;
this->_clk=clk;
//this->_ledA = _ledA;
_spiSettings = SPISettings(40000000, MSBFIRST, SPI_MODE0);
setGeometry(g,width,height);
}
bool connect(){
this->_buffheight=displayHeight / 8;
this->_buffheight+=displayHeight % 8 ? 1:0;
pinMode(_cs, OUTPUT);
pinMode(_dc, OUTPUT);
//pinMode(_ledA, OUTPUT);
if (_cs != (uint8_t) -1) {
pinMode(_cs, OUTPUT);
}
pinMode(_rst, OUTPUT);
#ifdef ESP_PLATFORM
_spi->begin(_clk,_miso,_mosi,-1);
#else
_spi->begin();
#endif
_spi->setClockDivider (SPI_CLOCK_DIV2);
// Pulse Reset low for 10ms
digitalWrite(_rst, HIGH);
delay(1);
digitalWrite(_rst, LOW);
delay(10);
digitalWrite(_rst, HIGH);
_spi->begin ();
//digitalWrite(_ledA, LED_A_ON);
return true;
}
void display(void) {
#ifdef OLEDDISPLAY_DOUBLE_BUFFER
uint16_t minBoundY = UINT16_MAX;
uint16_t maxBoundY = 0;
uint16_t minBoundX = UINT16_MAX;
uint16_t maxBoundX = 0;
uint16_t x, y;
// Calculate the Y bounding box of changes
// and copy buffer[pos] to buffer_back[pos];
for (y = 0; y < _buffheight; y++) {
for (x = 0; x < displayWidth; x++) {
//Serial.printf("x %d y %d\r\n",x,y);
uint16_t pos = x + y * displayWidth;
if (buffer[pos] != buffer_back[pos]) {
minBoundY = min(minBoundY, y);
maxBoundY = max(maxBoundY, y);
minBoundX = min(minBoundX, x);
maxBoundX = max(maxBoundX, x);
}
buffer_back[pos] = buffer[pos];
}
yield();
}
// If the minBoundY wasn't updated
// we can savely assume that buffer_back[pos] == buffer[pos]
// holdes true for all values of pos
if (minBoundY == UINT16_MAX) return;
set_CS(LOW);
_spi->beginTransaction(_spiSettings);
for (y = minBoundY; y <= maxBoundY; y++)
{
for(int temp = 0; temp<8;temp++)
{
//setAddrWindow(minBoundX,y*8+temp,maxBoundX-minBoundX+1,1);
setAddrWindow(minBoundX,y*8+temp,maxBoundX-minBoundX+1,1);
//setAddrWindow(y*8+temp,minBoundX,1,maxBoundX-minBoundX+1);
uint32_t const pixbufcount = maxBoundX-minBoundX+1;
uint16_t *pixbuf = (uint16_t *)rtos_malloc(2 * pixbufcount);
for (x = minBoundX; x <= maxBoundX; x++)
{
pixbuf[x-minBoundX] = ((buffer[x + y * displayWidth]>>temp)&0x01)==1?_RGB:0;
}
#ifdef ESP_PLATFORM
_spi->transferBytes((uint8_t *)pixbuf, NULL, 2 * pixbufcount);
#else
_spi->transfer(pixbuf, NULL, 2 * pixbufcount);
#endif
rtos_free(pixbuf);
}
}
_spi->endTransaction();
set_CS(HIGH);
#else
set_CS(LOW);
_spi->beginTransaction(_spiSettings);
uint8_t x, y;
for (y = 0; y < _buffheight; y++)
{
for(int temp = 0; temp<8;temp++)
{
//setAddrWindow(minBoundX,y*8+temp,maxBoundX-minBoundX+1,1);
//setAddrWindow(minBoundX,y*8+temp,maxBoundX-minBoundX+1,1);
setAddrWindow(y*8+temp,0,1,displayWidth);
uint32_t const pixbufcount = displayWidth;
uint16_t *pixbuf = (uint16_t *)rtos_malloc(2 * pixbufcount);
for (x = 0; x < displayWidth; x++)
{
pixbuf[x] = ((buffer[x + y * displayWidth]>>temp)&0x01)==1?_RGB:0;
}
#ifdef ESP_PLATFORM
_spi->transferBytes((uint8_t *)pixbuf, NULL, 2 * pixbufcount);
#else
_spi->transfer(pixbuf, NULL, 2 * pixbufcount);
#endif
rtos_free(pixbuf);
}
}
_spi->endTransaction();
set_CS(HIGH);
#endif
}
virtual void resetOrientation() {
uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MV;
sendCommand(ST77XX_MADCTL);
WriteData(madctl);
delay(10);
}
virtual void flipScreenVertically() {
uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MV|ST77XX_MADCTL_MY;
sendCommand(ST77XX_MADCTL);
WriteData(madctl);
delay(10);
}
virtual void mirrorScreen() {
uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MV|ST77XX_MADCTL_MX|ST77XX_MADCTL_MY;
sendCommand(ST77XX_MADCTL);
WriteData(madctl);
delay(10);
}
virtual void landscapeScreen() {
// For landscape mode rotated 180 degrees with correct text direction
// MV swaps rows/columns for landscape orientation
// Adding MX (instead of MY) flips X axis and rotates 180 degrees
uint8_t madctl = ST77XX_MADCTL_RGB | ST77XX_MADCTL_MV | ST77XX_MADCTL_MX;
sendCommand(ST77XX_MADCTL);
WriteData(madctl);
delay(10);
}
void setRGB(uint16_t c)
{
this->_RGB=0x00|c>>8|c<<8&0xFF00;
}
void displayOn(void) {
//sendCommand(DISPLAYON);
}
void displayOff(void) {
//sendCommand(DISPLAYOFF);
}
void drawBitmap(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const uint8_t *xbm) {
int16_t widthInXbm = (width + 7) / 8;
uint8_t data = 0;
for(int16_t y = 0; y < height; y++) {
for(int16_t x = 0; x < width; x++ ) {
if (x & 7) {
data <<= 1; // Move a bit
} else { // Read new data every 8 bit
data = pgm_read_byte(xbm + (x / 8) + y * widthInXbm);
}
// if there is a bit draw it
if (data & 0x80) {
setPixel(xMove + x, yMove + y);
}
}
}
}
//#define ST77XX_MADCTL_MY 0x80
//#define ST77XX_MADCTL_MX 0x40
//#define ST77XX_MADCTL_MV 0x20
//#define ST77XX_MADCTL_ML 0x10
protected:
// Send all the init commands
virtual void sendInitCommands()
{
sendCommand(ST77XX_SWRESET); // 1: Software reset, no args, w/delay
delay(150);
sendCommand(ST77XX_SLPOUT); // 2: Out of sleep mode, no args, w/delay
delay(10);
sendCommand(ST77XX_COLMOD); // 3: Set color mode, 16-bit color
WriteData(0x55);
delay(10);
// Initialize with landscape orientation rotated 180 degrees
uint8_t madctl = ST77XX_MADCTL_RGB | ST77XX_MADCTL_MV | ST77XX_MADCTL_MX;
sendCommand(ST77XX_MADCTL); // 4: Mem access ctrl (directions)
WriteData(madctl);
delay(10);
// Set column address range for landscape orientation (240 pixels wide)
sendCommand(ST77XX_CASET); // 5: Column addr set
WriteData(0x00);
WriteData(0x00); // XSTART = 0
WriteData(0x00);
WriteData(240); // XEND = 240
// Set row address range for landscape orientation (135 pixels tall)
sendCommand(ST77XX_RASET); // 6: Row addr set
WriteData(0x00);
WriteData(0x00); // YSTART = 0
WriteData(0x00);
WriteData(135); // YEND = 135
sendCommand(ST77XX_SLPOUT); // 7: hack
delay(10);
sendCommand(ST77XX_NORON); // 8: Normal display on, no args, w/delay
delay(10);
sendCommand(ST77XX_DISPON); // 9: Main screen turn on, no args, delay
delay(10);
sendCommand(ST77XX_INVON); // 10: invert
delay(10);
// Use landscape mode instead of portrait
landscapeScreen();
setRGB(ST77XX_GREEN);
}
private:
void setAddrWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h) {
x += (320-displayWidth)/2;
y += (240-displayHeight)/2;
uint32_t xa = ((uint32_t)x << 16) | (x + w - 1);
uint32_t ya = ((uint32_t)y << 16) | (y + h - 1);
writeCommand(ST77XX_CASET); // Column addr set
SPI_WRITE32(xa);
writeCommand(ST77XX_RASET); // Row addr set
SPI_WRITE32(ya);
writeCommand(ST77XX_RAMWR); // write to RAM
}
int getBufferOffset(void) {
return 0;
}
inline void set_CS(bool level) {
if (_cs != (uint8_t) -1) {
digitalWrite(_cs, level);
}
};
inline void sendCommand(uint8_t com) __attribute__((always_inline)){
set_CS(HIGH);
digitalWrite(_dc, LOW);
set_CS(LOW);
_spi->beginTransaction(_spiSettings);
_spi->transfer(com);
_spi->endTransaction();
set_CS(HIGH);
digitalWrite(_dc, HIGH);
}
inline void WriteData(uint8_t data) __attribute__((always_inline)){
digitalWrite(_cs, LOW);
_spi->beginTransaction(_spiSettings);
_spi->transfer(data);
_spi->endTransaction();
digitalWrite(_cs, HIGH);
}
void SPI_WRITE32(uint32_t l)
{
_spi->transfer(l >> 24);
_spi->transfer(l >> 16);
_spi->transfer(l >> 8);
_spi->transfer(l);
}
void writeCommand(uint8_t cmd) {
digitalWrite(_dc, LOW);
_spi->transfer(cmd);
digitalWrite(_dc, HIGH);
}
// Private functions
void setGeometry(OLEDDISPLAY_GEOMETRY g, uint16_t width, uint16_t height) {
this->geometry = g;
switch (g) {
case GEOMETRY_128_128:
this->displayWidth = 128;
this->displayHeight = 128;
break;
case GEOMETRY_128_64:
this->displayWidth = 128;
this->displayHeight = 64;
break;
case GEOMETRY_128_32:
this->displayWidth = 128;
this->displayHeight = 32;
break;
case GEOMETRY_64_48:
this->displayWidth = 64;
this->displayHeight = 48;
break;
case GEOMETRY_64_32:
this->displayWidth = 64;
this->displayHeight = 32;
break;
case GEOMETRY_RAWMODE:
this->displayWidth = width > 0 ? width : 128;
this->displayHeight = height > 0 ? height : 64;
break;
}
uint8_t tmp=displayHeight % 8;
uint8_t _buffheight=displayHeight / 8;
if(tmp!=0)
_buffheight++;
this->displayBufferSize = displayWidth * _buffheight ;
}
};
#endif

54
src/helpers/ui/buzzer.cpp Normal file
View file

@ -0,0 +1,54 @@
#ifdef PIN_BUZZER
#include "buzzer.h"
void genericBuzzer::begin() {
// Serial.print("DBG: Setting up buzzer on pin ");
// Serial.println(PIN_BUZZER);
#ifdef PIN_BUZZER_EN
pinMode(PIN_BUZZER_EN, OUTPUT);
digitalWrite(PIN_BUZZER_EN, HIGH);
#endif
quiet(false);
pinMode(PIN_BUZZER, OUTPUT);
startup();
}
void genericBuzzer::play(const char *melody) {
if (isPlaying()) // interrupt existing
{
rtttl::stop();
}
if (_is_quiet) return;
rtttl::begin(PIN_BUZZER,melody);
// Serial.print("DBG: Playing melody - isQuiet: ");
// Serial.println(isQuiet());
}
bool genericBuzzer::isPlaying() {
return rtttl::isPlaying();
}
void genericBuzzer::loop() {
if (!rtttl::done()) rtttl::play();
}
void genericBuzzer::startup() {
play(startup_song);
}
void genericBuzzer::shutdown() {
play(shutdown_song);
}
void genericBuzzer::quiet(bool buzzer_state) {
_is_quiet = buzzer_state;
}
bool genericBuzzer::isQuiet() {
return _is_quiet;
}
#endif // ifdef PIN_BUZZER

37
src/helpers/ui/buzzer.h Normal file
View file

@ -0,0 +1,37 @@
#pragma once
#include <Arduino.h>
#include <NonBlockingRtttl.h>
/* class abstracts underlying RTTTL library
Just a simple imlementation to start. At the moment use same
melody for message and discovery
Suggest enum type for different sounds
- on message
- on discovery
TODO
- make message ring tone configurable
*/
class genericBuzzer
{
public:
void begin(); // set up buzzer port
void play(const char *melody); // Generic play function
void loop(); // loop driven-nonblocking
void startup(); // play startup sound
void shutdown(); // play shutdown sound
bool isPlaying(); // returns true if a sound is still playing else false
void quiet(bool buzzer_state); // enables or disables the buzzer
bool isQuiet(); // get buzzer state on/off
private:
// gemini's picks:
const char *startup_song = "Startup:d=4,o=5,b=160:16c6,16e6,8g6";
const char *shutdown_song = "Shutdown:d=4,o=5,b=100:8g5,16e5,16c5";
bool _is_quiet = true;
};