ESP + Reorganizacja

This commit is contained in:
Kamil Siejka
2024-10-03 10:05:46 +02:00
parent 61df70df2c
commit d5e3929a12
124 changed files with 18835 additions and 0 deletions

View File

@@ -0,0 +1,192 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 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.
*
********************************************************************************/
//////////////////////////////////////////
// HAP CHARACTERISTICS (HAP Chapter 9) //
//////////////////////////////////////////
enum PERMS{ // create bitflags based on HAP Table 6-4
PR=1,
PW=2,
EV=4,
AA=8,
TW=16,
HD=32,
WR=64,
NV=128 // this is a non-HAP flag used to specify that no value should be provided (should be a HAP flag!)
};
enum FORMAT { // HAP Table 6-5
BOOL=0,
UINT8=1,
UINT16=2,
UINT32=3,
UINT64=4,
INT=5,
FLOAT=6,
STRING=7,
DATA=8,
TLV_ENC=9
};
///////////////////////////////
struct HapChar {
const char *type;
const char *hapName;
PERMS perms;
FORMAT format;
boolean staticRange;
};
///////////////////////////////
#define HAPCHAR(hapName,type,perms,format,staticRange) HapChar hapName {#type,#hapName,(PERMS)(perms),format,staticRange}
struct HapCharacteristics {
HAPCHAR( AccessoryFlags, A6, PR+EV, UINT32, true );
HAPCHAR( Active, B0, PW+PR+EV, UINT8, true );
HAPCHAR( ActiveIdentifier, E7, PW+PR+EV, UINT32, true );
HAPCHAR( AirQuality, 95, PR+EV, UINT8, true );
HAPCHAR( BatteryLevel, 68, PR+EV, UINT8, false );
HAPCHAR( Brightness, 8, PR+PW+EV, INT, false );
HAPCHAR( CarbonMonoxideLevel, 90, PR+EV, FLOAT, false );
HAPCHAR( CarbonMonoxidePeakLevel, 91, PR+EV, FLOAT, false );
HAPCHAR( CarbonDioxideDetected, 92, PR+EV, UINT8, true );
HAPCHAR( CarbonDioxideLevel, 93, PR+EV, FLOAT, false );
HAPCHAR( CarbonDioxidePeakLevel, 94, PR+EV, FLOAT, false );
HAPCHAR( CarbonMonoxideDetected, 69, PR+EV, UINT8, true );
HAPCHAR( ChargingState, 8F, PR+EV, UINT8, true );
HAPCHAR( ClosedCaptions, DD, PW+PR+EV, UINT8, true );
HAPCHAR( CoolingThresholdTemperature, D, PR+PW+EV, FLOAT, false );
HAPCHAR( ColorTemperature, CE, PR+PW+EV, UINT32, false );
HAPCHAR( ConfiguredName, E3, PW+PR+EV, STRING, false );
HAPCHAR( ContactSensorState, 6A, PR+EV, UINT8, true );
HAPCHAR( CurrentAmbientLightLevel, 6B, PR+EV, FLOAT, false );
HAPCHAR( CurrentHorizontalTiltAngle, 6C, PR+EV, INT, false );
HAPCHAR( CurrentAirPurifierState, A9, PR+EV, UINT8, true );
HAPCHAR( CurrentSlatState, AA, PR+EV, UINT8, true );
HAPCHAR( CurrentPosition, 6D, PR+EV, UINT8, false );
HAPCHAR( CurrentVerticalTiltAngle, 6E, PR+EV, INT, false );
HAPCHAR( CurrentHumidifierDehumidifierState, B3, PR+EV, UINT8, true );
HAPCHAR( CurrentDoorState, E, PR+EV, UINT8, true );
HAPCHAR( CurrentFanState, AF, PR+EV, UINT8, true );
HAPCHAR( CurrentHeatingCoolingState, F, PR+EV, UINT8, true );
HAPCHAR( CurrentHeaterCoolerState, B1, PR+EV, UINT8, true );
HAPCHAR( CurrentMediaState, E0, PR+EV, UINT8, true );
HAPCHAR( CurrentRelativeHumidity, 10, PR+EV, FLOAT, false );
HAPCHAR( CurrentTemperature, 11, PR+EV, FLOAT, false );
HAPCHAR( CurrentTiltAngle, C1, PR+EV, INT, false );
HAPCHAR( CurrentVisibilityState, 135, PR+EV, UINT8, true );
HAPCHAR( DisplayOrder, 136, PR+EV, TLV_ENC, true );
HAPCHAR( FilterLifeLevel, AB, PR+EV, FLOAT, false );
HAPCHAR( FilterChangeIndication, AC, PR+EV, UINT8, true );
HAPCHAR( FirmwareRevision, 52, PR+EV, STRING, true );
HAPCHAR( HardwareRevision, 53, PR, STRING, true );
HAPCHAR( HeatingThresholdTemperature, 12, PR+PW+EV, FLOAT, false );
HAPCHAR( HoldPosition, 6F, PW, BOOL, true );
HAPCHAR( Hue, 13, PR+PW+EV, FLOAT, false );
HAPCHAR( Identify, 14, PW, BOOL, true );
HAPCHAR( Identifier, E6, PR, UINT32, true );
HAPCHAR( InputDeviceType, DC, PR+EV, UINT8, true );
HAPCHAR( InputSourceType, DB, PR+EV, UINT8, true );
HAPCHAR( InUse, D2, PR+EV, UINT8, true );
HAPCHAR( IsConfigured, D6, PR+EV, UINT8, true );
HAPCHAR( LeakDetected, 70, PR+EV, UINT8, true );
HAPCHAR( LockCurrentState, 1D, PR+EV, UINT8, true );
HAPCHAR( LockPhysicalControls, A7, PW+PR+EV, UINT8, true );
HAPCHAR( LockTargetState, 1E, PW+PR+EV, UINT8, true );
HAPCHAR( Manufacturer, 20, PR, STRING, true );
HAPCHAR( Model, 21, PR, STRING, true );
HAPCHAR( MotionDetected, 22, PR+EV, BOOL, true );
HAPCHAR( Mute, 11A, PW+PR+EV, BOOL, true );
HAPCHAR( Name, 23, PR, STRING, true );
HAPCHAR( NitrogenDioxideDensity, C4, PR+EV, FLOAT, false );
HAPCHAR( ObstructionDetected, 24, PR+EV, BOOL, true );
HAPCHAR( PM25Density, C6, PR+EV, FLOAT, false );
HAPCHAR( OccupancyDetected, 71, PR+EV, UINT8, true );
HAPCHAR( OutletInUse, 26, PR+EV, BOOL, true );
HAPCHAR( On, 25, PR+PW+EV, BOOL, true );
HAPCHAR( OzoneDensity, C3, PR+EV, FLOAT, false );
HAPCHAR( PM10Density, C7, PR+EV, FLOAT, false );
HAPCHAR( PictureMode, E2, PW+PR+EV, UINT8, true );
HAPCHAR( PositionState, 72, PR+EV, UINT8, true );
HAPCHAR( PowerModeSelection, DF, PW, UINT8, true );
HAPCHAR( ProgramMode, D1, PR+EV, UINT8, true );
HAPCHAR( ProgrammableSwitchEvent, 73, PR+EV+NV, UINT8, true );
HAPCHAR( RelativeHumidityDehumidifierThreshold, C9, PR+PW+EV, FLOAT, false );
HAPCHAR( RelativeHumidityHumidifierThreshold, CA, PR+PW+EV, FLOAT, false );
HAPCHAR( RemainingDuration, D4, PR+EV, UINT32, false );
HAPCHAR( RemoteKey, E1, PW, UINT8, true );
HAPCHAR( ResetFilterIndication, AD, PW, UINT8, true );
HAPCHAR( RotationDirection, 28, PR+PW+EV, INT, true );
HAPCHAR( RotationSpeed, 29, PR+PW+EV, FLOAT, false );
HAPCHAR( Saturation, 2F, PR+PW+EV, FLOAT, false );
HAPCHAR( SecuritySystemAlarmType, 8E, PR+EV, UINT8, true );
HAPCHAR( SecuritySystemCurrentState, 66, PR+EV, UINT8, true );
HAPCHAR( SecuritySystemTargetState, 67, PW+PR+EV, UINT8, true );
HAPCHAR( SerialNumber, 30, PR, STRING, true );
HAPCHAR( ServiceLabelIndex, CB, PR, UINT8, true );
HAPCHAR( ServiceLabelNamespace, CD, PR, UINT8, true );
HAPCHAR( SlatType, C0, PR, UINT8, true );
HAPCHAR( SleepDiscoveryMode, E8, PR+EV, UINT8, true );
HAPCHAR( SmokeDetected, 76, PR+EV, UINT8, true );
HAPCHAR( StatusActive, 75, PR+EV, BOOL, true );
HAPCHAR( StatusFault, 77, PR+EV, UINT8, true );
HAPCHAR( StatusJammed, 78, PR+EV, UINT8, true );
HAPCHAR( StatusLowBattery, 79, PR+EV, UINT8, true );
HAPCHAR( StatusTampered, 7A, PR+EV, UINT8, true );
HAPCHAR( SulphurDioxideDensity, C5, PR+EV, FLOAT, false );
HAPCHAR( SwingMode, B6, PR+EV+PW, UINT8, true );
HAPCHAR( TargetAirPurifierState, A8, PW+PR+EV, UINT8, true );
HAPCHAR( TargetFanState, BF, PW+PR+EV, UINT8, true );
HAPCHAR( TargetTiltAngle, C2, PW+PR+EV, INT, false );
HAPCHAR( TargetHeaterCoolerState, B2, PW+PR+EV, UINT8, true );
HAPCHAR( SetDuration, D3, PW+PR+EV, UINT32, false );
HAPCHAR( TargetHorizontalTiltAngle, 7B, PW+PR+EV, INT, false );
HAPCHAR( TargetHumidifierDehumidifierState, B4, PW+PR+EV, UINT8, true );
HAPCHAR( TargetPosition, 7C, PW+PR+EV, UINT8, false );
HAPCHAR( TargetDoorState, 32, PW+PR+EV, UINT8, true );
HAPCHAR( TargetHeatingCoolingState, 33, PW+PR+EV, UINT8, true );
HAPCHAR( TargetMediaState, 137, PW+PR+EV, UINT8, true );
HAPCHAR( TargetRelativeHumidity, 34, PW+PR+EV, FLOAT, false );
HAPCHAR( TargetTemperature, 35, PW+PR+EV, FLOAT, false );
HAPCHAR( TargetVisibilityState, 134, PW+PR+EV, UINT8, true );
HAPCHAR( TemperatureDisplayUnits, 36, PW+PR+EV, UINT8, true );
HAPCHAR( TargetVerticalTiltAngle, 7D, PW+PR+EV, INT, false );
HAPCHAR( ValveType, D5, PR+EV, UINT8, true );
HAPCHAR( Version, 37, PR, STRING, true );
HAPCHAR( VOCDensity, C8, PR+EV, FLOAT, false );
HAPCHAR( Volume, 119, PW+PR+EV, UINT8, false );
HAPCHAR( VolumeControlType, E9, PR+EV, UINT8, true );
HAPCHAR( VolumeSelector, EA, PW, UINT8, true );
HAPCHAR( WaterLevel, B5, PR+EV, FLOAT, false );
};
extern HapCharacteristics hapChars;

View File

@@ -0,0 +1,72 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 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.
*
********************************************************************************/
// 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
#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)
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
};
#define DEVICE_SUFFIX ""
#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)
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
BUILTIN_PIXEL=18 // Built-in Neo-Pixel
};
#define DEVICE_SUFFIX "-S2"
#elif defined(ARDUINO_ESP32C3_DEV)
enum {
F27=19,F32=2,F14=10,F16=20,F17=21,F21=18, // Digital Only (6 pins)
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
BUILTIN_PIXEL=8 // Built-in Neo-Pixel
};
#define DEVICE_SUFFIX "-C3"
#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)
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
BUILTIN_PIXEL=48 // Built-in Neo-Pixel
};
#define DEVICE_SUFFIX "-S3"
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,239 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 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 <sstream>
#include <WiFi.h>
#include "HomeSpan.h"
#include "HAPConstants.h"
#include "HKDF.h"
#include "SRP.h"
const TLV8_names HAP_Names[] = {
{kTLVType_Separator,"SEPARATOR"},
{kTLVType_State,"STATE"},
{kTLVType_PublicKey,"PUBKEY"},
{kTLVType_Method,"METHOD"},
{kTLVType_Salt,"SALT"},
{kTLVType_Error,"ERROR"},
{kTLVType_Proof,"PROOF"},
{kTLVType_EncryptedData,"ENC.DATA"},
{kTLVType_Signature,"SIGNATURE"},
{kTLVType_Identifier,"IDENTIFIER"},
{kTLVType_Permissions,"PERMISSION"}
};
#define hap_controller_IDBYTES 36
#define hap_accessory_IDBYTES 17
/////////////////////////////////////////////////
// NONCE Structure (HAP used last 64 of 96 bits)
struct Nonce {
uint8_t x[12];
Nonce();
void zero();
uint8_t *get();
void inc();
};
/////////////////////////////////////////////////
// Accessory Structure for Permanently-Stored Data
struct Accessory {
uint8_t ID[hap_accessory_IDBYTES]; // Pairing ID in form "XX:XX:XX:XX:XX:XX" (no null terminator)
uint8_t LTSK[crypto_sign_SECRETKEYBYTES]; // Long Term Ed2519 Secret Key
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
struct HAPClient {
// common structures and data shared across all HAP Clients
static const int MAX_HTTP=8096; // max number of bytes allowed for HTTP message
static const int MAX_CONTROLLERS=16; // maximum number of paired controllers (HAP requires at least 16)
static const int MAX_ACCESSORIES=150; // maximum number of allowed Accessories (HAP limit=150)
static pairState pairStatus; // tracks pair-setup status
static Accessory accessory; // Accessory ID and Ed25519 public and secret keys - permanently stored
static list<Controller, Mallocator<Controller>> controllerList; // linked-list of Paired Controller IDs and ED25519 long-term public keys - permanently stored
// individual structures and data defined for each Hap Client connection
WiFiClient 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)
// These temporary Curve25519 keys are generated in the first call to pair-verify and used in the second call to pair-verify so must persist for a short period
struct tempKeys_t {
uint8_t publicCurveKey[crypto_box_PUBLICKEYBYTES]; // Accessory's Curve25519 Public Key
uint8_t sharedCurveKey[crypto_box_PUBLICKEYBYTES]; // Shared-Secret Curve25519 Key derived from Accessory's Secret Key and Controller's Public Key
uint8_t sessionKey[crypto_box_PUBLICKEYBYTES]; // Session Key Curve25519 (derived with various HKDF calls)
uint8_t iosCurveKey[crypto_box_PUBLICKEYBYTES]; // Controller's Curve25519 Public Key
} temp;
// CurveKey and CurveKey Nonces are created once each new session is verified in /pair-verify. Keys persist for as long as connection is open
uint8_t a2cKey[32]; // AccessoryToControllerKey derived from HKDF-SHA-512 of sharedCurveKey (HAP Section 6.5.2)
uint8_t c2aKey[32]; // ControllerToAccessoryKey derived from HKDF-SHA-512 of sharedCurveKey (HAP Section 6.5.2)
Nonce a2cNonce; // encryption nonce (starts at zero at end of each Pair-Verify and increment every encryption - NOT DOCUMENTED)
Nonce c2aNonce; // decryption nonce (starts at zero at end of each Pair-Verify and increment every encryption - NOT DOCUMENTED)
// define member methods
void processRequest(); // process HAP request
int postPairSetupURL(uint8_t *content, size_t len); // POST /pair-setup (HAP Section 5.6)
int postPairVerifyURL(uint8_t *content, size_t len); // POST /pair-verify (HAP Section 5.7)
int postPairingsURL(uint8_t *content, size_t len); // POST /pairings (HAP Sections 5.10-5.12)
int getAccessoriesURL(); // GET /accessories (HAP Section 6.6)
int getCharacteristicsURL(char *urlBuf); // GET /characteristics (HAP Section 6.7.4)
int putCharacteristicsURL(char *json); // PUT /characteristics (HAP Section 6.7.2)
int putPrepareURL(char *json); // PUT /prepare (HAP Section 6.7.2.4)
void tlvRespond(TLV8 &tlv8); // respond to client with HTTP OK header and all defined TLV data records
int receiveEncrypted(uint8_t *httpBuf, int messageSize); // decrypt HTTP request (HAP Section 6.5)
int notFoundError(); // return 404 error
int badRequestError(); // return 400 error
int unauthorizedError(); // return 470 error
// define static methods
static void init(); // initialize HAP after start-up
static void hexPrintColumn(const uint8_t *buf, int n, int minLogLevel=0); // prints 'n' bytes of *buf as HEX, one byte per row, subject to specified minimum log level
static void hexPrintRow(const uint8_t *buf, int n, int minLogLevel=0); // prints 'n' bytes of *buf as HEX, all on one row, subject to specified minimum log level
static void charPrintRow(const uint8_t *buf, int n, int minLogLevel=0); // prints 'n' bytes of *buf as CHAR, all on one row, subject to specified minimum log level
static Controller *findController(uint8_t *id); // returns pointer to controller with matching ID (or NULL if no match)
static tagError addController(uint8_t *id, uint8_t *ltpk, boolean admin); // stores data for new Controller with specified data. Returns tagError (if any)
static void removeController(uint8_t *id); // removes specific Controller. If no remaining admin Controllers, remove all others (if any) as per HAP requirements.
static void printControllers(int minLogLevel=0); // prints IDs of all allocated (paired) Controller, subject to specified minimum log level
static void saveControllers(); // saves Controller list in NVS
static int nAdminControllers(); // returns number of admin Controller
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 getStatusURL(HAPClient *, void (*)(const char *, void *), void *); // GET / status (an optional, non-HAP feature)
class HAPTLV : public TLV8 { // dedicated class for HAP TLV8 records
public:
HAPTLV() : TLV8(HAP_Names,11){}
};
};
/////////////////////////////////////////////////
// HapOut Structure
class HapOut : public std::ostream {
private:
struct HapStreamBuffer : public std::streambuf {
const size_t bufSize=1024; // max allowed for HAP encrypted records
char *buffer;
uint8_t *encBuf;
HAPClient *hapClient=NULL;
int logLevel=255; // default is NOT to print anything
boolean enablePrettyPrint=false;
size_t byteCount=0;
size_t indent=0;
uint8_t *hash;
mbedtls_sha512_context *ctx;
void (*callBack)(const char *, void *)=NULL;
void *callBackUserData = NULL;
void flushBuffer();
int_type overflow(int_type c) override;
int sync() override;
size_t getSize(){return(byteCount+pptr()-pbase());}
void printFormatted(char *buf, size_t nChars, size_t nsp);
HapStreamBuffer();
~HapStreamBuffer();
};
HapStreamBuffer hapBuffer;
public:
HapOut() : std::ostream(&hapBuffer){}
HapOut& setHapClient(HAPClient *hapClient){hapBuffer.hapClient=hapClient;return(*this);}
HapOut& setLogLevel(int logLevel){hapBuffer.logLevel=logLevel;return(*this);}
HapOut& prettyPrint(){hapBuffer.enablePrettyPrint=true;hapBuffer.logLevel=0;return(*this);}
HapOut& setCallback(void(*f)(const char *, void *)){hapBuffer.callBack=f;return(*this);}
HapOut& setCallbackUserData(void *userData){hapBuffer.callBackUserData=userData;return(*this);}
uint8_t *getHash(){return(hapBuffer.hash);}
size_t getSize(){return(hapBuffer.getSize());}
};
/////////////////////////////////////////////////
// Extern Variables
extern HapOut hapOut;

View File

@@ -0,0 +1,89 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 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
// HAP TLV Types (HAP Table 5-6)
typedef enum {
kTLVType_Method=0x00,
kTLVType_Identifier=0x01,
kTLVType_Salt=0x02,
kTLVType_PublicKey=0x03,
kTLVType_Proof=0x04,
kTLVType_EncryptedData=0x05,
kTLVType_State=0x06,
kTLVType_Error=0x07,
kTLVType_RetryDelay=0x08,
kTLVType_Certificate=0x09,
kTLVType_Signature=0x0A,
kTLVType_Permissions=0x0B,
kTLVType_FragmentData=0x0C,
kTLVType_FragmentLast=0x0D,
kTLVType_Flags=0x13,
kTLVType_Separator=0xFF
} kTLVType;
// HAP Error Codes (HAP Table 5-5)
typedef enum {
tagError_None=0x00,
tagError_Unknown=0x01,
tagError_Authentication=0x02,
tagError_Backoff=0x03,
tagError_MaxPeers=0x04,
tagError_MaxTries=0x05,
tagError_Unavailable=0x06,
tagError_Busy=0x07
} tagError;
// Pair-Setup and Pair-Verify States
typedef enum {
pairState_M0=0,
pairState_M1=1,
pairState_M2=2,
pairState_M3=3,
pairState_M4=4,
pairState_M5=5,
pairState_M6=6
} pairState;
// HAP Status Codes (HAP Table 6-11)
enum class StatusCode {
OK=0,
Unable=-70402,
ReadOnly=-70404,
WriteOnly=-70405,
NotifyNotAllowed=-70406,
UnknownResource=-70409,
InvalidValue=-70410,
TBD=-1 // status To-Be-Determined (TBD) once service.update() called - internal use only
};

View File

