HomeSpan/src/HomeSpan.h

449 lines
23 KiB
C++

/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2021 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 ARDUINO_ARCH_ESP32
#error ERROR: HOMESPAN IS ONLY AVAILABLE FOR ESP32 MICROCONTROLLERS!
#endif
#include <Arduino.h>
#include <unordered_map>
#include "Settings.h"
#include "Utils.h"
#include "Network.h"
#include "HAPConstants.h"
#include "HapQR.h"
#include "Characteristics.h"
using std::vector;
using std::unordered_map;
enum {
GET_AID=1,
GET_META=2,
GET_PERMS=4,
GET_TYPE=8,
GET_EV=16,
GET_DESC=32,
GET_NV=64,
GET_ALL=255
};
///////////////////////////////
// Forward-Declarations
struct Span;
struct SpanAccessory;
struct SpanService;
struct SpanCharacteristic;
struct SpanRange;
struct SpanBuf;
struct SpanButton;
extern Span homeSpan;
///////////////////////////////
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
int iid=0; // updated iid
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 Span{
const char *displayName; // display name for this device - broadcast as part of Bonjour MDNS
const 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
const 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; // full host name of this device - constructed from hostNameBase and 6-byte AccessoryID
const 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
int nFatalErrors=0; // number of fatal errors in user-defined configuration
String configLog; // log of configuration process, including any errors
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
boolean connected=false; // WiFi connection status
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
uint8_t statusPin=DEFAULT_STATUS_PIN; // pin for status LED
uint8_t controlPin=DEFAULT_CONTROL_PIN; // pin for Control Pushbutton
uint8_t logLevel=DEFAULT_LOG_LEVEL; // level for writing out log messages to serial monitor
uint8_t maxConnections=DEFAULT_MAX_CONNECTIONS; // number of simultaneous HAP connections
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
boolean otaEnabled=false; // enables Over-the-Air ("OTA") updates
char otaPwd[33]; // MD5 Hash of OTA password, represented as a string of hexidecimal characters
boolean otaAuth; // OTA requires password when set to true
void (*wifiCallback)()=NULL; // optional callback function to invoke once WiFi connectivity is established
WiFiServer *hapServer; // pointer to the HAP Server connection
Blinker statusLED; // indicates HomeSpan status
PushButton controlButton; // controls HomeSpan configuration and resets
Network network; // configures WiFi and Setup Code via either serial monitor or temporary Access Point
SpanConfig hapConfig; // track configuration changes to the HAP Accessory database; used to increment the configuration number (c#) when changes found
vector<SpanAccessory *> Accessories; // vector of pointers to all Accessories
vector<SpanService *> Loops; // vector of pointer to all Services that have over-ridden loop() methods
vector<SpanBuf> Notifications; // vector of SpanBuf objects that store info for Characteristics that are updated with setVal() and require a Notification Event
vector<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)
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(); // poll HAP Clients and process any new HAP requests
int getFreeSlot(); // returns free HAPClient slot number. HAPClients slot keep track of each active HAPClient connection
void checkConnect(); // check WiFi connection; connect if needed
void commandMode(); // allows user to control and reset HomeSpan settings with the control button
void processSerialCommand(const char *c); // process command 'c' (typically from readSerial, though can be called with any 'c')
int sprintfAttributes(char *cBuf); // prints Attributes JSON database into buf, unless buf=NULL; return number of characters printed, excluding null terminator, even if buf=NULL
void prettyPrint(char *buf, int nsp=2); // print arbitrary JSON from buf to serial monitor, formatted with indentions of 'nsp' spaces
SpanCharacteristic *find(uint32_t aid, int 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
int sprintfAttributes(SpanBuf *pObj, int nObj, char *cBuf); // prints SpanBuf object into buf, unless buf=NULL; return number of characters printed, excluding null terminator, even if buf=NULL
int sprintfAttributes(char **ids, int numIDs, int flags, char *cBuf); // prints accessory.characteristic ids into buf, unless buf=NULL; return number of characters printed, excluding null terminator, even if buf=NULL
void clearNotify(int slotNum); // set ev notification flags for connection 'slotNum' to false across all characteristics
int sprintfNotify(SpanBuf *pObj, int nObj, char *cBuf, int conNum); // prints notification JSON into buf based on SpanBuf objects and specified connection number
void setControlPin(uint8_t pin){controlPin=pin;} // sets Control Pin
void setStatusPin(uint8_t pin){statusPin=pin;} // sets Status Pin
int getStatusPin(){return(statusPin);} // gets Status Pin
void setApSSID(const char *ssid){network.apSSID=ssid;} // sets Access Point SSID
void setApPassword(const char *pwd){network.apPassword=pwd;} // sets Access Point Password
void setApTimeout(uint16_t nSec){network.lifetime=nSec*1000;} // sets Access Point Timeout (seconds)
void setCommandTimeout(uint16_t nSec){comModeLife=nSec*1000;} // sets Command Mode Timeout (seconds)
void setLogLevel(uint8_t level){logLevel=level;} // sets Log Level for log messages (0=baseline, 1=intermediate, 2=all)
void setMaxConnections(uint8_t nCon){maxConnections=nCon;} // sets maximum number of simultaneous HAP connections (HAP requires devices support at least 8)
void setHostNameSuffix(const char *suffix){hostNameSuffix=suffix;} // sets the hostName suffix to be used instead of the 6-byte AccessoryID
void setPortNum(uint16_t port){tcpPortNum=port;} // sets the TCP port number to use for communications between HomeKit and HomeSpan
void setQRID(const char *id); // sets the Setup ID for optional pairing with a QR Code
void enableOTA(boolean auth=true){otaEnabled=true;otaAuth=auth;} // enables Over-the-Air updates, with (auth=true) or without (auth=false) authorization password
void setSketchVersion(const char *sVer){sketchVersion=sVer;} // set optional sketch version number
const char *getSketchVersion(){return sketchVersion;} // get sketch version number
void setWifiCallback(void (*f)()){wifiCallback=f;} // sets an optional user-defined function to call once WiFi connectivity is established
};
///////////////////////////////
struct SpanAccessory{
uint32_t aid=0; // Accessory Instance ID (HAP Table 6-1)
int iidCount=0; // running count of iid to use for Services and Characteristics associated with this Accessory
vector<SpanService *> Services; // vector of pointers to all Services in this Accessory
SpanAccessory(uint32_t aid=0);
int sprintfAttributes(char *cBuf); // prints Accessory JSON database into buf, unless buf=NULL; return number of characters printed, excluding null terminator, even if buf=NULL
void validate(); // error-checks Accessory
};
///////////////////////////////
struct SpanService{
int 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 *> Characteristics; // vector of pointers to all Characteristics in this Service
vector<HapChar *> req; // vector of pointers to all required HAP Characteristic Types for this Service
vector<HapChar *> opt; // vector of pointers to all optional HAP Characteristic Types for this Service
vector<SpanService *> linkedServices; // vector of pointers to any optional linked Services
SpanService(const char *type, const char *hapName);
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
int sprintfAttributes(char *cBuf); // prints Service JSON records into buf; return number of characters printed, excluding null terminator
void validate(); // error-checks 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 and can be 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
};
///////////////////////////////
struct SpanCharacteristic{
union UVal {
boolean BOOL;
uint8_t UINT8;
uint16_t UINT16;
uint32_t UINT32;
uint64_t UINT64;
int32_t INT;
double FLOAT;
const char *STRING;
};
int iid=0; // Instance ID (HAP Table 6-3)
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)
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 indiates whether Range is static and cannot be changed with setRange()
boolean customRange=false; // Flag for custom ranges
boolean *ev; // Characteristic Event Notify Enable (per-connection)
uint32_t aid=0; // Accessory ID - passed through from Service containing this Characteristic
boolean isUpdated=false; // set to true when new value has been requested by PUT /characteristic
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
SpanCharacteristic(HapChar *hapChar); // contructor
int sprintfAttributes(char *cBuf, int flags); // prints Characteristic JSON records into buf, according to flags mask; return number of characters printed, excluding null terminator
StatusCode loadUpdate(char *val, char *ev); // load updated val/ev from PUT /characteristic JSON request. Return intiial HAP status code (checks to see if characteristic is found, is writable, etc.)
boolean updated(){return(isUpdated);} // returns isUpdated
unsigned long timeVal(); // returns time elapsed (in millis) since value was last updated
String uvPrint(UVal &u){
char c[64];
switch(format){
case FORMAT::BOOL:
return(String(u.BOOL));
case FORMAT::INT:
return(String(u.INT));
case FORMAT::UINT8:
return(String(u.UINT8));
case FORMAT::UINT16:
return(String(u.UINT16));
case FORMAT::UINT32:
return(String(u.UINT32));
case FORMAT::UINT64:
sprintf(c,"%llu",u.UINT64);
return(String(c));
case FORMAT::FLOAT:
sprintf(c,"%llg",u.FLOAT);
return(String(c));
case FORMAT::STRING:
sprintf(c,"\"%s\"",u.STRING);
return(String(c));
} // switch
} // str()
void uvSet(UVal &u, const char *val){
u.STRING=val;
}
template <typename T> void uvSet(UVal &u, T val){
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;
} // switch
} // set()
template <class T> T uvGet(UVal &u){
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);
case FORMAT::STRING:
Serial.print("\n*** WARNING: Can't use getVal() or getNewVal() with string Characteristics.\n\n");
return(0);
}
} // get()
template <typename A, typename B, typename S=int> SpanCharacteristic *setRange(A min, B max, S step=0){
char c[256];
homeSpan.configLog+=String("------>Set Range for ") + String(hapName) + "-" + String(iid);
if(customRange){
sprintf(c," *** ERROR! Range already set for this Characteristic! ***\n");
homeSpan.nFatalErrors++;
} else
if(staticRange){
sprintf(c," *** ERROR! Can't change range for this Characteristic! ***\n");
homeSpan.nFatalErrors++;
} else {
uvSet(minValue,min);
uvSet(maxValue,max);
uvSet(stepValue,step);
customRange=true;
if(uvGet<double>(stepValue)>0)
sprintf(c,": %s/%s/%s\n",uvPrint(minValue),uvPrint(maxValue),uvPrint(stepValue));
else
sprintf(c,": %s/%s\n",uvPrint(minValue),uvPrint(maxValue));
}
homeSpan.configLog+=c;
return(this);
} // setRange()
template <typename T, typename A=boolean, typename B=boolean> void init(T val, A min=0, B max=1){
uvSet(value,val);
uvSet(newValue,val);
uvSet(minValue,min);
uvSet(maxValue,max);
uvSet(stepValue,0);
} // init()
template <class T=int> T getVal(){
return(uvGet<T>(value));
}
template <class T=int> T getNewVal(){
return(uvGet<T>(newValue));
}
template <typename T> void setVal(T val){
if(format==STRING){
Serial.printf("\n*** WARNING: Attempt to update Characteristic::%s(\"%s\") with setVal() ignored. Can't update STRING Characteristics once they are initialized!\n\n",hapName,value.STRING);
return;
}
if(val < uvGet<T>(minValue) || val > uvGet<T>(maxValue)){
Serial.printf("\n*** WARNING: Attempt to update Characteristic::%s with setVal(%llg) is out of range [%llg,%llg]. This may cause device to become non-reponsive!\n\n",
hapName,(double)val,uvGet<double>(minValue),uvGet<double>(maxValue));
}
uvSet(value,val);
uvSet(newValue,val);
updateTime=homeSpan.snapTime;
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 sprintfNotify knows to consider this "update"
homeSpan.Notifications.push_back(sb); // store SpanBuf in Notifications vector
} // setVal()
};
///////////////////////////////
struct SpanRange{
SpanRange(int min, int max, int step);
};
///////////////////////////////
struct SpanButton{
enum {
SINGLE=0,
DOUBLE=1,
LONG=2
};
int pin; // pin number
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
PushButton *pushButton; // PushButton associated with this SpanButton
SpanButton(int pin, uint16_t longTime=2000, uint16_t singleTime=5, uint16_t doubleTime=200);
};
/////////////////////////////////////////////////
#include "Span.h"