diff --git a/README.md b/README.md index 04e58727..b9476dee 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ *All plugins depend upon the presence of the [H4 library](https://github.com/philbowles/H4), which must be installed first.* --- -Version **0.2.1** [Release Notes](changelog.txt) +Version **0.3.4** [Release Notes](changelog.txt) **MUST UPGRADE TO [H4 library](https://github.com/philbowles/H4) v0.4.1 first!** ![H4PluginsFF](/assets/h4plugins.jpg) @@ -49,7 +49,7 @@ As you can see, all you need to do is list the modules you want (in the right or The modular design of H4's plugin architecture minimises scarce resources in low-memory MCU targets: You only compile in what you need with a simple `#include`. Detailed diagnostics can be easily included and controlled at runtime via the serial console, HTTP REST or MQTT depending on which options you choose. It is built on top of the very stable [H4](https://github.com/philbowles/H4) timer/scheduler which traces its ancestry back to "Esparto" - of which one user recently said: *"and now have Esparto modules with months of uptime without an issue"*. -There are 31 example sketches demonstrating the features and API of all of the plugins. They should be used both as a template and a learning resource. +There are 40 example sketches demonstrating all the features and the API of all of the plugins. They should be used both as a template for your own sketches and as a learning resource. Users are strongly recommended to work through them in the order [listed below](readme.md#current-plugins-februrary-2020) @@ -89,27 +89,32 @@ When you think that H4Plugins also has "plug and play" rotary encoder handling, --- -# Current Plugins (Feb 2020) +# Current Plugins (as of v0.3.4 - Feb 2020) ## Core IOT functionality -* [**H4P_SerialCmd**](docs/h4sc.md): Send commands to H4 and/or plugins to control and/or diagnose +* [**H4P_SerialCmd**](docs/h4sc.md): Send commands from multiple sources to H4 and/or plugins to control and/or diagnose * [**H4P_FlasherController**](docs/h4fc.md): One-line coding of multiple simultaneous LED flashing by Square Wave, PWM, abitrary pattern and Morse code * [**H4P_GPIOManager**](docs/h4gm.md): One-line coding of debouncing, retriggering, rotary encoding plus numerous other GPIO strategies * [**H4P_WiFi**](docs/h4wifi.md): Automatic Connection / reconnection manager + AP configuration + OTA + HTTP REST +* [**H4P_AsyncWebServer**](docs/h4asws.md): Fully Asynchronous Webserver * [**H4P_MQTT**](docs/h4mqtt.md): Automatic Connection/ reconnection MQTT client alows remote control of H4 -* [**H4P_BasicSwitch**](docs/h4onof.md): GPIO object that allows control by simple commands that become available to other plugins -* [**H4P_UPNPSwitch**](docs/h4upnp.md): Extends [H4P_BasicSwitch](docs/h4onof.md) into full UPNP device with Alexa voice control -* [**H4P_ThreeFunctionButton**](docs/h43fnb.md): Multi-function physical control on/off,reboot,factory reset depending on hold time +* [**H4P_BinarySwitch**](docs/h4onof.md): GPIO object that allows control by commands from multiple sources +* [**H4P_BinaryThing**](docs/xxx.md): functional object that allows control by commands from multiple sources **NEW in v0.3.4** +* [**H4P_UPNPSwitch**](docs/h4upnp.md): Extends [H4P_BinarySwitch](docs/h4onof.md) into full UPNP device with Alexa voice control +* [**H4P_UPNPThing**](docs/xxx.md): Extends [H4P_BinaryThing](docs/xxx.md) into full UPNP device with Alexa voice control **NEW in v0.3.4** +* [**H4P_ThreeFunctionButton**](docs/h43fnb.md): Multi-function physical control on/off,reboot,factory reset depending on hold time. Binds to xSwitch or xThing ## Diagnostic / Development tools: * [**H4P_CmdErrors**](docs/h4ce.md): Provide text error messages instead of error codes to SerialCmd * [**H4P_QueueWarn**](docs/h4qw.md): Call user function on low Queue * [**H4P_TaskSniffer**](docs/h4ts.md): Low-level task / queue dumper for H4 + Plugins -* [**H4P_SerialLogger**](docs/h4logs.md): Event logging to serial monitor **NEW in v0.2.0** -* [**H4P_LocalLogger**](docs/h4logs.md): Event logging to SPIFFS file **NEW in v0.2.0** -* +* [**H4P_SerialLogger**](docs/h4logs.md): Event logging to serial monitor +* [**H4P_LocalLogger**](docs/h4logs.md): Event logging to SPIFFS file +* [**H4P_MQTTLogger**](docs/h4logs.md): Event logging to MQTT Server **NEW in v0.3.4** +* [**H4P_MQTTHeapLogger**](docs/h4logs.md): Specialised H4P_MQTTLogger which periodically logs value of FreeHEap **NEW in v0.3.4** + ## Specialist Device Drivers * [**H4P_ExternalSqWave**](docs/h4esw.md): Serial driver for cheap ebay square wave device @@ -164,8 +169,6 @@ And: If using WiFi, you will need to install either the [ESP8266 sketch data uploader](https://github.com/esp8266/arduino-esp8266fs-plugin) or the [ESP32 sketch data uploader](https://github.com/me-no-dev/arduino-esp32fs-plugin) (or both) depending on which platform you compile for. -*(Many thanks to Kerry Clendinning for his help with this section)* - ## Note for PlatformIO users Unfortunately PlatformIO has (had?) several issues that prevent *some* valid Arduino libraries from being installed correctly. I am happy to provide support for H4Plugins code if you manage to get an installation working, providing that none of the files are changed in any way. Sadly, until PlatformIO get the issues fixed, I am unable to provide any support for the installation / build process. @@ -185,7 +188,6 @@ are recommended (if available for the chosen board): ## For WiFi sketches * lwIP Variant: v2 Higher Bandwidth (No Features) -* SSL Support: Basic SSL Ciphers (Lower ROM Use) ### **IMPORTANT** @@ -201,7 +203,6 @@ WiFI sketches must reserve SPIFFS space to hold the AP Mode web pages. These tak * Server-pull OTA * NODE-RED nodes for H4 devices * NODE-RED GUI controller for management of grid of H4 devices -* Command logger (+SQL) * wifiClient http / https Plus of course any others you think may be useful. Let me know using one of the links below diff --git a/assets/h4plugins.jpg b/assets/h4plugins.jpg index 54006c94..1ab71537 100644 Binary files a/assets/h4plugins.jpg and b/assets/h4plugins.jpg differ diff --git a/assets/switchthing.jpg b/assets/switchthing.jpg new file mode 100644 index 00000000..99d87522 Binary files /dev/null and b/assets/switchthing.jpg differ diff --git a/changelog.txt b/changelog.txt index 8b9ce2c9..59f717d4 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,25 @@ +2020/02/18 0.3.4 + breaking changes: + 1. _hasName removed from H4: MUST upgrade to 0.4.1 + 2. BasicSwitch renamed to BinarySwitch + new + 1. Logging + filtering added to exisiting loggers + added loggers: + H4P_MQTTLogger + H4P_MQTTHeapLogger + 2. Thing/Switch paradigm + BasicSwitch renamed to BinarySwitch + added BinaryThing + UPNPThing + GPIOManager heavily modified + added DebouncedThing + EncoderThing + LatchingThing + PolledThing + RawThing + RetriggeringThing + 2020/02/12 0.2.1 patch - put back in ip display on got ip move AP mode ssid lists delete to STA got IP diff --git a/docs/h43fnb.md b/docs/h43fnb.md index 1c575975..15e01d58 100644 --- a/docs/h43fnb.md +++ b/docs/h43fnb.md @@ -1,6 +1,6 @@ ![H4P Flyer](/assets/GPIOLogo.jpg) -## Three Function Button +## Three Function Button (short name="tfnb") ### Adds 3-function* GPIO button to H4 Universal Scheduler/Timer @@ -9,20 +9,22 @@ --- ## What does it do? -Allows the user to control a switch plugin, either a [H4P_BasicSwitch](h4onof.md) or a [H4P_UPNPSwitch](h4upnp.md) by pressing and holding a simple non-latching 'tact' button. The button causes 3 different actions to occur depending on how long it held down. +Allows the user to control a switch plugin, a [H4P_BinarySwitch](things.md) [H4P_BinaryThing](things.md), [H4P_UPNPSwitch](things.md) or a [H4P_UPNPThing](things.md) by pressing and holding a simple non-latching 'tact' button. The button causes 3 different actions to occur depending on how long it held down. -In essence it links an output GPIO (defined by the swicth plugin) to an input GPIO and uses an LED to signal its changing state. +In essence it links an output GPIO (defined by the xSwitch or xThing plugin) to an input GPIO connector and uses an LED to signal its changing state the longer the button is held. -A "short" press (less than 2 seconds) simply switches the device to the opposite state as a "normal" press of any button might do. The resulting action depends on the definition of the linked switch plugin. +A "short" press (less than 2 seconds**) simply switches the device to the opposite state as a "normal" press of any button might do. The resulting action depends on the definition of the linked switch plugin. -A "medium" press over 2 seconds (but less than 5 seconds) starts the associated LED flashing rapidly and when the button is released, the device will reboot. +A "medium" press over 2 seconds (but less than 5 seconds**) starts the associated LED flashing rapidly and when the button is released, the device will reboot. -A "long" press - anything over 5 seconds - starts the LED flashing extremely rapidly and when the button is released, the device will clear any stored configuration information and then reboot, a process known as a "factory reset". +A "long" press - anything over 5 seconds** - starts the LED flashing extremely rapidly and when the button is released, the device will clear any stored configuration information and then reboot, a process known as a "factory reset". **N.B** If the [H4P_WiFi](h4wifi.md) plugin is in use, the factory reset will clear any stored WiFi credentials therefore ensuring the when the device reboots, it will start in AP configuration mode. (* ***Four** functions if you count the LED flashing* :) ) +(** These default values can be changes by the user - see "Tweakables" below) + --- # Usage @@ -32,11 +34,12 @@ A "long" press - anything over 5 seconds - starts the LED flashing extremely rap H4_USE_PLUGINS H4P_GPIOManager h4gm; -// either -H4P_UPNPSwitch h4upnp(... -//or -H4P_BasicSwitch h4upnp(... -H4P_ThreeFunctionButton h43fb(... +// one of +//H4P_UPNPSwitch h4bt(... +//H4P_UPNPThing h4bt(... +//H4P_BinarySwitch h4bt(... +//H4P_BinaryThing h4bt(... +H4P_ThreeFunctionButton h4bt(... ``` # Dependencies @@ -47,28 +50,18 @@ Requires a GPIO pin to be connected to an LED (default is LED_BUILTIN) and a "ta none -# Callbacks - -```cpp -void onChange(uint32_t sweptValue); // called when swept value changes -``` - -# Trusted Name - -*TFNB* - # Unloadable NO --- -## API +# API ```cpp /* constructor: */ H4P_ThreeFunctionButton( - H4P_BasicSwitch* bsp, //a reference to a previoulsy defined H4P_BasicSwitch or H4P_UPNPSwitch + H4P_BinarySwitch* bsp, //a reference to a previoulsy defined H4P_BinarySwitch or H4P_UPNPSwitch uint32_t dbTimeMs, // the switch debounce value in milliseconds: depends on the individual switch // the input button uint8_t pin, @@ -80,7 +73,28 @@ H4P_ThreeFunctionButton( ); ``` -[Example code](../examples/H43FNB/H4P_SONOFF_Basic/H4P_SONOFF_Basic.ino) +[Example code](../examples/H4P_SONOFF_Basic/H4P_SONOFF_Basic.ino) + +--- + +# Tweakables + +The following values are defined in `H4PConfig.h` . They are chosen initally to set a good balance between stability, performance and memory / stack usage. *It is not advisable to change them unless you know exactly what you are doing and why*. + +**N.B.** **Support will not be provided if any of these values are changed.** + +* H43F_MEDIUM 175 +Millisecond flash rate of a medium press + +* H43F_FAST 50 +Millisecond flash rate of a long press + +* H43F_TIMEBASE 175 +Timebase of the Morse S-O-S flash when signalling onWiFIDisconnect (if [H4P_WiFi](h4wifi.md) is used) +* H43F_REBOOT 2000 +Millisecond time defining transition from short->medium +* H43F_FACTORY 5000 +* Millisecond time defining transition from medium->long --- diff --git a/docs/h4asws.md b/docs/h4asws.md index 36bab6df..47be07dd 100644 --- a/docs/h4asws.md +++ b/docs/h4asws.md @@ -1,6 +1,6 @@ ![H4P Flyer](/assets/HTTPLogo.jpg) -# Asynchronous Web Server +# Asynchronous Web Server (short name="asws") ## Adds Asynchronous Webserver, AP mode configuration to H4 Universal Scheduler/Timer. Runs on ESP8266/32 only *All plugins depend upon the presence of the [H4 library](https://github.com/philbowles/H4), which must be installed first.* @@ -50,10 +50,6 @@ void onDisconnect(void); // webserver is down void h4AddAwsHandlers(void) // called after adding its own handlers, adds your in here ``` -## Trusted Name - -*ASWS* - ## Unloadable No, but can be stopped with `h4/asws/stop` command. @@ -95,7 +91,7 @@ The REST interface allows the user to enter commands in a similar fashion to the ![JSONREST](../assets/rest.jpg) -When "prettified" it loks like this: +When "prettified" it looks like this: ```json diff --git a/docs/h4ce.md b/docs/h4ce.md index 27f909eb..cabe625c 100644 --- a/docs/h4ce.md +++ b/docs/h4ce.md @@ -1,5 +1,6 @@ ![H4P Flyer](/assets/DiagLogo.jpg) -# Command Errors + +# Command Errors (short name="cerr") # Adds error-code to meaningful message translation to SerialCmd plugin for H4 Universal Scheduler/Timer. @@ -46,10 +47,6 @@ None, but must be included *before* [**H4P_SerialCmd**](h4sc.md) none -### Trusted Name - -*CERR* - ### Unloadable NO diff --git a/docs/h4esw.md b/docs/h4esw.md index 0c41d609..d5bf99af 100644 --- a/docs/h4esw.md +++ b/docs/h4esw.md @@ -1,6 +1,6 @@ ![H4P Logo](/assets/DriversLogo.jpg) -# External Square Wave Generator +# External Square Wave Generator (short name "esqw") ## Adds driver/controller for Square Wave Generator module to H4 Universal Scheduler/Timer *All plugins depend upon the presence of the [H4 library](https://github.com/philbowles/H4), which must be installed first.* @@ -61,10 +61,6 @@ y2 is the "to" y void onChange(uint32_t sweptValue); // called when swept value changes ``` -## Trusted Name - -*ESQW* - ## Unloadable NO diff --git a/docs/h4fc.md b/docs/h4fc.md index 76499715..133c6518 100644 --- a/docs/h4fc.md +++ b/docs/h4fc.md @@ -1,6 +1,6 @@ ![H4P Logo](/assets/GPIOLogo.jpg) -# Flasher Controller +# Flasher Controller (short name="wink") ## Adds multiple LED blinking methods to H4 Universal Scheduler/Timer. @@ -32,16 +32,12 @@ H4P_FlasherController h4fc; ## Dependencies -none, but must be created *after* [**H4P_SerialCmd**](h4sc.md) if using that plugin. Also if [**H4P_GPIOManager**](h4gm.md) is in use, the relevant pin will be automatically set as output - otherwise it is up to the user to call `pinMode` before any API function below and to manage its logical / physical and active high /active low states. The simplest solution is to always also include [**H4P_GPIOManager**](h4gm.md). +none, but must be created *after* [**H4P_SerialCmd**](h4sc.md) if using that plugin. Also if [**H4P_GPIOManager**](h4gm.md) is in use, the relevant pin will be automatically set as output - otherwise it is up to the user to call `pinMode` before any API function below and to manage its logical / physical and active high /active low states. The simplest solution is to always also include [**H4P_GPIOManager**](h4gm.md) before H4P_FlasherController. ## Commands Added none *TODO* add dynamic command-line control -## Trusted Name - -*WINK* - ## Unloadable NO: diff --git a/docs/h4gm.md b/docs/h4gm.md index 2c18e4a1..e85a4711 100644 --- a/docs/h4gm.md +++ b/docs/h4gm.md @@ -1,6 +1,6 @@ ![H4P Flyer](/assets/GPIOLogo.jpg) -# GPIO Manager +# GPIO Manager (short name="gpio") ## Adds sophisticated GPIO management to H4 Universal Scheduler/Timer. @@ -38,10 +38,6 @@ none h4/show/pins -## Trusted Name - -*GPIO* - ## Unloadable YES: No GPIO activity is futher handled. This cannot be undone. @@ -69,6 +65,14 @@ GPIOManager currently provides behaviours for: * Sequenced - Increments counter for each ON/OFF press * Timed - Reports how long button held ON +You should also read [Things vs Switches](things.md) which explains how some of the above strategies can be autmatically tied/bound/linked to predefined output actions. In it you will find how to use these variants of the above, each of which send an ON or OFF command to a linked output handler + +* DebouncedThing - binds a Debounced input to a BinarySwitch or UPNPSwitch output +* EncoderThing - binds a Encoder input to a BinarySwitch or UPNPSwitch output +* LatchingThing - binds a Latching input to a BinarySwitch or UPNPSwitch output +* PolledThing - binds a Polled input to a BinarySwitch or UPNPSwitch output +* RawThing - binds a Raw input to a BinarySwitch or UPNPSwitch output +* RetriggeringThing - binds a Retriggering input to a BinarySwitch or UPNPSwitch output Before we dive into the strategies, let's deal with the sometimes confusing concept of "Active High" and "Active Low" (experts can skip the next section) @@ -407,22 +411,28 @@ void toggle(uint8_t p); // reverse current logical state // // Strategies // -CircularPin* Circular(uint8_t p,uint8_t mode,H4GM_SENSE sense,uint32_t dbTimeMs,uint32_t nStages,H4GM_FN_EVENT callback); -DebouncedPin* Debounced(uint8_t p,uint8_t mode,H4GM_SENSE sense,uint32_t dbTimeMs,H4GM_FN_EVENT callback); +CircularPin* Circular(uint8_t p,uint8_t mode,H4GM_SENSE sense,uint32_t dbTimeMs,uint32_t nStages,H4GM_FN_EVENT callback);// +DebouncedPin* Debounced(uint8_t p,uint8_t mode,H4GM_SENSE sense,uint32_t dbTimeMs,H4GM_FN_EVENT callback);// +DebouncedPin* DebouncedThing(uint8_t p,uint8_t mode,H4GM_SENSE sense,uint32_t dbTimeMs,H4P_BinaryThing* btp);// EncoderPin* Encoder(uint8_t pA,uint8_t pB,uint8_t mode,H4GM_SENSE sense,H4GM_FN_EVENT); EncoderPin* Encoder(uint8_t pA,uint8_t pB,uint8_t mode,H4GM_SENSE sense,int&); +EncoderPin* EncoderThing(uint8_t pA,uint8_t pB,uint8_t mode,H4GM_SENSE sense,H4P_BinaryThing* btp); EncoderAutoPin* EncoderAuto(uint8_t pA,uint8_t pB,uint8_t mode,H4GM_SENSE sense,int vMin,int vMax,int vSet,uint32_t vIncr,H4GM_FN_EVENT); EncoderAutoPin* EncoderAuto(uint8_t pA,uint8_t pB,uint8_t mode,H4GM_SENSE sense,int vMin,int vMax,int vSet,uint32_t vIncr,int&); -FilteredPin* Filtered(uint8_t p,uint8_t mode,H4GM_SENSE sense,uint8_t filter,H4GM_FN_EVENT callback); -LatchingPin* Latching(uint8_t p,uint8_t mode,H4GM_SENSE sense,uint32_t dbTimeMs,H4GM_FN_EVENT callback); -MultistagePin* Multistage(uint8_t p,uint8_t mode,H4GM_SENSE sense,uint32_t dbTimeMs,H4GM_STAGE_MAP stageMap,H4GM_FN_EVENT callback); -OutputPin* Output(uint8_t p,H4GM_SENSE sense,uint8_t initial,H4GM_FN_EVENT callback=[](H4GPIOPin*){}); -PolledPin* Polled(uint8_t p,uint8_t mode,H4GM_SENSE sense,uint32_t frequency,uint32_t isAnalog,H4GM_FN_EVENT callback); -RawPin* Raw(uint8_t p,uint8_t mode,H4GM_SENSE sense,H4GM_FN_EVENT callback); -RepeatingPin* Repeating(uint8_t p,uint8_t mode,H4GM_SENSE sense,uint32_t dbTimeMs,uint32_t frequency,H4GM_FN_EVENT callback); +FilteredPin* Filtered(uint8_t p,uint8_t mode,H4GM_SENSE sense,uint8_t filter,H4GM_FN_EVENT callback);// +LatchingPin* Latching(uint8_t p,uint8_t mode,H4GM_SENSE sense,uint32_t dbTimeMs,H4GM_FN_EVENT callback);// +LatchingPin* LatchingThing(uint8_t p,uint8_t mode,H4GM_SENSE sense,uint32_t dbTimeMs,H4P_BinaryThing* btp);// +MultistagePin* Multistage(uint8_t p,uint8_t mode,H4GM_SENSE sense,uint32_t dbTimeMs,H4GM_STAGE_MAP stageMap,H4GM_FN_EVENT callback);// +OutputPin* Output(uint8_t p,H4GM_SENSE sense,uint8_t initial,H4GM_FN_EVENT callback=nullptr);// FIX ptr type +PolledPin* Polled(uint8_t p,uint8_t mode,H4GM_SENSE sense,uint32_t frequency,uint32_t isAnalog,H4GM_FN_EVENT callback);// +PolledPin* PolledThing(uint8_t p,uint8_t mode,H4GM_SENSE sense,uint32_t frequency,uint32_t isAnalog,H4P_BinaryThing* btp);// +RawPin* Raw(uint8_t p,uint8_t mode,H4GM_SENSE sense,H4GM_FN_EVENT callback);// +RawPin* RawThing(uint8_t p,uint8_t mode,H4GM_SENSE sense,H4P_BinaryThing*);// +RepeatingPin* Repeating(uint8_t p,uint8_t mode,H4GM_SENSE sense,uint32_t dbTimeMs,uint32_t frequency,H4GM_FN_EVENT callback);// RetriggeringPin* Retriggering(uint8_t _p, uint8_t _mode,H4GM_SENSE sense,uint32_t timeout, H4GM_FN_EVENT _callback); -SequencedPin* Sequenced(uint8_t p,uint8_t mode,H4GM_SENSE sense,uint32_t dbTimeMs,H4GM_FN_EVENT callback); -TimedPin* Timed(uint8_t p,uint8_t mode,H4GM_SENSE sense,uint32_t dbTimeMs,H4GM_FN_EVENT callback); +RetriggeringPin* RetriggeringThing(uint8_t _p, uint8_t _mode,H4GM_SENSE sense,uint32_t timeout,H4P_BinaryThing* btp); +SequencedPin* Sequenced(uint8_t p,uint8_t mode,H4GM_SENSE sense,uint32_t dbTimeMs,H4GM_FN_EVENT callback); // +TimedPin* Timed(uint8_t p,uint8_t mode,H4GM_SENSE sense,uint32_t dbTimeMs,H4GM_FN_EVENT callback); // // ``` diff --git a/docs/h4logs.md b/docs/h4logs.md index bf52c1df..ba741e17 100644 --- a/docs/h4logs.md +++ b/docs/h4logs.md @@ -2,7 +2,7 @@ # Logging -## Adds logging to H4 Universal Scheduler/Timer + Plugins. +## Adds event logging to multiple destinations for H4 Universal Scheduler/Timer + Plugins. *All plugins depend upon the presence of the [H4 library](https://github.com/philbowles/H4), which must be installed first.* @@ -10,41 +10,47 @@ # What do they do? -H4Plugins loggers can send cmd input (and result code) to a variety of destinations, depending on which logger you choose. There are currently +H4Plugins loggers can send messages to a variety of destinations, depending on which logger you choose. There are currently * H4P_LocalLogger which creates a log file in SPIFFS thus requires you allocating an amount at compile-time with Tools... menu +* H4P_MQTTLogger sends log messages to MQTT Server **NEW in v0.3.4** +* H4P_MQTTHeapLogger a specialised H4P_MQTTLogger which periodically logs value of FreeHeap to MQTT **NEW in v0.3.4** * H4P_SerialLogger which does exactly what it says on the tin -"In the pipeline" are an HTTP REST logger, MQTT logger and MySQL logger. Writing your own is seriously easy (see below) +In the experimental pipeline are an HTTP REST logger, and MySQL logger. Writing your own logger is seriously easy (see below) The important thing to note is that each logger is called in turn, thus you can log to several destinations at a time for a single message. The serial logger is a useful diagnostic aid, as it shows what is getting logged to other - less visible - destinations, e.g. remote servers. -Also note that the main interface `h4sc.logEvent("some message")` will simply do nothing if no loggers are installed. This allows it to be left in the code and "switched on or off" by commenting out the loggers, which can be flipped back in at a stroke for testing. +Also note that the main interface `h4sc.logEvent("some message",...)` (which operates like `printf` with variable number of parameters) will simply do nothing if no loggers are installed. This allows it to be left in the code and "switched on or off" by commenting out the loggers, which can be flipped back in at a stroke for testing. -Better still. there is a macro EVENT("some message) which simply calls `h4sc.logEvent` but can be "compiled out" by removing the `#define H4P_SERIAL_LOGGING` entry in `H4PConfig.h`, then there is zero overhead. +Better still. there is a macro EVENT("some message",...) which simply calls `h4sc.logEvent` but can be "compiled out" by removing the `#define H4P_LOG_EVENTS` entry in `H4PConfig.h`, then there is zero overhead. ---- +Finally, most loggers have `filter` parameter which allows the logger to operate on only certain event types: -# What gets logged? +```cpp +enum H4P_LOG_TYPE { + H4P_LOG_SVC_UP=1,// internal H4 event on service UP + H4P_LOG_SVC_DOWN=2,// internal H4 event on service DOWN + H4P_LOG_CMD=4,// command from any source (see below) + H4P_LOG_USER=8,// arbitrary message from user code + H4P_LOG_DEPENDFAIL=16,// dependent plugin omitted by user + H4P_LOG_MQTT_HEAP=32, // value of current heap + H4P_LOG_ALL=0xffffffff +}; +``` -The logger interface send the following data items to each logger: +The values are chosen such that logical operations can be use , e.g. `H4P_LOG_SVC_UP | H4P_LOG_SVC_DOWN` will react only to those two service events. The default filter is `H4P_LOG_ALL`; -```cpp -void _logEvent(const string &msg,H4P_LOG_TYPE type,const string& source,const string& target,uint32_t e){; +--- + +# What information gets logged? + +The logging interface send the following data items to each logger: -``` * Msg: the content of the message -* Message type, one of: +* Event type (see above) -```cpp -enum H4P_LOG_TYPE { - H4P_LOG_SVC_UP, // internal H4 event on service UP - H4P_LOG_SVC_DOWN, // internal H4 event on service DOWN - H4P_LOG_CMD, // command from any source (see below) - H4P_LOG_USER // arbitraty message from user code -}; -``` * Source: Origin of the event For `H4P_LOG_SVC_UP` and `H4P_LOG_SVC_DOWN` this will be "H4" @@ -69,12 +75,11 @@ For `H4P_LOG_CMD` it is the sub-system that initiated the cmd *N.B.* All loggers offer a minimum command set: in the following, `xxxx` should be replaced by the name of the logger -* h4/xxxx/msg/any old message // send "any old message" to the log * h4/xxxx/restart * h4/xxxx/start * h4/xxxx/stop -They all also support `restart`,`start` and `stop` API functions as with all other H4Plugins services +Most also implement h4/xxxx/msg/any old message to put any message you choose into the log # H4P_SerialLogger (name "slog") @@ -83,7 +88,7 @@ They all also support `restart`,`start` and `stop` API functions as with all oth ```cpp #include H4_USE_PLUGINS -H4P_SerialLogger h4sl; +H4P_LocalLogger h4ll(... ``` ## Prequisites @@ -93,10 +98,6 @@ none none -## Trusted Name - -*SLOG* - ## Unloadable NO @@ -105,7 +106,7 @@ NO ## API -none +Constructor takes `uint32_t` filter parameter that defaults to `H4P_LOG_ALL`. [Example Code](../examples/H4P_Loggers/H4P_Loggers.ino) @@ -135,15 +136,12 @@ Board must be compiled with an option to reserve an amount of SPIFFS * h4/log/clear * h4/log/flush -## Trusted Name - -*LOG* - ## Unloadable NO --- + # Example of raw log file ## Format @@ -175,7 +173,7 @@ millis(),type,source,target,error,message ```cpp // constructor -H4P_LocalLogger(uint32_t limit=10000); // amount of free SPIFFS space to use +H4P_LocalLogger(uint32_t limit=10000,uint32_t filter=H4P_LOG_ALL); // limit=amount of free SPIFFS space to use void clear(); // empties the log void flush(); // show then clear @@ -185,6 +183,81 @@ void show(); [Example Code](../examples/H4P_Loggers/H4P_Loggers.ino) +---- +# H4P_MQTTLogger (name: see text) [ ESP8266 / ESP32 only ] + +H4P_MQTTLogger differs from all other plugins, as it allows multiple instances in the same sketch - each differentiated byt the topic is publishes. The topic is alos use for the name in any command handling. + +## Usage + +```cpp +#include +H4_USE_PLUGINS +... +H4P_MQTTLogger h4m1("first",... +// optional: H4P_MQTTLogger h4m2("2nd",... +// ... " +// optional: H4P_MQTTLogger h4m999("police",... + +``` + +## Prequisistes + +H4P_WiFi +H4P_MQTT + +## Additional Commnds + +none + +## Unloadable + +NO, but can use stop and start cmds + +## API + +```cpp +H4P_MQTTLogger(const string& topic,uint32_t filter=H4P_LOG_ALL); +``` + +[Example Code](../examples/H4P_MQTTLogger/H4P_MQTTLogger.ino) + +---- + +# H4P_MQTTHeapLogger (name "heap") [ ESP8266 / ESP32 only ] + +H4P_MQTTHeapLogger is a specalised version of H4P_MQTTLogger which periodically publishes the `heap` topic. + +## Usage + +```cpp +#include +H4_USE_PLUGINS +... +H4P_MQTTHeapLogger h4hl(... +``` + +## Prequisistes + +H4P_WiFi +H4P_MQTT + +## Additional Commnds + +none + +## Unloadable + +NO, but can use stop and start cmds + +# API + +```cpp +// constructor +H4P_MQTTHeapLogger(uint32_t frequency,uint32_t filter=H4P_LOG_ALL); // frequency is in milliseconds +``` + +[Example Code](../examples/H4P_MQTTHeapLogger/H4P_MQTTHeapLogger.ino) ---- # Advanced topics @@ -200,105 +273,25 @@ The code would look like this: #include class myLogger: public H4PLogService { - void _logEvent(const string &msg,H4P_LOG_TYPE type,const string& source,const string& target,uint32_t error){ - if(_running) { - Serial.print("myLogger "); - Serial.print(millis()); - Serial.print(" "); - Serial.println(msg.c_str()); // or Serial.println(CSTR(msg)); - } + void _logEvent(const string &msg,H4P_LOG_TYPE type,const string& source,const string& target,uint32_t error){ + Serial.print("myLogger "); + Serial.print(millis()); + Serial.print(" "); + Serial.println(msg.c_str()); // or Serial.println(CSTR(msg)); } public: - myLogger(): H4PLogService("mylog"){ - subid=H4PC_MYLOG; // see note below - _names={ {H4P_TRID_MYLOG,uppercase(_pid)} }; // see note below - } -}; - -``` - -**Note** to get the next values for `H4PC_MYLOG` and `H4P_TRID_MYLOG` you need to edit `H4PCommon.h` - -```cpp -... - H4P_TRID_NTFY, - H4P_TRID_UBSW, - H4P_TRID_3FNB, - H4P_TRID_CERR, - H4P_TRID_SCMD, - H4P_TRID_QWRN, - H4P_TRID_SNIF, - H4P_TRID_LLOG, - H4P_TRID_SLOG, - H4P_TRID_CURL, // comma added by you - H4P_TRID_MYLOG // ADD THIS HERE and put a comma at the end of the line above + myLogger(): H4PLogService("mylog"){} }; -enum H4PC_CMD_ID{ - H4PC_ROOT=1, - H4PC_SHOW, - H4PC_SNIF, - H4PC_QWRN, - H4PC_ESW_ROOT, - H4PC_ESW_SET, - H4PC_ESW_SWEEP, - H4PC_WIFI, - H4PC_MQTT, - H4PC_ASWS, - H4PC_SPIF, - H4PC_UPNP, - H4PC_LLOG, - H4PC_SLOG, - H4PC_CURL, // comma added by you - H4PC_MYLOG // ADD THIS HERE and put a comma at the end of the line above -}; -... ``` ...and that's it! Then add the new logger to your sketch - Everything else is automatic. [Example Code](../examples/H4P_CustomLogger/H4P_CustomLogger.ino) -## Output form Example sketch -```cpp -slog -SVC slog UP -mylog -myLogger 117 mylog -SVC mylog UP -normal call -myLogger 117 normal call -test1 -myLogger 117 test1 -Ztest2 -myLogger 118 Ztest2 -scmd: h4/dump -scmd: h4/mylog/msg -scmd: h4/mylog/restart -scmd: h4/mylog/start -scmd: h4/mylog/stop -scmd: h4/reboot -scmd: h4/show/all -scmd: h4/show/config -scmd: h4/show/q -scmd: h4/show/qstats -scmd: h4/show/spif -scmd: h4/show/tnames -scmd: h4/show/unload -scmd: h4/slog/msg -scmd: h4/slog/restart -scmd: h4/slog/start -scmd: h4/slog/stop -scmd: h4/unload -scmd: help -help -myLogger 40943 help - -``` - --- -## MySQL schema (for future use) +## MySQL schema (for future use by HTTP REST / MySQL loggers) CREATE TABLE `event` ( `id` int(11) NOT NULL AUTO_INCREMENT, diff --git a/docs/h4mqtt.md b/docs/h4mqtt.md index 65068403..5bcab9b9 100644 --- a/docs/h4mqtt.md +++ b/docs/h4mqtt.md @@ -1,6 +1,6 @@ ![H4P Flyer](/assets/MQTTLogo.jpg) -# MQTT Manager +# MQTT Manager (short name="mqtt") ## Adds MQTT management to H4 Universal Scheduler/Timer. Runs on ESP8266/32 @@ -45,8 +45,7 @@ H4P_MQTT h4mqtt(... ## Dependencies - -* [H4P_WiFi](h4wifi.md) Plugin +* [H4P_WiFi](h4wifi.md) ## Commands Added @@ -71,10 +70,6 @@ void onDisconnect(void); uint32_t onMessage(vector); // where "onMessage" is a user-defined topic handling function ``` -## Trusted Name - -*MQTT* - ## Unloadable No, but can be stopped with `h4/mqtt/stop` command. @@ -129,11 +124,11 @@ Secondly, the message and payload are parsed and split up for you already and pa vs[0]=whatever was in the message payload ``` -You may wonder why you get a `vector` when there is only 1 item: the payload, but this will become obviuous later when we talk about subtopics and wildcards. +You may wonder why you get a `vector` when there is only 1 item: the payload, but this will become obvious later when we talk about subtopics and wildcards. No matter how many parts there are to the message, the payload is always the last item. To make life easier H4 has macros `PAYLOAD` if you are expecting a string and `PAYLOAD_INT` if you are expecting a number. -Also there is a global string called `H4_MQTT::target` which will contain the messge prefix (see above) such as "all" or "mything" or "WEMOS_D1MINI" depending on how/why you received this message. Normally you don't need to know this, but it's there if you want it. +Also there is a global string called `H4_MQTT::target` which will contain the message prefix (see above) such as "all" or "mything" or "WEMOS_D1MINI" depending on how/why you received this message. Normally you don't need to know this, but it's there if you want it. Your callback then "does it thing" but *must* return a value showing if it succeeded or not. It can be any of the following: @@ -169,11 +164,11 @@ uint32_t myCallback(vector vs){ h4mqtt.subscribeDevice("mytopic",myCallback); // MUST be done from inside onConnect callback ``` -[Example Code](../examples/H4MQTT/MQTT_Simple/MQTT_Simple.ino) +[Example Code](../examples/H4P_MQTT_Simple/MQTT_Simple.ino) ## Subtopics -You may need to implement multiple topics in a kind of "tree" hierarchy like the following, after all, H$Plugins does exactly this. +You may need to implement multiple topics in a kind of "tree" hierarchy like the following, after all, H4Plugins does exactly this. * `mytopic/a/b/x` * `mytopic/a/b/y` * `mytopic/a/c/x` @@ -194,7 +189,7 @@ void onMQTTConnect(){ ``` i.e. you need to subscribe to each part of the path in turn. This implies that you will also get any topic `a`, any topic `a/b` as well as the desired `a/b/c` . -You will be able to tell which is which because of the size of the `vector` input, "`vs`". It never includes the firt part of the topic because in the simple case there is only one part and you know what it its, or your code would never get to the topic handler in the first place if it weren't. +You will be able to tell which is which because of the size of the `vector` input, "`vs`". It never includes the first part of the topic because in the simple case there is only one part and you know what it its, or your code would never get to the topic handler in the first place. Look at it another way from the code above: all topics starting `a`... get sent to `myCallback` ,so we always know what the first part would be if it were included... so including what we already know just wastes space. Other than this, each subtopic is contained in the next entry in vs, with the payload always being in the final entry. @@ -291,7 +286,7 @@ The number of milliseconds H4P_MQTT will wait between attempst to reconnect if s The number of milliseconds for each call of `mqttClient.loop()`. Most basic sketches call this on every loop, which amounts to 40000 - 60000 times *per second* which is a massive peformance hit and totally unnecessary. -The larger the value, the less of a performace hit, BUT the longer it takes for published messages to arrive at the server. If you make the value too large, you cause exponetntial queue growth if you also publish a lot of messages. If you only publish say, once a minute then you can crank this up...but do not got past 15000 as pubsubclient will time out on its own and drop the link. +The larger the value, the less of a performace hit, BUT the longer it takes for published messages to arrive at the server. If you make the value too large, you cause exponential queue growth if you also publish a lot of messages. If you only publish say, once a minute then you can crank this up...but do not got past 15000 as pubsubclient will time out on its own and close the connection. Making it smaller will clear the queue and make the mesages arrive at the server more quickly, but - of course - will hurt overal performance / throughput diff --git a/docs/h4onof.md b/docs/h4onof.md deleted file mode 100644 index 00de4dbf..00000000 --- a/docs/h4onof.md +++ /dev/null @@ -1,87 +0,0 @@ -![H4P Flyer](/assets/GPIOLogo.jpg) - -# Basic Switch - -## Adds elementary GPIO on/off/toggle functions to H4 Universal Scheduler/Timer. - -*All plugins depend upon the presence of the [H4 library](https://github.com/philbowles/H4), which must be installed first.* - ---- - -# What does it do? - -H4P_BasicSwitch wraps a GPIO pin in an object that allows control by simple commands that are then available to [H4P_SerialCmd](h4scmd.md), [H4P_AsyncWebServer](h4asws.md)'s HTTP REST API, or [H4P_MQTT](h4mqtt.md) without requring them to know the GPIO pin number. - ---- -# Usage - -```cpp -#include -H4_USE_PLUGINS -H4P_GPIOManager h4gm; -H4P_BasicSwitch h4onof; -``` - -## Dependencies - -* [H4P_GPIOManager](h4gm.md) Plugin - -**N.B.** *H4P_BasicSwitch represents a **default** action: it cannot be used simultaneously with any other plugin that also defines a default action, e.g. [H4P_UPNPSwitch](h4upnp.md).* - -## Commands Added - -* h4/off -* h4/on -* h4/switch/n (payload = 1 or 0) -* h4/toggle (invert current state) - -## Topics automatically published - -If [H4P_MQTT](h4mqtt.md) is also used, this plugin publishes `h4/< your device name >/state` with a payload set to the current state whenever the state changes - -c## Trusted Name - -*ONOF* - -## Unloadable - -No - ---- - -# API - -```cpp -/* Constructor -pin is the GPIO output which gets "switched" when the state changes -sense is ACTIVE_HIGH or ACTIVE_LOW depending on the device -inital is the starting state ON or OFF -onChange is the name of a user function that gets called after the state change with b set to the current state see GPIOManager plugins for more details - -*/ -H4P_BasicSwitch(uint8_t pin,H4GM_SENSE sense, uint8_t initial,H4BS_FN_SWITCH onChange=[](bool){}); - -void turnOff(); -void turnOn(); -void toggle(); // invert state -void turn(bool b); // b = new state: 1 or 0; true/false; ON/OFF - -``` - -[Example Sketch - Basic](../examples/H4P_BasicSwitch/H4P_BasicSwitch.ino) -[Example Sketch - with 3-function button](../examples/H4P_BasicSwitch3fnb/H4P_BasicSwitch3fnb.ino) -[Example Sketch - with MQTT](../examples/H4P_BasicSwitchMQTT/H4P_BasicSwitchMQTT.ino) - ---- - -(c) 2020 Phil Bowles h4plugins@gmail.com - -* [Youtube channel (instructional videos)](https://www.youtube.com/channel/UCYi-Ko76_3p9hBUtleZRY6g) -* [Blog](https://8266iot.blogspot.com) -* [Facebook Esparto Support / Discussion](https://www.facebook.com/groups/esparto8266/) -* [Facebook H4 Support / Discussion](https://www.facebook.com/groups/444344099599131/) -* [Facebook General ESP8266 / ESP32](https://www.facebook.com/groups/2125820374390340/) -* [Facebook ESP8266 Programming Questions](https://www.facebook.com/groups/esp8266questions/) -* [Facebook IOT with ESP8266 (moderator)}](https://www.facebook.com/groups/1591467384241011/) -* [Facebook ESP Developers (moderator)](https://www.facebook.com/groups/ESP8266/) -* [Support me on Patreon](https://patreon.com/esparto) diff --git a/docs/h4qw.md b/docs/h4qw.md index 1fefa75d..2a53fb80 100644 --- a/docs/h4qw.md +++ b/docs/h4qw.md @@ -1,5 +1,5 @@ ![H4P Flyer](/assets/DiagLogo.jpg) -# Queue Warn +# Queue Warn (short name="qwrn") ## Adds low internal queue warning to H4 Universal Scheduler/Timer @@ -39,10 +39,6 @@ none, but must be created after [**H4P_SerialCmd**](h4sc.md) if using that plugi void onQueueWarning(bool) // called after switch state changes ``` -## Trusted Name - -*QWRN* - ## Unloadable YES: No further low-queue warnings will be received. diff --git a/docs/h4sc.md b/docs/h4sc.md index 9de9c50a..ad5489c5 100644 --- a/docs/h4sc.md +++ b/docs/h4sc.md @@ -10,22 +10,21 @@ # What does it do? -Provides the basis for H4 Plugins to be controlled from the serial console. Each plugin has the ability to add its own commands so you only see those that are provided by the currently used set of plugins. +H4P_SerialCmd is the "command and control" centre of H4 and its plugin system. It provides the basis for your app to be controlled by external commands. By default, it adds the serial console as a command source, hence its name. -Details of the commands that each plugin adds (if any) are found in the documentation for the relevant plugin. +It is much more powerful than that though as it is the lowest-level bulding block for handling commands from: -H4Plugins has at is core a command processor allowing user commands to come form several sources: - -* Serial Console (when using this plugin) * HTTP REST API (when using the [H4P_WiFi](h4wifi.md) plugin) * MQTT (when using the [H4P_MQTT](h4mqtt.md) plugin) * User code by calling API functions directly -While the different sources require slightly different treatment, the syntax of the command itself is common across all sources. +This is made possible by having a single command format across all sources, based on MQTT-style topics. While the different sources require slightly different treatment in how the command is entered by the user, the syntax of the command itself is common to all. + +Every other plugin has the ability to add its own specific commands on top of those provided by H4P_SerialCmd itself. Details of the additional commands that each plugin adds (if any) are found in the documentation for the relevant plugin. ## Command format -The general concept of the command-line format is designed to emulate MQTT. The major difference is that there is no separate field for the payload, so the last item of the command line is treated as a payload. For example, using the Serial monitor: +The general concept of the command-line format is designed to emulate MQTT. The major difference is that the sources other than MQTT have no separate field for the payload, so the last item of the command in those cases is treated as the payload. For example, using the Serial monitor: ```cpp h4/some/cmd/with/many/levels/42,666 @@ -33,9 +32,9 @@ h4/some/cmd/with/many/levels/42,666 The payload is "42,666" and the command is handled exactly the same way as an MQTT command with a topic of `h4/some/cmd/with/many/levels` and payload of "42,666" -The rationale behind this is that there is a consistent interface for all command handling irrespective of the source. See the [**H4P_AsyncWebServer** (http "REST")](h4asws.md) and [**H4P_MQTT**](h4mqtt.md) plugins for examples of this in action. +This allows a consistent interface for all command handling irrespective of the source. See the [**H4P_AsyncWebServer** (http "REST")](h4asws.md) and [**H4P_MQTT**](h4mqtt.md) plugins for examples of this in action. -So the following event all cause the same result: rebooting the device +The following external events all cause the same action: rebooting the device * Typing `h4/reboot` in the Serial monitor * Typing `http://your.ip.addr.ess/rest/h4/reboot` in a browser @@ -57,7 +56,7 @@ h4sc.invokeCmd("h4/some/cmd/with/many/levels","42,666"); * Direct call -Generally, each plugin also provides functions that correspond to the command-line equivalent that are more efficient than the invokeCmd method. For example, calling +Generally, each plugin also provides functions that correspond to the command-line equivalent, but are more efficient than the invokeCmd method. For example, calling ```cpp h4sc.dumpQ(); @@ -77,29 +76,26 @@ H4P_SerialCmd h4sc; ## Dependencies -none +none, but when preceded by [H4P_CmdErrors](h4ce.md) numeric cides are translated to meaningful messages ## Commands Added * h4/dump/x (payload x = SPIFFS file name. Show content of file.) * h4/reboot -* h4/show/q +* h4/show/all * h4/show/config +* h4/show/plugins +* h4/show/q * h4/show/qstats -* h4/show/all -* h4/show/spif (show all SPIFFS files + sizes and ised / free space) +* h4/show/spif (show all SPIFFS files + sizes and used / free space) * h4/show/tnames * h4/show/unload * h4/unload * help -## Trusted Name - -*SCMD* - ## Unloadable -YES: Serial commands will be no longer processed. Other command handling services remain availble to dependent plugins. Overall performance is greatly increased. +YES: Serial commands will be no longer processed. Other command handling services remain availble to dependent plugins. Overall performance will be greatly increased. --- @@ -118,7 +114,7 @@ void unload(const char* pid); // see the unload command below // SPIFFS string read(const string& fn); // reads contents of SPIFFS file into string uint32_t write(const string& fn,const string& data,const char* mode="w"); // "w"rites or "a"ppends string to SPIFFS file, return file size. -// advanced (separate documentation T.B.A) +// advanced (separate documentation T.B.A) void addCmd(const string& name,uint32_t owner, uint32_t levID,H4_FN_MSG f=nullptr); void removeCmd(const string& name); @@ -132,6 +128,7 @@ void config(); void dumpQ(); void h4reboot(); //** provided by h4, use h4.h4reboot(), NOT h4sc.h4reboot() void help(); +void plugins(); void Qstats(); void tnames(); void showUnload(); @@ -140,11 +137,11 @@ void unload(const char* pid); void unload(string pid) ``` -### all +### all() #### Equivalent command-line: h4/show/all -Runs all commands containing "show", i.e. +Runs all commands from any loaded plugins containing "show", i.e. * h4/show/q * h4/show/qstats @@ -153,19 +150,19 @@ Runs all commands containing "show", i.e. * h4/show/unload ... etc -### config +### config() #### Equivalent command-line: h4/show/config Shows the contents of H4's internal configuration items. Useful for debugging, but will probably only be relevant to expert users. -### dumpQ +### dumpQ() #### Equivalent command-line: h4/show/Q Shows the contents of H4's internal queue. Useful for debugging, but will probably only be relevant to expert users. -### h4reboot +### h4reboot() #### Equivalent command-line: h4/reboot @@ -175,33 +172,43 @@ Does exactly what it says on the tin! **NOTE** This command is provided by H4 it h4reboot(); ``` -### help +i.e. *do not* precede it with `h4sc.` + +### help() #### Equivalent command-line: help Shows all commands available across all installed plugins -### qstats +### plugins() + +#### Equivalent command-line: h4/show/plugins + +Shows a list of all currently loaded plugins + +Some plugins depend on others ( see for example [**H4P_CmdErrors**](h4ce.md) ), The plugin shortname is the identifier that plugins use to see which other plugins are installed. It is also the name to be used when calling unload. **WARNING** Do *NOT* call `unload` unless you know what you are doing - it will almost certainly break your system! + +### qstats() #### Equivalent command-line: h4/show/qstats Shows the capacity and current size of H4's internal queue. Useful for debugging, but will probably only be relevant to expert users. -### tnames +### tnames() #### Equivalent command-line: h4/show/tnames -Shows H4's list of "trusted names". Useful for debugging, but will probably only be relevant to expert users. Each plugins registers at least one "trusted name" when it starts. Since some plugins depend on others ( see for example [**H4P_CmdErrors**](h4ce.md) ), this is a method to see which plugins are installed. It is also the name to be used when calling unload. **WARNING** Do *NOT* call `unload` unless you know what you are doing - it will almost certainly break your system! +Shows H4's list of "trusted names". Useful for debugging, but will probably only be relevant to expert users. Each plugins registers a "trusted name" for every background task it can run. When using `dumpQ` each task can be indentified by a reasoanbly menaningful short name. -### showUnload +### showUnload() #### Equivalent command-line: h4/show/unload Shows which plugins can be unloaded. SerialCmd itsef can be (see `unload`) using the trusted name "SCMD". Useful for debugging, but will probably only be relevant to expert users. -### unload +### unload() -#### Equivalent command-line: h4/show/unload/< trusted name > +#### Equivalent command-line: h4/show/unload/< plugin name > **WARNING** Do *NOT* call `unload` unless you know what you are doing - it will almost certainly break your system! @@ -209,6 +216,8 @@ Many plugins "hook in" to H4's main loop. This takes processing power away from SerialCmd can be safely unloaded - it will continue to provide command functionality to other plugins that depend upon it. e.g. [**H4P_AsyncWebServer** (http "REST")](h4asws.md) and [**H4P_MQTT**](h4mqtt.md) but it will no longer accept serial commands on its own behalf. Thus it can only be done once and cannot be undone. Unloading SCMD will show a huge performance improvement, so it it worth considering once testing is complete, especially for devices that will be deployed remotely.and will never be able to recive serial commands. +--- + ### Error Messages Validation of commands is fairly basic, to reduce code size. Two main principles underlie command handling: diff --git a/docs/h4ts.md b/docs/h4ts.md index 547c45ed..a164f16d 100644 --- a/docs/h4ts.md +++ b/docs/h4ts.md @@ -1,6 +1,6 @@ ![H4P Flyer](/assets/DiagLogo.jpg) -# Task Sniffer +# Task Sniffer (short name="snif") ## Adds task activity dump to H4 Universal Scheduler/Timer. @@ -46,10 +46,6 @@ A task ID can be one of: Allows user to name his own tasks so that they are more easily identifiable in the task dump -## Trusted Name - -*SNIF* - ## Unloadable NO: but `h4/snif/exclude/0-99` will stop any display. There will still be a performance "hit". diff --git a/docs/h4upnp.md b/docs/h4upnp.md deleted file mode 100644 index 3c61e740..00000000 --- a/docs/h4upnp.md +++ /dev/null @@ -1,161 +0,0 @@ -![H4P Flyer](/assets/GPIOLogo.jpg) - -# UPNP Switch - -## Adds UPNP functionality to H4 Universal Scheduler/Timer, allowing control by e.g. Windows Network Explorer, NODE-RED via UPNP node and voice control by Amazon Alexa. Runs on ESP8266/32 only - -*All plugins depend upon the presence of the [H4 library](https://github.com/philbowles/H4), which must be installed first.* - ---- - -# What does it do? - -![upnp](/assets/upnp.jpg) - -H4P_UPNPSwitch wraps the simple functionality of [H4P_BasicSwitch](/onof.md) into a UPNP device. It emulates a Belkin Wemo device (with a few bells and whistles added). - -This enables it to appear in the Windows Network Explore and be switched on and off from your PC. It also allows voice control by Amazon Alexa - ---- - -# Usage - -```cpp -#include -H4_USE_PLUGINS - -H4P_WiFi h4wifi(... -H4P_AsyncWebServer h4asws(... -H4P_UPNPSwitch h4upnp; -``` - -## Dependencies - - -And: - -* [H4P_WiFi](h4wifi.md) Plugin - -* [H4P_AsyncWebServer](h4asws.md) Plugin - -**N.B.** *H4P_UPNPSwitch represents a default action: it cannot be used simultaneously with any other plugin that also defines a default action, e.g. [H4P_BasicSwitch](h4onof.md).* - -## Commands Added - -* h4/off -* h4/on -* h4/switch/n (payload = 1 or 0) -* h4/toggle (invert current state) -* h4/upnp/name/x (payload x=newUPNPname: causes a reboot ) - -## Topics automatically published - -If [H4P_MQTT](h4mqtt.md) is also used, this plugin publishes `h4/< your device name >/state` with a payload set to the current state whenever the state changes - -## Callbacks - -```cpp -onSwitch(bool) // called after switch state changes -``` - -## Trusted Name - -*ONOF* - -## Unloadable - -No - ---- - -# Installation of Windows components - -***N.B.** It seems Windows has a bug(!) in handling Wemo UPNP devices. Until this is fixed or a workaraound is found, the only way to activate your H4 device using UPNP is indirectly via MQTT* - -Until such time as an installation script is written (soon, I promise :) ) getting the windows functionality is a bit of an ordeal, I'm afraid. - -The first thing you need to do is to locate you Arduino library installation folder for H4Plugins, it will be something like `C:\Users\phil\Documents\Arduino\libraries\H4Plugins` - -The files you will need are in the `src` subfolder - -1. Install Powershell if you dont already have it and set it up so that it can run code without needing admin rights More information [here](https://superuser.com/questions/106360/how-to-enable-execution-of-powershell-scripts) - -2. Install [m2mqtt](https://github.com/eclipse/paho.mqtt.m2mqtt) You may find it easier to first install [nuget.exe](https://www.nuget.org/downloads) and run `nuget.exe install M2Mqtt -o c:\lib` - -3. Edit h4p.reg and change the location in the final to match your username Then right-click on that file and select "Merge" to add it to the registry - ---- - -# API - -```cpp -// Constructor -H4P_UPNPSwitch(string,uint8_t pin,H4GM_SENSE sense, uint8_t initial,H4BS_FN_SWITCH f=[](bool){}); -// name is the "friendly name" which appears in Windows Network Explorer -// and is also the name used by Amazon Alexa voce control -// pin is the GPIO pin which gets "switched" when the state changes -// sense is ACTIVE_HIGH or ACTIVE_LOW of the gpio pin, depending on the hardware -// initial is th intial desired logical state ON or OFF -// f is the name of a user function that gets called after the state change with b set to the current state -// -void friendlyName(const string& name); // sets UPNP friendly name. Causes a reboot -void turnOff(); -void turnOn(); -void toggle(); // invert state -void turn(bool b); // b = new state: 1 or 0; true/false; ON/OFF - -``` - -[Example Sketch - SONOFF Basic](../examples/H4P_SONOFF_Basic/H4P_SONOFF_Basic.ino) - ---- - -# Advanced Topics - -## Device naming - -If no name is given in the constructor, it defaults to "upnp XXXXXX" where XXXXXX is the unique chip ID of the device (usually the last 6 characters of the MAC address). - -This is useful to enable a single generic sketch to be uploaded to numerous devices without change. Each device should then be sent a `h4/wifi/name` command to give it a "sensible" name. This can be done by any MQTT client using stored messages (or e.g. NODE-RED), so that each device gets its own new name every time it reboots. - -## Precedence - -The situation is a little different if the device has a name defined in the constructor. Assume this is "firstname". When given a `h4/upnp/name` command with a payload of "secondname", it will reboot as - no surprises - "secondname". - -By default, it will stay as "secondname.local" until the next factory reset. After that it will return again to "firstname" until either another host command changes it, or a new value is compiled in. - -In some circumstances, you may want it to always revert to the compiled in name "firstname" even after a `h4/upnp/name` command. The default behaviour described above can be changed by editing `H4PConfig.h` and setting `H4P_PREFER_PERSISTENT` to `false`. - ---- - -# "Tweakables" - -The following values are defined in `H4PConfig.h` . They are chosen initally to set a good balance between stability, performance and memory / stack usage. *It is not advisable to change them unless you know exactly what you are doing and why*. - -**N.B.** **Support will not be provided if any of these values are changed.** - -* H4P_UDP_JITTER - -The number of milliseconds entropy between successive UPNP bradcasts, to minimise queue growth and UDP "flooding". - -* H4P_UDP_REFRESH - -The number of milliseconds "lifetime" between UPNP "keepalive" broadcasts - -* H4P_UDP_REPEAT - -The integer number of repeat UDP messages sent on each occasion to prevent packet loss - ---- - -(c) 2020 Phil Bowles h4plugins@gmail.com - -* [Youtube channel (instructional videos)](https://www.youtube.com/channel/UCYi-Ko76_3p9hBUtleZRY6g) -* [Blog](https://8266iot.blogspot.com) -* [Facebook Esparto Support / Discussion](https://www.facebook.com/groups/esparto8266/) -* [Facebook H4 Support / Discussion](https://www.facebook.com/groups/444344099599131/) -* [Facebook General ESP8266 / ESP32](https://www.facebook.com/groups/2125820374390340/) -* [Facebook ESP8266 Programming Questions](https://www.facebook.com/groups/esp8266questions/) -* [Facebook IOT with ESP8266 (moderator)}](https://www.facebook.com/groups/1591467384241011/) -* [Facebook ESP Developers (moderator)](https://www.facebook.com/groups/ESP8266/) -* [Support me on Patreon](https://patreon.com/esparto) diff --git a/docs/things.md b/docs/things.md new file mode 100644 index 00000000..2bb3ace0 --- /dev/null +++ b/docs/things.md @@ -0,0 +1,276 @@ +![H4P Flyer](/assets/GPIOLogo.jpg) + +# Switches and "Things" (shortname="onof") + +## Link on/off/toggle commands to a GPIO or functional object for H4 Universal Scheduler/Timer. + +*All plugins depend upon the presence of the [H4 library](https://github.com/philbowles/H4), which must be installed first.* + +--- + +# What do they do? + +Let's not forget that "IOT" is the Internet of *Things*. So what is a "Thing"? The answer is: *anything you want it to be*. In H4 Plugins terms, it is an object that holds an interal `state` of `ON` or `OFF` and reacts to commands `on`,`off`,`toggle` and `switch`. If you are using the [H4P_MQTT plugin](h4mqtt.md) it publishes the `state` topic whenever the state changes. A Switch is a just a Thing with a predefined output function linked to a single GPIO. + +There can only be one *output* Thing (or Switch) in a sketch/app: it is effectively the default handler for the above commands. It is what your device does when any source ( [Serial](h4sc.md), [MQTT](h4mqtt.md), [HTTP REST](h4asws.md) or [linked GPIO input connector](h5gpio.md) ) sends an `on`,`off`,`toggle` or `switch` command. + +You can have many *input* [linked GPIO input connector](h5gpio.md)s all linked to the single *output* Thing. + +## Simple Example + +Assume you have an output GPIO on pin 12 wired to a relay that controls security light + +Now add some inputs: +* Pushbutton on GPIO0 +* PIR sensor on GPIO4 +* Magnetic reed switches on front door (GPIO5) and window (GPIO6) + +You define the output GPIO12 as a BinarySwitch: + +```cpp +#include +H4_USE_PLUGINS +H4P_BinarySwitch h4bt(12,ACTIVE_HIGH,OFF); +``` + +Even before adding anything else you can now switch on the light when any source ( [Serial](h4sc.md), [MQTT](h4mqtt.md), [HTTP REST](h4asws.md) or [linked GPIO input connector](h5gpio.md) ) sends an `on`,`off`,`toggle` or `switch` command. + +Next, add the sensors and tie them all to the `BinarySwitch` `h4bt` + +```cpp +H4P_GPIOManager h4gm; + +void h4setup(){ + h4gm.LatchingThing(0,INPUT,ACTIVE_LOW,15,&h4bt); // 15ms debounce timeout + h4gm.RetriggeringThing(4,INPUT,ACTIVE_HIGH,10000,&h4bt); // 10sec motion timeout + h4gm.DebouncedThing(5,INPUT,ACTIVE_HIGH,15,&h4bt); // door alarm + h4gm.DebouncedThing(5,INPUT,ACTIVE_HIGH,15,&h4bt); // window alarm +} +``` + +Now the light go on when any of the following happen: + +* You press the button +* The door opens +* The window opens +* Motion is detected +* ON command recived from any source, e.g. MQTT etc + +The light will go off when: + +* You press the button again when its ON for any reason +* The door closes +* The window closes +* The PIR "times out" after 10 seconds. +* OFF command recived from any source, e.g. MQTT etc + +Not bad for 5 lines of code, eh? + +## More advanced example + +Now assume that instead of the light going ON you want your device to send you an SMS + +1. Write the SMS function to send ON / OFF notifications. +2. Change the **H4P_BinarySwitch** to a **H4P_BinaryThing** and give it the name of your SMS function + +That's it - the rest is the same. + +## xThing vs xSwitch Summary + +* xThing calls a user-defined function with `bool` value when `on`,`off`,`toggle` or `switch` command is received +* xSwitch drives a GPIO HIGH or LOW when `on`,`off`,`toggle` or `switch` command is received + +--- + +## UPNP variants + +Both **H4P_BinaryThing** and **H4P_BinarySwitch** have UPNP versions + +* H4P_UPNPThing +* H4P_UPNPSwitch + +The add functionality to the non-UPNP versions + +* Amazon Alexa voice control ON or OFF +* Windows10 desktop integration + +![upnp](../assets/upnpsmall.jpg) + +--- + +## Overall summary + +Your app can contain exactly one of these + +* H4P_BinaryThing +* H4P_BinarySwitch +* H4P_UPNPThing +* H4P_UPNPSwitch + +And as many GPIO input connectors as you need, to make your app do whatever it needs to do + +* DebouncedThing +* EncoderThing +* LatchingThing +* PolledThing +* RawThing +* RetriggeringThing +* [H4P_ThreeFunctionButton](h43fnb.md) + +--- + +![H4P Flyer](/assets/switchthing.jpg) +--- +# Usage + +```cpp +#include +H4_USE_PLUGINS +H4P_GPIOManager h4gm; +// H4P_AsyncWebServer h4asws(... if using the UPNP variants +H4P_BinarySwitch h4onof; +// OR H4P_UPNPSwitch h4onof; +// OR H4P_BinaryThing h4onof; +// OR H4P_UPNPThing h4onof; +``` + +## Dependencies + +* [H4P_GPIOManager](h4gm.md) Plugin + +## Commands Added + +* h4/off +* h4/on +* h4/switch/n (payload = 1 or 0) +* h4/toggle (invert current state) +* h4/state // report state + +// UPNP variants only +* h4/upnp/name/N (payload N= new UPNP "friendly name") + +## Topics automatically published + +If [H4P_MQTT](h4mqtt.md) is also used, this plugin publishes `h4/< your device name >/state` with a payload set to the current state whenever the state changes + +## Unloadable + +No + +--- + +# API + +```cpp +/* Constructor +pin is the GPIO output which gets "switched" when the state changes +sense is ACTIVE_HIGH or ACTIVE_LOW depending on the device +inital is the starting state ON or OFF +onChange is the name of a user function that gets called after the state change with b set to the current state see GPIOManager plugins for more details +btp is a r +*/ +H4P_BinarySwitch(uint8_t pin,H4GM_SENSE sense, uint8_t initial,H4BS_FN_SWITCH onChange=[](bool){}); +H4P_UPNPSwitch(string& upnpName,uint8_t pin,H4GM_SENSE sense, uint8_t initial,H4BS_FN_SWITCH onChange=[](bool){}); +H4P_BinaryThing(uint8_t pin,H4GM_SENSE sense, uint8_t initial); +H4P_UPNPSwitch(string& upnpName,uint8_t pin,H4GM_SENSE sense, uint8_t initial); + +void turnOff(); +void turnOn(); +void toggle(); // invert state +void turn(bool b); // b = new state: 1 or 0; true/false; ON/OFF + +// UPNP variants +void friendlyName(const string& name); // sets UPNP friendly name. Causes a reboot + +``` + +# Output Examples + +[Example Sketch - BinaryThing](../examples/H4P_BinaryThing/H4P_BinaryThing.ino) +[Example Sketch - BinarySwitch](../examples/H4P_BinarySwitch/H4P_BinarySwitch.ino) +[Example Sketch - BinarySwitch with 3-function button](../examples/H4P_BinarySwitch3fnb/H4P_BinarySwitch3fnb.ino) +[Example Sketch - BinarySwitch with MQTT](../examples/H4P_BinarySwitchMQTT/H4P_BinarySwitchMQTT.ino) +[Example Sketch - UPNPSwitch with MQTT](../examples/H4P_SONOFF_Basic/H4P_SONOFF_Basic.ino) + +--- +# Input Examples + +You need to read the [H4P_GPIOManager](h4gm.md) documentation before using these + +[Example Sketch - DebouncedThing](../examples/H4GM_DebouncedThing/H4GM_DebouncedThing.ino) +[Example Sketch - EncoderThing](../examples/H4GM_EncoderThing/H4GM_EncoderThing.ino) +[Example Sketch - LatchingThing](../examples/H4GM_LatchingThing/H4GM_LatchingThing.ino) +[Example Sketch - PolledThing](../examples/H4GM_PolledThing/H4GM_PolledThing.ino) +[Example Sketch - RawThing](../examples/H4GM_RawThing/H4GM_RawThing.ino) +[Example Sketch - RetriggeringThing](../examples/H4GM_RetriggeringThing/H4GM_RetriggeringThing.ino) + +--- + +# Advanced Topics + +## Installation of Windows components for UPNP variants + +***N.B.** It seems Windows has a bug(!) in handling Wemo UPNP devices. Until this is fixed or a workaraound is found, the only way to activate your H4 device using UPNP is indirectly via MQTT* + +Until such time as an installation script is written (soon, I promise :) ) getting the windows functionality is a bit of an ordeal, I'm afraid. + +The first thing you need to do is to locate you Arduino library installation folder for H4Plugins, it will be something like `C:\Users\phil\Documents\Arduino\libraries\H4Plugins` + +The files you will need are in the `src` subfolder + +1. Install Powershell if you dont already have it and set it up so that it can run code without needing admin rights More information [here](https://superuser.com/questions/106360/how-to-enable-execution-of-powershell-scripts) + +2. Install [m2mqtt](https://github.com/eclipse/paho.mqtt.m2mqtt) You may find it easier to first install [nuget.exe](https://www.nuget.org/downloads) and run `nuget.exe install M2Mqtt -o c:\lib` + +3. Edit h4p.reg and change the location in the final to match your username Then right-click on that file and select "Merge" to add it to the registry + +--- + +## Device naming of UPNP variants + +If no name is given in the constructor, it defaults to "upnp XXXXXX" where XXXXXX is the unique chip ID of the device (usually the last 6 characters of the MAC address). + +This is useful to enable a single generic sketch to be uploaded to numerous devices without change. Each device should then be sent a `h4/upnp/name` command to give it a "sensible" name. This can be done by any MQTT client using stored messages (or e.g. NODE-RED), so that each device gets its own new name every time it reboots. + +## Precedence + +The situation is a little different if the device has a name defined in the constructor. Assume this is "firstname". When given a `h4/upnp/name` command with a payload of "secondname", it will reboot as - no surprises - "secondname". + +By default, it will stay as "secondname.local" until the next factory reset. After that it will return again to "firstname" until either another host command changes it, or a new value is compiled in. + +In some circumstances, you may want it to always revert to the compiled in name "firstname" even after a `h4/upnp/name` command. The default behaviour described above can be changed by editing `H4PConfig.h` and setting `H4P_PREFER_PERSISTENT` to `false`. + +--- + +## "Tweakables" + +The following values are defined in `H4PConfig.h` . They are chosen initally to set a good balance between stability, performance and memory / stack usage. *It is not advisable to change them unless you know exactly what you are doing and why*. + +**N.B.** **Support will not be provided if any of these values are changed.** + +* H4P_UDP_JITTER + +The number of milliseconds entropy between successive UPNP bradcasts, to minimise queue growth and UDP "flooding". + +* H4P_UDP_REFRESH + +The number of milliseconds "lifetime" between UPNP "keepalive" broadcasts + +* H4P_UDP_REPEAT + +The integer number of repeat UDP messages sent on each occasion to prevent packet loss + +--- + + +(c) 2020 Phil Bowles h4plugins@gmail.com + +* [Youtube channel (instructional videos)](https://www.youtube.com/channel/UCYi-Ko76_3p9hBUtleZRY6g) +* [Blog](https://8266iot.blogspot.com) +* [Facebook Esparto Support / Discussion](https://www.facebook.com/groups/esparto8266/) +* [Facebook H4 Support / Discussion](https://www.facebook.com/groups/444344099599131/) +* [Facebook General ESP8266 / ESP32](https://www.facebook.com/groups/2125820374390340/) +* [Facebook ESP8266 Programming Questions](https://www.facebook.com/groups/esp8266questions/) +* [Facebook IOT with ESP8266 (moderator)}](https://www.facebook.com/groups/1591467384241011/) +* [Facebook ESP Developers (moderator)](https://www.facebook.com/groups/ESP8266/) +* [Support me on Patreon](https://patreon.com/esparto) diff --git a/examples/H4GM_Debounced/H4GM_Debounced.ino b/examples/H4GM_Debounced/H4GM_Debounced.ino index 7b7020e2..8ee56b69 100644 --- a/examples/H4GM_Debounced/H4GM_Debounced.ino +++ b/examples/H4GM_Debounced/H4GM_Debounced.ino @@ -57,8 +57,8 @@ void h4setup() { // H4 constructor starts Serial h4gm.Debounced(USER_BTN,INPUT,UB_ACTIVE,U_DBTIME_MS,[](H4GPIOPin* ptr){ H4GM_PIN(Debounced); // Required! turns ptr into correct pin-> pointer - Serial.print("GPIO ");Serial.print(pin->pin);Serial.print(" state ");Serial.print(pin->state); - Serial.print(" @ uS ");Serial.print(pin->Tevt);Serial.print(" delta=");Serial.print(pin->delta); - Serial.print(" rate=");Serial.print(pin->cps);Serial.println("/sec"); + Serial.print("GPIO ");Serial.print(pin->pin);Serial.print(" state ");Serial.print(pin->state); + Serial.print(" @ uS ");Serial.print(pin->Tevt);Serial.print(" delta=");Serial.print(pin->delta); + Serial.print(" rate=");Serial.print(pin->cps);Serial.println("/sec"); }); } diff --git a/examples/H4GM_DebouncedThing/H4GM_DebouncedThing.ino b/examples/H4GM_DebouncedThing/H4GM_DebouncedThing.ino new file mode 100644 index 00000000..52bee22a --- /dev/null +++ b/examples/H4GM_DebouncedThing/H4GM_DebouncedThing.ino @@ -0,0 +1,66 @@ +#include +H4_USE_PLUGINS +/* +My major testing devices were nodeMCU which has builtin button on GPIO0 which is ACTIVE_LOW +and STM32NUCLEO-F429ZI whuch has a user button that is ACTIVE_HIGH + +You will probably need to adjust these values for you own device +*/ +#ifdef ARDUINO_ARCH_STM32 + #define UB_ACTIVE ACTIVE_HIGH + #define UL_ACTIVE ACTIVE_HIGH +#else + #define USER_BTN 0 + #define UB_ACTIVE ACTIVE_LOW + #define UL_ACTIVE ACTIVE_LOW +#endif +/* + ALL GPIO strategies are derived from H4GPIOPin: the following members are available + inside ALL GPIO pin callbacks, once you have a valid pointer for the pin type using + H4GM_PIN( type ) with specific underlying type Raw needs H4GM_PIN(Raw); + + uint8_t pin=0; // GPIO hardware pin number + uint8_t gpioType=0; // INPUT, INPUT_PULLUP, OUTPUT etc + H4GM_STYLE style; // Strategy: Raw, debounced, retriggering etc + uint8_t sense=0; // active HIGH or LOW + unsigned long Tevt=0; // uS time of last event + int state=0; // 32 wide as it holds analog value as well as digital 0/1 + // and not uint because encoder returns -1 or +1 + uint32_t delta=0; // uS since last change + uint32_t rate=0; // instantaenous rate cps + uint32_t Rpeak=0; // peak rate + uint32_t cps=0; // sigma changes/sec (used internally, frequently re-set) + uint32_t cMax=UINT_MAX; // max permitted cps (see "throttling"); + uint32_t nEvents=UINT_MAX; // sigma "Events" (meaning depends on inheriting strategy) + + Additional members for Debounced: + + NONE + + Debounced passes only "stable" LOGICAL transitions to the callback + "Stable" means here that any transtions after the first are ignored if they last for less than dbTimeMs + Choosing the correct vale for dbTimeMs depends on the switch in use. Getting the best balance between + a low value for the smallest latency and a high value for "noisy" buttons can be trial-and-error if you + don't have a 'scope and EE experience. + + Now though, you can just tweak dbTimeMs until you only ever get 1x ON and 1x OFF per button press + + New in v0.0.3.4 is the "DebouncedThing" which "ties" or links" or "binds" the Debounced pin to any kind + of xSwitch or xThing + + +*/ +// Debouncing time in mS +#define U_DBTIME_MS 12 + +H4 h4(115200,20); //auto-start Serial @ 115200, Q size=20 +H4P_GPIOManager h4gm; +H4P_BinarySwitch h4bt(LED_BUILTIN,UL_ACTIVE,OFF); +// or e.g. H4P_BinaryThing, UPNPThing, UPNPSwitch - in fact any xSwitch or xThing + +void h4setup() { // H4 constructor starts Serial + Serial.println("H4P_GPIOManager Debounced Example v"H4P_VERSION); + Serial.print("GPIO ");Serial.print(USER_BTN);Serial.print(" ACTIVE ");Serial.println(UB_ACTIVE ? "HIGH":"LOW"); + + h4gm.DebouncedThing(USER_BTN,INPUT,UB_ACTIVE,U_DBTIME_MS,&h4bt); +} diff --git a/examples/H4GM_Encoder/H4GM_Encoder.ino b/examples/H4GM_Encoder/H4GM_Encoder.ino index 001c3bbf..2b268dc4 100644 --- a/examples/H4GM_Encoder/H4GM_Encoder.ino +++ b/examples/H4GM_Encoder/H4GM_Encoder.ino @@ -1,9 +1,5 @@ #include H4_USE_PLUGINS - -H4 h4(115200,20); //auto-start Serial @ 115200, Q size=20 - -H4P_GPIOManager h4gm; /* My major testing devices were nodeMCU which has builtin button on GPIO0 which is ACTIVE_LOW and STM32NUCLEO-F429ZI whuch has a user button that is ACTIVE_HIGH @@ -50,6 +46,9 @@ You will probably need to adjust these values for you own device #define ENCODER_A D7 #define ENCODER_B D6 +H4 h4(115200,20); //auto-start Serial @ 115200, Q size=20 +H4P_GPIOManager h4gm; + void h4setup() { // H4 constructor starts Serial Serial.println("H4P_GPIOManager Encoder Example v"H4P_VERSION); Serial.print("GPIO ");Serial.print(USER_BTN);Serial.print(" ACTIVE ");Serial.println(UB_ACTIVE ? "HIGH":"LOW"); @@ -60,4 +59,4 @@ void h4setup() { // H4 constructor starts Serial Serial.print("ENCODER 1 click ");Serial.print(pin->encoderValue < 0 ? "anti-":"");Serial.println("clockwise"); } else Serial.println("ENCODER STATIC");// ==0 at startup }); -} +} \ No newline at end of file diff --git a/examples/H4GM_EncoderThing/H4GM_EncoderThing.ino b/examples/H4GM_EncoderThing/H4GM_EncoderThing.ino new file mode 100644 index 00000000..ef2b3f26 --- /dev/null +++ b/examples/H4GM_EncoderThing/H4GM_EncoderThing.ino @@ -0,0 +1,64 @@ +#include +H4_USE_PLUGINS +/* +My major testing devices were nodeMCU which has builtin button on GPIO0 which is ACTIVE_LOW +and STM32NUCLEO-F429ZI whuch has a user button that is ACTIVE_HIGH + +You will probably need to adjust these values for you own device +*/ +#ifdef ARDUINO_ARCH_STM32 + #define UB_ACTIVE ACTIVE_HIGH + #define UL_ACTIVE ACTIVE_HIGH +#else + #define USER_BTN 0 + #define UB_ACTIVE ACTIVE_LOW + #define UL_ACTIVE ACTIVE_LOW +#endif +/* + ALL GPIO strategies are derived from H4GPIOPin: the following members are available + inside ALL GPIO pin callbacks, once you have a valid pointer for the pin type using + H4GM_PIN( type ) with specific underlying type Raw needs H4GM_PIN(Raw); + + uint8_t pin=0; // GPIO hardware pin number + uint8_t gpioType=0; // INPUT, INPUT_PULLUP, OUTPUT etc + H4GM_STYLE style; // Strategy: Raw, debounced, Encoder etc + uint8_t sense=0; // active HIGH or LOW + unsigned long Tevt=0; // uS time of last event + int state=0; // 32 wide as it holds analog value as well as digital 0/1 + // and not uint because encoder returns -1 or +1 + uint32_t delta=0; // uS since last change + uint32_t rate=0; // instantaenous rate cps + uint32_t Rpeak=0; // peak rate + uint32_t cps=0; // sigma changes/sec (used internally, frequently re-set) + uint32_t cMax=UINT_MAX; // max permitted cps (see "throttling"); + uint32_t nEvents=UINT_MAX; // sigma "Events" (meaning depends on inheriting strategy) + + Additional members for Encoder: + + int encoderValue // = +1 per clockwise click, -1 for anti-clockwise + + Manages a rotary encoder, including all necessary debouncing and decoding. You get called with + additional field `encoderValue` of -1 for every anti-clockwise click or +1 for clockwise. It's + that simple. Obviously it requires two physical pins but one single call does everything for you. + + It also has a variant that takes the name of an `int` variable instead of a callback: + the variable is incremented or decremented automatically when the encoder is turned. + + New in v0.0.3.4 is the "EncoderThing" which "ties" or links" or "binds" the encoder to any kind + of xSwitch or xThing +*/ +#define ENCODER_A D7 +#define ENCODER_B D6 + +H4 h4(115200,20); //auto-start Serial @ 115200, Q size=20 +H4P_GPIOManager h4gm; +H4P_BinarySwitch h4bt(LED_BUILTIN,UL_ACTIVE,OFF); +// or e.g. H4P_BinaryThing, UPNPThing, UPNPSwitch - in fact any xSwitch or xThing + +// Any clockwise rotation turns LED on, anticlockwise -> off +// Your rotary encoder just became a rotary on/off switch! + +void h4setup() { // H4 constructor starts Serial + Serial.println("H4P_GPIOManager EncoderThing Example v"H4P_VERSION); + h4gm.EncoderThing(ENCODER_A,ENCODER_B,INPUT,UB_ACTIVE,&h4bt); // tie rotations to BinarySwitch +} \ No newline at end of file diff --git a/examples/H4GM_LatchingThing/H4GM_LatchingThing.ino b/examples/H4GM_LatchingThing/H4GM_LatchingThing.ino new file mode 100644 index 00000000..c0521798 --- /dev/null +++ b/examples/H4GM_LatchingThing/H4GM_LatchingThing.ino @@ -0,0 +1,73 @@ +#include +H4_USE_PLUGINS +/* +My major testing devices were nodeMCU which has builtin button on GPIO0 which is ACTIVE_LOW +and STM32NUCLEO-F429ZI whuch has a user button that is ACTIVE_HIGH + +You will probably need to adjust these values for you own device +*/ +#ifdef ARDUINO_ARCH_STM32 + #define UB_ACTIVE ACTIVE_HIGH + #define UL_ACTIVE ACTIVE_HIGH +#else + #define USER_BTN 0 + #define UB_ACTIVE ACTIVE_LOW + #define UL_ACTIVE ACTIVE_LOW +#endif +/* + ALL GPIO strategies are derived from H4GPIOPin: the following members are available + inside ALL GPIO pin callbacks, once you have a valid pointer for the pin type using + H4GM_PIN( type ) with specific underlying type Raw needs H4GM_PIN(Raw); + + uint8_t pin=0; // GPIO hardware pin number + uint8_t gpioType=0; // INPUT, INPUT_PULLUP, OUTPUT etc + H4GM_STYLE style; // Strategy: Raw, debounced, retriggering etc + uint8_t sense=0; // active HIGH or LOW + unsigned long Tevt=0; // uS time of last event + int state=0; // 32 wide as it holds analog value as well as digital 0/1 + // and not uint because encoder returns -1 or +1 + uint32_t delta=0; // uS since last change + uint32_t rate=0; // instantaenous rate cps + uint32_t Rpeak=0; // peak rate + uint32_t cps=0; // sigma changes/sec (used internally, frequently re-set) + uint32_t cMax=UINT_MAX; // max permitted cps (see "throttling"); + uint32_t nEvents=UINT_MAX; // sigma "Events" (meaning depends on inheriting strategy) + + + Additional members for Latching: + + latched: current logical state + + Latching turns an ordinary "tact" button into a latching switch: + push/release once, its ON and stay ON until... + push/release once, its OFF and stay ON until...see above. Rinse and repeat. + + It is derived form the Debounced strategy, so it only changes state on a "clean" + transition. For this reason it also requires a switch-dependent value for dbTimeMs + + Debounced passes only "stable" LOGICAL transitions to the callback + "Stable" means here that any transtions after the first are ignored if they last for less than dbTimeMs + Choosing the correct vale for dbTimeMs depends on the switch in use. Getting the best balance between + a low value for the smallest latency and a high value for "noisy" buttons can be trial-and-error if you + don't have a 'scope and EE experience. + + Now though, you can just tweak dbTimeMs until you only ever get 1x ON and 1x OFF per button press + + New in v0.0.3.4 is the "LatchingThing" which "ties" or links" or "binds" the Latching pin to any kind + of xSwitch or xThing + +*/ +// Debouncing time in mS +#define U_DBTIME_MS 12 + +H4 h4(115200,20); //auto-start Serial @ 115200, Q size=20 +H4P_GPIOManager h4gm; +H4P_BinarySwitch h4bt(LED_BUILTIN,UL_ACTIVE,OFF); +// or e.g. H4P_BinaryThing, UPNPThing, UPNPSwitch - in fact any xSwitch or xThing + +void h4setup() { // H4 constructor starts Serial + Serial.println("H4P_GPIOManager LatchingThing Example v"H4P_VERSION); + Serial.print("GPIO ");Serial.print(USER_BTN);Serial.print(" ACTIVE ");Serial.println(UB_ACTIVE ? "HIGH":"LOW"); + + h4gm.LatchingThing(USER_BTN,INPUT,UB_ACTIVE,U_DBTIME_MS,&h4bt); +} diff --git a/examples/H4GM_PolledThing/H4GM_PolledThing.ino b/examples/H4GM_PolledThing/H4GM_PolledThing.ino new file mode 100644 index 00000000..38cafb77 --- /dev/null +++ b/examples/H4GM_PolledThing/H4GM_PolledThing.ino @@ -0,0 +1,65 @@ +#include +H4_USE_PLUGINS +/* +My major testing devices were nodeMCU which has builtin button on GPIO0 which is ACTIVE_LOW +and STM32NUCLEO-F429ZI whuch has a user button that is ACTIVE_HIGH + +You will probably need to adjust these values for you own device +*/ +#ifdef ARDUINO_ARCH_STM32 + #define UB_ACTIVE ACTIVE_HIGH + #define UL_ACTIVE ACTIVE_HIGH +#else + #define USER_BTN 0 + #define UB_ACTIVE ACTIVE_LOW + #define UL_ACTIVE ACTIVE_LOW +#endif +/* + ALL GPIO strategies are derived from H4GPIOPin: the following members are available + inside ALL GPIO pin callbacks, once you have a valid pointer for the pin type using + H4GM_PIN( type ) with specific underlying type Raw needs H4GM_PIN(Raw); + + uint8_t pin=0; // GPIO hardware pin number + uint8_t gpioType=0; // INPUT, INPUT_PULLUP, OUTPUT etc + H4GM_STYLE style; // Strategy: Raw, Polled, retriggering etc + uint8_t sense=0; // active HIGH or LOW + unsigned long Tevt=0; // uS time of last event + int state=0; // 32 wide as it holds analog value as well as digital 0/1 + // and not uint because encoder returns -1 or +1 + uint32_t delta=0; // uS since last change + uint32_t rate=0; // instantaenous rate cps + uint32_t Rpeak=0; // peak rate + uint32_t cps=0; // sigma changes/sec (used internally, frequently re-set) + uint32_t cMax=UINT_MAX; // max permitted cps (see "throttling"); + uint32_t nEvents=UINT_MAX; // sigma "Events" (meaning depends on inheriting strategy) + + Additional members for Polled: + + none + + Polled has its state checked periodically, usually with a large value, e.g. minutes. It is used + when you do *not* want to react quickly. A good example is a light sensor at dusk: you want lights + to come on when it is "dark". As the light fades the sensor will "flutter" rapidly for quite a long + time before settling to its "dark" value. Only *then* do you want to switch the lights. + + It can be used for both digital and analog values: when isAnalog == true; 'state' will contain raw + analog value. + + New in v0.0.3.4 is the "PolledThing" which "ties" or links" or "binds" the Polled pin to any kind + of xSwitch or xThing + +*/ +#define U_POLL_FREQ 5000 + +H4 h4(115200,20); //auto-start Serial @ 115200, Q size=20 + +H4P_GPIOManager h4gm; +H4P_BinarySwitch h4bt(LED_BUILTIN,UL_ACTIVE,OFF); +// or e.g. H4P_BinaryThing, UPNPThing, UPNPSwitch - in fact any xSwitch or xThing + +void h4setup() { // H4 constructor starts Serial + Serial.println("H4P_GPIOManager Polled Example v"H4P_VERSION); + Serial.print("GPIO ");Serial.print(USER_BTN);Serial.print(" ACTIVE ");Serial.println(UB_ACTIVE ? "HIGH":"LOW"); + + h4gm.PolledThing(USER_BTN,INPUT,UB_ACTIVE,U_POLL_FREQ,true,&h4bt); +} diff --git a/examples/H4GM_RawThing/H4GM_RawThing.ino b/examples/H4GM_RawThing/H4GM_RawThing.ino new file mode 100644 index 00000000..99ef7b48 --- /dev/null +++ b/examples/H4GM_RawThing/H4GM_RawThing.ino @@ -0,0 +1,56 @@ +#include +H4_USE_PLUGINS +/* +My major testing devices were nodeMCU which has builtin button on GPIO0 which is ACTIVE_LOW +and STM32NUCLEO-F429ZI whuch has a user button that is ACTIVE_HIGH + +You will probably need to adjust these values for you own device +*/ +#ifdef ARDUINO_ARCH_STM32 + #define UB_ACTIVE ACTIVE_HIGH + #define UL_ACTIVE ACTIVE_HIGH +#else + #define USER_BTN 0 + #define UB_ACTIVE ACTIVE_LOW + #define UL_ACTIVE ACTIVE_LOW +#endif +/* + ALL GPIO strategies are derived from H4GPIOPin: the following members are available + inside ALL GPIO pin callbacks, once you have a valid pointer for the pin type using + H4GM_PIN( type ) with specific underlying type Raw needs H4GM_PIN(Raw); + + uint8_t pin=0; // GPIO hardware pin number + uint8_t gpioType=0; // INPUT, INPUT_PULLUP, OUTPUT etc + H4GM_STYLE style; // Strategy: Raw, debounced, retriggering etc + uint8_t sense=0; // active HIGH or LOW + unsigned long Tevt=0; // uS time of last event + int state=0; // 32 wide as it holds analog value as well as digital 0/1 + // and not uint because encoder returns -1 or +1 + uint32_t delta=0; // uS since last change + uint32_t rate=0; // instantaenous rate cps + uint32_t Rpeak=0; // peak rate + uint32_t cps=0; // sigma changes/sec (used internally, frequently re-set) + uint32_t cMax=UINT_MAX; // max permitted cps (see "throttling"); + uint32_t nEvents=UINT_MAX; // sigma "Events" (meaning depends on inheriting strategy) + + Additional members for Raw: + + NONE + + Raw passes all transitions to the callback and does not "process" the signal in any way + + New in v0.0.3.4 is the "RawThing" which "ties" or links" or "binds" the Raw pin to any kind + of xSwitch or xThing + +*/ +H4 h4(115200,20); //auto-start Serial @ 115200, Q size=20 +H4P_GPIOManager h4gm; +H4P_BinarySwitch h4bt(LED_BUILTIN,UL_ACTIVE,OFF); +// or e.g. H4P_BinaryThing, UPNPThing, UPNPSwitch - in fact any xSwitch or xThing + +void h4setup() { // H4 constructor starts Serial + Serial.println("H4P_GPIOManager Raw Example v"H4P_VERSION); + Serial.print("GPIO ");Serial.print(USER_BTN);Serial.print(" ACTIVE ");Serial.println(UB_ACTIVE ? "HIGH":"LOW"); + + h4gm.RawThing(USER_BTN,INPUT,UB_ACTIVE,&h4bt); +} \ No newline at end of file diff --git a/examples/H4GM_RetriggeringThing/H4GM_RetriggeringThing.ino b/examples/H4GM_RetriggeringThing/H4GM_RetriggeringThing.ino new file mode 100644 index 00000000..63086b2f --- /dev/null +++ b/examples/H4GM_RetriggeringThing/H4GM_RetriggeringThing.ino @@ -0,0 +1,65 @@ +#include +H4_USE_PLUGINS +/* +My major testing devices were nodeMCU which has builtin button on GPIO0 which is ACTIVE_LOW +and STM32NUCLEO-F429ZI whuch has a user button that is ACTIVE_HIGH + +You will probably need to adjust these values for you own device +*/ +#ifdef ARDUINO_ARCH_STM32 + #define UB_ACTIVE ACTIVE_HIGH + #define UL_ACTIVE ACTIVE_HIGH +#else + #define USER_BTN 0 + #define UB_ACTIVE ACTIVE_LOW + #define UL_ACTIVE ACTIVE_LOW +#endif +/* + ALL GPIO strategies are derived from H4GPIOPin: the following members are available + inside ALL GPIO pin callbacks, once you have a valid pointer for the pin type using + H4GM_PIN( type ) with specific underlying type Raw needs H4GM_PIN(Raw); + + uint8_t pin=0; // GPIO hardware pin number + uint8_t gpioType=0; // INPUT, INPUT_PULLUP, OUTPUT etc + H4GM_STYLE style; // Strategy: Raw, debounced, retriggering etc + uint8_t sense=0; // active HIGH or LOW + unsigned long Tevt=0; // uS time of last event + int state=0; // 32 wide as it holds analog value as well as digital 0/1 + // and not uint because encoder returns -1 or +1 + uint32_t delta=0; // uS since last change + uint32_t rate=0; // instantaenous rate cps + uint32_t Rpeak=0; // peak rate + uint32_t cps=0; // sigma changes/sec (used internally, frequently re-set) + uint32_t cMax=UINT_MAX; // max permitted cps (see "throttling"); + uint32_t nEvents=UINT_MAX; // sigma "Events" (meaning depends on inheriting strategy) + + Additional members for Retriggering: + + none + + Think of a PIR movement sensor: On first detection it goes ON and stays on for a chosen period (the "timeout"). + Any *new* movement before the timout period expires will cause it to start all over again, staying ON and + re-starting the clock or "re-triggering the timeout". + + The Retriggering strategy behaves identically. You get a callback when the pin changes to ON and then again + to OFF when `timeout` milliseconds after the final physical retriggering event have elapsed. + + New in v0.0.3.4 is the "RetriggeringThing" which "ties" or links" or "binds" the Retriggering pin to any kind + of xSwitch or xThing + +*/ +#define U_TIMEOUT 10000 + + +H4 h4(115200,20); //auto-start Serial @ 115200, Q size=20 + +H4P_GPIOManager h4gm; +H4P_BinarySwitch h4bt(LED_BUILTIN,UL_ACTIVE,OFF); +// or e.g. H4P_BinaryThing, UPNPThing, UPNPSwitch - in fact any xSwitch or xThing + +void h4setup() { // H4 constructor starts Serial + Serial.println("H4P_GPIOManager Retriggering Example v"H4P_VERSION); + Serial.print("GPIO ");Serial.print(USER_BTN);Serial.print(" ACTIVE ");Serial.println(UB_ACTIVE ? "HIGH":"LOW"); + + h4gm.RetriggeringThing(USER_BTN,INPUT,UB_ACTIVE,U_TIMEOUT,&h4bt); +} diff --git a/examples/H4P_BasicSwitch/H4P_BasicSwitch.ino b/examples/H4P_BinarySwitch/H4P_BinarySwitch.ino similarity index 83% rename from examples/H4P_BasicSwitch/H4P_BasicSwitch.ino rename to examples/H4P_BinarySwitch/H4P_BinarySwitch.ino index 2937aedd..53c24897 100644 --- a/examples/H4P_BasicSwitch/H4P_BasicSwitch.ino +++ b/examples/H4P_BinarySwitch/H4P_BinarySwitch.ino @@ -22,12 +22,12 @@ H4 h4(115200); H4P_SerialCmd h4sc; H4P_GPIOManager h4gm; -H4P_BasicSwitch h4bs(LED_BUILTIN,UL_ACTIVE,OFF,[](bool b){ +H4P_BinarySwitch h4bs(LED_BUILTIN,UL_ACTIVE,OFF,[](bool b){ Serial.print("STATE NOW ");Serial.println(b); }); void h4setup() { // H4 constructor starts Serial - Serial.println("H4P_BasicSwitch Simple Example v"H4P_VERSION); + Serial.println("H4P_BinarySwitch Simple Example v"H4P_VERSION); h4.once(5000,[](){ h4bs.turnOn(); }); h4.once(10000,[](){ h4bs.turnOff(); }); h4.once(15000,[](){ h4bs.toggle(); }); diff --git a/examples/H4P_BasicSwitch3fnb/H4P_BasicSwitch3fnb.ino b/examples/H4P_BinarySwitch3fnb/H4P_BinarySwitch3fnb.ino similarity index 87% rename from examples/H4P_BasicSwitch3fnb/H4P_BasicSwitch3fnb.ino rename to examples/H4P_BinarySwitch3fnb/H4P_BinarySwitch3fnb.ino index 5a57b866..83feebd5 100644 --- a/examples/H4P_BasicSwitch3fnb/H4P_BasicSwitch3fnb.ino +++ b/examples/H4P_BinarySwitch3fnb/H4P_BinarySwitch3fnb.ino @@ -26,7 +26,7 @@ H4 h4(115200); H4P_SerialCmd h4sc; H4P_GPIOManager h4gm; H4P_FlasherController h4fc; -H4P_BasicSwitch h4bs(LED_BUILTIN,UL_ACTIVE,OFF,[](bool b){ +H4P_BinarySwitch h4bs(LED_BUILTIN,UL_ACTIVE,OFF,[](bool b){ Serial.print("STATE NOW ");Serial.println(b); }); H4P_ThreeFunctionButton h43fb(&h4bs,U_DEBOUNCE,USER_BTN,INPUT,UB_ACTIVE,LED_BUILTIN,UL_ACTIVE); @@ -39,7 +39,7 @@ void onFactoryReset(){ } void h4setup() { // H4 constructor starts Serial - Serial.println("H4P_BasicSwitch 3-function Button Example v"H4P_VERSION); + Serial.println("H4P_BinarySwitch 3-function Button Example v"H4P_VERSION); h4.once(5000,[](){ h4bs.turnOn(); }); h4.once(10000,[](){ h4bs.turnOff(); }); h4.once(15000,[](){ h4bs.toggle(); }); diff --git a/examples/H4P_BasicSwitchMQTT/H4P_BasicSwitchMQTT.ino b/examples/H4P_BinarySwitchMQTT/H4P_BinarySwitchMQTT.ino similarity index 89% rename from examples/H4P_BasicSwitchMQTT/H4P_BasicSwitchMQTT.ino rename to examples/H4P_BinarySwitchMQTT/H4P_BinarySwitchMQTT.ino index a4ceb9b2..dccc56b7 100644 --- a/examples/H4P_BasicSwitchMQTT/H4P_BasicSwitchMQTT.ino +++ b/examples/H4P_BinarySwitchMQTT/H4P_BinarySwitchMQTT.ino @@ -34,7 +34,7 @@ H4P_FlasherController h4fc; H4P_WiFi h4wifi("XXXXXXXX","XXXXXXXX","h4plugins"); H4P_MQTT h4mqtt("192.168.1.4",1883); H4P_AsyncWebServer h4asws("admin","admin"); -H4P_BasicSwitch h4bs(LED_BUILTIN,UL_ACTIVE,OFF,[](bool b){ +H4P_BinarySwitch h4bs(LED_BUILTIN,UL_ACTIVE,OFF,[](bool b){ Serial.print("STATE NOW ");Serial.println(b); }); H4P_ThreeFunctionButton h43fb(&h4bs,U_DEBOUNCE,USER_BTN,INPUT,UB_ACTIVE,LED_BUILTIN,UL_ACTIVE); @@ -48,7 +48,7 @@ void onFactoryReset(){ } void h4setup() { // H4 constructor starts Serial - Serial.println("H4P_BasicSwitch 3-function Button Example v"H4P_VERSION); + Serial.println("H4P_BinarySwitch 3-function Button Example v"H4P_VERSION); h4.once(5000,[](){ h4bs.turnOn(); }); h4.once(10000,[](){ h4bs.turnOff(); }); h4.once(15000,[](){ h4bs.toggle(); }); diff --git a/examples/H4P_BinaryThing/H4P_BinaryThing.ino b/examples/H4P_BinaryThing/H4P_BinaryThing.ino new file mode 100644 index 00000000..6694c895 --- /dev/null +++ b/examples/H4P_BinaryThing/H4P_BinaryThing.ino @@ -0,0 +1,18 @@ +#include +H4_USE_PLUGINS +// +// Function is called by +// h4/off +// h4/on +// h4/toggle +// h4/switch/n where n=0 or 1 +// And reports current state with h4/state +// If MQTT is used, publishes current state +// +H4 h4(115200); +H4P_SerialCmd h4sc; +H4P_BinaryThing h4bt([](bool b){ Serial.print("I am now ");Serial.println(b ? "ON":"OFF"); }); + +void h4setup(){ + h4.every(10000,[]{ h4bt.toggle(); }); +} \ No newline at end of file diff --git a/examples/H4P_CustomLogger/H4P_CustomLogger.ino b/examples/H4P_CustomLogger/H4P_CustomLogger.ino index 567693d3..c4559e19 100644 --- a/examples/H4P_CustomLogger/H4P_CustomLogger.ino +++ b/examples/H4P_CustomLogger/H4P_CustomLogger.ino @@ -1,71 +1,19 @@ #include H4_USE_PLUGINS -/* -**Note** to get the next values for `H4PC_MYLOG` and `H4P_TRID_MYLOG` you need to edit `H4PCommon.h` - -```cpp -... - H4P_TRID_NTFY, - H4P_TRID_UBSW, - H4P_TRID_3FNB, - H4P_TRID_CERR, - H4P_TRID_SCMD, - H4P_TRID_QWRN, - H4P_TRID_SNIF, - H4P_TRID_LLOG, - H4P_TRID_SLOG, - H4P_TRID_CURL, // comma added by you - H4P_TRID_MYLOG // ADD THIS HERE and put a comma at the end of the line above -}; - -enum H4PC_CMD_ID{ - H4PC_ROOT=1, - H4PC_SHOW, - H4PC_SNIF, - H4PC_QWRN, - H4PC_ESW_ROOT, - H4PC_ESW_SET, - H4PC_ESW_SWEEP, - H4PC_WIFI, - H4PC_MQTT, - H4PC_ASWS, - H4PC_SPIF, - H4PC_UPNP, - H4PC_LLOG, - H4PC_SLOG, - H4PC_CURL, // comma added by you - H4PC_MYLOG // ADD THIS HERE and put a comma at the end of the line above -}; -... - */ - - // this is a quick and dirty fix to allow this example to compile - // - DO NOT use these values in real life, see above! - #define H4P_TRID_MYLOG 49 - #define H4PC_MYLOG 49 - // class myLogger: public H4PLogService { void _logEvent(const string &msg,H4P_LOG_TYPE type,const string& source,const string& target,uint32_t error){ - if(_running) { - Serial.print("myLogger "); - Serial.print(millis()); - Serial.print(" "); - Serial.println(msg.c_str()); // or Serial.println(CSTR(msg)); - } + Serial.print("myLogger "); + Serial.print(millis()); + Serial.print(" "); + Serial.println(msg.c_str()); // or Serial.println(CSTR(msg)); } public: - myLogger(): H4PLogService("mylog"){ - subid=H4PC_MYLOG; // see note below - _names={ {H4P_TRID_MYLOG,uppercase(_pid)} }; // see note below - } + myLogger(): H4PLogService("mylog"){} }; H4 h4(115200); H4P_SerialCmd h4sc; -#ifndef ARDUINO_ARCH_STM32 -// H4P_LocalLogger h4ll(10000); // 10k = amount of SPIFFS to use ESP8266 / ESP32 only -#endif H4P_SerialLogger h4sl; myLogger lumberjack; diff --git a/examples/H4P_MQTTHeapLogger/H4P_MQTTHeapLogger.ino b/examples/H4P_MQTTHeapLogger/H4P_MQTTHeapLogger.ino new file mode 100644 index 00000000..1fa3f171 --- /dev/null +++ b/examples/H4P_MQTTHeapLogger/H4P_MQTTHeapLogger.ino @@ -0,0 +1,9 @@ +#include +H4_USE_PLUGINS + +H4 h4(115200); +H4P_SerialCmd h4sc; +H4P_WiFi h4wifi("XXXXXXXX","XXXXXXXX","pinger"); +H4P_SerialLogger h4sl; +H4P_MQTT h4mqtt("192.168.1.4",1883); +H4P_MQTTHeapLogger h4mh(1000); // publish hep value 1x per second diff --git a/examples/H4P_MQTTLogger/H4P_MQTTLogger.ino b/examples/H4P_MQTTLogger/H4P_MQTTLogger.ino new file mode 100644 index 00000000..8611173e --- /dev/null +++ b/examples/H4P_MQTTLogger/H4P_MQTTLogger.ino @@ -0,0 +1,28 @@ +#include +H4_USE_PLUGINS + +H4 h4(115200); +H4P_SerialCmd h4sc; +H4P_GPIOManager h4gm; +H4P_FlasherController h4fc; +H4P_WiFi h4wifi("XXXXXXXX","XXXXXXXX","pinger"); +H4P_SerialLogger h4sl; +H4P_MQTTLogger h4mlog1("hazlenuts",H4P_LOG_USER); // only log user events +H4P_MQTTLogger h4mlog2("pecans"); // name the topic + +H4_TIMER mqttPinger; +void onMQTTConnect(){ + EVENT("%u Testing the %s Logger",millis(),"variadic"); + mqttPinger=h4.every(60000,[](){ EVENT("something happened"); }); +} +void onMQTTDisconnect(){ + h4.cancel(mqttPinger); +} + +H4P_MQTT h4mqtt("192.168.1.4",1883,"","",onMQTTConnect,onMQTTDisconnect); + +H4P_AsyncWebServer h4asws("admin","admin"); + +void h4setup(){ + h4sc.logEvent("Log tester %d",666); // like printf +} \ No newline at end of file diff --git a/examples/H4P_QueueWarn/H4P_QueueWarn.ino b/examples/H4P_QueueWarn/H4P_QueueWarn.ino index 5a765767..68f49abb 100644 --- a/examples/H4P_QueueWarn/H4P_QueueWarn.ino +++ b/examples/H4P_QueueWarn/H4P_QueueWarn.ino @@ -30,8 +30,6 @@ void h4setup() { // H4 constructor starts Serial h4qw.show(); // show limits // // we need a lot of things in the queue -// initial size is 10 and we warn @ 50% (=5 tasks) -// so 6 will definitely bust it // h4.once(30000,[](){ Serial.println("30 seconds later"); }); h4.once(40000,[](){ @@ -52,4 +50,4 @@ void h4setup() { // H4 constructor starts Serial h4qw.pcent(100); // reset to max - no warnings second time around h4setup(); }); -} +} \ No newline at end of file diff --git a/examples/H4P_SONOFF_Basic/data/404.htm b/examples/H4P_SONOFF_Basic/data/404.htm new file mode 100644 index 00000000..f86b4654 --- /dev/null +++ b/examples/H4P_SONOFF_Basic/data/404.htm @@ -0,0 +1,15 @@ + + + + + %$101% [%$100%] + + + +
+
+ Sorry, Esparto doesn't have that
+ Click to go to the %$101% [%$100%] homepage +
+ + \ No newline at end of file diff --git a/examples/H4P_SONOFF_Basic/data/H4PLogo.jpg b/examples/H4P_SONOFF_Basic/data/H4PLogo.jpg new file mode 100644 index 00000000..21112b58 Binary files /dev/null and b/examples/H4P_SONOFF_Basic/data/H4PLogo.jpg differ diff --git a/examples/H4P_SONOFF_Basic/data/ap.htm b/examples/H4P_SONOFF_Basic/data/ap.htm new file mode 100644 index 00000000..24d014c5 --- /dev/null +++ b/examples/H4P_SONOFF_Basic/data/ap.htm @@ -0,0 +1,102 @@ + + + + + %device% AP MODE + + + +
+ +
+ + +
+ +
+
H4 Version
+
%h4v%
+
+
+
Plugins Version
+
%h4pv%
+
+
+
Device
+
%device%.local
+
+
+
Board
+
%board%
+
+
+
Chip
+
%chip%
+
+
+
IP Address
+
%ip%
+
+ +
+
+
+
SSID
+
+ +
+
+
+
Password
+
+ +
+
+
+
Device
+
+ +
+
+
+
Name
+
+ +
+
+ +
+ +
+ +
+
+ + + + diff --git a/examples/H4P_SONOFF_Basic/data/boot.png b/examples/H4P_SONOFF_Basic/data/boot.png new file mode 100644 index 00000000..5e6f6031 Binary files /dev/null and b/examples/H4P_SONOFF_Basic/data/boot.png differ diff --git a/examples/H4P_SONOFF_Basic/data/fact.png b/examples/H4P_SONOFF_Basic/data/fact.png new file mode 100644 index 00000000..dc94164d Binary files /dev/null and b/examples/H4P_SONOFF_Basic/data/fact.png differ diff --git a/examples/H4P_SONOFF_Basic/data/favicon.ico b/examples/H4P_SONOFF_Basic/data/favicon.ico new file mode 100644 index 00000000..e3306248 Binary files /dev/null and b/examples/H4P_SONOFF_Basic/data/favicon.ico differ diff --git a/examples/H4P_SONOFF_Basic/data/h4.css b/examples/H4P_SONOFF_Basic/data/h4.css new file mode 100644 index 00000000..2b4bdf72 --- /dev/null +++ b/examples/H4P_SONOFF_Basic/data/h4.css @@ -0,0 +1,63 @@ +body { + font-family: Arial, Helvetica, sans-serif; + margin: auto; +} +form { + margin-top: 10px; + margin-bottom: 8px; + grid-column: 2; +} +select { + width: 140px; +} +input { + width: 136px; +} +img{ + margin-left: 5px; +} +#main{ + display: grid; + grid-template-columns: 1fr 1fr 1fr; + grid-gap: 15px; + justify-items: center; +} + +#header { + grid-row-start: 1; + grid-column: 2; +} + +#footer { + font-size: x-small; + text-align: center; + grid-column: 2; +} +#creds{ + grid-column: 2; + width: 280px; +} +#cmds{ + grid-column: 3; + margin-top: 15px; + cursor: pointer; +} +.line { + display: inline-flex; + text-align: left; + grid-column: 2; + } +.fline { + display: inline-flex; +} +.btn{ + display: grid; + justify-items: center; + margin-top: 6px; +} +.title { + width: 140px; +} +.data{ + width: 140px; +} \ No newline at end of file diff --git a/examples/H4P_SONOFF_Basic/data/soap.xml b/examples/H4P_SONOFF_Basic/data/soap.xml new file mode 100644 index 00000000..ec48ddab --- /dev/null +++ b/examples/H4P_SONOFF_Basic/data/soap.xml @@ -0,0 +1,7 @@ + + + +%state% + + + diff --git a/examples/H4P_SONOFF_Basic/data/sta.htm b/examples/H4P_SONOFF_Basic/data/sta.htm new file mode 100644 index 00000000..c86c814e --- /dev/null +++ b/examples/H4P_SONOFF_Basic/data/sta.htm @@ -0,0 +1,71 @@ + + + + + %device% + + + +
+ +
+ + +
+ +
+
H4 Version
+
%h4v%
+
+
+
Plugins Version
+
%h4pv%
+
+
+
Device
+
%device%.local
+
+
+
Board
+
%board%
+
+
+
Chip
+
%chip%
+
+
+
IP Address
+
%ip%
+
+
+
Name
+
%name%
+
+ + + + + diff --git a/examples/H4P_SONOFF_Basic/data/ucom.txt b/examples/H4P_SONOFF_Basic/data/ucom.txt new file mode 100644 index 00000000..3dda15b8 --- /dev/null +++ b/examples/H4P_SONOFF_Basic/data/ucom.txt @@ -0,0 +1,10 @@ +CACHE-CONTROL: max-age=%age% +EXT: +LOCATION: http://%ip%:80/we +OPT: "http://schemas.upnp.org/upnp/1/0/"; ns=01 +01-NLS: %chip% +SERVER: Forked AsyncWebServer, UPnP/1.0, H4/%h4v% +X-H4-Device: %device% +X-H4-Chip: %chip% +X-User-Agent: redsonic +USN: %usn% diff --git a/examples/H4P_SONOFF_Basic/data/up.xml b/examples/H4P_SONOFF_Basic/data/up.xml new file mode 100644 index 00000000..e77c6d06 --- /dev/null +++ b/examples/H4P_SONOFF_Basic/data/up.xml @@ -0,0 +1,35 @@ + + + + 1 + 0 + + + + +HomeAutomation +{%udn%} +urn:%updt% + +urn:%updt% +%name% +%umfr% +https://github.com/philbowles/H4 +Chez Toi ioT H4 device +%board% +%h4v% +/ +%chip% +uuid:%udn% +/ + + +urn:%usvc% +urn:%usid% +/wescp.xml +/upnp + + + + + \ No newline at end of file diff --git a/examples/H4P_SONOFF_Basic/data/wescp.xml b/examples/H4P_SONOFF_Basic/data/wescp.xml new file mode 100644 index 00000000..245e5681 --- /dev/null +++ b/examples/H4P_SONOFF_Basic/data/wescp.xml @@ -0,0 +1,37 @@ + + + 1 + 0 + + + + GetBinaryState + + + + BinaryState + BinaryState + out + + + + + SetBinaryState + + + + BinaryState + BinaryState + in + + + + + + + BinaryState + boolean + 0 + + + \ No newline at end of file diff --git a/examples/H4P_TaskSniffer/H4P_TaskSniffer.ino b/examples/H4P_TaskSniffer/H4P_TaskSniffer.ino index bcc0580d..75eb2ee5 100644 --- a/examples/H4P_TaskSniffer/H4P_TaskSniffer.ino +++ b/examples/H4P_TaskSniffer/H4P_TaskSniffer.ino @@ -19,12 +19,17 @@ H4_USE_PLUGINS H4P_TaskSniffer h4ts({13,14,15}); dumps only tasks with IDs 13,14,15 ( equivalent to h4/include/13,14,15 ) */ -#define SMALL_Q 1 +#define SMALL_Q 10 #define Q_WARN_PCENT 50 H4 h4(115200,SMALL_Q); //auto-start Serial @ 115200, small Q of 10 to force a warning -void qIsLow(bool inDanger){ if(inDanger) h4.dumpQ(); } +void qIsLow(bool inDanger){ + if(inDanger) { + Serial.println("Warning, Will Robinson - low Q!!!"); // See 1960s TV SciFi series "Lost in Space" :) + h4.dumpQ(); + } +} H4P_SerialCmd h4sc; H4P_QueueWarn h4qw(qIsLow,50); // call qIsLow when free Q drops below 50% @@ -57,18 +62,17 @@ void h4setup() { // H4 constructor starts Serial h4qw.show(); // show limits // // we need a lot of things in the queue - // initial size is 10 and we warn @ 50% (=5 tasks) - // so 6 will definitely bust it // h4.once(30000,[](){ Serial.println("30 seconds later"); },nullptr,2); // "tag" this task as UF30 h4.once(40000,[](){ - Serial.println("40 seconds later"); - Serial.println("Add more jobs to generate 2nd Warning"); - h4.once(25000,[](){ Serial.println("65 seconds later"); },nullptr,1); // 40 + 25 "tag" as UF25 - h4.once(35000,[](){ Serial.println("75 seconds later"); },nullptr,3); // 40 + 35 "tag" as UF35 + Serial.println("40 seconds later"); + h4.once(25000,[](){ Serial.println("65 seconds later"); },nullptr,1); // 40 + 25 "tag" as UF25 + h4.once(35000,[](){ Serial.println("75 seconds later"); },nullptr,3); // 40 + 35 "tag" as UF35 },nullptr,4); // "tag" as UF40 h4.once(50000,[](){ Serial.println("50 seconds later"); },nullptr,5);// "tag" as UF50 h4.once(60000,[](){ Serial.println("60 seconds later"); },nullptr,6);// "tag" as UF60 h4.once(70000,[](){ Serial.println("70 seconds later"); },nullptr,7);// "tag" as UF70 h4.once(80000,[](){ Serial.println("80 seconds later"); },nullptr,8);// "tag" as UF80 -} + h4.once(90000,[](){ Serial.println("80 seconds later"); });// no tag: anon + h4.once(95000,[](){ Serial.println("80 seconds later"); });// no tag: anon +} \ No newline at end of file diff --git a/keywords.txt b/keywords.txt index c3906934..07157f8d 100644 --- a/keywords.txt +++ b/keywords.txt @@ -6,21 +6,8 @@ # Library ( KEYWORD1) ####################################### -H4P_SerialCmd KEYWORD1 -H4P_FlasherController KEYWORD1 -H4P_LocalLogger KEYWORD1 -H4P_MQTTLogger KEYWORD1 -H4P_SerialLogger KEYWORD1 -H4P_GPIOManager KEYWORD1 -H4P_WiFi KEYWORD1 -H4P_MQTT KEYWORD1 -H4P_BasicSwitch KEYWORD1 -H4P_UPNPSwitch KEYWORD1 -H4P_ThreeFunctionButton KEYWORD1 -H4P_CmdErrors KEYWORD1 -H4P_QueueWarn KEYWORD1 -H4P_TaskSniffer KEYWORD1 -H4P_ExternalSqWave KEYWORD1 +H4P_Plugins KEYWORD1 +H4P_USE_PLUGINS KEYWORD1 ####################################### # Datatypes @@ -31,14 +18,19 @@ H4P_FlasherController KEYWORD1 H4P_GPIOManager KEYWORD1 H4P_WiFi KEYWORD1 H4P_MQTT KEYWORD1 -H4P_BasicSwitch KEYWORD1 +H4P_BinarySwitch KEYWORD1 +H4P_BinaryThing KEYWORD1 H4P_UPNPSwitch KEYWORD1 +H4P_UPNPThing KEYWORD1 H4P_ThreeFunctionButton KEYWORD1 H4P_CmdErrors KEYWORD1 H4P_QueueWarn KEYWORD1 H4P_TaskSniffer KEYWORD1 H4P_AsyncWebServer KEYWORD1 -H4P_ExternalSqWave KEYWORD1 +H4P_SerialLogger KEYWORD1 +H4P_LocalLogger KEYWORD1 +H4P_MQTTLogger KEYWORD1 +H4P_MQTTHeapLogger KEYWORD1 ####################################### # Methods and Functions ( KEYWORD2) @@ -62,8 +54,13 @@ Encoder KEYWORD2 EncoderAuto KEYWORD2 Filtered KEYWORD2 Latching KEYWORD2 +logEvent KEYWORD2 +logicalRead KEYWORD2 +logicalWrite KEYWORD2 +Latching KEYWORD2 Multistage KEYWORD2 Output KEYWORD2 +plugins KEYWORD2 Polled KEYWORD2 Raw KEYWORD2 Repeating KEYWORD2 @@ -107,6 +104,13 @@ friendlyName KEYWORD2 host KEYWORD2 showSPIFFS KEYWORD2 +DebouncedThing KEYWORD2 +EncoderThing KEYWORD2 +LatchingThing KEYWORD2 +PolledThing KEYWORD2 +RawThing KEYWORD2 +RetriggeringThing KEYWORD2 + ####################################### # Constants ( LITERAL11) ####################################### diff --git a/library.properties b/library.properties index 4585ea77..36a37ba5 100644 --- a/library.properties +++ b/library.properties @@ -1,10 +1,10 @@ name=H4Plugins -version=0.2.1 +version=0.3.4 author=Phil Bowles maintainer=Phil Bowles sentence=Adds GPIO handling, WiFi, MQTT, Webserver to H4 timer/scheduler paragraph=Multi-Platform plugins adding everything needed to create your own multi-function IOT firmware. category=Device Control url=https://github.com/philbowles/H4Plugins -depends=H4(>=0.4.0),ESPAsyncUDP,AsyncUDP,ESPAsyncTCP,AsyncTCP,PubSubClient +depends=H4(>=0.4.3),ESPAsyncUDP,AsyncUDP,ESPAsyncTCP,AsyncTCP,PubSubClient architectures=* diff --git a/src/H4PCommon.h b/src/H4PCommon.h index b92bdf20..fe453121 100644 --- a/src/H4PCommon.h +++ b/src/H4PCommon.h @@ -26,15 +26,11 @@ 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. -beta: -Erickcampos50@gmail.com - - */ #ifndef H4P_HO #define H4P_HO -#define H4P_VERSION "0.2.1" +#define H4P_VERSION "0.3.4" #include #include @@ -51,7 +47,7 @@ using namespace std::placeholders; #endif // using H4_FN_MSG =function)>; -using H4BS_FN_SWITCH =function; +using H4BS_FN_SWITCH =function; struct command{ uint32_t owner; @@ -76,15 +72,20 @@ enum H4_CMD_ERROR:uint32_t { }; enum H4P_LOG_TYPE { - H4P_LOG_SVC_UP, - H4P_LOG_SVC_DOWN, - H4P_LOG_CMD, - H4P_LOG_USER + H4P_LOG_SVC_UP=1, + H4P_LOG_SVC_DOWN=2, + H4P_LOG_CMD=4, + H4P_LOG_USER=8, + H4P_LOG_DEPENDFAIL=16, + H4P_LOG_MQTT_HEAP=32, + H4P_LOG_ALL=0xffffffff }; +#define ON true +#define OFF false // // literal string RAM savers // -#define STAG(x) constexpr char* x##tag(){ return #x; } +#define STAG(x) constexpr char* x##Tag(){ return #x; } constexpr const char* cmdhash(){ return "h4/#"; } @@ -103,6 +104,7 @@ STAG(name); STAG(onof); STAG(port); STAG(qwrn); +STAG(scmd); STAG(snif); STAG(ssid); STAG(state); @@ -122,10 +124,16 @@ STAG(wifi); #define CMDVS(x) ([this](vector vs)->uint32_t{ return x(vs); }) #define VSCMD(x) uint32_t x(vector) -#ifdef H4P_SERIAL_LOGGING - #define EVENT(x) h4sc.logEvent(x) +#ifdef H4P_LOG_EVENTS + #define EVENT(x,...) h4sc.logEventType(H4P_LOG_USER,x, ##__VA_ARGS__) + #define SYSEVENT(e,x,...) h4sc.logEventType(e,x, ##__VA_ARGS__) + #define DEPENDFAIL(x) h4sc.logEventType(H4P_LOG_DEPENDFAIL,"%s->%s", CSTR(_pid),x##Tag()) + #define logEvent(x,...) logEventType(H4P_LOG_USER,x, ##__VA_ARGS__) #else - #define EVENT(x) + #define EVENT(x,...) + #define SYSEVENT(e,x,...) + #define DEPENDFAIL(x) + #define logEvent(x,...) _noOP() #endif // // PLUGINS @@ -134,14 +142,13 @@ enum trustedIds { H4P_TRID_PATN = 50, H4P_TRID_PP1x, H4P_TRID_PWM1, - H4P_TRID_GPIO, + H4P_TRID_SYNC, H4P_TRID_DBNC, H4P_TRID_RPTP, H4P_TRID_POLL, H4P_TRID_MULT, H4P_TRID_TRIG, H4P_TRID_SQWV, - H4P_TRID_WIFI, H4P_TRID_HOTA, H4P_TRID_WFAP, H4P_TRID_MQMS, @@ -151,50 +158,27 @@ enum trustedIds { H4P_TRID_SOAP, H4P_TRID_UDPM, H4P_TRID_NTFY, - H4P_TRID_UBSW, - H4P_TRID_3FNB, - H4P_TRID_CERR, H4P_TRID_SCMD, - H4P_TRID_QWRN, - H4P_TRID_SNIF, - H4P_TRID_LLOG, - H4P_TRID_SLOG, - H4P_TRID_MLOG, + H4P_TRID_HLOG }; enum H4PC_CMD_ID{ H4PC_ROOT=1, H4PC_SHOW, - H4PC_SNIF, - H4PC_QWRN, - H4PC_ESW_ROOT, - H4PC_ESW_SET, - H4PC_ESW_SWEEP, - H4PC_WIFI, - H4PC_MQTT, - H4PC_ASWS, - H4PC_SPIF, - H4PC_UPNP, - H4PC_LLOG, - H4PC_SLOG, - H4PC_MLOG + H4PC_UPNP, + H4PC_MAX }; class H4Plugin { protected: - static H4P_CONFIG_BLOCK _cb; - static H4_CMD_MAP commands; H4_FN_VOID _hook=nullptr; H4_INT_MAP _names={}; H4_CMD_MAP _cmds={}; - template - uint32_t guard(vector vs,H4_FN_MSG f){ - if(vs.size()N ? H4_CMD_TOO_MANY_PARAMS:f(vs); - } + static uint32_t nextSubid; + vector expectInt(string pl,const char* delim=","); @@ -202,14 +186,27 @@ class H4Plugin { uint32_t guardInt4(vector vs,function f); - uint32_t guardString1(vector vs,function f); - uint32_t guardString2(vector vs,function f); - + public: + static uint32_t guard1(vector vs,H4_FN_MSG f){ + if(!vs.size()) return H4_CMD_TOO_FEW_PARAMS; + return vs.size()>1 ? H4_CMD_TOO_MANY_PARAMS:f(vs); + } string _pid; // diag hoist - public: - static vector _pending; + uint32_t subid; + + static vector _plugins; + static H4P_CONFIG_BLOCK _cb; + + static H4Plugin* isLoaded(const string& x){ + for(auto const& p:H4Plugin::_plugins) if(p->_pid==x) return p; + return nullptr; + } + static string pidFromSubid(const uint32_t s){ + for(auto const& p:H4Plugin::_plugins) if(p->subid==s) return p->_pid; + return string("WTF? id=").append(stringFromInt(s)); + } virtual void _startup(); @@ -232,7 +229,6 @@ class H4Plugin { class H4PluginService: public H4Plugin { protected: - uint32_t subid; H4_CMD_MAP _local; vector _connChain; vector _discoChain; @@ -245,10 +241,6 @@ class H4PluginService: public H4Plugin { void _startup() override; - virtual void _hookIn() override {} - - virtual void _greenLight() override {} - H4PluginService(H4_FN_VOID onConnect=[](){},H4_FN_VOID onDisconnect=[](){}){ hookConnect(onConnect); hookDisconnect(onDisconnect); @@ -264,17 +256,21 @@ class H4PluginService: public H4Plugin { }; class H4PLogService: public H4PluginService { + bool _running=true; + uint32_t _filter=0; + virtual void _filterLog(const string &msg,H4P_LOG_TYPE type,const string& source,const string& target,uint32_t error=0){ + if(_running){ if(type & _filter) _logEvent(msg,type,source,target,error); } + } protected: - bool _running=true; - void _hookIn() override; + virtual void _logEvent(const string &msg,H4P_LOG_TYPE type,const string& source,const string& target,uint32_t error=0)=0; public: - H4PLogService(const string& lid){ _pid=lid; } - + H4PLogService(const string& lid,uint32_t filter=0xffffffff): _filter(filter){ + _pid=lid; + } void start() override { svc(_pid,H4P_LOG_SVC_UP); _running=true; }; void stop() override { _running=false; svc(_pid,H4P_LOG_SVC_DOWN); }; - }; #endif // H4P_HO diff --git a/src/H4PConfig.h b/src/H4PConfig.h index 01f89b26..4ecbc858 100644 --- a/src/H4PConfig.h +++ b/src/H4PConfig.h @@ -28,8 +28,7 @@ SOFTWARE. */ // comment this out to prevent and logging by EVENT( whatever ) messages -#define H4P_SERIAL_LOGGING - +#define H4P_LOG_EVENTS /* TWEAKABLES */ @@ -54,6 +53,9 @@ SOFTWARE. #define H43F_MEDIUM 175 #define H43F_FAST 50 #define H43F_TIMEBASE 175 +#define H43F_REBOOT 2000 +#define H43F_FACTORY 5000 + #define H4WF_OTA_RATE 1000 #endif \ No newline at end of file diff --git a/src/H4P_AsyncWebServer.cpp b/src/H4P_AsyncWebServer.cpp index e8108299..4549d479 100644 --- a/src/H4P_AsyncWebServer.cpp +++ b/src/H4P_AsyncWebServer.cpp @@ -53,8 +53,8 @@ void H4P_AsyncWebServer::_rest(AsyncWebServerRequest *request){ h4.queueFunction(bind([this](AsyncWebServerRequest *request){ string chop=replaceAll(CSTR(request->url()),"/rest/",""); string msg=""; - uint32_t res=h4sc._simulatePayload(CSTR(chop),aswstag()); - if(h4._hasName(H4P_TRID_CERR)) msg=h4ce.getErrorMessage(res); + uint32_t res=h4sc._simulatePayload(CSTR(chop),aswsTag()); + if(H4Plugin::isLoaded(cerrTag())) msg=h4ce.getErrorMessage(res); string j="{\"res\":"+stringFromInt(res)+",\"msg\":\""+msg+"\",\"lines\":["; string fl; if(!res){ @@ -81,7 +81,6 @@ void H4P_AsyncWebServer::start(){ Serial.printf("WOOT\n"); string rootweb=WiFi.getMode() & WIFI_AP ? "/ap.htm":"/sta.htm"; // streeamline - even fn change request->send(SPIFFS,CSTR(rootweb),String(),false,aswsReplace); -// _cb.erase("opts"); }); on("/",HTTP_POST, [this](AsyncWebServerRequest *request){ @@ -91,11 +90,11 @@ void H4P_AsyncWebServer::start(){ AsyncWebParameter* p = request->getParam(i); rp[CSTR(p->name())]=CSTR(p->value()); } - h4wifi.change(rp[ssidtag()],rp["psk"]); - if(h4._hasName(H4P_TRID_SOAP)){ - h4wifi.setPersistentValue(nametag(),rp[nametag()],false); + h4wifi.change(rp[ssidTag()],rp["psk"]); + if(H4Plugin::isLoaded(upnpTag())){ + h4wifi.setPersistentValue(nameTag(),rp[nameTag()],false); } - h4wifi.host(rp[devicetag()]); + h4wifi.host(rp[deviceTag()]); }); on("/rest",HTTP_GET,[this](AsyncWebServerRequest *request){ _rest(request); }); diff --git a/src/H4P_AsyncWebServer.h b/src/H4P_AsyncWebServer.h index f4bc97e6..bea09d89 100644 --- a/src/H4P_AsyncWebServer.h +++ b/src/H4P_AsyncWebServer.h @@ -31,12 +31,11 @@ SOFTWARE. #define H4P_AsyncWebServer_HO #include -#include -#include +#include #include #ifndef H4P_NO_WIFI - -#include +#include +#include class H4P_AsyncWebServer: public AsyncWebServer, public H4PluginService { H4_CMD_MAP _local={}; @@ -54,8 +53,7 @@ class H4P_AsyncWebServer: public AsyncWebServer, public H4PluginService { { _cb["auser"]=admin; _cb["apasswd"]=passwd; - _pid=aswstag(); - subid=H4PC_ASWS; + _pid=aswsTag(); _names={{H4P_TRID_ASWS,uppercase(_pid)}}; _cmds={}; } diff --git a/src/H4P_BasicSwitch.cpp b/src/H4P_BasicSwitch.cpp deleted file mode 100644 index 69ec78c9..00000000 --- a/src/H4P_BasicSwitch.cpp +++ /dev/null @@ -1,54 +0,0 @@ -/* - MIT License - -Copyright (c) 2019 Phil Bowles - github https://github.com/philbowles/esparto - blog https://8266iot.blogspot.com - groups https://www.facebook.com/groups/esp8266questions/ - https://www.facebook.com/Esparto-Esp8266-Firmware-Support-2338535503093896/ - - -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 -#ifndef H4P_NO_WIFI - -uint32_t H4P_BasicSwitch::_switch(vector vs){ return guardInt1(vs,bind(&H4P_BasicSwitch::turn,this,_1)); } - -void H4P_BasicSwitch::turn(bool b){ if(_pp->state!=b) _pp->logicalWrite(b); } -// -H4P_BasicSwitch::H4P_BasicSwitch(uint8_t pin,H4GM_SENSE sense, uint8_t initial,H4BS_FN_SWITCH f){ - if(!h4._hasName(H4P_TRID_GPIO)){ - _pid=onoftag(); - _names={ {H4P_TRID_UBSW,uppercase(_pid)} }; - _cmds={ - {"on", {H4PC_ROOT, 0, CMD(turnOn)}}, - {"off", {H4PC_ROOT, 0, CMD(turnOff)}}, - {"switch", {H4PC_ROOT, 0, CMDVS(_switch)}}, - {"toggle", {H4PC_ROOT, 0, CMD(toggle)}} - }; -// move to hookin ? NO - _pp=h4gm.Output(pin,sense,initial,[f,this](H4GPIOPin* ptr){ - H4GM_PIN(Output); - _publish(pin->state); - f(pin->state); - }); - } -} -#endif \ No newline at end of file diff --git a/src/H4P_BinarySwitch.h b/src/H4P_BinarySwitch.h new file mode 100644 index 00000000..91a05447 --- /dev/null +++ b/src/H4P_BinarySwitch.h @@ -0,0 +1,49 @@ +/* + MIT License + +Copyright (c) 2019 Phil Bowles + github https://github.com/philbowles/H4 + blog https://8266iot.blogspot.com + groups https://www.facebook.com/groups/esp8266questions/ + https://www.facebook.com/H4-Esp8266-Firmware-Support-2338535503093896/ + + +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. + +*/ +#ifndef H4P_BinarySwitch_HO +#define H4P_BinarySwitch_HO + +#include +#include + +class H4P_BinarySwitch: public H4P_BinaryThing{ +// + protected: + OutputPin* _pp; + virtual void _setState(bool b) override { _pp->logicalWrite(_state=b); } + public: + H4P_BinarySwitch(uint8_t pin,H4GM_SENSE sense, uint32_t initial,H4BS_FN_SWITCH f=[](bool){}): H4P_BinaryThing(f,initial){ + _pp=h4gm.Output(pin,sense,initial,[](H4GPIOPin* ptr){}); + } +}; + +//extern __attribute__((weak)) H4P_BinarySwitch h4bs; + +#endif // H4P_BinarySwitch_H \ No newline at end of file diff --git a/src/H4P_BinaryThing.h b/src/H4P_BinaryThing.h new file mode 100644 index 00000000..ef6cf8a9 --- /dev/null +++ b/src/H4P_BinaryThing.h @@ -0,0 +1,91 @@ +/* + MIT License + +Copyright (c) 2019 Phil Bowles + github https://github.com/philbowles/H4 + blog https://8266iot.blogspot.com + groups https://www.facebook.com/groups/esp8266questions/ + https://www.facebook.com/H4-Esp8266-Firmware-Support-2338535503093896/ + + +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. + +*/ +#ifndef H4P_BinaryThing_HO +#define H4P_BinaryThing_HO + +#include +#include +#ifndef H4P_NO_WIFI + #include +#endif + +class H4P_BinaryThing: public H4Plugin{ +// + protected: + bool _state=false; + H4BS_FN_SWITCH _f; + + #ifdef H4P_NO_WIFI + void _publish(bool b){} + #else + virtual void _hookIn() override { + if(H4Plugin::isLoaded(mqttTag())) h4mqtt.hookConnect([this](){ _publish(_getState()); }); + } + void _publish(bool b){ + if(H4Plugin::isLoaded(mqttTag())) h4mqtt.publishDevice(stateTag(),b); + } + #endif + + virtual bool _getState() { return _state; } + virtual void _setState(bool b) { _state=b; } + + uint32_t _showState(vector vs){ reply("State: %s\n",_getState() ? "ON":"OFF"); return H4_CMD_OK; } + uint32_t _switch(vector vs){ return guardInt1(vs,bind(&H4P_BinaryThing::turn,this,_1)); } + void _greenLight() override { _f(_state); } + + + + + public: + H4P_BinaryThing(H4BS_FN_SWITCH f=[](bool){},bool initial=OFF): _f(f),_state(initial){ + _pid=onofTag(); + _cmds={ + {"on", {H4PC_ROOT, 0, CMD(turnOn)}}, + {"off", {H4PC_ROOT, 0, CMD(turnOff)}}, + {"state", {H4PC_ROOT, 0, CMDVS(_showState)}}, + {"switch", {H4PC_ROOT, 0, CMDVS(_switch)}}, + {"toggle", {H4PC_ROOT, 0, CMD(toggle)}} + }; + } + bool state(){ return _getState(); } + void turnOff(){ turn(false); } + void turnOn(){ turn(true); } + void toggle(){ turn(!_state); } + void turn(bool b){ + if(b!=_getState()){ + _setState(b); + _f(_state); + _publish(_state); + } + } +}; + +//extern __attribute__((weak)) H4P_BinaryThing h4bt; +#endif // H4P_BinaryThing_H \ No newline at end of file diff --git a/src/H4P_CmdErrors.h b/src/H4P_CmdErrors.h index 8d59220c..7a0541f7 100644 --- a/src/H4P_CmdErrors.h +++ b/src/H4P_CmdErrors.h @@ -47,10 +47,7 @@ class H4P_CmdErrors: public H4Plugin { }; // public: - H4P_CmdErrors(){ - _pid=cerrtag(); - _names={ {H4P_TRID_CERR,uppercase(_pid)} }; - } + H4P_CmdErrors(){ _pid=cerrTag(); } string getErrorMessage(uint32_t e){ return cmdErrors.count(e) ? cmdErrors[e]:string("No such error (")+stringFromInt(e)+")"; diff --git a/src/H4P_ExternalSqWave.cpp b/src/H4P_ExternalSqWave.cpp index da909be6..70197bb1 100644 --- a/src/H4P_ExternalSqWave.cpp +++ b/src/H4P_ExternalSqWave.cpp @@ -71,15 +71,17 @@ uint32_t H4P_ExternalSqWave::_dsweep(vector vs){ return __sweep(vs,'D',H // H4P_ExternalSqWave // H4P_ExternalSqWave::H4P_ExternalSqWave(uint8_t rx,uint8_t tx,uint32_t initialF,uint32_t initialD): SoftwareSerial(rx,tx){ - _pid=esqwtag(); + _pid=esqwTag(); _names={ {H4P_TRID_SQWV,uppercase(_pid)} }; + uint32_t H4PC_ESW_SET=++nextSubid; + uint32_t H4PC_ESW_SWEEP=++nextSubid; _cmds={ - {_pid, { H4PC_ROOT, H4PC_ESW_ROOT, nullptr}}, - {"set", { H4PC_ESW_ROOT, H4PC_ESW_SET, nullptr}}, + {_pid, { H4PC_ROOT, subid, nullptr}}, + {"set", { subid, H4PC_ESW_SET, nullptr}}, {"d", { H4PC_ESW_SET, 0, CMDVS(_dset)}}, {"f", { H4PC_ESW_SET, 0, CMDVS(_fset)}}, - {"stop", { H4PC_ESW_ROOT, 0, CMD(stop)}}, - {"sweep", { H4PC_ESW_ROOT, H4PC_ESW_SWEEP, nullptr}}, + {"stop", { subid, 0, CMD(stop)}}, + {"sweep", { subid, H4PC_ESW_SWEEP, nullptr}}, {"f", { H4PC_ESW_SWEEP, 0, CMDVS(_fsweep)}}, {"d", { H4PC_ESW_SWEEP, 0, CMDVS(_dsweep)}} }; diff --git a/src/H4P_FlasherController.cpp b/src/H4P_FlasherController.cpp index e2c93487..edc6e433 100644 --- a/src/H4P_FlasherController.cpp +++ b/src/H4P_FlasherController.cpp @@ -64,9 +64,9 @@ std::unordered_map H4P_FlasherController::_morse={ // H4P_FlasherController // H4P_FlasherController::H4P_FlasherController(){ - _pid=winktag(); + _pid=winkTag(); _names={ - {H4P_TRID_PP1x,uppercase(winktag())}, + {H4P_TRID_PP1x,uppercase(winkTag())}, {H4P_TRID_PATN,"PATN"}, {H4P_TRID_PWM1,"PWM1"} }; @@ -135,9 +135,7 @@ H4Flasher::H4Flasher(uint8_t pin,const char* pattern,uint32_t timebase,uint8_t a flashPattern(timebase,pattern); } -void H4Flasher::autoOutput(){ - if(h4._hasName(H4P_TRID_GPIO)){ h4gm.Output(_pin,a ? ACTIVE_HIGH:ACTIVE_LOW,OFF); } -} +void H4Flasher::autoOutput(){ if(H4Plugin::isLoaded(gpioTag())) h4gm.Output(_pin,a ? ACTIVE_HIGH:ACTIVE_LOW,OFF); } void H4Flasher::_toggle() { digitalWrite(_pin,!digitalRead(_pin)); } diff --git a/src/H4P_FlasherController.h b/src/H4P_FlasherController.h index 61660d9b..11b64abc 100644 --- a/src/H4P_FlasherController.h +++ b/src/H4P_FlasherController.h @@ -55,7 +55,7 @@ class H4Flasher{ void stop(); }; -constexpr const char* ftag(){ return "wink"; } +constexpr const char* fTag(){ return "wink"; } class H4P_FlasherController: public H4Plugin { std::unordered_map _flashMap; diff --git a/src/H4P_GPIOManager.cpp b/src/H4P_GPIOManager.cpp index 187965a3..6a10481d 100644 --- a/src/H4P_GPIOManager.cpp +++ b/src/H4P_GPIOManager.cpp @@ -46,6 +46,25 @@ H4GPIOPin::H4GPIOPin( // // DIAGNOSTICS // +void H4GPIOPin::_factoryCommon(H4P_BinaryThing* btp){ +// hookThing(btp); // denormalise + if(btp){ + onEvent=[btp](H4GPIOPin* pp){ + /* + Serial.print("Hooked thing says "); + Serial.print(pp->logicalRead()); + Serial.print(" nE="); + Serial.println(pp->nEvents); + */ + if(pp->style==H4GM_PS_LATCHING) { + if(pp->nEvents) btp->toggle(); // POTENTIAL BUG! if btp already commanded on, 1st time wont toggle + } else btp->turn(pp->logicalRead()); + }; + } + H4P_GPIOManager::pins[pin]=this; + begin(); +} + void H4GPIOPin::dump(){ // tart this up Serial.print("PIN ");Serial.println(pin); Serial.print(" gpioType=");Serial.println(gpioType); @@ -91,7 +110,6 @@ void H4GPIOPin::run(){ void H4P_GPIOManager::run(){ for(auto const& p:pins) (p.second)->run(); } void H4P_GPIOManager::_greenLight(){ -// Serial.println("_greenLight"); h4.every(1000,[this](){ for(auto p:pins){ H4GPIOPin* ptr=p.second; @@ -100,14 +118,14 @@ void H4P_GPIOManager::_greenLight(){ ptr->Rpeak=std::max(ptr->Rpeak,ptr->rate); ptr->cps=0; } - },nullptr,H4P_TRID_GPIO,true); + },nullptr,H4P_TRID_SYNC,true); } H4P_GPIOManager::H4P_GPIOManager(){ - _pid=gpiotag(); + _pid=gpioTag(); _hook=[this](){ run(); }; _names={ - {H4P_TRID_GPIO,uppercase(_pid)}, + {H4P_TRID_SYNC,"SYNC"}, {H4P_TRID_DBNC,"DBNC"}, {H4P_TRID_RPTP,"RPTP"}, {H4P_TRID_POLL,"POLL"}, @@ -214,7 +232,7 @@ SequencedPin::SequencedPin(uint8_t _p,uint8_t _g,H4GM_STYLE _s,uint8_t _a,uint32 void SequencedPin::sendEvent() { if(state){ ++stage; - lastCall(); + lastCall(); } } @@ -223,10 +241,7 @@ void RetriggeringPin::stateChange(){ // Serial.print("SC ");Serial.println(state); if(state){ if(timer) timer=h4.cancel(timer); - else { -// Serial.print("SE NO TIMER ");Serial.println(state); - sendEvent(); - } + else sendEvent(); timer=h4.once(timeout,[this]( ){ timer=nullptr; sendEvent(); @@ -275,7 +290,7 @@ OutputPin* H4P_GPIOManager::isOutput(uint8_t p){ return nullptr; } -uint32_t H4P_GPIOManager::logicalRead(uint8_t p){ if(pins.count(p)) return pins[p]->state; } +uint32_t H4P_GPIOManager::logicalRead(uint8_t p){ if(pins.count(p)) return pins[p]->logicalRead(); } void H4P_GPIOManager::logicalWrite(uint8_t p,uint8_t l) { if(isManaged(p)) if(pins[p]->style==H4GM_PS_OUTPUT) reinterpret_cast(pins[p])->logicalWrite(l); @@ -288,37 +303,39 @@ void H4P_GPIOManager::toggle(uint8_t p){ if(isManaged(p)) reinterpret_cast(pin,mode,H4GM_PS_CIRCULAR,sense,dbTimeMs,nStages,callback); + return thingFactory(nullptr,pin,mode,H4GM_PS_CIRCULAR,sense,dbTimeMs,nStages,callback); } -LatchingPin* H4P_GPIOManager::Latching(uint8_t pin,uint8_t mode,H4GM_SENSE sense,uint32_t dbTimeMs,H4GM_FN_EVENT callback){ - return pinFactory(pin, mode, H4GM_PS_LATCHING, sense, dbTimeMs, callback); -} DebouncedPin* H4P_GPIOManager::Debounced(uint8_t pin,uint8_t mode,H4GM_SENSE sense,uint32_t dbTimeMs,H4GM_FN_EVENT callback){ - return pinFactory(pin, mode, H4GM_PS_DEBOUNCED, sense, dbTimeMs, callback); + return thingFactory(nullptr,pin, mode, H4GM_PS_DEBOUNCED, sense, dbTimeMs, callback); +} +DebouncedPin* H4P_GPIOManager::DebouncedThing(uint8_t pin,uint8_t mode,H4GM_SENSE sense,uint32_t dbTimeMs,H4P_BinaryThing* btp){ + return thingFactory(btp,pin, mode, H4GM_PS_DEBOUNCED, sense, dbTimeMs, nullptr); } EncoderPin* H4P_GPIOManager::Encoder(uint8_t pinA,uint8_t pinB,uint8_t mode,H4GM_SENSE sense,H4GM_FN_EVENT callback){ - EncoderPin* pa=pinFactory(pinA,mode, H4GM_PS_ENCODER_A, sense, [](H4GPIOPin*){}); - EncoderPin* pb=pinFactory(pinB,mode, H4GM_PS_ENCODER_B, sense, callback); - pb->_primary=false; - pa->_otherPin=pb; - pb->_otherPin=pa; - return pb; -} + EncoderPin* pa=thingFactory(nullptr,pinA,mode, H4GM_PS_ENCODER_A, sense, nullptr); + EncoderPin* pb=thingFactory(nullptr,pinB,mode, H4GM_PS_ENCODER_B, sense, callback); + return pb->_pairUp(pa); +} EncoderPin* H4P_GPIOManager::Encoder(uint8_t pinA,uint8_t pinB,uint8_t mode,H4GM_SENSE sense,int& i){ - return Encoder(pinA,pinB,mode,sense,[&i](H4GPIOPin* p){ i+=reinterpret_cast(p)->encoderValue; }); + return Encoder(pinA,pinB,mode,sense,[&i](H4GPIOPin* p){ i+=reinterpret_cast(p)->encoderValue; }); +} +EncoderPin* H4P_GPIOManager::EncoderThing(uint8_t pinA,uint8_t pinB,uint8_t mode,H4GM_SENSE sense,H4P_BinaryThing* btp){ + EncoderPin* pa=thingFactory(nullptr,pinA,mode, H4GM_PS_ENCODER_A, sense, nullptr); + EncoderPin* pb=thingFactory(btp,pinB,mode, H4GM_PS_ENCODER_B, sense, nullptr); + return pb->_pairUp(pa); } EncoderAutoPin* H4P_GPIOManager::EncoderAuto(uint8_t pinA,uint8_t pinB,uint8_t mode,H4GM_SENSE sense,int vMin,int vMax,int vSet,uint32_t vIncr,H4GM_FN_EVENT callback){ - EncoderAutoPin* pa=pinFactory(pinA,mode, H4GM_PS_ENCODER_AUTO_A, sense, vMin, vMax, vIncr, [](H4GPIOPin*){}); - EncoderAutoPin* pb=pinFactory(pinB,mode, H4GM_PS_ENCODER_AUTO_B, sense, vMin, vMax, vIncr, callback); + EncoderAutoPin* pa=thingFactory(nullptr,pinA,mode, H4GM_PS_ENCODER_AUTO_A, sense, vMin, vMax, vIncr, nullptr); + EncoderAutoPin* pb=thingFactory(nullptr,pinB,mode, H4GM_PS_ENCODER_AUTO_B, sense, vMin, vMax, vIncr, callback); + if(vSet < vMin || vSet > vMax) pb->setCenter(); + else pb->setValue(vSet); pb->_primary=false; pa->_otherPin=pb; pb->_otherPin=pa; - if(vSet < vMin || vSet > vMax) pb->setCenter(); - else pb->setValue(vSet); return pb; } @@ -327,45 +344,57 @@ EncoderAutoPin* H4P_GPIOManager::EncoderAuto(uint8_t pinA,uint8_t pinB,uint8_t m } FilteredPin* H4P_GPIOManager::Filtered(uint8_t pin,uint8_t mode,H4GM_SENSE sense,uint8_t filter,H4GM_FN_EVENT callback){ - return pinFactory(pin, mode, H4GM_PS_FILTERED, sense, filter, callback); + return thingFactory(nullptr,pin, mode, H4GM_PS_FILTERED, sense, filter, callback); +} + +LatchingPin* H4P_GPIOManager::Latching(uint8_t pin,uint8_t mode,H4GM_SENSE sense,uint32_t dbTimeMs,H4GM_FN_EVENT callback){ + return thingFactory(nullptr,pin, mode, H4GM_PS_LATCHING, sense, dbTimeMs, callback); +} +LatchingPin* H4P_GPIOManager::LatchingThing(uint8_t pin,uint8_t mode,H4GM_SENSE sense,uint32_t dbTimeMs,H4P_BinaryThing* btp){ + return thingFactory(btp,pin, mode, H4GM_PS_LATCHING, sense, dbTimeMs,nullptr); // replace with nullotr and if? } MultistagePin* H4P_GPIOManager::Multistage(uint8_t pin,uint8_t mode,H4GM_SENSE sense,uint32_t dbTimeMs,H4GM_STAGE_MAP stageMap,H4GM_FN_EVENT callback){ - return pinFactory(pin,mode,H4GM_PS_MULTISTAGE,sense,dbTimeMs,stageMap,callback); + return thingFactory(nullptr,pin,mode,H4GM_PS_MULTISTAGE,sense,dbTimeMs,stageMap,callback); } OutputPin* H4P_GPIOManager::Output(uint8_t pin,H4GM_SENSE sense,uint8_t initial,H4GM_FN_EVENT callback){ - return pinFactory(pin, H4GM_PS_OUTPUT, sense, initial, callback); + return thingFactory(nullptr,pin, H4GM_PS_OUTPUT, sense, initial, callback); } PolledPin* H4P_GPIOManager::Polled(uint8_t pin,uint8_t mode,H4GM_SENSE sense,uint32_t frequency,uint32_t isAnalog,H4GM_FN_EVENT callback){ // fix order - return pinFactory(pin, mode, H4GM_PS_POLLED, sense, frequency, isAnalog, callback); + return thingFactory(nullptr,pin, mode, H4GM_PS_POLLED, sense, frequency, isAnalog, callback); +} +PolledPin* H4P_GPIOManager::PolledThing(uint8_t pin,uint8_t mode,H4GM_SENSE sense,uint32_t frequency,uint32_t isAnalog,H4P_BinaryThing* btp){ // fix order + return thingFactory(btp,pin, mode, H4GM_PS_POLLED, sense, frequency, isAnalog, nullptr); } RawPin* H4P_GPIOManager::Raw(uint8_t pin,uint8_t mode,H4GM_SENSE sense,H4GM_FN_EVENT callback){ - return pinFactory(pin, mode, H4GM_PS_RAW, sense, callback); + return thingFactory(nullptr,pin, mode, H4GM_PS_RAW, sense, callback); +} +RawPin* H4P_GPIOManager::RawThing(uint8_t pin,uint8_t mode,H4GM_SENSE sense,H4P_BinaryThing* btp){ + return thingFactory(btp,pin, mode, H4GM_PS_RAW, sense, nullptr); } RepeatingPin* H4P_GPIOManager::Repeating(uint8_t pin,uint8_t mode,H4GM_SENSE sense,uint32_t dbTimeMs,uint32_t frequency,H4GM_FN_EVENT callback){ - return pinFactory(pin, mode, H4GM_PS_REPEATING, sense, dbTimeMs, frequency, callback); + return thingFactory(nullptr,pin, mode, H4GM_PS_REPEATING, sense, dbTimeMs, frequency, callback); } RetriggeringPin* H4P_GPIOManager::Retriggering(uint8_t pin,uint8_t mode,H4GM_SENSE sense,uint32_t timeout,H4GM_FN_EVENT callback){ - return pinFactory(pin, mode, H4GM_PS_REPEATING, sense, timeout, callback); + return thingFactory(nullptr,pin, mode, H4GM_PS_REPEATING, sense, timeout, callback); +} +RetriggeringPin* H4P_GPIOManager::RetriggeringThing(uint8_t pin,uint8_t mode,H4GM_SENSE sense,uint32_t timeout,H4P_BinaryThing* btp){ + return thingFactory(btp,pin, mode, H4GM_PS_REPEATING, sense, timeout, nullptr); } SequencedPin* H4P_GPIOManager::Sequenced(uint8_t pin,uint8_t mode,H4GM_SENSE sense,uint32_t dbTimeMs,H4GM_FN_EVENT callback){ - return pinFactory(pin, mode, H4GM_PS_SEQUENCED, sense, dbTimeMs, callback); + return thingFactory(nullptr,pin, mode, H4GM_PS_SEQUENCED, sense, dbTimeMs, callback); } TimedPin* H4P_GPIOManager::Timed(uint8_t pin,uint8_t mode,H4GM_SENSE sense,uint32_t dbTimeMs,H4GM_FN_EVENT callback){ - return pinFactory(pin, mode, H4GM_PS_TIMED, sense, dbTimeMs, 0, callback); + return thingFactory(nullptr,pin, mode, H4GM_PS_TIMED, sense, dbTimeMs, 0, callback); } // // DIAGNOSTICS // - -// -// cmd functions -// void H4P_GPIOManager::dump(){ for(auto const& p:pins) p.second->dump(); } diff --git a/src/H4P_GPIOManager.h b/src/H4P_GPIOManager.h index 476566ea..0a9b5f52 100644 --- a/src/H4P_GPIOManager.h +++ b/src/H4P_GPIOManager.h @@ -31,6 +31,7 @@ SOFTWARE. #define H4GPIOmanager_HO #include +#include #include #define H4GM_PASTE(x) x##Pin @@ -59,8 +60,6 @@ enum H4GM_SENSE:uint8_t { ACTIVE_HIGH }; -#define ON true -#define OFF false #define NORMALISE(a,b) (!(a ^ b)) class H4GPIOPin; @@ -81,7 +80,6 @@ class H4GPIOPin{ H4GM_FN_EVENT onEvent=nullptr; void _setState(uint32_t s){ // tidy -// Serial.printf("_setState %d\n",s); state=s; stateChange(); } @@ -100,7 +98,10 @@ class H4GPIOPin{ void stampEvent(); virtual void stateChange(){ sendEvent(); }; - + + virtual uint32_t logicalRead(){ return state; } + + void _factoryCommon(H4P_BinaryThing* btp); public: uint8_t pin=0; // GPIO hardware pin number uint8_t gpioType=0; // INPUT, INOUT_PULLUP, OUTPUT etc @@ -120,6 +121,7 @@ class H4GPIOPin{ sendEvent increments nEvents, so unless we do this hack, all pins will appear to have had an event, when in reality, they haven't */ + virtual void begin(){ lastCall(); } virtual void lastCall(){ sendEvent(); } @@ -140,7 +142,6 @@ class H4GPIOPin{ virtual ~H4GPIOPin(){} virtual void dump(); - }; // // Inheritance order, not alphabetical @@ -290,12 +291,12 @@ class LatchingPin: public CircularPin { protected: virtual void lastCall() override; public: + uint32_t logicalRead() override { return latched; } virtual void dump(){ CircularPin::dump(); Serial.print(" Latching=");Serial.println(latched); } uint32_t latched; // logical state - LatchingPin(uint8_t _p,uint8_t _g,H4GM_STYLE _s,uint8_t _a,uint32_t _d,H4GM_FN_EVENT _c); virtual ~LatchingPin(){} }; @@ -314,12 +315,19 @@ class OutputPin: public H4GPIOPin { class EncoderPin: public H4GPIOPin{ protected: - static int8_t rot_states[];// {0, -1, 1, 0, 1, 0, 0, -1, -1, 0, 0, 1, 0, 1, -1, 0}; // make static + + static int8_t rot_states[];// {0, -1, 1, 0, 1, 0, 0, -1, -1, 0, 0, 1, 0, 1, -1, 0}; // make static uint8_t AB = 0x03; void stateChange(); - public: + EncoderPin* _pairUp(EncoderPin* pa){ + _primary=false; + pa->_otherPin=this; + _otherPin=pa; + return this; + } + uint32_t logicalRead() override { return constrain(encoderValue,0,1); } // constrain virtual void dump(){ H4GPIOPin::dump(); Serial.print(" encoderValue=");Serial.println(encoderValue); @@ -340,6 +348,7 @@ class EncoderAutoPin: public EncoderPin{ virtual void sendEvent() override { setValue(autoValue+(vIncr*encoderValue)); } int vMin,vMax,vIncr; public: + uint32_t logicalRead() override { return autoValue; } int autoValue=0; virtual void dump(){ EncoderPin::dump(); @@ -364,24 +373,22 @@ class EncoderAutoPin: public EncoderPin{ // H4P_GPIOManager // class H4P_GPIOManager: public H4Plugin{// - H4GM_PINMAP pins={}; - void _greenLight() override; H4GPIOPin* isManaged(uint8_t p){ return pins.count(p) ? pins[p]:nullptr; } OutputPin* isOutput(uint8_t p); template - T* pinFactory(uint8_t _p,Args... args) { + T* thingFactory(H4P_BinaryThing* btp,uint8_t _p,Args... args) { T* pinclass=new T(_p,args...); - pins[_p]=pinclass; - pinclass->begin(); + pinclass->_factoryCommon(btp); return pinclass; } void run(); public: + static H4GM_PINMAP pins; H4P_GPIOManager(); // returns 32 not 8 as it can also analogRead and state will hold analog value as well as digital 1/0 uint32_t logicalRead(uint8_t p); @@ -393,18 +400,24 @@ class H4P_GPIOManager: public H4Plugin{// // CircularPin* Circular(uint8_t p,uint8_t mode,H4GM_SENSE sense,uint32_t dbTimeMs,uint32_t nStages,H4GM_FN_EVENT callback);// DebouncedPin* Debounced(uint8_t p,uint8_t mode,H4GM_SENSE sense,uint32_t dbTimeMs,H4GM_FN_EVENT callback);// - EncoderPin* Encoder(uint8_t pA,uint8_t pB,uint8_t mode,H4GM_SENSE sense,H4GM_FN_EVENT); - EncoderPin* Encoder(uint8_t pA,uint8_t pB,uint8_t mode,H4GM_SENSE sense,int&); - EncoderAutoPin* EncoderAuto(uint8_t pA,uint8_t pB,uint8_t mode,H4GM_SENSE sense,int vMin,int vMax,int vSet,uint32_t vIncr,H4GM_FN_EVENT); - EncoderAutoPin* EncoderAuto(uint8_t pA,uint8_t pB,uint8_t mode,H4GM_SENSE sense,int vMin,int vMax,int vSet,uint32_t vIncr,int&); + DebouncedPin* DebouncedThing(uint8_t p,uint8_t mode,H4GM_SENSE sense,uint32_t dbTimeMs,H4P_BinaryThing* btp);// + EncoderPin* Encoder(uint8_t pA,uint8_t pB,uint8_t mode,H4GM_SENSE sense,H4GM_FN_EVENT); + EncoderPin* Encoder(uint8_t pA,uint8_t pB,uint8_t mode,H4GM_SENSE sense,int&); + EncoderPin* EncoderThing(uint8_t pA,uint8_t pB,uint8_t mode,H4GM_SENSE sense,H4P_BinaryThing* btp); + EncoderAutoPin* EncoderAuto(uint8_t pA,uint8_t pB,uint8_t mode,H4GM_SENSE sense,int vMin,int vMax,int vSet,uint32_t vIncr,H4GM_FN_EVENT); + EncoderAutoPin* EncoderAuto(uint8_t pA,uint8_t pB,uint8_t mode,H4GM_SENSE sense,int vMin,int vMax,int vSet,uint32_t vIncr,int&); FilteredPin* Filtered(uint8_t p,uint8_t mode,H4GM_SENSE sense,uint8_t filter,H4GM_FN_EVENT callback);// LatchingPin* Latching(uint8_t p,uint8_t mode,H4GM_SENSE sense,uint32_t dbTimeMs,H4GM_FN_EVENT callback);// + LatchingPin* LatchingThing(uint8_t p,uint8_t mode,H4GM_SENSE sense,uint32_t dbTimeMs,H4P_BinaryThing* btp);// MultistagePin* Multistage(uint8_t p,uint8_t mode,H4GM_SENSE sense,uint32_t dbTimeMs,H4GM_STAGE_MAP stageMap,H4GM_FN_EVENT callback);// - OutputPin* Output(uint8_t p,H4GM_SENSE sense,uint8_t initial,H4GM_FN_EVENT callback=[](H4GPIOPin*){});// FIX ptr type + OutputPin* Output(uint8_t p,H4GM_SENSE sense,uint8_t initial,H4GM_FN_EVENT callback=nullptr);// FIX ptr type PolledPin* Polled(uint8_t p,uint8_t mode,H4GM_SENSE sense,uint32_t frequency,uint32_t isAnalog,H4GM_FN_EVENT callback);// + PolledPin* PolledThing(uint8_t p,uint8_t mode,H4GM_SENSE sense,uint32_t frequency,uint32_t isAnalog,H4P_BinaryThing* btp);// RawPin* Raw(uint8_t p,uint8_t mode,H4GM_SENSE sense,H4GM_FN_EVENT callback);// + RawPin* RawThing(uint8_t p,uint8_t mode,H4GM_SENSE sense,H4P_BinaryThing*);// RepeatingPin* Repeating(uint8_t p,uint8_t mode,H4GM_SENSE sense,uint32_t dbTimeMs,uint32_t frequency,H4GM_FN_EVENT callback);// RetriggeringPin* Retriggering(uint8_t _p, uint8_t _mode,H4GM_SENSE sense,uint32_t timeout, H4GM_FN_EVENT _callback); + RetriggeringPin* RetriggeringThing(uint8_t _p, uint8_t _mode,H4GM_SENSE sense,uint32_t timeout,H4P_BinaryThing* btp); SequencedPin* Sequenced(uint8_t p,uint8_t mode,H4GM_SENSE sense,uint32_t dbTimeMs,H4GM_FN_EVENT callback); // TimedPin* Timed(uint8_t p,uint8_t mode,H4GM_SENSE sense,uint32_t dbTimeMs,H4GM_FN_EVENT callback); // // diff --git a/src/H4P_HttpMySQLLogger.h b/src/H4P_HttpMySQLLogger.h new file mode 100644 index 00000000..ca2f4384 --- /dev/null +++ b/src/H4P_HttpMySQLLogger.h @@ -0,0 +1,70 @@ +/* + MIT License + +Copyright (c) 2019 Phil Bowles + github https://github.com/philbowles/H4 + blog https://8266iot.blogspot.com + groups https://www.facebook.com/groups/esp8266questions/ + https://www.facebook.com/H4-Esp8266-Firmware-Support-2338535503093896/ + + +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. + +*/ +#ifndef H4P_HttpMySQLLogger_HO +#define H4P_HttpMySQLLogger_HO + +#ifndef ARDUINO_ARCH_STM32 +#include +#include + +class H4P_HttpMySQLLogger: public H4PLogService { + asyncHTTPrequest request; + + void requestCB(void* optParm, asyncHTTPrequest* request, int readyState){ + if(readyState == 4){ + Serial.println(request->responseText()); + Serial.println(); + request->setDebug(false); + } + } + // + void _logEvent(const string &msg,H4P_LOG_TYPE type,const string& source,const string& target,uint32_t error){ + request.open("POST", "http://192.168.1.22:8266/"); + char buf[256]; + sprintf(buf,"msg=%s&type=%d&source=%s&target=%s&error=%d",CSTR(msg),type,CSTR(source),CSTR(target),error); +// Serial.printf("H=%u %s\n",ESP.getFreeHeap(),buf); + request.setReqHeader("Content-Type","application/x-www-form-urlencoded"); + request.send(buf); + } + void _hookIn() override { // protect + if(H4Plugin::isLoaded(wifiTag())){ + H4PLogService::_hookIn(); + h4wifi.hookConnect([this](){ start(); }); + h4wifi.hookDisconnect([this](){ stop(); }); + request.setDebug(true); + request.onReadyStateChange(bind(&H4P_HttpMySQLLogger::requestCB,this,_1,_2,_3)); + } + else { DEPENDFAIL(wifi); } + } + public: + H4P_HttpMySQLLogger(uint32_t filter=H4P_LOG_ALL): H4PLogService("hlog",filter){} +}; +#endif +#endif // H4P_HttpMySQLLogger_H \ No newline at end of file diff --git a/src/H4P_LocalLogger.cpp b/src/H4P_LocalLogger.cpp index 99d671a6..9b0f4af8 100644 --- a/src/H4P_LocalLogger.cpp +++ b/src/H4P_LocalLogger.cpp @@ -29,32 +29,30 @@ SOFTWARE. #ifndef ARDUINO_ARCH_STM32 #include // -H4P_LocalLogger::H4P_LocalLogger(uint32_t limit): H4PLogService(logtag()), _limit(limit) { - subid=H4PC_LLOG; - _names={ {H4P_TRID_LLOG,uppercase(_pid)} }; +H4P_LocalLogger::H4P_LocalLogger(uint32_t limit): H4PLogService(logTag()), _limit(limit) { + // subid=H4PC_LLOG; +// _names={ {H4P_TRID_LLOG,uppercase(_pid)} }; _local={ {_pid, {H4PC_SHOW, 0, CMD(show)}}, - {"clear", {H4PC_LLOG, 0, CMD(clear)}}, - {"flush", {H4PC_LLOG, 0, CMD(flush)}} + {"clear", {subid, 0, CMD(clear)}}, + {"flush", {subid, 0, CMD(flush)}} }; } -void H4P_LocalLogger::clear(){ SPIFFS.remove(CSTR(string("/").append(logtag()))); } +void H4P_LocalLogger::clear(){ SPIFFS.remove(CSTR(string("/").append(logTag()))); } void H4P_LocalLogger::flush(){ show(); clear(); } -void H4P_LocalLogger::show(){ h4sc._dump(vector{logtag()}); } +void H4P_LocalLogger::show(){ h4sc._dump(vector{logTag()}); } // // our raison d'etre // void H4P_LocalLogger::_logEvent(const string &msg,H4P_LOG_TYPE type,const string& source,const string& target,uint32_t e){ - if(_running){ - vector msgparts={stringFromInt(millis()),stringFromInt(type),source,target,stringFromInt(e),msg}; - uint32_t size=h4sc.write("/log",join(msgparts,",").append("\n"),"a"); - if(size > _limit) flush(); - } + vector msgparts={stringFromInt(millis()),stringFromInt(type),source,target,stringFromInt(e),msg}; + uint32_t size=h4sc.write("/log",join(msgparts,",").append("\n"),"a"); + if(size > _limit) flush(); } #endif \ No newline at end of file diff --git a/src/H4P_MQTT.cpp b/src/H4P_MQTT.cpp index 4d11be4a..c602dbfc 100644 --- a/src/H4P_MQTT.cpp +++ b/src/H4P_MQTT.cpp @@ -29,10 +29,10 @@ SOFTWARE. #include #ifndef H4P_NO_WIFI uint32_t H4P_MQTT::_change(vector vs){ - return guard<1>(vs,[this](vector vs){ - auto vg=split(PAYLOAD,","); - if(vg.size()==2 && isNumeric(vg[1])) return ([this](string s,uint16_t p){ change(s,p); return H4_CMD_OK; })(vg[0],atoi(CSTR(vg[1]))); - else return H4_CMD_PAYLOAD_FORMAT; + return guardString2(vs,[this](string a,string b){ + if(isNumeric(b)){ + change(a,atoi(CSTR(b))); + } }); } @@ -42,28 +42,28 @@ void H4P_MQTT::_forceDisconnect(bool autorestart){ if(!_discoDone){ h4pcDisconnected(); _discoDone=true; - Serial.printf("MQTT down=%d\n",state()); + EVENT("MQTT down=%d\n",state()); } if(autorestart && WiFi.status()==WL_CONNECTED) start(); } void H4P_MQTT::_hookIn() { - if(h4._hasName(H4P_TRID_SCMD) && h4._hasName(H4P_TRID_WIFI) ){ + if(H4Plugin::isLoaded(wifiTag()) ){ _setup(); h4wifi.hookConnect([this](){ start(); }); h4wifi.hookDisconnect([this](){ _forceDisconnect(); }); - } + } else { DEPENDFAIL(wifi); } } uint32_t H4P_MQTT::_offline(vector vs){ - return guard<1>(vs,[this](vector vs){ + return guard1(vs,[this](vector vs){ if(PAYLOAD!=device) _grid.erase(PAYLOAD); return H4_CMD_OK; }); } uint32_t H4P_MQTT::_online(vector vs){ - return guard<1>(vs,[this](vector vs){ + return guard1(vs,[this](vector vs){ if(PAYLOAD!=device) _grid.insert(PAYLOAD); return H4_CMD_OK; }); @@ -71,8 +71,8 @@ uint32_t H4P_MQTT::_online(vector vs){ void H4P_MQTT::_setup(){ setClient(_wifiClient); - string broker=_cb[brokertag()]; - uint16_t port=atoi(CSTR(_cb[porttag()])); + string broker=_cb[brokerTag()]; + uint16_t port=atoi(CSTR(_cb[portTag()])); if(atoi(CSTR(broker))) { vector vs=split(broker,"."); setServer(IPAddress(PARAM_INT(0),PARAM_INT(1),PARAM_INT(2),PARAM_INT(3)),port); @@ -81,8 +81,7 @@ void H4P_MQTT::_setup(){ setCallback([](char* topic, byte* payload, unsigned int length){ h4.queueFunction( bind([](string topic, string pload){ -// Serial.printf("H4P_MQTT MESSAGE %s [%s]\n",CSTR(topic),CSTR(pload)); - h4sc._executeCmd(CSTR(string(mqtttag()).append("/").append(topic)),pload); + h4sc._executeCmd(CSTR(string(mqttTag()).append("/").append(topic)),pload); },string(topic),stringFromBuff(payload,length)),nullptr,H4P_TRID_MQMS); }); } @@ -90,8 +89,8 @@ void H4P_MQTT::_setup(){ void H4P_MQTT::change(const string& broker,uint16_t port){ stop(); _wifiClient.stop(); - _cb[brokertag()]=broker; - _cb[porttag()]=stringFromInt(port); + _cb[brokerTag()]=broker; + _cb[portTag()]=stringFromInt(port); _setup(); start(); } @@ -130,7 +129,7 @@ void H4P_MQTT::unsubscribeDevice(string topic){ */ void H4P_MQTT::start(){ if(!(WiFi.getMode() & WIFI_AP)){ - device=_cb[devicetag()]; + device=_cb[deviceTag()]; if(connect( CSTR(device), CSTR(_cb["muser"]), CSTR(_cb["mpasswd"]), @@ -147,11 +146,11 @@ void H4P_MQTT::start(){ },nullptr,H4P_TRID_MQTT,true); subscribe(CSTR(string("all/").append(cmdhash()))); subscribe(CSTR(string(device+"/"+cmdhash()))); - subscribe(CSTR(string(_cb[chiptag()]+"/"+cmdhash()))); - subscribe(CSTR(string(_cb[boardtag()]+"/"+cmdhash()))); + subscribe(CSTR(string(_cb[chipTag()]+"/"+cmdhash()))); + subscribe(CSTR(string(_cb[boardTag()]+"/"+cmdhash()))); publish("all/h4/mqtt/online",CSTR(device)); h4pcConnected(); } else h4.once(H4MQ_RETRY,[this](){ start(); },nullptr,H4P_TRID_MQRC,true); - } //else Serial.printf("MQTT IGNORED IN AP MODE"); + } } #endif \ No newline at end of file diff --git a/src/H4P_MQTT.h b/src/H4P_MQTT.h index db0ba5ff..861e5143 100644 --- a/src/H4P_MQTT.h +++ b/src/H4P_MQTT.h @@ -12,7 +12,7 @@ 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 +copies of the Software, and to permit persons to whom the Software iss furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all @@ -32,6 +32,7 @@ SOFTWARE. #include #include +#include #include #ifndef H4P_NO_WIFI @@ -62,8 +63,8 @@ class H4P_MQTT: public H4PluginService, public PubSubClient{ _cb["muser"]=user, _cb["mpasswd"]=pass; - _pid=mqtttag(); - subid=H4PC_MQTT; + _pid=mqttTag(); + //subid=subid; _names={ {H4P_TRID_MQMS,"MQMS"}, @@ -72,11 +73,11 @@ class H4P_MQTT: public H4PluginService, public PubSubClient{ }; _local={ - {"change", { H4PC_MQTT, 0, CMDVS(_change) }}, - {"grid", { H4PC_MQTT, 0, CMD(showGrid) }}, - {"offline", { H4PC_MQTT, 0, CMDVS(_offline) }}, - {"online", { H4PC_MQTT, 0, CMDVS(_online) }} -// {"set", { H4PC_MQTT, 0, [this](vector vs){ return H4PluginService::_setHandler(vs); }}} + {"change", { subid, 0, CMDVS(_change) }}, + {"grid", { subid, 0, CMD(showGrid) }}, + {"offline", { subid, 0, CMDVS(_offline) }}, + {"online", { subid, 0, CMDVS(_online) }} +// {"set", { subid, 0, [this](vector vs){ return H4PluginService::_setHandler(vs); }}} }; } void change(const string& broker,uint16_t port); diff --git a/src/H4P_MQTTHeapLogger.h b/src/H4P_MQTTHeapLogger.h new file mode 100644 index 00000000..8843231b --- /dev/null +++ b/src/H4P_MQTTHeapLogger.h @@ -0,0 +1,46 @@ +/* + MIT License + +Copyright (c) 2019 Phil Bowles + github https://github.com/philbowles/H4 + blog https://8266iot.blogspot.com + groups https://www.facebook.com/groups/esp8266questions/ + https://www.facebook.com/H4-Esp8266-Firmware-Support-2338535503093896/ + + +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. + +*/ +#ifndef H4P_MQTTHeapLogger_HO +#define H4P_MQTTHeapLogger_HO + +#ifndef ARDUINO_ARCH_STM32 +#include +#include + +class H4P_MQTTHeapLogger: public H4P_MQTTLogger { + uint32_t _f; + void start() override { h4.every(_f,[](){ SYSEVENT(H4P_LOG_MQTT_HEAP,"%u",ESP.getFreeHeap()); },nullptr,H4P_TRID_HLOG,true); } + void stop() override { h4.cancelSingleton(H4P_TRID_HLOG); } + void _greenLight() override { h4sc.removeCmd("msg",subid); } // msg is meaningless - we only "see" H4P_LOG_MQTT_HEAP events + public: + H4P_MQTTHeapLogger(uint32_t f): _f(f),H4P_MQTTLogger("heap",H4P_LOG_MQTT_HEAP){ _names={{H4P_TRID_HLOG,"HLOG"}}; } +}; +#endif +#endif // H4P_MQTTLogger_H \ No newline at end of file diff --git a/src/H4P_MQTTLogger.h b/src/H4P_MQTTLogger.h new file mode 100644 index 00000000..50a21d6b --- /dev/null +++ b/src/H4P_MQTTLogger.h @@ -0,0 +1,52 @@ +/* + MIT License + +Copyright (c) 2019 Phil Bowles + github https://github.com/philbowles/H4 + blog https://8266iot.blogspot.com + groups https://www.facebook.com/groups/esp8266questions/ + https://www.facebook.com/H4-Esp8266-Firmware-Support-2338535503093896/ + + +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. + +*/ +#ifndef H4P_MQTTLogger_HO +#define H4P_MQTTLogger_HO + +#ifndef ARDUINO_ARCH_STM32 +#include +#include + +class H4P_MQTTLogger: public H4PLogService { + void _logEvent(const string &msg,H4P_LOG_TYPE type,const string& source,const string& target,uint32_t error){ + h4mqtt.publishDevice(_pid,msg); + } + void _hookIn() override { // protect + if(H4Plugin::isLoaded(mqttTag())){ + H4PLogService::_hookIn(); + h4mqtt.hookConnect([this](){ start(); }); + h4mqtt.hookDisconnect([this](){ stop(); }); + } else { DEPENDFAIL(mqtt); } + } + public: + H4P_MQTTLogger(const string& topic,uint32_t filter=H4P_LOG_ALL): H4PLogService(topic,filter){} +}; +#endif +#endif // H4P_MQTTLogger_H \ No newline at end of file diff --git a/src/H4P_QueueWarn.cpp b/src/H4P_QueueWarn.cpp index ac9912eb..095fbb9a 100644 --- a/src/H4P_QueueWarn.cpp +++ b/src/H4P_QueueWarn.cpp @@ -47,13 +47,12 @@ uint32_t H4P_QueueWarn::_qwPcent(vector vs){ return guardInt1(vs,bind(&H // H4P_QueueWarn // H4P_QueueWarn::H4P_QueueWarn(function _f,uint32_t _limit){ - _pid=qwrntag(); + _pid=qwrnTag(); _hook=[this](){ run(); }; - _names={ {H4P_TRID_QWRN,uppercase(_pid)} }; _cmds={ {_pid, {H4PC_SHOW, 0, CMD(show)}}, - {_pid, {H4PC_ROOT, H4PC_QWRN, nullptr}}, - {"pcent", {H4PC_QWRN, 0, CMDVS(_qwPcent)}} + {_pid, {H4PC_ROOT, subid, nullptr}}, + {"pcent", {subid, 0, CMDVS(_qwPcent)}} }; f=_f; limit=__setLimit(_limit); diff --git a/src/H4P_SerialCmd.cpp b/src/H4P_SerialCmd.cpp index 613777bb..706afa80 100644 --- a/src/H4P_SerialCmd.cpp +++ b/src/H4P_SerialCmd.cpp @@ -34,7 +34,7 @@ H4_CMD_MAP_I H4P_SerialCmd::__exactMatch(const string& cmd,uint32_t owner){ return commands.end(); } -void H4P_SerialCmd::removeCmd(const string& s){ if(__exactMatch(s,0)!=commands.end()) commands.erase(s); } +void H4P_SerialCmd::removeCmd(const string& s,uint32_t subid){ if(__exactMatch(s,subid)!=commands.end()) commands.erase(s); } void H4P_SerialCmd::__flatten(function fn){ H4_CMD_MAP_I ptr; @@ -77,16 +77,22 @@ uint32_t H4P_SerialCmd::_simulatePayload(string flat,const char* src){ // refac string topic=join(vs,"/"); return invokeCmd(topic,pload,src); } - else return H4_CMD_TOO_FEW_PARAMS; + else return H4_CMD_TOO_FEW_PARAMS; // really? } // -// cmd responders -// + uint32_t H4P_SerialCmd::_unload(vector vs){ - return guardString1(vs,[this](string s){ - unload(CSTR(s)); - showUnload(); - }); + return guard1(vs,[this](vector vs){ + return ([this](string s){ + H4Plugin* p=H4Plugin::isLoaded(s); + if(p) { + unload(p->subid); + showUnload(); + return H4_CMD_OK; + } + return H4_CMD_NAME_UNKNOWN; + })(PAYLOAD); + }); } // // public @@ -106,7 +112,6 @@ uint32_t H4P_SerialCmd::_executeCmd(string topic, string pload){ vector vs=split(CSTR(topic),"/"); _cb["source"]=vs[0]; _cb["target"]=vs[1]; -// Serial.printf("CMD src=%s tgt=%s %s[%s]\n",CSTR(_cb["source"]),CSTR(_cb["target"]),CSTR(topic),CSTR(pload)); vs.push_back(pload); vector cmd(vs.begin()+2,vs.end()); uint32_t rv=_dispatch(vector(cmd)); // optimise? @@ -130,10 +135,10 @@ uint32_t H4P_SerialCmd::invokeCmd(string topic,uint32_t payload,const char* src) return invokeCmd(topic,stringFromInt(payload),src); } -void H4P_SerialCmd::unload(const char* pid){ // move to H4Plugin? - if(H4::unloadables.count(pid)) { - h4._unHook(H4::unloadables[pid]); - H4::unloadables.erase(pid); +void H4P_SerialCmd::unload(const uint32_t subid){ + if(H4::unloadables.count(subid)) { + h4._unHook(H4::unloadables[subid]); + H4::unloadables.erase(subid); } } // @@ -141,12 +146,13 @@ void H4P_SerialCmd::unload(const char* pid){ // move to H4Plugin? // void H4P_SerialCmd::_hookIn(){ #ifndef ARDUINO_ARCH_STM32 - if(!SPIFFS.begin()) Serial.println("Warning: NO SPIFFS"); +// if(!SPIFFS.begin()) Serial.println("Warning: NO SPIFFS"); + SPIFFS.begin(); #endif } H4P_SerialCmd::H4P_SerialCmd(){ - _pid=stag(); + _pid=scmdTag(); _hook=[this](){ run(); }; _names={ {H4P_TRID_SCMD,uppercase(_pid)} }; _cmds={ @@ -156,6 +162,7 @@ H4P_SerialCmd::H4P_SerialCmd(){ {"show", { H4PC_ROOT, H4PC_SHOW, nullptr}}, {"all", { H4PC_SHOW, 0, CMD(all) }}, {"config", { H4PC_SHOW, 0, CMD(config) }}, + {"plugins", { H4PC_SHOW, 0, CMD(plugins) }}, #ifndef ARDUINO_ARCH_STM32 {"spif", { H4PC_SHOW, 0, CMD(showSPIFFS)}}, {"dump", { H4PC_ROOT, 0, CMDVS(_dump)}}, @@ -170,8 +177,22 @@ H4P_SerialCmd::H4P_SerialCmd(){ }; // diag void H4P_SerialCmd::dumpQ(){ h4.dumpQ(); } -void H4P_SerialCmd::Qstats(){ reply("Q capacity: %u size: %u\n",h4._capacity(),h4._size()); } -void H4P_SerialCmd::showUnload(){ for(auto const& v:H4::unloadables) reply("U: %s=%d\n",CSTR(v.first),v.second); } + +void H4P_SerialCmd::logEventType(H4P_LOG_TYPE t,const string& fmt,...){ + char buff[256]; + va_list ap; + va_start(ap, fmt); + vsnprintf(buff,255,CSTR(fmt),ap); + va_end(ap); + _logEvent(buff,t,"user","self",0); +} + +void H4P_SerialCmd::plugins(){ for(auto const& p:H4Plugin::_plugins) reply("P: %s ID=%d\n",CSTR(p->_pid),p->subid); } + +void H4P_SerialCmd::Qstats(){ reply("Q capacity: %u size: %u\n",h4._capacity(),h4.size()); } + +void H4P_SerialCmd::showUnload(){ for(auto const& u:H4::unloadables) reply("U: %d=%s\n",u.first,CSTR(H4Plugin::pidFromSubid(u.first))); } + void H4P_SerialCmd::tnames(){ for(auto const& n:H4::trustedNames) { reply("T: %d=%s\n",n.first,CSTR(n.second)); }} // void H4P_SerialCmd::run(){ // halting loop optimise @@ -184,7 +205,7 @@ void H4P_SerialCmd::run(){ // halting loop optimise uint32_t err=_simulatePayload(cmd); if(err){ string msg; - if(h4._hasName(H4P_TRID_CERR)) msg=h4ce.getErrorMessage(err)+" "+cmd; + if(H4Plugin::isLoaded(cerrTag())) msg=h4ce.getErrorMessage(err)+" "+cmd; else msg="Error: "+stringFromInt(err); reply("%s\n",CSTR(msg)); } @@ -218,10 +239,10 @@ uint32_t H4P_SerialCmd::write(const string& fn,const string& data,const char* mo } uint32_t H4P_SerialCmd::_dump(vector vs){ - return guard<1>(vs,[this](vector vs){ + return guard1(vs,[this](vector vs){ return ([this](string h){ - Serial.printf("DUMP FILE %s\n",CSTR(h)); - Serial.printf("%s\n",CSTR(read("/"+h))); + reply("DUMP FILE %s\n",CSTR(h)); + reply("%s\n",CSTR(read("/"+h))); return H4_CMD_OK; })(PAYLOAD); }); diff --git a/src/H4P_SerialCmd.h b/src/H4P_SerialCmd.h index 6a256eee..8535370a 100644 --- a/src/H4P_SerialCmd.h +++ b/src/H4P_SerialCmd.h @@ -43,8 +43,6 @@ using namespace std::placeholders; class H4Plugin; -constexpr const char* stag(){ return "scmd"; } - using H4P_FN_LOG = function; class H4P_SerialCmd: public H4Plugin { @@ -73,6 +71,7 @@ class H4P_SerialCmd: public H4Plugin { void config(){ for(auto const& c:_cb) H4Plugin::reply("%s=%s\n",CSTR(c.first),CSTR(c.second)); } void dumpQ(); void help(); + void plugins(); void Qstats(); void tnames(); void showUnload(); @@ -80,11 +79,12 @@ class H4P_SerialCmd: public H4Plugin { void addCmd(const string& name,uint32_t owner, uint32_t levID,H4_FN_MSG f=nullptr){ _addCmd(name, {owner,levID,f}); } uint32_t invokeCmd(string,string="",const char* src="user"); uint32_t invokeCmd(string,uint32_t,const char* src="user"); - void logEvent(const string& msg){ _logEvent(msg,H4P_LOG_USER,"user","self",0); } - void removeCmd(const string& name); - void unload(const char* pid); + void logEventType(H4P_LOG_TYPE,const string& fmt,...); + void removeCmd(const string& name,uint32_t subid=0); + void unload(const uint32_t subid); // syscall only void _addCmd(const string& name,struct command cmd){ commands.insert(make_pair(name,cmd)); } + void _noOP(){} // for non-events void _hookLogChain(H4P_FN_LOG f){ _logChain.push_back(f); } void _logEvent(const string &msg,H4P_LOG_TYPE type,const string& source,const string& target,uint32_t error); uint32_t _executeCmd(string topic, string pload); diff --git a/src/H4P_SerialLogger.h b/src/H4P_SerialLogger.h index 371738c0..1d994502 100644 --- a/src/H4P_SerialLogger.h +++ b/src/H4P_SerialLogger.h @@ -35,13 +35,14 @@ SOFTWARE. class H4P_SerialLogger: public H4PLogService { void _logEvent(const string &msg,H4P_LOG_TYPE type,const string& source,const string& target,uint32_t error){ - if(_running) Serial.println(CSTR(msg)); + Serial.print("TYPE"); Serial.print(type); + Serial.print(" s="); Serial.print(CSTR(source)); + Serial.print(" t="); Serial.print(CSTR(target)); + Serial.print(" e="); Serial.print(error); + Serial.print(" ");Serial.println(CSTR(msg)); } public: - H4P_SerialLogger(): H4PLogService("slog"){ - subid=H4PC_SLOG; - _names={ {H4P_TRID_SLOG,uppercase(_pid)} }; - } + H4P_SerialLogger(uint32_t filter=H4P_LOG_ALL): H4PLogService("slog",filter){} }; #endif // H4P_SerialLogger_H \ No newline at end of file diff --git a/src/H4P_TaskSniffer.cpp b/src/H4P_TaskSniffer.cpp index 354e7991..2cde808e 100644 --- a/src/H4P_TaskSniffer.cpp +++ b/src/H4P_TaskSniffer.cpp @@ -29,7 +29,7 @@ SOFTWARE. #include uint32_t H4P_TaskSniffer::__incexc(vector vs,function)> f){ - return guard<1>(vs,[f,this](vector vs){ + return guard1(vs,[f,this](vector vs){ auto vi=expectInt(PAYLOAD); if(vi.size()) return ([f,this](vector vu){ f(vu); @@ -54,38 +54,30 @@ uint32_t H4P_TaskSniffer::__incexc(vector vs,functionuid)%100)) { - Serial.print(h4._size()); - Serial.print(":"); - Serial.print(micros()); - Serial.print(":"); - Serial.print(micros()); - Serial.print(":"); - Serial.print(c); - Serial.print(": "); - h4._dumpTask(t); + reply("%d:%u:%c: ",h4.size(),micros(),c); + h4._dumpTask(t); } } // diff --git a/src/H4P_ThreeFunctionButton.cpp b/src/H4P_ThreeFunctionButton.cpp index 7356f88d..c0ce912e 100644 --- a/src/H4P_ThreeFunctionButton.cpp +++ b/src/H4P_ThreeFunctionButton.cpp @@ -45,7 +45,7 @@ void H4P_ThreeFunctionButton::progress(H4GPIOPin* ptr){ // run this as each stag void H4P_ThreeFunctionButton::_hookIn(){ _createMS(); - if(h4._hasName(H4P_TRID_WIFI)){ + if(H4Plugin::isLoaded(wifiTag()) && H4Plugin::isLoaded(winkTag())){ /// && dlasher h4wifi.hookConnect([this](){ if(WiFi.getMode() & WIFI_AP) h4fc.flashPWM(1000,10,_led,_active); else h4fc.stopLED(_led); @@ -55,18 +55,16 @@ void H4P_ThreeFunctionButton::_hookIn(){ else h4fc.flashMorse("... --- ... ",H43F_TIMEBASE,_led,_active); }); } - if(h4._hasName(H4P_TRID_MQTT)){ + if(H4Plugin::isLoaded(mqttTag())){ h4mqtt.hookConnect([this](){ h4fc.stopLED(_led); }); h4mqtt.hookDisconnect([this](){ h4fc.flashPattern("10100000",H43F_TIMEBASE,_led,_active); }); } } -H4P_ThreeFunctionButton::H4P_ThreeFunctionButton(H4P_BasicSwitch* bsp,uint32_t dbTimeMs,uint8_t pin,uint8_t mode,H4GM_SENSE b_sense,uint8_t led,H4GM_SENSE l_sense): +H4P_ThreeFunctionButton::H4P_ThreeFunctionButton(H4P_BinaryThing* bsp,uint32_t dbTimeMs,uint8_t pin,uint8_t mode,H4GM_SENSE b_sense,uint8_t led,H4GM_SENSE l_sense): _bsp(bsp),_led(led),_active(l_sense){ - - _pid=tfnbtag(); - _names={{H4P_TRID_3FNB,uppercase(_pid)}}; - + + _pid=tfnbTag(); H4GM_FN_EVENT cb=bind(&H4P_ThreeFunctionButton::progress,this,_1); _createMS=bind(&H4P_GPIOManager::Multistage,&h4gm,pin,mode,b_sense,dbTimeMs,_sm,cb); } diff --git a/src/H4P_ThreeFunctionButton.h b/src/H4P_ThreeFunctionButton.h index 6a448c9a..c198fe02 100644 --- a/src/H4P_ThreeFunctionButton.h +++ b/src/H4P_ThreeFunctionButton.h @@ -39,22 +39,24 @@ SOFTWARE. #include #include #include + +#include #include #include -#include +#include #ifndef H4P_NO_WIFI extern void h4FactoryReset(); class H4P_ThreeFunctionButton: public H4Plugin{ - H4P_BasicSwitch* _bsp; + H4P_BinaryThing* _bsp; uint8_t _led; H4GM_SENSE _active; H4GM_STAGE_MAP _sm={ {0,[this](H4GPIOPin* ptr){ _bsp->toggle(); }}, - {2000,[](H4GPIOPin* ptr){ h4reboot(); }}, - {5000,[](H4GPIOPin* ptr){ h4FactoryReset(); }} + {H43F_REBOOT,[](H4GPIOPin* ptr){ h4reboot(); }}, + {H43F_FACTORY,[](H4GPIOPin* ptr){ h4FactoryReset(); }} }; H4_FN_VOID _createMS; @@ -63,7 +65,7 @@ class H4P_ThreeFunctionButton: public H4Plugin{ void progress(H4GPIOPin* ptr); public: H4P_ThreeFunctionButton( - H4P_BasicSwitch* bsp, // + H4P_BinaryThing* bsp, // uint32_t dbTimeMs, // arbitrary // the input button uint8_t pin, diff --git a/src/H4P_UPNPSwitch.cpp b/src/H4P_UPNPCommon.cpp similarity index 62% rename from src/H4P_UPNPSwitch.cpp rename to src/H4P_UPNPCommon.cpp index 55b23682..564ee5d0 100644 --- a/src/H4P_UPNPSwitch.cpp +++ b/src/H4P_UPNPCommon.cpp @@ -26,40 +26,51 @@ 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 +#include #ifndef H4P_NO_WIFI -void H4P_UPNPSwitch::_hookIn(){ - H4P_BasicSwitch::_hookIn(); - _ubIP=IPAddress(239,255,255,250); - - h4sc.addCmd(_pid,H4PC_ROOT, H4PC_UPNP, nullptr); - h4sc.addCmd(nametag(),H4PC_UPNP,0, CMDVS(_friendly)); - - h4asws.hookConnect([this](){ start(); }); - h4asws.hookDisconnect([this](){ stop(); }); - H4PluginService::hookFactory([this](){ SPIFFS.remove(CSTR(string("/"+string(nametag())))); }); +void H4P_UPNPCommon::_pseudoHookIn(){ + if(H4Plugin::isLoaded(aswsTag())){ + _ubIP=IPAddress(239,255,255,250); + + h4._hookLoop(nullptr,{ + {H4P_TRID_SOAP, "SOAP"}, + {H4P_TRID_SOAP, "UDPM"}, + {H4P_TRID_NTFY, "NTFY"} + },H4PC_UPNP); + + h4sc.addCmd(upnpTag(),H4PC_ROOT, H4PC_UPNP, nullptr); + h4sc.addCmd(nameTag(),H4PC_UPNP,0, CMDVS(_friendly)); + + h4asws.hookConnect([this](){ start(); }); + h4asws.hookDisconnect([this](){ stop(); }); + H4PluginService::hookFactory([this](){ SPIFFS.remove(CSTR(string("/"+string(nameTag())))); }); + } else { + #ifdef H4_LOG_EVENTS + //DEPENDFAIL(asws); + h4sc.logEventType(H4P_LOG_DEPENDFAIL,"%s->%s", H4PC_UPNP,aswsTag()); + #endif + } } -void H4P_UPNPSwitch::friendlyName(const string& name){ h4wifi.setPersistentValue(nametag(),name,true); } +void H4P_UPNPCommon::friendlyName(const string& name){ h4wifi.setPersistentValue(nameTag(),name,true); } -uint32_t H4P_UPNPSwitch::_friendly(vector vs){ - return guard<1>(vs,[this](vector vs){ - return ([this](string h){ - h4wifi.setPersistentValue(nametag(),h,true); - return H4_CMD_OK; - })(PAYLOAD); - }); +uint32_t H4P_UPNPCommon::_friendly(vector vs){ + return H4Plugin::guard1(vs,[this](vector vs){ + //h4wifi.setPersistentValue(nameTag(),PAYLOAD,true); + Serial.printf("WOULD FRIEND %s\n",CSTR(PAYLOAD)); + return H4_CMD_OK; + }); } -void H4P_UPNPSwitch::__upnpSend(uint32_t mx,const string s,IPAddress ip,uint16_t port){ +void H4P_UPNPCommon::__upnpSend(uint32_t mx,const string s,IPAddress ip,uint16_t port){ h4.nTimesRandom(H4P_UDP_REPEAT,0,mx,bind([this](IPAddress ip,uint16_t port,string s) { _udp.writeTo((uint8_t *)CSTR(s), s.size(), ip, port); },ip,port,s) ); // name this!! } -void H4P_UPNPSwitch::_listenUDP(){ +void H4P_UPNPCommon::_listenUDP(){ if(_udp.listenMulticast(_ubIP, 1900)) { _udp.onPacket([this](AsyncUDPPacket packet) { h4.queueFunction(bind([this](string msg, IPAddress ip, uint16_t port) { @@ -75,7 +86,7 @@ if(_udp.listenMulticast(_ubIP, 1900)) { } } string ST = uhdrs["ST"]; - if (ST==_pups[1] || ST==_uuid+_cb["udn"]) { // make tag + if (ST==_pups[1] || ST==_uuid+H4Plugin::_cb["udn"]) { // make tag string tail=((ST==_pups[1]) ? ST:""); __upnpSend(1000 * atoi(CSTR(uhdrs["MX"])), "HTTP/1.1 200 OK\r\nST:" + ST +"\r\n" +__upnpCommon(tail), ip,port); } @@ -85,40 +96,38 @@ if(_udp.listenMulticast(_ubIP, 1900)) { } } -string H4P_UPNPSwitch::__makeUSN(const string& s){ - string full=_uuid+_cb["udn"]; +string H4P_UPNPCommon::__makeUSN(const string& s){ + string full=_uuid+H4Plugin::_cb["udn"]; return s.size() ? full+="::"+s:full; } -string H4P_UPNPSwitch::__upnpCommon(const string& usn){ - _cb["usn"]=__makeUSN(usn); +string H4P_UPNPCommon::__upnpCommon(const string& usn){ + H4Plugin::_cb["usn"]=__makeUSN(usn); string rv=H4P_WiFi::replaceParams(_ucom); return rv+"\r\n\r\n"; } -void H4P_UPNPSwitch::start(){ - _cb[nametag()]=_name; - h4wifi.getPersistentValue(nametag(),"upnp "); +void H4P_UPNPCommon::start(){ + H4Plugin::_cb[nameTag()]=_name; + h4wifi.getPersistentValue(nameTag(),"upnp "); if(!(WiFi.getMode() & WIFI_AP)){ -// _cb["root"]=_urn+"device-1-0", -// _cb["root"]="urn:schemas-upnp-org:device-1-0"; // hoist to up.xml - _cb["age"]=stringFromInt(H4P_UDP_REFRESH/1000); // fix + H4Plugin::_cb["age"]=stringFromInt(H4P_UDP_REFRESH/1000); // fix - _cb["udn"]="Socket-1_0-upnp"+_cb[chiptag()]; - _cb["updt"]=_pups[2]; - _cb["umfr"]="Belkin International Inc."; - _cb["usvc"]=_pups[3]; - _cb["usid"]=_urn+"serviceId:basicevent1"; + H4Plugin::_cb["udn"]="Socket-1_0-upnp"+H4Plugin::_cb[chipTag()]; + H4Plugin::_cb["updt"]=_pups[2]; + H4Plugin::_cb["umfr"]="Belkin International Inc."; + H4Plugin::_cb["usvc"]=_pups[3]; + H4Plugin::_cb["usid"]=_urn+"serviceId:basicevent1"; _xml=H4P_WiFi::replaceParamsFile("/up.xml"); _ucom=H4P_WiFi::replaceParamsFile("/ucom.txt"); _soap=H4P_SerialCmd::read("/soap.xml"); -// erase redundant _cb? - _cb.erase("age"); - _cb.erase("updt"); - _cb.erase("umfr"); - _cb.erase("usvc"); - _cb.erase("usid"); +// erase redundant H4Plugin::_cb? + H4Plugin::_cb.erase("age"); + H4Plugin::_cb.erase("updt"); + H4Plugin::_cb.erase("umfr"); + H4Plugin::_cb.erase("usvc"); + H4Plugin::_cb.erase("usid"); // h4asws.on("/we",HTTP_GET, [this](AsyncWebServerRequest *request){ request->send(200,"text/xml",CSTR(_xml)); }); h4asws.on("/upnp", HTTP_POST,[this](AsyncWebServerRequest *request){ _upnp(request); }, @@ -132,28 +141,28 @@ void H4P_UPNPSwitch::start(){ _listenUDP(); _notify("alive"); // TAG h4.every(H4P_UDP_REFRESH / 3,[this](){ _notify("alive"); },nullptr,H4P_TRID_NTFY,true); // TAG - H4PluginService::svc(upnptag(),H4P_LOG_SVC_UP); // simulate service + H4PluginService::svc(upnpTag(),H4P_LOG_SVC_UP); // simulate service } } -void H4P_UPNPSwitch::_upnp(AsyncWebServerRequest *request){ // redo +void H4P_UPNPCommon::_upnp(AsyncWebServerRequest *request){ // redo h4.queueFunction(bind([this](AsyncWebServerRequest *request) { string soap=stringFromBuff((const byte*) request->_tempObject,strlen((const char*) request->_tempObject)); - _cb["gs"]=(soap.find("Get")==string::npos) ? "Set":"Get"; - if(_cb["gs"]=="Set") _pp->logicalWrite(soap.find(">1<")==string::npos ? 0:1); - _cb[statetag()]=_pp->state ? "1":"0"; + H4Plugin::_cb["gs"]=(soap.find("Get")==string::npos) ? "Set":"Get"; + if(H4Plugin::_cb["gs"]=="Set") _setState(soap.find(">1<")==string::npos ? 0:1); + H4Plugin::_cb[stateTag()]=_getState() ? "1":"0"; request->send(200, "text/xml", CSTR(H4P_WiFi::replaceParams(_soap))); // refac },request),nullptr, H4P_TRID_SOAP); // TRID_SOAP } -void H4P_UPNPSwitch::stop(){ +void H4P_UPNPCommon::stop(){ _notify("byebye"); h4.cancelSingleton(H4P_TRID_NTFY); _udp.close(); - H4PluginService::svc(upnptag(),H4P_LOG_SVC_DOWN); // simulate service + H4PluginService::svc(upnpTag(),H4P_LOG_SVC_DOWN); // simulate service } -void H4P_UPNPSwitch::_notify(const string& phase){ // chunker it up +void H4P_UPNPCommon::_notify(const string& phase){ // chunker it up chunker>(_pups,[this,phase](vector::const_iterator i){ string NT=(*i).size() ? (*i):__makeUSN(""); string nfy="NOTIFY * HTTP/1.1\r\nHOST:"+string(_ubIP.toString().c_str())+":1900\r\nNTS:ssdp:"+phase+"\r\nNT:"+NT+"\r\n"+__upnpCommon((*i)); diff --git a/src/H4P_UPNPCommon.h b/src/H4P_UPNPCommon.h new file mode 100644 index 00000000..32028894 --- /dev/null +++ b/src/H4P_UPNPCommon.h @@ -0,0 +1,87 @@ +/* + MIT License + +Copyright (c) 2019 Phil Bowles + github https://github.com/philbowles/H4 + blog https://8266iot.blogspot.com + groups https://www.facebook.com/groups/esp8266questions/ + https://www.facebook.com/H4-Esp8266-Firmware-Support-2338535503093896/ + + +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. + +*/ +#ifndef H4P_UPNPCommon_HO +#define H4P_UPNPCommon_HO + +#include +#include +#include +#include +#ifndef H4P_NO_WIFI + +#include + +class H4P_UPNPCommon{ + AsyncUDP _udp; + IPAddress _ubIP; + + VSCMD(_friendly); + + string _uuid="uuid:"; + string _urn="Belkin:"; + + vector _pups={ + "", + "upnp:rootdevice" + }; + + string _name; + string _soap; + string _ucom; + string _xml; + + string __makeUSN(const string& s); + string __upnpCommon(const string& usn); + void __upnpSend(uint32_t mx,const string s,IPAddress ip,uint16_t port); + + void broadcast(uint32_t mx,const string s){ __upnpSend(mx,s,_ubIP,1900); } + + void _listenUDP(); + void _notify(const string& s); + void _upnp(AsyncWebServerRequest *request); + + void start(); + void stop(); + protected: + void _pseudoHookIn(); + virtual bool _getState()=0; + virtual void _setState(bool b)=0; + public: + H4P_UPNPCommon(const string& name): _name(name){ + _pups.push_back(_urn+"device:controllee:1"); + _pups.push_back(_urn+"service:basicevent:1"); + } + + void friendlyName(const string& name); +}; +// extern __attribute__((weak)) H4P_UPNPCommon h4upnp; +#endif + +#endif // H4P_UPNPCommon_H \ No newline at end of file diff --git a/src/H4P_UPNPSwitch.h b/src/H4P_UPNPSwitch.h index 83a0e2ac..6ee5428d 100644 --- a/src/H4P_UPNPSwitch.h +++ b/src/H4P_UPNPSwitch.h @@ -32,60 +32,26 @@ SOFTWARE. #include #include -#include +#include #include -#ifndef H4P_NO_WIFI - +#include #include +#include -class H4P_UPNPSwitch: public H4P_BasicSwitch { - AsyncUDP _udp; - IPAddress _ubIP; - - VSCMD(_friendly); - - string _uuid="uuid:"; - string _urn="Belkin:"; - - vector _pups={ - "", - "upnp:rootdevice" - }; - - string _name; - string _soap; - string _ucom; - string _xml; - - string __makeUSN(const string& s); - string __upnpCommon(const string& usn); - void __upnpSend(uint32_t mx,const string s,IPAddress ip,uint16_t port); - - void broadcast(uint32_t mx,const string s){ __upnpSend(mx,s,_ubIP,1900); } - - void _hookIn(); - void _listenUDP(); - void _notify(const string& s); - void _upnp(AsyncWebServerRequest *request); +#ifndef H4P_NO_WIFI - void start(); - void stop(); +class H4P_UPNPSwitch: public H4P_BinarySwitch, public H4P_UPNPCommon { + void _hookIn() override{ H4P_UPNPCommon::_pseudoHookIn(); } + bool _getState() override { return H4P_BinarySwitch::_getState(); } + void _setState(bool b) override { H4P_BinarySwitch::_setState(b); } public: - H4P_UPNPSwitch(string name,uint8_t pin,H4GM_SENSE sense, uint8_t initial,H4BS_FN_SWITCH f=[](bool){}): - _name(name), - H4P_BasicSwitch(pin,sense,initial,f){ - _pups.push_back(_urn+"device:controllee:1"); - _pups.push_back(_urn+"service:basicevent:1"); - _pid=upnptag(); - _names = { - {H4P_TRID_SOAP, "SOAP"}, - {H4P_TRID_NTFY, "NTFY"} - }; - } - - void friendlyName(const string& name); + H4P_UPNPSwitch(const string& name,uint8_t pin,H4GM_SENSE sense, uint32_t initial,H4BS_FN_SWITCH f=[](bool){}): + H4P_BinarySwitch(pin,sense,initial,f), + H4P_UPNPCommon(name){ + } }; - extern __attribute__((weak)) H4P_UPNPSwitch h4upnp; -#endif + +//extern __attribute__((weak)) H4P_UPNPSwitch h4thing; +#endif #endif // H4P_UPNPSwitch_H \ No newline at end of file diff --git a/src/H4P_BasicSwitch.h b/src/H4P_UPNPThing.h similarity index 60% rename from src/H4P_BasicSwitch.h rename to src/H4P_UPNPThing.h index 9fed7ae7..66692e64 100644 --- a/src/H4P_BasicSwitch.h +++ b/src/H4P_UPNPThing.h @@ -27,40 +27,32 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#ifndef H4P_BasicSwitch_HO -#define H4P_BasicSwitch_HO +#ifndef H4P_UPNPThing_HO +#define H4P_UPNPThing_HO #include -#include -#include -#ifndef H4P_NO_WIFI - -class H4P_BasicSwitch: public H4Plugin{ -// - protected: - VSCMD(_switch); +#include +#include +#include +#include +#include +#include - void _publish(bool b){ - if(h4._hasName(H4P_TRID_MQTT)) h4mqtt.publishDevice(statetag(),b); - } +#ifndef H4P_NO_WIFI - virtual void _hookIn() override { - if(h4._hasName(H4P_TRID_MQTT)) { - h4mqtt.hookConnect([this](){ _publish(_pp->state); }); - } - } - OutputPin* _pp; +class H4P_UPNPThing: public H4P_BinaryThing, public H4P_UPNPCommon { + void _hookIn() override{ H4P_UPNPCommon::_pseudoHookIn(); } + bool _getState() override { return H4P_BinaryThing::_getState(); } + void _setState(bool b) override { H4P_BinaryThing::_setState(b); } public: - H4P_BasicSwitch(uint8_t pin,H4GM_SENSE sense, uint8_t initial,H4BS_FN_SWITCH f=[](bool){}); - - void turnOff(){ turn(false); } - void turnOn(){ turn(true); } - void toggle(){ turn(!_pp->state); } - void turn(bool b); + H4P_UPNPThing(const string& name,H4BS_FN_SWITCH f=[](bool){},bool initial=OFF): + H4P_BinaryThing(f,initial), + H4P_UPNPCommon(name){ + } }; - -extern __attribute__((weak)) H4P_BasicSwitch h4bs; + +//extern __attribute__((weak)) H4P_UPNPThing h4thing; #endif -#endif // H4P_BasicSwitch_H \ No newline at end of file +#endif // H4P_UPNPThing_H \ No newline at end of file diff --git a/src/H4P_WiFi.cpp b/src/H4P_WiFi.cpp index 09af7775..201cd4ed 100644 --- a/src/H4P_WiFi.cpp +++ b/src/H4P_WiFi.cpp @@ -36,36 +36,31 @@ void H4P_WiFi::_hookIn(){ } void H4P_WiFi::_greenLight() { - _cb[chiptag()]=_getChipID(); - _cb[boardtag()]=replaceAll(H4_BOARD,"ESP8266_",""); - getPersistentValue(devicetag(),"H4_"); + _cb[chipTag()]=_getChipID(); + _cb[boardTag()]=replaceAll(H4_BOARD,"ESP8266_",""); + getPersistentValue(deviceTag(),"H4_"); start(); } void H4P_WiFi::_startAP(){ -// if(!_dnsServer){ - _dnsServer= new DNSServer; -// WiFi.disconnect(); - WiFi.mode(WIFI_AP); - WiFi.enableSTA(false); // force AP only - WiFi.softAP(CSTR(_cb[devicetag()])); - _dnsServer=new DNSServer; - _dnsServer->start(53, "*", WiFi.softAPIP()); - h4.every(1000,[this](){ _dnsServer->processNextRequest(); },nullptr,H4P_TRID_WFAP,true); - _cb["ip"]=WiFi.softAPIP().toString().c_str(); - _scan(); - h4pcConnected(); - // } + _dnsServer= new DNSServer; + WiFi.mode(WIFI_AP); + WiFi.enableSTA(false); // force AP only + WiFi.softAP(CSTR(_cb[deviceTag()])); + delay(0); // solve 192.168.244.1 probelm ? + _dnsServer=new DNSServer; + _dnsServer->start(53, "*", WiFi.softAPIP()); + h4.every(1000,[this](){ _dnsServer->processNextRequest(); },nullptr,H4P_TRID_WFAP,true); + _cb["ip"]=WiFi.softAPIP().toString().c_str(); + _scan(); + h4pcConnected(); } void H4P_WiFi::getPersistentValue(string v,string prefix){ string persistent=H4P_SerialCmd::read("/"+v); string cat=_cb[v]+persistent; - if(persistent.size()){ - if(H4P_PREFER_PERSISTENT) _cb[v]=persistent; - } - if(!cat.size()) _cb[v]=string(prefix)+_cb[chiptag()]; -// Serial.printf("PV %s IS %s\n",CSTR(v),CSTR(_cb[v])); + if(persistent.size()) if(H4P_PREFER_PERSISTENT) _cb[v]=persistent; + if(!cat.size()) _cb[v]=string(prefix)+_cb[chipTag()]; } void H4P_WiFi::setPersistentValue(string n,string v,bool reboot){ @@ -95,7 +90,7 @@ string H4P_WiFi::replaceParams(const string& s){ // oh for a working regex... } uint32_t H4P_WiFi::_host(vector vs){ - return guard<1>(vs,[this](vector vs){ + return guard1(vs,[this](vector vs){ return ([this](string h){ host(h); return H4_CMD_OK; @@ -103,17 +98,11 @@ uint32_t H4P_WiFi::_host(vector vs){ }); } -uint32_t H4P_WiFi::_change(vector vs){ - return guard<1>(vs,[this](vector vs){ - auto vg=split(PAYLOAD,","); - if(vg.size()==2) return ([this](string s,string p){ change(s,p); return H4_CMD_OK; })(vg[0],vg[1]); - else return H4_CMD_PAYLOAD_FORMAT; - }); -} +uint32_t H4P_WiFi::_change(vector vs){ return guardString2(vs,[this](string a,string b){ change(a,b); }); } void H4P_WiFi::change(string ssid,string psk){ // add device / name? stop(); - _cb[ssidtag()]=ssid; + _cb[ssidTag()]=ssid; _cb["psk"]=psk; _startSTA(); } @@ -122,7 +111,6 @@ void H4P_WiFi::_lostIP(){ if(!_discoDone){ h4pcDisconnected(); _discoDone=true; - Serial.println("WiFi down"); } } @@ -133,13 +121,13 @@ void H4P_WiFi::clear(){ stop(); WiFi.disconnect(true); ESP.eraseConfig(); - SPIFFS.remove(CSTR(string("/"+string(devicetag())))); + SPIFFS.remove(CSTR(string("/"+string(deviceTag())))); } void H4P_WiFi::_scan(){ // check 4 common hoist WiFi.enableSTA(true); int n=WiFi.scanNetworks(); - Serial.printf("SCAN finds %d\n",n); + EVENT("SCAN finds %d\n",n); for (uint8_t i = 0; i < n; i++){ char buf[128]; @@ -156,7 +144,7 @@ void H4P_WiFi::_startSTA(){ WiFi.enableAP(false); WiFi.setAutoConnect(true); WiFi.setAutoReconnect(true); - WiFi.begin(CSTR(_cb[ssidtag()]),CSTR(_cb["psk"])); + WiFi.begin(CSTR(_cb[ssidTag()]),CSTR(_cb["psk"])); } void H4P_WiFi::start(){ @@ -186,17 +174,17 @@ void H4P_WiFi::stop(){ void H4P_WiFi::_gotIP(){ _discoDone=false; _cb["ip"]=WiFi.localIP().toString().c_str(); - _cb[ssidtag()]=CSTR(WiFi.SSID()); + _cb[ssidTag()]=CSTR(WiFi.SSID()); _cb["psk"]=CSTR(WiFi.psk()); - string host=_cb[devicetag()]; + string host=_cb[deviceTag()]; h4.every(H4WF_OTA_RATE,[](){ ArduinoOTA.handle(); },nullptr,H4P_TRID_HOTA,true); WiFi.hostname(CSTR(host)); ArduinoOTA.setHostname(CSTR(host)); ArduinoOTA.setRebootOnSuccess(false); ArduinoOTA.begin(); - Serial.printf("IP=%s\n",CSTR(_cb["ip"])); + EVENT("IP=%s\n",CSTR(_cb["ip"])); h4pcConnected(); } @@ -217,7 +205,6 @@ ESP8266 */ void H4P_WiFi::_wifiEvent(WiFiEvent_t event) { -// Serial.printf("FH=%d _wifiEvent %d\n",ESP.getFreeHeap(),event); switch(event) { case WIFI_EVENT_STAMODE_DISCONNECTED: h4.queueFunction([](){ h4wifi._lostIP(); }); @@ -225,9 +212,6 @@ void H4P_WiFi::_wifiEvent(WiFiEvent_t event) { case WIFI_EVENT_STAMODE_GOT_IP: h4.queueFunction([](){ h4wifi._gotIP(); }); break; -// default: -// Serial.printf("FH=%d _wifiEvent %d\n",ESP.getFreeHeap(),event); -// break; } } #else @@ -252,42 +236,20 @@ void H4P_WiFi::_scan(){ WiFi.scanDelete(); WiFi.enableSTA(false); // force AP only } -/* -void H4P_WiFi::_startAP(){ -// Serial.printf("_startAP\n"); -// if(!_dnsServer){ - _dnsServer= new DNSServer; - WiFi.mode(WIFI_AP); - WiFi.enableSTA(false); // force AP only - WiFi.softAP(CSTR(_cb[devicetag()])); - _dnsServer=new DNSServer; - _dnsServer->start(53, "*", WiFi.softAPIP()); - h4.every(1000,[this](){ _dnsServer->processNextRequest(); },nullptr,H4P_TRID_WFAP,true); -// _cb["ip"]=WiFi.softAPIP().toString().c_str(); - _scan(); - h4pcConnected(); -// } -} -*/ + void H4P_WiFi::_startSTA(){ WiFi.mode(WIFI_STA); WiFi.setSleep(false); WiFi.enableAP(false); WiFi.setAutoReconnect(true); - WiFi.begin(CSTR(_cb[ssidtag()]),CSTR(_cb["psk"])); + WiFi.begin(CSTR(_cb[ssidTag()]),CSTR(_cb["psk"])); } -void H4P_WiFi::start(){ - if(WiFi.begin()){ -// Serial.printf("Cannot start status=%d ssid=%s psk=%s\n",WiFi.status(),CSTR(WiFi.SSID()),CSTR(WiFi.psk())); - if(WiFi.SSID()=="" && WiFi.psk()=="") _startAP(); - } -} +void H4P_WiFi::start(){ if(WiFi.begin()) if(WiFi.SSID()=="" && WiFi.psk()=="") _startAP(); } void H4P_WiFi::_stop(){ h4.cancelSingleton({H4P_TRID_HOTA,H4P_TRID_WFAP}); if(_dnsServer){ -// Serial.printf("AP %s stopping\n",CSTR(_cb[devicetag()])); _dnsServer->stop(); delete _dnsServer; _dnsServer=nullptr; @@ -305,11 +267,11 @@ void H4P_WiFi::stop(){ void H4P_WiFi::_gotIP(){ _discoDone=false; _cb["ip"]=WiFi.localIP().toString().c_str(); - _cb[ssidtag()]=CSTR(WiFi.SSID()); + _cb[ssidTag()]=CSTR(WiFi.SSID()); _cb["psk"]=CSTR(WiFi.psk()); - string host=_cb[devicetag()]; + string host=_cb[deviceTag()]; _cb.erase("opts"); // lose any old AP ssids - Serial.printf("IP=%s\n",CSTR(_cb["ip"])); + EVENT("IP=%s\n",CSTR(_cb["ip"])); h4pcConnected(); } /* ESP32 @@ -352,30 +314,14 @@ void H4P_WiFi::_gotIP(){ */ void H4P_WiFi::_wifiEvent(WiFiEvent_t event) { -// Serial.printf("FH=%d _wifiEvent %d\n",ESP.getFreeHeap(),event); switch(event) { -/*2 SYSTEM_EVENT_STA_START for ap_mode - case SYSTEM_EVENT_WIFI_READY: // diag hoist - Serial.printf("SYSTEM_EVENT_WIFI_READY %s\n",CSTR(WiFi.localIP().toString())); - h4.queueFunction([](){ h4wifi.show();h4wifi.start(); }); - break; -*/ case SYSTEM_EVENT_STA_STOP: -// Serial.printf("SYSTEM_EVENT_STA_STOP mode? %s AP mode? %s [%d]\n",WiFi.getMode() & WIFI_STA ? "true":"false",WiFi.getMode() & WIFI_AP ? "true":"false",WiFi.getMode()); case SYSTEM_EVENT_STA_LOST_IP: -// case SYSTEM_EVENT_STA_DISCONNECTED: -// WiFi.printDiag(Serial); - if(!(WiFi.getMode() & WIFI_AP)) { - // Serial.printf("STA mode, going down\n"); - h4.queueFunction([](){ h4wifi._lostIP(); }); - } //else Serial.printf("STA stop ignored in AP_MODE\n"); - break; + if(!(WiFi.getMode() & WIFI_AP)) h4.queueFunction([](){ h4wifi._lostIP(); }); + break; case SYSTEM_EVENT_STA_GOT_IP: h4.queueFunction([](){ h4wifi._gotIP(); }); break; -// default: -// Serial.printf("FH=%d _wifiEvent %d\n",ESP.getFreeHeap(),event); -// break; } } #endif diff --git a/src/H4P_WiFi.h b/src/H4P_WiFi.h index 97561292..d3c672ea 100644 --- a/src/H4P_WiFi.h +++ b/src/H4P_WiFi.h @@ -27,31 +27,16 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#ifndef H4P_WiFi_H -#define H4P_WiFi_H #include -extern void h4FactoryReset(); - +#ifndef H4P_WiFi_H +#define H4P_WiFi_H +#include #ifndef ARDUINO_ARCH_STM32 #include -#ifdef ARDUINO_ARCH_ESP8266 - #include - #include - #include - #include - #include - #include - -#else - #include -// #include - #include - #include - #include -#endif +extern void h4FactoryReset(); class H4P_WiFi: public H4PluginService{ DNSServer* _dnsServer; @@ -73,23 +58,22 @@ class H4P_WiFi: public H4PluginService{ public: // included here aginst better wishes due to compiler bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=89605 H4P_WiFi(string ssid,string psk,string device="",H4_FN_VOID onC=[](){},H4_FN_VOID onD=[](){}): H4PluginService(onC,onD){ - _cb[ssidtag()]=ssid; + _cb[ssidTag()]=ssid; _cb["psk"]=psk; - _cb[devicetag()]=device; + _cb[deviceTag()]=device; - _pid=wifitag(); - subid=H4PC_WIFI; + _pid=wifiTag(); _names={ - {H4P_TRID_WIFI,uppercase(_pid)}, +// {H4P_TRID_WIFI,uppercase(_pid)}, {H4P_TRID_WFAP,"WFAP"}, {H4P_TRID_HOTA,"HOTA"} }; _local={ - {"clear", { H4PC_WIFI, 0, CMD(clear)}}, + {"clear", { subid, 0, CMD(clear)}}, {"factory", { H4PC_ROOT, 0, CMD(h4FactoryReset) }}, - {"change", { H4PC_WIFI, 0, CMDVS(_change)}}, - {"host", { H4PC_WIFI, 0, CMDVS(_host)}}, + {"change", { subid, 0, CMDVS(_change)}}, + {"host", { subid, 0, CMDVS(_host)}}, {_pid, { H4PC_SHOW, 0, CMD(show) }} }; } @@ -97,7 +81,7 @@ class H4P_WiFi: public H4PluginService{ void clear(); void change(string ssid,string psk); void getPersistentValue(string v,string prefix); - void host(string h){ setPersistentValue(devicetag(),h,true); } + void host(string h){ setPersistentValue(deviceTag(),h,true); } void setPersistentValue(string n,string v,bool reboot); void start() override; void stop() override; diff --git a/src/H4P_WiFiSelect.h b/src/H4P_WiFiSelect.h new file mode 100644 index 00000000..7d47c0d0 --- /dev/null +++ b/src/H4P_WiFiSelect.h @@ -0,0 +1,18 @@ +#ifndef ARDUINO_ARCH_STM32 + #ifdef ARDUINO_ARCH_ESP8266 + #include + #include + #include + #include + #include + #else + // #pragma message("ESP32 compile with AsyncTCP"); + #include + #include + #include + #endif + #include + #include +#else + #define H4P_NO_WIFI +#endif \ No newline at end of file diff --git a/src/H4Plugins.cpp b/src/H4Plugins.cpp index c724d8bb..db4366f9 100644 --- a/src/H4Plugins.cpp +++ b/src/H4Plugins.cpp @@ -34,17 +34,12 @@ SOFTWARE. void __attribute__((weak)) h4AddAwsHandlers(){} void __attribute__((weak)) onFactoryReset(){} -H4P_CONFIG_BLOCK H4Plugin::_cb; -//vector H4Plugin::_pending; -H4_CMD_MAP H4Plugin::commands; - vector H4PluginService::_factoryChain; void h4StartPlugins(){ - for(auto const& p:H4Plugin::_pending) p->_startup(); - for(auto const& p:H4Plugin::_pending) { p->_hookIn(); } - for(auto const& p:H4Plugin::_pending) p->_greenLight(); - H4Plugin::_pending.clear(); + for(auto const& p:H4Plugin::_plugins) p->_startup(); + for(auto const& p:H4Plugin::_plugins) { p->_hookIn(); } + for(auto const& p:H4Plugin::_plugins) p->_greenLight(); H4PluginService::hookFactory(onFactoryReset); } @@ -53,7 +48,10 @@ void h4FactoryReset(){ h4reboot(); } -H4Plugin::H4Plugin(){ _pending.push_back(this); } +H4Plugin::H4Plugin(){ + subid=++nextSubid; + _plugins.push_back(this); +} vector H4Plugin::expectInt(string pl,const char* delim){ vector results; @@ -66,7 +64,7 @@ vector H4Plugin::expectInt(string pl,const char* delim){ } uint32_t H4Plugin::guardInt1(vector vs,function f){ - return guard<1>(vs,[f,this](vector vs){ + return guard1(vs,[f,this](vector vs){ auto vi=expectInt(PAYLOAD); if(vi.size()==1) return ([f](uint32_t v){ f(v); return H4_CMD_OK; })(vi[0]); else return H4_CMD_NOT_NUMERIC; @@ -74,25 +72,20 @@ uint32_t H4Plugin::guardInt1(vector vs,function f){ } uint32_t H4Plugin::guardInt4(vector vs,function f){ - return guard<1>(vs,[f,this](vector vs){ + return guard1(vs,[f,this](vector vs){ auto vi=expectInt(PAYLOAD); if(vi.size()==4) return ([f](uint32_t v1,uint32_t v2,uint32_t v3,uint32_t v4){ f(v1,v2,v3,v4); return H4_CMD_OK; })(vi[0],vi[1],vi[2],vi[3]); else return H4_CMD_NOT_NUMERIC; }); } -uint32_t H4Plugin::guardString1(vector vs,function f){ - return guard<1>(vs,[f,this](vector vs){ - auto vg=split(PAYLOAD,","); - if(vg.size()<2) return ([f](string s1){ f(s1); return H4_CMD_OK; })(vg[0]); - else return H4_CMD_TOO_MANY_PARAMS; - }); -} - uint32_t H4Plugin::guardString2(vector vs,function f){ - return guard<1>(vs,[f,this](vector vs){ + return guard1(vs,[f,this](vector vs){ auto vg=split(PAYLOAD,","); - if(vg.size()<3) return ([f](string s1,string s2){ f(s1,s2); return H4_CMD_OK; })(vg[0],vg[1]); + if(vg.size()<3){ + if(vg.size()>1) return ([f](string s1,string s2){ f(s1,s2); return H4_CMD_OK; })(vg[0],vg[1]); + return H4_CMD_TOO_FEW_PARAMS; + } else return H4_CMD_TOO_MANY_PARAMS; }); } @@ -105,9 +98,9 @@ void H4Plugin::reply(const char* fmt,...){ // find pub sub size va_end(ap); string source=_cb["source"]; #ifndef H4P_NO_WIFI - if(source==aswstag()) h4asws._reply(buff); + if(source==aswsTag()) h4asws._reply(buff); else { - if(source==mqtttag()) h4mqtt._reply(buff); + if(source==mqttTag()) h4mqtt._reply(buff); else { #endif H4Plugin::_reply(buff); @@ -119,7 +112,7 @@ void H4Plugin::reply(const char* fmt,...){ // find pub sub size void H4Plugin::_startup(){ // reply("H4Plugin::_startup %s nN=%d nC=%d\n",CSTR(_pid),_names.size(),_cmds.size()); - h4._hookLoop(_hook,_names,_pid); + h4._hookLoop(_hook,_names,subid); if(_cmds.size()) commands.insert(_cmds.begin(),_cmds.end()); _cmds.clear(); _names.clear(); @@ -129,7 +122,7 @@ void H4Plugin::_startup(){ // H4PluginService // void H4PluginService::_startup(){ -// reply("H4PluginService::_startup %s nN=%d nC=%d\n",CSTR(_pid),_names.size(),_cmds.size()); +// reply("H4PluginService::_startup %s nN=%d nC=%d SUBID=%d\n",CSTR(_pid),_names.size(),_cmds.size(),subid); _cmds={ {"restart", { 0, 0, CMD(restart)}}, {"start", { 0, 0, CMD(start)}}, @@ -141,6 +134,7 @@ void H4PluginService::_startup(){ _local.clear(); H4Plugin::_startup(); } + void H4PluginService::h4pcConnected(){ for(auto const& c:_connChain) c(); svc(_pid,H4P_LOG_SVC_UP); @@ -152,10 +146,9 @@ void H4PluginService::h4pcDisconnected(){ } void H4PluginService::svc(const string& uid,H4P_LOG_TYPE ud) { - #ifdef H4P_SERIAL_LOGGING - if(h4._hasName(H4P_TRID_SCMD)) { + #ifdef H4P_LOG_EVENTS + if(H4Plugin::isLoaded(scmdTag())) { h4sc._logEvent(uid,ud,"h4","",0); - // Serial.printf("SVC %s %s\n",CSTR(uid),ud==H4P_LOG_SVC_UP ? "UP":"DOWN"); Serial.print("SVC ");Serial.print(CSTR(uid)); Serial.print(" ");Serial.println(ud==H4P_LOG_SVC_UP ? "UP":"DOWN"); } @@ -165,8 +158,7 @@ void H4PluginService::svc(const string& uid,H4P_LOG_TYPE ud) { // H4PlogService // void H4PLogService::_hookIn(){ - h4sc._hookLogChain(bind(&H4PLogService::_logEvent,this,_1,_2,_3,_4,_5)); + h4sc._hookLogChain(bind(&H4PLogService::_filterLog,this,_1,_2,_3,_4,_5)); h4sc.addCmd("msg",subid, 0, CMDNULL); - start(); } diff --git a/src/H4Plugins.h b/src/H4Plugins.h index e694600b..24c3f0c3 100644 --- a/src/H4Plugins.h +++ b/src/H4Plugins.h @@ -1,27 +1,35 @@ #ifndef H4P_PLUGINS_H #define H4P_PLUGINS_H -#include - #include #include #include -#include -//#include -//#include +#include +//#include #include #include #include #include #include -#include -#include -#include -#include -#include -#include -//frig initialisation order -#define H4_USE_PLUGINS std::vector H4Plugin::_pending; +#ifndef ARDUINO_ARCH_STM32 + #include + #include + #include + #include + #include + #include + #include +#endif + +#include +#include +#include +//force static initialisation +#define H4_USE_PLUGINS std::vector H4Plugin::_plugins; \ + uint32_t H4Plugin::nextSubid=H4PC_MAX-1; \ + H4P_CONFIG_BLOCK H4Plugin::_cb; \ + H4_CMD_MAP H4Plugin::commands; \ + H4GM_PINMAP H4P_GPIOManager::pins; #endif \ No newline at end of file diff --git a/src/asyncHTTPrequest.cpp b/src/asyncHTTPrequest.cpp new file mode 100644 index 00000000..4df40592 --- /dev/null +++ b/src/asyncHTTPrequest.cpp @@ -0,0 +1,799 @@ +#include +#ifndef H4P_NO_WIFI +#include "asyncHTTPrequest.h" + +//************************************************************************************************************** +asyncHTTPrequest::asyncHTTPrequest() + : _readyState(readyStateUnsent) + , _HTTPcode(0) + , _chunked(false) + , _debug(DEBUG_IOTA_HTTP_SET) + , _timeout(DEFAULT_RX_TIMEOUT) + , _lastActivity(0) + , _requestStartTime(0) + , _requestEndTime(0) + , _URL(nullptr) + , _connectedHost(nullptr) + , _connectedPort(-1) + , _client(nullptr) + , _contentLength(0) + , _contentRead(0) + , _readyStateChangeCB(nullptr) + , _readyStateChangeCBarg(nullptr) + , _onDataCB(nullptr) + , _onDataCBarg(nullptr) + , _request(nullptr) + , _response(nullptr) + , _chunks(nullptr) + , _headers(nullptr) +{ + DEBUG_HTTP("New request.");} + +//************************************************************************************************************** +asyncHTTPrequest::~asyncHTTPrequest(){ + if(_client) _client->close(true); + delete _URL; + delete _headers; + delete _request; + delete _response; + delete _chunks; + delete[] _connectedHost; +} + +//************************************************************************************************************** +void asyncHTTPrequest::setDebug(bool debug){ + if(_debug || debug) { + _debug = true; + DEBUG_HTTP("setDebug(%s) version %s\r\n", debug ? "on" : "off", asyncHTTPrequest_h); + } + _debug = debug; +} + +//************************************************************************************************************** +bool asyncHTTPrequest::debug(){ + return(_debug); +} + +//************************************************************************************************************** +bool asyncHTTPrequest::open(const char* method, const char* URL){ + DEBUG_HTTP("open(%s, %.32s)\r\n", method, URL); + if(_readyState != readyStateUnsent && _readyState != readyStateDone) {return false;} + _requestStartTime = millis(); + delete _URL; + delete _headers; + delete _request; + delete _response; + delete _chunks; + _URL = nullptr; + _headers = nullptr; + _response = nullptr; + _request = nullptr; + _chunks = nullptr; + _chunked = false; + _contentRead = 0; + _readyState = readyStateUnsent; + + if (strcmp(method, "GET") == 0) { + _HTTPmethod = HTTPmethodGET; + } else if (strcmp(method, "POST") == 0) { + _HTTPmethod = HTTPmethodPOST; + } else + return false; + + if (!_parseURL(URL)) { + return false;} + if( _client && _client->connected() && + (strcmp(_URL->host, _connectedHost) != 0 || _URL->port != _connectedPort)){return false;} + char* hostName = new char[strlen(_URL->host)+10]; + sprintf(hostName,"%s:%d", _URL->host, _URL->port); + _addHeader("host",hostName); + delete[] hostName; + _lastActivity = millis(); + return _connect(); +} +//************************************************************************************************************** +void asyncHTTPrequest::onReadyStateChange(readyStateChangeCB cb, void* arg){ + _readyStateChangeCB = cb; + _readyStateChangeCBarg = arg; +} + +//************************************************************************************************************** +void asyncHTTPrequest::setTimeout(int seconds){ + DEBUG_HTTP("setTimeout(%d)\r\n", seconds); + _timeout = seconds; +} + +//************************************************************************************************************** +bool asyncHTTPrequest::send(){ + DEBUG_HTTP("send()\r\n"); + if( ! _buildRequest()) return false; + _send(); + return true; +} + +//************************************************************************************************************** +bool asyncHTTPrequest::send(String body){ + DEBUG_HTTP("send(String) %s... (%d)\r\n", body.substring(0,16).c_str(), body.length()); + _addHeader("Content-Length", String(body.length()).c_str()); + if( ! _buildRequest()) return false; + _request->write(body); + _send(); + return true; +} + +//************************************************************************************************************** +bool asyncHTTPrequest::send(const char* body){ + Serial.printf("BODY1 %s [%d]\n",body, strlen(body)); +// DEBUG_HTTP("send(char*) %s.16... (%d)\r\n",body, strlen(body)); + _addHeader("Content-Length", String(strlen(body)).c_str()); + Serial.printf("BODY2 %s %s\n",body,String(strlen(body)).c_str()); + if( ! _buildRequest()) return false; + _request->write(body); + _send(); + return true; +} + +//************************************************************************************************************** +bool asyncHTTPrequest::send(const uint8_t* body, size_t len){ + DEBUG_HTTP("send(char*) %s.16... (%d)\r\n",(char*)body, len); + _addHeader("Content-Length", String(len).c_str()); + if( ! _buildRequest()) return false; + _request->write(body, len); + _send(); + return true; +} + +//************************************************************************************************************** +bool asyncHTTPrequest::send(xbuf* body, size_t len){ + DEBUG_HTTP("send(char*) %s.16... (%d)\r\n", body->peekString(16).c_str(), len); + _addHeader("Content-Length", String(len).c_str()); + if( ! _buildRequest()) return false; + _request->write(body, len); + _send(); + return true; +} + +//************************************************************************************************************** +void asyncHTTPrequest::abort(){ + DEBUG_HTTP("abort()\r\n"); + if(! _client) return; + _client->abort(); +} +//************************************************************************************************************** +int asyncHTTPrequest::readyState(){ + return _readyState; +} + +//************************************************************************************************************** +int asyncHTTPrequest::responseHTTPcode(){ + return _HTTPcode; +} + +//************************************************************************************************************** +String asyncHTTPrequest::responseText(){ + DEBUG_HTTP("responseText() "); + if( ! _response || _readyState < readyStateLoading || ! available()){ + DEBUG_HTTP("responseText() no data\r\n"); + return String(); + } + String localString; + size_t avail = available(); + if( ! localString.reserve(avail)) { + DEBUG_HTTP("!responseText() no buffer\r\n") + _HTTPcode = HTTPCODE_TOO_LESS_RAM; + _client->abort(); + return String(); + } + localString = _response->readString(avail); + _contentRead += localString.length(); + DEBUG_HTTP("responseText() %s... (%d)\r\n", localString.substring(0,16).c_str() , avail); + return localString; +} + +//************************************************************************************************************** +size_t asyncHTTPrequest::responseRead(uint8_t* buf, size_t len){ + if( ! _response || _readyState < readyStateLoading || ! available()){ + DEBUG_HTTP("responseRead() no data\r\n"); + return 0; + } + size_t avail = available() > len ? len : available(); + _response->read(buf, avail); + DEBUG_HTTP("responseRead() %.16s... (%d)\r\n", (char*)buf , avail); + _contentRead += avail; + return avail; +} + +//************************************************************************************************************** +size_t asyncHTTPrequest::available(){ + if(_readyState < readyStateLoading) return 0; + if(_chunked && (_contentLength - _contentRead) < _response->available()){ + return _contentLength - _contentRead; + } + return _response->available(); +} + +//************************************************************************************************************** +size_t asyncHTTPrequest::responseLength(){ + if(_readyState < readyStateLoading) return 0; + return _contentLength; +} + +//************************************************************************************************************** +void asyncHTTPrequest::onData(onDataCB cb, void* arg){ + DEBUG_HTTP("onData() CB set\r\n"); + _onDataCB = cb; + _onDataCBarg = arg; +} + +//************************************************************************************************************** +uint32_t asyncHTTPrequest::elapsedTime(){ + if(_readyState <= readyStateOpened) return 0; + if(_readyState != readyStateDone){ + return millis() - _requestStartTime; + } + return _requestEndTime - _requestStartTime; +} + +//************************************************************************************************************** +String asyncHTTPrequest::version(){ + return String(asyncHTTPrequest_h); +} + +/*______________________________________________________________________________________________________________ + + PPPP RRRR OOO TTTTT EEEEE CCC TTTTT EEEEE DDDD + P P R R O O T E C C T E D D + PPPP RRRR O O T EEE C T EEE D D + P R R O O T E C C T E D D + P R R OOO T EEEEE CCC T EEEEE DDDD +_______________________________________________________________________________________________________________*/ + +//************************************************************************************************************** +bool asyncHTTPrequest::_parseURL(const char* url){ + return _parseURL(String(url)); +} + +//************************************************************************************************************** +bool asyncHTTPrequest::_parseURL(String url){ + Serial.printf("_parseURL() %s\n", url.c_str()); + delete _URL; + int hostBeg = 0; + _URL = new URL; + _URL->scheme = new char[8]; + strcpy(_URL->scheme, "HTTP://"); + if(url.substring(0,7).equalsIgnoreCase("HTTP://")){ + hostBeg += 7; + } + else if(url.substring(0,8).equalsIgnoreCase("HTTPS://")){ + return false; + } + Serial.printf("_parseURL(2) %s\n", url.c_str()); + + int pathBeg = url.indexOf('/', hostBeg); + if(pathBeg < 0) return false; + Serial.printf("_parseURL(3) %s\n", url.c_str()); + int hostEnd = pathBeg; + int portBeg = url.indexOf(':',hostBeg); + if(portBeg > 0 && portBeg < pathBeg){ + _URL->port = url.substring(portBeg+1, pathBeg).toInt(); + hostEnd = portBeg; + } + _URL->host = new char[hostEnd - hostBeg + 1]; + strcpy(_URL->host, url.substring(hostBeg, hostEnd).c_str()); + int queryBeg = url.indexOf('?'); + if(queryBeg < 0) queryBeg = url.length(); + _URL->path = new char[queryBeg - pathBeg + 1]; + strcpy(_URL->path, url.substring(pathBeg, queryBeg).c_str()); + _URL->query = new char[url.length() - queryBeg + 1]; + strcpy(_URL->query, url.substring(queryBeg).c_str()); + DEBUG_HTTP("_parseURL() %s%s:%d%s%.16s\r\n", _URL->scheme, _URL->host, _URL->port, _URL->path, _URL->query); + Serial.printf("_parseURL(99) %s%s:%d%s%.16s\r\n", _URL->scheme, _URL->host, _URL->port, _URL->path, _URL->query); + return true; +} + +//************************************************************************************************************** +bool asyncHTTPrequest::_connect(){ + DEBUG_HTTP("_connect()\r\n"); + if( ! _client){ + _client = new AsyncClient(); + } + delete[] _connectedHost; + _connectedHost = new char[strlen(_URL->host) + 1]; + strcpy(_connectedHost, _URL->host); + _connectedPort = _URL->port; + _client->onConnect([](void *obj, AsyncClient *client){((asyncHTTPrequest*)(obj))->_onConnect(client);}, this); + _client->onDisconnect([](void *obj, AsyncClient* client){((asyncHTTPrequest*)(obj))->_onDisconnect(client);}, this); + _client->onPoll([](void *obj, AsyncClient *client){((asyncHTTPrequest*)(obj))->_onPoll(client);}, this); + _client->onError([](void *obj, AsyncClient *client, uint32_t error){((asyncHTTPrequest*)(obj))->_onError(client, error);}, this); + if( ! _client->connected()){ + if( ! _client->connect(_URL->host, _URL->port)) { + DEBUG_HTTP("!client.connect(%s, %d) failed\r\n", _URL->host, _URL->port); + _HTTPcode = HTTPCODE_NOT_CONNECTED; + _setReadyState(readyStateDone); + return false; + } + } + else { + _onConnect(_client); + } + _lastActivity = millis(); + return true; +} + +//************************************************************************************************************** +bool asyncHTTPrequest::_buildRequest(){ + DEBUG_HTTP("_buildRequest()\r\n"); + + // Build the header. + + if( ! _request) _request = new xbuf; + _request->write(_HTTPmethod == HTTPmethodGET ? "GET " : "POST "); + Serial.printf("_buildRequest 1\n"); + Serial.printf("_buildRequest 2 %s\n",_URL->path); + _request->write(_URL->path); + _request->write(_URL->query); + Serial.printf("_buildRequest 3\n"); + _request->write(" HTTP/1.1\r\n"); + Serial.printf("_buildRequest 4\n"); + delete _URL; + _URL = nullptr; + header* hdr = _headers; + while(hdr){ + _request->write(hdr->name); + _request->write(':'); + _request->write(hdr->value); + _request->write("\r\n"); + hdr = hdr->next; + } + delete _headers; + _headers = nullptr; + _request->write("\r\n"); + + return true; +} + +//************************************************************************************************************** +size_t asyncHTTPrequest::_send(){ + if( ! _request) return 0; + DEBUG_HTTP("_send() %d\r\n", _request->available()); + if( ! _client->connected() || ! _client->canSend()){ + DEBUG_HTTP("*can't send\r\n"); + return 0; + } + size_t supply = _request->available(); + size_t demand = _client->space(); + if(supply > demand) supply = demand; + size_t sent = 0; + uint8_t* temp = new uint8_t[100]; + while(supply){ + size_t chunk = supply < 100 ? supply : 100; + supply -= _request->read(temp, chunk); + sent += _client->add((char*)temp, chunk); + } + delete temp; + if(_request->available() == 0){ + delete _request; + _request = nullptr; + } + _client->send(); + DEBUG_HTTP("*sent %d\r\n", sent); + _lastActivity = millis(); + return sent; +} + +//************************************************************************************************************** +void asyncHTTPrequest::_setReadyState(readyStates newState){ + if(_readyState != newState){ + _readyState = newState; + DEBUG_HTTP("_setReadyState(%d)\r\n", _readyState); + if(_readyStateChangeCB){ + _readyStateChangeCB(_readyStateChangeCBarg, this, _readyState); + } + } +} + +//************************************************************************************************************** +void asyncHTTPrequest::_processChunks(){ + while(_chunks->available()){ + DEBUG_HTTP("_processChunks() %.16s... (%d)\r\n", _chunks->peekString(16).c_str(), _chunks->available()); + size_t _chunkRemaining = _contentLength - _contentRead - _response->available(); + _chunkRemaining -= _response->write(_chunks, _chunkRemaining); + if(_chunks->indexOf("\r\n") == -1){ + return; + } + String chunkHeader = _chunks->readStringUntil("\r\n"); + DEBUG_HTTP("*getChunkHeader %.16s... (%d)\r\n", chunkHeader.c_str(), chunkHeader.length()); + size_t chunkLength = strtol(chunkHeader.c_str(),nullptr,16); + _contentLength += chunkLength; + if(chunkLength == 0){ + char* connectionHdr = respHeaderValue("connection"); + if(connectionHdr && (strcasecmp_P(connectionHdr,PSTR("disconnect")) == 0)){ + DEBUG_HTTP("*all chunks received - closing TCP\r\n"); + _client->close(); + } + else { + DEBUG_HTTP("*all chunks received - no disconnect\r\n"); + } + _requestEndTime = millis(); + _lastActivity = 0; + _timeout = 0; + _setReadyState(readyStateDone); + return; + } + } +} + +/*______________________________________________________________________________________________________________ + +EEEEE V V EEEEE N N TTTTT H H AAA N N DDDD L EEEEE RRRR SSS +E V V E NN N T H H A A NN N D D L E R R S +EEE V V EEE N N N T HHHHH AAAAA N N N D D L EEE RRRR SSS +E V V E N NN T H H A A N NN D D L E R R S +EEEEE V EEEEE N N T H H A A N N DDDD LLLLL EEEEE R R SSS +_______________________________________________________________________________________________________________*/ + +//************************************************************************************************************** +void asyncHTTPrequest::_onConnect(AsyncClient* client){ + DEBUG_HTTP("_onConnect handler\r\n"); + _client = client; + _setReadyState(readyStateOpened); + _response = new xbuf; + _contentLength = 0; + _contentRead = 0; + _chunked = false; + _client->onAck([](void* obj, AsyncClient* client, size_t len, uint32_t time){((asyncHTTPrequest*)(obj))->_send();}, this); + _client->onData([](void* obj, AsyncClient* client, void* data, size_t len){((asyncHTTPrequest*)(obj))->_onData(data, len);}, this); + if(_client->canSend()){ + _send(); + } + _lastActivity = millis(); +} + +//************************************************************************************************************** +void asyncHTTPrequest::_onPoll(AsyncClient* client){ + if(_timeout && (millis() - _lastActivity) > (_timeout * 1000)){ + _client->close(); + _HTTPcode = HTTPCODE_TIMEOUT; + DEBUG_HTTP("_onPoll timeout\r\n"); + } + if(_onDataCB && available()){ + _onDataCB(_onDataCBarg, this, available()); + } +} + +//************************************************************************************************************** +void asyncHTTPrequest::_onError(AsyncClient* client, int8_t error){ + DEBUG_HTTP("_onError handler error=%d\r\n", error); + _HTTPcode = error; +} + +//************************************************************************************************************** +void asyncHTTPrequest::_onDisconnect(AsyncClient* client){ + DEBUG_HTTP("_onDisconnect handler\r\n"); + if(_readyState < readyStateOpened){ + _HTTPcode = HTTPCODE_NOT_CONNECTED; + } + else if (_HTTPcode > 0 && + (_readyState < readyStateHdrsRecvd || (_contentRead + _response->available()) < _contentLength)) { + _HTTPcode = HTTPCODE_CONNECTION_LOST; + } + delete _client; + _client = nullptr; + delete[] _connectedHost; + _connectedHost = nullptr; + _connectedPort = -1; + _requestEndTime = millis(); + _lastActivity = 0; + _setReadyState(readyStateDone); +} + +//************************************************************************************************************** +void asyncHTTPrequest::_onData(void* Vbuf, size_t len){ + DEBUG_HTTP("_onData handler %.16s... (%d)\r\n",(char*) Vbuf, len); + _lastActivity = millis(); + + // Transfer data to xbuf + + if(_chunks){ + _chunks->write((uint8_t*)Vbuf, len); + _processChunks(); + } + else { + _response->write((uint8_t*)Vbuf, len); + } + + // if headers not complete, collect them. + // if still not complete, just return. + + if(_readyState == readyStateOpened){ + if( ! _collectHeaders()) return; + } + + // If there's data in the buffer and not Done, + // advance readyState to Loading. + + if(_response->available() && _readyState != readyStateDone){ + _setReadyState(readyStateLoading); + } + + // If not chunked and all data read, close it up. + + if( ! _chunked && (_response->available() + _contentRead) >= _contentLength){ + char* connectionHdr = respHeaderValue("connection"); + if(connectionHdr && (strcasecmp_P(connectionHdr,PSTR("disconnect")) == 0)){ + DEBUG_HTTP("*all data received - closing TCP\r\n"); + _client->close(); + } + else { + DEBUG_HTTP("*all data received - no disconnect\r\n"); + } + _requestEndTime = millis(); + _lastActivity = 0; + _timeout = 0; + _setReadyState(readyStateDone); + } + + // If onData callback requested, do so. + + if(_onDataCB && available()){ + _onDataCB(_onDataCBarg, this, available()); + } + +} + +//************************************************************************************************************** +bool asyncHTTPrequest::_collectHeaders(){ + DEBUG_HTTP("_collectHeaders()\r\n"); + + // Loop to parse off each header line. + // Drop out and return false if no \r\n (incomplete) + + do { + String headerLine = _response->readStringUntil("\r\n"); + + // If no line, return false. + + if( ! headerLine.length()){ + return false; + } + + // If empty line, all headers are in, advance readyState. + + if(headerLine.length() == 2){ + _setReadyState(readyStateHdrsRecvd); + } + + // If line is HTTP header, capture HTTPcode. + + else if(headerLine.substring(0,7) == "HTTP/1."){ + _HTTPcode = headerLine.substring(9, headerLine.indexOf(' ', 9)).toInt(); + } + + // Ordinary header, add to header list. + + else { + int colon = headerLine.indexOf(':'); + if(colon != -1){ + String name = headerLine.substring(0, colon); + name.trim(); + String value = headerLine.substring(colon+1); + value.trim(); + _addHeader(name.c_str(), value.c_str()); + } + } + } while(_readyState == readyStateOpened); + + // If content-Length header, set _contentLength + + header *hdr = _getHeader("Content-Length"); + if(hdr){ + _contentLength = strtol(hdr->value,nullptr,10); + } + + // If chunked specified, try to set _contentLength to size of first chunk + + hdr = _getHeader("Transfer-Encoding"); + if(hdr && strcasecmp_P(hdr->value, PSTR("chunked")) == 0){ + DEBUG_HTTP("*transfer-encoding: chunked\r\n"); + _chunked = true; + _contentLength = 0; + _chunks = new xbuf; + _chunks->write(_response, _response->available()); + _processChunks(); + } + + + return true; +} + + +/*_____________________________________________________________________________________________________________ + + H H EEEEE AAA DDDD EEEEE RRRR SSS + H H E A A D D E R R S + HHHHH EEE AAAAA D D EEE RRRR SSS + H H E A A D D E R R S + H H EEEEE A A DDDD EEEEE R R SSS +______________________________________________________________________________________________________________*/ + +//************************************************************************************************************** +void asyncHTTPrequest::setReqHeader(const char* name, const char* value){ + if(_readyState <= readyStateOpened && _headers){ + _addHeader(name, value); + } +} + +//************************************************************************************************************** +void asyncHTTPrequest::setReqHeader(const char* name, const __FlashStringHelper* value){ + if(_readyState <= readyStateOpened && _headers){ + char* _value = _charstar(value); + _addHeader(name, _value); + delete[] _value; + } +} + +//************************************************************************************************************** +void asyncHTTPrequest::setReqHeader(const __FlashStringHelper *name, const char* value){ + if(_readyState <= readyStateOpened && _headers){ + char* _name = _charstar(name); + _addHeader(_name, value); + delete[] _name; + } +} + +//************************************************************************************************************** +void asyncHTTPrequest::setReqHeader(const __FlashStringHelper *name, const __FlashStringHelper* value){ + if(_readyState <= readyStateOpened && _headers){ + char* _name = _charstar(name); + char* _value = _charstar(value); + _addHeader(_name, _value); + delete[] _name; + delete[] _value; + } +} + +//************************************************************************************************************** +void asyncHTTPrequest::setReqHeader(const char* name, int32_t value){ + if(_readyState <= readyStateOpened && _headers){ + setReqHeader(name, String(value).c_str()); + } +} + +//************************************************************************************************************** +void asyncHTTPrequest::setReqHeader(const __FlashStringHelper *name, int32_t value){ + if(_readyState <= readyStateOpened && _headers){ + char* _name = _charstar(name); + setReqHeader(_name, String(value).c_str()); + delete[] _name; + } +} + +//************************************************************************************************************** +int asyncHTTPrequest::respHeaderCount(){ + if(_readyState < readyStateHdrsRecvd) return 0; + int count = 0; + header* hdr = _headers; + while(hdr){ + count++; + hdr = hdr->next; + } + return count; +} + +//************************************************************************************************************** +char* asyncHTTPrequest::respHeaderName(int ndx){ + if(_readyState < readyStateHdrsRecvd) return nullptr; + header* hdr = _getHeader(ndx); + if ( ! hdr) return nullptr; + return hdr->name; +} + +//************************************************************************************************************** +char* asyncHTTPrequest::respHeaderValue(const char* name){ + if(_readyState < readyStateHdrsRecvd) return nullptr; + header* hdr = _getHeader(name); + if( ! hdr) return nullptr; + return hdr->value; +} + +//************************************************************************************************************** +char* asyncHTTPrequest::respHeaderValue(const __FlashStringHelper *name){ + if(_readyState < readyStateHdrsRecvd) return nullptr; + char* _name = _charstar(name); + header* hdr = _getHeader(_name); + delete[] _name; + if( ! hdr) return nullptr; + return hdr->value; +} + +//************************************************************************************************************** +char* asyncHTTPrequest::respHeaderValue(int ndx){ + if(_readyState < readyStateHdrsRecvd) return nullptr; + header* hdr = _getHeader(ndx); + if ( ! hdr) return nullptr; + return hdr->value; +} + +//************************************************************************************************************** +bool asyncHTTPrequest::respHeaderExists(const char* name){ + if(_readyState < readyStateHdrsRecvd) return false; + header* hdr = _getHeader(name); + if ( ! hdr) return false; + return true; +} + +//************************************************************************************************************** +bool asyncHTTPrequest::respHeaderExists(const __FlashStringHelper *name){ + if(_readyState < readyStateHdrsRecvd) return false; + char* _name = _charstar(name); + header* hdr = _getHeader(_name); + delete[] _name; + if ( ! hdr) return false; + return true; +} + +//************************************************************************************************************** +String asyncHTTPrequest::headers(){ + String _response = ""; + header* hdr = _headers; + while(hdr){ + _response += hdr->name; + _response += ':'; + _response += hdr->value; + _response += "\r\n"; + hdr = hdr->next; + } + _response += "\r\n"; + return _response; +} + +//************************************************************************************************************** +asyncHTTPrequest::header* asyncHTTPrequest::_addHeader(const char* name, const char* value){ + header* hdr = (header*) &_headers; + while(hdr->next) { + if(strcasecmp(name, hdr->next->name) == 0){ + header* oldHdr = hdr->next; + hdr->next = hdr->next->next; + oldHdr->next = nullptr; + delete oldHdr; + } + else { + hdr = hdr->next; + } + } + hdr->next = new header; + hdr->next->name = new char[strlen(name)+1]; + strcpy(hdr->next->name, name); + hdr->next->value = new char[strlen(value)+1]; + strcpy(hdr->next->value, value); + return hdr->next; +} + +//************************************************************************************************************** +asyncHTTPrequest::header* asyncHTTPrequest::_getHeader(const char* name){ + header* hdr = _headers; + while (hdr) { + if(strcasecmp(name, hdr->name) == 0) break; + hdr = hdr->next; + } + return hdr; +} + +//************************************************************************************************************** +asyncHTTPrequest::header* asyncHTTPrequest::_getHeader(int ndx){ + header* hdr = _headers; + while (hdr) { + if( ! ndx--) break; + hdr = hdr->next; + } + return hdr; +} + +//************************************************************************************************************** +char* asyncHTTPrequest::_charstar(const __FlashStringHelper * str){ + if( ! str) return nullptr; + char* ptr = new char[strlen_P((PGM_P)str)+1]; + strcpy_P(ptr, (PGM_P)str); + return ptr; +} +#endif \ No newline at end of file diff --git a/src/asyncHTTPrequest.h b/src/asyncHTTPrequest.h new file mode 100644 index 00000000..b4b99a9a --- /dev/null +++ b/src/asyncHTTPrequest.h @@ -0,0 +1,217 @@ +#ifndef asyncHTTPrequest_h +#define asyncHTTPrequest_h "1.1.15" + + /*********************************************************************************** + Copyright (C) <2018> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***********************************************************************************/ + +#ifndef DEBUG_IOTA_PORT +#define DEBUG_IOTA_PORT Serial +#endif + +#ifdef DEBUG_IOTA_HTTP +#define DEBUG_IOTA_HTTP_SET true +#else +#define DEBUG_IOTA_HTTP_SET false +#endif + +#include +#include +#include +#include + +#define DEBUG_HTTP(format,...) if(_debug){\ + DEBUG_IOTA_PORT.printf("Debug(%3ld): ", millis()-_requestStartTime);\ + DEBUG_IOTA_PORT.printf_P(PSTR(format),##__VA_ARGS__);} + + +#define DEFAULT_RX_TIMEOUT 3 // Seconds for timeout + +#define HTTPCODE_CONNECTION_REFUSED (-1) +#define HTTPCODE_SEND_HEADER_FAILED (-2) +#define HTTPCODE_SEND_PAYLOAD_FAILED (-3) +#define HTTPCODE_NOT_CONNECTED (-4) +#define HTTPCODE_CONNECTION_LOST (-5) +#define HTTPCODE_NO_STREAM (-6) +#define HTTPCODE_NO_HTTP_SERVER (-7) +#define HTTPCODE_TOO_LESS_RAM (-8) +#define HTTPCODE_ENCODING (-9) +#define HTTPCODE_STREAM_WRITE (-10) +#define HTTPCODE_TIMEOUT (-11) + +class asyncHTTPrequest { + + struct header { + header* next; + char* name; + char* value; + header(): + next(nullptr), + name(nullptr), + value(nullptr) + {}; + ~header(){ + delete[] name; + delete[] value; + delete next; + } + }; + + struct URL { + char* scheme; + char* user; + char* pwd; + char* host; + int port; + char* path; + char* query; + char* fragment; + URL(): + scheme(nullptr), + user(nullptr), + pwd(nullptr), + host(nullptr), + port(80), + path(nullptr), + query(nullptr), + fragment(nullptr) + {}; + ~URL(){ + delete[] scheme; + delete[] user; + delete[] pwd; + delete[] host; + delete[] path; + delete[] query; + delete[] fragment; + } + }; + + typedef std::function readyStateChangeCB; + typedef std::function onDataCB; + + public: + asyncHTTPrequest(); + ~asyncHTTPrequest(); + + + //External functions in typical order of use: + //__________________________________________________________________________________________________________*/ + void setDebug(bool); // Turn debug message on/off + bool debug(); // is debug on or off? + + bool open(const char* /*GET/POST*/, const char* URL); // Initiate a request + void onReadyStateChange(readyStateChangeCB, void* arg = 0); // Optional event handler for ready state change + // or you can simply poll readyState() + void setTimeout(int); // overide default timeout (seconds) + + void setReqHeader(const char* name, const char* value); // add a request header + void setReqHeader(const char* name, const __FlashStringHelper* value); + void setReqHeader(const __FlashStringHelper *name, const char* value); + void setReqHeader(const __FlashStringHelper *name, const __FlashStringHelper* value); + + void setReqHeader(const char* name, int32_t value); // overload to use integer value + void setReqHeader(const __FlashStringHelper *name, int32_t value); + + bool send(); // Send the request (GET) + bool send(String body); // Send the request (POST) + bool send(const char* body); // Send the request (POST) + bool send(const uint8_t* buffer, size_t len); // Send the request (POST) (binary data?) + bool send(xbuf* body, size_t len); // Send the request (POST) data in an xbuf + void abort(); // Abort the current operation + + int readyState(); // Return the ready state + + int respHeaderCount(); // Retrieve count of response headers + char* respHeaderName(int index); // Return header name by index + char* respHeaderValue(int index); // Return header value by index + char* respHeaderValue(const char* name); // Return header value by name + char* respHeaderValue(const __FlashStringHelper *name); + bool respHeaderExists(const char* name); // Does header exist by name? + bool respHeaderExists(const __FlashStringHelper *name); + String headers(); // Return all headers as String + + void onData(onDataCB, void* arg = 0); // Notify when min data is available + size_t available(); // response available + size_t responseLength(); // indicated response length or sum of chunks to date + int responseHTTPcode(); // HTTP response code or (negative) error code + String responseText(); // response (whole* or partial* as string) + size_t responseRead(uint8_t* buffer, size_t len); // Read response into buffer + uint32_t elapsedTime(); // Elapsed time of in progress transaction or last completed (ms) + String version(); // Version of asyncHTTPrequest +//___________________________________________________________________________________________________________________________________ + + private: + + enum {HTTPmethodGET, HTTPmethodPOST} _HTTPmethod; + + enum readyStates { + readyStateUnsent = 0, // Client created, open not yet called + readyStateOpened = 1, // open() has been called, connected + readyStateHdrsRecvd = 2, // send() called, response headers available + readyStateLoading = 3, // receiving, partial data available + readyStateDone = 4} _readyState; // Request complete, all data available. + + int16_t _HTTPcode; // HTTP response code or (negative) exception code + bool _chunked; // Processing chunked response + bool _debug; // Debug state + uint32_t _timeout; // Default or user overide RxTimeout in seconds + uint32_t _lastActivity; // Time of last activity + uint32_t _requestStartTime; // Time last open() issued + uint32_t _requestEndTime; // Time of last disconnect + URL* _URL; // -> URL data structure + char* _connectedHost; // Host when connected + int _connectedPort; // Port when connected + AsyncClient* _client; // ESPAsyncTCP AsyncClient instance + size_t _contentLength; // content-length header value or sum of chunk headers + size_t _contentRead; // number of bytes retrieved by user since last open() + readyStateChangeCB _readyStateChangeCB; // optional callback for readyState change + void* _readyStateChangeCBarg; // associated user argument + onDataCB _onDataCB; // optional callback when data received + void* _onDataCBarg; // associated user argument + + // request and response String buffers and header list (same queue for request and response). + + xbuf* _request; // Tx data buffer + xbuf* _response; // Rx data buffer for headers + xbuf* _chunks; // First stage for chunked response + header* _headers; // request or (readyState > readyStateHdrsRcvd) response headers + + // Protected functions + + header* _addHeader(const char*, const char*); + header* _getHeader(const char*); + header* _getHeader(int); + bool _buildRequest(); + bool _parseURL(const char*); + bool _parseURL(String); + void _processChunks(); + bool _connect(); + size_t _send(); + void _setReadyState(readyStates); + char* _charstar(const __FlashStringHelper *str); + + // callbacks + + void _onConnect(AsyncClient*); + void _onDisconnect(AsyncClient*); + void _onData(void*, size_t); + void _onError(AsyncClient*, int8_t); + void _onPoll(AsyncClient*); + bool _collectHeaders(); +}; +#endif \ No newline at end of file diff --git a/src/conflictStrategy options.txt b/src/conflictStrategy options.txt new file mode 100644 index 00000000..93cfde40 --- /dev/null +++ b/src/conflictStrategy options.txt @@ -0,0 +1,7 @@ +x honest Debounced(uint8_t p,uint8_t mode,H4GM_SENSE sense,uint32_t dbTimeMs,H4GM_FN_EVENT callback);// +X constrain(v,0,1) Encoder(uint8_t pA,uint8_t pB,uint8_t mode,H4GM_SENSE sense,H4GM_FN_EVENT); +X toggle Latching(uint8_t p,uint8_t mode,H4GM_SENSE sense,uint32_t dbTimeMs,H4GM_FN_EVENT callback);// +X honest Polled(uint8_t p,uint8_t mode,H4GM_SENSE sense,uint32_t frequency,uint32_t isAnalog,H4GM_FN_EVENT callback);// +X honest Raw(uint8_t p,uint8_t mode,H4GM_SENSE sense,H4GM_FN_EVENT callback);// +x honest Retriggering(uint8_t _p, uint8_t _mode,H4GM_SENSE sense,uint32_t timeout, H4GM_FN_EVENT _callback); +// diff --git a/src/xbuf.cpp b/src/xbuf.cpp new file mode 100644 index 00000000..634cf648 --- /dev/null +++ b/src/xbuf.cpp @@ -0,0 +1,269 @@ +#include +#ifndef H4P_NO_WIFI +#include + +xbuf::xbuf(const uint16_t segSize) + : _head(nullptr) + , _tail(nullptr) + , _used(0) + , _free(0) + , _offset(0) { + _segSize = (segSize + 3) & -4;//((segSize + 3) >> 2) << 2; +} + +//******************************************************************************************************************* +xbuf::~xbuf(){ + flush(); +} + +//******************************************************************************************************************* +size_t xbuf::write(const uint8_t byte){ + return write((uint8_t*) &byte, 1); +} + +//******************************************************************************************************************* +size_t xbuf::write(const char* buf){ + return write((uint8_t*)buf, strlen(buf)); +} + +//******************************************************************************************************************* +size_t xbuf::write(String string){ + return write((uint8_t*)string.c_str(), string.length()); +} + +//******************************************************************************************************************* +size_t xbuf::write(const uint8_t* buf, const size_t len){ + Serial.printf(" xbuf::write %s %d\n",buf,len); + size_t supply = len; + while(supply){ + if(!_free){ + addSeg(); + } + size_t demand = _free < supply ? _free : supply; + memcpy(_tail->data + ((_offset + _used) % _segSize), buf + (len - supply), demand); + _free -= demand; + _used += demand; + supply -= demand; + } + return len; +} + +//******************************************************************************************************************* +size_t xbuf::write(xbuf* buf, const size_t len){ + size_t supply = len; + if(supply > buf->available()){ + supply = buf->available(); + } + size_t read = 0; + while(supply){ + if(!_free){ + addSeg(); + } + size_t demand = _free < supply ? _free : supply; + read += buf->read(_tail->data + ((_offset + _used) % _segSize), demand); + _free -= demand; + _used += demand; + supply -= demand; + } + return read; +} + +//******************************************************************************************************************* +uint8_t xbuf::read(){ + uint8_t byte = 0; + read((uint8_t*) &byte, 1); + return byte; +} + +//******************************************************************************************************************* +uint8_t xbuf::peek(){ + uint8_t byte = 0; + peek((uint8_t*) &byte, 1); + return byte; +} + +//******************************************************************************************************************* +size_t xbuf::read(uint8_t* buf, const size_t len){ + size_t read = 0; + while(read < len && _used){ + size_t supply = (_offset + _used) > _segSize ? _segSize - _offset : _used; + size_t demand = len - read; + size_t chunk = supply < demand ? supply : demand; + memcpy(buf + read, _head->data + _offset, chunk); + _offset += chunk; + _used -= chunk; + read += chunk; + if(_offset == _segSize){ + remSeg(); + _offset = 0; + } + } + if( ! _used){ + flush(); + } + return read; + +} + +//******************************************************************************************************************* +size_t xbuf::peek(uint8_t* buf, const size_t len){ + size_t read = 0; + xseg* seg = _head; + size_t offset = _offset; + size_t used = _used; + while(read < len && used){ + size_t supply = (offset + used) > _segSize ? _segSize - offset : used; + size_t demand = len - read; + size_t chunk = supply < demand ? supply : demand; + memcpy(buf + read, seg->data + offset, chunk); + offset += chunk; + used -= chunk; + read += chunk; + if(offset == _segSize){ + seg = seg->next; + offset = 0; + } + } + return read; +} + +//******************************************************************************************************************* +size_t xbuf::available(){ + return _used; +} + +//******************************************************************************************************************* +int xbuf::indexOf(const char target, const size_t begin){ + char targetstr[2] = " "; + targetstr[0] = target; + return indexOf(targetstr, begin); +} + +//******************************************************************************************************************* +int xbuf::indexOf(const char* target, const size_t begin){ + size_t targetLen = strlen(target); + if(targetLen > _segSize || targetLen > _used) return -1; + size_t searchPos = _offset + begin; + size_t searchEnd = _offset + _used - targetLen; + if(searchPos > searchEnd) return -1; + size_t searchSeg = searchPos / _segSize; + xseg* seg = _head; + while(searchSeg){ + seg = seg->next; + searchSeg --; + } + size_t segPos = searchPos % _segSize; + while(searchPos <= searchEnd){ + size_t compLen = targetLen; + if(compLen <= (_segSize - segPos)){ + if(memcmp(target,seg->data+segPos,compLen) == 0){ + return searchPos - _offset; + } + } + else { + size_t compLen = _segSize - segPos; + if(memcmp(target,seg->data+segPos,compLen) == 0){ + compLen = targetLen - compLen; + if(memcmp(target+targetLen-compLen, seg->next->data, compLen) == 0){ + return searchPos - _offset; + } + } + } + searchPos++; + segPos++; + if(segPos == _segSize){ + seg = seg->next; + segPos = 0; + } + } + return -1; +} + +//******************************************************************************************************************* +String xbuf::readStringUntil(const char target){ + return readString(indexOf(target)+1); +} + +//******************************************************************************************************************* +String xbuf::readStringUntil(const char* target){ + int index = indexOf(target); + if(index < 0) return String(); + return readString(index + strlen(target)); +} + +//******************************************************************************************************************* +String xbuf::readString(int endPos){ + String result; + if( ! result.reserve(endPos+1)){ + return result; + } + if(endPos > _used){ + endPos = _used; + } + if(endPos > 0 && result.reserve(endPos+1)){ + while(endPos--){ + result += (char)_head->data[_offset++]; + _used--; + if(_offset >= _segSize){ + remSeg(); + } + } + } + return result; +} + +//******************************************************************************************************************* +String xbuf::peekString(int endPos){ + String result; + xseg* seg = _head; + size_t offset = _offset; + if(endPos > _used){ + endPos = _used; + } + if(endPos > 0 && result.reserve(endPos+1)){ + while(endPos--){ + result += (char)seg->data[offset++]; + if( offset >= _segSize){ + seg = seg->next; + offset = 0; + } + } + } + return result; +} + +//******************************************************************************************************************* +void xbuf::flush(){ + while(_head) remSeg(); + _tail = nullptr; + _offset = 0; + _used = 0; + _free = 0; +} + +//******************************************************************************************************************* +void xbuf::addSeg(){ + if(_tail){ + _tail->next = (xseg*) new uint32_t[_segSize / 4 + 1]; + _tail = _tail->next; + } + else { + _tail = _head = (xseg*) new uint32_t[_segSize / 4 + 1]; + } + _tail->next = nullptr; + _free += _segSize; +} + +//******************************************************************************************************************* +void xbuf::remSeg(){ + if(_head){ + xseg *next = _head->next; + delete[] (uint32_t*) _head; + _head = next; + if( ! _head){ + _tail = nullptr; + } + } + _offset = 0; +} +#endif diff --git a/src/xbuf.h b/src/xbuf.h new file mode 100644 index 00000000..940f4d96 --- /dev/null +++ b/src/xbuf.h @@ -0,0 +1,116 @@ +#pragma once +/*********************************************************************************** + Copyright (C) <2018> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + ************************** end of license section **************************** + + xbuf is a dynamic buffering system that supports reading and writing much like cbuf. + The class has it's own provision for writing from buffers, Strings and other xbufs + as well as the inherited Print functions. + Rather than use a large contiguous heap allocation, xbuf uses a linked chain of segments + to dynamically grow and shrink with the contents. + There are other benefits as well to using smaller heap allocation units: + 1) A buffer can work fine in a fragmented heap environment (admittedly contributing to it) + 2) xbuf contents can be copied from one buffer to another without the need for + 2x heap during the copy. + The segment size defaults to 64 but can be dynamically set in the constructor at creation. + The inclusion of indexOf and read/peek until functions make it useful for handling + data streams like HTTP, and in fact is why it was created. + + NOTE: The size of the indexOf() search string is limited to the segment size. + It could be extended but didn't seem to be a practical consideration. + +***********************************************************************************/ +#include + +struct xseg { + xseg *next; + uint8_t data[]; +}; + +class xbuf: public Print { + public: + + xbuf(const uint16_t segSize=64); + virtual ~xbuf(); + + size_t write(const uint8_t); + size_t write(const char*); + size_t write(const uint8_t*, const size_t); + size_t write(xbuf*, const size_t); + size_t write(String); + size_t available(); + int indexOf(const char, const size_t begin=0); + int indexOf(const char*, const size_t begin=0); + uint8_t read(); + size_t read(uint8_t*, size_t); + String readStringUntil(const char); + String readStringUntil(const char*); + String readString(int); + String readString(){return readString(available());} + void flush(); + + uint8_t peek(); + size_t peek(uint8_t*, const size_t); + String peekStringUntil(const char target) {return peekString(indexOf(target, 0));} + String peekStringUntil(const char* target) {return peekString(indexOf(target, 0));} + String peekString() {return peekString(_used);} + String peekString(int); + +/* In addition to the above functions, + the following inherited functions from the Print class are available. + + size_t printf(const char * format, ...) __attribute__ ((format (printf, 2, 3))); + size_t printf_P(PGM_P format, ...) __attribute__((format(printf, 2, 3))); + size_t print(const __FlashStringHelper *); + size_t print(const String &); + size_t print(const char[]); + size_t print(char); + size_t print(unsigned char, int = DEC); + size_t print(int, int = DEC); + size_t print(unsigned int, int = DEC); + size_t print(long, int = DEC); + size_t print(unsigned long, int = DEC); + size_t print(double, int = 2); + size_t print(const Printable&); + + size_t println(const __FlashStringHelper *); + size_t println(const String &s); + size_t println(const char[]); + size_t println(char); + size_t println(unsigned char, int = DEC); + size_t println(int, int = DEC); + size_t println(unsigned int, int = DEC); + size_t println(long, int = DEC); + size_t println(unsigned long, int = DEC); + size_t println(double, int = 2); + size_t println(const Printable&); + size_t println(void); +*/ + + protected: + + xseg *_head; + xseg *_tail; + uint16_t _used; + uint16_t _free; + uint16_t _offset; + uint16_t _segSize; + + void addSeg(); + void remSeg(); + +}; \ No newline at end of file