@@ -0,0 +1,217 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 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.
*
********************************************************************************/
#include <mbedtls/hkdf.h>
#include <mbedtls/platform_util.h>
#include "HKDF.h"
/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////
// Wrapper function to call mbedtls_hkdf, below, with
// HAP-specific parameters and assumptions
int HKDF::create(uint8_t *outputKey, uint8_t *inputKey, int inputLen, const char *salt, const char *info){
return(mbedtls_hkdf( mbedtls_md_info_from_type(MBEDTLS_MD_SHA512),
(uint8_t *) salt, (size_t) strlen(salt),
inputKey, (size_t) inputLen,
(uint8_t *) info, (size_t) strlen(info),
outputKey, 32 ));
}
/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////
// CODE FOR HKDF IS MISSING FROM THE MBEDTLS LIBRARY INCLUDED WITH THE
// ARDUINO-ESP32 LIBRARY. THE CODE BELOW IS SOURCE DIRECTLY FROM THE MBEDTLS
// GITHUB. SEE THE MBEDTLS GITHUB SITE FOR LICENSING TERMS:
//
// https://github.com/ARMmbed/mbedtls/blob/development/LICENSE
//
//
int mbedtls_hkdf( const mbedtls_md_info_t *md, const unsigned char *salt,
size_t salt_len, const unsigned char *ikm, size_t ikm_len,
const unsigned char *info, size_t info_len,
unsigned char *okm, size_t okm_len )
{
int ret;
unsigned char prk[MBEDTLS_MD_MAX_SIZE];
ret = mbedtls_hkdf_extract( md, salt, salt_len, ikm, ikm_len, prk );
if( ret == 0 )
{
ret = mbedtls_hkdf_expand( md, prk, mbedtls_md_get_size( md ),
info, info_len, okm, okm_len );
}
mbedtls_platform_zeroize( prk, sizeof( prk ) );
return( ret );
}
int mbedtls_hkdf_extract( const mbedtls_md_info_t *md,
const unsigned char *salt, size_t salt_len,
const unsigned char *ikm, size_t ikm_len,
unsigned char *prk )
{
unsigned char null_salt[MBEDTLS_MD_MAX_SIZE] = { '\0' };
if( salt == NULL )
{
size_t hash_len;
if( salt_len != 0 )
{
return MBEDTLS_ERR_HKDF_BAD_INPUT_DATA;
}
hash_len = mbedtls_md_get_size( md );
if( hash_len == 0 )
{
return MBEDTLS_ERR_HKDF_BAD_INPUT_DATA;
}
salt = null_salt;
salt_len = hash_len;
}
return( mbedtls_md_hmac( md, salt, salt_len, ikm, ikm_len, prk ) );
}
int mbedtls_hkdf_expand( const mbedtls_md_info_t *md, const unsigned char *prk,
size_t prk_len, const unsigned char *info,
size_t info_len, unsigned char *okm, size_t okm_len )
{
size_t hash_len;
size_t where = 0;
size_t n;
size_t t_len = 0;
size_t i;
int ret = 0;
mbedtls_md_context_t ctx;
unsigned char t[MBEDTLS_MD_MAX_SIZE];
if( okm == NULL )
{
return( MBEDTLS_ERR_HKDF_BAD_INPUT_DATA );
}
hash_len = mbedtls_md_get_size( md );
if( prk_len < hash_len || hash_len == 0 )
{
return( MBEDTLS_ERR_HKDF_BAD_INPUT_DATA );
}
if( info == NULL )
{
info = (const unsigned char *) "";
info_len = 0;
}
n = okm_len / hash_len;
if( okm_len % hash_len != 0 )
{
n++;
}
/*
* Per RFC 5869 Section 2.3, okm_len must not exceed
* 255 times the hash length
*/
if( n > 255 )
{
return( MBEDTLS_ERR_HKDF_BAD_INPUT_DATA );
}
mbedtls_md_init( &ctx );
if( ( ret = mbedtls_md_setup( &ctx, md, 1 ) ) != 0 )
{
goto exit;
}
memset( t, 0, hash_len );
/*
* Compute T = T(1) | T(2) | T(3) | ... | T(N)
* Where T(N) is defined in RFC 5869 Section 2.3
*/
for( i = 1; i <= n; i++ )
{
size_t num_to_copy;
unsigned char c = i & 0xff;
ret = mbedtls_md_hmac_starts( &ctx, prk, prk_len );
if( ret != 0 )
{
goto exit;
}
ret = mbedtls_md_hmac_update( &ctx, t, t_len );
if( ret != 0 )
{
goto exit;
}
ret = mbedtls_md_hmac_update( &ctx, info, info_len );
if( ret != 0 )
{
goto exit;
}
/* The constant concatenated to the end of each T(n) is a single octet.
* */
ret = mbedtls_md_hmac_update( &ctx, &c, 1 );
if( ret != 0 )
{
goto exit;
}
ret = mbedtls_md_hmac_finish( &ctx, t );
if( ret != 0 )
{
goto exit;
}
num_to_copy = i != n ? hash_len : okm_len - where;
memcpy( okm + where, t, num_to_copy );
where += hash_len;
t_len = hash_len;
}
exit:
mbedtls_md_free( &ctx );
mbedtls_platform_zeroize( t, sizeof( t ) );
return( ret );
}

View File

@@ -0,0 +1,43 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 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>
/////////////////////////////////////////////////
// HKDF-SHA-512 Structure
//
// This is a wrapper around mbedtls_hkdf, which is NOT
// included in the normal Arduino-ESP32 library.
// Code was instead sourced directly from MBED GitHub and
// incorporated under hkdf.cpp, with a wrapper to always
// use SHA-512 with 32 bytes of output as required by HAP.
namespace HKDF{
int create(uint8_t *outputKey, uint8_t *inputKey, int inputLen, const char *salt, const char *info); // output of HKDF is always a 32-byte key derived from an input key, a salt string, and an info string
};

View File

@@ -0,0 +1,59 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 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.
*
********************************************************************************/
class HapQR {
private:
char qrCode[21];
public:
enum {
NFC=1,
IP=2,
BLTE=4
};
char *get(uint32_t setupCode, const char *setupID, uint8_t category, uint8_t protocols=IP, uint8_t qVersion=0, uint8_t qReserved=0){
setupCode&=0x07FFFFFF; // valid values: 0-99999999
qVersion&=0x7; // valid values: 0-7
qReserved&=0xF; // valid values: 0-15
protocols&=0x7; // valid values: 0-7
uint64_t n=((uint64_t) qVersion<<43) | ((uint64_t) qReserved<<39) | ((uint64_t) category<<31) | (protocols<<27) | setupCode;
sprintf(qrCode,"X-HM://");
for(int i=15;i>=7;i--){
qrCode[i]=n%36+48;
if(qrCode[i]>57)
qrCode[i]+=7;
n/=36;
}
sprintf(qrCode+16,"%-4.4s",setupID);
return(qrCode);
} // create
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,832 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 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 "version.h"
#pragma GCC diagnostic ignored "-Wpmf-conversions" // eliminates warning messages from use of pointers to member functions to detect whether update() and loop() are overridden by user
#pragma GCC diagnostic ignored "-Wunused-result" // eliminates warning message regarded unused result from call to crypto_scalarmult_curve25519()
#include <Arduino.h>
#include <unordered_map>
#include <vector>
#include <list>
#include <nvs.h>
#include <ArduinoOTA.h>
#include <esp_now.h>
#include <mbedtls/base64.h>
#include "src/extras/Blinker.h"
#include "src/extras/Pixel.h"
#include "src/extras/RFControl.h"
#include "src/extras/PwmPin.h"
#include "src/extras/StepperControl.h"
#include "Settings.h"
#include "Utils.h"
#include "Network.h"
#include "HAPConstants.h"
#include "HapQR.h"
#include "Characteristics.h"
#include "TLV8.h"
using std::vector;
using std::unordered_map;
using std::list;
enum {
GET_AID=1,
GET_META=2,
GET_PERMS=4,
GET_TYPE=8,
GET_EV=16,
GET_DESC=32,
GET_NV=64,
GET_VALUE=128,
GET_STATUS=256
};
typedef boolean BOOL_t;
typedef uint8_t UINT8_t;
typedef uint16_t UINT16_t;
typedef uint32_t UINT32_t;
typedef uint64_t UINT64_t;
typedef int32_t INT_t;
typedef double FLOAT_t;
typedef const char * STRING_t;
typedef const TLV8 & TLV_ENC_t;
typedef std::pair<const uint8_t *, size_t> DATA_t;
static DATA_t NULL_DATA={NULL,0};
static TLV8 NULL_TLV{};
///////////////////////////////
#define STATUS_UPDATE(LED_UPDATE,MESSAGE_UPDATE) {homeSpan.statusLED->LED_UPDATE;if(homeSpan.statusCallback)homeSpan.statusCallback(MESSAGE_UPDATE);}
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_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
HS_CONFIG_MODE_LAUNCH_AP, // HomeSpan is in Command Mode with "Launch Access Point" specified as choice
HS_CONFIG_MODE_UNPAIR, // HomeSpan is in Command Mode with "Unpair Device" specified as choice
HS_CONFIG_MODE_ERASE_WIFI, // HomeSpan is in Command Mode with "Erase WiFi Credentials" specified as choice
HS_CONFIG_MODE_EXIT_SELECTED, // User has selected "Exit Command Mode"
HS_CONFIG_MODE_REBOOT_SELECTED, // User has select "Reboot" from the Command Mode
HS_CONFIG_MODE_LAUNCH_AP_SELECTED, // User has selected "Launch AP Access" from the Command Mode
HS_CONFIG_MODE_UNPAIR_SELECTED, // User has seleected "Unpair Device" from the Command Mode
HS_CONFIG_MODE_ERASE_WIFI_SELECTED, // User has selected "Erase WiFi Credentials" from the Command Mode
HS_REBOOTING, // HomeSpan is in the process of rebooting the device
HS_FACTORY_RESET, // HomeSpan is in the process of performing a Factory Reset of device
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
};
///////////////////////////////
// Forward-Declarations
struct Span;
struct SpanAccessory;
struct SpanService;
struct SpanCharacteristic;
struct SpanBuf;
struct SpanButton;
struct SpanUserCommand;
struct HAPClient;
class Controller;
extern Span homeSpan;
////////////////////////////////////////////////////////
// INTERNAL HOMESPAN STRUCTURES - NOT FOR USER ACCESS //
////////////////////////////////////////////////////////
struct SpanPartition{
char magicCookie[32];
uint8_t reserved[224];
};
///////////////////////////////
struct SpanConfig{
int configNumber=0; // configuration number - broadcast as Bonjour "c#" (computed automatically)
uint8_t hashCode[48]={0}; // SHA-384 hash of Span Database stored as a form of unique "signature" to know when to update the config number upon changes
};
///////////////////////////////
struct SpanBuf{ // temporary storage buffer for use with putCharacteristicsURL() and checkTimedResets()
uint32_t aid=0; // updated aid
uint32_t iid=0; // updated iid
boolean wr=false; // flag to indicate write-response has been requested
char *val=NULL; // updated value (optional, though either at least 'val' or 'ev' must be specified)
char *ev=NULL; // updated event notification flag (optional, though either at least 'val' or 'ev' must be specified)
StatusCode status; // return status (HAP Table 6-11)
SpanCharacteristic *characteristic=NULL; // Characteristic to update (NULL if not found)
};
///////////////////////////////
struct SpanWebLog{ // optional web status/log data
boolean isEnabled=false; // flag to inidicate WebLog has been enabled
uint16_t maxEntries=0; // max number of log entries;
int nEntries=0; // total cumulative number of log entries
const char *timeServer=NULL; // optional time server to use for acquiring clock time
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
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
struct log_t { // log entry type
uint64_t upTime; // number of seconds since booting
struct tm clockTime; // clock time
char *message; // pointers to log entries of arbitrary size
String clientIP; // IP address of client making request (or "0.0.0.0" if not applicable)
} *log=NULL; // array of log entries
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);
};
///////////////////////////////
struct SpanOTA{ // manages OTA process
char otaPwd[33]=""; // MD5 Hash of OTA password, represented as a string of hexidecimal characters
static boolean enabled; // enables OTA - default if not enabled
static boolean auth; // indicates whether OTA password is required
static int otaPercent;
static boolean safeLoad; // indicates whether OTA update should reject any application update that is not another HomeSpan sketch
int init(boolean auth, boolean safeLoad, const char *pwd);
int setPassword(const char *pwd);
static void start();
static void end();
static void progress(uint32_t progress, uint32_t total);
static void error(ota_error_t err);
};
//////////////////////////////////////
// USER API CLASSES BEGINS HERE //
//////////////////////////////////////
class Span{
friend class SpanAccessory;
friend class SpanService;
friend class SpanCharacteristic;
friend class SpanUserCommand;
friend class SpanButton;
friend class SpanWebLog;
friend class SpanOTA;
friend class Network;
friend class HAPClient;
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
char *hostNameSuffix=NULL; // optional "suffix" of hostName of this device. If specified, will be used as the hostName suffix instead of the 6-byte accessoryID
char *hostName=NULL; // derived full hostname
char *modelName; // model name of this device - broadcast as Bonjour field "md"
char category[3]=""; // category ID of primary accessory - broadcast as Bonjour field "ci" (HAP Section 13)
unsigned long snapTime; // current time (in millis) snapped before entering Service loops() or updates()
boolean isInitialized=false; // flag indicating HomeSpan has been initialized
boolean isBridge=true; // flag indicating whether device is configured as a bridge (i.e. first Accessory contains nothing but AccessoryInformation and HAPProtocolInformation)
HapQR qrCode; // optional QR Code to use for pairing
const char *sketchVersion="n/a"; // version of the sketch
char pairingCodeCommand[12]=""; // user-specified Pairing Code - only needed if Pairing Setup Code is specified in sketch using setPairingCode()
String lastClientIP="0.0.0.0"; // IP address of last client accessing device through encrypted channel
boolean newCode; // flag indicating new application code has been loaded (based on keeping track of app SHA256)
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
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
nvs_handle srpNVS; // handle for non-volatile storage of SRP data
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
unsigned long alarmConnect=0; // time after which WiFi connection attempt should be tried again
const char *defaultSetupCode=DEFAULT_SETUP_CODE; // Setup Code used for pairing
uint16_t autoOffLED=0; // automatic turn-off duration (in seconds) for Status LED
int logLevel=DEFAULT_LOG_LEVEL; // level for writing out log messages to serial monitor
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 (*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
void (*apFunction)()=NULL; // optional function to invoke when starting Access Point
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
WiFiServer *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
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
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
list<HAPClient, Mallocator<HAPClient>> hapList; // linked-list of HAPClient structures containing HTTP client connections, parsing routines, and state variables
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
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 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
static boolean invalidUUID(const char *uuid){
int x=0;
sscanf(uuid,"%*8[0-9a-fA-F]%n",&x); // check for short-form of UUID
if(strlen(uuid)==x && uuid[0]!='0')
return(false);
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);
}
public:
Span(); // constructor
void begin(Category catID=DEFAULT_CATEGORY,
const char *displayName=DEFAULT_DISPLAY_NAME,
const char *hostNameBase=DEFAULT_HOST_NAME,
const char *modelName=DEFAULT_MODEL_NAME);
void poll(); // calls pollTask() with some error checking
void processSerialCommand(const char *c); // process command 'c' (typically from readSerial, though can be called with any 'c')
boolean updateDatabase(boolean updateMDNS=true); // updates HAP Configuration Number and Loop vector; if updateMDNS=true and config number has changed, re-broadcasts MDNS 'c#' record; returns true if config number changed
boolean deleteAccessory(uint32_t aid); // deletes Accessory with matching aid; returns true if found, else returns false
Span& setControlPin(uint8_t pin, PushButton::triggerType_t triggerType=PushButton::TRIGGER_ON_LOW){ // sets Control Pin, with optional trigger type
controlButton=new PushButton(pin, triggerType);
return(*this);
}
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& 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)
int getStatusPin(){return(statusLED->getPin());} // get Status Pin (returns -1 if undefined)
void refreshStatusDevice(){if(statusLED)statusLED->refresh();} // refreshes state of Status LED
Span& setApSSID(const char *ssid){network.apSSID=ssid;return(*this);} // sets Access Point SSID
Span& setApPassword(const char *pwd){network.apPassword=pwd;return(*this);} // sets Access Point Password
Span& setApTimeout(uint16_t nSec){network.lifetime=nSec*1000;return(*this);} // sets Access Point Timeout (seconds)
Span& setCommandTimeout(uint16_t nSec){comModeLife=nSec*1000;return(*this);} // sets Command Mode Timeout (seconds)
Span& setLogLevel(int level){logLevel=level;return(*this);} // sets Log Level for log messages (0=baseline, 1=intermediate, 2=all, -1=disable all serial input/output)
int getLogLevel(){return(logLevel);} // get Log Level
Span& setSerialInputDisable(boolean val){serialInputDisabled=val;return(*this);} // sets whether serial input is disabled (true) or enabled (false)
boolean getSerialInputDisable(){return(serialInputDisabled);} // returns true if serial input is disabled, or false if serial input in enabled
Span& setPortNum(uint16_t port){tcpPortNum=port;return(*this);} // sets the TCP port number to use for communications between HomeKit and HomeSpan
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& 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& 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
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)
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);
}
void addWebLog(boolean sysMsg, const char *fmt, ...){ // add Web Log entry
va_list ap;
va_start(ap,fmt);
webLog.vLog(sysMsg,fmt,ap);
va_end(ap);
}
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 *);
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));
}
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
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);}
};
///////////////////////////////
class SpanAccessory{
friend class Span;
friend class SpanService;
friend class SpanCharacteristic;
friend class SpanButton;
uint32_t aid=0; // Accessory Instance ID (HAP Table 6-1)
uint32_t iidCount=0; // running count of iid to use for Services and Characteristics associated with this Accessory
vector<SpanService *, Mallocator<SpanService*>> Services; // vector of pointers to all Services in this Accessory
void printfAttributes(int flags); // writes Accessory JSON to hapOut stream
protected:
~SpanAccessory(); // destructor
public:
void *operator new(size_t size){return(HS_MALLOC(size));} // override new operator to use PSRAM when available
SpanAccessory(uint32_t aid=0); // constructor
};
///////////////////////////////
class SpanService{
friend class Span;
friend class SpanAccessory;
friend class SpanCharacteristic;
uint32_t iid=0; // Instance ID (HAP Table 6-2)
const char *type; // Service Type
const char *hapName; // HAP Name
boolean hidden=false; // optional property indicating service is hidden
boolean primary=false; // optional property indicating service is primary
vector<SpanCharacteristic *, Mallocator<SpanCharacteristic*>> Characteristics; // vector of pointers to all Characteristics in this Service
vector<SpanService *, Mallocator<SpanService *>> linkedServices; // vector of pointers to any optional linked Services
boolean isCustom; // flag to indicate this is a Custom Service
SpanAccessory *accessory=NULL; // pointer to Accessory containing this Service
void printfAttributes(int flags); // writes Service JSON to hapOut stream
protected:
virtual ~SpanService(); // destructor
vector<HapChar *, Mallocator<HapChar*>> req; // vector of pointers to all required HAP Characteristic Types for this Service
vector<HapChar *, Mallocator<HapChar*>> opt; // vector of pointers to all optional HAP Characteristic Types for this Service
public:
void *operator new(size_t size){return(HS_MALLOC(size));} // override new operator to use PSRAM when available
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
SpanService *addLink(SpanService *svc); // adds svc as a Linked Service and returns pointer to self
template <typename T=SpanService *> vector<T, Mallocator<T>> getLinks(const char *hapName=NULL){ // returns linkedServices vector, mapped to <T>, for use as range in "for-each" loops
vector<T, Mallocator<T>> v;
for(auto svc : linkedServices){
if(hapName==NULL || !strcmp(hapName,svc->hapName))
v.push_back(static_cast<T>(svc));
}
return(v);
}
uint32_t getIID(){return(iid);} // returns IID of Service
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
virtual void button(int pin, int pressType){} // method called for a Service when a button attached to "pin" has a Single, Double, or Long Press, according to pressType
};
///////////////////////////////
class SpanCharacteristic{
friend class Span;
friend class SpanService;
union UVal {
boolean BOOL;
uint8_t UINT8;
uint16_t UINT16;
uint32_t UINT32;
uint64_t UINT64;
int32_t INT;
double FLOAT;
char * STRING = NULL;
};
class EVLIST : public vector<HAPClient *, Mallocator<HAPClient *>>{ // vector of current connections that have subscribed to EV notifications for this Characteristic
public:
boolean has(HAPClient *hc); // returns true if pointer to connection hc is subscribed, else returns false
void add(HAPClient *hc); // adds connection hc as new subscriber, IF not already a subscriber
void remove(HAPClient *hc); // removes connection hc as a subscriber; okay to remove even if hc was not already a subscriber
};
uint32_t iid=0; // Instance ID (HAP Table 6-3)
HapChar *hapChar; // pointer to HAP Characteristic structure
const char *type; // Characteristic Type
const char *hapName; // HAP Name
UVal value; // Characteristic Value
uint8_t perms; // Characteristic Permissions
FORMAT format; // Characteristic Format
char *desc=NULL; // Characteristic Description (optional)
char *unit=NULL; // Characteristic Unit (optional)
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)
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
char *nvsKey=NULL; // key for NVS storage of Characteristic value
boolean isCustom; // flag to indicate this is a Custom Characteristic
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
SpanService *service=NULL; // pointer to Service containing this Characteristic
EVLIST evList; // vector of current connections that have subscribed to EV notifications for this Characteristic
void printfAttributes(int flags); // writes Characteristic JSON to hapOut stream
StatusCode loadUpdate(char *val, char *ev, boolean wr); // load updated val/ev from PUT /characteristic JSON request. Return intitial HAP status code (checks to see if characteristic is found, is writable, etc.)
String uvPrint(UVal &u); // returns "printable" String for any type of Characteristic
void uvSet(UVal &dest, UVal &src); // copies UVal src into UVal dest
void uvSet(UVal &u, STRING_t val); // copies string val into UVal u
void uvSet(UVal &u, DATA_t data); // copies DATA data into UVal u (after transforming to a char *)
void uvSet(UVal &u, TLV_ENC_t tlv); // copies TLV8 tlv into UVal u (after transforming to a char *)
template <typename T> void uvSet(UVal &u, T val){ // copies numeric val into UVal u
switch(format){
case FORMAT::BOOL:
u.BOOL=(boolean)val;
break;
case FORMAT::INT:
u.INT=(int)val;
break;
case FORMAT::UINT8:
u.UINT8=(uint8_t)val;
break;
case FORMAT::UINT16:
u.UINT16=(uint16_t)val;
break;
case FORMAT::UINT32:
u.UINT32=(uint32_t)val;
break;
case FORMAT::UINT64:
u.UINT64=(uint64_t)val;
break;
case FORMAT::FLOAT:
u.FLOAT=(double)val;
break;
default:
break;
} // switch
}
char *getStringGeneric(UVal &val); // gets the specified UVal for string-based Characteristics
size_t getDataGeneric(uint8_t *data, size_t len, UVal &val); // gets the specified UVal for data-based Characteristics
size_t getTLVGeneric(TLV8 &tlv, UVal &val); // gets the specified UVal for tlv8-based Characteristics
template <class T> T uvGet(UVal &u){ // gets the specified UVal for numeric-based Characteristics
switch(format){
case FORMAT::BOOL:
return((T) u.BOOL);
case FORMAT::INT:
return((T) u.INT);
case FORMAT::UINT8:
return((T) u.UINT8);
case FORMAT::UINT16:
return((T) u.UINT16);
case FORMAT::UINT32:
return((T) u.UINT32);
case FORMAT::UINT64:
return((T) u.UINT64);
case FORMAT::FLOAT:
return((T) u.FLOAT);
default:
break;
}
return((T)0); // included to prevent compiler warnings
}
void setValCheck(); // initial check before setting value of any Characteristic
void setValFinish(boolean notify); // final processing after setting value of any Characteristic
protected:
~SpanCharacteristic(); // destructor
template <typename T> void init(T val, boolean nvsStore, T min, T max){
uvSet(value,val);
if(nvsStore){
nvsKey=(char *)HS_MALLOC(16);
uint16_t t;
sscanf(type,"%hx",&t);
sprintf(nvsKey,"%04X%08X%03X",t,aid,iid&0xFFF);
size_t len;
if(format<FORMAT::STRING){
if(nvs_get_u64(homeSpan.charNVS,nvsKey,&(value.UINT64))!=ESP_OK) {
nvs_set_u64(homeSpan.charNVS,nvsKey,value.UINT64); // store data as uint64_t regardless of actual type (it will be read correctly when access through uvGet())
nvs_commit(homeSpan.charNVS); // commit to NVS
}
} else {
if(!nvs_get_str(homeSpan.charNVS,nvsKey,NULL,&len)){
value.STRING = (char *)HS_REALLOC(value.STRING,len);
nvs_get_str(homeSpan.charNVS,nvsKey,value.STRING,&len);
}
else {
nvs_set_str(homeSpan.charNVS,nvsKey,value.STRING); // store string data
nvs_commit(homeSpan.charNVS); // commit to NVS
}
}
}
uvSet(newValue,value);
if(format<FORMAT::STRING){
uvSet(minValue,min);
uvSet(maxValue,max);
uvSet(stepValue,0);
}
} // init()
public:
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
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
size_t getData(uint8_t *data, size_t len){return(getDataGeneric(data,len,value));} // gets the value for data-based Characteristics
size_t getTLV(TLV8 &tlv){return(getTLVGeneric(tlv,value));} // gets the value for tlv8-based Characteristics
template <class T=int> T getNewVal(){return(uvGet<T>(newValue));} // gets the newValue for numeric-based Characteristics
char *getNewString(){return(getStringGeneric(newValue));} // gets the newValue for string-based Characteristics
size_t getNewData(uint8_t *data, size_t len){return(getDataGeneric(data,len,newValue));} // gets the newValue for data-based Characteristics
size_t getNewTLV(TLV8 &tlv){return(getTLVGeneric(tlv,newValue));} // gets the newValue for tlv8-based Characteristics
void setString(const char *val, boolean notify=true); // sets the value and newValue for string-based Characteristic
void setData(const uint8_t *data, size_t len, boolean notify=true); // sets the value and newValue for data-based Characteristic
void setTLV(const TLV8 &tlv, boolean notify=true); // sets the value and newValue for tlv8-based Characteristic
template <typename T> void setVal(T val, boolean notify=true){ // sets the value and newValue for numeric-based Characteristics
setValCheck();
if(!((val >= uvGet<T>(minValue)) && (val <= uvGet<T>(maxValue)))){
LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setVal(%g) is out of range [%g,%g]. This may cause device to become non-responsive!\n\n",
hapName,(double)val,uvGet<double>(minValue),uvGet<double>(maxValue));
}
uvSet(value,val);
uvSet(newValue,value);
updateTime=homeSpan.snapTime;
if(notify){
if(updateFlag!=2){ // do not broadcast EV if update is being done in context of write-response
SpanBuf sb; // create SpanBuf object
sb.characteristic=this; // set characteristic
sb.status=StatusCode::OK; // set status
char dummy[]="";
sb.val=dummy; // set dummy "val" so that printfNotify knows to consider this "update"
homeSpan.Notifications.push_back(sb); // store SpanBuf in Notifications vector
}
if(nvsKey){
nvs_set_u64(homeSpan.charNVS,nvsKey,value.UINT64); // store data as uint64_t regardless of actual type (it will be read correctly when access through uvGet())
nvs_commit(homeSpan.charNVS);
}
}
} // setVal()
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
SpanCharacteristic *setPerms(uint8_t perms); // sets permissions of a Characteristic
SpanCharacteristic *addPerms(uint8_t dPerms); // add permissions of a Characteristic
SpanCharacteristic *removePerms(uint8_t dPerms); // removes permissions of a Characteristic
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
template <typename A, typename B, typename S=int> SpanCharacteristic *setRange(A min, B max, S step=0){ // sets the allowed range of a Characteristic
if(!staticRange){
uvSet(minValue,min);
uvSet(maxValue,max);
uvSet(stepValue,step);
customRange=true;
} else
setRangeError=true;
return(this);
} // setRange()
};
///////////////////////////////
class SpanButton : public PushButton {
friend class Span;
friend class SpanService;
uint16_t singleTime; // minimum time (in millis) required to register a single press
uint16_t longTime; // minimum time (in millis) required to register a long press
uint16_t doubleTime; // maximum time (in millis) between single presses to register a double press instead
SpanService *service; // Service to which this PushButton is attached
void check(); // check PushButton and call button() if "pressed"
protected:
enum buttonType_t {
HS_BUTTON,
HS_TOGGLE
};
buttonType_t buttonType=HS_BUTTON; // type of SpanButton
public:
SpanButton(int pin, uint16_t longTime=2000, uint16_t singleTime=5, uint16_t doubleTime=200, triggerType_t triggerType=TRIGGER_ON_LOW);
SpanButton(int pin, triggerType_t triggerType, uint16_t longTime=2000, uint16_t singleTime=5, uint16_t doubleTime=200) : SpanButton(pin,longTime,singleTime,doubleTime,triggerType){};
};
///////////////////////////////
class SpanToggle : public SpanButton {
public:
SpanToggle(int pin, triggerType_t triggerType=TRIGGER_ON_LOW, uint16_t toggleTime=5) : SpanButton(pin,triggerType,toggleTime){buttonType=HS_TOGGLE;};
int position(){return(pressType);}
};
///////////////////////////////
class SpanUserCommand {
friend class Span;
const char *s; // description of command
void (*userFunction1)(const char *v)=NULL; // user-defined function to call
void (*userFunction2)(const char *v, void *arg)=NULL; // user-defined function to call with user-defined arg
void *userArg;
public:
SpanUserCommand(char c, const char *s, void (*f)(const char *));
SpanUserCommand(char c, const char *s, void (*f)(const char *, void *), void *arg);
};
///////////////////////////////
class SpanPoint {
friend class Span;
int receiveSize; // size (in bytes) of messages to receive
int sendSize; // size (in bytes) of messages to send
esp_now_peer_info_t peerInfo; // structure for all ESP-NOW peer data
QueueHandle_t receiveQueue; // queue to store data after it is received
uint32_t receiveTime=0; // time (in millis) of most recent data received
static uint8_t lmk[16];
static boolean initialized;
static boolean isHub;
static boolean useEncryption;
static vector<SpanPoint *, Mallocator<SpanPoint *>> SpanPoints;
static uint16_t channelMask; // channel mask (only used for remote devices)
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 init(const char *password="HomeSpan");
static void setAsHub(){isHub=true;}
static uint8_t nextChannel();
static void dataSent(const uint8_t *mac, esp_now_send_status_t status) {
xQueueOverwrite( statusQueue, &status );
}
public:
SpanPoint(const char *macAddress, int sendSize, int receiveSize, int queueDepth=1, boolean useAPaddress=false);
static void setPassword(const char *pwd){init(pwd);}
static void setChannelMask(uint16_t mask);
static void setEncryption(boolean encrypt){useEncryption=encrypt;}
boolean get(void *dataBuf);
boolean send(const void *data);
uint32_t time(){return(millis()-receiveTime);}
};
/////////////////////////////////////////////////
#include "Span.h"

