Idf 5.1.4/Arduino 3.0.4 porting for esp32 (#1046)

* Update WebSocket library
* Update SSDP library
* Update TFT_eSPI library
* Update EspLuaEngine library
* Update SDFat library
* Change to pioarduino
* Make ESP3DMessageFIFO and ESP3DMessage  more thread safe
* Fix sanity checks for BT
* Add some C6 support
* Refactor ethernet code
* Split Ethernet Sta / WiFi sta ESP Commands  and settings
* Simplify wait and wdtFeed code
* Set C3 with 4MB by default in platformio.ini
* Apply Disable brown out only on ESP32 to avoid crash e.g:ESP32S3
* Add missing entries in platformio.ini
This commit is contained in:
Luc
2024-09-05 16:27:47 +08:00
committed by GitHub
parent 8deab1f06c
commit 93312ff8b5
1965 changed files with 37800 additions and 139618 deletions

View File

@@ -1,11 +0,0 @@
# These are supported funding model platforms
#github: luc-github
#open_collective: # Replace with a single Open Collective username
#ko_fi: # Replace with a single Ko-fi username
#tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
#community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: ESP3D
#issuehunt: # Replace with a single IssueHunt username
#otechie: # Replace with a single Otechie username
#custom: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=Y8FFE7NA4LJWQ

View File

@@ -1,38 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: "[BUG]<Enter comprehensive title>"
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Firmware:**
- ESP core version: [2.5.2]
- Library Version:[e.g 2.1b4]
- Wifi mode:[e.g. STA]
**Board used (please complete the following information):**
- MCU: [e.g. ESP32]
- Name:[e.g. NodeMCU2s]
- Flash size: [e.g. 4M: 2M/2M]
**Additional context**
Add any other context about the problem here.
Wiring, decoded stack, etc...

View File

@@ -1 +0,0 @@
blank_issues_enabled: false

View File

@@ -1,10 +0,0 @@
---
name: Question template
about: Ask your question, if not bug neither feature request.
title: "[Question]<Enter comprehensive title>"
labels: question
assignees: ''
---
What is your question ?

View File

@@ -1,27 +0,0 @@
#!/bin/bash
# Exit immediately if a command exits with a non-zero status.
set -e
# Enable the globstar shell option
shopt -s globstar
#arduino or PlatformIO
ide=$1
# Make sure we are inside the github workspace
cd $GITHUB_WORKSPACE
#export paths
export PATH="$HOME/arduino_ide:$PATH"
export ARDUINO_IDE_PATH="$HOME/arduino_ide"
if [[ "$ide" == "arduino" ]];
then
echo "Arduino"
fqbn=esp32:esp32:esp32:PartitionScheme=min_spiffs,FlashFreq=80,PSRAM=disabled,CPUFreq=240,FlashMode=qio,FlashSize=4M,DebugLevel=none
arduino-builder -hardware "$ARDUINO_IDE_PATH/hardware" -tools "$ARDUINO_IDE_PATH/tools-builder" -tools "$ARDUINO_IDE_PATH/tools" -libraries "$ARDUINO_IDE_PATH/libraries" -fqbn=$fqbn -compile -logger=human -core-api-version=10810 ./examples/SSDP/SSDP.ino
else
echo "PlatformIO"
cp -r ./src/ESP32SSDP.cpp ./examples/SSDP/
cp -r ./src/ESP32SSDP.h ./examples/SSDP/
cp ./test/platformio.ini ./examples/
cd examples
platformio run -e esp32dev
fi

View File

@@ -1,21 +0,0 @@
#!/bin/bash
# Exit immediately if a command exits with a non-zero status.
set -e
# Enable the globstar shell option
shopt -s globstar
# Make sure we are inside the github workspace
cd $GITHUB_WORKSPACE
echo $STEPS_CONTEXT
step=$1
status=$2
if [[ "$status" == "success" ]];
then
echo "Success build"
exit 0
else
echo "Build failed"
exit 1
fi

View File

@@ -1,12 +0,0 @@
#!/bin/bash
# Exit immediately if a command exits with a non-zero status.
set -e
# Enable the globstar shell option
shopt -s globstar
wget http://downloads.arduino.cc/arduino-1.8.13-linux64.tar.xz
tar xf arduino-1.8.13-linux64.tar.xz
mv arduino-1.8.13 $HOME/arduino_ide

View File

@@ -1,19 +0,0 @@
#!/bin/bash
# Exit immediately if a command exits with a non-zero status.
set -e
# Enable the globstar shell option
shopt -s globstar
#install pyserial
echo "Installing Python Serial ..."
pip install pyserial
echo "Clone esp32 core"
cd $HOME/arduino_ide/hardware
mkdir esp32
cd esp32
git clone https://github.com/espressif/arduino-esp32.git esp32
cd esp32
git submodule update --init
cd tools
python get.py

View File

@@ -1,9 +0,0 @@
#!/bin/bash
# Exit immediately if a command exits with a non-zero status.
set -e
# Enable the globstar shell option
shopt -s globstar
pip install -U platformio
platformio update

View File

@@ -1,12 +0,0 @@
#!/bin/bash
# Exit immediately if a command exits with a non-zero status.
set -e
# Enable the globstar shell option
shopt -s globstar
ls $HOME
# Make sure we are inside the github workspace
cd $GITHUB_WORKSPACE
mkdir -p $HOME/arduino_ide/libraries/SSDP
cp -R ./src $HOME/arduino_ide/libraries/SSDP
cp ./library.properties $HOME/arduino_ide/libraries/SSDP

View File

@@ -1,46 +0,0 @@
name: build-ci-dev
on:
pull_request:
branches:
- Dev
push:
branches:
- Dev
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.x
uses: actions/setup-python@v2
with:
python-version: "3.x"
architecture: "x64"
- name: Install Arduino
run: bash ./.github/ci/install-arduino.sh
- name: Install platformIO
run: bash ./.github/ci/install-platformio.sh
- name: Install ESP32
run: bash ./.github/ci/install-esp32.sh
- name: Setup libraries
run: bash ./.github/ci/prepare-libs.sh
- name: Build ESP32 arduino
id: esp32_1
run: bash ./.github/ci/build-ssdp.sh arduino
continue-on-error: true
- name: Build platformIO
id: pio_1
run: bash ./.github/ci/build-ssdp.sh pio
continue-on-error: true
- name: Final check
env:
STEPS_CONTEXT: ${{ toJson(steps) }}
if: steps.pio_1.outcome == 'failure' || steps.esp32_1.outcome == 'failure'
run: bash ./.github/ci/final-check.sh "GLOBAL" "failure"
- name: Final confirmation
env:
STEPS_CONTEXT: ${{ toJson(steps) }}
if: steps.pio_1.outcome == 'success' && steps.esp32_1.outcome == 'success'
run: bash ./.github/ci/final-check.sh "GLOBAL" "success"

View File

@@ -1,46 +0,0 @@
name: build-ci
on:
pull_request:
branches:
- master
push:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.x
uses: actions/setup-python@v2
with:
python-version: "3.x"
architecture: "x64"
- name: Install Arduino
run: bash ./.github/ci/install-arduino.sh
- name: Install platformIO
run: bash ./.github/ci/install-platformio.sh
- name: Install ESP32
run: bash ./.github/ci/install-esp32.sh
- name: Setup libraries
run: bash ./.github/ci/prepare-libs.sh
- name: Build ESP32 arduino
id: esp32_1
run: bash ./.github/ci/build-ssdp.sh arduino
continue-on-error: true
- name: Build platformIO
id: pio_1
run: bash ./.github/ci/build-ssdp.sh pio
continue-on-error: true
- name: Final check
env:
STEPS_CONTEXT: ${{ toJson(steps) }}
if: steps.pio_1.outcome == 'failure' || steps.esp32_1.outcome == 'failure'
run: bash ./.github/ci/final-check.sh "GLOBAL" "failure"
- name: Final confirmation
env:
STEPS_CONTEXT: ${{ toJson(steps) }}
if: steps.pio_1.outcome == 'success' && steps.esp32_1.outcome == 'success'
run: bash ./.github/ci/final-check.sh "GLOBAL" "success"

View File

@@ -1,13 +0,0 @@
name: Greetings
on: [issues]
jobs:
greeting:
runs-on: ubuntu-latest
steps:
- uses: actions/first-interaction@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
issue-message: 'Thank your for submiting, please be sure you followed template or your issue may be dismissed.'
pr-message: 'Thank you for your contribution, be patient, review can take a time.'

View File

@@ -1,27 +0,0 @@
name: Deploy Wiki
on:
push:
paths:
# Trigger only when wiki directory changes
- 'wiki/**'
branches:
# And only on master branch
- wiki
jobs:
deploy-wiki:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Push Wiki Changes
uses: Andrew-Chen-Wang/github-wiki-action@v2
env:
# Make sure you have that / at the end. We use rsync
# WIKI_DIR's default is wiki/
WIKI_DIR: wiki/
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_MAIL: ${{ secrets.MY_EMAIL }}
GH_NAME: ${{ github.repository_owner }}
EXCLUDED_FILES: "a/ b.md"

View File

@@ -1,105 +0,0 @@
#include <WiFi.h>
#include <WebServer.h>
#include <ESP32SSDP.h>
const char* ssid = "********";
const char* password = "********";
WebServer HTTP(80);
void setup()
{
Serial.begin(115200);
Serial.println();
Serial.println("Starting WiFi...");
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
if(WiFi.waitForConnectResult() == WL_CONNECTED) {
Serial.printf("Starting HTTP...\n");
HTTP.on("/index.html", HTTP_GET, []() {
HTTP.send(200, "text/plain", "Hello World!");
});
HTTP.on("/description.xml", HTTP_GET, []() {
SSDP.schema(HTTP.client());
});
HTTP.begin();
//set schema xml url, nees to match http handler
//"ssdp/schema.xml" if not set
SSDP.setSchemaURL("description.xml");
//set port
//80 if not set
SSDP.setHTTPPort(80);
//set device name
//Null string if not set
SSDP.setName("Philips hue clone");
//set Serial Number
//Null string if not set
SSDP.setSerialNumber("001788102201");
//set device url
//Null string if not set
SSDP.setURL("index.html");
//set model name
//Null string if not set
SSDP.setModelName("Philips hue bridge 2012");
//set model description
//Null string if not set
SSDP.setModelDescription("This device can be controled by WiFi");
//set model number
//Null string if not set
SSDP.setModelNumber("929000226503");
//set model url
//Null string if not set
SSDP.setModelURL("http://www.meethue.com");
//set model manufacturer name
//Null string if not set
SSDP.setManufacturer("Royal Philips Electronics");
//set model manufacturer url
//Null string if not set
SSDP.setManufacturerURL("http://www.philips.com");
//set device type
//"urn:schemas-upnp-org:device:Basic:1" if not set
SSDP.setDeviceType("rootdevice"); //to appear as root device, other examples: MediaRenderer, MediaServer ...
//set server name
//"Arduino/1.0" if not set
SSDP.setServerName("SSDPServer/1.0");
//set UUID, you can use https://www.uuidgenerator.net/
//use 38323636-4558-4dda-9188-cda0e6 + 4 last bytes of mac address if not set
//use SSDP.setUUID("daa26fa3-d2d4-4072-bc7a-a1b88ab4234a", false); for full UUID
SSDP.setUUID("daa26fa3-d2d4-4072-bc7a");
//Set icons list, NB: optional, this is ignored under windows
SSDP.setIcons( "<icon>"
"<mimetype>image/png</mimetype>"
"<height>48</height>"
"<width>48</width>"
"<depth>24</depth>"
"<url>icon48.png</url>"
"</icon>");
//Set service list, NB: optional for simple device
SSDP.setServices( "<service>"
"<serviceType>urn:schemas-upnp-org:service:SwitchPower:1</serviceType>"
"<serviceId>urn:upnp-org:serviceId:SwitchPower:1</serviceId>"
"<SCPDURL>/SwitchPower1.xml</SCPDURL>"
"<controlURL>/SwitchPower/Control</controlURL>"
"<eventSubURL>/SwitchPower/Event</eventSubURL>"
"</service>");
Serial.printf("Starting SSDP...\n");
SSDP.begin();
Serial.printf("Ready!\n");
} else {
Serial.printf("WiFi Failed\n");
while(1) {
delay(100);
}
}
}
void loop()
{
HTTP.handleClient();
delay(1);
}

View File

@@ -1,103 +0,0 @@
#include "ESPAsyncWebServer.h"
#include "ESP32SSDP.h"
const char* ssid = "********";
const char* password = "********";
AsyncWebServer webserver(80);
void setup()
{
Serial.begin(115200);
Serial.println();
Serial.println("Starting WiFi...");
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
if(WiFi.waitForConnectResult() == WL_CONNECTED) {
Serial.printf("Starting HTTP...\n");
webserver.on("/index.html", HTTP_GET, [&](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "Hello World!");
});
webserver.on("/description.xml", HTTP_GET, [&](AsyncWebServerRequest *request) {
request->send(200, "text/xml", SSDP.schema(false));
});
webserver.begin();
//set schema xml url, nees to match http handler
//"ssdp/schema.xml" if not set
SSDP.setSchemaURL("description.xml");
//set port
//80 if not set
SSDP.setHTTPPort(80);
//set device name
//Null string if not set
SSDP.setName("Philips hue clone");
//set Serial Number
//Null string if not set
SSDP.setSerialNumber("001788102201");
//set device url
//Null string if not set
SSDP.setURL("index.html");
//set model name
//Null string if not set
SSDP.setModelName("Philips hue bridge 2012");
//set model description
//Null string if not set
SSDP.setModelDescription("This device can be controled by WiFi");
//set model number
//Null string if not set
SSDP.setModelNumber("929000226503");
//set model url
//Null string if not set
SSDP.setModelURL("http://www.meethue.com");
//set model manufacturer name
//Null string if not set
SSDP.setManufacturer("Royal Philips Electronics");
//set model manufacturer url
//Null string if not set
SSDP.setManufacturerURL("http://www.philips.com");
//set device type
//"urn:schemas-upnp-org:device:Basic:1" if not set
SSDP.setDeviceType("rootdevice"); //to appear as root device, other examples: MediaRenderer, MediaServer ...
//set server name
//"Arduino/1.0" if not set
SSDP.setServerName("SSDPServer/1.0");
//set UUID, you can use https://www.uuidgenerator.net/
//use 38323636-4558-4dda-9188-cda0e6 + 4 last bytes of mac address if not set
//use SSDP.setUUID("daa26fa3-d2d4-4072-bc7a-a1b88ab4234a", false); for full UUID
SSDP.setUUID("daa26fa3-d2d4-4072-bc7a");
//Set icons list, NB: optional, this is ignored under windows
SSDP.setIcons( "<icon>"
"<mimetype>image/png</mimetype>"
"<height>48</height>"
"<width>48</width>"
"<depth>24</depth>"
"<url>icon48.png</url>"
"</icon>");
//Set service list, NB: optional for simple device
SSDP.setServices( "<service>"
"<serviceType>urn:schemas-upnp-org:service:SwitchPower:1</serviceType>"
"<serviceId>urn:upnp-org:serviceId:SwitchPower:1</serviceId>"
"<SCPDURL>/SwitchPower1.xml</SCPDURL>"
"<controlURL>/SwitchPower/Control</controlURL>"
"<eventSubURL>/SwitchPower/Event</eventSubURL>"
"</service>");
Serial.printf("Starting SSDP...\n");
SSDP.begin();
Serial.printf("Ready!\n");
} else {
Serial.printf("WiFi Failed\n");
while(1) {
delay(100);
}
}
}
void loop()
{
delay(1);
}

