Completed all SpanPoint code

Allows for bi-directional transmission between main HomeSpan device and one or more remote devices.

To Do:  Create and test with Temperature Sensor Example
This commit is contained in:
Gregg 2022-10-01 15:33:11 -05:00
parent b7317c3b5f
commit 23afdb3711
6 changed files with 201 additions and 301 deletions

View File

@ -1,71 +0,0 @@
#include "HomePeer.h"
#include <WiFi.h>
#include <esp_wifi.h>
#include <mbedtls/sha256.h>
#include <esp_now.h>
void SpanPeer::start(const char *macAddress, const char *password){
if(sscanf(macAddress,"%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",peerInfo.peer_addr,peerInfo.peer_addr+1,peerInfo.peer_addr+2,peerInfo.peer_addr+3,peerInfo.peer_addr+4,peerInfo.peer_addr+5)!=6){
Serial.printf("*** ERROR: Can't start HomeSpan NOW! Bad MAC Address '%s'\n\n",macAddress);
return;
}
statusQueue = xQueueCreate(1,sizeof(esp_now_send_status_t));
WiFi.mode(WIFI_AP_STA);
esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE);
esp_now_init();
esp_now_register_send_cb(onDataSent);
uint8_t lmk[32];
uint8_t mac[6];
mbedtls_sha256_ret((const unsigned char *)password,strlen(password),lmk,0);
esp_now_set_pmk(lmk+16);
peerInfo.channel=0;
peerInfo.ifidx=WIFI_IF_STA;
peerInfo.encrypt = true;
memcpy(peerInfo.lmk, lmk, 16);
esp_now_add_peer(&peerInfo);
esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE);
Serial.printf("Started HomePeer: MAC Address = %s HomeSpan Address = %X:%X:%X:%X:%X:%X\n",WiFi.macAddress().c_str(),
peerInfo.peer_addr[0],peerInfo.peer_addr[1],peerInfo.peer_addr[2],peerInfo.peer_addr[3],peerInfo.peer_addr[4],peerInfo.peer_addr[5]);
started=true;
}
boolean SpanPeer::send(uint8_t *data, size_t len){
if(!started){
Serial.printf("*** ERROR: Can't send data until HomePeer has been started.\n\n");
return(false);
}
esp_now_send_status_t status = ESP_NOW_SEND_FAIL;
for(int c=0;c<13;c++){
if((1<<channel) & channelMask){
for(int i=1;i<=3;i++){
Serial.printf("Sending on channel %d, attempt #%d\n",channel,i);
esp_now_send(peerInfo.peer_addr, data, len);
xQueueReceive(statusQueue, &status, pdMS_TO_TICKS(2000));
if(status==ESP_NOW_SEND_SUCCESS)
return(true);
delay(10);
}
}
channel++;
if(channel==14)
channel=1;
esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE);
}
return(false);
}
QueueHandle_t SpanPeer::statusQueue;
SpanPeer homePeer;

View File

@ -1,25 +0,0 @@
#include <Arduino.h>
#include <esp_now.h>
class SpanPeer {
esp_now_peer_info_t peerInfo;
boolean started=false;
static QueueHandle_t statusQueue;
int channel=1;
uint16_t channelMask=0x3FFE;
static void onDataSent(const uint8_t *mac, esp_now_send_status_t status) {
xQueueOverwrite( statusQueue, &status );
}
public:
void setChannelMask(uint16_t cm){channelMask = cm & 0x3FFE;}
void start(const char *macAddress, const char *password="HomeSpan");
boolean send(uint8_t *data, size_t len);
};
extern SpanPeer homePeer;

View File