View File

@@ -0,0 +1,421 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 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.
*
********************************************************************************/
#include "version.h"
#include <DNSServer.h>
#include "Network.h"
#include "HomeSpan.h"
#include "Utils.h"
using namespace Utils;
///////////////////////////////
void Network::scan(){
int n=WiFi.scanNetworks();
free(ssidList);
ssidList=(char **)HS_CALLOC(n,sizeof(char *));
numSSID=0;
for(int i=0;i<n;i++){
boolean found=false;
for(int j=0;j<numSSID;j++){
if(!strcmp(WiFi.SSID(i).c_str(),ssidList[j]))
found=true;
}
if(!found){
ssidList[numSSID]=(char *)HS_CALLOC(WiFi.SSID(i).length()+1,sizeof(char));
sprintf(ssidList[numSSID],"%s",WiFi.SSID(i).c_str());
numSSID++;
}
}
}
///////////////////////////////
void Network::serialConfigure(){
wifiData.ssid[0]='\0';
wifiData.pwd[0]='\0';
LOG0("*** WiFi Setup - Scanning for Networks...\n\n");
scan(); // scan for networks
for(int i=0;i<numSSID;i++)
LOG0(" %d) %s\n",i+1,ssidList[i]);
while(!strlen(wifiData.ssid)){
LOG0("\n>>> WiFi SSID: ");
readSerial(wifiData.ssid,MAX_SSID);
if(atoi(wifiData.ssid)>0 && atoi(wifiData.ssid)<=numSSID){
strcpy(wifiData.ssid,ssidList[atoi(wifiData.ssid)-1]);
}
LOG0("%s\n",wifiData.ssid);
}
while(!strlen(wifiData.pwd)){
LOG0(">>> WiFi PASS: ");
readSerial(wifiData.pwd,MAX_PWD);
LOG0("%s\n",mask(wifiData.pwd,2).c_str());
}
return;
}
///////////////////////////////
boolean Network::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") &&
strcmp(s,"88888888") && strcmp(s,"99999999") && strcmp(s,"12345678") && strcmp(s,"87654321"));
}
///////////////////////////////
void Network::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
for(int i=0;i<numSSID;i++)
LOG0(" %d) %s\n",i+1,ssidList[i]);
WiFiServer apServer(80);
client=0;
const byte DNS_PORT = 53;
DNSServer dnsServer;
IPAddress apIP(192, 168, 4, 1);
WiFi.mode(WIFI_AP);
WiFi.softAP(apSSID,apPassword); // start access point
dnsServer.start(DNS_PORT, "*", apIP); // start DNS server that resolves every request to the address of this device
apServer.begin();
alarmTimeOut=millis()+lifetime; // Access Point will shut down when alarmTimeOut is reached
apStatus=0; // status will be "timed out" unless changed
LOG0("\nReady.\n");
while(1){ // loop until we get timed out (which will be accelerated if save/cancel selected)
if(homeSpan.controlButton && homeSpan.controlButton->triggered(9999,3000)){
LOG0("\n*** Access Point Terminated. Restarting...\n\n");
STATUS_UPDATE(start(LED_ALERT),HS_AP_TERMINATED)
homeSpan.controlButton->wait();
homeSpan.reboot();
}
if(millis()>alarmTimeOut){
WiFi.softAPdisconnect(true); // terminate connections and shut down captive access point
delay(100);
if(apStatus==1){
LOG0("\n*** Access Point: Exiting and Saving Settings\n\n");
return;
} else {
if(apStatus==0)
LOG0("\n*** Access Point: Timed Out (%ld seconds).",lifetime/1000);
else
LOG0("\n*** Access Point: Configuration Cancelled.");
LOG0(" Restarting...\n\n");
STATUS_UPDATE(start(LED_ALERT),HS_AP_TERMINATED)
homeSpan.reboot();
}
}
dnsServer.processNextRequest();
if(client=apServer.available()){ // found a new HTTP client
LOG2("=======================================\n");
LOG1("** Access Point Client Connected: (");
LOG1(millis()/1000);
LOG1(" sec) ");
LOG1(client.remoteIP());
LOG1("\n");
LOG2("\n");
delay(50); // pause to allow data buffer to begin to populate
}
if(client && client.available()){ // if connection exists and data is available
LOG2("<<<<<<<<< ");
LOG2(client.remoteIP());
LOG2(" <<<<<<<<<\n");
int messageSize=client.available();
if(messageSize>MAX_HTTP){ // exceeded maximum number of bytes allowed
badRequestError();
LOG0("\n*** ERROR: HTTP message of %d bytes exceeds maximum allowed (%d)\n\n",messageSize,MAX_HTTP);
continue;
}
TempBuffer<uint8_t> httpBuf(messageSize+1); // leave room for null character added below
int nBytes=client.read(httpBuf,messageSize); // read all available bytes up to maximum allowed+1
if(nBytes!=messageSize || client.available()!=0){
badRequestError();
LOG0("\n*** ERROR: HTTP message not read correctly. Expected %d bytes, read %d bytes, %d bytes remaining\n\n",messageSize,nBytes,client.available());
continue;
}
httpBuf[nBytes]='\0'; // add null character to enable string functions
char *body=(char *)httpBuf.get(); // char pointer to start of HTTP Body
char *p; // char pointer used for searches
if(!(p=strstr((char *)httpBuf.get(),"\r\n\r\n"))){
badRequestError();
LOG0("\n*** ERROR: Malformed HTTP request (can't find blank line indicating end of BODY)\n\n");
continue;
}
*p='\0'; // null-terminate end of HTTP Body to faciliate additional string processing
uint8_t *content=(uint8_t *)p+4; // byte pointer to start of optional HTTP Content
int cLen=0; // length of optional HTTP Content
if((p=strstr(body,"Content-Length: "))) // Content-Length is specified
cLen=atoi(p+16);
if(nBytes!=strlen(body)+4+cLen){
badRequestError();
LOG0("\n*** ERROR: Malformed HTTP request (Content-Length plus Body Length does not equal total number of bytes read)\n\n");
continue;
}
LOG2(body);
LOG2("\n------------ END BODY! ------------\n");
content[cLen]='\0'; // add a trailing null on end of any contents, which should always be text-based
processRequest(body, (char *)content); // process request
LOG2("\n");
} // process Client
} // while 1
}
///////////////////////////////
void Network::processRequest(char *body, char *formData){
String responseHead="HTTP/1.1 200 OK\r\nContent-type: text/html\r\n";
String responseBody="<html><meta charset=\"utf-8\"><head><style>"
"p{font-size:300%; margin:1em}"
"label{font-size:300%; margin:1em}"
"input{font-size:250%; margin:1em}"
"button{font-size:250%; margin:1em}"
"</style></head>"
"<body style=\"background-color:lightyellow;\">"
"<center><p><b>HomeSpan Setup</b></p></center>";
if(!strncmp(body,"POST /configure ",16) && // POST CONFIGURE
strstr(body,"Content-Type: application/x-www-form-urlencoded")){ // check that content is from a form
LOG2(formData); // print form data
LOG2("\n------------ END DATA! ------------\n");
LOG1("In Post Configure...\n");
getFormValue(formData,"network",wifiData.ssid,MAX_SSID);
getFormValue(formData,"pwd",wifiData.pwd,MAX_PWD);
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>";
WiFi.begin(wifiData.ssid,wifiData.pwd);
} else
if(!strncmp(body,"POST /save ",11)){ // GET SAVE
getFormValue(formData,"code",setupCode,8);
if(allowedCode(setupCode)){
responseBody+="<p><b>Settings saved!</b></p><p>Restarting HomeSpan.</p><p>Closing window...</p>";
alarmTimeOut=millis()+2000;
apStatus=1;
} else {
responseBody+="<meta http-equiv = \"refresh\" content = \"4; url = /wifi-status\" />"
"<p><b>Disallowed Setup Code - too simple!</b></p><p>Returning to configuration page...</p>";
}
} else
if(!strncmp(body,"GET /cancel ",12)){ // GET CANCEL
responseBody+="<p><b>Configuration Canceled!</b></p><p>Restarting HomeSpan.</p><p>Closing window...</p>";
alarmTimeOut=millis()+2000;
apStatus=-1;
} else
if(!strncmp(body,"GET /wifi-status ",17)){ // GET WIFI-STATUS
LOG1("In Get WiFi Status...\n");
if(WiFi.status()!=WL_CONNECTED){
waitTime+=2;
if(waitTime==12)
waitTime=2;
responseHead+="Refresh: " + String(waitTime) + "\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>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);
} else {
STATUS_UPDATE(start(LED_AP_CONNECTED),HS_AP_CONNECTED)
responseBody+="<p>SUCCESS! Connected to:</p><p><b>" + String(wifiData.ssid) + "</b></p>";
responseBody+="<p>You may enter new 8-digit Setup Code below, or leave blank to retain existing code.</p>";
responseBody+="<form action=\"/save\" method=\"post\">"
"<label for=\"code\">Setup Code:</label>"
"<center><input size=\"32\" type=\"tel\" id=\"code\" name=\"code\" placeholder=\"12345678\" pattern=\"[0-9]{8}\" maxlength=8></center>"
"<center><input style=\"font-size:300%\" type=\"submit\" value=\"SAVE Settings\"></center>"
"</form>";
responseBody+="<center><button style=\"font-size:300%\" onclick=\"document.location='/cancel'\">CANCEL Configuration</button></center>";
}
} else
if(!strstr(body,"wispr") && !strncmp(body,"GET /hotspot-detect.html ",25)){ // GET LANDING-PAGE, but only if request does NOT contain "wispr" user agent
LOG1("In Landing Page...\n");
STATUS_UPDATE(start(LED_AP_CONNECTED),HS_AP_CONNECTED)
waitTime=2;
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>"
"<form action=\"/configure\" method=\"post\">"
"<label for=\"ssid\">WiFi Network:</label>"
"<center><input size=\"32\" list=\"network\" name=\"network\" placeholder=\"Choose or Type\" required maxlength=" + String(MAX_SSID) + "></center>"
"<datalist id=\"network\">";
for(int i=0;i<numSSID;i++)
responseBody+="<option value=\"" + String(ssidList[i]) + "\">" + String(ssidList[i]) + "</option>";
responseBody+="</datalist><br><br>"
"<label for=\"pwd\">WiFi Password:</label>"
"<center><input size=\"32\" type=\"password\" id=\"pwd\" name=\"pwd\" required maxlength=" + String(MAX_PWD) + "></center>"
"<br><br>";
responseBody+="<center><input style=\"font-size:300%\" type=\"submit\" value=\"SUBMIT\"></center>"
"</form>";
responseBody+="<center><button style=\"font-size:300%\" onclick=\"document.location='/cancel'\">CANCEL Configuration</button></center>";
}
responseHead+="\r\n"; // add blank line between reponse header and body
responseBody+="</body></html>"; // close out body and html tags
LOG2("\n>>>>>>>>>> ");
LOG2(client.remoteIP());
LOG2(" >>>>>>>>>>\n");
LOG2(responseHead);
LOG2(responseBody);
LOG2("\n");
client.print(responseHead);
client.print(responseBody);
LOG2("------------ SENT! --------------\n");
} // processRequest
//////////////////////////////////////
int Network::getFormValue(char *formData, const char *tag, char *value, int maxSize){
char *s=strstr(formData,tag); // find start of tag
if(!s) // if not found, return -1
return(-1);
char *v=index(s,'='); // find '='
if(!v) // if not found, return -1 (this should not happen)
return(-1);
v++; // point to begining of value
int len=0; // track length of value
while(*v!='\0' && *v!='&' && len<maxSize){ // copy the value until null, '&', or maxSize is reached
if(*v=='%'){ // this is an escaped character of form %XX
v++;
sscanf(v,"%2x",(unsigned int *)value++);
v+=2;
} else {
*value++=(*v=='+'?' ':*v); // HTML Forms use '+' for spaces (and '+' signs are escaped)
v++;
}
len++;
}
*value='\0'; // add terminating null
return(len);
}
//////////////////////////////////////
int Network::badRequestError(){
char s[]="HTTP/1.1 400 Bad Request\r\n\r\n";
LOG2("\n>>>>>>>>>> ");
LOG2(client.remoteIP());
LOG2(" >>>>>>>>>>\n");
LOG2(s);
client.print(s);
LOG2("------------ SENT! --------------\n");
delay(1);
client.stop();
return(-1);
}
//////////////////////////////////////

View File