View File

@@ -1,734 +0,0 @@
/*
ESP32 Simple Service Discovery
Copyright (c) 2015 Hristo Gochkov
Original (Arduino) version by Filippo Sallemi, July 23, 2014.
Can be found at: https://github.com/nomadnt/uSSDP
License (MIT license):
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.
*/
#ifdef ARDUINO_ARCH_ESP32
#include <functional>
#include "ESP32SSDP.h"
#include <AsyncUDP.h>
#include <lwip/ip_addr.h>
//#define DEBUG_SSDP Serial
//#define DEBUG_VERBOSE_SSDP
//#define DEBUG_WITH_MARLIN
#if defined (DEBUG_WITH_MARLIN)
class FlushableHardwareSerial : public HardwareSerial
{
public:
FlushableHardwareSerial(int uart_nr) : HardwareSerial(uart_nr) {}
};
extern FlushableHardwareSerial flushableSerial;
#define DEBUG_SSDP flushableSerial
#endif //endif DEBUG_WITH_MARLIN
#define SSDP_INTERVAL 1200
#define SSDP_PORT 1900
#define SSDP_METHOD_SIZE 10
#define SSDP_URI_SIZE 2
#define SSDP_BUFFER_SIZE 64
#define SSDP_MULTICAST_TTL 2
static const IPAddress SSDP_MULTICAST_ADDR(239, 255, 255, 250);
#define SSDP_UUID_ROOT "38323636-4558-4dda-9188-cda0e6"
static const char _ssdp_response_template[] PROGMEM =
"HTTP/1.1 200 OK\r\n"
"EXT:\r\n";
static const char _ssdp_notify_template[] PROGMEM =
"NOTIFY * HTTP/1.1\r\n"
"HOST: 239.255.255.250:1900\r\n"
"NTS: ssdp:alive\r\n";
static const char _ssdp_packet_template[] PROGMEM =
"%s" // _ssdp_response_template / _ssdp_notify_template
"CACHE-CONTROL: max-age=%u\r\n" // _interval
"SERVER: %s UPNP/1.1 %s/%s\r\n" // _servername, _modelName, _modelNumber
"USN: uuid:%s%s\r\n" // _uuid, _usn_suffix
"%s: %s\r\n" // "NT" or "ST", _deviceType
"LOCATION: http://%u.%u.%u.%u:%u/%s\r\n" // WiFi.localIP(), _port, _schemaURL
"\r\n";
/*This need to be removed as part as deprecated, headers should be handled outside of library*/
static const char _ssdp_schema_header[] PROGMEM =
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/xml\r\n"
"Connection: close\r\n"
"Access-Control-Allow-Origin: *\r\n"
"\r\n";
static const char _ssdp_schema_template[] PROGMEM =
"<?xml version=\"1.0\"?>"
"<root xmlns=\"urn:schemas-upnp-org:device-1-0\">"
"<specVersion>"
"<major>1</major>"
"<minor>0</minor>"
"</specVersion>"
"<URLBase>http://%u.%u.%u.%u:%u/</URLBase>" // WiFi.localIP(), _port
"<device>"
"<deviceType>urn:schemas-upnp-org:device:%s:1</deviceType>"
"<friendlyName>%s</friendlyName>"
"<presentationURL>%s</presentationURL>"
"<serialNumber>%s</serialNumber>"
"<modelName>%s</modelName>"
"<modelDescription>%s</modelDescription>"
"<modelNumber>%s</modelNumber>"
"<modelURL>%s</modelURL>"
"<manufacturer>%s</manufacturer>"
"<manufacturerURL>%s</manufacturerURL>"
"<UDN>uuid:%s</UDN>"
"<serviceList>%s</serviceList>"
"<iconList>%s</iconList>"
"</device>"
"</root>\r\n"
"\r\n";
SSDPClass::SSDPClass() :
_replySlots{NULL},
_respondToAddr{0,0,0,0}
{
_port = 80;
_ttl = SSDP_MULTICAST_TTL;
_interval = SSDP_INTERVAL;
_respondToPort = 0;
_pending = false;
_stmatch = false;
_delay=0;
_process_time = 0;
_notify_time = 0;
_uuid[0] = '\0';
_usn_suffix[0] = '\0';
_respondType[0] = '\0';
_modelNumber[0] = '\0';
sprintf(_deviceType, "Basic");
_friendlyName[0] = '\0';
_presentationURL[0] = '\0';
_serialNumber[0] = '\0';
_modelName[0] = '\0';
_modelURL[0] = '\0';
_manufacturer[0] = '\0';
_manufacturerURL[0] = '\0';
_servername = "Arduino/1.0";
sprintf(_schemaURL, "ssdp/schema.xml");
_schema = nullptr;
}
SSDPClass::~SSDPClass()
{
end();
}
void SSDPClass::end()
{
if (_schema) {
free(_schema);
_schema = nullptr;
}
#if defined( DEBUG_SSDP) && defined (DEBUG_VERBOSE_SSDP)
DEBUG_SSDP.printf_P(PSTR("SSDP end ... "));
#endif
}
IPAddress SSDPClass::localIP()
{
tcpip_adapter_ip_info_t ip;
if (WiFi.getMode() == WIFI_STA) {
if (tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip)) {
return IPAddress();
}
} else if (WiFi.getMode() == WIFI_OFF) {
if (tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip)) {
return IPAddress();
}
}
return IPAddress(ip.ip.addr);
}
void SSDPClass::setUUID(const char *uuid, bool rootonly)
{
//no sanity check is done - TBD
if (rootonly) {
uint32_t chipId = ((uint16_t) (ESP.getEfuseMac() >> 32));
sprintf(_uuid, "%s%02x%02x%02x",
uuid,
(uint16_t) ((chipId >> 16) & 0xff),
(uint16_t) ((chipId >> 8) & 0xff),
(uint16_t) chipId & 0xff );
} else {
strlcpy(_uuid, uuid,sizeof(_uuid));
}
}
bool SSDPClass::begin()
{
_pending = false;
_stmatch = false;
end();
if (strlen(_uuid) == 0) {
setUUID(SSDP_UUID_ROOT);
}
#if defined (DEBUG_SSDP) && defined(DEBUG_VERBOSE_SSDP)
DEBUG_SSDP.printf("SSDP UUID: %s\n", (char *)_uuid);
#endif
if(_udp.connected()) {
#ifdef DEBUG_SSDP
DEBUG_SSDP.println("Already connected, abort begin");
#endif
return false;
}
_udp.onPacket([](void * arg, AsyncUDPPacket& packet) {
((SSDPClass*)(arg))->_onPacket(packet);
}, this);
if (!_udp.listenMulticast(IPAddress(SSDP_MULTICAST_ADDR),SSDP_PORT, _ttl)) {
#ifdef DEBUG_SSDP
DEBUG_SSDP.println("Error begin");
#endif
return false;
}
return true;
}
void SSDPClass::_send(ssdp_method_t method)
{
char buffer[1460];
IPAddress ip = localIP();
char * valueBuffer = (char *)malloc(strlen_P(_ssdp_notify_template)+1);
if (!valueBuffer) {
#ifdef DEBUG_SSDP
DEBUG_SSDP.println("Error not enough memory for valueBuffer creation");
#endif
return;
}
strcpy_P(valueBuffer, (method == NONE)?_ssdp_response_template:_ssdp_notify_template);
int len = snprintf_P(buffer, sizeof(buffer)-1,
_ssdp_packet_template,
valueBuffer,
_interval,
_servername.c_str(),
_modelName, _modelNumber,
_uuid, _usn_suffix,
(method == NONE)?"ST":"NT",
_respondType,
ip[0], ip[1], ip[2], ip[3], _port, _schemaURL
);
if(len <= 0) {
free(valueBuffer);
#ifdef DEBUG_SSDP
DEBUG_SSDP.println("Error not enough memory for using valueBuffer");
#endif
return;
}
IPAddress remoteAddr;
uint16_t remotePort;
if(method == NONE) {
remoteAddr = _respondToAddr;
remotePort = _respondToPort;
#ifdef DEBUG_SSDP
DEBUG_SSDP.print("Sending Response to ");
#endif
} else {
remoteAddr = IPAddress(SSDP_MULTICAST_ADDR);
remotePort = SSDP_PORT;
#ifdef DEBUG_SSDP
DEBUG_SSDP.print("Sending Notify to ");
#endif
}
#ifdef DEBUG_SSDP
DEBUG_SSDP.print(remoteAddr);
DEBUG_SSDP.print(":");
DEBUG_SSDP.println(remotePort);
#endif
_udp.writeTo((const uint8_t *)buffer, len, remoteAddr, remotePort);
#if defined (DEBUG_SSDP) && defined(DEBUG_VERBOSE_SSDP)
DEBUG_SSDP.println("*************************TX*************************");
DEBUG_SSDP.println(buffer);
DEBUG_SSDP.println("****************************************************");
#endif
free(valueBuffer);
}
const char * SSDPClass::getSchema()
{
uint len = strlen(_ssdp_schema_template)
+ 21 //(IP = 15) + 1 (:) + 5 (port)
+ SSDP_DEVICE_TYPE_SIZE
+ SSDP_FRIENDLY_NAME_SIZE
+ SSDP_SCHEMA_URL_SIZE
+ SSDP_SERIAL_NUMBER_SIZE
+ SSDP_MODEL_NAME_SIZE
+ _modelDescription.length()
+ SSDP_MODEL_VERSION_SIZE
+ SSDP_MODEL_URL_SIZE
+ SSDP_MANUFACTURER_SIZE
+ SSDP_MANUFACTURER_URL_SIZE
+ SSDP_UUID_SIZE
+ _services.length()
+ _icons.length();
if (_schema) {
free (_schema);
_schema = nullptr;
}
_schema = (char *)malloc(len+1);
if (_schema) {
IPAddress ip = localIP();
sprintf(_schema, _ssdp_schema_template,
ip[0], ip[1], ip[2], ip[3], _port,
_deviceType,
_friendlyName,
_presentationURL,
_serialNumber,
_modelName,
_modelDescription.c_str(),
_modelNumber,
_modelURL,
_manufacturer,
_manufacturerURL,
_uuid,
_services.c_str(),
_icons.c_str()
);
} else {
#ifdef DEBUG_SSDP
DEBUG_SSDP.println("not enough memory for schema");
#endif
}
return _schema;
}
/*This function is now deprecated and will be removed in future release*/
/*Please use getSchema() instead */
void SSDPClass::schema(WiFiClient client, bool sendHeaders)
{
if(sendHeaders) {
client.print(_ssdp_schema_header);
}
client.print(getSchema());
}
/*This function is now deprecated and will be removed in future release*/
/*Please use getSchema() instead */
const char * SSDPClass::schema(bool includeheader)
{
return getSchema();
}
void SSDPClass::_onPacket(AsyncUDPPacket& packet)
{
if (packet.length()== 0) {
return;
}
int nbBytes =0;
char * packetBuffer = nullptr;
if(!_pending ) {
ssdp_method_t method = NONE;
nbBytes= packet.length();
typedef enum {METHOD, URI, PROTO, KEY, VALUE, ABORT} states;
states state = METHOD;
typedef enum {STRIP, START, SKIP, MAN, ST, MX} headers;
headers header = STRIP;
uint8_t cursor = 0;
uint8_t cr = 0;
char buffer[SSDP_BUFFER_SIZE] = {0};
packetBuffer = new char[nbBytes +1];
if (packetBuffer == nullptr) {
#ifdef DEBUG_SSDP
DEBUG_SSDP.println("not enough memory for the packet");
#endif
return;
}
int process_pos = 0;
strncpy(packetBuffer,(const char*)packet.data(),nbBytes);
packetBuffer[nbBytes]='\0';
_respondToAddr = packet.remoteIP();
_respondToPort = packet.remotePort();
#if defined( DEBUG_SSDP) && defined (DEBUG_VERBOSE_SSDP)
if (nbBytes) {
DEBUG_SSDP.println("*************************RX*************************");
DEBUG_SSDP.print(packet.remoteIP());
DEBUG_SSDP.print(":");
DEBUG_SSDP.println(packet.remotePort());
DEBUG_SSDP.println(packetBuffer);
DEBUG_SSDP.println("****************************************************");
}
#endif
while(process_pos < nbBytes) {
char c = packetBuffer[process_pos];
process_pos++;
(c == '\r' || c == '\n') ? cr++ : cr = 0;
switch(state) {
case METHOD:
if(c == ' ') {
if(strcmp(buffer, "M-SEARCH") == 0) {
method = SEARCH;
}
if(method == NONE) {
state = ABORT;
} else {
state = URI;
}
cursor = 0;
} else if(cursor < SSDP_METHOD_SIZE - 1) {
buffer[cursor++] = c;
buffer[cursor] = '\0';
}
break;
case URI:
if(c == ' ') {
if(strcmp(buffer, "*")) {
state = ABORT;
} else {
state = PROTO;
}
cursor = 0;
} else if(cursor < SSDP_URI_SIZE - 1) {
buffer[cursor++] = c;
buffer[cursor] = '\0';
}
break;
case PROTO:
if(cr == 2) {
state = KEY;
cursor = 0;
}
break;
case KEY:
// end of HTTP request parsing. If we find a match start reply delay.
if(cr == 4) {
if (_stmatch) {
_pending = true;
_process_time = millis();
}
} else if(c == ':') {
cursor = 0;
state = VALUE;
} else if(c != '\r' && c != '\n' && c != ' ' && cursor < SSDP_BUFFER_SIZE - 1) {
buffer[cursor++] = c;
buffer[cursor] = '\0';
}
break;
case VALUE:
if(cr == 2) {
switch(header) {
case START:
#ifdef DEBUG_SSDP
DEBUG_SSDP.println("***********************");
#endif
case STRIP:
case SKIP:
break;
case MAN:
#ifdef DEBUG_SSDP
DEBUG_SSDP.printf("MAN: %s\n", (char *)buffer);
#endif
break;
case ST:
// save the search term for the reply and clear usn suffix.
strlcpy(_respondType, buffer, sizeof(_respondType));
_usn_suffix[0] = '\0';
#ifdef DEBUG_SSDP
DEBUG_SSDP.printf("ST: '%s'\n",buffer);
#endif
// if looking for all or root reply with upnp:rootdevice
if(strcmp(buffer, "ssdp:all")==0 || strcmp(buffer, "upnp:rootdevice")==0) {
_stmatch = true;
// set USN suffix
strlcpy(_usn_suffix, "::upnp:rootdevice", sizeof(_usn_suffix));
#ifdef DEBUG_SSDP
DEBUG_SSDP.println("the search type matches all and root");
#endif
state = KEY;
} else
// if the search type matches our type, we should respond instead of ABORT
if(strcasecmp(buffer, _deviceType) == 0) {
_stmatch = true;
// set USN suffix to the device type
strlcpy(_usn_suffix, "::", sizeof(_usn_suffix));
strlcat(_usn_suffix, _deviceType, sizeof(_usn_suffix));
#ifdef DEBUG_SSDP
DEBUG_SSDP.println("the search type matches our type");
#endif
state = KEY;
} else {
state = ABORT;
#ifdef DEBUG_SSDP
DEBUG_SSDP.println("REJECT. The search type does not match our type");
DEBUG_SSDP.println("***********************");
#endif
}
break;
case MX:
// delay in ms from 0 to MX*1000 where MX is in seconds with limits.
_delay = (short)random(0, atoi(buffer) * 1000L);
if (_delay > SSDP_MAX_DELAY) {
_delay = SSDP_MAX_DELAY;
}
break;
}
if(state != ABORT) {
state = KEY;
header = STRIP;
cursor = 0;
}
} else if(c != '\r' && c != '\n') {
if(header == STRIP) {
if(c == ' ') {
break;
} else {
header = START;
}
}
if(header == START) {
if(strncmp(buffer, "MA", 2) == 0) {
header = MAN;
} else if(strcmp(buffer, "ST") == 0) {
header = ST;
} else if(strcmp(buffer, "MX") == 0) {
header = MX;
} else {
header = SKIP;
}
}
if(cursor < SSDP_BUFFER_SIZE - 1) {
buffer[cursor++] = c;
buffer[cursor] = '\0';
}
}
break;
case ABORT:
_pending = false;
_delay = 0;
break;
}
}
}
if(packetBuffer) {
delete packetBuffer;
}
// save reply in reply queue if one is pending
if(_pending) {
int i;
// Many UPNP hosts send out mulitple M-SEARCH packets at the same time to mitigate
// packet loss. Just reply to one for a given host:port.
for (i = 0; i < SSDP_MAX_REPLY_SLOTS; i++) {
if (_replySlots[i]) {
if (_replySlots[i]->_respondToPort == _respondToPort &&
_replySlots[i]->_respondToAddr == _respondToAddr
) {
// keep original delay
_delay = _replySlots[i]->_delay;
_process_time = _replySlots[i]->_process_time;
#ifdef DEBUG_SSDP
DEBUG_SSDP.printf("Remove duplicate SSDP reply in slot %i.\n", i);
#endif
delete _replySlots[i];
_replySlots[i] = 0;
}
}
}
// save packet to available reply queue slot
for (i = 0; i < SSDP_MAX_REPLY_SLOTS; i++) {
if (!_replySlots[i]) {
#ifdef DEBUG_SSDP
DEBUG_SSDP.printf("Saving deferred SSDP reply to queue slot %i.\n", i);
DEBUG_SSDP.println("***********************");
#endif
_replySlots[i] = new ssdp_reply_slot_item_t;
if (_replySlots[i]) {
_replySlots[i]->_process_time = _process_time;
_replySlots[i]->_delay = _delay;
_replySlots[i]->_respondToAddr = _respondToAddr;
_replySlots[i]->_respondToPort = _respondToPort;
strlcpy(_replySlots[i]->_respondType, _respondType, sizeof(_replySlots[i]->_respondType));
strlcpy(_replySlots[i]->_usn_suffix, _usn_suffix, sizeof(_replySlots[i]->_usn_suffix));
}
break;
}
}
#ifdef DEBUG_SSDP
if (i == SSDP_MAX_REPLY_SLOTS) {
DEBUG_SSDP.println("SSDP reply queue is full dropping packet.");
}
#endif
_pending = false;
_delay = 0;
}
// send any packets that are pending and overdue.
unsigned long t = millis();
bool sent = false;
for (int i = 0; i < SSDP_MAX_REPLY_SLOTS; i++) {
if (_replySlots[i]) {
// millis delay with overflow protection.
if (t - _replySlots[i]->_process_time > _replySlots[i]->_delay) {
// reply ready. restore and send.
_respondToAddr = _replySlots[i]->_respondToAddr;
_respondToPort = _replySlots[i]->_respondToPort;
strlcpy(_respondType, _replySlots[i]->_respondType, sizeof(_respondType));
strlcpy(_usn_suffix, _replySlots[i]->_usn_suffix, sizeof(_usn_suffix));
#ifdef DEBUG_SSDP
DEBUG_SSDP.printf("Slot(%d) ", i);
DEBUG_SSDP.println("Send None");
#endif
_send(NONE);
sent = true;
delete _replySlots[i];
_replySlots[i] = 0;
#ifdef DEBUG_SSDP
DEBUG_SSDP.println("***********************");
#endif
}
}
}
#if defined (DEBUG_SSDP) && defined(DEBUG_VERBOSE_SSDP)
uint8_t rcount = 0;
DEBUG_SSDP.print("SSDP reply queue status: [");
for (int i = 0; i < SSDP_MAX_REPLY_SLOTS; i++) {
DEBUG_SSDP.print(_replySlots[i] ? "X" : "-" );
}
DEBUG_SSDP.println("]");
#endif
if(_notify_time == 0 || (millis() - _notify_time) > (_interval * 1000L)) {
_notify_time = millis();
// send notify with our root device type
strlcpy(_respondType, "upnp:rootdevice", sizeof(_respondType));
strlcpy(_usn_suffix, "::upnp:rootdevice", sizeof(_usn_suffix));
#ifdef DEBUG_SSDP
DEBUG_SSDP.println("Send Notify");
#endif
_send(NOTIFY);
sent = true;
#ifdef DEBUG_SSDP
DEBUG_SSDP.println("***********************");
#endif
}
if (!sent) {
#if defined (DEBUG_SSDP) && defined(DEBUG_VERBOSE_SSDP)
DEBUG_SSDP.println("Do not sent");
#endif
}
}
void SSDPClass::setSchemaURL(const char *url)
{
strlcpy(_schemaURL, url, sizeof(_schemaURL));
}
void SSDPClass::setHTTPPort(uint16_t port)
{
_port = port;
}
void SSDPClass::setDeviceType(const char *deviceType)
{
strlcpy(_deviceType, deviceType, sizeof(_deviceType));
}
void SSDPClass::setName(const char *name)
{
strlcpy(_friendlyName, name, sizeof(_friendlyName));
}
void SSDPClass::setURL(const char *url)
{
strlcpy(_presentationURL, url, sizeof(_presentationURL));
}
void SSDPClass::setSerialNumber(const char *serialNumber)
{
strlcpy(_serialNumber, serialNumber, sizeof(_serialNumber));
}
void SSDPClass::setSerialNumber(const uint32_t serialNumber)
{
snprintf(_serialNumber, sizeof(uint32_t)*2+1, "%08X", serialNumber);
}
void SSDPClass::setModelName(const char *name)
{
strlcpy(_modelName, name, sizeof(_modelName));
}
void SSDPClass::setModelDescription(const char *desc)
{
_modelDescription = desc;
}
void SSDPClass::setServerName(const char *name)
{
_servername = name;
}
void SSDPClass::setModelNumber(const char *num)
{
strlcpy(_modelNumber, num, sizeof(_modelNumber));
}
void SSDPClass::setModelURL(const char *url)
{
strlcpy(_modelURL, url, sizeof(_modelURL));
}
void SSDPClass::setManufacturer(const char *name)
{
strlcpy(_manufacturer, name, sizeof(_manufacturer));
}
void SSDPClass::setManufacturerURL(const char *url)
{
strlcpy(_manufacturerURL, url, sizeof(_manufacturerURL));
}
void SSDPClass::setTTL(const uint8_t ttl)
{
_ttl = ttl;
}
void SSDPClass::setInterval(uint32_t interval)
{
_interval = interval;
}
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SSDP)
SSDPClass SSDP;
#endif
#endif

