Update libki

This commit is contained in:
sieja
2025-07-21 21:31:40 +02:00
parent d145590bd3
commit a9fba3267d
47 changed files with 2072 additions and 914 deletions

View File

@@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
* Copyright (c) 2020-2025 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*

View File

@@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
* Copyright (c) 2020-2025 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
@@ -26,13 +26,14 @@
********************************************************************************/
// For developer use and testing only - provides a common set of pin numbers mapped to the Adafruit Feather Board.
// Facilitates the testing of identical code on an ESP32, ESP32-S2, and ESP32-C3 using a common jig without rewiring
// Facilitates the testing of identical code on different ESP32 chips using a common jig without rewiring
#pragma once
#if defined(ARDUINO_FEATHER_ESP32)
enum {
F13=13,F12=12,F27=27,F15=15,F32=32,F14=14,F16=16,F17=17,F21=21, // Digital Only (9 pins)
F13=13,F12=12,F27=27, // Digital w/Touch (3 pins)
F15=15,F32=32,F14=14,F16=16,F17=17,F21=21, // Digital Only (6 pins)
F26=26,F25=25,F34=34,F39=39,F36=36,F4=4, // A0-A5
F22=22,F23=23, // I2C SCL/SDA
F5=5,F18=18,F19=19,F33=33 // SPI SCK/SDO/SDI/CS
@@ -41,7 +42,8 @@
#elif defined(ARDUINO_ESP32S2_DEV)
enum {
F13=1,F12=3,F27=7,F15=10,F32=42,F14=11,F16=20,F17=21,F21=16, // Digital Only (9 pins)
F13=1,F12=3,F27=7, // Digital w/Touch (3 pins)
F15=10,F32=42,F14=11,F16=20,F17=21,F21=16, // Digital Only (6 pins)
F26=17,F25=14,F34=13,F39=12,F36=18,F4=19, // A0-A5
F22=9,F23=8, // I2C SCL/SDA
F5=36,F18=35,F19=37,F33=34, // SPI SCK/SDO/SDI/CS
@@ -51,7 +53,7 @@
#elif defined(ARDUINO_ESP32C3_DEV)
enum {
F27=19,F32=2,F14=10,F16=20,F17=21,F21=18, // Digital Only (6 pins)
F27=19,F32=2,F14=10,F16=20,F17=21,F21=18, // Digital Only (6 pins, but F16 and F17 used for Serial)
F26=0,F25=1,F4=3, // A0/A1/A5
F22=9,F23=8, // I2C SCL/SDA
F5=4,F18=6,F19=5,F33=7, // SPI SCK/SDO/SDI/CS
@@ -61,7 +63,8 @@
#elif defined(ARDUINO_ESP32S3_DEV)
enum {
F13=5,F12=6,F27=7,F15=16,F32=17,F14=18,F16=37,F17=36,F21=38, // Digital Only (9 pins)
F13=5,F12=6,F27=7, // Digital w/Touch (3 pins)
F15=16,F32=17,F14=18,F16=41,F17=40,F21=38, // Digital Only (6 pins)
F26=1,F25=2,F34=20,F39=19,F36=15,F4=4, // A0-A5
F22=9,F23=8, // I2C SCL/SDA
F5=12,F18=11,F19=13,F33=10, // SPI SCK/SDO/SDI/CS
@@ -69,4 +72,23 @@
};
#define DEVICE_SUFFIX "-S3"
#elif defined(ARDUINO_ESP32C6_DEV)
enum {
F12=9,F27=6,F15=7,F32=10,F14=11,F16=13,F17=12,F21=15, // Digital Only (8 pins)
F26=3,F25=2,F34=1,F39=0,F36=5,F4=4, // A0-A5
F22=22,F23=23, // I2C SCL/SDA
F5=21,F18=19,F19=20,F33=18, // SPI SCK/SDO/SDI/CS
BUILTIN_PIXEL=8 // Built-in Neo-Pixel
};
#define DEVICE_SUFFIX "-C6"
#elif defined(ARDUINO_WESP32)
enum {
F12=2,F27=32, // Digital w/Touch (2 pins)
F26=12,F25=14,F32=18,F14=23,F21=5,F33=13, // Digital Only (6 pins)
F34=35,F39=34,F36=36,F4=33, // A2-A5
F22=4,F23=15 // I2C SCL/SDA
};
#define DEVICE_SUFFIX "-WESP32"
#endif

View File

@@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
* Copyright (c) 2020-2025 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
@@ -233,6 +233,8 @@ void HAPClient::processRequest(){
} // PUT request
if(!strncmp(body,"GET ",4)){ // this is a GET request
int refreshTime;
if(!strncmp(body,"GET /accessories ",17)) // GET ACCESSORIES
getAccessoriesURL();
@@ -240,8 +242,8 @@ void HAPClient::processRequest(){
else if(!strncmp(body,"GET /characteristics?",21)) // GET CHARACTERISTICS
getCharacteristicsURL(body+21);
else if(homeSpan.webLog.isEnabled && !strncmp(body,homeSpan.webLog.statusURL.c_str(),homeSpan.webLog.statusURL.length())) // GET STATUS - AN OPTIONAL, NON-HAP-R2 FEATURE
getStatusURL(this,NULL,NULL);
else if(homeSpan.webLog.isEnabled && (refreshTime=homeSpan.webLog.check(body+4))>=0) // OPTIONAL (NON-HAP) STATUS REQUEST
getStatusURL(this,NULL,NULL,refreshTime);
else {
notFoundError();
@@ -914,6 +916,9 @@ int HAPClient::getCharacteristicsURL(char *urlBuf){
LOG1("In Get Characteristics #%d (%s)...\n",clientNumber,client.remoteIP().toString().c_str());
if(homeSpan.getCharacteristicsCallback)
homeSpan.getCharacteristicsCallback(urlBuf);
int len=strlen(urlBuf); // determine number of IDs specified by counting commas in URL
int numIDs=1;
for(int i=0;i<len;i++)
@@ -982,17 +987,14 @@ int HAPClient::putCharacteristicsURL(char *json){
LOG1("In Put Characteristics #%d (%s)...\n",clientNumber,client.remoteIP().toString().c_str());
int n=homeSpan.countCharacteristics(json); // count number of objects in JSON request
if(n==0) // if no objects found, return
return(0);
SpanBuf pObj[n]; // reserve space for objects
if(!homeSpan.updateCharacteristics(json, pObj)) // perform update
SpanBufVec pVec;
if(!homeSpan.updateCharacteristics(json, pVec)) // perform update and check for success
return(0); // return if failed to update (error message will have been printed in update)
boolean multiCast=false;
for(int i=0;i<n && !multiCast;i++) // for each characterstic, check if any status is either NOT OKAY, or if WRITE-RESPONSE is requested
if(pObj[i].status!=StatusCode::OK || pObj[i].wr) // if so, to use multicast response
boolean multiCast=false;
for(auto it=pVec.begin();it!=pVec.end() && !multiCast;it++) // for each characteristic, check if any status is either NOT OKAY, or if WRITE-RESPONSE is requested
if((*it).status!=StatusCode::OK || (*it).wr) // if so, must use multicast response
multiCast=true;
LOG2("\n>>>>>>>>>> %s >>>>>>>>>>\n",client.remoteIP().toString().c_str());
@@ -1005,13 +1007,13 @@ int HAPClient::putCharacteristicsURL(char *json){
} else { // multicast respose is required
homeSpan.printfAttributes(pObj,n);
homeSpan.printfAttributes(pVec);
size_t nBytes=hapOut.getSize();
hapOut.flush();
hapOut.setLogLevel(2).setHapClient(this);
hapOut << "HTTP/1.1 207 Multi-Status\r\nContent-Type: application/hap+json\r\nContent-Length: " << nBytes << "\r\n\r\n";
homeSpan.printfAttributes(pObj,n);
homeSpan.printfAttributes(pVec);
hapOut.flush();
}
@@ -1019,7 +1021,7 @@ int HAPClient::putCharacteristicsURL(char *json){
// Create and send Event Notifications if needed
eventNotify(pObj,n,this); // transmit EVENT Notification for "n" pObj objects, except DO NOT notify client making request
eventNotify(pVec,this); // transmit EVENT Notification for objects, except DO NOT notify client making request
return(1);
}
@@ -1043,7 +1045,7 @@ int HAPClient::putPrepareURL(char *json){
uint64_t pid;
if((cBuf=strstr(json,ttlToken)))
sscanf(cBuf+strlen(ttlToken),"%u",&ttl);
sscanf(cBuf+strlen(ttlToken),"%lu",&ttl);
if((cBuf=strstr(json,pidToken)))
sscanf(cBuf+strlen(ttlToken),"%llu",&pid);
@@ -1074,8 +1076,10 @@ int HAPClient::putPrepareURL(char *json){
//////////////////////////////////////
void HAPClient::getStatusURL(HAPClient *hapClient, void (*callBack)(const char *, void *), void *user_data){
void HAPClient::getStatusURL(HAPClient *hapClient, void (*callBack)(const char *, void *), void *user_data, int refreshTime){
std::shared_lock readLock(homeSpan.webLog.mux); // wait for mux to be unlocked, or already locked non-exclusively, and then lock *non-exclusively* to prevent writing in vLog
char clocktime[33];
if(homeSpan.webLog.timeInit){
@@ -1100,8 +1104,12 @@ void HAPClient::getStatusURL(HAPClient *hapClient, void (*callBack)(const char *
hapOut.setHapClient(hapClient).setLogLevel(2).setCallback(callBack).setCallbackUserData(user_data);
if(!callBack)
hapOut << "HTTP/1.1 200 OK\r\nContent-type: text/html; charset=utf-8\r\n\r\n";
if(!callBack){
hapOut << "HTTP/1.1 200 OK\r\nContent-type: text/html; charset=utf-8\r\n";
if(refreshTime>0)
hapOut << "Refresh: " << refreshTime << "\r\n";
hapOut << "\r\n";
}
hapOut << "<html><head><title>" << homeSpan.displayName << "</title>\n";
hapOut << "<style>body {background-color:lightblue;} th, td {padding-right: 10px; padding-left: 10px; border:1px solid black;}" << homeSpan.webLog.css.c_str() << "</style></head>\n";
@@ -1111,51 +1119,26 @@ void HAPClient::getStatusURL(HAPClient *hapClient, void (*callBack)(const char *
hapOut << "<tr><td>Up Time:</td><td>" << uptime << "</td></tr>\n";
hapOut << "<tr><td>Current Time:</td><td>" << clocktime << "</td></tr>\n";
hapOut << "<tr><td>Boot Time:</td><td>" << homeSpan.webLog.bootTime << "</td></tr>\n";
hapOut << "<tr><td>Reset Reason:</td><td>";
switch(esp_reset_reason()) {
case ESP_RST_UNKNOWN:
hapOut << "Cannot be determined";
break;
case ESP_RST_POWERON:
hapOut << "Power-on event";
break;
case ESP_RST_EXT:
hapOut << "External pin";
break;
case ESP_RST_SW:
hapOut << "Software reboot via esp_restart";
break;
case ESP_RST_PANIC:
hapOut << "Software Exception/Panic";
break;
case ESP_RST_INT_WDT:
hapOut << "Interrupt watchdog";
break;
case ESP_RST_TASK_WDT:
hapOut << "Task watchdog";
break;
case ESP_RST_WDT:
hapOut << "Other watchdogs";
break;
case ESP_RST_DEEPSLEEP:
hapOut << "Exiting deep sleep mode";
break;
case ESP_RST_BROWNOUT:
hapOut << "Brownout";
break;
case ESP_RST_SDIO:
hapOut << "SDIO";
break;
default:
hapOut << "Unknown Reset Code";
hapOut << "<tr><td>Reset Reason:</td><td>" << Utils::resetReason() << " (" << esp_reset_reason() << ")</td></tr>\n";
if(homeSpan.compileTime)
hapOut << "<tr><td>Compile Time:</td><td>" << homeSpan.compileTime << "</td></tr>\n";
if(!homeSpan.ethernetEnabled){
hapOut << "<tr><td>WiFi Disconnects:</td><td>" << homeSpan.connected/2 << "</td></tr>\n";
hapOut << "<tr><td>WiFi Signal:</td><td>" << (int)WiFi.RSSI() << " dBm</td></tr>\n";
if(homeSpan.bssidNames.count(WiFi.BSSIDstr().c_str()))
hapOut << "<tr><td>BSSID:</td><td>" << WiFi.BSSIDstr().c_str() << " \"" << homeSpan.bssidNames[WiFi.BSSIDstr().c_str()].c_str() << "\"" << "</td></tr>\n";
else
hapOut << "<tr><td>BSSID:</td><td>" << WiFi.BSSIDstr().c_str() << "</td></tr>\n";
hapOut << "<tr><td>WiFi Local IP:</td><td>" << WiFi.localIP().toString().c_str() << "</td></tr>\n";
hapOut << "<tr><td>WiFi Gateway:</td><td>" << WiFi.gatewayIP().toString().c_str() << "</td></tr>\n";
} else {
hapOut << "<tr><td>Ethernet Disconnects:</td><td>" << homeSpan.connected/2 << "</td></tr>\n";
hapOut << "<tr><td>Ethernet Local IP:</td><td>" << ETH.localIP().toString().c_str() << "</td></tr>\n";
hapOut << "<tr><td>Ethernet Gateway:</td><td>" << ETH.gatewayIP().toString().c_str() << "</td></tr>\n";
}
hapOut << " (" << esp_reset_reason() << ")</td></tr>\n";
hapOut << "<tr><td>WiFi Disconnects:</td><td>" << homeSpan.connected/2 << "</td></tr>\n";
hapOut << "<tr><td>WiFi Signal:</td><td>" << (int)WiFi.RSSI() << " dBm</td></tr>\n";
hapOut << "<tr><td>WiFi Gateway:</td><td>" << WiFi.gatewayIP().toString().c_str() << "</td></tr>\n";
hapOut << "<tr><td>ESP32 Board:</td><td>" << ARDUINO_BOARD << "</td></tr>\n";
hapOut << "<tr><td>Arduino-ESP Version:</td><td>" << ARDUINO_ESP_VERSION << "</td></tr>\n";
hapOut << "<tr><td>ESP-IDF Version:</td><td>" << ESP_IDF_VERSION_MAJOR << "." << ESP_IDF_VERSION_MINOR << "." << ESP_IDF_VERSION_PATCH << "</td></tr>\n";
@@ -1218,9 +1201,9 @@ void HAPClient::getStatusURL(HAPClient *hapClient, void (*callBack)(const char *
void HAPClient::checkNotifications(){
if(!homeSpan.Notifications.empty()){ // if there are Notifications to process
eventNotify(&homeSpan.Notifications[0],homeSpan.Notifications.size()); // transmit EVENT Notifications
homeSpan.Notifications.clear(); // clear Notifications vector
if(!homeSpan.Notifications.empty()){ // if there are Notifications to process
eventNotify(homeSpan.Notifications); // transmit EVENT Notifications
homeSpan.Notifications.clear(); // clear Notifications vector
}
}
@@ -1233,7 +1216,7 @@ void HAPClient::checkTimedWrites(){
auto tw=homeSpan.TimedWrites.begin();
while(tw!=homeSpan.TimedWrites.end()){
if(cTime>tw->second){ // timer has expired
LOG2("Removing PID=%llu ALARM=%u\n",tw->first,tw->second);
LOG2("Removing PID=%llu ALARM=%lu\n",tw->first,tw->second);
tw=homeSpan.TimedWrites.erase(tw);
}
else
@@ -1243,22 +1226,22 @@ void HAPClient::checkTimedWrites(){
//////////////////////////////////////
void HAPClient::eventNotify(SpanBuf *pObj, int nObj, HAPClient *ignore){
void HAPClient::eventNotify(SpanBufVec &pVec, HAPClient *ignore){
for(auto it=homeSpan.hapList.begin(); it!=homeSpan.hapList.end(); ++it){ // loop over all connection slots
if(&(*it)!=ignore){ // if NOT flagged to be ignored (in cases where it is the client making a PUT request)
homeSpan.printfNotify(pObj,nObj,&(*it)); // create JSON (which may be of zero length if there are no applicable notifications for this cNum)
homeSpan.printfNotify(pVec,&(*it)); // create JSON (which may be of zero length if there are no applicable notifications for this cNum)
size_t nBytes=hapOut.getSize();
hapOut.flush();
if(nBytes>0){ // if there ARE notifications to send to client cNum
if(nBytes>0){ // if there ARE notifications to send to client cNum
LOG2("\n>>>>>>>>>> %s >>>>>>>>>>\n",it->client.remoteIP().toString().c_str());
hapOut.setLogLevel(2).setHapClient(&(*it));
hapOut << "EVENT/1.0 200 OK\r\nContent-Type: application/hap+json\r\nContent-Length: " << nBytes << "\r\n\r\n";
homeSpan.printfNotify(pObj,nObj,&(*it));
homeSpan.printfNotify(pVec,&(*it));
hapOut.flush();
LOG2("\n-------- SENT ENCRYPTED! --------\n");
@@ -1555,7 +1538,7 @@ HapOut::HapStreamBuffer::HapStreamBuffer(){
ctx = (mbedtls_sha512_context *)heap_caps_malloc(sizeof(mbedtls_sha512_context),caps); // space for hash context
mbedtls_sha512_init(ctx); // initialize context
mbedtls_sha512_starts_ret(ctx,1); // start SHA-384 hash (note second argument=1)
mbedtls_sha512_starts(ctx,1); // start SHA-384 hash (note second argument=1)
setp(buffer, buffer+bufSize-1); // assign buffer pointers
}
@@ -1607,7 +1590,7 @@ void HapOut::HapStreamBuffer::flushBuffer(){
delay(1);
}
mbedtls_sha512_update_ret(ctx,(uint8_t *)buffer,num); // update hash
mbedtls_sha512_update(ctx,(uint8_t *)buffer,num); // update hash
pbump(-num); // reset buffer pointers
}
@@ -1643,8 +1626,8 @@ int HapOut::HapStreamBuffer::sync(){
callBackUserData=NULL;
}
mbedtls_sha512_finish_ret(ctx,hash); // finish SHA-384 and store hash
mbedtls_sha512_starts_ret(ctx,1); // re-start hash for next time
mbedtls_sha512_finish(ctx,hash); // finish SHA-384 and store hash
mbedtls_sha512_starts(ctx,1); // re-start hash for next time
return(0);
}

View File

@@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
* Copyright (c) 2020-2025 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
@@ -72,34 +72,6 @@ struct Accessory {
uint8_t LTPK[crypto_sign_PUBLICKEYBYTES]; // Long Term Ed2519 Public Key
};
//////////////////////////////////////////////////////////
// Paired Controller Structure for Permanently-Stored Data
class Controller {
friend class HAPClient;
boolean allocated=false; // DEPRECATED (but needed for backwards compatability with original NVS storage of Controller info)
boolean admin; // Controller has admin privileges
uint8_t ID[36]; // Pairing ID
uint8_t LTPK[32]; // Long Term Ed2519 Public Key
public:
Controller(uint8_t *id, uint8_t *ltpk, boolean ad){
allocated=true;
admin=ad;
memcpy(ID,id,36);
memcpy(LTPK,ltpk,32);
}
Controller(){}
const uint8_t *getID() const {return(ID);}
const uint8_t *getLTPK() const {return(LTPK);}
boolean isAdmin() const {return(admin);}
};
/////////////////////////////////////////////////
// HAPClient Structure
// Reads and Writes from each HAP Client connection
@@ -118,7 +90,7 @@ struct HAPClient {
// individual structures and data defined for each Hap Client connection
WiFiClient client; // handle to client
NetworkClient client; // handle to client
int clientNumber; // client number
Controller *cPair=NULL; // pointer to info on current, session-verified Paired Controller (NULL=un-verified, and therefore un-encrypted, connection)
@@ -173,9 +145,9 @@ struct HAPClient {
static void tearDown(uint8_t *id); // tears down connections using Controller with ID=id; tears down all connections if id=NULL
static void checkNotifications(); // checks for Event Notifications and reports to controllers as needed (HAP Section 6.8)
static void checkTimedWrites(); // checks for expired Timed Write PIDs, and clears any found (HAP Section 6.7.2.4)
static void eventNotify(SpanBuf *pObj, int nObj, HAPClient *ignore=NULL); // transmits EVENT Notifications for nObj SpanBuf objects, pObj, with optional flag to ignore a specific client
static void eventNotify(SpanBufVec &pVec, HAPClient *ignore=NULL); // transmits EVENT Notifications for SpanBuf objects with optional flag to ignore a specific client
static void getStatusURL(HAPClient *, void (*)(const char *, void *), void *); // GET / status (an optional, non-HAP feature)
static void getStatusURL(HAPClient *, void (*)(const char *, void *), void *, int refreshTime=0); // GET / status (an optional, non-HAP feature)
class HAPTLV : public TLV8 { // dedicated class for HAP TLV8 records
public:

View File

@@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
* Copyright (c) 2020-2025 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*

View File

@@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
* Copyright (c) 2020-2025 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*

View File

@@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
* Copyright (c) 2020-2025 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*

View File

@@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
* Copyright (c) 2020-2025 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
* Copyright (c) 2020-2025 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
@@ -36,10 +36,13 @@
#include <unordered_map>
#include <vector>
#include <list>
#include <shared_mutex>
#include <nvs.h>
#include <ArduinoOTA.h>
#include <ETH.h>
#include <esp_now.h>
#include <mbedtls/base64.h>
#include <esp_ota_ops.h>
#include "src/extras/Blinker.h"
#include "src/extras/Pixel.h"
@@ -49,7 +52,7 @@
#include "Settings.h"
#include "Utils.h"
#include "Network.h"
#include "Network_HS.h"
#include "HAPConstants.h"
#include "HapQR.h"
#include "Characteristics.h"
@@ -58,6 +61,7 @@
using std::vector;
using std::unordered_map;
using std::list;
using std::string;
enum {
GET_AID=1,
@@ -85,6 +89,16 @@ typedef std::pair<const uint8_t *, size_t> DATA_t;
static DATA_t NULL_DATA={NULL,0};
static TLV8 NULL_TLV{};
///////////////////////////////
// Macros to lock/unlock poll() mutex
#define homeSpanPAUSE std::shared_lock pollLock(homeSpan.getMutex());
#define homeSpanRESUME if(pollLock.owns_lock()){pollLock.unlock();}
///////////////////////////////
extern "C" bool verifyRollbackLater(); // declare pre-defined Arduino-ESP32 version, unless over-ridden in user sketch with #include "SpanRollback.h"
///////////////////////////////
#define STATUS_UPDATE(LED_UPDATE,MESSAGE_UPDATE) {homeSpan.statusLED->LED_UPDATE;if(homeSpan.statusCallback)homeSpan.statusCallback(MESSAGE_UPDATE);}
@@ -93,7 +107,7 @@ enum HS_STATUS {
HS_WIFI_NEEDED, // WiFi Credentials have not yet been set/stored
HS_WIFI_CONNECTING, // HomeSpan is trying to connect to the network specified in the stored WiFi Credentials
HS_PAIRING_NEEDED, // HomeSpan is connected to central WiFi network, but device has not yet been paired to HomeKit
HS_PAIRED, // HomeSpan is connected to central WiFi network and ther device has been paired to HomeKit
HS_PAIRED, // HomeSpan is connected to central WiFi network and the device has been paired to HomeKit
HS_ENTERING_CONFIG_MODE, // User has requested the device to enter into Command Mode
HS_CONFIG_MODE_EXIT, // HomeSpan is in Command Mode with "Exit Command Mode" specified as choice
HS_CONFIG_MODE_REBOOT, // HomeSpan is in Command Mode with "Reboot" specified as choice
@@ -110,7 +124,37 @@ enum HS_STATUS {
HS_AP_STARTED, // HomeSpan has started the Access Point but no one has yet connected
HS_AP_CONNECTED, // The Access Point is started and a user device has been connected
HS_AP_TERMINATED, // HomeSpan has terminated the Access Point
HS_OTA_STARTED // HomeSpan is in the process of recveived an Over-the-Air software update
HS_OTA_STARTED, // HomeSpan is in the process of receiving an Over-the-Air software update
HS_WIFI_SCANNING, // HomeSpan is in the process of scanning for WiFi networks
HS_ETH_CONNECTING // HomeSpan is trying to connect to an Ethernet network
};
//////////////////////////////////////////////////////////
// Paired Controller Structure for Permanently-Stored Data
class Controller {
friend class HAPClient;
boolean allocated=false; // DEPRECATED (but needed for backwards compatability with original NVS storage of Controller info)
boolean admin; // Controller has admin privileges
uint8_t ID[36]; // Pairing ID
uint8_t LTPK[32]; // Long Term Ed2519 Public Key
public:
Controller(uint8_t *id, uint8_t *ltpk, boolean ad){
allocated=true;
admin=ad;
memcpy(ID,id,36);
memcpy(LTPK,ltpk,32);
}
Controller(){}
const uint8_t *getID() const {return(ID);}
const uint8_t *getLTPK() const {return(LTPK);}
boolean isAdmin() const {return(admin);}
};
///////////////////////////////
@@ -126,7 +170,6 @@ struct SpanButton;
struct SpanUserCommand;
struct HAPClient;
class Controller;
extern Span homeSpan;
@@ -157,6 +200,8 @@ struct SpanBuf{ // temporary storage buffer for us
StatusCode status; // return status (HAP Table 6-11)
SpanCharacteristic *characteristic=NULL; // Characteristic to update (NULL if not found)
};
typedef vector<SpanBuf, Mallocator<SpanBuf>> SpanBufVec;
///////////////////////////////
@@ -168,9 +213,10 @@ struct SpanWebLog{ // optional web status/log data
const char *timeZone; // optional time-zone specification
boolean timeInit=false; // flag to indicate time has been initialized
char bootTime[33]="Unknown"; // boot time
String statusURL; // URL of status log
char *statusURL=NULL; // URL of status log
uint32_t waitTime=120000; // number of milliseconds to wait for initial connection to time server
String css=""; // optional user-defined style sheet for web log
std::shared_mutex mux; // shared read/write lock
struct log_t { // log entry type
uint64_t upTime; // number of seconds since booting
@@ -182,6 +228,7 @@ struct SpanWebLog{ // optional web status/log data
void init(uint16_t maxEntries, const char *serv, const char *tz, const char *url);
static void initTime(void *args);
void vLog(boolean sysMsg, const char *fmr, va_list ap);
int check(const char *uri);
};
///////////////////////////////
@@ -216,8 +263,9 @@ class Span{
friend class SpanButton;
friend class SpanWebLog;
friend class SpanOTA;
friend class Network;
friend class Network_HS;
friend class HAPClient;
friend void init();
char *displayName; // display name for this device - broadcast as part of Bonjour MDNS
char *hostNameBase; // base of hostName of this device - full host name broadcast by Bonjour MDNS will have 6-byte accessoryID as well as '.local' automatically appended
@@ -236,7 +284,10 @@ class Span{
boolean serialInputDisabled=false; // flag indiating that serial input is disabled
uint8_t rebootCount=0; // counts number of times device was rebooted (used in optional Reboot callback)
uint32_t rebootCallbackTime; // length of time to wait (in milliseconds) before calling optional Reboot callback
boolean ethernetEnabled=false; // flag to indicate whether Ethernet is being used instead of WiFi
boolean initialPollingCompleted=false; // flag to indicate whether polling task has initially completed
char *compileTime=NULL; // optional compile time string --- can be set with call to setCompileTime()
nvs_handle charNVS; // handle for non-volatile-storage of Characteristics data
nvs_handle wifiNVS=0; // handle for non-volatile-storage of WiFi data
nvs_handle otaNVS; // handle for non-volatile storage of OTA data
@@ -244,8 +295,21 @@ class Span{
nvs_handle hapNVS; // handle for non-volatile-storage of HAP data
int connected=0; // WiFi connection status (increments upon each connect and disconnect)
unsigned long waitTime=60000; // time to wait (in milliseconds) between WiFi connection attempts
HS_ExpCounter wifiTimeCounter; // exponentially-increasing wait time counter between WiFi connection attempts
unsigned long alarmConnect=0; // time after which WiFi connection attempt should be tried again
static constexpr char delims[]="\"{[:,]}";
static const uint8_t DELIM = 0xF5;
static const uint8_t END_DELIM = DELIM+strlen(delims)-1;
void (*wifiBegin)(const char *s, const char *p)=[](const char *s, const char *p){WiFi.begin(s,p);}; // default call to WiFi.begin()
uint32_t rescanInitialTime=0;
uint32_t rescanPeriodicTime=0;
int rescanThreshold;
unsigned long rescanAlarm;
enum {RESCAN_IDLE, RESCAN_PENDING, RESCAN_RUNNING} rescanStatus=RESCAN_IDLE;
unordered_map<string, string> bssidNames;
const char *defaultSetupCode=DEFAULT_SETUP_CODE; // Setup Code used for pairing
uint16_t autoOffLED=0; // automatic turn-off duration (in seconds) for Status LED
@@ -253,8 +317,8 @@ class Span{
unsigned long comModeLife=DEFAULT_COMMAND_TIMEOUT*1000; // length of time (in milliseconds) to keep Command Mode alive before resuming normal operations
uint16_t tcpPortNum=DEFAULT_TCP_PORT; // port for TCP communications between HomeKit and HomeSpan
char qrID[5]=""; // Setup ID used for pairing with QR Code
void (*wifiCallback)()=NULL; // optional callback function to invoke once WiFi connectivity is initially established
void (*wifiCallbackAll)(int)=NULL; // optional callback function to invoke every time WiFi connectivity is established or re-established
void (*wifiCallback)()=NULL; // optional callback function to invoke once WiFi connectivity is initially established *** TO BE DEPRECATED ***
void (*connectionCallback)(int)=NULL; // optional callback function to invoke every time WiFi or Ethernet connectivity is established or re-established
void (*weblogCallback)(String &)=NULL; // optional callback function to invoke after header table in Web Log is produced
void (*pairCallback)(boolean isPaired)=NULL; // optional callback function to invoke when pairing is established (true) or lost (false)
boolean autoStartAPEnabled=false; // enables auto start-up of Access Point when WiFi Credentials not found
@@ -262,16 +326,20 @@ class Span{
void (*statusCallback)(HS_STATUS status)=NULL; // optional callback when HomeSpan status changes
void (*rebootCallback)(uint8_t)=NULL; // optional callback when device reboots
void (*controllerCallback)()=NULL; // optional callback when Controller is added/removed/changed
void (*pollingCallback)()=NULL; // optional callback when polling task reaching initial completion (only called once)
void (*getCharacteristicsCallback)(const char *)=NULL; // optional callback function to invoke every time HomeKit sends a getCharacteristics request
WiFiServer *hapServer; // pointer to the HAP Server connection
NetworkServer *hapServer; // pointer to the HAP Server connection
Blinker *statusLED; // indicates HomeSpan status
Blinkable *statusDevice = NULL; // the device used for the Blinker
PushButton *controlButton = NULL; // controls HomeSpan configuration and resets
Network network; // configures WiFi and Setup Code via either serial monitor or temporary Access Point
Network_HS network; // configures WiFi and Setup Code via either serial monitor or temporary Access Point
SpanWebLog webLog; // optional web status/log
TaskHandle_t pollTaskHandle = NULL; // optional task handle to use for poll() function
TaskHandle_t loopTaskHandle; // Arduino Loop Task handle
boolean verboseWifiReconnect = true; // set to false to not print WiFi reconnect attempts messages
std::shared_mutex pollMutex; // mutex lock for poll task
hsWatchdogTimer hsWDT; // general homeSpan watchdog timer
SpanOTA spanOTA; // manages OTA process
SpanConfig hapConfig; // track configuration changes to the HAP Accessory database; used to increment the configuration number (c#) when changes found
@@ -280,26 +348,27 @@ class Span{
list<HAPClient, Mallocator<HAPClient>>::iterator currentClient; // iterator to current client
vector<SpanAccessory *, Mallocator<SpanAccessory *>> Accessories; // vector of pointers to all Accessories
vector<SpanService *, Mallocator<SpanService *>> Loops; // vector of pointer to all Services that have over-ridden loop() methods
vector<SpanBuf, Mallocator<SpanBuf>> Notifications; // vector of SpanBuf objects that store info for Characteristics that are updated with setVal() and require a Notification Event
SpanBufVec Notifications; // vector of SpanBuf objects that store info for Characteristics that are updated with setVal() and require a Notification Event
vector<SpanButton *, Mallocator<SpanButton *>> PushButtons; // vector of pointer to all PushButtons
unordered_map<uint64_t, uint32_t> TimedWrites; // map of timed-write PIDs and Alarm Times (based on TTLs)
unordered_map<char, SpanUserCommand *> UserCommands; // map of pointers to all UserCommands
void pollTask(); // poll HAP Clients and process any new HAP requests
void checkConnect(); // check WiFi connection; connect if needed
void commandMode(); // allows user to control and reset HomeSpan settings with the control button
void resetStatus(); // resets statusLED and calls statusCallback based on current HomeSpan status
void reboot(); // reboots device
void pollTask(); // poll HAP Clients and process any new HAP requests
void configureNetwork(); // configure Network services (MDNS, WebLog, OTA, etc.) and start HAP Server
void commandMode(); // allows user to control and reset HomeSpan settings with the control button
void resetStatus(); // resets statusLED and calls statusCallback based on current HomeSpan status
void reboot(); // reboots device
void printfAttributes(int flags=GET_VALUE|GET_META|GET_PERMS|GET_TYPE|GET_DESC); // writes Attributes JSON database to hapOut stream
SpanCharacteristic *find(uint32_t aid, uint32_t iid); // return Characteristic with matching aid and iid (else NULL if not found)
int countCharacteristics(char *buf); // return number of characteristic objects referenced in PUT /characteristics JSON request
int updateCharacteristics(char *buf, SpanBuf *pObj); // parses PUT /characteristics JSON request 'buf into 'pObj' and updates referenced characteristics; returns 1 on success, 0 on fail
void printfAttributes(SpanBuf *pObj, int nObj); // writes SpanBuf objects to hapOut stream
boolean printfAttributes(char **ids, int numIDs, int flags); // writes accessory requested characteristic ids to hapOut stream - returns true if all characteristics are found and readable, else returns false
void clearNotify(HAPClient *hc); // clear all notifications related to specific client connection
void printfNotify(SpanBuf *pObj, int nObj, HAPClient *hc); // writes notification JSON to hapOut stream based on SpanBuf objects and specified connection
SpanCharacteristic *find(uint32_t aid, uint32_t iid); // return Characteristic with matching aid and iid (else NULL if not found)
void printfAttributes(SpanBufVec &pVec); // writes SpanBuf objects to hapOut stream
boolean printfAttributes(char **ids, int numIDs, int flags); // writes accessory requested characteristic ids to hapOut stream - returns true if all characteristics are found and readable, else returns false
void clearNotify(HAPClient *hc); // clear all notifications related to specific client connection
void printfNotify(SpanBufVec &pVec, HAPClient *hc); // writes notification JSON to hapOut stream based on SpanBuf objects and specified connection
char *escapeJSON(char *jObj); // remove all whitespace not within double-quotes, and converts special characters to unused UTF-8 bytes as a placeholder
char *unEscapeJSON(char *jObj); // converts UTF-8 placeholder bytes back to original special characters
boolean updateCharacteristics(char *buf, SpanBufVec &pVec); // parses PUT /characteristics JSON request and updates referenced characteristics; returns true on success, false on fail
static boolean invalidUUID(const char *uuid){
int x=0;
@@ -309,10 +378,15 @@ class Span{
sscanf(uuid,"%*8[0-9a-fA-F]-%*4[0-9a-fA-F]-%*4[0-9a-fA-F]-%*4[0-9a-fA-F]-%*12[0-9a-fA-F]%n",&x);
return(strlen(uuid)!=36 || x!=36);
}
QueueHandle_t networkEventQueue; // queue to transmit network events from callback thread to HomeSpan thread
void networkCallback(WiFiEvent_t event); // network event handler (works for WiFi as well as Ethernet)
void init(); // performs all late-stage initializations needed
public:
Span(); // constructor
Span(); // constructor
void begin(Category catID=DEFAULT_CATEGORY,
const char *displayName=DEFAULT_DISPLAY_NAME,
@@ -333,10 +407,10 @@ class Span{
int getControlPin(){return(controlButton?controlButton->getPin():-1);} // get Control Pin (returns -1 if undefined)
Span& setStatusPin(uint8_t pin){statusDevice=new GenericLED(pin);return(*this);} // sets Status Device to a simple LED on specified pin
Span& setStatusPixel(uint8_t pin,float h=0,float s=100,float v=100){ // sets Status Device to an RGB Pixel on specified pin
statusDevice=((new Pixel(pin))->setOnColor(Pixel::HSV(h,s,v)));
return(*this);
}
Span& setStatusPixel(uint8_t pin,float h=0,float s=100,float v=100){ // sets Status Device to an RGB Pixel on specified pin
statusDevice=((new Pixel(pin))->setOnColor(Pixel::HSV(h,s,v)));
return(*this);
}
Span& setStatusDevice(Blinkable *sDev){statusDevice=sDev;return(*this);} // sets Status Device to a generic Blinkable object
Span& setStatusAutoOff(uint16_t duration){autoOffLED=duration;return(*this);} // sets Status LED auto off (seconds)
@@ -355,24 +429,31 @@ class Span{
Span& setQRID(const char *id); // sets the Setup ID for optional pairing with a QR Code
Span& setSketchVersion(const char *sVer){sketchVersion=sVer;return(*this);} // set optional sketch version number
const char *getSketchVersion(){return sketchVersion;} // get sketch version number
Span& setWifiCallback(void (*f)()){wifiCallback=f;return(*this);} // sets an optional user-defined function to call once WiFi connectivity is initially established
Span& setWifiCallbackAll(void (*f)(int)){wifiCallbackAll=f;return(*this);} // sets an optional user-defined function to call every time WiFi connectivity is established or re-established
Span& setConnectionCallback(void (*f)(int)){connectionCallback=f;return(*this);} // sets an optional user-defined function to call every time WiFi or Ethernet connectivity is established or re-established
Span& setPairCallback(void (*f)(boolean isPaired)){pairCallback=f;return(*this);} // sets an optional user-defined function to call when Pairing is established (true) or lost (false)
Span& setApFunction(void (*f)()){apFunction=f;return(*this);} // sets an optional user-defined function to call when activating the WiFi Access Point
Span& enableAutoStartAP(){autoStartAPEnabled=true;return(*this);} // enables auto start-up of Access Point when WiFi Credentials not found
Span& setWifiCredentials(const char *ssid, const char *pwd); // sets WiFi Credentials
Span& setConnectionTimes(uint32_t minTime, uint32_t maxTime, uint8_t nSteps); // sets min/max WiFi connection times (in seconds) and number of steps
Span& setStatusCallback(void (*f)(HS_STATUS status)){statusCallback=f;return(*this);} // sets an optional user-defined function to call when HomeSpan status changes
const char* statusString(HS_STATUS s); // returns char string for HomeSpan status change messages
Span& setPairingCode(const char *s, boolean progCall=true); // sets the Pairing Code - use is NOT recommended. Use 'S' from CLI instead
void deleteStoredValues(){processSerialCommand("V");} // deletes stored Characteristic values from NVS
Span& resetIID(uint32_t newIID); // resets the IID count for the current Accessory to start at newIID
Span& setControllerCallback(void (*f)()){controllerCallback=f;return(*this);} // sets an optional user-defined function to call whenever a Controller is added/removed/changed
Span& setHostNameSuffix(const char *suffix){asprintf(&hostNameSuffix,"%s",suffix);return(*this);} // sets the hostName suffix to be used instead of the 6-byte AccessoryID
Span& setControllerCallback(void (*f)()){controllerCallback=f;return(*this);} // sets an optional user-defined function to call whenever a Controller is added/removed
Span& setWifiBegin(void (*f)(const char *, const char *)){wifiBegin=f;return(*this);} // sets an optional user-defined function to over-ride WiFi.begin() with additional logic
Span& setPollingCallback(void (*f)()){pollingCallback=f;return(*this);} // sets an optional user-defined function to call upon INITIAL completion of the polling task (only called once)
Span& useEthernet(){ethernetEnabled=true;return(*this);} // force use of Ethernet instead of WiFi, even if ETH not called or Ethernet card not detected
Span& setGetCharacteristicsCallback(void (*f)(const char *)){getCharacteristicsCallback=f;return(*this);} // sets an optional callback called whenever HomeKit sends a getCharacteristics request
Span& setHostNameSuffix(const char *suffix){asprintf(&hostNameSuffix,"%s",suffix);return(*this);} // sets the hostName suffix to be used instead of the 6-byte AccessoryID
Span& setCompileTime(const char *compTime=__DATE__ " " __TIME__){asprintf(&compileTime,"%s",compTime);return(*this);} // sets the compile time to compTime; default is to use compiler-provided date/time
int enableOTA(boolean auth=true, boolean safeLoad=true){return(spanOTA.init(auth, safeLoad, NULL));} // enables Over-the-Air updates, with (auth=true) or without (auth=false) authorization password
int enableOTA(const char *pwd, boolean safeLoad=true){return(spanOTA.init(true, safeLoad, pwd));} // enables Over-the-Air updates, with custom authorization password (overrides any password stored with the 'O' command)
void markSketchOK(){esp_ota_mark_app_valid_cancel_rollback();}
Span& enableWebLog(uint16_t maxEntries=0, const char *serv=NULL, const char *tz="UTC", const char *url=DEFAULT_WEBLOG_URL){ // enable Web Logging
webLog.init(maxEntries, serv, tz, url);
return(*this);
@@ -388,32 +469,43 @@ class Span{
Span& setWebLogCSS(const char *css){webLog.css="\n" + String(css) + "\n";return(*this);}
Span& setWebLogCallback(void (*f)(String &)){weblogCallback=f;return(*this);}
void getWebLog(void (*f)(const char *, void *), void *);
void assumeTimeAcquired(){webLog.timeInit=true;}
Span& setVerboseWifiReconnect(bool verbose=true){verboseWifiReconnect=verbose;return(*this);}
Span& setRebootCallback(void (*f)(uint8_t),uint32_t t=DEFAULT_REBOOT_CALLBACK_TIME){rebootCallback=f;rebootCallbackTime=t;return(*this);}
void autoPoll(uint32_t stackSize=8192, uint32_t priority=1, uint32_t cpu=0){ // start pollTask()
xTaskCreateUniversal([](void *parms){
for(;;){
homeSpan.pollTask();
vTaskDelay(5);
}
},
"pollTask", stackSize, NULL, priority, &pollTaskHandle, cpu);
LOG0("\n*** AutoPolling Task started with priority=%d\n\n",uxTaskPriorityGet(pollTaskHandle));
std::shared_mutex& getMutex(){return(pollMutex);}
void autoPoll(uint32_t stackSize=8192, uint32_t priority=1, uint32_t core=0){
xTaskCreateUniversal( [](void *parms){for(;;)homeSpan.pollTask();}, "pollTask", stackSize, NULL, priority, &pollTaskHandle, core);
}
TaskHandle_t getAutoPollTask(){return(pollTaskHandle);}
Span& setTimeServerTimeout(uint32_t tSec){webLog.waitTime=tSec*1000;return(*this);} // sets wait time (in seconds) for optional web log time server to connect
Span& enableWiFiRescan(uint32_t iTime=1, uint32_t pTime=0, int thresh=3){ // enables periodic WiFi rescan to search for stronger BSSID
rescanInitialTime=iTime*60000;
rescanPeriodicTime=pTime*60000;
rescanThreshold=thresh;
return(*this);
}
Span& enableWatchdog(uint16_t nSeconds=CONFIG_ESP_TASK_WDT_TIMEOUT_S){hsWDT.enable(nSeconds);return(*this);} // enables HomeSpan watchdog with timeout of nSeconds
void disableWatchdog(){hsWDT.disable();} // disables HomeSpan watchdog
void resetWatchdog(){hsWDT.reset();} // resets HomeSpan watchdog
Span& addBssidName(String bssid, string name){bssid.toUpperCase();bssidNames[bssid.c_str()]=name;return(*this);}
list<Controller, Mallocator<Controller>>::const_iterator controllerListBegin();
list<Controller, Mallocator<Controller>>::const_iterator controllerListEnd();
[[deprecated("This function has been deprecated (it is not needed) and no longer does anything. Please remove from sketch to ensure backwards compatilibilty with future versions.")]]
Span& reserveSocketConnections(uint8_t n){return(*this);}
[[deprecated("This homeSpan method has been deprecated and will be removed in a future version. Please use the more generic setConnectionCallback() method instead.")]]
Span& setWifiCallback(void (*f)()){wifiCallback=f;return(*this);} // sets an optional user-defined function to call once WiFi connectivity is initially established
[[deprecated("This homeSpan method has been deprecated and will be removed in a future version. Please use the more generic setConnectionCallback() method instead.")]]
Span& setWifiCallbackAll(void (*f)(int)){connectionCallback=f;return(*this);} // sets an optional user-defined function to call every time WiFi connectivity is established or re-established
};
///////////////////////////////
@@ -438,7 +530,10 @@ class SpanAccessory{
public:
void *operator new(size_t size){return(HS_MALLOC(size));} // override new operator to use PSRAM when available
void operator delete(void *p){free(p);}
SpanAccessory(uint32_t aid=0); // constructor
uint32_t getAID(){return(aid);}
};
///////////////////////////////
@@ -470,6 +565,8 @@ class SpanService{
public:
void *operator new(size_t size){return(HS_MALLOC(size));} // override new operator to use PSRAM when available
void operator delete(void *p){free(p);}
SpanService(const char *type, const char *hapName, boolean isCustom=false); // constructor
SpanService *setPrimary(); // sets the Service Type to be primary and returns pointer to self
SpanService *setHidden(); // sets the Service Type to be hidden and returns pointer to self
@@ -485,6 +582,7 @@ class SpanService{
}
uint32_t getIID(){return(iid);} // returns IID of Service
uint32_t getAID(){return(accessory->aid);} // returns AID of enclosing Accessory
virtual boolean update() {return(true);} // placeholder for code that is called when a Service is updated via a Controller. Must return true/false depending on success of update
virtual void loop(){} // loops for each Service - called every cycle if over-ridden with user-defined code
@@ -517,6 +615,7 @@ class SpanCharacteristic{
};
uint32_t iid=0; // Instance ID (HAP Table 6-3)
uint32_t aid=0; // AID for the enclosing Accessory
HapChar *hapChar; // pointer to HAP Characteristic structure
const char *type; // Characteristic Type
const char *hapName; // HAP Name
@@ -528,6 +627,7 @@ class SpanCharacteristic{
UVal minValue; // Characteristic minimum (not applicable for STRING)
UVal maxValue; // Characteristic maximum (not applicable for STRING)
UVal stepValue; // Characteristic step size (not applicable for STRING)
uint8_t maxLen=0; // Characteristic maximum length (only applicable for STRING, 0=default)
boolean staticRange; // Flag that indicates whether Range is static and cannot be changed with setRange()
boolean customRange=false; // Flag for custom ranges
char *validValues=NULL; // Optional JSON array of valid values. Applicable only to uint8 Characteristics
@@ -536,7 +636,6 @@ class SpanCharacteristic{
boolean setRangeError=false; // flag to indicate attempt to set Range on Characteristic that does not support changes to Range
boolean setValidValuesError=false; // flag to indicate attempt to set Valid Values on Characteristic that does not support changes to Valid Values
uint32_t aid=0; // Accessory ID - passed through from Service containing this Characteristic
uint8_t updateFlag=0; // set to either 1 (for normal write) or 2 (for write-response) inside update() when Characteristic is successfully updated via Home App
unsigned long updateTime=0; // last time value was updated (in millis) either by PUT /characteristic OR by setVal()
UVal newValue; // the updated value requested by PUT /characteristic
@@ -622,7 +721,7 @@ class SpanCharacteristic{
nvsKey=(char *)HS_MALLOC(16);
uint16_t t;
sscanf(type,"%hx",&t);
sprintf(nvsKey,"%04X%08X%03X",t,aid,iid&0xFFF);
sprintf(nvsKey,"%04X%08lX%03lX",t,aid,iid&0xFFF);
size_t len;
if(format<FORMAT::STRING){
@@ -656,6 +755,7 @@ class SpanCharacteristic{
SpanCharacteristic(HapChar *hapChar, boolean isCustom=false); // SpanCharacteristic constructor
void *operator new(size_t size){return(HS_MALLOC(size));} // override new operator to use PSRAM when available
void operator delete(void *p){free(p);}
template <class T=int> T getVal(){return(uvGet<T>(value));} // gets the value for numeric-based Characteristics
char *getString(){return(getStringGeneric(value));} // gets the value for string-based Characteristics
@@ -706,6 +806,8 @@ class SpanCharacteristic{
boolean updated(); // returns true within update() if Characteristic was updated by Home App
unsigned long timeVal(); // returns time elapsed (in millis) since value was last updated, either by Home App or by using setVal()
uint32_t getIID(); // returns IID of Characteristic
uint32_t getAID(); // returns AID of enclosing Accessory
boolean foundIn(const char *getCharList); // returns true if Characteristics is found in getCharList, else returns false
SpanCharacteristic *setPerms(uint8_t perms); // sets permissions of a Characteristic
SpanCharacteristic *addPerms(uint8_t dPerms); // add permissions of a Characteristic
@@ -713,6 +815,7 @@ class SpanCharacteristic{
SpanCharacteristic *setDescription(const char *c); // sets description of a Characteristic
SpanCharacteristic *setUnit(const char *c); // set unit of a Characteristic
SpanCharacteristic *setValidValues(int n, ...); // sets a list of 'n' valid values allowed for a Characteristic - only applicable if format=INT, UINT8, UINT16, or UINT32
SpanCharacteristic *setMaxStringLength(uint8_t n); // sets maximum length of STRING Characteristics
template <typename A, typename B, typename S=int> SpanCharacteristic *setRange(A min, B max, S step=0){ // sets the allowed range of a Characteristic
@@ -807,7 +910,7 @@ class SpanPoint {
static QueueHandle_t statusQueue; // queue for communication between SpanPoint::dataSend and SpanPoint::send
static nvs_handle pointNVS; // NVS storage for channel number (only used for remote devices)
static void dataReceived(const uint8_t *mac, const uint8_t *incomingData, int len);
static void dataReceived(const esp_now_recv_info *info, const uint8_t *incomingData, int len);
static void init(const char *password="HomeSpan");
static void setAsHub(){isHub=true;}
static uint8_t nextChannel();

View File

@@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
* Copyright (c) 2020-2025 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
@@ -29,7 +29,7 @@
#include <DNSServer.h>
#include "Network.h"
#include "Network_HS.h"
#include "HomeSpan.h"
#include "Utils.h"
@@ -37,8 +37,10 @@ using namespace Utils;
///////////////////////////////
void Network::scan(){
void Network_HS::scan(){
WiFi.scanDelete();
STATUS_UPDATE(start(LED_WIFI_SCANNING),HS_WIFI_SCANNING)
int n=WiFi.scanNetworks();
free(ssidList);
@@ -62,7 +64,7 @@ void Network::scan(){
///////////////////////////////
void Network::serialConfigure(){
void Network_HS::serialConfigure(){
wifiData.ssid[0]='\0';
wifiData.pwd[0]='\0';
@@ -94,7 +96,7 @@ void Network::serialConfigure(){
///////////////////////////////
boolean Network::allowedCode(char *s){
boolean Network_HS::allowedCode(char *s){
return(
strcmp(s,"00000000") && strcmp(s,"11111111") && strcmp(s,"22222222") && strcmp(s,"33333333") &&
strcmp(s,"44444444") && strcmp(s,"55555555") && strcmp(s,"66666666") && strcmp(s,"77777777") &&
@@ -103,12 +105,10 @@ boolean Network::allowedCode(char *s){
///////////////////////////////
void Network::apConfigure(){
void Network_HS::apConfigure(){
LOG0("*** Starting Access Point: %s / %s\n",apSSID,apPassword);
STATUS_UPDATE(start(LED_AP_STARTED),HS_AP_STARTED)
LOG0("\nScanning for Networks...\n\n");
scan(); // scan for networks
@@ -116,7 +116,9 @@ void Network::apConfigure(){
for(int i=0;i<numSSID;i++)
LOG0(" %d) %s\n",i+1,ssidList[i]);
WiFiServer apServer(80);
STATUS_UPDATE(start(LED_AP_STARTED),HS_AP_STARTED)
NetworkServer apServer(80);
client=0;
const byte DNS_PORT = 53;
@@ -161,7 +163,7 @@ void Network::apConfigure(){
dnsServer.processNextRequest();
if(client=apServer.available()){ // found a new HTTP client
if((client=apServer.accept())){ // found a new HTTP client
LOG2("=======================================\n");
LOG1("** Access Point Client Connected: (");
LOG1(millis()/1000);
@@ -229,13 +231,14 @@ void Network::apConfigure(){
} // process Client
homeSpan.resetWatchdog();
} // while 1
}
///////////////////////////////
void Network::processRequest(char *body, char *formData){
void Network_HS::processRequest(char *body, char *formData){
String responseHead="HTTP/1.1 200 OK\r\nContent-type: text/html\r\n";
@@ -261,9 +264,10 @@ void Network::processRequest(char *body, char *formData){
STATUS_UPDATE(start(LED_WIFI_CONNECTING),HS_WIFI_CONNECTING)
responseBody+="<meta http-equiv = \"refresh\" content = \"" + String(waitTime) + "; url = /wifi-status\" />"
"<p>Initiating WiFi connection to:</p><p><b>" + String(wifiData.ssid) + "</p>";
responseBody+="<meta http-equiv = \"refresh\" content = \"" + String(homeSpan.wifiTimeCounter/1000) + "; url = /wifi-status\" />"
"<p>Initiating WiFi connection to:</p><p><b>" + String(wifiData.ssid) + "</b></p>"
"<p>(waiting " + String((homeSpan.wifiTimeCounter++)/1000) + " seconds to check for response)</p>";
WiFi.begin(wifiData.ssid,wifiData.pwd);
} else
@@ -294,12 +298,9 @@ void Network::processRequest(char *body, char *formData){
LOG1("In Get WiFi Status...\n");
if(WiFi.status()!=WL_CONNECTED){
waitTime+=2;
if(waitTime==12)
waitTime=2;
responseHead+="Refresh: " + String(waitTime) + "\r\n";
responseHead+="Refresh: " + String(homeSpan.wifiTimeCounter/1000) + "\r\n";
responseBody+="<p>Re-initiating connection to:</p><p><b>" + String(wifiData.ssid) + "</b></p>";
responseBody+="<p>(waiting " + String(waitTime) + " seconds to check for response)</p>";
responseBody+="<p>(waiting " + String((homeSpan.wifiTimeCounter++)/1000) + " seconds to check for response)</p>";
responseBody+="<p>Access Point termination in " + String((alarmTimeOut-millis())/1000) + " seconds.</p>";
responseBody+="<center><button onclick=\"document.location='/hotspot-detect.html'\">Cancel</button></center>";
WiFi.begin(wifiData.ssid,wifiData.pwd);
@@ -327,7 +328,7 @@ void Network::processRequest(char *body, char *formData){
LOG1("In Landing Page...\n");
STATUS_UPDATE(start(LED_AP_CONNECTED),HS_AP_CONNECTED)
waitTime=2;
homeSpan.wifiTimeCounter.reset();
responseBody+="<p>Welcome to HomeSpan! This page allows you to configure the above HomeSpan device to connect to your WiFi network.</p>"
"<p>The LED on this device should be <em>double-blinking</em> during this configuration.</p>"
@@ -368,9 +369,9 @@ void Network::processRequest(char *body, char *formData){
//////////////////////////////////////
int Network::getFormValue(char *formData, const char *tag, char *value, int maxSize){
int Network_HS::getFormValue(const char *formData, const char *tag, char *value, int maxSize){
char *s=strstr(formData,tag); // find start of tag
char *s=strcasestr(formData,tag); // find start of tag
if(!s) // if not found, return -1
return(-1);
@@ -380,7 +381,7 @@ int Network::getFormValue(char *formData, const char *tag, char *value, int maxS
if(!v) // if not found, return -1 (this should not happen)
return(-1);
v++; // point to begining of value
v++; // point to beginning of value
int len=0; // track length of value
while(*v!='\0' && *v!='&' && len<maxSize){ // copy the value until null, '&', or maxSize is reached
@@ -402,7 +403,7 @@ int Network::getFormValue(char *formData, const char *tag, char *value, int maxS
//////////////////////////////////////
int Network::badRequestError(){
int Network_HS::badRequestError(){
char s[]="HTTP/1.1 400 Bad Request\r\n\r\n";
LOG2("\n>>>>>>>>>> ");
@@ -419,3 +420,41 @@ int Network::badRequestError(){
}
//////////////////////////////////////
HS_ExpCounter::HS_ExpCounter(uint32_t _minCount, uint32_t _maxCount, uint8_t _totalSteps){
config(_minCount,_maxCount,_totalSteps);
}
void HS_ExpCounter::config(uint32_t _minCount, uint32_t _maxCount, uint8_t _totalSteps){
if(_minCount==0 || _maxCount==0 || _totalSteps==0){
ESP_LOGE("HS_Counter","call to config(%ld,%ld,%d) ignored: all parameters must be non-zero\n",_minCount,_maxCount,_totalSteps);
} else {
minCount=_minCount;
maxCount=_maxCount;
totalSteps=_totalSteps;
}
reset();
}
void HS_ExpCounter::reset(){
nStep=0;
}
HS_ExpCounter::operator uint32_t(){
return(minCount*pow((double)maxCount/(double)minCount,(double)nStep/(double)totalSteps));
}
HS_ExpCounter& HS_ExpCounter::operator++(){
nStep++;
if(nStep>totalSteps)
nStep=0;
return(*this);
}
HS_ExpCounter HS_ExpCounter::operator++(int){
HS_ExpCounter temp=*this;
operator++();
return(temp);
}
//////////////////////////////////////

View File

@@ -0,0 +1,90 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2025 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
* 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.
*
********************************************************************************/
#pragma once
#include <Arduino.h>
#include <WiFi.h>
#include "Settings.h"
const int MAX_SSID=32; // max number of characters in WiFi SSID
const int MAX_PWD=64; // max number of characters in WiFi Password
///////////////////////////////
struct Network_HS {
const int MAX_HTTP=4095; // max number of bytes in HTTP message
const char *apSSID=DEFAULT_AP_SSID; // Access Point SSID
const char *apPassword=DEFAULT_AP_PASSWORD; // Access Point password (does not need to be secret - only used to ensure encrypted WiFi connection)
unsigned long lifetime=DEFAULT_AP_TIMEOUT*1000; // length of time (in milliseconds) to keep Access Point alive before shutting down and restarting
char **ssidList=NULL;
int numSSID;
NetworkClient client; // client used for HTTP calls
unsigned long alarmTimeOut; // alarm time after which access point is shut down and HomeSpan is re-started
int apStatus; // tracks access point status (0=timed-out, -1=cancel, 1=save)
struct {
char ssid[MAX_SSID+1]="";
char pwd[MAX_PWD+1]="";
} wifiData;
char setupCode[8+1];
void scan(); // scan for WiFi networks and save only those with unique SSIDs
void serialConfigure(); // configure homeSpan WiFi from serial monitor
boolean allowedCode(char *s); // checks if Setup Code is allowed (HAP defines a list of disallowed codes)
void apConfigure(); // configure homeSpan WiFi and Setup Code using temporary Captive Access Point; only returns if sucessful, else ESP restarts
void processRequest(char *body, char *formData); // process the HTTP request
int badRequestError(); // return 400 error
static int getFormValue(const char *formData, const char *tag, char *value, int maxSize); // search for 'tag' in 'formData' and copy result into 'value' up to 'maxSize' characters; returns number of characters, else -1 if 'tag' not found
};
///////////////////////////////
class HS_ExpCounter{
uint8_t nStep;
uint32_t minCount;
uint32_t maxCount;
uint8_t totalSteps;
public:
HS_ExpCounter(uint32_t _minCount=5000, uint32_t _maxCount=60000, uint8_t _totalSteps=5);
void config(uint32_t _minCount, uint32_t _maxCount, uint8_t _totalSteps);
void reset();
operator uint32_t();
HS_ExpCounter& operator++();
HS_ExpCounter operator++(int);
};
///////////////////////////////

View File

@@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
* Copyright (c) 2020-2025 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*

View File

@@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
* Copyright (c) 2020-2025 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
@@ -100,10 +100,10 @@ void SRP6A::createVerifyCode(const char *setupCode, Verification *vData){
// compute x = SHA512( s | SHA512( I | ":" | P ) )
memcpy(tBuf,vData->salt,16); // write salt into first 16 bytes of staging buffer
mbedtls_sha512_ret((uint8_t *)icp,strlen(icp),tBuf+16,0); // create hash of username:password and write into last 64 bytes of staging buffer
mbedtls_sha512_ret(tBuf,80,tHash,0); // create second hash of salted, hashed username:password
mbedtls_mpi_read_binary(&x,tHash,64); // load hash result into x
memcpy(tBuf,vData->salt,16); // write salt into first 16 bytes of staging buffer
mbedtls_sha512((uint8_t *)icp,strlen(icp),tBuf+16,0); // create hash of username:password and write into last 64 bytes of staging buffer
mbedtls_sha512(tBuf,80,tHash,0); // create second hash of salted, hashed username:password
mbedtls_mpi_read_binary(&x,tHash,64); // load hash result into x
// compute v = g^x %N
@@ -136,7 +136,7 @@ void SRP6A::createPublicKey(const Verification *vData, uint8_t *publicKey){
mbedtls_mpi_write_binary(&N,tBuf,384); // write N into first half of staging buffer
mbedtls_mpi_write_binary(&g,tBuf+384,384); // write g into second half of staging buffer (fully padded with leading zeros)
mbedtls_sha512_ret(tBuf,768,tHash,0); // create hash of data
mbedtls_sha512(tBuf,768,tHash,0); // create hash of data
mbedtls_mpi_read_binary(&k,tHash,64); // load hash result into k
// compute B = (k*v + g^b) %N
@@ -163,7 +163,7 @@ void SRP6A::createSessionKey(const uint8_t *publicKey, size_t len){
mbedtls_mpi_write_binary(&A,tBuf,384); // write A into first half of staging buffer (padding with initial zeros is less than 384 bytes)
mbedtls_mpi_write_binary(&B,tBuf+384,384); // write B into second half of staging buffer (padding with initial zeros is less than 384 bytes)
mbedtls_sha512_ret(tBuf,768,tHash,0); // create hash of data
mbedtls_sha512(tBuf,768,tHash,0); // create hash of data
mbedtls_mpi_read_binary(&u,tHash,64); // load hash result into mpi structure u
// compute S = (A * v^u)^b %N
@@ -176,7 +176,7 @@ void SRP6A::createSessionKey(const uint8_t *publicKey, size_t len){
// compute K = SHA512( PAD(S) )
mbedtls_mpi_write_binary(&S,tBuf,384); // write S into staging buffer (only first half of buffer will be used)
mbedtls_sha512_ret(tBuf,384,K,0); // create hash of data - this is the SRP SHARED SESSION KEY, K
mbedtls_sha512(tBuf,384,K,0); // create hash of data - this is the SRP SHARED SESSION KEY, K
}
@@ -196,13 +196,13 @@ int SRP6A::verifyClientProof(const uint8_t *proof){
// compute M1V = SHA512( SHA512(N) xor SHA512(g) | SHA512(I) | s | A | B | K )
mbedtls_mpi_write_binary(&N,tBuf,384); // write N into staging buffer
mbedtls_sha512_ret(tBuf,384,tHash,0); // create hash of data
mbedtls_sha512_ret(&g3072,1,tBuf,0); // create hash of g, but place output directly into staging buffer
mbedtls_sha512(tBuf,384,tHash,0); // create hash of data
mbedtls_sha512(&g3072,1,tBuf,0); // create hash of g, but place output directly into staging buffer
for(int i=0;i<64;i++) // H(g) -> H(g) XOR H(N), with results in first 64 bytes of staging buffer
tBuf[i]^=tHash[i];
mbedtls_sha512_ret((uint8_t *)I,strlen(I),tBuf+64,0); // create hash of userName and concatenate result to end of staging buffer
mbedtls_sha512((uint8_t *)I,strlen(I),tBuf+64,0); // create hash of userName and concatenate result to end of staging buffer
mbedtls_mpi_write_binary(&s,tBuf+128,16); // concatenate s to staging buffer
@@ -217,7 +217,7 @@ int SRP6A::verifyClientProof(const uint8_t *proof){
memcpy(tBuf+count,K,64); // concatenate K to staging buffer (should always be 64 bytes since it is a hashed value)
count+=64; // final total of bytes written to staging buffer
mbedtls_sha512_ret(tBuf,count,tHash,0); // create hash of data - this is M1V
mbedtls_sha512(tBuf,count,tHash,0); // create hash of data - this is M1V
if(!memcmp(M1,tHash,64)) // check that client Proof M1 matches M1V
return(1); // success - proof from HAP Client is verified
@@ -236,7 +236,7 @@ void SRP6A::createAccProof(uint8_t *proof){
mbedtls_mpi_write_binary(&A,tBuf,384); // write A into staging buffer
memcpy(tBuf+384,M1,64); // concatenate M1 (now verified) to staging buffer
memcpy(tBuf+448,K,64); // concatenate K to staging buffer
mbedtls_sha512_ret(tBuf,512,proof,0); // create hash of data writing directly to proof - this is M2
mbedtls_sha512(tBuf,512,proof,0); // create hash of data writing directly to proof - this is M2
}

View File

@@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
* Copyright (c) 2020-2025 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
@@ -90,6 +90,7 @@ struct SRP6A {
~SRP6A();
void *operator new(size_t size){return(HS_MALLOC(size));} // override new operator to use PSRAM when available
void operator delete(void *p){free(p);}
void createVerifyCode(const char *setupCode, Verification *vData); // generates random s and computes v; writes back resulting Verification Data
void createPublicKey(const Verification *vData, uint8_t *publicKey); // generates random b and computes k and B; writes back resulting Accessory Public Key

View File

@@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
* Copyright (c) 2020-2025 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
@@ -69,8 +69,9 @@
#define LED_ALERT 100 // rapid flashing
#define LED_WIFI_CONNECTING 2000 // slow flashing
#define LED_AP_STARTED 100,0.5,2,300 // rapid double-blink
#define LED_AP_CONNECTED 300,0.5,2,400 // medium double-blink
#define LED_AP_CONNECTED 300,0.5,2,400 // medium double-blink
#define LED_OTA_STARTED 300,0.5,3,400 // medium triple-blink
#define LED_WIFI_SCANNING 300,0.8,3,400 // medium inverted triple-blink
/////////////////////////////////////////////////////
// Message Log Level Control Macros //

View File

@@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
* Copyright (c) 2020-2025 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
@@ -475,7 +475,7 @@ namespace Service {
// Macro to define Span Characteristic structures based on name of HAP Characteristic, default value, and min/max value (not applicable for STRING or BOOL which default to min=0, max=1)
#define CREATE_CHAR(TYPE,HAPCHAR,DEFVAL,MINVAL,MAXVAL,...) \
struct HAPCHAR : SpanCharacteristic { __VA_OPT__(enum{) __VA_ARGS__ __VA_OPT__(};) HAPCHAR(TYPE val=DEFVAL, boolean nvsStore=false) : SpanCharacteristic {&hapChars.HAPCHAR} { init<TYPE>(val,nvsStore,MINVAL,MAXVAL); } };
struct HAPCHAR : SpanCharacteristic { __VA_OPT__(enum Value_t {) __VA_ARGS__ __VA_OPT__(};) HAPCHAR(TYPE val=DEFVAL, boolean nvsStore=false) : SpanCharacteristic {&hapChars.HAPCHAR} { init<TYPE>(val,nvsStore,MINVAL,MAXVAL); } };
namespace Characteristic {
@@ -525,7 +525,7 @@ namespace Characteristic {
CREATE_CHAR(UINT32_t,Identifier,0,0,255); // numerical Identifer of the <b>InputSource</b>.
CREATE_CHAR(UINT8_t,InputDeviceType,0,0,6); // not used
CREATE_CHAR(UINT8_t,InputSourceType,0,0,10); // not used
CREATE_CHAR(UINT8_t,InUse,0,0,1,NOT_IN_USE,IN_USE); // if Service is set to active, this indictes whether it is currently in use
CREATE_CHAR(UINT8_t,InUse,0,0,1,NOT_IN_USE,IN_USE); // if Service is set to active, this indicates whether it is currently in use
CREATE_CHAR(UINT8_t,IsConfigured,0,0,1,NOT_CONFIGURED,CONFIGURED); // indicates if a predefined Service has been configured
CREATE_CHAR(UINT8_t,LeakDetected,0,0,1,NOT_DETECTED,DETECTED); // indictates if a leak is detected
CREATE_CHAR(UINT8_t,LockCurrentState,0,0,3,UNLOCKED,LOCKED,JAMMED,UNKNOWN); // indicates state of a lock

View File

@@ -0,0 +1,32 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2025 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
* 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.
*
********************************************************************************/
#pragma once
// Override of weakly-defined Arduino-ESP32 function to enable auto rollback
extern "C" bool verifyRollbackLater() {return true;}

View File

@@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
* Copyright (c) 2020-2025 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
@@ -266,7 +266,7 @@ void TLV8::print(TLV8_itc it1, TLV8_itc it2) const {
if(it1->getLen()==0)
Serial.printf(" [null]");
else if(it1->getLen()<=4)
Serial.printf(" [%u]",it1->getVal());
Serial.printf(" [%lu]",it1->getVal());
else if(it1->getLen()<=8)
Serial.printf(" [%llu]",it1->getVal<uint64_t>());
Serial.printf("\n");

View File

@@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
* Copyright (c) 2020-2025 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*

View File

@@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
* Copyright (c) 2020-2025 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
@@ -34,8 +34,10 @@
//
// Utils::readSerial - reads all characters from Serial port and saves only up to max specified
// Utils::mask - masks a string with asterisks (good for displaying passwords)
// Utils::resetReason - returns literal string description of esp_reset_reason()
//
// class PushButton - tracks Single, Double, and Long Presses of a pushbutton that connects a specified pin to ground
// class hsWatchdogTimer - a generic watchdog timer that reboots the ESP32 device if not reset periodically
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -51,7 +53,8 @@ char *Utils::readSerial(char *c, int max){
while(1){
while(!Serial.available()); // wait until there is a new character
while(!Serial.available()) // wait until there is a new character
homeSpan.resetWatchdog();
buf=Serial.read();
@@ -101,6 +104,32 @@ String Utils::mask(char *c, int n){
return(s);
} // mask
//////////////////////////////////////
const char *Utils::resetReason(){
switch(esp_reset_reason()) {
case ESP_RST_UNKNOWN: return "Cannot be determined"; break;
case ESP_RST_POWERON: return "Power-on event"; break;
case ESP_RST_EXT: return "External pin"; break;
case ESP_RST_SW: return "Software reboot via esp_restart"; break;
case ESP_RST_PANIC: return "Software Exception/Panic"; break;
case ESP_RST_INT_WDT: return "Interrupt watchdog"; break;
case ESP_RST_TASK_WDT: return "Task watchdog"; break;
case ESP_RST_WDT: return "Other watchdogs"; break;
case ESP_RST_DEEPSLEEP: return "Exiting deep sleep mode"; break;
case ESP_RST_BROWNOUT: return "Brownout"; break;
case ESP_RST_SDIO: return "SDIO"; break;
case ESP_RST_USB: return "USB peripheral"; break;
case ESP_RST_JTAG: return "JTAG"; break;
case ESP_RST_EFUSE: return "Efuse error"; break;
case ESP_RST_PWR_GLITCH: return "Power glitch"; break;
case ESP_RST_CPU_LOCKUP: return "CPU Lockup"; break;
default: break;
}
return "Unknown Reset Code";
}
////////////////////////////////
// PushButton //
////////////////////////////////
@@ -123,12 +152,12 @@ PushButton::PushButton(int pin, triggerType_t triggerType){
for(int i=0;i<calibCount;i++)
threshold+=touchRead(pin);
threshold/=calibCount;
#if SOC_TOUCH_VERSION_1
#if defined(SOC_TOUCH_VERSION_1) || SOC_TOUCH_SENSOR_VERSION==1
threshold/=2;
LOG0("Touch Sensor at pin=%d used for calibration. Triggers when sensor reading < %d.\n",pin,threshold);
#elif SOC_TOUCH_VERSION_2
LOG0("Touch Sensor at pin=%d used for calibration. Triggers when sensor reading < %u.\n",pin,threshold);
#else
threshold*=2;
LOG0("Touch Sensor at pin=%d used for calibration. Triggers when sensor reading > %d.\n",pin,threshold);
LOG0("Touch Sensor at pin=%d used for calibration. Triggers when sensor reading > %lu.\n",pin,threshold);
#endif
}
#endif
@@ -281,7 +310,8 @@ int PushButton::type(){
//////////////////////////////////////
void PushButton::wait(){
while(triggerType(pin));
while(triggerType(pin))
homeSpan.resetWatchdog();
}
//////////////////////////////////////
@@ -293,3 +323,56 @@ void PushButton::reset(){
//////////////////////////////////////
PushButton::touch_value_t PushButton::threshold=0;
////////////////////////////////
// hsWatchdogTimer //
////////////////////////////////
void hsWatchdogTimer::enable(uint16_t nSeconds){
if(nSeconds<CONFIG_ESP_TASK_WDT_TIMEOUT_S) // minimum allowed value is CONFIG_ESP_TASK_WDT_TIMEOUT_S
nSeconds=CONFIG_ESP_TASK_WDT_TIMEOUT_S;
this->nSeconds=nSeconds;
esp_task_wdt_config_t twdtConfig;
twdtConfig.timeout_ms=nSeconds*1000;
twdtConfig.trigger_panic=true;
twdtConfig.idle_core_mask=0;
for(int i=0;i<CONFIG_FREERTOS_NUMBER_OF_CORES;i++)
twdtConfig.idle_core_mask |= (ESP_OK==esp_task_wdt_status(xTaskGetIdleTaskHandleForCore(i))) << i; // replicate existing idle task subscriptions to task watchdog
esp_task_wdt_reconfigure(&twdtConfig); // reconfigure task watchdog with new time=nSeconds but DO NOT alter state of idle task subscriptions on either core
if(!wdtHandle)
esp_task_wdt_add_user(WATCHDOG_TAG,&wdtHandle);
ESP_LOGI(WATCHDOG_TAG,"Enabled with %d-second timeout. Idle Task Mask = %d",nSeconds,twdtConfig.idle_core_mask);
}
//////////////////////////////////////
void hsWatchdogTimer::disable(){
if(wdtHandle)
esp_task_wdt_delete_user(wdtHandle);
wdtHandle=NULL;
ESP_LOGI(WATCHDOG_TAG,"Disabled");
}
//////////////////////////////////////
void hsWatchdogTimer::reset(){
vTaskDelay(1);
if(wdtHandle)
esp_task_wdt_reset_user(wdtHandle);
}
//////////////////////////////////////
uint16_t hsWatchdogTimer::getSeconds(){
return(nSeconds);
}

View File

@@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
* Copyright (c) 2020-2025 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
@@ -28,14 +28,18 @@
#pragma once
#include <Arduino.h>
#include <esp_task_wdt.h>
#include "PSRAM.h"
[[maybe_unused]] static const char* WATCHDOG_TAG = "HomeSpan Watchdog";
namespace Utils {
char *readSerial(char *c, int max); // read serial port into 'c' until <newline>, but storing only first 'max' characters (the rest are discarded)
String mask(char *c, int n); // simply utility that creates a String from 'c' with all except the first and last 'n' characters replaced by '*'
char *stripBackslash(char *c); // strips backslashes out of c (Apple unecessesarily "escapes" forward slashes in JSON)
char *stripBackslash(char *c); // strips backslashes out of c (Apple unecessesarily "escapes" forward slashes in JSON)
const char *resetReason(); // returns literal string description of esp_reset_reason()
}
/////////////////////////////////////////////////
@@ -112,11 +116,11 @@ class PushButton{
uint32_t doubleAlarm;
uint32_t longAlarm;
#if SOC_TOUCH_VERSION_2
typedef uint32_t touch_value_t;
#else
#if defined(SOC_TOUCH_VERSION_1) || SOC_TOUCH_SENSOR_VERSION==1
typedef uint16_t touch_value_t;
#endif
#else
typedef uint32_t touch_value_t;
#endif
static touch_value_t threshold;
static const int calibCount=20;
@@ -145,10 +149,10 @@ class PushButton{
static boolean TRIGGER_ON_HIGH(int pin){return(digitalRead(pin));}
#if SOC_TOUCH_SENSOR_NUM > 0
#if SOC_TOUCH_VERSION_2
static boolean TRIGGER_ON_TOUCH(int pin){return(touchRead(pin)>threshold);}
#else
#if defined(SOC_TOUCH_VERSION_1) || SOC_TOUCH_SENSOR_VERSION==1
static boolean TRIGGER_ON_TOUCH(int pin){return(touchRead(pin)<threshold);}
#else
static boolean TRIGGER_ON_TOUCH(int pin){return(touchRead(pin)>threshold);}
#endif
#endif
@@ -235,3 +239,21 @@ class PushButton{
#endif
};
////////////////////////////////
// hsWatchdogTimer //
////////////////////////////////
class hsWatchdogTimer {
uint16_t nSeconds=0;
esp_task_wdt_user_handle_t wdtHandle=NULL;
public:
hsWatchdogTimer(){};
void enable(uint16_t nSeconds);
void disable();
void reset();
uint16_t getSeconds();
};

View File

@@ -27,7 +27,7 @@
#include "Blinker.h"
////////////////////////////////
////////////////////////////////
// Blinker //
////////////////////////////////

View File

@@ -28,7 +28,6 @@
#pragma once
#include <Arduino.h>
#include <driver/timer.h>
[[maybe_unused]] static const char* BLINKER_TAG = "Blinker";

View File

@@ -35,105 +35,120 @@
// Single-Wire RGB/RGBW NeoPixels //
////////////////////////////////////////////
Pixel::Pixel(int pin, pixelType_t pixelType){
Pixel::Pixel(int pin, const char *pixelType){
rf=new RFControl(pin,false,false); // set clock to 1/80 usec, no default driver
if(!*rf)
this->pin=pin;
rmt_tx_channel_config_t tx_chan_config;
tx_chan_config.clk_src = RMT_CLK_SRC_DEFAULT; // always use 80MHz clock source
tx_chan_config.gpio_num = (gpio_num_t)pin; // GPIO number
tx_chan_config.mem_block_symbols = SOC_RMT_MEM_WORDS_PER_CHANNEL; // set number of symbols to match those in a single channel block
tx_chan_config.resolution_hz = 80 * 1000 * 1000; // set to 80MHz
tx_chan_config.intr_priority = 3; // medium interrupt priority
tx_chan_config.trans_queue_depth = 4; // set the number of transactions that can pend in the background
tx_chan_config.flags.invert_out = false; // do not invert output signal
tx_chan_config.flags.with_dma = false; // use RMT channel memory, not DMA (most chips do not support use of DMA anyway)
tx_chan_config.flags.io_loop_back = false; // do not use loop-back mode
tx_chan_config.flags.io_od_mode = false; // do not use open-drain output
if(!GPIO_IS_VALID_OUTPUT_GPIO(pin)){
ESP_LOGE(PIXEL_TAG,"Can't create Pixel(%d) - invalid output pin",pin);
return;
}
if(rmt_new_tx_channel(&tx_chan_config, &tx_chan)!=ESP_OK){
ESP_LOGE(PIXEL_TAG,"Can't create Pixel(%d) - no open channels",pin);
return;
}
bytesPerPixel=0;
size_t len=strlen(pixelType);
boolean invalidMap=false;
char v[]="RGBWC01234-"; // list of valid mapping characters for pixelType
for(int i=0;i<len && i<5;i++){ // parse and then validate pixelType
int index=strchrnul(v,toupper(pixelType[i]))-v;
if(index==strlen(v)) // invalid mapping character found
invalidMap=true;
map[bytesPerPixel++]=index%5; // create pixel map and compute number of bytes per pixel
}
map=pixelType;
if(bytesPerPixel<3 || len>5 || invalidMap){
ESP_LOGE(PIXEL_TAG,"Can't create Pixel(%d, \"%s\") - invalid pixelType",pin,pixelType);
return;
}
sscanf(pixelType,"%ms",&pType); // save pixelType for later use with hasColor()
if(map[3])
bytesPerPixel=4;
else
bytesPerPixel=3;
rmt_enable(tx_chan); // enable channel
channel=((int *)tx_chan)[0]; // get channel number
tx_config.loop_count=0; // populate tx_config structure
tx_config.flags.eot_level=0;
tx_config.flags.queue_nonblocking=0;
rmt_bytes_encoder_config_t encoder_config; // can leave blank for now - will populate from within setTiming() below
rmt_new_bytes_encoder(&encoder_config, &encoder); // create byte encoder
setTiming(0.32, 0.88, 0.64, 0.56, 80.0); // set default timing parameters (suitable for most SK68 and WS28 RGB pixels)
rmt_isr_register(loadData,NULL,0,NULL); // set custom interrupt handler
rmt_set_tx_thr_intr_en(rf->getChannel(),false,8); // disable threshold interrupt
txThrMask=RMT.int_ena.val; // save interrupt enable vector
rmt_set_tx_thr_intr_en(rf->getChannel(),true,8); // enable threshold interrupt to trigger every 8 pulses
txThrMask^=RMT.int_ena.val; // find bit that flipped and save as threshold mask for this channel
rmt_set_tx_intr_en(rf->getChannel(),false); // disable end-of-transmission interrupt
txEndMask=RMT.int_ena.val; // save interrupt enable vector
rmt_set_tx_intr_en(rf->getChannel(),true); // enable end-of-transmission interrupt
txEndMask^=RMT.int_ena.val; // find bit that flipped and save as end-of-transmission mask for this channel
onColor.HSV(0,100,100,0);
}
///////////////////
void Pixel::setTiming(float high0, float low0, float high1, float low1, uint32_t lowReset){
Pixel *Pixel::setTiming(float high0, float low0, float high1, float low1, uint32_t lowReset){
if(channel<0)
return(this);
rmt_bytes_encoder_config_t encoder_config;
encoder_config.bit0.level0=1;
encoder_config.bit0.duration0=high0*80+0.5;
encoder_config.bit0.level1=0;
encoder_config.bit0.duration1=low0*80+0.5;
encoder_config.bit1.level0=1;
encoder_config.bit1.duration0=high1*80+0.5;
encoder_config.bit1.level1=0;
encoder_config.bit1.duration1=low1*80+0.5;
encoder_config.flags.msb_first=1; // MSB of data bytes should be converted and transmitted first
rmt_bytes_encoder_update_config(encoder,&encoder_config); // update config
pattern[0]=RF_PULSE(high0*80+0.5,low0*80+0.5);
pattern[1]=RF_PULSE(high1*80+0.5,low1*80+0.5);
resetTime=lowReset;
return(this);
}
///////////////////
void Pixel::set(Color *c, int nPixels, boolean multiColor){
if(!*rf || nPixels==0)
if(channel<0 || nPixels==0)
return;
status.nPixels=nPixels;
status.color=c;
status.iMem=0;
status.started=true;
status.px=this;
status.multiColor=multiColor;
status.iByte=0;
Color data[2]; // temp ping/pong structure to store re-mapped color bytes
int index=0; // points to current slot in ping/pong structure
loadData(this); // load first two bytes of data to get started
loadData(this);
rmt_ll_set_group_clock_src(&RMT, channel, RMT_CLK_SRC_DEFAULT, 1, 0, 0); // ensure use of DEFAULT CLOCK, which is always 80 MHz, without any scaling
do {
for(int i=0;i<bytesPerPixel;i++) // remap colors into ping/pong structure
data[index].col[i]=c->col[map[i]];
rmt_tx_start(rf->getChannel(),true);
rmt_tx_wait_all_done(tx_chan,-1); // wait until any outstanding data is transmitted
rmt_transmit(tx_chan, encoder, data[index].col, bytesPerPixel, &tx_config); // transmit data
index=1-index; // flips index to second data structure
c+=multiColor;
} while(--nPixels>0);
while(status.started); // wait for transmission to be complete
delayMicroseconds(resetTime); // end-of-marker delay
rmt_tx_wait_all_done(tx_chan,-1); // wait until final data is transmitted
delayMicroseconds(resetTime); // end-of-marker delay
}
///////////////////
void IRAM_ATTR Pixel::loadData(void *arg){
if(RMT.int_st.val & status.px->txEndMask){
RMT.int_clr.val=status.px->txEndMask;
status.started=false;
return;
}
RMT.int_clr.val=status.px->txThrMask; // if loadData() is called and it is NOT because of an END interrupt (above) then must either be a pre-load, or a threshold trigger
if(status.nPixels==0){
RMTMEM.chan[status.px->rf->getChannel()].data32[status.iMem].val=0;
return;
}
int startBit=status.px->map[status.iByte];
int endBit=startBit-8;
for(int iBit=startBit;iBit>endBit;iBit--)
RMTMEM.chan[status.px->rf->getChannel()].data32[status.iMem++].val=status.px->pattern[(status.color->val>>iBit)&1];
if(++status.iByte==status.px->bytesPerPixel){
status.iByte=0;
status.color+=status.multiColor;
status.nPixels--;
}
status.iMem%=status.px->memSize;
}
///////////////////
volatile Pixel::pixel_status_t Pixel::status;
////////////////////////////////////////////
// Two-Wire RGB DotStars //
////////////////////////////////////////////
@@ -148,29 +163,27 @@ Dot::Dot(uint8_t dataPin, uint8_t clockPin){
dataMask=1<<(dataPin%32);
clockMask=1<<(clockPin%32);
#ifdef CONFIG_IDF_TARGET_ESP32C3
dataSetReg=&GPIO.out_w1ts.val;
dataClearReg=&GPIO.out_w1tc.val;
clockSetReg=&GPIO.out_w1ts.val;
clockClearReg=&GPIO.out_w1tc.val;
#if defined(CONFIG_IDF_TARGET_ESP32C3)
#define OUT_W1TS &GPIO.out_w1ts.val
#define OUT_W1TC &GPIO.out_w1tc.val
#define OUT1_W1TS NULL
#define OUT1_W1TC NULL
#elif defined(CONFIG_IDF_TARGET_ESP32C6)
#define OUT_W1TS &GPIO.out_w1ts.val
#define OUT_W1TC &GPIO.out_w1tc.val
#define OUT1_W1TS &GPIO.out1_w1ts.val
#define OUT1_W1TC &GPIO.out1_w1tc.val
#else
if(dataPin<32){
dataSetReg=&GPIO.out_w1ts;
dataClearReg=&GPIO.out_w1tc;
} else {
dataSetReg=&GPIO.out1_w1ts.val;
dataClearReg=&GPIO.out1_w1tc.val;
}
if(clockPin<32){
clockSetReg=&GPIO.out_w1ts;
clockClearReg=&GPIO.out_w1tc;
} else {
clockSetReg=&GPIO.out1_w1ts.val;
clockClearReg=&GPIO.out1_w1tc.val;
}
#define OUT_W1TS &GPIO.out_w1ts
#define OUT_W1TC &GPIO.out_w1tc
#define OUT1_W1TS &GPIO.out1_w1ts.val
#define OUT1_W1TC &GPIO.out1_w1tc.val
#endif
dataSetReg= dataPin<32 ? (OUT_W1TS) : (OUT1_W1TS);
dataClearReg= dataPin<32 ? (OUT_W1TC) : (OUT1_W1TC);
clockSetReg= clockPin<32 ? (OUT_W1TS) : (OUT1_W1TS);
clockClearReg= clockPin<32 ? (OUT_W1TC) : (OUT1_W1TC);
}
///////////////////

View File

@@ -31,30 +31,43 @@
#pragma once
#include "RFControl.h"
#include "PwmPin.h"
#include "Blinker.h"
#pragma GCC diagnostic ignored "-Wvolatile"
#include <driver/rmt_tx.h> // IDF 5 RMT driver
#include <soc/rmt_struct.h> // where RMT register structure is defined
#include <hal/rmt_ll.h> // where low-level RMT calls are defined
#include <soc/gpio_struct.h>
[[maybe_unused]] static const char* PIXEL_TAG = "Pixel";
typedef const uint8_t pixelType_t[];
/***********************************/
/* TO BE DEPRECATED IN 2.X RELEASE */
typedef const String pixelType_t;
namespace PixelType {
pixelType_t RGB={31,23,15,0};
pixelType_t RBG={31,15,23,0};
pixelType_t BRG={23,15,31,0};
pixelType_t BGR={15,23,31,0};
pixelType_t GBR={15,31,23,0};
pixelType_t GRB={23,31,15,0};
pixelType_t RGBW={31,23,15,7};
pixelType_t RBGW={31,15,23,7};
pixelType_t BRGW={23,15,31,7};
pixelType_t BGRW={15,23,31,7};
pixelType_t GBRW={15,31,23,7};
pixelType_t GRBW={23,31,15,7};
pixelType_t RGB="rgb";
pixelType_t RBG="rbg";
pixelType_t BRG="brg";
pixelType_t BGR="bgr";
pixelType_t GBR="gbr";
pixelType_t GRB="grb";
pixelType_t RGBW="rgbw";
pixelType_t RBGW="rbgw";
pixelType_t BRGW="brgw";
pixelType_t BGRW="bgrw";
pixelType_t GBRW="gbrw";
pixelType_t GRBW="grbw";
};
/***********************************/
////////////////////////////////////////////
// Single-Wire RGB/RGBW NeoPixels //
////////////////////////////////////////////
@@ -63,127 +76,133 @@ class Pixel : public Blinkable {
public:
struct Color {
union{
struct {
uint8_t white:8;
uint8_t blue:8;
uint8_t green:8;
uint8_t red:8;
};
uint32_t val;
};
uint8_t col[5];
Color RGB(uint8_t r, uint8_t g, uint8_t b, uint8_t w=0){ // returns Color based on provided RGB(W) values where r/g/b/w=[0-255]
this->red=r;
this->green=g;
this->blue=b;
this->white=w;
Color(){
col[0]=0;
col[1]=0;
col[2]=0;
col[3]=0;
col[4]=0;
}
Color RGB(uint8_t r, uint8_t g, uint8_t b, uint8_t w=0, uint8_t c=0){ // returns Color based on provided RGB(WC) values where r/g/b/w/c=[0-255]
col[0]=r;
col[1]=g;
col[2]=b;
col[3]=w;
col[4]=c;
return(*this);
}
Color HSV(float h, float s, float v, double w=0){ // returns Color based on provided HSV(W) values where h=[0,360] and s/v/w=[0,100]
float r,g,b;
LedPin::HSVtoRGB(h,s/100.0,v/100.0,&r,&g,&b);
this->red=r*255;
this->green=g*255;
this->blue=b*255;
this->white=w*2.555;
Color WC(uint8_t w, uint8_t c=0){ // returns Color based on provided RGB(WC) values where r/g/b/w/c=[0-255]
col[0]=0;
col[1]=0;
col[2]=0;
col[3]=w;
col[4]=c;
return(*this);
}
Color HSV(float h, float s, float v, double w=0, double c=0){ // returns Color based on provided HSV(WC) values where h=[0,360] and s/v/w/c=[0,100]
float r,g,b;
LedPin::HSVtoRGB(h,s/100.0,v/100.0,&r,&g,&b);
col[0]=r*255;
col[1]=g*255;
col[2]=b*255;
col[3]=w*2.555;
col[4]=c*2.555;
return(*this);
}
Color CCT(float temp, float v, float wTemp, float cTemp){
col[0]=0;
col[1]=0;
col[2]=0;
if(temp<wTemp)
temp=wTemp;
else if(temp>cTemp)
temp=cTemp;
col[4]=(temp-wTemp)/(cTemp-wTemp)*255.0;
col[3]=255-col[4];
col[3]*=v/100.0;
col[4]*=v/100.0;
return(*this);
}
bool operator==(const Color& color){
return(val==color.val);
boolean eq=true;
for(int i=0;i<5;i++)
eq&=(col[i]==color.col[i]);
return(eq);
}
bool operator!=(const Color& color){
return(val!=color.val);
return(!(*this==color));
}
Color operator+(const Color& color){
Color newColor;
newColor.white=white+color.white;
newColor.blue=blue+color.blue;
newColor.red=red+color.red;
newColor.green=green+color.green;
for(int i=0;i<5;i++)
newColor.col[i]=col[i]+color.col[i];
return(newColor);
}
Color& operator+=(const Color& color){
white+=color.white;
red+=color.red;
blue+=color.blue;
green+=color.green;
for(int i=0;i<5;i++)
col[i]+=color.col[i];
return(*this);
}
Color operator-(const Color& color){
Color newColor;
newColor.white=white-color.white;
newColor.blue=blue-color.blue;
newColor.red=red-color.red;
newColor.green=green-color.green;
return(newColor);
}
Color& operator-=(const Color& color){
white-=color.white;
red-=color.red;
blue-=color.blue;
green-=color.green;
return(*this);
}
}
}; // Color
private:
struct pixel_status_t {
int nPixels;
Color *color;
int iMem;
boolean started;
Pixel *px;
boolean multiColor;
int iByte;
};
uint8_t pin;
int channel=-1;
char *pType=NULL;
rmt_channel_handle_t tx_chan = NULL;
rmt_encoder_handle_t encoder;
rmt_transmit_config_t tx_config;
RFControl *rf; // Pixel utilizes RFControl
uint32_t pattern[2]; // storage for zero-bit and one-bit pulses
uint32_t resetTime; // minimum time (in usec) between pulse trains
uint32_t txEndMask; // mask for end-of-transmission interrupt
uint32_t txThrMask; // mask for threshold interrupt
uint8_t bytesPerPixel; // RGBW=4; RGB=3
const uint8_t *map; // color map representing order in which color bytes are transmitted
uint8_t bytesPerPixel; // WC=2, RGB=3, RGBW=4, RGBWC=5
float warmTemp=2000; // default temperature (in Kelvin) of warm-white LED
float coolTemp=7000; // defult temperature (in Kelvin) of cool-white LED
uint8_t map[5]; // color map representing order in which color bytes are transmitted
Color onColor; // color used for on() command
const int memSize=sizeof(RMTMEM.chan[0].data32)/4; // determine size (in pulses) of one channel
static void loadData(void *arg); // interrupt handler
volatile static pixel_status_t status; // storage for volatile information modified in interupt handler
public:
Pixel(int pin, pixelType_t pixelType=PixelType::GRB); // creates addressable single-wire LED of pixelType connected to pin (such as the SK68 or WS28)
Pixel(int pin, const char *pixelType="GRB"); // creates addressable single-wire LED of pixelType connected to pin (such as the SK68 or WS28)
void set(Color *c, int nPixels, boolean multiColor=true); // sets colors of nPixels based on array of Colors c; setting multiColor to false repeats Color in c[0] for all nPixels
void set(Color c, int nPixels=1){set(&c,nPixels,false);} // sets color of nPixels to be equal to specific Color c
static Color RGB(uint8_t r, uint8_t g, uint8_t b, uint8_t w=0){return(Color().RGB(r,g,b,w));} // an alternative method for returning an RGB Color
static Color HSV(float h, float s, float v, double w=0){return(Color().HSV(h,s,v,w));} // an alternative method for returning an HSV Color
static Color RGB(uint8_t r, uint8_t g, uint8_t b, uint8_t w=0, uint8_t c=0){return(Color().RGB(r,g,b,w,c));} // a static method for returning an RGB(WC) Color
static Color HSV(float h, float s, float v, double w=0, double c=0){return(Color().HSV(h,s,v,w,c));} // a static method for returning an HSV(WC) Color
static Color WC(uint8_t w, uint8_t c=0){return(Color().WC(w,c));} // a static method for returning an Warm-White/Cold-White (WC) Color
static Color CCT(float temp, float v, float wTemp, float cTemp){return(Color().CCT(temp,v,wTemp,cTemp));} // a static method for returning a CCT Color
Color CCT(float temp, float v){return(Color().CCT(temp,v,warmTemp,coolTemp));} // a member function for returning a CCT Color using pixel-specific temperatures
int getPin(){return(rf->getPin());} // returns pixel pin if valid, else returns -1
boolean isRGBW(){return(bytesPerPixel==4);} // returns true if RGBW LED, else false if RGB LED
void setTiming(float high0, float low0, float high1, float low1, uint32_t lowReset); // changes default timings for bit pulse - note parameters are in MICROSECONDS
int getPin(){return(channel>=0?pin:-1);} // returns pixel pin (=-1 if channel is not valid)
Pixel *setTiming(float high0, float low0, float high1, float low1, uint32_t lowReset); // changes default timings for bit pulse - note parameters are in MICROSECONDS
Pixel *setTemperatures(float wTemp, float cTemp){warmTemp=wTemp;coolTemp=cTemp;return(this);} // changes default warm-white and cool-white LED temperatures (in Kelvin)
boolean hasColor(char c){return(strchr(pType,toupper(c))!=NULL || strchr(pType,tolower(c))!=NULL);} // returns true if pixelType includes c (case-insensitive)
operator bool(){ // override boolean operator to return true/false if creation succeeded/failed
return(*rf);
return(channel>=0);
}
void on() {set(onColor);}
void off() {set(RGB(0,0,0,0));}
Pixel *setOnColor(Color c){onColor=c;return(this);}
[[deprecated("Please use Pixel(int pin, pixelType_t pixelType) constructor instead.")]]
Pixel(int pin, boolean isRGBW):Pixel(pin,isRGBW?PixelType::GRBW:PixelType::GRB){};
[[deprecated("*** Please use Pixel(int pin, const char *pixelType) constructor instead to ensure future compatibility.")]]
Pixel(int pin, boolean isRGBW) : Pixel(pin,isRGBW?"GRBW":"GRB"){}
[[deprecated("*** Please use Pixel(int pin, const char *pixelType) constructor instead to ensure future compatibility.")]]
Pixel(int pin, pixelType_t pixelType) : Pixel(pin, pixelType.c_str()){}
[[deprecated("*** This method will be deprecated in a future release.")]]
boolean isRGBW(){return(bytesPerPixel==4);}
};
////////////////////////////////////////////
@@ -205,6 +224,14 @@ class Dot {
uint32_t val;
};
Color(){
this->red=0;
this->green=0;
this->blue=0;
this->drive=31;
this->flags=7;
}
Color RGB(uint8_t r, uint8_t g, uint8_t b, uint8_t driveLevel=31){ // returns Color based on provided RGB values where r/g/b=[0-255] and current-limiting drive level=[0,31]
this->red=r;
this->green=g;
@@ -214,7 +241,7 @@ class Dot {
return(*this);
}
Color HSV(float h, float s, float v, double drivePercent=100){ // returns Color based on provided HSV values where h=[0,360], s/v=[0,100], and current-limiting drive percent=[0,100]
Color HSV(float h, float s, float v, double drivePercent=100){ // returns Color based on provided HSV values where h=[0,360], s/v=[0,100], and current-limiting drive percent=[0,100]
float r,g,b;
LedPin::HSVtoRGB(h,s/100.0,v/100.0,&r,&g,&b);
this->red=r*255;

View File

@@ -44,10 +44,19 @@ LedC::LedC(uint8_t pin, uint16_t freq, boolean invert){
timerList[nTimer][nMode]->speed_mode=(ledc_mode_t)nMode;
timerList[nTimer][nMode]->timer_num=(ledc_timer_t)nTimer;
timerList[nTimer][nMode]->freq_hz=freq;
#if defined(SOC_LEDC_SUPPORT_APB_CLOCK)
timerList[nTimer][nMode]->clk_cfg=LEDC_USE_APB_CLK;
#elif defined(SOC_LEDC_SUPPORT_PLL_DIV_CLOCK)
timerList[nTimer][nMode]->clk_cfg=LEDC_USE_PLL_DIV_CLK;
#endif
#if ESP_IDF_VERSION > ESP_IDF_VERSION_VAL(5, 1, 5)
timerList[nTimer][nMode]->deconfigure=false;
#endif
int res=LEDC_TIMER_BIT_MAX-1; // find the maximum possible resolution
while(getApbFrequency()/(freq*pow(2,res))<1)
while(80.0e6/(freq*pow(2,res))<1)
res--;
timerList[nTimer][nMode]->duty_resolution=(ledc_timer_bit_t)res;
@@ -60,7 +69,7 @@ LedC::LedC(uint8_t pin, uint16_t freq, boolean invert){
}
if(timerList[nTimer][nMode]->freq_hz==freq){ // if timer matches desired frequency (always true if newly-created above)
channelList[nChannel][nMode]=new ledc_channel_config_t; // create new channel instance
channelList[nChannel][nMode]=new ledc_channel_config_t(); // create new channel instance
channelList[nChannel][nMode]->speed_mode=(ledc_mode_t)nMode;
channelList[nChannel][nMode]->channel=(ledc_channel_t)nChannel;
channelList[nChannel][nMode]->timer_sel=(ledc_timer_t)nTimer;
@@ -96,8 +105,11 @@ LedPin::LedPin(uint8_t pin, float level, uint16_t freq, boolean invert) : LedC(p
timer->duty_resolution,
channel->flags.output_invert?"(inverted)":""
);
ledc_fade_func_install(0);
if(!fadeInitialized){
ledc_fade_func_install(0);
fadeInitialized=true;
}
ledc_cbs_t fadeCallbackList = {.fade_cb = fadeCallback}; // for some reason, ledc_cb_register requires the function to be wrapped in a structure
ledc_cb_register(channel->speed_mode,channel->channel,&fadeCallbackList,this);
@@ -273,3 +285,4 @@ void ServoPin::set(double degrees){
ledc_channel_config_t *LedC::channelList[LEDC_CHANNEL_MAX][LEDC_SPEED_MODE_MAX]={};
ledc_timer_config_t *LedC::timerList[LEDC_TIMER_MAX][LEDC_SPEED_MODE_MAX]={};
boolean LedPin::fadeInitialized=false;

View File

@@ -91,9 +91,10 @@ class LedPin : public LedC {
private:
int fadeState=NOT_FADING;
static bool fadeCallback(const ledc_cb_param_t *param, void *arg);
static boolean fadeInitialized;
public:
LedPin(uint8_t pin, float level=0, uint16_t freq=DEFAULT_PWM_FREQ, boolean invert=false); // assigns pin to be output of one of 16 PWM channels initial level and frequency
LedPin(uint8_t pin, float level=0, uint16_t freq=DEFAULT_PWM_FREQ, boolean invert=false); // assigns LED pin
void set(float level); // sets the PWM duty to level (0-100)
int fade(float level, uint32_t fadeTime, int fadeType=ABSOLUTE); // sets the PWM duty to level (0-100) within fadeTime in milliseconds, returns success (0) or fail (1)
int fadeStatus(); // returns fading state

View File

@@ -29,67 +29,73 @@
///////////////////
RFControl::RFControl(uint8_t pin, boolean refClock, boolean installDriver){
RFControl::RFControl(uint8_t pin, boolean refClock){
#if defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3)
if(nChannels==RMT_CHANNEL_MAX/2){
#else
if(nChannels==RMT_CHANNEL_MAX){
#endif
this->refClock=refClock;
this->pin=pin;
rmt_tx_channel_config_t tx_chan_config;
tx_chan_config.clk_src = RMT_CLK_SRC_DEFAULT; // use default as a placeholder - will be reset to required clock before each transmission
tx_chan_config.gpio_num = (gpio_num_t)pin; // GPIO number
tx_chan_config.mem_block_symbols = SOC_RMT_MEM_WORDS_PER_CHANNEL; // set number of symbols to match those in a single channel block
tx_chan_config.resolution_hz = 1 * 1000 * 1000; // use 1MHz as a placeholder - will be reset to required clock before each transmission
tx_chan_config.intr_priority = 3; // medium interrupt priority
tx_chan_config.trans_queue_depth = 4; // set the number of transactions that can pend in the background
tx_chan_config.flags.invert_out = false; // do not invert output signal
tx_chan_config.flags.with_dma = false; // use RMT channel memory, not DMA (most chips do not support use of DMA anyway)
tx_chan_config.flags.io_loop_back = false; // do not use loop-back mode
tx_chan_config.flags.io_od_mode = false; // do not use open-drain output
if(!GPIO_IS_VALID_OUTPUT_GPIO(pin)){
ESP_LOGE(RFControl_TAG,"Can't create RFControl(%d) - invalid output pin",pin);
return;
}
if(rmt_new_tx_channel(&tx_chan_config, &tx_chan)!=ESP_OK){
ESP_LOGE(RFControl_TAG,"Can't create RFControl(%d) - no open channels",pin);
return;
}
config=new rmt_config_t;
rmt_enable(tx_chan); // enable channel
channel=((int *)tx_chan)[0]; // get channel number
tx_config.loop_count=0; // populate tx_config structure
tx_config.flags.eot_level=0;
tx_config.flags.queue_nonblocking=0;
config->rmt_mode=RMT_MODE_TX;
config->tx_config.carrier_en=false;
config->channel=(rmt_channel_t)nChannels;
config->flags=0;
config->clk_div = 1;
config->mem_block_num=1;
config->gpio_num=(gpio_num_t)pin;
config->tx_config.idle_output_en=false;
config->tx_config.idle_level=RMT_IDLE_LEVEL_LOW;
config->tx_config.loop_en=false;
rmt_config(config);
if(installDriver)
rmt_driver_install(config->channel,0,0);
// If specified, set the base clock to 1 MHz so tick-units are in microseconds (before any CLK_DIV is applied), otherwise default will be 80 MHz APB clock
this->refClock=refClock;
if(refClock)
#ifdef RMT_SYS_CONF_REG
REG_SET_FIELD(RMT_SYS_CONF_REG,RMT_SCLK_DIV_NUM,79); // ESP32-C3 and ESP32-S3 do not have a 1 MHz REF Tick Clock, but allows the 80 MHz APB clock to be scaled by an additional RMT-specific divider
#else
rmt_set_source_clk(config->channel,RMT_BASECLK_REF); // use 1 MHz REF Tick Clock for ESP32 and ESP32-S2
#endif
nChannels++;
rmt_copy_encoder_config_t copy_config; // required, though nothing to populate
rmt_new_copy_encoder(&copy_config, &encoder); // create copy encoder
}
///////////////////
void RFControl::start(uint8_t nCycles, uint8_t tickTime){ // starts transmission of pulses from internal data structure, repeated for nCycles, where each tick in pulse is tickTime microseconds long
void RFControl::start(uint8_t nCycles, uint8_t tickTime){ // starts transmission of pulses from internal data structure, repeated for nCycles
start(data.data(), data.size(), nCycles, tickTime);
}
///////////////////
void RFControl::start(uint32_t *data, int nData, uint8_t nCycles, uint8_t tickTime){ // starts transmission of pulses from specified data pointer, repeated for nCycles, where each tick in pulse is tickTime microseconds long
void RFControl::start(uint32_t *data, size_t nData, uint8_t nCycles, uint8_t tickTime){ // starts transmission of pulses from specified data pointer, repeated for nCycles
if(!config || nData==0)
if(channel<0 || nData==0)
return;
rmt_set_clk_div(config->channel,tickTime); // set clock divider
for(int i=0;i<nCycles;i++) // loop over nCycles
rmt_write_items(config->channel, (rmt_item32_t *) data, nData, true); // start transmission and wait until completed before returning
rmt_ll_tx_set_channel_clock_div(&RMT, channel, tickTime); // set clock divider
if(refClock)
#if defined(SOC_RMT_SUPPORT_REF_TICK)
rmt_ll_set_group_clock_src(&RMT, channel, RMT_CLK_SRC_REF_TICK, 0, 0, 0); // use REF_TICK, which is 1 MHz
#else
rmt_ll_set_group_clock_src(&RMT, channel, RMT_CLK_SRC_DEFAULT, 80, 0, 0); // use DEFAULT CLOCK, which is always 80 MHz, and scale by 80 to get 1 MHz
#endif
else
rmt_ll_set_group_clock_src(&RMT, channel, RMT_CLK_SRC_DEFAULT, 1, 0, 0); // use DEFAULT CLOCK, which is always 80 MHz, without any scaling
for(int i=0;i<nCycles;i++){
rmt_transmit(tx_chan, encoder, data, nData*4, &tx_config);
rmt_tx_wait_all_done(tx_chan,-1);
}
}
///////////////////
@@ -128,37 +134,44 @@ void RFControl::phase(uint32_t nTicks, uint8_t phase){
void RFControl::enableCarrier(uint32_t freq, float duty){
if(channel<0)
return;
if(freq==0){
rmt_ll_tx_enable_carrier_modulation(&RMT, channel, 0); // disable carrier wave
return;
}
if(duty<0)
duty=0;
if(duty>1)
duty=1;
float period=(refClock?1.0e6:80.0e6)/freq;
uint32_t highTime=period*duty+0.5;
uint32_t lowTime=period*(1.0-duty)+0.5;
if(freq>0){
float period=1.0e6/freq*(refClock?1:80);
uint32_t highTime=period*duty+0.5;
uint32_t lowTime=period*(1.0-duty)+0.5;
if(highTime>0xFFFF || lowTime>0xFFFF){
ESP_LOGE(RFControl_TAG,"Can't enable carrier frequency=%d Hz for RF Control pin=%d, duty=%0.2f. Frequency is too low!",freq,config->gpio_num,duty);
return;
}
if(highTime==0){
ESP_LOGE(RFControl_TAG,"Can't enable carrier frequency=%d Hz for RF Control pin=%d, duty=%0.2f. Duty is too low or frequency is too high!",freq,config->gpio_num,duty);
return;
}
if(lowTime==0){
ESP_LOGE(RFControl_TAG,"Can't enable carrier frequency=%d Hz for RF Control pin=%d, duty=%0.2f. Duty is too high or frequency is too high!",freq,config->gpio_num,duty);
return;
}
rmt_set_tx_carrier(config->channel,true,highTime,lowTime,RMT_CARRIER_LEVEL_HIGH);
} else {
rmt_set_tx_carrier(config->channel,false,0,0,RMT_CARRIER_LEVEL_HIGH);
if(highTime>0xFFFF || lowTime>0xFFFF){
ESP_LOGE(RFControl_TAG,"Can't enable carrier frequency=%d Hz for RF Control pin=%d, duty=%0.2f. Frequency is too low!",freq,pin,duty);
return;
}
if(highTime==0){
ESP_LOGE(RFControl_TAG,"Can't enable carrier frequency=%d Hz for RF Control pin=%d, duty=%0.2f. Duty is too low or frequency is too high!",freq,pin,duty);
return;
}
if(lowTime==0){
ESP_LOGE(RFControl_TAG,"Can't enable carrier frequency=%d Hz for RF Control pin=%d, duty=%0.2f. Duty is too high or frequency is too high!",freq,pin,duty);
return;
}
#if SOC_RMT_SUPPORT_TX_CARRIER_DATA_ONLY
rmt_ll_tx_enable_carrier_always_on(&RMT, channel, 0); // for chips that support carrier is always on, disable it
#endif
rmt_ll_tx_set_carrier_level(&RMT, channel, 1); // turn on carrier wave when signal is HIGH
rmt_ll_tx_set_carrier_high_low_ticks(&RMT, channel, highTime, lowTime); // set high/low ticks for carrier wave frequency and duty cycle
rmt_ll_tx_enable_carrier_modulation(&RMT, channel, 1); // enable carrier wave
}
///////////////////
uint8_t RFControl::nChannels=0;

View File

@@ -31,9 +31,15 @@
#pragma once
#include "driver/gpio.h"
#pragma GCC diagnostic ignored "-Wvolatile"
#include <Arduino.h>
#include <soc/rmt_reg.h>
#include "driver/rmt.h"
#include <driver/rmt_tx.h> // IDF 5 RMT driver
#include <soc/rmt_struct.h> // where RMT register structure is defined
#include <hal/rmt_ll.h> // where low-level RMT calls are defined
#include <vector>
[[maybe_unused]] static const char* RFControl_TAG = "RFControl";
@@ -41,21 +47,21 @@
using std::vector;
class RFControl {
friend class Pixel;
private:
rmt_config_t *config=NULL;
vector<uint32_t> data;
boolean lowWord=true;
boolean refClock;
static uint8_t nChannels;
RFControl(uint8_t pin, boolean refClock, boolean installDriver); // private constructor (only used by Pixel class)
uint8_t pin;
int channel=-1;
rmt_channel_handle_t tx_chan = NULL;
rmt_encoder_handle_t encoder;
rmt_transmit_config_t tx_config;
public:
RFControl(uint8_t pin, boolean refClock=true):RFControl(pin,refClock,true){}; // public constructor to create transmitter on pin, using 1-MHz Ref Tick clock or 80-MHz APB clock
RFControl(uint8_t pin, boolean refClock=true); // public constructor to create transmitter on pin, using 1-MHz Ref Tick clock or 80-MHz APB clock
void start(uint32_t *data, int nData, uint8_t nCycles=1, uint8_t tickTime=1); // starts transmission of pulses from specified data pointer, repeated for numCycles, where each tick in pulse is tickTime microseconds long
void start(uint32_t *data, size_t nData, uint8_t nCycles=1, uint8_t tickTime=1); // starts transmission of pulses from specified data pointer, repeated for numCycles, where each tick in pulse is tickTime microseconds long
void start(uint8_t nCycles=1, uint8_t tickTime=1); // starts transmission of pulses from internal data structure, repeated for numCycles, where each tick in pulse is tickTime microseconds long
void clear(); // clears transmitter memory
@@ -64,11 +70,8 @@ class RFControl {
void enableCarrier(uint32_t freq, float duty=0.5); // enables carrier wave if freq>0, else disables carrier wave; duty is a fraction from 0-1
void disableCarrier(){enableCarrier(0);} // disables carrier wave
int getPin(){return(config?config->gpio_num:-1);} // returns the pin number, or -1 if no channel defined
rmt_channel_t getChannel(){return(config?config->channel:RMT_CHANNEL_0);} // returns channel, or channel_0 is no channel defined
operator bool(){ // override boolean operator to return true/false if creation succeeded/failed
return(config);
return(channel>=0);
}
};

View File

@@ -24,41 +24,116 @@
* SOFTWARE.
*
********************************************************************************/
#include "PwmPin.h"
ServoPin servo(21,0,500,2200,-60,60);
#include "Pixel.h"
#include "RFControl.h"
void setup() {
#define PIXEL_PIN 26
#define LED_PIN 15
#define NCYCLES 4
#define COUNT 5
#define ONTIME 5000
#define OFFTIME 5000
void setup(){
Serial.begin(115200);
Serial.begin(115200); // start the Serial interface
delay(1000);
Serial.print("\n\nReady\n\n");
Serial.print("\n\nHomeSpan Pixel+RF Example\n\n");
for(int count=0;count<3;count++){
for(int i=-60;i<61;i++){
servo.set(i);
delay(10);
}
Pixel px(PIXEL_PIN,"G-B");
px.setOnColor(Pixel::RGB(0,255,0))->setTemperatures(2000,6000)->setTiming(0.32, 0.88, 0.64, 0.56, 80.0);
RFControl rf(LED_PIN);
for(int i=60;i>-61;i--){
servo.set(i);
delay(10);
}
Pixel::Color q;
q.CCT(4000,25,3000,6500);
Serial.printf("%d %d %d %d %d\n",q.col[0],q.col[1],q.col[2],q.col[3],q.col[4]);
q.CCT(4000,100,3000,6500);
Serial.printf("%d %d %d %d %d\n",q.col[0],q.col[1],q.col[2],q.col[3],q.col[4]);
q.CCT(3000,100,2000,7000);
Serial.printf("%d %d %d %d %d\n",q.col[0],q.col[1],q.col[2],q.col[3],q.col[4]);
q.CCT(4500,100,2000,7000);
Serial.printf("%d %d %d %d %d\n",q.col[0],q.col[1],q.col[2],q.col[3],q.col[4]);
q.CCT(6000,10,2000,7000);
Serial.printf("%d %d %d %d %d\n",q.col[0],q.col[1],q.col[2],q.col[3],q.col[4]);
q=px.CCT(6000,10,2000,7000);
Serial.printf("%d %d %d %d %d\n",q.col[0],q.col[1],q.col[2],q.col[3],q.col[4]);
q=px.CCT(6000,10);
Serial.printf("%d %d %d %d %d\n",q.col[0],q.col[1],q.col[2],q.col[3],q.col[4]);
q.CCT(7000,50,2000,7000);
Serial.printf("%d %d %d %d %d\n",q.col[0],q.col[1],q.col[2],q.col[3],q.col[4]);
q=Pixel::CCT(8000,25,2000,7000);
Serial.printf("%d %d %d %d %d\n",q.col[0],q.col[1],q.col[2],q.col[3],q.col[4]);
Pixel::Color c[8]={
Pixel::RGB(255,0,0),
Pixel::RGB(255,0,0),
Pixel::RGB(255,0,0),
px.RGB(0,255,0),
px.RGB(0,255,0),
Pixel::RGB(0,0,255),
Pixel::RGB(0,0,255),
Pixel::RGB(0,0,255)
};
Pixel::Color d[8]={
Pixel::HSV(0,100,10),
Pixel::HSV(0,100,10),
Pixel::HSV(0,100,10),
Pixel::HSV(120,100,10),
Pixel::HSV(120,100,10),
Pixel::HSV(240,100,10),
Pixel::HSV(240,100,10),
Pixel::HSV(240,100,10)
};
Serial.printf("Starting cycles of RGB pattern...\n");
for(int i=0;i<NCYCLES;i++){
px.set(c,8);
delay(1000);
px.set(d,8);
delay(1000);
}
delay(5000);
px.set(Pixel::RGB(0,0,0,255,0),100);
delay(2000);
px.set(Pixel::RGB(0,0,0,0,255),100);
delay(2000);
px.set(Pixel::RGB(0,0,0,0,0),100);
while(1);
rf.clear(); // clear the pulse train memory buffer
servo.set(NAN);
for(int i=0;i<COUNT;i++)
rf.add(ONTIME,OFFTIME);
rf.phase(OFFTIME,LOW);
delay(10000);
long int x,y;
Serial.printf("Starting cycles of pulses without carrier...\n");
x=millis();
rf.start(NCYCLES,100);
y=millis();
Serial.printf("Total time: %ld ms\n",y-x);
servo.set(0);
rf.enableCarrier(10,0.4);
Serial.printf("Starting cycles of pulses with 10 Hz carrier...\n");
x=millis();
rf.start(NCYCLES,100);
y=millis();
Serial.printf("Total time: %ld ms\n",y-x);
Serial.printf("Turning off RGB LEDs.\n");
px.set(Pixel::RGB(0,0,0),8);
Serial.print("Done!\n");
}
//////////////////////////////////////
void loop(){
}

View File

@@ -1,7 +1,7 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 Gregg E. Berman
* Copyright (c) 2020-2025 Gregg E. Berman
*
* https://github.com/HomeSpan/HomeSpan
*
@@ -27,12 +27,15 @@
#pragma once
#include <Arduino.h>
//////////////////////////////////////////////////////
// HomeSpan Version //
#define HS_MAJOR 1
#define HS_MINOR 9
#define HS_PATCH 1
#define HS_MAJOR 2
#define HS_MINOR 1
#define HS_PATCH 2
#define HS_EXTENSION ""
//////////////////////////////////////////////////////
@@ -41,17 +44,22 @@
#include <FATAL_ERROR>
#endif
#if !(defined(CONFIG_IDF_TARGET_ESP32) || defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32C6))
#error ERROR: SELECTED MICROCONTROLLER NOT SUPPORTED. HOMESPAN SUPPORTS THE FOLLOWING CHIPS: ESP32, ESP32-S2, ESP32-S3, ESP32-C3, AND ESP32-C6
#include <FATAL_ERROR>
#endif
#include <esp_arduino_version.h>
#if ESP_ARDUINO_VERSION_MAJOR!=2
#error ERROR: HOMESPAN REQUIRES VERSION 2 OF THE ARDUINO ESP32 LIBRARY. HOMESPAN IS NOT COMPATIBLE WITH VERSION 1 OR VERSION 3
#if ESP_ARDUINO_VERSION < ESP_ARDUINO_VERSION_VAL(3, 0, 2)
#error ERROR: THIS VERSION OF HOMESPAN REQUIRES VERSION 3.0.2 OR GREATER OF THE ARDUINO-ESP32 BOARD MANAGER
#include <FATAL_ERROR>
#endif
#define STRINGIFY(x) _STR(x)
#define _STR(x) #x
#define HOMESPAN_VERSION STRINGIFY(HS_MAJOR) "." STRINGIFY(HS_MINOR) "." STRINGIFY(HS_PATCH)
#define HOMESPAN_VERSION STRINGIFY(HS_MAJOR) "." STRINGIFY(HS_MINOR) "." STRINGIFY(HS_PATCH) HS_EXTENSION
#define VERSION(major,minor,patch) major*10000+minor*100+patch