@@ -0,0 +1,69 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 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 <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 {
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;
WiFiClient client; // client used for HTTP calls
int waitTime; // time to wait between HTTP refreshed when checking for WiFi connection
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 getFormValue(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
int badRequestError(); // return 400 error
};

View File

@@ -0,0 +1,64 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 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
#ifndef HS_MALLOC
#if defined(BOARD_HAS_PSRAM)
#define HS_MALLOC ps_malloc
#define HS_CALLOC ps_calloc
#define HS_REALLOC ps_realloc
#define ps_new(X) new(ps_malloc(sizeof(X)))X
#else
#define HS_MALLOC malloc
#define HS_CALLOC calloc
#define HS_REALLOC realloc
#define ps_new(X) new X
#endif
template <class T>
struct Mallocator {
typedef T value_type;
Mallocator() = default;
template <class U> constexpr Mallocator(const Mallocator<U>&) {}
[[nodiscard]] T* allocate(std::size_t n) {
auto p = static_cast<T*>(HS_MALLOC(n*sizeof(T)));
if(p==NULL){
Serial.printf("\n\n*** FATAL ERROR: Requested allocation of %d bytes failed. Program Halting.\n\n",n*sizeof(T));
while(1);
}
return p;
}
void deallocate(T* p, std::size_t) noexcept { std::free(p); }
};
template <class T, class U>
bool operator==(const Mallocator<T>&, const Mallocator<U>&) { return true; }
template <class T, class U>
bool operator!=(const Mallocator<T>&, const Mallocator<U>&) { return false; }
#endif

View File

@@ -0,0 +1,260 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 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.
*
********************************************************************************/
#include <sodium.h>
#include <Arduino.h>
#include "SRP.h"
/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////
SRP6A::SRP6A(){
// initialize MPI structures
mbedtls_mpi_init(&N);
mbedtls_mpi_init(&g);
mbedtls_mpi_init(&s);
mbedtls_mpi_init(&x);
mbedtls_mpi_init(&v);
mbedtls_mpi_init(&A);
mbedtls_mpi_init(&b);
mbedtls_mpi_init(&B);
mbedtls_mpi_init(&S);
mbedtls_mpi_init(&k);
mbedtls_mpi_init(&u);
mbedtls_mpi_init(&_rr);
mbedtls_mpi_init(&t1);
mbedtls_mpi_init(&t2);
mbedtls_mpi_init(&t3);
// load N and g into MPI structures
mbedtls_mpi_read_string(&N,16,N3072);
mbedtls_mpi_lset(&g,g3072);
}
//////////////////////////////////////
SRP6A::~SRP6A(){
mbedtls_mpi_free(&N);
mbedtls_mpi_free(&g);
mbedtls_mpi_free(&s);
mbedtls_mpi_free(&x);
mbedtls_mpi_free(&v);
mbedtls_mpi_free(&A);
mbedtls_mpi_free(&b);
mbedtls_mpi_free(&B);
mbedtls_mpi_free(&S);
mbedtls_mpi_free(&k);
mbedtls_mpi_free(&u);
mbedtls_mpi_free(&_rr);
mbedtls_mpi_free(&t1);
mbedtls_mpi_free(&t2);
mbedtls_mpi_free(&t3);
}
//////////////////////////////////////
void SRP6A::createVerifyCode(const char *setupCode, Verification *vData){
TempBuffer<uint8_t> tBuf(80); // temporary buffer for staging
TempBuffer<uint8_t> tHash(64); // temporary buffer for storing SHA-512 results
char *icp; // storage for I:P
// generate random salt, s
randombytes_buf(vData->salt,16); // generate 16 random bytes for salt
// create I:P
asprintf(&icp,"%s:%.3s-%.2s-%.3s",I,setupCode,setupCode+3,setupCode+5);
// 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
// compute v = g^x %N
mbedtls_mpi_exp_mod(&v,&g,&x,&N,&_rr); // create verifier, v (_rr is an internal "helper" structure that mbedtls uses to speed up subsequent exponential calculations)
mbedtls_mpi_write_binary(&v,vData->verifyCode,384); // write v into verifyCode (padding with initial zeros is less than 384 bytes)
free(icp);
}
//////////////////////////////////////
void SRP6A::createPublicKey(const Verification *vData, uint8_t *publicKey){
TempBuffer<uint8_t> tBuf(768); // temporary buffer for staging
TempBuffer<uint8_t> tHash(64); // temporary buffer for storing SHA-512 results
TempBuffer<uint8_t> privateKey(32); // temporary buffer for generating private key random numbers
// load stored salt, s, and verification code, v
mbedtls_mpi_read_binary(&s,vData->salt,16); // load salt into s for use in later steps
mbedtls_mpi_read_binary(&v,vData->verifyCode,384); // load verifyCode into v for use below
// generate random private key, b
randombytes_buf(privateKey,32); // generate 32 random bytes for private key
mbedtls_mpi_read_binary(&b,privateKey,32); // load private key into b
// compute k = SHA512( N | PAD(g) )
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_mpi_read_binary(&k,tHash,64); // load hash result into k
// compute B = (k*v + g^b) %N
mbedtls_mpi_mul_mpi(&t1,&k,&v); // t1 = k*v
mbedtls_mpi_exp_mod(&t2,&g,&b,&N,&_rr); // t2 = g^b %N
mbedtls_mpi_add_mpi(&t3,&t1,&t2); // t3 = t1 + t2
mbedtls_mpi_mod_mpi(&B,&t3,&N); // B = t3 %N = ACCESSORY PUBLIC KEY
mbedtls_mpi_write_binary(&B,publicKey,384); // write B into publicKey (padding with initial zeros is less than 384 bytes)
}
//////////////////////////////////////
void SRP6A::createSessionKey(const uint8_t *publicKey, size_t len){
TempBuffer<uint8_t> tBuf(768); // temporary buffer for staging
TempBuffer<uint8_t> tHash(64); // temporary buffer for storing SHA-512 results
mbedtls_mpi_read_binary(&A,publicKey,len); // load client PublicKey into A
// compute u = SHA512( PAD(A) | PAD(B) )
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_mpi_read_binary(&u,tHash,64); // load hash result into mpi structure u
// compute S = (A * v^u)^b %N
mbedtls_mpi_exp_mod(&t1,&v,&u,&N,&_rr); // t1 = v^u %N
mbedtls_mpi_mul_mpi(&t2,&A,&t1); // t2 = A*t1
mbedtls_mpi_mod_mpi(&t1,&t2,&N); // t1 = t2 %N (this is needed to reduce size of t2 before next calculation)
mbedtls_mpi_exp_mod(&S,&t1,&b,&N,&_rr); // S = t1^b %N
// 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
}
//////////////////////////////////////
int SRP6A::verifyClientProof(const uint8_t *proof){
TempBuffer<uint8_t> tBuf(976); // temporary buffer for staging
TempBuffer<uint8_t> tHash(64); // temporary buffer for storing SHA-512 results
memcpy(M1,proof,64); // load client Proof into M1
size_t count=0; // total number of bytes for final hash
size_t sLen;
// 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
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_mpi_write_binary(&s,tBuf+128,16); // concatenate s to staging buffer
sLen=mbedtls_mpi_size(&A); // get actual size of A
mbedtls_mpi_write_binary(&A,tBuf+144,sLen); // concatenate A to staging buffer. Note A is NOT padded with leading zeros (so may be less than 384 bytes)
count=144+sLen; // total bytes written to staging buffer so far
sLen=mbedtls_mpi_size(&B); // get actual size of B
mbedtls_mpi_write_binary(&B,tBuf+count,sLen); // concatenate B to staging buffer. Note B is NOT padded with leading zeros (so may be less than 384 bytes)
count+=sLen; // increment total bytes written to staging buffer
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
if(!memcmp(M1,tHash,64)) // check that client Proof M1 matches M1V
return(1); // success - proof from HAP Client is verified
return(0);
}
//////////////////////////////////////
void SRP6A::createAccProof(uint8_t *proof){
TempBuffer<uint8_t> tBuf(512); // temporary buffer for staging
// compute M2 = SHA512( A | M1 | K )
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
}
//////////////////////////////////////
void SRP6A::print(mbedtls_mpi *mpi){
size_t sLen;
mbedtls_mpi_write_string(mpi,16,NULL,0,&sLen);
TempBuffer<char> sBuf(sLen);
mbedtls_mpi_write_string(mpi,16,sBuf,sLen,&sLen);
Serial.printf("%d %s\n",(sLen-1)/2,sBuf.get()); // subtract 1 for null-terminator, and then divide by 2 to get number of bytes (e.g. 4F = 2 characters, but represents just one mpi byte)
}
//////////////////////////////////////
constexpr char SRP6A::N3072[];
constexpr char SRP6A::I[];
const uint8_t SRP6A::g3072;

View File

@@ -0,0 +1,102 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 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 <mbedtls/sha512.h>
#include <mbedtls/bignum.h>
#include <mbedtls/base64.h>
#include "Utils.h"
/////////////////////////////////////////////////
// Pair-Setup Code Verification Data and Salt
struct Verification {
uint8_t salt[16];
uint8_t verifyCode[384];
};
/////////////////////////////////////////////////
// SRP-6A Structure from RFC 5054 (Nov 2007)
// ** HAP uses N=3072-bit Group specified in RFC 5054 with Generator g=5
// ** HAP replaces H=SHA-1 with H=SHA-512 (HAP Section 5.5)
//
// I = SRP-6A username, defined by HAP to be the word "Pair-Setup"
// P = SRP-6A password, defined to be equal to the accessory's 8-digit setup code in the format "XXX-XX-XXX"
struct SRP6A {
static constexpr char N3072[]="FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74"
"020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437"
"4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED"
"EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05"
"98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB"
"9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B"
"E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718"
"3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33"
"A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7"
"ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864"
"D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E2"
"08E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF";
static const uint8_t g3072=5;
static constexpr char I[]="Pair-Setup";
mbedtls_mpi N; // N - 3072-bit Group pre-defined prime used for all SRP-6A calculations (384 bytes)
mbedtls_mpi g; // g - pre-defined generator for the specified 3072-bit Group (g=5)
mbedtls_mpi k; // k = H(N | PAD(g)) - SRP-6A multiplier (which is different from versions SRP-6 or SRP-3)
mbedtls_mpi s; // s - randomly-generated salt (16 bytes)
mbedtls_mpi x; // x = H(s | H(I | ":" | P)) - salted, double-hash of username and password (64 bytes)
mbedtls_mpi v; // v = g^x %N - SRP-6A verifier (max 384 bytes)
mbedtls_mpi b; // b - randomly-generated private key for this HAP accessory (i.e. the SRP Server) (32 bytes)
mbedtls_mpi B; // B = k*v + g^b %N - public key for this accessory (max 384 bytes)
mbedtls_mpi A; // A - public key RECEIVED from HAP Client (max 384 bytes)
mbedtls_mpi u; // u = H(PAD(A) | PAB(B)) - "u-factor" (64 bytes)
mbedtls_mpi S; // S = (A*v^u)^b %N - SRP shared "premaster" key, based on accessory private key and client public key (max 384 bytes)
uint8_t K[64]; // K = H(S) - SRP SHARED SECRET KEY (64 bytes)
uint8_t M1[64]; // M1 - proof RECEIVED from HAP Client (64 bytes)
mbedtls_mpi t1; // temp1 - temporary mpi structures for intermediate results
mbedtls_mpi t2; // temp2 - temporary mpi structures for intermediate results
mbedtls_mpi t3; // temp3 - temporary mpi structures for intermediate results
mbedtls_mpi _rr; // _rr - temporary "helper" for large exponential modulus calculations
SRP6A(); // initializes N, G, and computes k
~SRP6A();
void *operator new(size_t size){return(HS_MALLOC(size));} // override new operator to use PSRAM when available
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
void createSessionKey(const uint8_t *publicKey, size_t len); // computes u, S, and K from Client Public Key, A (of variable length)
int verifyClientProof(const uint8_t *proof); // verifies Client Proof, M1, received from HAP client (return 1 on success, 0 on failure)
void createAccProof(uint8_t *proof); // computes M2; write back resulting Accessory Proof
void print(mbedtls_mpi *mpi); // prints size of mpi (in bytes), followed by the mpi itself (as a hex character string)
};

View File

@@ -0,0 +1,116 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 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
//////////////////////////////////////////////////////
// DEFAULT SETTINGS //
#define DEFAULT_CATEGORY Category::Lighting // change with optional first argument in homeSpan.begin()
#define DEFAULT_DISPLAY_NAME "HomeSpan Server" // change with optional second argument in homeSpan.begin()
#define DEFAULT_HOST_NAME "HomeSpan" // change with optional third argument in homeSpan.begin()
#define DEFAULT_MODEL_NAME "HomeSpan-ESP32" // change with optional fourth argument in homeSpan.begin()
#define DEFAULT_SETUP_CODE "46637726" // changed during network setup or with 'S' command
#define DEFAULT_QR_ID "HSPN" // change with homeSpan.setQRID(qrID);
#define DEFAULT_AP_SSID "HomeSpan-Setup" // change with homeSpan.setApSSID(ssid)
#define DEFAULT_AP_PASSWORD "homespan" // change with homeSpan.setApPassword(pwd)
#define DEFAULT_OTA_PASSWORD "homespan-ota" // change with 'O' command
#define DEFAULT_AP_TIMEOUT 300 // change with homeSpan.setApTimeout(nSeconds)
#define DEFAULT_COMMAND_TIMEOUT 120 // change with homeSpan.setCommandTimeout(nSeconds)
#define DEFAULT_LOG_LEVEL 0 // change with homeSpan.setLogLevel(level)
#define DEFAULT_TCP_PORT 80 // change with homeSpan.setPort(port);
#define DEFAULT_WEBLOG_URL "status" // change with optional fourth argument in homeSpan.enableWebLog()
#define DEFAULT_LOW_MEM_THRESHOLD 80000 // default low watermark memory (for internal RAM) threshold that triggers warning
#define DEFAULT_REBOOT_CALLBACK_TIME 5000 // default time (in milliseconds) to check for reboot callback
/////////////////////////////////////////////////////
// OTA PARTITION INFO //
#define HOMESPAN_MAGIC_COOKIE "HomeSpanMagicCookie##2022"
/////////////////////////////////////////////////////
// STATUS LED SETTINGS //
#define LED_WIFI_NEEDED 300,0.5,1,2700 // slow single-blink
#define LED_PAIRING_NEEDED 300,0.5,2,2400 // slow double-blink
#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_OTA_STARTED 300,0.5,3,400 // medium triple-blink
/////////////////////////////////////////////////////
// Message Log Level Control Macros //
// 0=Minimal, 1=Informative, 2=All //
#define LOG0(format,...) do{ if(homeSpan.getLogLevel()>=0)Serial.print ##__VA_OPT__(f)(format __VA_OPT__(,) __VA_ARGS__); }while(0)
#define LOG1(format,...) do{ if(homeSpan.getLogLevel()>=1)Serial.print ##__VA_OPT__(f)(format __VA_OPT__(,) __VA_ARGS__); }while(0)
#define LOG2(format,...) do{ if(homeSpan.getLogLevel()>=2)Serial.print ##__VA_OPT__(f)(format __VA_OPT__(,) __VA_ARGS__); }while(0)
#define WEBLOG(format,...) homeSpan.addWebLog(false, format __VA_OPT__(,) __VA_ARGS__);
//////////////////////////////////////////////////////
// Types of Accessory Categories //
// Reference: HAP Section 13 //
enum class Category {
Other=1,
Bridges=2,
Fans=3,
GarageDoorOpeners=4,
Lighting=5,
Locks=6,
Outlets=7,
Switches=8,
Thermostats=9,
Sensors=10,
SecuritySystems=11,
Doors=12,
Windows=13,
WindowCoverings=14,
ProgrammableSwitches=15,
IPCameras=17,
VideoDoorbells=18,
AirPurifiers=19,
Heaters=20,
AirConditioners=21,
Humidifiers=22,
Dehumidifiers=23,
Sprinklers=28,
Faucets=29,
ShowerSystems=30,
Television=31
};

View File

@@ -0,0 +1,636 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 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.
*
********************************************************************************/
///////////////////////////////////
// SPAN SERVICES (HAP Chapter 8) //
///////////////////////////////////
// Macros to define Services, along with vectors of required and optional Characteristics for each Span Service structure.
//
// NOTE: these macros are parsed by an external awk script to auto-generate Services and Characteristics documentation.
//
// The CREATE_SERV_DEP() macro is the same as the CREATE_SERV() macro, except that it is used for deprecated Services that will not
// be included in documentation. The REQ_DEP and OPT_DEP() macros are the same as the REQ() and OPT() macros, except that they are used
// for deprecated Characteristics that will not be included in documentation.
#define CREATE_SERV(NAME,_UUID) struct NAME : SpanService { static constexpr const char *UUID=#_UUID; NAME() : SpanService{#_UUID,#NAME}{
#define CREATE_SERV_DEP(NAME,_UUID) struct NAME : SpanService { static constexpr const char *UUID=#_UUID; NAME() : SpanService{#_UUID,#NAME}{
#define END_SERV }};
#define REQ(HAPCHAR) req.push_back(&hapChars.HAPCHAR)
#define REQ_DEP(HAPCHAR) req.push_back(&hapChars.HAPCHAR)
#define OPT(HAPCHAR) opt.push_back(&hapChars.HAPCHAR)
#define OPT_DEP(HAPCHAR) opt.push_back(&hapChars.HAPCHAR)
#define SERVICES_GROUP
namespace Service {
SERVICES_GROUP // Mandatory Services
CREATE_SERV(AccessoryInformation,3E) // Required Identification Information. For each Accessory in a HomeSpan device this must be included as the first Service.
REQ(Identify);
OPT(Name);
OPT(FirmwareRevision);
OPT(Manufacturer);
OPT(Model);
OPT(SerialNumber);
OPT(HardwareRevision);
OPT_DEP(AccessoryFlags);
END_SERV
SERVICES_GROUP // Lights, Power, and Switches
CREATE_SERV(BatteryService,96) // Defines a standalone Battery Service.
REQ(BatteryLevel);
REQ(ChargingState);
REQ(StatusLowBattery);
OPT(ConfiguredName);
OPT_DEP(Name);
END_SERV
CREATE_SERV(LightBulb,43) // Defines any type of Light.
REQ(On);
OPT(Brightness);
OPT(Hue);
OPT(Saturation);
OPT(ColorTemperature);
OPT(ConfiguredName);
OPT_DEP(Name);
END_SERV
CREATE_SERV(Outlet,47) // Defines a controllable Outlet used to power any light or appliance.
REQ(On);
REQ(OutletInUse);
OPT(ConfiguredName);
OPT_DEP(Name);
END_SERV
CREATE_SERV(StatelessProgrammableSwitch,89) // Defines a "Stateless" Programmable Switch that can be used to trigger actions in the Home App.
REQ(ProgrammableSwitchEvent);
OPT(ServiceLabelIndex);
OPT_DEP(Name);
END_SERV
CREATE_SERV(Switch,49) // Defines a generic Switch.
REQ(On);
OPT(ConfiguredName);
OPT_DEP(Name);
END_SERV
SERVICES_GROUP // Heating, Ventilation, and Air Conditioning (HVAC)
CREATE_SERV(AirPurifier,BB) // Defines a basic Air Purifier with an optional fan and swing mode. Optional Linked Services: <b>FilterMaintenance</b>. Combine with an <b>AirSensor</b> Service for automated operations.
REQ(Active);
REQ(CurrentAirPurifierState);
REQ(TargetAirPurifierState);
OPT(RotationSpeed);
OPT(SwingMode);
OPT(LockPhysicalControls);
OPT(ConfiguredName);
OPT_DEP(Name);
END_SERV
CREATE_SERV(Fan,B7) // Defines a Fan. Combine with a <b>LightBulb</b> Service to create a Lighted Ceiling Fan.
REQ(Active);
OPT(CurrentFanState);
OPT(TargetFanState);
OPT(RotationDirection);
OPT(RotationSpeed);
OPT(SwingMode);
OPT(LockPhysicalControls);
OPT(ConfiguredName);
OPT_DEP(Name);
END_SERV
CREATE_SERV(FilterMaintenance,BA) // Defines a Filter Maintainence check. Use only as a Linked Service for the <b>AirPurifier</b> Service.
REQ(FilterChangeIndication);
OPT(FilterLifeLevel);
OPT(ResetFilterIndication);
OPT(ConfiguredName);
OPT_DEP(Name);
END_SERV
CREATE_SERV(HeaterCooler,BC) // Defines a standalone Heater, Cooler, or combined Heater/Cooler.
REQ(Active);
REQ(CurrentTemperature);
REQ(CurrentHeaterCoolerState);
REQ(TargetHeaterCoolerState);
OPT(RotationSpeed);
OPT(TemperatureDisplayUnits);
OPT(SwingMode);
OPT(CoolingThresholdTemperature);
OPT(HeatingThresholdTemperature);
OPT(LockPhysicalControls);
OPT(ConfiguredName);
OPT_DEP(Name);
END_SERV
CREATE_SERV(HumidifierDehumidifier,BD) // Defines a Humidifer, Dehumidifier, or combined Humidifer/Dehumidifier.
REQ(Active);
REQ(CurrentRelativeHumidity);
REQ(CurrentHumidifierDehumidifierState);
REQ(TargetHumidifierDehumidifierState);
OPT(RelativeHumidityDehumidifierThreshold);
OPT(RelativeHumidityHumidifierThreshold);
OPT(RotationSpeed);
OPT(SwingMode);
OPT(WaterLevel);
OPT(LockPhysicalControls);
OPT(ConfiguredName);
OPT_DEP(Name);
END_SERV
CREATE_SERV(Slat,B9) // Defines a motorized ventilation Slat(s).
REQ(CurrentSlatState);
REQ(SlatType);
OPT(SwingMode);
OPT(CurrentTiltAngle);
OPT(TargetTiltAngle);
OPT(ConfiguredName);
OPT_DEP(Name);
END_SERV
CREATE_SERV(Thermostat,4A) // Defines a Thermostat used to control a furnace, air conditioner, or both.
REQ(CurrentHeatingCoolingState);
REQ(TargetHeatingCoolingState);
REQ(CurrentTemperature);
REQ(TargetTemperature);
REQ(TemperatureDisplayUnits);
OPT(CoolingThresholdTemperature);
OPT(CurrentRelativeHumidity);
OPT(HeatingThresholdTemperature);
OPT(TargetRelativeHumidity);
OPT(ConfiguredName);
OPT_DEP(Name);
END_SERV
SERVICES_GROUP // Standalone Sensors
CREATE_SERV(AirQualitySensor,8D) // Defines an Air Quality Sensor.
REQ(AirQuality);
OPT(OzoneDensity);
OPT(NitrogenDioxideDensity);
OPT(SulphurDioxideDensity);
OPT(PM25Density);
OPT(PM10Density);
OPT(VOCDensity);
OPT(StatusActive);
OPT(StatusFault);
OPT(StatusTampered);
OPT(StatusLowBattery);
OPT(ConfiguredName);
OPT_DEP(Name);
END_SERV
CREATE_SERV(CarbonDioxideSensor,97) // Defines a Carbon Dioxide Sensor.
REQ(CarbonDioxideDetected);
OPT(CarbonDioxideLevel);
OPT(CarbonDioxidePeakLevel);
OPT(StatusActive);
OPT(StatusFault);
OPT(StatusTampered);
OPT(StatusLowBattery);
OPT(ConfiguredName);
OPT_DEP(Name);
END_SERV
CREATE_SERV(CarbonMonoxideSensor,7F) // Defines a Carbon Monoxide Sensor.
REQ(CarbonMonoxideDetected);
OPT(CarbonMonoxideLevel);
OPT(CarbonMonoxidePeakLevel);
OPT(StatusActive);
OPT(StatusFault);
OPT(StatusTampered);
OPT(StatusLowBattery);
OPT(ConfiguredName);
OPT_DEP(Name);
END_SERV
CREATE_SERV(ContactSensor,80) // Defines a Contact Sensor.
REQ(ContactSensorState);
OPT(StatusActive);
OPT(StatusFault);
OPT(StatusTampered);
OPT(StatusLowBattery);
OPT(ConfiguredName);
OPT_DEP(Name);
END_SERV
CREATE_SERV(HumiditySensor,82) // Defines a Humidity Sensor.
REQ(CurrentRelativeHumidity);
OPT(StatusActive);
OPT(StatusFault);
OPT(StatusTampered);
OPT(StatusLowBattery);
OPT(ConfiguredName);
OPT_DEP(Name);
END_SERV
CREATE_SERV(LeakSensor,83) // Defines a Leak Sensor.
REQ(LeakDetected);
OPT(StatusActive);
OPT(StatusFault);
OPT(StatusTampered);
OPT(StatusLowBattery);
OPT(ConfiguredName);
OPT_DEP(Name);
END_SERV
CREATE_SERV(LightSensor,84) // Defines a Light Sensor.
REQ(CurrentAmbientLightLevel);
OPT(StatusActive);
OPT(StatusFault);
OPT(StatusTampered);
OPT(StatusLowBattery);
OPT(ConfiguredName);
OPT_DEP(Name);
END_SERV
CREATE_SERV(MotionSensor,85) // Defines a Motion Sensor.
REQ(MotionDetected);
OPT(StatusActive);
OPT(StatusFault);
OPT(StatusTampered);
OPT(StatusLowBattery);
OPT(ConfiguredName);
OPT_DEP(Name);
END_SERV
CREATE_SERV(OccupancySensor,86) // Defines and Occupancy Sensor.
REQ(OccupancyDetected);
OPT(StatusActive);
OPT(StatusFault);
OPT(StatusTampered);
OPT(StatusLowBattery);
OPT(ConfiguredName);
OPT_DEP(Name);
END_SERV
CREATE_SERV(SmokeSensor,87) // Defines a Smoke Sensor.
REQ(SmokeDetected);
OPT(StatusActive);
OPT(StatusFault);
OPT(StatusTampered);
OPT(StatusLowBattery);
OPT(ConfiguredName);
OPT_DEP(Name);
END_SERV
CREATE_SERV(TemperatureSensor,8A) // Defines a Temperature Sensor.
REQ(CurrentTemperature);
OPT(StatusActive);
OPT(StatusFault);
OPT(StatusTampered);
OPT(StatusLowBattery);
OPT(ConfiguredName);
END_SERV
SERVICES_GROUP // Doors, Locks, and Windows
CREATE_SERV(Door,81) // Defines a motorized Door.
REQ(CurrentPosition);
REQ(TargetPosition);
OPT(ObstructionDetected);
OPT(ConfiguredName);
OPT_DEP(Name);
OPT_DEP(PositionState);
OPT_DEP(HoldPosition);
END_SERV
CREATE_SERV(Doorbell,121) // Defines a Doorbell. Can be used on a standalone basis or in conjunction with a <b>LockMechanism</b> Service.
REQ(ProgrammableSwitchEvent);
OPT_DEP(Volume);
OPT_DEP(Brightness);
OPT(ConfiguredName);
OPT_DEP(Name);
END_SERV
CREATE_SERV(GarageDoorOpener,41) // Defines a motorized Garage Door Opener.
REQ(CurrentDoorState);
REQ(TargetDoorState);
REQ(ObstructionDetected);
OPT(LockCurrentState);
OPT(LockTargetState);
OPT(ConfiguredName);
OPT_DEP(Name);
END_SERV
CREATE_SERV(LockMechanism,45) // Defines an electronic Lock.
REQ(LockCurrentState);
REQ(LockTargetState);
OPT(ConfiguredName);
OPT_DEP(Name);
END_SERV
CREATE_SERV(Window,8B) // Defines a motorized Window.
REQ(CurrentPosition);
REQ(TargetPosition);
OPT(ObstructionDetected);
OPT(ConfiguredName);
OPT_DEP(Name);
OPT_DEP(PositionState);
OPT_DEP(HoldPosition);
END_SERV
CREATE_SERV(WindowCovering,8C) // Defines a motorized Window Shade, Screen, Awning, etc.
REQ(TargetPosition);
REQ(CurrentPosition);
OPT(CurrentHorizontalTiltAngle);
OPT(TargetHorizontalTiltAngle);
OPT(CurrentVerticalTiltAngle);
OPT(TargetVerticalTiltAngle);
OPT(ObstructionDetected);
OPT(ConfiguredName);
OPT_DEP(Name);
OPT_DEP(PositionState);
OPT_DEP(HoldPosition);
END_SERV
SERVICES_GROUP // Water Systems
CREATE_SERV(Faucet,D7) // Defines the master control for a multi-Valve appliance. Linked Services: <b>Valve</b> (at least one required), and <b>HeaterCooler</b> (optional).
REQ(Active);
OPT(StatusFault);
OPT(ConfiguredName);
OPT_DEP(Name);
END_SERV
CREATE_SERV(IrrigationSystem,CF) // Defines an Irrigation System. Linked Services: <b>Valve</b> Service (at least one required).
REQ(Active);
REQ(ProgramMode);
REQ(InUse);
OPT(RemainingDuration);
OPT(StatusFault);
OPT(ConfiguredName);
OPT_DEP(Name);
END_SERV
CREATE_SERV(Valve,D0) // Defines an electronic Valve. Can be used standalone or as a Linked Service for either a <b>Faucet</b> or <b>IrrigationSystem</b> Service.
REQ(Active);
REQ(InUse);
REQ(ValveType);
OPT(SetDuration);
OPT(RemainingDuration);
OPT(IsConfigured);
OPT(ServiceLabelIndex);
OPT(StatusFault);
OPT(ConfiguredName);
OPT_DEP(Name);
END_SERV
SERVICES_GROUP // Security Systems
CREATE_SERV(SecuritySystem,7E) // Defines a Security System. Often used in combination with <b>MotionSensor</b> and <b>ContactSensor</b> Services.
REQ(SecuritySystemCurrentState);
REQ(SecuritySystemTargetState);
OPT(SecuritySystemAlarmType);
OPT(StatusFault);
OPT(StatusTampered);
OPT(ConfiguredName);
OPT_DEP(Name);
END_SERV
SERVICES_GROUP // Televisions
CREATE_SERV(InputSource,D9) // Defines an Input Source for a TV. Use only as a Linked Service for the <b>Television</b> Service.
REQ(Identifier);
OPT(ConfiguredName);
OPT(IsConfigured);
OPT(CurrentVisibilityState);
OPT(TargetVisibilityState);
END_SERV
CREATE_SERV(Television,D8) // Defines a TV. Optional Linked Services: <b>InputSource</b> and <b>TelevisionSpeaker</b>.
REQ(Active);
OPT(ActiveIdentifier);
OPT(DisplayOrder);
OPT(RemoteKey);
OPT(PowerModeSelection);
OPT(ConfiguredName);
END_SERV
CREATE_SERV(TelevisionSpeaker,113) // Defines a Television Speaker that can be controlled via the Remote Control widget on an iPhone. Use only as a Linked Service for the <b>Television</b> Service.
REQ(VolumeControlType);
REQ(VolumeSelector);
OPT(ConfiguredName);
END_SERV
SERVICES_GROUP // Miscellaneous
CREATE_SERV(ServiceLabel,CC) // Defines a naming scheme for un-nameable Services, such as a <b>StatelessProgrammableSwitch</b>, by Linking them to this Service. When used, those other Services must each include a <b>ServiceLabelIndex</b> Characteristic with a unique value.
REQ(ServiceLabelNamespace);
END_SERV
// Deprecated or unsupported Services
CREATE_SERV_DEP(HAPProtocolInformation,A2)
REQ_DEP(Version);
END_SERV
CREATE_SERV_DEP(Microphone,112)
REQ_DEP(Mute);
OPT_DEP(Volume);
OPT_DEP(ConfiguredName);
OPT_DEP(Name);
END_SERV
CREATE_SERV_DEP(Speaker,113)
REQ_DEP(Mute);
OPT_DEP(Volume);
OPT_DEP(ConfiguredName);
OPT_DEP(Name);
END_SERV
}
//////////////////////////////////////////
// SPAN CHARACTERISTICS (HAP Chapter 9) //
//////////////////////////////////////////
// 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); } };
namespace Characteristic {
CREATE_CHAR(UINT32_t,AccessoryFlags,1,1,1); // not applicable for HomeSpan
CREATE_CHAR(UINT8_t,Active,0,0,1,INACTIVE,ACTIVE); // indicates if the Service is active/on
CREATE_CHAR(UINT32_t,ActiveIdentifier,0,0,255); // numerical Identifier of the <b>InputSource</b> selected in the Home App.
CREATE_CHAR(UINT8_t,AirQuality,0,0,5,UNKNOWN,EXCELLENT,GOOD,FAIR,INFERIOR,POOR); // a subjective description
CREATE_CHAR(UINT8_t,BatteryLevel,100,0,100); // measured as a percentage
CREATE_CHAR(INT_t,Brightness,0,0,100); // measured as a percentage
CREATE_CHAR(FLOAT_t,CarbonMonoxideLevel,0,0,100); // measured in parts per million (ppm)
CREATE_CHAR(FLOAT_t,CarbonMonoxidePeakLevel,0,0,100); // measured in parts per million (ppm)
CREATE_CHAR(UINT8_t,CarbonMonoxideDetected,0,0,1,NORMAL,ABNORMAL); // indicates if abnormal level is detected
CREATE_CHAR(FLOAT_t,CarbonDioxideLevel,0,0,100000); // measured on parts per million (ppm)
CREATE_CHAR(FLOAT_t,CarbonDioxidePeakLevel,0,0,100000); // measured in parts per million (ppm)
CREATE_CHAR(UINT8_t,CarbonDioxideDetected,0,0,1,NORMAL,ABNORMAL); // indicates if abnormal level is detected
CREATE_CHAR(UINT8_t,ChargingState,0,0,2,NOT_CHARGING,CHARGING,NOT_CHARGEABLE); // indicates state of battery charging
CREATE_CHAR(UINT8_t,ClosedCaptions,0,0,1); // unused by any Service
CREATE_CHAR(FLOAT_t,CoolingThresholdTemperature,10,10,35); // cooling turns on when temperature (in Celsius) rises above this threshold
CREATE_CHAR(UINT32_t,ColorTemperature,200,140,500); // measured in inverse megaKelvin (= 1,000,000 / Kelvin)
CREATE_CHAR(UINT8_t,ContactSensorState,1,0,1,DETECTED,NOT_DETECTED); // indictates if contact is detected (i.e. closed)
CREATE_CHAR(STRING_t,ConfiguredName,"unnamed",NULL,NULL); // default display name of this Service
CREATE_CHAR(FLOAT_t,CurrentAmbientLightLevel,1,0.0001,100000); // measured in Lux (lumens/m<sup>2</sup>
CREATE_CHAR(INT_t,CurrentHorizontalTiltAngle,0,-90,90); // current angle (in degrees) of slats from fully up (-90) to fully open (0) to fully down (90)
CREATE_CHAR(UINT8_t,CurrentAirPurifierState,0,0,2,INACTIVE,IDLE,PURIFYING); // indicates current state of air purification
CREATE_CHAR(UINT8_t,CurrentSlatState,0,0,2,FIXED,JAMMED,SWINGING); // indicates current state of slats
CREATE_CHAR(UINT8_t,CurrentPosition,0,0,100); // current position (as a percentage) from fully closed (0) to full open (100)
CREATE_CHAR(INT_t,CurrentVerticalTiltAngle,0,-90,90); // current angle (in degrees) of slats from fully left (-90) to fully open (0) to fully right (90)
CREATE_CHAR(UINT8_t,CurrentVisibilityState,0,0,1,VISIBLE,NOT_VISIBLE); // current visibility of the Service, as selectable on the Settings Page of the Home App
CREATE_CHAR(UINT8_t,CurrentHumidifierDehumidifierState,1,0,3,INACTIVE,IDLE,HUMIDIFYING,DEHUMIDIFYING); // indicates current state of humidifier/dehumidifer
CREATE_CHAR(UINT8_t,CurrentDoorState,1,0,4,OPEN,CLOSED,OPENING,CLOSING,STOPPED); // indicates current state of a door
CREATE_CHAR(UINT8_t,CurrentFanState,1,0,2,INACTIVE,IDLE,BLOWING); // indicates current state of a fan
CREATE_CHAR(UINT8_t,CurrentHeatingCoolingState,0,0,2,IDLE,HEATING,COOLING); // indicates whether appliance is currently heating, cooling, or just idle
CREATE_CHAR(UINT8_t,CurrentHeaterCoolerState,1,0,3,INACTIVE,IDLE,HEATING,COOLING); // indicates whether appliance is currently heating, cooling, idle, or off
CREATE_CHAR(UINT8_t,CurrentMediaState,0,0,5); // not used
CREATE_CHAR(FLOAT_t,CurrentRelativeHumidity,0,0,100); // current humidity measured as a percentage
CREATE_CHAR(FLOAT_t,CurrentTemperature,0,0,100); // current temperature measured in Celsius
CREATE_CHAR(INT_t,CurrentTiltAngle,0,-90,90); // current angle (in degrees) of slats from fully up or left (-90) to fully open (0) to fully down or right (90)
CREATE_CHAR(TLV_ENC_t,DisplayOrder,NULL_TLV,NULL_TLV,NULL_TLV); // specifies the order in which the TV inputs are displayed for selection in the Home App
CREATE_CHAR(FLOAT_t,FilterLifeLevel,100,0,100); // measured as a percentage of remaining life
CREATE_CHAR(UINT8_t,FilterChangeIndication,0,0,1,NO_CHANGE_NEEDED,CHANGE_NEEDED); // indicates state of filter
CREATE_CHAR(STRING_t,FirmwareRevision,"1.0.0",NULL,NULL); // must be in form x[.y[.z]] - informational only
CREATE_CHAR(STRING_t,HardwareRevision,"1.0.0",NULL,NULL); // must be in form x[.y[.z]] - informational only
CREATE_CHAR(FLOAT_t,HeatingThresholdTemperature,16,0,25); // heating turns on when temperature (in Celsius) falls below this threshold
CREATE_CHAR(BOOL_t,HoldPosition,false,0,1); // deprecated
CREATE_CHAR(FLOAT_t,Hue,0,0,360); // color (in degrees) from red (0) to green (120) to blue (240) and back to red (360)
CREATE_CHAR(BOOL_t,Identify,1,1,1,RUN_ID=1); // triggers an update when HomeKit wants HomeSpan to run its identification routine for an Accessory
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,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
CREATE_CHAR(UINT8_t,LockPhysicalControls,0,0,1,CONTROL_LOCK_DISABLED,CONTROL_LOCK_ENABLED); // indicates if local control lock is enabled
CREATE_CHAR(UINT8_t,LockTargetState,0,0,1,UNLOCK,LOCK); // indicates desired state of lock
CREATE_CHAR(STRING_t,Manufacturer,"HomeSpan",NULL,NULL); // any string - informational only
CREATE_CHAR(STRING_t,Model,"HomeSpan-ESP32",NULL,NULL); // any string - informational only
CREATE_CHAR(BOOL_t,MotionDetected,0,0,1,NOT_DETECTED,DETECTED); // indicates if motion is detected
CREATE_CHAR(BOOL_t,Mute,0,0,1,OFF,ON); // not used
CREATE_CHAR(STRING_t,Name,"unnamed",NULL,NULL); // default display name of the Accessory
CREATE_CHAR(FLOAT_t,NitrogenDioxideDensity,0,0,1000); // measured in &micro;g/m<sup>3</sup>
CREATE_CHAR(BOOL_t,ObstructionDetected,0,0,1,NOT_DETECTED,DETECTED); // indicates if obstruction is detected
CREATE_CHAR(FLOAT_t,PM25Density,0,0,1000); // 2.5-micron particulate density, measured in &micro;g/m<sup>3</sup>
CREATE_CHAR(UINT8_t,OccupancyDetected,0,0,1,NOT_DETECTED,DETECTED); // indicates if occupanccy is detected
CREATE_CHAR(BOOL_t,OutletInUse,0,0,1,NOT_IN_USE,IN_USE); // indicates if an appliance or light is plugged into the outlet, regardless of whether on or off
CREATE_CHAR(BOOL_t,On,0,0,1,OFF,ON); // indicates if the Service is active/on
CREATE_CHAR(FLOAT_t,OzoneDensity,0,0,1000); // measured in &micro;g/m<sup>3</sup>
CREATE_CHAR(UINT8_t,PictureMode,0,0,13); // not used
CREATE_CHAR(FLOAT_t,PM10Density,0,0,1000); // 10-micron particulate density, measured in &micro;g/m<sup>3</sup>
CREATE_CHAR(UINT8_t,PositionState,2,0,2,GOING_TO_MINIMUM,GOING_TO_MAXIMUM,STOPPED); // deprecated
CREATE_CHAR(UINT8_t,PowerModeSelection,0,0,0,VIEW_SETTINGS); // when defined, creates a "View TV Settings" button in the Home App that triggers an update to this Characteristic when pressed
CREATE_CHAR(UINT8_t,ProgramMode,0,0,2,NONE,SCHEDULED,SCHEDULE_OVERRIDEN); // indicates if pre-scheduled program is running
CREATE_CHAR(UINT8_t,ProgrammableSwitchEvent,0,0,2,SINGLE_PRESS,DOUBLE_PRESS,LONG_PRESS); // specifies type of button press
CREATE_CHAR(FLOAT_t,RelativeHumidityDehumidifierThreshold,50,0,100); // dehumidfier turns on when humidity rises above this threshold
CREATE_CHAR(FLOAT_t,RelativeHumidityHumidifierThreshold,50,0,100); // humidfier turns on when humidity falls below this threshold
CREATE_CHAR(UINT32_t,RemainingDuration,60,0,3600); // duration (in seconds) remaining for Service to be active/on
CREATE_CHAR(UINT8_t,RemoteKey,4,4,15,UP=4,DOWN,LEFT,RIGHT,CENTER,BACK,PLAY_PAUSE=11,INFO=15); // triggers an update when the corresponding key is pressed in the Remote Control widget on an iPhone
CREATE_CHAR(UINT8_t,ResetFilterIndication,1,1,1,RESET_FILTER=1); // triggers an update when the user chooses to reset the <b>FilterChangeIndication</b> (only appears in Eve App, not Home App)
CREATE_CHAR(INT_t,RotationDirection,0,0,1,CLOCKWISE,COUNTERCLOCKWISE); // indicates the rotation direction of a fan
CREATE_CHAR(FLOAT_t,RotationSpeed,0,0,100); // measured as a percentage
CREATE_CHAR(FLOAT_t,Saturation,0,0,100); // color saturation, measured as a percentage
CREATE_CHAR(UINT8_t,SecuritySystemAlarmType,0,0,1,KNOWN,UNKNOWN); // indicates whether alarm was triggered for known reason
CREATE_CHAR(UINT8_t,SecuritySystemCurrentState,3,0,4,ARMED_STAY,ARMED_AWAY,ARMED_NIGHT,DISARMED,ALARM_TRIGGERED); // indicates current state of the security system
CREATE_CHAR(UINT8_t,SecuritySystemTargetState,3,0,3,ARM_STAY,ARM_AWAY,ARM_NIGHT,DISARM); // indicates desired state of the security system
CREATE_CHAR(STRING_t,SerialNumber,"HS-12345",NULL,NULL); // any string - informational only
CREATE_CHAR(UINT8_t,ServiceLabelIndex,1,1,255); // numerical index used to distinguish multiple copies of the same Service within an Accessory
CREATE_CHAR(UINT8_t,ServiceLabelNamespace,1,0,1,DOTS,NUMERALS); // indicates how un-named Services linked together with a <b>ServiceLabel</b> Service should be displayed in the Home App
CREATE_CHAR(UINT8_t,SlatType,0,0,1,HORIZONTAL,VERTICAL); // indicates the direction of a slat or group of slats
CREATE_CHAR(UINT8_t,SleepDiscoveryMode,0,0,1); // not used
CREATE_CHAR(UINT8_t,SmokeDetected,0,0,1,NOT_DETECTED,DETECTED); // indicates if smoke is detected
CREATE_CHAR(BOOL_t,StatusActive,1,0,1,NOT_FUNCTIONING,FUNCTIONING); // indicates whether the Service is properly functioning
CREATE_CHAR(UINT8_t,StatusFault,0,0,1,NO_FAULT,FAULT); // indicates whether the Service has a fault (only appears in Eve App, not Home App)
CREATE_CHAR(UINT8_t,StatusJammed,0,0,1,NOT_JAMMED,JAMMED); // indicates whether the Service has been "jammed"
CREATE_CHAR(UINT8_t,StatusLowBattery,0,0,1,NOT_LOW_BATTERY,LOW_BATTERY); // indicates state of battery
CREATE_CHAR(UINT8_t,StatusTampered,0,0,1,NOT_TAMPERED,TAMPERED); // indicates whether the Service has been tampered with
CREATE_CHAR(FLOAT_t,SulphurDioxideDensity,0,0,1000); // measured in &micro;g/m<sup>3</sup>
CREATE_CHAR(UINT8_t,SwingMode,0,0,1,SWING_DISABLED,SWING_ENABLED); // indicates whether swing-mode is enabled
CREATE_CHAR(UINT8_t,TargetAirPurifierState,1,0,1,MANUAL,AUTO); // indicates desired state of air purifier
CREATE_CHAR(UINT8_t,TargetFanState,1,0,1,MANUAL,AUTO); // indicates desired state of fan
CREATE_CHAR(INT_t,TargetTiltAngle,0,-90,90); // indicated desired angle (in degrees) of slats from fully up or left (-90) to fully open (0) to fully down or right (90)
CREATE_CHAR(UINT8_t,TargetHeaterCoolerState,0,0,2,AUTO,HEAT,COOL); // indicates desired state of heater/cooler
CREATE_CHAR(UINT32_t,SetDuration,60,0,3600); // specifies the duration (in seconds) for a Service to remain on once activated
CREATE_CHAR(INT_t,TargetHorizontalTiltAngle,0,-90,90); // indicates desired angle (in degrees) of slats from fully up (-90) to fully open (0) to fully down (90)
CREATE_CHAR(UINT8_t,TargetHumidifierDehumidifierState,0,0,2,AUTO,HUMIDIFY,DEHUMIDIFY); // indicates desired state of humidifier/dehumidifier
CREATE_CHAR(UINT8_t,TargetPosition,0,0,100); // indicates target position (as a percentage) from fully closed (0) to full open (100)
CREATE_CHAR(UINT8_t,TargetDoorState,1,0,1,OPEN,CLOSED); // indicates desired state of door
CREATE_CHAR(UINT8_t,TargetHeatingCoolingState,0,0,3,OFF,HEAT,COOL,AUTO); // indicates desired state of appliance
CREATE_CHAR(UINT8_t,TargetMediaState,0,0,2); // unused
CREATE_CHAR(FLOAT_t,TargetRelativeHumidity,0,0,100); // indicates desired humidity measured as a percentage
CREATE_CHAR(FLOAT_t,TargetTemperature,16,10,38); // indicates desired temperature measures in Celsius
CREATE_CHAR(UINT8_t,TargetVisibilityState,0,0,1,VISIBLE,NOT_VISIBLE); // indicates desired visibility of the Service, as selectable on the Settings Page of the Home App
CREATE_CHAR(UINT8_t,TemperatureDisplayUnits,0,0,1,CELSIUS,FAHRENHEIT); // indicates the desired units to display the temperature on the device itself (has no effect on Home App)
CREATE_CHAR(INT_t,TargetVerticalTiltAngle,0,-90,90); // indicates desired angle (in degrees) of slats from fully left (-90) to fully open (0) to fully right (90)
CREATE_CHAR(UINT8_t,ValveType,0,0,3,GENERIC,IRRIGATION,SHOWER_HEAD,FAUCET); // indicates the type of valve
CREATE_CHAR(STRING_t,Version,"1.0.0",NULL,NULL); // unused
CREATE_CHAR(FLOAT_t,VOCDensity,0,0,1000); // measured in &micro;g/m<sup>3</sup>
CREATE_CHAR(UINT8_t,Volume,0,0,100); // unused
CREATE_CHAR(UINT8_t,VolumeControlType,3,0,3,NONE,RELATIVE,RELATIVE_CURRENT,ABSOLUTE); // indicates the type of volume control
CREATE_CHAR(UINT8_t,VolumeSelector,0,0,1,VOLUME_UP,VOLUME_DOWN); // triggered by presses to the iPhone's volume up/down buttons when TV is selected in the Remote Control widget
CREATE_CHAR(FLOAT_t,WaterLevel,0,0,100); // measured as a percentage
}
////////////////////////////////////////////////////////
// MACROS TO ADD CUSTOM SERVICES AND CHARACTERISTICS //
////////////////////////////////////////////////////////
#ifndef CUSTOM_CHAR_HEADER
#define CUSTOM_CHAR(NAME,UUID,PERMISISONS,FORMAT,DEFVAL,MINVAL,MAXVAL,STATIC_RANGE) \
HapChar _CUSTOM_##NAME {#UUID,#NAME,(PERMS)(PERMISISONS),FORMAT,STATIC_RANGE}; \
namespace Characteristic { struct NAME : SpanCharacteristic { NAME(FORMAT##_t val=DEFVAL, boolean nvsStore=false) : SpanCharacteristic {&_CUSTOM_##NAME,true} { init<FORMAT##_t>(val,nvsStore,MINVAL,MAXVAL); } }; }
#else
#define CUSTOM_CHAR(NAME,UUID,PERMISISONS,FORMAT,DEFVAL,MINVAL,MAXVAL,STATIC_RANGE) \
extern HapChar _CUSTOM_##NAME; \
namespace Characteristic { struct NAME : SpanCharacteristic { NAME(FORMAT##_t val=DEFVAL, boolean nvsStore=false) : SpanCharacteristic {&_CUSTOM_##NAME,true} { init<FORMAT##_t>(val,nvsStore,MINVAL,MAXVAL); } }; }
#endif
#define CUSTOM_CHAR_STRING(NAME,UUID,PERMISISONS,DEFVAL) CUSTOM_CHAR(NAME,UUID,PERMISISONS,STRING,DEFVAL,NULL,NULL,true);
#define CUSTOM_CHAR_TLV8(NAME,UUID,PERMISISONS) CUSTOM_CHAR(NAME,UUID,PERMISISONS,TLV_ENC,NULL_TLV,NULL_TLV,NULL_TLV,true);
#define CUSTOM_CHAR_DATA(NAME,UUID,PERMISISONS) CUSTOM_CHAR(NAME,UUID,PERMISISONS,DATA,NULL_DATA,NULL_DATA,NULL_DATA,true);
#define CUSTOM_SERV(NAME,UUID) \
namespace Service { struct NAME : SpanService { NAME() : SpanService{#UUID,#NAME,true}{} }; }
////////////////////////////////////////////////////////
// MACROS TO ADD A NEW ACCESSORY WITH OPTIONAL NAME //
////////////////////////////////////////////////////////
#define SPAN_ACCESSORY(...) new SpanAccessory(); new Service::AccessoryInformation(); new Characteristic::Identify(); __VA_OPT__(new Characteristic::Name(__VA_ARGS__));

View File

@@ -0,0 +1,299 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 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.
*
********************************************************************************/
#include "TLV8.h"
//////////////////////////////////////
tlv8_t::tlv8_t(uint8_t tag, size_t len, const uint8_t* val) : tag{tag}, len{len} {
if(len>0){
this->val=std::unique_ptr<uint8_t>((uint8_t *)HS_MALLOC(len));
if(val!=NULL)
memcpy((this->val).get(),val,len);
}
}
//////////////////////////////////////
void tlv8_t::update(size_t addLen, const uint8_t *addVal){
if(addLen>0){
uint8_t *p=val.release();
p=(uint8_t *)HS_REALLOC(p,len+addLen);
val=std::unique_ptr<uint8_t>(p);
if(addVal!=NULL)
memcpy(p+len,addVal,addLen);
len+=addLen;
}
}
/////////////////////////////////////
void tlv8_t::osprint(std::ostream& os) const {
uint8_t *p=val.get(); // starting pointer
uint8_t *pend=p+len; // ending pointer (may equal starting if len=0)
do{
uint8_t nBytes=(pend-p)>255?255:(pend-p); // max is 255 bytes per TLV record
os.write((char *)&tag,1);
os.write((char *)&nBytes,1);
os.write((char *)p,nBytes);
p+=nBytes;
} while(p<pend);
}
/////////////////////////////////////
TLV8_itc TLV8::add(uint8_t tag, size_t len, const uint8_t* val) {
if(!empty() && back().getTag()==tag)
back().update(len,val);
else
emplace_back(tag,len,val);
return(--end());
}
/////////////////////////////////////
TLV8_itc TLV8::add(uint8_t tag, TLV8 &subTLV){
auto it=add(tag,subTLV.pack_size(),NULL); // create space for inserting sub TLV and store iterator to new element
subTLV.pack(*it); // pack subTLV into new element
return(--end());
}
/////////////////////////////////////
TLV8_itc TLV8::add(uint8_t tag, uint64_t val){
uint8_t *p=reinterpret_cast<uint8_t *>(&val);
size_t nBytes=sizeof(uint64_t);
while(nBytes>1 && p[nBytes-1]==0) // TLV requires little endian of size 1, 2, 4, or 8 bytes (include trailing zeros as needed)
nBytes--;
if(nBytes==3) // need to include a trailing zero so that total bytes=4
nBytes=4;
else if(nBytes>4) // need to include multiple trailing zeros so that total bytes=8
nBytes=8;
return(add(tag, nBytes, p));
}
/////////////////////////////////////
TLV8_itc TLV8::find(uint8_t tag, TLV8_itc it1, TLV8_itc it2) const {
auto it=it1;
while(it!=it2 && it->getTag()!=tag)
it++;
return(it);
}
/////////////////////////////////////
size_t TLV8::pack_size(TLV8_itc it1, TLV8_itc it2) const {
size_t nBytes=0;
while(it1!=it2){
nBytes+=2+(*it1).getLen();
if((*it1).getLen()>255)
nBytes+=2*(((*it1).getLen()-1)/255);
it1++;
}
return(nBytes);
}
/////////////////////////////////////
size_t TLV8::pack(uint8_t *buf, size_t bufSize) const {
size_t nBytes=0;
while(nBytes<bufSize && currentPackIt!=endPackIt){
switch(currentPackPhase){
case 0:
currentPackBuf=*currentPackIt;
endPackBuf=(*currentPackIt)+currentPackIt->getLen();
currentPackPhase=1;
break;
case 1:
*buf++=currentPackIt->getTag();
nBytes++;
currentPackPhase=2;
break;
case 2:
currentPackLen=endPackBuf-currentPackBuf;
if(currentPackLen>255)
currentPackLen=255;
*buf++=currentPackLen;
nBytes++;
currentPackPhase=3;
break;
case 3:
if(currentPackLen==0){
if(endPackBuf==currentPackBuf){
currentPackIt++;
currentPackPhase=0;
} else {
currentPackPhase=1;
}
break;
}
size_t copyBytes=(currentPackLen<(bufSize-nBytes)) ? currentPackLen : (bufSize-nBytes);
memcpy(buf,currentPackBuf,copyBytes);
buf+=copyBytes;
currentPackBuf+=copyBytes;
currentPackLen-=copyBytes;
nBytes+=copyBytes;
break;
}
}
return(nBytes);
}
/////////////////////////////////////
int TLV8::unpack(uint8_t *buf, size_t bufSize){
if(bufSize==0)
return(-1);
if(empty())
unpackPhase=0;
while(bufSize>0){
switch(unpackPhase){
case 0:
unpackTag=*buf++;
bufSize--;
add(unpackTag);
unpackPhase=1;
break;
case 1:
unpackBytes=*buf++;
bufSize--;
if(unpackBytes==0)
unpackPhase=0;
else
unpackPhase=2;
break;
case 2:
size_t copyBytes=unpackBytes<bufSize ? unpackBytes : bufSize;
add(unpackTag,copyBytes,buf);
buf+=copyBytes;
unpackBytes-=copyBytes;
bufSize-=copyBytes;
if(unpackBytes==0)
unpackPhase=0;
break;
}
}
return(unpackPhase);
}
/////////////////////////////////////
int TLV8::unpack(TLV8_itc it){
if(it==end())
return(0);
return(unpack(*it,it->getLen()));
}
/////////////////////////////////////
const char *TLV8::getName(uint8_t tag) const {
if(names==NULL)
return(NULL);
for(int i=0;i<nNames;i++){
if(names[i].tag==tag)
return(names[i].name);
}
return(NULL);
}
/////////////////////////////////////
void TLV8::print(TLV8_itc it1, TLV8_itc it2) const {
while(it1!=it2){
const char *name=getName(it1->getTag());
if(name)
Serial.printf("%s",name);
else
Serial.printf("%d",it1->getTag());
Serial.printf("(%d) ",it1->getLen());
for(int i=0;i<it1->getLen();i++)
Serial.printf("%02X",(*it1)[i]);
if(it1->getLen()==0)
Serial.printf(" [null]");
else if(it1->getLen()<=4)
Serial.printf(" [%u]",it1->getVal());
else if(it1->getLen()<=8)
Serial.printf(" [%llu]",it1->getVal<uint64_t>());
Serial.printf("\n");
it1++;
}
}
//////////////////////////////////////
void TLV8::printAll_r(String label) const{
for(auto it=begin();it!=end();it++){
Serial.printf("%s",label.c_str());
print(it);
TLV8 tlv;
if(tlv.unpack(*it,(*it).getLen())==0)
tlv.printAll_r(label+String((*it).getTag())+"-");
}
Serial.printf("%sDONE\n",label.c_str());
}
//////////////////////////////////////
void TLV8::osprint(std::ostream& os, TLV8_itc it1, TLV8_itc it2) const {
for(auto it=it1;it!=it2;it++)
(*it).osprint(os);
}
//////////////////////////////////////

View File

@@ -0,0 +1,147 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 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 <sstream>
#include <list>
#include <memory>
#include "PSRAM.h"
class tlv8_t {
private:
uint8_t tag;
size_t len;
std::unique_ptr<uint8_t> val;
public:
tlv8_t(uint8_t tag, size_t len, const uint8_t* val);
void update(size_t addLen, const uint8_t *addVal);
void osprint(std::ostream& os) const;
operator uint8_t*() const {
return(val.get());
}
uint8_t & operator[](int index) const {
return(val.get()[index]);
}
uint8_t *get() const {
return(val.get());
}
size_t getLen() const {
return(len);
}
uint8_t getTag() const {
return(tag);
}
template<class T=uint32_t> T getVal() const {
T iVal=0;
for(int i=0;i<len;i++)
iVal|=static_cast<T>(val.get()[i])<<(i*8);
return(iVal);
}
};
/////////////////////////////////////
typedef std::list<tlv8_t, Mallocator<tlv8_t>>::const_iterator TLV8_itc;
typedef struct { const uint8_t tag; const char *name; } TLV8_names;
/////////////////////////////////////
class TLV8 : public std::list<tlv8_t, Mallocator<tlv8_t>> {
TLV8_itc mutable currentPackIt;
TLV8_itc mutable endPackIt;
uint8_t mutable *currentPackBuf;
uint8_t mutable *endPackBuf;
int mutable currentPackPhase;
size_t mutable currentPackLen;
uint8_t unpackTag;
size_t unpackBytes;
int unpackPhase;
const TLV8_names *names=NULL;
int nNames=0;
void printAll_r(String label) const;
public:
TLV8(){};
TLV8(const TLV8_names *names, int nNames) : names{names}, nNames{nNames} {};
TLV8_itc add(uint8_t tag, size_t len, const uint8_t *val);
TLV8_itc add(uint8_t tag, uint64_t val);
TLV8_itc add(uint8_t tag, TLV8 &subTLV);
TLV8_itc add(uint8_t tag){return(add(tag, 0, NULL));}
TLV8_itc add(uint8_t tag, const char *val){return(add(tag, strlen(val), reinterpret_cast<const uint8_t*>(val)));}
TLV8_itc find(uint8_t tag, TLV8_itc it1, TLV8_itc it2) const;
TLV8_itc find(uint8_t tag, TLV8_itc it1) const {return(find(tag, it1, end()));}
TLV8_itc find(uint8_t tag) const {return(find(tag, begin(), end()));}
int len(TLV8_itc it) const {return(it==end()?-1:(*it).getLen());}
size_t pack_size(TLV8_itc it1, TLV8_itc it2) const;
size_t pack_size() const {return(pack_size(begin(), end()));}
void pack_init(TLV8_itc it1, TLV8_itc it2) const {currentPackIt=it1; endPackIt=it2; currentPackPhase=0;}
void pack_init(TLV8_itc it1) const {pack_init(it1, std::next(it1));}
void pack_init() const {pack_init(begin(),end());}
size_t pack(uint8_t *buf, size_t bufSize) const;
size_t pack(uint8_t *buf) const {pack_init(); return(pack(buf,pack_size()));}
const char *getName(uint8_t tag) const;
void print(TLV8_itc it1, TLV8_itc it2) const;
void print(TLV8_itc it1) const {print(it1, std::next(it1));}
void print() const {print(begin(), end());}
void printAll() const {printAll_r("");}
void osprint(std::ostream& os, TLV8_itc it1, TLV8_itc it2) const;
void osprint(std::ostream& os, TLV8_itc it1) const {osprint(os, it1, std::next(it1));}
void osprint(std::ostream& os) const {osprint(os, begin(), end());}
int unpack(uint8_t *buf, size_t bufSize);
int unpack(TLV8_itc it);
void wipe() {std::list<tlv8_t, Mallocator<tlv8_t>>().swap(*this);}
};

View File

@@ -0,0 +1,295 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 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.
*
********************************************************************************/
#include "Utils.h"
#include "HomeSpan.h"
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Contains various generic utility functions and classes:
//
// 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)
//
// class PushButton - tracks Single, Double, and Long Presses of a pushbutton that connects a specified pin to ground
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
char *Utils::readSerial(char *c, int max){
if(homeSpan.getSerialInputDisable()){
c[0]='\0';
return(c);
}
int i=0;
char buf;
while(1){
while(!Serial.available()); // wait until there is a new character
buf=Serial.read();
if(buf=='\n'){ // exit upon newline
if(i>0) // characters have been typed
c[i]='\0'; // replace newline with string terminator
return(c); // return updated string
}
if(buf!='\r'){ // save any character except carriage return
c[i]=buf; // store new character
if(i<max) // do not store more than max characters (excluding string terminator)
i++;
}
} // while(1)
} // readSerial
//////////////////////////////////////
char *Utils::stripBackslash(char *c){
size_t n=strlen(c);
char *p=c;
for(int i=0;i<=n;i++){
*p=c[i];
if(*p!='\\')
p++;
}
return(c);
}
//////////////////////////////////////
String Utils::mask(char *c, int n){
String s="";
int len=strlen(c);
for(int i=0;i<len;i++){
if(i<n || i>=len-n)
s+=c[i];
else
s+='*';
}
return(s);
} // mask
////////////////////////////////
// PushButton //
////////////////////////////////
PushButton::PushButton(int pin, triggerType_t triggerType){
this->pin=pin;
this->triggerType=triggerType;
status=0;
doubleCheck=false;
if(triggerType==TRIGGER_ON_LOW)
pinMode(pin, INPUT_PULLUP);
else if(triggerType==TRIGGER_ON_HIGH)
pinMode(pin, INPUT_PULLDOWN);
#if SOC_TOUCH_SENSOR_NUM > 0
else if (triggerType==TRIGGER_ON_TOUCH && threshold==0){
for(int i=0;i<calibCount;i++)
threshold+=touchRead(pin);
threshold/=calibCount;
#if SOC_TOUCH_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
threshold*=2;
LOG0("Touch Sensor at pin=%d used for calibration. Triggers when sensor reading > %d.\n",pin,threshold);
#endif
}
#endif
if(triggerType(pin)){
pressType=CLOSED;
toggleStatus=2;
} else {
pressType=OPEN;
toggleStatus=0;
}
}
//////////////////////////////////////
boolean PushButton::triggered(uint16_t singleTime, uint16_t longTime, uint16_t doubleTime){
unsigned long cTime=millis();
switch(status){
case 0:
if(doubleCheck && cTime>doubleAlarm){
doubleCheck=false;
pressType=SINGLE;
return(true);
}
if(triggerType(pin)){ // button is "pressed"
singleAlarm=cTime+singleTime;
if(!doubleCheck){
status=1;
doubleAlarm=singleAlarm+doubleTime;
longAlarm=cTime+longTime;
} else {
status=4;
}
}
break;
case 1:
case 2:
if(!triggerType(pin)){ // button is released
status=0;
if(cTime>singleAlarm){
doubleCheck=true;
}
} else
if(cTime>longAlarm){ // button is long-pressed
longAlarm=cTime+longTime;
status=3;
pressType=LONG;
return(true);
}
break;
case 3:
if(!triggerType(pin)) // button has been released after a long press
status=0;
else if(cTime>longAlarm){
longAlarm=cTime+longTime;
pressType=LONG;
return(true);
}
break;
case 4:
if(!triggerType(pin)){ // button is released
status=0;
} else
if(cTime>singleAlarm){ // button is still pressed
status=5;
pressType=DOUBLE;
doubleCheck=false;
return(true);
}
break;
case 5:
if(!triggerType(pin)) // button has been released after double-click
status=0;
break;
}
return(false);
}
//////////////////////////////////////
boolean PushButton::toggled(uint16_t toggleTime){
unsigned long cTime=millis();
switch(toggleStatus){
case 0:
if(triggerType(pin)){ // switch is toggled CLOSED
singleAlarm=cTime+toggleTime;
toggleStatus=1;
}
break;
case 1:
if(!triggerType(pin)){ // switch is toggled back OPEN too soon
toggleStatus=0;
}
else if(cTime>singleAlarm){ // switch has been in CLOSED state for sufficient time
toggleStatus=2;
pressType=CLOSED;
return(true);
}
break;
case 2:
if(!triggerType(pin)){ // switch is toggled OPEN after being in CLOSED state
toggleStatus=0;
pressType=OPEN;
return(true);
}
break;
} // switch
return(false);
}
//////////////////////////////////////
boolean PushButton::primed(){
if(millis()>singleAlarm && status==1){
status=2;
return(true);
}
return(false);
}
//////////////////////////////////////
int PushButton::type(){
return(pressType);
}
//////////////////////////////////////
void PushButton::wait(){
while(triggerType(pin));
}
//////////////////////////////////////
void PushButton::reset(){
status=0;
}
//////////////////////////////////////
PushButton::touch_value_t PushButton::threshold=0;

View File

@@ -0,0 +1,237 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 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 "PSRAM.h"
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)
}
/////////////////////////////////////////////////
// Creates a temporary buffer that is freed after
// going out of scope
template <class bufType>
class TempBuffer {
private:
bufType *buf=NULL;
size_t nElements;
public:
TempBuffer(size_t _nElements=1) : nElements(_nElements) {
buf=(bufType *)HS_MALLOC(nElements*sizeof(bufType));
if(buf==NULL){
Serial.printf("\n\n*** FATAL ERROR: Requested allocation of %d bytes failed. Program Halting.\n\n",nElements*sizeof(bufType));
while(1);
}
}
TempBuffer(bufType *addBuf...) : nElements(0) {
va_list args;
va_start(args,addBuf);
while(addBuf!=NULL){
size_t addElements=va_arg(args,size_t);
buf=(bufType *)HS_REALLOC(buf,(nElements+addElements)*sizeof(bufType));
if(buf==NULL){
Serial.printf("\n\n*** FATAL ERROR: Requested allocation of %d bytes failed. Program Halting.\n\n",nElements*sizeof(bufType));
while(1);
}
memcpy(buf+nElements,addBuf,addElements*sizeof(bufType));
nElements+=addElements;
addBuf=va_arg(args,bufType *);
}
va_end(args);
}
~TempBuffer(){
free(buf);
}
int len(){
return(nElements*sizeof(bufType));
}
int size(){
return(nElements);
}
bufType *get(){
return(buf);
}
operator bufType*() const{
return(buf);
}
};
////////////////////////////////
// PushButton //
////////////////////////////////
class PushButton{
int status;
int toggleStatus;
boolean doubleCheck;
uint32_t singleAlarm;
uint32_t doubleAlarm;
uint32_t longAlarm;
#if SOC_TOUCH_VERSION_2
typedef uint32_t touch_value_t;
#else
typedef uint16_t touch_value_t;
#endif
static touch_value_t threshold;
static const int calibCount=20;
public:
typedef boolean (*triggerType_t)(int pin);
protected:
int pressType;
int pin;
triggerType_t triggerType;
public:
enum {
SINGLE=0, // applicable only for push button
DOUBLE=1, // applicable only for push button
LONG=2, // applicable only for push button
CLOSED=3, // applicable only for toggle switch
OPEN=4 // applicable only for toggle switch
};
static boolean TRIGGER_ON_LOW(int pin){return(!digitalRead(pin));}
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
static boolean TRIGGER_ON_TOUCH(int pin){return(touchRead(pin)<threshold);}
#endif
#endif
PushButton(int pin, triggerType_t triggerType=TRIGGER_ON_LOW);
// Creates a push-button/toggle-switch of specified type on specified pin
//
// pin: pin number to which the button is connected
// triggerType: a function of of the form 'boolean f(int)' that is passed
// the parameter *pin* and returns TRUE if the button associated
// with *pin* is pressed/on, or FALSE if not. Can choose from 3 pre-specifed
// triggerType_t functions (TRIGGER_ON_LOW, TRIGGER_ON_HIGH, and TRIGGER_ON_TOUCH),
// or write your own custom handler
void reset();
// Resets state of PushButton. Should be called once before any loops that will
// repeatedly check the button for a trigger() event.
boolean triggered(uint16_t singleTime, uint16_t longTime, uint16_t doubleTime=0);
// Returns true if button has been triggered by an press event based on the following parameters:
// singleTime: the minimum time required for the button to be pressed to trigger a Single Press
// doubleTime: the maximum time allowed between button presses to qualify as a Double Press
// longTime: the minimum time required for the button to be pressed and held to trigger a Long Press
// All times are in milliseconds (ms). Trigger Rules:
// * If button is pressed and continuously held, a Long Press will be triggered every longTime ms until the
// button is released.
// * If button is pressed for more than singleTime ms but less than longTime ms and then released, a Single Press
// will be triggered, UNLESS
// * The button is pressed a second time within doubleTime ms AND held again for at least singleTime ms, in which case
// a DoublePress will be triggered. No further events will occur until the button is released.
// * If singleTime>longTime, only Long Press triggers can occur.
// * If doubleTime=0, Double Presses cannot occur.
// * Once triggered() returns true, if will subsequently return false until there is a new trigger event.
boolean primed();
// Returns true if button has been pressed and held for greater than singleTime, but has not yet been released.
// After returning true, subsequent calls will always return false until the button has been released and reset.
int type();
// Returns the press type based on the whether triggered() or toggled() is called:
// * For a push button, returns the last trigger event: 0=Single Press, 1=Double Press, 2=Long Press
// * For a toggle switch, returns the current state of the switch: 4=ON, 5=OFF
void wait();
// Waits for button to be released. Use after Long Press if button release confirmation is desired
boolean toggled(uint16_t toggleTime);
// Returns true if switch has been toggled, where
// toggleTime: the minimum time (in milliseconds) a switch needs to be ON to register a toggle event
// Once toggled() returns true, if will subsequently return false until the switch is toggled again.
int getPin(){return(pin);}
// Returns pin number
#if SOC_TOUCH_SENSOR_NUM > 0
static void setTouchCycles(uint16_t measureTime, uint16_t sleepTime){touchSetCycles(measureTime,sleepTime);}
// Sets the measure time and sleep time touch cycles , and lower threshold that triggers a touch - used only when triggerType=PushButton::TRIGGER_ON_TOUCH
// measureTime: duration of measurement time of all touch sensors in number of clock cycles
// sleepTime: duration of sleep time (between measurements) of all touch sensors number of clock cycles
static void setTouchThreshold(touch_value_t thresh){threshold=thresh;}
// Sets the threshold that triggers a touch - used only when triggerType=TRIGGER_ON_TOUCH
// thresh: the read value of touch sensors, beyond which which sensors are considered touched (i.e. "pressed").
// This is a class-level value applied to all touch sensor buttons.
#endif
};

View File

@@ -0,0 +1,30 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2022 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 "../src/extras/Blinker.h"

View File

@@ -0,0 +1,30 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2022 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 "../src/extras/Pixel.h"

View File

@@ -0,0 +1,30 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2022 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 "../src/extras/PwmPin.h"

View File

@@ -0,0 +1,30 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2022 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 "../src/extras/RFControl.h"

View File

@@ -0,0 +1,30 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2023 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 "../src/extras/StepperControl.h"

View File

@@ -0,0 +1,30 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2022 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 "../src/extras/Stepper_A3967.h"

View File

@@ -0,0 +1,30 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2022 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 "../src/extras/Stepper_TB6612.h"

View File

@@ -0,0 +1,149 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 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.
*
********************************************************************************/
#include "Blinker.h"
////////////////////////////////
// Blinker //
////////////////////////////////
Blinker::Blinker(Blinkable *led, uint16_t autoOffDuration){
this->led=led;
pauseDuration=autoOffDuration*1000;
}
//////////////////////////////////////
void Blinker::blinkTask(void *arg){
Blinker *b=(Blinker *)arg;
for(;;){
for(int i=0;i<b->nBlinks;i++){
b->led->on();
delay(b->onTime);
b->led->off();
delay(b->offTime);
}
delay(b->delayTime);
}
}
//////////////////////////////////////
void Blinker::start(int period, float dutyCycle){
start(period, dutyCycle, 1, 0);
}
//////////////////////////////////////
void Blinker::start(int period, float dutyCycle, int nBlinks, int delayTime){
if(!led)
return;
onTime=dutyCycle*period;
offTime=period-onTime;
this->delayTime=delayTime+offTime;
this->nBlinks=nBlinks;
stop();
xTaskCreate( blinkTask, "BlinkTask", 1024, (void *)this, 2, &blinkHandle );
pauseTime=millis();
isPaused=false;
status=STATUS::BLINKING;
}
//////////////////////////////////////
void Blinker::stop(){
if(!led)
return;
if(blinkHandle!=NULL){
vTaskDelete(blinkHandle);
blinkHandle=NULL;
}
isPaused=true;
status=STATUS::OFF;
}
//////////////////////////////////////
void Blinker::on(){
if(!led)
return;
stop();
led->on();
pauseTime=millis();
isPaused=false;
status=STATUS::ON;
}
//////////////////////////////////////
void Blinker::off(){
if(!led)
return;
stop();
led->off();
status=STATUS::OFF;
}
//////////////////////////////////////
void Blinker::check(){
if(!led)
return;
if(pauseDuration==0 || isPaused || (millis()-pauseTime)<pauseDuration)
return;
ESP_LOGI(BLINKER_TAG,"Pausing LED");
off();
}
//////////////////////////////////////
int Blinker::getPin(){
if(!led)
return(-1);
return(led->getPin());
}

View File

@@ -0,0 +1,137 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 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 <driver/timer.h>
[[maybe_unused]] static const char* BLINKER_TAG = "Blinker";
////////////////////////////////
// Blinkable Interface //
////////////////////////////////
class Blinkable {
public:
virtual void on()=0;
virtual void off()=0;
virtual int getPin()=0;
};
////////////////////////////////
// Blinker //
////////////////////////////////
class Blinker {
enum STATUS {OFF, BLINKING, ON};
TaskHandle_t blinkHandle = NULL;
Blinkable *led;
int nBlinks;
int onTime;
int offTime;
int delayTime;
STATUS status=STATUS::OFF;
unsigned long pauseDuration;
unsigned long pauseTime;
boolean isPaused=false;
static void blinkTask(void *arg);
public:
Blinker(Blinkable *led, uint16_t autoOffDuration=0);
// Creates a generic blinking LED in a separate task thread
//
// led: An initialized LED device that implements the Blinkable Interface
////
// autoOffDuration: If greater than zero, Blinker will automatically turn off after autoOffDuration (in seconds) has elapsed
// Blinker will resume normal operation upon next call to start(), on(), or off()
// Program must periodically call check() for auto-off functionality to work
void start(int period, float dutyCycle=0.5);
// Starts simple ON/OFF blinking.
//
// period: ON/OFF blinking period, in milliseconds
// dutyCycle: Fraction of period that LED is ON (default=50%)
void start(int period, float dutyCycle, int nBlinks, int delayTime);
// Starts ON/OFF blinking pattern.
//
// period: ON/OFF blinking period, in milliseconds, used for blinking portion of pattern
// dutyCycle: Fraction of period that LED is ON (default=50%)
// nBlinks: Number of blinks in blinking portion of pattern
// delayTime: delay, in milliseconds, during which LED is off before restarting blinking pattern
void stop();
// Stops current blinking pattern.
void on();
// Stops current blinking pattern and turns on LED
void off();
// Stops current blinking pattern and turns off LED
void refresh(){if(status==STATUS::ON)on();}
// Refreshes LED color by turning device ON if status=ON (if status=BLINKING, new color is automatically used at next blink)
void check();
// Optional check to see if LED output should be paused (check is bypassed if pauseDuration=0)
int getPin();
// Returns pin number of connected LED
};
////////////////////////////////
// GenericLED //
////////////////////////////////
class GenericLED : public Blinkable {
int pin;
public:
GenericLED(int pin) : pin{pin} {pinMode(pin,OUTPUT);digitalWrite(pin,0);}
void on() {digitalWrite(pin,HIGH);}
void off() {digitalWrite(pin,LOW);}
int getPin() {return(pin);}
};

View File

@@ -0,0 +1,205 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 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.
*
********************************************************************************/
////////////////////////////////////////////
// Addressable LEDs //
////////////////////////////////////////////
#include "Pixel.h"
////////////////////////////////////////////
// Single-Wire RGB/RGBW NeoPixels //
////////////////////////////////////////////
Pixel::Pixel(int pin, pixelType_t pixelType){
rf=new RFControl(pin,false,false); // set clock to 1/80 usec, no default driver
if(!*rf)
return;
map=pixelType;
if(map[3])
bytesPerPixel=4;
else
bytesPerPixel=3;
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){
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;
}
///////////////////
void Pixel::set(Color *c, int nPixels, boolean multiColor){
if(!*rf || nPixels==0)
return;
status.nPixels=nPixels;
status.color=c;
status.iMem=0;
status.started=true;
status.px=this;
status.multiColor=multiColor;
status.iByte=0;
loadData(this); // load first two bytes of data to get started
loadData(this);
rmt_tx_start(rf->getChannel(),true);
while(status.started); // wait for transmission to be complete
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 //
////////////////////////////////////////////
Dot::Dot(uint8_t dataPin, uint8_t clockPin){
pinMode(dataPin,OUTPUT);
pinMode(clockPin,OUTPUT);
digitalWrite(dataPin,LOW);
digitalWrite(clockPin,LOW);
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;
#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;
}
#endif
}
///////////////////
void Dot::set(Color *c, int nPixels, boolean multiColor){
*dataClearReg=dataMask; // send all zeros
for(int j=0;j<31;j++){
*clockSetReg=clockMask;
*clockClearReg=clockMask;
}
for(int i=0;i<nPixels;i++){
for(int b=31;b>=0;b--){
if((c->val>>b)&1)
*dataSetReg=dataMask;
else
*dataClearReg=dataMask;
*clockSetReg=clockMask;
*clockClearReg=clockMask;
}
c+=multiColor;
}
*dataClearReg=dataMask; // send all zeros
for(int j=0;j<31;j++){
*clockSetReg=clockMask;
*clockClearReg=clockMask;
}
}
////////////////////////////////////////////

View File

@@ -0,0 +1,286 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 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.
*
********************************************************************************/
////////////////////////////////////////////
// Addressable LEDs //
////////////////////////////////////////////
#pragma once
#include "RFControl.h"
#include "PwmPin.h"
#include "Blinker.h"
[[maybe_unused]] static const char* PIXEL_TAG = "Pixel";
typedef const uint8_t 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};
};
////////////////////////////////////////////
// Single-Wire RGB/RGBW NeoPixels //
////////////////////////////////////////////
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;
};
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;
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;
return(*this);
}
bool operator==(const Color& color){
return(val==color.val);
}
bool operator!=(const Color& color){
return(val!=color.val);
}
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 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;
};
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
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)
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
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
operator bool(){ // override boolean operator to return true/false if creation succeeded/failed
return(*rf);
}
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){};
};
////////////////////////////////////////////
// Two-Wire RGB DotStars //
////////////////////////////////////////////
class Dot {
public:
struct Color {
union{
struct {
uint8_t red:8;
uint8_t green:8;
uint8_t blue:8;
uint8_t drive:5;
uint8_t flags:3;
};
uint32_t val;
};
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;
this->blue=b;
this->drive=driveLevel;
this->flags=7;
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]
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->drive=drivePercent*0.315;
this->flags=7;
return(*this);
}
bool operator==(const Color& color){
return(val==color.val);
}
bool operator!=(const Color& color){
return(val!=color.val);
}
Color operator+(const Color& color){
Color newColor;
newColor.blue=blue+color.blue;
newColor.red=red+color.red;
newColor.green=green+color.green;
return(newColor);
}
Color& operator+=(const Color& color){
red+=color.red;
blue+=color.blue;
green+=color.green;
return(*this);
}
Color operator-(const Color& color){
Color newColor;
newColor.blue=blue-color.blue;
newColor.red=red-color.red;
newColor.green=green-color.green;
return(newColor);
}
Color& operator-=(const Color& color){
red-=color.red;
blue-=color.blue;
green-=color.green;
return(*this);
}
};
private:
uint32_t dataMask;
uint32_t clockMask;
volatile uint32_t *dataSetReg;
volatile uint32_t *dataClearReg;
volatile uint32_t *clockSetReg;
volatile uint32_t *clockClearReg;
public:
Dot(uint8_t dataPin, uint8_t clockPin); // creates addressable two-wire RGB LED connected to dataPin and clockPin (such as the DotStar SK9822 or APA102)
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 driveLevel=31){return(Color().RGB(r,g,b,driveLevel));} // an alternative method for returning an RGB Color
static Color HSV(float h, float s, float v, double drivePercent=100){return(Color().HSV(h,s,v,drivePercent));} // an alternative method for returning an HSV Color
};
////////////////////////////////////////////