@ -1,192 +0,0 @@
/*********************************************************************************
* 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.
*
********************************************************************************/
#include "HomeSpan.h"
#include <mbedtls/sha256.h>
#include <esp_wifi.h>
SpanPoint::SpanPoint(const char *macAddress, int sendSize, int receiveSize, int queueDepth){
if(sscanf(macAddress,"%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",peerInfo.peer_addr,peerInfo.peer_addr+1,peerInfo.peer_addr+2,peerInfo.peer_addr+3,peerInfo.peer_addr+4,peerInfo.peer_addr+5)!=6){
Serial.printf("\nFATAL ERROR! Can't create new SpanPoint(\"%s\") - Invalid MAC Address ***\n",macAddress);
Serial.printf("\n=== PROGRAM HALTED ===");
while(1);
}
if(sendSize<0 || sendSize>200 || receiveSize<0 || receiveSize>200 || queueDepth<1 || (sendSize==0 && receiveSize==0)){
Serial.printf("\nFATAL ERROR! Can't create new SpanPoint(\"%s\",%d,%d,%d) - one or more invalid parameters ***\n",macAddress,sendSize,receiveSize,queueDepth);
Serial.printf("\n=== PROGRAM HALTED ===");
while(1);
}
this->sendSize=sendSize;
this->receiveSize=receiveSize;
Serial.printf("SpanPoint: Created link to device with MAC Address %02X:%02X:%02X:%02X:%02X:%02X. Send size=%d bytes, Receive size=%d bytes with queue depth=%d.\n",
peerInfo.peer_addr[0],peerInfo.peer_addr[1],peerInfo.peer_addr[2],peerInfo.peer_addr[3],peerInfo.peer_addr[4],peerInfo.peer_addr[5],sendSize,receiveSize,queueDepth);
init(); // initialize SpanPoint
peerInfo.channel=0; // 0 = matches current WiFi channel
peerInfo.ifidx=WIFI_IF_STA; // must specify interface
peerInfo.encrypt=true; // turn on encryption for this peer
memcpy(peerInfo.lmk, lmk, 16); // set local key
esp_now_add_peer(&peerInfo); // add peer to ESP-NOW
if(receiveSize>0)
receiveQueue = xQueueCreate(queueDepth,receiveSize);
SpanPoints.push_back(this);
}
///////////////////////////////
void SpanPoint::init(const char *password){
if(initialized)
return;
if(WiFi.getMode()!=WIFI_AP_STA)
WiFi.mode(WIFI_AP_STA); // set mode to mixed AP/STA. This does not start any servers, just configures the WiFi radio to ensure it does not sleep (required for ESP-NOW)
uint8_t hash[32];
mbedtls_sha256_ret((const unsigned char *)password,strlen(password),hash,0); // produce 256-bit bit hash from password
esp_now_init(); // initialize ESP-NOW
memcpy(lmk, hash, 16); // store first 16 bytes of hash for later use as local key
esp_now_set_pmk(hash+16); // set hash for primary key using last 16 bytes of hash
esp_now_register_recv_cb(dataReceived); // set callback for receiving data
esp_now_register_send_cb(dataSent); // set callback for sending data
statusQueue = xQueueCreate(1,sizeof(esp_now_send_status_t)); // create statusQueue even if not needed
setChannelMask(0x3FFE); // default channel mask uses channels 1-13
initialized=true;
}
///////////////////////////////
void SpanPoint::setChannelMask(uint16_t mask){
channelMask = mask & 0x3FFE;
channel=0;
for(int i=1;i<=13 && channel==0;i++)
channel=(channelMask & (1<<i))?i:0;
if(channel==0){
Serial.printf("\nFATAL ERROR! SpanPoint::setChannelMask(0x%04X) - one or more invalid parameters ***\n",mask);
Serial.printf("\n=== PROGRAM HALTED ===");
while(1);
}
if(!isHub) // if this is NOT the main HomeSpan device
esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE); // set the WiFi channel
Serial.printf("ChannelMask=0x%04X => channel=%d\n",channelMask,channel);
}
///////////////////////////////
boolean SpanPoint::get(void *dataBuf){
if(receiveSize==0)
return(false);
return(xQueueReceive(receiveQueue, dataBuf, 0));
}
///////////////////////////////
boolean SpanPoint::send(void *data){
if(sendSize==0)
return(false);
esp_now_send_status_t status = ESP_NOW_SEND_FAIL;
for(int c=0;c<13;c++){
if((1<<channel) & channelMask){
for(int i=1;i<=3;i++){
Serial.printf("Sending on channel %d, attempt #%d\n",channel,i);
esp_now_send(peerInfo.peer_addr, (uint8_t *) data, sendSize);
xQueueReceive(statusQueue, &status, pdMS_TO_TICKS(2000));
if(status==ESP_NOW_SEND_SUCCESS)
return(true);
delay(10);
}
}
channel++;
if(channel==14)
channel=1;
esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE);
}
return(false);
}
///////////////////////////////
void SpanPoint::setAsHub(){
if(SpanPoints.size()>0){
Serial.printf("\nFATAL ERROR! SpanPoint objects created in main hub device must be instantiated AFTER calling homeSpan.begin() ***\n");
Serial.printf("\n=== PROGRAM HALTED ===");
while(1);
}
isHub=true;
}
///////////////////////////////
void SpanPoint::dataReceived(const uint8_t *mac, const uint8_t *incomingData, int len){
auto it=SpanPoints.begin();
for(;it!=SpanPoints.end() && memcmp((*it)->peerInfo.peer_addr,mac,6)!=0; it++);
if(it==SpanPoints.end())
return;
if((*it)->receiveSize==0)
return;
if(len!=(*it)->receiveSize){
Serial.printf("SpanPoint Warning! %d bytes received from %02X:%02X:%02X:%02X:%02X:%02X does not match %d-byte queue size\n",len,mac[0],mac[1],mac[2],mac[3],mac[4],mac[5],(*it)->receiveSize);
return;
}
xQueueSend((*it)->receiveQueue, incomingData, 0); // send to queue - do not wait if queue is full and instead fail immediately since we need to return from this function ASAP
}
///////////////////////////////
uint8_t SpanPoint::lmk[16];
boolean SpanPoint::initialized=false;
boolean SpanPoint::isHub=false;
vector<SpanPoint *> SpanPoint::SpanPoints;
int SpanPoint::channel;
uint16_t SpanPoint::channelMask;
QueueHandle_t SpanPoint::statusQueue;