View File

@@ -1,196 +0,0 @@
/*
ESP32 Simple Service Discovery
Copyright (c) 2015 Hristo Gochkov
Original (Arduino) version by Filippo Sallemi, July 23, 2014.
Can be found at: https://github.com/nomadnt/uSSDP
License (MIT license):
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.
*/
#ifdef ARDUINO_ARCH_ESP32
#ifndef ESP32SSDP_H
#define ESP32SSDP_H
#include <Arduino.h>
#include <WiFi.h>
#include <AsyncUDP.h>
#define SSDP_UUID_SIZE 37
#define SSDP_SCHEMA_URL_SIZE 64
#define SSDP_DEVICE_TYPE_SIZE 64
#define SSDP_FRIENDLY_NAME_SIZE 64
#define SSDP_SERIAL_NUMBER_SIZE 32
#define SSDP_PRESENTATION_URL_SIZE 128
#define SSDP_MODEL_NAME_SIZE 64
#define SSDP_MODEL_URL_SIZE 128
#define SSDP_MODEL_VERSION_SIZE 32
#define SSDP_MANUFACTURER_SIZE 64
#define SSDP_MANUFACTURER_URL_SIZE 128
#define SSDP_MAX_REPLY_SLOTS 5
#define SSDP_MAX_DELAY 10000
typedef enum {
NONE,
SEARCH,
NOTIFY
} ssdp_method_t;
typedef struct {
unsigned long _process_time;
short _delay;
IPAddress _respondToAddr;
uint16_t _respondToPort;
char _respondType[SSDP_DEVICE_TYPE_SIZE];
char _usn_suffix[SSDP_DEVICE_TYPE_SIZE];
} ssdp_reply_slot_item_t;
class SSDPClass
{
public:
SSDPClass();
~SSDPClass();
bool begin();
void end();
void schema(WiFiClient client, bool sendHeaders = true) __attribute__((deprecated));
const char * schema(bool includeheader = true) __attribute__((deprecated));
const char * getSchema();
void setDeviceType(const String& deviceType)
{
setDeviceType(deviceType.c_str());
}
void setDeviceType(const char *deviceType);
void setName(const String& name)
{
setName(name.c_str());
}
void setName(const char *name);
void setURL(const String& url)
{
setURL(url.c_str());
}
void setURL(const char *url);
void setSchemaURL(const String& url)
{
setSchemaURL(url.c_str());
}
void setSchemaURL(const char *url);
void setSerialNumber(const String& serialNumber)
{
setSerialNumber(serialNumber.c_str());
}
void setSerialNumber(const char *serialNumber);
void setSerialNumber(const uint32_t serialNumber);
void setModelName(const String& name)
{
setModelName(name.c_str());
}
void setModelName(const char *name);
void setModelNumber(const String& num)
{
setModelNumber(num.c_str());
}
void setModelNumber(const char *num);
void setModelURL(const String& url)
{
setModelURL(url.c_str());
}
void setModelDescription(const String& desc)
{
setModelDescription(desc.c_str());
}
void setModelDescription(const char *desc);
void setServerName(const String& name)
{
setServerName(name.c_str());
}
void setServerName(const char *name);
void setModelURL(const char *url);
void setManufacturer(const String& name)
{
setManufacturer(name.c_str());
}
void setManufacturer(const char *name);
void setManufacturerURL(const String& url)
{
setManufacturerURL(url.c_str());
}
void setManufacturerURL(const char *url);
void setHTTPPort(uint16_t port);
void setTTL(uint8_t ttl);
void setInterval(uint32_t interval);
void setUUID(const char * uuid, bool rootonly = true);
void setServices(const char * services)
{
_services = services;
}
void setIcons(const char * icons)
{
_icons = icons;
}
protected:
void _onPacket(AsyncUDPPacket& packet);
void _send(ssdp_method_t method);
IPAddress localIP();
uint16_t _port;
uint32_t _ttl;
uint32_t _interval;
AsyncUDP _udp;
ssdp_reply_slot_item_t *_replySlots[SSDP_MAX_REPLY_SLOTS];
IPAddress _respondToAddr;
uint16_t _respondToPort;
bool _pending;
bool _stmatch;
short _delay;
unsigned long _process_time;
unsigned long _notify_time;
char _respondType[SSDP_DEVICE_TYPE_SIZE];
char _schemaURL[SSDP_SCHEMA_URL_SIZE];
char _uuid[SSDP_UUID_SIZE];
char _usn_suffix[SSDP_DEVICE_TYPE_SIZE];
char _deviceType[SSDP_DEVICE_TYPE_SIZE];
char _friendlyName[SSDP_FRIENDLY_NAME_SIZE];
char _serialNumber[SSDP_SERIAL_NUMBER_SIZE];
char _presentationURL[SSDP_PRESENTATION_URL_SIZE];
char _manufacturer[SSDP_MANUFACTURER_SIZE];
char _manufacturerURL[SSDP_MANUFACTURER_URL_SIZE];
char _modelName[SSDP_MODEL_NAME_SIZE];
char _modelURL[SSDP_MODEL_URL_SIZE];
char _modelNumber[SSDP_MODEL_VERSION_SIZE];
String _modelDescription;
String _servername;
char * _schema;
String _services;
String _icons;
};
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SSDP)
extern SSDPClass SSDP;
#endif
#endif
#endif