View File

@@ -0,0 +1,275 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 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.
*
********************************************************************************/
#include "PwmPin.h"
///////////////////
LedC::LedC(uint8_t pin, uint16_t freq, boolean invert){
if(freq==0)
freq=DEFAULT_PWM_FREQ;
for(int nMode=0;nMode<LEDC_SPEED_MODE_MAX;nMode++){
for(int nChannel=0;nChannel<LEDC_CHANNEL_MAX;nChannel++){
for(int nTimer=0;nTimer<LEDC_TIMER_MAX;nTimer++){
if(!channelList[nChannel][nMode]){
if(!timerList[nTimer][nMode]){ // if this timer slot is free, use it
timerList[nTimer][nMode]=new ledc_timer_config_t; // create new timer instance
timerList[nTimer][nMode]->speed_mode=(ledc_mode_t)nMode;
timerList[nTimer][nMode]->timer_num=(ledc_timer_t)nTimer;
timerList[nTimer][nMode]->freq_hz=freq;
timerList[nTimer][nMode]->clk_cfg=LEDC_USE_APB_CLK;
int res=LEDC_TIMER_BIT_MAX-1; // find the maximum possible resolution
while(getApbFrequency()/(freq*pow(2,res))<1)
res--;
timerList[nTimer][nMode]->duty_resolution=(ledc_timer_bit_t)res;
if(ledc_timer_config(timerList[nTimer][nMode])!=0){
ESP_LOGE(PWM_TAG,"Frequency=%d Hz is out of allowed range ---",freq);
delete timerList[nTimer][nMode];
timerList[nTimer][nMode]=NULL;
return;
}
}
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]->speed_mode=(ledc_mode_t)nMode;
channelList[nChannel][nMode]->channel=(ledc_channel_t)nChannel;
channelList[nChannel][nMode]->timer_sel=(ledc_timer_t)nTimer;
channelList[nChannel][nMode]->intr_type=LEDC_INTR_DISABLE;
channelList[nChannel][nMode]->flags.output_invert=invert;
channelList[nChannel][nMode]->hpoint=0;
channelList[nChannel][nMode]->gpio_num=pin;
timer=timerList[nTimer][nMode];
channel=channelList[nChannel][nMode];
return;
}
}
}
}
}
}
///////////////////
LedPin::LedPin(uint8_t pin, float level, uint16_t freq, boolean invert) : LedC(pin, freq, invert){
if(!channel){
ESP_LOGE(PWM_TAG,"Can't create LedPin(%d) - no open PWM channels and/or Timers",pin);
return;
}
else
ESP_LOGI(PWM_TAG,"LedPin=%d: mode=%d, channel=%d, timer=%d, freq=%d Hz, resolution=%d bits %s",
channel->gpio_num,
channel->speed_mode,
channel->channel,
channel->timer_sel,
timer->freq_hz,
timer->duty_resolution,
channel->flags.output_invert?"(inverted)":""
);
ledc_fade_func_install(0);
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);
set(level);
}
///////////////////
void LedPin::set(float level){
if(!channel)
return;
if(level>100)
level=100;
float d=level*(pow(2,(int)timer->duty_resolution)-1)/100.0;
channel->duty=d;
ledc_channel_config(channel);
}
///////////////////
int LedPin::fade(float level, uint32_t fadeTime, int fadeType){
if(!channel)
return(1);
if(fadeState==FADING) // fading already in progress
return(1); // return error
if(level>100)
level=100;
float d=level*(pow(2,(int)timer->duty_resolution)-1)/100.0;
if(fadeType==PROPORTIONAL)
fadeTime*=fabs((float)ledc_get_duty(channel->speed_mode,channel->channel)-d)/(float)(pow(2,(int)timer->duty_resolution)-1);
fadeState=FADING;
ledc_set_fade_time_and_start(channel->speed_mode,channel->channel,d,fadeTime,LEDC_FADE_NO_WAIT);
return(0);
}
///////////////////
int LedPin::fadeStatus(){
if(fadeState==COMPLETED){
fadeState=NOT_FADING;
return(COMPLETED);
}
return(fadeState);
}
///////////////////
bool IRAM_ATTR LedPin::fadeCallback(const ledc_cb_param_t *param, void *arg){
((LedPin *)arg)->fadeState=COMPLETED;
return(false);
}
///////////////////
void LedPin::HSVtoRGB(float h, float s, float v, float *r, float *g, float *b ){
// The algorithm below was provided on the web at https://www.cs.rit.edu/~ncs/color/t_convert.html
// h = [0,360]
// s = [0,1]
// v = [0,1]
int i;
float f, p, q, t;
if( s == 0 ){
*r = *g = *b = v;
return;
}
h /= 60;
i = floor( h ) ;
f = h - i;
p = v * ( 1 - s );
q = v * ( 1 - s * f );
t = v * ( 1 - s * ( 1 - f ) );
switch( i % 6 ) {
case 0:
*r = v;
*g = t;
*b = p;
break;
case 1:
*r = q;
*g = v;
*b = p;
break;
case 2:
*r = p;
*g = v;
*b = t;
break;
case 3:
*r = p;
*g = q;
*b = v;
break;
case 4:
*r = t;
*g = p;
*b = v;
break;
case 5:
*r = v;
*g = p;
*b = q;
break;
}
}
////////////////////////////
ServoPin::ServoPin(uint8_t pin, double initDegrees, uint16_t minMicros, uint16_t maxMicros, double minDegrees, double maxDegrees) : LedC(pin, 50){
if(!channel)
ESP_LOGE(PWM_TAG,"Can't create ServoPin(%d) - no open PWM channels and/or Timers",pin);
else
ESP_LOGI(PWM_TAG,"ServoPin=%d: mode=%d channel=%d, timer=%d, freq=%d Hz, resolution=%d bits",
channel->gpio_num,
channel->speed_mode,
channel->channel,
channel->timer_sel,
timer->freq_hz,
timer->duty_resolution
);
this->minMicros=minMicros;
this->maxMicros=maxMicros;
this->minDegrees=minDegrees;
microsPerDegree=(double)(maxMicros-minMicros)/(maxDegrees-minDegrees);
set(initDegrees);
}
///////////////////
void ServoPin::set(double degrees){
if(!channel)
return;
if(!isnan(degrees)){
double usec=(degrees-minDegrees)*microsPerDegree+minMicros;
if(usec<minMicros)
usec=minMicros;
else if(usec>maxMicros)
usec=maxMicros;
usec*=timer->freq_hz/1e6*(pow(2,(int)timer->duty_resolution)-1);
channel->duty=usec;
} else {
channel->duty=0;
}
ledc_channel_config(channel);
}
////////////////////////////
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]={};

