mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-09-17 00:26:37 +00:00
Merge remote-tracking branch 'upstream/dev' into rak-ethernet
# Conflicts: # docs/faq.md
This commit is contained in:
+2
-1
@@ -17,6 +17,7 @@
|
||||
#define PATH_HASH_SIZE 1
|
||||
|
||||
#define MAX_PACKET_PAYLOAD 184
|
||||
#define MAX_GROUP_DATA_LENGTH (MAX_PACKET_PAYLOAD - CIPHER_BLOCK_SIZE - 3)
|
||||
#define MAX_PATH_SIZE 64
|
||||
#define MAX_TRANS_UNIT 255
|
||||
|
||||
@@ -100,4 +101,4 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ namespace mesh {
|
||||
#define PAYLOAD_TYPE_ACK 0x03 // a simple ack
|
||||
#define PAYLOAD_TYPE_ADVERT 0x04 // a node advertising its Identity
|
||||
#define PAYLOAD_TYPE_GRP_TXT 0x05 // an (unverified) group text message (prefixed with channel hash, MAC) (enc data: timestamp, "name: msg")
|
||||
#define PAYLOAD_TYPE_GRP_DATA 0x06 // an (unverified) group datagram (prefixed with channel hash, MAC) (enc data: timestamp, blob)
|
||||
#define PAYLOAD_TYPE_GRP_DATA 0x06 // an (unverified) group datagram (prefixed with channel hash, MAC) (enc data: data_type(uint16), data_len, blob)
|
||||
#define PAYLOAD_TYPE_ANON_REQ 0x07 // generic request (prefixed with dest_hash, ephemeral pub_key, MAC) (enc data: ...)
|
||||
#define PAYLOAD_TYPE_PATH 0x08 // returned path (prefixed with dest/src hashes, MAC) (enc data: path, extra)
|
||||
#define PAYLOAD_TYPE_TRACE 0x09 // trace a path, collecting SNI for each hop
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "AutoDiscoverRTCClock.h"
|
||||
#include "RTClib.h"
|
||||
#include <Melopero_RV3028.h>
|
||||
#include "RTC_RX8130CE.h"
|
||||
|
||||
static RTC_DS3231 rtc_3231;
|
||||
static bool ds3231_success = false;
|
||||
@@ -11,9 +12,13 @@ static bool rv3028_success = false;
|
||||
static RTC_PCF8563 rtc_8563;
|
||||
static bool rtc_8563_success = false;
|
||||
|
||||
static RTC_RX8130CE rtc_8130;
|
||||
static bool rtc_8130_success = false;
|
||||
|
||||
#define DS3231_ADDRESS 0x68
|
||||
#define RV3028_ADDRESS 0x52
|
||||
#define PCF8563_ADDRESS 0x51
|
||||
#define RX8130CE_ADDRESS 0x32
|
||||
|
||||
bool AutoDiscoverRTCClock::i2c_probe(TwoWire& wire, uint8_t addr) {
|
||||
wire.beginTransmission(addr);
|
||||
@@ -25,22 +30,32 @@ void AutoDiscoverRTCClock::begin(TwoWire& wire) {
|
||||
if (i2c_probe(wire, DS3231_ADDRESS)) {
|
||||
ds3231_success = rtc_3231.begin(&wire);
|
||||
}
|
||||
|
||||
if (i2c_probe(wire, RV3028_ADDRESS)) {
|
||||
rtc_rv3028.initI2C(wire);
|
||||
rtc_rv3028.writeToRegister(0x35, 0x00);
|
||||
rtc_rv3028.writeToRegister(0x37, 0xB4); // Direct Switching Mode (DSM): when VDD < VBACKUP, switchover occurs from VDD to VBACKUP
|
||||
rtc_rv3028.set24HourMode(); // Set the device to use the 24hour format (default) instead of the 12 hour format
|
||||
rtc_rv3028.writeToRegister(0x35, 0x00);
|
||||
rtc_rv3028.writeToRegister(0x37, 0xB4); // Direct Switching Mode (DSM): when VDD < VBACKUP, switchover occurs from VDD to VBACKUP
|
||||
rtc_rv3028.set24HourMode(); // Set the device to use the 24hour format (default) instead of the 12 hour format
|
||||
rv3028_success = true;
|
||||
}
|
||||
if(i2c_probe(wire,PCF8563_ADDRESS)){
|
||||
|
||||
if (i2c_probe(wire, PCF8563_ADDRESS)) {
|
||||
rtc_8563_success = rtc_8563.begin(&wire);
|
||||
}
|
||||
|
||||
if (i2c_probe(wire, RX8130CE_ADDRESS)) {
|
||||
MESH_DEBUG_PRINTLN("RX8130CE: Found");
|
||||
rtc_8130.begin(&wire);
|
||||
rtc_8130_success = true;
|
||||
MESH_DEBUG_PRINTLN("RX8130CE: Initialized");
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t AutoDiscoverRTCClock::getCurrentTime() {
|
||||
if (ds3231_success) {
|
||||
return rtc_3231.now().unixtime();
|
||||
}
|
||||
|
||||
if (rv3028_success) {
|
||||
return DateTime(
|
||||
rtc_rv3028.getYear(),
|
||||
@@ -51,9 +66,16 @@ uint32_t AutoDiscoverRTCClock::getCurrentTime() {
|
||||
rtc_rv3028.getSecond()
|
||||
).unixtime();
|
||||
}
|
||||
if(rtc_8563_success){
|
||||
|
||||
if (rtc_8563_success) {
|
||||
return rtc_8563.now().unixtime();
|
||||
}
|
||||
|
||||
if (rtc_8130_success) {
|
||||
MESH_DEBUG_PRINTLN("RX8130CE: Reading time");
|
||||
return rtc_8130.now().unixtime();
|
||||
}
|
||||
|
||||
return _fallback->getCurrentTime();
|
||||
}
|
||||
|
||||
@@ -66,6 +88,9 @@ void AutoDiscoverRTCClock::setCurrentTime(uint32_t time) {
|
||||
rtc_rv3028.setTime(dt.year(), dt.month(), weekday, dt.day(), dt.hour(), dt.minute(), dt.second());
|
||||
} else if (rtc_8563_success) {
|
||||
rtc_8563.adjust(DateTime(time));
|
||||
} else if (rtc_8130_success) {
|
||||
MESH_DEBUG_PRINTLN("RX8130CE: Setting time");
|
||||
rtc_8130.adjust(DateTime(time));
|
||||
} else {
|
||||
_fallback->setCurrentTime(time);
|
||||
}
|
||||
|
||||
@@ -353,8 +353,18 @@ int BaseChatMesh::searchChannelsByHash(const uint8_t* hash, mesh::GroupChannel d
|
||||
#endif
|
||||
|
||||
void BaseChatMesh::onGroupDataRecv(mesh::Packet* packet, uint8_t type, const mesh::GroupChannel& channel, uint8_t* data, size_t len) {
|
||||
uint8_t txt_type = data[4];
|
||||
if (type == PAYLOAD_TYPE_GRP_TXT && len > 5 && (txt_type >> 2) == 0) { // 0 = plain text msg
|
||||
if (type == PAYLOAD_TYPE_GRP_TXT) {
|
||||
if (len < 5) {
|
||||
MESH_DEBUG_PRINTLN("onGroupDataRecv: dropping short group text payload len=%d", (uint32_t)len);
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t txt_type = data[4];
|
||||
if ((txt_type >> 2) != 0) {
|
||||
MESH_DEBUG_PRINTLN("onGroupDataRecv: dropping unsupported group text type=%d", (uint32_t)txt_type);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t timestamp;
|
||||
memcpy(×tamp, data, 4);
|
||||
|
||||
@@ -363,6 +373,23 @@ void BaseChatMesh::onGroupDataRecv(mesh::Packet* packet, uint8_t type, const mes
|
||||
|
||||
// notify UI of this new message
|
||||
onChannelMessageRecv(channel, packet, timestamp, (const char *) &data[5]); // let UI know
|
||||
} else if (type == PAYLOAD_TYPE_GRP_DATA) {
|
||||
if (len < 3) {
|
||||
MESH_DEBUG_PRINTLN("onGroupDataRecv: dropping short group data payload len=%d", (uint32_t)len);
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t data_type = ((uint16_t)data[0]) | (((uint16_t)data[1]) << 8);
|
||||
uint8_t data_len = data[2];
|
||||
size_t available_len = len - 3;
|
||||
|
||||
if (data_len > available_len) {
|
||||
MESH_DEBUG_PRINTLN("onGroupDataRecv: dropping malformed group data type=%d len=%d available=%d",
|
||||
(uint32_t)data_type, (uint32_t)data_len, (uint32_t)available_len);
|
||||
return;
|
||||
}
|
||||
|
||||
onChannelDataRecv(channel, packet, data_type, &data[3], data_len);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -454,6 +481,37 @@ bool BaseChatMesh::sendGroupMessage(uint32_t timestamp, mesh::GroupChannel& chan
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BaseChatMesh::sendGroupData(mesh::GroupChannel& channel, uint8_t* path, uint8_t path_len, uint16_t data_type, const uint8_t* data, int data_len) {
|
||||
if (data_len < 0) {
|
||||
MESH_DEBUG_PRINTLN("sendGroupData: invalid negative data_len=%d", data_len);
|
||||
return false;
|
||||
}
|
||||
if (data_len > MAX_GROUP_DATA_LENGTH) {
|
||||
MESH_DEBUG_PRINTLN("sendGroupData: data_len=%d exceeds max=%d", data_len, MAX_GROUP_DATA_LENGTH);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t temp[3 + MAX_GROUP_DATA_LENGTH];
|
||||
temp[0] = (uint8_t)(data_type & 0xFF);
|
||||
temp[1] = (uint8_t)(data_type >> 8);
|
||||
temp[2] = (uint8_t)data_len;
|
||||
if (data_len > 0) memcpy(&temp[3], data, data_len);
|
||||
|
||||
auto pkt = createGroupDatagram(PAYLOAD_TYPE_GRP_DATA, channel, temp, 3 + data_len);
|
||||
if (pkt == NULL) {
|
||||
MESH_DEBUG_PRINTLN("sendGroupData: unable to create group datagram, data_len=%d", data_len);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (path_len == OUT_PATH_UNKNOWN) {
|
||||
sendFloodScoped(channel, pkt);
|
||||
} else {
|
||||
sendDirect(pkt, path, path_len);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BaseChatMesh::shareContactZeroHop(const ContactInfo& contact) {
|
||||
int plen = getBlobByKey(contact.id.pub_key, PUB_KEY_SIZE, temp_buf); // retrieve last raw advert packet
|
||||
if (plen == 0) return false; // not found
|
||||
|
||||
@@ -111,6 +111,8 @@ 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 void onChannelDataRecv(const mesh::GroupChannel& channel, mesh::Packet* pkt, uint16_t data_type,
|
||||
const uint8_t* data, size_t data_len) {}
|
||||
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;
|
||||
virtual void handleReturnPathRetry(const ContactInfo& contact, const uint8_t* path, uint8_t path_len);
|
||||
@@ -148,6 +150,7 @@ public:
|
||||
int sendMessage(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char* text, uint32_t& expected_ack, uint32_t& est_timeout);
|
||||
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);
|
||||
bool sendGroupData(mesh::GroupChannel& channel, uint8_t* path, uint8_t path_len, uint16_t data_type, const uint8_t* data, int data_len);
|
||||
int sendLogin(const ContactInfo& recipient, const char* password, uint32_t& est_timeout);
|
||||
int sendAnonReq(const ContactInfo& recipient, const uint8_t* data, uint8_t len, uint32_t& tag, uint32_t& est_timeout);
|
||||
int sendRequest(const ContactInfo& recipient, uint8_t req_type, uint32_t& tag, uint32_t& est_timeout);
|
||||
|
||||
+579
-417
File diff suppressed because it is too large
Load Diff
+24
-3
@@ -4,6 +4,7 @@
|
||||
#include <helpers/IdentityStore.h>
|
||||
#include <helpers/SensorManager.h>
|
||||
#include <helpers/ClientACL.h>
|
||||
#include <helpers/RegionMap.h>
|
||||
|
||||
#if defined(WITH_RS232_BRIDGE) || defined(WITH_ESPNOW_BRIDGE)
|
||||
#define WITH_BRIDGE
|
||||
@@ -57,6 +58,7 @@ struct NodePrefs { // persisted to file
|
||||
uint32_t discovery_mod_timestamp;
|
||||
float adc_multiplier;
|
||||
char owner_info[120];
|
||||
uint8_t rx_boosted_gain; // power settings
|
||||
uint8_t path_hash_mode; // which path mode to use when sending
|
||||
uint8_t loop_detect;
|
||||
};
|
||||
@@ -87,6 +89,16 @@ public:
|
||||
virtual void clearStats() = 0;
|
||||
virtual void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) = 0;
|
||||
|
||||
virtual void startRegionsLoad() {
|
||||
// no op by default
|
||||
}
|
||||
virtual bool saveRegions() {
|
||||
return false;
|
||||
}
|
||||
virtual void onDefaultRegionChanged(const RegionEntry* r) {
|
||||
// no op by default
|
||||
}
|
||||
|
||||
virtual void setBridgeState(bool enable) {
|
||||
// no op by default
|
||||
};
|
||||
@@ -94,6 +106,10 @@ public:
|
||||
virtual void restartBridge() {
|
||||
// no op by default
|
||||
};
|
||||
|
||||
virtual void setRxBoostedGain(bool enable) {
|
||||
// no op by default
|
||||
};
|
||||
};
|
||||
|
||||
class CommonCLI {
|
||||
@@ -102,6 +118,7 @@ class CommonCLI {
|
||||
CommonCLICallbacks* _callbacks;
|
||||
mesh::MainBoard* _board;
|
||||
SensorManager* _sensors;
|
||||
RegionMap* _region_map;
|
||||
ClientACL* _acl;
|
||||
char tmp[PRV_KEY_SIZE*2 + 4];
|
||||
|
||||
@@ -109,12 +126,16 @@ class CommonCLI {
|
||||
void savePrefs();
|
||||
void loadPrefsInt(FILESYSTEM* _fs, const char* filename);
|
||||
|
||||
void handleRegionCmd(char* command, char* reply);
|
||||
void handleGetCmd(uint32_t sender_timestamp, char* command, char* reply);
|
||||
void handleSetCmd(uint32_t sender_timestamp, char* command, char* reply);
|
||||
|
||||
public:
|
||||
CommonCLI(mesh::MainBoard& board, mesh::RTCClock& rtc, SensorManager& sensors, ClientACL& acl, NodePrefs* prefs, CommonCLICallbacks* callbacks)
|
||||
: _board(&board), _rtc(&rtc), _sensors(&sensors), _acl(&acl), _prefs(prefs), _callbacks(callbacks) { }
|
||||
CommonCLI(mesh::MainBoard& board, mesh::RTCClock& rtc, SensorManager& sensors, RegionMap& region_map, ClientACL& acl, NodePrefs* prefs, CommonCLICallbacks* callbacks)
|
||||
: _board(&board), _rtc(&rtc), _sensors(&sensors), _region_map(®ion_map), _acl(&acl), _prefs(prefs), _callbacks(callbacks) { }
|
||||
|
||||
void loadPrefs(FILESYSTEM* _fs);
|
||||
void savePrefs(FILESYSTEM* _fs);
|
||||
void handleCommand(uint32_t sender_timestamp, const char* command, char* reply);
|
||||
void handleCommand(uint32_t sender_timestamp, char* command, char* reply);
|
||||
uint8_t buildAdvertData(uint8_t node_type, uint8_t* app_data);
|
||||
};
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
#include <MeshCore.h>
|
||||
#include <Arduino.h>
|
||||
|
||||
#ifndef USER_BTN_PRESSED
|
||||
#define USER_BTN_PRESSED LOW
|
||||
#endif
|
||||
|
||||
#if defined(ESP_PLATFORM)
|
||||
|
||||
#include <rom/rtc.h>
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
#include "RTC_RX8130CE.h"
|
||||
#include "RTClib.h"
|
||||
|
||||
|
||||
bool RTC_RX8130CE::stop(bool stop) {
|
||||
write_register(0x1E, stop ? 0x040 : 0x00);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RTC_RX8130CE::begin(TwoWire *wire) {
|
||||
if (i2c_dev) {
|
||||
delete i2c_dev;
|
||||
}
|
||||
|
||||
i2c_dev = new Adafruit_I2CDevice(this->_addr, wire);
|
||||
if (!i2c_dev->begin()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Digital offset register:
|
||||
* [7] DET: 0 -> disabled
|
||||
* [6:0] L7-L1: 0 -> no offset
|
||||
*/
|
||||
write_register(0x30, 0x00);
|
||||
|
||||
/*
|
||||
* Extension Register register:
|
||||
* [7:6] FSEL: 0 -> 0
|
||||
* [5] USEL: 0 -> 0
|
||||
* [4] TE: 0 ->
|
||||
* [3] WADA: 0 -> 0
|
||||
* [2-0] TSEL: 0 -> 0
|
||||
*/
|
||||
write_register(0x1C, 0x00);
|
||||
|
||||
/*
|
||||
* Flag Register register:
|
||||
* [7] VBLF: 0 -> 0
|
||||
* [6] 0: 0 ->
|
||||
* [5] UF: 0 ->
|
||||
* [4] TF: 0 ->
|
||||
* [3] AF: 0 -> 0
|
||||
* [2] RSF: 0 -> 0
|
||||
* [1] VLF: 0 -> 0
|
||||
* [0] VBFF: 0 -> 0
|
||||
*/
|
||||
write_register(0x1D, 0x00);
|
||||
|
||||
/*
|
||||
* Control Register0 register:
|
||||
* [7] TEST: 0 -> 0
|
||||
* [6] STOP: 0 ->
|
||||
* [5] UIE: 0 ->
|
||||
* [4] TIE: 0 ->
|
||||
* [3] AIE: 0 -> 0
|
||||
* [2] TSTP: 0 -> 0
|
||||
* [1] TBKON: 0 -> 0
|
||||
* [0] TBKE: 0 -> 0
|
||||
*/
|
||||
write_register(0x1E, 0x00);
|
||||
|
||||
/*
|
||||
* Control Register1 register:
|
||||
* [7-6] SMPTSEL: 0 -> 0
|
||||
* [5] CHGEN: 0 ->
|
||||
* [4] INIEN: 0 ->
|
||||
* [3] 0: 0 ->
|
||||
* [2] RSVSEL: 0 -> 0
|
||||
* [1-0] BFVSEL: 0 -> 0
|
||||
*/
|
||||
write_register(0x1F, 0x00);
|
||||
|
||||
this->stop(false); // clear STOP bit
|
||||
|
||||
/*
|
||||
* Function register:
|
||||
* [7] 100TH: 0 -> disabled
|
||||
* [6:5] Periodic interrupt: 0 -> no periodic interrupt
|
||||
* [4] RTCM: 0 -> real-time clock mode
|
||||
* [3] STOPM: 0 -> RTC stop is controlled by STOP bit only
|
||||
* [2:0] Clock output frequency: 000 (Default value)
|
||||
*/
|
||||
write_register(0x28, 0x00);
|
||||
|
||||
// Battery switch register
|
||||
write_register(0x26, 0x00); // enable battery switch feature
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RTC_RX8130CE::setTime(struct tm *t) {
|
||||
uint8_t buf[8];
|
||||
buf[0] = 0x10;
|
||||
buf[1] = bin2bcd(t->tm_sec) & 0x7F;
|
||||
buf[2] = bin2bcd(t->tm_min) & 0x7F;
|
||||
buf[3] = bin2bcd(t->tm_hour) & 0x3F;
|
||||
buf[4] = bin2bcd(t->tm_wday) & 0x07;
|
||||
buf[5] = bin2bcd(t->tm_mday) & 0x3F;
|
||||
buf[6] = bin2bcd(t->tm_mon + 1) & 0x1F;
|
||||
buf[7] = bin2bcd((t->tm_year - 100));
|
||||
|
||||
this->stop(true);
|
||||
i2c_dev->write(buf, sizeof(buf));
|
||||
this->stop(false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void RTC_RX8130CE::adjust(DateTime dt) {
|
||||
struct tm *atv;
|
||||
time_t utime;
|
||||
|
||||
utime = (time_t)dt.unixtime();
|
||||
atv = gmtime(&utime);
|
||||
|
||||
this->setTime(atv);
|
||||
}
|
||||
|
||||
DateTime RTC_RX8130CE::now() {
|
||||
struct tm atv;
|
||||
this->getTime(&atv);
|
||||
|
||||
return DateTime((uint32_t)mktime(&atv));
|
||||
}
|
||||
|
||||
uint32_t RTC_RX8130CE::unixtime() {
|
||||
struct tm atv;
|
||||
this->getTime(&atv);
|
||||
|
||||
return (uint32_t)mktime(&atv);
|
||||
}
|
||||
|
||||
bool RTC_RX8130CE::getTime(struct tm *t) {
|
||||
uint8_t buff[7];
|
||||
|
||||
buff[0] = 0x10;
|
||||
|
||||
i2c_dev->write_then_read(buff, 1, buff, 7);
|
||||
|
||||
t->tm_sec = bcd2bin(buff[0] & 0x7F);
|
||||
t->tm_min = bcd2bin(buff[1] & 0x7F);
|
||||
t->tm_hour = bcd2bin(buff[2] & 0x3F);
|
||||
t->tm_wday = bcd2bin(buff[3] & 0x07);
|
||||
t->tm_mday = bcd2bin(buff[4] & 0x3F);
|
||||
t->tm_mon = bcd2bin(buff[5] & 0x1F) - 1;
|
||||
t->tm_year = bcd2bin(buff[6]) + 100;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RTC_RX8130CE::writeRAM(uint8_t address, uint8_t value) {
|
||||
return this->writeRAM(address, &value, 1);
|
||||
}
|
||||
|
||||
size_t RTC_RX8130CE::writeRAM(uint8_t address, uint8_t *value, size_t len) {
|
||||
uint8_t buf[len + 1];
|
||||
|
||||
if (address > 3) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ((address + len) > 3) {
|
||||
len = 3 - address;
|
||||
}
|
||||
|
||||
buf[0] = 0x20 + address;
|
||||
|
||||
for (int i = 1; i <= len + 1; i++) {
|
||||
buf[i] = value[i - 1];
|
||||
}
|
||||
|
||||
i2c_dev->write(buf, len + 1);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
bool RTC_RX8130CE::readRAM(uint8_t address, uint8_t *value, size_t len) {
|
||||
uint8_t real_address = 0x20 + address;
|
||||
|
||||
if (address > 3) { // Oversize of 64-bytes RAM
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((address + len) > 3) { // Data size over RAM size
|
||||
len = 3 - address;
|
||||
}
|
||||
|
||||
i2c_dev->write_then_read(&real_address, 1, value, len);
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t RTC_RX8130CE::readRAM(uint8_t address) {
|
||||
uint8_t value = 0xFF;
|
||||
this->readRAM(address, &value, 1);
|
||||
return value;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
#ifndef __RTC_RX8130CE_H__
|
||||
#define __RTC_RX8130CE_H__
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
#include <time.h>
|
||||
#include "RTClib.h"
|
||||
|
||||
class RTC_RX8130CE : RTC_I2C {
|
||||
private:
|
||||
const uint8_t _addr = 0x32;
|
||||
|
||||
bool stop(bool stop);
|
||||
|
||||
protected:
|
||||
|
||||
public:
|
||||
bool begin(TwoWire *wire);
|
||||
bool setTime(struct tm *t);
|
||||
bool getTime(struct tm *t);
|
||||
void adjust(DateTime t);
|
||||
|
||||
DateTime now();
|
||||
uint32_t unixtime();
|
||||
|
||||
bool writeRAM(uint8_t address, uint8_t value);
|
||||
size_t writeRAM(uint8_t address, uint8_t *value, size_t len);
|
||||
bool readRAM(uint8_t address, uint8_t *value, size_t len);
|
||||
uint8_t readRAM(uint8_t address);
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
+34
-17
@@ -42,7 +42,8 @@ private:
|
||||
|
||||
|
||||
RegionMap::RegionMap(TransportKeyStore& store) : _store(&store) {
|
||||
next_id = 1; num_regions = 0; home_id = 0;
|
||||
next_id = 1; num_regions = 0;
|
||||
default_id = home_id = 0;
|
||||
wildcard.id = wildcard.parent = 0;
|
||||
wildcard.flags = 0; // default behaviour, allow flood and direct
|
||||
strcpy(wildcard.name, "*");
|
||||
@@ -79,9 +80,11 @@ bool RegionMap::load(FILESYSTEM* _fs, const char* path) {
|
||||
if (file) {
|
||||
uint8_t pad[128];
|
||||
|
||||
num_regions = 0; next_id = 1; home_id = 0;
|
||||
num_regions = 0; next_id = 1;
|
||||
default_id = home_id = 0;
|
||||
|
||||
bool success = file.read(pad, 5) == 5; // reserved header
|
||||
bool success = file.read(pad, 3) == 3; // reserved header
|
||||
success = success && file.read((uint8_t *) &default_id, sizeof(default_id)) == sizeof(default_id);
|
||||
success = success && file.read((uint8_t *) &home_id, sizeof(home_id)) == sizeof(home_id);
|
||||
success = success && file.read((uint8_t *) &wildcard.flags, sizeof(wildcard.flags)) == sizeof(wildcard.flags);
|
||||
success = success && file.read((uint8_t *) &next_id, sizeof(next_id)) == sizeof(next_id);
|
||||
@@ -117,7 +120,8 @@ bool RegionMap::save(FILESYSTEM* _fs, const char* path) {
|
||||
uint8_t pad[128];
|
||||
memset(pad, 0, sizeof(pad));
|
||||
|
||||
bool success = file.write(pad, 5) == 5; // reserved header
|
||||
bool success = file.write(pad, 3) == 3; // reserved header
|
||||
success = success && file.write((uint8_t *) &default_id, sizeof(default_id)) == sizeof(default_id);
|
||||
success = success && file.write((uint8_t *) &home_id, sizeof(home_id)) == sizeof(home_id);
|
||||
success = success && file.write((uint8_t *) &wildcard.flags, sizeof(wildcard.flags)) == sizeof(wildcard.flags);
|
||||
success = success && file.write((uint8_t *) &next_id, sizeof(next_id)) == sizeof(next_id);
|
||||
@@ -164,24 +168,29 @@ RegionEntry* RegionMap::putRegion(const char* name, uint16_t parent_id, uint16_t
|
||||
return region;
|
||||
}
|
||||
|
||||
int RegionMap::getTransportKeysFor(const RegionEntry& src, TransportKey dest[], int max_num) {
|
||||
int num;
|
||||
if (src.name[0] == '$') { // private region
|
||||
num = _store->loadKeysFor(src.id, dest, max_num);
|
||||
} else if (src.name[0] == '#') { // auto hashtag region
|
||||
_store->getAutoKeyFor(src.id, src.name, dest[0]);
|
||||
num = 1;
|
||||
} else { // new: implicit auto hashtag region
|
||||
char tmp[sizeof(src.name)+1];
|
||||
tmp[0] = '#';
|
||||
strcpy(&tmp[1], src.name);
|
||||
_store->getAutoKeyFor(src.id, tmp, dest[0]);
|
||||
num = 1;
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
RegionEntry* RegionMap::findMatch(mesh::Packet* packet, uint8_t mask) {
|
||||
for (int i = 0; i < num_regions; i++) {
|
||||
auto region = ®ions[i];
|
||||
if ((region->flags & mask) == 0) { // does region allow this? (per 'mask' param)
|
||||
TransportKey keys[4];
|
||||
int num;
|
||||
if (region->name[0] == '$') { // private region
|
||||
num = _store->loadKeysFor(region->id, keys, 4);
|
||||
} else if (region->name[0] == '#') { // auto hashtag region
|
||||
_store->getAutoKeyFor(region->id, region->name, keys[0]);
|
||||
num = 1;
|
||||
} else { // new: implicit auto hashtag region
|
||||
char tmp[sizeof(region->name)];
|
||||
tmp[0] = '#';
|
||||
strcpy(&tmp[1], region->name);
|
||||
_store->getAutoKeyFor(region->id, tmp, keys[0]);
|
||||
num = 1;
|
||||
}
|
||||
int num = getTransportKeysFor(*region, keys, 4);
|
||||
for (int j = 0; j < num; j++) {
|
||||
uint16_t code = keys[j].calcTransportCode(packet);
|
||||
if (packet->transport_codes[0] == code) { // a match!!
|
||||
@@ -237,6 +246,14 @@ void RegionMap::setHomeRegion(const RegionEntry* home) {
|
||||
home_id = home ? home->id : 0;
|
||||
}
|
||||
|
||||
RegionEntry* RegionMap::getDefaultRegion() {
|
||||
return default_id == 0 ? NULL : findById(default_id);
|
||||
}
|
||||
|
||||
void RegionMap::setDefaultRegion(const RegionEntry* def) {
|
||||
default_id = def ? def->id : 0;
|
||||
}
|
||||
|
||||
bool RegionMap::removeRegion(const RegionEntry& region) {
|
||||
if (region.id == 0) return false; // failed (cannot remove the wildcard Region)
|
||||
|
||||
|
||||
@@ -16,11 +16,13 @@ struct RegionEntry {
|
||||
uint16_t parent;
|
||||
uint8_t flags;
|
||||
char name[31];
|
||||
|
||||
bool isWildcard() const { return id == 0; }
|
||||
};
|
||||
|
||||
class RegionMap {
|
||||
TransportKeyStore* _store;
|
||||
uint16_t next_id, home_id;
|
||||
uint16_t next_id, home_id, default_id;
|
||||
uint16_t num_regions;
|
||||
RegionEntry regions[MAX_REGION_ENTRIES];
|
||||
RegionEntry wildcard;
|
||||
@@ -43,6 +45,8 @@ public:
|
||||
RegionEntry* findById(uint16_t id);
|
||||
RegionEntry* getHomeRegion(); // NOTE: can be NULL
|
||||
void setHomeRegion(const RegionEntry* home);
|
||||
RegionEntry* getDefaultRegion(); // NOTE: can be NULL
|
||||
void setDefaultRegion(const RegionEntry* def);
|
||||
bool removeRegion(const RegionEntry& region);
|
||||
bool clear();
|
||||
void resetFrom(const RegionMap& src) { num_regions = 0; next_id = src.next_id; }
|
||||
@@ -50,6 +54,7 @@ public:
|
||||
const RegionEntry* getByIdx(int i) const { return ®ions[i]; }
|
||||
const RegionEntry* getRoot() const { return &wildcard; }
|
||||
int exportNamesTo(char *dest, int max_len, uint8_t mask, bool invert = false);
|
||||
int getTransportKeysFor(const RegionEntry& src, TransportKey dest[], int max_num);
|
||||
|
||||
void exportTo(Stream& out) const;
|
||||
size_t exportTo(char *dest, size_t max_len) const;
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define TXT_TYPE_PLAIN 0 // a plain text message
|
||||
#define TXT_TYPE_CLI_DATA 1 // a CLI command
|
||||
#define TXT_TYPE_SIGNED_PLAIN 2 // plain text, signed by sender
|
||||
#define TXT_TYPE_PLAIN 0 // a plain text message
|
||||
#define TXT_TYPE_CLI_DATA 1 // a CLI command
|
||||
#define TXT_TYPE_SIGNED_PLAIN 2 // plain text, signed by sender
|
||||
#define DATA_TYPE_RESERVED 0x0000 // reserved for future use
|
||||
#define DATA_TYPE_DEV 0xFFFF // developer namespace for experimenting with group/channel datagrams and building apps
|
||||
|
||||
class StrHelper {
|
||||
public:
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
|
||||
class ESPNOWRadio : public mesh::Radio {
|
||||
protected:
|
||||
uint32_t n_recv, n_sent;
|
||||
uint32_t n_recv, n_sent, n_recv_errors;
|
||||
|
||||
public:
|
||||
ESPNOWRadio() { n_recv = n_sent = 0; }
|
||||
ESPNOWRadio() { n_recv = n_sent = n_recv_errors = 0; }
|
||||
|
||||
void init();
|
||||
int recvRaw(uint8_t* bytes, int sz) override;
|
||||
@@ -19,12 +19,21 @@ public:
|
||||
|
||||
uint32_t getPacketsRecv() const { return n_recv; }
|
||||
uint32_t getPacketsSent() const { return n_sent; }
|
||||
void resetStats() { n_recv = n_sent = 0; }
|
||||
uint32_t getPacketsRecvErrors() const { return n_recv_errors; }
|
||||
void resetStats() { n_recv = n_sent = n_recv_errors = 0; }
|
||||
|
||||
virtual float getLastRSSI() const override;
|
||||
virtual float getLastSNR() const override;
|
||||
|
||||
float packetScore(float snr, int packet_len) override { return 0; }
|
||||
|
||||
/**
|
||||
* These two functions do nothing for ESP-NOW, but are needed for the
|
||||
* Radio interface.
|
||||
*/
|
||||
virtual void setRxBoostedGainMode(bool) { }
|
||||
virtual bool getRxBoostedGainMode() const { return false; }
|
||||
|
||||
uint32_t intID();
|
||||
void setTxPower(uint8_t dbm);
|
||||
};
|
||||
|
||||
@@ -181,6 +181,13 @@ void SerialBLEInterface::begin(const char* prefix, char* name, uint32_t pin_code
|
||||
bleuart.begin();
|
||||
bleuart.setRxCallback(onBleUartRX);
|
||||
|
||||
|
||||
|
||||
// Register DFU on the main BLE stack so paired clients can discover it
|
||||
// without switching the device into a separate OTA-only BLE mode first.
|
||||
bledfu.setPermission(SECMODE_ENC_WITH_MITM, SECMODE_ENC_WITH_MITM);
|
||||
bledfu.begin();
|
||||
|
||||
Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
|
||||
Bluefruit.Advertising.addTxPower();
|
||||
Bluefruit.Advertising.addService(bleuart);
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#endif
|
||||
|
||||
class SerialBLEInterface : public BaseSerialInterface {
|
||||
BLEDfu bledfu;
|
||||
BLEUart bleuart;
|
||||
bool _isEnabled;
|
||||
bool _isDeviceConnected;
|
||||
|
||||
@@ -83,4 +83,10 @@ class CustomLLCC68 : public LLCC68 {
|
||||
bool detected = (irq & SX126X_IRQ_HEADER_VALID) || (irq & SX126X_IRQ_PREAMBLE_DETECTED);
|
||||
return detected;
|
||||
}
|
||||
|
||||
bool getRxBoostedGainMode() {
|
||||
uint8_t rxGain = 0;
|
||||
readRegister(RADIOLIB_SX126X_REG_RX_GAIN, &rxGain, 1);
|
||||
return (rxGain == RADIOLIB_SX126X_RX_GAIN_BOOSTED);
|
||||
}
|
||||
};
|
||||
@@ -22,4 +22,11 @@ public:
|
||||
}
|
||||
|
||||
void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); }
|
||||
|
||||
void setRxBoostedGainMode(bool en) override {
|
||||
((CustomLLCC68 *)_radio)->setRxBoostedGainMode(en);
|
||||
}
|
||||
bool getRxBoostedGainMode() const override {
|
||||
return ((CustomLLCC68 *)_radio)->getRxBoostedGainMode();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include "MeshCore.h"
|
||||
|
||||
class CustomLR1110 : public LR1110 {
|
||||
bool _rx_boosted = false;
|
||||
|
||||
public:
|
||||
CustomLR1110(Module *mod) : LR1110(mod) { }
|
||||
|
||||
@@ -22,6 +24,13 @@ class CustomLR1110 : public LR1110 {
|
||||
|
||||
float getFreqMHz() const { return freqMHz; }
|
||||
|
||||
int16_t setRxBoostedGainMode(bool en) {
|
||||
_rx_boosted = en;
|
||||
return LR1110::setRxBoostedGainMode(en);
|
||||
}
|
||||
|
||||
bool getRxBoostedGainMode() const { return _rx_boosted; }
|
||||
|
||||
bool isReceiving() {
|
||||
uint16_t irq = getIrqStatus();
|
||||
bool detected = ((irq & RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID) || (irq & RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED));
|
||||
|
||||
@@ -24,5 +24,11 @@ public:
|
||||
|
||||
float getLastRSSI() const override { return ((CustomLR1110 *)_radio)->getRSSI(); }
|
||||
float getLastSNR() const override { return ((CustomLR1110 *)_radio)->getSNR(); }
|
||||
int16_t setRxBoostedGainMode(bool en) { return ((CustomLR1110 *)_radio)->setRxBoostedGainMode(en); };
|
||||
|
||||
void setRxBoostedGainMode(bool en) override {
|
||||
((CustomLR1110 *)_radio)->setRxBoostedGainMode(en);
|
||||
}
|
||||
bool getRxBoostedGainMode() const override {
|
||||
return ((CustomLR1110 *)_radio)->getRxBoostedGainMode();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include <RadioLib.h>
|
||||
|
||||
#define SX126X_IRQ_HEADER_VALID 0b0000010000 // 4 4 valid LoRa header received
|
||||
#define SX126X_IRQ_HEADER_VALID 0b0000010000 // 4 4 valid LoRa header received
|
||||
#define SX126X_IRQ_PREAMBLE_DETECTED 0x04
|
||||
|
||||
class CustomSX1262 : public SX1262 {
|
||||
@@ -91,4 +91,10 @@ class CustomSX1262 : public SX1262 {
|
||||
bool detected = (irq & SX126X_IRQ_HEADER_VALID) || (irq & SX126X_IRQ_PREAMBLE_DETECTED);
|
||||
return detected;
|
||||
}
|
||||
|
||||
bool getRxBoostedGainMode() {
|
||||
uint8_t rxGain = 0;
|
||||
readRegister(RADIOLIB_SX126X_REG_RX_GAIN, &rxGain, 1);
|
||||
return (rxGain == RADIOLIB_SX126X_RX_GAIN_BOOSTED);
|
||||
}
|
||||
};
|
||||
@@ -4,6 +4,10 @@
|
||||
#include "RadioLibWrappers.h"
|
||||
#include "SX126xReset.h"
|
||||
|
||||
#ifndef USE_SX1262
|
||||
#define USE_SX1262
|
||||
#endif
|
||||
|
||||
class CustomSX1262Wrapper : public RadioLibWrapper {
|
||||
public:
|
||||
CustomSX1262Wrapper(CustomSX1262& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { }
|
||||
@@ -25,4 +29,11 @@ public:
|
||||
}
|
||||
|
||||
void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); }
|
||||
|
||||
void setRxBoostedGainMode(bool en) override {
|
||||
((CustomSX1262 *)_radio)->setRxBoostedGainMode(en);
|
||||
}
|
||||
bool getRxBoostedGainMode() const override {
|
||||
return ((CustomSX1262 *)_radio)->getRxBoostedGainMode();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include <RadioLib.h>
|
||||
|
||||
#define SX126X_IRQ_HEADER_VALID 0b0000010000 // 4 4 valid LoRa header received
|
||||
#define SX126X_IRQ_HEADER_VALID 0b0000010000 // 4 4 valid LoRa header received
|
||||
#define SX126X_IRQ_PREAMBLE_DETECTED 0x04
|
||||
|
||||
class CustomSX1268 : public SX1268 {
|
||||
@@ -83,4 +83,10 @@ class CustomSX1268 : public SX1268 {
|
||||
bool detected = (irq & SX126X_IRQ_HEADER_VALID) || (irq & SX126X_IRQ_PREAMBLE_DETECTED);
|
||||
return detected;
|
||||
}
|
||||
|
||||
bool getRxBoostedGainMode() {
|
||||
uint8_t rxGain = 0;
|
||||
readRegister(RADIOLIB_SX126X_REG_RX_GAIN, &rxGain, 1);
|
||||
return (rxGain == RADIOLIB_SX126X_RX_GAIN_BOOSTED);
|
||||
}
|
||||
};
|
||||
@@ -4,6 +4,10 @@
|
||||
#include "RadioLibWrappers.h"
|
||||
#include "SX126xReset.h"
|
||||
|
||||
#ifndef USE_SX1268
|
||||
#define USE_SX1268
|
||||
#endif
|
||||
|
||||
class CustomSX1268Wrapper : public RadioLibWrapper {
|
||||
public:
|
||||
CustomSX1268Wrapper(CustomSX1268& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { }
|
||||
@@ -22,4 +26,11 @@ public:
|
||||
}
|
||||
|
||||
void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); }
|
||||
|
||||
void setRxBoostedGainMode(bool en) override {
|
||||
((CustomSX1268 *)_radio)->setRxBoostedGainMode(en);
|
||||
}
|
||||
bool getRxBoostedGainMode() const override {
|
||||
return ((CustomSX1268 *)_radio)->getRxBoostedGainMode();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
#include "CustomSX1276.h"
|
||||
#include "RadioLibWrappers.h"
|
||||
|
||||
#ifndef USE_SX1276
|
||||
#define USE_SX1276
|
||||
#endif
|
||||
|
||||
class CustomSX1276Wrapper : public RadioLibWrapper {
|
||||
public:
|
||||
CustomSX1276Wrapper(CustomSX1276& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { }
|
||||
|
||||
@@ -54,6 +54,9 @@ public:
|
||||
virtual float getLastSNR() const override;
|
||||
|
||||
float packetScore(float snr, int packet_len) override { return packetScoreInt(snr, 10, packet_len); } // assume sf=10
|
||||
|
||||
virtual void setRxBoostedGainMode(bool) { }
|
||||
virtual bool getRxBoostedGainMode() const { return false; }
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#endif
|
||||
#define TELEM_BME680_SEALEVELPRESSURE_HPA (1013.25)
|
||||
#include <Adafruit_BME680.h>
|
||||
static Adafruit_BME680 BME680;
|
||||
static Adafruit_BME680 BME680(TELEM_WIRE);
|
||||
#endif
|
||||
|
||||
#ifdef ENV_INCLUDE_BMP085
|
||||
@@ -62,9 +62,15 @@ LPS22HBClass LPS22HB(*TELEM_WIRE);
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_INA3221
|
||||
#ifndef TELEM_INA3221_ADDRESS
|
||||
#define TELEM_INA3221_ADDRESS 0x42 // INA3221 3 channel current sensor I2C address
|
||||
#endif
|
||||
#ifndef TELEM_INA3221_SHUNT_VALUE
|
||||
#define TELEM_INA3221_SHUNT_VALUE 0.100 // most variants will have a 0.1 ohm shunts
|
||||
#endif
|
||||
#ifndef TELEM_INA3221_NUM_CHANNELS
|
||||
#define TELEM_INA3221_NUM_CHANNELS 3
|
||||
#endif
|
||||
#include <Adafruit_INA3221.h>
|
||||
static Adafruit_INA3221 INA3221;
|
||||
#endif
|
||||
@@ -101,6 +107,12 @@ static Adafruit_MLX90614 MLX90614;
|
||||
static Adafruit_VL53L0X VL53L0X;
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_RAK12035
|
||||
#define TELEM_RAK12035_ADDRESS 0x20 // RAK12035 Soil Moisture sensor I2C address
|
||||
#include "RAK12035_SoilMoisture.h"
|
||||
static RAK12035_SoilMoisture RAK12035;
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_GPS && defined(RAK_BOARD) && !defined(RAK_WISMESH_TAG)
|
||||
#define RAK_WISBLOCK_GPS
|
||||
#endif
|
||||
@@ -180,7 +192,7 @@ bool EnvironmentSensorManager::begin() {
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_BME680
|
||||
if (BME680.begin(TELEM_BME680_ADDRESS, TELEM_WIRE)) {
|
||||
if (BME680.begin(TELEM_BME680_ADDRESS)) {
|
||||
MESH_DEBUG_PRINTLN("Found BME680 at address: %02X", TELEM_BME680_ADDRESS);
|
||||
BME680_initialized = true;
|
||||
} else {
|
||||
@@ -331,6 +343,17 @@ bool EnvironmentSensorManager::begin() {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_RAK12035
|
||||
RAK12035.setup(*TELEM_WIRE);
|
||||
if (RAK12035.begin(TELEM_RAK12035_ADDRESS)) {
|
||||
MESH_DEBUG_PRINTLN("Found sensor RAK12035 at address: %02X", TELEM_RAK12035_ADDRESS);
|
||||
RAK12035_initialized = true;
|
||||
} else {
|
||||
RAK12035_initialized = false;
|
||||
MESH_DEBUG_PRINTLN("RAK12035 was not found at I2C address %02X", TELEM_RAK12035_ADDRESS);
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -483,8 +506,36 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
#if ENV_INCLUDE_RAK12035
|
||||
if (RAK12035_initialized) {
|
||||
|
||||
// RAK12035 Telemetry is Channel 2
|
||||
telemetry.addTemperature(2, RAK12035.get_sensor_temperature());
|
||||
telemetry.addPercentage(2, RAK12035.get_sensor_moisture());
|
||||
|
||||
// RAK12035 CALIBRATION Telemetry is Channel 3, if enabled
|
||||
|
||||
#ifdef ENABLE_RAK12035_CALIBRATION
|
||||
// Calibration Data Screen is Channel 3
|
||||
float cap = RAK12035.get_sensor_capacitance();
|
||||
float _wet = RAK12035.get_humidity_full();
|
||||
float _dry = RAK12035.get_humidity_zero();
|
||||
|
||||
telemetry.addFrequency(3, cap);
|
||||
telemetry.addTemperature(3, _wet);
|
||||
telemetry.addPower(3, _dry);
|
||||
|
||||
if(cap > _dry){
|
||||
RAK12035.set_humidity_zero(cap);
|
||||
}
|
||||
|
||||
if(cap < _wet){
|
||||
RAK12035.set_humidity_full(cap);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -672,7 +723,7 @@ bool EnvironmentSensorManager::gpsIsAwake(uint8_t ioPin){
|
||||
gps_detected = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
pinMode(ioPin, INPUT);
|
||||
MESH_DEBUG_PRINTLN("GPS did not init with this IO pin... try the next");
|
||||
return false;
|
||||
|
||||
@@ -22,6 +22,7 @@ protected:
|
||||
bool SHT4X_initialized = false;
|
||||
bool BME680_initialized = false;
|
||||
bool BMP085_initialized = false;
|
||||
bool RAK12035_initialized = false;
|
||||
|
||||
bool gps_detected = false;
|
||||
bool gps_active = false;
|
||||
|
||||
@@ -0,0 +1,554 @@
|
||||
/*----------------------------------------------------------------------*
|
||||
* RAK12035_SoilMoistureSensor.cpp - Arduino library for the Sensor *
|
||||
* version of I2C Soil Moisture Sensor version from Chrirp *
|
||||
* (https://github.com/Miceuz/i2c-moisture-sensor). *
|
||||
* *
|
||||
* Ingo Fischer 11Nov2015 *
|
||||
* https://github.com/Apollon77/I2CSoilMoistureSensor *
|
||||
* *
|
||||
* Ken Privitt 8Feb2026 *
|
||||
* Adapted for MeshCore Firmware Stack *
|
||||
* *
|
||||
* MIT license *
|
||||
* *
|
||||
* This file contains a collection of routines to access the *
|
||||
* RAK12035 Soil Moisture Sensor via I2C. The sensor provides *
|
||||
* Soil Temperature and capacitance-based Soil Moisture Readings. *
|
||||
* *
|
||||
*----------------------------------------------------------------------*/
|
||||
|
||||
#include "RAK12035_SoilMoisture.h"
|
||||
#include "MeshCore.h"
|
||||
#include <Wire.h>
|
||||
|
||||
/*----------------------------------------------------------------------*
|
||||
* Constructor. *
|
||||
*----------------------------------------------------------------------*/
|
||||
// RAK12035_SoilMoisture(uint8_t addr)
|
||||
//
|
||||
// Accepts the I2C Address to look for the RAK12035
|
||||
// Initializes the I2C to null (will be setup later in Wire.Begin()
|
||||
//
|
||||
// No hardware is touched in the constructor.
|
||||
// I2C communication is deferred until begin() is called.
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
RAK12035_SoilMoisture::RAK12035_SoilMoisture(uint8_t addr)
|
||||
{
|
||||
_addr = addr; // Save the sensor's I2C address
|
||||
_i2c = nullptr; // Bus not assigned yet; must be set in begin()
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// setup()
|
||||
//------------------------------------------------------------------------------
|
||||
// setup(TwoWire &i2c)
|
||||
//
|
||||
// Assigns the I2C bus that this driver instance will use. This allows the
|
||||
// application to choose between Wire, Wire1, or any other TwoWire instance
|
||||
// supported by the platform.
|
||||
//
|
||||
// No I2C communication occurs here; setup() simply stores the pointer so that
|
||||
// begin() and all register‑level operations know which bus to use.
|
||||
//------------------------------------------------------------------------------
|
||||
void RAK12035_SoilMoisture::setup(TwoWire &i2c)
|
||||
|
||||
{
|
||||
_i2c = &i2c; // assigns the bus pointer
|
||||
_i2c->begin(); // Initialize the bus to Wire or Wire1
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// RAK12035 Soil Moisture begin()
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Performs initialization of the RAK12035 soil‑moisture sensor. This
|
||||
// routine assumes that the application has already selected the I2C bus via
|
||||
// setup() and that the bus has been initialized externally (Wire.begin()).
|
||||
// It uses the passed in I2C Address (default 0x20)
|
||||
//
|
||||
// *** This code does not supprt three sensors ***
|
||||
// The RAK12023 has three connectors, but each of the sensors attached must
|
||||
// all have a different I2C addresses.
|
||||
// This code has a function to set the I2C adress of a sensor
|
||||
// and currently only supports one address 0x20 (the default).
|
||||
// To support three sensors, EnvironmentSensorManager would need to be modified
|
||||
// to support multiple instances of the RAK12035_SoilMoisture class,
|
||||
// each with a different address. (0x20, 0x21, 0x22)
|
||||
// The begin() function would need to be modified to loop through the three addresses
|
||||
//
|
||||
// DEBUG STATEMENTS: Can be enabled by uncommenting or adding:
|
||||
// File: varients/rak4631 platformio.ini
|
||||
// Section example: [env:RAK_4631_companion_radio_ble]
|
||||
// Enable Debug statements: -D MESH_DEBUG=1
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
bool RAK12035_SoilMoisture::begin(uint8_t addr)
|
||||
{
|
||||
// MESH_DEBUG_PRINTLN("begin() - Start of RAK12035 initialization");
|
||||
// MESH_DEBUG_PRINTLN("begin() - RAK12035 passed in Address %02X", addr);
|
||||
|
||||
// 1. Ensure setup() was called
|
||||
if (_i2c == nullptr) {
|
||||
MESH_DEBUG_PRINTLN("RAK12035 ERROR: I2C bus not set!");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t _dry_cal = 200;
|
||||
uint16_t _wet_cal = 600;
|
||||
uint8_t _version = 0;
|
||||
uint8_t _addr; // The I2C address to be used (passed in parameter)
|
||||
|
||||
/*------------------------------------------------------------------------------------------
|
||||
* Set Calibration values - This is done with custom a firmware version
|
||||
*
|
||||
* USE the Build Flag: -D ENABLE_RAK12035_CALIBRATION = 1
|
||||
* OR
|
||||
* Change the value to 1 in the RAK12035_SoilMoisture.h file
|
||||
*
|
||||
* Calibration Procedure:
|
||||
* 1) Flash the the Calibration version of the firmware.
|
||||
* 2) Leave the sensor dry, power up the device.
|
||||
* 3) After detecting the RAK12035 this firmware will display calibration data on Channel 3
|
||||
*
|
||||
* Frequency = Current Capacitance Value
|
||||
* Temperature = Current Wet calibration value
|
||||
* Power = Current Dry calibration value
|
||||
*
|
||||
* 4) Click refresh several times. This will take a capacitance reading and if it is
|
||||
* greater than the current Dry value it will store it in the sensor
|
||||
* The value will bounce a little as you click refresh, but it eventually settles down (a few clicks)
|
||||
* the stored value will stabalize at it's Maximum value.
|
||||
*
|
||||
* 5) Put the sensor in water.
|
||||
*
|
||||
* 6) Click refresh several times. This will take a capacitance reading and if it is
|
||||
* less than the current Wet value it will store it in the sensor
|
||||
* The value will bounce a little as you click refresh, but it eventually settles down (a few clicks)
|
||||
* the stored value will stabalize at it's Minimum value.
|
||||
*
|
||||
* 7) The Sensor is now calibrated, turn off the device.
|
||||
*
|
||||
* 8) Reflash the device with the non-Calibration Firmware, Data will be shown on Channel 2
|
||||
*
|
||||
*------------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#if ENABLE_RAK12035_CALIBRATION
|
||||
uint16_t _wet = 2000; // A high value the should be out of the normal Wet range
|
||||
set_humidity_full(_wet);
|
||||
|
||||
uint16_t _dry = 50; // A low value the should be out of the normal Dry range
|
||||
set_humidity_zero(_dry);
|
||||
#endif
|
||||
|
||||
/*--------------------------------------------------------------------------------
|
||||
*
|
||||
* Check if a sensor is present and return true if found, false if not present
|
||||
*
|
||||
*--------------------------------------------------------------------------------
|
||||
*/
|
||||
if (query_sensor()) {
|
||||
MESH_DEBUG_PRINTLN("begin() - Sensor responded with valid version");
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
MESH_DEBUG_PRINTLN("begin() - Sensor version FAIL");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------------
|
||||
*
|
||||
* Below are all the routines to execute the various I2C commands supported
|
||||
* by the RAK12035 sensor
|
||||
*
|
||||
*--------------------------------------------------------------------------------*/
|
||||
|
||||
uint16_t RAK12035_SoilMoisture::get_sensor_capacitance() //Command 01 - (r) 2 byte
|
||||
{
|
||||
uint8_t buf[2] = {0};
|
||||
if (!read_rak12035(SOILMOISTURESENSOR_GET_CAPACITANCE, buf, 2)) {
|
||||
MESH_DEBUG_PRINTLN("Function 1: get_capacitance() FAIL: Bad data returned = %02X %02X", buf[0], buf[1]);
|
||||
return (buf[0] << 8) | buf[1]; // return raw for debugging
|
||||
}
|
||||
uint16_t cap = (buf[0] << 8) | buf[1];
|
||||
MESH_DEBUG_PRINTLN("Function 1: get_capacitance() SUCCESS: Capacitance = %d", cap);
|
||||
return cap;
|
||||
}
|
||||
|
||||
|
||||
uint8_t RAK12035_SoilMoisture::get_I2C_address() //Command 02 - (r) 1 byte
|
||||
{
|
||||
uint8_t addr = 0;
|
||||
if (!read_rak12035(SOILMOISTURESENSOR_GET_I2C_ADDR, &addr, 1)) {
|
||||
MESH_DEBUG_PRINTLN("Function 2: get_I2C_address() FAIL: Bad data returned = %02X", addr);
|
||||
return addr; // return raw for debugging
|
||||
}
|
||||
MESH_DEBUG_PRINTLN("Function 2: get_I2C_address() SUCCESS: I2C Address = %02X", addr);
|
||||
return addr;
|
||||
}
|
||||
|
||||
|
||||
bool RAK12035_SoilMoisture::set_sensor_addr(uint8_t addr) //Command 03 - (w) 1 byte
|
||||
{
|
||||
if (!write_rak12035(SOILMOISTURESENSOR_SET_I2C_ADDR, &addr, 1)) {
|
||||
MESH_DEBUG_PRINTLN("Function 3: set_I2C_address() FAIL: Could not set new address %02X", addr);
|
||||
return false;
|
||||
}
|
||||
MESH_DEBUG_PRINTLN("Function 3: set_I2C_address() SUCCESS: New address = %02X", addr);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
uint8_t RAK12035_SoilMoisture::get_sensor_version() // Command 04 - 1 byte
|
||||
{
|
||||
uint8_t v = 0;
|
||||
|
||||
read_rak12035(SOILMOISTURESENSOR_GET_VERSION, &v, 1);
|
||||
if (!read_rak12035(SOILMOISTURESENSOR_GET_VERSION, &v, 1)) {
|
||||
MESH_DEBUG_PRINTLN("Function 4: get_sensor_version() FAIL: Bad data returned = %02X", v);
|
||||
return v;
|
||||
}
|
||||
MESH_DEBUG_PRINTLN("Function 4: get_sensor_version() SUCCESS: Version = %02X", v);
|
||||
return v;
|
||||
}
|
||||
|
||||
|
||||
float RAK12035_SoilMoisture::get_sensor_temperature() //Command 05 - (r) 2 bytes
|
||||
{
|
||||
uint8_t buf[2] = {0};
|
||||
if (!read_rak12035(SOILMOISTURESENSOR_GET_TEMPERATURE, buf, 2)) {
|
||||
MESH_DEBUG_PRINTLN("Function 5: get_temperature() FAIL: Bad data returned = %02X %02X", buf[0], buf[1]);
|
||||
return (buf[0] << 8) | buf[1]; // raw data returned for debugging 0XFFFF is error
|
||||
}
|
||||
// Sensor returns a 16-bit signed integer (°C * 10)
|
||||
int16_t raw = (buf[0] << 8) | buf[1];
|
||||
float tempC = raw / 10.0f;
|
||||
MESH_DEBUG_PRINTLN("Function 5: get_temperature() SUCCESS: Raw=%04X Temp=%.1f C", raw, tempC);
|
||||
return tempC;
|
||||
}
|
||||
|
||||
|
||||
bool RAK12035_SoilMoisture::sensor_sleep() //Command 06 - (w) 1 byte
|
||||
{
|
||||
uint8_t tmp = 0;
|
||||
if (!write_rak12035(SOILMOISTURESENSOR_SET_SLEEP, &tmp, 1)) {
|
||||
MESH_DEBUG_PRINTLN("Function 6: sensor_sleep() FAIL: Could not send sleep command");
|
||||
return false;
|
||||
}
|
||||
MESH_DEBUG_PRINTLN("Function 6: sensor_sleep() SUCCESS: Sensor acknowledged sleep command");
|
||||
|
||||
// Optional: turn off sensor power AFTER successful sleep command
|
||||
|
||||
// This has been commented out due to a pin name conflict with the Heltec v3
|
||||
// This will need to be resolved if this funstion is to be utilized in the future
|
||||
/*
|
||||
digitalWrite(WB_IO2, LOW);
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool RAK12035_SoilMoisture::set_humidity_full(uint16_t full) //Command 07 - (w) 2 bytes
|
||||
{
|
||||
uint8_t buf[2];
|
||||
buf[0] = (full >> 8) & 0xFF; // High byte
|
||||
buf[1] = full & 0xFF; // Low byte
|
||||
|
||||
if (!write_rak12035(SOILMOISTURESENSOR_SET_WET_CAL, buf, 2)) {
|
||||
MESH_DEBUG_PRINTLN("Function 7: set_humidity_full() FAIL: Could not set wet calibration value"
|
||||
);
|
||||
return false;
|
||||
}
|
||||
MESH_DEBUG_PRINTLN("Function 7: set_humidity_full() SUCCESS: New Full = %04X", full);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool RAK12035_SoilMoisture::set_humidity_zero(uint16_t zero) //Command 08 - (w) 2 bytes
|
||||
{
|
||||
uint8_t buf[2];
|
||||
buf[0] = (zero >> 8) & 0xFF; // High byte
|
||||
buf[1] = zero & 0xFF; // Low byte
|
||||
|
||||
if (!write_rak12035(SOILMOISTURESENSOR_SET_DRY_CAL, buf, 2)) {
|
||||
MESH_DEBUG_PRINTLN("Function 8: set_humidity_zero() FAIL: Could not set dry calibration value");
|
||||
return false;
|
||||
}
|
||||
MESH_DEBUG_PRINTLN("Function 8: set_humidity_zero() SUCCESS: New Zero = %04X", zero);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
uint8_t RAK12035_SoilMoisture::get_sensor_moisture() //Command 09 - (r) 1 byte
|
||||
{
|
||||
// Load calibration values from sensor
|
||||
_wet_cal = get_humidity_full();
|
||||
_dry_cal = get_humidity_zero();
|
||||
|
||||
MESH_DEBUG_PRINTLN("Function 9: get_moisture() - Read from sensor or calculate from capacitance");
|
||||
|
||||
// Read sensor version
|
||||
uint8_t v = get_sensor_version();
|
||||
|
||||
// If version > 2, read moisture directly from the sensor
|
||||
if (v > 2) {
|
||||
MESH_DEBUG_PRINTLN("Version > 02 - Reading moisture directly from sensor");
|
||||
uint8_t moisture = get_sensor_humid();
|
||||
MESH_DEBUG_PRINTLN("get_moisture() Direct Read = %d%%", moisture);
|
||||
return moisture;
|
||||
}
|
||||
// Otherwise calculate moisture from capacitance
|
||||
MESH_DEBUG_PRINTLN("Calculating moisture from capacitance");
|
||||
|
||||
uint16_t cap = get_sensor_capacitance();
|
||||
|
||||
// Clamp capacitance between calibration points
|
||||
if (_dry_cal < _wet_cal) {
|
||||
if (cap <= _dry_cal) cap = _dry_cal;
|
||||
if (cap >= _wet_cal) cap = _wet_cal;
|
||||
|
||||
float pct = (_wet_cal - cap) * 100.0f / (_wet_cal - _dry_cal);
|
||||
if (pct > 100.0f) pct = 100.0f;
|
||||
|
||||
MESH_DEBUG_PRINTLN("get_moisture Case 1() Calculated = %d%%", (uint8_t)pct);
|
||||
return (uint8_t)pct;
|
||||
} else {
|
||||
if (cap >= _dry_cal) cap = _dry_cal;
|
||||
if (cap <= _wet_cal) cap = _wet_cal;
|
||||
|
||||
float pct = (_dry_cal - cap) * 100.0f / (_dry_cal - _wet_cal);
|
||||
if (pct > 100.0f) pct = 100.0f;
|
||||
|
||||
MESH_DEBUG_PRINTLN("get_moisture Case 2() Calculated = %d%%", (uint8_t)pct);
|
||||
return (uint8_t)pct;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
uint8_t RAK12035_SoilMoisture::get_sensor_humid() //Command 09 - (r) 1 byte
|
||||
{
|
||||
uint8_t moisture = 0;
|
||||
|
||||
if (!read_rak12035(SOILMOISTURESENSOR_GET_MOISTURE, &moisture, 1)) {
|
||||
MESH_DEBUG_PRINTLN("Function 9: get_sensor_humid() FAIL: Bad data returned = %02X", moisture);
|
||||
return moisture; // raw fallback
|
||||
}
|
||||
MESH_DEBUG_PRINTLN("Function 9: get_sensor_humid() SUCCESS: Moisture = %d%%",moisture);
|
||||
return moisture;
|
||||
}
|
||||
|
||||
|
||||
uint16_t RAK12035_SoilMoisture::get_humidity_full() //Command 0A - (r) 2 bytes
|
||||
{
|
||||
uint8_t buf[2] = {0};
|
||||
|
||||
if (!read_rak12035(SOILMOISTURESENSOR_GET_WET_CAL, buf, 2)) {
|
||||
MESH_DEBUG_PRINTLN("Function A: get_humidity_full() FAIL: Bad data returned = %02X%02X", buf[0], buf[1]);
|
||||
return 0xFFFF; // error indicator
|
||||
}
|
||||
|
||||
uint16_t full = (buf[0] << 8) | buf[1];
|
||||
|
||||
MESH_DEBUG_PRINTLN("Function A: get_humidity_full() SUCCESS: Full = %04X = %d", full, full);
|
||||
return full;
|
||||
}
|
||||
|
||||
|
||||
uint16_t RAK12035_SoilMoisture::get_humidity_zero() //Command 0B - 2 bytes
|
||||
{
|
||||
uint8_t buf[2] = {0};
|
||||
|
||||
if (!read_rak12035(SOILMOISTURESENSOR_GET_DRY_CAL, buf, 2)) {
|
||||
MESH_DEBUG_PRINTLN("Function B: get_humidity_zero() FAIL: Bad data returned = %02X%02X", buf[0], buf[1]);
|
||||
return 0xFFFF; // error indicator
|
||||
}
|
||||
|
||||
uint16_t zero = (buf[0] << 8) | buf[1];
|
||||
|
||||
MESH_DEBUG_PRINTLN("Function B: get_humidity_zero() SUCCESS: Zero = %04X = %d", zero, zero);
|
||||
return zero;
|
||||
}
|
||||
|
||||
|
||||
/*------------------------------------------------------------------------------------------*
|
||||
* getEvent() - High-level function to read both moisture and temperature in one call. *
|
||||
*------------------------------------------------------------------------------------------*
|
||||
* This function reads the moisture percentage and temperature from the sensor and returns *
|
||||
* them via output parameters. This may be used for the telemerty delivery in the MeshCore *
|
||||
* firmware, with a single function to get all sensor data. *
|
||||
* *
|
||||
* The function returns true if both readings were successfully obtained, or false if any *
|
||||
* error occurred during I2C communication. *
|
||||
* *
|
||||
* This function is currently not used *
|
||||
*------------------------------------------------------------------------------------------*/
|
||||
bool RAK12035_SoilMoisture::getEvent(uint8_t *humidity, uint16_t *temp)
|
||||
{
|
||||
// Read moisture (0-100%)
|
||||
uint8_t moist = get_sensor_moisture();
|
||||
if (moist == 0xFF) //error indicator
|
||||
return false;
|
||||
MESH_DEBUG_PRINTLN("getEvent() - Humidity = %d", moist);
|
||||
*humidity = moist;
|
||||
|
||||
//Read temperature (degrees C)
|
||||
uint16_t t = get_sensor_temperature();
|
||||
if (t == 0XFFFF) // error indicator
|
||||
return false;
|
||||
|
||||
*temp = t;
|
||||
MESH_DEBUG_PRINTLN("getEvent() - Temperature = %d", t);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------------------*
|
||||
* Sensor Power Management and Reset Routines
|
||||
*
|
||||
* These routines manage the power and reset state of the sensor. The sensor_on() routine is
|
||||
* designed to power on the sensor and wait for it to become responsive, while the reset()
|
||||
* routine toggles the reset pin and waits for the sensor to respond with a valid version.
|
||||
*
|
||||
* They are for a future sensor power management function.
|
||||
*------------------------------------------------------------------------------------------*/
|
||||
|
||||
bool RAK12035_SoilMoisture::sensor_on()
|
||||
{
|
||||
uint8_t data;
|
||||
// This has been commented out due to a pin name conflict with the Heltec v3
|
||||
// This will need to be resolved if this funstion is to be utilized in the future
|
||||
|
||||
/*
|
||||
pinMode(WB_IO2, OUTPUT);
|
||||
digitalWrite(WB_IO2, HIGH); //Turn on Sensor Power
|
||||
|
||||
pinMode(WB_IO4, OUTPUT); //Set IO4 Pin to Output (connected to *reset on sensor)
|
||||
digitalWrite(WB_IO4, LOW); //*reset - Reset the Sensor
|
||||
delay(1); //Wait for the minimum *reset, 1mS is longer than required minimum
|
||||
digitalWrite(WB_IO4, HIGH); //Deassert Reset
|
||||
|
||||
delay(10); // Wait for the sensor code to complete initialization
|
||||
*/
|
||||
uint8_t v = 0;
|
||||
time_t timeout = millis();
|
||||
while ((!query_sensor())) //Wait for sensor to respond to I2C commands,
|
||||
{ //indicating it is ready
|
||||
if ((millis() - timeout) > 50){ //0.5 second timeout for sensor to respond
|
||||
MESH_DEBUG_PRINTLN("reset() - Timeout, no response from I2C commands");
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
delay(10); //delay 10mS
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool RAK12035_SoilMoisture::reset()
|
||||
{
|
||||
// This function is for a future Sensor Power Management function.
|
||||
// When power is reapplied this will reset the sensor and wait for it to respond
|
||||
// with a valid version.
|
||||
//
|
||||
// The Atmel 8495 Microcoltroller: Reset input. A low level on this pin for longer than
|
||||
// the minimum pulse length will generate a reset, even if the clock is not
|
||||
// running and provided the reset pin has not been disabled. The minimum pulse length is
|
||||
// given in Table 25-5 on page 240. 2000ns = .002mS
|
||||
// Shorter pulses are not guaranteed to generate a reset.
|
||||
//
|
||||
// Power is never removed so the Sensor reset was removed and is not needed,
|
||||
// But might be needed if power is ever switched off. Here is tested code.
|
||||
|
||||
// This has been commented out due to a pin name conflict with the Heltec v3
|
||||
// This will need to be resolved if this funstion is to be utilized in the future
|
||||
|
||||
/*
|
||||
pinMode(WB_IO4, OUTPUT); //Set IO4 Pin to Output (connected to *reset on sensor)
|
||||
MESH_DEBUG_PRINTLN("Assert *reset (Low) for 1 mS");
|
||||
digitalWrite(WB_IO4, LOW); //Reset the Sensor
|
||||
delay(1); //Wait for the minimum *reset, 1mS is longer than required minimum
|
||||
MESH_DEBUG_PRINTLN("reset() - De-assert *reset (High)");
|
||||
digitalWrite(WB_IO4, HIGH); // Deassert Reset
|
||||
*/
|
||||
|
||||
MESH_DEBUG_PRINTLN("reset() - Begin poling in 100mS intervals for a non-zero version");
|
||||
uint32_t start_time = millis();
|
||||
MESH_DEBUG_PRINTLN("reset() - Timeout, Start Time: %d milliseconds", start_time);
|
||||
|
||||
const uint32_t timeout_ms = 500; // Wait for 0.5 seconds
|
||||
uint32_t start = millis();
|
||||
|
||||
while (true) {
|
||||
if (query_sensor()) {
|
||||
MESH_DEBUG_PRINTLN("reset() - First Pass, Sensor responded with valid version");
|
||||
uint32_t stop_time = millis();
|
||||
MESH_DEBUG_PRINTLN("reset() - Timeout, Stop Time: %d mS", stop_time);
|
||||
MESH_DEBUG_PRINTLN("reset() - Timeout, Duration: %d mS", (stop_time - start_time));
|
||||
|
||||
return true;
|
||||
}
|
||||
if (millis() - start > timeout_ms) {
|
||||
MESH_DEBUG_PRINTLN("reset() - Timeout waiting for valid sensor version");
|
||||
uint32_t stop_time = millis();
|
||||
MESH_DEBUG_PRINTLN("reset() - Timeout, Stop Time: %d mS", stop_time);
|
||||
MESH_DEBUG_PRINTLN("reset() - Timeout, Duration: %d mS", (stop_time - start_time));
|
||||
return false;
|
||||
}
|
||||
delay(100);
|
||||
}
|
||||
}
|
||||
|
||||
bool RAK12035_SoilMoisture::query_sensor()
|
||||
{
|
||||
uint8_t v = 0;
|
||||
v = get_sensor_version();
|
||||
|
||||
// Treat 0x00 and 0xFF as invalid / bootloader / garbage
|
||||
if (v == 0x00 || v == 0xFF) {
|
||||
MESH_DEBUG_PRINTLN("query_sensor() FAIL: Version value invalid: %02X", v);
|
||||
return false;
|
||||
}
|
||||
MESH_DEBUG_PRINTLN("query_sensor() SUCCESS: Sensor Present, Version = %02X", v);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*------------------------------------------------------------------------------------------*
|
||||
* Below are the low-level I2C read and write functions. These handle the actual
|
||||
* communication with the sensor registers. The higher-level functions call these
|
||||
* to perform specific tasks.
|
||||
*------------------------------------------------------------------------------------------*/
|
||||
|
||||
bool RAK12035_SoilMoisture::read_rak12035(uint8_t cmd, uint8_t *data, uint8_t length)
|
||||
{
|
||||
_i2c->beginTransmission(_addr);
|
||||
_i2c->write(cmd); // <-- COMMAND, not register index
|
||||
if (_i2c->endTransmission() != 0)
|
||||
return false;
|
||||
|
||||
delay(20);
|
||||
|
||||
int received = _i2c->requestFrom(_addr, length);
|
||||
if (received != length)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
data[i] = _i2c->read();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RAK12035_SoilMoisture::write_rak12035(uint8_t cmd, uint8_t *data, uint8_t length)
|
||||
{
|
||||
_i2c->beginTransmission(_addr);
|
||||
_i2c->write(cmd); // <-- COMMAND, not register index
|
||||
|
||||
for (uint8_t i = 0; i < length; i++)
|
||||
_i2c->write(data[i]);
|
||||
|
||||
if (_i2c->endTransmission() != 0)
|
||||
return false;
|
||||
|
||||
delay(20);
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* @file RAK12035_SoilMoisture.h
|
||||
* @author Bernd Giesecke (bernd.giesecke@rakwireless.com)
|
||||
* @brief Header file for Class RAK12035
|
||||
* @version 0.1
|
||||
* @date 2021-11-20
|
||||
*
|
||||
* Updates for MeshCore integration
|
||||
* Ken Privitt
|
||||
* 2/26/2026
|
||||
*
|
||||
* @copyright Copyright (c) 2021
|
||||
*
|
||||
*/
|
||||
#ifndef RAK12035_SOILMOISTURE_H
|
||||
#define RAK12035_SOILMOISTURE_H
|
||||
#endif
|
||||
|
||||
#ifndef ENABLE_RAK12025_CALIBRATION
|
||||
#define ENABLE_RAK12025_CALIBRATION = 0 // Used to generate Calibration Version of Firmware
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
|
||||
#define RAK12035_I2C_ADDR_DEFAULT 0x20
|
||||
#define RAK12035_0_ADDR 0x20
|
||||
#define RAK12035_1_ADDR 0x21
|
||||
#define RAK12035_2_ADDR 0x22
|
||||
|
||||
// Command codes used by the RAK12035 firmware
|
||||
#define SOILMOISTURESENSOR_GET_CAPACITANCE 0x01 // (r) 2 bytes
|
||||
#define SOILMOISTURESENSOR_GET_I2C_ADDR 0x02 // (r) 1 bytes
|
||||
#define SOILMOISTURESENSOR_SET_I2C_ADDR 0x03 // (w) 1 bytes
|
||||
#define SOILMOISTURESENSOR_GET_VERSION 0x04 // (r) 1 bytes
|
||||
#define SOILMOISTURESENSOR_GET_TEMPERATURE 0x05 // (r) 2 bytes
|
||||
#define SOILMOISTURESENSOR_SET_SLEEP 0x06 // (w) 1 bytes
|
||||
#define SOILMOISTURESENSOR_SET_WET_CAL 0x07 // (w) 2 bytes
|
||||
#define SOILMOISTURESENSOR_SET_DRY_CAL 0x08 // (w) 2 bytes
|
||||
#define SOILMOISTURESENSOR_GET_MOISTURE 0x09 // (r) 1 bytes
|
||||
#define SOILMOISTURESENSOR_GET_WET_CAL 0x0A // (r) 2 bytes
|
||||
#define SOILMOISTURESENSOR_GET_DRY_CAL 0x0B // (r) 2 bytes
|
||||
|
||||
class RAK12035_SoilMoisture
|
||||
{
|
||||
public:
|
||||
RAK12035_SoilMoisture(uint8_t addr = RAK12035_I2C_ADDR_DEFAULT);
|
||||
|
||||
void setup(TwoWire& i2c);
|
||||
bool begin(uint8_t addr);
|
||||
bool getEvent(uint8_t *humidity, uint16_t *temperature);
|
||||
|
||||
uint16_t get_sensor_capacitance(); //Command 01 - (r) 2 byte
|
||||
uint8_t get_I2C_address(); //Command 02 - (r) 1 byte
|
||||
bool set_sensor_addr(uint8_t addr); //Command 03 - (w) 1 byte
|
||||
uint8_t get_sensor_version(); //Command 04 - (r) 1 byte
|
||||
float get_sensor_temperature(); //Command 05 - (r) 2 bytes
|
||||
bool sensor_sleep(); //Command 06 - (w) 1 byte
|
||||
bool set_humidity_full(uint16_t hundred_val); //Command 07 - (w) 2 bytes
|
||||
bool set_humidity_zero(uint16_t zero_val); //Command 08 - (w) 2 bytes
|
||||
uint8_t get_sensor_moisture(); //Command 09 - (r) 1 byte
|
||||
uint8_t get_sensor_humid(); //Command 09 - (r) 1 byte
|
||||
uint16_t get_humidity_full(); //Command 0A - (r) 2 bytes
|
||||
uint16_t get_humidity_zero(); //Command 0B - (r) 2 bytes
|
||||
|
||||
bool read_rak12035(uint8_t cmd, uint8_t *data, uint8_t length);
|
||||
bool write_rak12035(uint8_t cmd, uint8_t *data, uint8_t length);
|
||||
|
||||
bool query_sensor();
|
||||
bool sensor_on();
|
||||
bool reset();
|
||||
|
||||
uint16_t _dry_cal;
|
||||
uint16_t _wet_cal;
|
||||
|
||||
private:
|
||||
bool read_reg(uint8_t reg, uint8_t *data, uint8_t len);
|
||||
bool write_reg(uint8_t reg, uint8_t *data, uint8_t len);
|
||||
|
||||
TwoWire *_i2c = &Wire;
|
||||
uint8_t _addr;
|
||||
|
||||
uint16_t default_dry_cal = 2000;
|
||||
uint16_t default_wet_cal = 50;
|
||||
uint8_t _capacitance = 0;
|
||||
uint16_t _temperature = 0;
|
||||
uint8_t _moisture = 0;
|
||||
};
|
||||
#endif
|
||||
@@ -59,44 +59,58 @@ bool E213Display::begin() {
|
||||
}
|
||||
|
||||
void E213Display::powerOn() {
|
||||
if (_periph_power) {
|
||||
_periph_power->claim();
|
||||
} else {
|
||||
#ifdef PIN_VEXT_EN
|
||||
pinMode(PIN_VEXT_EN, OUTPUT);
|
||||
pinMode(PIN_VEXT_EN, OUTPUT);
|
||||
#ifdef PIN_VEXT_EN_ACTIVE
|
||||
digitalWrite(PIN_VEXT_EN, PIN_VEXT_EN_ACTIVE);
|
||||
digitalWrite(PIN_VEXT_EN, PIN_VEXT_EN_ACTIVE);
|
||||
#else
|
||||
digitalWrite(PIN_VEXT_EN, LOW); // Active low
|
||||
digitalWrite(PIN_VEXT_EN, LOW); // Active low
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
delay(50); // Allow power to stabilize
|
||||
#endif
|
||||
}
|
||||
|
||||
void E213Display::powerOff() {
|
||||
if (_periph_power) {
|
||||
_periph_power->release();
|
||||
} else {
|
||||
#ifdef PIN_VEXT_EN
|
||||
#ifdef PIN_VEXT_EN_ACTIVE
|
||||
digitalWrite(PIN_VEXT_EN, !PIN_VEXT_EN_ACTIVE);
|
||||
digitalWrite(PIN_VEXT_EN, !PIN_VEXT_EN_ACTIVE);
|
||||
#else
|
||||
digitalWrite(PIN_VEXT_EN, HIGH); // Turn off power
|
||||
digitalWrite(PIN_VEXT_EN, HIGH); // Turn off power
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void E213Display::turnOn() {
|
||||
if (!_init) begin();
|
||||
powerOn();
|
||||
else if (!_isOn) {
|
||||
powerOn();
|
||||
display->fastmodeOn(); // Reinitialize display controller after power was cut
|
||||
}
|
||||
_isOn = true;
|
||||
}
|
||||
|
||||
void E213Display::turnOff() {
|
||||
powerOff();
|
||||
_isOn = false;
|
||||
if (_isOn) {
|
||||
powerOff();
|
||||
_isOn = false;
|
||||
}
|
||||
}
|
||||
|
||||
void E213Display::clear() {
|
||||
display->clear();
|
||||
|
||||
}
|
||||
|
||||
void E213Display::startFrame(Color bkg) {
|
||||
display_crc.reset();
|
||||
|
||||
// Fill screen with white first to ensure clean background
|
||||
display->fillRect(0, 0, width(), height(), WHITE);
|
||||
|
||||
@@ -107,31 +121,50 @@ void E213Display::startFrame(Color bkg) {
|
||||
}
|
||||
|
||||
void E213Display::setTextSize(int sz) {
|
||||
display_crc.update<int>(sz);
|
||||
// The library handles text size internally
|
||||
display->setTextSize(sz);
|
||||
}
|
||||
|
||||
void E213Display::setColor(Color c) {
|
||||
display_crc.update<Color>(c);
|
||||
// implemented in individual display methods
|
||||
}
|
||||
|
||||
void E213Display::setCursor(int x, int y) {
|
||||
display_crc.update<int>(x);
|
||||
display_crc.update<int>(y);
|
||||
display->setCursor(x, y);
|
||||
}
|
||||
|
||||
void E213Display::print(const char *str) {
|
||||
display_crc.update<char>(str, strlen(str));
|
||||
display->print(str);
|
||||
}
|
||||
|
||||
void E213Display::fillRect(int x, int y, int w, int h) {
|
||||
display_crc.update<int>(x);
|
||||
display_crc.update<int>(y);
|
||||
display_crc.update<int>(w);
|
||||
display_crc.update<int>(h);
|
||||
display->fillRect(x, y, w, h, BLACK);
|
||||
}
|
||||
|
||||
void E213Display::drawRect(int x, int y, int w, int h) {
|
||||
display_crc.update<int>(x);
|
||||
display_crc.update<int>(y);
|
||||
display_crc.update<int>(w);
|
||||
display_crc.update<int>(h);
|
||||
display->drawRect(x, y, w, h, BLACK);
|
||||
}
|
||||
|
||||
void E213Display::drawXbm(int x, int y, const uint8_t *bits, int w, int h) {
|
||||
display_crc.update<int>(x);
|
||||
display_crc.update<int>(y);
|
||||
display_crc.update<int>(w);
|
||||
display_crc.update<int>(h);
|
||||
display_crc.update<uint8_t>(bits, w * h / 8);
|
||||
|
||||
// Width in bytes for bitmap processing
|
||||
uint16_t widthInBytes = (w + 7) / 8;
|
||||
|
||||
@@ -160,5 +193,9 @@ uint16_t E213Display::getTextWidth(const char *str) {
|
||||
}
|
||||
|
||||
void E213Display::endFrame() {
|
||||
uint32_t crc = display_crc.finalize();
|
||||
if (crc != last_display_crc_value) {
|
||||
display->update();
|
||||
last_display_crc_value = crc;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,15 +5,20 @@
|
||||
#include <SPI.h>
|
||||
#include <Wire.h>
|
||||
#include <heltec-eink-modules.h>
|
||||
#include <CRC32.h>
|
||||
#include <helpers/RefCountedDigitalPin.h>
|
||||
|
||||
// Display driver for E213 e-ink display
|
||||
class E213Display : public DisplayDriver {
|
||||
BaseDisplay* display=NULL;
|
||||
bool _init = false;
|
||||
bool _isOn = false;
|
||||
RefCountedDigitalPin* _periph_power;
|
||||
CRC32 display_crc;
|
||||
uint32_t last_display_crc_value = 0;
|
||||
|
||||
public:
|
||||
E213Display() : DisplayDriver(250, 122) {}
|
||||
E213Display(RefCountedDigitalPin* periph_power = NULL) : DisplayDriver(250, 122), _periph_power(periph_power) {}
|
||||
~E213Display(){
|
||||
if(display!=NULL) {
|
||||
delete display;
|
||||
@@ -39,4 +44,4 @@ private:
|
||||
BaseDisplay* detectEInk();
|
||||
void powerOn();
|
||||
void powerOff();
|
||||
};
|
||||
};
|
||||
|
||||
@@ -21,28 +21,41 @@ bool E290Display::begin() {
|
||||
}
|
||||
|
||||
void E290Display::powerOn() {
|
||||
if (_periph_power) {
|
||||
_periph_power->claim();
|
||||
} else {
|
||||
#ifdef PIN_VEXT_EN
|
||||
pinMode(PIN_VEXT_EN, OUTPUT);
|
||||
digitalWrite(PIN_VEXT_EN, PIN_VEXT_EN_ACTIVE);
|
||||
delay(50); // Allow power to stabilize
|
||||
pinMode(PIN_VEXT_EN, OUTPUT);
|
||||
digitalWrite(PIN_VEXT_EN, PIN_VEXT_EN_ACTIVE);
|
||||
#endif
|
||||
}
|
||||
delay(50); // Allow power to stabilize
|
||||
}
|
||||
|
||||
void E290Display::powerOff() {
|
||||
if (_periph_power) {
|
||||
_periph_power->release();
|
||||
} else {
|
||||
#ifdef PIN_VEXT_EN
|
||||
digitalWrite(PIN_VEXT_EN, !PIN_VEXT_EN_ACTIVE); // Turn off power
|
||||
digitalWrite(PIN_VEXT_EN, !PIN_VEXT_EN_ACTIVE); // Turn off power
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void E290Display::turnOn() {
|
||||
if (!_init) begin();
|
||||
powerOn();
|
||||
else if (!_isOn) {
|
||||
powerOn();
|
||||
display.fastmodeOn(); // Reinitialize display controller after power was cut
|
||||
}
|
||||
_isOn = true;
|
||||
}
|
||||
|
||||
void E290Display::turnOff() {
|
||||
powerOff();
|
||||
_isOn = false;
|
||||
if (_isOn) {
|
||||
powerOff();
|
||||
_isOn = false;
|
||||
}
|
||||
}
|
||||
|
||||
void E290Display::clear() {
|
||||
@@ -50,6 +63,8 @@ void E290Display::clear() {
|
||||
}
|
||||
|
||||
void E290Display::startFrame(Color bkg) {
|
||||
display_crc.reset();
|
||||
|
||||
// Fill screen with white first to ensure clean background
|
||||
display.fillRect(0, 0, width(), height(), WHITE);
|
||||
if (bkg == LIGHT) {
|
||||
@@ -59,31 +74,50 @@ void E290Display::startFrame(Color bkg) {
|
||||
}
|
||||
|
||||
void E290Display::setTextSize(int sz) {
|
||||
display_crc.update<int>(sz);
|
||||
// The library handles text size internally
|
||||
display.setTextSize(sz);
|
||||
}
|
||||
|
||||
void E290Display::setColor(Color c) {
|
||||
display_crc.update<Color>(c);
|
||||
// implemented in individual display methods
|
||||
}
|
||||
|
||||
void E290Display::setCursor(int x, int y) {
|
||||
display_crc.update<int>(x);
|
||||
display_crc.update<int>(y);
|
||||
display.setCursor(x, y);
|
||||
}
|
||||
|
||||
void E290Display::print(const char *str) {
|
||||
display_crc.update<char>(str, strlen(str));
|
||||
display.print(str);
|
||||
}
|
||||
|
||||
void E290Display::fillRect(int x, int y, int w, int h) {
|
||||
display_crc.update<int>(x);
|
||||
display_crc.update<int>(y);
|
||||
display_crc.update<int>(w);
|
||||
display_crc.update<int>(h);
|
||||
display.fillRect(x, y, w, h, BLACK);
|
||||
}
|
||||
|
||||
void E290Display::drawRect(int x, int y, int w, int h) {
|
||||
display_crc.update<int>(x);
|
||||
display_crc.update<int>(y);
|
||||
display_crc.update<int>(w);
|
||||
display_crc.update<int>(h);
|
||||
display.drawRect(x, y, w, h, BLACK);
|
||||
}
|
||||
|
||||
void E290Display::drawXbm(int x, int y, const uint8_t *bits, int w, int h) {
|
||||
display_crc.update<int>(x);
|
||||
display_crc.update<int>(y);
|
||||
display_crc.update<int>(w);
|
||||
display_crc.update<int>(h);
|
||||
display_crc.update<uint8_t>(bits, w * h / 8);
|
||||
|
||||
// Width in bytes for bitmap processing
|
||||
uint16_t widthInBytes = (w + 7) / 8;
|
||||
|
||||
@@ -112,5 +146,9 @@ uint16_t E290Display::getTextWidth(const char *str) {
|
||||
}
|
||||
|
||||
void E290Display::endFrame() {
|
||||
display.update();
|
||||
uint32_t crc = display_crc.finalize();
|
||||
if (crc != last_display_crc_value) {
|
||||
display.update();
|
||||
last_display_crc_value = crc;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,15 +5,20 @@
|
||||
#include <SPI.h>
|
||||
#include <Wire.h>
|
||||
#include <heltec-eink-modules.h>
|
||||
#include <CRC32.h>
|
||||
#include <helpers/RefCountedDigitalPin.h>
|
||||
|
||||
// Display driver for E290 e-ink display
|
||||
class E290Display : public DisplayDriver {
|
||||
EInkDisplay_VisionMasterE290 display;
|
||||
bool _init = false;
|
||||
bool _isOn = false;
|
||||
RefCountedDigitalPin* _periph_power;
|
||||
CRC32 display_crc;
|
||||
uint32_t last_display_crc_value = 0;
|
||||
|
||||
public:
|
||||
E290Display() : DisplayDriver(296, 128) {}
|
||||
E290Display(RefCountedDigitalPin* periph_power = NULL) : DisplayDriver(296, 128), _periph_power(periph_power) {}
|
||||
|
||||
bool begin();
|
||||
bool isOn() override { return _isOn; }
|
||||
@@ -34,4 +39,4 @@ public:
|
||||
private:
|
||||
void powerOn();
|
||||
void powerOff();
|
||||
};
|
||||
};
|
||||
|
||||
@@ -21,10 +21,14 @@ bool ST7735Display::begin() {
|
||||
if (_peripher_power) _peripher_power->claim();
|
||||
|
||||
pinMode(PIN_TFT_LEDA_CTL, OUTPUT);
|
||||
digitalWrite(PIN_TFT_LEDA_CTL, HIGH);
|
||||
#if defined(PIN_TFT_LEDA_CTL_ACTIVE)
|
||||
digitalWrite(PIN_TFT_LEDA_CTL, PIN_TFT_LEDA_CTL_ACTIVE);
|
||||
#else
|
||||
digitalWrite(PIN_TFT_LEDA_CTL, HIGH);
|
||||
#endif
|
||||
digitalWrite(PIN_TFT_RST, HIGH);
|
||||
|
||||
#if defined(HELTEC_TRACKER_V2)
|
||||
#if defined(HELTEC_TRACKER_V2) || defined(HELTEC_T096)
|
||||
display.initR(INITR_MINI160x80);
|
||||
display.setRotation(DISPLAY_ROTATION);
|
||||
uint8_t madctl = ST77XX_MADCTL_MY | ST77XX_MADCTL_MV |ST7735_MADCTL_BGR;//Adjust color to BGR
|
||||
@@ -50,9 +54,12 @@ void ST7735Display::turnOn() {
|
||||
|
||||
void ST7735Display::turnOff() {
|
||||
if (_isOn) {
|
||||
digitalWrite(PIN_TFT_LEDA_CTL, HIGH);
|
||||
digitalWrite(PIN_TFT_RST, LOW);
|
||||
digitalWrite(PIN_TFT_LEDA_CTL, LOW);
|
||||
#if defined(PIN_TFT_LEDA_CTL_ACTIVE)
|
||||
digitalWrite(PIN_TFT_LEDA_CTL, !PIN_TFT_LEDA_CTL_ACTIVE);
|
||||
#else
|
||||
digitalWrite(PIN_TFT_LEDA_CTL, LOW);
|
||||
#endif
|
||||
_isOn = false;
|
||||
|
||||
if (_peripher_power) _peripher_power->release();
|
||||
|
||||
Reference in New Issue
Block a user