View File

@ -35,6 +35,7 @@
#include <esp_task_wdt.h>
#include <esp_sntp.h>
#include <esp_ota_ops.h>
#include <esp_wifi.h>
#include "HomeSpan.h"
#include "HAP.h"
@ -2152,6 +2153,189 @@ void SpanOTA::error(ota_error_t err){
else if (err == OTA_END_ERROR) Serial.println("End Failed\n");
}
///////////////////////////////
int SpanOTA::otaPercent;
boolean SpanOTA::safeLoad;
boolean SpanOTA::enabled=false;
boolean SpanOTA::auth;
///////////////////////////////
// SpanPoint //
///////////////////////////////
SpanPoint::SpanPoint(const char *macAddress, int sendSize, int receiveSize, int queueDepth){
if(sscanf(macAddress,"%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",peerInfo.peer_addr,peerInfo.peer_addr+1,peerInfo.peer_addr+2,peerInfo.peer_addr+3,peerInfo.peer_addr+4,peerInfo.peer_addr+5)!=6){
Serial.printf("\nFATAL ERROR! Can't create new SpanPoint(\"%s\") - Invalid MAC Address ***\n",macAddress);
Serial.printf("\n=== PROGRAM HALTED ===");
while(1);
}
if(sendSize<0 || sendSize>200 || receiveSize<0 || receiveSize>200 || queueDepth<1 || (sendSize==0 && receiveSize==0)){
Serial.printf("\nFATAL ERROR! Can't create new SpanPoint(\"%s\",%d,%d,%d) - one or more invalid parameters ***\n",macAddress,sendSize,receiveSize,queueDepth);
Serial.printf("\n=== PROGRAM HALTED ===");
while(1);
}
this->sendSize=sendSize;
this->receiveSize=receiveSize;
Serial.printf("SpanPoint: Created link to device with MAC Address %02X:%02X:%02X:%02X:%02X:%02X. Send size=%d bytes, Receive size=%d bytes with queue depth=%d.\n",
peerInfo.peer_addr[0],peerInfo.peer_addr[1],peerInfo.peer_addr[2],peerInfo.peer_addr[3],peerInfo.peer_addr[4],peerInfo.peer_addr[5],sendSize,receiveSize,queueDepth);
init(); // initialize SpanPoint
peerInfo.channel=0; // 0 = matches current WiFi channel
peerInfo.ifidx=WIFI_IF_STA; // must specify interface
peerInfo.encrypt=true; // turn on encryption for this peer
memcpy(peerInfo.lmk, lmk, 16); // set local key
esp_now_add_peer(&peerInfo); // add peer to ESP-NOW
if(receiveSize>0)
receiveQueue = xQueueCreate(queueDepth,receiveSize);
SpanPoints.push_back(this);
}
///////////////////////////////
void SpanPoint::init(const char *password){
if(initialized)
return;
if(WiFi.getMode()!=WIFI_AP_STA)
WiFi.mode(WIFI_AP_STA); // set mode to mixed AP/STA. This does not start any servers, just configures the WiFi radio to ensure it does not sleep (required for ESP-NOW)
uint8_t hash[32];
mbedtls_sha256_ret((const unsigned char *)password,strlen(password),hash,0); // produce 256-bit bit hash from password
esp_now_init(); // initialize ESP-NOW
memcpy(lmk, hash, 16); // store first 16 bytes of hash for later use as local key
esp_now_set_pmk(hash+16); // set hash for primary key using last 16 bytes of hash
esp_now_register_recv_cb(dataReceived); // set callback for receiving data
esp_now_register_send_cb(dataSent); // set callback for sending data
statusQueue = xQueueCreate(1,sizeof(esp_now_send_status_t)); // create statusQueue even if not needed
setChannelMask(channelMask); // default channel mask at start-up uses channels 1-13
initialized=true;
}
///////////////////////////////
void SpanPoint::setChannelMask(uint16_t mask){
channelMask = mask & 0x3FFE;
if(isHub)
return;
uint8_t channel=0;
for(int i=1;i<=13 && channel==0;i++) // find first "allowed" channel based on mask
channel=(channelMask & (1<<i))?i:0;
if(channel==0){
Serial.printf("\nFATAL ERROR! SpanPoint::setChannelMask(0x%04X) - mask must allow for at least one channel ***\n",mask);
Serial.printf("\n=== PROGRAM HALTED ===");
while(1);
}
esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE);
}
///////////////////////////////
uint8_t SpanPoint::nextChannel(){
uint8_t channel;
wifi_second_chan_t channel2;
esp_wifi_get_channel(&channel,&channel2); // get current channel
if(isHub || channelMask==(1<<channel)) // do not change channel if device is either a hub, or channel mask does not allow for any other channels
return(channel);
do {
channel=(channel<13)?channel+1:1; // advance to next channel
} while(!(channelMask & (1<<channel))); // until we find next valid one
esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE); // set the WiFi channel
return(channel);
}
///////////////////////////////
boolean SpanPoint::get(void *dataBuf){
if(receiveSize==0)
return(false);
return(xQueueReceive(receiveQueue, dataBuf, 0));
}
///////////////////////////////
boolean SpanPoint::send(void *data){
if(sendSize==0)
return(false);
uint8_t channel;
wifi_second_chan_t channel2;
esp_wifi_get_channel(&channel,&channel2); // get current channel
uint8_t startingChannel=channel; // set starting channel to current channel
esp_now_send_status_t status = ESP_NOW_SEND_FAIL;
do {
for(int i=1;i<=3;i++){
LOG1("SpanPoint: Sending %d bytes to MAC Address %02X:%02X:%02X:%02X:%02X:%02X using channel %hhu...\n",
sendSize,peerInfo.peer_addr[0],peerInfo.peer_addr[1],peerInfo.peer_addr[2],peerInfo.peer_addr[3],peerInfo.peer_addr[4],peerInfo.peer_addr[5],channel);
esp_now_send(peerInfo.peer_addr, (uint8_t *) data, sendSize);
xQueueReceive(statusQueue, &status, pdMS_TO_TICKS(2000));
if(status==ESP_NOW_SEND_SUCCESS)
return(true);
delay(10);
}
channel=nextChannel();
} while(channel!=startingChannel);
return(false);
}
///////////////////////////////
void SpanPoint::dataReceived(const uint8_t *mac, const uint8_t *incomingData, int len){
auto it=SpanPoints.begin();
for(;it!=SpanPoints.end() && memcmp((*it)->peerInfo.peer_addr,mac,6)!=0; it++);
if(it==SpanPoints.end())
return;
if((*it)->receiveSize==0)
return;
if(len!=(*it)->receiveSize){
Serial.printf("SpanPoint Warning! %d bytes received from %02X:%02X:%02X:%02X:%02X:%02X does not match %d-byte queue size\n",len,mac[0],mac[1],mac[2],mac[3],mac[4],mac[5],(*it)->receiveSize);
return;
}
xQueueSend((*it)->receiveQueue, incomingData, 0); // send to queue - do not wait if queue is full and instead fail immediately since we need to return from this function ASAP
}
///////////////////////////////
uint8_t SpanPoint::lmk[16];
boolean SpanPoint::initialized=false;
boolean SpanPoint::isHub=false;
vector<SpanPoint *> SpanPoint::SpanPoints;
uint16_t SpanPoint::channelMask=0x3FFE;
QueueHandle_t SpanPoint::statusQueue;
///////////////////////////////
// MISC //
///////////////////////////////
@ -2161,10 +2345,6 @@ void __attribute__((weak)) loop(){
///////////////////////////////
int SpanOTA::otaPercent;
boolean SpanOTA::safeLoad;
boolean SpanOTA::enabled=false;
boolean SpanOTA::auth;

View File

@ -765,13 +765,13 @@ class SpanPoint {
static boolean initialized;
static boolean isHub;
static vector<SpanPoint *> SpanPoints;
static int channel; // WiFi channel (1-13)
static uint16_t channelMask; // channel mask
static QueueHandle_t statusQueue; // queue for communication between SpanPoint::dataSend and SpanPoint::send
static void dataReceived(const uint8_t *mac, const uint8_t *incomingData, int len);
static void init(const char *password="HomeSpan");
static void setAsHub();
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 );

View File

@ -52,17 +52,17 @@ void setup() {
homeSpan.enableWebLog(10,"pool.ntp.org","UTC","myLog"); // creates a web log on the URL /HomeSpan-[DEVICE-ID].local:[TCP-PORT]/myLog
SpanPoint::setPassword("Hello Thert");
homeSpan.setLogLevel(1);
dev1=new SpanPoint("AC:67:B2:77:42:20",sizeof(int),0);
dev2=new SpanPoint("7C:DF:A1:61:E4:A8",sizeof(int),sizeof(message_t));
homeSpan.begin(Category::Lighting,"HomeSpan Lamp Server","homespan");
SpanPoint::setPassword("Hello Thert");
dev1=new SpanPoint("AC:67:B2:77:42:20",4,0);
dev2=new SpanPoint("7C:DF:A1:61:E4:A8",0,sizeof(message_t));
SpanPoint::setChannelMask(1<<13);
SpanPoint::setChannelMask(0x3FFE);
dev2->setChannelMask(1<<1);
dev2->setChannelMask(1<<3 | 1<<8 | 1<<13);
dev2->setChannelMask(1<<13);
new SpanAccessory(); // Begin by creating a new Accessory using SpanAccessory(), which takes no arguments
@ -115,6 +115,8 @@ void setup() {
} // end of setup()
unsigned long alarmTime=0;
//////////////////////////////////////
void loop(){
@ -125,6 +127,12 @@ void loop(){
if(dev2->get(&message))
Serial.printf("DEV2: '%s' %d %f %d\n",message.a,message.b,message.c,message.d);
if(millis()-alarmTime>5000){
alarmTime=millis();
boolean success = dev2->send(&alarmTime);
Serial.printf("Success = %d\n",success);
}
} // end of loop()
//////////////////////////////////////