View File

@@ -0,0 +1,121 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 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.
*
********************************************************************************/
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// ----- PWM Pin Control -----
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Wrappers around the ESP-IDF ledc library to control PWM-based devices:
//
// LedPin(pin) - controls a Dimmable LED on specified pin with frequency=5000 Hz
// - use set(level) to set brightness from 0-100%
//
// ServoPin(pin) - controls a Servo Motor on specified pin with frequency=50 Hz
// - use set(degrees) to set position to degrees
//
//
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma once
#include <Arduino.h>
#include <driver/ledc.h>
#include "Blinker.h"
[[maybe_unused]] static const char* PWM_TAG = "PwmPin";
#define DEFAULT_PWM_FREQ 5000
/////////////////////////////////////
class LedC {
protected:
static ledc_channel_config_t *channelList[LEDC_CHANNEL_MAX][LEDC_SPEED_MODE_MAX];
static ledc_timer_config_t *timerList[LEDC_TIMER_MAX][LEDC_SPEED_MODE_MAX];
ledc_channel_config_t *channel=NULL;
ledc_timer_config_t *timer;
LedC(uint8_t pin, uint16_t freq, boolean invert=false);
public:
int getPin(){return(channel?channel->gpio_num:-1);} // returns the pin number
operator bool(){ // override boolean operator to return true/false if creation succeeded/failed
return(channel);
}
};
/////////////////////////////////////
class LedPin : public LedC {
public:
enum {
NOT_FADING,
COMPLETED,
FADING
};
enum {
ABSOLUTE,
PROPORTIONAL
};
private:
int fadeState=NOT_FADING;
static bool fadeCallback(const ledc_cb_param_t *param, void *arg);
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
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
static void HSVtoRGB(float h, float s, float v, float *r, float *g, float *b ); // converts Hue/Saturation/Brightness to R/G/B
};
////////////////////////////////
// ServoPin //
////////////////////////////////
class ServoPin : public LedC {
uint16_t minMicros;
uint16_t maxMicros;
double minDegrees;
double microsPerDegree;
public:
ServoPin(uint8_t pin, double initDegrees, uint16_t minMicros, uint16_t maxMicros, double minDegrees, double maxDegrees);
ServoPin(uint8_t pin, double initDegrees=0) : ServoPin(pin,initDegrees,1000,2000,-90,90) {};
void set(double degrees); // sets the Servo to degrees, where degrees is bounded by [minDegrees,maxDegrees]
};