View File

@@ -3,11 +3,12 @@ ESP32 Simple Service Discovery Copyright (c) 2015 Hristo Gochkov
[![All Contributors](https://img.shields.io/badge/all_contributors-3-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END -->
Original (Arduino) version by Filippo Sallemi, July 23, 2014. Can be
found at: https://github.com/nomadnt/uSSDP
found at: https://github.com/nomadnt/uSSDP
[Latest stable release ![Release Version](https://img.shields.io/github/release/luc-github/ESP32SSDP.svg?style=plastic) ![Release Date](https://img.shields.io/github/release-date/luc-github/ESP32SSDP.svg?style=plastic)](https://github.com/luc-github/ESP32SSDP/releases/latest/) [![github-ci](https://github.com/luc-github/ESP32SSDP/workflows/build-ci/badge.svg)](https://github.com/luc-github/ESP32SSDP/actions/workflows/build-ci.yml)
Version 2.x for ESP32 V3.0.0 and later.
[Latest stable release ![Release Version](https://img.shields.io/github/release/luc-github/ESP32SSDP.svg?style=plastic) ![Release Date](https://img.shields.io/github/release-date/luc-github/ESP32SSDP.svg?style=plastic)](https://github.com/luc-github/ESP32SSDP/releases/latest/) [![github-ci](https://github.com/luc-github/ESP32SSDP/workflows/build-ci-v2/badge.svg)](https://github.com/luc-github/ESP32SSDP/actions/workflows/build-ci-v2.yml) [![ESP32 Core Version](https://img.shields.io/badge/ESP32-v3.0.0-yellow?style=plastic&label=ESP32)](https://github.com/espressif/arduino-esp32/releases/tag/3.0.0)
[Latest development version ![Development Version](https://img.shields.io/badge/devt-yellow?style=plastic) ![GitHub last commit (branch)](https://img.shields.io/github/last-commit/luc-github/ESP32SSDP/Dev?style=plastic)](https://github.com/luc-github/ESP32SSDP/tree/devt) [![github-ci](https://github.com/luc-github/ESP32SSDP/workflows/build-ci-dev/badge.svg)](https://github.com/luc-github/ESP32SSDP/actions/workflows/build-ci-dev.yml)
The IDF component version can be found here: https://github.com/luc-github/SSDP_IDF
@@ -22,16 +23,21 @@ 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.
> [!WARNING]
>### Disclaimer
> The software is provided 'as is,' without any warranty of any kind, expressed or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. 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.
>It is essential that you carefully read and understand this disclaimer before using this software and its components. If you do not agree with any part of this disclaimer, please refrain from using the software.
> [!NOTE]
>### Be Noted
> This version is an update for ESP32 V3.0.0 and later which is a breaking change, so this version of the library is not compatible with the previous versions of the ESP32 core. The previous version of the library is available in the branch [V1.x](https://github.com/luc-github/ESP32SSDP/tree/V1.x).
> The deprecated functions of 1.x version have been removed.
## Contributors ✨
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
@@ -52,4 +58,4 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!

View File

@@ -0,0 +1,104 @@
#include <ESP32SSDP.h>
#include <WebServer.h>
#include <WiFi.h>
const char* ssid = "********";
const char* password = "********";
WebServer HTTP(80);
void setup() {
Serial.begin(115200);
Serial.println();
Serial.println("Starting WiFi...");
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
if (WiFi.waitForConnectResult() == WL_CONNECTED) {
Serial.printf("Starting HTTP...\n");
HTTP.on("/index.html", HTTP_GET,
[]() { HTTP.send(200, "text/plain", "Hello World!"); });
HTTP.on("/description.xml", HTTP_GET, []() { HTTP.send(200, "text/xml", SSDP.getSchema());});
HTTP.begin();
// set schema xml url, nees to match http handler
//"ssdp/schema.xml" if not set
SSDP.setSchemaURL("description.xml");
// set port
// 80 if not set
SSDP.setHTTPPort(80);
// set device name
// Null string if not set
SSDP.setName("Philips hue clone");
// set Serial Number
// Null string if not set
SSDP.setSerialNumber("001788102201");
// set device url
// Null string if not set
SSDP.setURL("index.html");
// set model name
// Null string if not set
SSDP.setModelName("Philips hue bridge 2012");
// set model description
// Null string if not set
SSDP.setModelDescription("This device can be controled by WiFi");
// set model number
// Null string if not set
SSDP.setModelNumber("929000226503");
// set model url
// Null string if not set
SSDP.setModelURL("http://www.meethue.com");
// set model manufacturer name
// Null string if not set
SSDP.setManufacturer("Royal Philips Electronics");
// set model manufacturer url
// Null string if not set
SSDP.setManufacturerURL("http://www.philips.com");
// set device type
//"urn:schemas-upnp-org:device:Basic:1" if not set
SSDP.setDeviceType(
"rootdevice"); // to appear as root device, other examples:
// MediaRenderer, MediaServer ...
// set server name
//"Arduino/1.0" if not set
SSDP.setServerName("SSDPServer/1.0");
// set UUID, you can use https://www.uuidgenerator.net/
// use 38323636-4558-4dda-9188-cda0e6 + 4 last bytes of mac address if not
// set use SSDP.setUUID("daa26fa3-d2d4-4072-bc7a-a1b88ab4234a", false); for
// full UUID
SSDP.setUUID("daa26fa3-d2d4-4072-bc7a");
// Set icons list, NB: optional, this is ignored under windows
SSDP.setIcons(
"<icon>"
"<mimetype>image/png</mimetype>"
"<height>48</height>"
"<width>48</width>"
"<depth>24</depth>"
"<url>icon48.png</url>"
"</icon>");
// Set service list, NB: optional for simple device
SSDP.setServices(
"<service>"
"<serviceType>urn:schemas-upnp-org:service:SwitchPower:1</serviceType>"
"<serviceId>urn:upnp-org:serviceId:SwitchPower:1</serviceId>"
"<SCPDURL>/SwitchPower1.xml</SCPDURL>"
"<controlURL>/SwitchPower/Control</controlURL>"
"<eventSubURL>/SwitchPower/Event</eventSubURL>"
"</service>");
Serial.printf("Starting SSDP...\n");
SSDP.begin();
Serial.printf("Ready!\n");
} else {
Serial.printf("WiFi Failed\n");
while (1) {
delay(100);
}
}
}
void loop() {
HTTP.handleClient();
delay(1);
}

View File

@@ -0,0 +1,104 @@
#include "ESP32SSDP.h"
#include "ESPAsyncWebServer.h"
const char* ssid = "********";
const char* password = "********";
AsyncWebServer webserver(80);
void setup() {
Serial.begin(115200);
Serial.println();
Serial.println("Starting WiFi...");
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
if (WiFi.waitForConnectResult() == WL_CONNECTED) {
Serial.printf("Starting HTTP...\n");
webserver.on("/index.html", HTTP_GET, [&](AsyncWebServerRequest* request) {
request->send(200, "text/plain", "Hello World!");
});
webserver.on("/description.xml", HTTP_GET,
[&](AsyncWebServerRequest* request) {
request->send(200, "text/xml", SSDP.getSchema());
});
webserver.begin();
// set schema xml url, nees to match http handler
//"ssdp/schema.xml" if not set
SSDP.setSchemaURL("description.xml");
// set port
// 80 if not set
SSDP.setHTTPPort(80);
// set device name
// Null string if not set
SSDP.setName("Philips hue clone");
// set Serial Number
// Null string if not set
SSDP.setSerialNumber("001788102201");
// set device url
// Null string if not set
SSDP.setURL("index.html");
// set model name
// Null string if not set
SSDP.setModelName("Philips hue bridge 2012");
// set model description
// Null string if not set
SSDP.setModelDescription("This device can be controled by WiFi");
// set model number
// Null string if not set
SSDP.setModelNumber("929000226503");
// set model url
// Null string if not set
SSDP.setModelURL("http://www.meethue.com");
// set model manufacturer name
// Null string if not set
SSDP.setManufacturer("Royal Philips Electronics");
// set model manufacturer url
// Null string if not set
SSDP.setManufacturerURL("http://www.philips.com");
// set device type
//"urn:schemas-upnp-org:device:Basic:1" if not set
SSDP.setDeviceType(
"rootdevice"); // to appear as root device, other examples:
// MediaRenderer, MediaServer ...
// set server name
//"Arduino/1.0" if not set
SSDP.setServerName("SSDPServer/1.0");
// set UUID, you can use https://www.uuidgenerator.net/
// use 38323636-4558-4dda-9188-cda0e6 + 4 last bytes of mac address if not
// set use SSDP.setUUID("daa26fa3-d2d4-4072-bc7a-a1b88ab4234a", false); for
// full UUID
SSDP.setUUID("daa26fa3-d2d4-4072-bc7a");
// Set icons list, NB: optional, this is ignored under windows
SSDP.setIcons(
"<icon>"
"<mimetype>image/png</mimetype>"
"<height>48</height>"
"<width>48</width>"
"<depth>24</depth>"
"<url>icon48.png</url>"
"</icon>");
// Set service list, NB: optional for simple device
SSDP.setServices(
"<service>"
"<serviceType>urn:schemas-upnp-org:service:SwitchPower:1</serviceType>"
"<serviceId>urn:upnp-org:serviceId:SwitchPower:1</serviceId>"
"<SCPDURL>/SwitchPower1.xml</SCPDURL>"
"<controlURL>/SwitchPower/Control</controlURL>"
"<eventSubURL>/SwitchPower/Event</eventSubURL>"
"</service>");
Serial.printf("Starting SSDP...\n");
SSDP.begin();
Serial.printf("Ready!\n");
} else {
Serial.printf("WiFi Failed\n");
while (1) {
delay(100);
}
}
}
void loop() { delay(1); }

View File

@@ -1,5 +1,5 @@
name=ESP32SSDP
version=1.2.1
version=2.0.2
author=Me-No-Dev
maintainer=luc-github
sentence=Simple SSDP library for ESP32

View File

@@ -0,0 +1,687 @@
/*
ESP32 Simple Service Discovery
Copyright (c) 2015 Hristo Gochkov
Original (Arduino) version by Filippo Sallemi, July 23, 2014.
Can be found at: https://github.com/nomadnt/uSSDP
License (MIT license):
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.
*/
#ifdef ARDUINO_ARCH_ESP32
#include "ESP32SSDP.h"
#include <AsyncUDP.h>
#include <lwip/ip_addr.h>
#include <functional>
// #define DEBUG_SSDP Serial
// #define DEBUG_VERBOSE_SSDP
// #define DEBUG_WITH_MARLIN
#if defined(DEBUG_WITH_MARLIN)
class FlushableHardwareSerial : public HardwareSerial {
public:
FlushableHardwareSerial(int uart_nr) : HardwareSerial(uart_nr) {}
};
extern FlushableHardwareSerial flushableSerial;
#define DEBUG_SSDP flushableSerial
#endif // endif DEBUG_WITH_MARLIN
#define SSDP_INTERVAL 1200
#define SSDP_PORT 1900
#define SSDP_METHOD_SIZE 10
#define SSDP_URI_SIZE 2
#define SSDP_BUFFER_SIZE 64
#define SSDP_MULTICAST_TTL 2
static const IPAddress SSDP_MULTICAST_ADDR(239, 255, 255, 250);
#define SSDP_UUID_ROOT "38323636-4558-4dda-9188-cda0e6"
esp_netif_t *get_esp_interface_netif(esp_interface_t interface);
static const char _ssdp_response_template[] PROGMEM =
"HTTP/1.1 200 OK\r\n"
"EXT:\r\n";
static const char _ssdp_notify_template[] PROGMEM =
"NOTIFY * HTTP/1.1\r\n"
"HOST: 239.255.255.250:1900\r\n"
"NTS: ssdp:alive\r\n";
static const char _ssdp_packet_template[] PROGMEM =
"%s" // _ssdp_response_template / _ssdp_notify_template
#if (ESP_ARDUINO_VERSION_MAJOR < 3)
"CACHE-CONTROL: max-age=%u\r\n" // _interval
#else
"CACHE-CONTROL: max-age=%lu\r\n" // _interval
#endif //(ESP_ARDUINO_VERSION_MAJOR < 3)
"SERVER: %s UPNP/1.1 %s/%s\r\n" // _servername, _modelName, _modelNumber
"USN: uuid:%s%s\r\n" // _uuid, _usn_suffix
"%s: %s\r\n" // "NT" or "ST", _deviceType
"LOCATION: http://%u.%u.%u.%u:%u/%s\r\n" // WiFi.localIP(), _port,
// _schemaURL
"\r\n";
/*This need to be removed as part as deprecated, headers should be handled
* outside of library*/
static const char _ssdp_schema_header[] PROGMEM =
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/xml\r\n"
"Connection: close\r\n"
"Access-Control-Allow-Origin: *\r\n"
"\r\n";
static const char _ssdp_schema_template[] PROGMEM =
"<?xml version=\"1.0\"?>"
"<root xmlns=\"urn:schemas-upnp-org:device-1-0\">"
"<specVersion>"
"<major>1</major>"
"<minor>0</minor>"
"</specVersion>"
"<URLBase>http://%u.%u.%u.%u:%u/</URLBase>" // WiFi.localIP(), _port
"<device>"
"<deviceType>urn:schemas-upnp-org:device:%s:1</deviceType>"
"<friendlyName>%s</friendlyName>"
"<presentationURL>%s</presentationURL>"
"<serialNumber>%s</serialNumber>"
"<modelName>%s</modelName>"
"<modelDescription>%s</modelDescription>"
"<modelNumber>%s</modelNumber>"
"<modelURL>%s</modelURL>"
"<manufacturer>%s</manufacturer>"
"<manufacturerURL>%s</manufacturerURL>"
"<UDN>uuid:%s</UDN>"
"<serviceList>%s</serviceList>"
"<iconList>%s</iconList>"
"</device>"
"</root>\r\n"
"\r\n";
SSDPClass::SSDPClass() : _replySlots{NULL}, _respondToAddr{0, 0, 0, 0} {
_port = 80;
_ttl = SSDP_MULTICAST_TTL;
_interval = SSDP_INTERVAL;
_respondToPort = 0;
_pending = false;
_stmatch = false;
_delay = 0;
_process_time = 0;
_notify_time = 0;
_uuid[0] = '\0';
_usn_suffix[0] = '\0';
_respondType[0] = '\0';
_modelNumber[0] = '\0';
sprintf(_deviceType, "Basic");
_friendlyName[0] = '\0';
_presentationURL[0] = '\0';
_serialNumber[0] = '\0';
_modelName[0] = '\0';
_modelURL[0] = '\0';
_manufacturer[0] = '\0';
_manufacturerURL[0] = '\0';
_servername = "Arduino/1.0";
sprintf(_schemaURL, "ssdp/schema.xml");
_schema = nullptr;
}
SSDPClass::~SSDPClass() { end(); }
void SSDPClass::end() {
if (_schema) {
free(_schema);
_schema = nullptr;
}
if (_udp.connected()) {
_udp.close();
}
#ifdef DEBUG_SSDP
DEBUG_SSDP.printf_P(PSTR("SSDP end ... "));
#endif
}
IPAddress SSDPClass::localIP() {
#if (ESP_ARDUINO_VERSION_MAJOR < 3)
// Arduino ESP32 2.x board version
tcpip_adapter_ip_info_t ip;
if (WiFi.getMode() == WIFI_STA) {
if (tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip)) {
return IPAddress();
}
} else if (WiFi.getMode() == WIFI_OFF) {
if (tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip)) {
return IPAddress();
}
}
#else
// Arduino ESP32 3.x board version
esp_netif_ip_info_t ip;
if (WiFi.getMode() == WIFI_STA) {
if (esp_netif_get_ip_info(get_esp_interface_netif(ESP_IF_WIFI_STA), &ip)) {
return IPAddress();
}
} else if (WiFi.getMode() == WIFI_OFF) {
if (esp_netif_get_ip_info(get_esp_interface_netif(ESP_IF_ETH), &ip)) {
return IPAddress();
}
}
#endif
return IPAddress(ip.ip.addr);
}
void SSDPClass::setUUID(const char *uuid, bool rootonly) {
// no sanity check is done - TBD
if (rootonly) {
uint32_t chipId = ((uint16_t)(ESP.getEfuseMac() >> 32));
sprintf(_uuid, "%s%02x%02x%02x", uuid, (uint16_t)((chipId >> 16) & 0xff),
(uint16_t)((chipId >> 8) & 0xff), (uint16_t)chipId & 0xff);
} else {
strlcpy(_uuid, uuid, sizeof(_uuid));
}
}
bool SSDPClass::begin() {
_pending = false;
_stmatch = false;
end();
if (strlen(_uuid) == 0) {
setUUID(SSDP_UUID_ROOT);
}
#if defined(DEBUG_SSDP) && defined(DEBUG_VERBOSE_SSDP)
DEBUG_SSDP.printf("SSDP UUID: %s\n", (char *)_uuid);
#endif
if (_udp.connected()) {
#ifdef DEBUG_SSDP
DEBUG_SSDP.println("Already connected, abort begin");
#endif
return true;
}
_udp.onPacket(
[](void *arg, AsyncUDPPacket &packet) {
((SSDPClass *)(arg))->_onPacket(packet);
},
this);
if (!_udp.listenMulticast(IPAddress(SSDP_MULTICAST_ADDR), SSDP_PORT, _ttl)) {
#ifdef DEBUG_SSDP
DEBUG_SSDP.println("Error begin");
#endif
return false;
}
return true;
}
void SSDPClass::_send(ssdp_method_t method) {
char buffer[1460];
IPAddress ip = localIP();
char valueBuffer[strlen_P(_ssdp_notify_template) + 1];
strcpy_P(valueBuffer,
(method == NONE) ? _ssdp_response_template : _ssdp_notify_template);
int len =
snprintf_P(buffer, sizeof(buffer), _ssdp_packet_template, valueBuffer,
_interval, _servername.c_str(), _modelName, _modelNumber,
_uuid, _usn_suffix, (method == NONE) ? "ST" : "NT",
_respondType, ip[0], ip[1], ip[2], ip[3], _port, _schemaURL);
if (len < 0) {
#ifdef DEBUG_SSDP
DEBUG_SSDP.println("Error not enough memory for using valueBuffer");
#endif
return;
}
IPAddress remoteAddr;
uint16_t remotePort;
if (method == NONE) {
remoteAddr = _respondToAddr;
remotePort = _respondToPort;
#ifdef DEBUG_SSDP
DEBUG_SSDP.print("Sending Response to ");
#endif
} else {
remoteAddr = IPAddress(SSDP_MULTICAST_ADDR);
remotePort = SSDP_PORT;
#ifdef DEBUG_SSDP
DEBUG_SSDP.print("Sending Notify to ");
#endif
}
#ifdef DEBUG_SSDP
DEBUG_SSDP.print(remoteAddr);
DEBUG_SSDP.print(":");
DEBUG_SSDP.println(remotePort);
#endif
_udp.writeTo((const uint8_t *)buffer, len, remoteAddr, remotePort);
#if defined(DEBUG_SSDP) && defined(DEBUG_VERBOSE_SSDP)
DEBUG_SSDP.println("*************************TX*************************");
DEBUG_SSDP.println(buffer);
DEBUG_SSDP.println("****************************************************");
#endif
}
const char *SSDPClass::getSchema() {
uint len = strlen(_ssdp_schema_template) + 21 //(IP = 15) + 1 (:) + 5 (port)
+ SSDP_DEVICE_TYPE_SIZE + SSDP_FRIENDLY_NAME_SIZE +
SSDP_SCHEMA_URL_SIZE + SSDP_SERIAL_NUMBER_SIZE +
SSDP_MODEL_NAME_SIZE + _modelDescription.length() +
SSDP_MODEL_VERSION_SIZE + SSDP_MODEL_URL_SIZE +
SSDP_MANUFACTURER_SIZE + SSDP_MANUFACTURER_URL_SIZE +
SSDP_UUID_SIZE + _services.length() + _icons.length();
if (_schema) {
free(_schema);
_schema = nullptr;
}
_schema = (char *)malloc(len + 1);
if (_schema) {
IPAddress ip = localIP();
sprintf(_schema, _ssdp_schema_template, ip[0], ip[1], ip[2], ip[3], _port,
_deviceType, _friendlyName, _presentationURL, _serialNumber,
_modelName, _modelDescription.c_str(), _modelNumber, _modelURL,
_manufacturer, _manufacturerURL, _uuid, _services.c_str(),
_icons.c_str());
} else {
#ifdef DEBUG_SSDP
DEBUG_SSDP.println("not enough memory for schema");
#endif
}
return _schema;
}
void SSDPClass::_onPacket(AsyncUDPPacket &packet) {
if (packet.length() == 0) {
return;
}
int nbBytes = 0;
char *packetBuffer = nullptr;
if (!_pending) {
ssdp_method_t method = NONE;
nbBytes = packet.length();
typedef enum { METHOD, URI, PROTO, KEY, VALUE, ABORT } states;
states state = METHOD;
typedef enum { STRIP, START, SKIP, MAN, ST, MX } headers;
headers header = STRIP;
uint8_t cursor = 0;
uint8_t cr = 0;
char buffer[SSDP_BUFFER_SIZE] = {0};
packetBuffer = new char[nbBytes + 1];
if (packetBuffer == nullptr) {
#ifdef DEBUG_SSDP
DEBUG_SSDP.println("not enough memory for the packet");
#endif
return;
}
int process_pos = 0;
strncpy(packetBuffer, (const char *)packet.data(), nbBytes);
packetBuffer[nbBytes] = '\0';
_respondToAddr = packet.remoteIP();
_respondToPort = packet.remotePort();
#if defined(DEBUG_SSDP) && defined(DEBUG_VERBOSE_SSDP)
if (nbBytes) {
DEBUG_SSDP.println(
"*************************RX*************************");
DEBUG_SSDP.print(packet.remoteIP());
DEBUG_SSDP.print(":");
DEBUG_SSDP.println(packet.remotePort());
DEBUG_SSDP.println(packetBuffer);
DEBUG_SSDP.println(
"****************************************************");
}
#endif
while (process_pos < nbBytes) {
char c = packetBuffer[process_pos];
process_pos++;
(c == '\r' || c == '\n') ? cr++ : cr = 0;
switch (state) {
case METHOD:
if (c == ' ') {
if (strcmp(buffer, "M-SEARCH") == 0) {
method = SEARCH;
}
if (method == NONE) {
state = ABORT;
} else {
state = URI;
}
cursor = 0;
} else if (cursor < SSDP_METHOD_SIZE - 1) {
buffer[cursor++] = c;
buffer[cursor] = '\0';
}
break;
case URI:
if (c == ' ') {
if (strcmp(buffer, "*")) {
state = ABORT;
} else {
state = PROTO;
}
cursor = 0;
} else if (cursor < SSDP_URI_SIZE - 1) {
buffer[cursor++] = c;
buffer[cursor] = '\0';
}
break;
case PROTO:
if (cr == 2) {
state = KEY;
cursor = 0;
}
break;
case KEY:
// end of HTTP request parsing. If we find a match start reply delay.
if (cr == 4) {
if (_stmatch) {
_pending = true;
_process_time = millis();
}
} else if (c == ':') {
cursor = 0;
state = VALUE;
} else if (c != '\r' && c != '\n' && c != ' ' &&
cursor < SSDP_BUFFER_SIZE - 1) {
buffer[cursor++] = c;
buffer[cursor] = '\0';
}
break;
case VALUE:
if (cr == 2) {
switch (header) {
case START:
#ifdef DEBUG_SSDP
DEBUG_SSDP.println("***********************");
#endif
case STRIP:
case SKIP:
break;
case MAN:
#ifdef DEBUG_SSDP
DEBUG_SSDP.printf("MAN: %s\n", (char *)buffer);
#endif
break;
case ST:
// save the search term for the reply and clear usn suffix.
strlcpy(_respondType, buffer, sizeof(_respondType));
_usn_suffix[0] = '\0';
#ifdef DEBUG_SSDP
DEBUG_SSDP.printf("ST: '%s'\n", buffer);
#endif
// if looking for all or root reply with upnp:rootdevice
if (strcmp(buffer, "ssdp:all") == 0 ||
strcmp(buffer, "upnp:rootdevice") == 0) {
_stmatch = true;
// set USN suffix
strlcpy(_usn_suffix, "::upnp:rootdevice",
sizeof(_usn_suffix));
#ifdef DEBUG_SSDP
DEBUG_SSDP.println("the search type matches all and root");
#endif
state = KEY;
} else
// if the search type matches our type, we should respond
// instead of ABORT
if (strcasecmp(buffer, _deviceType) == 0) {
_stmatch = true;
// set USN suffix to the device type
strlcpy(_usn_suffix, "::", sizeof(_usn_suffix));
strlcat(_usn_suffix, _deviceType, sizeof(_usn_suffix));
#ifdef DEBUG_SSDP
DEBUG_SSDP.println("the search type matches our type");
#endif
state = KEY;
} else {
state = ABORT;
#ifdef DEBUG_SSDP
DEBUG_SSDP.println(
"REJECT. The search type does not match our type");
DEBUG_SSDP.println("***********************");
#endif
}
break;
case MX:
// delay in ms from 0 to MX*1000 where MX is in seconds with
// limits.
_delay = (short)random(0, atoi(buffer) * 1000L);
if (_delay > SSDP_MAX_DELAY) {
_delay = SSDP_MAX_DELAY;
}
break;
}
if (state != ABORT) {
state = KEY;
header = STRIP;
cursor = 0;
}
} else if (c != '\r' && c != '\n') {
if (header == STRIP) {
if (c == ' ') {
break;
} else {
header = START;
}
}
if (header == START) {
if (strncmp(buffer, "MA", 2) == 0) {
header = MAN;
} else if (strcmp(buffer, "ST") == 0) {
header = ST;
} else if (strcmp(buffer, "MX") == 0) {
header = MX;
} else {
header = SKIP;
}
}
if (cursor < SSDP_BUFFER_SIZE - 1) {
buffer[cursor++] = c;
buffer[cursor] = '\0';
}
}
break;
case ABORT:
_pending = false;
_delay = 0;
break;
}
}
}
if (packetBuffer) {
delete[] packetBuffer;
}
// save reply in reply queue if one is pending
if (_pending) {
int i;
// Many UPNP hosts send out mulitple M-SEARCH packets at the same time to
// mitigate packet loss. Just reply to one for a given host:port.
for (i = 0; i < SSDP_MAX_REPLY_SLOTS; i++) {
if (_replySlots[i]) {
if (_replySlots[i]->_respondToPort == _respondToPort &&
_replySlots[i]->_respondToAddr == _respondToAddr) {
// keep original delay
_delay = _replySlots[i]->_delay;
_process_time = _replySlots[i]->_process_time;
#ifdef DEBUG_SSDP
DEBUG_SSDP.printf("Remove duplicate SSDP reply in slot %i.\n", i);
#endif
delete _replySlots[i];
_replySlots[i] = 0;
}
}
}
// save packet to available reply queue slot
for (i = 0; i < SSDP_MAX_REPLY_SLOTS; i++) {
if (!_replySlots[i]) {
#ifdef DEBUG_SSDP
DEBUG_SSDP.printf("Saving deferred SSDP reply to queue slot %i.\n", i);
DEBUG_SSDP.println("***********************");
#endif
_replySlots[i] = new ssdp_reply_slot_item_t;
if (_replySlots[i]) {
_replySlots[i]->_process_time = _process_time;
_replySlots[i]->_delay = _delay;
_replySlots[i]->_respondToAddr = _respondToAddr;
_replySlots[i]->_respondToPort = _respondToPort;
strlcpy(_replySlots[i]->_respondType, _respondType,
sizeof(_replySlots[i]->_respondType));
strlcpy(_replySlots[i]->_usn_suffix, _usn_suffix,
sizeof(_replySlots[i]->_usn_suffix));
}
break;
}
}
#ifdef DEBUG_SSDP
if (i == SSDP_MAX_REPLY_SLOTS) {
DEBUG_SSDP.println("SSDP reply queue is full dropping packet.");
}
#endif
_pending = false;
_delay = 0;
}
// send any packets that are pending and overdue.
unsigned long t = millis();
bool sent = false;
for (int i = 0; i < SSDP_MAX_REPLY_SLOTS; i++) {
if (_replySlots[i]) {
// millis delay with overflow protection.
if (t - _replySlots[i]->_process_time > _replySlots[i]->_delay) {
// reply ready. restore and send.
_respondToAddr = _replySlots[i]->_respondToAddr;
_respondToPort = _replySlots[i]->_respondToPort;
strlcpy(_respondType, _replySlots[i]->_respondType,
sizeof(_respondType));
strlcpy(_usn_suffix, _replySlots[i]->_usn_suffix, sizeof(_usn_suffix));
#ifdef DEBUG_SSDP
DEBUG_SSDP.printf("Slot(%d) ", i);
DEBUG_SSDP.println("Send None");
#endif
_send(NONE);
sent = true;
delete _replySlots[i];
_replySlots[i] = 0;
#ifdef DEBUG_SSDP
DEBUG_SSDP.println("***********************");
#endif
}
}
}
#if defined(DEBUG_SSDP) && defined(DEBUG_VERBOSE_SSDP)
uint8_t rcount = 0;
DEBUG_SSDP.print("SSDP reply queue status: [");
for (int i = 0; i < SSDP_MAX_REPLY_SLOTS; i++) {
DEBUG_SSDP.print(_replySlots[i] ? "X" : "-");
}
DEBUG_SSDP.println("]");
#endif
if (_notify_time == 0 || (millis() - _notify_time) > (_interval * 1000L)) {
_notify_time = millis();
// send notify with our root device type
strlcpy(_respondType, "upnp:rootdevice", sizeof(_respondType));
strlcpy(_usn_suffix, "::upnp:rootdevice", sizeof(_usn_suffix));
#ifdef DEBUG_SSDP
DEBUG_SSDP.println("Send Notify");
#endif
_send(NOTIFY);
sent = true;
#ifdef DEBUG_SSDP
DEBUG_SSDP.println("***********************");
#endif
}
if (!sent) {
#if defined(DEBUG_SSDP) && defined(DEBUG_VERBOSE_SSDP)
DEBUG_SSDP.println("Do not sent");
#endif
}
}
void SSDPClass::setSchemaURL(const char *url) {
strlcpy(_schemaURL, url, sizeof(_schemaURL));
}
void SSDPClass::setHTTPPort(uint16_t port) { _port = port; }
void SSDPClass::setDeviceType(const char *deviceType) {
strlcpy(_deviceType, deviceType, sizeof(_deviceType));
}
void SSDPClass::setName(const char *name) {
strlcpy(_friendlyName, name, sizeof(_friendlyName));
}
void SSDPClass::setURL(const char *url) {
strlcpy(_presentationURL, url, sizeof(_presentationURL));
}
void SSDPClass::setSerialNumber(const char *serialNumber) {
strlcpy(_serialNumber, serialNumber, sizeof(_serialNumber));
}
void SSDPClass::setSerialNumber(const uint32_t serialNumber) {
snprintf(_serialNumber, sizeof(uint32_t) * 2 + 1, "%08X",
(unsigned int)serialNumber);
}
void SSDPClass::setModelName(const char *name) {
strlcpy(_modelName, name, sizeof(_modelName));
}
void SSDPClass::setModelDescription(const char *desc) {
_modelDescription = desc;
}
void SSDPClass::setServerName(const char *name) { _servername = name; }
void SSDPClass::setModelNumber(const char *num) {
strlcpy(_modelNumber, num, sizeof(_modelNumber));
}
void SSDPClass::setModelURL(const char *url) {
strlcpy(_modelURL, url, sizeof(_modelURL));
}
void SSDPClass::setManufacturer(const char *name) {
strlcpy(_manufacturer, name, sizeof(_manufacturer));
}
void SSDPClass::setManufacturerURL(const char *url) {
strlcpy(_manufacturerURL, url, sizeof(_manufacturerURL));
}
void SSDPClass::setTTL(const uint8_t ttl) { _ttl = ttl; }
void SSDPClass::setInterval(uint32_t interval) { _interval = interval; }
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SSDP)
SSDPClass SSDP;
#endif
#endif

View File

@@ -0,0 +1,154 @@
/*
ESP32 Simple Service Discovery
Copyright (c) 2015 Hristo Gochkov
Original (Arduino) version by Filippo Sallemi, July 23, 2014.
Can be found at: https://github.com/nomadnt/uSSDP
License (MIT license):
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.
*/
#ifdef ARDUINO_ARCH_ESP32
#ifndef ESP32SSDP_H
#define ESP32SSDP_H
#include <Arduino.h>
#include <AsyncUDP.h>
#include <WiFi.h>
#define SSDP_UUID_SIZE 37
#define SSDP_SCHEMA_URL_SIZE 64
#define SSDP_DEVICE_TYPE_SIZE 64
#define SSDP_FRIENDLY_NAME_SIZE 64
#define SSDP_SERIAL_NUMBER_SIZE 32
#define SSDP_PRESENTATION_URL_SIZE 128
#define SSDP_MODEL_NAME_SIZE 64
#define SSDP_MODEL_URL_SIZE 128
#define SSDP_MODEL_VERSION_SIZE 32
#define SSDP_MANUFACTURER_SIZE 64
#define SSDP_MANUFACTURER_URL_SIZE 128
#define SSDP_MAX_REPLY_SLOTS 5
#define SSDP_MAX_DELAY 10000
typedef struct {
unsigned long _process_time;
short _delay;
IPAddress _respondToAddr;
uint16_t _respondToPort;
char _respondType[SSDP_DEVICE_TYPE_SIZE];
char _usn_suffix[SSDP_DEVICE_TYPE_SIZE];
} ssdp_reply_slot_item_t;
class SSDPClass {
public:
SSDPClass();
~SSDPClass();
bool begin();
void end();
const char* getSchema();
void setDeviceType(const String& deviceType) {
setDeviceType(deviceType.c_str());
}
void setDeviceType(const char* deviceType);
void setName(const String& name) { setName(name.c_str()); }
void setName(const char* name);
void setURL(const String& url) { setURL(url.c_str()); }
void setURL(const char* url);
void setSchemaURL(const String& url) { setSchemaURL(url.c_str()); }
void setSchemaURL(const char* url);
void setSerialNumber(const String& serialNumber) {
setSerialNumber(serialNumber.c_str());
}
void setSerialNumber(const char* serialNumber);
void setSerialNumber(const uint32_t serialNumber);
void setModelName(const String& name) { setModelName(name.c_str()); }
void setModelName(const char* name);
void setModelNumber(const String& num) { setModelNumber(num.c_str()); }
void setModelNumber(const char* num);
void setModelURL(const String& url) { setModelURL(url.c_str()); }
void setModelDescription(const String& desc) {
setModelDescription(desc.c_str());
}
void setModelDescription(const char* desc);
void setServerName(const String& name) { setServerName(name.c_str()); }
void setServerName(const char* name);
void setModelURL(const char* url);
void setManufacturer(const String& name) { setManufacturer(name.c_str()); }
void setManufacturer(const char* name);
void setManufacturerURL(const String& url) {
setManufacturerURL(url.c_str());
}
void setManufacturerURL(const char* url);
void setHTTPPort(uint16_t port);
void setTTL(uint8_t ttl);
void setInterval(uint32_t interval);
void setUUID(const char* uuid, bool rootonly = true);
void setServices(const char* services) { _services = services; }
void setIcons(const char* icons) { _icons = icons; }
protected:
typedef enum { NONE, SEARCH, NOTIFY } ssdp_method_t;
void _onPacket(AsyncUDPPacket& packet);
void _send(ssdp_method_t method);
IPAddress localIP();
uint16_t _port;
uint32_t _ttl;
uint32_t _interval;
AsyncUDP _udp;
ssdp_reply_slot_item_t* _replySlots[SSDP_MAX_REPLY_SLOTS];
IPAddress _respondToAddr;
uint16_t _respondToPort;
bool _pending;
bool _stmatch;
short _delay;
unsigned long _process_time;
unsigned long _notify_time;
char _respondType[SSDP_DEVICE_TYPE_SIZE];
char _schemaURL[SSDP_SCHEMA_URL_SIZE];
char _uuid[SSDP_UUID_SIZE];
char _usn_suffix[SSDP_DEVICE_TYPE_SIZE];
char _deviceType[SSDP_DEVICE_TYPE_SIZE];
char _friendlyName[SSDP_FRIENDLY_NAME_SIZE];
char _serialNumber[SSDP_SERIAL_NUMBER_SIZE];
char _presentationURL[SSDP_PRESENTATION_URL_SIZE];
char _manufacturer[SSDP_MANUFACTURER_SIZE];
char _manufacturerURL[SSDP_MANUFACTURER_URL_SIZE];
char _modelName[SSDP_MODEL_NAME_SIZE];
char _modelURL[SSDP_MODEL_URL_SIZE];
char _modelNumber[SSDP_MODEL_VERSION_SIZE];
String _modelDescription;
String _servername;
char* _schema;
String _services;
String _icons;
};
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SSDP)
extern SSDPClass SSDP;
#endif
#endif
#endif

View File

@@ -0,0 +1,50 @@
#!/usr/bin/python
import os
import subprocess
def format_sources():
"""
Formats the source code files in the ESP3D project using clang-format with Google style.
This script recursively searches for C, C++, H, and INO files in the ESP3D project directory
and its subdirectories. It then applies the clang-format tool to each file, using the Google
style for formatting.
Note: Make sure you have clang-format installed and available in your system's PATH.
Returns:
None
"""
# Base directory of the script
script_path = os.path.abspath(__file__)
# Extract dir path
script_dir = os.path.dirname(script_path)
# Build path of sources dir: ../esp3d
src_dir = os.path.abspath(os.path.normpath(os.path.join(script_dir, '..', 'src')))
examples_dir = os.path.abspath(os.path.normpath(os.path.join(script_dir, '..', 'examples')))
# Parse all c, h, cpp, and ino files in all directories and subdirectories
file_paths = []
for base_dir in [src_dir, examples_dir]:
for root, dirs, files in os.walk(base_dir):
for file in files:
if file.endswith(('.c', '.cpp', '.h', '.ino')):
file_path = os.path.join(root, file)
file_paths.append(os.path.abspath(os.path.normpath(file_path)))
# Now format all files one by one with clang-format
for file_path in file_paths:
tmpPath = '"' + file_path + '"'
print("Formatting " + tmpPath, end="")
try:
command = ['clang-format', '-i', '--style=Google', file_path]
subprocess.run(command, check=False)
print(" => Ok")
except subprocess.CalledProcessError as e:
print(f'=> Error: {e}')
# Call the format_sources function to format the source code
format_sources()

View File

@@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: "[FEATURE REQUEST]<Enter comprehensive title>"
labels: Feature request
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -1,49 +0,0 @@
# Configuration for probot-stale - https://github.com/probot/stale
# Number of days of inactivity before an Issue or Pull Request becomes stale
daysUntilStale: 21
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
daysUntilClose: 3
# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled)
onlyLabels: []
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
exemptLabels:
- pinned
- security
- "Under Investigation"
- "Work in progress"
- Planned
- "Feedback Welcome"
# Set to true to ignore issues in a project (defaults to false)
exemptProjects: false
# Set to true to ignore issues in a milestone (defaults to false)
exemptMilestones: false
# Set to true to ignore issues with an assignee (defaults to false)
exemptAssignees: false
# Label to use when marking as stale
staleLabel: stale
# Comment to post when marking as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when removing the stale label.
unmarkComment: >
This issue has been removed from the stale queue. Please ensure activity to keep it openin the future.
# Comment to post when closing a stale Issue or Pull Request.
closeComment: >
This stale issue has been automatically closed. Thank you for your contributions.
# Limit the number of actions per hour, from 1-30. Default is 30
limitPerRun: 30
# Limit to only `issues` or `pulls`
only: issues

View File

@@ -1,53 +0,0 @@
# EspLuaEngine for ESP32
## 📚 API Reference
### `EspLuaEngine()`
Constructor. Initializes a new Lua state and loads standard libraries.
### `~EspLuaEngine()`
Destructor. Closes the Lua state and frees resources.
### `bool executeScript(const char* script)`
Executes a Lua script.
- **Parameters:**
- `script`: A null-terminated string containing the Lua code to execute.
- **Returns:** `true` if the script executes successfully, `false` otherwise.
- **Note:** This is a blocking function. For long-running scripts, consider integrating it into a separate task to allow the rest of your firmware to continue running.
### `bool registerFunction(const char* name, lua_CFunction function)`
Registers a C function to be callable from Lua scripts.
- **Parameters:**
- `name`: The name to use for the function in Lua.
- `function`: A pointer to the C function to register.
- **Returns:** `true` if the function is successfully registered, `false` otherwise.
### `template<typename T> bool registerConstant(const char* name, T value)`
Registers a constant value that can be accessed from Lua scripts.
- **Parameters:**
- `name`: The name to use for the constant in Lua.
- `value`: The value of the constant. Supported types include `lua_Number`, `const char*`, `bool`, `int`, and `unsigned char`.
- **Returns:** `true` if the constant is successfully registered, `false` otherwise.
## 🔧 Usage Examples
```cpp
EspLuaEngine lua;
// Register a custom function
lua.registerFunction("myFunction", l_myCustomFunction);
// Register constants
lua.registerConstant("PI", 3.14159);
lua.registerConstant("PROJECT_NAME", "MyESP32Project");
lua.registerConstant("DEBUG_MODE", true);
// Execute a Lua script
lua.executeScript(R"(
print("PI is: " .. PI)
print("Project: " .. PROJECT_NAME)
myFunction()
)");
```

View File

@@ -0,0 +1,142 @@
# EspLuaEngine for ESP32
## 📚 API Reference
### `EspLuaEngine()`
Constructor. Initializes a new Lua state and loads standard libraries.
### `~EspLuaEngine()`
Destructor. Closes the Lua state and frees resources.
### `bool executeScript(const char* script)`
Executes a Lua script.
- **Parameters:**
- `script`: A null-terminated string containing the Lua code to execute.
- **Returns:** `true` if the script executes successfully, `false` otherwise.
- **Note:** This function is non-blocking and supports pause and stop operations.
### `bool registerFunction(const char* name, lua_CFunction function, void* userData = nullptr)`
Registers a C function to be callable from Lua scripts.
- **Parameters:**
- `name`: The name to use for the function in Lua.
- `function`: A pointer to the C function to register.
- `userData`: Optional user data to be passed to the function.
- **Returns:** `true` if the function is successfully registered, `false` otherwise.
### `template<typename T> bool registerConstant(const char* name, T value)`
Registers a constant value that can be accessed from Lua scripts.
- **Parameters:**
- `name`: The name to use for the constant in Lua.
- `value`: The value of the constant. Supported types include `lua_Number`, `const char*`, `bool`, `int`, and `unsigned char`.
- **Returns:** `true` if the constant is successfully registered, `false` otherwise.
### `void resetState()`
Resets the Lua state, clearing all registered functions and constants.
### `void setPauseFunction(PauseFunction func)`
Sets a custom function to be called when execution is paused.
- **Parameters:**
- `func`: A function of type `std::function<void(EspLuaEngine*)>` to be called during pauses.
### `void pauseExecution()`
Pauses the execution of the current script.
### `void resumeExecution()`
Resumes the execution of a paused script.
### `void stopExecution()`
Stops the execution of the current script.
### `bool isPaused()`
Checks if the script execution is currently paused.
- **Returns:** `true` if paused, `false` otherwise.
### `bool isRunning()`
Checks if a script is currently running.
- **Returns:** `true` if running, `false` otherwise.
### `Status getStatus()`
Gets the current status of the EspLuaEngine.
- **Returns:** An enum of type `EspLuaEngine::Status` with possible values:
- `Idle`: No script is currently running.
- `Running`: A script is currently executing.
- `Paused`: Script execution is paused.
### `bool hasError()`
Checks if an error occurred during the last script execution.
- **Returns:** `true` if an error occurred, `false` otherwise.
### `const char* getLastError()`
Gets the last error message.
- **Returns:** A string containing the last error message, or an empty string if no error occurred.
### `lua_State* getLuaState()`
Gets the underlying Lua state.
- **Returns:** A pointer to the `lua_State` object.
## 🔧 Usage Examples
```cpp
EspLuaEngine lua;
// Register a custom function
lua.registerFunction("myFunction", l_myCustomFunction);
// Register constants
lua.registerConstant("PI", 3.14159);
lua.registerConstant("PROJECT_NAME", "MyESP32Project");
lua.registerConstant("DEBUG_MODE", true);
// Execute a Lua script
if (lua.executeScript(R"(
print("PI is: " .. PI)
print("Project: " .. PROJECT_NAME)
myFunction()
)")) {
Serial.println("Script executed successfully");
} else {
Serial.print("Error executing script: ");
Serial.println(lua.getLastError());
}
// Pause and resume execution
lua.pauseExecution();
// Do something while paused
lua.resumeExecution();
// Check status
if (lua.getStatus() == EspLuaEngine::Status::Running) {
Serial.println("Script is running");
}
// Stop execution
lua.stopExecution();
// Reset state
lua.resetState();
```
## 📝 Notes
- The EspLuaEngine now supports non-blocking script execution with pause and stop capabilities.
- Use `setPauseFunction()` to define custom behavior during pauses.
- Always check for errors after executing a script using `hasError()` and `getLastError()`.
- The engine supports multitasking environments, making it suitable for complex ESP32 projects.
- **Important note for ESP8266 users:** While state monitoring and control are straightforward on ESP32 due to its task management capabilities, implementation on ESP8266 may require additional libraries or the use of an interrupt system. Currently, this functionality is not fully supported on ESP8266 platforms.
## 🚀 Platform-Specific Considerations
### ESP32
On ESP32, the EspLuaEngine takes full advantage of the FreeRTOS task management system, allowing for efficient multitasking and state control without additional setup.
### ESP8266
For ESP8266 users:
- The current implementation may not fully support all state monitoring and control features.
- To achieve similar functionality as on ESP32, you might need to:
1. Implement a custom interrupt-based system for state checks.
2. Use additional libraries for task management (e.g., `TaskScheduler`).
3. Carefully manage your main loop to prevent blocking while allowing for state checks.
Please note that these advanced features on ESP8266 are not officially supported in the current version of EspLuaEngine and may require custom modifications to the library.

View File

@@ -1,5 +1,5 @@
name=EspLuaEngine
version=1.0.1
version=1.0.2
author=Luc LEBOSSE <luc.lebosse@tech-hunters.com>
maintainer=Luc LEBOSSE <luc.lebosse@tech-hunters.com>
sentence=Lua engine for ESP

View File

@@ -19,9 +19,28 @@
*/
#include "EspLuaEngine.h"
#define ESP_LUA_NB_LINES_BEFORE_HOOK 1000
#include <Arduino.h>
#if defined(ARDUINO_ARCH_ESP32)
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#endif // defined(ARDUINO_ARCH_ESP32)
#if defined(ARDUINO_ARCH_ESP8266)
// #define DEBUG_ESP_LUA_ENGINE Serial
#if defined(DEBUG_ESP_LUA_ENGINE)
#define log_e(format, ...) \
DEBUG_ESP_LUA_ENGINE.printf("E: " format "\n", ##__VA_ARGS__)
#define log_v(format, ...) \
DEBUG_ESP_LUA_ENGINE.printf("V: " format "\n", ##__VA_ARGS__)
#else
#define log_e(format, ...)
#define log_v(format, ...)
#endif // defined(DEBUG_ESP_LUA_ENGINE)
#endif // defined(ARDUINO_ARCH_ESP8266)
EspLuaEngine::PauseFunction EspLuaEngine::_pauseFunction = nullptr;
String EspLuaEngine::_lastError;
/*Public methods*/
@@ -40,15 +59,112 @@ EspLuaEngine::~EspLuaEngine() {
}
}
bool EspLuaEngine::executeScript(const char* script) {
if (luaL_dostring(_lua_state, script) != LUA_OK) {
log_e("%s", lua_tostring(_lua_state, -1));
lua_pop(_lua_state, 1);
return false;
}
return true;
void EspLuaEngine::setPauseFunction(PauseFunction func) {
_pauseFunction = func;
}
void EspLuaEngine::_defaultPauseFunction() {
#if defined(ARDUINO_ARCH_ESP32)
vTaskDelay(ESP_LUA_CHECK_INTERVAL);
#endif // defined(ARDUINO_ARCH_ESP32)
#if defined(ARDUINO_ARCH_ESP8266)
delay(ESP_LUA_CHECK_INTERVAL);
#endif // defined(ARDUINO_ARCH_ESP8266)
}
void EspLuaEngine::hookFunction(lua_State* L, lua_Debug* ar) {
if (_isPaused.load()) {
while (_isPaused.load() && _isRunning.load()) {
if (_pauseFunction) {
_pauseFunction();
} else {
_defaultPauseFunction();
}
}
}
if (!_isRunning.load()) {
if (_lastError.length() == 0) _lastError = "Execution stopped";
luaL_error(L, "Execution stopped");
}
}
void EspLuaEngine::resetState() {
if (_lua_state) {
lua_close(_lua_state);
_lua_state = luaL_newstate();
if (_lua_state) {
_loadLibraries();
} else {
log_e("Error: Impossible to create a new Lua state");
}
}
}
bool EspLuaEngine::executeScript(const char* script) {
_lastError=""; // Clear the error message
_isPaused.store(false);
_isRunning.store(true);
// Configure the hook function
lua_sethook(_lua_state, hookFunction, LUA_MASKCOUNT,
ESP_LUA_NB_LINES_BEFORE_HOOK);
bool success = true;
// Compile the script
if (luaL_loadstring(_lua_state, script) != LUA_OK) {
if (_lastError.length() == 0) _lastError = lua_tostring(_lua_state, -1);
log_e("Error loading script: %s", _lastError.c_str());
lua_pop(_lua_state, 1);
success = false;
} else {
// Execute the script
if (lua_pcall(_lua_state, 0, 0, 0) != LUA_OK) {
if (_lastError.length() == 0) _lastError = lua_tostring(_lua_state, -1);
log_e("Error executing script: %s", _lastError.c_str());
lua_pop(_lua_state, 1);
success = false;
}
}
// Disable the hook function
lua_sethook(_lua_state, nullptr, 0, 0);
_isRunning.store(false);
_isPaused.store(false);
return success;
}
void EspLuaEngine::pauseExecution() {
if (_isRunning.load()) {
_isPaused.store(true);
}
}
void EspLuaEngine::resumeExecution() { _isPaused.store(false); }
void EspLuaEngine::stopExecution() {
_isRunning.store(false);
_isPaused.store(false);
if (_lastError.length() == 0) _lastError = "Execution stopped by user";
}
bool EspLuaEngine::isRunning() { return _isRunning.load(); }
bool EspLuaEngine::isPaused() { return _isPaused.load(); }
EspLuaEngine::Status EspLuaEngine::getStatus() {
if (_isPaused.load()) {
return Status::Paused;
} else if (_isRunning.load()) {
return Status::Running;
} else {
return Status::Idle;
}
}
bool EspLuaEngine::hasError() { return _lastError.length() > 0; }
bool EspLuaEngine::registerFunction(const char* name, lua_CFunction function,
void* userData) {
if (!_checkPreconditions(name) || !function) {
@@ -222,4 +338,4 @@ bool EspLuaEngine::_makeReadOnly(const char* name) {
lua_setmetatable(_lua_state, -2);
lua_pop(_lua_state, 2);
return true;
}
}

View File

@@ -20,22 +20,62 @@
#pragma once
#define LUA_USE_C89
#include <Arduino.h>
#include <atomic>
#include <functional>
#include "lua-5.4.7/src/lua.hpp"
#if defined(ARDUINO_ARCH_ESP32)
#define ESP_LUA_CHECK_INTERVAL pdMS_TO_TICKS(10)
#endif // defined(ARDUINO_ARCH_ESP32)
#if defined(ARDUINO_ARCH_ESP8266)
#define ESP_LUA_CHECK_INTERVAL 10
#endif // defined(ARDUINO_ARCH_ESP8266)
class EspLuaEngine {
public:
enum class Status {
Idle,
Running,
Paused,
};
EspLuaEngine();
~EspLuaEngine();
using PauseFunction = std::function<void(void)>;
bool executeScript(const char* script);
bool registerFunction(const char* name, lua_CFunction function,
void* userData = nullptr);
template <typename T>
bool registerConstant(const char* name, T value);
lua_State* getLuaState() { return _lua_state; }
const char* getLastError() { return _lastError.c_str(); }
void resetState();
void setPauseFunction(PauseFunction func);
static void pauseExecution();
static void resumeExecution();
static void stopExecution();
bool isPaused();
bool isRunning();
Status getStatus();
bool hasError();
private:
lua_State* _lua_state;
static PauseFunction _pauseFunction;
static String _lastError;
static inline std::atomic<bool> _isPaused{false};
static inline std::atomic<bool> _isRunning{false};
static void hookFunction(lua_State* L, lua_Debug* ar);
static void _defaultPauseFunction();
void _loadLibraries();
bool _checkPreconditions(const char* name);
bool _verifyGlobal(const char* name, int type);
@@ -46,3 +86,5 @@ class EspLuaEngine {
bool _registerConstantImpl(const char* name, bool value);
bool _registerConstantImpl(const char* name, int value);
};
using EspLuaStatus = EspLuaEngine::Status;

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

Some files were not shown because too many files have changed in this diff Show More