View File

@@ -0,0 +1,164 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 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.
*
********************************************************************************/
#include "RFControl.h"
///////////////////
RFControl::RFControl(uint8_t pin, boolean refClock, boolean installDriver){
#if defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3)
if(nChannels==RMT_CHANNEL_MAX/2){
#else
if(nChannels==RMT_CHANNEL_MAX){
#endif
ESP_LOGE(RFControl_TAG,"Can't create RFControl(%d) - no open channels",pin);
return;
}
config=new rmt_config_t;
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++;
}
///////////////////
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
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
if(!config || 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
}
///////////////////
void RFControl::clear(){
data.clear();
lowWord=true;
}
///////////////////
void RFControl::add(uint32_t onTime, uint32_t offTime){
phase(onTime,HIGH);
phase(offTime,LOW);
}
///////////////////
void RFControl::phase(uint32_t nTicks, uint8_t phase){
while(nTicks>0){ // create as many repeated phases as needed to accomodate duration of nTicks
uint32_t ticks=nTicks>0x7FFF?0x7FFF:nTicks;
nTicks-=ticks;
if(lowWord)
data.push_back(ticks | (phase?(1<<15):0));
else
data.back()|=ticks<<16 | (phase?(1<<31):0);
lowWord=!lowWord;
}
}
///////////////////
void RFControl::enableCarrier(uint32_t freq, float duty){
if(duty<0)
duty=0;
if(duty>1)
duty=1;
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);
}
}
///////////////////
uint8_t RFControl::nChannels=0;

View File

@@ -0,0 +1,78 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 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.
*
********************************************************************************/
////////////////////////////////////
// RF Control Module //
////////////////////////////////////
#pragma once
#include <Arduino.h>
#include <soc/rmt_reg.h>
#include "driver/rmt.h"
#include <vector>
[[maybe_unused]] static const char* RFControl_TAG = "RFControl";
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)
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
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(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
void add(uint32_t onTime, uint32_t offTime); // adds pulse of onTime ticks HIGH followed by offTime ticks LOW
void phase(uint32_t nTicks, uint8_t phase); // adds either a HIGH phase or LOW phase lasting numTicks ticks
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);
}
};
// Helper macro for creating your own storage of uint32_t data array elements - used with first variation of start() above
#define RF_PULSE(highTicks,lowTicks) (1 << 15 | uint32_t(highTicks) | uint32_t(lowTicks) << 16)

View File

@@ -0,0 +1,205 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2023-2024 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.
*
********************************************************************************/
#include "StepperControl.h"
//////////////////////////
StepperControl::StepperControl(uint32_t priority, uint32_t cpu){
upLinkQueue = xQueueCreate(1,sizeof(upLink_t));
downLinkQueue = xQueueCreate(1,sizeof(downLink_t));
xTaskCreateUniversal(motorTask, "motorTaskHandle", 8096, this, priority, &motorTaskHandle, cpu);
ESP_LOGI(STEPPER_TAG,"motor task started with priority %d on cpu %d",priority,cpu);
}
//////////////////////////
StepperControl *StepperControl::setAccel(float accelSize, float accelSteps){
if(accelSize<0.0){
ESP_LOGE(STEPPER_TAG,"accelSize cannot be less than 0.0");
return(this);
}
if(accelSteps<1.0){
ESP_LOGE(STEPPER_TAG,"accelSteps cannot be less than 1.0");
return(this);
}
this->accelSize=accelSize;
this->accelSteps=accelSteps;
return(this);
}
//////////////////////////
void StepperControl::move(int nSteps, uint32_t msDelay, endAction_t endAction){
if(msDelay==0){
ESP_LOGE(STEPPER_TAG,"msDelay must be greater than zero");
return;
}
upLink_t upLinkData = { .nSteps=nSteps, .msDelay=msDelay, .action=MOVE, .endAction=endAction };
xQueueOverwrite(upLinkQueue,&upLinkData);
waitForAck();
}
//////////////////////////
void StepperControl::moveTo(int nPosition, uint32_t msDelay, endAction_t endAction){
if(msDelay==0){
ESP_LOGE(STEPPER_TAG,"msDelay must be greater than zero");
return;
}
upLink_t upLinkData = { .nSteps=nPosition, .msDelay=msDelay, .action=MOVETO, .endAction=endAction };
xQueueOverwrite(upLinkQueue,&upLinkData);
waitForAck();
}
//////////////////////////
int StepperControl::stepsRemaining(){
xQueuePeek(downLinkQueue,&downLinkData,0);
return(downLinkData.stepsRemaining);
}
//////////////////////////
int StepperControl::position(){
xQueuePeek(downLinkQueue,&downLinkData,0);
return(downLinkData.position);
}
//////////////////////////
void StepperControl::setPosition(int pos){
if(!stepsRemaining()){
upLink_t upLinkData = { .nSteps=pos, .msDelay=10, .action=SET_POSITION, .endAction=NONE };
xQueueOverwrite(upLinkQueue,&upLinkData);
waitForAck();
} else {
ESP_LOGE(STEPPER_TAG,"can't set position while motor is running");
}
}
//////////////////////////
void StepperControl::waitForAck(){
downLinkData.ack=false;
while(downLinkData.ack==false)
xQueueReceive(downLinkQueue,&downLinkData,0);
};
//////////////////////////
StepperControl *StepperControl::brake(){
move(0,10,BRAKE);
while(stepsRemaining());
return(this);
}
//////////////////////////
StepperControl *StepperControl::disable(){
move(0,10,DISABLE);
while(stepsRemaining());
return(this);
}
//////////////////////////
StepperControl *StepperControl::enable(){
move(0,10);
while(stepsRemaining());
return(this);
}
//////////////////////////
void StepperControl::motorTask(void *args){
StepperControl *motor = (StepperControl *)args;
upLink_t upLinkData = { .nSteps=0, .msDelay=10, .action=MOVE, .endAction=NONE };
downLink_t downLinkData;
boolean running=false;
for(;;){
if(xQueueReceive(motor->upLinkQueue, &upLinkData, 0)){
switch(upLinkData.action){
case SET_POSITION:
downLinkData.position=upLinkData.nSteps;
break;
case MOVETO:
upLinkData.nSteps-=downLinkData.position;
[[fallthrough]];
case MOVE:
downLinkData.stepsRemaining=upLinkData.nSteps;
motor->onEnable();
break;
}
running=true;
downLinkData.ack=true;
}
uint32_t msDelay=upLinkData.msDelay;
if(running==false){
vTaskDelay(msDelay);
continue;
}
if(downLinkData.stepsRemaining!=0)
msDelay+=msDelay * motor->accelSize * (exp(-fabs(upLinkData.nSteps-downLinkData.stepsRemaining)/motor->accelSteps) + exp(-(fabs(downLinkData.stepsRemaining)-1.0)/motor->accelSteps));
ESP_LOGD(STEPPER_TAG,"Position: %d Steps Remaining: %d Delay=%d ms",downLinkData.position,downLinkData.stepsRemaining,(int)(msDelay));
int dStep=0;
if(downLinkData.stepsRemaining>0){
dStep=-1;
motor->onStep(1);
} else if(downLinkData.stepsRemaining<0){
dStep=1;
motor->onStep(0);
} else {
if(upLinkData.endAction==DISABLE)
motor->onDisable();
else if(upLinkData.endAction==BRAKE)
motor->onBrake();
running=false;
}
xQueueOverwrite(motor->downLinkQueue,&downLinkData);
downLinkData.stepsRemaining+=dStep;
downLinkData.position-=dStep;
downLinkData.ack=false;
vTaskDelay(msDelay);
}
}
//////////////////////////

View File

@@ -0,0 +1,115 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2023-2024 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 "PwmPin.h"
[[maybe_unused]] static const char* STEPPER_TAG = "StepperControl";
//////////////////////////
class StepperControl {
public:
enum {
FULL_STEP_ONE_PHASE=0,
FULL_STEP_TWO_PHASE=1,
HALF_STEP=2,
QUARTER_STEP=4,
EIGHTH_STEP=8
};
enum endAction_t {
NONE,
DISABLE,
BRAKE
};
enum action_t {
MOVE,
MOVETO,
SET_POSITION
};
private:
struct upLink_t {
int nSteps;
uint32_t msDelay;
action_t action;
endAction_t endAction;
};
struct downLink_t {
int stepsRemaining=0;
int position=0;
boolean ack=false;
};
float accelSteps=1;
float accelSize=0;
downLink_t downLinkData;
TaskHandle_t motorTaskHandle;
QueueHandle_t upLinkQueue;
QueueHandle_t downLinkQueue;
void waitForAck();
virtual void onStep(boolean direction)=0;
virtual void onEnable(){};
virtual void onDisable(){};
virtual void onBrake(){};
static void motorTask(void *args);
public:
StepperControl(uint32_t priority=1, uint32_t cpu=0);
virtual StepperControl *setStepType(int mode){return(this);};
StepperControl *setAccel(float accelSize, float accelSteps);
void move(int nSteps, uint32_t msDelay, endAction_t endAction=NONE);
void moveTo(int nPosition, uint32_t msDelay, endAction_t endAction=NONE);
int position();
void setPosition(int pos);
int stepsRemaining();
StepperControl *enable();
StepperControl *disable();
StepperControl *brake();
};
//////////////////////////
#include "Stepper_UNIPOLAR.h"
#include "Stepper_TB6612.h" // https://www.adafruit.com/product/2448
#include "Stepper_A3967.h" // https://www.sparkfun.com/products/12779
struct Stepper_ULN2003A : Stepper_UNIPOLAR {
Stepper_ULN2003A(int IN1, int IN2, int IN3, int IN4, std::pair<uint32_t, uint32_t> taskParams = {1,0}) : Stepper_UNIPOLAR(IN1,IN3,IN2,IN4,taskParams){}
};

View File

@@ -0,0 +1,118 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2023-2024 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
// Implementation of StepperControl for a Sparkfun A3967 EasyDriver Stepper Motor Driver
// Breakout Board (https://www.sparkfun.com/products/12779)
// This implementation uses the driver board's MS1, MS2, STEP, DIR, and ENABLE pins.
// Separate PWM pins on the ESP32 are NOT needed since the driver board contains its own
// waveform generation. Supported modes include FULL_STEP (double-phase only), HALF_STEP,
// QUARTER_STEP, and EIGHTH_STEP.
// The motor outputs can be enabled (current running through the coils) or
// disabled (no current / high impedence). The EasyDriver board does NOT support
// the short brake mode.
//////////////////////////
struct Stepper_A3967 : StepperControl {
int m1Pin;
int m2Pin;
int stepPin;
int dirPin;
int enablePin;
//////////////////////////
Stepper_A3967(int m1Pin, int m2Pin, int stepPin, int dirPin, int enablePin, std::pair<uint32_t, uint32_t> taskParams = {1,0}) : StepperControl(taskParams.first,taskParams.second){
this->m1Pin=m1Pin;
this->m2Pin=m2Pin;
this->stepPin=stepPin;
this->dirPin=dirPin;
this->enablePin=enablePin;
pinMode(m1Pin,OUTPUT);
pinMode(m2Pin,OUTPUT);
pinMode(stepPin,OUTPUT);
pinMode(dirPin,OUTPUT);
pinMode(enablePin,OUTPUT);
setStepType(FULL_STEP_TWO_PHASE);
}
//////////////////////////
void onStep(boolean direction) override {
digitalWrite(dirPin,direction);
digitalWrite(stepPin,HIGH);
digitalWrite(stepPin,LOW);
}
//////////////////////////
void onEnable() override {
digitalWrite(enablePin,0);
}
//////////////////////////
void onDisable() override {
digitalWrite(enablePin,1);
}
//////////////////////////
StepperControl *setStepType(int mode) override {
switch(mode){
case FULL_STEP_TWO_PHASE:
digitalWrite(m1Pin,LOW);
digitalWrite(m2Pin,LOW);
break;
case HALF_STEP:
digitalWrite(m1Pin,HIGH);
digitalWrite(m2Pin,LOW);
break;
case QUARTER_STEP:
digitalWrite(m1Pin,LOW);
digitalWrite(m2Pin,HIGH);
break;
case EIGHTH_STEP:
digitalWrite(m1Pin,HIGH);
digitalWrite(m2Pin,HIGH);
break;
default:
ESP_LOGE(STEPPER_TAG,"Unknown StepType=%d",mode);
}
return(this);
}
};
//////////////////////////

View File

@@ -0,0 +1,179 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2023-2024 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
// Implementation of StepperControl for an Adafruit TB6612 1.2A DC/Stepper Motor Driver
// Breakout Board (https://www.adafruit.com/product/2448)
// This implementation supports two constructors reflecting implementations both with and
// without the use of PWM pins. The first operates the driver board using only its
// four digital control pins: AIN1, AIN2, BIN1, BIN2. In this configuration the
// PWMA, PWMB, and STBY pins on the driver board should be directly connected to Vcc.
// The second configuration uses the four digital control pins (AIN1, AIN2, BIN1, and BIN2)
// as well as the PWMA and PWMB pins. In this configuration only the STBY pin on the
// driver board should be directly connected to Vcc.
// The first configuration supports both single-phase and double-phase FULL_STEP modes,
// as well as a HALF_STEP mode. The second configuration also includes QUARTER_STEP
// and EIGHTH_STEP modes, made possible by the use of the PWM pins to micro-step the motor.
// In either configuration the motor outputs can be enabled (current running through the coils)
// disabled (no current / high impedence) or set to a short brake.
//////////////////////////
struct Stepper_TB6612 : StepperControl {
int ain1, ain2, bin1, bin2;
uint8_t phase, nPhases;
double offset;
LedPin *pwmA=NULL, *pwmB;
//////////////////////////
Stepper_TB6612(int AIN1, int AIN2, int BIN1, int BIN2, std::pair<uint32_t, uint32_t> taskParams = {1,0}) : StepperControl(taskParams.first,taskParams.second){
ain1=AIN1;
ain2=AIN2;
bin1=BIN1;
bin2=BIN2;
pinMode(ain1,OUTPUT);
pinMode(ain2,OUTPUT);
pinMode(bin1,OUTPUT);
pinMode(bin2,OUTPUT);
setStepType(FULL_STEP_TWO_PHASE);
}
//////////////////////////
Stepper_TB6612(int AIN1, int AIN2, int BIN1, int BIN2, int PWMA, int PWMB, std::pair<uint32_t, uint32_t> taskParams = {1,0}) : Stepper_TB6612(AIN1,AIN2,BIN1,BIN2,taskParams){
pwmA=new LedPin(PWMA,0,50000);
pwmB=new LedPin(PWMB,0,50000);
}
//////////////////////////
void onEnable() override {
setPins();
}
//////////////////////////
void onDisable() override {
digitalWrite(ain1,0);
digitalWrite(ain2,0);
digitalWrite(bin1,0);
digitalWrite(bin2,0);
}
//////////////////////////
void onBrake() override {
digitalWrite(ain1,1);
digitalWrite(ain2,1);
digitalWrite(bin1,1);
digitalWrite(bin2,1);
}
//////////////////////////
void onStep(boolean direction) override {
if(direction)
phase=(phase+1)%nPhases;
else
phase=(phase+nPhases-1)%nPhases;
setPins();
}
//////////////////////////
void setPins(){
float levelA=cos(phase*TWO_PI/nPhases+offset)*100.0;
float levelB=sin(phase*TWO_PI/nPhases+offset)*100.0;
digitalWrite(ain1,levelA>0.01);
digitalWrite(ain2,levelA<-0.01);
digitalWrite(bin1,levelB>0.01);
digitalWrite(bin2,levelB<-0.01);
if(pwmA){
pwmA->set(fabs(levelA));
pwmB->set(fabs(levelB));
}
}
//////////////////////////
StepperControl *setStepType(int mode) override {
switch(mode){
case FULL_STEP_ONE_PHASE:
phase=0;
nPhases=4;
offset=0;
break;
case FULL_STEP_TWO_PHASE:
phase=0;
nPhases=4;
offset=TWO_PI/8.0;
break;
case HALF_STEP:
phase=0;
nPhases=8;
offset=0;
break;
case QUARTER_STEP:
if(!pwmA){
ESP_LOGE(STEPPER_TAG,"QUARTER_STEP requires PWM pins");
return(this);
}
phase=0;
nPhases=16;
offset=0;
break;
case EIGHTH_STEP:
if(!pwmA){
ESP_LOGE(STEPPER_TAG,"EIGHTH_STEP requires PWM pins");
return(this);
}
phase=0;
nPhases=32;
offset=0;
break;
default:
ESP_LOGE(STEPPER_TAG,"Unknown StepType=%d",mode);
}
return(this);
}
};
//////////////////////////

View File

@@ -0,0 +1,169 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2024 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
// Implementation of a basic 4-wire controller for a center-tapped Unipolar Stepper Motor
// with two coils (Coil 1 and Coil 2) each having two driving inputs (A and B).
//
// Requires 4 pins on the ESP32 connected to an appropriate driver circuit, such as a set
// of Darlington transitors, that are in turn connected to each of the 4 Stepper Motor wires:
//
// * Coil 1, Input A (1A)
// * Coil 1, Input B (1B)
// * Coil 2, Input A (2A)
// * Coil 2, Input B (2B)
//
// When any of the pins are HIGH, the driver circuit should cause current to flow in the corresponding
// half of the coil. When the pin is set LOW, the driver circuit should stop the flow of current through
// that half of the coil. Supported modes and driving logic are as follows:
//
// FULL_STEP_ONE_PHASE (4 steps, where current flows only through ONE of the phases of ONE of the coils at each step):
//
// 1A 2A 1B 2B
// Step 1: HI -- -- --
// Step 2: -- HI -- --
// Step 3: -- -- HI --
// Step 4: -- -- -- HI
//
// FULL_STEP_TWO_PHASE (4 steps, where current flows through ONE of the phases of EACH of the coils at each step):
//
// 1A 2A 1B 2B
// Step 1: HI HI -- --
// Step 2: -- HI HI --
// Step 3: -- -- HI HI
// Step 4: HI -- -- HI
//
// HALF_STEP (8 steps that alternate between the 4 steps of the FULL_STEP modes above):
//
// 1A 2A 1B 2B
// Step 1: HI -- -- --
// Step 2: HI HI -- --
// Step 3: -- HI -- --
// Step 4: -- HI HI --
// Step 5: -- -- HI --
// Step 6: -- -- HI HI
// Step 7: -- -- -- HI
// Step 8: HI -- -- HI
// NOTE ORDER OF CONSTRUCTOR PARAMETERS: First the two pins that drive the A and B side of Coil 1,
// followed by the two pints that drive the A and B side of Coil 2.
// It does not matter which coil is defined as 1 or 2, nor which side is called A or B, as long as
// the first two parameters are for one of the coils and the second two are for the other coil.
// Note: This driver supports enabling and disabling all current flow, but does NOT support a short brake.
//////////////////////////
struct Stepper_UNIPOLAR : StepperControl {
int c1A, c1B, c2A, c2B;
uint8_t phase, nPhases;
double offset;
//////////////////////////
Stepper_UNIPOLAR(int coil1A, int coil1B, int coil2A, int coil2B, std::pair<uint32_t, uint32_t> taskParams = {1,0}) : StepperControl(taskParams.first,taskParams.second) {
c1A=coil1A;
c1B=coil1B;
c2A=coil2A;
c2B=coil2B;
pinMode(c1A,OUTPUT);
pinMode(c1B,OUTPUT);
pinMode(c2A,OUTPUT);
pinMode(c2B,OUTPUT);
setStepType(FULL_STEP_ONE_PHASE);
}
//////////////////////////
void onEnable() override {
setPins();
}
//////////////////////////
void onDisable() override {
digitalWrite(c1A,0);
digitalWrite(c1B,0);
digitalWrite(c2A,0);
digitalWrite(c2B,0);
}
//////////////////////////
void onStep(boolean direction) override {
if(direction)
phase=(phase+1)%nPhases;
else
phase=(phase+nPhases-1)%nPhases;
setPins();
}
//////////////////////////
void setPins(){
float levelA=cos(phase*TWO_PI/nPhases+offset)*100.0;
float levelB=sin(phase*TWO_PI/nPhases+offset)*100.0;
digitalWrite(c1A,levelA>0.01);
digitalWrite(c1B,levelA<-0.01);
digitalWrite(c2A,levelB>0.01);
digitalWrite(c2B,levelB<-0.01);
}
//////////////////////////
StepperControl *setStepType(int mode) override {
switch(mode){
case FULL_STEP_ONE_PHASE:
phase=0;
nPhases=4;
offset=0;
break;
case FULL_STEP_TWO_PHASE:
phase=0;
nPhases=4;
offset=TWO_PI/8.0;
break;
case HALF_STEP:
phase=0;
nPhases=8;
offset=0;
break;
default:
ESP_LOGE(STEPPER_TAG,"Unknown StepType=%d",mode);
}
return(this);
}
};

View File

@@ -0,0 +1,64 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 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.
*
********************************************************************************/
#include "PwmPin.h"
ServoPin servo(21,0,500,2200,-60,60);
void setup() {
Serial.begin(115200);
delay(1000);
Serial.print("\n\nReady\n\n");
for(int count=0;count<3;count++){
for(int i=-60;i<61;i++){
servo.set(i);
delay(10);
}
for(int i=60;i>-61;i--){
servo.set(i);
delay(10);
}
}
delay(5000);
servo.set(NAN);
delay(10000);
servo.set(0);
}
//////////////////////////////////////
void loop(){
}

View File

@@ -0,0 +1,67 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2024 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
//////////////////////////////////////////////////////
// HomeSpan Version //
#define HS_MAJOR 1
#define HS_MINOR 9
#define HS_PATCH 1
//////////////////////////////////////////////////////
#ifndef ARDUINO_ARCH_ESP32
#error ERROR: HOMESPAN IS ONLY AVAILABLE FOR ESP32 MICROCONTROLLERS!
#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
#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 VERSION(major,minor,patch) major*10000+minor*100+patch
#ifndef REQUIRED
#define REQUIRED 0
#endif
#if (REQUIRED>VERSION(HS_MAJOR,HS_MINOR,HS_PATCH))
#error ERROR: THIS SKETCH REQUIRES A LATER VERSION OF THE HOMESPAN LIBRARY
#include <FATAL_ERROR>
#endif
#define ARDUINO_ESP_VERSION STRINGIFY(ESP_ARDUINO_VERSION_MAJOR) "." STRINGIFY(ESP_ARDUINO_VERSION_MINOR) "." STRINGIFY(ESP_ARDUINO_VERSION_PATCH)