diff --git a/docs/source/Plugin/P169.rst b/docs/source/Plugin/P169.rst new file mode 100644 index 0000000000..4d0b9bfee3 --- /dev/null +++ b/docs/source/Plugin/P169.rst @@ -0,0 +1,362 @@ +.. include:: ../Plugin/_plugin_substitutions_p16x.repl +.. _P169_page: + +|P169_typename| +================================================== + +|P169_shortinfo| + +Plugin details +-------------- + +Type: |P169_type| + +Name: |P169_name| + +Status: |P169_status| + +GitHub: |P169_github|_ + +Maintainer: |P169_maintainer| + +Used libraries: |P169_usedlibraries| + +Description +----------- + +The AS3935 is a programmable fully integrated Lightning Sensor IC that detects the presence and approach of potentially +hazardous lightning activity in the vicinity and provides an estimation on the distance to the head of the storm. +The embedded lightning algorithm checks the incoming signal pattern to reject the potential man-made disturbers. + +Highlights: + +* Can detect lightning storm activity within a 40 km range. +* Provides distance estimation to the head of the storm. +* Detects both cloud-to-ground and intra-cloud (cloud-to-cloud) flashes. +* Internal algorithm to reject false disturbances. + + + +This chip can be found on a number of boards, like this one from DFRobot. + +.. image:: P169_dfrobot-gravity-lightning-distance-sensor-as3935-600x600.jpg + +(Image (c) DFRobot) + + +Calibration Procedure +--------------------- + +The AS3935 sensor is using 3 separate oscillators: + +* **LCO**: 500 kHz resonance frequency used to tune the antenna. +* **SRCO**: Typ. ~1.1 MHz signal used internally in the sensor. +* **TRCO**: Typ. 32768 Hz signal used internally in the sensor. + +The LCO oscillator needs to be calibrated within 3.5% of its intended 500 kHz. +For this the sensor can connect upto 15 tuning capacitors of 8 pF parallel to another capacitor to tune the resonance frequency of the antenna to 500 kHz. + +The frequency of all of these three oscillators depends on environmental factors like temperature, but also the presence of other materials close to the antenna. + +The LCO calibration is performed by feeding the LCO clock via some divisor to a GPIO pin on the ESP board. +This signal is then measured several times to find the best tuning capacitor. + +By default this LCO calibration does a quick test with antenna capacitor ``0`` and ``15`` and then computes the most likely capacitor. +This candidate and its neighbors are then measured for a longer period (about 30 msec) to reduce the error in measurement. + +When the checkbox for "Slow LCO Calibration" is checked, each antenna capacitor is tested for about 30 msec. +This may improve the accuracy and the success rate of the calibration. + +Below the calibration charts of a quick and slow LCO calibration of the same sensor: + +.. image:: P169_Quick_LCO_calibration_curve.png + :width: 500 + :alt: Quick LCO Calibration Curve + +.. image:: P169_Slow_LCO_calibration_curve.png + :width: 500 + :alt: Slow LCO Calibration Curve + +As can be seen, both were successful in calibrating the resonance frequency within 3.5% of 500 kHz. + +However, the best one on this specific board and setup is antenna capacitor 15, which is the last one. +So in this specific setup, it is very well possible the calibration may fail when some external factor changes. (e.g. temperature) + +For setups like these, where the best option is close to the edge of the adjustable range, it is best to check the checkbox "Tolerate out-of-range calibration". +This way the calibration will not be considered failed when the tolerance ends up slightly above 3.5%. + +On the other hand, if the best calibration is significantly further off from the optimal 500 kHz, there is something wrong with the setup. + +For example: + +* Metal parts mounted close to the antenna. +* Noisy environment. (See also the reported noise level, as this should be around 2 or 3) +* Unstable power supplied to the sensor. + +See the Wiring section below for more tips. + + +.. note:: The **SRCO** and **TRCO** frequencies are calibrated after the **LCO** frequency. When the **LCO** frequency is off by too much, the calibration of the other two may also fail. + + +Sensor Operating Modes +---------------------- + +The sensor can signal some event via the IRQ pin to ESPEasy. +This pin state remains high until the sensor state is read. + +Power-Down Mode +^^^^^^^^^^^^^^^ + +This mode is set when ESPEasy enters deep sleep to reduce current consumption +(typ 1μA). + +Listening Mode +^^^^^^^^^^^^^^ + +The sensor will be operating in this mode for most of the time. +Typical current consumption in this mode is about 60μA. (70μA when the internal voltage regulator is enabled) + +There will always be some noise picked up by the antenna. +When this noise exceeds the set noise floor, the sensor will pull the IRQ pin high and return to "Listening mode". + +ESPEasy will then try to increase the noise floor. + +After 15 seconds of not receiving any interrupt signal, ESPEasy will try to lower the noise floor. + +Every time the set watchdog threshold is passed, the sensor will enter "Signal Validation" mode. + + +Signal Validation Mode +^^^^^^^^^^^^^^^^^^^^^^ + +Typical current consumption in this mode is about 350μA. + +In case the incoming signal does not have the shape characteristic to lightning, the signal validation fails and the +event is classified as disturber. + +If the signal is classified as disturber the chip immediately aborts the signal processing and goes back into the "Listening Mode". +Otherwise, the energy calculation is performed and the distance estimate provided. + +.. note:: The calculated energy does not reflect any physical unit of measure. It is just a number. However it seems the sensor does use it internally to estimate the distance. + +The received signal of a typical lightning strike consists of 3 .. 4 pulses about 40 msec apart. + +According to the datasheet, the sensor needs roughly 1 second to classify an event as a lightning strike. +However tests have shown the sensor might be able to classify _some_ lightning strikes in as little as 250 msec. + +If the classification takes longer than 1.5 seconds, it will be classified as a disturber. + +This implies the practial shortest time span between two lightning strikes that the sensor can resolve is approximately one second. + +.. note:: Sometimes during intense thunderstorms there might be several lightning strikes in short succession. This sensor might classify those as disturbances as the total evaluation time might exceed 1.5 seconds. + +As soon as the event has been classified, the sensor will return to "Listening Mode". + + +Signal Validation Parameters +---------------------------- + +During the signal validation phase the shape of the incoming signal is analyzed. +The sensor can differentiate between signals that show the pattern characteristic of lightning strikes and man-made disturbers such as random impulses. + +Noise Floor +^^^^^^^^^^^ + +Range: ``0`` .. ``7`` (default: ``2``) + +The noise floor acts as a threshold to differentiate real signals from noise. + +When an interrupt signal for "noise floor threshold exceeded" is received, ESPEasy will increase the noise floor. + + +Watchdog +^^^^^^^^ + +Range: ``0`` .. ``15`` (default: ``2``) + +This sets the duration for a signal to last before entering "Signal Validation" mode. + +Spike Rejection +^^^^^^^^^^^^^^^ + +Range: ``0`` .. ``15`` (default: ``2``) + +Can be used to increase the robustness against false alarms from such disturbers. +Larger values correspond to more robust disturber rejection, yet with the +drawback of a decrease in detection efficiency. + +When an "disturber detected" event is triggered, ESPEasy will try to increase either the Spike Rejection setting or Watchdog. + + +AFE gain and Energy level +^^^^^^^^^^^^^^^^^^^^^^^^^ + +The AS3935 sensor does have an amplifier with an adjustable gain factor which amplifies the incoming signal. +This amplified signal is then used to estimate the "Energy" value. + +Tests have shown the AS3935 sensor does use a lookup table to estimate the distance based on this energy level. + + + +Dynamic Adjustment +^^^^^^^^^^^^^^^^^^ + +ESPEasy does dynamically change these three settings. +Upon interrupt signals for either noise floor exceeded or disturbance detected, these will be increased. + +After 15 seconds ESPEasy will try to lower these values again to get as close as possible to the optimal settings. + + +Lightning Threshold +^^^^^^^^^^^^^^^^^^^ + +Values: ``1``, ``5``, ``9``, ``16`` (default: ``1``) + +This set minimum number of lightning events counted within 15 minutes must occur before the first "Lightning detected" interrupt is sent. +Once this threshold is passed, the sensor will resume its normal interrupt handling with an interrupt per detected lightning. + +This internal count will also be cleared when the internal statistics are cleared or when the sensor is put to sleep. (when the ESPEasy task is ended, the sensor is put to sleep) + + +Wiring +------ + +This sensor can be used via SPI or I2C. + +For ESPEasy it needs to be wired for I2C: + +* ``SI`` pin ("Select Interface") must be pulled high. +* ``MOSI`` pin is I2C SDA. +* ``SCL`` pin is I2C SCL. +* ``A1`` and ``A0`` pulled high for the default address of ``0x03``. + +.. image:: P169_Guideline_I2C_PullUp_resistors.png + :width: 700 + :alt: Guideline for Pull Up Resistors on I2CL and I2CD + + +Apart from the I2C connection, there is an ``IRQ`` signal which must also be connected to the ESP board. +This IRQ signal is used to calibrate the sensor as well as to signal the ESP about some changed condition like a detected lightning strike or noise disturbances. + +Board Specific +^^^^^^^^^^^^^^ + +Some boards already have resistors present to pull the unused pins high or low where needed and are default set for I2C. +For example 8-pin brown or purple boards with ``GY-AS3935`` written on the back is default configured for I2C with address ``0x03``. + +Other boards like a slightly larger 11-pin purple board with ``WCMCU-3935`` written on the board, +might need to have the unused pins (``CS`` & ``MISO``) explicitly pulled to GND. +The ``EN-V`` pin should be pulled high. + + +Power Supply and Noise +^^^^^^^^^^^^^^^^^^^^^^ + +This sensor is quite sensitive to noise in its direct surroundings. +Especially signals around 500 kHz will cause this sensor to perform significantly worse. + +This sensor needs to have its resonance frequency calibrated within 3.5% of 500 kHz. + +Some tips: + +* Keep DC/DC converters and other power supplies away from this sensor. +* Keep smart phone and smart watch displays away from the sensor. +* Add some 100 uF ... 220 uF capacitor close to the power supply pins of the sensor. +* Do not use any metal close to the antenna of the sensor as this will change the antenna resonance frequency. +* Use short wires. +* Lower I2C clock frequency to 100 kHz for all I2C devices on this ESPEasy board. +* Lower I2C clock frequency on devices within a few meters from this sensor. (Explicitly stay away from 500 kHz) +* Do not leave unused pins 'floating'. Either pull them to 3V3 or GND. +* Orientation of the antenna is not really important to measure lightning strikes, since lightning is not discharging straight, but in a zigzag pattern and the distance is very far away. However the orientation of the antenna can have an effect on the noise picked up from nearby sources. + +The sensor chip does have an internal voltage regulator. + +On some boards, this regulator can be enabled by pulling the ``EN-V`` or ``EN_VREG`` pin high (if made availabe on the board). + +Onboard voltage regulator: + +* Enabled: ``EN_VREG`` pin high, ``VREG`` via 1uF to GND. Supply voltage range is 2.4V to 5.5V +* Disabled: ``EN_VREG`` pin low, ``VREG`` connected to VDD pin. Supply voltage range is 2.4V to 3.6V + +With the onboard voltage regulator enabled, the current consumption will be slight higher. +But the supplied voltage to the sensor will be more stable. + +See the `datasheet `_ pages 15 and 16 for more information. + +.. image:: P169_VoltageRegulator_OFF.png + :width: 500 + :alt: AS3935 Application Diagram (Voltage Regulator OFF, I²C Active) + +.. image:: P169_VoltageRegulator_ON.png + :width: 500 + :alt: AS3935 Application Diagram (Voltage Regulator ON, I²C Active) + + +Configuration +------------- + +.. image:: P169_DeviceConfiguration.png + +* **Name**: Required by ESPEasy, must be unique among the list of available devices/tasks. + +* **Enabled**: The device can be disabled or enabled. When not enabled the device should not use any resources. + +I2C options +^^^^^^^^^^^ + +* **I2C Address**: The device supports 83 addresses, and by default comes configured for address ``0x03``. + +Available addresses are in the range ``0x01`` to ``0x03``. + +The available I2C settings here depend on the build used. At least the **Force Slow I2C speed** option is available, but selections for the I2C Multiplexer can also be shown. For details see the :ref:`Hardware_page` + +Device Settings +^^^^^^^^^^^^^^^ + +* **IRQ**: Configure the GPIO pin on the ESP board connected to the IRQ pin of the sensor. + This pin is used for both the antenna calibration as well as to notify the ESP board of any lightning strike or detected disturbance. +* **Lightning Threshold**: Minimum number of detected strikes in 15 minutes to let the sensor trigger the IRQ pin. +* **Mode**: Set the Analog Front-End (AFE) gain for typical indoor/outdoor use case. +* **Ignore Disturbance**: The sensor may trigger the IRQ pin to signal a lightning strike, high noise or detected disturbances. With "Ignore Disturbance" checked, this last one is ignored. + + + + +Current Sensor Data +^^^^^^^^^^^^^^^^^^^ + + +Data Acquisition +^^^^^^^^^^^^^^^^ + +This group of settings, **Single event with all values** and **Send to Controller** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured. + +* **Interval** By default, Interval will be set to 0 sec. The data will be collected and optionally sent to any configured controllers using this interval. When an output value is changed, the data will be sent to any configured controller, and an event will also be generated when the Rules are enabled (Tools/Advanced). + +Values +^^^^^^ + + +In selected builds, per Value is a **Stats** checkbox available, that when checked, gathers the data and presents recent data in a graph, as described here: :ref:`Task Value Statistics: ` + +Commands available +^^^^^^^^^^^^^^^^^^ + +.. include:: P169_commands.repl + +Get Config Values +^^^^^^^^^^^^^^^^^ + + +.. include:: P169_config_values.repl + +Change log +---------- + +.. versionchanged:: 2.0 + ... + + |added| + 2024-05-24 Initial release version. + diff --git a/docs/source/Plugin/P169_DeviceConfiguration.png b/docs/source/Plugin/P169_DeviceConfiguration.png new file mode 100644 index 0000000000..3f8594c8fc Binary files /dev/null and b/docs/source/Plugin/P169_DeviceConfiguration.png differ diff --git a/docs/source/Plugin/P169_Guideline_I2C_PullUp_resistors.png b/docs/source/Plugin/P169_Guideline_I2C_PullUp_resistors.png new file mode 100644 index 0000000000..5a86da14a9 Binary files /dev/null and b/docs/source/Plugin/P169_Guideline_I2C_PullUp_resistors.png differ diff --git a/docs/source/Plugin/P169_Quick_LCO_calibration_curve.png b/docs/source/Plugin/P169_Quick_LCO_calibration_curve.png new file mode 100644 index 0000000000..ce7dd45ba9 Binary files /dev/null and b/docs/source/Plugin/P169_Quick_LCO_calibration_curve.png differ diff --git a/docs/source/Plugin/P169_Slow_LCO_calibration_curve.png b/docs/source/Plugin/P169_Slow_LCO_calibration_curve.png new file mode 100644 index 0000000000..dbb2003658 Binary files /dev/null and b/docs/source/Plugin/P169_Slow_LCO_calibration_curve.png differ diff --git a/docs/source/Plugin/P169_Stats_no_lightning_strikes.png b/docs/source/Plugin/P169_Stats_no_lightning_strikes.png new file mode 100644 index 0000000000..14efb88758 Binary files /dev/null and b/docs/source/Plugin/P169_Stats_no_lightning_strikes.png differ diff --git a/docs/source/Plugin/P169_VoltageRegulator_OFF.png b/docs/source/Plugin/P169_VoltageRegulator_OFF.png new file mode 100644 index 0000000000..528158381d Binary files /dev/null and b/docs/source/Plugin/P169_VoltageRegulator_OFF.png differ diff --git a/docs/source/Plugin/P169_VoltageRegulator_ON.png b/docs/source/Plugin/P169_VoltageRegulator_ON.png new file mode 100644 index 0000000000..38c6c78f3a Binary files /dev/null and b/docs/source/Plugin/P169_VoltageRegulator_ON.png differ diff --git a/docs/source/Plugin/P169_commands.repl b/docs/source/Plugin/P169_commands.repl new file mode 100644 index 0000000000..3c28b5e654 --- /dev/null +++ b/docs/source/Plugin/P169_commands.repl @@ -0,0 +1,37 @@ +.. csv-table:: + :header: "Command Syntax", "Extra information" + :widths: 20, 30 + + " + ``as3935,clearstats`` + "," + Clear statistics in the sensor, like lightning strike counts and intermediate values used to estimate the distance of the storm front. + " + " + ``as3935,calibrate`` + "," + Perform calibration of all oscillators in the sensor. + " + " + ``as3935,setgain,`` + "," + Set the AFE gain to given value. + Input can either be the internal sensor register value of ``10`` ... ``18`` or a floating point value signifying the gain factor. + This latter one will then be matched to the closest matching internal register value. + The floating point factor values range from ``0.30x`` ... ``3.34x`` . + " + " + ``as3935,setnf,`` + "," + Set the Noise Floor threshold to given value. + " + " + ``as3935,setwd,`` + "," + Set the Watchdog threshold to given value. + " + " + ``as3935,setsrej,`` + "," + Set Spike Rejection threshold to given value. + " diff --git a/docs/source/Plugin/P169_config_values.repl b/docs/source/Plugin/P169_config_values.repl new file mode 100644 index 0000000000..2f7f01749b --- /dev/null +++ b/docs/source/Plugin/P169_config_values.repl @@ -0,0 +1,25 @@ +.. csv-table:: + :header: "Config value", "Information" + :widths: 20, 30 + + " + ``[#noisefloor]`` + "," + Returns the current set Noise Floor threshold of the sensor. + " + " + ``[#watchdog]`` + "," + Returns the current set Watchdog threshold of the sensor. + " + " + ``[#srej]`` + "," + Returns the current set Spike Rejection threshold of the sensor. + " + " + ``[#gain]`` + "," + Returns the current active AFE gain factor of the sensor. (``0.30x`` ... ``3.34x``) + " + diff --git a/docs/source/Plugin/P169_dfrobot-gravity-lightning-distance-sensor-as3935-600x600.jpg b/docs/source/Plugin/P169_dfrobot-gravity-lightning-distance-sensor-as3935-600x600.jpg new file mode 100644 index 0000000000..477f83cbe1 Binary files /dev/null and b/docs/source/Plugin/P169_dfrobot-gravity-lightning-distance-sensor-as3935-600x600.jpg differ diff --git a/docs/source/Plugin/_Plugin.rst b/docs/source/Plugin/_Plugin.rst index 6778608dd0..73dead336b 100644 --- a/docs/source/Plugin/_Plugin.rst +++ b/docs/source/Plugin/_Plugin.rst @@ -392,6 +392,7 @@ There are different released versions of ESP Easy: ":ref:`P166_page`","|P166_status|","P166" ":ref:`P167_page`","|P167_status|","P167" ":ref:`P168_page`","|P168_status|","P168" + ":ref:`P169_page`","|P169_status|","P169" ":ref:`P170_page`","|P170_status|","P170" diff --git a/docs/source/Plugin/_plugin_categories.repl b/docs/source/Plugin/_plugin_categories.repl index c491645ee7..cf7d3e5f8f 100644 --- a/docs/source/Plugin/_plugin_categories.repl +++ b/docs/source/Plugin/_plugin_categories.repl @@ -8,7 +8,7 @@ .. |Plugin_Energy_AC| replace:: :ref:`P076_page`, :ref:`P077_page`, :ref:`P078_page`, :ref:`P102_page`, :ref:`P108_page` .. |Plugin_Energy_DC| replace:: :ref:`P027_page`, :ref:`P085_page`, :ref:`P115_page`, :ref:`P132_page` .. |Plugin_Energy_Heat| replace:: :ref:`P088_page`, :ref:`P093_page` -.. |Plugin_Environment| replace:: :ref:`P004_page`, :ref:`P005_page`, :ref:`P006_page`, :ref:`P014_page`, :ref:`P024_page`, :ref:`P028_page`, :ref:`P030_page`, :ref:`P031_page`, :ref:`P032_page`, :ref:`P034_page`, :ref:`P039_page`, :ref:`P047_page`, :ref:`P051_page`, :ref:`P068_page`, :ref:`P069_page`, :ref:`P072_page`, :ref:`P103_page`, :ref:`P105_page`, :ref:`P106_page`, :ref:`P122_page`, :ref:`P150_page`, :ref:`P151_page`, :ref:`P153_page`, :ref:`P154_page`, :ref:`P167_page` +.. |Plugin_Environment| replace:: :ref:`P004_page`, :ref:`P005_page`, :ref:`P006_page`, :ref:`P014_page`, :ref:`P024_page`, :ref:`P028_page`, :ref:`P030_page`, :ref:`P031_page`, :ref:`P032_page`, :ref:`P034_page`, :ref:`P039_page`, :ref:`P047_page`, :ref:`P051_page`, :ref:`P068_page`, :ref:`P069_page`, :ref:`P072_page`, :ref:`P103_page`, :ref:`P105_page`, :ref:`P106_page`, :ref:`P122_page`, :ref:`P150_page`, :ref:`P151_page`, :ref:`P153_page`, :ref:`P154_page`, :ref:`P167_page`, :ref:`P169_page` .. |Plugin_Extra_IO| replace:: :ref:`P011_page`, :ref:`P022_page` .. |Plugin_Gases| replace:: :ref:`P049_page`, :ref:`P052_page`, :ref:`P083_page`, :ref:`P090_page`, :ref:`P117_page`, :ref:`P127_page`, :ref:`P135_page`, :ref:`P145_page`, :ref:`P147_page`, :ref:`P164_page` .. |Plugin_Generic| replace:: :ref:`P003_page`, :ref:`P026_page`, :ref:`P033_page`, :ref:`P037_page`, :ref:`P081_page`, :ref:`P100_page`, :ref:`P146_page` diff --git a/docs/source/Plugin/_plugin_sets_overview.repl b/docs/source/Plugin/_plugin_sets_overview.repl index 9bc869c87f..2b2f929133 100644 --- a/docs/source/Plugin/_plugin_sets_overview.repl +++ b/docs/source/Plugin/_plugin_sets_overview.repl @@ -2391,6 +2391,7 @@ Build set: :yellow:`CLIMATE` ":ref:`P154_page`","P154" ":ref:`P164_page`","P164" ":ref:`P167_page`","P167" + ":ref:`P169_page`","P169" ":ref:`C001_page`","C001" ":ref:`C002_page`","C002" ":ref:`C003_page`","C003" @@ -2963,6 +2964,7 @@ Build set: :yellow:`MAX` ":ref:`P164_page`","P164" ":ref:`P166_page`","P166" ":ref:`P167_page`","P167" + ":ref:`P169_page`","P169" ":ref:`C001_page`","C001" ":ref:`C002_page`","C002" ":ref:`C003_page`","C003" diff --git a/docs/source/Plugin/_plugin_substitutions_p16x.repl b/docs/source/Plugin/_plugin_substitutions_p16x.repl index 7cf8b8c8c7..4caabb115f 100644 --- a/docs/source/Plugin/_plugin_substitutions_p16x.repl +++ b/docs/source/Plugin/_plugin_substitutions_p16x.repl @@ -61,4 +61,17 @@ .. |P168_shortinfo| replace:: `VEML6030/VEML7700 Light/Lux sensor` .. |P168_maintainer| replace:: `tonhuisman` .. |P168_compileinfo| replace:: `.` -.. |P168_usedlibraries| replace:: `modified version of Adafruit_VEML7700` \ No newline at end of file +.. |P168_usedlibraries| replace:: `modified version of Adafruit_VEML7700` + +.. |P169_name| replace:: :cyan:`AS3935 Lightning Detector` +.. |P169_type| replace:: :cyan:`Environment` +.. |P169_typename| replace:: :cyan:`Environment - AS3935 Lightning Detector` +.. |P169_porttype| replace:: `.` +.. |P169_status| replace:: :yellow:`CLIMATE` +.. |P169_github| replace:: P169_AS3935_LightningDetector.ino +.. _P169_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P169_AS3935_LightningDetector.ino +.. |P169_usedby| replace:: `.` +.. |P169_shortinfo| replace:: `AS3935 Franklin Lightning Sensor IC / Lightning Detector` +.. |P169_maintainer| replace:: `TD-er` +.. |P169_compileinfo| replace:: `.` +.. |P169_usedlibraries| replace:: `https://bitbucket.org/christandlg/as3935mi/src/master/` diff --git a/docs/source/Reference/Command.rst b/docs/source/Reference/Command.rst index aa6439bded..d667bf64f5 100644 --- a/docs/source/Reference/Command.rst +++ b/docs/source/Reference/Command.rst @@ -815,6 +815,11 @@ P167 :ref:`P167_page` .. include:: ../Plugin/P167_commands.repl +P169 :ref:`P169_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P169_commands.repl + .. .. *** Insert regular plugin commands above this remark! *** .. _AdafruitGFX Helper commands: diff --git a/docs/source/Reference/URLs.rst b/docs/source/Reference/URLs.rst index d7e00e7ec9..8c686fe5ea 100644 --- a/docs/source/Reference/URLs.rst +++ b/docs/source/Reference/URLs.rst @@ -38,6 +38,13 @@ At the root of the JSON output is a value names ``TTL`` which reflects the lowes N.B. task nr starts at 1. " + " + ``http:///json?view=sensorupdate&tasknr=1&showpluginstats=1`` + "," + All task values of a specific task nr and needed information to format the values, including all info needed to create ChartJS charts when ``stats`` has been enabled for that task. + + N.B. task nr starts at 1. + " @@ -87,3 +94,4 @@ N.B. task number and variable number do count starting at 0. Control ------- + diff --git a/lib/AS3935MI/README.md b/lib/AS3935MI/README.md new file mode 100644 index 0000000000..05428435cd --- /dev/null +++ b/lib/AS3935MI/README.md @@ -0,0 +1,84 @@ +# Yet Another Arduino ams AS3935 'Franklin' lightning sensor library +home: https://bitbucket.org/christandlg/as3935mi +sensor: https://ams.com/as3935 + +## Features: + - Supports I2C and SPI via the Wire and SPI libraries, respectively + - Supports I2C and SPI interfaces via other libraries (e.g. Software I2C) by inheritance + - Automatic antenna tuning + +## Changelog: +- 1.3.5 + - fixed #50 + - implemented a more robust, interrupt based calibration procedure that is also faster. thanks to @td-er for reporting and impementing this. + - updated increase / decrease function signatures (should be backwards compatible) + - updated examples + +- 1.3.4 + - partially fixed https://bitbucket.org/christandlg/as3935mi/issues/50/resonance-frequency-calibration-inaccurate : fixed a bug where occasionally I2C comms will silently fail during oscillator calibration - thanks to @td-er for reporting and fixing this issue + +- 1.3.3 + - fixed https://bitbucket.org/christandlg/as3935mi/issues/49/class-spiclass-has-no-member-named + +- 1.3.2 + - fixed https://bitbucket.org/christandlg/as3935mi/issues/47/need-help-using-as3935spiclass + +- 1.3.1 + - fixed https://bitbucket.org/christandlg/as3935mi/issues/48/clear-statistics-function-to-be-added + +- 1.3.0 + - fixed https://bitbucket.org/christandlg/as3935mi/issues/12/autocalibrate-no-longer-working + +- 1.2.1 + - Merged PR by Hernán Freschi https://bitbucket.org/christandlg/as3935mi/pull-requests/2 + +- 1.2.0 + - extended examples to include increasing sensitivity if no disturbances are detected. + +- 1.1.1 + - fixed an issue where ESP8266 would crash with message "ISR not in IRAM" + +- 1.1.0 + - extended function calibrateResonanceFrequency() to return the resonance frequency of the antenna + +- 1.0.0 + - added more values for watchdog threshold and spike rejection ratio settings + - fixed an incorrect function name + - added missing function names to keywords.txt + +- 0.5.0 + - added new classes AS3935TwoWire and AS3935SPIClass for TwoWire and SPIClass interfaces + - moved AS3935I2C and AS3935SPI classes into their own respecitve source files, further separating data processing from communications + - when updating from an earlier version and using the AS3935I2C or AS3935SPI classes, change ```#include ``` to ```#include ``` or ```#include ```, respectively + - added examples for AS3935TwoWire and AS3935SPIClass + +- 0.4.1 + - fixed an issue where checkIRQ() causes a deadlock on Arduino Nano + +- 0.4.0 + - added functions to increase / decrease noise floor threshold + - added functions to increase / decrease watchdog threshold + - added functions to increase / decrease spike rejection ratio + - added function to check communication to sensor + - added function to check IRQ pin assignment + +- 0.3.0 + - derived classes must now implement function beginInterface() instead of begin() + +- 0.2.0 + - split code into 3 classes - AS3935MI, AS3935I2C, AS3935SPI + - users can now implement classes derived from AS3935MI easily + - writeRegister is nur used to send direct commands + - updated examples + - added arduino due I2C issue workaround + - minor fixes + - derived + +- 0.1.2 + - renamed library + +- 0.1.1 + - added license information + +- 0.1.0 + - initial release \ No newline at end of file diff --git a/lib/AS3935MI/examples/AS3935MI_LightningDetector_I2C/AS3935MI_LightningDetector_I2C.ino b/lib/AS3935MI/examples/AS3935MI_LightningDetector_I2C/AS3935MI_LightningDetector_I2C.ino new file mode 100644 index 0000000000..9a300fdbf2 --- /dev/null +++ b/lib/AS3935MI/examples/AS3935MI_LightningDetector_I2C/AS3935MI_LightningDetector_I2C.ino @@ -0,0 +1,264 @@ +// AS3935MI_LightningDetector_I2C.ino +// +// shows how to use the AS3935MI library with the lightning sensor connected using I2C. +// +// Copyright (c) 2018-2019 Gregor Christandl +// +// connect the AS3935 to the Arduino like this: +// +// Arduino - AS3935 +// 5V ------ VCC +// GND ----- GND +// D2 ------ IRQ must be a pin supporting external interrupts, e.g. D2 or D3 on an Arduino Uno. +// SDA ----- MOSI +// SCL ----- SCL +// 5V ------ SI (activates I2C for the AS3935) +// 5V ------ A0 (sets the AS3935' I2C address to 0x01) +// GND ----- A1 (sets the AS3935' I2C address to 0x01) +// 5V ------ EN_VREG !IMPORTANT when using 5V Arduinos (Uno, Mega2560, ...) +// other pins can be left unconnected. + +#include +#include + +#include + +#ifdef D1 +#define PIN_IRQ D1 +#else +#define PIN_IRQ 2 +#endif + +//create an AS3935 object using the I2C interface, I2C address 0x01 and IRQ pin number 2 +AS3935I2C as3935(AS3935I2C::AS3935I2C_A01, PIN_IRQ); + +//this value will be set to true by the AS3935 interrupt service routine. +volatile bool interrupt_ = false; + +constexpr uint32_t SENSE_INCREASE_INTERVAL = 15000; //15 s sensitivity increase interval +uint32_t sense_adj_last_ = 0L; //time of last sensitivity adjustment + +void setup() { + // put your setup code here, to run once: + Serial.begin(9600); + + //wait for serial connection to open (only necessary on some boards) + while (!Serial); + + //set the IRQ pin as an input pin. do not use INPUT_PULLUP - the AS3935 will pull the pin + //high if an event is registered. + pinMode(PIN_IRQ, INPUT); + + +#if defined(ESP8266) + Wire.begin(D2, D3); +#else + Wire.begin(); //for Arduino boards +#endif + + //begin() checks the Interface and I2C Address passed to the constructor and resets the AS3935 to + //default values. + if (!as3935.begin()) + { + Serial.println("begin() failed. Check the I2C address passed to the AS3935I2C constructor. "); + while (1); + } + + //check I2C connection. + if (!as3935.checkConnection()) + { + Serial.println("checkConnection() failed. check your I2C connection and I2C Address. "); + while (1); + } + else + Serial.println("I2C connection check passed. "); + + //check the IRQ pin connection. + if (!as3935.checkIRQ()) + { + Serial.println("checkIRQ() failed. check if the correct IRQ pin was passed to the AS3935I2C constructor. "); + while (1); + } + else + Serial.println("IRQ pin connection check passed. "); + + //calibrate the resonance frequency. failing the resonance frequency could indicate an issue + //of the sensor. resonance frequency calibration will take about 1.7 seconds to complete. + uint8_t division_ratio = AS3935MI::AS3935_DR_16; + if (F_CPU < 48000000) //fixes https://bitbucket.org/christandlg/as3935mi/issues/12/autocalibrate-no-longer-working + division_ratio = AS3935MI::AS3935_DR_64; + + int32_t frequency = 0; + if (!as3935.calibrateResonanceFrequency(frequency, division_ratio)) + { + Serial.print("Resonance Frequency Calibration failed: is "); + Serial.print(frequency); + Serial.println(" Hz, should be 482500 Hz - 517500 Hz"); + // while (1); + } + else + { + Serial.println("Resonance Frequency Calibration passed. Resonance Frequency is "); + Serial.print(frequency); + Serial.println(" Hz"); + } + + //calibrate the RCO. + if (!as3935.calibrateRCO()) + { + Serial.println("RCO Calibration failed. "); + while (1); + } + else + Serial.println("RCP Calibration passed. "); + + //set the analog front end to 'indoors' + as3935.writeAFE(AS3935MI::AS3935_INDOORS); + + //set default value for noise floor threshold + as3935.writeNoiseFloorThreshold(AS3935MI::AS3935_NFL_2); + + //set the default Watchdog Threshold + as3935.writeWatchdogThreshold(AS3935MI::AS3935_WDTH_2); + + //set the default Spike Rejection + as3935.writeSpikeRejection(AS3935MI::AS3935_SREJ_2); + + //write default value for minimum lightnings (1) + as3935.writeMinLightnings(AS3935MI::AS3935_MNL_1); + + //do not mask disturbers + as3935.writeMaskDisturbers(false); + + //the AS3935 will pull the interrupt pin HIGH when an event is registered and will keep it + //pulled high until the event register is read. + attachInterrupt(digitalPinToInterrupt(PIN_IRQ), AS3935ISR, RISING); + + Serial.println("Initialization complete, waiting for events..."); +} + +void loop() { + // put your main code here, to run repeatedly: + + if (interrupt_) + { + //the Arduino should wait at least 2ms after the IRQ pin has been pulled high + delay(2); + + //reset the interrupt variable + interrupt_ = false; + + //query the interrupt source from the AS3935 + uint8_t event = as3935.readInterruptSource(); + + //send a report if the noise floor is too high. + if (event == AS3935MI::AS3935_INT_NH) + { + Serial.println("Noise floor too high. attempting to increase noise floor threshold. "); + + //if the noise floor threshold setting is not yet maxed out, increase the setting. + //note that noise floor threshold events can also be triggered by an incorrect + //analog front end setting. + if (as3935.increaseNoiseFloorThreshold() == AS3935MI::AS3935_NFL_0) + Serial.println("noise floor threshold already at maximum"); + else + Serial.println("increased noise floor threshold"); + } + + //send a report if a disturber was detected. if disturbers are masked with as3935.writeMaskDisturbers(true); + //this event will never be reported. + else if (event == AS3935MI::AS3935_INT_D) + { + Serial.println("Disturber detected, attempting to increase noise floor threshold. "); + + //increasing the Watchdog Threshold and / or Spike Rejection setting improves the AS3935s resistance + //against disturbers but also decrease the lightning detection efficiency (see AS3935 datasheet) + uint8_t wdth = as3935.readWatchdogThreshold(); + uint8_t srej = as3935.readSpikeRejection(); + + if ((wdth < AS3935MI::AS3935_WDTH_10) || (srej < AS3935MI::AS3935_SREJ_10)) + { + sense_adj_last_ = millis(); + + //alternatively increase spike rejection and watchdog threshold + if (srej < wdth) + { + if (as3935.increaseSpikeRejection() == AS3935MI::AS3935_SREJ_0) + Serial.println("spike rejection ratio already at maximum"); + else + Serial.println("increased spike rejection ratio"); + } + else + { + if (as3935.increaseWatchdogThreshold() == AS3935MI::AS3935_WDTH_0) + Serial.println("watchdog threshold already at maximum"); + else + Serial.println("increased watchdog threshold"); + } + } + else + { + Serial.println("error: Watchdog Threshold and Spike Rejection settings are already maxed out."); + } + } + + else if (event == AS3935MI::AS3935_INT_L) + { + Serial.print("Lightning detected! Storm Front is "); + Serial.print(as3935.readStormDistance()); + Serial.println("km away."); + } + } + + //increase sensor sensitivity every once in a while. SENSE_INCREASE_INTERVAL controls how quickly the code + //attempts to increase sensitivity. + if (millis() - sense_adj_last_ > SENSE_INCREASE_INTERVAL) + { + sense_adj_last_ = millis(); + + Serial.println("No disturber detected, attempting to decrease noise floor threshold. "); + + uint8_t wdth = as3935.readWatchdogThreshold(); + uint8_t srej = as3935.readSpikeRejection(); + + if ((wdth > AS3935MI::AS3935_WDTH_0) || (srej > AS3935MI::AS3935_SREJ_0)) + { + + //alternatively derease spike rejection and watchdog threshold + if (srej > wdth) + { + if (as3935.decreaseSpikeRejection()) + Serial.println("decreased spike rejection ratio"); + else + Serial.println("spike rejection ratio already at minimum"); + } + else + { + if (as3935.decreaseWatchdogThreshold()) + Serial.println("decreased watchdog threshold"); + else + Serial.println("watchdog threshold already at minimum"); + } + } + } +} + + +//interrupt service routine. this function is called each time the AS3935 reports an event by pulling +//the IRQ pin high. +#if defined(ESP32) +ICACHE_RAM_ATTR void AS3935ISR() +{ + interrupt_ = true; +} +#elif defined(ESP8266) +ICACHE_RAM_ATTR void AS3935ISR() +{ + interrupt_ = true; +} +#else +void AS3935ISR() +{ + interrupt_ = true; +} +#endif \ No newline at end of file diff --git a/lib/AS3935MI/examples/AS3935MI_LightningDetector_SPI/AS3935MI_LightningDetector_SPI.ino b/lib/AS3935MI/examples/AS3935MI_LightningDetector_SPI/AS3935MI_LightningDetector_SPI.ino new file mode 100644 index 0000000000..b030c61925 --- /dev/null +++ b/lib/AS3935MI/examples/AS3935MI_LightningDetector_SPI/AS3935MI_LightningDetector_SPI.ino @@ -0,0 +1,251 @@ +// AS3935_LightningDetector_SPI.ino +// +// shows how to use the AS3935 library with the lightning sensor connected using SPI. +// +// Copyright (c) 2018-2019 Gregor Christandl +// +// connect the AS3935 to the Arduino like this: +// +// Arduino - AS3935 +// 5V ------ VCC +// GND ----- GND +// D2 ------ IRQ must be a pin supporting external interrupts, e.g. D2 or D3 on an Arduino Uno. +// MOSI ---- MOSI +// MISO ---- MISO +// SCK ----- SCK +// GND ----- SI (activates SPI for the AS3935) +// D3 ------ CS chip select pin for AS3935 +// 5V ------ EN_VREG !IMPORTANT when using 5V Arduinos (Uno, Mega2560, ...) +// other pins can be left unconnected. + +#include +#include + +#include + +#define PIN_IRQ 3 +#define PIN_CS 4 + +//create an AS3935 object using the SPI interface, chip select pin 4 and IRQ pin number 3 +AS3935SPI as3935(PIN_CS, PIN_IRQ); + +//this value will be set to true by the AS3935 interrupt service routine. +volatile bool interrupt_ = false; + +constexpr uint32_t SENSE_INCREASE_INTERVAL = 15000; //15 s sensitivity increase interval +uint32_t sense_adj_last_ = 0L; //time of last sensitivity adjustment + +void setup() { + // put your setup code here, to run once: + Serial.begin(9600); + + //wait for serial connection to open (only necessary on some boards) + while (!Serial); + + //set the IRQ pin as an input pin. do not use INPUT_PULLUP - the AS3935 will pull the pin + //high if an event is registered. + pinMode(PIN_IRQ, INPUT); + + SPI.begin(); + + //begin() checks the Interface passed to the constructor and resets the AS3935 to + //default values. + if (!as3935.begin()) + { + Serial.println("begin() failed. check your AS3935 Interface setting."); + while (1); + } + + //check SPI connection. + if (!as3935.checkConnection()) + { + Serial.println("checkConnection() failed. check your SPI connection and SPI chip select pin. "); + while (1); + } + else + Serial.println("SPI connection check passed. "); + + //check the IRQ pin connection. + if (!as3935.checkIRQ()) + { + Serial.println("checkIRQ() failed. check if the correct IRQ pin was passed to the AS3935SPI constructor. "); + while (1); + } + else + Serial.println("IRQ pin connection check passed. "); + + //calibrate the resonance frequency. failing the resonance frequency could indicate an issue + //of the sensor. resonance frequency calibration will take about 1.7 seconds to complete. + int32_t frequency = 0; + if (!as3935.calibrateResonanceFrequency(frequency)) + { + Serial.print("Resonance Frequency Calibration failed: is "); + Serial.print(frequency); + Serial.println(" Hz, should be 482500 Hz - 517500 Hz"); + //while (1); + } + else + Serial.println("Resonance Frequency Calibration passed. "); + + Serial.print("Resonance Frequency is "); Serial.print(frequency); Serial.println(" Hz"); + + + //calibrate the RCO. + if (!as3935.calibrateRCO()) + { + Serial.println("RCP Calibration failed. "); + while (1); + } + else + Serial.println("RCO Calibration passed. "); + + //set the analog front end to 'indoors' + as3935.writeAFE(AS3935MI::AS3935_INDOORS); + + //set default value for noise floor threshold + as3935.writeNoiseFloorThreshold(AS3935MI::AS3935_NFL_2); + + //set the default Watchdog Threshold + as3935.writeWatchdogThreshold(AS3935MI::AS3935_WDTH_2); + + //set the default Spike Rejection + as3935.writeSpikeRejection(AS3935MI::AS3935_SREJ_2); + + //write default value for minimum lightnings (1) + as3935.writeMinLightnings(AS3935MI::AS3935_MNL_1); + + //do not mask disturbers + as3935.writeMaskDisturbers(false); + + //the AS3935 will pull the interrupt pin HIGH when an event is registered and will keep it + //pulled high until the event register is read. + attachInterrupt(digitalPinToInterrupt(PIN_IRQ), AS3935ISR, RISING); + + Serial.println("Initialization complete, waiting for events..."); +} + +void loop() { + // put your main code here, to run repeatedly: + + if (interrupt_) + { + //the Arduino should wait at least 2ms after the IRQ pin has been pulled high + delay(2); + + //reset the interrupt variable + interrupt_ = false; + + //query the interrupt source from the AS3935 + uint8_t event = as3935.readInterruptSource(); + + //send a report if the noise floor is too high. + if (event == AS3935MI::AS3935_INT_NH) + { + Serial.println("Noise floor too high. attempting to increase noise floor threshold. "); + + //if the noise floor threshold setting is not yet maxed out, increase the setting. + //note that noise floor threshold events can also be triggered by an incorrect + //analog front end setting. + if (as3935.increaseNoiseFloorThreshold() == AS3935MI::AS3935_NFL_0) + Serial.println("noise floor threshold already at maximum"); + else + Serial.println("increased noise floor threshold"); + } + + //send a report if a disturber was detected. if disturbers are masked with as3935.writeMaskDisturbers(true); + //this event will never be reported. + else if (event == AS3935MI::AS3935_INT_D) + { + Serial.println("Disturber detected, attempting to increase noise floor threshold. "); + + //increasing the Watchdog Threshold and / or Spike Rejection setting improves the AS3935s resistance + //against disturbers but also decrease the lightning detection efficiency (see AS3935 datasheet) + uint8_t wdth = as3935.readWatchdogThreshold(); + uint8_t srej = as3935.readSpikeRejection(); + + if ((wdth < AS3935MI::AS3935_WDTH_10) || (srej < AS3935MI::AS3935_SREJ_10)) + { + sense_adj_last_ = millis(); + + //alternatively increase spike rejection and watchdog threshold + if (srej < wdth) + { + if (as3935.increaseSpikeRejection() == AS3935MI::AS3935_SREJ_0) + Serial.println("spike rejection ratio already at maximum"); + else + Serial.println("increased spike rejection ratio"); + } + else + { + if (as3935.increaseWatchdogThreshold() == AS3935MI::AS3935_WDTH_0) + Serial.println("watchdog threshold already at maximum"); + else + Serial.println("increased watchdog threshold"); + } + } + else + { + Serial.println("error: Watchdog Threshold and Spike Rejection settings are already maxed out."); + } + } + + else if (event == AS3935MI::AS3935_INT_L) + { + Serial.print("Lightning detected! Storm Front is "); + Serial.print(as3935.readStormDistance()); + Serial.println("km away."); + } + } + + //increase sensor sensitivity every once in a while. SENSE_INCREASE_INTERVAL controls how quickly the code + //attempts to increase sensitivity. + if (millis() - sense_adj_last_ > SENSE_INCREASE_INTERVAL) + { + sense_adj_last_ = millis(); + + Serial.println("No disturber detected, attempting to decrease noise floor threshold. "); + + uint8_t wdth = as3935.readWatchdogThreshold(); + uint8_t srej = as3935.readSpikeRejection(); + + if ((wdth > AS3935MI::AS3935_WDTH_0) || (srej > AS3935MI::AS3935_SREJ_0)) + { + + //alternatively derease spike rejection and watchdog threshold + if (srej > wdth) + { + if (as3935.decreaseSpikeRejection()) + Serial.println("decreased spike rejection ratio"); + else + Serial.println("spike rejection ratio already at minimum"); + } + else + { + if (as3935.decreaseWatchdogThreshold()) + Serial.println("decreased watchdog threshold"); + else + Serial.println("watchdog threshold already at minimum"); + } + } + } +} + + +//interrupt service routine. this function is called each time the AS3935 reports an event by pulling +//the IRQ pin high. +#if defined(ESP32) +ICACHE_RAM_ATTR void AS3935ISR() +{ + interrupt_ = true; +} +#elif defined(ESP8266) +ICACHE_RAM_ATTR void AS3935ISR() +{ + interrupt_ = true; +} +#else +void AS3935ISR() +{ + interrupt_ = true; +} +#endif \ No newline at end of file diff --git a/lib/AS3935MI/examples/AS3935MI_LightningDetector_SPIClass/AS3935MI_LightningDetector_SPIClass.ino b/lib/AS3935MI/examples/AS3935MI_LightningDetector_SPIClass/AS3935MI_LightningDetector_SPIClass.ino new file mode 100644 index 0000000000..5198d02f0c --- /dev/null +++ b/lib/AS3935MI/examples/AS3935MI_LightningDetector_SPIClass/AS3935MI_LightningDetector_SPIClass.ino @@ -0,0 +1,252 @@ +// AS3935_LightningDetector_SPIClass.ino +// +// shows how to use the AS3935 library with the lightning sensor connected using an interface that inherits from SPIClass. +// in this example, the SPI object is used, but other objects that inherit from SPIClass may be used instead. +// +// Copyright (c) 2018-2019 Gregor Christandl +// +// connect the AS3935 to the Arduino like this: +// +// Arduino - AS3935 +// 5V ------ VCC +// GND ----- GND +// D2 ------ IRQ must be a pin supporting external interrupts, e.g. D2 or D3 on an Arduino Uno. +// MOSI ---- MOSI +// MISO ---- MISO +// SCK ----- SCK +// GND ----- SI (activates SPI for the AS3935) +// D3 ------ CS chip select pin for AS3935 +// 5V ------ EN_VREG !IMPORTANT when using 5V Arduinos (Uno, Mega2560, ...) +// other pins can be left unconnected. + +#include +#include + +#include + +#define PIN_IRQ 3 +#define PIN_CS 4 + +//create an AS3935 object using the SPI interface, chip select pin 4 and IRQ pin number 3 +//the SPIClass object is passed by reference +AS3935SPIClass as3935(&SPI, PIN_CS, PIN_IRQ); + +//this value will be set to true by the AS3935 interrupt service routine. +volatile bool interrupt_ = false; + +constexpr uint32_t SENSE_INCREASE_INTERVAL = 15000; //15 s sensitivity increase interval +uint32_t sense_adj_last_ = 0L; //time of last sensitivity adjustment + +void setup() { + // put your setup code here, to run once: + Serial.begin(9600); + + //wait for serial connection to open (only necessary on some boards) + while (!Serial); + + //set the IRQ pin as an input pin. do not use INPUT_PULLUP - the AS3935 will pull the pin + //high if an event is registered. + pinMode(PIN_IRQ, INPUT); + + SPI.begin(); + + //begin() checks the Interface passed to the constructor and resets the AS3935 to + //default values. + if (!as3935.begin()) + { + Serial.println("begin() failed. check your AS3935 Interface setting."); + while (1); + } + + //check SPI connection. + if (!as3935.checkConnection()) + { + Serial.println("checkConnection() failed. check your SPI connection and SPI chip select pin. "); + while (1); + } + else + Serial.println("SPI connection check passed. "); + + //check the IRQ pin connection. + if (!as3935.checkIRQ()) + { + Serial.println("checkIRQ() failed. check if the correct IRQ pin was passed to the AS3935SPI constructor. "); + while (1); + } + else + Serial.println("IRQ pin connection check passed. "); + + //calibrate the resonance frequency. failing the resonance frequency could indicate an issue + //of the sensor. resonance frequency calibration will take about 1.7 seconds to complete. + int32_t frequency = 0; + if (!as3935.calibrateResonanceFrequency(frequency)) + { + Serial.print("Resonance Frequency Calibration failed: is "); + Serial.print(frequency); + Serial.println(" Hz, should be 482500 Hz - 517500 Hz"); + //while (1); + } + else + Serial.println("Resonance Frequency Calibration passed. "); + + Serial.print("Resonance Frequency is "); Serial.print(frequency); Serial.println(" Hz"); + + + //calibrate the RCO. + if (!as3935.calibrateRCO()) + { + Serial.println("RCP Calibration failed. "); + while (1); + } + else + Serial.println("RCO Calibration passed. "); + + //set the analog front end to 'indoors' + as3935.writeAFE(AS3935MI::AS3935_INDOORS); + + //set default value for noise floor threshold + as3935.writeNoiseFloorThreshold(AS3935MI::AS3935_NFL_2); + + //set the default Watchdog Threshold + as3935.writeWatchdogThreshold(AS3935MI::AS3935_WDTH_2); + + //set the default Spike Rejection + as3935.writeSpikeRejection(AS3935MI::AS3935_SREJ_2); + + //write default value for minimum lightnings (1) + as3935.writeMinLightnings(AS3935MI::AS3935_MNL_1); + + //do not mask disturbers + as3935.writeMaskDisturbers(false); + + //the AS3935 will pull the interrupt pin HIGH when an event is registered and will keep it + //pulled high until the event register is read. + attachInterrupt(digitalPinToInterrupt(PIN_IRQ), AS3935ISR, RISING); + + Serial.println("Initialization complete, waiting for events..."); +} + +void loop() { + // put your main code here, to run repeatedly: + + if (interrupt_) + { + //the Arduino should wait at least 2ms after the IRQ pin has been pulled high + delay(2); + + //reset the interrupt variable + interrupt_ = false; + + //query the interrupt source from the AS3935 + uint8_t event = as3935.readInterruptSource(); + + //send a report if the noise floor is too high. + if (event == AS3935MI::AS3935_INT_NH) + { + Serial.println("Noise floor too high. attempting to increase noise floor threshold. "); + + //if the noise floor threshold setting is not yet maxed out, increase the setting. + //note that noise floor threshold events can also be triggered by an incorrect + //analog front end setting. + if (as3935.increaseNoiseFloorThreshold() == AS3935MI::AS3935_NFL_0) + Serial.println("noise floor threshold already at maximum"); + else + Serial.println("increased noise floor threshold"); + } + + //send a report if a disturber was detected. if disturbers are masked with as3935.writeMaskDisturbers(true); + //this event will never be reported. + else if (event == AS3935MI::AS3935_INT_D) + { + Serial.println("Disturber detected, attempting to increase noise floor threshold. "); + + //increasing the Watchdog Threshold and / or Spike Rejection setting improves the AS3935s resistance + //against disturbers but also decrease the lightning detection efficiency (see AS3935 datasheet) + uint8_t wdth = as3935.readWatchdogThreshold(); + uint8_t srej = as3935.readSpikeRejection(); + + if ((wdth < AS3935MI::AS3935_WDTH_10) || (srej < AS3935MI::AS3935_SREJ_10)) + { + sense_adj_last_ = millis(); + + //alternatively increase spike rejection and watchdog threshold + if (srej < wdth) + { + if (as3935.increaseSpikeRejection() == AS3935MI::AS3935_SREJ_0) + Serial.println("spike rejection ratio already at maximum"); + else + Serial.println("increased spike rejection ratio"); + } + else + { + if (as3935.increaseWatchdogThreshold() == AS3935MI::AS3935_WDTH_0) + Serial.println("watchdog threshold already at maximum"); + else + Serial.println("increased watchdog threshold"); + } + } + else + { + Serial.println("error: Watchdog Threshold and Spike Rejection settings are already maxed out."); + } + } + + else if (event == AS3935MI::AS3935_INT_L) + { + Serial.print("Lightning detected! Storm Front is "); + Serial.print(as3935.readStormDistance()); + Serial.println("km away."); + } + } + + //increase sensor sensitivity every once in a while. SENSE_INCREASE_INTERVAL controls how quickly the code + //attempts to increase sensitivity. + if (millis() - sense_adj_last_ > SENSE_INCREASE_INTERVAL) + { + sense_adj_last_ = millis(); + + Serial.println("No disturber detected, attempting to decrease noise floor threshold. "); + + uint8_t wdth = as3935.readWatchdogThreshold(); + uint8_t srej = as3935.readSpikeRejection(); + + if ((wdth > AS3935MI::AS3935_WDTH_0) || (srej > AS3935MI::AS3935_SREJ_0)) + { + + //alternatively derease spike rejection and watchdog threshold + if (srej > wdth) + { + if (as3935.decreaseSpikeRejection()) + Serial.println("decreased spike rejection ratio"); + else + Serial.println("spike rejection ratio already at minimum"); + } + else + { + if (as3935.decreaseWatchdogThreshold()) + Serial.println("decreased watchdog threshold"); + else + Serial.println("watchdog threshold already at minimum"); + } + } + } +} + +//interrupt service routine. this function is called each time the AS3935 reports an event by pulling +//the IRQ pin high. +#if defined(ESP32) +ICACHE_RAM_ATTR void AS3935ISR() +{ + interrupt_ = true; +} +#elif defined(ESP8266) +ICACHE_RAM_ATTR void AS3935ISR() +{ + interrupt_ = true; +} +#else +void AS3935ISR() +{ + interrupt_ = true; +} +#endif \ No newline at end of file diff --git a/lib/AS3935MI/examples/AS3935MI_LightningDetector_TwoWire/AS3935MI_LightningDetector_TwoWire.ino b/lib/AS3935MI/examples/AS3935MI_LightningDetector_TwoWire/AS3935MI_LightningDetector_TwoWire.ino new file mode 100644 index 0000000000..108a654c66 --- /dev/null +++ b/lib/AS3935MI/examples/AS3935MI_LightningDetector_TwoWire/AS3935MI_LightningDetector_TwoWire.ino @@ -0,0 +1,249 @@ +// AS3935MI_LightningDetector_TwoWire.ino +// +// shows how to use the AS3935 library with the lightning sensor connected using an interface that inherits from TwoWire. +// in this example, the Wire object is used, but other objects that inherit from TwoWire may be used instead. +// +// Copyright (c) 2018-2019 Gregor Christandl +// +// connect the AS3935 to the Arduino like this: +// +// Arduino - AS3935 +// 5V ------ VCC +// GND ----- GND +// D2 ------ IRQ must be a pin supporting external interrupts, e.g. D2 or D3 on an Arduino Uno. +// SDA ----- MOSI +// SCL ----- SCL +// 5V ------ SI (activates I2C for the AS3935) +// 5V ------ A0 (sets the AS3935' I2C address to 0x01) +// GND ----- A1 (sets the AS3935' I2C address to 0x01) +// 5V ------ EN_VREG !IMPORTANT when using 5V Arduinos (Uno, Mega2560, ...) +// other pins can be left unconnected. + +#include +#include + +#include + +#define PIN_IRQ 2 + +//create an AS3935 object using the I2C interface, I2C address 0x01 and IRQ pin number 2 +AS3935TwoWire as3935(&Wire, AS3935TwoWire::AS3935I2C_A01, PIN_IRQ); + +//this value will be set to true by the AS3935 interrupt service routine. +volatile bool interrupt_ = false; + +constexpr uint32_t SENSE_INCREASE_INTERVAL = 15000; //15 s sensitivity increase interval +uint32_t sense_adj_last_ = 0L; //time of last sensitivity adjustment + +void setup() { + // put your setup code here, to run once: + Serial.begin(9600); + + //wait for serial connection to open (only necessary on some boards) + while (!Serial); + + //set the IRQ pin as an input pin. do not use INPUT_PULLUP - the AS3935 will pull the pin + //high if an event is registered. + pinMode(PIN_IRQ, INPUT); + + Wire.begin(); + + //begin() checks the Interface and I2C Address passed to the constructor and resets the AS3935 to + //default values. + if (!as3935.begin()) + { + Serial.println("begin() failed. Check the I2C address passed to the AS3935I2C constructor. "); + while (1); + } + + //check I2C connection. + if (!as3935.checkConnection()) + { + Serial.println("checkConnection() failed. check your I2C connection and I2C Address. "); + while (1); + } + else + Serial.println("I2C connection check passed. "); + + //check the IRQ pin connection. + if (!as3935.checkIRQ()) + { + Serial.println("checkIRQ() failed. check if the correct IRQ pin was passed to the AS3935I2C constructor. "); + while (1); + } + else + Serial.println("IRQ pin connection check passed. "); + + //calibrate the resonance frequency. failing the resonance frequency could indicate an issue + //of the sensor. resonance frequency calibration will take about 1.7 seconds to complete. + int32_t frequency = 0; + if (!as3935.calibrateResonanceFrequency(frequency)) + { + Serial.print("Resonance Frequency Calibration failed: is "); + Serial.print(frequency); + Serial.println(" Hz, should be 482500 Hz - 517500 Hz"); + //while (1); + } + else + Serial.println("Resonance Frequency Calibration passed. "); + + Serial.print("Resonance Frequency is "); Serial.print(frequency); Serial.println(" Hz"); + + //calibrate the RCO. + if (!as3935.calibrateRCO()) + { + Serial.println("RCO Calibration failed. "); + while (1); + } + else + Serial.println("RCP Calibration passed. "); + + //set the analog front end to 'indoors' + as3935.writeAFE(AS3935MI::AS3935_INDOORS); + + //set default value for noise floor threshold + as3935.writeNoiseFloorThreshold(AS3935MI::AS3935_NFL_2); + + //set the default Watchdog Threshold + as3935.writeWatchdogThreshold(AS3935MI::AS3935_WDTH_2); + + //set the default Spike Rejection + as3935.writeSpikeRejection(AS3935MI::AS3935_SREJ_2); + + //write default value for minimum lightnings (1) + as3935.writeMinLightnings(AS3935MI::AS3935_MNL_1); + + //do not mask disturbers + as3935.writeMaskDisturbers(false); + + //the AS3935 will pull the interrupt pin HIGH when an event is registered and will keep it + //pulled high until the event register is read. + attachInterrupt(digitalPinToInterrupt(PIN_IRQ), AS3935ISR, RISING); + + Serial.println("Initialization complete, waiting for events..."); +} + +void loop() { + // put your main code here, to run repeatedly: + + if (interrupt_) + { + //the Arduino should wait at least 2ms after the IRQ pin has been pulled high + delay(2); + + //reset the interrupt variable + interrupt_ = false; + + //query the interrupt source from the AS3935 + uint8_t event = as3935.readInterruptSource(); + + //send a report if the noise floor is too high. + if (event == AS3935MI::AS3935_INT_NH) + { + Serial.println("Noise floor too high. attempting to increase noise floor threshold. "); + + //if the noise floor threshold setting is not yet maxed out, increase the setting. + //note that noise floor threshold events can also be triggered by an incorrect + //analog front end setting. + if (as3935.increaseNoiseFloorThreshold() == AS3935MI::AS3935_NFL_0) + Serial.println("noise floor threshold already at maximum"); + else + Serial.println("increased noise floor threshold"); + } + + //send a report if a disturber was detected. if disturbers are masked with as3935.writeMaskDisturbers(true); + //this event will never be reported. + else if (event == AS3935MI::AS3935_INT_D) + { + Serial.println("Disturber detected, attempting to increase noise floor threshold. "); + + //increasing the Watchdog Threshold and / or Spike Rejection setting improves the AS3935s resistance + //against disturbers but also decrease the lightning detection efficiency (see AS3935 datasheet) + uint8_t wdth = as3935.readWatchdogThreshold(); + uint8_t srej = as3935.readSpikeRejection(); + + if ((wdth < AS3935MI::AS3935_WDTH_10) || (srej < AS3935MI::AS3935_SREJ_10)) + { + sense_adj_last_ = millis(); + + //alternatively increase spike rejection and watchdog threshold + if (srej < wdth) + { + if (as3935.increaseSpikeRejection() == AS3935MI::AS3935_SREJ_0) + Serial.println("spike rejection ratio already at maximum"); + else + Serial.println("increased spike rejection ratio"); + } + else + { + if (as3935.increaseWatchdogThreshold() == AS3935MI::AS3935_WDTH_0) + Serial.println("watchdog threshold already at maximum"); + else + Serial.println("increased watchdog threshold"); + } + } + else + { + Serial.println("error: Watchdog Threshold and Spike Rejection settings are already maxed out."); + } + } + + else if (event == AS3935MI::AS3935_INT_L) + { + Serial.print("Lightning detected! Storm Front is "); + Serial.print(as3935.readStormDistance()); + Serial.println("km away."); + } + } + + //increase sensor sensitivity every once in a while. SENSE_INCREASE_INTERVAL controls how quickly the code + //attempts to increase sensitivity. + if (millis() - sense_adj_last_ > SENSE_INCREASE_INTERVAL) + { + sense_adj_last_ = millis(); + + Serial.println("No disturber detected, attempting to decrease noise floor threshold. "); + + uint8_t wdth = as3935.readWatchdogThreshold(); + uint8_t srej = as3935.readSpikeRejection(); + + if ((wdth > AS3935MI::AS3935_WDTH_0) || (srej > AS3935MI::AS3935_SREJ_0)) + { + + //alternatively derease spike rejection and watchdog threshold + if (srej > wdth) + { + if (as3935.decreaseSpikeRejection()) + Serial.println("decreased spike rejection ratio"); + else + Serial.println("spike rejection ratio already at minimum"); + } + else + { + if (as3935.decreaseWatchdogThreshold()) + Serial.println("decreased watchdog threshold"); + else + Serial.println("watchdog threshold already at minimum"); + } + } + } +} + +//interrupt service routine. this function is called each time the AS3935 reports an event by pulling +//the IRQ pin high. +#if defined(ESP32) +ICACHE_RAM_ATTR void AS3935ISR() +{ + interrupt_ = true; +} +#elif defined(ESP8266) +ICACHE_RAM_ATTR void AS3935ISR() +{ + interrupt_ = true; +} +#else +void AS3935ISR() +{ + interrupt_ = true; +} +#endif \ No newline at end of file diff --git a/lib/AS3935MI/examples/AS3935MI_LightningDetector_otherInterfaces/AS3935MI_LightningDetector_otherInterfaces.ino b/lib/AS3935MI/examples/AS3935MI_LightningDetector_otherInterfaces/AS3935MI_LightningDetector_otherInterfaces.ino new file mode 100644 index 0000000000..bc1c1a6334 --- /dev/null +++ b/lib/AS3935MI/examples/AS3935MI_LightningDetector_otherInterfaces/AS3935MI_LightningDetector_otherInterfaces.ino @@ -0,0 +1,327 @@ +// AS3935_LightningDetector_otherInterfaces.ino +// +// shows how to use the AS3935 library with interfaces that are not derived from TwoWire or SPIClass. +// here, the second I2C port of an Arduino Due is used (Wire1) +// +// Copyright (c) 2018-2019 Gregor Christandl +// +// connect the AS3935 to the Arduino Due like this: +// +// Arduino - AS3935 +// 3.3V ---- VCC +// GND ----- GND +// D2 ------ IRQ must be a pin supporting external interrupts, e.g. D2 or D3 on an Arduino Uno. +// SDA1 ---- MOSI +// SCL1 ---- SCL +// 5V ------ SI (activates I2C for the AS3935) +// 5V ------ A0 (sets the AS3935' I2C address to 0x01) +// GND ----- A1 (sets the AS3935' I2C address to 0x01) +// 5V ------ EN_VREG !IMPORTANT when using 5V Arduinos (Uno, Mega2560, ...) +// other pins can be left unconnected. + +#include + +#include + +#include + +#define PIN_IRQ 2 + +//class derived from AS3935MI that implements communication via an interface other than native I2C or SPI. +class AS3935Wire1 : public AS3935MI +{ + public: + enum I2C_address_t : uint8_t + { + AS3935I2C_A01 = 0b01, + AS3935I2C_A10 = 0b10, + AS3935I2C_A11 = 0b11 + }; + + //constructor of the derived class. in this case, only 2 parameters are needed + //@param address i2c address of the sensor. + //@param irq input pin the sensors irq pin is connected to. this parameter is passed to the constructor of the parent class (AS3935MI) + AS3935Wire1(uint8_t address, uint8_t irq) : + AS3935MI(irq), //AS3935MI does not have a default constructor therefore the constructor must be called explicitly. it takes the irq pin number as an argument. + address_(address) //initialize the AS3935Wire1 classes private member address_ to the i2c address provided + { + //nothing else to do here... + } + + //this function must be implemented by derived classes. it is used to initialize the interface. + //@return true if the interface was initializes successfully, false otherwise. + bool beginInterface() + { + //check if a valid i2c address for AS3935 lightning sensors has been provided. + switch (address_) + { + case 0x01: + case 0x02: + case 0x03: + break; //exit the switch statement + default: + //return false if an invalid I2C address was given. + return false; + } + + return true; + } + + private: + //this function must be implemented by derived classes. this function is responsible for reading data from the sensor. + //@param reg register to read. + //@return read data (1 byte). + uint8_t readRegister(uint8_t reg) + { + #if defined(ARDUINO_SAM_DUE) + //workaround for Arduino Due. The Due seems not to send a repeated start with the code below, so this + //undocumented feature of Wire::requestFrom() is used. can be used on other Arduinos too (tested on Mega2560) + //see this thread for more info: https://forum.arduino.cc/index.php?topic=385377.0 + Wire1.requestFrom(address_, 1, reg, 1, true); + #else + Wire1.beginTransmission(address_); + Wire1.write(reg); + Wire1.endTransmission(false); + Wire1.requestFrom(address_, static_cast(1)); + #endif + + return Wire1.read(); + } + + //this function must be implemented by derived classes. this function is responsible for sending data to the sensor. + //@param reg register to write to. + //@param data data to write to register. + void writeRegister(uint8_t reg, uint8_t data) + { + Wire1.beginTransmission(address_); + Wire1.write(reg); + Wire1.write(data); + Wire1.endTransmission(); + } + + uint8_t address_; //i2c address of sensor +}; + +//create an AS3935 object using the Wire1 interface, I2C address 0x01 and IRQ pin number 2 +AS3935Wire1 as3935(AS3935Wire1::AS3935I2C_A01, PIN_IRQ); + +//this value will be set to true by the AS3935 interrupt service routine. +volatile bool interrupt_ = false; + +constexpr uint32_t SENSE_INCREASE_INTERVAL = 15000; //15 s sensitivity increase interval +uint32_t sense_adj_last_ = 0L; //time of last sensitivity adjustment + +void setup() { + // put your setup code here, to run once: + Serial.begin(9600); + + //wait for serial connection to open (only necessary on some boards) + while (!Serial); + + //set the IRQ pin as an input pin. do not use INPUT_PULLUP - the AS3935 will pull the pin + //high if an event is registered. + pinMode(PIN_IRQ, INPUT); + + Wire1.begin(); + + //begin() checks the Interface passed to the constructor and resets the AS3935 to + //default values. + if (!as3935.begin()) + { + Serial.println("begin() failed. Check the I2C address passed to the AS3935I2C constructor. "); + while (1); + } + + //check I2C connection. + if (!as3935.checkConnection()) + { + Serial.println("checkConnection() failed. check your I2C connection and I2C Address. "); + while (1); + } + else + Serial.println("I2C connection check passed. "); + + //check the IRQ pin connection. + if (!as3935.checkIRQ()) + { + Serial.println("checkIRQ() failed. check if the correct IRQ pin was passed to the AS3935Wire1 constructor. "); + while (1); + } + else + Serial.println("IRQ pin connection check passed. "); + + //calibrate the resonance frequency. failing the resonance frequency could indicate an issue + //of the sensor. resonance frequency calibration will take about 1.7 seconds to complete. + int32_t frequency = 0; + if (!as3935.calibrateResonanceFrequency(frequency)) + { + Serial.print("Resonance Frequency Calibration failed: is "); + Serial.print(frequency); + Serial.println(" Hz, should be 482500 Hz - 517500 Hz"); + //while (1); + } + else + Serial.println("Resonance Frequency Calibration passed. "); + + Serial.print("Resonance Frequency is "); Serial.print(frequency); Serial.println(" Hz"); + + + //calibrate the RCO. + if (!as3935.calibrateRCO()) + { + Serial.println("RCP Calibration failed. "); + while (1); + } + else + Serial.println("RCO Calibration passed. "); + + //set the analog front end to 'indoors' + as3935.writeAFE(AS3935MI::AS3935_INDOORS); + + //set default value for noise floor threshold + as3935.writeNoiseFloorThreshold(AS3935MI::AS3935_NFL_2); + + //set the default Watchdog Threshold + as3935.writeWatchdogThreshold(AS3935MI::AS3935_WDTH_2); + + //set the default Spike Rejection + as3935.writeSpikeRejection(AS3935MI::AS3935_SREJ_2); + + //write default value for minimum lightnings (1) + as3935.writeMinLightnings(AS3935MI::AS3935_MNL_1); + + //do not mask disturbers + as3935.writeMaskDisturbers(false); + + //the AS3935 will pull the interrupt pin HIGH when an event is registered and will keep it + //pulled high until the event register is read. + attachInterrupt(digitalPinToInterrupt(PIN_IRQ), AS3935ISR, RISING); + + Serial.println("Initialization complete, waiting for events..."); +} + +void loop() { + // put your main code here, to run repeatedly: + + if (interrupt_) + { + //the Arduino should wait at least 2ms after the IRQ pin has been pulled high + delay(2); + + //reset the interrupt variable + interrupt_ = false; + + //query the interrupt source from the AS3935 + uint8_t event = as3935.readInterruptSource(); + + //send a report if the noise floor is too high. + if (event == AS3935MI::AS3935_INT_NH) + { + Serial.println("Noise floor too high. attempting to increase noise floor threshold. "); + + //if the noise floor threshold setting is not yet maxed out, increase the setting. + //note that noise floor threshold events can also be triggered by an incorrect + //analog front end setting. + if (as3935.increaseNoiseFloorThreshold() == AS3935MI::AS3935_NFL_0) + Serial.println("noise floor threshold already at maximum"); + else + Serial.println("increased noise floor threshold"); + } + + //send a report if a disturber was detected. if disturbers are masked with as3935.writeMaskDisturbers(true); + //this event will never be reported. + else if (event == AS3935MI::AS3935_INT_D) + { + Serial.println("Disturber detected, attempting to increase noise floor threshold. "); + + //increasing the Watchdog Threshold and / or Spike Rejection setting improves the AS3935s resistance + //against disturbers but also decrease the lightning detection efficiency (see AS3935 datasheet) + uint8_t wdth = as3935.readWatchdogThreshold(); + uint8_t srej = as3935.readSpikeRejection(); + + if ((wdth < AS3935MI::AS3935_WDTH_10) || (srej < AS3935MI::AS3935_SREJ_10)) + { + sense_adj_last_ = millis(); + + //alternatively increase spike rejection and watchdog threshold + if (srej < wdth) + { + if (as3935.increaseSpikeRejection() == AS3935MI::AS3935_SREJ_0) + Serial.println("spike rejection ratio already at maximum"); + else + Serial.println("increased spike rejection ratio"); + } + else + { + if (as3935.increaseWatchdogThreshold() == AS3935MI::AS3935_WDTH_0) + Serial.println("watchdog threshold already at maximum"); + else + Serial.println("increased watchdog threshold"); + } + } + else + { + Serial.println("error: Watchdog Threshold and Spike Rejection settings are already maxed out."); + } + } + + else if (event == AS3935MI::AS3935_INT_L) + { + Serial.print("Lightning detected! Storm Front is "); + Serial.print(as3935.readStormDistance()); + Serial.println("km away."); + } + } + + //increase sensor sensitivity every once in a while. SENSE_INCREASE_INTERVAL controls how quickly the code + //attempts to increase sensitivity. + if (millis() - sense_adj_last_ > SENSE_INCREASE_INTERVAL) + { + sense_adj_last_ = millis(); + + Serial.println("No disturber detected, attempting to decrease noise floor threshold. "); + + uint8_t wdth = as3935.readWatchdogThreshold(); + uint8_t srej = as3935.readSpikeRejection(); + + if ((wdth > AS3935MI::AS3935_WDTH_0) || (srej > AS3935MI::AS3935_SREJ_0)) + { + + //alternatively derease spike rejection and watchdog threshold + if (srej > wdth) + { + if (as3935.decreaseSpikeRejection()) + Serial.println("decreased spike rejection ratio"); + else + Serial.println("spike rejection ratio already at minimum"); + } + else + { + if (as3935.decreaseWatchdogThreshold()) + Serial.println("decreased watchdog threshold"); + else + Serial.println("watchdog threshold already at minimum"); + } + } + } +} + + +//interrupt service routine. this function is called each time the AS3935 reports an event by pulling +//the IRQ pin high. +#if defined(ESP32) +ICACHE_RAM_ATTR void AS3935ISR() +{ + interrupt_ = true; +} +#elif defined(ESP8266) +ICACHE_RAM_ATTR void AS3935ISR() +{ + interrupt_ = true; +} +#else +void AS3935ISR() +{ + interrupt_ = true; +} +#endif \ No newline at end of file diff --git a/lib/AS3935MI/keywords.txt b/lib/AS3935MI/keywords.txt new file mode 100644 index 0000000000..6391acd288 --- /dev/null +++ b/lib/AS3935MI/keywords.txt @@ -0,0 +1,128 @@ +####################################### +# Syntax Coloring Map For ExampleLibrary +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +AS3935MI KEYWORD1 +AS3935I2C KEYWORD1 +AS3935SPI KEYWORD1 +AS3935TwoWire KEYWORD1 +AS3935SPIClass KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +begin KEYWORD2 +checkConnection KEYWORD2 +checkIRQ KEYWORD2 +clearstatistics KEYWORD2 +readStormDistance KEYWORD2 +readInterruptSource KEYWORD2 +readPowerDown KEYWORD2 +writePowerDown KEYWORD2 +readMaskDisturbers KEYWORD2 +writeMaskDisturbers KEYWORD2 +readAFE KEYWORD2 +writeAFE KEYWORD2 +readNoiseFloorThreshold KEYWORD2 +writeNoiseFloorThreshold KEYWORD2 +increaseNoiseFloorThreshold KEYWORD2 +decreaseNoiseFloorThreshold KEYWORD2 +readWatchdogThreshold KEYWORD2 +writeWatchdogThreshold KEYWORD2 +increaseWatchdogThreshold KEYWORD2 +decreaseWatchdogThreshold KEYWORD2 +readSpikeRejection KEYWORD2 +writeSpikeRejection KEYWORD2 +increaseSpikeRejection KEYWORD2 +decreaseSpikeRejection KEYWORD2 +readEnergy KEYWORD2 +readAntennaTuning KEYWORD2 +writeAntennaTuning KEYWORD2 +readDivisionRatio KEYWORD2 +writeDivisionRatio KEYWORD2 +readMinLightnings KEYWORD2 +writeMinLightnings KEYWORD2 +resetToDefaults KEYWORD2 +calibrateRCO KEYWORD2 +calibrateResonanceFrequency KEYWORD2 +readRegister KEYWORD2 +writeRegister KEYWORD2 + +####################################### +# Instances (KEYWORD2) +####################################### + +####################################### +# Constants (LITERAL1) + +AS3935_INDOORS LITERAL1 +AS3935_OUTDOORS LITERAL1 + +AS3935_INT_NH LITERAL1 +AS3935_INT_D LITERAL1 +AS3935_INT_L LITERAL1 + +AS3935_WDTH_0 LITERAL1 +AS3935_WDTH_1 LITERAL1 +AS3935_WDTH_2 LITERAL1 +AS3935_WDTH_3 LITERAL1 +AS3935_WDTH_4 LITERAL1 +AS3935_WDTH_5 LITERAL1 +AS3935_WDTH_6 LITERAL1 +AS3935_WDTH_7 LITERAL1 +AS3935_WDTH_8 LITERAL1 +AS3935_WDTH_9 LITERAL1 +AS3935_WDTH_10 LITERAL1 +AS3935_WDTH_11 LITERAL1 +AS3935_WDTH_12 LITERAL1 +AS3935_WDTH_13 LITERAL1 +AS3935_WDTH_14 LITERAL1 +AS3935_WDTH_15 LITERAL1 + +AS3935_SREJ_0 LITERAL1 +AS3935_SREJ_1 LITERAL1 +AS3935_SREJ_2 LITERAL1 +AS3935_SREJ_3 LITERAL1 +AS3935_SREJ_4 LITERAL1 +AS3935_SREJ_5 LITERAL1 +AS3935_SREJ_6 LITERAL1 +AS3935_SREJ_7 LITERAL1 +AS3935_SREJ_8 LITERAL1 +AS3935_SREJ_9 LITERAL1 +AS3935_SREJ_10 LITERAL1 +AS3935_SREJ_11 LITERAL1 +AS3935_SREJ_12 LITERAL1 +AS3935_SREJ_13 LITERAL1 +AS3935_SREJ_14 LITERAL1 +AS3935_SREJ_15 LITERAL1 + +AS3935_NFL_0 LITERAL1 +AS3935_NFL_1 LITERAL1 +AS3935_NFL_2 LITERAL1 +AS3935_NFL_3 LITERAL1 +AS3935_NFL_4 LITERAL1 +AS3935_NFL_5 LITERAL1 +AS3935_NFL_6 LITERAL1 +AS3935_NFL_7 LITERAL1 + +AS3935_MNL_1 LITERAL1 +AS3935_MNL_5 LITERAL1 +AS3935_MNL_9 LITERAL1 +AS3935_MNL_16 LITERAL1 + +AS3935_DR_16 LITERAL1 +AS3935_DR_32 LITERAL1 +AS3935_DR_64 LITERAL1 +AS3935_DR_128 LITERAL1 + +AS3935I2C_A01 LITERAL1 +AS3935I2C_A10 LITERAL1 +AS3935I2C_A11 LITERAL1 + +AS3935_DST_OOR LITERAL1 +####################################### \ No newline at end of file diff --git a/lib/AS3935MI/library.json b/lib/AS3935MI/library.json new file mode 100644 index 0000000000..f15df8c853 --- /dev/null +++ b/lib/AS3935MI/library.json @@ -0,0 +1,19 @@ +{ + "name": "AS3935MI", + "version": "1.3.5", + "keywords": "AS3935", + "description": "A library for the ams AS3935 lightning sensor. The library supports both the SPI (via the SPI Library) and I2C (via the Wire Library) interfaces. Use of other I2C / SPI libraries (e.g. software I2C) is supported by inheritance. ", + "authors": + { + "name": "Gregor Christandl", + "email": "christandlg@yahoo.com", + "url": "https://bitbucket.org/christandlg/as3935mi" + }, + "repository": + { + "type": "git", + "url": "https://bitbucket.org/christandlg/as3935mi.git" + }, + "frameworks": "arduino", + "platforms": "*" +} \ No newline at end of file diff --git a/lib/AS3935MI/library.properties b/lib/AS3935MI/library.properties new file mode 100644 index 0000000000..9a04cf66aa --- /dev/null +++ b/lib/AS3935MI/library.properties @@ -0,0 +1,10 @@ +name=AS3935MI +version=1.3.5 +author=Gregor Christandl +maintainer=Gregor Christandl +sentence=A library for the Austria Microsystems AS3935 Franklin Lightning Detector, supporting I2C and SPI interfaces. +paragraph=The library supports both the SPI (via the SPI Library) and I2C (via the Wire Library) interfaces. Use of other I2C / SPI libraries (e.g. software I2C) is supported by inheritance. +category=Sensors +url=https://bitbucket.org/christandlg/as3935mi/ +architectures=* +includes= \ No newline at end of file diff --git a/lib/AS3935MI/src/AS3935I2C.cpp b/lib/AS3935MI/src/AS3935I2C.cpp new file mode 100644 index 0000000000..e071e00c6a --- /dev/null +++ b/lib/AS3935MI/src/AS3935I2C.cpp @@ -0,0 +1,28 @@ +//Yet Another Arduino ams AS3935 'Franklin' lightning sensor library +// Copyright (c) 2018-2019 Gregor Christandl +// home: https://bitbucket.org/christandlg/as3935mi +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#include "AS3935I2C.h" + +AS3935I2C::AS3935I2C(uint8_t address, uint8_t irq) : + AS3935TwoWire(&Wire, address, irq) +{ +} + +AS3935I2C::~AS3935I2C() +{ +} diff --git a/lib/AS3935MI/src/AS3935I2C.h b/lib/AS3935MI/src/AS3935I2C.h new file mode 100644 index 0000000000..8c42899f9d --- /dev/null +++ b/lib/AS3935MI/src/AS3935I2C.h @@ -0,0 +1,32 @@ +//Yet Another Arduino ams AS3935 'Franklin' lightning sensor library +// Copyright (c) 2018-2019 Gregor Christandl +// home: https://bitbucket.org/christandlg/as3935mi +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef AS3935I2C_H_ +#define AS3935I2C_H_ + +#include "AS3935TwoWire.h" + +class AS3935I2C : + public AS3935TwoWire +{ +public: + AS3935I2C(uint8_t address, uint8_t irq); + virtual ~AS3935I2C(); +}; + +#endif /* AS3935I2C_H_ */ diff --git a/lib/AS3935MI/src/AS3935MI.cpp b/lib/AS3935MI/src/AS3935MI.cpp new file mode 100644 index 0000000000..4358460c71 --- /dev/null +++ b/lib/AS3935MI/src/AS3935MI.cpp @@ -0,0 +1,877 @@ +//Yet Another Arduino ams AS3935 'Franklin' lightning sensor library +// Copyright (c) 2018-2019 Gregor Christandl +// home: https://bitbucket.org/christandlg/as3935 +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. + +// This library 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 +// Lesser General Public License for more details. + +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#include "AS3935MI.h" + + +#ifdef ESP8266 +#define getMicros64 micros64 +#elif defined(ESP32) +#define getMicros64 esp_timer_get_time +#else +#define getMicros64 micros +#endif + + +// When we can't use attachInterruptArg to directly access volatile members, +// we must use static variables in the .cpp file +#ifndef AS3935MI_HAS_ATTACHINTERRUPTARG_FUNCTION + AS3935MI_VOLATILE_TYPE interrupt_timestamp_ = 0; + AS3935MI_VOLATILE_TYPE interrupt_count_ = 0; + + // Store the time micros as 32-bit int so it can be stored and comprared as an atomic operation. + // Expected duration will be much less than 2^32 usec, thus overflow isn't an issue here + AS3935MI_VOLATILE_TYPE calibration_start_micros_ = 0; + AS3935MI_VOLATILE_TYPE calibration_end_micros_ = 0; + + uint32_t nr_calibration_samples_ = AS3935MI_NR_CALIBRATION_SAMPLES; +#endif + +AS3935MI::AS3935MI(uint8_t irq) : + irq_(irq), + tuning_cap_cache_(0), + mode_(AS3935MI::AS3935_INTERRUPT_UNINITIALIZED), + calibration_mode_edgetrigger_trigger_(AS3935MI_CALIBRATION_MODE_EDGE_TRIGGER), + calibration_mode_division_ratio_(AS3935MI_LCO_DIVISION_RATIO), + calibrated_ant_cap_(-1), + calibrate_all_ant_cap_(true) +{ + // Setup these in the constructor body as these might not be a member + // if AS3935MI_HAS_ATTACHINTERRUPTARG_FUNCTION is not defined. + interrupt_timestamp_ = 0; + interrupt_count_ = 0; + + calibration_start_micros_ = 0; + calibration_end_micros_ = 0; + + nr_calibration_samples_ = AS3935MI_NR_CALIBRATION_SAMPLES; + + pinMode(irq_, INPUT); +} + +AS3935MI::~AS3935MI() +{ + if (mode_ == AS3935MI::AS3935_INTERRUPT_NORMAL || + mode_ == AS3935MI::AS3935_INTERRUPT_CALIBRATION) { + detachInterrupt(irq_); + } +} + +bool AS3935MI::begin() +{ + if (!beginInterface()) + return false; + + writePowerDown(false); + + setInterruptMode(AS3935MI::AS3935_INTERRUPT_DETACHED); + resetToDefaults(); + + return true; +} + +uint8_t AS3935MI::readStormDistance() +{ + return readRegisterValue(AS3935_REGISTER_DISTANCE, AS3935_MASK_DISTANCE); +} + +uint8_t AS3935MI::readInterruptSource() +{ + interrupt_timestamp_ = 0; + interrupt_count_ = 0; + return readRegisterValue(AS3935_REGISTER_INT, AS3935_MASK_INT); +} + +bool AS3935MI::readPowerDown() +{ + return (readRegisterValue(AS3935_REGISTER_PWD, AS3935_MASK_PWD) == 1 ? true : false); +} + +void AS3935MI::writePowerDown(bool enabled) +{ + writeRegisterValue(AS3935_REGISTER_PWD, AS3935_MASK_PWD, enabled ? 1 : 0); + if (!enabled) { + delayMicroseconds(AS3935_TIMEOUT); + } +} + +bool AS3935MI::readMaskDisturbers() +{ + return (readRegisterValue(AS3935_REGISTER_MASK_DIST, AS3935_MASK_MASK_DIST) == 1 ? true : false); +} + +void AS3935MI::writeMaskDisturbers(bool enabled) +{ + writeRegisterValue(AS3935_REGISTER_MASK_DIST, AS3935_MASK_MASK_DIST, enabled ? 1 : 0); +} + +uint8_t AS3935MI::readAFE() +{ + return readRegisterValue(AS3935_REGISTER_AFE_GB, AS3935_MASK_AFE_GB); +} + +void AS3935MI::writeAFE(uint8_t afe_setting) +{ + writeRegisterValue(AS3935_REGISTER_AFE_GB, AS3935_MASK_AFE_GB, afe_setting); +} + +uint8_t AS3935MI::readNoiseFloorThreshold() +{ + return readRegisterValue(AS3935_REGISTER_NF_LEV, AS3935_MASK_NF_LEV); +} + +void AS3935MI::writeNoiseFloorThreshold(uint8_t threshold) +{ + if (threshold > AS3935_NFL_7) + return; + + writeRegisterValue(AS3935_REGISTER_NF_LEV, AS3935_MASK_NF_LEV, threshold); + + delayMicroseconds(AS3935_TIMEOUT); +} + +uint8_t AS3935MI::readWatchdogThreshold() +{ + return readRegisterValue(AS3935_REGISTER_WDTH, AS3935_MASK_WDTH); +} + +void AS3935MI::writeWatchdogThreshold(uint8_t threshold) +{ + if (threshold > AS3935_WDTH_15) + return; + + writeRegisterValue(AS3935_REGISTER_WDTH, AS3935_MASK_WDTH, threshold); + + delayMicroseconds(AS3935_TIMEOUT); +} + +uint8_t AS3935MI::readSpikeRejection() +{ + return readRegisterValue(AS3935_REGISTER_SREJ, AS3935_MASK_SREJ); +} + +void AS3935MI::writeSpikeRejection(uint8_t threshold) +{ + if (threshold > AS3935_SREJ_15) + return; + + writeRegisterValue(AS3935_REGISTER_SREJ, AS3935_MASK_SREJ, threshold); + + delayMicroseconds(AS3935_TIMEOUT); +} + +uint32_t AS3935MI::readEnergy() +{ + uint32_t energy = 0; + //from https://www.eevblog.com/forum/microcontrollers/define-mmsbyte-for-as3935-lightning-detector/ + //Reg 0x04: Energy word, bits 0 : 7 + //Reg 0x05 : Energy word, bits 8 : 15 + //Reg 0x06 : Energy word, bits 16 : 20 + //energy |= LSB + //energy |= (MSB << 8) + //energy |= (MMSB << 16) + energy |= static_cast(readRegisterValue(AS3935_REGISTER_S_LIG_L, AS3935_MASK_S_LIG_L)); + energy |= (static_cast(readRegisterValue(AS3935_REGISTER_S_LIG_M, AS3935_MASK_S_LIG_M)) << 8); + energy |= (static_cast(readRegisterValue(AS3935_REGISTER_S_LIG_MM, AS3935_MASK_S_LIG_MM)) << 16); + + return energy; +} + +uint8_t AS3935MI::readAntennaTuning() +{ + // Do not call readRegisterValue(AS3935_REGISTER_TUN_CAP, AS3935_MASK_TUN_CAP) + // here as we need to be able to detect read errors. + const uint8_t return_value = readRegister(AS3935_REGISTER_TUN_CAP); + if (return_value != static_cast(-1)) { + // No read error, so update the tuning_cap_cache_ + tuning_cap_cache_ = return_value & AS3935_MASK_TUN_CAP; + } else { + return tuning_cap_cache_ & AS3935_MASK_TUN_CAP; + } + + return return_value & AS3935_MASK_TUN_CAP; +} + +bool AS3935MI::writeAntennaTuning(uint8_t tuning) +{ + if ((tuning & ~AS3935_MASK_TUN_CAP) != 0) { + return false; + } + tuning_cap_cache_ = tuning; + writeRegisterValue(AS3935_REGISTER_TUN_CAP, AS3935_MASK_TUN_CAP, tuning); + return true; +} + +uint8_t AS3935MI::readDivisionRatio() +{ + return readRegisterValue(AS3935_REGISTER_LCO_FDIV, AS3935_MASK_LCO_FDIV); +} + +void AS3935MI::writeDivisionRatio(uint8_t ratio) +{ + writeRegisterValue(AS3935_REGISTER_LCO_FDIV, AS3935_MASK_LCO_FDIV, ratio); +} + +uint8_t AS3935MI::readMinLightnings() +{ + return readRegisterValue(AS3935_REGISTER_MIN_NUM_LIGH, AS3935_MASK_MIN_NUM_LIGH); +} + +void AS3935MI::writeMinLightnings(uint8_t number) +{ + writeRegisterValue(AS3935_REGISTER_MIN_NUM_LIGH, AS3935_MASK_MIN_NUM_LIGH, number); +} + +void AS3935MI::resetToDefaults() +{ + writeRegister(AS3935_REGISTER_PRESET_DEFAULT, AS3935_DIRECT_CMD); + + delayMicroseconds(AS3935_TIMEOUT); +} + +bool AS3935MI::calibrateRCO() +{ + //cannot calibrate if in power down mode. + if (readPowerDown()) + return false; + + //issue calibration command + writeRegister(AS3935_REGISTER_CALIB_RCO, AS3935_DIRECT_CMD); + + //expose 1.1 MHz SRCO clock on IRQ pin + displaySrcoOnIrq(true); + + //wait for calibration to finish... + delayMicroseconds(AS3935_TIMEOUT); + + //stop exposing clock on IRQ pin + displaySrcoOnIrq(false); + + //check calibration results. bits will be set if calibration failed. + bool success_TRCO = (readRegisterValue(AS3935_REGISTER_TRCO_CALIB_NOK, AS3935_MASK_TRCO_CALIB_ALL) == 0b10); + bool success_SRCO = (readRegisterValue(AS3935_REGISTER_SRCO_CALIB_NOK, AS3935_MASK_SRCO_CALIB_ALL) == 0b10); + + return (success_TRCO && success_SRCO); +} + +void AS3935MI::setFrequencyMeasureNrSamples(uint32_t nrSamples) +{ + nr_calibration_samples_ = nrSamples; +} + +void AS3935MI::setFrequencyMeasureEdgeChange(bool triggerRisingAndFalling) +{ + calibration_mode_edgetrigger_trigger_ = triggerRisingAndFalling ? CHANGE : RISING; +} + +void AS3935MI::setCalibrationDivisionRatio(uint8_t division_ratio) +{ + if (division_ratio <= AS3935MI::division_ratio_t::AS3935_DR_128) { + calibration_mode_division_ratio_ = static_cast(division_ratio); + } else { + calibration_mode_division_ratio_ = AS3935MI_LCO_DIVISION_RATIO; + } +} + +bool AS3935MI::calibrateResonanceFrequency(int32_t& frequency, uint8_t division_ratio) +{ + if (readPowerDown()) + return false; + + setCalibrationDivisionRatio(division_ratio); + + // Check for allowed deviation + constexpr uint32_t allowedDeviation = 500000 * AS3935MI_ALLOWED_DEVIATION; + const uint32_t cur_nr_samples = nr_calibration_samples_; + + calibrated_ant_cap_ = -1; + + uint32_t best_diff = 500000; + int8_t best_i = -1; + + frequency = 0; + + // Clear previous calibration results + for (uint8_t i = 0; i < 16; i++) + { + calibration_frequencies_[i] = 0.0f; + } + + // When set to calibrate all ant_cap, the + uint8_t attempt = calibrate_all_ant_cap_ ? 0 : 2; + uint8_t lowest_cap = 0; + uint8_t highest_cap = 15; + + // Find upper and lower bound of ant_caps to test using more samples + while (attempt > 0) { + --attempt; + const int32_t freq_0 = measureResonanceFrequency( + display_frequency_source_t::LCO, 0); + const int32_t freq_15 = measureResonanceFrequency( + display_frequency_source_t::LCO, 15); + + if ((freq_0 == 0 || freq_15 == 0) || (freq_0 == freq_15)) { + setFrequencyMeasureNrSamples(nr_calibration_samples_ * 2); + } else { + const int estimated_cap = map(500000, freq_0, freq_15, 0, 15); + if (estimated_cap <= 0) { + highest_cap = 1; + } else if (estimated_cap >= 15) { + lowest_cap = 14; + } else { + lowest_cap = estimated_cap - 1; + highest_cap = estimated_cap + 1; + } + attempt = 0; + } + } + + // Now test with higher number of samples to get better accuracy + if (nr_calibration_samples_ < AS3935MI_NR_CALIBRATION_SAMPLES) { + setFrequencyMeasureNrSamples(AS3935MI_NR_CALIBRATION_SAMPLES); + } + for (uint8_t i = lowest_cap; i <= highest_cap; i++) + { + const int32_t freq = measureResonanceFrequency( + display_frequency_source_t::LCO, i); + + if (freq == 0) { + // restore nr of samples set by user + setFrequencyMeasureNrSamples(cur_nr_samples); + return false; + } + const uint32_t freq_diff = abs(500000 - freq); + + if (freq_diff < best_diff) { + best_diff = freq_diff; + best_i = i; + frequency = freq; + } + } + + // restore nr of samples set by user + setFrequencyMeasureNrSamples(cur_nr_samples); + + if (best_i < 0) { + frequency = 0; + return false; + } + + calibrated_ant_cap_ = best_i; + + writeAntennaTuning(calibrated_ant_cap_); + + return best_diff < allowedDeviation; +} + +bool AS3935MI::calibrateResonanceFrequency(int32_t& frequency) +{ + return calibrateResonanceFrequency(frequency, calibration_mode_division_ratio_); +} + +bool AS3935MI::calibrateResonanceFrequency() +{ + int32_t frequency = 0; + return calibrateResonanceFrequency(frequency, calibration_mode_division_ratio_); +} + +bool AS3935MI::checkConnection() +{ + uint8_t afe = readAFE(); + + return ((afe == AS3935_INDOORS) || (afe == AS3935_OUTDOORS)); +} + +bool AS3935MI::checkIRQ() +{ + // Only need a quick check, so set nr of samples low as we're not yet interested in an accurate measurement + const uint32_t cur_nr_samples = nr_calibration_samples_; + setFrequencyMeasureNrSamples(128); + const uint32_t freq = measureResonanceFrequency(display_frequency_source_t::LCO); + setFrequencyMeasureNrSamples(cur_nr_samples); + + // Expected LCO frequency is several kHz, so we should see at the very least see 1 kHz + return freq > 1000; +} + +void AS3935MI::clearStatistics() +{ + writeRegisterValue(AS3935_REGISTER_CL_STAT, AS3935_MASK_CL_STAT, 1); + writeRegisterValue(AS3935_REGISTER_CL_STAT, AS3935_MASK_CL_STAT, 0); + writeRegisterValue(AS3935_REGISTER_CL_STAT, AS3935_MASK_CL_STAT, 1); +} + +bool AS3935MI::decreaseNoiseFloorThreshold() { + uint8_t nf_lev{}; + return decreaseNoiseFloorThreshold(nf_lev); +} + +bool AS3935MI::decreaseNoiseFloorThreshold(uint8_t& nf_lev) +{ + nf_lev = readNoiseFloorThreshold(); + + if (nf_lev == AS3935_NFL_0) + return false; + + writeNoiseFloorThreshold(--nf_lev); + + return true; +} + +bool AS3935MI::increaseNoiseFloorThreshold() { + uint8_t nf_lev{}; + return increaseNoiseFloorThreshold(nf_lev); +} + +bool AS3935MI::increaseNoiseFloorThreshold(uint8_t& nf_lev) +{ + nf_lev = readNoiseFloorThreshold(); + + if (nf_lev >= AS3935_NFL_7) + return false; + + writeNoiseFloorThreshold(++nf_lev); + + return true; +} + +bool AS3935MI::decreaseWatchdogThreshold() { + uint8_t wdth{}; + return decreaseWatchdogThreshold(wdth); +} + +bool AS3935MI::decreaseWatchdogThreshold(uint8_t& wdth) +{ + wdth = readWatchdogThreshold(); + + if (wdth == AS3935_WDTH_0) + return false; + + writeWatchdogThreshold(--wdth); + + return true; +} + +bool AS3935MI::increaseWatchdogThreshold() { + uint8_t wdth{}; + return increaseWatchdogThreshold(wdth); +} + +bool AS3935MI::increaseWatchdogThreshold(uint8_t& wdth) +{ + wdth = readWatchdogThreshold(); + + if (wdth >= AS3935_WDTH_15) + return false; + + writeWatchdogThreshold(++wdth); + + return true; +} + + +bool AS3935MI::decreaseSpikeRejection() +{ + uint8_t srej{}; + return decreaseSpikeRejection(srej); +} + +bool AS3935MI::decreaseSpikeRejection(uint8_t& srej) +{ + srej = readSpikeRejection(); + + if (srej == AS3935_SREJ_0) + return false; + + writeSpikeRejection(--srej); + + return true; +} + +bool AS3935MI::increaseSpikeRejection() +{ + uint8_t srej{}; + return increaseSpikeRejection(srej); +} + +bool AS3935MI::increaseSpikeRejection(uint8_t& srej) +{ + srej = readSpikeRejection(); + + if (srej >= AS3935_SREJ_15) + return false; + + writeSpikeRejection(++srej); + + return true; +} + +void AS3935MI::displayLcoOnIrq(bool enable) +{ + // With display of any frequency, the device may sometimes report NAK when reading registers + // So for this reason we're now writing directly and not try to read first, patch bits, write + uint8_t value = tuning_cap_cache_; + if (enable) { + value |= AS3935_MASK_DISP_LCO; + } + writeRegister(AS3935_REGISTER_DISP_LCO, value); +} + +void AS3935MI::displaySrcoOnIrq(bool enable) +{ + uint8_t value = tuning_cap_cache_; + if (enable) { + value |= AS3935_MASK_DISP_SRCO; + } + writeRegister(AS3935_REGISTER_DISP_SRCO, value); +} + + +void AS3935MI::displayTrcoOnIrq(bool enable) +{ + uint8_t value = tuning_cap_cache_; + if (enable) { + value |= AS3935_MASK_DISP_TRCO; + } + writeRegister(AS3935_REGISTER_DISP_TRCO, value); +} + + +bool AS3935MI::validateCurrentResonanceFrequency(int32_t& frequency) +{ + frequency = measureResonanceFrequency( + display_frequency_source_t::LCO, + readAntennaTuning()); + + // Check for allowed deviation + constexpr int allowedDeviation = 500000 * AS3935MI_ALLOWED_DEVIATION; + + return abs(500000 - frequency) < allowedDeviation; +} + +int32_t AS3935MI::measureResonanceFrequency(display_frequency_source_t source) +{ + return measureResonanceFrequency( + source, + readAntennaTuning()); +} + + +uint8_t AS3935MI::getMaskShift(uint8_t mask) +{ + uint8_t return_value = 0; + + //count how many times the mask must be shifted right until the lowest bit is set + if (mask != 0) + { + while (!(mask & 1)) + { + return_value++; + mask >>= 1; + } + } + + return return_value; +} + +uint8_t AS3935MI::getMaskedBits(uint8_t reg, uint8_t mask) +{ + //extract masked bits + return ((reg & mask) >> getMaskShift(mask)); +} + +uint8_t AS3935MI::setMaskedBits(uint8_t reg, uint8_t mask, uint8_t value) +{ + //clear mask bits in register + reg &= (~mask); + + //set masked bits in register according to value + return ((value << getMaskShift(mask)) & mask) | reg; +} + +uint8_t AS3935MI::readRegisterValue(uint8_t reg, uint8_t mask) +{ + return getMaskedBits(readRegister(reg), mask); +} + +void AS3935MI::writeRegisterValue(uint8_t reg, uint8_t mask, uint8_t value) +{ + uint8_t reg_val = readRegister(reg); + writeRegister(reg, setMaskedBits(reg_val, mask, value)); +} + + +uint32_t AS3935MI::computeCalibratedFrequency(int32_t divider) +{ + switch (divider) + { + case AS3935_DIVIDER_1: + case AS3935_DIVIDER_16: + case AS3935_DIVIDER_32: + case AS3935_DIVIDER_64: + case AS3935_DIVIDER_128: + break; + default: + return 0ul; + } + + // Need to copy the timestamps first as they are volatile + const uint32_t start = calibration_start_micros_; + const uint32_t end = calibration_end_micros_; + + if ((start == 0ul) || (end == 0ul)) { + return 0ul; + } + + const int32_t duration_usec = (int32_t) (end - start); + + if (duration_usec <= 0l) { + return 0ul; + } + + // Compute measured frequency + // we have duration of nr_calibration_samples_ pulses in usec, thus measured frequency is: + // (nr_calibration_samples_ * 1000'000) / duration in usec. + // Actual frequency should take the division ratio into account. + uint64_t freq = (static_cast(divider) * 1000000ull * (nr_calibration_samples_ + 1)); + if (calibration_mode_edgetrigger_trigger_ == CHANGE) { + // Counting on both rising and falling edge, so actual frequency is half + freq /= 2ull; + } + + freq /= duration_usec; + + return static_cast(freq); +} + + +uint32_t AS3935MI::measureResonanceFrequency(display_frequency_source_t source, uint8_t tuningCapacitance) +{ + setInterruptMode(interrupt_mode_t::AS3935_INTERRUPT_DETACHED); + +// delayMicroseconds(AS3935_TIMEOUT); + + unsigned sourceFreq_kHz = 500; + int32_t divider = 1; + + // display LCO on IRQ + switch (source) { + case display_frequency_source_t::LCO: + // set tuning capacitors + if (!writeAntennaTuning(tuningCapacitance)) { + return 0u; + } + displayLcoOnIrq(true); + writeDivisionRatio(calibration_mode_division_ratio_); + divider = 16 << static_cast(calibration_mode_division_ratio_); + sourceFreq_kHz = 500; + break; + + // TD-er: Do not try to measure the 1.1 MHz signal as the ESP32 will not be able to keep up with all the interrupts. + case display_frequency_source_t::SRCO: + displaySrcoOnIrq(true); + sourceFreq_kHz = 1100; + break; + case display_frequency_source_t::TRCO: + displayTrcoOnIrq(true); + sourceFreq_kHz = 33; + break; + } + + setInterruptMode(interrupt_mode_t::AS3935_INTERRUPT_CALIBRATION); + + // Need to give enough time for the sensor to set the LCO signal on the IRQ pin + delayMicroseconds(AS3935_TIMEOUT); + calibration_end_micros_ = 0ul; + interrupt_count_ = 0ul; + calibration_start_micros_ = static_cast(getMicros64()); + + // Wait for the amount of samples to be counted (or timeout) + // Typically this takes 32 msec for the 500 kHz LCO when taking 1000 samples + unsigned expectedDuration = (divider * nr_calibration_samples_) / sourceFreq_kHz; + if (expectedDuration < 10) { + // For low nr of samples, we should still keep some minimum timeout of 10 msec. + expectedDuration = 10; + } + + const uint32_t timeout = millis() + (2 * expectedDuration); + uint32_t freq = 0; + + while (freq == 0 && (((int32_t)(millis() - timeout)) < 0)) { + delay(1); + freq = computeCalibratedFrequency(divider); + } + + // Need to disable interrupts first or else sending I2C commands may fail + setInterruptMode(interrupt_mode_t::AS3935_INTERRUPT_DETACHED); + + // stop displaying LCO on IRQ + displayLcoOnIrq(false); + + if (source == display_frequency_source_t::LCO) { + calibration_frequencies_[tuningCapacitance] = freq; + } + + return freq; +} + +uint32_t AS3935MI::getInterruptTimestamp() const { + return interrupt_timestamp_; +} + +uint32_t AS3935MI::getInterruptCount() const { + return interrupt_count_; +} + +void AS3935MI::setInterruptMode(interrupt_mode_t mode) { + if (mode_ == mode) { + return; + } + + if (mode_ == AS3935MI::AS3935_INTERRUPT_NORMAL || + mode_ == AS3935MI::AS3935_INTERRUPT_CALIBRATION) { + detachInterrupt(irq_); + } + + // set the IRQ pin as an input pin. do not use INPUT_PULLUP - the AS3935 will pull the pin + // high if an event is registered. + pinMode(irq_, INPUT); + + interrupt_timestamp_ = 0; + interrupt_count_ = 0; + mode_ = mode; + + switch (mode) { + case interrupt_mode_t::AS3935_INTERRUPT_UNINITIALIZED: + case interrupt_mode_t::AS3935_INTERRUPT_DETACHED: + break; + case interrupt_mode_t::AS3935_INTERRUPT_NORMAL: +#ifdef AS3935MI_HAS_ATTACHINTERRUPTARG_FUNCTION + attachInterruptArg(digitalPinToInterrupt(irq_), + reinterpret_cast(interruptISR), + this, + RISING); +#else + attachInterrupt(digitalPinToInterrupt(irq_), + interruptISR, + RISING); +#endif + break; + case interrupt_mode_t::AS3935_INTERRUPT_CALIBRATION: + calibration_start_micros_ = 0; + calibration_end_micros_ = 0; +#ifdef AS3935MI_HAS_ATTACHINTERRUPTARG_FUNCTION + attachInterruptArg(digitalPinToInterrupt(irq_), + reinterpret_cast(calibrateISR), + this, + calibration_mode_edgetrigger_trigger_); +#else + attachInterrupt(digitalPinToInterrupt(irq_), + calibrateISR, + calibration_mode_edgetrigger_trigger_); +#endif + break; + } +} + +bool AS3935MI::checkProperlySetToListenMode() { + if (mode_ != AS3935MI::AS3935_INTERRUPT_NORMAL) { + // Nothing to check here, so just return all OK + return true; + } + + if (interrupt_timestamp_ == 0 || interrupt_count_ == 0) { + // No interrupts since last clearing of these interrupt variables. + return true; + } + + int retries = 2; + while (retries > 0 && interrupt_count_ < 2) { + // The possible displayed frequencies are several kHz, + // so time since last interrupt should be less than a msec since + // last trigger if these frequencies are still present. + // + // In 'normal' mode, the interrupt count should never be more than 1 + // as the IRQ pin will be kept high until the interrupt source is read. + // + // N.B. Use the volatile variable here as those may be updated inbetween. + const int32_t msec_passed_since = (int32_t)(millis() - interrupt_timestamp_); + if (msec_passed_since > 2) { + return true; + } + --retries; + delay(1); + } + + // Apparently there is still some frequency being displayed. + setInterruptMode(AS3935MI::AS3935_INTERRUPT_DETACHED); + + delayMicroseconds(AS3935_TIMEOUT); + + // stop displaying LCO on IRQ + displayLcoOnIrq(false); + delayMicroseconds(AS3935_TIMEOUT); + + // restore normal operation + setInterruptMode(AS3935MI::AS3935_INTERRUPT_NORMAL); + + // It wasn't how it should be so return false + return false; +} + + +#ifdef AS3935MI_HAS_ATTACHINTERRUPTARG_FUNCTION +void AS3935MI_IRAM_ATTR AS3935MI::interruptISR(AS3935MI *self) { + self->interrupt_timestamp_ = millis(); + ++(self->interrupt_count_); +} + +void AS3935MI_IRAM_ATTR AS3935MI::calibrateISR(AS3935MI *self) { + // interrupt_count_ is volatile, so we can miss when testing for exactly nr_calibration_samples_ + if (self->interrupt_count_ < self->nr_calibration_samples_) { + ++self->interrupt_count_; + } + else if (self->calibration_end_micros_ == 0ul) { + self->calibration_end_micros_ = static_cast(getMicros64()); + } +} +#else +void AS3935MI_IRAM_ATTR AS3935MI::interruptISR() { + interrupt_timestamp_ = millis(); + ++interrupt_count_; +} + +void AS3935MI_IRAM_ATTR AS3935MI::calibrateISR() { + // interrupt_count_ is volatile, so we can miss when testing for exactly nr_calibration_samples_ + if (interrupt_count_ < nr_calibration_samples_) { + ++interrupt_count_; + } + else if (calibration_end_micros_ == 0ul) { + calibration_end_micros_ = static_cast(getMicros64()); + } +} +#endif + +int32_t AS3935MI::getAntCapFrequency(uint8_t tuningCapacitance) const +{ + constexpr unsigned int nrElements = sizeof(calibration_frequencies_) / sizeof(calibration_frequencies_[0]); + if (tuningCapacitance < nrElements) { + return calibration_frequencies_[tuningCapacitance]; + } + return -1; +} \ No newline at end of file diff --git a/lib/AS3935MI/src/AS3935MI.h b/lib/AS3935MI/src/AS3935MI.h new file mode 100644 index 0000000000..090ff99c33 --- /dev/null +++ b/lib/AS3935MI/src/AS3935MI.h @@ -0,0 +1,574 @@ +//Yet Another Arduino ams AS3935 'Franklin' lightning sensor library +// Copyright (c) 2018-2019 Gregor Christandl +// home: https://bitbucket.org/christandlg/as3935mi +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef AS3935MI_H_ +#define AS3935MI_H_ + +#include + +#if defined(ESP8266) || defined(ESP32) +// When we can't use attachInterruptArg to directly access volatile members, +// we must use static variables in the .cpp file +// This means only a single instance of this class can be used. +// +// When we can use attachInterruptArg, we can use volatile members +// and thus have multiple instances of this class without jumping through hoops +// to avoid issues sharing volatile variables + +#define AS3935MI_HAS_ATTACHINTERRUPTARG_FUNCTION + +#define AS3935MI_IRAM_ATTR IRAM_ATTR +#endif + +#if ESP_IDF_VERSION_MAJOR >= 5 +# include +#endif + +#ifndef AS3935MI_IRAM_ATTR +// Define this attribute as empty for platforms that don't need a special +// IRAM_ATTR for ISR callback functions +#define AS3935MI_IRAM_ATTR +#endif + +// Allow for 3.5% deviation +# define AS3935MI_ALLOWED_DEVIATION 0.035f + +// Division ratio and nr of samples chosen so we expect a +// 500 kHz LCO measurement to take about 18 msec on ESP32 +// On others it will take about 32 msec. +// ESP8266 can't handle > 20 kHz interrupt calls very well, +// therefore set to DR_32 and edge trigger to "RISING" +# ifdef ESP32 + +// Expected LCO frequency for DR_16 = 31250 Hz +# define AS3935MI_LCO_DIVISION_RATIO AS3935MI::AS3935_DR_16 +# define AS3935MI_NR_CALIBRATION_SAMPLES 1000ul +# define AS3935MI_CALIBRATION_MODE_EDGE_TRIGGER CHANGE +# else // ifdef ESP32 + +// Expected LCO frequency for DR_32 = 15625 Hz +# define AS3935MI_LCO_DIVISION_RATIO AS3935MI::AS3935_DR_32 +# define AS3935MI_NR_CALIBRATION_SAMPLES 500ul +# define AS3935MI_CALIBRATION_MODE_EDGE_TRIGGER RISING +# endif // ifdef ESP32 + +class AS3935MI +{ +public: + enum afe_setting_t : uint8_t + { + AS3935_INDOORS = 0b10010, + AS3935_OUTDOORS = 0b01110 + }; + + enum interrupt_name_t : uint8_t + { + AS3935_INT_DUPDATE = 0b0000,//distance estimation has changed due to purging of old events in the statistics, based on the lightning distance estimation algorithm. + AS3935_INT_NH = 0b0001, //noise level too high + AS3935_INT_D = 0b0100, //disturber detected + AS3935_INT_L = 0b1000 //lightning interrupt + }; + + enum wdth_setting_t : uint8_t + { + AS3935_WDTH_0 = 0b0000, + AS3935_WDTH_1 = 0b0001, + AS3935_WDTH_2 = 0b0010, + AS3935_WDTH_3 = 0b0011, + AS3935_WDTH_4 = 0b0100, + AS3935_WDTH_5 = 0b0101, + AS3935_WDTH_6 = 0b0110, + AS3935_WDTH_7 = 0b0111, + AS3935_WDTH_8 = 0b1000, + AS3935_WDTH_9 = 0b1001, + AS3935_WDTH_10 = 0b1010, + AS3935_WDTH_11 = 0b1011, + AS3935_WDTH_12 = 0b1100, + AS3935_WDTH_13 = 0b1101, + AS3935_WDTH_14 = 0b1110, + AS3935_WDTH_15 = 0b1111 + }; + + enum srej_setting_t : uint8_t + { + AS3935_SREJ_0 = 0b0000, + AS3935_SREJ_1 = 0b0001, + AS3935_SREJ_2 = 0b0010, + AS3935_SREJ_3 = 0b0011, + AS3935_SREJ_4 = 0b0100, + AS3935_SREJ_5 = 0b0101, + AS3935_SREJ_6 = 0b0110, + AS3935_SREJ_7 = 0b0111, + AS3935_SREJ_8 = 0b1000, + AS3935_SREJ_9 = 0b1001, + AS3935_SREJ_10 = 0b1010, + AS3935_SREJ_11 = 0b1011, + AS3935_SREJ_12 = 0b1100, + AS3935_SREJ_13 = 0b1101, + AS3935_SREJ_14 = 0b1110, + AS3935_SREJ_15 = 0b1111 + }; + + enum noise_floor_threshold_t : uint8_t + { + AS3935_NFL_0 = 0b000, + AS3935_NFL_1 = 0b001, + AS3935_NFL_2 = 0b010, //default + AS3935_NFL_3 = 0b011, + AS3935_NFL_4 = 0b100, + AS3935_NFL_5 = 0b101, + AS3935_NFL_6 = 0b110, + AS3935_NFL_7 = 0b111, + }; + + enum min_num_lightnings_t : uint8_t + { + AS3935_MNL_1 = 0b00, //minimum number of lightnings: 1 + AS3935_MNL_5 = 0b01, //minimum number of lightnings: 5 + AS3935_MNL_9 = 0b10, //minimum number of lightnings: 9 + AS3935_MNL_16 = 0b11, //minimum number of lightnings: 16 + }; + + enum division_ratio_t : uint8_t + { + AS3935_DR_16 = 0b00, + AS3935_DR_32 = 0b01, + AS3935_DR_64 = 0b10, + AS3935_DR_128 = 0b11 + }; + + + enum class display_frequency_source_t { + LCO, // 500 kHz resonance freq + SRCO, // 1.1 MHz signal + TRCO // 32768 Hz signal + }; + + static const uint8_t AS3935_DST_OOR = 0b111111; //detected lightning was out of range + + AS3935MI(uint8_t irq); + virtual ~AS3935MI(); + + bool begin(); + + /* + @return storm distance in km. */ + uint8_t readStormDistance(); + + /* + @return interrupt source as AS9395::interrupt_name_t. */ + uint8_t readInterruptSource(); + + /* + @return true: powered down, false: powered up. */ + bool readPowerDown(); + + /* + @param enabled: true to power down, false to power up. */ + void writePowerDown(bool enabled); + + /* + @return true if disturbers are masked, false otherwise. */ + bool readMaskDisturbers(); + + /* + @param enabled true to mask disturbers, false otherwise. */ + void writeMaskDisturbers(bool enabled); + + /* + @return AFE setting as afe_setting_t. */ + uint8_t readAFE(); + + /* + @param afe_setting AFE setting as one if afe_setting_t. */ + void writeAFE(uint8_t afe_setting); + + /* + @return current noise floor. */ + uint8_t readNoiseFloorThreshold(); + + /* + writes a noise floor threshold setting to the sensor. + @param threshold as noise_floor_threshold_t*/ + void writeNoiseFloorThreshold(uint8_t threshold); + + /* + @return current noise floor threshold. */ + uint8_t readWatchdogThreshold(); + + /* + @param noise floor threshold setting. */ + void writeWatchdogThreshold(uint8_t noise_floor); + + /* + @return current spike rejection setting as srej_setting_t. */ + uint8_t readSpikeRejection(); + + /* + @param spike rejection setting as srej_setting_t. */ + void writeSpikeRejection(uint8_t threshold); + + /* + @return lightning energy. no physical meaning. */ + uint32_t readEnergy(); + + /* + @return antenna tuning*/ + uint8_t readAntennaTuning(); + + /* + writes an antenna tuning setting to the sensor. */ + bool writeAntennaTuning(uint8_t tuning); + + /* + read the currently set antenna tuning division ratio from the sensor. */ + uint8_t readDivisionRatio(); + + /* + writes an antenna tuning division ratio setting to the sensor. */ + void writeDivisionRatio(uint8_t ratio); + + /* + get the currently set minimum number of lightnings in the last 15 minues before lightning interrupts are issued, as min_num_lightnings_t. */ + uint8_t readMinLightnings(); + + /* + @param minimum number of lightnings in the last 15 minues before lightning interrupts are issued, as min_num_lightnings_t. */ + void writeMinLightnings(uint8_t number); + + /* + resets all registers to default values. */ + void resetToDefaults(); + + /* + calibrates the AS3935 TCRO accordingto procedure in AS3935 datasheet p36. must be done *after* calibrating the resonance frequency. + @return true on success, false otherwise. */ + bool calibrateRCO(); + + // Set the number of samples counted during frequency measurements. + void setFrequencyMeasureNrSamples(uint32_t nrSamples); + + // Set the edge mode trigger for any frequency measurement to either RISING or CHANGE + void setFrequencyMeasureEdgeChange(bool triggerRisingAndFalling); + + // Set the division ratio, only used when measuring LCO (thus only during calibration) + void setCalibrationDivisionRatio(uint8_t division_ratio); + + /* + calibrates the AS3935 antenna's resonance frequency. + @param (by reference, write only) frequency: after return, will hold the frequency the AS3935 + has been calibrated to. + @return true on success, false on failure or if the resonance frequency could not be tuned + to within +-3.5% of 500kHz. */ + bool calibrateResonanceFrequency( + int32_t& frequency, + uint8_t division_ratio); + bool calibrateResonanceFrequency( + int32_t& frequency); + bool calibrateResonanceFrequency(); + + /* + checks if the sensor is connected by attempting to read the AFE gain boost setting. + @return true if the AFE gain boost setting is 0b10010 or 0b01110, false otherwise. */ + bool checkConnection(); + + /* + checks the IRQ pin by instructing the AS3935 to display the antenna's resonance frequency on the IRQ pin + and monitoring the pin for changing levels. IRQ pin interrupt must not be enabled during this test. + The test takes approximately 14ms. the test is considered successful if more than 100 transitions have + been detected (to prevent false positives). + @return true if more than 100 changes in IRQ pin logic level were detected, false otherwise. */ + bool checkIRQ(); + + /* + * clears lightning distance estimation statistics + */ + void clearStatistics(); + + /* + increases the noise floor threshold setting, if possible. + @return true on success, false otherwise. */ + bool decreaseNoiseFloorThreshold(); + bool decreaseNoiseFloorThreshold(uint8_t& nf_lev); + + /* + increases the noise floor threshold setting, if possible. + @return new value on success, 0 otherwise. */ + bool increaseNoiseFloorThreshold(); + bool increaseNoiseFloorThreshold(uint8_t& nf_lev); + + /* + increases the watchdog threshold setting, if possible. + @return true on success, false otherwise. */ + bool decreaseWatchdogThreshold(); + bool decreaseWatchdogThreshold(uint8_t& wdth); + + /* + increases the watchdog threshold setting, if possible. + @return true on success, false otherwise. */ + bool increaseWatchdogThreshold(); + bool increaseWatchdogThreshold(uint8_t& wdth); + + /* + increases the spike rejection setting, if possible. + @return true on success, false otherwise. */ + bool decreaseSpikeRejection(); + bool decreaseSpikeRejection(uint8_t& srej); + + /* + increases the spike rejection setting, if possible. + @return true on success, false otherwise. */ + bool increaseSpikeRejection(); + bool increaseSpikeRejection(uint8_t& srej); + + // Ideally 500 kHz signal divided by the set division ratio + void displayLcoOnIrq(bool enable); + + // Ideally 1.1 MHz signal + void displaySrcoOnIrq(bool enable); + + // Ideally 32.768 kHz signal + void displayTrcoOnIrq(bool enable); + + + bool validateCurrentResonanceFrequency(int32_t& frequency); + + int32_t measureResonanceFrequency(display_frequency_source_t source); + + +private: + enum AS3935_registers_t : uint8_t + { + AS3935_REGISTER_AFE_GB = 0x00, //Analog Frontend Gain Boost + AS3935_REGISTER_PWD = 0x00, //Power Down + AS3935_REGISTER_NF_LEV = 0x01, //Noise Floor Level + AS3935_REGISTER_WDTH = 0x01, //Watchdog threshold + AS3935_REGISTER_CL_STAT = 0x02, //Clear statistics + AS3935_REGISTER_MIN_NUM_LIGH = 0x02, //Minimum number of lightnings + AS3935_REGISTER_SREJ = 0x02, //Spike rejection + AS3935_REGISTER_LCO_FDIV = 0x03, //Frequency division ratio for antenna tuning + AS3935_REGISTER_MASK_DIST = 0x03, //Mask Disturber + AS3935_REGISTER_INT = 0x03, //Interrupt + AS3935_REGISTER_S_LIG_L = 0x04, //Energy of the Single Lightning LSBYTE + AS3935_REGISTER_S_LIG_M = 0x05, //Energy of the Single Lightning MSBYTE + AS3935_REGISTER_S_LIG_MM = 0x06, //Energy of the Single Lightning MMSBYTE + AS3935_REGISTER_DISTANCE = 0x07, //Distance estimation + AS3935_REGISTER_DISP_LCO = 0x08, //Display LCO on IRQ pin + AS3935_REGISTER_DISP_SRCO = 0x08, //Display SRCO on IRQ pin + AS3935_REGISTER_DISP_TRCO = 0x08, //Display TRCO on IRQ pin + AS3935_REGISTER_TUN_CAP = 0x08, //Internal Tuning Capacitors (from 0 to 120pF in steps of 8pF) + AS3935_REGISTER_TRCO_CALIB_DONE = 0x3A, //Calibration of TRCO done (1=successful) + AS3935_REGISTER_TRCO_CALIB_NOK = 0x3A, //Calibration of TRCO unsuccessful (1 = not successful) + AS3935_REGISTER_SRCO_CALIB_DONE = 0x3B, //Calibration of SRCO done (1=successful) + AS3935_REGISTER_SRCO_CALIB_NOK = 0x3B, //Calibration of SRCO unsuccessful (1 = not successful) + AS3935_REGISTER_PRESET_DEFAULT = 0x3C, //Sets all registers in default mode + AS3935_REGISTER_CALIB_RCO = 0x3D //Sets all registers in default mode + }; + + enum AS3935_register_mask_t : uint8_t + { + AS3935_MASK_AFE_GB = 0b00111110, //Analog Frontend Gain Boost + AS3935_MASK_PWD = 0b00000001, //Power Down + AS3935_MASK_NF_LEV = 0b01110000, //Noise Floor Level + AS3935_MASK_WDTH = 0b00001111, //Watchdog threshold + AS3935_MASK_CL_STAT = 0b01000000, //Clear statistics + AS3935_MASK_MIN_NUM_LIGH = 0b00110000, //Minimum number of lightnings + AS3935_MASK_SREJ = 0b00001111, //Spike rejection + AS3935_MASK_LCO_FDIV = 0b11000000, //Frequency division ratio for antenna tuning + AS3935_MASK_MASK_DIST = 0b00100000, //Mask Disturber + AS3935_MASK_INT = 0b00001111, //Interrupt + AS3935_MASK_S_LIG_L = 0b11111111, //Energy of the Single Lightning LSBYTE + AS3935_MASK_S_LIG_M = 0b11111111, //Energy of the Single Lightning MSBYTE + AS3935_MASK_S_LIG_MM = 0b00001111, //Energy of the Single Lightning MMSBYTE + AS3935_MASK_DISTANCE = 0b00111111, //Distance estimation + AS3935_MASK_DISP_LCO = 0b10000000, //Display LCO on IRQ pin + AS3935_MASK_DISP_SRCO = 0b01000000, //Display SRCO on IRQ pin + AS3935_MASK_DISP_TRCO = 0b00100000, //Display TRCO on IRQ pin + AS3935_MASK_TUN_CAP = 0b00001111, //Internal Tuning Capacitors (from 0 to 120pF in steps of 8pF) + AS3935_MASK_TRCO_CALIB_DONE = 0b10000000, //Calibration of TRCO done (1=successful) + AS3935_MASK_TRCO_CALIB_NOK = 0b01000000, //Calibration of TRCO unsuccessful (1 = not successful) + AS3935_MASK_TRCO_CALIB_ALL = 0b11000000, //Calibration of TRCO done (0b10 = successful) + AS3935_MASK_SRCO_CALIB_DONE = 0b10000000, //Calibration of SRCO done (1=successful) + AS3935_MASK_SRCO_CALIB_NOK = 0b01000000, //Calibration of SRCO unsuccessful (1 = not successful) + AS3935_MASK_SRCO_CALIB_ALL = 0b11000000, //Calibration of SRCO done (0b10 = successful) + AS3935_MASK_PRESET_DEFAULT = 0b11111111, //Sets all registers in default mode + AS3935_MASK_CALIB_RCO = 0b11111111 //Sets all registers in default mode + }; + + enum co_divider_t + { + AS3935_DIVIDER_1 = 1, + AS3935_DIVIDER_16 = 16, + AS3935_DIVIDER_32 = 32, + AS3935_DIVIDER_64 = 64, + AS3935_DIVIDER_128 = 128, + }; + + + virtual bool beginInterface() = 0; + + /* + @param mask + @return number of bits to shift value so it fits into mask. */ + uint8_t getMaskShift(uint8_t mask); + + /* + @param register value of register. + @param mask mask of value in register + @return value of masked bits. */ + uint8_t getMaskedBits(uint8_t reg, uint8_t mask); + + /* + @param register value of register + @param mask mask of value in register + @param value value to write into masked area + @param register value with masked bits set to value. */ + uint8_t setMaskedBits(uint8_t reg, uint8_t mask, uint8_t value); + + /* + reads the masked value from the register. + @param reg register to read. + @param mask mask of value. + @return masked value in register. */ + uint8_t readRegisterValue(uint8_t reg, uint8_t mask); + + /* + sets values in a register. + @param reg register to set values in + @param mask bits of register to set value in + @param value value to set */ + void writeRegisterValue(uint8_t reg, uint8_t mask, uint8_t value); + + /* + reads a register from the sensor. must be overwritten by derived classes. + @param reg register to read. + @return register content*/ + virtual uint8_t readRegister(uint8_t reg) = 0; + + /* + writes a register to the sensor. must be overwritten by derived classes. + this function is also used to send direct commands. + @param reg register to write to. + @param value value writeRegister write to register. */ + virtual void writeRegister(uint8_t reg, uint8_t value) = 0; + + + uint32_t computeCalibratedFrequency(int32_t divider); + +public: + + // Internal Tuning Capacitors (from 0 to 120pF in steps of 8pF) + uint32_t measureResonanceFrequency(display_frequency_source_t source, uint8_t tuningCapacitance); + + + enum interrupt_mode_t { + AS3935_INTERRUPT_UNINITIALIZED, + AS3935_INTERRUPT_DETACHED, + AS3935_INTERRUPT_NORMAL, + AS3935_INTERRUPT_CALIBRATION + }; + + interrupt_mode_t getInterruptMode() const { return mode_; } + + uint32_t getInterruptTimestamp() const; + + uint32_t getInterruptCount() const; + + void setInterruptMode(interrupt_mode_t mode); + + // For unknown reasons, the sensor may not have turned off any of the + // displayed frequencies and thus those may still cause lots of unwanted interrupts to be triggered. + bool checkProperlySetToListenMode(); + +private: + static const uint8_t AS3935_DIRECT_CMD = 0x96; + + static const uint32_t AS3935_TIMEOUT = 2000; + + uint8_t irq_; //interrupt pin + + // Tuning cap value is located in the same register as the display LCO/SRCO/TRCO flags + // When those are active the device may not give an ACK when trying to read + // (via I2C) the register to update those display flags + // To overcome this issue, we keep a cache of the tuning cap parameter + // and write directly to the register instead of read/set bits/write. + uint8_t tuning_cap_cache_ = 0; + + AS3935MI::interrupt_mode_t mode_ = AS3935MI::AS3935_INTERRUPT_UNINITIALIZED; + + int calibration_mode_edgetrigger_trigger_ = AS3935MI_CALIBRATION_MODE_EDGE_TRIGGER; + AS3935MI::division_ratio_t calibration_mode_division_ratio_ = AS3935MI_LCO_DIVISION_RATIO; + + +#if ESP_IDF_VERSION_MAJOR >= 5 +#define AS3935MI_VOLATILE_TYPE std::atomic +#else +#define AS3935MI_VOLATILE_TYPE volatile uint32_t +#endif + + +#ifdef AS3935MI_HAS_ATTACHINTERRUPTARG_FUNCTION + static void AS3935MI_IRAM_ATTR interruptISR(AS3935MI *self); + static void AS3935MI_IRAM_ATTR calibrateISR(AS3935MI *self); + + AS3935MI_VOLATILE_TYPE interrupt_timestamp_ = 0; + AS3935MI_VOLATILE_TYPE interrupt_count_ = 0; + + // Store the time micros as 32-bit int so it can be stored and comprared as an atomic operation. + // Expected duration will be much less than 2^32 usec, thus overflow isn't an issue here + AS3935MI_VOLATILE_TYPE calibration_start_micros_ = 0; + AS3935MI_VOLATILE_TYPE calibration_end_micros_ = 0; + + uint32_t nr_calibration_samples_ = AS3935MI_NR_CALIBRATION_SAMPLES; + +#else + static void AS3935MI_IRAM_ATTR interruptISR(); + static void AS3935MI_IRAM_ATTR calibrateISR(); +#endif + + +public: + // Return the result of the last frequency measurement of the given tuning cap index + // @retval -1 when tuningCapacitance is out of range + int32_t getAntCapFrequency(uint8_t tuningCapacitance) const; + + // Return the best ant_cap found during last LCO calibration + // @retval -1 when no LCO calibration was performed + int8_t getCalibratedAntCap() const { + return calibrated_ant_cap_; + } + + // When set to calibrate all ant_cap indices, the LCO calibration is + // effectively set to perform a 'slow' calibration. + // All caps will be tried and also using more samples. + void setCalibrateAllAntCap(bool calibrate_all) { + calibrate_all_ant_cap_ = calibrate_all; + } + + bool getCalibrateAllAntCap() const { + return calibrate_all_ant_cap_; + } + +private: + int32_t calibration_frequencies_[16]{}; + int8_t calibrated_ant_cap_ = -1; + bool calibrate_all_ant_cap_ = true; + +}; + +#endif /* AS3935_H_ */ \ No newline at end of file diff --git a/lib/AS3935MI/src/AS3935SPI.cpp b/lib/AS3935MI/src/AS3935SPI.cpp new file mode 100644 index 0000000000..5f1d81fbcc --- /dev/null +++ b/lib/AS3935MI/src/AS3935SPI.cpp @@ -0,0 +1,28 @@ +//Yet Another Arduino ams AS3935 'Franklin' lightning sensor library +// Copyright (c) 2018-2019 Gregor Christandl +// home: https://bitbucket.org/christandlg/as3935mi +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#include "AS3935SPI.h" + +AS3935SPI::AS3935SPI(uint8_t cs, uint8_t irq) : + AS3935SPIClass(&SPI, cs, irq) +{ +} + +AS3935SPI::~AS3935SPI() +{ +} diff --git a/lib/AS3935MI/src/AS3935SPI.h b/lib/AS3935MI/src/AS3935SPI.h new file mode 100644 index 0000000000..20daa973e9 --- /dev/null +++ b/lib/AS3935MI/src/AS3935SPI.h @@ -0,0 +1,32 @@ +//Yet Another Arduino ams AS3935 'Franklin' lightning sensor library +// Copyright (c) 2018-2019 Gregor Christandl +// home: https://bitbucket.org/christandlg/as3935mi +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef AS3935SPI_H_ +#define AS3935SPI_H_ + +#include "AS3935SPIClass.h" + +class AS3935SPI : + public AS3935SPIClass +{ +public: + AS3935SPI(uint8_t cs, uint8_t irq); + virtual ~AS3935SPI(); +}; + +#endif /* AS3935SPI_H_ */ diff --git a/lib/AS3935MI/src/AS3935SPIClass.cpp b/lib/AS3935MI/src/AS3935SPIClass.cpp new file mode 100644 index 0000000000..dc31019e77 --- /dev/null +++ b/lib/AS3935MI/src/AS3935SPIClass.cpp @@ -0,0 +1,103 @@ +//Yet Another Arduino ams AS3935 'Franklin' lightning sensor library +// Copyright (c) 2018-2019 Gregor Christandl +// home: https://bitbucket.org/christandlg/as3935mi +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#include "AS3935SPIClass.h" + +#ifndef ESP32 +SPISettings AS3935SPIClass::spi_settings_ = SPISettings(1000000, MSBFIRST, SPI_MODE1); +#endif + +AS3935SPIClass::AS3935SPIClass(SPIClass *spi, uint8_t cs, uint8_t irq) : + AS3935MI(irq), + spi_(spi), + cs_(cs) +{ +} + +AS3935SPIClass::~AS3935SPIClass() +{ + spi_ = nullptr; +} + +bool AS3935SPIClass::beginInterface() +{ + if (!spi_) + return false; + + pinMode(cs_, OUTPUT); + digitalWrite(cs_, HIGH); //deselect + + return true; +} + +uint8_t AS3935SPIClass::readRegister(uint8_t reg) +{ + if (!spi_) + return 0; + + uint8_t return_value = 0; + +#ifdef ESP32 + spi_->setBitOrder(MSBFIRST); + spi_->setDataMode(SPI_MODE1); + spi_->setFrequency(1000000); + //spi_->setClockDivider(SPI_CLOCK_DIV16); +#else + spi_->beginTransaction(spi_settings_); +#endif + + digitalWrite(cs_, LOW); //select sensor + + spi_->transfer((reg & 0x3F) | 0x40); //select register and set pin 7 (indicates read) + + return_value = spi_->transfer(0); + + digitalWrite(cs_, HIGH); //deselect sensor + +#ifndef ESP32 + spi_->endTransaction(); +#endif + + return return_value; +} + +void AS3935SPIClass::writeRegister(uint8_t reg, uint8_t value) +{ + if (!spi_) + return; + +#ifdef ESP32 + spi_->setBitOrder(MSBFIRST); + spi_->setDataMode(SPI_MODE1); + spi_->setFrequency(1000000); + //spi_->setClockDivider(SPI_CLOCK_DIV16); +#else + spi_->beginTransaction(spi_settings_); +#endif + + digitalWrite(cs_, LOW); //select sensor + + spi_->transfer((reg & 0x3F)); //select regsiter + spi_->transfer(value); + + digitalWrite(cs_, HIGH); //deselect sensor + +#ifndef ESP32 + spi_->endTransaction(); +#endif +} diff --git a/lib/AS3935MI/src/AS3935SPIClass.h b/lib/AS3935MI/src/AS3935SPIClass.h new file mode 100644 index 0000000000..55041541db --- /dev/null +++ b/lib/AS3935MI/src/AS3935SPIClass.h @@ -0,0 +1,50 @@ +//Yet Another Arduino ams AS3935 'Franklin' lightning sensor library +// Copyright (c) 2018-2019 Gregor Christandl +// home: https://bitbucket.org/christandlg/as3935mi +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef AS3935SPICLASS_H_ +#define AS3935SPICLASS_H_ + +#include "AS3935MI.h" + +#include + +#include + +class AS3935SPIClass : + public AS3935MI +{ +public: + AS3935SPIClass(SPIClass *spi, uint8_t cs, uint8_t irq); + virtual ~AS3935SPIClass(); + +protected: + SPIClass *spi_; + + uint8_t cs_; + + static SPISettings spi_settings_; //spi settings object. is the same for all AS3935 sensors + +private: + virtual bool beginInterface(); + + virtual uint8_t readRegister(uint8_t reg); + + virtual void writeRegister(uint8_t reg, uint8_t value); +}; + +#endif /* AS3935SPICLASS_H_ */ diff --git a/lib/AS3935MI/src/AS3935TwoWire.cpp b/lib/AS3935MI/src/AS3935TwoWire.cpp new file mode 100644 index 0000000000..c1267ea239 --- /dev/null +++ b/lib/AS3935MI/src/AS3935TwoWire.cpp @@ -0,0 +1,81 @@ +//Yet Another Arduino ams AS3935 'Franklin' lightning sensor library +// Copyright (c) 2018-2019 Gregor Christandl +// home: https://bitbucket.org/christandlg/as3935mi +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#include "AS3935TwoWire.h" + +AS3935TwoWire::AS3935TwoWire(TwoWire *wire, uint8_t address, uint8_t irq) : + AS3935MI(irq), + wire_(wire), + address_(address) +{ +} + +AS3935TwoWire::~AS3935TwoWire() +{ + wire_ = nullptr; +} + +bool AS3935TwoWire::beginInterface() +{ + if (!wire_) + return false; + + switch (address_) + { + case AS3935I2C_A01: + case AS3935I2C_A10: + case AS3935I2C_A11: + break; + default: + //return false if an invalid I2C address was given. + return false; + } + + return true; +} + +uint8_t AS3935TwoWire::readRegister(uint8_t reg) +{ + if (!wire_) + return 0; + +#if defined(ARDUINO_SAM_DUE) + //workaround for Arduino Due. The Due seems not to send a repeated start with the code below, so this + //undocumented feature of Wire::requestFrom() is used. can be used on other Arduinos too (tested on Mega2560) + //see this thread for more info: https://forum.arduino.cc/index.php?topic=385377.0 + wire_->requestFrom(address_, 1, reg, 1, true); +#else + wire_->beginTransmission(address_); + wire_->write(reg); + wire_->endTransmission(false); + wire_->requestFrom(address_, static_cast(1)); +#endif + + return wire_->read(); +} + +void AS3935TwoWire::writeRegister(uint8_t reg, uint8_t value) +{ + if (!wire_) + return; + + wire_->beginTransmission(address_); + wire_->write(reg); + wire_->write(value); + wire_->endTransmission(); +} \ No newline at end of file diff --git a/lib/AS3935MI/src/AS3935TwoWire.h b/lib/AS3935MI/src/AS3935TwoWire.h new file mode 100644 index 0000000000..1395288bef --- /dev/null +++ b/lib/AS3935MI/src/AS3935TwoWire.h @@ -0,0 +1,55 @@ +//Yet Another Arduino ams AS3935 'Franklin' lightning sensor library +// Copyright (c) 2018-2019 Gregor Christandl +// home: https://bitbucket.org/christandlg/as3935mi +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef AS3935TWOWIRE_H_ +#define AS3935TWOWIRE_H_ + +#include "AS3935MI.h" + +#include + +#include + +class AS3935TwoWire : + public AS3935MI +{ +public: + enum I2C_address_t : uint8_t + { + AS3935I2C_A01 = 0b01, + AS3935I2C_A10 = 0b10, + AS3935I2C_A11 = 0b11 + }; + + AS3935TwoWire(TwoWire *wire, uint8_t address, uint8_t irq); + virtual ~AS3935TwoWire(); + +protected: + TwoWire *wire_; + + uint8_t address_; + +private: + virtual bool beginInterface(); + + virtual uint8_t readRegister(uint8_t reg); + + virtual void writeRegister(uint8_t reg, uint8_t value); +}; + +#endif /* AS3935TWOWIRE_H_ */ diff --git a/lib/CircularBuffer/CircularBuffer.h b/lib/CircularBuffer/CircularBuffer.h index 8a955753f5..088c675c3a 100644 --- a/lib/CircularBuffer/CircularBuffer.h +++ b/lib/CircularBuffer/CircularBuffer.h @@ -74,6 +74,8 @@ template. - */ - -template -constexpr CircularBuffer::CircularBuffer() : - head(buffer), tail(buffer), count(0) { -} - -template -bool CircularBuffer::unshift(T value) { - if (head == buffer) { - head = buffer + capacity; - } - *--head = value; - if (count == capacity) { - if (tail-- == buffer) { - tail = buffer + capacity - 1; - } - return false; - } else { - if (count++ == 0) { - tail = head; - } - return true; - } -} - -template -bool CircularBuffer::push(T value) { - if (++tail == buffer + capacity) { - tail = buffer; - } - *tail = value; - if (count == capacity) { - if (++head == buffer + capacity) { - head = buffer; - } - return false; - } else { - if (count++ == 0) { - head = tail; - } - return true; - } -} - -template -T CircularBuffer::shift() { - if (count == 0) return *head; - T result = *head++; - if (head >= buffer + capacity) { - head = buffer; - } - count--; - return result; -} - -template -T CircularBuffer::pop() { - if (count == 0) return *tail; - T result = *tail--; - if (tail < buffer) { - tail = buffer + capacity - 1; - } - count--; - return result; -} - -template -T inline CircularBuffer::first() const { - return *head; -} - -template -T inline CircularBuffer::last() const { - return *tail; -} - -template -T CircularBuffer::operator [](IT index) const { - if (index >= count) return *tail; - return *(buffer + ((head - buffer + index) % capacity)); -} - -template -IT inline CircularBuffer::size() const { - return count; -} - -template -IT inline CircularBuffer::available() const { - return capacity - count; -} - -template -bool inline CircularBuffer::isEmpty() const { - return count == 0; -} - -template -bool inline CircularBuffer::isFull() const { - return count == capacity; -} - -template -void inline CircularBuffer::clear() { - head = tail = buffer; - count = 0; -} - -#ifdef CIRCULAR_BUFFER_DEBUG -#include -template -void inline CircularBuffer::debug(Print* out) { - for (IT i = 0; i < capacity; i++) { - int hex = (int)buffer + i; - out->print("["); - out->print(hex, HEX); - out->print("] "); - out->print(*(buffer + i)); - if (head == buffer + i) { - out->print("<-head"); - } - if (tail == buffer + i) { - out->print("<-tail"); - } - out->println(); - } -} - -template -void inline CircularBuffer::debugFn(Print* out, void (*printFunction)(Print*, T)) { - for (IT i = 0; i < capacity; i++) { - int hex = (int)buffer + i; - out->print("["); - out->print(hex, HEX); - out->print("] "); - printFunction(out, *(buffer + i)); - if (head == buffer + i) { - out->print("<-head"); - } - if (tail == buffer + i) { - out->print("<-tail"); - } - out->println(); - } -} -#endif +/* + CircularBuffer.tpp - Circular buffer library for Arduino. + Copyright (c) 2017 Roberto Lo Giacco. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser 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 . + */ + +template +constexpr CircularBuffer::CircularBuffer() : + head(buffer), tail(buffer), count(0) { +} + +template +bool CircularBuffer::unshift(T value) { + if (head == buffer) { + head = buffer + capacity; + } + *--head = value; + if (count == capacity) { + if (tail-- == buffer) { + tail = buffer + capacity - 1; + } + return false; + } else { + if (count++ == 0) { + tail = head; + } + return true; + } +} + +template +bool CircularBuffer::push(T value) { + if (++tail == buffer + capacity) { + tail = buffer; + } + *tail = value; + if (count == capacity) { + if (++head == buffer + capacity) { + head = buffer; + } + return false; + } else { + if (count++ == 0) { + head = tail; + } + return true; + } +} + +template +bool CircularBuffer::set(IT index, T value) { + if (index >= count) return false; + + *(buffer + ((head - buffer + index) % capacity)) = value; + return true; +} + + +template +T CircularBuffer::shift() { + if (count == 0) return *head; + T result = *head++; + if (head >= buffer + capacity) { + head = buffer; + } + count--; + return result; +} + +template +T CircularBuffer::pop() { + if (count == 0) return *tail; + T result = *tail--; + if (tail < buffer) { + tail = buffer + capacity - 1; + } + count--; + return result; +} + +template +T inline CircularBuffer::first() const { + return *head; +} + +template +T inline CircularBuffer::last() const { + return *tail; +} + +template +T CircularBuffer::operator [](IT index) const { + if (index >= count) return *tail; + return *(buffer + ((head - buffer + index) % capacity)); +} + +template +IT inline CircularBuffer::size() const { + return count; +} + +template +IT inline CircularBuffer::available() const { + return capacity - count; +} + +template +bool inline CircularBuffer::isEmpty() const { + return count == 0; +} + +template +bool inline CircularBuffer::isFull() const { + return count == capacity; +} + +template +void inline CircularBuffer::clear() { + head = tail = buffer; + count = 0; +} + +#ifdef CIRCULAR_BUFFER_DEBUG +#include +template +void inline CircularBuffer::debug(Print* out) { + for (IT i = 0; i < capacity; i++) { + int hex = (int)buffer + i; + out->print("["); + out->print(hex, HEX); + out->print("] "); + out->print(*(buffer + i)); + if (head == buffer + i) { + out->print("<-head"); + } + if (tail == buffer + i) { + out->print("<-tail"); + } + out->println(); + } +} + +template +void inline CircularBuffer::debugFn(Print* out, void (*printFunction)(Print*, T)) { + for (IT i = 0; i < capacity; i++) { + int hex = (int)buffer + i; + out->print("["); + out->print(hex, HEX); + out->print("] "); + printFunction(out, *(buffer + i)); + if (head == buffer + i) { + out->print("<-head"); + } + if (tail == buffer + i) { + out->print("<-tail"); + } + out->println(); + } +} +#endif diff --git a/lib/VL53L0X/library.properties b/lib/VL53L0X/library.properties index cac8675faf..5c3209c25a 100644 --- a/lib/VL53L0X/library.properties +++ b/lib/VL53L0X/library.properties @@ -1,9 +1,9 @@ -name=VL53L0X -version=1.3.0 -author=Pololu -maintainer=Pololu -sentence=VL53L0X distance sensor library -paragraph=This is a library for the Arduino IDE that helps interface with ST's VL53L0X distance sensor. -category=Sensors -url=https://github.com/pololu/vl53l0x-arduino -architectures=* +name=VL53L0X +version=1.3.0 +author=Pololu +maintainer=Pololu +sentence=VL53L0X distance sensor library (heavily modified for ESPEasy) +paragraph=This is a library for the Arduino IDE that helps interface with ST's VL53L0X distance sensor. +category=Sensors +url=https://github.com/pololu/vl53l0x-arduino +architectures=* diff --git a/lib/VL53L0X/src/VL53L0X.cpp b/lib/VL53L0X/src/VL53L0X.cpp index f72b383079..72ef0173e6 100644 --- a/lib/VL53L0X/src/VL53L0X.cpp +++ b/lib/VL53L0X/src/VL53L0X.cpp @@ -1,1049 +1,1133 @@ -// Most of the functionality of this library is based on the VL53L0X API -// provided by ST (STSW-IMG005), and some of the explanatory comments are quoted -// or paraphrased from the API source code, API user manual (UM2039), and the -// VL53L0X datasheet. - -#include "VL53L0X.h" -#include - -// Defines ///////////////////////////////////////////////////////////////////// - -// The Arduino two-wire interface uses a 7-bit number for the address, -// and sets the last bit correctly based on reads and writes -#define ADDRESS_DEFAULT 0b0101001 - -// Record the current time to check an upcoming timeout against -#define startTimeout() (timeout_start_ms = millis()) - -// Check if timeout is enabled (set to nonzero value) and has expired -#define checkTimeoutExpired() (io_timeout > 0 && ((uint16_t)(millis() - timeout_start_ms) > io_timeout)) - -// Decode VCSEL (vertical cavity surface emitting laser) pulse period in PCLKs -// from register value -// based on VL53L0X_decode_vcsel_period() -#define decodeVcselPeriod(reg_val) (((reg_val) + 1) << 1) - -// Encode VCSEL pulse period register value from period in PCLKs -// based on VL53L0X_encode_vcsel_period() -#define encodeVcselPeriod(period_pclks) (((period_pclks) >> 1) - 1) - -// Calculate macro period in *nanoseconds* from VCSEL period in PCLKs -// based on VL53L0X_calc_macro_period_ps() -// PLL_period_ps = 1655; macro_period_vclks = 2304 -#define calcMacroPeriod(vcsel_period_pclks) ((((uint32_t)2304 * (vcsel_period_pclks) * 1655) + 500) / 1000) - -// Constructors //////////////////////////////////////////////////////////////// - -VL53L0X::VL53L0X() - : bus(&Wire) - , address(ADDRESS_DEFAULT) - , io_timeout(0) // no timeout - , did_timeout(false) -{ -} - -// Public Methods ////////////////////////////////////////////////////////////// - -void VL53L0X::setAddress(uint8_t new_addr) -{ - writeReg(I2C_SLAVE_DEVICE_ADDRESS, new_addr & 0x7F); - address = new_addr; -} - -// Initialize sensor using sequence based on VL53L0X_DataInit(), -// VL53L0X_StaticInit(), and VL53L0X_PerformRefCalibration(). -// This function does not perform reference SPAD calibration -// (VL53L0X_PerformRefSpadManagement()), since the API user manual says that it -// is performed by ST on the bare modules; it seems like that should work well -// enough unless a cover glass is added. -// If io_2v8 (optional) is true or not given, the sensor is configured for 2V8 -// mode. -bool VL53L0X::init(bool io_2v8) -{ - initResult = F(""); // Clear any previous result - // check model ID register (value specified in datasheet) - uint8_t modelId = readReg(IDENTIFICATION_MODEL_ID); - if (modelId != 0xEE) { // Recognize VL53L0X (0xEE) - initResult = F("VL53L0X: Init: unrecognized Model-ID: 0x"); - initResult += String(modelId, HEX); - return false; - } - - // VL53L0X_DataInit() begin - - // sensor uses 1V8 mode for I/O by default; switch to 2V8 mode if necessary - if (io_2v8) - { - writeReg(VHV_CONFIG_PAD_SCL_SDA__EXTSUP_HV, - readReg(VHV_CONFIG_PAD_SCL_SDA__EXTSUP_HV) | 0x01); // set bit 0 - } - - // "Set I2C standard mode" - writeReg(0x88, 0x00); - - writeReg(0x80, 0x01); - writeReg(0xFF, 0x01); - writeReg(0x00, 0x00); - stop_variable = readReg(0x91); - writeReg(0x00, 0x01); - writeReg(0xFF, 0x00); - writeReg(0x80, 0x00); - - // disable SIGNAL_RATE_MSRC (bit 1) and SIGNAL_RATE_PRE_RANGE (bit 4) limit checks - writeReg(MSRC_CONFIG_CONTROL, readReg(MSRC_CONFIG_CONTROL) | 0x12); - - // set final range signal rate limit to 0.25 MCPS (million counts per second) - setSignalRateLimit(0.25); - - writeReg(SYSTEM_SEQUENCE_CONFIG, 0xFF); - - // VL53L0X_DataInit() end - - // VL53L0X_StaticInit() begin - - uint8_t spad_count; - bool spad_type_is_aperture; - if (!getSpadInfo(&spad_count, &spad_type_is_aperture)) { return false; } - - // The SPAD map (RefGoodSpadMap) is read by VL53L0X_get_info_from_device() in - // the API, but the same data seems to be more easily readable from - // GLOBAL_CONFIG_SPAD_ENABLES_REF_0 through _6, so read it from there - uint8_t ref_spad_map[6]; - readMulti(GLOBAL_CONFIG_SPAD_ENABLES_REF_0, ref_spad_map, 6); - - // -- VL53L0X_set_reference_spads() begin (assume NVM values are valid) - - writeReg(0xFF, 0x01); - writeReg(DYNAMIC_SPAD_REF_EN_START_OFFSET, 0x00); - writeReg(DYNAMIC_SPAD_NUM_REQUESTED_REF_SPAD, 0x2C); - writeReg(0xFF, 0x00); - writeReg(GLOBAL_CONFIG_REF_EN_START_SELECT, 0xB4); - - uint8_t first_spad_to_enable = spad_type_is_aperture ? 12 : 0; // 12 is the first aperture spad - uint8_t spads_enabled = 0; - - for (uint8_t i = 0; i < 48; i++) - { - if (i < first_spad_to_enable || spads_enabled == spad_count) - { - // This bit is lower than the first one that should be enabled, or - // (reference_spad_count) bits have already been enabled, so zero this bit - ref_spad_map[i / 8] &= ~(1 << (i % 8)); - } - else if ((ref_spad_map[i / 8] >> (i % 8)) & 0x1) - { - spads_enabled++; - } - } - - writeMulti(GLOBAL_CONFIG_SPAD_ENABLES_REF_0, ref_spad_map, 6); - - // -- VL53L0X_set_reference_spads() end - - // -- VL53L0X_load_tuning_settings() begin - // DefaultTuningSettings from vl53l0x_tuning.h - - writeReg(0xFF, 0x01); - writeReg(0x00, 0x00); - - writeReg(0xFF, 0x00); - writeReg(0x09, 0x00); - writeReg(0x10, 0x00); - writeReg(0x11, 0x00); - - writeReg(0x24, 0x01); - writeReg(0x25, 0xFF); - writeReg(0x75, 0x00); - - writeReg(0xFF, 0x01); - writeReg(0x4E, 0x2C); - writeReg(0x48, 0x00); - writeReg(0x30, 0x20); - - writeReg(0xFF, 0x00); - writeReg(0x30, 0x09); - writeReg(0x54, 0x00); - writeReg(0x31, 0x04); - writeReg(0x32, 0x03); - writeReg(0x40, 0x83); - writeReg(0x46, 0x25); - writeReg(0x60, 0x00); - writeReg(0x27, 0x00); - writeReg(0x50, 0x06); - writeReg(0x51, 0x00); - writeReg(0x52, 0x96); - writeReg(0x56, 0x08); - writeReg(0x57, 0x30); - writeReg(0x61, 0x00); - writeReg(0x62, 0x00); - writeReg(0x64, 0x00); - writeReg(0x65, 0x00); - writeReg(0x66, 0xA0); - - writeReg(0xFF, 0x01); - writeReg(0x22, 0x32); - writeReg(0x47, 0x14); - writeReg(0x49, 0xFF); - writeReg(0x4A, 0x00); - - writeReg(0xFF, 0x00); - writeReg(0x7A, 0x0A); - writeReg(0x7B, 0x00); - writeReg(0x78, 0x21); - - writeReg(0xFF, 0x01); - writeReg(0x23, 0x34); - writeReg(0x42, 0x00); - writeReg(0x44, 0xFF); - writeReg(0x45, 0x26); - writeReg(0x46, 0x05); - writeReg(0x40, 0x40); - writeReg(0x0E, 0x06); - writeReg(0x20, 0x1A); - writeReg(0x43, 0x40); - - writeReg(0xFF, 0x00); - writeReg(0x34, 0x03); - writeReg(0x35, 0x44); - - writeReg(0xFF, 0x01); - writeReg(0x31, 0x04); - writeReg(0x4B, 0x09); - writeReg(0x4C, 0x05); - writeReg(0x4D, 0x04); - - writeReg(0xFF, 0x00); - writeReg(0x44, 0x00); - writeReg(0x45, 0x20); - writeReg(0x47, 0x08); - writeReg(0x48, 0x28); - writeReg(0x67, 0x00); - writeReg(0x70, 0x04); - writeReg(0x71, 0x01); - writeReg(0x72, 0xFE); - writeReg(0x76, 0x00); - writeReg(0x77, 0x00); - - writeReg(0xFF, 0x01); - writeReg(0x0D, 0x01); - - writeReg(0xFF, 0x00); - writeReg(0x80, 0x01); - writeReg(0x01, 0xF8); - - writeReg(0xFF, 0x01); - writeReg(0x8E, 0x01); - writeReg(0x00, 0x01); - writeReg(0xFF, 0x00); - writeReg(0x80, 0x00); - - // -- VL53L0X_load_tuning_settings() end - - // "Set interrupt config to new sample ready" - // -- VL53L0X_SetGpioConfig() begin - - writeReg(SYSTEM_INTERRUPT_CONFIG_GPIO, 0x04); - writeReg(GPIO_HV_MUX_ACTIVE_HIGH, readReg(GPIO_HV_MUX_ACTIVE_HIGH) & ~0x10); // active low - writeReg(SYSTEM_INTERRUPT_CLEAR, 0x01); - - // -- VL53L0X_SetGpioConfig() end - - measurement_timing_budget_us = getMeasurementTimingBudget(); - - // "Disable MSRC and TCC by default" - // MSRC = Minimum Signal Rate Check - // TCC = Target CentreCheck - // -- VL53L0X_SetSequenceStepEnable() begin - - writeReg(SYSTEM_SEQUENCE_CONFIG, 0xE8); - - // -- VL53L0X_SetSequenceStepEnable() end - - // "Recalculate timing budget" - setMeasurementTimingBudget(measurement_timing_budget_us); - - // VL53L0X_StaticInit() end - - // VL53L0X_PerformRefCalibration() begin (VL53L0X_perform_ref_calibration()) - - // -- VL53L0X_perform_vhv_calibration() begin - - writeReg(SYSTEM_SEQUENCE_CONFIG, 0x01); - if (!performSingleRefCalibration(0x40)) { return false; } - - // -- VL53L0X_perform_vhv_calibration() end - - // -- VL53L0X_perform_phase_calibration() begin - - writeReg(SYSTEM_SEQUENCE_CONFIG, 0x02); - if (!performSingleRefCalibration(0x00)) { return false; } - - // -- VL53L0X_perform_phase_calibration() end - - // "restore the previous Sequence Config" - writeReg(SYSTEM_SEQUENCE_CONFIG, 0xE8); - - // VL53L0X_PerformRefCalibration() end - - return true; -} - -// Write an 8-bit register -void VL53L0X::writeReg(uint8_t reg, uint8_t value) -{ - bus->beginTransmission(address); - bus->write(reg); - bus->write(value); - last_status = bus->endTransmission(); -} - -// Write a 16-bit register -void VL53L0X::writeReg16Bit(uint8_t reg, uint16_t value) -{ - bus->beginTransmission(address); - bus->write(reg); - bus->write((value >> 8) & 0xFF); // value high byte - bus->write( value & 0xFF); // value low byte - last_status = bus->endTransmission(); -} - -// Write a 32-bit register -void VL53L0X::writeReg32Bit(uint8_t reg, uint32_t value) -{ - bus->beginTransmission(address); - bus->write(reg); - bus->write((value >> 24) & 0xFF); // value highest byte - bus->write((value >> 16) & 0xFF); - bus->write((value >> 8) & 0xFF); - bus->write( value & 0xFF); // value lowest byte - last_status = bus->endTransmission(); -} - -// Read an 8-bit register -uint8_t VL53L0X::readReg(uint8_t reg) -{ - uint8_t value; - - bus->beginTransmission(address); - bus->write(reg); - last_status = bus->endTransmission(); - - bus->requestFrom(address, (uint8_t)1); - value = bus->read(); - - return value; -} - -// Read a 16-bit register -uint16_t VL53L0X::readReg16Bit(uint8_t reg) -{ - uint16_t value; - - bus->beginTransmission(address); - bus->write(reg); - last_status = bus->endTransmission(); - - bus->requestFrom(address, (uint8_t)2); - value = (uint16_t)bus->read() << 8; // value high byte - value |= bus->read(); // value low byte - - return value; -} - -// Read a 32-bit register -uint32_t VL53L0X::readReg32Bit(uint8_t reg) -{ - uint32_t value; - - bus->beginTransmission(address); - bus->write(reg); - last_status = bus->endTransmission(); - - bus->requestFrom(address, (uint8_t)4); - value = (uint32_t)bus->read() << 24; // value highest byte - value |= (uint32_t)bus->read() << 16; - value |= (uint16_t)bus->read() << 8; - value |= bus->read(); // value lowest byte - - return value; -} - -// Write an arbitrary number of bytes from the given array to the sensor, -// starting at the given register -void VL53L0X::writeMulti(uint8_t reg, uint8_t const * src, uint8_t count) -{ - bus->beginTransmission(address); - bus->write(reg); - - while (count-- > 0) - { - bus->write(*(src++)); - } - - last_status = bus->endTransmission(); -} - -// Read an arbitrary number of bytes from the sensor, starting at the given -// register, into the given array -void VL53L0X::readMulti(uint8_t reg, uint8_t * dst, uint8_t count) -{ - bus->beginTransmission(address); - bus->write(reg); - last_status = bus->endTransmission(); - - bus->requestFrom(address, count); - - while (count-- > 0) - { - *(dst++) = bus->read(); - } -} - -// Set the return signal rate limit check value in units of MCPS (mega counts -// per second). "This represents the amplitude of the signal reflected from the -// target and detected by the device"; setting this limit presumably determines -// the minimum measurement necessary for the sensor to report a valid reading. -// Setting a lower limit increases the potential range of the sensor but also -// seems to increase the likelihood of getting an inaccurate reading because of -// unwanted reflections from objects other than the intended target. -// Defaults to 0.25 MCPS as initialized by the ST API and this library. -bool VL53L0X::setSignalRateLimit(float limit_Mcps) -{ - if (limit_Mcps < 0 || limit_Mcps > 511.99f) { return false; } - - // Q9.7 fixed point format (9 integer bits, 7 fractional bits) - writeReg16Bit(FINAL_RANGE_CONFIG_MIN_COUNT_RATE_RTN_LIMIT, limit_Mcps * (1 << 7)); - return true; -} - -// Get the return signal rate limit check value in MCPS -float VL53L0X::getSignalRateLimit() -{ - return (float)readReg16Bit(FINAL_RANGE_CONFIG_MIN_COUNT_RATE_RTN_LIMIT) / (1 << 7); -} - -// Set the measurement timing budget in microseconds, which is the time allowed -// for one measurement; the ST API and this library take care of splitting the -// timing budget among the sub-steps in the ranging sequence. A longer timing -// budget allows for more accurate measurements. Increasing the budget by a -// factor of N decreases the range measurement standard deviation by a factor of -// sqrt(N). Defaults to about 33 milliseconds; the minimum is 20 ms. -// based on VL53L0X_set_measurement_timing_budget_micro_seconds() -bool VL53L0X::setMeasurementTimingBudget(uint32_t budget_us) -{ - SequenceStepEnables enables; - SequenceStepTimeouts timeouts; - - uint16_t const StartOverhead = 1910; - uint16_t const EndOverhead = 960; - uint16_t const MsrcOverhead = 660; - uint16_t const TccOverhead = 590; - uint16_t const DssOverhead = 690; - uint16_t const PreRangeOverhead = 660; - uint16_t const FinalRangeOverhead = 550; - - uint32_t const MinTimingBudget = 20000; - - if (budget_us < MinTimingBudget) { return false; } - - uint32_t used_budget_us = StartOverhead + EndOverhead; - - getSequenceStepEnables(&enables); - getSequenceStepTimeouts(&enables, &timeouts); - - if (enables.tcc) - { - used_budget_us += (timeouts.msrc_dss_tcc_us + TccOverhead); - } - - if (enables.dss) - { - used_budget_us += 2 * (timeouts.msrc_dss_tcc_us + DssOverhead); - } - else if (enables.msrc) - { - used_budget_us += (timeouts.msrc_dss_tcc_us + MsrcOverhead); - } - - if (enables.pre_range) - { - used_budget_us += (timeouts.pre_range_us + PreRangeOverhead); - } - - if (enables.final_range) - { - used_budget_us += FinalRangeOverhead; - - // "Note that the final range timeout is determined by the timing - // budget and the sum of all other timeouts within the sequence. - // If there is no room for the final range timeout, then an error - // will be set. Otherwise the remaining time will be applied to - // the final range." - - if (used_budget_us > budget_us) - { - // "Requested timeout too big." - return false; - } - - uint32_t final_range_timeout_us = budget_us - used_budget_us; - - // set_sequence_step_timeout() begin - // (SequenceStepId == VL53L0X_SEQUENCESTEP_FINAL_RANGE) - - // "For the final range timeout, the pre-range timeout - // must be added. To do this both final and pre-range - // timeouts must be expressed in macro periods MClks - // because they have different vcsel periods." - - uint32_t final_range_timeout_mclks = - timeoutMicrosecondsToMclks(final_range_timeout_us, - timeouts.final_range_vcsel_period_pclks); - - if (enables.pre_range) - { - final_range_timeout_mclks += timeouts.pre_range_mclks; - } - - writeReg16Bit(FINAL_RANGE_CONFIG_TIMEOUT_MACROP_HI, - encodeTimeout(final_range_timeout_mclks)); - - // set_sequence_step_timeout() end - - measurement_timing_budget_us = budget_us; // store for internal reuse - } - return true; -} - -// Get the measurement timing budget in microseconds -// based on VL53L0X_get_measurement_timing_budget_micro_seconds() -// in us -uint32_t VL53L0X::getMeasurementTimingBudget() -{ - SequenceStepEnables enables; - SequenceStepTimeouts timeouts; - - uint16_t const StartOverhead = 1910; - uint16_t const EndOverhead = 960; - uint16_t const MsrcOverhead = 660; - uint16_t const TccOverhead = 590; - uint16_t const DssOverhead = 690; - uint16_t const PreRangeOverhead = 660; - uint16_t const FinalRangeOverhead = 550; - - // "Start and end overhead times always present" - uint32_t budget_us = StartOverhead + EndOverhead; - - getSequenceStepEnables(&enables); - getSequenceStepTimeouts(&enables, &timeouts); - - if (enables.tcc) - { - budget_us += (timeouts.msrc_dss_tcc_us + TccOverhead); - } - - if (enables.dss) - { - budget_us += 2 * (timeouts.msrc_dss_tcc_us + DssOverhead); - } - else if (enables.msrc) - { - budget_us += (timeouts.msrc_dss_tcc_us + MsrcOverhead); - } - - if (enables.pre_range) - { - budget_us += (timeouts.pre_range_us + PreRangeOverhead); - } - - if (enables.final_range) - { - budget_us += (timeouts.final_range_us + FinalRangeOverhead); - } - - measurement_timing_budget_us = budget_us; // store for internal reuse - return budget_us; -} - -// Set the VCSEL (vertical cavity surface emitting laser) pulse period for the -// given period type (pre-range or final range) to the given value in PCLKs. -// Longer periods seem to increase the potential range of the sensor. -// Valid values are (even numbers only): -// pre: 12 to 18 (initialized default: 14) -// final: 8 to 14 (initialized default: 10) -// based on VL53L0X_set_vcsel_pulse_period() -bool VL53L0X::setVcselPulsePeriod(vcselPeriodType type, uint8_t period_pclks) -{ - uint8_t vcsel_period_reg = encodeVcselPeriod(period_pclks); - - SequenceStepEnables enables; - SequenceStepTimeouts timeouts; - - getSequenceStepEnables(&enables); - getSequenceStepTimeouts(&enables, &timeouts); - - // "Apply specific settings for the requested clock period" - // "Re-calculate and apply timeouts, in macro periods" - - // "When the VCSEL period for the pre or final range is changed, - // the corresponding timeout must be read from the device using - // the current VCSEL period, then the new VCSEL period can be - // applied. The timeout then must be written back to the device - // using the new VCSEL period. - // - // For the MSRC timeout, the same applies - this timeout being - // dependant on the pre-range vcsel period." - - - if (type == VcselPeriodPreRange) - { - // "Set phase check limits" - switch (period_pclks) - { - case 12: - writeReg(PRE_RANGE_CONFIG_VALID_PHASE_HIGH, 0x18); - break; - - case 14: - writeReg(PRE_RANGE_CONFIG_VALID_PHASE_HIGH, 0x30); - break; - - case 16: - writeReg(PRE_RANGE_CONFIG_VALID_PHASE_HIGH, 0x40); - break; - - case 18: - writeReg(PRE_RANGE_CONFIG_VALID_PHASE_HIGH, 0x50); - break; - - default: - // invalid period - return false; - } - writeReg(PRE_RANGE_CONFIG_VALID_PHASE_LOW, 0x08); - - // apply new VCSEL period - writeReg(PRE_RANGE_CONFIG_VCSEL_PERIOD, vcsel_period_reg); - - // update timeouts - - // set_sequence_step_timeout() begin - // (SequenceStepId == VL53L0X_SEQUENCESTEP_PRE_RANGE) - - uint16_t new_pre_range_timeout_mclks = - timeoutMicrosecondsToMclks(timeouts.pre_range_us, period_pclks); - - writeReg16Bit(PRE_RANGE_CONFIG_TIMEOUT_MACROP_HI, - encodeTimeout(new_pre_range_timeout_mclks)); - - // set_sequence_step_timeout() end - - // set_sequence_step_timeout() begin - // (SequenceStepId == VL53L0X_SEQUENCESTEP_MSRC) - - uint16_t new_msrc_timeout_mclks = - timeoutMicrosecondsToMclks(timeouts.msrc_dss_tcc_us, period_pclks); - - writeReg(MSRC_CONFIG_TIMEOUT_MACROP, - (new_msrc_timeout_mclks > 256) ? 255 : (new_msrc_timeout_mclks - 1)); - - // set_sequence_step_timeout() end - } - else if (type == VcselPeriodFinalRange) - { - switch (period_pclks) - { - case 8: - writeReg(FINAL_RANGE_CONFIG_VALID_PHASE_HIGH, 0x10); - writeReg(FINAL_RANGE_CONFIG_VALID_PHASE_LOW, 0x08); - writeReg(GLOBAL_CONFIG_VCSEL_WIDTH, 0x02); - writeReg(ALGO_PHASECAL_CONFIG_TIMEOUT, 0x0C); - writeReg(0xFF, 0x01); - writeReg(ALGO_PHASECAL_LIM, 0x30); - writeReg(0xFF, 0x00); - break; - - case 10: - writeReg(FINAL_RANGE_CONFIG_VALID_PHASE_HIGH, 0x28); - writeReg(FINAL_RANGE_CONFIG_VALID_PHASE_LOW, 0x08); - writeReg(GLOBAL_CONFIG_VCSEL_WIDTH, 0x03); - writeReg(ALGO_PHASECAL_CONFIG_TIMEOUT, 0x09); - writeReg(0xFF, 0x01); - writeReg(ALGO_PHASECAL_LIM, 0x20); - writeReg(0xFF, 0x00); - break; - - case 12: - writeReg(FINAL_RANGE_CONFIG_VALID_PHASE_HIGH, 0x38); - writeReg(FINAL_RANGE_CONFIG_VALID_PHASE_LOW, 0x08); - writeReg(GLOBAL_CONFIG_VCSEL_WIDTH, 0x03); - writeReg(ALGO_PHASECAL_CONFIG_TIMEOUT, 0x08); - writeReg(0xFF, 0x01); - writeReg(ALGO_PHASECAL_LIM, 0x20); - writeReg(0xFF, 0x00); - break; - - case 14: - writeReg(FINAL_RANGE_CONFIG_VALID_PHASE_HIGH, 0x48); - writeReg(FINAL_RANGE_CONFIG_VALID_PHASE_LOW, 0x08); - writeReg(GLOBAL_CONFIG_VCSEL_WIDTH, 0x03); - writeReg(ALGO_PHASECAL_CONFIG_TIMEOUT, 0x07); - writeReg(0xFF, 0x01); - writeReg(ALGO_PHASECAL_LIM, 0x20); - writeReg(0xFF, 0x00); - break; - - default: - // invalid period - return false; - } - - // apply new VCSEL period - writeReg(FINAL_RANGE_CONFIG_VCSEL_PERIOD, vcsel_period_reg); - - // update timeouts - - // set_sequence_step_timeout() begin - // (SequenceStepId == VL53L0X_SEQUENCESTEP_FINAL_RANGE) - - // "For the final range timeout, the pre-range timeout - // must be added. To do this both final and pre-range - // timeouts must be expressed in macro periods MClks - // because they have different vcsel periods." - - uint16_t new_final_range_timeout_mclks = - timeoutMicrosecondsToMclks(timeouts.final_range_us, period_pclks); - - if (enables.pre_range) - { - new_final_range_timeout_mclks += timeouts.pre_range_mclks; - } - - writeReg16Bit(FINAL_RANGE_CONFIG_TIMEOUT_MACROP_HI, - encodeTimeout(new_final_range_timeout_mclks)); - - // set_sequence_step_timeout end - } - else - { - // invalid type - return false; - } - - // "Finally, the timing budget must be re-applied" - - setMeasurementTimingBudget(measurement_timing_budget_us); - - // "Perform the phase calibration. This is needed after changing on vcsel period." - // VL53L0X_perform_phase_calibration() begin - - uint8_t sequence_config = readReg(SYSTEM_SEQUENCE_CONFIG); - writeReg(SYSTEM_SEQUENCE_CONFIG, 0x02); - performSingleRefCalibration(0x0); - writeReg(SYSTEM_SEQUENCE_CONFIG, sequence_config); - - // VL53L0X_perform_phase_calibration() end - - return true; -} - -// Get the VCSEL pulse period in PCLKs for the given period type. -// based on VL53L0X_get_vcsel_pulse_period() -uint8_t VL53L0X::getVcselPulsePeriod(vcselPeriodType type) -{ - if (type == VcselPeriodPreRange) - { - return decodeVcselPeriod(readReg(PRE_RANGE_CONFIG_VCSEL_PERIOD)); - } - else if (type == VcselPeriodFinalRange) - { - return decodeVcselPeriod(readReg(FINAL_RANGE_CONFIG_VCSEL_PERIOD)); - } - else { return 255; } -} - -// Start continuous ranging measurements. If period_ms (optional) is 0 or not -// given, continuous back-to-back mode is used (the sensor takes measurements as -// often as possible); otherwise, continuous timed mode is used, with the given -// inter-measurement period in milliseconds determining how often the sensor -// takes a measurement. -// based on VL53L0X_StartMeasurement() -void VL53L0X::startContinuous(uint32_t period_ms) -{ - writeReg(0x80, 0x01); - writeReg(0xFF, 0x01); - writeReg(0x00, 0x00); - writeReg(0x91, stop_variable); - writeReg(0x00, 0x01); - writeReg(0xFF, 0x00); - writeReg(0x80, 0x00); - - if (period_ms != 0) - { - // continuous timed mode - - // VL53L0X_SetInterMeasurementPeriodMilliSeconds() begin - - uint16_t osc_calibrate_val = readReg16Bit(OSC_CALIBRATE_VAL); - - if (osc_calibrate_val != 0) - { - period_ms *= osc_calibrate_val; - } - - writeReg32Bit(SYSTEM_INTERMEASUREMENT_PERIOD, period_ms); - - // VL53L0X_SetInterMeasurementPeriodMilliSeconds() end - - writeReg(SYSRANGE_START, 0x04); // VL53L0X_REG_SYSRANGE_MODE_TIMED - } - else - { - // continuous back-to-back mode - writeReg(SYSRANGE_START, 0x02); // VL53L0X_REG_SYSRANGE_MODE_BACKTOBACK - } -} - -// Stop continuous measurements -// based on VL53L0X_StopMeasurement() -void VL53L0X::stopContinuous() -{ - writeReg(SYSRANGE_START, 0x01); // VL53L0X_REG_SYSRANGE_MODE_SINGLESHOT - - writeReg(0xFF, 0x01); - writeReg(0x00, 0x00); - writeReg(0x91, 0x00); - writeReg(0x00, 0x01); - writeReg(0xFF, 0x00); -} - -// Returns a range reading in millimeters when continuous mode is active -// (readRangeSingleMillimeters() also calls this function after starting a -// single-shot range measurement) -uint16_t VL53L0X::readRangeContinuousMillimeters() -{ - startTimeout(); - while ((readReg(RESULT_INTERRUPT_STATUS) & 0x07) == 0) - { - if (checkTimeoutExpired()) - { - did_timeout = true; - return 65535; - } - } - - // assumptions: Linearity Corrective Gain is 1000 (default); - // fractional ranging is not enabled - uint16_t range = readReg16Bit(RESULT_RANGE_STATUS + 10); - - writeReg(SYSTEM_INTERRUPT_CLEAR, 0x01); - - return range; -} - -// Performs a single-shot range measurement and returns the reading in -// millimeters -// based on VL53L0X_PerformSingleRangingMeasurement() -uint16_t VL53L0X::readRangeSingleMillimeters() -{ - writeReg(0x80, 0x01); - writeReg(0xFF, 0x01); - writeReg(0x00, 0x00); - writeReg(0x91, stop_variable); - writeReg(0x00, 0x01); - writeReg(0xFF, 0x00); - writeReg(0x80, 0x00); - - writeReg(SYSRANGE_START, 0x01); - - // "Wait until start bit has been cleared" - startTimeout(); - while (readReg(SYSRANGE_START) & 0x01) - { - if (checkTimeoutExpired()) - { - did_timeout = true; - return 65535; - } - } - - return readRangeContinuousMillimeters(); -} - -// Did a timeout occur in one of the read functions since the last call to -// timeoutOccurred()? -bool VL53L0X::timeoutOccurred() -{ - bool tmp = did_timeout; - did_timeout = false; - return tmp; -} - -// Private Methods ///////////////////////////////////////////////////////////// - -// Get reference SPAD (single photon avalanche diode) count and type -// based on VL53L0X_get_info_from_device(), -// but only gets reference SPAD count and type -bool VL53L0X::getSpadInfo(uint8_t * count, bool * type_is_aperture) -{ - uint8_t tmp; - - writeReg(0x80, 0x01); - writeReg(0xFF, 0x01); - writeReg(0x00, 0x00); - - writeReg(0xFF, 0x06); - writeReg(0x83, readReg(0x83) | 0x04); - writeReg(0xFF, 0x07); - writeReg(0x81, 0x01); - - writeReg(0x80, 0x01); - - writeReg(0x94, 0x6b); - writeReg(0x83, 0x00); - startTimeout(); - while (readReg(0x83) == 0x00) - { - if (checkTimeoutExpired()) { return false; } - } - writeReg(0x83, 0x01); - tmp = readReg(0x92); - - *count = tmp & 0x7f; - *type_is_aperture = (tmp >> 7) & 0x01; - - writeReg(0x81, 0x00); - writeReg(0xFF, 0x06); - writeReg(0x83, readReg(0x83) & ~0x04); - writeReg(0xFF, 0x01); - writeReg(0x00, 0x01); - - writeReg(0xFF, 0x00); - writeReg(0x80, 0x00); - - return true; -} - -// Get sequence step enables -// based on VL53L0X_GetSequenceStepEnables() -void VL53L0X::getSequenceStepEnables(SequenceStepEnables * enables) -{ - uint8_t sequence_config = readReg(SYSTEM_SEQUENCE_CONFIG); - - enables->tcc = (sequence_config >> 4) & 0x1; - enables->dss = (sequence_config >> 3) & 0x1; - enables->msrc = (sequence_config >> 2) & 0x1; - enables->pre_range = (sequence_config >> 6) & 0x1; - enables->final_range = (sequence_config >> 7) & 0x1; -} - -// Get sequence step timeouts -// based on get_sequence_step_timeout(), -// but gets all timeouts instead of just the requested one, and also stores -// intermediate values -void VL53L0X::getSequenceStepTimeouts(SequenceStepEnables const * enables, SequenceStepTimeouts * timeouts) -{ - timeouts->pre_range_vcsel_period_pclks = getVcselPulsePeriod(VcselPeriodPreRange); - - timeouts->msrc_dss_tcc_mclks = readReg(MSRC_CONFIG_TIMEOUT_MACROP) + 1; - timeouts->msrc_dss_tcc_us = - timeoutMclksToMicroseconds(timeouts->msrc_dss_tcc_mclks, - timeouts->pre_range_vcsel_period_pclks); - - timeouts->pre_range_mclks = - decodeTimeout(readReg16Bit(PRE_RANGE_CONFIG_TIMEOUT_MACROP_HI)); - timeouts->pre_range_us = - timeoutMclksToMicroseconds(timeouts->pre_range_mclks, - timeouts->pre_range_vcsel_period_pclks); - - timeouts->final_range_vcsel_period_pclks = getVcselPulsePeriod(VcselPeriodFinalRange); - - timeouts->final_range_mclks = - decodeTimeout(readReg16Bit(FINAL_RANGE_CONFIG_TIMEOUT_MACROP_HI)); - - if (enables->pre_range) - { - timeouts->final_range_mclks -= timeouts->pre_range_mclks; - } - - timeouts->final_range_us = - timeoutMclksToMicroseconds(timeouts->final_range_mclks, - timeouts->final_range_vcsel_period_pclks); -} - -// Decode sequence step timeout in MCLKs from register value -// based on VL53L0X_decode_timeout() -// Note: the original function returned a uint32_t, but the return value is -// always stored in a uint16_t. -uint16_t VL53L0X::decodeTimeout(uint16_t reg_val) -{ - // format: "(LSByte * 2^MSByte) + 1" - return (uint16_t)((reg_val & 0x00FF) << - (uint16_t)((reg_val & 0xFF00) >> 8)) + 1; -} - -// Encode sequence step timeout register value from timeout in MCLKs -// based on VL53L0X_encode_timeout() -uint16_t VL53L0X::encodeTimeout(uint32_t timeout_mclks) -{ - // format: "(LSByte * 2^MSByte) + 1" - - uint32_t ls_byte = 0; - uint16_t ms_byte = 0; - - if (timeout_mclks > 0) - { - ls_byte = timeout_mclks - 1; - - while ((ls_byte & 0xFFFFFF00) > 0) - { - ls_byte >>= 1; - ms_byte++; - } - - return (ms_byte << 8) | (ls_byte & 0xFF); - } - else { return 0; } -} - -// Convert sequence step timeout from MCLKs to microseconds with given VCSEL period in PCLKs -// based on VL53L0X_calc_timeout_us() -uint32_t VL53L0X::timeoutMclksToMicroseconds(uint16_t timeout_period_mclks, uint8_t vcsel_period_pclks) -{ - uint32_t macro_period_ns = calcMacroPeriod(vcsel_period_pclks); - - return ((timeout_period_mclks * macro_period_ns) + 500) / 1000; -} - -// Convert sequence step timeout from microseconds to MCLKs with given VCSEL period in PCLKs -// based on VL53L0X_calc_timeout_mclks() -uint32_t VL53L0X::timeoutMicrosecondsToMclks(uint32_t timeout_period_us, uint8_t vcsel_period_pclks) -{ - uint32_t macro_period_ns = calcMacroPeriod(vcsel_period_pclks); - - return (((timeout_period_us * 1000) + (macro_period_ns / 2)) / macro_period_ns); -} - - -// based on VL53L0X_perform_single_ref_calibration() -bool VL53L0X::performSingleRefCalibration(uint8_t vhv_init_byte) -{ - writeReg(SYSRANGE_START, 0x01 | vhv_init_byte); // VL53L0X_REG_SYSRANGE_MODE_START_STOP - - startTimeout(); - while ((readReg(RESULT_INTERRUPT_STATUS) & 0x07) == 0) - { - if (checkTimeoutExpired()) { return false; } - } - - writeReg(SYSTEM_INTERRUPT_CLEAR, 0x01); - - writeReg(SYSRANGE_START, 0x00); - - return true; -} - -// Simply return the string, it is cleared and could be set during init -String VL53L0X::getInitResult() { - return initResult; +// Most of the functionality of this library is based on the VL53L0X API +// provided by ST (STSW-IMG005), and some of the explanatory comments are quoted +// or paraphrased from the API source code, API user manual (UM2039), and the +// VL53L0X datasheet. + +#include "VL53L0X.h" +#include + +// Defines ///////////////////////////////////////////////////////////////////// + +// The Arduino two-wire interface uses a 7-bit number for the address, +// and sets the last bit correctly based on reads and writes +#define ADDRESS_DEFAULT 0b0101001 + +// Record the current time to check an upcoming timeout against +#define startTimeout() (timeout_start_ms = millis()) + +// Check if timeout is enabled (set to nonzero value) and has expired +#define checkTimeoutExpired() (io_timeout > 0 && ((uint16_t)(millis() - timeout_start_ms) > io_timeout)) + +// Decode VCSEL (vertical cavity surface emitting laser) pulse period in PCLKs +// from register value +// based on VL53L0X_decode_vcsel_period() +#define decodeVcselPeriod(reg_val) (((reg_val) + 1) << 1) + +// Encode VCSEL pulse period register value from period in PCLKs +// based on VL53L0X_encode_vcsel_period() +#define encodeVcselPeriod(period_pclks) (((period_pclks) >> 1) - 1) + +// Calculate macro period in *nanoseconds* from VCSEL period in PCLKs +// based on VL53L0X_calc_macro_period_ps() +// PLL_period_ps = 1655; macro_period_vclks = 2304 +#define calcMacroPeriod(vcsel_period_pclks) ((((uint32_t)2304 * (vcsel_period_pclks) * 1655) + 500) / 1000) + +// Constructors //////////////////////////////////////////////////////////////// + +VL53L0X::VL53L0X() + : bus(&Wire) + , address(ADDRESS_DEFAULT) + , io_timeout(0) // no timeout + , did_timeout(false) +{ +} + +// Public Methods ////////////////////////////////////////////////////////////// + +void VL53L0X::setAddress(uint8_t new_addr) +{ + writeReg(I2C_SLAVE_DEVICE_ADDRESS, new_addr & 0x7F); + address = new_addr; +} + +// Initialize sensor using sequence based on VL53L0X_DataInit(), +// VL53L0X_StaticInit(), and VL53L0X_PerformRefCalibration(). +// This function does not perform reference SPAD calibration +// (VL53L0X_PerformRefSpadManagement()), since the API user manual says that it +// is performed by ST on the bare modules; it seems like that should work well +// enough unless a cover glass is added. +// If io_2v8 (optional) is true or not given, the sensor is configured for 2V8 +// mode. +bool VL53L0X::init(bool io_2v8) +{ + initResult = F(""); // Clear any previous result + // check model ID register (value specified in datasheet) + uint8_t modelId = readReg(IDENTIFICATION_MODEL_ID); + if (modelId != 0xEE) { // Recognize VL53L0X (0xEE) + initResult = F("VL53L0X: Init: unrecognized Model-ID: 0x"); + initResult += String(modelId, HEX); + return false; + } + + // VL53L0X_DataInit() begin + + // sensor uses 1V8 mode for I/O by default; switch to 2V8 mode if necessary + if (io_2v8) + { + writeReg(VHV_CONFIG_PAD_SCL_SDA__EXTSUP_HV, + readReg(VHV_CONFIG_PAD_SCL_SDA__EXTSUP_HV) | 0x01); // set bit 0 + } + + // "Set I2C standard mode" + writeReg(0x88, 0x00); + + writeReg(0x80, 0x01); + writeReg(0xFF, 0x01); + writeReg(0x00, 0x00); + stop_variable = readReg(0x91); + writeReg(0x00, 0x01); + writeReg(0xFF, 0x00); + writeReg(0x80, 0x00); + + // disable SIGNAL_RATE_MSRC (bit 1) and SIGNAL_RATE_PRE_RANGE (bit 4) limit checks + writeReg(MSRC_CONFIG_CONTROL, readReg(MSRC_CONFIG_CONTROL) | 0x12); + + // set final range signal rate limit to 0.25 MCPS (million counts per second) + setSignalRateLimit(0.25); + + writeReg(SYSTEM_SEQUENCE_CONFIG, 0xFF); + + // VL53L0X_DataInit() end + + // VL53L0X_StaticInit() begin + + uint8_t spad_count; + bool spad_type_is_aperture; + if (!getSpadInfo(&spad_count, &spad_type_is_aperture)) { return false; } + + // The SPAD map (RefGoodSpadMap) is read by VL53L0X_get_info_from_device() in + // the API, but the same data seems to be more easily readable from + // GLOBAL_CONFIG_SPAD_ENABLES_REF_0 through _6, so read it from there + uint8_t ref_spad_map[6]; + readMulti(GLOBAL_CONFIG_SPAD_ENABLES_REF_0, ref_spad_map, 6); + + // -- VL53L0X_set_reference_spads() begin (assume NVM values are valid) + + writeReg(0xFF, 0x01); + writeReg(DYNAMIC_SPAD_REF_EN_START_OFFSET, 0x00); + writeReg(DYNAMIC_SPAD_NUM_REQUESTED_REF_SPAD, 0x2C); + writeReg(0xFF, 0x00); + writeReg(GLOBAL_CONFIG_REF_EN_START_SELECT, 0xB4); + + uint8_t first_spad_to_enable = spad_type_is_aperture ? 12 : 0; // 12 is the first aperture spad + uint8_t spads_enabled = 0; + + for (uint8_t i = 0; i < 48; i++) + { + if (i < first_spad_to_enable || spads_enabled == spad_count) + { + // This bit is lower than the first one that should be enabled, or + // (reference_spad_count) bits have already been enabled, so zero this bit + ref_spad_map[i / 8] &= ~(1 << (i % 8)); + } + else if ((ref_spad_map[i / 8] >> (i % 8)) & 0x1) + { + spads_enabled++; + } + } + + writeMulti(GLOBAL_CONFIG_SPAD_ENABLES_REF_0, ref_spad_map, 6); + + // -- VL53L0X_set_reference_spads() end + + // -- VL53L0X_load_tuning_settings() begin + // DefaultTuningSettings from vl53l0x_tuning.h + + writeReg(0xFF, 0x01); + writeReg(0x00, 0x00); + + writeReg(0xFF, 0x00); + writeReg(0x09, 0x00); + writeReg(0x10, 0x00); + writeReg(0x11, 0x00); + + writeReg(0x24, 0x01); + writeReg(0x25, 0xFF); + writeReg(0x75, 0x00); + + writeReg(0xFF, 0x01); + writeReg(0x4E, 0x2C); + writeReg(0x48, 0x00); + writeReg(0x30, 0x20); + + writeReg(0xFF, 0x00); + writeReg(0x30, 0x09); + writeReg(0x54, 0x00); + writeReg(0x31, 0x04); + writeReg(0x32, 0x03); + writeReg(0x40, 0x83); + writeReg(0x46, 0x25); + writeReg(0x60, 0x00); + writeReg(0x27, 0x00); + writeReg(0x50, 0x06); + writeReg(0x51, 0x00); + writeReg(0x52, 0x96); + writeReg(0x56, 0x08); + writeReg(0x57, 0x30); + writeReg(0x61, 0x00); + writeReg(0x62, 0x00); + writeReg(0x64, 0x00); + writeReg(0x65, 0x00); + writeReg(0x66, 0xA0); + + writeReg(0xFF, 0x01); + writeReg(0x22, 0x32); + writeReg(0x47, 0x14); + writeReg(0x49, 0xFF); + writeReg(0x4A, 0x00); + + writeReg(0xFF, 0x00); + writeReg(0x7A, 0x0A); + writeReg(0x7B, 0x00); + writeReg(0x78, 0x21); + + writeReg(0xFF, 0x01); + writeReg(0x23, 0x34); + writeReg(0x42, 0x00); + writeReg(0x44, 0xFF); + writeReg(0x45, 0x26); + writeReg(0x46, 0x05); + writeReg(0x40, 0x40); + writeReg(0x0E, 0x06); + writeReg(0x20, 0x1A); + writeReg(0x43, 0x40); + + writeReg(0xFF, 0x00); + writeReg(0x34, 0x03); + writeReg(0x35, 0x44); + + writeReg(0xFF, 0x01); + writeReg(0x31, 0x04); + writeReg(0x4B, 0x09); + writeReg(0x4C, 0x05); + writeReg(0x4D, 0x04); + + writeReg(0xFF, 0x00); + writeReg(0x44, 0x00); + writeReg(0x45, 0x20); + writeReg(0x47, 0x08); + writeReg(0x48, 0x28); + writeReg(0x67, 0x00); + writeReg(0x70, 0x04); + writeReg(0x71, 0x01); + writeReg(0x72, 0xFE); + writeReg(0x76, 0x00); + writeReg(0x77, 0x00); + + writeReg(0xFF, 0x01); + writeReg(0x0D, 0x01); + + writeReg(0xFF, 0x00); + writeReg(0x80, 0x01); + writeReg(0x01, 0xF8); + + writeReg(0xFF, 0x01); + writeReg(0x8E, 0x01); + writeReg(0x00, 0x01); + writeReg(0xFF, 0x00); + writeReg(0x80, 0x00); + + // -- VL53L0X_load_tuning_settings() end + + // "Set interrupt config to new sample ready" + // -- VL53L0X_SetGpioConfig() begin + + writeReg(SYSTEM_INTERRUPT_CONFIG_GPIO, 0x04); + writeReg(GPIO_HV_MUX_ACTIVE_HIGH, readReg(GPIO_HV_MUX_ACTIVE_HIGH) & ~0x10); // active low + writeReg(SYSTEM_INTERRUPT_CLEAR, 0x01); + + // -- VL53L0X_SetGpioConfig() end + + measurement_timing_budget_us = getMeasurementTimingBudget(); + + // "Disable MSRC and TCC by default" + // MSRC = Minimum Signal Rate Check + // TCC = Target CentreCheck + // -- VL53L0X_SetSequenceStepEnable() begin + + writeReg(SYSTEM_SEQUENCE_CONFIG, 0xE8); + + // -- VL53L0X_SetSequenceStepEnable() end + + // "Recalculate timing budget" + setMeasurementTimingBudget(measurement_timing_budget_us); + + // VL53L0X_StaticInit() end + + // VL53L0X_PerformRefCalibration() begin (VL53L0X_perform_ref_calibration()) + + // -- VL53L0X_perform_vhv_calibration() begin + + writeReg(SYSTEM_SEQUENCE_CONFIG, 0x01); + if (!performSingleRefCalibration(0x40)) { return false; } + + // -- VL53L0X_perform_vhv_calibration() end + + // -- VL53L0X_perform_phase_calibration() begin + + writeReg(SYSTEM_SEQUENCE_CONFIG, 0x02); + if (!performSingleRefCalibration(0x00)) { return false; } + + // -- VL53L0X_perform_phase_calibration() end + + // "restore the previous Sequence Config" + writeReg(SYSTEM_SEQUENCE_CONFIG, 0xE8); + + // VL53L0X_PerformRefCalibration() end + + return true; +} + +// Write an 8-bit register +void VL53L0X::writeReg(uint8_t reg, uint8_t value) +{ + bus->beginTransmission(address); + bus->write(reg); + bus->write(value); + last_status = bus->endTransmission(); +} + +// Write a 16-bit register +void VL53L0X::writeReg16Bit(uint8_t reg, uint16_t value) +{ + bus->beginTransmission(address); + bus->write(reg); + bus->write((value >> 8) & 0xFF); // value high byte + bus->write( value & 0xFF); // value low byte + last_status = bus->endTransmission(); +} + +// Write a 32-bit register +void VL53L0X::writeReg32Bit(uint8_t reg, uint32_t value) +{ + bus->beginTransmission(address); + bus->write(reg); + bus->write((value >> 24) & 0xFF); // value highest byte + bus->write((value >> 16) & 0xFF); + bus->write((value >> 8) & 0xFF); + bus->write( value & 0xFF); // value lowest byte + last_status = bus->endTransmission(); +} + +// Read an 8-bit register +uint8_t VL53L0X::readReg(uint8_t reg) +{ + uint8_t value; + + bus->beginTransmission(address); + bus->write(reg); + last_status = bus->endTransmission(); + + bus->requestFrom(address, (uint8_t)1); + value = bus->read(); + + return value; +} + +// Read a 16-bit register +uint16_t VL53L0X::readReg16Bit(uint8_t reg) +{ + uint16_t value; + + bus->beginTransmission(address); + bus->write(reg); + last_status = bus->endTransmission(); + + bus->requestFrom(address, (uint8_t)2); + value = (uint16_t)bus->read() << 8; // value high byte + value |= bus->read(); // value low byte + + return value; +} + +// Read a 32-bit register +uint32_t VL53L0X::readReg32Bit(uint8_t reg) +{ + uint32_t value; + + bus->beginTransmission(address); + bus->write(reg); + last_status = bus->endTransmission(); + + bus->requestFrom(address, (uint8_t)4); + value = (uint32_t)bus->read() << 24; // value highest byte + value |= (uint32_t)bus->read() << 16; + value |= (uint16_t)bus->read() << 8; + value |= bus->read(); // value lowest byte + + return value; +} + +// Write an arbitrary number of bytes from the given array to the sensor, +// starting at the given register +void VL53L0X::writeMulti(uint8_t reg, uint8_t const * src, uint8_t count) +{ + bus->beginTransmission(address); + bus->write(reg); + + while (count-- > 0) + { + bus->write(*(src++)); + } + + last_status = bus->endTransmission(); +} + +// Read an arbitrary number of bytes from the sensor, starting at the given +// register, into the given array +void VL53L0X::readMulti(uint8_t reg, uint8_t * dst, uint8_t count) +{ + bus->beginTransmission(address); + bus->write(reg); + last_status = bus->endTransmission(); + + bus->requestFrom(address, count); + + while (count-- > 0) + { + *(dst++) = bus->read(); + } +} + +// Set the return signal rate limit check value in units of MCPS (mega counts +// per second). "This represents the amplitude of the signal reflected from the +// target and detected by the device"; setting this limit presumably determines +// the minimum measurement necessary for the sensor to report a valid reading. +// Setting a lower limit increases the potential range of the sensor but also +// seems to increase the likelihood of getting an inaccurate reading because of +// unwanted reflections from objects other than the intended target. +// Defaults to 0.25 MCPS as initialized by the ST API and this library. +bool VL53L0X::setSignalRateLimit(float limit_Mcps) +{ + if (limit_Mcps < 0 || limit_Mcps > 511.99f) { return false; } + + // Q9.7 fixed point format (9 integer bits, 7 fractional bits) + writeReg16Bit(FINAL_RANGE_CONFIG_MIN_COUNT_RATE_RTN_LIMIT, limit_Mcps * (1 << 7)); + return true; +} + +// Get the return signal rate limit check value in MCPS +float VL53L0X::getSignalRateLimit() +{ + return (float)readReg16Bit(FINAL_RANGE_CONFIG_MIN_COUNT_RATE_RTN_LIMIT) / (1 << 7); +} + +// Set the measurement timing budget in microseconds, which is the time allowed +// for one measurement; the ST API and this library take care of splitting the +// timing budget among the sub-steps in the ranging sequence. A longer timing +// budget allows for more accurate measurements. Increasing the budget by a +// factor of N decreases the range measurement standard deviation by a factor of +// sqrt(N). Defaults to about 33 milliseconds; the minimum is 20 ms. +// based on VL53L0X_set_measurement_timing_budget_micro_seconds() +bool VL53L0X::setMeasurementTimingBudget(uint32_t budget_us) +{ + uint32_t const MinTimingBudget = 20000; + + if (budget_us < MinTimingBudget) { return false; } + + // FIXME TD-er: Following is nearly the same as VL53L0X::getMeasurementTimingBudget() + + SequenceStepEnables enables; + SequenceStepTimeouts timeouts; + + uint16_t const StartOverhead = 1910; + uint16_t const EndOverhead = 960; + uint16_t const MsrcOverhead = 660; + uint16_t const TccOverhead = 590; + uint16_t const DssOverhead = 690; + uint16_t const PreRangeOverhead = 660; + uint16_t const FinalRangeOverhead = 550; + + uint32_t used_budget_us = StartOverhead + EndOverhead; + + getSequenceStepEnables(&enables); + getSequenceStepTimeouts(&enables, &timeouts); + + if (enables.tcc) + { + used_budget_us += (timeouts.msrc_dss_tcc_us + TccOverhead); + } + + if (enables.dss) + { + used_budget_us += 2 * (timeouts.msrc_dss_tcc_us + DssOverhead); + } + else if (enables.msrc) + { + used_budget_us += (timeouts.msrc_dss_tcc_us + MsrcOverhead); + } + + if (enables.pre_range) + { + used_budget_us += (timeouts.pre_range_us + PreRangeOverhead); + } + + if (enables.final_range) + { + used_budget_us += FinalRangeOverhead; + + // "Note that the final range timeout is determined by the timing + // budget and the sum of all other timeouts within the sequence. + // If there is no room for the final range timeout, then an error + // will be set. Otherwise the remaining time will be applied to + // the final range." + + if (used_budget_us > budget_us) + { + // "Requested timeout too big." + return false; + } + + uint32_t final_range_timeout_us = budget_us - used_budget_us; + + // set_sequence_step_timeout() begin + // (SequenceStepId == VL53L0X_SEQUENCESTEP_FINAL_RANGE) + + // "For the final range timeout, the pre-range timeout + // must be added. To do this both final and pre-range + // timeouts must be expressed in macro periods MClks + // because they have different vcsel periods." + + uint32_t final_range_timeout_mclks = + timeoutMicrosecondsToMclks(final_range_timeout_us, + timeouts.final_range_vcsel_period_pclks); + + if (enables.pre_range) + { + final_range_timeout_mclks += timeouts.pre_range_mclks; + } + + writeReg16Bit(FINAL_RANGE_CONFIG_TIMEOUT_MACROP_HI, + encodeTimeout(final_range_timeout_mclks)); + + // set_sequence_step_timeout() end + + measurement_timing_budget_us = budget_us; // store for internal reuse + } + return true; +} + +// Get the measurement timing budget in microseconds +// based on VL53L0X_get_measurement_timing_budget_micro_seconds() +// in us +uint32_t VL53L0X::getMeasurementTimingBudget() +{ + SequenceStepEnables enables; + SequenceStepTimeouts timeouts; + + uint16_t const StartOverhead = 1910; + uint16_t const EndOverhead = 960; + uint16_t const MsrcOverhead = 660; + uint16_t const TccOverhead = 590; + uint16_t const DssOverhead = 690; + uint16_t const PreRangeOverhead = 660; + uint16_t const FinalRangeOverhead = 550; + + // "Start and end overhead times always present" + uint32_t budget_us = StartOverhead + EndOverhead; + + getSequenceStepEnables(&enables); + getSequenceStepTimeouts(&enables, &timeouts); + + if (enables.tcc) + { + budget_us += (timeouts.msrc_dss_tcc_us + TccOverhead); + } + + if (enables.dss) + { + budget_us += 2 * (timeouts.msrc_dss_tcc_us + DssOverhead); + } + else if (enables.msrc) + { + budget_us += (timeouts.msrc_dss_tcc_us + MsrcOverhead); + } + + if (enables.pre_range) + { + budget_us += (timeouts.pre_range_us + PreRangeOverhead); + } + + if (enables.final_range) + { + budget_us += (timeouts.final_range_us + FinalRangeOverhead); + } + + measurement_timing_budget_us = budget_us; // store for internal reuse + return budget_us; +} + +// Set the VCSEL (vertical cavity surface emitting laser) pulse period for the +// given period type (pre-range or final range) to the given value in PCLKs. +// Longer periods seem to increase the potential range of the sensor. +// Valid values are (even numbers only): +// pre: 12 to 18 (initialized default: 14) +// final: 8 to 14 (initialized default: 10) +// based on VL53L0X_set_vcsel_pulse_period() +bool VL53L0X::setVcselPulsePeriod(vcselPeriodType type, uint8_t period_pclks) +{ + uint8_t vcsel_period_reg = encodeVcselPeriod(period_pclks); + + SequenceStepEnables enables; + SequenceStepTimeouts timeouts; + + getSequenceStepEnables(&enables); + getSequenceStepTimeouts(&enables, &timeouts); + + // "Apply specific settings for the requested clock period" + // "Re-calculate and apply timeouts, in macro periods" + + // "When the VCSEL period for the pre or final range is changed, + // the corresponding timeout must be read from the device using + // the current VCSEL period, then the new VCSEL period can be + // applied. The timeout then must be written back to the device + // using the new VCSEL period. + // + // For the MSRC timeout, the same applies - this timeout being + // dependant on the pre-range vcsel period." + + + if (type == VcselPeriodPreRange) + { + // "Set phase check limits" + switch (period_pclks) + { + case 12: + writeReg(PRE_RANGE_CONFIG_VALID_PHASE_HIGH, 0x18); + break; + + case 14: + writeReg(PRE_RANGE_CONFIG_VALID_PHASE_HIGH, 0x30); + break; + + case 16: + writeReg(PRE_RANGE_CONFIG_VALID_PHASE_HIGH, 0x40); + break; + + case 18: + writeReg(PRE_RANGE_CONFIG_VALID_PHASE_HIGH, 0x50); + break; + + default: + // invalid period + return false; + } + writeReg(PRE_RANGE_CONFIG_VALID_PHASE_LOW, 0x08); + + // apply new VCSEL period + writeReg(PRE_RANGE_CONFIG_VCSEL_PERIOD, vcsel_period_reg); + + // update timeouts + + // set_sequence_step_timeout() begin + // (SequenceStepId == VL53L0X_SEQUENCESTEP_PRE_RANGE) + + uint16_t new_pre_range_timeout_mclks = + timeoutMicrosecondsToMclks(timeouts.pre_range_us, period_pclks); + + writeReg16Bit(PRE_RANGE_CONFIG_TIMEOUT_MACROP_HI, + encodeTimeout(new_pre_range_timeout_mclks)); + + // set_sequence_step_timeout() end + + // set_sequence_step_timeout() begin + // (SequenceStepId == VL53L0X_SEQUENCESTEP_MSRC) + + uint16_t new_msrc_timeout_mclks = + timeoutMicrosecondsToMclks(timeouts.msrc_dss_tcc_us, period_pclks); + + writeReg(MSRC_CONFIG_TIMEOUT_MACROP, + (new_msrc_timeout_mclks > 256) ? 255 : (new_msrc_timeout_mclks - 1)); + + // set_sequence_step_timeout() end + } + else if (type == VcselPeriodFinalRange) + { + switch (period_pclks) + { + case 8: + writeReg(FINAL_RANGE_CONFIG_VALID_PHASE_HIGH, 0x10); + writeReg(FINAL_RANGE_CONFIG_VALID_PHASE_LOW, 0x08); + writeReg(GLOBAL_CONFIG_VCSEL_WIDTH, 0x02); + writeReg(ALGO_PHASECAL_CONFIG_TIMEOUT, 0x0C); + writeReg(0xFF, 0x01); + writeReg(ALGO_PHASECAL_LIM, 0x30); + writeReg(0xFF, 0x00); + break; + + case 10: + writeReg(FINAL_RANGE_CONFIG_VALID_PHASE_HIGH, 0x28); + writeReg(FINAL_RANGE_CONFIG_VALID_PHASE_LOW, 0x08); + writeReg(GLOBAL_CONFIG_VCSEL_WIDTH, 0x03); + writeReg(ALGO_PHASECAL_CONFIG_TIMEOUT, 0x09); + writeReg(0xFF, 0x01); + writeReg(ALGO_PHASECAL_LIM, 0x20); + writeReg(0xFF, 0x00); + break; + + case 12: + writeReg(FINAL_RANGE_CONFIG_VALID_PHASE_HIGH, 0x38); + writeReg(FINAL_RANGE_CONFIG_VALID_PHASE_LOW, 0x08); + writeReg(GLOBAL_CONFIG_VCSEL_WIDTH, 0x03); + writeReg(ALGO_PHASECAL_CONFIG_TIMEOUT, 0x08); + writeReg(0xFF, 0x01); + writeReg(ALGO_PHASECAL_LIM, 0x20); + writeReg(0xFF, 0x00); + break; + + case 14: + writeReg(FINAL_RANGE_CONFIG_VALID_PHASE_HIGH, 0x48); + writeReg(FINAL_RANGE_CONFIG_VALID_PHASE_LOW, 0x08); + writeReg(GLOBAL_CONFIG_VCSEL_WIDTH, 0x03); + writeReg(ALGO_PHASECAL_CONFIG_TIMEOUT, 0x07); + writeReg(0xFF, 0x01); + writeReg(ALGO_PHASECAL_LIM, 0x20); + writeReg(0xFF, 0x00); + break; + + default: + // invalid period + return false; + } + + // apply new VCSEL period + writeReg(FINAL_RANGE_CONFIG_VCSEL_PERIOD, vcsel_period_reg); + + // update timeouts + + // set_sequence_step_timeout() begin + // (SequenceStepId == VL53L0X_SEQUENCESTEP_FINAL_RANGE) + + // "For the final range timeout, the pre-range timeout + // must be added. To do this both final and pre-range + // timeouts must be expressed in macro periods MClks + // because they have different vcsel periods." + + uint16_t new_final_range_timeout_mclks = + timeoutMicrosecondsToMclks(timeouts.final_range_us, period_pclks); + + if (enables.pre_range) + { + new_final_range_timeout_mclks += timeouts.pre_range_mclks; + } + + writeReg16Bit(FINAL_RANGE_CONFIG_TIMEOUT_MACROP_HI, + encodeTimeout(new_final_range_timeout_mclks)); + + // set_sequence_step_timeout end + } + else + { + // invalid type + return false; + } + + // "Finally, the timing budget must be re-applied" + + setMeasurementTimingBudget(measurement_timing_budget_us); + + // "Perform the phase calibration. This is needed after changing on vcsel period." + // VL53L0X_perform_phase_calibration() begin + + uint8_t sequence_config = readReg(SYSTEM_SEQUENCE_CONFIG); + writeReg(SYSTEM_SEQUENCE_CONFIG, 0x02); + performSingleRefCalibration(0x0); + writeReg(SYSTEM_SEQUENCE_CONFIG, sequence_config); + + // VL53L0X_perform_phase_calibration() end + + return true; +} + +// Get the VCSEL pulse period in PCLKs for the given period type. +// based on VL53L0X_get_vcsel_pulse_period() +uint8_t VL53L0X::getVcselPulsePeriod(vcselPeriodType type) +{ + if (type == VcselPeriodPreRange) + { + return decodeVcselPeriod(readReg(PRE_RANGE_CONFIG_VCSEL_PERIOD)); + } + else if (type == VcselPeriodFinalRange) + { + return decodeVcselPeriod(readReg(FINAL_RANGE_CONFIG_VCSEL_PERIOD)); + } + else { return 255; } +} + +// Start continuous ranging measurements. If period_ms (optional) is 0 or not +// given, continuous back-to-back mode is used (the sensor takes measurements as +// often as possible); otherwise, continuous timed mode is used, with the given +// inter-measurement period in milliseconds determining how often the sensor +// takes a measurement. +// based on VL53L0X_StartMeasurement() +void VL53L0X::startContinuous(uint32_t period_ms) +{ + writeReg(0x80, 0x01); + writeReg(0xFF, 0x01); + writeReg(0x00, 0x00); + writeReg(0x91, stop_variable); + writeReg(0x00, 0x01); + writeReg(0xFF, 0x00); + writeReg(0x80, 0x00); + + if (period_ms != 0) + { + // continuous timed mode + + // VL53L0X_SetInterMeasurementPeriodMilliSeconds() begin + + uint16_t osc_calibrate_val = readReg16Bit(OSC_CALIBRATE_VAL); + + if (osc_calibrate_val != 0) + { + period_ms *= osc_calibrate_val; + } + + writeReg32Bit(SYSTEM_INTERMEASUREMENT_PERIOD, period_ms); + + // VL53L0X_SetInterMeasurementPeriodMilliSeconds() end + + writeReg(SYSRANGE_START, 0x04); // VL53L0X_REG_SYSRANGE_MODE_TIMED + } + else + { + // continuous back-to-back mode + writeReg(SYSRANGE_START, 0x02); // VL53L0X_REG_SYSRANGE_MODE_BACKTOBACK + } + state = state_e::waitMeasurement; + start_timeout_ms = millis(); +} + +// Stop continuous measurements +// based on VL53L0X_StopMeasurement() +void VL53L0X::stopContinuous() +{ + writeReg(SYSRANGE_START, 0x01); // VL53L0X_REG_SYSRANGE_MODE_SINGLESHOT + + writeReg(0xFF, 0x01); + writeReg(0x00, 0x00); + writeReg(0x91, 0x00); + writeReg(0x00, 0x01); + writeReg(0xFF, 0x00); + state = state_e::initialized; + start_timeout_ms = 0; +} + +// Returns a range reading in millimeters when continuous mode is active +// (readRangeSingleMillimeters() also calls this function after starting a +// single-shot range measurement) +uint16_t VL53L0X::readRangeContinuousMillimeters() +{ + int16_t distance; + start_timeout_ms = millis(); + while (!loop(distance)) { + if (VL53L0X_NOT_WAITING == distance) { + return 65535u; + } + } + if (VL53L0X_TIMEOUT == distance) + { + return 65535u; + } + if (VL53L0X_WAITING == distance) + { + return 65534u; + } + return distance; +} + +void VL53L0X::startSingleMeasurement() +{ + writeReg(0x80, 0x01); + writeReg(0xFF, 0x01); + writeReg(0x00, 0x00); + writeReg(0x91, stop_variable); + writeReg(0x00, 0x01); + writeReg(0xFF, 0x00); + writeReg(0x80, 0x00); + + writeReg(SYSRANGE_START, 0x01); + + state = state_e::waitStartBitCleared; + start_timeout_ms = millis(); +} + +bool VL53L0X::asyncReadRangeSingleMillimeters(int16_t& distance) +{ + const bool res = loop(distance); + if (res || (VL53L0X_WAITING != distance)) { + state = state_e::initialized; + start_timeout_ms = 0; + return true; + } + return false; +} + +bool VL53L0X::asyncReadRangeContinuousMillimeters(int16_t& distance) +{ + return loop(distance); +} + +// Performs a single-shot range measurement and returns the reading in +// millimeters +// based on VL53L0X_PerformSingleRangingMeasurement() +uint16_t VL53L0X::readRangeSingleMillimeters() +{ + startSingleMeasurement(); + + // "Wait until start bit has been cleared" + int16_t distance; + while (!loop(distance)) { + delay(0); + } + state = state_e::initialized; + start_timeout_ms = 0; + + uint16_t res = 65535u; + if (distance >= 0) + { + res = distance; + } + return res; +} + +// Did a timeout occur in one of the read functions since the last call to +// timeoutOccurred()? +bool VL53L0X::timeoutOccurred() +{ + bool tmp = did_timeout; + did_timeout = false; + return tmp; +} + +// Private Methods ///////////////////////////////////////////////////////////// + +// Get reference SPAD (single photon avalanche diode) count and type +// based on VL53L0X_get_info_from_device(), +// but only gets reference SPAD count and type +bool VL53L0X::getSpadInfo(uint8_t * count, bool * type_is_aperture) +{ + uint8_t tmp; + + writeReg(0x80, 0x01); + writeReg(0xFF, 0x01); + writeReg(0x00, 0x00); + + writeReg(0xFF, 0x06); + writeReg(0x83, readReg(0x83) | 0x04); + writeReg(0xFF, 0x07); + writeReg(0x81, 0x01); + + writeReg(0x80, 0x01); + + writeReg(0x94, 0x6b); + writeReg(0x83, 0x00); + startTimeout(); + while (readReg(0x83) == 0x00) + { + if (checkTimeoutExpired()) { return false; } + } + writeReg(0x83, 0x01); + tmp = readReg(0x92); + + *count = tmp & 0x7f; + *type_is_aperture = (tmp >> 7) & 0x01; + + writeReg(0x81, 0x00); + writeReg(0xFF, 0x06); + writeReg(0x83, readReg(0x83) & ~0x04); + writeReg(0xFF, 0x01); + writeReg(0x00, 0x01); + + writeReg(0xFF, 0x00); + writeReg(0x80, 0x00); + + return true; +} + +// Get sequence step enables +// based on VL53L0X_GetSequenceStepEnables() +void VL53L0X::getSequenceStepEnables(SequenceStepEnables * enables) +{ + uint8_t sequence_config = readReg(SYSTEM_SEQUENCE_CONFIG); + + enables->tcc = (sequence_config >> 4) & 0x1; + enables->dss = (sequence_config >> 3) & 0x1; + enables->msrc = (sequence_config >> 2) & 0x1; + enables->pre_range = (sequence_config >> 6) & 0x1; + enables->final_range = (sequence_config >> 7) & 0x1; +} + +// Get sequence step timeouts +// based on get_sequence_step_timeout(), +// but gets all timeouts instead of just the requested one, and also stores +// intermediate values +void VL53L0X::getSequenceStepTimeouts(SequenceStepEnables const * enables, SequenceStepTimeouts * timeouts) +{ + timeouts->pre_range_vcsel_period_pclks = getVcselPulsePeriod(VcselPeriodPreRange); + + timeouts->msrc_dss_tcc_mclks = readReg(MSRC_CONFIG_TIMEOUT_MACROP) + 1; + timeouts->msrc_dss_tcc_us = + timeoutMclksToMicroseconds(timeouts->msrc_dss_tcc_mclks, + timeouts->pre_range_vcsel_period_pclks); + + timeouts->pre_range_mclks = + decodeTimeout(readReg16Bit(PRE_RANGE_CONFIG_TIMEOUT_MACROP_HI)); + timeouts->pre_range_us = + timeoutMclksToMicroseconds(timeouts->pre_range_mclks, + timeouts->pre_range_vcsel_period_pclks); + + timeouts->final_range_vcsel_period_pclks = getVcselPulsePeriod(VcselPeriodFinalRange); + + timeouts->final_range_mclks = + decodeTimeout(readReg16Bit(FINAL_RANGE_CONFIG_TIMEOUT_MACROP_HI)); + + if (enables->pre_range) + { + timeouts->final_range_mclks -= timeouts->pre_range_mclks; + } + + timeouts->final_range_us = + timeoutMclksToMicroseconds(timeouts->final_range_mclks, + timeouts->final_range_vcsel_period_pclks); +} + +// Decode sequence step timeout in MCLKs from register value +// based on VL53L0X_decode_timeout() +// Note: the original function returned a uint32_t, but the return value is +// always stored in a uint16_t. +uint16_t VL53L0X::decodeTimeout(uint16_t reg_val) +{ + // format: "(LSByte * 2^MSByte) + 1" + return (uint16_t)((reg_val & 0x00FF) << + (uint16_t)((reg_val & 0xFF00) >> 8)) + 1; +} + +// Encode sequence step timeout register value from timeout in MCLKs +// based on VL53L0X_encode_timeout() +uint16_t VL53L0X::encodeTimeout(uint32_t timeout_mclks) +{ + // format: "(LSByte * 2^MSByte) + 1" + + uint32_t ls_byte = 0; + uint16_t ms_byte = 0; + + if (timeout_mclks > 0) + { + ls_byte = timeout_mclks - 1; + + while ((ls_byte & 0xFFFFFF00) > 0) + { + ls_byte >>= 1; + ms_byte++; + } + + return (ms_byte << 8) | (ls_byte & 0xFF); + } + else { return 0; } +} + +// Convert sequence step timeout from MCLKs to microseconds with given VCSEL period in PCLKs +// based on VL53L0X_calc_timeout_us() +uint32_t VL53L0X::timeoutMclksToMicroseconds(uint16_t timeout_period_mclks, uint8_t vcsel_period_pclks) +{ + uint32_t macro_period_ns = calcMacroPeriod(vcsel_period_pclks); + + return ((timeout_period_mclks * macro_period_ns) + 500) / 1000; +} + +// Convert sequence step timeout from microseconds to MCLKs with given VCSEL period in PCLKs +// based on VL53L0X_calc_timeout_mclks() +uint32_t VL53L0X::timeoutMicrosecondsToMclks(uint32_t timeout_period_us, uint8_t vcsel_period_pclks) +{ + uint32_t macro_period_ns = calcMacroPeriod(vcsel_period_pclks); + + return (((timeout_period_us * 1000) + (macro_period_ns / 2)) / macro_period_ns); +} + + +// based on VL53L0X_perform_single_ref_calibration() +bool VL53L0X::performSingleRefCalibration(uint8_t vhv_init_byte) +{ + writeReg(SYSRANGE_START, 0x01 | vhv_init_byte); // VL53L0X_REG_SYSRANGE_MODE_START_STOP + + startTimeout(); + while ((readReg(RESULT_INTERRUPT_STATUS) & 0x07) == 0) + { + if (checkTimeoutExpired()) { return false; } + } + + writeReg(SYSTEM_INTERRUPT_CLEAR, 0x01); + + writeReg(SYSRANGE_START, 0x00); + + return true; +} + +// Simply return the string, it is cleared and could be set during init +String VL53L0X::getInitResult() { + return initResult; +} + + +bool VL53L0X::loop(int16_t& distance) { + distance = VL53L0X_NOT_WAITING; + + const int32_t timePassedSince = (start_timeout_ms == 0) ? 0 : (int32_t)(millis() - start_timeout_ms); + switch (state) { + case state_e::uninitialized: + case state_e::initialized: + break; + case state_e::waitStartBitCleared: + { + distance = VL53L0X_WAITING; + if (timePassedSince > io_timeout) { + distance = VL53L0X_TIMEOUT; + did_timeout = true; + state = state_e::initialized; + start_timeout_ms = 0; + return true; + } else { + if (!(readReg(SYSRANGE_START) & 0x01)) { + // start bit has been cleared, wait for measurement to complete + state = state_e::waitMeasurement; + start_timeout_ms = millis(); + } + } + + return false; + } + case state_e::waitMeasurement: + { + if ((readReg(RESULT_INTERRUPT_STATUS) & 0x07) == 0) { + if (timePassedSince > io_timeout) { + distance = VL53L0X_TIMEOUT; + did_timeout = true; + start_timeout_ms = millis(); + return true; + } + distance = VL53L0X_WAITING; + return false; + } + // assumptions: Linearity Corrective Gain is 1000 (default); + // fractional ranging is not enabled + distance = readReg16Bit(RESULT_RANGE_STATUS + 10); + + writeReg(SYSTEM_INTERRUPT_CLEAR, 0x01); + did_timeout = false; + start_timeout_ms = millis(); + return true; + } + } + return true; } \ No newline at end of file diff --git a/lib/VL53L0X/src/VL53L0X.h b/lib/VL53L0X/src/VL53L0X.h index 0b2884f343..4b2c4d3fa6 100644 --- a/lib/VL53L0X/src/VL53L0X.h +++ b/lib/VL53L0X/src/VL53L0X.h @@ -1,183 +1,220 @@ -#ifndef VL53L0X_h -#define VL53L0X_h - -#include -#include - -class VL53L0X -{ - public: - // register addresses from API vl53l0x_device.h (ordered as listed there) - enum regAddr - { - SYSRANGE_START = 0x00, - - SYSTEM_THRESH_HIGH = 0x0C, - SYSTEM_THRESH_LOW = 0x0E, - - SYSTEM_SEQUENCE_CONFIG = 0x01, - SYSTEM_RANGE_CONFIG = 0x09, - SYSTEM_INTERMEASUREMENT_PERIOD = 0x04, - - SYSTEM_INTERRUPT_CONFIG_GPIO = 0x0A, - - GPIO_HV_MUX_ACTIVE_HIGH = 0x84, - - SYSTEM_INTERRUPT_CLEAR = 0x0B, - - RESULT_INTERRUPT_STATUS = 0x13, - RESULT_RANGE_STATUS = 0x14, - - RESULT_CORE_AMBIENT_WINDOW_EVENTS_RTN = 0xBC, - RESULT_CORE_RANGING_TOTAL_EVENTS_RTN = 0xC0, - RESULT_CORE_AMBIENT_WINDOW_EVENTS_REF = 0xD0, - RESULT_CORE_RANGING_TOTAL_EVENTS_REF = 0xD4, - RESULT_PEAK_SIGNAL_RATE_REF = 0xB6, - - ALGO_PART_TO_PART_RANGE_OFFSET_MM = 0x28, - - I2C_SLAVE_DEVICE_ADDRESS = 0x8A, - - MSRC_CONFIG_CONTROL = 0x60, - - PRE_RANGE_CONFIG_MIN_SNR = 0x27, - PRE_RANGE_CONFIG_VALID_PHASE_LOW = 0x56, - PRE_RANGE_CONFIG_VALID_PHASE_HIGH = 0x57, - PRE_RANGE_MIN_COUNT_RATE_RTN_LIMIT = 0x64, - - FINAL_RANGE_CONFIG_MIN_SNR = 0x67, - FINAL_RANGE_CONFIG_VALID_PHASE_LOW = 0x47, - FINAL_RANGE_CONFIG_VALID_PHASE_HIGH = 0x48, - FINAL_RANGE_CONFIG_MIN_COUNT_RATE_RTN_LIMIT = 0x44, - - PRE_RANGE_CONFIG_SIGMA_THRESH_HI = 0x61, - PRE_RANGE_CONFIG_SIGMA_THRESH_LO = 0x62, - - PRE_RANGE_CONFIG_VCSEL_PERIOD = 0x50, - PRE_RANGE_CONFIG_TIMEOUT_MACROP_HI = 0x51, - PRE_RANGE_CONFIG_TIMEOUT_MACROP_LO = 0x52, - - SYSTEM_HISTOGRAM_BIN = 0x81, - HISTOGRAM_CONFIG_INITIAL_PHASE_SELECT = 0x33, - HISTOGRAM_CONFIG_READOUT_CTRL = 0x55, - - FINAL_RANGE_CONFIG_VCSEL_PERIOD = 0x70, - FINAL_RANGE_CONFIG_TIMEOUT_MACROP_HI = 0x71, - FINAL_RANGE_CONFIG_TIMEOUT_MACROP_LO = 0x72, - CROSSTALK_COMPENSATION_PEAK_RATE_MCPS = 0x20, - - MSRC_CONFIG_TIMEOUT_MACROP = 0x46, - - SOFT_RESET_GO2_SOFT_RESET_N = 0xBF, - IDENTIFICATION_MODEL_ID = 0xC0, - IDENTIFICATION_REVISION_ID = 0xC2, - - OSC_CALIBRATE_VAL = 0xF8, - - GLOBAL_CONFIG_VCSEL_WIDTH = 0x32, - GLOBAL_CONFIG_SPAD_ENABLES_REF_0 = 0xB0, - GLOBAL_CONFIG_SPAD_ENABLES_REF_1 = 0xB1, - GLOBAL_CONFIG_SPAD_ENABLES_REF_2 = 0xB2, - GLOBAL_CONFIG_SPAD_ENABLES_REF_3 = 0xB3, - GLOBAL_CONFIG_SPAD_ENABLES_REF_4 = 0xB4, - GLOBAL_CONFIG_SPAD_ENABLES_REF_5 = 0xB5, - - GLOBAL_CONFIG_REF_EN_START_SELECT = 0xB6, - DYNAMIC_SPAD_NUM_REQUESTED_REF_SPAD = 0x4E, - DYNAMIC_SPAD_REF_EN_START_OFFSET = 0x4F, - POWER_MANAGEMENT_GO1_POWER_FORCE = 0x80, - - VHV_CONFIG_PAD_SCL_SDA__EXTSUP_HV = 0x89, - - ALGO_PHASECAL_LIM = 0x30, - ALGO_PHASECAL_CONFIG_TIMEOUT = 0x30, - }; - - enum vcselPeriodType { VcselPeriodPreRange, VcselPeriodFinalRange }; - - uint8_t last_status; // status of last I2C transmission - - VL53L0X(); - - void setBus(TwoWire * bus) { this->bus = bus; } - TwoWire * getBus() { return bus; } - - void setAddress(uint8_t new_addr); - inline uint8_t getAddress() { return address; } - - bool init(bool io_2v8 = true); - - void writeReg(uint8_t reg, uint8_t value); - void writeReg16Bit(uint8_t reg, uint16_t value); - void writeReg32Bit(uint8_t reg, uint32_t value); - uint8_t readReg(uint8_t reg); - uint16_t readReg16Bit(uint8_t reg); - uint32_t readReg32Bit(uint8_t reg); - - void writeMulti(uint8_t reg, uint8_t const * src, uint8_t count); - void readMulti(uint8_t reg, uint8_t * dst, uint8_t count); - - bool setSignalRateLimit(float limit_Mcps); - float getSignalRateLimit(); - - bool setMeasurementTimingBudget(uint32_t budget_us); - uint32_t getMeasurementTimingBudget(); - - bool setVcselPulsePeriod(vcselPeriodType type, uint8_t period_pclks); - uint8_t getVcselPulsePeriod(vcselPeriodType type); - - void startContinuous(uint32_t period_ms = 0); - void stopContinuous(); - uint16_t readRangeContinuousMillimeters(); - uint16_t readRangeSingleMillimeters(); - - inline void setTimeout(uint16_t timeout) { io_timeout = timeout; } - inline uint16_t getTimeout() { return io_timeout; } - bool timeoutOccurred(); - String getInitResult(); - - private: - // TCC: Target CentreCheck - // MSRC: Minimum Signal Rate Check - // DSS: Dynamic Spad Selection - - struct SequenceStepEnables - { - boolean tcc, msrc, dss, pre_range, final_range; - }; - - struct SequenceStepTimeouts - { - uint16_t pre_range_vcsel_period_pclks, final_range_vcsel_period_pclks; - - uint16_t msrc_dss_tcc_mclks, pre_range_mclks, final_range_mclks; - uint32_t msrc_dss_tcc_us, pre_range_us, final_range_us; - }; - - TwoWire * bus; - uint8_t address; - uint16_t io_timeout; - bool did_timeout; - uint16_t timeout_start_ms; - - uint8_t stop_variable; // read by init and used when starting measurement; is StopVariable field of VL53L0X_DevData_t structure in API - uint32_t measurement_timing_budget_us; - - bool getSpadInfo(uint8_t * count, bool * type_is_aperture); - - void getSequenceStepEnables(SequenceStepEnables * enables); - void getSequenceStepTimeouts(SequenceStepEnables const * enables, SequenceStepTimeouts * timeouts); - - bool performSingleRefCalibration(uint8_t vhv_init_byte); - - static uint16_t decodeTimeout(uint16_t value); - static uint16_t encodeTimeout(uint32_t timeout_mclks); - static uint32_t timeoutMclksToMicroseconds(uint16_t timeout_period_mclks, uint8_t vcsel_period_pclks); - static uint32_t timeoutMicrosecondsToMclks(uint32_t timeout_period_us, uint8_t vcsel_period_pclks); - String initResult; -}; - -#endif - - - +#ifndef VL53L0X_h +#define VL53L0X_h + +#include +#include + +class VL53L0X +{ + public: + // register addresses from API vl53l0x_device.h (ordered as listed there) + enum regAddr + { + SYSRANGE_START = 0x00, + + SYSTEM_THRESH_HIGH = 0x0C, + SYSTEM_THRESH_LOW = 0x0E, + + SYSTEM_SEQUENCE_CONFIG = 0x01, + SYSTEM_RANGE_CONFIG = 0x09, + SYSTEM_INTERMEASUREMENT_PERIOD = 0x04, + + SYSTEM_INTERRUPT_CONFIG_GPIO = 0x0A, + + GPIO_HV_MUX_ACTIVE_HIGH = 0x84, + + SYSTEM_INTERRUPT_CLEAR = 0x0B, + + RESULT_INTERRUPT_STATUS = 0x13, + RESULT_RANGE_STATUS = 0x14, + + RESULT_CORE_AMBIENT_WINDOW_EVENTS_RTN = 0xBC, + RESULT_CORE_RANGING_TOTAL_EVENTS_RTN = 0xC0, + RESULT_CORE_AMBIENT_WINDOW_EVENTS_REF = 0xD0, + RESULT_CORE_RANGING_TOTAL_EVENTS_REF = 0xD4, + RESULT_PEAK_SIGNAL_RATE_REF = 0xB6, + + ALGO_PART_TO_PART_RANGE_OFFSET_MM = 0x28, + + I2C_SLAVE_DEVICE_ADDRESS = 0x8A, + + MSRC_CONFIG_CONTROL = 0x60, + + PRE_RANGE_CONFIG_MIN_SNR = 0x27, + PRE_RANGE_CONFIG_VALID_PHASE_LOW = 0x56, + PRE_RANGE_CONFIG_VALID_PHASE_HIGH = 0x57, + PRE_RANGE_MIN_COUNT_RATE_RTN_LIMIT = 0x64, + + FINAL_RANGE_CONFIG_MIN_SNR = 0x67, + FINAL_RANGE_CONFIG_VALID_PHASE_LOW = 0x47, + FINAL_RANGE_CONFIG_VALID_PHASE_HIGH = 0x48, + FINAL_RANGE_CONFIG_MIN_COUNT_RATE_RTN_LIMIT = 0x44, + + PRE_RANGE_CONFIG_SIGMA_THRESH_HI = 0x61, + PRE_RANGE_CONFIG_SIGMA_THRESH_LO = 0x62, + + PRE_RANGE_CONFIG_VCSEL_PERIOD = 0x50, + PRE_RANGE_CONFIG_TIMEOUT_MACROP_HI = 0x51, + PRE_RANGE_CONFIG_TIMEOUT_MACROP_LO = 0x52, + + SYSTEM_HISTOGRAM_BIN = 0x81, + HISTOGRAM_CONFIG_INITIAL_PHASE_SELECT = 0x33, + HISTOGRAM_CONFIG_READOUT_CTRL = 0x55, + + FINAL_RANGE_CONFIG_VCSEL_PERIOD = 0x70, + FINAL_RANGE_CONFIG_TIMEOUT_MACROP_HI = 0x71, + FINAL_RANGE_CONFIG_TIMEOUT_MACROP_LO = 0x72, + CROSSTALK_COMPENSATION_PEAK_RATE_MCPS = 0x20, + + MSRC_CONFIG_TIMEOUT_MACROP = 0x46, + + SOFT_RESET_GO2_SOFT_RESET_N = 0xBF, + IDENTIFICATION_MODEL_ID = 0xC0, + IDENTIFICATION_REVISION_ID = 0xC2, + + OSC_CALIBRATE_VAL = 0xF8, + + GLOBAL_CONFIG_VCSEL_WIDTH = 0x32, + GLOBAL_CONFIG_SPAD_ENABLES_REF_0 = 0xB0, + GLOBAL_CONFIG_SPAD_ENABLES_REF_1 = 0xB1, + GLOBAL_CONFIG_SPAD_ENABLES_REF_2 = 0xB2, + GLOBAL_CONFIG_SPAD_ENABLES_REF_3 = 0xB3, + GLOBAL_CONFIG_SPAD_ENABLES_REF_4 = 0xB4, + GLOBAL_CONFIG_SPAD_ENABLES_REF_5 = 0xB5, + + GLOBAL_CONFIG_REF_EN_START_SELECT = 0xB6, + DYNAMIC_SPAD_NUM_REQUESTED_REF_SPAD = 0x4E, + DYNAMIC_SPAD_REF_EN_START_OFFSET = 0x4F, + POWER_MANAGEMENT_GO1_POWER_FORCE = 0x80, + + VHV_CONFIG_PAD_SCL_SDA__EXTSUP_HV = 0x89, + + ALGO_PHASECAL_LIM = 0x30, + ALGO_PHASECAL_CONFIG_TIMEOUT = 0x30, + }; + + enum vcselPeriodType { VcselPeriodPreRange, VcselPeriodFinalRange }; + + uint8_t last_status; // status of last I2C transmission + + VL53L0X(); + + void setBus(TwoWire * bus) { this->bus = bus; } + TwoWire * getBus() { return bus; } + + void setAddress(uint8_t new_addr); + inline uint8_t getAddress() { return address; } + + bool init(bool io_2v8 = true); + + void writeReg(uint8_t reg, uint8_t value); + void writeReg16Bit(uint8_t reg, uint16_t value); + void writeReg32Bit(uint8_t reg, uint32_t value); + uint8_t readReg(uint8_t reg); + uint16_t readReg16Bit(uint8_t reg); + uint32_t readReg32Bit(uint8_t reg); + + void writeMulti(uint8_t reg, uint8_t const * src, uint8_t count); + void readMulti(uint8_t reg, uint8_t * dst, uint8_t count); + + bool setSignalRateLimit(float limit_Mcps); + float getSignalRateLimit(); + + bool setMeasurementTimingBudget(uint32_t budget_us); + uint32_t getMeasurementTimingBudget(); + + bool setVcselPulsePeriod(vcselPeriodType type, uint8_t period_pclks); + uint8_t getVcselPulsePeriod(vcselPeriodType type); + + void startContinuous(uint32_t period_ms = 0); + void stopContinuous(); + uint16_t readRangeContinuousMillimeters(); + uint16_t readRangeSingleMillimeters(); + + inline void setTimeout(uint16_t timeout) { io_timeout = timeout; } + inline uint16_t getTimeout() { return io_timeout; } + bool timeoutOccurred(); + String getInitResult(); + + private: + // TCC: Target CentreCheck + // MSRC: Minimum Signal Rate Check + // DSS: Dynamic Spad Selection + + struct SequenceStepEnables + { + boolean tcc, msrc, dss, pre_range, final_range; + }; + + struct SequenceStepTimeouts + { + uint16_t pre_range_vcsel_period_pclks, final_range_vcsel_period_pclks; + + uint16_t msrc_dss_tcc_mclks, pre_range_mclks, final_range_mclks; + uint32_t msrc_dss_tcc_us, pre_range_us, final_range_us; + }; + + TwoWire * bus; + uint8_t address; + uint16_t io_timeout; + bool did_timeout; + uint16_t timeout_start_ms; + + uint8_t stop_variable; // read by init and used when starting measurement; is StopVariable field of VL53L0X_DevData_t structure in API + uint32_t measurement_timing_budget_us; + + bool getSpadInfo(uint8_t * count, bool * type_is_aperture); + + void getSequenceStepEnables(SequenceStepEnables * enables); + void getSequenceStepTimeouts(SequenceStepEnables const * enables, SequenceStepTimeouts * timeouts); + + bool performSingleRefCalibration(uint8_t vhv_init_byte); + + static uint16_t decodeTimeout(uint16_t value); + static uint16_t encodeTimeout(uint32_t timeout_mclks); + static uint32_t timeoutMclksToMicroseconds(uint16_t timeout_period_mclks, uint8_t vcsel_period_pclks); + static uint32_t timeoutMicrosecondsToMclks(uint32_t timeout_period_us, uint8_t vcsel_period_pclks); + String initResult; + +public: + + #define VL53L0X_NOT_WAITING -1 + #define VL53L0X_WAITING -2 + #define VL53L0X_TIMEOUT -3 + +private: + + // Returns true when measurement done. + // distance < 0 when there was an error, otherwise successful measurement + bool loop(int16_t& distance); + +public: + + void startSingleMeasurement(); + + // Check if taking a sample has finished + // Return true when measurement has finished or aborted. + // Valid distance when distance >= 0 + bool asyncReadRangeSingleMillimeters(int16_t& distance); + + // Check if new sample is ready. + // Return true when measurement has finished or aborted. + // Valid distance when distance >= 0 + bool asyncReadRangeContinuousMillimeters(int16_t& distance); + +private: + + enum class state_e { + uninitialized, + initialized, + waitStartBitCleared, + waitMeasurement + }; + state_e state = state_e::uninitialized; + uint32_t start_timeout_ms = 0; +}; + +#endif + + + diff --git a/lib/pubsubclient/src/PubSubClient.cpp b/lib/pubsubclient/src/PubSubClient.cpp index 5976062b6c..30948c57f9 100644 --- a/lib/pubsubclient/src/PubSubClient.cpp +++ b/lib/pubsubclient/src/PubSubClient.cpp @@ -1,772 +1,841 @@ -/* - PubSubClient.cpp - A simple client for MQTT. - Nick O'Leary - http://knolleary.net -*/ - -#include "PubSubClient.h" -#include - -#ifdef ESP32 -#include -#endif - -#ifdef USE_SECOND_HEAP - #include -#endif - - -PubSubClient::PubSubClient() { - this->_state = MQTT_DISCONNECTED; - this->_client = NULL; - this->stream = NULL; - setCallback(NULL); -} - -PubSubClient::PubSubClient(Client& client) { - this->_state = MQTT_DISCONNECTED; - setClient(client); - this->stream = NULL; -} - -PubSubClient::PubSubClient(IPAddress addr, uint16_t port, Client& client) { - this->_state = MQTT_DISCONNECTED; - setServer(addr, port); - setClient(client); - this->stream = NULL; -} -PubSubClient::PubSubClient(IPAddress addr, uint16_t port, Client& client, Stream& stream) { - this->_state = MQTT_DISCONNECTED; - setServer(addr,port); - setClient(client); - setStream(stream); -} -PubSubClient::PubSubClient(IPAddress addr, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) { - this->_state = MQTT_DISCONNECTED; - setServer(addr, port); - setCallback(callback); - setClient(client); - this->stream = NULL; -} -PubSubClient::PubSubClient(IPAddress addr, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) { - this->_state = MQTT_DISCONNECTED; - setServer(addr,port); - setCallback(callback); - setClient(client); - setStream(stream); -} - -PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, Client& client) { - this->_state = MQTT_DISCONNECTED; - setServer(ip, port); - setClient(client); - this->stream = NULL; -} -PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, Client& client, Stream& stream) { - this->_state = MQTT_DISCONNECTED; - setServer(ip,port); - setClient(client); - setStream(stream); -} -PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) { - this->_state = MQTT_DISCONNECTED; - setServer(ip, port); - setCallback(callback); - setClient(client); - this->stream = NULL; -} -PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) { - this->_state = MQTT_DISCONNECTED; - setServer(ip,port); - setCallback(callback); - setClient(client); - setStream(stream); -} - -PubSubClient::PubSubClient(const char* domain, uint16_t port, Client& client) { - this->_state = MQTT_DISCONNECTED; - setServer(domain,port); - setClient(client); - this->stream = NULL; -} -PubSubClient::PubSubClient(const char* domain, uint16_t port, Client& client, Stream& stream) { - this->_state = MQTT_DISCONNECTED; - setServer(domain,port); - setClient(client); - setStream(stream); -} -PubSubClient::PubSubClient(const char* domain, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) { - this->_state = MQTT_DISCONNECTED; - setServer(domain,port); - setCallback(callback); - setClient(client); - this->stream = NULL; -} -PubSubClient::PubSubClient(const char* domain, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) { - this->_state = MQTT_DISCONNECTED; - setServer(domain,port); - setCallback(callback); - setClient(client); - setStream(stream); -} - -PubSubClient::~PubSubClient() -{ - if (buffer != nullptr) { - free(buffer); - buffer = nullptr; - } -} - -boolean PubSubClient::connect(const char *id) { - return connect(id,NULL,NULL,0,0,0,0,1); -} - -boolean PubSubClient::connect(const char *id, const char *user, const char *pass) { - return connect(id,user,pass,0,0,0,0,1); -} - -boolean PubSubClient::connect(const char *id, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage) { - return connect(id,NULL,NULL,willTopic,willQos,willRetain,willMessage,1); -} - -boolean PubSubClient::connect(const char *id, const char *user, const char *pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage) { - return connect(id,user,pass,willTopic,willQos,willRetain,willMessage,1); -} - -boolean PubSubClient::connect(const char *id, const char *user, const char *pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage, boolean cleanSession) { - if (!initBuffer()) { - return false; - } - - if (!connected()) { - int result = 0; - - if (_client == nullptr) { - return false; - } - if (_client->connected()) { - result = 1; - } else { - if (domain.length() != 0) { -#ifdef ESP32 - WiFiClient* wfc = (WiFiClient*)_client; - result = wfc->connect(this->domain.c_str(), this->port, ESP32_CONNECTION_TIMEOUT); -#else - result = _client->connect(this->domain.c_str(), this->port); -#endif - } else { -#ifdef ESP32 - WiFiClient* wfc = (WiFiClient*)_client; - result = wfc->connect(this->ip, this->port, ESP32_CONNECTION_TIMEOUT); -#else - result = _client->connect(this->ip, this->port); -#endif - } - } - if (result == 1) { - nextMsgId = 1; - // Leave room in the buffer for header and variable length field - uint16_t length = MQTT_MAX_HEADER_SIZE; - unsigned int j; - -#if MQTT_VERSION == MQTT_VERSION_3_1 - uint8_t d[9] = {0x00,0x06,'M','Q','I','s','d','p', MQTT_VERSION}; -#define MQTT_HEADER_VERSION_LENGTH 9 -#elif MQTT_VERSION == MQTT_VERSION_3_1_1 - uint8_t d[7] = {0x00,0x04,'M','Q','T','T',MQTT_VERSION}; -#define MQTT_HEADER_VERSION_LENGTH 7 -#endif - for (j = 0;j>1); - } - } - - buffer[length++] = v; - - buffer[length++] = ((MQTT_KEEPALIVE) >> 8); - buffer[length++] = ((MQTT_KEEPALIVE) & 0xFF); - - CHECK_STRING_LENGTH(length,id) - length = writeString(id,buffer,length); - if (willTopic) { - CHECK_STRING_LENGTH(length,willTopic) - length = writeString(willTopic,buffer,length); - CHECK_STRING_LENGTH(length,willMessage) - length = writeString(willMessage,buffer,length); - } - - if(user != NULL) { - CHECK_STRING_LENGTH(length,user) - length = writeString(user,buffer,length); - if(pass != NULL) { - CHECK_STRING_LENGTH(length,pass) - length = writeString(pass,buffer,length); - } - } - - write(MQTTCONNECT,buffer,length-MQTT_MAX_HEADER_SIZE); - - lastInActivity = lastOutActivity = millis(); - pingOutstanding = false; // See: https://github.com/knolleary/pubsubclient/pull/802 - - while (!_client->available()) { - delay(0); // Prevent watchdog crashes - unsigned long t = millis(); - if (t-lastInActivity >= ((int32_t) MQTT_SOCKET_TIMEOUT*1000UL)) { - _state = MQTT_CONNECTION_TIMEOUT; - _client->stop(); - return false; - } - } - uint8_t llen; - uint16_t len = readPacket(&llen); - - if (len == 4) { - if (buffer[3] == 0) { - lastInActivity = millis(); - pingOutstanding = false; - _state = MQTT_CONNECTED; - return true; - } else { - _state = buffer[3]; - } - } - _client->stop(); - } else { - _state = MQTT_CONNECT_FAILED; - } - return false; - } - return true; -} - -// reads a byte into result -boolean PubSubClient::readByte(uint8_t * result) { - if (_client == nullptr) { - return false; - } - uint32_t previousMillis = millis(); - while(!_client->available()) { - delay(1); // Prevent watchdog crashes - uint32_t currentMillis = millis(); - if(currentMillis - previousMillis >= ((int32_t) MQTT_SOCKET_TIMEOUT * 1000)){ - return false; - } - } - *result = _client->read(); - return true; -} - -// reads a byte into result[*index] and increments index -boolean PubSubClient::readByte(uint8_t * result, uint16_t * index){ - uint16_t current_index = *index; - uint8_t * write_address = &(result[current_index]); - if(readByte(write_address)){ - *index = current_index + 1; - return true; - } - return false; -} - -uint16_t PubSubClient::readPacket(uint8_t* lengthLength) { - if (!initBuffer()) { - return 0; - } - - uint16_t len = 0; - if(!readByte(buffer, &len)) return 0; - bool isPublish = (buffer[0]&0xF0) == MQTTPUBLISH; - uint32_t multiplier = 1; - uint16_t length = 0; - uint8_t digit = 0; - uint16_t skip = 0; - uint8_t start = 0; - - do { - if (len == 5) { - // Invalid remaining length encoding - kill the connection - _state = MQTT_DISCONNECTED; - _client->stop(); - return 0; - } - if(!readByte(&digit)) return 0; - buffer[len++] = digit; - length += (digit & 127) * multiplier; - multiplier *= 128; - } while ((digit & 128) != 0 && len < (MQTT_MAX_PACKET_SIZE -2)); - *lengthLength = len-1; - - if (isPublish) { - // Read in topic length to calculate bytes to skip over for Stream writing - if(!readByte(buffer, &len)) return 0; - if(!readByte(buffer, &len)) return 0; - skip = (buffer[*lengthLength+1]<<8)+buffer[*lengthLength+2]; - start = 2; - if (buffer[0]&MQTTQOS1) { - // skip message id - skip += 2; - } - } - - for (uint16_t i = start;istream) { - if (isPublish && len-*lengthLength-2>skip) { - this->stream->write(digit); - } - } - if (len < MQTT_MAX_PACKET_SIZE) { - buffer[len] = digit; - } - len++; - } - - if (!this->stream && len > MQTT_MAX_PACKET_SIZE) { - len = 0; // This will cause the packet to be ignored. - } - - return len; -} - -bool PubSubClient::loop_read() { - if (!initBuffer()) { - return false; - } - - if (_client == nullptr) { - return false; - } - if (!_client->available()) { - return false; - } - uint8_t llen; - uint16_t len = readPacket(&llen); - if (len == 0) { - return false; - } - unsigned long t = millis(); - lastInActivity = t; - uint8_t type = buffer[0]&0xF0; - - switch(type) { - case MQTTPUBLISH: - { - if (callback) { - const bool msgId_present = (buffer[0]&0x06) == MQTTQOS1; - const uint16_t tl_offset = llen+1; - const uint16_t tl = (buffer[tl_offset]<<8)+buffer[tl_offset+1]; /* topic length in bytes */ - const uint16_t topic_offset = tl_offset+2; - const uint16_t msgId_offset = topic_offset+tl; - const uint16_t payload_offset = msgId_present ? msgId_offset+2 : msgId_offset; - if (payload_offset >= MQTT_MAX_PACKET_SIZE) return false; - if (len < payload_offset) return false; - // Need to move the topic 1 byte to insert a '\0' at the end of the topic. - memmove(buffer+topic_offset-1,buffer+topic_offset,tl); /* move topic inside buffer 1 byte to front */ - buffer[topic_offset-1+tl] = 0; /* end the topic as a 'C' string with \x00 */ - char *topic = (char*) buffer+topic_offset-1; - uint8_t *payload; - // msgId only present for QOS>0 - if (msgId_present) { - const uint16_t msgId = (buffer[msgId_offset]<<8)+buffer[msgId_offset+1]; - payload = buffer+payload_offset; - callback(topic,payload,len-payload_offset); - if (_client->connected()) { - buffer[0] = MQTTPUBACK; - buffer[1] = 2; - buffer[2] = (msgId >> 8); - buffer[3] = (msgId & 0xFF); - if (_client->write(buffer,4) != 0) { - lastOutActivity = t; - } - } - } else { - // No msgId - payload = buffer+payload_offset; - callback(topic,payload,len-payload_offset); - } - } - break; - } - case MQTTPINGREQ: - { - if (_client->connected()) { - buffer[0] = MQTTPINGRESP; - buffer[1] = 0; - _client->write(buffer,2); - } - break; - } - case MQTTPINGRESP: - { - pingOutstanding = false; - break; - } - default: - return false; - } - return true; -} - -boolean PubSubClient::loop() { - loop_read(); - if (connected()) { - unsigned long t = millis(); - if ((t - lastInActivity > MQTT_KEEPALIVE*1000UL) || (t - lastOutActivity > MQTT_KEEPALIVE*1000UL)) { - if (pingOutstanding) { - this->_state = MQTT_CONNECTION_TIMEOUT; - _client->stop(); - return false; - } else { - buffer[0] = MQTTPINGREQ; - buffer[1] = 0; - if (_client->write(buffer,2) != 0) { - lastOutActivity = t; - lastInActivity = t; - } - pingOutstanding = true; - } - } - return true; - } - return false; -} - -boolean PubSubClient::publish(const char* topic, const char* payload) { - size_t plength = (payload != nullptr) ? strlen(payload) : 0; - return publish(topic,(const uint8_t*)payload,plength,false); -} - -boolean PubSubClient::publish(const char* topic, const char* payload, boolean retained) { - size_t plength = (payload != nullptr) ? strlen(payload) : 0; - return publish(topic,(const uint8_t*)payload,plength,retained); -} - -boolean PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength) { - return publish(topic, payload, plength, false); -} - -boolean PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength, boolean retained) { - if (!beginPublish(topic, plength, retained)) { - return false; - } - for (unsigned int i=0;iwrite(buffer+(MQTT_MAX_HEADER_SIZE-hlen),length-(MQTT_MAX_HEADER_SIZE-hlen)); - if (rc > 0) { - lastOutActivity = millis(); - } - return (rc == (length-(MQTT_MAX_HEADER_SIZE-hlen))); - } - return false; -} - -int PubSubClient::endPublish() { - flushBuffer(); - return 1; -} - -size_t PubSubClient::write(uint8_t data) { - if (_client == nullptr) { - lastOutActivity = millis(); - return 0; - } - size_t rc = appendBuffer(data); - if (rc != 0) { - lastOutActivity = millis(); - } - return rc; -} - -size_t PubSubClient::write(const uint8_t *data, size_t size) { - if (_client == nullptr) { - lastOutActivity = millis(); - return 0; - } - size_t rc = appendBuffer(data,size); - if (rc != 0) { - lastOutActivity = millis(); - } - return rc; -} - -size_t PubSubClient::write(const String& message) { - return write(reinterpret_cast(message.c_str()), message.length()); -} - - -size_t PubSubClient::buildHeader(uint8_t header, uint8_t* buf, uint32_t length) { - uint8_t lenBuf[4]; - uint8_t llen = 0; - uint8_t digit; - uint8_t pos = 0; - uint32_t len = length; - do { - digit = len % 128; - len = len / 128; - if (len > 0) { - digit |= 0x80; - } - lenBuf[pos++] = digit; - llen++; - } while(len>0 && pos < 4); - - buf[4-llen] = header; - for (int i=0;i 0) && result) { - delay(0); // Prevent watchdog crashes - bytesToWrite = (bytesRemaining > MQTT_MAX_TRANSFER_SIZE)?MQTT_MAX_TRANSFER_SIZE:bytesRemaining; - rc = _client->write(writeBuf,bytesToWrite); - result = (rc == bytesToWrite); - bytesRemaining -= rc; - writeBuf += rc; - } - return result; -#else - rc = _client->write(buf+(MQTT_MAX_HEADER_SIZE-hlen),length+hlen); - if (rc != 0) { - lastOutActivity = millis(); - } - return (rc == hlen+length); -#endif -} - -boolean PubSubClient::subscribe(const char* topic) { - return subscribe(topic, 0); -} - -boolean PubSubClient::subscribe(const char* topic, uint8_t qos) { - if (qos > 1) { - return false; - } - if (MQTT_MAX_PACKET_SIZE < 9 + strlen(topic)) { - // Too long - return false; - } - if (connected()) { - // Leave room in the buffer for header and variable length field - uint16_t length = MQTT_MAX_HEADER_SIZE; - nextMsgId++; - if (nextMsgId == 0) { - nextMsgId = 1; - } - buffer[length++] = (nextMsgId >> 8); - buffer[length++] = (nextMsgId & 0xFF); - length = writeString((char*)topic, buffer,length); - buffer[length++] = qos; - return write(MQTTSUBSCRIBE|MQTTQOS1,buffer,length-MQTT_MAX_HEADER_SIZE); - } - return false; -} - -boolean PubSubClient::unsubscribe(const char* topic) { - if (MQTT_MAX_PACKET_SIZE < 9 + strlen(topic)) { - // Too long - return false; - } - if (connected()) { - uint16_t length = MQTT_MAX_HEADER_SIZE; - nextMsgId++; - if (nextMsgId == 0) { - nextMsgId = 1; - } - buffer[length++] = (nextMsgId >> 8); - buffer[length++] = (nextMsgId & 0xFF); - length = writeString(topic, buffer,length); - return write(MQTTUNSUBSCRIBE|MQTTQOS1,buffer,length-MQTT_MAX_HEADER_SIZE); - } - return false; -} - -void PubSubClient::disconnect() { - if (_state == MQTT_DISCONNECTED || !initBuffer()) { - _state = MQTT_DISCONNECTED; - lastInActivity = lastOutActivity = millis(); - - return; - } - - buffer[0] = MQTTDISCONNECT; - buffer[1] = 0; - if (_client != nullptr) { - _client->write(buffer,2); - _client->flush(); - _client->stop(); - } - _state = MQTT_DISCONNECTED; - lastInActivity = lastOutActivity = millis(); -} - -uint16_t PubSubClient::writeString(const char* string, uint8_t* buf, uint16_t pos) { - const char* idp = string; - uint16_t i = 0; - pos += 2; - while (*idp && pos < (MQTT_MAX_PACKET_SIZE - 2)) { - buf[pos++] = *idp++; - i++; - } - buf[pos-i-2] = (i >> 8); - buf[pos-i-1] = (i & 0xFF); - return pos; -} - -size_t PubSubClient::appendBuffer(uint8_t data) { - if (!initBuffer()) { - return 0; - } - - buffer[_bufferWritePos] = data; - ++_bufferWritePos; - if (_bufferWritePos >= MQTT_MAX_PACKET_SIZE) { - if (flushBuffer() == 0) return 0; - } - return 1; -} - -size_t PubSubClient::appendBuffer(const uint8_t *data, size_t size) { - for (size_t i = 0; i < size; ++i) { - if (appendBuffer(data[i]) == 0) return i; - } - return size; -} - -size_t PubSubClient::flushBuffer() { - size_t rc = 0; - if (_bufferWritePos > 0) { - if (connected()) { - rc = _client->write(buffer, _bufferWritePos); - if (rc != 0) { - lastOutActivity = millis(); - } - } - _bufferWritePos = 0; - } - return rc; -} - -bool PubSubClient::initBuffer() -{ - if (buffer == nullptr) { -#ifdef USE_SECOND_HEAP - HeapSelectIram ephemeral; -#endif - buffer = (uint8_t*) malloc(sizeof(uint8_t) * MQTT_MAX_PACKET_SIZE); - } - return buffer != nullptr; -} - -boolean PubSubClient::connected() { - if (_client == NULL ) { - this->_state = MQTT_DISCONNECTED; - return false; - } - if (_client->connected() == 0) { - bool lastStateConnected = this->_state == MQTT_CONNECTED; - this->disconnect(); - if (lastStateConnected) { - this->_state = MQTT_CONNECTION_LOST; - } - return false; - } - return this->_state == MQTT_CONNECTED; -} - -PubSubClient& PubSubClient::setServer(uint8_t * ip, uint16_t port) { - IPAddress addr(ip[0],ip[1],ip[2],ip[3]); - return setServer(addr,port); -} - -PubSubClient& PubSubClient::setServer(IPAddress ip, uint16_t port) { - this->ip = ip; - this->port = port; - this->domain = ""; - return *this; -} - -PubSubClient& PubSubClient::setServer(const char * domain, uint16_t port) { - this->domain = domain; - this->port = port; - return *this; -} - -PubSubClient& PubSubClient::setCallback(MQTT_CALLBACK_SIGNATURE) { - this->callback = callback; - return *this; -} - -PubSubClient& PubSubClient::setClient(Client& client){ - this->_client = &client; - return *this; -} - -PubSubClient& PubSubClient::setStream(Stream& stream){ - this->stream = &stream; - return *this; -} - -int PubSubClient::state() { - return this->_state; -} +/* + PubSubClient.cpp - A simple client for MQTT. + Nick O'Leary + http://knolleary.net +*/ + +#include "PubSubClient.h" +#include + +#ifdef ESP32 +#include +#endif + +#ifdef USE_SECOND_HEAP + #include +#endif + + +PubSubClient::PubSubClient() { + this->_state = MQTT_DISCONNECTED; + this->_client = NULL; + this->stream = NULL; + this->keepAlive_sec = MQTT_KEEPALIVE; + this->socketTimeout_msec = MQTT_SOCKET_TIMEOUT * 1000; + + setCallback(NULL); +} + +PubSubClient::PubSubClient(Client& client) { + this->_state = MQTT_DISCONNECTED; + setClient(client); + this->stream = NULL; + this->keepAlive_sec = MQTT_KEEPALIVE; + this->socketTimeout_msec = MQTT_SOCKET_TIMEOUT * 1000; +} + +PubSubClient::PubSubClient(IPAddress addr, uint16_t port, Client& client) { + this->_state = MQTT_DISCONNECTED; + setServer(addr, port); + setClient(client); + this->stream = NULL; + this->keepAlive_sec = MQTT_KEEPALIVE; + this->socketTimeout_msec = MQTT_SOCKET_TIMEOUT * 1000; +} +PubSubClient::PubSubClient(IPAddress addr, uint16_t port, Client& client, Stream& stream) { + this->_state = MQTT_DISCONNECTED; + setServer(addr,port); + setClient(client); + setStream(stream); + this->keepAlive_sec = MQTT_KEEPALIVE; + this->socketTimeout_msec = MQTT_SOCKET_TIMEOUT * 1000; +} +PubSubClient::PubSubClient(IPAddress addr, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) { + this->_state = MQTT_DISCONNECTED; + setServer(addr, port); + setCallback(callback); + setClient(client); + this->stream = NULL; + this->keepAlive_sec = MQTT_KEEPALIVE; + this->socketTimeout_msec = MQTT_SOCKET_TIMEOUT * 1000; +} +PubSubClient::PubSubClient(IPAddress addr, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) { + this->_state = MQTT_DISCONNECTED; + setServer(addr,port); + setCallback(callback); + setClient(client); + setStream(stream); + this->keepAlive_sec = MQTT_KEEPALIVE; + this->socketTimeout_msec = MQTT_SOCKET_TIMEOUT * 1000; +} + +PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, Client& client) { + this->_state = MQTT_DISCONNECTED; + setServer(ip, port); + setClient(client); + this->stream = NULL; + this->keepAlive_sec = MQTT_KEEPALIVE; + this->socketTimeout_msec = MQTT_SOCKET_TIMEOUT * 1000; +} +PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, Client& client, Stream& stream) { + this->_state = MQTT_DISCONNECTED; + setServer(ip,port); + setClient(client); + setStream(stream); + this->keepAlive_sec = MQTT_KEEPALIVE; + this->socketTimeout_msec = MQTT_SOCKET_TIMEOUT * 1000; +} +PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) { + this->_state = MQTT_DISCONNECTED; + setServer(ip, port); + setCallback(callback); + setClient(client); + this->stream = NULL; + this->keepAlive_sec = MQTT_KEEPALIVE; + this->socketTimeout_msec = MQTT_SOCKET_TIMEOUT * 1000; +} +PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) { + this->_state = MQTT_DISCONNECTED; + setServer(ip,port); + setCallback(callback); + setClient(client); + setStream(stream); + this->keepAlive_sec = MQTT_KEEPALIVE; + this->socketTimeout_msec = MQTT_SOCKET_TIMEOUT * 1000; +} + +PubSubClient::PubSubClient(const char* domain, uint16_t port, Client& client) { + this->_state = MQTT_DISCONNECTED; + setServer(domain,port); + setClient(client); + this->stream = NULL; + this->keepAlive_sec = MQTT_KEEPALIVE; + this->socketTimeout_msec = MQTT_SOCKET_TIMEOUT * 1000; +} +PubSubClient::PubSubClient(const char* domain, uint16_t port, Client& client, Stream& stream) { + this->_state = MQTT_DISCONNECTED; + setServer(domain,port); + setClient(client); + setStream(stream); + this->keepAlive_sec = MQTT_KEEPALIVE; + this->socketTimeout_msec = MQTT_SOCKET_TIMEOUT * 1000; +} +PubSubClient::PubSubClient(const char* domain, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) { + this->_state = MQTT_DISCONNECTED; + setServer(domain,port); + setCallback(callback); + setClient(client); + this->stream = NULL; + this->keepAlive_sec = MQTT_KEEPALIVE; + this->socketTimeout_msec = MQTT_SOCKET_TIMEOUT * 1000; +} +PubSubClient::PubSubClient(const char* domain, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) { + this->_state = MQTT_DISCONNECTED; + setServer(domain,port); + setCallback(callback); + setClient(client); + setStream(stream); + this->keepAlive_sec = MQTT_KEEPALIVE; + this->socketTimeout_msec = MQTT_SOCKET_TIMEOUT * 1000; +} + +PubSubClient::~PubSubClient() +{ + if (buffer != nullptr) { + free(buffer); + buffer = nullptr; + } +} + +boolean PubSubClient::connect(const char *id) { + return connect(id,NULL,NULL,0,0,0,0,1); +} + +boolean PubSubClient::connect(const char *id, const char *user, const char *pass) { + return connect(id,user,pass,0,0,0,0,1); +} + +boolean PubSubClient::connect(const char *id, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage) { + return connect(id,NULL,NULL,willTopic,willQos,willRetain,willMessage,1); +} + +boolean PubSubClient::connect(const char *id, const char *user, const char *pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage) { + return connect(id,user,pass,willTopic,willQos,willRetain,willMessage,1); +} + +boolean PubSubClient::connect(const char *id, const char *user, const char *pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage, boolean cleanSession) { + if (!initBuffer()) { + return false; + } + + if (!connected()) { + int result = 0; + + if (_client == nullptr) { + return false; + } + if (_client->connected()) { + result = 1; + } else { + if (domain.length() != 0) { +#if defined(ESP32) && ESP_IDF_VERSION_MAJOR < 5 + WiFiClient* wfc = (WiFiClient*)_client; + result = wfc->connect(this->domain.c_str(), this->port, _client->getTimeout()); +#else + result = _client->connect(this->domain.c_str(), this->port); +#endif + } else { +#if defined(ESP32) && ESP_IDF_VERSION_MAJOR < 5 + WiFiClient* wfc = (WiFiClient*)_client; + result = wfc->connect(this->ip, this->port, _client->getTimeout()); +#else + result = _client->connect(this->ip, this->port); +#endif + } + } + if (result == 1) { + nextMsgId = 1; + // Leave room in the buffer for header and variable length field + uint16_t length = MQTT_MAX_HEADER_SIZE; + unsigned int j; + +#if MQTT_VERSION == MQTT_VERSION_3_1 + uint8_t d[9] = {0x00,0x06,'M','Q','I','s','d','p', MQTT_VERSION}; +#define MQTT_HEADER_VERSION_LENGTH 9 +#elif MQTT_VERSION == MQTT_VERSION_3_1_1 + uint8_t d[7] = {0x00,0x04,'M','Q','T','T',MQTT_VERSION}; +#define MQTT_HEADER_VERSION_LENGTH 7 +#endif + for (j = 0;j>1); + } + } + + buffer[length++] = v; + + buffer[length++] = ((keepAlive_sec) >> 8); + buffer[length++] = ((keepAlive_sec) & 0xFF); + + CHECK_STRING_LENGTH(length,id) + length = writeString(id,buffer,length); + if (willTopic) { + CHECK_STRING_LENGTH(length,willTopic) + length = writeString(willTopic,buffer,length); + CHECK_STRING_LENGTH(length,willMessage) + length = writeString(willMessage,buffer,length); + } + + if(user != NULL) { + CHECK_STRING_LENGTH(length,user) + length = writeString(user,buffer,length); + if(pass != NULL) { + CHECK_STRING_LENGTH(length,pass) + length = writeString(pass,buffer,length); + } + } + + write(MQTTCONNECT,buffer,length-MQTT_MAX_HEADER_SIZE); + + lastInActivity = lastOutActivity = millis(); + pingOutstanding = false; // See: https://github.com/knolleary/pubsubclient/pull/802 + + while (!_client->available()) { + delay(0); // Prevent watchdog crashes + unsigned long t = millis(); + if ((int32_t)(t - lastInActivity) >= socketTimeout_msec) { + _state = MQTT_CONNECTION_TIMEOUT; + _client->stop(); + return false; + } + } + uint8_t llen; + uint16_t len = readPacket(&llen); + + if (len == 4) { + if (buffer[3] == 0) { + lastInActivity = millis(); + pingOutstanding = false; + _state = MQTT_CONNECTED; + return true; + } else { + _state = buffer[3]; + } + } + _client->stop(); + } else { + _state = MQTT_CONNECT_FAILED; + } + return false; + } + return true; +} + +// reads a byte into result +boolean PubSubClient::readByte(uint8_t * result) { + if (_client == nullptr) { + return false; + } + uint32_t previousMillis = millis(); + + while(!_client->available() && _client->connected()) { + delay(1); // Prevent watchdog crashes + + if((int32_t)(millis() - previousMillis) >= socketTimeout_msec){ + return false; + } + } + *result = _client->read(); + return true; +} + +// reads a byte into result[*index] and increments index +boolean PubSubClient::readByte(uint8_t * result, uint16_t * index){ + uint16_t current_index = *index; + uint8_t * write_address = &(result[current_index]); + if(readByte(write_address)){ + *index = current_index + 1; + return true; + } + return false; +} + +uint16_t PubSubClient::readPacket(uint8_t* lengthLength) { + if (!initBuffer()) { + return 0; + } + + uint16_t len = 0; + if(!readByte(buffer, &len)) return 0; + bool isPublish = (buffer[0]&0xF0) == MQTTPUBLISH; + uint32_t multiplier = 1; + uint16_t length = 0; + uint8_t digit = 0; + uint16_t skip = 0; + uint8_t start = 0; + + do { + if (len == 5) { + // Invalid remaining length encoding - kill the connection + _state = MQTT_DISCONNECTED; + _client->stop(); + return 0; + } + if(!readByte(&digit)) return 0; + buffer[len++] = digit; + length += (digit & 127) * multiplier; + multiplier *= 128; + } while ((digit & 128) != 0 && len < (MQTT_MAX_PACKET_SIZE -2)); + *lengthLength = len-1; + + if (isPublish) { + // Read in topic length to calculate bytes to skip over for Stream writing + if(!readByte(buffer, &len)) return 0; + if(!readByte(buffer, &len)) return 0; + skip = (buffer[*lengthLength+1]<<8)+buffer[*lengthLength+2]; + start = 2; + if (buffer[0]&MQTTQOS1) { + // skip message id + skip += 2; + } + } + + for (uint16_t i = start;istream) { + if (isPublish && len-*lengthLength-2>skip) { + this->stream->write(digit); + } + } + if (len < MQTT_MAX_PACKET_SIZE) { + buffer[len] = digit; + } + len++; + } + + if (!this->stream && len > MQTT_MAX_PACKET_SIZE) { + len = 0; // This will cause the packet to be ignored. + } + + return len; +} + +bool PubSubClient::loop_read() { + if (!initBuffer()) { + return false; + } + + if (_client == nullptr) { + return false; + } + if (!_client->available()) { + return false; + } + uint8_t llen; + uint16_t len = readPacket(&llen); + if (len == 0) { + return false; + } + unsigned long t = millis(); + lastInActivity = t; + uint8_t type = buffer[0]&0xF0; + + switch(type) { + case MQTTPUBLISH: + { + if (callback) { + const bool msgId_present = (buffer[0]&0x06) == MQTTQOS1; + const uint16_t tl_offset = llen+1; + const uint16_t tl = (buffer[tl_offset]<<8)+buffer[tl_offset+1]; /* topic length in bytes */ + const uint16_t topic_offset = tl_offset+2; + const uint16_t msgId_offset = topic_offset+tl; + const uint16_t payload_offset = msgId_present ? msgId_offset+2 : msgId_offset; + if (payload_offset >= MQTT_MAX_PACKET_SIZE) return false; + if (len < payload_offset) return false; + // Need to move the topic 1 byte to insert a '\0' at the end of the topic. + memmove(buffer+topic_offset-1,buffer+topic_offset,tl); /* move topic inside buffer 1 byte to front */ + buffer[topic_offset-1+tl] = 0; /* end the topic as a 'C' string with \x00 */ + char *topic = (char*) buffer+topic_offset-1; + uint8_t *payload; + // msgId only present for QOS>0 + if (msgId_present) { + const uint16_t msgId = (buffer[msgId_offset]<<8)+buffer[msgId_offset+1]; + payload = buffer+payload_offset; + callback(topic,payload,len-payload_offset); + if (_client->connected()) { + buffer[0] = MQTTPUBACK; + buffer[1] = 2; + buffer[2] = (msgId >> 8); + buffer[3] = (msgId & 0xFF); + if (_client->write(buffer,4) != 0) { + lastOutActivity = t; + } + } + } else { + // No msgId + payload = buffer+payload_offset; + callback(topic,payload,len-payload_offset); + } + } + break; + } + case MQTTPINGREQ: + { + if (_client->connected()) { + buffer[0] = MQTTPINGRESP; + buffer[1] = 0; + if (_client->write(buffer,2) != 0) { + lastOutActivity = t; + } + } + break; + } + case MQTTPINGRESP: + { + pingOutstanding = false; + break; + } + default: + return false; + } + return true; +} + +boolean PubSubClient::loop() { + loop_read(); + if (connected()) { + unsigned long t = millis(); + // Send message at 2/3 of keepalive interval + // Wait for server-sent keep-alive till 3/2 of keepalive interval + // Just to make sure the broker will not disconnect us + const int32_t keepalive_66pct = pingOutstanding + ? keepAlive_sec * 1500 + : keepAlive_sec * 666; + if (((int32_t)(t - lastInActivity) > keepalive_66pct) || + ((int32_t)(t - lastOutActivity) > keepalive_66pct)) { + if (pingOutstanding) { + this->_state = MQTT_CONNECTION_TIMEOUT; + _client->stop(); + return false; + } else { + buffer[0] = MQTTPINGREQ; + buffer[1] = 0; + if (_client->write(buffer,2) != 0) { + lastOutActivity = t; + lastInActivity = t; + } + pingOutstanding = true; + } + } + return true; + } + return false; +} + +boolean PubSubClient::publish(const char* topic, const char* payload) { + size_t plength = (payload != nullptr) ? strlen(payload) : 0; + return publish(topic,(const uint8_t*)payload,plength,false); +} + +boolean PubSubClient::publish(const char* topic, const char* payload, boolean retained) { + size_t plength = (payload != nullptr) ? strlen(payload) : 0; + return publish(topic,(const uint8_t*)payload,plength,retained); +} + +boolean PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength) { + return publish(topic, payload, plength, false); +} + +boolean PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength, boolean retained) { + if (!beginPublish(topic, plength, retained)) { + return false; + } + for (unsigned int i=0;iwrite(buffer+(MQTT_MAX_HEADER_SIZE-hlen),length-(MQTT_MAX_HEADER_SIZE-hlen)); + if (rc > 0) { + lastOutActivity = millis(); + } + return (rc == (length-(MQTT_MAX_HEADER_SIZE-hlen))); + } + return false; +} + +int PubSubClient::endPublish() { + flushBuffer(); + return 1; +} + +size_t PubSubClient::write(uint8_t data) { + if (_client == nullptr) { + lastOutActivity = millis(); + return 0; + } + size_t rc = appendBuffer(data); + if (rc != 0) { + lastOutActivity = millis(); + } + return rc; +} + +size_t PubSubClient::write(const uint8_t *data, size_t size) { + if (_client == nullptr) { + lastOutActivity = millis(); + return 0; + } + size_t rc = appendBuffer(data,size); + if (rc != 0) { + lastOutActivity = millis(); + } + return rc; +} + +size_t PubSubClient::write(const String& message) { + return write(reinterpret_cast(message.c_str()), message.length()); +} + + +size_t PubSubClient::buildHeader(uint8_t header, uint8_t* buf, uint32_t length) { + uint8_t lenBuf[4]; + uint8_t llen = 0; + uint8_t digit; + uint8_t pos = 0; + uint32_t len = length; + do { + digit = len % 128; + len = len / 128; + if (len > 0) { + digit |= 0x80; + } + lenBuf[pos++] = digit; + llen++; + } while(len>0 && pos < 4); + + buf[4-llen] = header; + for (int i=0;i 0) && result) { + delay(0); // Prevent watchdog crashes + bytesToWrite = (bytesRemaining > MQTT_MAX_TRANSFER_SIZE)?MQTT_MAX_TRANSFER_SIZE:bytesRemaining; + rc = _client->write(writeBuf,bytesToWrite); + result = (rc == bytesToWrite); + bytesRemaining -= rc; + writeBuf += rc; + if (rc != 0) { + lastOutActivity = millis(); + } + } + return result; +#else + rc = _client->write(buf+(MQTT_MAX_HEADER_SIZE-hlen),length+hlen); + if (rc != 0) { + lastOutActivity = millis(); + } + return (rc == hlen+length); +#endif +} + +boolean PubSubClient::subscribe(const char* topic) { + return subscribe(topic, 0); +} + +boolean PubSubClient::subscribe(const char* topic, uint8_t qos) { + if (qos > 1) { + return false; + } + if (MQTT_MAX_PACKET_SIZE < 9 + strlen(topic)) { + // Too long + return false; + } + if (connected()) { + // Leave room in the buffer for header and variable length field + uint16_t length = MQTT_MAX_HEADER_SIZE; + nextMsgId++; + if (nextMsgId == 0) { + nextMsgId = 1; + } + buffer[length++] = (nextMsgId >> 8); + buffer[length++] = (nextMsgId & 0xFF); + length = writeString((char*)topic, buffer,length); + buffer[length++] = qos; + return write(MQTTSUBSCRIBE|MQTTQOS1,buffer,length-MQTT_MAX_HEADER_SIZE); + } + return false; +} + +boolean PubSubClient::unsubscribe(const char* topic) { + if (MQTT_MAX_PACKET_SIZE < 9 + strlen(topic)) { + // Too long + return false; + } + if (connected()) { + uint16_t length = MQTT_MAX_HEADER_SIZE; + nextMsgId++; + if (nextMsgId == 0) { + nextMsgId = 1; + } + buffer[length++] = (nextMsgId >> 8); + buffer[length++] = (nextMsgId & 0xFF); + length = writeString(topic, buffer,length); + return write(MQTTUNSUBSCRIBE|MQTTQOS1,buffer,length-MQTT_MAX_HEADER_SIZE); + } + return false; +} + +void PubSubClient::disconnect() { + if (_state == MQTT_DISCONNECTED || !initBuffer()) { + _state = MQTT_DISCONNECTED; + lastInActivity = lastOutActivity = millis(); + + return; + } + + buffer[0] = MQTTDISCONNECT; + buffer[1] = 0; + if (_client != nullptr) { + _client->write(buffer,2); + _client->flush(); + _client->stop(); + } + _state = MQTT_DISCONNECTED; + lastInActivity = lastOutActivity = millis(); +} + +uint16_t PubSubClient::writeString(const char* string, uint8_t* buf, uint16_t pos) { + const char* idp = string; + uint16_t i = 0; + pos += 2; + while (*idp && pos < (MQTT_MAX_PACKET_SIZE - 2)) { + buf[pos++] = *idp++; + i++; + } + buf[pos-i-2] = (i >> 8); + buf[pos-i-1] = (i & 0xFF); + return pos; +} + +size_t PubSubClient::appendBuffer(uint8_t data) { + if (!initBuffer()) { + return 0; + } + + buffer[_bufferWritePos] = data; + ++_bufferWritePos; + if (_bufferWritePos >= MQTT_MAX_PACKET_SIZE) { + if (flushBuffer() == 0) return 0; + } + return 1; +} + +size_t PubSubClient::appendBuffer(const uint8_t *data, size_t size) { + for (size_t i = 0; i < size; ++i) { + if (appendBuffer(data[i]) == 0) return i; + } + return size; +} + +size_t PubSubClient::flushBuffer() { + size_t rc = 0; + if (_bufferWritePos > 0) { + if (connected()) { + rc = _client->write(buffer, _bufferWritePos); + if (rc != 0) { + lastOutActivity = millis(); + } + } + _bufferWritePos = 0; + } + return rc; +} + +bool PubSubClient::initBuffer() +{ + constexpr size_t size = sizeof(uint8_t) * MQTT_MAX_PACKET_SIZE; + if (buffer == nullptr) { +#ifdef ESP32 + buffer = (uint8_t*) heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); + if (buffer == nullptr) { + buffer = (uint8_t*) malloc(size); + } +#else + { +#ifdef USE_SECOND_HEAP + // Try allocating on ESP8266 2nd heap + HeapSelectIram ephemeral; +#endif + buffer = (uint8_t*) malloc(size); + if (buffer != nullptr) return true; + } +#ifdef USE_SECOND_HEAP + // Not successful, try allocating on (ESP8266) main heap + HeapSelectDram ephemeral; +#endif + buffer = (uint8_t*) malloc(size); +#endif + } + return buffer != nullptr; +} + +boolean PubSubClient::connected() { + if (_client == NULL ) { + this->_state = MQTT_DISCONNECTED; + return false; + } + if (_client->connected() == 0) { + bool lastStateConnected = this->_state == MQTT_CONNECTED; + this->disconnect(); + if (lastStateConnected) { + this->_state = MQTT_CONNECTION_LOST; + } + return false; + } + return this->_state == MQTT_CONNECTED; +} + +PubSubClient& PubSubClient::setServer(uint8_t * ip, uint16_t port) { + IPAddress addr(ip[0],ip[1],ip[2],ip[3]); + return setServer(addr,port); +} + +PubSubClient& PubSubClient::setServer(IPAddress ip, uint16_t port) { + this->ip = ip; + this->port = port; + this->domain = ""; + return *this; +} + +PubSubClient& PubSubClient::setServer(const char * domain, uint16_t port) { + this->domain = domain; + this->port = port; + return *this; +} + +PubSubClient& PubSubClient::setCallback(MQTT_CALLBACK_SIGNATURE) { + this->callback = callback; + return *this; +} + +PubSubClient& PubSubClient::setClient(Client& client){ + this->_client = &client; + return *this; +} + +PubSubClient& PubSubClient::setStream(Stream& stream){ + this->stream = &stream; + return *this; +} + +int PubSubClient::state() { + return this->_state; +} + +PubSubClient& PubSubClient::setKeepAlive(uint16_t keepAlive_sec) { + this->keepAlive_sec = keepAlive_sec; + return *this; +} + +PubSubClient& PubSubClient::setSocketTimeout(uint16_t timeout_ms) { + this->socketTimeout_msec = timeout_ms; + return *this; +} \ No newline at end of file diff --git a/lib/pubsubclient/src/PubSubClient.h b/lib/pubsubclient/src/PubSubClient.h index 199e243bfd..4a59af578c 100644 --- a/lib/pubsubclient/src/PubSubClient.h +++ b/lib/pubsubclient/src/PubSubClient.h @@ -100,10 +100,10 @@ class PubSubClient : public Print { private: Client* _client; uint8_t *buffer = nullptr; - uint16_t nextMsgId; - unsigned long lastOutActivity; - unsigned long lastInActivity; - bool pingOutstanding; + uint16_t nextMsgId = 0; + unsigned long lastOutActivity = 0; + unsigned long lastInActivity = 0; + bool pingOutstanding = false; MQTT_CALLBACK_SIGNATURE; // Try to read from the client whatever is available. bool loop_read(); @@ -127,10 +127,12 @@ class PubSubClient : public Print { IPAddress ip; String domain; - uint16_t port; + uint16_t port = 0; Stream* stream; - int _state; + int _state = MQTT_DISCONNECTED; int _bufferWritePos = 0; + int16_t keepAlive_sec = MQTT_KEEPALIVE; + int16_t socketTimeout_msec = MQTT_SOCKET_TIMEOUT*1000; public: PubSubClient(); PubSubClient(Client& client); @@ -194,6 +196,9 @@ class PubSubClient : public Print { boolean loop(); boolean connected(); int state(); + + PubSubClient& setKeepAlive(uint16_t keepAlive_sec); + PubSubClient& setSocketTimeout(uint16_t timeout_ms); }; diff --git a/platformio.ini b/platformio.ini index 07784dfedd..2765912768 100644 --- a/platformio.ini +++ b/platformio.ini @@ -1,108 +1,108 @@ -; -; PlatformIO Project Configuration File -; -; Please make sure to read documentation with examples first -; http://docs.platformio.org/en/stable/projectconf.html -; - -; *********************************************************************; -; You can uncomment or add here Your favorite environment you want to work on at the moment -; (uncomment only one !) -; *********************************************************************; - -[platformio] -description = Firmware for ESP82xx/ESP32/ESP32-S2/ESP32-S3/ESP32-C3 for easy IoT deployment of sensors. -boards_dir = boards -lib_dir = lib -extra_configs = - platformio_core_defs.ini - platformio_esp82xx_base.ini - platformio_esp82xx_envs.ini - platformio_esp32_envs.ini - platformio_esp32_solo1.ini - platformio_esp32c3_envs.ini - platformio_esp32s2_envs.ini - platformio_esp32s3_envs.ini - platformio_special_envs.ini - platformio_esp32c2_envs.ini - platformio_esp32c6_envs.ini - -;default_envs = normal_ESP32_4M -default_envs = max_ESP32_16M8M_LittleFS_ETH -;default_envs = normal_ESP32c6_4M316k_LittleFS_CDC -; default_envs = custom_ESP8266_4M1M - -;default_envs = normal_ESP8266_4M1M -;default_envs = test_beta_ESP8266_4M1M -; ..etc -;build_cache_dir = $PROJECT_DIR\.buildcache - - -; add these: -; -Werror -Wall -Wextra -pedantic -Wcast-align -Wcast-qual -Wctor-dtor-privacy -Wdisabled-optimization -Wformat=2 -Winit-self -Wlogical-op -; -Wmissing-include-dirs -Wnoexcept -Wold-style-cast -Woverloaded-virtual -Wredundant-decls -Wshadow -Wsign-promo -Wstrict-null-sentinel -; -Wstrict-overflow=5 -Wundef -Wno-unused -Wno-variadic-macros -Wno-parentheses -fdiagnostics-show-option -; thanks @chouffe103 -[compiler_warnings] -build_flags = -Wall -Wno-parentheses -fdiagnostics-show-option - - -[minimal_size] -build_flags = - -Os - -ffunction-sections - -fdata-sections - -Wl,--gc-sections - -s - - -[espota] -upload_protocol = espota -; each flag in a new line -; Do not use port 8266 for OTA, since that's used for ESPeasy p2p -upload_flags_esp8266 = - --port=18266 -upload_flags_esp32 = - --port=3232 -build_flags = -DFEATURE_ARDUINO_OTA=1 -upload_port = 192.168.1.152 - - -[debug_flags] -;build_flags = -Wstack-usage=300 -build_flags = - -[mqtt_flags] -build_flags = -DMQTT_MAX_PACKET_SIZE=1024 - -[extra_scripts_default] -extra_scripts = pre:tools/pio/install-requirements.py - pre:tools/pio/set-ci-defines.py - pre:tools/pio/generate-compiletime-defines.py - tools/pio/copy_files.py - -[extra_scripts_esp8266] -extra_scripts = tools/pio/gzip-firmware.py - pre:tools/pio/concat_cpp_files.py - post:tools/pio/remove_concat_cpp_files.py - ${extra_scripts_default.extra_scripts} - - -[common] -lib_archive = false -lib_ldf_mode = chain -lib_compat_mode = strict -shared_libdeps_dir = lib -framework = arduino -upload_speed = 115200 -monitor_speed = 115200 -;targets = size, checkprogsize -targets = -src_filter = +<*> -<.git/> -<.svn/> - - - - -<*/Commands_tmp/> -<*/ControllerQueue_tmp/> -<*/DataStructs_tmp/> -<*/DataTypes_tmp/> -<*/ESPEasyCore_tmp/> -<*/Globals_tmp/> -<*/Helpers_tmp/> -<*/PluginStructs_tmp/> -<*/WebServer_tmp/> - -; Backwards compatibility: https://github.com/platformio/platformio-core/issues/4270 -;build_src_filter = +<*> -<.git/> -<.svn/> - - - - -<*/Commands/> -<*/ControllerQueue/> -<*/DataStructs/> -<*/DataTypes/> -<*/Globals/> -<*/Helpers/> -<*/PluginStructs/> -<*/WebServer/> - - -[env] -extends = common +; +; PlatformIO Project Configuration File +; +; Please make sure to read documentation with examples first +; http://docs.platformio.org/en/stable/projectconf.html +; + +; *********************************************************************; +; You can uncomment or add here Your favorite environment you want to work on at the moment +; (uncomment only one !) +; *********************************************************************; + +[platformio] +description = Firmware for ESP82xx/ESP32/ESP32-S2/ESP32-S3/ESP32-C3 for easy IoT deployment of sensors. +boards_dir = boards +lib_dir = lib +extra_configs = + platformio_core_defs.ini + platformio_esp82xx_base.ini + platformio_esp82xx_envs.ini + platformio_esp32_envs.ini + platformio_esp32_solo1.ini + platformio_esp32c3_envs.ini + platformio_esp32s2_envs.ini + platformio_esp32s3_envs.ini + platformio_special_envs.ini + platformio_esp32c2_envs.ini + platformio_esp32c6_envs.ini + +;default_envs = normal_ESP32_4M +default_envs = max_ESP32_16M8M_LittleFS_ETH +;default_envs = normal_ESP32c6_4M316k_LittleFS_CDC +; default_envs = custom_ESP8266_4M1M + +;default_envs = normal_ESP8266_4M1M +;default_envs = test_beta_ESP8266_4M1M +; ..etc +;build_cache_dir = $PROJECT_DIR\.buildcache + + +; add these: +; -Werror -Wall -Wextra -pedantic -Wcast-align -Wcast-qual -Wctor-dtor-privacy -Wdisabled-optimization -Wformat=2 -Winit-self -Wlogical-op +; -Wmissing-include-dirs -Wnoexcept -Wold-style-cast -Woverloaded-virtual -Wredundant-decls -Wshadow -Wsign-promo -Wstrict-null-sentinel +; -Wstrict-overflow=5 -Wundef -Wno-unused -Wno-variadic-macros -Wno-parentheses -fdiagnostics-show-option +; thanks @chouffe103 +[compiler_warnings] +build_flags = -Wall -Wno-parentheses -fdiagnostics-show-option + + +[minimal_size] +build_flags = + -Os + -ffunction-sections + -fdata-sections + -Wl,--gc-sections + -s + + +[espota] +upload_protocol = espota +; each flag in a new line +; Do not use port 8266 for OTA, since that's used for ESPeasy p2p +upload_flags_esp8266 = + --port=18266 +upload_flags_esp32 = + --port=3232 +build_flags = -DFEATURE_ARDUINO_OTA=1 +upload_port = 192.168.1.152 + + +[debug_flags] +;build_flags = -Wstack-usage=300 +build_flags = + +[mqtt_flags] +build_flags = -DMQTT_MAX_PACKET_SIZE=1024 + +[extra_scripts_default] +extra_scripts = pre:tools/pio/install-requirements.py + pre:tools/pio/set-ci-defines.py + pre:tools/pio/generate-compiletime-defines.py + tools/pio/copy_files.py + +[extra_scripts_esp8266] +extra_scripts = tools/pio/gzip-firmware.py + pre:tools/pio/remove_concat_cpp_files.py + pre:tools/pio/concat_cpp_files.py + ${extra_scripts_default.extra_scripts} + + +[common] +lib_archive = false +lib_ldf_mode = chain +lib_compat_mode = strict +shared_libdeps_dir = lib +framework = arduino +upload_speed = 115200 +monitor_speed = 115200 +;targets = size, checkprogsize +targets = +src_filter = +<*> -<.git/> -<.svn/> - - - - -<*/Commands_tmp/> -<*/ControllerQueue_tmp/> -<*/DataStructs_tmp/> -<*/DataTypes_tmp/> -<*/ESPEasyCore_tmp/> -<*/Globals_tmp/> -<*/Helpers_tmp/> -<*/PluginStructs_tmp/> -<*/WebServer_tmp/> + +; Backwards compatibility: https://github.com/platformio/platformio-core/issues/4270 +;build_src_filter = +<*> -<.git/> -<.svn/> - - - - -<*/Commands/> -<*/ControllerQueue/> -<*/DataStructs/> -<*/DataTypes/> -<*/Globals/> -<*/Helpers/> -<*/PluginStructs/> -<*/WebServer/> + + +[env] +extends = common diff --git a/platformio_core_defs.ini b/platformio_core_defs.ini index 75e04c8086..2defe40116 100644 --- a/platformio_core_defs.ini +++ b/platformio_core_defs.ini @@ -1,232 +1,235 @@ -; ********************************************************************* - -; **** Definition cheat sheet: -; board_build.flash_mode in terms of performance: QIO > QOUT > DIO > DOUT -; for lib_ldf_mode, see http://docs.platformio.org/en/latest/librarymanager/ldf.html;ldf - -; **** Frequently used build flags: -; Use custom.h file to override default settings for ESPeasy: -D USE_CUSTOM_H -; Set VCC mode to measure Vcc of ESP chip : -D FEATURE_ADC_VCC=1 - -; Build Flags: -; -DUSE_CONFIG_OVERRIDE -; lwIP 1.4 (Default) -; -DPIO_FRAMEWORK_ARDUINO_LWIP_HIGHER_BANDWIDTH -; lwIP 2 - Low Memory -; -DPIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY -; lwIP 2 - Higher Bandwitdh -; -DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH -; VTABLES in Flash (default) -; -DVTABLES_IN_FLASH -; VTABLES in Heap -; -DVTABLES_IN_DRAM -; VTABLES in IRAM -; -DVTABLES_IN_IRAM -; NO_EXTRA_4K_HEAP - this forces the default NONOS-SDK user's heap location -; Default currently overlaps cont stack (Arduino) with sys stack (System) -; to save up-to 4 kB of heap. (starting core_2.4.2) -; ESP8266_DISABLE_EXTRA4K - Calls disable_extra4k_at_link_time() from setup -; to force the linker keep user's stack in user ram. -; CONT_STACKSIZE to set the 'cont' (Arduino) stack size. Default = 4096 -; -mtarget-align see: https://github.com/arendst/Sonoff-Tasmota/issues/3678#issuecomment-419712437 - -[esp82xx_defaults] -build_flags = -D NDEBUG - -lstdc++ -lsupc++ - -mtarget-align - -DPIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY - -DVTABLES_IN_FLASH - -DPUYA_SUPPORT=1 - -DDISABLE_SC16IS752_SPI - -DCRON_USE_LOCAL_TIME - -fno-strict-aliasing - -I$PROJECT_DIR/src/include - -include "ESPEasy_config.h" - -lib_ignore = ESP32_ping - ESP32WebServer - ESP32HTTPUpdateServer - ServoESP32 - IRremoteESP8266 - HeatpumpIR - TinyWireM - ESP8266SdFat - SD(esp8266) - SD - SDFS - LittleFS(esp8266) - LittleFS - ArduinoOTA - ESP8266mDNS - I2C AXP192 Power management -; EspSoftwareSerial - - - -; Keep optimization flag to -O2 -; See: https://github.com/platformio/platform-espressif8266/issues/288 -; For "-fno-strict-aliasing" -; See: https://github.com/esp8266/Arduino/issues/8261 -[esp82xx_2_7_x] -build_flags = -DNDEBUG - -mtarget-align - -DVTABLES_IN_FLASH - -fno-exceptions - -lstdc++ - -DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH - -DPUYA_SUPPORT=1 - -DCORE_POST_2_5_0 - -DDISABLE_SC16IS752_SPI - -DCRON_USE_LOCAL_TIME - -fno-strict-aliasing - -DLIBRARIES_NO_LOG=1 - -DNO_GLOBAL_I2S - -I$PROJECT_DIR/src/include - -include "ESPEasy_config.h" - -O2 - -s - -DBEARSSL_SSL_BASIC - -DCORE_POST_2_6_0 - ; remove the 4-bytes alignment for PSTR() - -DPSTR_ALIGN=1 - -Werror=return-type -build_unflags = ${esp82xx_common.build_unflags} -lib_ignore = ${esp82xx_defaults.lib_ignore} - EspSoftwareSerial - - -[esp82xx_3_0_x] -build_flags = ${esp82xx_2_7_x.build_flags} - -DCORE_POST_3_0_0 - -Wno-deprecated-declarations -; -flto=auto -; -Wl,-flto -build_unflags = -DDEBUG_ESP_PORT - -fexceptions - -Wall -; -fno-lto -lib_ignore = ${esp82xx_defaults.lib_ignore} -extra_scripts = pre:tools/pio/pre_custom_esp8266_toolchain.py - - - -; See for SDK flags: https://github.com/esp8266/Arduino/blob/master/tools/platformio-build.py - -[core_2_7_4] -extends = esp82xx_2_7_x -platform = espressif8266@2.6.3 -platform_packages = - framework-arduinoespressif8266 @ https://github.com/esp8266/Arduino.git#2.7.4 -build_flags = ${esp82xx_2_7_x.build_flags} - -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK22x_190703 - -DUSES_LATEST_SOFTWARE_SERIAL_LIBRARY=0 - -Wno-deprecated-declarations - -DLIBRARIES_NO_LOG=1 -lib_ignore = ${esp82xx_2_7_x.lib_ignore} -build_unflags = ${esp82xx_2_7_x.build_unflags} -extra_scripts = ${esp82xx_common.extra_scripts} - - -[core_stage] -extends = esp82xx_3_0_x -platform = espressif8266@4.2.1 -platform_packages = -build_flags = ${esp82xx_3_0_x.build_flags} - -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK3 - -DUSES_LATEST_SOFTWARE_SERIAL_LIBRARY=1 - -DLIBRARIES_NO_LOG=1 - -DPHASE_LOCKED_WAVEFORM -build_unflags = ${esp82xx_3_0_x.build_unflags} -lib_ignore = ${esp82xx_defaults.lib_ignore} -extra_scripts = ${esp82xx_common.extra_scripts} - - - -; See: https://arduino-esp8266.readthedocs.io/en/latest/mmu.html -[core_stage_2ndheap] -extends = esp82xx_3_0_x -platform = espressif8266@4.2.1 -platform_packages = -build_flags = ${esp82xx_3_0_x.build_flags} - -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK3 - -DUSES_LATEST_SOFTWARE_SERIAL_LIBRARY=1 - -DLIBRARIES_NO_LOG=1 - -DPHASE_LOCKED_WAVEFORM - -DPIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48_SECHEAP_SHARED - -DUSE_SECOND_HEAP -build_unflags = ${esp82xx_3_0_x.build_unflags} -lib_ignore = ${core_stage.lib_ignore} -extra_scripts = ${esp82xx_common.extra_scripts} - - - -; Updated ESP-IDF to the latest stable 4.0.1 -; See: https://github.com/platformio/platform-espressif32/releases -; IDF 4.4 = platform-espressif32 3.4.x = espressif/arduino-esp32 tag 2.0.4 -; Just for those who lost track of the extremely confusing numbering schema. -; For MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS See: https://github.com/espressif/arduino-esp32/pull/6676 -[core_esp32_IDF4_4__2_0_14] -;platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.4.1/platform-espressif32-2.0.4.1.zip - -; debug boot log enabled -; See: https://github.com/letscontrolit/ESPEasy/pull/4200#issuecomment-1216929859 -;platform = https://github.com/Jason2866/platform-espressif32.git -;platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/936/framework-arduinoespressif32-443_esp421-9ce849ce72.tar.gz - -; debug boot log disabled -;platform = https://github.com/Jason2866/platform-espressif32.git -;platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/938/framework-arduinoespressif32-443_esp421-10ab11e815.tar.gz - -;platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.5.2/platform-espressif32-2.0.5.2.zip -;platform = https://github.com/tasmota/platform-espressif32/releases/download/2022.12.2/platform-espressif32.zip -;platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.01.01/platform-espressif32.zip -;platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/1212/framework-arduinoespressif32-release_v4.4-fb9a7685e1.zip -;platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.01.02/platform-espressif32.zip -;platform_packages = -;platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.02.00/platform-espressif32.zip -;platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/1243/framework-arduinoespressif32-lwip_timeout-ed6742e7f0.zip - -;platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.05.03/platform-espressif32.zip -;platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.06.04/platform-espressif32.zip -platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.10.03/platform-espressif32.zip -platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/1645/framework-arduinoespressif32-release_v4.4_spiffs-e3fc63b439.zip -build_flags = -DESP32_STAGE - -DESP_IDF_VERSION_MAJOR=4 - -DMUSTFIX_CLIENT_TIMEOUT_IN_SECONDS - -DLIBRARIES_NO_LOG=1 - -DDISABLE_SC16IS752_SPI - -DCONFIG_PM_ENABLE - -DCONFIG_FREERTOS_USE_TICKLESS_IDLE=1 - -DCONFIG_FREERTOS_IDLE_TIME_BEFORE_SLEEP=3 - -DNEOPIXEL_ESP32_RMT_DEFAULT - -DCRON_USE_LOCAL_TIME - -I$PROJECT_DIR/src/include - -include "sdkconfig.h" - -include "ESPEasy_config.h" - -include "esp32x_fixes.h" -lib_ignore = - -; ESP_IDF 5.1 -[core_esp32_IDF5_1__3_0_0] -;platform = https://github.com/tasmota/platform-espressif32/releases/download/2024.04.11/platform-espressif32.zip -;platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/2286/framework-arduinoespressif32-all-release_v5.1-11140aa.zip -;platform = https://github.com/tasmota/platform-espressif32/releases/download/2024.04.14/platform-espressif32.zip -;platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/2386/framework-arduinoespressif32-all-release_v5.1-324fdc1.zip -platform = https://github.com/tasmota/platform-espressif32/releases/download/2024.05.13/platform-espressif32.zip -platform_packages = -build_flags = -DESP32_STAGE - -DESP_IDF_VERSION_MAJOR=5 - -DLIBRARIES_NO_LOG=1 - -DDISABLE_SC16IS752_SPI - -DCONFIG_PM_ENABLE -; -DCONFIG_LWIP_L2_TO_L3_COPY -; -DETH_SPI_SUPPORTS_NO_IRQ=1 - -DCONFIG_FREERTOS_USE_TICKLESS_IDLE=1 - -DCONFIG_FREERTOS_IDLE_TIME_BEFORE_SLEEP=3 - -DNEOPIXEL_ESP32_RMT_DEFAULT - -DCRON_USE_LOCAL_TIME - -I$PROJECT_DIR/src/include - -include "sdkconfig.h" - -include "ESPEasy_config.h" - -include "esp32x_fixes.h" -lib_ignore = +; ********************************************************************* + +; **** Definition cheat sheet: +; board_build.flash_mode in terms of performance: QIO > QOUT > DIO > DOUT +; for lib_ldf_mode, see http://docs.platformio.org/en/latest/librarymanager/ldf.html;ldf + +; **** Frequently used build flags: +; Use custom.h file to override default settings for ESPeasy: -D USE_CUSTOM_H +; Set VCC mode to measure Vcc of ESP chip : -D FEATURE_ADC_VCC=1 + +; Build Flags: +; -DUSE_CONFIG_OVERRIDE +; lwIP 1.4 (Default) +; -DPIO_FRAMEWORK_ARDUINO_LWIP_HIGHER_BANDWIDTH +; lwIP 2 - Low Memory +; -DPIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY +; lwIP 2 - Higher Bandwitdh +; -DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH +; VTABLES in Flash (default) +; -DVTABLES_IN_FLASH +; VTABLES in Heap +; -DVTABLES_IN_DRAM +; VTABLES in IRAM +; -DVTABLES_IN_IRAM +; NO_EXTRA_4K_HEAP - this forces the default NONOS-SDK user's heap location +; Default currently overlaps cont stack (Arduino) with sys stack (System) +; to save up-to 4 kB of heap. (starting core_2.4.2) +; ESP8266_DISABLE_EXTRA4K - Calls disable_extra4k_at_link_time() from setup +; to force the linker keep user's stack in user ram. +; CONT_STACKSIZE to set the 'cont' (Arduino) stack size. Default = 4096 +; -mtarget-align see: https://github.com/arendst/Sonoff-Tasmota/issues/3678#issuecomment-419712437 + +[esp82xx_defaults] +build_flags = -D NDEBUG + -lstdc++ -lsupc++ + -mtarget-align + -DPIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY + -DVTABLES_IN_FLASH + -DPUYA_SUPPORT=1 + -DDISABLE_SC16IS752_SPI + -DCRON_USE_LOCAL_TIME + -fno-strict-aliasing + -I$PROJECT_DIR/src/include + -include "ESPEasy_config.h" + +lib_ignore = ESP32_ping + ESP32WebServer + ESP32HTTPUpdateServer + ServoESP32 + IRremoteESP8266 + HeatpumpIR + TinyWireM + ESP8266SdFat + SD(esp8266) + SD + SDFS + LittleFS(esp8266) + LittleFS + ArduinoOTA + ESP8266mDNS + I2C AXP192 Power management +; EspSoftwareSerial + + + +; Keep optimization flag to -O2 +; See: https://github.com/platformio/platform-espressif8266/issues/288 +; For "-fno-strict-aliasing" +; See: https://github.com/esp8266/Arduino/issues/8261 +[esp82xx_2_7_x] +build_flags = -DNDEBUG + -mtarget-align + -DVTABLES_IN_FLASH + -fno-exceptions + -lstdc++ + -DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH + -DPUYA_SUPPORT=1 + -DCORE_POST_2_5_0 + -DDISABLE_SC16IS752_SPI + -DCRON_USE_LOCAL_TIME + -fno-strict-aliasing + -DLIBRARIES_NO_LOG=1 + -DNO_GLOBAL_I2S + -I$PROJECT_DIR/src/include + -include "ESPEasy_config.h" + -O2 + -s + -DBEARSSL_SSL_BASIC + -DCORE_POST_2_6_0 + ; remove the 4-bytes alignment for PSTR() + -DPSTR_ALIGN=1 + -Werror=return-type +build_unflags = ${esp82xx_common.build_unflags} +lib_ignore = ${esp82xx_defaults.lib_ignore} + EspSoftwareSerial + + +[esp82xx_3_0_x] +build_flags = ${esp82xx_2_7_x.build_flags} + -DCORE_POST_3_0_0 + -Wno-deprecated-declarations +; -flto=auto +; -Wl,-flto +build_unflags = -DDEBUG_ESP_PORT + -fexceptions + -Wall +; -fno-lto +lib_ignore = ${esp82xx_defaults.lib_ignore} +extra_scripts = pre:tools/pio/pre_custom_esp8266_toolchain.py + + + +; See for SDK flags: https://github.com/esp8266/Arduino/blob/master/tools/platformio-build.py + +[core_2_7_4] +extends = esp82xx_2_7_x +platform = espressif8266@2.6.3 +platform_packages = + framework-arduinoespressif8266 @ https://github.com/esp8266/Arduino.git#2.7.4 +build_flags = ${esp82xx_2_7_x.build_flags} + -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK22x_190703 + -DUSES_LATEST_SOFTWARE_SERIAL_LIBRARY=0 + -Wno-deprecated-declarations + -DLIBRARIES_NO_LOG=1 +lib_ignore = ${esp82xx_2_7_x.lib_ignore} +build_unflags = ${esp82xx_2_7_x.build_unflags} +extra_scripts = ${esp82xx_common.extra_scripts} + + +[core_stage] +extends = esp82xx_3_0_x +platform = espressif8266@4.2.1 +platform_packages = +build_flags = ${esp82xx_3_0_x.build_flags} + -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK3 + -DUSES_LATEST_SOFTWARE_SERIAL_LIBRARY=1 + -DLIBRARIES_NO_LOG=1 + -DPHASE_LOCKED_WAVEFORM +build_unflags = ${esp82xx_3_0_x.build_unflags} +lib_ignore = ${esp82xx_defaults.lib_ignore} +extra_scripts = ${esp82xx_common.extra_scripts} + + + +; See: https://arduino-esp8266.readthedocs.io/en/latest/mmu.html +[core_stage_2ndheap] +extends = esp82xx_3_0_x +platform = espressif8266@4.2.1 +platform_packages = +build_flags = ${esp82xx_3_0_x.build_flags} + -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK3 + -DUSES_LATEST_SOFTWARE_SERIAL_LIBRARY=1 + -DLIBRARIES_NO_LOG=1 + -DPHASE_LOCKED_WAVEFORM + -DPIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48_SECHEAP_SHARED + -DUSE_SECOND_HEAP +build_unflags = ${esp82xx_3_0_x.build_unflags} +lib_ignore = ${core_stage.lib_ignore} +extra_scripts = ${esp82xx_common.extra_scripts} + + + +; Updated ESP-IDF to the latest stable 4.0.1 +; See: https://github.com/platformio/platform-espressif32/releases +; IDF 4.4 = platform-espressif32 3.4.x = espressif/arduino-esp32 tag 2.0.4 +; Just for those who lost track of the extremely confusing numbering schema. +; For MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS See: https://github.com/espressif/arduino-esp32/pull/6676 +[core_esp32_IDF4_4__2_0_14] +;platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.4.1/platform-espressif32-2.0.4.1.zip + +; debug boot log enabled +; See: https://github.com/letscontrolit/ESPEasy/pull/4200#issuecomment-1216929859 +;platform = https://github.com/Jason2866/platform-espressif32.git +;platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/936/framework-arduinoespressif32-443_esp421-9ce849ce72.tar.gz + +; debug boot log disabled +;platform = https://github.com/Jason2866/platform-espressif32.git +;platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/938/framework-arduinoespressif32-443_esp421-10ab11e815.tar.gz + +;platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.5.2/platform-espressif32-2.0.5.2.zip +;platform = https://github.com/tasmota/platform-espressif32/releases/download/2022.12.2/platform-espressif32.zip +;platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.01.01/platform-espressif32.zip +;platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/1212/framework-arduinoespressif32-release_v4.4-fb9a7685e1.zip +;platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.01.02/platform-espressif32.zip +;platform_packages = +;platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.02.00/platform-espressif32.zip +;platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/1243/framework-arduinoespressif32-lwip_timeout-ed6742e7f0.zip + +;platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.05.03/platform-espressif32.zip +;platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.06.04/platform-espressif32.zip +platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.10.03/platform-espressif32.zip +platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/1645/framework-arduinoespressif32-release_v4.4_spiffs-e3fc63b439.zip +build_flags = -DESP32_STAGE + -DESP_IDF_VERSION_MAJOR=4 + -DMUSTFIX_CLIENT_TIMEOUT_IN_SECONDS + -DLIBRARIES_NO_LOG=1 + -DDISABLE_SC16IS752_SPI + -DCONFIG_PM_ENABLE + -DCONFIG_FREERTOS_USE_TICKLESS_IDLE=1 + -DCONFIG_FREERTOS_IDLE_TIME_BEFORE_SLEEP=3 + -DNEOPIXEL_ESP32_RMT_DEFAULT + -DCRON_USE_LOCAL_TIME + -I$PROJECT_DIR/src/include + -include "sdkconfig.h" + -include "ESPEasy_config.h" + -include "esp32x_fixes.h" + -Wnull-dereference +lib_ignore = + +; ESP_IDF 5.1 +[core_esp32_IDF5_1__3_0_0] +;platform = https://github.com/tasmota/platform-espressif32/releases/download/2024.04.11/platform-espressif32.zip +;platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/2286/framework-arduinoespressif32-all-release_v5.1-11140aa.zip +;platform = https://github.com/tasmota/platform-espressif32/releases/download/2024.04.14/platform-espressif32.zip +;platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/2386/framework-arduinoespressif32-all-release_v5.1-324fdc1.zip +platform = https://github.com/tasmota/platform-espressif32/releases/download/2024.06.11/platform-espressif32.zip +platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/2543/framework-arduinoespressif32-all-release_v5.1-bd03553.zip +build_flags = -DESP32_STAGE + -DESP_IDF_VERSION_MAJOR=5 + -DLIBRARIES_NO_LOG=1 + -DDISABLE_SC16IS752_SPI + -DCONFIG_PM_ENABLE +; -DCONFIG_LWIP_L2_TO_L3_COPY +; -DETH_SPI_SUPPORTS_NO_IRQ=1 + -DCONFIG_FREERTOS_USE_TICKLESS_IDLE=1 + -DCONFIG_FREERTOS_IDLE_TIME_BEFORE_SLEEP=3 + -DNEOPIXEL_ESP32_RMT_DEFAULT + -DCRON_USE_LOCAL_TIME + -I$PROJECT_DIR/src/include + -include "sdkconfig.h" + -include "ESPEasy_config.h" + -include "esp32x_fixes.h" + -Wnull-dereference +lib_ignore = + diff --git a/platformio_esp32_envs.ini b/platformio_esp32_envs.ini index 6930f5a3ff..105a56172f 100644 --- a/platformio_esp32_envs.ini +++ b/platformio_esp32_envs.ini @@ -1,572 +1,572 @@ -;;; ESP32 test build ********************************************************************; -; Status of the ESP32 support is still considered "beta" ; -; Most plugins work just fine on ESP32. ; -; Especially some plugins using serial may not run very well (GPS does run fine). ; -; ***************************************************************************************; - - -[esp32_base] -extends = common, core_esp32_IDF4_4__2_0_14 -upload_speed = 460800 -upload_before_reset = default_reset -upload_after_reset = hard_reset -extra_scripts = post:tools/pio/post_esp32.py - ${extra_scripts_default.extra_scripts} -; you can disable debug linker flag to reduce binary size (comment out line below), but the backtraces will become less readable -; tools/pio/extra_linker_flags.py -; fix the platform package to use gcc-ar and gcc-ranlib to enable lto linker plugin -; more detail: https://embeddedartistry.com/blog/2020/04/13/prefer-gcc-ar-to-ar-in-your-buildsystems/ -; pre:tools/pio/apply_patches.py -build_unflags = -Wall -; -fno-lto -build_flags = ${core_esp32_IDF4_4__2_0_14.build_flags} -; ${mqtt_flags.build_flags} - -DMQTT_MAX_PACKET_SIZE=2048 - -DCONFIG_FREERTOS_ASSERT_DISABLE - -DCONFIG_LWIP_ESP_GRATUITOUS_ARP - -fno-strict-aliasing -; -flto - -Wswitch - -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_NONE -monitor_filters = esp32_exception_decoder -lib_ignore = - ${core_esp32_IDF4_4__2_0_14.lib_ignore} - - -[esp32_base_idf5] -extends = common, core_esp32_IDF5_1__3_0_0 -upload_speed = 460800 -upload_before_reset = default_reset -upload_after_reset = hard_reset -extra_scripts = post:tools/pio/post_esp32.py - ${extra_scripts_default.extra_scripts} -; you can disable debug linker flag to reduce binary size (comment out line below), but the backtraces will become less readable -; tools/pio/extra_linker_flags.py -; fix the platform package to use gcc-ar and gcc-ranlib to enable lto linker plugin -; more detail: https://embeddedartistry.com/blog/2020/04/13/prefer-gcc-ar-to-ar-in-your-buildsystems/ -; pre:tools/pio/apply_patches.py - -; When using LTO, make sure NOT to use -mtext-section-literals -; -mtext-section-literals may be required when building large builds -; However LTO cannot optimize builds with text section literals and thus will result in quite a lot larger builds (80k - 140k larger) -build_unflags = -Wall - -fno-lto -build_flags = ${core_esp32_IDF5_1__3_0_0.build_flags} -; ${mqtt_flags.build_flags} - -DMQTT_MAX_PACKET_SIZE=2048 - -DCONFIG_FREERTOS_ASSERT_DISABLE - -DCONFIG_LWIP_ESP_GRATUITOUS_ARP - -fno-strict-aliasing - -flto=auto - -Wswitch - -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_NONE -; -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_INFO -; -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_VERBOSE - -DLWIP_IPV6=1 -monitor_filters = esp32_exception_decoder -lib_ignore = - ${core_esp32_IDF5_1__3_0_0.lib_ignore} - - -; -flto cannot be used for ESP32 C3! -; See: https://github.com/letscontrolit/ESPEasy/pull/3845#issuecomment-1014857366 -; TD-er: 2022-01-20: Disabled for now as it also resulted in obscure linker errors on ESP32-S2 and ESP32 running custom builds. -;build_flags = ${esp32_base.build_flags} -; -flto -;build_unflags = ${esp32_base.build_unflags} -; -fexceptions -; -fno-lto - - -[esp32_always] -lib_ignore = ESP8266Ping - ESP8266HTTPUpdateServer - ESP8266WiFi - ESP8266WebServer - ESP8266mDNS - ESPEasy_ESP8266Ping - RABurton ESP8266 Mutex - TinyWireM - LittleFS_esp32 - Adafruit NeoPixel - ${esp32_base.lib_ignore} - - -[esp32_common] -extends = esp32_base -lib_ignore = ${esp32_always.lib_ignore} - ESP32_ping - ${no_ir.lib_ignore} - ESP32 BLE Arduino -build_flags = ${esp32_base.build_flags} - -DESP32_CLASSIC -extra_scripts = ${esp32_base.extra_scripts} -build_unflags = ${esp32_base.build_unflags} - -fexceptions - -[esp32_common_LittleFS] -extends = esp32_base_idf5 -build_flags = ${esp32_base_idf5.build_flags} -; -mtext-section-literals - -DESP32_CLASSIC - -DUSE_LITTLEFS -build_unflags = ${esp32_base_idf5.build_unflags} -extra_scripts = ${esp32_common.extra_scripts} -board_build.filesystem = littlefs -lib_ignore = ${esp32_always.lib_ignore} - ESP32_ping - ESP32 BLE Arduino - ${core_esp32_IDF5_1__3_0_0.lib_ignore} - - -[esp32_IRExt] -extends = esp32_common -lib_ignore = ${esp32_always.lib_ignore} - ESP32_ping -build_flags = ${esp32_common.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DPLUGIN_BUILD_NORMAL_IRext - -DCOLLECTION_USE_RTTTL -extra_scripts = ${esp32_common.extra_scripts} - pre:tools/pio/ir_build_check.py - - -[esp32_custom_base] -extends = esp32_common -build_flags = ${esp32_common.build_flags} - -DPLUGIN_BUILD_CUSTOM -extra_scripts = ${esp32_common.extra_scripts} - pre:tools/pio/pre_custom_esp32.py - -[esp32_custom_base_LittleFS] -extends = esp32_common_LittleFS -build_flags = ${esp32_common_LittleFS.build_flags} - -DPLUGIN_BUILD_CUSTOM -extra_scripts = ${esp32_common_LittleFS.extra_scripts} - pre:tools/pio/pre_custom_esp32.py - - -[env:custom_ESP32_4M316k] -extends = esp32_custom_base -board = esp32_4M - -; [env:custom_ESP32_4M316k_LittleFS] -; extends = esp32_custom_base_LittleFS -; board = esp32_4M - -[env:custom_ESP32_16M8M_LittleFS_ETH] -extends = esp32_custom_base_LittleFS -board = esp32_16M8M -board_upload.flash_size = 16MB -build_flags = ${esp32_custom_base_LittleFS.build_flags} - -DFEATURE_ETHERNET=1 - -[env:custom_IR_ESP32_4M316k] -extends = esp32_common -board = esp32_4M -build_flags = ${esp32_common.build_flags} - -DPLUGIN_BUILD_CUSTOM - -DPLUGIN_BUILD_IR -lib_ignore = ${esp32_always.lib_ignore} - ESP32_ping -extra_scripts = ${esp32_common.extra_scripts} - pre:tools/pio/pre_custom_esp32_IR.py - pre:tools/pio/ir_build_check.py - -[env:custom_ESP32_4M2M_NO_OTA_LittleFS_ETH] -extends = esp32_custom_base_LittleFS -board = esp32_4M2M -build_flags = ${esp32_custom_base_LittleFS.build_flags} - -DNO_HTTP_UPDATER - -DFEATURE_ETHERNET=1 - - -[env:normal_ESP32_4M316k] -extends = esp32_common -board = esp32_4M -lib_ignore = ${esp32_common.lib_ignore} - ${no_ir.lib_ignore} - -; [env:normal_ESP32_4M316k_LittleFS] -; extends = esp32_common_LittleFS -; board = esp32_4M -; lib_ignore = ${esp32_common_LittleFS.lib_ignore} -; ${no_ir.lib_ignore} - -[env:collection_A_ESP32_4M316k] -extends = esp32_common -board = esp32_4M -build_flags = ${esp32_common.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DPLUGIN_SET_COLLECTION_ESP32 - -DCOLLECTION_USE_RTTTL - -[env:collection_B_ESP32_4M316k] -extends = esp32_common -board = esp32_4M -build_flags = ${esp32_common.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DPLUGIN_SET_COLLECTION_B_ESP32 - -DCOLLECTION_USE_RTTTL - -[env:collection_C_ESP32_4M316k] -extends = esp32_common -board = esp32_4M -build_flags = ${esp32_common.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DPLUGIN_SET_COLLECTION_C_ESP32 - -DCOLLECTION_USE_RTTTL - -[env:collection_D_ESP32_4M316k] -extends = esp32_common -board = esp32_4M -build_flags = ${esp32_common.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DPLUGIN_SET_COLLECTION_D_ESP32 - -DCOLLECTION_USE_RTTTL - -[env:collection_E_ESP32_4M316k] -extends = esp32_common -board = esp32_4M -build_flags = ${esp32_common.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DPLUGIN_SET_COLLECTION_E_ESP32 - -DCOLLECTION_USE_RTTTL - -[env:collection_F_ESP32_4M316k] -extends = esp32_common -board = esp32_4M -build_flags = ${esp32_common.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DPLUGIN_SET_COLLECTION_F_ESP32 - -DCOLLECTION_USE_RTTTL - -[env:collection_G_ESP32_4M316k] -extends = esp32_common -board = esp32_4M -build_flags = ${esp32_common.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DPLUGIN_SET_COLLECTION_G_ESP32 - -DCOLLECTION_USE_RTTTL - - -[env:collection_A_ESP32_IRExt_4M316k] -extends = esp32_IRExt -board = esp32_4M -build_flags = ${esp32_IRExt.build_flags} - -DPLUGIN_SET_COLLECTION_ESP32 - -[env:collection_B_ESP32_IRExt_4M316k] -extends = esp32_IRExt -board = esp32_4M -build_flags = ${esp32_IRExt.build_flags} - -DPLUGIN_SET_COLLECTION_B_ESP32 - -[env:collection_C_ESP32_IRExt_4M316k] -extends = esp32_IRExt -board = esp32_4M -build_flags = ${esp32_IRExt.build_flags} - -DPLUGIN_SET_COLLECTION_C_ESP32 - -[env:collection_D_ESP32_IRExt_4M316k] -extends = esp32_IRExt -board = esp32_4M -build_flags = ${esp32_IRExt.build_flags} - -DPLUGIN_SET_COLLECTION_D_ESP32 - -[env:collection_E_ESP32_IRExt_4M316k] -extends = esp32_IRExt -board = esp32_4M -build_flags = ${esp32_IRExt.build_flags} - -DPLUGIN_SET_COLLECTION_E_ESP32 - -[env:collection_F_ESP32_IRExt_4M316k] -extends = esp32_IRExt -board = esp32_4M -build_flags = ${esp32_IRExt.build_flags} - -DPLUGIN_SET_COLLECTION_F_ESP32 - -[env:collection_G_ESP32_IRExt_4M316k] -extends = esp32_IRExt -board = esp32_4M -build_flags = ${esp32_IRExt.build_flags} - -DPLUGIN_SET_COLLECTION_G_ESP32 - -[env:energy_ESP32_4M316k] -extends = esp32_common -board = esp32_4M -build_flags = ${esp32_common.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DPLUGIN_ENERGY_COLLECTION - -[env:display_ESP32_4M316k] -extends = esp32_common -board = esp32_4M -build_flags = ${esp32_common.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DPLUGIN_DISPLAY_COLLECTION - -[env:climate_ESP32_4M316k] -extends = esp32_common -board = esp32_4M -build_flags = ${esp32_common.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DPLUGIN_CLIMATE_COLLECTION - -[env:neopixel_ESP32_4M316k] -extends = esp32_common -board = esp32_4M -build_flags = ${esp32_common.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DFEATURE_SD=1 - -D PLUGIN_NEOPIXEL_COLLECTION - - -[env:custom_ESP32_4M316k_ETH] -extends = env:custom_ESP32_4M316k -build_flags = ${env:custom_ESP32_4M316k.build_flags} - -DFEATURE_ETHERNET=1 - -[env:custom_ESP32_4M316k_LittleFS_ETH] -extends = esp32_custom_base_LittleFS -board = esp32_4M -build_flags = ${esp32_custom_base_LittleFS.build_flags} - -DFEATURE_ETHERNET=1 - - -[env:custom_IR_ESP32_4M316k_ETH] -extends = env:custom_IR_ESP32_4M316k -build_flags = ${env:custom_IR_ESP32_4M316k.build_flags} - -DFEATURE_ETHERNET=1 -extra_scripts = ${env:custom_IR_ESP32_4M316k.extra_scripts} - -[env:custom_IR_ESP32_16M8M_LittleFS_ETH] -extends = esp32_common_LittleFS -board = esp32_16M8M -board_upload.flash_size = 16MB -build_flags = ${esp32_common_LittleFS.build_flags} - -DPLUGIN_BUILD_CUSTOM - -DPLUGIN_BUILD_IR - -DFEATURE_ETHERNET=1 -lib_ignore = ${esp32_always.lib_ignore} - ESP32_ping - ${esp32_common_LittleFS.lib_ignore} -extra_scripts = ${esp32_common.extra_scripts} - pre:tools/pio/pre_custom_esp32_IR.py - pre:tools/pio/ir_build_check.py - -[env:normal_ESP32_4M316k_ETH] -extends = env:normal_ESP32_4M316k -build_flags = ${env:normal_ESP32_4M316k.build_flags} - -DFEATURE_ETHERNET=1 - - -[env:normal_ESP32_4M316k_LittleFS_ETH] -extends = esp32_common_LittleFS -board = esp32_4M -lib_ignore = ${esp32_always.lib_ignore} - ESP32_ping - ${esp32_common_LittleFS.lib_ignore} - ${no_ir.lib_ignore} -build_flags = ${esp32_common_LittleFS.build_flags} - -DFEATURE_ETHERNET=1 - - -[env:normal_ESP32_IRExt_4M316k_ETH] -extends = esp32_IRExt -board = esp32_4M -build_flags = ${esp32_IRExt.build_flags} - -DFEATURE_ETHERNET=1 - -[env:collection_A_ESP32_4M316k_ETH] -extends = env:collection_A_ESP32_4M316k -build_flags = ${env:collection_A_ESP32_4M316k.build_flags} - -DFEATURE_ETHERNET=1 - -DCOLLECTION_USE_RTTTL - -[env:collection_B_ESP32_4M316k_ETH] -extends = env:collection_B_ESP32_4M316k -build_flags = ${env:collection_B_ESP32_4M316k.build_flags} - -DFEATURE_ETHERNET=1 - -DCOLLECTION_USE_RTTTL - -[env:collection_C_ESP32_4M316k_ETH] -extends = env:collection_C_ESP32_4M316k -build_flags = ${env:collection_C_ESP32_4M316k.build_flags} - -DFEATURE_ETHERNET=1 - -DCOLLECTION_USE_RTTTL - -[env:collection_D_ESP32_4M316k_ETH] -extends = env:collection_D_ESP32_4M316k -build_flags = ${env:collection_D_ESP32_4M316k.build_flags} - -DFEATURE_ETHERNET=1 - -DCOLLECTION_USE_RTTTL - -[env:collection_E_ESP32_4M316k_ETH] -extends = env:collection_E_ESP32_4M316k -build_flags = ${env:collection_E_ESP32_4M316k.build_flags} - -DFEATURE_ETHERNET=1 - -DCOLLECTION_USE_RTTTL - -[env:collection_F_ESP32_4M316k_ETH] -extends = env:collection_F_ESP32_4M316k -build_flags = ${env:collection_F_ESP32_4M316k.build_flags} - -DFEATURE_ETHERNET=1 - -DCOLLECTION_USE_RTTTL - -[env:collection_G_ESP32_4M316k_ETH] -extends = env:collection_G_ESP32_4M316k -build_flags = ${env:collection_G_ESP32_4M316k.build_flags} - -DFEATURE_ETHERNET=1 - -DCOLLECTION_USE_RTTTL - -[env:energy_ESP32_4M316k_ETH] -extends = env:energy_ESP32_4M316k -build_flags = ${env:energy_ESP32_4M316k.build_flags} - -DFEATURE_ETHERNET=1 - -[env:display_ESP32_4M316k_ETH] -extends = env:display_ESP32_4M316k -build_flags = ${env:display_ESP32_4M316k.build_flags} - -DFEATURE_ETHERNET=1 - -[env:climate_ESP32_4M316k_ETH] -extends = env:climate_ESP32_4M316k -build_flags = ${env:climate_ESP32_4M316k.build_flags} - -DFEATURE_ETHERNET=1 - -[env:neopixel_ESP32_4M316k_ETH] -extends = env:neopixel_ESP32_4M316k -build_flags = ${env:neopixel_ESP32_4M316k.build_flags} - -DFEATURE_ETHERNET=1 - -; [env:collection_A_ESP32_IRExt_4M316k_ETH] -; extends = esp32_IRExt -; board = esp32_4M -; build_flags = ${esp32_IRExt.build_flags} -; -DPLUGIN_SET_COLLECTION_ESP32 -; -DFEATURE_ETHERNET=1 - -; [env:collection_B_ESP32_IRExt_4M316k_ETH] -; extends = esp32_IRExt -; board = esp32_4M -; build_flags = ${esp32_IRExt.build_flags} -; -DPLUGIN_SET_COLLECTION_B_ESP32 -; -DFEATURE_ETHERNET=1 - -; [env:collection_C_ESP32_IRExt_4M316k_ETH] -; extends = esp32_IRExt -; board = esp32_4M -; build_flags = ${esp32_IRExt.build_flags} -; -DPLUGIN_SET_COLLECTION_C_ESP32 -; -DFEATURE_ETHERNET=1 - -; [env:collection_D_ESP32_IRExt_4M316k_ETH] -; extends = esp32_IRExt -; board = esp32_4M -; build_flags = ${esp32_IRExt.build_flags} -; -DPLUGIN_SET_COLLECTION_D_ESP32 -; -DFEATURE_ETHERNET=1 - -; [env:collection_E_ESP32_IRExt_4M316k_ETH] -; extends = esp32_IRExt -; board = esp32_4M -; build_flags = ${esp32_IRExt.build_flags} -; -DPLUGIN_SET_COLLECTION_E_ESP32 -; -DFEATURE_ETHERNET=1 - -; [env:collection_F_ESP32_IRExt_4M316k_ETH] -; extends = esp32_IRExt -; board = esp32_4M -; build_flags = ${esp32_IRExt.build_flags} -; -DPLUGIN_SET_COLLECTION_F_ESP32 -; -DFEATURE_ETHERNET=1 - -; [env:collection_G_ESP32_IRExt_4M316k_ETH] -; extends = esp32_IRExt -; board = esp32_4M -; build_flags = ${esp32_IRExt.build_flags} -; -DPLUGIN_SET_COLLECTION_G_ESP32 -; -DFEATURE_ETHERNET=1 - -; [env:energy_ESP32_IRExt_4M316k_ETH] -; extends = esp32_IRExt -; board = esp32_4M -; build_flags = ${esp32_IRExt.build_flags} -; -DPLUGIN_ENERGY_COLLECTION -; -DFEATURE_ETHERNET=1 - -; [env:display_ESP32_IRExt_4M316k_ETH] -; extends = esp32_IRExt -; board = esp32_4M -; build_flags = ${esp32_IRExt.build_flags} -; -DPLUGIN_DISPLAY_COLLECTION -; -DFEATURE_ETHERNET=1 - -; [env:climate_ESP32_IRExt_4M316k_ETH] -; extends = esp32_IRExt -; board = esp32_4M -; build_flags = ${esp32_IRExt.build_flags} -; -DPLUGIN_CLIMATE_COLLECTION -; -DFEATURE_ETHERNET=1 - -; [env:neopixel_ESP32_IRExt_4M316k_ETH] -; extends = esp32_IRExt -; board = esp32_4M -; build_flags = ${esp32_IRExt.build_flags} -; -D PLUGIN_NEOPIXEL_COLLECTION -; -DFEATURE_ETHERNET=1 - - -; ESP32 MAX builds 16M flash ------------------------------ - -; A Lolin D32 PRO with 16MB Flash, allowing 4MB sketch size, and file storage using the default (SPIFFS) filesystem -[env:max_ESP32_16M1M] -extends = esp32_common -board = esp32_16M1M -board_upload.flash_size = 16MB -lib_ignore = ${esp32_always.lib_ignore} - ESP32_ping -build_flags = ${esp32_common.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DPLUGIN_BUILD_MAX_ESP32 - -DPLUGIN_BUILD_IR_EXTENDED - -[env:max_ESP32_16M1M_ETH] -extends = env:max_ESP32_16M1M -build_flags = ${env:max_ESP32_16M1M.build_flags} - -DFEATURE_ETHERNET=1 - - -; A Lolin D32 PRO with 16MB Flash, allowing 4MB sketch size, and file storage using LittleFS filesystem -[env:max_ESP32_16M8M_LittleFS_ETH] -extends = esp32_common_LittleFS -board = esp32_16M8M -board_upload.flash_size = 16MB -lib_ignore = ${esp32_always.lib_ignore} - ESP32_ping - ${esp32_common_LittleFS.lib_ignore} -build_flags = ${esp32_common_LittleFS.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DPLUGIN_BUILD_MAX_ESP32 - -DPLUGIN_BUILD_IR_EXTENDED - -DFEATURE_ETHERNET=1 -extra_scripts = ${esp32_common.extra_scripts} -board_build.filesystem = littlefs - -; If you have a board with Ethernet integrated and 16MB Flash, then this configuration could be enabled, it's based on the max_ESP32_16M8M_LittleFS definition -; [env:max_ESP32_16M8M_LittleFS_ETH] -; extends = env:max_ESP32_16M8M_LittleFS -; board = ${env:max_ESP32_16M8M_LittleFS.board} -; build_flags = ${env:max_ESP32_16M8M_LittleFS.build_flags} -; -DFEATURE_ETHERNET=1 - - - - - - - - - - +;;; ESP32 test build ********************************************************************; +; Status of the ESP32 support is still considered "beta" ; +; Most plugins work just fine on ESP32. ; +; Especially some plugins using serial may not run very well (GPS does run fine). ; +; ***************************************************************************************; + + +[esp32_base] +extends = common, core_esp32_IDF4_4__2_0_14 +upload_speed = 460800 +upload_before_reset = default_reset +upload_after_reset = hard_reset +extra_scripts = post:tools/pio/post_esp32.py + ${extra_scripts_default.extra_scripts} +; you can disable debug linker flag to reduce binary size (comment out line below), but the backtraces will become less readable +; tools/pio/extra_linker_flags.py +; fix the platform package to use gcc-ar and gcc-ranlib to enable lto linker plugin +; more detail: https://embeddedartistry.com/blog/2020/04/13/prefer-gcc-ar-to-ar-in-your-buildsystems/ +; pre:tools/pio/apply_patches.py +build_unflags = -Wall +; -fno-lto +build_flags = ${core_esp32_IDF4_4__2_0_14.build_flags} +; ${mqtt_flags.build_flags} + -DMQTT_MAX_PACKET_SIZE=2048 + -DCONFIG_FREERTOS_ASSERT_DISABLE + -DCONFIG_LWIP_ESP_GRATUITOUS_ARP + -fno-strict-aliasing +; -flto + -Wswitch + -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_NONE +monitor_filters = esp32_exception_decoder +lib_ignore = + ${core_esp32_IDF4_4__2_0_14.lib_ignore} + + +[esp32_base_idf5] +extends = common, core_esp32_IDF5_1__3_0_0 +upload_speed = 460800 +upload_before_reset = default_reset +upload_after_reset = hard_reset +extra_scripts = post:tools/pio/post_esp32.py + ${extra_scripts_default.extra_scripts} +; you can disable debug linker flag to reduce binary size (comment out line below), but the backtraces will become less readable +; tools/pio/extra_linker_flags.py +; fix the platform package to use gcc-ar and gcc-ranlib to enable lto linker plugin +; more detail: https://embeddedartistry.com/blog/2020/04/13/prefer-gcc-ar-to-ar-in-your-buildsystems/ +; pre:tools/pio/apply_patches.py + +; When using LTO, make sure NOT to use -mtext-section-literals +; -mtext-section-literals may be required when building large builds +; However LTO cannot optimize builds with text section literals and thus will result in quite a lot larger builds (80k - 140k larger) +build_unflags = -Wall + -fno-lto +build_flags = ${core_esp32_IDF5_1__3_0_0.build_flags} +; ${mqtt_flags.build_flags} + -DMQTT_MAX_PACKET_SIZE=2048 + -DCONFIG_FREERTOS_ASSERT_DISABLE + -DCONFIG_LWIP_ESP_GRATUITOUS_ARP + -fno-strict-aliasing + -flto=auto + -Wswitch + -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_NONE +; -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_INFO +; -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_VERBOSE + -DLWIP_IPV6=1 +monitor_filters = esp32_exception_decoder +lib_ignore = + ${core_esp32_IDF5_1__3_0_0.lib_ignore} + + +; -flto cannot be used for ESP32 C3! +; See: https://github.com/letscontrolit/ESPEasy/pull/3845#issuecomment-1014857366 +; TD-er: 2022-01-20: Disabled for now as it also resulted in obscure linker errors on ESP32-S2 and ESP32 running custom builds. +;build_flags = ${esp32_base.build_flags} +; -flto +;build_unflags = ${esp32_base.build_unflags} +; -fexceptions +; -fno-lto + + +[esp32_always] +lib_ignore = ESP8266Ping + ESP8266HTTPUpdateServer + ESP8266WiFi + ESP8266WebServer + ESP8266mDNS + ESPEasy_ESP8266Ping + RABurton ESP8266 Mutex + TinyWireM + LittleFS_esp32 + Adafruit NeoPixel + ${esp32_base.lib_ignore} + + +[esp32_common] +extends = esp32_base +lib_ignore = ${esp32_always.lib_ignore} + ESP32_ping + ${no_ir.lib_ignore} + ESP32 BLE Arduino +build_flags = ${esp32_base.build_flags} + -DESP32_CLASSIC +extra_scripts = ${esp32_base.extra_scripts} +build_unflags = ${esp32_base.build_unflags} + -fexceptions + +[esp32_common_LittleFS] +extends = esp32_base_idf5 +build_flags = ${esp32_base_idf5.build_flags} +; -mtext-section-literals + -DESP32_CLASSIC + -DUSE_LITTLEFS +build_unflags = ${esp32_base_idf5.build_unflags} +extra_scripts = ${esp32_common.extra_scripts} +board_build.filesystem = littlefs +lib_ignore = ${esp32_always.lib_ignore} + ESP32_ping + ESP32 BLE Arduino + ${core_esp32_IDF5_1__3_0_0.lib_ignore} + + +[esp32_IRExt] +extends = esp32_common +lib_ignore = ${esp32_always.lib_ignore} + ESP32_ping +build_flags = ${esp32_common.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_BUILD_NORMAL_IRext + -DCOLLECTION_USE_RTTTL +extra_scripts = ${esp32_common.extra_scripts} + pre:tools/pio/ir_build_check.py + + +[esp32_custom_base] +extends = esp32_common +build_flags = ${esp32_common.build_flags} + -DPLUGIN_BUILD_CUSTOM +extra_scripts = ${esp32_common.extra_scripts} + pre:tools/pio/pre_custom_esp32.py + +[esp32_custom_base_LittleFS] +extends = esp32_common_LittleFS +build_flags = ${esp32_common_LittleFS.build_flags} + -DPLUGIN_BUILD_CUSTOM +extra_scripts = ${esp32_common_LittleFS.extra_scripts} + pre:tools/pio/pre_custom_esp32.py + + +[env:custom_ESP32_4M316k] +extends = esp32_custom_base +board = esp32_4M + +; [env:custom_ESP32_4M316k_LittleFS] +; extends = esp32_custom_base_LittleFS +; board = esp32_4M + +[env:custom_ESP32_16M8M_LittleFS_ETH] +extends = esp32_custom_base_LittleFS +board = esp32_16M8M +board_upload.flash_size = 16MB +build_flags = ${esp32_custom_base_LittleFS.build_flags} + -DFEATURE_ETHERNET=1 + +[env:custom_IR_ESP32_4M316k] +extends = esp32_common +board = esp32_4M +build_flags = ${esp32_common.build_flags} + -DPLUGIN_BUILD_CUSTOM + -DPLUGIN_BUILD_IR +lib_ignore = ${esp32_always.lib_ignore} + ESP32_ping +extra_scripts = ${esp32_common.extra_scripts} + pre:tools/pio/pre_custom_esp32_IR.py + pre:tools/pio/ir_build_check.py + +[env:custom_ESP32_4M2M_NO_OTA_LittleFS_ETH] +extends = esp32_custom_base_LittleFS +board = esp32_4M2M +build_flags = ${esp32_custom_base_LittleFS.build_flags} + -DNO_HTTP_UPDATER + -DFEATURE_ETHERNET=1 + + +[env:normal_ESP32_4M316k] +extends = esp32_common +board = esp32_4M +lib_ignore = ${esp32_common.lib_ignore} + ${no_ir.lib_ignore} + +; [env:normal_ESP32_4M316k_LittleFS] +; extends = esp32_common_LittleFS +; board = esp32_4M +; lib_ignore = ${esp32_common_LittleFS.lib_ignore} +; ${no_ir.lib_ignore} + +[env:collection_A_ESP32_4M316k] +extends = esp32_common +board = esp32_4M +build_flags = ${esp32_common.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_SET_COLLECTION_ESP32 + -DCOLLECTION_USE_RTTTL + +[env:collection_B_ESP32_4M316k] +extends = esp32_common +board = esp32_4M +build_flags = ${esp32_common.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_SET_COLLECTION_B_ESP32 + -DCOLLECTION_USE_RTTTL + +[env:collection_C_ESP32_4M316k] +extends = esp32_common +board = esp32_4M +build_flags = ${esp32_common.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_SET_COLLECTION_C_ESP32 + -DCOLLECTION_USE_RTTTL + +[env:collection_D_ESP32_4M316k] +extends = esp32_common +board = esp32_4M +build_flags = ${esp32_common.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_SET_COLLECTION_D_ESP32 + -DCOLLECTION_USE_RTTTL + +[env:collection_E_ESP32_4M316k] +extends = esp32_common +board = esp32_4M +build_flags = ${esp32_common.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_SET_COLLECTION_E_ESP32 + -DCOLLECTION_USE_RTTTL + +[env:collection_F_ESP32_4M316k] +extends = esp32_common +board = esp32_4M +build_flags = ${esp32_common.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_SET_COLLECTION_F_ESP32 + -DCOLLECTION_USE_RTTTL + +[env:collection_G_ESP32_4M316k] +extends = esp32_common +board = esp32_4M +build_flags = ${esp32_common.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_SET_COLLECTION_G_ESP32 + -DCOLLECTION_USE_RTTTL + + +[env:collection_A_ESP32_IRExt_4M316k] +extends = esp32_IRExt +board = esp32_4M +build_flags = ${esp32_IRExt.build_flags} + -DPLUGIN_SET_COLLECTION_ESP32 + +[env:collection_B_ESP32_IRExt_4M316k] +extends = esp32_IRExt +board = esp32_4M +build_flags = ${esp32_IRExt.build_flags} + -DPLUGIN_SET_COLLECTION_B_ESP32 + +[env:collection_C_ESP32_IRExt_4M316k] +extends = esp32_IRExt +board = esp32_4M +build_flags = ${esp32_IRExt.build_flags} + -DPLUGIN_SET_COLLECTION_C_ESP32 + +[env:collection_D_ESP32_IRExt_4M316k] +extends = esp32_IRExt +board = esp32_4M +build_flags = ${esp32_IRExt.build_flags} + -DPLUGIN_SET_COLLECTION_D_ESP32 + +[env:collection_E_ESP32_IRExt_4M316k] +extends = esp32_IRExt +board = esp32_4M +build_flags = ${esp32_IRExt.build_flags} + -DPLUGIN_SET_COLLECTION_E_ESP32 + +[env:collection_F_ESP32_IRExt_4M316k] +extends = esp32_IRExt +board = esp32_4M +build_flags = ${esp32_IRExt.build_flags} + -DPLUGIN_SET_COLLECTION_F_ESP32 + +[env:collection_G_ESP32_IRExt_4M316k] +extends = esp32_IRExt +board = esp32_4M +build_flags = ${esp32_IRExt.build_flags} + -DPLUGIN_SET_COLLECTION_G_ESP32 + +[env:energy_ESP32_4M316k] +extends = esp32_common +board = esp32_4M +build_flags = ${esp32_common.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_ENERGY_COLLECTION + +[env:display_ESP32_4M316k] +extends = esp32_common +board = esp32_4M +build_flags = ${esp32_common.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_DISPLAY_COLLECTION + +[env:climate_ESP32_4M316k] +extends = esp32_common +board = esp32_4M +build_flags = ${esp32_common.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_CLIMATE_COLLECTION + +[env:neopixel_ESP32_4M316k] +extends = esp32_common +board = esp32_4M +build_flags = ${esp32_common.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DFEATURE_SD=1 + -D PLUGIN_NEOPIXEL_COLLECTION + + +[env:custom_ESP32_4M316k_ETH] +extends = env:custom_ESP32_4M316k +build_flags = ${env:custom_ESP32_4M316k.build_flags} + -DFEATURE_ETHERNET=1 + +[env:custom_ESP32_4M316k_LittleFS_ETH] +extends = esp32_custom_base_LittleFS +board = esp32_4M +build_flags = ${esp32_custom_base_LittleFS.build_flags} + -DFEATURE_ETHERNET=1 + + +[env:custom_IR_ESP32_4M316k_ETH] +extends = env:custom_IR_ESP32_4M316k +build_flags = ${env:custom_IR_ESP32_4M316k.build_flags} + -DFEATURE_ETHERNET=1 +extra_scripts = ${env:custom_IR_ESP32_4M316k.extra_scripts} + +[env:custom_IR_ESP32_16M8M_LittleFS_ETH] +extends = esp32_common_LittleFS +board = esp32_16M8M +board_upload.flash_size = 16MB +build_flags = ${esp32_common_LittleFS.build_flags} + -DPLUGIN_BUILD_CUSTOM + -DPLUGIN_BUILD_IR + -DFEATURE_ETHERNET=1 +lib_ignore = ${esp32_always.lib_ignore} + ESP32_ping + ${esp32_common_LittleFS.lib_ignore} +extra_scripts = ${esp32_common.extra_scripts} + pre:tools/pio/pre_custom_esp32_IR.py + pre:tools/pio/ir_build_check.py + +[env:normal_ESP32_4M316k_ETH] +extends = env:normal_ESP32_4M316k +build_flags = ${env:normal_ESP32_4M316k.build_flags} + -DFEATURE_ETHERNET=1 + + +[env:normal_ESP32_4M316k_LittleFS_ETH] +extends = esp32_common_LittleFS +board = esp32_4M +lib_ignore = ${esp32_always.lib_ignore} + ESP32_ping + ${esp32_common_LittleFS.lib_ignore} + ${no_ir.lib_ignore} +build_flags = ${esp32_common_LittleFS.build_flags} + -DFEATURE_ETHERNET=1 + + +[env:normal_ESP32_IRExt_4M316k_ETH] +extends = esp32_IRExt +board = esp32_4M +build_flags = ${esp32_IRExt.build_flags} + -DFEATURE_ETHERNET=1 + +[env:collection_A_ESP32_4M316k_ETH] +extends = env:collection_A_ESP32_4M316k +build_flags = ${env:collection_A_ESP32_4M316k.build_flags} + -DFEATURE_ETHERNET=1 + -DCOLLECTION_USE_RTTTL + +[env:collection_B_ESP32_4M316k_ETH] +extends = env:collection_B_ESP32_4M316k +build_flags = ${env:collection_B_ESP32_4M316k.build_flags} + -DFEATURE_ETHERNET=1 + -DCOLLECTION_USE_RTTTL + +[env:collection_C_ESP32_4M316k_ETH] +extends = env:collection_C_ESP32_4M316k +build_flags = ${env:collection_C_ESP32_4M316k.build_flags} + -DFEATURE_ETHERNET=1 + -DCOLLECTION_USE_RTTTL + +[env:collection_D_ESP32_4M316k_ETH] +extends = env:collection_D_ESP32_4M316k +build_flags = ${env:collection_D_ESP32_4M316k.build_flags} + -DFEATURE_ETHERNET=1 + -DCOLLECTION_USE_RTTTL + +[env:collection_E_ESP32_4M316k_ETH] +extends = env:collection_E_ESP32_4M316k +build_flags = ${env:collection_E_ESP32_4M316k.build_flags} + -DFEATURE_ETHERNET=1 + -DCOLLECTION_USE_RTTTL + +[env:collection_F_ESP32_4M316k_ETH] +extends = env:collection_F_ESP32_4M316k +build_flags = ${env:collection_F_ESP32_4M316k.build_flags} + -DFEATURE_ETHERNET=1 + -DCOLLECTION_USE_RTTTL + +[env:collection_G_ESP32_4M316k_ETH] +extends = env:collection_G_ESP32_4M316k +build_flags = ${env:collection_G_ESP32_4M316k.build_flags} + -DFEATURE_ETHERNET=1 + -DCOLLECTION_USE_RTTTL + +[env:energy_ESP32_4M316k_ETH] +extends = env:energy_ESP32_4M316k +build_flags = ${env:energy_ESP32_4M316k.build_flags} + -DFEATURE_ETHERNET=1 + +[env:display_ESP32_4M316k_ETH] +extends = env:display_ESP32_4M316k +build_flags = ${env:display_ESP32_4M316k.build_flags} + -DFEATURE_ETHERNET=1 + +[env:climate_ESP32_4M316k_ETH] +extends = env:climate_ESP32_4M316k +build_flags = ${env:climate_ESP32_4M316k.build_flags} + -DFEATURE_ETHERNET=1 + +[env:neopixel_ESP32_4M316k_ETH] +extends = env:neopixel_ESP32_4M316k +build_flags = ${env:neopixel_ESP32_4M316k.build_flags} + -DFEATURE_ETHERNET=1 + +; [env:collection_A_ESP32_IRExt_4M316k_ETH] +; extends = esp32_IRExt +; board = esp32_4M +; build_flags = ${esp32_IRExt.build_flags} +; -DPLUGIN_SET_COLLECTION_ESP32 +; -DFEATURE_ETHERNET=1 + +; [env:collection_B_ESP32_IRExt_4M316k_ETH] +; extends = esp32_IRExt +; board = esp32_4M +; build_flags = ${esp32_IRExt.build_flags} +; -DPLUGIN_SET_COLLECTION_B_ESP32 +; -DFEATURE_ETHERNET=1 + +; [env:collection_C_ESP32_IRExt_4M316k_ETH] +; extends = esp32_IRExt +; board = esp32_4M +; build_flags = ${esp32_IRExt.build_flags} +; -DPLUGIN_SET_COLLECTION_C_ESP32 +; -DFEATURE_ETHERNET=1 + +; [env:collection_D_ESP32_IRExt_4M316k_ETH] +; extends = esp32_IRExt +; board = esp32_4M +; build_flags = ${esp32_IRExt.build_flags} +; -DPLUGIN_SET_COLLECTION_D_ESP32 +; -DFEATURE_ETHERNET=1 + +; [env:collection_E_ESP32_IRExt_4M316k_ETH] +; extends = esp32_IRExt +; board = esp32_4M +; build_flags = ${esp32_IRExt.build_flags} +; -DPLUGIN_SET_COLLECTION_E_ESP32 +; -DFEATURE_ETHERNET=1 + +; [env:collection_F_ESP32_IRExt_4M316k_ETH] +; extends = esp32_IRExt +; board = esp32_4M +; build_flags = ${esp32_IRExt.build_flags} +; -DPLUGIN_SET_COLLECTION_F_ESP32 +; -DFEATURE_ETHERNET=1 + +; [env:collection_G_ESP32_IRExt_4M316k_ETH] +; extends = esp32_IRExt +; board = esp32_4M +; build_flags = ${esp32_IRExt.build_flags} +; -DPLUGIN_SET_COLLECTION_G_ESP32 +; -DFEATURE_ETHERNET=1 + +; [env:energy_ESP32_IRExt_4M316k_ETH] +; extends = esp32_IRExt +; board = esp32_4M +; build_flags = ${esp32_IRExt.build_flags} +; -DPLUGIN_ENERGY_COLLECTION +; -DFEATURE_ETHERNET=1 + +; [env:display_ESP32_IRExt_4M316k_ETH] +; extends = esp32_IRExt +; board = esp32_4M +; build_flags = ${esp32_IRExt.build_flags} +; -DPLUGIN_DISPLAY_COLLECTION +; -DFEATURE_ETHERNET=1 + +; [env:climate_ESP32_IRExt_4M316k_ETH] +; extends = esp32_IRExt +; board = esp32_4M +; build_flags = ${esp32_IRExt.build_flags} +; -DPLUGIN_CLIMATE_COLLECTION +; -DFEATURE_ETHERNET=1 + +; [env:neopixel_ESP32_IRExt_4M316k_ETH] +; extends = esp32_IRExt +; board = esp32_4M +; build_flags = ${esp32_IRExt.build_flags} +; -D PLUGIN_NEOPIXEL_COLLECTION +; -DFEATURE_ETHERNET=1 + + +; ESP32 MAX builds 16M flash ------------------------------ + +; A Lolin D32 PRO with 16MB Flash, allowing 4MB sketch size, and file storage using the default (SPIFFS) filesystem +[env:max_ESP32_16M1M] +extends = esp32_common +board = esp32_16M1M +board_upload.flash_size = 16MB +lib_ignore = ${esp32_always.lib_ignore} + ESP32_ping +build_flags = ${esp32_common.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_BUILD_MAX_ESP32 + -DPLUGIN_BUILD_IR_EXTENDED + +[env:max_ESP32_16M1M_ETH] +extends = env:max_ESP32_16M1M +build_flags = ${env:max_ESP32_16M1M.build_flags} + -DFEATURE_ETHERNET=1 + + +; A Lolin D32 PRO with 16MB Flash, allowing 4MB sketch size, and file storage using LittleFS filesystem +[env:max_ESP32_16M8M_LittleFS_ETH] +extends = esp32_common_LittleFS +board = esp32_16M8M +board_upload.flash_size = 16MB +lib_ignore = ${esp32_always.lib_ignore} + ESP32_ping + ${esp32_common_LittleFS.lib_ignore} +build_flags = ${esp32_common_LittleFS.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_BUILD_MAX_ESP32 + -DPLUGIN_BUILD_IR_EXTENDED + -DFEATURE_ETHERNET=1 +extra_scripts = ${esp32_common.extra_scripts} +board_build.filesystem = littlefs + +; If you have a board with Ethernet integrated and 16MB Flash, then this configuration could be enabled, it's based on the max_ESP32_16M8M_LittleFS definition +; [env:max_ESP32_16M8M_LittleFS_ETH] +; extends = env:max_ESP32_16M8M_LittleFS +; board = ${env:max_ESP32_16M8M_LittleFS.board} +; build_flags = ${env:max_ESP32_16M8M_LittleFS.build_flags} +; -DFEATURE_ETHERNET=1 + + + + + + + + + + diff --git a/platformio_esp32_solo1.ini b/platformio_esp32_solo1.ini index 09f9d53633..b27d256b84 100644 --- a/platformio_esp32_solo1.ini +++ b/platformio_esp32_solo1.ini @@ -1,63 +1,63 @@ - - - -; IDF 4.4 -[esp32_solo1_common] -extends = esp32_base -platform_packages = framework-arduino-solo1 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/1646/framework-arduinoespressif32-solo1-release_v4.4_spiffs-e3fc63b439.zip -lib_ignore = ${esp32_always.lib_ignore} - ESP32_ping - ${no_ir.lib_ignore} - ESP32 BLE Arduino -build_flags = ${esp32_base.build_flags} - -DFEATURE_ARDUINO_OTA=1 -extra_scripts = ${esp32_base.extra_scripts} -build_unflags = ${esp32_base.build_unflags} - -fexceptions - -; IDF 5.1.2 -[esp32_solo1_common_LittleFS] -extends = esp32_base_idf5 -platform = https://github.com/tasmota/platform-espressif32/releases/download/2024.05.13/platform-espressif32.zip -platform_packages = -build_flags = ${esp32_base_idf5.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DUSE_LITTLEFS -extra_scripts = ${esp32_base_idf5.extra_scripts} -build_unflags = ${esp32_base_idf5.build_unflags} - -fexceptions -board_build.filesystem = littlefs - - -[env:custom_ESP32solo1_4M316k_LittleFS_ETH] -extends = esp32_solo1_common_LittleFS -board = esp32_solo1_4M -build_flags = ${esp32_solo1_common_LittleFS.build_flags} - -DPLUGIN_BUILD_CUSTOM - -DFEATURE_ETHERNET=1 -extra_scripts = ${esp32_solo1_common_LittleFS.extra_scripts} - pre:tools/pio/pre_custom_esp32.py - - -[env:normal_ESP32solo1_4M316k_LittleFS_ETH] -extends = esp32_solo1_common_LittleFS -board = esp32_solo1_4M -build_flags = ${esp32_solo1_common_LittleFS.build_flags} - -DFEATURE_ETHERNET=1 -lib_ignore = ${esp32_solo1_common_LittleFS.lib_ignore} - ${no_ir.lib_ignore} - - -[env:energy_ESP32solo1_4M316k_LittleFS_ETH] -extends = esp32_solo1_common_LittleFS -board = esp32_solo1_4M -build_flags = ${esp32_solo1_common_LittleFS.build_flags} - -D PLUGIN_ENERGY_COLLECTION - -DFEATURE_ETHERNET=1 - -[env:climate_ESP32solo1_4M316k_LittleFS_ETH] -extends = esp32_solo1_common_LittleFS -board = esp32_solo1_4M -build_flags = ${esp32_solo1_common_LittleFS.build_flags} - -D PLUGIN_CLIMATE_COLLECTION - -DFEATURE_ETHERNET=1 + + + +; IDF 4.4 +[esp32_solo1_common] +extends = esp32_base +platform_packages = framework-arduino-solo1 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/1646/framework-arduinoespressif32-solo1-release_v4.4_spiffs-e3fc63b439.zip +lib_ignore = ${esp32_always.lib_ignore} + ESP32_ping + ${no_ir.lib_ignore} + ESP32 BLE Arduino +build_flags = ${esp32_base.build_flags} + -DFEATURE_ARDUINO_OTA=1 +extra_scripts = ${esp32_base.extra_scripts} +build_unflags = ${esp32_base.build_unflags} + -fexceptions + +; IDF 5.1.2 +[esp32_solo1_common_LittleFS] +extends = esp32_base_idf5 +platform = https://github.com/tasmota/platform-espressif32/releases/download/2024.06.11/platform-espressif32.zip +platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/2525/framework-arduinoespressif32-solo1-release_v5.1-e9a74b6.zip +build_flags = ${esp32_base_idf5.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DUSE_LITTLEFS +extra_scripts = ${esp32_base_idf5.extra_scripts} +build_unflags = ${esp32_base_idf5.build_unflags} + -fexceptions +board_build.filesystem = littlefs + + +[env:custom_ESP32solo1_4M316k_LittleFS_ETH] +extends = esp32_solo1_common_LittleFS +board = esp32_solo1_4M +build_flags = ${esp32_solo1_common_LittleFS.build_flags} + -DPLUGIN_BUILD_CUSTOM + -DFEATURE_ETHERNET=1 +extra_scripts = ${esp32_solo1_common_LittleFS.extra_scripts} + pre:tools/pio/pre_custom_esp32.py + + +[env:normal_ESP32solo1_4M316k_LittleFS_ETH] +extends = esp32_solo1_common_LittleFS +board = esp32_solo1_4M +build_flags = ${esp32_solo1_common_LittleFS.build_flags} + -DFEATURE_ETHERNET=1 +lib_ignore = ${esp32_solo1_common_LittleFS.lib_ignore} + ${no_ir.lib_ignore} + + +[env:energy_ESP32solo1_4M316k_LittleFS_ETH] +extends = esp32_solo1_common_LittleFS +board = esp32_solo1_4M +build_flags = ${esp32_solo1_common_LittleFS.build_flags} + -D PLUGIN_ENERGY_COLLECTION + -DFEATURE_ETHERNET=1 + +[env:climate_ESP32solo1_4M316k_LittleFS_ETH] +extends = esp32_solo1_common_LittleFS +board = esp32_solo1_4M +build_flags = ${esp32_solo1_common_LittleFS.build_flags} + -D PLUGIN_CLIMATE_COLLECTION + -DFEATURE_ETHERNET=1 diff --git a/platformio_esp32c2_envs.ini b/platformio_esp32c2_envs.ini index e5937ff2e0..7f8919bf5e 100644 --- a/platformio_esp32c2_envs.ini +++ b/platformio_esp32c2_envs.ini @@ -1,55 +1,51 @@ - - -[esp32c2_common_LittleFS] -extends = esp32_base_idf5 -build_flags = ${esp32_base_idf5.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DUSE_LITTLEFS - -DESP32C2 -extra_scripts = ${esp32_base_idf5.extra_scripts} -build_unflags = ${esp32_base_idf5.build_unflags} - -fexceptions -board_build.filesystem = littlefs -lib_ignore = ${esp32_base_idf5.lib_ignore} - NeoPixelBus - NeoPixelBus_wrapper - Adafruit NeoMatrix via NeoPixelBus - - -[env:safeboot_ESP32c2_4M_LittleFS_ETH] -extends = esp32c2_common_LittleFS -board = esp32c2 -build_flags = ${esp32c2_common_LittleFS.build_flags} - -DPLUGIN_BUILD_CUSTOM - -DPLUGIN_BUILD_SAFEBOOT - -DFEATURE_ETHERNET=1 -extra_scripts = ${esp32c2_common_LittleFS.extra_scripts} - pre:tools/pio/pre_safeboot_esp32c2.py -lib_ignore = ${esp32c2_common_LittleFS.lib_ignore} - - -[env:custom_ESP32c2_2M320k_LittleFS_noOTA_ETH] -extends = esp32c2_common_LittleFS -board = esp32c2_2M -build_flags = ${esp32c2_common_LittleFS.build_flags} - -DPLUGIN_BUILD_CUSTOM - -DFEATURE_ETHERNET=1 -extra_scripts = ${esp32c2_common_LittleFS.extra_scripts} - pre:tools/pio/pre_custom_esp32c2.py - -[env:custom_ESP32c2_4M316k_LittleFS_ETH] -extends = esp32c2_common_LittleFS -board = esp32c2 -build_flags = ${esp32c2_common_LittleFS.build_flags} - -DPLUGIN_BUILD_CUSTOM - -DFEATURE_ETHERNET=1 -extra_scripts = ${esp32c2_common_LittleFS.extra_scripts} - pre:tools/pio/pre_custom_esp32c2.py - -[env:normal_ESP32c2_4M316k_LittleFS_ETH] -extends = esp32c2_common_LittleFS -board = esp32c2 -build_flags = ${esp32c2_common_LittleFS.build_flags} - -DFEATURE_ETHERNET=1 -lib_ignore = ${esp32c2_common_LittleFS.lib_ignore} - ${no_ir.lib_ignore} +; No Ethernet for ESP32-C2 as this one already hasn't much RAM. +; Thus Jason removed Ethernet support for ESP32-C2 from the PIO platform_packages +[esp32c2_common_LittleFS] +extends = esp32_base_idf5 +build_flags = ${esp32_base_idf5.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DUSE_LITTLEFS + -DESP32C2 +extra_scripts = ${esp32_base_idf5.extra_scripts} +build_unflags = ${esp32_base_idf5.build_unflags} + -fexceptions +board_build.filesystem = littlefs +lib_ignore = ${esp32_base_idf5.lib_ignore} + NeoPixelBus + NeoPixelBus_wrapper + Adafruit NeoMatrix via NeoPixelBus + + +[env:safeboot_ESP32c2_4M_LittleFS] +extends = esp32c2_common_LittleFS +board = esp32c2 +build_flags = ${esp32c2_common_LittleFS.build_flags} + -DPLUGIN_BUILD_CUSTOM + -DPLUGIN_BUILD_SAFEBOOT +extra_scripts = ${esp32c2_common_LittleFS.extra_scripts} + pre:tools/pio/pre_safeboot_esp32c2.py +lib_ignore = ${esp32c2_common_LittleFS.lib_ignore} + + +[env:custom_ESP32c2_2M320k_LittleFS_noOTA] +extends = esp32c2_common_LittleFS +board = esp32c2_2M +build_flags = ${esp32c2_common_LittleFS.build_flags} + -DPLUGIN_BUILD_CUSTOM +extra_scripts = ${esp32c2_common_LittleFS.extra_scripts} + pre:tools/pio/pre_custom_esp32c2.py + +[env:custom_ESP32c2_4M316k_LittleFS] +extends = esp32c2_common_LittleFS +board = esp32c2 +build_flags = ${esp32c2_common_LittleFS.build_flags} + -DPLUGIN_BUILD_CUSTOM +extra_scripts = ${esp32c2_common_LittleFS.extra_scripts} + pre:tools/pio/pre_custom_esp32c2.py + +[env:normal_ESP32c2_4M316k_LittleFS] +extends = esp32c2_common_LittleFS +board = esp32c2 +build_flags = ${esp32c2_common_LittleFS.build_flags} +lib_ignore = ${esp32c2_common_LittleFS.lib_ignore} + ${no_ir.lib_ignore} diff --git a/platformio_esp32c3_envs.ini b/platformio_esp32c3_envs.ini index 2fb29ddb7b..f4a36b912b 100644 --- a/platformio_esp32c3_envs.ini +++ b/platformio_esp32c3_envs.ini @@ -1,174 +1,174 @@ - - - -[esp32c3_common] -extends = esp32_base -lib_ignore = ${esp32_always.lib_ignore} - ESP32_ping - ${no_ir.lib_ignore} - ESP32 BLE Arduino -build_flags = ${esp32_base.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DESP32C3 -extra_scripts = ${esp32_base.extra_scripts} -build_unflags = ${esp32_base.build_unflags} - -fexceptions - -[esp32c3_common_LittleFS] -extends = esp32_base_idf5 -build_flags = ${esp32_base_idf5.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DUSE_LITTLEFS - -DESP32C3 -extra_scripts = ${esp32_base_idf5.extra_scripts} -build_unflags = ${esp32_base_idf5.build_unflags} - -fexceptions -board_build.filesystem = littlefs - - -[env:custom_ESP32c3_4M316k_CDC] -extends = esp32c3_common -board = esp32c3cdc -build_flags = ${esp32c3_common.build_flags} - -DPLUGIN_BUILD_CUSTOM -extra_scripts = ${esp32c3_common.extra_scripts} - pre:tools/pio/pre_custom_esp32.py - - -[env:custom_IR_ESP32c3_4M316k_CDC] -extends = esp32c3_common -board = esp32c3cdc -build_flags = ${esp32c3_common.build_flags} - -DPLUGIN_BUILD_CUSTOM - -DPLUGIN_BUILD_IR -lib_ignore = ${esp32_always.lib_ignore} - ESP32_ping -extra_scripts = ${esp32c3_common.extra_scripts} - pre:tools/pio/pre_custom_esp32_IR.py - pre:tools/pio/ir_build_check.py - -; [env:custom_ESP32c3_4M316k_LittleFS_CDC] -; extends = esp32c3_common_LittleFS -; board = esp32c3cdc -; build_flags = ${esp32c3_common_LittleFS.build_flags} -; -DPLUGIN_BUILD_CUSTOM -; extra_scripts = ${esp32c3_common_LittleFS.extra_scripts} -; pre:tools/pio/pre_custom_esp32.py - -[env:custom_ESP32c3_4M316k_LittleFS_CDC_ETH] -extends = esp32c3_common_LittleFS -board = esp32c3cdc -build_flags = ${esp32c3_common_LittleFS.build_flags} - -DPLUGIN_BUILD_CUSTOM - -DFEATURE_ETHERNET=1 -extra_scripts = ${esp32c3_common_LittleFS.extra_scripts} - pre:tools/pio/pre_custom_esp32.py - - - -[env:normal_ESP32c3_4M316k_CDC] -extends = esp32c3_common -board = esp32c3cdc -lib_ignore = ${esp32_common.lib_ignore} - ${no_ir.lib_ignore} - - -[env:normal_ESP32c3_4M316k_LittleFS_CDC_ETH] -extends = esp32c3_common_LittleFS -board = esp32c3cdc -build_flags = ${esp32c3_common_LittleFS.build_flags} - -DFEATURE_ETHERNET=1 -lib_ignore = ${esp32c3_common_LittleFS.lib_ignore} - ${no_ir.lib_ignore} - -[env:collection_A_ESP32c3_4M316k_CDC] -extends = esp32c3_common -board = esp32c3cdc -build_flags = ${esp32c3_common.build_flags} - -DPLUGIN_SET_COLLECTION_ESP32 - -DCOLLECTION_USE_RTTTL - -[env:collection_B_ESP32c3_4M316k_CDC] -extends = esp32c3_common -board = esp32c3cdc -build_flags = ${esp32c3_common.build_flags} - -DPLUGIN_SET_COLLECTION_B_ESP32 - -DCOLLECTION_USE_RTTTL - -[env:collection_C_ESP32c3_4M316k_CDC] -extends = esp32c3_common -board = esp32c3cdc -build_flags = ${esp32c3_common.build_flags} - -DPLUGIN_SET_COLLECTION_C_ESP32 - -DCOLLECTION_USE_RTTTL - -[env:collection_D_ESP32c3_4M316k_CDC] -extends = esp32c3_common -board = esp32c3cdc -build_flags = ${esp32c3_common.build_flags} - -DPLUGIN_SET_COLLECTION_D_ESP32 - -DCOLLECTION_USE_RTTTL - -[env:collection_E_ESP32c3_4M316k_CDC] -extends = esp32c3_common -board = esp32c3cdc -build_flags = ${esp32c3_common.build_flags} - -DPLUGIN_SET_COLLECTION_E_ESP32 - -DCOLLECTION_USE_RTTTL - -[env:collection_F_ESP32c3_4M316k_CDC] -extends = esp32c3_common -board = esp32c3cdc -build_flags = ${esp32c3_common.build_flags} - -DPLUGIN_SET_COLLECTION_F_ESP32 - -DCOLLECTION_USE_RTTTL - -[env:collection_G_ESP32c3_4M316k_CDC] -extends = esp32c3_common -board = esp32c3cdc -build_flags = ${esp32c3_common.build_flags} - -DPLUGIN_SET_COLLECTION_G_ESP32 - -DCOLLECTION_USE_RTTTL - - -[env:energy_ESP32c3_4M316k_CDC] -extends = esp32c3_common -board = esp32c3cdc -build_flags = ${esp32c3_common.build_flags} - -D PLUGIN_ENERGY_COLLECTION - -[env:energy_ESP32c3_4M316k_LittleFS_CDC_ETH] -extends = esp32c3_common_LittleFS -board = esp32c3cdc -build_flags = ${esp32c3_common_LittleFS.build_flags} - -D PLUGIN_ENERGY_COLLECTION - -DFEATURE_ETHERNET=1 - -[env:display_ESP32c3_4M316k_CDC] -extends = esp32c3_common -board = esp32c3cdc -build_flags = ${esp32c3_common.build_flags} - -D PLUGIN_DISPLAY_COLLECTION - -[env:climate_ESP32c3_4M316k_CDC] -extends = esp32c3_common -board = esp32c3cdc -build_flags = ${esp32c3_common.build_flags} - -D PLUGIN_CLIMATE_COLLECTION - -[env:neopixel_ESP32c3_4M316k_CDC] -extends = esp32c3_common -board = esp32c3cdc -build_flags = ${esp32c3_common.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DFEATURE_SD=1 - -DPLUGIN_NEOPIXEL_COLLECTION - -[env:neopixel_ESP32c3_4M316k_LittleFS_CDC_ETH] -extends = esp32c3_common_LittleFS -board = esp32c3cdc -build_flags = ${esp32c3_common_LittleFS.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DFEATURE_SD=1 - -DFEATURE_ETHERNET=1 - -DPLUGIN_NEOPIXEL_COLLECTION + + + +[esp32c3_common] +extends = esp32_base +lib_ignore = ${esp32_always.lib_ignore} + ESP32_ping + ${no_ir.lib_ignore} + ESP32 BLE Arduino +build_flags = ${esp32_base.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DESP32C3 +extra_scripts = ${esp32_base.extra_scripts} +build_unflags = ${esp32_base.build_unflags} + -fexceptions + +[esp32c3_common_LittleFS] +extends = esp32_base_idf5 +build_flags = ${esp32_base_idf5.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DUSE_LITTLEFS + -DESP32C3 +extra_scripts = ${esp32_base_idf5.extra_scripts} +build_unflags = ${esp32_base_idf5.build_unflags} + -fexceptions +board_build.filesystem = littlefs + + +[env:custom_ESP32c3_4M316k_CDC] +extends = esp32c3_common +board = esp32c3cdc +build_flags = ${esp32c3_common.build_flags} + -DPLUGIN_BUILD_CUSTOM +extra_scripts = ${esp32c3_common.extra_scripts} + pre:tools/pio/pre_custom_esp32.py + + +[env:custom_IR_ESP32c3_4M316k_CDC] +extends = esp32c3_common +board = esp32c3cdc +build_flags = ${esp32c3_common.build_flags} + -DPLUGIN_BUILD_CUSTOM + -DPLUGIN_BUILD_IR +lib_ignore = ${esp32_always.lib_ignore} + ESP32_ping +extra_scripts = ${esp32c3_common.extra_scripts} + pre:tools/pio/pre_custom_esp32_IR.py + pre:tools/pio/ir_build_check.py + +; [env:custom_ESP32c3_4M316k_LittleFS_CDC] +; extends = esp32c3_common_LittleFS +; board = esp32c3cdc +; build_flags = ${esp32c3_common_LittleFS.build_flags} +; -DPLUGIN_BUILD_CUSTOM +; extra_scripts = ${esp32c3_common_LittleFS.extra_scripts} +; pre:tools/pio/pre_custom_esp32.py + +[env:custom_ESP32c3_4M316k_LittleFS_CDC_ETH] +extends = esp32c3_common_LittleFS +board = esp32c3cdc +build_flags = ${esp32c3_common_LittleFS.build_flags} + -DPLUGIN_BUILD_CUSTOM + -DFEATURE_ETHERNET=1 +extra_scripts = ${esp32c3_common_LittleFS.extra_scripts} + pre:tools/pio/pre_custom_esp32.py + + + +[env:normal_ESP32c3_4M316k_CDC] +extends = esp32c3_common +board = esp32c3cdc +lib_ignore = ${esp32_common.lib_ignore} + ${no_ir.lib_ignore} + + +[env:normal_ESP32c3_4M316k_LittleFS_CDC_ETH] +extends = esp32c3_common_LittleFS +board = esp32c3cdc +build_flags = ${esp32c3_common_LittleFS.build_flags} + -DFEATURE_ETHERNET=1 +lib_ignore = ${esp32c3_common_LittleFS.lib_ignore} + ${no_ir.lib_ignore} + +[env:collection_A_ESP32c3_4M316k_CDC] +extends = esp32c3_common +board = esp32c3cdc +build_flags = ${esp32c3_common.build_flags} + -DPLUGIN_SET_COLLECTION_ESP32 + -DCOLLECTION_USE_RTTTL + +[env:collection_B_ESP32c3_4M316k_CDC] +extends = esp32c3_common +board = esp32c3cdc +build_flags = ${esp32c3_common.build_flags} + -DPLUGIN_SET_COLLECTION_B_ESP32 + -DCOLLECTION_USE_RTTTL + +[env:collection_C_ESP32c3_4M316k_CDC] +extends = esp32c3_common +board = esp32c3cdc +build_flags = ${esp32c3_common.build_flags} + -DPLUGIN_SET_COLLECTION_C_ESP32 + -DCOLLECTION_USE_RTTTL + +[env:collection_D_ESP32c3_4M316k_CDC] +extends = esp32c3_common +board = esp32c3cdc +build_flags = ${esp32c3_common.build_flags} + -DPLUGIN_SET_COLLECTION_D_ESP32 + -DCOLLECTION_USE_RTTTL + +[env:collection_E_ESP32c3_4M316k_CDC] +extends = esp32c3_common +board = esp32c3cdc +build_flags = ${esp32c3_common.build_flags} + -DPLUGIN_SET_COLLECTION_E_ESP32 + -DCOLLECTION_USE_RTTTL + +[env:collection_F_ESP32c3_4M316k_CDC] +extends = esp32c3_common +board = esp32c3cdc +build_flags = ${esp32c3_common.build_flags} + -DPLUGIN_SET_COLLECTION_F_ESP32 + -DCOLLECTION_USE_RTTTL + +[env:collection_G_ESP32c3_4M316k_CDC] +extends = esp32c3_common +board = esp32c3cdc +build_flags = ${esp32c3_common.build_flags} + -DPLUGIN_SET_COLLECTION_G_ESP32 + -DCOLLECTION_USE_RTTTL + + +[env:energy_ESP32c3_4M316k_CDC] +extends = esp32c3_common +board = esp32c3cdc +build_flags = ${esp32c3_common.build_flags} + -D PLUGIN_ENERGY_COLLECTION + +[env:energy_ESP32c3_4M316k_LittleFS_CDC_ETH] +extends = esp32c3_common_LittleFS +board = esp32c3cdc +build_flags = ${esp32c3_common_LittleFS.build_flags} + -D PLUGIN_ENERGY_COLLECTION + -DFEATURE_ETHERNET=1 + +[env:display_ESP32c3_4M316k_CDC] +extends = esp32c3_common +board = esp32c3cdc +build_flags = ${esp32c3_common.build_flags} + -D PLUGIN_DISPLAY_COLLECTION + +[env:climate_ESP32c3_4M316k_CDC] +extends = esp32c3_common +board = esp32c3cdc +build_flags = ${esp32c3_common.build_flags} + -D PLUGIN_CLIMATE_COLLECTION + +[env:neopixel_ESP32c3_4M316k_CDC] +extends = esp32c3_common +board = esp32c3cdc +build_flags = ${esp32c3_common.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DFEATURE_SD=1 + -DPLUGIN_NEOPIXEL_COLLECTION + +[env:neopixel_ESP32c3_4M316k_LittleFS_CDC_ETH] +extends = esp32c3_common_LittleFS +board = esp32c3cdc +build_flags = ${esp32c3_common_LittleFS.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DFEATURE_SD=1 + -DFEATURE_ETHERNET=1 + -DPLUGIN_NEOPIXEL_COLLECTION diff --git a/platformio_esp32c6_envs.ini b/platformio_esp32c6_envs.ini index 461498bb1e..af5e25d2c4 100644 --- a/platformio_esp32c6_envs.ini +++ b/platformio_esp32c6_envs.ini @@ -1,55 +1,55 @@ - - -[esp32c6_common_LittleFS] -extends = esp32_base_idf5 -build_flags = ${esp32_base_idf5.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DUSE_LITTLEFS - -DESP32C6 -extra_scripts = ${esp32_base_idf5.extra_scripts} -build_unflags = ${esp32_base_idf5.build_unflags} - -fexceptions -board_build.filesystem = littlefs -lib_ignore = ${esp32_base_idf5.lib_ignore} -board = esp32c6cdc - - -[env:custom_ESP32c6_4M316k_LittleFS_CDC_ETH] -extends = esp32c6_common_LittleFS -build_flags = ${esp32c6_common_LittleFS.build_flags} - -DPLUGIN_BUILD_CUSTOM - -DFEATURE_ETHERNET=1 -extra_scripts = ${esp32c6_common_LittleFS.extra_scripts} - pre:tools/pio/pre_custom_esp32c6.py - - -[env:normal_ESP32c6_4M316k_LittleFS_CDC_ETH] -extends = esp32c6_common_LittleFS -build_flags = ${esp32c6_common_LittleFS.build_flags} - -DFEATURE_ETHERNET=1 -lib_ignore = ${esp32c6_common_LittleFS.lib_ignore} - ${no_ir.lib_ignore} - - -[env:max_ESP32c6_8M1M_LittleFS_CDC_ETH] -extends = esp32c6_common_LittleFS -board = esp32c6cdc-8M -build_flags = ${esp32c6_common_LittleFS.build_flags} - -DFEATURE_ETHERNET=1 - -DFEATURE_ARDUINO_OTA=1 - -DPLUGIN_BUILD_MAX_ESP32 - -DPLUGIN_BUILD_IR_EXTENDED -extra_scripts = ${esp32c6_common_LittleFS.extra_scripts} - - -[env:max_ESP32c6_16M8M_LittleFS_CDC_ETH] -extends = esp32c6_common_LittleFS -board = esp32c6cdc-16M -build_flags = ${esp32c6_common_LittleFS.build_flags} - -DFEATURE_ETHERNET=1 - -DFEATURE_ARDUINO_OTA=1 - -DPLUGIN_BUILD_MAX_ESP32 - -DPLUGIN_BUILD_IR_EXTENDED -extra_scripts = ${esp32c6_common_LittleFS.extra_scripts} - - + + +[esp32c6_common_LittleFS] +extends = esp32_base_idf5 +build_flags = ${esp32_base_idf5.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DUSE_LITTLEFS + -DESP32C6 +extra_scripts = ${esp32_base_idf5.extra_scripts} +build_unflags = ${esp32_base_idf5.build_unflags} + -fexceptions +board_build.filesystem = littlefs +lib_ignore = ${esp32_base_idf5.lib_ignore} +board = esp32c6cdc + + +[env:custom_ESP32c6_4M316k_LittleFS_CDC_ETH] +extends = esp32c6_common_LittleFS +build_flags = ${esp32c6_common_LittleFS.build_flags} + -DPLUGIN_BUILD_CUSTOM + -DFEATURE_ETHERNET=1 +extra_scripts = ${esp32c6_common_LittleFS.extra_scripts} + pre:tools/pio/pre_custom_esp32c6.py + + +[env:normal_ESP32c6_4M316k_LittleFS_CDC_ETH] +extends = esp32c6_common_LittleFS +build_flags = ${esp32c6_common_LittleFS.build_flags} + -DFEATURE_ETHERNET=1 +lib_ignore = ${esp32c6_common_LittleFS.lib_ignore} + ${no_ir.lib_ignore} + + +[env:max_ESP32c6_8M1M_LittleFS_CDC_ETH] +extends = esp32c6_common_LittleFS +board = esp32c6cdc-8M +build_flags = ${esp32c6_common_LittleFS.build_flags} + -DFEATURE_ETHERNET=1 + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_BUILD_MAX_ESP32 + -DPLUGIN_BUILD_IR_EXTENDED +extra_scripts = ${esp32c6_common_LittleFS.extra_scripts} + + +[env:max_ESP32c6_16M8M_LittleFS_CDC_ETH] +extends = esp32c6_common_LittleFS +board = esp32c6cdc-16M +build_flags = ${esp32c6_common_LittleFS.build_flags} + -DFEATURE_ETHERNET=1 + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_BUILD_MAX_ESP32 + -DPLUGIN_BUILD_IR_EXTENDED +extra_scripts = ${esp32c6_common_LittleFS.extra_scripts} + + diff --git a/platformio_esp32s2_envs.ini b/platformio_esp32s2_envs.ini index f4b530945f..4419571ba0 100644 --- a/platformio_esp32s2_envs.ini +++ b/platformio_esp32s2_envs.ini @@ -1,167 +1,167 @@ - - - - -[esp32s2_common] -extends = esp32_base -lib_ignore = ${esp32_always.lib_ignore} - ESP32_ping - ${no_ir.lib_ignore} - ESP32 BLE Arduino -build_flags = ${esp32_base.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DESP32S2 -extra_scripts = ${esp32_base.extra_scripts} -build_unflags = ${esp32_base.build_unflags} - -fexceptions - -[esp32s2_common_LittleFS] -extends = esp32_base_idf5 -build_flags = ${esp32_base_idf5.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DUSE_LITTLEFS - -DESP32S2 -extra_scripts = ${esp32_base_idf5.extra_scripts} -build_unflags = ${esp32_base_idf5.build_unflags} - -fexceptions -board_build.filesystem = littlefs - - -[env:custom_ESP32s2_4M316k_CDC] -extends = esp32s2_common -board = esp32s2cdc -build_flags = ${esp32s2_common.build_flags} - -DPLUGIN_BUILD_CUSTOM - -DESP_CONSOLE_USB_CDC=y -extra_scripts = ${esp32s2_common.extra_scripts} - pre:tools/pio/pre_custom_esp32.py - -[env:neopixel_ESP32s2_4M316k_CDC] -extends = esp32s2_common -board = esp32s2cdc -build_flags = ${esp32s2_common.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DFEATURE_SD=1 - -DPLUGIN_NEOPIXEL_COLLECTION - -[env:neopixel_ESP32s2_4M316k_LittleFS_CDC_ETH] -extends = esp32s2_common_LittleFS -board = esp32s2cdc -build_flags = ${esp32s2_common_LittleFS.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DFEATURE_SD=1 - -DPLUGIN_NEOPIXEL_COLLECTION - -DFEATURE_ETHERNET=1 - - -[env:custom_IR_ESP32s2_4M316k_CDC] -extends = esp32s2_common -board = esp32s2cdc -build_flags = ${esp32s2_common.build_flags} - -DPLUGIN_BUILD_CUSTOM - -DPLUGIN_BUILD_IR -lib_ignore = ${esp32_always.lib_ignore} - ESP32_ping -extra_scripts = ${esp32s2_common.extra_scripts} - pre:tools/pio/pre_custom_esp32_IR.py - pre:tools/pio/ir_build_check.py - - - -[env:normal_ESP32s2_4M316k_CDC] -extends = esp32s2_common -board = esp32s2cdc -lib_ignore = ${esp32s2_common.lib_ignore} - ${no_ir.lib_ignore} - -[env:custom_ESP32s2_4M316k_LittleFS_CDC_ETH] -extends = esp32s2_common_LittleFS -board = esp32s2cdc -lib_ignore = ${esp32s2_common_LittleFS.lib_ignore} - ${no_ir.lib_ignore} -build_flags = ${esp32s2_common_LittleFS.build_flags} - -DPLUGIN_BUILD_CUSTOM - -DESP_CONSOLE_USB_CDC=y - -DFEATURE_ETHERNET=1 -extra_scripts = ${esp32s2_common_LittleFS.extra_scripts} - pre:tools/pio/pre_custom_esp32.py - - - -[env:normal_ESP32s2_4M316k_LittleFS_CDC_ETH] -extends = esp32s2_common_LittleFS -board = esp32s2cdc -build_flags = ${esp32s2_common_LittleFS.build_flags} - -DESP_CONSOLE_USB_CDC=y - -DFEATURE_ETHERNET=1 -lib_ignore = ${esp32s2_common_LittleFS.lib_ignore} - ${no_ir.lib_ignore} - -[env:collection_A_ESP32s2_4M316k_CDC] -extends = esp32s2_common -board = esp32s2cdc -build_flags = ${esp32s2_common.build_flags} - -DPLUGIN_SET_COLLECTION_ESP32 - -DCOLLECTION_USE_RTTTL - -[env:collection_B_ESP32s2_4M316k_CDC] -extends = esp32s2_common -board = esp32s2cdc -build_flags = ${esp32s2_common.build_flags} - -DPLUGIN_SET_COLLECTION_B_ESP32 - -DCOLLECTION_USE_RTTTL - -[env:collection_C_ESP32s2_4M316k_CDC] -extends = esp32s2_common -board = esp32s2cdc -build_flags = ${esp32s2_common.build_flags} - -DPLUGIN_SET_COLLECTION_C_ESP32 - -DCOLLECTION_USE_RTTTL - -[env:collection_D_ESP32s2_4M316k_CDC] -extends = esp32s2_common -board = esp32s2cdc -build_flags = ${esp32s2_common.build_flags} - -DPLUGIN_SET_COLLECTION_D_ESP32 - -DCOLLECTION_USE_RTTTL - -[env:collection_E_ESP32s2_4M316k_CDC] -extends = esp32s2_common -board = esp32s2cdc -build_flags = ${esp32s2_common.build_flags} - -DPLUGIN_SET_COLLECTION_E_ESP32 - -DCOLLECTION_USE_RTTTL - -[env:collection_F_ESP32s2_4M316k_CDC] -extends = esp32s2_common -board = esp32s2cdc -build_flags = ${esp32s2_common.build_flags} - -DPLUGIN_SET_COLLECTION_F_ESP32 - -DCOLLECTION_USE_RTTTL - -[env:collection_G_ESP32s2_4M316k_CDC] -extends = esp32s2_common -board = esp32s2cdc -build_flags = ${esp32s2_common.build_flags} - -DPLUGIN_SET_COLLECTION_G_ESP32 - -DCOLLECTION_USE_RTTTL - - -[env:energy_ESP32s2_4M316k_CDC] -extends = esp32s2_common -board = esp32s2cdc -build_flags = ${esp32s2_common.build_flags} - -D PLUGIN_ENERGY_COLLECTION - -[env:display_ESP32s2_4M316k_CDC] -extends = esp32s2_common -board = esp32s2cdc -build_flags = ${esp32s2_common.build_flags} - -D PLUGIN_DISPLAY_COLLECTION - -[env:climate_ESP32s2_4M316k_CDC] -extends = esp32s2_common -board = esp32s2cdc -build_flags = ${esp32s2_common.build_flags} - -D PLUGIN_CLIMATE_COLLECTION - + + + + +[esp32s2_common] +extends = esp32_base +lib_ignore = ${esp32_always.lib_ignore} + ESP32_ping + ${no_ir.lib_ignore} + ESP32 BLE Arduino +build_flags = ${esp32_base.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DESP32S2 +extra_scripts = ${esp32_base.extra_scripts} +build_unflags = ${esp32_base.build_unflags} + -fexceptions + +[esp32s2_common_LittleFS] +extends = esp32_base_idf5 +build_flags = ${esp32_base_idf5.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DUSE_LITTLEFS + -DESP32S2 +extra_scripts = ${esp32_base_idf5.extra_scripts} +build_unflags = ${esp32_base_idf5.build_unflags} + -fexceptions +board_build.filesystem = littlefs + + +[env:custom_ESP32s2_4M316k_CDC] +extends = esp32s2_common +board = esp32s2cdc +build_flags = ${esp32s2_common.build_flags} + -DPLUGIN_BUILD_CUSTOM + -DESP_CONSOLE_USB_CDC=y +extra_scripts = ${esp32s2_common.extra_scripts} + pre:tools/pio/pre_custom_esp32.py + +[env:neopixel_ESP32s2_4M316k_CDC] +extends = esp32s2_common +board = esp32s2cdc +build_flags = ${esp32s2_common.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DFEATURE_SD=1 + -DPLUGIN_NEOPIXEL_COLLECTION + +[env:neopixel_ESP32s2_4M316k_LittleFS_CDC_ETH] +extends = esp32s2_common_LittleFS +board = esp32s2cdc +build_flags = ${esp32s2_common_LittleFS.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DFEATURE_SD=1 + -DPLUGIN_NEOPIXEL_COLLECTION + -DFEATURE_ETHERNET=1 + + +[env:custom_IR_ESP32s2_4M316k_CDC] +extends = esp32s2_common +board = esp32s2cdc +build_flags = ${esp32s2_common.build_flags} + -DPLUGIN_BUILD_CUSTOM + -DPLUGIN_BUILD_IR +lib_ignore = ${esp32_always.lib_ignore} + ESP32_ping +extra_scripts = ${esp32s2_common.extra_scripts} + pre:tools/pio/pre_custom_esp32_IR.py + pre:tools/pio/ir_build_check.py + + + +[env:normal_ESP32s2_4M316k_CDC] +extends = esp32s2_common +board = esp32s2cdc +lib_ignore = ${esp32s2_common.lib_ignore} + ${no_ir.lib_ignore} + +[env:custom_ESP32s2_4M316k_LittleFS_CDC_ETH] +extends = esp32s2_common_LittleFS +board = esp32s2cdc +lib_ignore = ${esp32s2_common_LittleFS.lib_ignore} + ${no_ir.lib_ignore} +build_flags = ${esp32s2_common_LittleFS.build_flags} + -DPLUGIN_BUILD_CUSTOM + -DESP_CONSOLE_USB_CDC=y + -DFEATURE_ETHERNET=1 +extra_scripts = ${esp32s2_common_LittleFS.extra_scripts} + pre:tools/pio/pre_custom_esp32.py + + + +[env:normal_ESP32s2_4M316k_LittleFS_CDC_ETH] +extends = esp32s2_common_LittleFS +board = esp32s2cdc +build_flags = ${esp32s2_common_LittleFS.build_flags} + -DESP_CONSOLE_USB_CDC=y + -DFEATURE_ETHERNET=1 +lib_ignore = ${esp32s2_common_LittleFS.lib_ignore} + ${no_ir.lib_ignore} + +[env:collection_A_ESP32s2_4M316k_CDC] +extends = esp32s2_common +board = esp32s2cdc +build_flags = ${esp32s2_common.build_flags} + -DPLUGIN_SET_COLLECTION_ESP32 + -DCOLLECTION_USE_RTTTL + +[env:collection_B_ESP32s2_4M316k_CDC] +extends = esp32s2_common +board = esp32s2cdc +build_flags = ${esp32s2_common.build_flags} + -DPLUGIN_SET_COLLECTION_B_ESP32 + -DCOLLECTION_USE_RTTTL + +[env:collection_C_ESP32s2_4M316k_CDC] +extends = esp32s2_common +board = esp32s2cdc +build_flags = ${esp32s2_common.build_flags} + -DPLUGIN_SET_COLLECTION_C_ESP32 + -DCOLLECTION_USE_RTTTL + +[env:collection_D_ESP32s2_4M316k_CDC] +extends = esp32s2_common +board = esp32s2cdc +build_flags = ${esp32s2_common.build_flags} + -DPLUGIN_SET_COLLECTION_D_ESP32 + -DCOLLECTION_USE_RTTTL + +[env:collection_E_ESP32s2_4M316k_CDC] +extends = esp32s2_common +board = esp32s2cdc +build_flags = ${esp32s2_common.build_flags} + -DPLUGIN_SET_COLLECTION_E_ESP32 + -DCOLLECTION_USE_RTTTL + +[env:collection_F_ESP32s2_4M316k_CDC] +extends = esp32s2_common +board = esp32s2cdc +build_flags = ${esp32s2_common.build_flags} + -DPLUGIN_SET_COLLECTION_F_ESP32 + -DCOLLECTION_USE_RTTTL + +[env:collection_G_ESP32s2_4M316k_CDC] +extends = esp32s2_common +board = esp32s2cdc +build_flags = ${esp32s2_common.build_flags} + -DPLUGIN_SET_COLLECTION_G_ESP32 + -DCOLLECTION_USE_RTTTL + + +[env:energy_ESP32s2_4M316k_CDC] +extends = esp32s2_common +board = esp32s2cdc +build_flags = ${esp32s2_common.build_flags} + -D PLUGIN_ENERGY_COLLECTION + +[env:display_ESP32s2_4M316k_CDC] +extends = esp32s2_common +board = esp32s2cdc +build_flags = ${esp32s2_common.build_flags} + -D PLUGIN_DISPLAY_COLLECTION + +[env:climate_ESP32s2_4M316k_CDC] +extends = esp32s2_common +board = esp32s2cdc +build_flags = ${esp32s2_common.build_flags} + -D PLUGIN_CLIMATE_COLLECTION + diff --git a/platformio_esp32s3_envs.ini b/platformio_esp32s3_envs.ini index a3d2a4e0b9..59ce3af294 100644 --- a/platformio_esp32s3_envs.ini +++ b/platformio_esp32s3_envs.ini @@ -1,220 +1,220 @@ - - - - -[esp32s3_common] -extends = esp32_base -lib_ignore = ${esp32_always.lib_ignore} - ESP32_ping - ${no_ir.lib_ignore} - ESP32 BLE Arduino -build_flags = ${esp32_base.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DESP32S3 -extra_scripts = ${esp32_base.extra_scripts} -build_unflags = ${esp32_base.build_unflags} - -fexceptions - -[esp32s3_common_LittleFS] -extends = esp32_base_idf5 -lib_ignore = ${esp32_common_LittleFS.lib_ignore} - ESP32_ping - ${esp32_base_idf5.lib_ignore} -build_flags = ${esp32_base_idf5.build_flags} -; -mtext-section-literals - -DFEATURE_ARDUINO_OTA=1 - -DUSE_LITTLEFS - -DESP32S3 -extra_scripts = ${esp32_base_idf5.extra_scripts} -build_unflags = ${esp32_base_idf5.build_unflags} - -fexceptions -board_build.filesystem = littlefs - - -[env:custom_ESP32s3_4M316k_CDC] -extends = esp32s3_common -board = esp32s3cdc-qio_qspi -build_flags = ${esp32s3_common.build_flags} - -DPLUGIN_BUILD_CUSTOM -extra_scripts = ${esp32s3_common.extra_scripts} - pre:tools/pio/pre_custom_esp32.py - - -[env:custom_IR_ESP32s3_4M316k_CDC] -extends = esp32s3_common -board = esp32s3cdc-qio_qspi -build_flags = ${esp32s3_common.build_flags} - -DPLUGIN_BUILD_CUSTOM - -DPLUGIN_BUILD_IR -lib_ignore = ${esp32_always.lib_ignore} - ESP32_ping -extra_scripts = ${esp32s3_common.extra_scripts} - pre:tools/pio/pre_custom_esp32_IR.py - pre:tools/pio/ir_build_check.py - - - -[env:normal_ESP32s3_4M316k_CDC] -extends = esp32s3_common -board = esp32s3cdc-qio_qspi -lib_ignore = ${esp32s3_common.lib_ignore} - ${no_ir.lib_ignore} - - -[env:collection_A_ESP32s3_4M316k_CDC] -extends = esp32s3_common -board = esp32s3cdc-qio_qspi -build_flags = ${esp32s3_common.build_flags} - -DPLUGIN_SET_COLLECTION_ESP32 - -DCOLLECTION_USE_RTTTL - -[env:collection_B_ESP32s3_4M316k_CDC] -extends = esp32s3_common -board = esp32s3cdc-qio_qspi -build_flags = ${esp32s3_common.build_flags} - -DPLUGIN_SET_COLLECTION_B_ESP32 - -DCOLLECTION_USE_RTTTL - -[env:collection_C_ESP32s3_4M316k_CDC] -extends = esp32s3_common -board = esp32s3cdc-qio_qspi -build_flags = ${esp32s3_common.build_flags} - -DPLUGIN_SET_COLLECTION_C_ESP32 - -DCOLLECTION_USE_RTTTL - -[env:collection_D_ESP32s3_4M316k_CDC] -extends = esp32s3_common -board = esp32s3cdc-qio_qspi -build_flags = ${esp32s3_common.build_flags} - -DPLUGIN_SET_COLLECTION_D_ESP32 - -DCOLLECTION_USE_RTTTL - -[env:collection_E_ESP32s3_4M316k_CDC] -extends = esp32s3_common -board = esp32s3cdc-qio_qspi -build_flags = ${esp32s3_common.build_flags} - -DPLUGIN_SET_COLLECTION_E_ESP32 - -DCOLLECTION_USE_RTTTL - -[env:collection_F_ESP32s3_4M316k_CDC] -extends = esp32s3_common -board = esp32s3cdc-qio_qspi -build_flags = ${esp32s3_common.build_flags} - -DPLUGIN_SET_COLLECTION_F_ESP32 - -DCOLLECTION_USE_RTTTL - -[env:collection_G_ESP32s3_4M316k_CDC] -extends = esp32s3_common -board = esp32s3cdc-qio_qspi -build_flags = ${esp32s3_common.build_flags} - -DPLUGIN_SET_COLLECTION_G_ESP32 - -DCOLLECTION_USE_RTTTL - - -[env:energy_ESP32s3_4M316k_CDC] -extends = esp32s3_common -board = esp32s3cdc-qio_qspi -build_flags = ${esp32s3_common.build_flags} - -D PLUGIN_ENERGY_COLLECTION - -[env:display_ESP32s3_4M316k_CDC] -extends = esp32s3_common -board = esp32s3cdc-qio_qspi -build_flags = ${esp32s3_common.build_flags} - -D PLUGIN_DISPLAY_COLLECTION - -[env:climate_ESP32s3_4M316k_CDC] -extends = esp32s3_common -board = esp32s3cdc-qio_qspi -build_flags = ${esp32s3_common.build_flags} - -D PLUGIN_CLIMATE_COLLECTION - -[env:neopixel_ESP32s3_4M316k_CDC] -extends = esp32s3_common -board = esp32s3cdc-qio_qspi -build_flags = ${esp32s3_common.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DFEATURE_SD=1 - -DPLUGIN_NEOPIXEL_COLLECTION - -[env:neopixel_ESP32s3_4M316k_LittleFS_CDC_ETH] -extends = esp32s3_common_LittleFS -board = esp32s3cdc-qio_qspi -build_flags = ${esp32s3_common_LittleFS.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DFEATURE_SD=1 - -DPLUGIN_NEOPIXEL_COLLECTION - -DFEATURE_ETHERNET=1 - - -[env:custom_ESP32s3_8M1M_LittleFS_CDC_ETH] -extends = esp32s3_common_LittleFS -board = esp32s3cdc-qio_qspi-8M -build_flags = ${esp32s3_common_LittleFS.build_flags} - -DFEATURE_ETHERNET=1 - -DFEATURE_ARDUINO_OTA=1 - -DPLUGIN_BUILD_CUSTOM - -DFEATURE_SD=1 -extra_scripts = ${esp32s3_common_LittleFS.extra_scripts} - pre:tools/pio/pre_custom_esp32.py - -[env:custom_ESP32s3_8M1M_LittleFS_OPI_PSRAM_CDC_ETH] -extends = env:custom_ESP32s3_8M1M_LittleFS_CDC_ETH -board = esp32s3cdc-qio_opi-8M - - -[env:max_ESP32s3_8M1M_LittleFS_CDC_ETH] -extends = esp32s3_common_LittleFS -board = esp32s3cdc-qio_qspi-8M -build_flags = ${esp32s3_common_LittleFS.build_flags} - -DFEATURE_ETHERNET=1 - -DFEATURE_ARDUINO_OTA=1 - -DPLUGIN_BUILD_MAX_ESP32 - -DPLUGIN_BUILD_IR_EXTENDED -extra_scripts = ${esp32s3_common_LittleFS.extra_scripts} - - -[env:max_ESP32s3_8M1M_LittleFS_OPI_PSRAM_CDC_ETH] -extends = env:max_ESP32s3_8M1M_LittleFS_CDC_ETH -board = esp32s3cdc-qio_opi-8M - - -[env:custom_ESP32s3_16M8M_LittleFS_CDC_ETH] -extends = esp32s3_common_LittleFS -board = esp32s3cdc-qio_qspi-16M -build_flags = ${esp32s3_common_LittleFS.build_flags} - -DFEATURE_ETHERNET=1 - -DFEATURE_ARDUINO_OTA=1 - -DPLUGIN_BUILD_CUSTOM - -DPLUGIN_BUILD_IR_EXTENDED - -DFEATURE_SD=1 -extra_scripts = ${esp32s3_common_LittleFS.extra_scripts} - pre:tools/pio/pre_custom_esp32.py - -[env:custom_ESP32s3_16M8M_LittleFS_OPI_PSRAM_CDC_ETH] -extends = env:custom_ESP32s3_16M8M_LittleFS_CDC_ETH -board = esp32s3cdc-qio_opi-16M - - -[env:max_ESP32s3_16M8M_LittleFS_CDC_ETH] -extends = esp32s3_common_LittleFS -board = esp32s3cdc-qio_qspi-16M -build_flags = ${esp32s3_common_LittleFS.build_flags} - -DFEATURE_ETHERNET=1 - -DFEATURE_ARDUINO_OTA=1 - -DPLUGIN_BUILD_MAX_ESP32 - -DPLUGIN_BUILD_IR_EXTENDED -extra_scripts = ${esp32s3_common_LittleFS.extra_scripts} - - -[env:max_ESP32s3_16M8M_LittleFS_OPI_PSRAM_CDC_ETH] -extends = esp32s3_common_LittleFS -board = esp32s3cdc-qio_opi-16M -build_flags = ${esp32s3_common_LittleFS.build_flags} - -DFEATURE_ETHERNET=1 - -DFEATURE_ARDUINO_OTA=1 - -DPLUGIN_BUILD_MAX_ESP32 - -DPLUGIN_BUILD_IR_EXTENDED -extra_scripts = ${esp32s3_common_LittleFS.extra_scripts} - - + + + + +[esp32s3_common] +extends = esp32_base +lib_ignore = ${esp32_always.lib_ignore} + ESP32_ping + ${no_ir.lib_ignore} + ESP32 BLE Arduino +build_flags = ${esp32_base.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DESP32S3 +extra_scripts = ${esp32_base.extra_scripts} +build_unflags = ${esp32_base.build_unflags} + -fexceptions + +[esp32s3_common_LittleFS] +extends = esp32_base_idf5 +lib_ignore = ${esp32_common_LittleFS.lib_ignore} + ESP32_ping + ${esp32_base_idf5.lib_ignore} +build_flags = ${esp32_base_idf5.build_flags} +; -mtext-section-literals + -DFEATURE_ARDUINO_OTA=1 + -DUSE_LITTLEFS + -DESP32S3 +extra_scripts = ${esp32_base_idf5.extra_scripts} +build_unflags = ${esp32_base_idf5.build_unflags} + -fexceptions +board_build.filesystem = littlefs + + +[env:custom_ESP32s3_4M316k_CDC] +extends = esp32s3_common +board = esp32s3cdc-qio_qspi +build_flags = ${esp32s3_common.build_flags} + -DPLUGIN_BUILD_CUSTOM +extra_scripts = ${esp32s3_common.extra_scripts} + pre:tools/pio/pre_custom_esp32.py + + +[env:custom_IR_ESP32s3_4M316k_CDC] +extends = esp32s3_common +board = esp32s3cdc-qio_qspi +build_flags = ${esp32s3_common.build_flags} + -DPLUGIN_BUILD_CUSTOM + -DPLUGIN_BUILD_IR +lib_ignore = ${esp32_always.lib_ignore} + ESP32_ping +extra_scripts = ${esp32s3_common.extra_scripts} + pre:tools/pio/pre_custom_esp32_IR.py + pre:tools/pio/ir_build_check.py + + + +[env:normal_ESP32s3_4M316k_CDC] +extends = esp32s3_common +board = esp32s3cdc-qio_qspi +lib_ignore = ${esp32s3_common.lib_ignore} + ${no_ir.lib_ignore} + + +[env:collection_A_ESP32s3_4M316k_CDC] +extends = esp32s3_common +board = esp32s3cdc-qio_qspi +build_flags = ${esp32s3_common.build_flags} + -DPLUGIN_SET_COLLECTION_ESP32 + -DCOLLECTION_USE_RTTTL + +[env:collection_B_ESP32s3_4M316k_CDC] +extends = esp32s3_common +board = esp32s3cdc-qio_qspi +build_flags = ${esp32s3_common.build_flags} + -DPLUGIN_SET_COLLECTION_B_ESP32 + -DCOLLECTION_USE_RTTTL + +[env:collection_C_ESP32s3_4M316k_CDC] +extends = esp32s3_common +board = esp32s3cdc-qio_qspi +build_flags = ${esp32s3_common.build_flags} + -DPLUGIN_SET_COLLECTION_C_ESP32 + -DCOLLECTION_USE_RTTTL + +[env:collection_D_ESP32s3_4M316k_CDC] +extends = esp32s3_common +board = esp32s3cdc-qio_qspi +build_flags = ${esp32s3_common.build_flags} + -DPLUGIN_SET_COLLECTION_D_ESP32 + -DCOLLECTION_USE_RTTTL + +[env:collection_E_ESP32s3_4M316k_CDC] +extends = esp32s3_common +board = esp32s3cdc-qio_qspi +build_flags = ${esp32s3_common.build_flags} + -DPLUGIN_SET_COLLECTION_E_ESP32 + -DCOLLECTION_USE_RTTTL + +[env:collection_F_ESP32s3_4M316k_CDC] +extends = esp32s3_common +board = esp32s3cdc-qio_qspi +build_flags = ${esp32s3_common.build_flags} + -DPLUGIN_SET_COLLECTION_F_ESP32 + -DCOLLECTION_USE_RTTTL + +[env:collection_G_ESP32s3_4M316k_CDC] +extends = esp32s3_common +board = esp32s3cdc-qio_qspi +build_flags = ${esp32s3_common.build_flags} + -DPLUGIN_SET_COLLECTION_G_ESP32 + -DCOLLECTION_USE_RTTTL + + +[env:energy_ESP32s3_4M316k_CDC] +extends = esp32s3_common +board = esp32s3cdc-qio_qspi +build_flags = ${esp32s3_common.build_flags} + -D PLUGIN_ENERGY_COLLECTION + +[env:display_ESP32s3_4M316k_CDC] +extends = esp32s3_common +board = esp32s3cdc-qio_qspi +build_flags = ${esp32s3_common.build_flags} + -D PLUGIN_DISPLAY_COLLECTION + +[env:climate_ESP32s3_4M316k_CDC] +extends = esp32s3_common +board = esp32s3cdc-qio_qspi +build_flags = ${esp32s3_common.build_flags} + -D PLUGIN_CLIMATE_COLLECTION + +[env:neopixel_ESP32s3_4M316k_CDC] +extends = esp32s3_common +board = esp32s3cdc-qio_qspi +build_flags = ${esp32s3_common.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DFEATURE_SD=1 + -DPLUGIN_NEOPIXEL_COLLECTION + +[env:neopixel_ESP32s3_4M316k_LittleFS_CDC_ETH] +extends = esp32s3_common_LittleFS +board = esp32s3cdc-qio_qspi +build_flags = ${esp32s3_common_LittleFS.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DFEATURE_SD=1 + -DPLUGIN_NEOPIXEL_COLLECTION + -DFEATURE_ETHERNET=1 + + +[env:custom_ESP32s3_8M1M_LittleFS_CDC_ETH] +extends = esp32s3_common_LittleFS +board = esp32s3cdc-qio_qspi-8M +build_flags = ${esp32s3_common_LittleFS.build_flags} + -DFEATURE_ETHERNET=1 + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_BUILD_CUSTOM + -DFEATURE_SD=1 +extra_scripts = ${esp32s3_common_LittleFS.extra_scripts} + pre:tools/pio/pre_custom_esp32.py + +[env:custom_ESP32s3_8M1M_LittleFS_OPI_PSRAM_CDC_ETH] +extends = env:custom_ESP32s3_8M1M_LittleFS_CDC_ETH +board = esp32s3cdc-qio_opi-8M + + +[env:max_ESP32s3_8M1M_LittleFS_CDC_ETH] +extends = esp32s3_common_LittleFS +board = esp32s3cdc-qio_qspi-8M +build_flags = ${esp32s3_common_LittleFS.build_flags} + -DFEATURE_ETHERNET=1 + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_BUILD_MAX_ESP32 + -DPLUGIN_BUILD_IR_EXTENDED +extra_scripts = ${esp32s3_common_LittleFS.extra_scripts} + + +[env:max_ESP32s3_8M1M_LittleFS_OPI_PSRAM_CDC_ETH] +extends = env:max_ESP32s3_8M1M_LittleFS_CDC_ETH +board = esp32s3cdc-qio_opi-8M + + +[env:custom_ESP32s3_16M8M_LittleFS_CDC_ETH] +extends = esp32s3_common_LittleFS +board = esp32s3cdc-qio_qspi-16M +build_flags = ${esp32s3_common_LittleFS.build_flags} + -DFEATURE_ETHERNET=1 + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_BUILD_CUSTOM + -DPLUGIN_BUILD_IR_EXTENDED + -DFEATURE_SD=1 +extra_scripts = ${esp32s3_common_LittleFS.extra_scripts} + pre:tools/pio/pre_custom_esp32.py + +[env:custom_ESP32s3_16M8M_LittleFS_OPI_PSRAM_CDC_ETH] +extends = env:custom_ESP32s3_16M8M_LittleFS_CDC_ETH +board = esp32s3cdc-qio_opi-16M + + +[env:max_ESP32s3_16M8M_LittleFS_CDC_ETH] +extends = esp32s3_common_LittleFS +board = esp32s3cdc-qio_qspi-16M +build_flags = ${esp32s3_common_LittleFS.build_flags} + -DFEATURE_ETHERNET=1 + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_BUILD_MAX_ESP32 + -DPLUGIN_BUILD_IR_EXTENDED +extra_scripts = ${esp32s3_common_LittleFS.extra_scripts} + + +[env:max_ESP32s3_16M8M_LittleFS_OPI_PSRAM_CDC_ETH] +extends = esp32s3_common_LittleFS +board = esp32s3cdc-qio_opi-16M +build_flags = ${esp32s3_common_LittleFS.build_flags} + -DFEATURE_ETHERNET=1 + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_BUILD_MAX_ESP32 + -DPLUGIN_BUILD_IR_EXTENDED +extra_scripts = ${esp32s3_common_LittleFS.extra_scripts} + + diff --git a/platformio_esp82xx_envs.ini b/platformio_esp82xx_envs.ini index 9cbff2de14..d459c27b27 100644 --- a/platformio_esp82xx_envs.ini +++ b/platformio_esp82xx_envs.ini @@ -696,13 +696,16 @@ lib_ignore = ${regular_platform.lib_ignore} ; GPIO13 Blue Led (0 = On, 1 = Off) [env:hard_SONOFF_POW_4M1M] extends = esp8266_4M1M, hard_esp82xx -platform = ${hard_esp82xx.platform} -platform_packages = ${hard_esp82xx.platform_packages} -build_flags = ${hard_esp82xx.build_flags} +platform = ${core_2_7_4.platform} +platform_packages = ${core_2_7_4.platform_packages} +build_flags = ${core_2_7_4.build_flags} ${esp8266_4M1M.build_flags} + -DBUILD_NO_DEBUG + -DPLUGIN_BUILD_CUSTOM -DPLUGIN_SET_SONOFF_POW -DFEATURE_IMPROV=0 -lib_ignore = ${hard_esp82xx.lib_ignore} + -DPLUGIN_STATS_NR_ELEMENTS=64 +lib_ignore = ${esp8266_custom_common_274.lib_ignore} diff --git a/src/Custom-sample.h b/src/Custom-sample.h index 76ef872ab5..aae29b2472 100644 --- a/src/Custom-sample.h +++ b/src/Custom-sample.h @@ -542,6 +542,7 @@ static const char DATA_ESPEASY_DEFAULT_MIN_CSS[] PROGMEM = { // #define USES_P164 // Gases - ENS16x TVOC/eCO2 // #define USES_P166 // Output - GP8403 Dual channel DAC (Digital Analog Converter) // #define USES_P167 // Environment - Sensirion SEN5x / Ikea Vindstyrka +// #define USES_P169 // Environment - AS3935 Lightning Detector /* ####################################################################################################### diff --git a/src/_C001.cpp b/src/_C001.cpp index 13e180aa09..46ce884e6a 100644 --- a/src/_C001.cpp +++ b/src/_C001.cpp @@ -129,7 +129,7 @@ bool CPlugin_001(CPlugin::Function function, struct EventStruct *event, String& // Uncrustify may change this into multi line, which will result in failed builds // *INDENT-OFF* -bool do_process_c001_delay_queue(int controller_number, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { +bool do_process_c001_delay_queue(cpluginID_t cpluginID, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { const C001_queue_element& element = static_cast(element_base); // *INDENT-ON* @@ -142,7 +142,7 @@ bool do_process_c001_delay_queue(int controller_number, const Queue_element_base int httpCode = -1; send_via_http( - controller_number, + cpluginID, ControllerSettings, element._controller_idx, element.txt, diff --git a/src/_C003.cpp b/src/_C003.cpp index ca58458271..0405f090bb 100644 --- a/src/_C003.cpp +++ b/src/_C003.cpp @@ -85,7 +85,7 @@ bool CPlugin_003(CPlugin::Function function, struct EventStruct *event, String& // Uncrustify may change this into multi line, which will result in failed builds // *INDENT-OFF* -bool do_process_c003_delay_queue(int controller_number, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { +bool do_process_c003_delay_queue(cpluginID_t cpluginID, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { const C003_queue_element& element = static_cast(element_base); // *INDENT-ON* bool success = false; @@ -93,7 +93,7 @@ bool do_process_c003_delay_queue(int controller_number, const Queue_element_base // Use WiFiClient class to create TCP connections WiFiClient client; - if (!try_connect_host(controller_number, client, ControllerSettings, F("TELNT: "))) + if (!try_connect_host(cpluginID, client, ControllerSettings, F("TELNT: "))) { return success; } diff --git a/src/_C004.cpp b/src/_C004.cpp index aa63b2dad1..5e32ca2eb7 100644 --- a/src/_C004.cpp +++ b/src/_C004.cpp @@ -94,7 +94,7 @@ bool CPlugin_004(CPlugin::Function function, struct EventStruct *event, String& // Uncrustify may change this into multi line, which will result in failed builds // *INDENT-OFF* -bool do_process_c004_delay_queue(int controller_number, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { +bool do_process_c004_delay_queue(cpluginID_t cpluginID, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { const C004_queue_element& element = static_cast(element_base); // *INDENT-ON* String postDataStr = F("api_key="); @@ -122,7 +122,7 @@ bool do_process_c004_delay_queue(int controller_number, const Queue_element_base int httpCode = -1; send_via_http( - controller_number, + cpluginID, ControllerSettings, element._controller_idx, F("/update"), // uri diff --git a/src/_C005.cpp b/src/_C005.cpp index f837e4374a..222994361d 100644 --- a/src/_C005.cpp +++ b/src/_C005.cpp @@ -1,290 +1,312 @@ -#include "src/Helpers/_CPlugin_Helper.h" -#ifdef USES_C005 - -# include "src/Commands/ExecuteCommand.h" -# include "src/Globals/EventQueue.h" -# include "src/Helpers/PeriodicalActions.h" -# include "src/Helpers/StringParser.h" -# include "_Plugin_Helper.h" - -// ####################################################################################################### -// ################### Controller Plugin 005: Home Assistant (openHAB) MQTT ############################## -// ####################################################################################################### - -# define CPLUGIN_005 -# define CPLUGIN_ID_005 5 -# define CPLUGIN_NAME_005 "Home Assistant (openHAB) MQTT" - -String CPlugin_005_pubname; -bool CPlugin_005_mqtt_retainFlag = false; - -bool C005_parse_command(struct EventStruct *event); - -bool CPlugin_005(CPlugin::Function function, struct EventStruct *event, String& string) -{ - bool success = false; - - switch (function) - { - case CPlugin::Function::CPLUGIN_PROTOCOL_ADD: - { - ProtocolStruct& proto = getProtocolStruct(event->idx); // = CPLUGIN_ID_005; - proto.usesMQTT = true; - proto.usesTemplate = true; - proto.usesAccount = true; - proto.usesPassword = true; - proto.usesExtCreds = true; - proto.defaultPort = 1883; - proto.usesID = false; - break; - } - - case CPlugin::Function::CPLUGIN_GET_DEVICENAME: - { - string = F(CPLUGIN_NAME_005); - break; - } - - case CPlugin::Function::CPLUGIN_INIT: - { - success = init_mqtt_delay_queue(event->ControllerIndex, CPlugin_005_pubname, CPlugin_005_mqtt_retainFlag); - break; - } - - case CPlugin::Function::CPLUGIN_EXIT: - { - exit_mqtt_delay_queue(); - break; - } - - case CPlugin::Function::CPLUGIN_PROTOCOL_TEMPLATE: - { - event->String1 = F("%sysname%/#"); - event->String2 = F("%sysname%/%tskname%/%valname%"); - break; - } - - case CPlugin::Function::CPLUGIN_PROTOCOL_RECV: - { - controllerIndex_t ControllerID = findFirstEnabledControllerWithId(CPLUGIN_ID_005); - - if (validControllerIndex(ControllerID)) { - C005_parse_command(event); - } - break; - } - - case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: - { - if (MQTT_queueFull(event->ControllerIndex)) { - break; - } - - - String pubname = CPlugin_005_pubname; - bool mqtt_retainFlag = CPlugin_005_mqtt_retainFlag; - - parseControllerVariables(pubname, event, false); - - uint8_t valueCount = getValueCountForTask(event->TaskIndex); - - for (uint8_t x = 0; x < valueCount; x++) - { - // MFD: skip publishing for values with empty labels (removes unnecessary publishing of unwanted values) - if (getTaskValueName(event->TaskIndex, x).isEmpty()) { - continue; // we skip values with empty labels - } - - String tmppubname = pubname; - parseSingleControllerVariable(tmppubname, event, x, false); - String value; - if (event->sensorType == Sensor_VType::SENSOR_TYPE_STRING) { - value = event->String2.substring(0, 20); // For the log - } else { - value = formatUserVarNoCheck(event, x); - } -# ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - addLogMove(LOG_LEVEL_DEBUG, - strformat( - F("MQTT : %s %s"), - tmppubname.c_str(), - value.c_str())); - } -# endif // ifndef BUILD_NO_DEBUG - - // Small optimization so we don't try to copy potentially large strings - if (event->sensorType == Sensor_VType::SENSOR_TYPE_STRING) { - if (MQTTpublish(event->ControllerIndex, event->TaskIndex, tmppubname.c_str(), event->String2.c_str(), mqtt_retainFlag)) - success = true; - } else { - // Publish using move operator, thus tmppubname and value are empty after this call - if (MQTTpublish(event->ControllerIndex, event->TaskIndex, std::move(tmppubname), std::move(value), mqtt_retainFlag)) - success = true; - } - } - break; - } - - case CPlugin::Function::CPLUGIN_FLUSH: - { - processMQTTdelayQueue(); - delay(0); - break; - } - - default: - break; - } - - return success; -} - -bool C005_parse_command(struct EventStruct *event) { - // FIXME TD-er: Command is not parsed for template arguments. - - // Topic : event->String1 - // Message: event->String2 - String cmd; - bool validTopic = false; - const int lastindex = event->String1.lastIndexOf('/'); - const String lastPartTopic = event->String1.substring(lastindex + 1); - const bool has_cmd_arg_index = event->String1.lastIndexOf(F("cmd_arg")) != -1; - - if (equals(lastPartTopic, F("cmd"))) { - // Example: - // Topic: ESP_Easy/Bathroom_pir_env/cmd - // Message: gpio,14,0 - // Full command: gpio,14,0 - - move_special(cmd, String(event->String2)); - - // SP_C005a: string= ;cmd=gpio,12,0 ;taskIndex=12 ;string1=ESPT12/cmd ;string2=gpio,12,0 - validTopic = true; - } else if (has_cmd_arg_index) { - // Example: - // Topic: ESP_Easy/Bathroom_pir_env/cmd_arg1/GPIO/0 - // Message: 14 - // Full command: gpio,14,0 - - uint8_t topic_index = 1; - String topic_folder = parseStringKeepCase(event->String1, topic_index, '/'); - - while(!topic_folder.startsWith(F("cmd_arg")) && !topic_folder.isEmpty()) { - ++topic_index; - topic_folder = parseStringKeepCase(event->String1, topic_index, '/'); - } - if (!topic_folder.isEmpty()) { - int32_t cmd_arg_nr = -1; - if (validIntFromString(topic_folder.substring(7), cmd_arg_nr)) { - int constructed_cmd_arg_nr = 0; - ++topic_index; - topic_folder = parseStringKeepCase(event->String1, topic_index, '/'); - bool msg_added = false; - while(!topic_folder.isEmpty()) { - if (constructed_cmd_arg_nr != 0) { - cmd += ','; - } - if (constructed_cmd_arg_nr == cmd_arg_nr) { - cmd += event->String2; - msg_added = true; - } else { - cmd += topic_folder; - ++topic_index; - topic_folder = parseStringKeepCase(event->String1, topic_index, '/'); - } - ++constructed_cmd_arg_nr; - } - if (!msg_added) { - cmd += ','; - cmd += event->String2; - } - // addLog(LOG_LEVEL_INFO, concat(F("MQTT cmd: "), cmd)); - - validTopic = true; - } - } - } else { - // Example: - // Topic: ESP_Easy/Bathroom_pir_env/GPIO/14 - // Message: 0 or 1 - // Full command: gpio,14,0 - if (lastindex > 0) { - // Topic has at least one separator - int32_t lastPartTopic_int; - float value_f; - - if (validFloatFromString(event->String2, value_f) && - validIntFromString(lastPartTopic, lastPartTopic_int)) { - const int prevLastindex = event->String1.lastIndexOf('/', lastindex - 1); - - cmd = strformat( - F("%s,%d,%s"), - event->String1.substring(prevLastindex + 1, lastindex).c_str(), - lastPartTopic_int, - event->String2.c_str() // Just use the original format - ); - validTopic = true; - } - } - } - - if (validTopic) { - // in case of event, store to buffer and return... - const String command = parseString(cmd, 1); - - if ((equals(command, F("event"))) || (equals(command, F("asyncevent")))) { - if (Settings.UseRules) { - // Need to sanitize the event a bit to allow for sending event values as MQTT messages. - // For example: - // Publish topic: espeasy_node/cmd_arg2/event/myevent/2 - // Message: 1 - // Actual event: myevent=1,2 - - // Strip out the "event" or "asyncevent" part, leaving the actual event string - cmd = parseStringToEndKeepCase(cmd, 2); - - { - // Get the first part upto a parameter separator - // Example: "myEvent,1,2,3", which needs to be converted to "myEvent=1,2,3" - // N.B. This may contain the first eventvalue too - // e.g. "myEvent=1,2,3" => "myEvent=1" - String eventName = parseStringKeepCase(cmd, 1); - String eventValues = parseStringToEndKeepCase(cmd, 2); - const int equal_pos = eventName.indexOf('='); - if (equal_pos != -1) { - // We found an '=' character, so the actual event name is everything before that char. - eventName = cmd.substring(0, equal_pos); - eventValues = cmd.substring(equal_pos + 1); // Rest of the event, after the '=' char - } - if (eventValues.startsWith(F(","))) { - // Need to reconstruct the event to get rid of calls like these: - // myevent=,1,2 - eventValues = eventValues.substring(1); - } - // Now reconstruct the complete event - // Without event values: "myEvent" (no '=' char) - // With event values: "myEvent=1,2,3" - - // Re-using the 'cmd' String as that has pre-allocated memory which is - // known to be large enough to hold the entire event. - cmd = eventName; - if (eventValues.length() > 0) { - // Only append an = if there are eventvalues. - cmd += '='; - cmd += eventValues; - } - } - // Check for duplicates, as sometimes a node may have multiple subscriptions to the same topic. - // Then it may add several of the same events in a burst. - eventQueue.addMove(std::move(cmd), true); - } - } else { - ExecuteCommand_all({EventValueSource::Enum::VALUE_SOURCE_MQTT, std::move(cmd)}, true); - } - } - return validTopic; -} - -#endif // ifdef USES_C005 +#include "src/Helpers/_CPlugin_Helper.h" +#ifdef USES_C005 + +# include "src/Commands/ExecuteCommand.h" +# include "src/Globals/EventQueue.h" +# include "src/Helpers/PeriodicalActions.h" +# include "src/Helpers/StringParser.h" +# include "_Plugin_Helper.h" + +// ####################################################################################################### +// ################### Controller Plugin 005: Home Assistant (openHAB) MQTT ############################## +// ####################################################################################################### + +# define CPLUGIN_005 +# define CPLUGIN_ID_005 5 +# define CPLUGIN_NAME_005 "Home Assistant (openHAB) MQTT" + +String CPlugin_005_pubname; +bool CPlugin_005_mqtt_retainFlag = false; + +bool C005_parse_command(struct EventStruct *event); + +bool CPlugin_005(CPlugin::Function function, struct EventStruct *event, String& string) +{ + bool success = false; + + switch (function) + { + case CPlugin::Function::CPLUGIN_PROTOCOL_ADD: + { + ProtocolStruct& proto = getProtocolStruct(event->idx); // = CPLUGIN_ID_005; + proto.usesMQTT = true; + proto.usesTemplate = true; + proto.usesAccount = true; + proto.usesPassword = true; + proto.usesExtCreds = true; + proto.defaultPort = 1883; + proto.usesID = false; + break; + } + + case CPlugin::Function::CPLUGIN_GET_DEVICENAME: + { + string = F(CPLUGIN_NAME_005); + break; + } + + case CPlugin::Function::CPLUGIN_INIT: + { + success = init_mqtt_delay_queue(event->ControllerIndex, CPlugin_005_pubname, CPlugin_005_mqtt_retainFlag); + break; + } + + case CPlugin::Function::CPLUGIN_EXIT: + { + exit_mqtt_delay_queue(); + break; + } + + case CPlugin::Function::CPLUGIN_PROTOCOL_TEMPLATE: + { + event->String1 = F("%sysname%/#"); + event->String2 = F("%sysname%/%tskname%/%valname%"); + break; + } + + case CPlugin::Function::CPLUGIN_PROTOCOL_RECV: + { + controllerIndex_t ControllerID = findFirstEnabledControllerWithId(CPLUGIN_ID_005); + + if (validControllerIndex(ControllerID)) { + C005_parse_command(event); + } + break; + } + + case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: + { + if (MQTT_queueFull(event->ControllerIndex)) { + break; + } + + + String pubname = CPlugin_005_pubname; + const bool contains_valname = pubname.indexOf(F("%valname%")) != -1; + bool mqtt_retainFlag = CPlugin_005_mqtt_retainFlag; + + parseControllerVariables(pubname, event, false); + + uint8_t valueCount = getValueCountForTask(event->TaskIndex); + + for (uint8_t x = 0; x < valueCount; x++) + { + // MFD: skip publishing for values with empty labels (removes unnecessary publishing of unwanted values) + if (Cache.getTaskDeviceValueName(event->TaskIndex, x).isEmpty()) { + continue; // we skip values with empty labels + } + + String tmppubname = pubname; + + if (contains_valname) { + parseSingleControllerVariable(tmppubname, event, x, false); + } + String value; + + if (event->sensorType == Sensor_VType::SENSOR_TYPE_STRING) { +# ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + value = event->String2.substring(0, 20); // For the log + } +# endif + } else { + value = formatUserVarNoCheck(event, x); + } +# ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLogMove(LOG_LEVEL_DEBUG, + strformat( + F("MQTT : %s %s"), + tmppubname.c_str(), + value.c_str())); + } +# endif // ifndef BUILD_NO_DEBUG + + // Small optimization so we don't try to copy potentially large strings + if (event->sensorType == Sensor_VType::SENSOR_TYPE_STRING) { + if (MQTTpublish(event->ControllerIndex, event->TaskIndex, tmppubname.c_str(), event->String2.c_str(), mqtt_retainFlag)) { + success = true; + } + } else { + // Publish using move operator, thus tmppubname and value are empty after this call + if (MQTTpublish(event->ControllerIndex, event->TaskIndex, std::move(tmppubname), std::move(value), mqtt_retainFlag)) { + success = true; + } + } + } + break; + } + + case CPlugin::Function::CPLUGIN_FLUSH: + { + processMQTTdelayQueue(); + delay(0); + break; + } + + default: + break; + } + + return success; +} + +bool C005_parse_command(struct EventStruct *event) { + // FIXME TD-er: Command is not parsed for template arguments. + + // Topic : event->String1 + // Message: event->String2 + String cmd; + bool validTopic = false; + const int lastindex = event->String1.lastIndexOf('/'); + const String lastPartTopic = event->String1.substring(lastindex + 1); + const bool has_cmd_arg_index = event->String1.lastIndexOf(F("cmd_arg")) != -1; + + if (equals(lastPartTopic, F("cmd"))) { + // Example: + // Topic: ESP_Easy/Bathroom_pir_env/cmd + // Message: gpio,14,0 + // Full command: gpio,14,0 + + move_special(cmd, String(event->String2)); + + // SP_C005a: string= ;cmd=gpio,12,0 ;taskIndex=12 ;string1=ESPT12/cmd ;string2=gpio,12,0 + validTopic = true; + } else if (has_cmd_arg_index) { + // Example: + // Topic: ESP_Easy/Bathroom_pir_env/cmd_arg1/GPIO/0 + // Message: 14 + // Full command: gpio,14,0 + + uint8_t topic_index = 1; + String topic_folder = parseStringKeepCase(event->String1, topic_index, '/'); + + while (!topic_folder.startsWith(F("cmd_arg")) && !topic_folder.isEmpty()) { + ++topic_index; + topic_folder = parseStringKeepCase(event->String1, topic_index, '/'); + } + + if (!topic_folder.isEmpty()) { + int32_t cmd_arg_nr = -1; + + if (validIntFromString(topic_folder.substring(7), cmd_arg_nr)) { + int constructed_cmd_arg_nr = 0; + ++topic_index; + topic_folder = parseStringKeepCase(event->String1, topic_index, '/'); + bool msg_added = false; + + while (!topic_folder.isEmpty()) { + if (constructed_cmd_arg_nr != 0) { + cmd += ','; + } + + if (constructed_cmd_arg_nr == cmd_arg_nr) { + cmd += event->String2; + msg_added = true; + } else { + cmd += topic_folder; + ++topic_index; + topic_folder = parseStringKeepCase(event->String1, topic_index, '/'); + } + ++constructed_cmd_arg_nr; + } + + if (!msg_added) { + cmd += ','; + cmd += event->String2; + } + + // addLog(LOG_LEVEL_INFO, concat(F("MQTT cmd: "), cmd)); + + validTopic = true; + } + } + } else { + // Example: + // Topic: ESP_Easy/Bathroom_pir_env/GPIO/14 + // Message: 0 or 1 + // Full command: gpio,14,0 + if (lastindex > 0) { + // Topic has at least one separator + int32_t lastPartTopic_int; + float value_f; + + if (validFloatFromString(event->String2, value_f) && + validIntFromString(lastPartTopic, lastPartTopic_int)) { + const int prevLastindex = event->String1.lastIndexOf('/', lastindex - 1); + + cmd = strformat( + F("%s,%d,%s"), + event->String1.substring(prevLastindex + 1, lastindex).c_str(), + lastPartTopic_int, + event->String2.c_str() // Just use the original format + ); + validTopic = true; + } + } + } + + if (validTopic) { + // in case of event, store to buffer and return... + const String command = parseString(cmd, 1); + + if ((equals(command, F("event"))) || (equals(command, F("asyncevent")))) { + if (Settings.UseRules) { + // Need to sanitize the event a bit to allow for sending event values as MQTT messages. + // For example: + // Publish topic: espeasy_node/cmd_arg2/event/myevent/2 + // Message: 1 + // Actual event: myevent=1,2 + + // Strip out the "event" or "asyncevent" part, leaving the actual event string + cmd = parseStringToEndKeepCase(cmd, 2); + + { + // Get the first part upto a parameter separator + // Example: "myEvent,1,2,3", which needs to be converted to "myEvent=1,2,3" + // N.B. This may contain the first eventvalue too + // e.g. "myEvent=1,2,3" => "myEvent=1" + String eventName = parseStringKeepCase(cmd, 1); + String eventValues = parseStringToEndKeepCase(cmd, 2); + const int equal_pos = eventName.indexOf('='); + + if (equal_pos != -1) { + // We found an '=' character, so the actual event name is everything before that char. + eventName = cmd.substring(0, equal_pos); + eventValues = cmd.substring(equal_pos + 1); // Rest of the event, after the '=' char + } + + if (eventValues.startsWith(F(","))) { + // Need to reconstruct the event to get rid of calls like these: + // myevent=,1,2 + eventValues = eventValues.substring(1); + } + + // Now reconstruct the complete event + // Without event values: "myEvent" (no '=' char) + // With event values: "myEvent=1,2,3" + + // Re-using the 'cmd' String as that has pre-allocated memory which is + // known to be large enough to hold the entire event. + cmd = eventName; + + if (eventValues.length() > 0) { + // Only append an = if there are eventvalues. + cmd += '='; + cmd += eventValues; + } + } + + // Check for duplicates, as sometimes a node may have multiple subscriptions to the same topic. + // Then it may add several of the same events in a burst. + eventQueue.addMove(std::move(cmd), true); + } + } else { + ExecuteCommand_all({ EventValueSource::Enum::VALUE_SOURCE_MQTT, std::move(cmd) }, true); + } + } + return validTopic; +} + +#endif // ifdef USES_C005 diff --git a/src/_C006.cpp b/src/_C006.cpp index 601fcaf498..e514e5cb1e 100644 --- a/src/_C006.cpp +++ b/src/_C006.cpp @@ -1,154 +1,157 @@ -#include "src/Helpers/_CPlugin_Helper.h" -#ifdef USES_C006 - -// ####################################################################################################### -// ########################### Controller Plugin 006: PiDome MQTT ######################################## -// ####################################################################################################### - -# include "src/Commands/ExecuteCommand.h" -# include "src/ESPEasyCore/Controller.h" -# include "src/Globals/Settings.h" -# include "src/Helpers/Network.h" -# include "src/Helpers/PeriodicalActions.h" -# include "_Plugin_Helper.h" - -# define CPLUGIN_006 -# define CPLUGIN_ID_006 6 -# define CPLUGIN_NAME_006 "PiDome MQTT" - -String CPlugin_006_pubname; -bool CPlugin_006_mqtt_retainFlag = false; - - -bool CPlugin_006(CPlugin::Function function, struct EventStruct *event, String& string) -{ - bool success = false; - - switch (function) - { - case CPlugin::Function::CPLUGIN_PROTOCOL_ADD: - { - ProtocolStruct& proto = getProtocolStruct(event->idx); // = CPLUGIN_ID_006; - proto.usesMQTT = true; - proto.usesTemplate = true; - proto.usesAccount = false; - proto.usesPassword = false; - proto.usesExtCreds = true; - proto.defaultPort = 1883; - proto.usesID = false; - break; - } - - case CPlugin::Function::CPLUGIN_GET_DEVICENAME: - { - string = F(CPLUGIN_NAME_006); - break; - } - - case CPlugin::Function::CPLUGIN_INIT: - { - success = init_mqtt_delay_queue(event->ControllerIndex, CPlugin_006_pubname, CPlugin_006_mqtt_retainFlag); - break; - } - - case CPlugin::Function::CPLUGIN_EXIT: - { - exit_mqtt_delay_queue(); - break; - } - - case CPlugin::Function::CPLUGIN_PROTOCOL_TEMPLATE: - { - event->String1 = F("/Home/#"); - event->String2 = F("/hooks/devices/%id%/SensorData/%valname%"); - break; - } - - case CPlugin::Function::CPLUGIN_PROTOCOL_RECV: - { - // topic structure /Home/Floor/Location/device//gpio/16 - // Split topic into array - String tmpTopic = event->String1.substring(1); - String topicSplit[10]; - int SlashIndex = tmpTopic.indexOf('/'); - uint8_t count = 0; - - while (SlashIndex > 0 && count < 10 - 1) - { - topicSplit[count] = tmpTopic.substring(0, SlashIndex); - tmpTopic = tmpTopic.substring(SlashIndex + 1); - SlashIndex = tmpTopic.indexOf('/'); - count++; - } - topicSplit[count] = tmpTopic; - - String name = topicSplit[4]; - - if (name.equals(Settings.getName())) - { - String cmd = topicSplit[5]; - cmd += ','; - cmd += topicSplit[6].toInt(); // Par1 - cmd += ','; - - if ((event->String2.equalsIgnoreCase(F("false"))) || - (event->String2.equalsIgnoreCase(F("true")))) - { - cmd += (event->String2.equalsIgnoreCase(F("true"))) ? '1' : '0'; // Par2 - } - else - { - cmd += event->String2; // Par2 - } - ExecuteCommand_all({EventValueSource::Enum::VALUE_SOURCE_MQTT, std::move(cmd)}, true); - } - break; - } - - case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: - { - if (MQTT_queueFull(event->ControllerIndex)) { - break; - } - - String pubname = CPlugin_006_pubname; - bool mqtt_retainFlag = CPlugin_006_mqtt_retainFlag; - - statusLED(true); - - //LoadTaskSettings(event->TaskIndex); // FIXME TD-er: This can probably be removed - parseControllerVariables(pubname, event, false); - - uint8_t valueCount = getValueCountForTask(event->TaskIndex); - - for (uint8_t x = 0; x < valueCount; x++) - { - String tmppubname = pubname; - parseSingleControllerVariable(tmppubname, event, x, false); - - // Small optimization so we don't try to copy potentially large strings - if (event->sensorType == Sensor_VType::SENSOR_TYPE_STRING) { - if (MQTTpublish(event->ControllerIndex, event->TaskIndex, tmppubname.c_str(), event->String2.c_str(), mqtt_retainFlag)) - success = true; - } else { - if (MQTTpublish(event->ControllerIndex, event->TaskIndex, std::move(tmppubname), formatUserVarNoCheck(event, x), mqtt_retainFlag)) - success = true; - } - } - break; - } - - case CPlugin::Function::CPLUGIN_FLUSH: - { - processMQTTdelayQueue(); - delay(0); - break; - } - - default: - break; - } - return success; -} - -#endif // ifdef USES_C006 +#include "src/Helpers/_CPlugin_Helper.h" +#ifdef USES_C006 + +// ####################################################################################################### +// ########################### Controller Plugin 006: PiDome MQTT ######################################## +// ####################################################################################################### + +# include "src/Commands/ExecuteCommand.h" +# include "src/ESPEasyCore/Controller.h" +# include "src/Globals/Settings.h" +# include "src/Helpers/Network.h" +# include "src/Helpers/PeriodicalActions.h" +# include "_Plugin_Helper.h" + +# define CPLUGIN_006 +# define CPLUGIN_ID_006 6 +# define CPLUGIN_NAME_006 "PiDome MQTT" + +String CPlugin_006_pubname; +bool CPlugin_006_mqtt_retainFlag = false; + + +bool CPlugin_006(CPlugin::Function function, struct EventStruct *event, String& string) +{ + bool success = false; + + switch (function) + { + case CPlugin::Function::CPLUGIN_PROTOCOL_ADD: + { + ProtocolStruct& proto = getProtocolStruct(event->idx); // = CPLUGIN_ID_006; + proto.usesMQTT = true; + proto.usesTemplate = true; + proto.usesAccount = false; + proto.usesPassword = false; + proto.usesExtCreds = true; + proto.defaultPort = 1883; + proto.usesID = false; + break; + } + + case CPlugin::Function::CPLUGIN_GET_DEVICENAME: + { + string = F(CPLUGIN_NAME_006); + break; + } + + case CPlugin::Function::CPLUGIN_INIT: + { + success = init_mqtt_delay_queue(event->ControllerIndex, CPlugin_006_pubname, CPlugin_006_mqtt_retainFlag); + break; + } + + case CPlugin::Function::CPLUGIN_EXIT: + { + exit_mqtt_delay_queue(); + break; + } + + case CPlugin::Function::CPLUGIN_PROTOCOL_TEMPLATE: + { + event->String1 = F("/Home/#"); + event->String2 = F("/hooks/devices/%id%/SensorData/%valname%"); + break; + } + + case CPlugin::Function::CPLUGIN_PROTOCOL_RECV: + { + // topic structure /Home/Floor/Location/device//gpio/16 + // Split topic into array + String tmpTopic = event->String1.substring(1); + String topicSplit[10]; + int SlashIndex = tmpTopic.indexOf('/'); + uint8_t count = 0; + + while (SlashIndex > 0 && count < 10 - 1) + { + topicSplit[count] = tmpTopic.substring(0, SlashIndex); + tmpTopic = tmpTopic.substring(SlashIndex + 1); + SlashIndex = tmpTopic.indexOf('/'); + count++; + } + topicSplit[count] = tmpTopic; + + String name = topicSplit[4]; + + if (name.equals(Settings.getName())) + { + String cmd = topicSplit[5]; + cmd += ','; + cmd += topicSplit[6].toInt(); // Par1 + cmd += ','; + + if ((event->String2.equalsIgnoreCase(F("false"))) || + (event->String2.equalsIgnoreCase(F("true")))) + { + cmd += (event->String2.equalsIgnoreCase(F("true"))) ? '1' : '0'; // Par2 + } + else + { + cmd += event->String2; // Par2 + } + ExecuteCommand_all({EventValueSource::Enum::VALUE_SOURCE_MQTT, std::move(cmd)}, true); + } + break; + } + + case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: + { + if (MQTT_queueFull(event->ControllerIndex)) { + break; + } + + String pubname = CPlugin_006_pubname; + const bool contains_valname = pubname.indexOf(F("%valname%")) != -1; + bool mqtt_retainFlag = CPlugin_006_mqtt_retainFlag; + + statusLED(true); + + //LoadTaskSettings(event->TaskIndex); // FIXME TD-er: This can probably be removed + parseControllerVariables(pubname, event, false); + + const uint8_t valueCount = getValueCountForTask(event->TaskIndex); + + for (uint8_t x = 0; x < valueCount; x++) + { + String tmppubname = pubname; + if (contains_valname) { + parseSingleControllerVariable(tmppubname, event, x, false); + } + + // Small optimization so we don't try to copy potentially large strings + if (event->sensorType == Sensor_VType::SENSOR_TYPE_STRING) { + if (MQTTpublish(event->ControllerIndex, event->TaskIndex, tmppubname.c_str(), event->String2.c_str(), mqtt_retainFlag)) + success = true; + } else { + if (MQTTpublish(event->ControllerIndex, event->TaskIndex, std::move(tmppubname), formatUserVarNoCheck(event, x), mqtt_retainFlag)) + success = true; + } + } + break; + } + + case CPlugin::Function::CPLUGIN_FLUSH: + { + processMQTTdelayQueue(); + delay(0); + break; + } + + default: + break; + } + return success; +} + +#endif // ifdef USES_C006 diff --git a/src/_C007.cpp b/src/_C007.cpp index dcab924655..8d2846cb96 100644 --- a/src/_C007.cpp +++ b/src/_C007.cpp @@ -90,7 +90,7 @@ bool CPlugin_007(CPlugin::Function function, struct EventStruct *event, String& // Uncrustify may change this into multi line, which will result in failed builds // *INDENT-OFF* -bool do_process_c007_delay_queue(int controller_number, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { +bool do_process_c007_delay_queue(cpluginID_t cpluginID, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { const C007_queue_element& element = static_cast(element_base); // *INDENT-ON* String url = F("/emoncms/input/post.json?node="); @@ -117,7 +117,7 @@ bool do_process_c007_delay_queue(int controller_number, const Queue_element_base int httpCode = -1; send_via_http( - controller_number, + cpluginID, ControllerSettings, element._controller_idx, url, diff --git a/src/_C008.cpp b/src/_C008.cpp index f9a3129882..3c21e97436 100644 --- a/src/_C008.cpp +++ b/src/_C008.cpp @@ -76,6 +76,7 @@ bool CPlugin_008(CPlugin::Function function, struct EventStruct *event, String& LoadControllerSettings(event->ControllerIndex, *ControllerSettings); pubname = ControllerSettings->Publish; } + const bool contains_valname = pubname.indexOf(F("%valname%")) != -1; uint8_t valueCount = getValueCountForTask(event->TaskIndex); std::unique_ptr element(new (std::nothrow) C008_queue_element(event, valueCount)); @@ -101,7 +102,9 @@ bool CPlugin_008(CPlugin::Function function, struct EventStruct *event, String& String txt; txt += '/'; txt += pubname; - parseSingleControllerVariable(txt, event, x, true); + if (contains_valname) { + parseSingleControllerVariable(txt, event, x, true); + } # ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { @@ -145,7 +148,7 @@ bool CPlugin_008(CPlugin::Function function, struct EventStruct *event, String& // Uncrustify may change this into multi line, which will result in failed builds // *INDENT-OFF* -bool do_process_c008_delay_queue(int controller_number, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { +bool do_process_c008_delay_queue(cpluginID_t cpluginID, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { const C008_queue_element& element = static_cast(element_base); // *INDENT-ON* while (element.txt[element.valuesSent].isEmpty()) { @@ -158,7 +161,7 @@ bool do_process_c008_delay_queue(int controller_number, const Queue_element_base int httpCode = -1; send_via_http( - controller_number, + cpluginID, ControllerSettings, element._controller_idx, element.txt[element.valuesSent], diff --git a/src/_C009.cpp b/src/_C009.cpp index 47f7a4cadb..bcca8adf54 100644 --- a/src/_C009.cpp +++ b/src/_C009.cpp @@ -106,7 +106,7 @@ bool CPlugin_009(CPlugin::Function function, struct EventStruct *event, String& // Uncrustify may change this into multi line, which will result in failed builds // *INDENT-OFF* -bool do_process_c009_delay_queue(int controller_number, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { +bool do_process_c009_delay_queue(cpluginID_t cpluginID, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { const C009_queue_element& element = static_cast(element_base); // *INDENT-ON* String jsonString; @@ -178,7 +178,7 @@ bool do_process_c009_delay_queue(int controller_number, const Queue_element_base { jsonString += to_json_object_value(F("deviceName"), getTaskDeviceName(element._taskIndex)); jsonString += ','; - jsonString += to_json_object_value(F("valueName"), getTaskValueName(element._taskIndex, x)); + jsonString += to_json_object_value(F("valueName"), Cache.getTaskDeviceValueName(element._taskIndex, x)); jsonString += ','; jsonString += to_json_object_value(F("type"), static_cast(element.sensorType)); jsonString += ','; @@ -203,7 +203,7 @@ bool do_process_c009_delay_queue(int controller_number, const Queue_element_base int httpCode = -1; send_via_http( - controller_number, + cpluginID, ControllerSettings, element._controller_idx, F("/ESPEasy"), diff --git a/src/_C010.cpp b/src/_C010.cpp index c4c1904907..ff8921c1c5 100644 --- a/src/_C010.cpp +++ b/src/_C010.cpp @@ -83,8 +83,8 @@ bool CPlugin_010(CPlugin::Function function, struct EventStruct *event, String& LoadControllerSettings(event->ControllerIndex, *ControllerSettings); pubname = ControllerSettings->Publish; } - parseControllerVariables(pubname, event, false); + const bool contains_valname = pubname.indexOf(F("%valname%")) != -1; for (uint8_t x = 0; x < valueCount; x++) { @@ -94,7 +94,9 @@ bool CPlugin_010(CPlugin::Function function, struct EventStruct *event, String& if (isvalid) { String txt; txt = pubname; - parseSingleControllerVariable(txt, event, x, false); + if (contains_valname) { + parseSingleControllerVariable(txt, event, x, false); + } txt.replace(F("%value%"), formattedValue); move_special(element->txt[x], std::move(txt)); #ifndef BUILD_NO_DEBUG @@ -129,7 +131,7 @@ bool CPlugin_010(CPlugin::Function function, struct EventStruct *event, String& // Uncrustify may change this into multi line, which will result in failed builds // *INDENT-OFF* -bool do_process_c010_delay_queue(int controller_number, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { +bool do_process_c010_delay_queue(cpluginID_t cpluginID, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { const C010_queue_element& element = static_cast(element_base); // *INDENT-ON* while (element.txt[element.valuesSent].isEmpty()) { @@ -143,7 +145,7 @@ bool do_process_c010_delay_queue(int controller_number, const Queue_element_base if (!beginWiFiUDP_randomPort(C010_portUDP)) { return false; } - if (!try_connect_host(controller_number, C010_portUDP, ControllerSettings)) { + if (!try_connect_host(cpluginID, C010_portUDP, ControllerSettings)) { return false; } diff --git a/src/_C011.cpp b/src/_C011.cpp index 8c091d5ea8..b4d1aab50b 100644 --- a/src/_C011.cpp +++ b/src/_C011.cpp @@ -194,7 +194,7 @@ bool CPlugin_011(CPlugin::Function function, struct EventStruct *event, String& // Uncrustify may change this into multi line, which will result in failed builds // *INDENT-OFF* -bool do_process_c011_delay_queue(int controller_number, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { +bool do_process_c011_delay_queue(cpluginID_t cpluginID, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { const C011_queue_element& element = static_cast(element_base); // *INDENT-ON* @@ -203,7 +203,7 @@ bool do_process_c011_delay_queue(int controller_number, const Queue_element_base int httpCode = -1; send_via_http( - controller_number, + cpluginID, ControllerSettings, element._controller_idx, element.uri, diff --git a/src/_C012.cpp b/src/_C012.cpp index 6eccb2aee1..94d639cf60 100644 --- a/src/_C012.cpp +++ b/src/_C012.cpp @@ -105,7 +105,7 @@ bool CPlugin_012(CPlugin::Function function, struct EventStruct *event, String& // Uncrustify may change this into multi line, which will result in failed builds // *INDENT-OFF* -bool do_process_c012_delay_queue(int controller_number, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { +bool do_process_c012_delay_queue(cpluginID_t cpluginID, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { const C012_queue_element& element = static_cast(element_base); // *INDENT-ON* while (element.txt[element.valuesSent].isEmpty()) { diff --git a/src/_C013.cpp b/src/_C013.cpp index c9855e882c..56243a0819 100644 --- a/src/_C013.cpp +++ b/src/_C013.cpp @@ -1,369 +1,424 @@ -#include "src/Helpers/_CPlugin_Helper.h" -#ifdef USES_C013 - -# if FEATURE_ESPEASY_P2P == 0 - # error "Controller C013 ESPEasy P2P requires the FEATURE_ESPEASY_P2P enabled" -# endif // if FEATURE_ESPEASY_P2P == 0 - - -# include "src/Globals/Nodes.h" -# include "src/DataStructs/C013_p2p_dataStructs.h" -# include "src/ESPEasyCore/ESPEasyRules.h" -# include "src/Helpers/Misc.h" -# include "src/Helpers/Network.h" - -// ####################################################################################################### -// ########################### Controller Plugin 013: ESPEasy P2P network ################################ -// ####################################################################################################### - -# define CPLUGIN_013 -# define CPLUGIN_ID_013 13 -# define CPLUGIN_NAME_013 "ESPEasy P2P Networking" - - -// Forward declarations -void C013_SendUDPTaskInfo(uint8_t destUnit, - uint8_t sourceTaskIndex, - uint8_t destTaskIndex); -void C013_SendUDPTaskData(struct EventStruct *event, - uint8_t destUnit, - uint8_t destTaskIndex); -void C013_sendUDP(uint8_t unit, - const uint8_t *data, - uint8_t size); -void C013_Receive(struct EventStruct *event); - - -bool CPlugin_013(CPlugin::Function function, struct EventStruct *event, String& string) -{ - bool success = false; - - switch (function) - { - case CPlugin::Function::CPLUGIN_PROTOCOL_ADD: - { - ProtocolStruct& proto = getProtocolStruct(event->idx); // = CPLUGIN_ID_013; - proto.usesMQTT = false; - proto.usesTemplate = false; - proto.usesAccount = false; - proto.usesPassword = false; - proto.usesHost = false; - proto.defaultPort = 8266; - proto.usesID = false; - proto.Custom = true; - break; - } - - case CPlugin::Function::CPLUGIN_GET_DEVICENAME: - { - string = F(CPLUGIN_NAME_013); - break; - } - - case CPlugin::Function::CPLUGIN_TASK_CHANGE_NOTIFICATION: - { - C013_SendUDPTaskInfo(0, event->TaskIndex, event->TaskIndex); - break; - } - - case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: - { - C013_SendUDPTaskData(event, 0, event->TaskIndex); - success = true; - break; - } - - case CPlugin::Function::CPLUGIN_UDP_IN: - { - C013_Receive(event); - break; - } - - case CPlugin::Function::CPLUGIN_WEBFORM_SHOW_HOST_CONFIG: - { - string = F("-"); - break; - } - - /* - case CPlugin::Function::CPLUGIN_FLUSH: - { - process_c013_delay_queue(event->ControllerIndex); - delay(0); - break; - } - */ - - default: - break; - } - return success; -} - -// ******************************************************************************** -// Generic UDP message -// ******************************************************************************** -void C013_SendUDPTaskInfo(uint8_t destUnit, uint8_t sourceTaskIndex, uint8_t destTaskIndex) -{ - if (!NetworkConnected(10)) { - return; - } - - if (!validTaskIndex(sourceTaskIndex) || !validTaskIndex(destTaskIndex)) { - return; - } - pluginID_t pluginID = Settings.getPluginID_for_task(sourceTaskIndex); - - if (!validPluginID_fullcheck(pluginID)) { - return; - } - - struct C013_SensorInfoStruct infoReply; - - infoReply.sourceUnit = Settings.Unit; - infoReply.sourceTaskIndex = sourceTaskIndex; - infoReply.destTaskIndex = destTaskIndex; - infoReply.deviceNumber = pluginID; - safe_strncpy(infoReply.taskName, getTaskDeviceName(infoReply.sourceTaskIndex), sizeof(infoReply.taskName)); - - for (uint8_t x = 0; x < VARS_PER_TASK; x++) { - safe_strncpy(infoReply.ValueNames[x], getTaskValueName(infoReply.sourceTaskIndex, x), sizeof(infoReply.ValueNames[x])); - } - - if (destUnit != 0) - { - infoReply.destUnit = destUnit; - C013_sendUDP(destUnit, reinterpret_cast(&infoReply), sizeof(C013_SensorInfoStruct)); - } else { - for (auto it = Nodes.begin(); it != Nodes.end(); ++it) { - if (it->first != Settings.Unit) { - infoReply.destUnit = it->first; - C013_sendUDP(it->first, reinterpret_cast(&infoReply), sizeof(C013_SensorInfoStruct)); - } - } - } -} - -void C013_SendUDPTaskData(struct EventStruct *event, uint8_t destUnit, uint8_t destTaskIndex) -{ - if (!NetworkConnected(10)) { - return; - } - struct C013_SensorDataStruct dataReply; - - dataReply.sourceUnit = Settings.Unit; - dataReply.sourceTaskIndex = event->TaskIndex; - dataReply.destTaskIndex = destTaskIndex; - dataReply.deviceNumber = Settings.getPluginID_for_task(event->TaskIndex); - - // FIXME TD-er: We should check for sensorType and pluginID on both sides. - // For example sending different sensor type data from one dummy to another is probably not going to work well - dataReply.sensorType = event->getSensorType(); - - const TaskValues_Data_t *taskValues = UserVar.getRawTaskValues_Data(event->TaskIndex); - - if (taskValues != nullptr) { - for (taskVarIndex_t x = 0; x < VARS_PER_TASK; ++x) - { - dataReply.values.copyValue(*taskValues, x, dataReply.sensorType); - } - } - - if (destUnit != 0) - { - dataReply.destUnit = destUnit; - C013_sendUDP(destUnit, reinterpret_cast(&dataReply), sizeof(C013_SensorDataStruct)); - } else { - for (auto it = Nodes.begin(); it != Nodes.end(); ++it) { - if (it->first != Settings.Unit) { - dataReply.destUnit = it->first; - C013_sendUDP(it->first, reinterpret_cast(&dataReply), sizeof(C013_SensorDataStruct)); - } - } - } -} - -/*********************************************************************************************\ - Send UDP message (unit 255=broadcast) -\*********************************************************************************************/ -void C013_sendUDP(uint8_t unit, const uint8_t *data, uint8_t size) -{ - if (!NetworkConnected(10)) { - return; - } - - const IPAddress remoteNodeIP = getIPAddressForUnit(unit); - - -# ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) { - addLogMove(LOG_LEVEL_DEBUG_MORE, strformat( - F("C013 : Send UDP message to %d (%s)"), - unit, - remoteNodeIP.toString().c_str())); - } -# endif // ifndef BUILD_NO_DEBUG - - statusLED(true); - - WiFiUDP C013_portUDP; - - if (!beginWiFiUDP_randomPort(C013_portUDP)) { return; } - - FeedSW_watchdog(); - - if (C013_portUDP.beginPacket(remoteNodeIP, Settings.UDPPort) == 0) { return; } - C013_portUDP.write(data, size); - C013_portUDP.endPacket(); - C013_portUDP.stop(); - FeedSW_watchdog(); - delay(0); -} - -void C013_Receive(struct EventStruct *event) { - if (event->Par2 < 6) { return; } -# ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) { - if ((event->Data != nullptr) && - (event->Data[1] > 1) && (event->Data[1] < 6)) - { - String log = (F("C013 : msg ")); - - for (uint8_t x = 1; x < 6; x++) - { - log += ' '; - log += static_cast(event->Data[x]); - } - addLogMove(LOG_LEVEL_DEBUG_MORE, log); - } - } -# endif // ifndef BUILD_NO_DEBUG - - switch (event->Data[1]) { - case 2: // sensor info pull request - { - // SendUDPTaskInfo(packetBuffer[2], packetBuffer[5], packetBuffer[4]); - break; - } - - case 3: // sensor info - { - struct C013_SensorInfoStruct infoReply; - int structSize = sizeof(C013_SensorInfoStruct); - - if (event->Par2 < structSize) { structSize = event->Par2; } - - memcpy(reinterpret_cast(&infoReply), event->Data, structSize); - - if (infoReply.isValid()) { - // to prevent flash wear out (bugs in communication?) we can only write to an empty task - // so it will write only once and has to be cleared manually through webgui - // Also check the receiving end does support the plugin ID. - if (!validPluginID_fullcheck(Settings.getPluginID_for_task(infoReply.destTaskIndex)) && - supportedPluginID(infoReply.deviceNumber)) - { - taskClear(infoReply.destTaskIndex, false); - Settings.TaskDeviceNumber[infoReply.destTaskIndex] = infoReply.deviceNumber.value; - Settings.TaskDeviceDataFeed[infoReply.destTaskIndex] = infoReply.sourceUnit; // remote feed store unit nr sending the data - - constexpr pluginID_t DUMMY_PLUGIN_ID{33}; - if ((infoReply.deviceNumber == DUMMY_PLUGIN_ID) && (infoReply.sensorType != Sensor_VType::SENSOR_TYPE_NONE)) { - // Received a dummy device and the sensor type is actually set - Settings.TaskDevicePluginConfig[infoReply.destTaskIndex][0] = static_cast(infoReply.sensorType); - } - - for (controllerIndex_t x = 0; x < CONTROLLER_MAX; x++) { - Settings.TaskDeviceSendData[x][infoReply.destTaskIndex] = false; - } - safe_strncpy(ExtraTaskSettings.TaskDeviceName, infoReply.taskName, sizeof(infoReply.taskName)); - - for (uint8_t x = 0; x < VARS_PER_TASK; x++) { - safe_strncpy(ExtraTaskSettings.TaskDeviceValueNames[x], infoReply.ValueNames[x], sizeof(infoReply.ValueNames[x])); - } - ExtraTaskSettings.TaskIndex = infoReply.destTaskIndex; - SaveTaskSettings(infoReply.destTaskIndex); - SaveSettings(); - } - } - break; - } - - case 4: // sensor data pull request - { - // SendUDPTaskData(packetBuffer[2], packetBuffer[5], packetBuffer[4]); - break; - } - - case 5: // sensor data - { - struct C013_SensorDataStruct dataReply; - int structSize = sizeof(C013_SensorDataStruct); - - if (event->Par2 < structSize) { structSize = event->Par2; } - memcpy(reinterpret_cast(&dataReply), event->Data, structSize); - - // FIXME TD-er: We should check for sensorType and pluginID on both sides. - // For example sending different sensor type data from one dummy to another is probably not going to work well - if (dataReply.isValid()) { - // only if this task has a remote feed, update values - const uint8_t remoteFeed = Settings.TaskDeviceDataFeed[dataReply.destTaskIndex]; - - if ((remoteFeed != 0) && (remoteFeed == dataReply.sourceUnit)) - { - // deviceNumber and sensorType were not present before build 2023-05-05. (build NR 20460) - // See: https://github.com/letscontrolit/ESPEasy/commit/cf791527eeaf31ca98b07c45c1b64e2561a7b041#diff-86b42dd78398b103e272503f05f55ee0870ae5fb907d713c2505d63279bb0321 - // Thus should not be checked - // - // If the node is not present in the nodes list (e.g. it had not announced itself in the last 10 minutes or announcement was missed) - // Then we cannot be sure about its build. - bool mustMatch = false; - NodeStruct *sourceNode = Nodes.getNode(dataReply.sourceUnit); - if (sourceNode != nullptr) { - mustMatch = sourceNode->build >= 20460; - } - - if (mustMatch && !dataReply.matchesPluginID(Settings.getPluginID_for_task(dataReply.destTaskIndex))) { - // Mismatch in plugin ID from sending node - if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - String log = concat(F("P2P data : PluginID mismatch for task "), dataReply.destTaskIndex + 1); - log += concat(F(" from unit "), dataReply.sourceUnit); - log += concat(F(" remote: "), dataReply.deviceNumber.value); - log += concat(F(" local: "), Settings.getPluginID_for_task(dataReply.destTaskIndex).value); - addLogMove(LOG_LEVEL_ERROR, log); - } - } else { - struct EventStruct TempEvent(dataReply.destTaskIndex); - TempEvent.Source = EventValueSource::Enum::VALUE_SOURCE_UDP; - - const Sensor_VType sensorType = TempEvent.getSensorType(); - - if (!mustMatch || dataReply.matchesSensorType(sensorType)) { - TaskValues_Data_t *taskValues = UserVar.getRawTaskValues_Data(dataReply.destTaskIndex); - - if (taskValues != nullptr) { - for (taskVarIndex_t x = 0; x < VARS_PER_TASK; ++x) - { - taskValues->copyValue(dataReply.values, x, sensorType); - } - } - - SensorSendTask(&TempEvent); - } else { - // Mismatch in sensor types - if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - String log = concat(F("P2P data : SensorType mismatch for task "), dataReply.destTaskIndex + 1); - log += concat(F(" from unit "), dataReply.sourceUnit); - addLogMove(LOG_LEVEL_ERROR, log); - } - } - } - } - } - break; - } - } -} - -#endif // ifdef USES_C013 +#include "src/Helpers/_CPlugin_Helper.h" +#ifdef USES_C013 + +# if FEATURE_ESPEASY_P2P == 0 + # error "Controller C013 ESPEasy P2P requires the FEATURE_ESPEASY_P2P enabled" +# endif // if FEATURE_ESPEASY_P2P == 0 + + +# include "src/Globals/Nodes.h" +# include "src/DataStructs/C013_p2p_SensorDataStruct.h" +# include "src/DataStructs/C013_p2p_SensorInfoStruct.h" +# include "src/ESPEasyCore/ESPEasyRules.h" +# include "src/Helpers/Misc.h" +# include "src/Helpers/Network.h" + +// ####################################################################################################### +// ########################### Controller Plugin 013: ESPEasy P2P network ################################ +// ####################################################################################################### + +# define CPLUGIN_013 +# define CPLUGIN_ID_013 13 +# define CPLUGIN_NAME_013 "ESPEasy P2P Networking" + + +// Forward declarations +void C013_SendUDPTaskInfo(uint8_t destUnit, + uint8_t sourceTaskIndex, + uint8_t destTaskIndex); +void C013_SendUDPTaskData(struct EventStruct *event, + uint8_t destUnit, + uint8_t destTaskIndex); +void C013_sendUDP(uint8_t unit, + const uint8_t *data, + size_t size); +void C013_Receive(struct EventStruct *event); + + +bool CPlugin_013(CPlugin::Function function, struct EventStruct *event, String& string) +{ + bool success = false; + + switch (function) + { + case CPlugin::Function::CPLUGIN_PROTOCOL_ADD: + { + ProtocolStruct& proto = getProtocolStruct(event->idx); // = CPLUGIN_ID_013; + proto.usesMQTT = false; + proto.usesTemplate = false; + proto.usesAccount = false; + proto.usesPassword = false; + proto.usesHost = false; + proto.defaultPort = 8266; + proto.usesID = false; + proto.Custom = true; + break; + } + + case CPlugin::Function::CPLUGIN_GET_DEVICENAME: + { + string = F(CPLUGIN_NAME_013); + break; + } + + case CPlugin::Function::CPLUGIN_TASK_CHANGE_NOTIFICATION: + { + C013_SendUDPTaskInfo(0, event->TaskIndex, event->TaskIndex); + break; + } + + case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: + { + C013_SendUDPTaskData(event, 0, event->TaskIndex); + success = true; + break; + } + + case CPlugin::Function::CPLUGIN_UDP_IN: + { + C013_Receive(event); + break; + } + + case CPlugin::Function::CPLUGIN_WEBFORM_SHOW_HOST_CONFIG: + { + string = F("-"); + break; + } + + /* + case CPlugin::Function::CPLUGIN_FLUSH: + { + process_c013_delay_queue(event->ControllerIndex); + delay(0); + break; + } + */ + + default: + break; + } + return success; +} + +// ******************************************************************************** +// Generic UDP message +// ******************************************************************************** +void C013_SendUDPTaskInfo(uint8_t destUnit, uint8_t sourceTaskIndex, uint8_t destTaskIndex) +{ + if (!NetworkConnected(10)) { + return; + } + + if (!validTaskIndex(sourceTaskIndex) || !validTaskIndex(destTaskIndex)) { + return; + } + pluginID_t pluginID = Settings.getPluginID_for_task(sourceTaskIndex); + + if (!validPluginID_fullcheck(pluginID)) { + return; + } + + struct C013_SensorInfoStruct infoReply; + + infoReply.sourceUnit = Settings.Unit; + infoReply.sourceTaskIndex = sourceTaskIndex; + infoReply.destTaskIndex = destTaskIndex; + infoReply.deviceNumber = pluginID; + infoReply.destUnit = destUnit; + + if (destUnit == 0) + { + // Send to broadcast address + infoReply.destUnit = 255; + } + size_t sizeToSend{}; + + if (infoReply.prepareForSend(sizeToSend)) { + C013_sendUDP(infoReply.destUnit, reinterpret_cast(&infoReply), sizeToSend); + } +} + +void C013_SendUDPTaskData(struct EventStruct *event, uint8_t destUnit, uint8_t destTaskIndex) +{ + if (!NetworkConnected(10)) { + return; + } + struct C013_SensorDataStruct dataReply; + + dataReply.sourceUnit = Settings.Unit; + dataReply.sourceTaskIndex = event->TaskIndex; + dataReply.destTaskIndex = destTaskIndex; + dataReply.deviceNumber = Settings.getPluginID_for_task(event->TaskIndex); + + // FIXME TD-er: We should check for sensorType and pluginID on both sides. + // For example sending different sensor type data from one dummy to another is probably not going to work well + dataReply.sensorType = event->getSensorType(); + + const TaskValues_Data_t *taskValues = UserVar.getRawTaskValues_Data(event->TaskIndex); + + if (taskValues != nullptr) { + for (taskVarIndex_t x = 0; x < VARS_PER_TASK; ++x) + { + dataReply.values.copyValue(*taskValues, x, dataReply.sensorType); + } + } + dataReply.destUnit = destUnit; + + if (destUnit == 0) + { + // Send to broadcast address + dataReply.destUnit = 255; + } + dataReply.prepareForSend(); + C013_sendUDP(dataReply.destUnit, reinterpret_cast(&dataReply), sizeof(C013_SensorDataStruct)); +} + +/*********************************************************************************************\ + Send UDP message (unit 255=broadcast) +\*********************************************************************************************/ +void C013_sendUDP(uint8_t unit, const uint8_t *data, size_t size) +{ + START_TIMER + + if (!NetworkConnected(10)) { + return; + } + + const IPAddress remoteNodeIP = getIPAddressForUnit(unit); + + +# ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) { + addLogMove(LOG_LEVEL_DEBUG_MORE, strformat( + F("C013 : Send UDP message to %d (%s)"), + unit, + remoteNodeIP.toString().c_str())); + } +# endif // ifndef BUILD_NO_DEBUG + + statusLED(true); + + WiFiUDP C013_portUDP; + + if (!beginWiFiUDP_randomPort(C013_portUDP)) { + STOP_TIMER(C013_SEND_UDP_FAIL); + return; + } + + FeedSW_watchdog(); + + if (C013_portUDP.beginPacket(remoteNodeIP, Settings.UDPPort) == 0) { + STOP_TIMER(C013_SEND_UDP_FAIL); + return; + } + C013_portUDP.write(data, size); + C013_portUDP.endPacket(); + C013_portUDP.stop(); + FeedSW_watchdog(); + delay(0); + STOP_TIMER(C013_SEND_UDP); +} + +void C013_Receive(struct EventStruct *event) { + if (event->Par2 < 6) { return; } +# ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) { + if ((event->Data != nullptr) && + (event->Data[1] > 1) && (event->Data[1] < 6)) + { + String log = (F("C013 : msg ")); + + for (uint8_t x = 1; x < 6; x++) + { + log += ' '; + log += static_cast(event->Data[x]); + } + addLogMove(LOG_LEVEL_DEBUG_MORE, log); + } + } +# endif // ifndef BUILD_NO_DEBUG + + START_TIMER + + switch (event->Data[1]) { + case 2: // sensor info pull request + { + // SendUDPTaskInfo(packetBuffer[2], packetBuffer[5], packetBuffer[4]); + break; + } + + case 3: // sensor info + { + bool mustSave = false; + taskIndex_t taskIndex = INVALID_TASK_INDEX; + { + // Allocate this is a separate scope since C013_SensorInfoStruct is a HUGE object + // Should not be left allocated on the stack when calling PLUGIN_INIT and save, etc. + struct C013_SensorInfoStruct infoReply; + + if (infoReply.setData(event->Data, event->Par2)) { + // to prevent flash wear out (bugs in communication?) we can only write to an empty task + // so it will write only once and has to be cleared manually through webgui + // Also check the receiving end does support the plugin ID. + const pluginID_t currentPluginID = Settings.getPluginID_for_task(infoReply.destTaskIndex); + bool mustUpdateCurrentTask = false; + + if (currentPluginID == infoReply.deviceNumber) { + // Check to see if task already is set to receive from this host + if ((Settings.TaskDeviceDataFeed[infoReply.destTaskIndex] == infoReply.sourceUnit) && + Settings.TaskDeviceEnabled[infoReply.destTaskIndex]) { + mustUpdateCurrentTask = true; + } + } + + if ((mustUpdateCurrentTask || !validPluginID_fullcheck(currentPluginID)) && + supportedPluginID(infoReply.deviceNumber)) + { + taskClear(infoReply.destTaskIndex, false); + Settings.TaskDeviceNumber[infoReply.destTaskIndex] = infoReply.deviceNumber.value; + Settings.TaskDeviceDataFeed[infoReply.destTaskIndex] = infoReply.sourceUnit; // remote feed store unit nr sending the data + + if (mustUpdateCurrentTask) { + Settings.TaskDeviceEnabled[infoReply.destTaskIndex] = true; + } + + constexpr pluginID_t DUMMY_PLUGIN_ID{ 33 }; + + if ((infoReply.deviceNumber == DUMMY_PLUGIN_ID) && (infoReply.sensorType != Sensor_VType::SENSOR_TYPE_NONE)) { + // Received a dummy device and the sensor type is actually set + Settings.TaskDevicePluginConfig[infoReply.destTaskIndex][0] = static_cast(infoReply.sensorType); + } + + for (controllerIndex_t x = 0; x < CONTROLLER_MAX; x++) { + Settings.TaskDeviceSendData[x][infoReply.destTaskIndex] = false; + } + safe_strncpy(ExtraTaskSettings.TaskDeviceName, infoReply.taskName, sizeof(infoReply.taskName)); + + for (uint8_t x = 0; x < VARS_PER_TASK; x++) { + safe_strncpy(ExtraTaskSettings.TaskDeviceValueNames[x], infoReply.ValueNames[x], sizeof(infoReply.ValueNames[x])); + } + + if (infoReply.sourceNodeBuild >= 20871) { + ExtraTaskSettings.version = infoReply.ExtraTaskSettings_version; + + for (uint8_t x = 0; x < VARS_PER_TASK; x++) { +// safe_strncpy(ExtraTaskSettings.TaskDeviceFormula[x], infoReply.TaskDeviceFormula[x], sizeof(infoReply.TaskDeviceFormula[x])); + ExtraTaskSettings.TaskDeviceValueDecimals[x] = infoReply.TaskDeviceValueDecimals[x]; + ExtraTaskSettings.TaskDeviceMinValue[x] = infoReply.TaskDeviceMinValue[x]; + ExtraTaskSettings.TaskDeviceMaxValue[x] = infoReply.TaskDeviceMaxValue[x]; + ExtraTaskSettings.TaskDeviceErrorValue[x] = infoReply.TaskDeviceErrorValue[x]; + ExtraTaskSettings.VariousBits[x] = infoReply.VariousBits[x]; + } + + for (uint8_t x = 0; x < PLUGIN_CONFIGVAR_MAX; ++x) { + Settings.TaskDevicePluginConfig[infoReply.destTaskIndex][x] = infoReply.TaskDevicePluginConfig[x]; + } + } + + ExtraTaskSettings.TaskIndex = infoReply.destTaskIndex; + taskIndex = infoReply.destTaskIndex; + mustSave = true; + } + } + } + + if (mustSave) { + SaveTaskSettings(taskIndex); + SaveSettings(); + + if (Settings.TaskDeviceEnabled[taskIndex]) { + struct EventStruct TempEvent(taskIndex); + TempEvent.Source = EventValueSource::Enum::VALUE_SOURCE_UDP; + + String dummy; + PluginCall(PLUGIN_INIT, &TempEvent, dummy); + } + } + break; + } + + case 4: // sensor data pull request + { + // SendUDPTaskData(packetBuffer[2], packetBuffer[5], packetBuffer[4]); + break; + } + + case 5: // sensor data + { + struct C013_SensorDataStruct dataReply; + + // FIXME TD-er: We should check for sensorType and pluginID on both sides. + // For example sending different sensor type data from one dummy to another is probably not going to work well + + if (dataReply.setData(event->Data, event->Par2)) { + // only if this task has a remote feed, update values + const uint8_t remoteFeed = Settings.TaskDeviceDataFeed[dataReply.destTaskIndex]; + + if ((remoteFeed != 0) && (remoteFeed == dataReply.sourceUnit)) + { + // deviceNumber and sensorType were not present before build 2023-05-05. (build NR 20460) + // See: + // https://github.com/letscontrolit/ESPEasy/commit/cf791527eeaf31ca98b07c45c1b64e2561a7b041#diff-86b42dd78398b103e272503f05f55ee0870ae5fb907d713c2505d63279bb0321 + // Thus should not be checked + // + // If the node is not present in the nodes list (e.g. it had not announced itself in the last 10 minutes or announcement was + // missed) + // Then we cannot be sure about its build. + const bool mustMatch = dataReply.sourceNodeBuild >= 20460; + + if (mustMatch && !dataReply.matchesPluginID(Settings.getPluginID_for_task(dataReply.destTaskIndex))) { + // Mismatch in plugin ID from sending node + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + String log = concat(F("P2P data : PluginID mismatch for task "), dataReply.destTaskIndex + 1); + log += concat(F(" from unit "), dataReply.sourceUnit); + log += concat(F(" remote: "), dataReply.deviceNumber.value); + log += concat(F(" local: "), Settings.getPluginID_for_task(dataReply.destTaskIndex).value); + addLogMove(LOG_LEVEL_ERROR, log); + } + } else { + struct EventStruct TempEvent(dataReply.destTaskIndex); + TempEvent.Source = EventValueSource::Enum::VALUE_SOURCE_UDP; + + const Sensor_VType sensorType = TempEvent.getSensorType(); + + if (!mustMatch || dataReply.matchesSensorType(sensorType)) { + TaskValues_Data_t *taskValues = UserVar.getRawTaskValues_Data(dataReply.destTaskIndex); + + if (taskValues != nullptr) { + for (taskVarIndex_t x = 0; x < VARS_PER_TASK; ++x) + { + taskValues->copyValue(dataReply.values, x, sensorType); + } + } + STOP_TIMER(C013_RECEIVE_SENSOR_DATA); + + if (node_time.systemTimePresent() && (dataReply.timestamp_sec != 0)) { + // Only use timestamp of remote unit when we got a system time ourselves + // If not, then the order of samples can get messed up. + // timestamp_fraq is 16 bit, so need to scale it to 32 bit + TempEvent.timestamp_frac = static_cast(dataReply.timestamp_frac) << 16; + SensorSendTask(&TempEvent, dataReply.timestamp_sec); + } else { + SensorSendTask(&TempEvent); + } + } else { + // Mismatch in sensor types + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + String log = concat(F("P2P data : SensorType mismatch for task "), dataReply.destTaskIndex + 1); + log += concat(F(" from unit "), dataReply.sourceUnit); + addLogMove(LOG_LEVEL_ERROR, log); + } + } + } + } + } + + break; + } + } +} + +#endif // ifdef USES_C013 diff --git a/src/_C014.cpp b/src/_C014.cpp index 9a3486c6a6..08e2064f71 100644 --- a/src/_C014.cpp +++ b/src/_C014.cpp @@ -878,6 +878,7 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& } String pubname = CPlugin_014_pubname; + const bool contains_valname = pubname.indexOf(F("%valname%")) != -1; bool mqtt_retainFlag = CPlugin_014_mqtt_retainFlag; statusLED(true); @@ -891,7 +892,9 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& { String tmppubname = pubname; String value; - parseSingleControllerVariable(tmppubname, event, x, false); + if (contains_valname) { + parseSingleControllerVariable(tmppubname, event, x, false); + } // Small optimization so we don't try to copy potentially large strings if (event->getSensorType() == Sensor_VType::SENSOR_TYPE_STRING) { diff --git a/src/_C015.cpp b/src/_C015.cpp index 8356993e32..e06d6eddac 100644 --- a/src/_C015.cpp +++ b/src/_C015.cpp @@ -193,6 +193,8 @@ bool CPlugin_015(CPlugin::Function function, struct EventStruct *event, String& // and thus preventing the need to create a long string only to copy it to a queue element. C015_queue_element& element = static_cast(*(C015_DelayHandler->sendQueue.back())); + const String taskDeviceName = getTaskDeviceName(event->TaskIndex); + for (uint8_t x = 0; x < valueCount; x++) { bool isvalid; @@ -203,10 +205,10 @@ bool CPlugin_015(CPlugin::Function function, struct EventStruct *event, String& formattedValue = String(); } - const String valueName = getTaskValueName(event->TaskIndex, x); + const String valueName = Cache.getTaskDeviceValueName(event->TaskIndex, x); const String valueFullName = strformat( F("%s.%s"), - getTaskDeviceName(event->TaskIndex).c_str(), + taskDeviceName.c_str(), valueName.c_str()); const String vPinNumberStr = valueName.substring(1, 4); int vPinNumber = vPinNumberStr.toInt(); @@ -253,7 +255,7 @@ bool CPlugin_015(CPlugin::Function function, struct EventStruct *event, String& // Uncrustify may change this into multi line, which will result in failed builds // *INDENT-OFF* -bool do_process_c015_delay_queue(int controller_number, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { +bool do_process_c015_delay_queue(cpluginID_t cpluginID, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { const C015_queue_element& element = static_cast(element_base); // *INDENT-ON* if (!Settings.ControllerEnabled[element._controller_idx]) { diff --git a/src/_C016.cpp b/src/_C016.cpp index 59bfecbb1a..625c377b0a 100644 --- a/src/_C016.cpp +++ b/src/_C016.cpp @@ -1,183 +1,186 @@ -#include "src/Helpers/_CPlugin_Helper.h" -#ifdef USES_C016 - -// ####################################################################################################### -// ########################### Controller Plugin 016: Controller - Cache ################################# -// ####################################################################################################### - -/* - This is a cache layer to collect data while not connected to a network. - The data will first be stored in RTC memory, which will survive a crash/reboot and even an OTA update. - If this RTC buffer is full, it will be flushed to whatever is set here as storage. - - Typical sample sets contain: - - UNIX timestamp - - task index delivering the data - - 4 float values - - These are the result of any plugin sending data to this controller. - - The controller can save the samples from RTC memory to several places on the flash: - - Files on FS - - Part reserved for OTA update (TODO) - - Unused flash after the partitioned space (TODO) - - The controller can deliver the data to: - - */ - -# include "src/Globals/C016_ControllerCache.h" -# include "src/Globals/ESPEasy_time.h" - -# define CPLUGIN_016 -# define CPLUGIN_ID_016 16 -# define CPLUGIN_NAME_016 "Cache Controller [Experimental]" - -// #include - -bool C016_allowLocalSystemTime = false; - -bool CPlugin_016(CPlugin::Function function, struct EventStruct *event, String& string) -{ - bool success = false; - - switch (function) - { - case CPlugin::Function::CPLUGIN_PROTOCOL_ADD: - { - ProtocolStruct& proto = getProtocolStruct(event->idx); // = CPLUGIN_ID_016; - proto.usesMQTT = false; - proto.usesTemplate = false; - proto.usesAccount = false; - proto.usesPassword = false; - proto.usesExtCreds = false; - proto.defaultPort = 80; - proto.usesID = false; - proto.usesHost = false; - proto.usesPort = false; - proto.usesQueue = false; - proto.usesCheckReply = false; - proto.usesTimeout = false; - proto.usesSampleSets = false; - proto.needsNetwork = false; - proto.allowsExpire = false; - proto.allowLocalSystemTime = true; - break; - } - - case CPlugin::Function::CPLUGIN_GET_DEVICENAME: - { - string = F(CPLUGIN_NAME_016); - break; - } - - case CPlugin::Function::CPLUGIN_INIT: - { - { - MakeControllerSettings(ControllerSettings); // -V522 - - if (AllocatedControllerSettings()) { - LoadControllerSettings(event->ControllerIndex, *ControllerSettings); - C016_allowLocalSystemTime = ControllerSettings->useLocalSystemTime(); - } - } - success = init_c016_delay_queue(event->ControllerIndex); - ControllerCache.init(); - break; - } - - case CPlugin::Function::CPLUGIN_EXIT: - { - exit_c016_delay_queue(); - break; - } - - case CPlugin::Function::CPLUGIN_WEBFORM_LOAD: - { - break; - } - - case CPlugin::Function::CPLUGIN_WEBFORM_SAVE: - { - break; - } - - case CPlugin::Function::CPLUGIN_PROTOCOL_TEMPLATE: - { - event->String1 = String(); - event->String2 = String(); - break; - } - - case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: - { - // Collect the values at the same run, to make sure all are from the same sample - uint8_t valueCount = getValueCountForTask(event->TaskIndex); - - if (event->timestamp == 0) { - event->timestamp = C016_allowLocalSystemTime ? node_time.now() : node_time.getUnixTime(); - } - const C016_queue_element element( - event, - valueCount); - - const C016_binary_element binary_element = element.getBinary(); - success = ControllerCache.write(reinterpret_cast(&binary_element), sizeof(C016_binary_element)); - break; - } - - case CPlugin::Function::CPLUGIN_WRITE: - { - if (C016_CacheInitialized()) { - const String command = parseString(string, 1); - - if (equals(command, F("cachecontroller"))) { - const String subcommand = parseString(string, 2); - - if (equals(subcommand, F("flush"))) { - C016_flush(); - success = true; - } - } - } - break; - } - - case CPlugin::Function::CPLUGIN_FLUSH: - { - C016_flush(); - delay(0); - break; - } - - case CPlugin::Function::CPLUGIN_WEBFORM_SHOW_HOST_CONFIG: - { - string = F("-"); - break; - } - - default: - break; - } - return success; -} - -// ******************************************************************************** -// Process the data from the cache -// ******************************************************************************** -// Uncrustify may change this into multi line, which will result in failed builds -// *INDENT-OFF* -bool do_process_c016_delay_queue(int controller_number, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { -// *INDENT-ON* -return true; - -// FIXME TD-er: Hand over data to wherever it needs to be. -// Ideas: -// - Upload bin files to some server (HTTP post?) -// - Provide a sample to any connected controller -// - Do nothing and let some extern host pull the data from the node. -// - JavaScript to process the data inside the browser. -// - Feed it to some plugin (e.g. a display to show a chart) -} - -#endif // ifdef USES_C016 +#include "src/Helpers/_CPlugin_Helper.h" +#ifdef USES_C016 + +// ####################################################################################################### +// ########################### Controller Plugin 016: Controller - Cache ################################# +// ####################################################################################################### + +/* + This is a cache layer to collect data while not connected to a network. + The data will first be stored in RTC memory, which will survive a crash/reboot and even an OTA update. + If this RTC buffer is full, it will be flushed to whatever is set here as storage. + + Typical sample sets contain: + - UNIX timestamp + - task index delivering the data + - 4 float values + + These are the result of any plugin sending data to this controller. + + The controller can save the samples from RTC memory to several places on the flash: + - Files on FS + - Part reserved for OTA update (TODO) + - Unused flash after the partitioned space (TODO) + + The controller can deliver the data to: + + */ + +# include "src/Globals/C016_ControllerCache.h" +# include "src/Globals/ESPEasy_time.h" + +# define CPLUGIN_016 +# define CPLUGIN_ID_016 16 +# define CPLUGIN_NAME_016 "Cache Controller [Experimental]" + +// #include + +bool C016_allowLocalSystemTime = false; + +bool CPlugin_016(CPlugin::Function function, struct EventStruct *event, String& string) +{ + bool success = false; + + switch (function) + { + case CPlugin::Function::CPLUGIN_PROTOCOL_ADD: + { + ProtocolStruct& proto = getProtocolStruct(event->idx); // = CPLUGIN_ID_016; + proto.usesMQTT = false; + proto.usesTemplate = false; + proto.usesAccount = false; + proto.usesPassword = false; + proto.usesExtCreds = false; + proto.defaultPort = 80; + proto.usesID = false; + proto.usesHost = false; + proto.usesPort = false; + proto.usesQueue = false; + proto.usesCheckReply = false; + proto.usesTimeout = false; + proto.usesSampleSets = false; + proto.needsNetwork = false; + proto.allowsExpire = false; + proto.allowLocalSystemTime = true; + break; + } + + case CPlugin::Function::CPLUGIN_GET_DEVICENAME: + { + string = F(CPLUGIN_NAME_016); + break; + } + + case CPlugin::Function::CPLUGIN_INIT: + { + { + MakeControllerSettings(ControllerSettings); // -V522 + + if (AllocatedControllerSettings()) { + LoadControllerSettings(event->ControllerIndex, *ControllerSettings); + C016_allowLocalSystemTime = ControllerSettings->useLocalSystemTime(); + } + } + success = init_c016_delay_queue(event->ControllerIndex); + ControllerCache.init(); + break; + } + + case CPlugin::Function::CPLUGIN_EXIT: + { + exit_c016_delay_queue(); + break; + } + + case CPlugin::Function::CPLUGIN_WEBFORM_LOAD: + { + break; + } + + case CPlugin::Function::CPLUGIN_WEBFORM_SAVE: + { + break; + } + + case CPlugin::Function::CPLUGIN_PROTOCOL_TEMPLATE: + { + event->String1 = String(); + event->String2 = String(); + break; + } + + case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: + { + // Collect the values at the same run, to make sure all are from the same sample + uint8_t valueCount = getValueCountForTask(event->TaskIndex); + + if (event->timestamp_sec == 0) { + if (C016_allowLocalSystemTime) + event->setLocalTimeTimestamp(); + else + event->setUnixTimeTimestamp(); + } + const C016_queue_element element( + event, + valueCount); + + const C016_binary_element binary_element = element.getBinary(); + success = ControllerCache.write(reinterpret_cast(&binary_element), sizeof(C016_binary_element)); + break; + } + + case CPlugin::Function::CPLUGIN_WRITE: + { + if (C016_CacheInitialized()) { + const String command = parseString(string, 1); + + if (equals(command, F("cachecontroller"))) { + const String subcommand = parseString(string, 2); + + if (equals(subcommand, F("flush"))) { + C016_flush(); + success = true; + } + } + } + break; + } + + case CPlugin::Function::CPLUGIN_FLUSH: + { + C016_flush(); + delay(0); + break; + } + + case CPlugin::Function::CPLUGIN_WEBFORM_SHOW_HOST_CONFIG: + { + string = F("-"); + break; + } + + default: + break; + } + return success; +} + +// ******************************************************************************** +// Process the data from the cache +// ******************************************************************************** +// Uncrustify may change this into multi line, which will result in failed builds +// *INDENT-OFF* +bool do_process_c016_delay_queue(cpluginID_t cpluginID, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { +// *INDENT-ON* +return true; + +// FIXME TD-er: Hand over data to wherever it needs to be. +// Ideas: +// - Upload bin files to some server (HTTP post?) +// - Provide a sample to any connected controller +// - Do nothing and let some extern host pull the data from the node. +// - JavaScript to process the data inside the browser. +// - Feed it to some plugin (e.g. a display to show a chart) +} + +#endif // ifdef USES_C016 diff --git a/src/_C017.cpp b/src/_C017.cpp index d10cee5227..fc8f5131d0 100644 --- a/src/_C017.cpp +++ b/src/_C017.cpp @@ -88,7 +88,7 @@ bool CPlugin_017(CPlugin::Function function, struct EventStruct *event, String& // Uncrustify may change this into multi line, which will result in failed builds // *INDENT-OFF* -bool do_process_c017_delay_queue(int controller_number, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { +bool do_process_c017_delay_queue(cpluginID_t cpluginID, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { const C017_queue_element& element = static_cast(element_base); // *INDENT-ON* if (element.valueCount == 0) { @@ -102,7 +102,7 @@ bool do_process_c017_delay_queue(int controller_number, const Queue_element_base WiFiClient client; - if (!try_connect_host(controller_number, client, ControllerSettings, F("ZBX : "))) + if (!try_connect_host(cpluginID, client, ControllerSettings, F("ZBX : "))) { return false; } @@ -120,7 +120,7 @@ bool do_process_c017_delay_queue(int controller_number, const Queue_element_base // Populate JSON with the data for (uint8_t i = 0; i < element.valueCount; i++) { - const String taskValueName = getTaskValueName(element._taskIndex, i); + const String taskValueName = Cache.getTaskDeviceValueName(element._taskIndex, i); if (taskValueName.isEmpty()) { continue; // Zabbix will ignore an empty key anyway } diff --git a/src/_C018.cpp b/src/_C018.cpp index f4032e4a67..6642609dc1 100644 --- a/src/_C018.cpp +++ b/src/_C018.cpp @@ -356,7 +356,7 @@ bool C018_init(struct EventStruct *event) { // Uncrustify may change this into multi line, which will result in failed builds // *INDENT-OFF* -bool do_process_c018_delay_queue(int controller_number, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { +bool do_process_c018_delay_queue(cpluginID_t cpluginID, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { const C018_queue_element& element = static_cast(element_base); // *INDENT-ON* uint8_t pl = (element.packed.length() / 2); diff --git a/src/_P039_Thermosensors.ino b/src/_P039_Thermosensors.ino index 122768a157..096d141089 100644 --- a/src/_P039_Thermosensors.ino +++ b/src/_P039_Thermosensors.ino @@ -617,11 +617,13 @@ boolean Plugin_039(uint8_t function, struct EventStruct *event, String& string) if (loglevelActiveFor(LOG_LEVEL_INFO)) { String log = strformat(F("P039 : %s :"), getTaskDeviceName(event->TaskIndex).c_str()); - for (uint8_t i = 0; i < getValueCountForTask(event->TaskIndex); ++i) + const uint8_t valueCount = getValueCountForTask(event->TaskIndex); + + for (uint8_t i = 0; i < valueCount; ++i) { log += strformat( F(" %s: %s"), - getTaskValueName(event->TaskIndex, i).c_str(), + Cache.getTaskDeviceValueName(event->TaskIndex, i).c_str(), formatUserVarNoCheck(event, i).c_str()); } addLogMove(LOG_LEVEL_INFO, log); diff --git a/src/_P110_VL53L0X.ino b/src/_P110_VL53L0X.ino index 7661329e3c..0f45c94ac4 100644 --- a/src/_P110_VL53L0X.ino +++ b/src/_P110_VL53L0X.ino @@ -142,56 +142,30 @@ boolean Plugin_110(uint8_t function, struct EventStruct *event, String& string) initPluginTaskData(event->TaskIndex, new (std::nothrow) P110_data_struct(P110_I2C_ADDRESS, P110_TIMING, P110_RANGE == 1)); P110_data_struct *P110_data = static_cast(getPluginTaskData(event->TaskIndex)); - success = (nullptr != P110_data) && P110_data->begin(); // Start the sensor - break; - } - case PLUGIN_READ: - { - P110_data_struct *P110_data = static_cast(getPluginTaskData(event->TaskIndex)); - if (nullptr != P110_data) { - const uint16_t dist = P110_data->getDistance(); - const uint16_t p_dist = UserVar.getFloat(event->TaskIndex, 0); - const int16_t direct = dist == p_dist ? 0 : (dist < p_dist ? -1 : 1); - const bool triggered = (dist > (p_dist + P110_DELTA)) || (dist < (p_dist - P110_DELTA)); - - #ifdef P110_INFO_LOG - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLog(LOG_LEVEL_INFO, strformat(F("VL53L0x: Perform read: trig: %d, prev: %d, dist: %d"), triggered, p_dist, dist)); - } - #endif // ifdef P110_INFO_LOG - - if (triggered || (P110_SEND_ALWAYS == 1)) { - UserVar.setFloat(event->TaskIndex, 0, dist); // Value is classified as invalid when > 8190, so no conversion or 'split' needed - UserVar.setFloat(event->TaskIndex, 1, direct); - success = true; - } + const uint32_t interval_ms = Settings.TaskDeviceTimer[event->TaskIndex] * 1000; + + // Clear the "previous" distance so there will be a new result when starting the task + UserVar.setFloat(event->TaskIndex, 3, -1); + success = P110_data->begin(interval_ms); // Start the sensor } break; } - - case PLUGIN_TEN_PER_SECOND: // Handle sensor reading + case PLUGIN_READ: { P110_data_struct *P110_data = static_cast(getPluginTaskData(event->TaskIndex)); if (nullptr != P110_data) { - P110_data->readDistance(); - - if (P110_data->isReadSuccessful() && (Settings.TaskDeviceTimer[event->TaskIndex] == 0)) { // Trigger as soon as there's a valid - // measurement and 0 interval is set - Scheduler.schedule_task_device_timer(event->TaskIndex, millis() + 10); - } + success = P110_data->plugin_read(event); } break; } - - case PLUGIN_FIFTY_PER_SECOND: // Handle startup delay + case PLUGIN_TEN_PER_SECOND: // Handle startup delay and sensor reading { P110_data_struct *P110_data = static_cast(getPluginTaskData(event->TaskIndex)); if (nullptr != P110_data) { - success = P110_data->plugin_fifty_per_second(); + success = P110_data->check_reading_ready(event); } break; } diff --git a/src/_P132_INA3221.ino b/src/_P132_INA3221.ino index 7a1460dee0..43aaf8c081 100644 --- a/src/_P132_INA3221.ino +++ b/src/_P132_INA3221.ino @@ -70,10 +70,10 @@ boolean Plugin_132(uint8_t function, struct EventStruct *event, String& string) const uint8_t i2cAddressValues[] = { 0x40, 0x41, 0x42, 0x43 }; if (function == PLUGIN_WEBFORM_SHOW_I2C_PARAMS) { - addFormSelectorI2C(F("i2c_addr"), 4, i2cAddressValues, P132_I2C_ADDR); + addFormSelectorI2C(F("i2c_addr"), NR_ELEMENTS(i2cAddressValues), i2cAddressValues, P132_I2C_ADDR); addFormNote(F("A0 connected to: GND= 0x40, VCC= 0x41, SDA= 0x42, SCL= 0x43")); } else { - success = intArrayContains(4, i2cAddressValues, event->Par1); + success = intArrayContains(NR_ELEMENTS(i2cAddressValues), i2cAddressValues, event->Par1); } break; } diff --git a/src/_P169_AS3935_LightningDetector.ino b/src/_P169_AS3935_LightningDetector.ino new file mode 100644 index 0000000000..8f1f8692bd --- /dev/null +++ b/src/_P169_AS3935_LightningDetector.ino @@ -0,0 +1,284 @@ +#include "_Plugin_Helper.h" +#ifdef USES_P169 + +// ####################################################################################################### +// ######################## Plugin 169 AS3935 Lightning Detector I2C ################################## +// ####################################################################################################### + +# include "./src/PluginStructs/P169_data_struct.h" + +# define PLUGIN_169 +# define PLUGIN_ID_169 169 +# define PLUGIN_NAME_169 "Environment - AS3935 Lightning Detector" +# define PLUGIN_VALUENAME1_169 "DistanceNear" +# define PLUGIN_VALUENAME2_169 "DistanceFar" +# define PLUGIN_VALUENAME3_169 "Lightning" +# define PLUGIN_VALUENAME4_169 "Total" + + +boolean Plugin_169(uint8_t function, struct EventStruct *event, String& string) +{ + boolean success = false; + + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + Device[++deviceCount].Number = PLUGIN_ID_169; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_TRIPLE; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 4; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].I2CNoDeviceCheck = true; // Sensor may sometimes not respond immediately + Device[deviceCount].GlobalSyncOption = true; + Device[deviceCount].PluginStats = true; + break; + } + + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_169); + break; + } + + + case PLUGIN_GET_DEVICEVALUENAMES: + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_169)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_169)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_VALUENAME3_169)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[3], PSTR(PLUGIN_VALUENAME4_169)); + break; + } + + + case PLUGIN_SET_DEFAULTS: + { + // Set a default config here, which will be called when a plugin is assigned to a task. + P169_I2C_ADDRESS = P169_I2C_ADDRESS_DFLT; + P169_LIGHTNING_THRESHOLD = AS3935MI::AS3935_MNL_1; + P169_AFE_GAIN_LOW = AS3935MI::AS3935_OUTDOORS; + P169_AFE_GAIN_HIGH = AS3935MI::AS3935_OUTDOORS; + P169_SET_MASK_DISTURBANCE(false); + P169_SET_SEND_ONLY_ON_LIGHTNING(true); + P169_SET_TOLERANT_CALIBRATION_RANGE(true); + + ExtraTaskSettings.TaskDeviceValueDecimals[0] = 1; // Distance Near + ExtraTaskSettings.TaskDeviceValueDecimals[1] = 1; // Distance Far + ExtraTaskSettings.TaskDeviceValueDecimals[2] = 0; // Lightning count since last PLUGIN_READ + ExtraTaskSettings.TaskDeviceValueDecimals[3] = 0; // Total lightning count + success = true; + break; + } + + + # if FEATURE_I2C_GET_ADDRESS + case PLUGIN_I2C_GET_ADDRESS: + { + event->Par1 = P169_I2C_ADDRESS; + success = true; + break; + } + # endif // if FEATURE_I2C_GET_ADDRESS + + + case PLUGIN_I2C_HAS_ADDRESS: + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: + { + const uint8_t i2cAddressValues[] = { 0x01, 0x02, 0x03 }; + + if (function == PLUGIN_WEBFORM_SHOW_I2C_PARAMS) + { + // addFormSelectorI2C(P169_I2C_ADDRESS_LABEL, 3, i2cAddressValues, P169_I2C_ADDRESS); + addFormSelectorI2C(F("i2c_addr"), NR_ELEMENTS(i2cAddressValues), i2cAddressValues, P169_I2C_ADDRESS); + addFormNote(F("Addr: 0-0-0-0-0-A1-A0. Both A0 & A1 low is not valid.")); + } + else + { + success = intArrayContains(NR_ELEMENTS(i2cAddressValues), i2cAddressValues, event->Par1); + } + break; + } + + case PLUGIN_WEBFORM_SHOW_GPIO_DESCR: + { + string = concat(F("IRQ: "), formatGpioLabel(P169_IRQ_PIN, false)); + success = true; + break; + } + + case PLUGIN_WEBFORM_LOAD: + { + addFormPinSelect( + PinSelectPurpose::Generic_input, + formatGpioName_input(F("IRQ")), + F(P169_IRQ_PIN_LABEL), + P169_IRQ_PIN); + + { + const __FlashStringHelper *options[] = { F("1"), F("5"), F("9"), F("16") }; + const int optionValues[] = { + AS3935MI::AS3935_MNL_1, + AS3935MI::AS3935_MNL_5, + AS3935MI::AS3935_MNL_9, + AS3935MI::AS3935_MNL_16 }; + addFormSelector(F("Lightning Threshold"), + P169_LIGHTNING_THRESHOLD_LABEL, + NR_ELEMENTS(optionValues), + options, + optionValues, + P169_LIGHTNING_THRESHOLD); + addFormNote(F("Minimum number of lightning strikes in the last 15 minutes")); + } + { + const __FlashStringHelper *options[] = { + F("0.30x"), + F("0.40x"), + F("0.55x"), + F("0.74x"), + F("1.00x (Outdoor)"), + F("1.35x"), + F("1.83x"), + F("2.47x"), + F("3.34x (Indoor)") + }; + const int optionValues[] = { + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18 + }; + addFormSelector(F("AFE Gain Min"), P169_AFE_GAIN_LOW_LABEL, NR_ELEMENTS(optionValues), options, optionValues, P169_AFE_GAIN_LOW); + addFormSelector(F("AFE Gain Max"), P169_AFE_GAIN_HIGH_LABEL, NR_ELEMENTS(optionValues), options, optionValues, P169_AFE_GAIN_HIGH); + addFormNote(F("Lower and upper limit for the Analog Frond-End auto gain to use.")); + } + + addFormCheckBox(F("Ignore Disturbance"), F(P169_MASK_DISTURBANCE_LABEL), P169_GET_MASK_DISTURBANCE); + addFormCheckBox(F("Tolerate out-of-range calibration"), F(P169_TOLERANT_CALIBRATION_RANGE_LABEL), P169_GET_TOLERANT_CALIBRATION_RANGE); + addFormNote(F("When checked, allow for more than 3.5% deviation for the 500 kHz LCO resonance frequency")); + addFormCheckBox(F("Slow LCO Calibration"), F(P169_SLOW_LCO_CALIBRATION_LABEL), P169_GET_SLOW_LCO_CALIBRATION); + addFormNote(F("Slow Calibration may improve accuracy of measured resonance frequency")); + addFormCheckBox(F("Send Only On Lightning"), F(P169_SEND_ONLY_ON_LIGHTNING_LABEL), P169_GET_SEND_ONLY_ON_LIGHTNING); + addFormNote(F("Only send to controller when lightning detected since last taskrun")); + + P169_data_struct *P169_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (P169_data != nullptr) { + P169_data->html_show_sensor_info(event); + } + + success = true; + break; + } + + case PLUGIN_WEBFORM_SAVE: + { + P169_I2C_ADDRESS = getFormItemInt(F("i2c_addr")); + + /* + P169_NOISE = getFormItemInt(P169_NOISE_LABEL); + P169_WATCHDOG = getFormItemInt(P169_WATCHDOG_LABEL); + P169_SPIKE_REJECTION = getFormItemInt(P169_SPIKE_REJECTION_LABEL); + */ + P169_LIGHTNING_THRESHOLD = getFormItemInt(P169_LIGHTNING_THRESHOLD_LABEL); + const int gain_low = getFormItemInt(P169_AFE_GAIN_LOW_LABEL); + const int gain_high = getFormItemInt(P169_AFE_GAIN_HIGH_LABEL); + P169_AFE_GAIN_LOW = gain_low; + P169_AFE_GAIN_HIGH = gain_high; + + if (gain_low > gain_high) { + P169_AFE_GAIN_LOW = gain_high; + P169_AFE_GAIN_HIGH = gain_low; + } + P169_SET_MASK_DISTURBANCE(isFormItemChecked(F(P169_MASK_DISTURBANCE_LABEL))); + P169_SET_SEND_ONLY_ON_LIGHTNING(isFormItemChecked(F(P169_SEND_ONLY_ON_LIGHTNING_LABEL))); + P169_SET_TOLERANT_CALIBRATION_RANGE(isFormItemChecked(F(P169_TOLERANT_CALIBRATION_RANGE_LABEL))); + P169_SET_SLOW_LCO_CALIBRATION(isFormItemChecked(F(P169_SLOW_LCO_CALIBRATION_LABEL))); + success = true; + break; + } + + case PLUGIN_INIT: + { + initPluginTaskData(event->TaskIndex, new (std::nothrow) P169_data_struct(event)); + P169_data_struct *P169_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P169_data) { + success = P169_data->plugin_init(event); + } + + break; + } + + case PLUGIN_READ: + { + P169_data_struct *P169_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P169_data) { + if (P169_data->getAndClearLightningCount() > 0) { + success = true; + } else { + UserVar.setFloat(event->TaskIndex, 0, -1.0f); + UserVar.setFloat(event->TaskIndex, 1, -1.0f); + UserVar.setFloat(event->TaskIndex, 2, 0.0f); + P169_data->clearStatistics(); + + if (!P169_GET_SEND_ONLY_ON_LIGHTNING) { + success = true; + } + } + } + + break; + } + + case PLUGIN_TEN_PER_SECOND: + { + P169_data_struct *P169_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P169_data) { + if (P169_data->loop(event)) {} + success = true; + } + break; + } + + case PLUGIN_WRITE: + { + P169_data_struct *P169_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P169_data) { + success = P169_data->plugin_write(event, string); + } + + break; + } + + case PLUGIN_GET_CONFIG_VALUE: + { + P169_data_struct *P169_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P169_data) { + success = P169_data->plugin_get_config_value(event, string); + } + + break; + } + } + + return success; +} // function + +#endif // USES_P169 diff --git a/src/_Plugin_Helper.cpp b/src/_Plugin_Helper.cpp index fedde4567a..5e49b55123 100644 --- a/src/_Plugin_Helper.cpp +++ b/src/_Plugin_Helper.cpp @@ -1,208 +1,208 @@ -#include "_Plugin_Helper.h" - -#include "ESPEasy_common.h" - -#include "src/CustomBuild/ESPEasyLimits.h" -#include "src/DataStructs/PluginTaskData_base.h" -#include "src/DataStructs/SettingsStruct.h" -#include "src/DataStructs/TimingStats.h" -#include "src/Globals/Cache.h" -#include "src/Globals/Plugins.h" -#include "src/Globals/Settings.h" -#include "src/Helpers/Misc.h" -#include "src/Helpers/StringParser.h" - - -PluginTaskData_base *Plugin_task_data[TASKS_MAX] = {}; - - -String PCONFIG_LABEL(int n) { - if (n < PLUGIN_CONFIGVAR_MAX) { - return concat(F("pconf_"), n); - } - return F("error"); -} - -void resetPluginTaskData() { - for (taskIndex_t i = 0; i < TASKS_MAX; ++i) { - Plugin_task_data[i] = nullptr; - } -} - -void clearPluginTaskData(taskIndex_t taskIndex) { - if (validTaskIndex(taskIndex)) { - if (Plugin_task_data[taskIndex] != nullptr) { - delete Plugin_task_data[taskIndex]; - Plugin_task_data[taskIndex] = nullptr; - } - } -} - -bool initPluginTaskData(taskIndex_t taskIndex, PluginTaskData_base *data) { - if (!validTaskIndex(taskIndex)) { - if (data != nullptr) { - delete data; - } - return false; - } - - // 2nd heap may have been active to allocate the PluginTaskData, but here we need to keep the default heap active - # ifdef USE_SECOND_HEAP - HeapSelectDram ephemeral; - # endif // ifdef USE_SECOND_HEAP - - - clearPluginTaskData(taskIndex); - - if (data != nullptr) { - if (Settings.TaskDeviceEnabled[taskIndex]) { - Plugin_task_data[taskIndex] = data; - Plugin_task_data[taskIndex]->_taskdata_pluginID = Settings.getPluginID_for_task(taskIndex); - - #if FEATURE_PLUGIN_STATS - const uint8_t valueCount = getValueCountForTask(taskIndex); - for (size_t i = 0; i < valueCount; ++i) { - if (Cache.enabledPluginStats(taskIndex, i)) { - Plugin_task_data[taskIndex]->initPluginStats(i); - } - } - #endif - #if FEATURE_PLUGIN_FILTER - // TODO TD-er: Implement init - - #endif - - } else { - delete data; - } - } - return getPluginTaskData(taskIndex) != nullptr; -} - -PluginTaskData_base* getPluginTaskData(taskIndex_t taskIndex) { - if (pluginTaskData_initialized(taskIndex)) { - - if (!Plugin_task_data[taskIndex]->baseClassOnly()) { - return Plugin_task_data[taskIndex]; - } - } - return nullptr; -} - -PluginTaskData_base* getPluginTaskDataBaseClassOnly(taskIndex_t taskIndex) { - if (pluginTaskData_initialized(taskIndex)) { - return Plugin_task_data[taskIndex]; - } - return nullptr; -} - - -bool pluginTaskData_initialized(taskIndex_t taskIndex) { - if (!validTaskIndex(taskIndex)) { - return false; - } - return Plugin_task_data[taskIndex] != nullptr && - (Plugin_task_data[taskIndex]->_taskdata_pluginID == Settings.getPluginID_for_task(taskIndex)); -} - -String getPluginCustomArgName(int varNr) { - return getPluginCustomArgName(F("pc_arg"), varNr); -} - -String getPluginCustomArgName(const __FlashStringHelper * label, int varNr) { - return concat(label, varNr + 1); -} - -int getFormItemIntCustomArgName(int varNr) { - return getFormItemInt(getPluginCustomArgName(varNr)); -} - -// Helper function to create formatted custom values for display in the devices overview page. -// When called from PLUGIN_WEBFORM_SHOW_VALUES, the last item should add a traling div_br class -// if the regular values should also be displayed. -// The call to PLUGIN_WEBFORM_SHOW_VALUES should only return success = true when no regular values should be displayed -// Note that the varNr of the custom values should not conflict with the existing variable numbers (e.g. start at VARS_PER_TASK) -void pluginWebformShowValue(taskIndex_t taskIndex, uint8_t varNr, const __FlashStringHelper * label, const String& value, bool addTrailingBreak) { - pluginWebformShowValue(taskIndex, varNr, String(label), value, addTrailingBreak); -} - -void pluginWebformShowValue(taskIndex_t taskIndex, - uint8_t varNr, - const String& label, - const String& value, - bool addTrailingBreak) { - if (varNr > 0) { - addHtmlDiv(F("div_br")); - } - String postfix(taskIndex); - postfix += '_'; - postfix += varNr; - - pluginWebformShowValue( - label, concat(F("valuename_"), postfix), - value, concat(F("value_"), postfix), - addTrailingBreak); -} - -void pluginWebformShowValue(const String& valName, const String& value, bool addBR) { - pluginWebformShowValue(valName, EMPTY_STRING, value, EMPTY_STRING, addBR); -} - -void pluginWebformShowValue(const String& valName, const String& valName_id, const String& value, const String& value_id, bool addBR) { - String valName_tmp(valName); - - if (!valName_tmp.endsWith(F(":"))) { - valName_tmp += ':'; - } - addHtmlDiv(F("div_l"), valName_tmp, valName_id); - addHtmlDiv(F("div_r"), value, value_id); - - if (addBR) { - addHtmlDiv(F("div_br")); - } -} - -bool pluginOptionalTaskIndexArgumentMatch(taskIndex_t taskIndex, const String& string, uint8_t paramNr) { - if (!validTaskIndex(taskIndex)) { - return false; - } - const taskIndex_t found_taskIndex = parseCommandArgumentTaskIndex(string, paramNr); - - if (!validTaskIndex(found_taskIndex)) { - // Optional parameter not present - return true; - } - return found_taskIndex == taskIndex; -} - -bool pluginWebformShowGPIOdescription(taskIndex_t taskIndex, - const __FlashStringHelper * newline, - String& description) -{ - struct EventStruct TempEvent(taskIndex); - TempEvent.String1 = newline; - return PluginCall(PLUGIN_WEBFORM_SHOW_GPIO_DESCR, &TempEvent, description); -} - -int getValueCountForTask(taskIndex_t taskIndex) { - struct EventStruct TempEvent(taskIndex); - String dummy; - - PluginCall(PLUGIN_GET_DEVICEVALUECOUNT, &TempEvent, dummy); - return TempEvent.Par1; -} - -int checkDeviceVTypeForTask(struct EventStruct *event) { - // TD-er: Do not use event->getSensorType() here - if (event->sensorType == Sensor_VType::SENSOR_TYPE_NOT_SET) { - if (validTaskIndex(event->TaskIndex)) { - String dummy; - - event->idx = -1; - if (PluginCall(PLUGIN_GET_DEVICEVTYPE, event, dummy)) { - return event->idx; // pconfig_index - } - } - } - return -1; -} +#include "_Plugin_Helper.h" + +#include "ESPEasy_common.h" + +#include "src/CustomBuild/ESPEasyLimits.h" +#include "src/DataStructs/PluginTaskData_base.h" +#include "src/DataStructs/SettingsStruct.h" +#include "src/DataStructs/TimingStats.h" +#include "src/Globals/Cache.h" +#include "src/Globals/Plugins.h" +#include "src/Globals/Settings.h" +#include "src/Helpers/Misc.h" +#include "src/Helpers/StringParser.h" + + +PluginTaskData_base *Plugin_task_data[TASKS_MAX] = {}; + + +String PCONFIG_LABEL(int n) { + if (n < PLUGIN_CONFIGVAR_MAX) { + return concat(F("pconf_"), n); + } + return F("error"); +} + +void resetPluginTaskData() { + for (taskIndex_t i = 0; i < TASKS_MAX; ++i) { + Plugin_task_data[i] = nullptr; + } +} + +void clearPluginTaskData(taskIndex_t taskIndex) { + if (validTaskIndex(taskIndex)) { + if (Plugin_task_data[taskIndex] != nullptr) { + delete Plugin_task_data[taskIndex]; + Plugin_task_data[taskIndex] = nullptr; + } + } +} + +bool initPluginTaskData(taskIndex_t taskIndex, PluginTaskData_base *data) { + if (!validTaskIndex(taskIndex)) { + if (data != nullptr) { + delete data; + } + return false; + } + + // 2nd heap may have been active to allocate the PluginTaskData, but here we need to keep the default heap active + # ifdef USE_SECOND_HEAP + HeapSelectDram ephemeral; + # endif // ifdef USE_SECOND_HEAP + + + clearPluginTaskData(taskIndex); + + if (data != nullptr) { + if (Settings.TaskDeviceEnabled[taskIndex]) { + Plugin_task_data[taskIndex] = data; + Plugin_task_data[taskIndex]->_taskdata_pluginID = Settings.getPluginID_for_task(taskIndex); + + #if FEATURE_PLUGIN_STATS + const uint8_t valueCount = getValueCountForTask(taskIndex); + for (size_t i = 0; i < valueCount; ++i) { + if (Cache.enabledPluginStats(taskIndex, i)) { + Plugin_task_data[taskIndex]->initPluginStats(taskIndex, i); + } + } + #endif + #if FEATURE_PLUGIN_FILTER + // TODO TD-er: Implement init + + #endif + + } else { + delete data; + } + } + return getPluginTaskData(taskIndex) != nullptr; +} + +PluginTaskData_base* getPluginTaskData(taskIndex_t taskIndex) { + if (pluginTaskData_initialized(taskIndex)) { + + if (!Plugin_task_data[taskIndex]->baseClassOnly()) { + return Plugin_task_data[taskIndex]; + } + } + return nullptr; +} + +PluginTaskData_base* getPluginTaskDataBaseClassOnly(taskIndex_t taskIndex) { + if (pluginTaskData_initialized(taskIndex)) { + return Plugin_task_data[taskIndex]; + } + return nullptr; +} + + +bool pluginTaskData_initialized(taskIndex_t taskIndex) { + if (!validTaskIndex(taskIndex)) { + return false; + } + return Plugin_task_data[taskIndex] != nullptr && + (Plugin_task_data[taskIndex]->_taskdata_pluginID == Settings.getPluginID_for_task(taskIndex)); +} + +String getPluginCustomArgName(int varNr) { + return getPluginCustomArgName(F("pc_arg"), varNr); +} + +String getPluginCustomArgName(const __FlashStringHelper * label, int varNr) { + return concat(label, varNr + 1); +} + +int getFormItemIntCustomArgName(int varNr) { + return getFormItemInt(getPluginCustomArgName(varNr)); +} + +// Helper function to create formatted custom values for display in the devices overview page. +// When called from PLUGIN_WEBFORM_SHOW_VALUES, the last item should add a traling div_br class +// if the regular values should also be displayed. +// The call to PLUGIN_WEBFORM_SHOW_VALUES should only return success = true when no regular values should be displayed +// Note that the varNr of the custom values should not conflict with the existing variable numbers (e.g. start at VARS_PER_TASK) +void pluginWebformShowValue(taskIndex_t taskIndex, uint8_t varNr, const __FlashStringHelper * label, const String& value, bool addTrailingBreak) { + pluginWebformShowValue(taskIndex, varNr, String(label), value, addTrailingBreak); +} + +void pluginWebformShowValue(taskIndex_t taskIndex, + uint8_t varNr, + const String& label, + const String& value, + bool addTrailingBreak) { + if (varNr > 0) { + addHtmlDiv(F("div_br")); + } + String postfix(taskIndex); + postfix += '_'; + postfix += varNr; + + pluginWebformShowValue( + label, concat(F("valuename_"), postfix), + value, concat(F("value_"), postfix), + addTrailingBreak); +} + +void pluginWebformShowValue(const String& valName, const String& value, bool addBR) { + pluginWebformShowValue(valName, EMPTY_STRING, value, EMPTY_STRING, addBR); +} + +void pluginWebformShowValue(const String& valName, const String& valName_id, const String& value, const String& value_id, bool addBR) { + String valName_tmp(valName); + + if (!valName_tmp.endsWith(F(":"))) { + valName_tmp += ':'; + } + addHtmlDiv(F("div_l"), valName_tmp, valName_id); + addHtmlDiv(F("div_r"), value, value_id); + + if (addBR) { + addHtmlDiv(F("div_br")); + } +} + +bool pluginOptionalTaskIndexArgumentMatch(taskIndex_t taskIndex, const String& string, uint8_t paramNr) { + if (!validTaskIndex(taskIndex)) { + return false; + } + const taskIndex_t found_taskIndex = parseCommandArgumentTaskIndex(string, paramNr); + + if (!validTaskIndex(found_taskIndex)) { + // Optional parameter not present + return true; + } + return found_taskIndex == taskIndex; +} + +bool pluginWebformShowGPIOdescription(taskIndex_t taskIndex, + const __FlashStringHelper * newline, + String& description) +{ + struct EventStruct TempEvent(taskIndex); + TempEvent.String1 = newline; + return PluginCall(PLUGIN_WEBFORM_SHOW_GPIO_DESCR, &TempEvent, description); +} + +int getValueCountForTask(taskIndex_t taskIndex) { + struct EventStruct TempEvent(taskIndex); + String dummy; + + PluginCall(PLUGIN_GET_DEVICEVALUECOUNT, &TempEvent, dummy); + return TempEvent.Par1; +} + +int checkDeviceVTypeForTask(struct EventStruct *event) { + // TD-er: Do not use event->getSensorType() here + if (event->sensorType == Sensor_VType::SENSOR_TYPE_NOT_SET) { + if (validTaskIndex(event->TaskIndex)) { + String dummy; + + event->idx = -1; + if (PluginCall(PLUGIN_GET_DEVICEVTYPE, event, dummy)) { + return event->idx; // pconfig_index + } + } + } + return -1; +} diff --git a/src/src/ControllerQueue/C016_queue_element.cpp b/src/src/ControllerQueue/C016_queue_element.cpp index 7f4ffb350d..d21222ee39 100644 --- a/src/src/ControllerQueue/C016_queue_element.cpp +++ b/src/src/ControllerQueue/C016_queue_element.cpp @@ -1,102 +1,102 @@ -#include "../ControllerQueue/C016_queue_element.h" - -#ifdef USES_C016 - -# include "../DataStructs/ESPEasy_EventStruct.h" -# include "../Globals/Plugins.h" -# include "../Globals/RuntimeData.h" -# include "../Helpers/_Plugin_SensorTypeHelper.h" -# include "../Helpers/ESPEasy_math.h" - -C016_queue_element::C016_queue_element() : sensorType( - Sensor_VType::SENSOR_TYPE_NONE) { - _timestamp = 0; - _controller_idx = 0; - _taskIndex = INVALID_TASK_INDEX; - values.clear(); -} - -C016_queue_element::C016_queue_element(C016_queue_element&& other) - : sensorType(other.sensorType) - , valueCount(other.valueCount) -{ - _timestamp = other._timestamp; - _controller_idx = other._controller_idx; - _taskIndex = other._taskIndex; - values = other.values; -} - -C016_queue_element::C016_queue_element(const struct EventStruct *event, uint8_t value_count) : - unixTime(event->timestamp), - sensorType(event->sensorType), - valueCount(value_count) -{ - _controller_idx = event->ControllerIndex; - _taskIndex = event->TaskIndex; - values.clear(); - const TaskValues_Data_t* data = UserVar.getRawTaskValues_Data(event->TaskIndex); - - if (data != nullptr) { - for (uint8_t i = 0; i < value_count; ++i) { - values.copyValue(*data, i, sensorType); - } - } -} - -C016_queue_element& C016_queue_element::operator=(C016_queue_element&& other) { - _timestamp = other._timestamp; - _taskIndex = other._taskIndex; - _controller_idx = other._controller_idx; - sensorType = other.sensorType; - valueCount = other.valueCount; - unixTime = other.unixTime; - values = other.values; - - return *this; -} - -size_t C016_queue_element::getSize() const { - return sizeof(*this); -} - -bool C016_queue_element::isDuplicate(const Queue_element_base& other) const { - const C016_queue_element& oth = static_cast(other); - - if ((oth._controller_idx != _controller_idx) || - (oth._taskIndex != _taskIndex) || - (oth.sensorType != sensorType) || - (oth.valueCount != valueCount)) { - return false; - } - - for (uint8_t i = 0; i < valueCount; ++i) { - if (isFloatOutputDataType(sensorType)) { - if (!essentiallyEqual(oth.values.getFloat(i), values.getFloat(i))) { - return false; - } - } else { - if (oth.values.getUint32(i) != values.getUint32(i)) { - return false; - } - } - } - return true; -} - -C016_binary_element C016_queue_element::getBinary() const { - C016_binary_element element; - - element.unixTime = unixTime; - element.TaskIndex = _taskIndex; - element.sensorType = sensorType; - element.valueCount = valueCount; - element.values = values; - - // It makes no sense to keep the controller index when storing it. - // re-purpose it to store the pluginID - element.pluginID = getPluginID_from_TaskIndex(_taskIndex); - - return element; -} - -#endif // ifdef USES_C016 +#include "../ControllerQueue/C016_queue_element.h" + +#ifdef USES_C016 + +# include "../DataStructs/ESPEasy_EventStruct.h" +# include "../Globals/Plugins.h" +# include "../Globals/RuntimeData.h" +# include "../Helpers/_Plugin_SensorTypeHelper.h" +# include "../Helpers/ESPEasy_math.h" + +C016_queue_element::C016_queue_element() : sensorType( + Sensor_VType::SENSOR_TYPE_NONE) { + _timestamp = 0; + _controller_idx = 0; + _taskIndex = INVALID_TASK_INDEX; + values.clear(); +} + +C016_queue_element::C016_queue_element(C016_queue_element&& other) + : sensorType(other.sensorType) + , valueCount(other.valueCount) +{ + _timestamp = other._timestamp; + _controller_idx = other._controller_idx; + _taskIndex = other._taskIndex; + values = other.values; +} + +C016_queue_element::C016_queue_element(const struct EventStruct *event, uint8_t value_count) : + unixTime(event->timestamp_sec), + sensorType(event->sensorType), + valueCount(value_count) +{ + _controller_idx = event->ControllerIndex; + _taskIndex = event->TaskIndex; + values.clear(); + const TaskValues_Data_t* data = UserVar.getRawTaskValues_Data(event->TaskIndex); + + if (data != nullptr) { + for (uint8_t i = 0; i < value_count; ++i) { + values.copyValue(*data, i, sensorType); + } + } +} + +C016_queue_element& C016_queue_element::operator=(C016_queue_element&& other) { + _timestamp = other._timestamp; + _taskIndex = other._taskIndex; + _controller_idx = other._controller_idx; + sensorType = other.sensorType; + valueCount = other.valueCount; + unixTime = other.unixTime; + values = other.values; + + return *this; +} + +size_t C016_queue_element::getSize() const { + return sizeof(*this); +} + +bool C016_queue_element::isDuplicate(const Queue_element_base& other) const { + const C016_queue_element& oth = static_cast(other); + + if ((oth._controller_idx != _controller_idx) || + (oth._taskIndex != _taskIndex) || + (oth.sensorType != sensorType) || + (oth.valueCount != valueCount)) { + return false; + } + + for (uint8_t i = 0; i < valueCount; ++i) { + if (isFloatOutputDataType(sensorType)) { + if (!essentiallyEqual(oth.values.getFloat(i), values.getFloat(i))) { + return false; + } + } else { + if (oth.values.getUint32(i) != values.getUint32(i)) { + return false; + } + } + } + return true; +} + +C016_binary_element C016_queue_element::getBinary() const { + C016_binary_element element; + + element.unixTime = unixTime; + element.TaskIndex = _taskIndex; + element.sensorType = sensorType; + element.valueCount = valueCount; + element.values = values; + + // It makes no sense to keep the controller index when storing it. + // re-purpose it to store the pluginID + element.pluginID = getPluginID_from_TaskIndex(_taskIndex); + + return element; +} + +#endif // ifdef USES_C016 diff --git a/src/src/ControllerQueue/ControllerDelayHandlerStruct.cpp b/src/src/ControllerQueue/ControllerDelayHandlerStruct.cpp index 98b95cdbe1..9bdb7ac0f7 100644 --- a/src/src/ControllerQueue/ControllerDelayHandlerStruct.cpp +++ b/src/src/ControllerQueue/ControllerDelayHandlerStruct.cpp @@ -258,7 +258,7 @@ size_t ControllerDelayHandlerStruct::getQueueMemorySize() const { } void ControllerDelayHandlerStruct::process( - int controller_number, + cpluginID_t cpluginID, do_process_function func, TimingStatsElements timerstats_id, SchedulerIntervalTimer_e timerID) @@ -274,7 +274,7 @@ void ControllerDelayHandlerStruct::process( LoadControllerSettings(element->_controller_idx, *ControllerSettings); cacheControllerSettings(*ControllerSettings); START_TIMER; - markProcessed(func(controller_number, *element, *ControllerSettings)); + markProcessed(func(cpluginID, *element, *ControllerSettings)); #if FEATURE_TIMING_STATS STOP_TIMER_VAR(timerstats_id); #endif diff --git a/src/src/ControllerQueue/ControllerDelayHandlerStruct.h b/src/src/ControllerQueue/ControllerDelayHandlerStruct.h index d6bd792bd9..f5442cf502 100644 --- a/src/src/ControllerQueue/ControllerDelayHandlerStruct.h +++ b/src/src/ControllerQueue/ControllerDelayHandlerStruct.h @@ -28,7 +28,7 @@ # define CONTROLLER_QUEUE_MINIMAL_EXPIRE_TIME 10000 #endif // ifndef CONTROLLER_QUEUE_MINIMAL_EXPIRE_TIME -typedef bool (*do_process_function)(int, +typedef bool (*do_process_function)(cpluginID_t, const Queue_element_base&, ControllerSettingsStruct&); @@ -71,7 +71,7 @@ struct ControllerDelayHandlerStruct { size_t getQueueMemorySize() const; void process( - int controller_number, + cpluginID_t cpluginID, do_process_function func, TimingStatsElements timerstats_id, SchedulerIntervalTimer_e timerID); diff --git a/src/src/ControllerQueue/DelayQueueElements.h b/src/src/ControllerQueue/DelayQueueElements.h index cf6e73c2a9..3c44e89edf 100644 --- a/src/src/ControllerQueue/DelayQueueElements.h +++ b/src/src/ControllerQueue/DelayQueueElements.h @@ -52,7 +52,7 @@ // in the element stored in the queue. #define DEFINE_Cxxx_DELAY_QUEUE_MACRO(NNN, M) \ extern struct ControllerDelayHandlerStruct *C##NNN####M##_DelayHandler; \ - bool do_process_c##NNN####M##_delay_queue(int controller_number, const Queue_element_base & element, ControllerSettingsStruct & ControllerSettings); \ + bool do_process_c##NNN####M##_delay_queue(cpluginID_t cpluginID, const Queue_element_base & element, ControllerSettingsStruct & ControllerSettings); \ void process_c##NNN####M##_delay_queue(); \ bool init_c##NNN####M##_delay_queue(controllerIndex_t ControllerIndex); \ void exit_c##NNN####M##_delay_queue(); \ diff --git a/src/src/CustomBuild/ESPEasyLimits.h b/src/src/CustomBuild/ESPEasyLimits.h index f2a0048bb1..73b5ecdeb8 100644 --- a/src/src/CustomBuild/ESPEasyLimits.h +++ b/src/src/CustomBuild/ESPEasyLimits.h @@ -151,7 +151,7 @@ #define MAX_FLASHWRITES_PER_DAY 100 // per 24 hour window #endif #ifndef UDP_PACKETSIZE_MAX - #define UDP_PACKETSIZE_MAX 256 // Currently only needed for C013_Receive + #define UDP_PACKETSIZE_MAX 512 // Currently only needed for C013_Receive #endif #ifndef TIMER_GRATUITOUS_ARP_MAX #define TIMER_GRATUITOUS_ARP_MAX 5000 diff --git a/src/src/CustomBuild/define_plugin_sets.h b/src/src/CustomBuild/define_plugin_sets.h index 0737c45372..6342bcc27d 100644 --- a/src/src/CustomBuild/define_plugin_sets.h +++ b/src/src/CustomBuild/define_plugin_sets.h @@ -1962,6 +1962,10 @@ To create/register a plugin, you have to : #define USES_P168 // Light - VEML6030/VEML7700 #endif + #ifndef USES_P169 + #define USES_P169 // Environment - AS3935 Lightning Detector + #endif + // Controllers #ifndef USES_C011 #define USES_C011 // HTTP Advanced @@ -2023,17 +2027,29 @@ To create/register a plugin, you have to : #endif #ifdef CONTROLLER_SET_COLLECTION + #ifndef USES_C011 #define USES_C011 // Generic HTTP Advanced + #endif + #ifndef USES_C012 #define USES_C012 // Blynk HTTP + #endif + #ifndef USES_C014 #define USES_C014 // homie 3 & 4dev MQTT + #endif + #ifndef USES_C015 //#define USES_C015 // Blynk + #endif + #ifndef USES_C017 #define USES_C017 // Zabbix - #ifdef ESP32 - #ifndef USES_C018 - #define USES_C018 // TTN RN2483 - #endif + #endif + #ifdef ESP32 + #ifndef USES_C018 + #define USES_C018 // TTN RN2483 #endif + #endif + #ifndef USES_C019 // #define USES_C019 // ESPEasy-NOW + #endif #endif @@ -2374,7 +2390,7 @@ To create/register a plugin, you have to : #define USES_P166 // Output - GP8403 DAC 0-10V #endif #ifndef USES_P167 - #define USES_P167 // Environment - SensirionSEN5x / Ikea Vindstyrka + #define USES_P167 // Environment - IKEA Vindstyrka SEN54 temperature , humidity and air quality #endif #ifndef USES_P168 #define USES_P168 // Light - VEML6030/VEML7700 @@ -2383,6 +2399,10 @@ To create/register a plugin, you have to : #define USES_P170 // Input - I2C Liquid level sensor #endif + #ifndef USES_P169 + #define USES_P169 // Environment - AS3935 Lightning Detector + #endif + // Controllers #ifndef USES_C015 #define USES_C015 // Blynk @@ -2839,7 +2859,13 @@ To create/register a plugin, you have to : #ifndef LIMIT_BUILD_SIZE #ifndef FEATURE_MDNS #ifdef ESP32 - #define FEATURE_MDNS 0 + #if ESP_IDF_VERSION_MAJOR >= 5 + // See if it is now more usable... + // See: https://github.com/letscontrolit/ESPEasy/issues/5061 + #define FEATURE_MDNS 0 + #else + #define FEATURE_MDNS 0 + #endif #else // Do not use MDNS on ESP8266 due to memory leak #define FEATURE_MDNS 0 diff --git a/src/src/DataStructs/C013_p2p_SensorDataStruct.cpp b/src/src/DataStructs/C013_p2p_SensorDataStruct.cpp new file mode 100644 index 0000000000..881a378236 --- /dev/null +++ b/src/src/DataStructs/C013_p2p_SensorDataStruct.cpp @@ -0,0 +1,116 @@ +#include "../DataStructs/C013_p2p_SensorDataStruct.h" + +#ifdef USES_C013 + +# include "../DataStructs/NodeStruct.h" +# include "../Globals/Nodes.h" +# include "../Globals/ESPEasy_time.h" +# include "../Globals/Plugins.h" + +# include "../CustomBuild/CompiletimeDefines.h" + +bool C013_SensorDataStruct::prepareForSend() +{ + sourceNodeBuild = get_build_nr(); + checksum.clear(); + + if (sourceNodeBuild >= 20871) { + if (node_time.systemTimePresent()) { + uint32_t unix_time_frac{}; + timestamp_sec = node_time.getUnixTime(unix_time_frac); + timestamp_frac = unix_time_frac >> 16; + } + + + // Make sure to add checksum as last step + constexpr unsigned len_upto_checksum = offsetof(C013_SensorDataStruct, checksum); + + const ShortChecksumType tmpChecksum( + reinterpret_cast(this), + sizeof(C013_SensorDataStruct), + len_upto_checksum); + + checksum = tmpChecksum; + } + + return validTaskIndex(sourceTaskIndex) && + validTaskIndex(destTaskIndex); +} + +bool C013_SensorDataStruct::setData(const uint8_t *data, size_t size) +{ + // First clear entire struct + memset(this, 0, sizeof(C013_SensorDataStruct)); + + if (size < 6) { + return false; + } + + if ((data[0] != 255) || // header + (data[1] != 5)) { // ID + return false; + } + + constexpr unsigned len_upto_checksum = offsetof(C013_SensorDataStruct, checksum); + const ShortChecksumType tmpChecksum( + data, + size, + len_upto_checksum); + + + // Need to keep track of different possible versions of data which still need to be supported. + // Really old versions of ESPEasy might send upto 80 bytes of uninitialized data + // meaning for sizes > 24 bytes we may need to check the version of ESPEasy running on the node. + if (size > sizeof(C013_SensorDataStruct)) { + size = sizeof(C013_SensorDataStruct); + } + NodeStruct *sourceNode = Nodes.getNode(data[2]); // sourceUnit + + if (sourceNode != nullptr) { + if (sourceNode->build < 20871) { + if (size > 24) { + size = 24; + } + } + } + + if (size <= 24) { + deviceNumber = INVALID_PLUGIN_ID; + sensorType = Sensor_VType::SENSOR_TYPE_NONE; + + if (sourceNode != nullptr) { + sourceNodeBuild = sourceNode->build; + } + } + + memcpy(this, data, size); + + if (checksum.isSet()) { + if (!(tmpChecksum == checksum)) { + return false; + } + } + + return validTaskIndex(sourceTaskIndex) && + validTaskIndex(destTaskIndex); +} + +bool C013_SensorDataStruct::matchesPluginID(pluginID_t pluginID) const +{ + if ((deviceNumber.value == 255) || !validPluginID(deviceNumber) || !validPluginID(pluginID)) { + // Was never set, so probably received data from older node. + return true; + } + return pluginID == deviceNumber; +} + +bool C013_SensorDataStruct::matchesSensorType(Sensor_VType sensor_type) const +{ + if ((deviceNumber.value == 255) || (sensorType == Sensor_VType::SENSOR_TYPE_NONE)) { + // Was never set, so probably received data from older node. + return true; + } + return sensorType == sensor_type; +} + +#endif // ifdef USES_C013 diff --git a/src/src/DataStructs/C013_p2p_SensorDataStruct.h b/src/src/DataStructs/C013_p2p_SensorDataStruct.h new file mode 100644 index 0000000000..8306d2d0bc --- /dev/null +++ b/src/src/DataStructs/C013_p2p_SensorDataStruct.h @@ -0,0 +1,59 @@ +#ifndef DATASTRUCTS_C013_P2P_SENSORDATASTRUCTS_H +#define DATASTRUCTS_C013_P2P_SENSORDATASTRUCTS_H + +#include "../../ESPEasy_common.h" + +#ifdef USES_C013 + + +# include "../CustomBuild/ESPEasyLimits.h" +# include "../DataStructs/DeviceStruct.h" +# include "../DataStructs/ShortChecksumType.h" +# include "../DataTypes/TaskIndex.h" +# include "../DataTypes/TaskValues_Data.h" +# include "../DataTypes/PluginID.h" + + +// These structs are sent to other nodes, so make sure not to change order or offset in struct. +struct __attribute__((__packed__)) C013_SensorDataStruct +{ + C013_SensorDataStruct() = default; + + bool setData(const uint8_t *data, + size_t size); + + bool prepareForSend(); + + bool matchesPluginID(pluginID_t pluginID) const; + + bool matchesSensorType(Sensor_VType sensor_type) const; + + uint8_t header = 255; + uint8_t ID = 5; + uint8_t sourceUnit = 0; + uint8_t destUnit = 0; + taskIndex_t sourceTaskIndex = INVALID_TASK_INDEX; + taskIndex_t destTaskIndex = INVALID_TASK_INDEX; + + // deviceNumber and sensorType were not present before build 2023-05-05. (build NR 20460) + // See: + // https://github.com/letscontrolit/ESPEasy/commit/cf791527eeaf31ca98b07c45c1b64e2561a7b041#diff-86b42dd78398b103e272503f05f55ee0870ae5fb907d713c2505d63279bb0321 + // Thus should not be checked + pluginID_t deviceNumber = INVALID_PLUGIN_ID; + Sensor_VType sensorType = Sensor_VType::SENSOR_TYPE_NONE; + TaskValues_Data_t values{}; + + // Extra info added on 20240619 (build ID 20871) + ShortChecksumType checksum; + uint16_t sourceNodeBuild = 0; + uint16_t timestamp_frac = 0; + uint32_t timestamp_sec = 0; + + // Optional IDX value to allow receiving remote + // feed data on a different task index as is used on the sender node. + uint32_t IDX = 0; +}; + +#endif // ifdef USES_C013 + +#endif // ifndef DATASTRUCTS_C013_P2P_SENSORDATASTRUCTS_H diff --git a/src/src/DataStructs/C013_p2p_SensorInfoStruct.cpp b/src/src/DataStructs/C013_p2p_SensorInfoStruct.cpp new file mode 100644 index 0000000000..d215bd07d5 --- /dev/null +++ b/src/src/DataStructs/C013_p2p_SensorInfoStruct.cpp @@ -0,0 +1,147 @@ +#include "../DataStructs/C013_p2p_SensorInfoStruct.h" + +#ifdef USES_C013 + +# include "../DataStructs/NodeStruct.h" +# include "../Globals/ExtraTaskSettings.h" +# include "../Globals/Nodes.h" +# include "../Globals/Plugins.h" +# include "../Globals/Settings.h" + +# include "../CustomBuild/CompiletimeDefines.h" + +# include "../Helpers/ESPEasy_Storage.h" +# include "../Helpers/StringConverter.h" + +bool C013_SensorInfoStruct::prepareForSend(size_t& sizeToSend) +{ + if (!(validTaskIndex(sourceTaskIndex) && + validTaskIndex(destTaskIndex) && + validPluginID(deviceNumber))) { + return false; + } + + sizeToSend = sizeof(C013_SensorInfoStruct); + + sourceNodeBuild = get_build_nr(); + checksum.clear(); + + ZERO_FILL(taskName); + safe_strncpy(taskName, getTaskDeviceName(sourceTaskIndex), sizeof(taskName)); + + for (uint8_t x = 0; x < VARS_PER_TASK; x++) { + ZERO_FILL(ValueNames[x]); + safe_strncpy(ValueNames[x], getTaskValueName(sourceTaskIndex, x), sizeof(ValueNames[x])); + } + + + if (sourceNodeBuild >= 20871) { + LoadTaskSettings(sourceTaskIndex); + + ExtraTaskSettings_version = ExtraTaskSettings.version; + + for (uint8_t x = 0; x < VARS_PER_TASK; x++) { + TaskDeviceValueDecimals[x] = ExtraTaskSettings.TaskDeviceValueDecimals[x]; + TaskDeviceMinValue[x] = ExtraTaskSettings.TaskDeviceMinValue[x]; + TaskDeviceMaxValue[x] = ExtraTaskSettings.TaskDeviceMaxValue[x]; + TaskDeviceErrorValue[x] = ExtraTaskSettings.TaskDeviceErrorValue[x]; + VariousBits[x] = ExtraTaskSettings.VariousBits[x]; + +/* + ZERO_FILL(TaskDeviceFormula[x]); + + if (ExtraTaskSettings.TaskDeviceFormula[x][0] != 0) { + safe_strncpy(TaskDeviceFormula[x], ExtraTaskSettings.TaskDeviceFormula[x], sizeof(TaskDeviceFormula[x])); + } +*/ + } + + for (uint8_t x = 0; x < PLUGIN_CONFIGVAR_MAX; ++x) { + TaskDevicePluginConfig[x] = Settings.TaskDevicePluginConfig[sourceTaskIndex][x]; + } + } + + // Check to see if last bytes are all zero, so we can simply not send them + bool doneShrinking = false; + constexpr unsigned len_upto_sourceNodeBuild = offsetof(C013_SensorInfoStruct, sourceNodeBuild); + + const uint8_t *data = reinterpret_cast(this); + + while (!doneShrinking) { + if (sizeToSend < len_upto_sourceNodeBuild) { + doneShrinking = true; + } + else { + if (data[sizeToSend - 1] == 0) { + --sizeToSend; + } else { + doneShrinking = true; + } + } + } + + if (sourceNodeBuild >= 20871) { + // Make sure to add checksum as last step + constexpr unsigned len_upto_checksum = offsetof(C013_SensorInfoStruct, checksum); + const ShortChecksumType tmpChecksum( + reinterpret_cast(this), + sizeToSend, + len_upto_checksum); + + checksum = tmpChecksum; + } + + return true; +} + +bool C013_SensorInfoStruct::setData(const uint8_t *data, size_t size) +{ + // First clear entire struct + memset(this, 0, sizeof(C013_SensorInfoStruct)); + + if (size < 6) { + return false; + } + + if ((data[0] != 255) || // header + (data[1] != 3)) { // ID + return false; + } + + // Before copying the data, compute the checksum of the entire packet + constexpr unsigned len_upto_checksum = offsetof(C013_SensorInfoStruct, checksum); + const ShortChecksumType tmpChecksum( + data, + size, + len_upto_checksum); + + // Need to keep track of different possible versions of data which still need to be supported. + if (size > sizeof(C013_SensorInfoStruct)) { + size = sizeof(C013_SensorInfoStruct); + } + + if (size <= 138) { + deviceNumber = INVALID_PLUGIN_ID; + sensorType = Sensor_VType::SENSOR_TYPE_NONE; + + NodeStruct *sourceNode = Nodes.getNode(data[2]); // sourceUnit + + if (sourceNode != nullptr) { + sourceNodeBuild = sourceNode->build; + } + } + + memcpy(this, data, size); + + if (checksum.isSet()) { + if (!(tmpChecksum == checksum)) { + return false; + } + } + + return validTaskIndex(sourceTaskIndex) && + validTaskIndex(destTaskIndex) && + validPluginID(deviceNumber); +} + +#endif // ifdef USES_C013 diff --git a/src/src/DataStructs/C013_p2p_SensorInfoStruct.h b/src/src/DataStructs/C013_p2p_SensorInfoStruct.h new file mode 100644 index 0000000000..7f4650698e --- /dev/null +++ b/src/src/DataStructs/C013_p2p_SensorInfoStruct.h @@ -0,0 +1,65 @@ +#ifndef DATASTRUCTS_C013_P2P_SENSORINFOSTRUCTS_H +#define DATASTRUCTS_C013_P2P_SENSORINFOSTRUCTS_H + +#include "../../ESPEasy_common.h" + +#ifdef USES_C013 + + +# include "../CustomBuild/ESPEasyLimits.h" +# include "../DataStructs/DeviceStruct.h" +# include "../DataStructs/ShortChecksumType.h" +# include "../DataTypes/TaskIndex.h" +# include "../DataTypes/TaskValues_Data.h" +# include "../DataTypes/PluginID.h" + + +// These structs are sent to other nodes, so make sure not to change order or offset in struct. +struct __attribute__((__packed__)) C013_SensorInfoStruct +{ + C013_SensorInfoStruct() = default; + + bool setData(const uint8_t *data, + size_t size); + + bool prepareForSend(size_t& sizeToSend); + + uint8_t header = 255; + uint8_t ID = 3; + uint8_t sourceUnit = 0; + uint8_t destUnit = 0; + taskIndex_t sourceTaskIndex = INVALID_TASK_INDEX; + taskIndex_t destTaskIndex = INVALID_TASK_INDEX; + pluginID_t deviceNumber = INVALID_PLUGIN_ID; + char taskName[26]{}; + char ValueNames[VARS_PER_TASK][26]{}; + Sensor_VType sensorType = Sensor_VType::SENSOR_TYPE_NONE; + + // Extra info added on 20240619 (build ID 20871) + ShortChecksumType checksum; + uint16_t sourceNodeBuild = 0; + + // Optional IDX value to allow receiving remote + // feed data on a different task index as is used on the sender node. + uint32_t IDX = 0; + + // Settings PCONFIG values + int16_t TaskDevicePluginConfig[PLUGIN_CONFIGVAR_MAX]{}; + + // Some info from ExtraTaskSettings Sorted so the most likely member to be 0 is at the end. + uint8_t ExtraTaskSettings_version = 0; + uint8_t TaskDeviceValueDecimals[VARS_PER_TASK]{}; + uint32_t VariousBits[VARS_PER_TASK]{}; + float TaskDeviceErrorValue[VARS_PER_TASK]{}; + float TaskDeviceMinValue[VARS_PER_TASK]{}; + float TaskDeviceMaxValue[VARS_PER_TASK]{}; + + // Put these as last as they are most likely to be empty + // FIXME TD-er: Sending formula over is not working well on the receiving end. +// char TaskDeviceFormula[VARS_PER_TASK][NAME_FORMULA_LENGTH_MAX + 1]{}; +}; + + +#endif // ifdef USES_C013 + +#endif // ifndef DATASTRUCTS_C013_P2P_SENSORINFOSTRUCTS_H diff --git a/src/src/DataStructs/C013_p2p_dataStructs.cpp b/src/src/DataStructs/C013_p2p_dataStructs.cpp deleted file mode 100644 index 6587ff001b..0000000000 --- a/src/src/DataStructs/C013_p2p_dataStructs.cpp +++ /dev/null @@ -1,44 +0,0 @@ -#include "../DataStructs/C013_p2p_dataStructs.h" - -#ifdef USES_C013 - -# include "../Globals/Plugins.h" - - - -bool C013_SensorInfoStruct::isValid() const -{ - if ((header != 255) || (ID != 3)) { return false; } - - return validTaskIndex(sourceTaskIndex) && - validTaskIndex(destTaskIndex) && - validPluginID(deviceNumber); -} - -bool C013_SensorDataStruct::isValid() const -{ - if ((header != 255) || (ID != 5)) { return false; } - - return validTaskIndex(sourceTaskIndex) && - validTaskIndex(destTaskIndex); -} - -bool C013_SensorDataStruct::matchesPluginID(pluginID_t pluginID) const -{ - if (deviceNumber.value == 255 || !validPluginID(deviceNumber) || !validPluginID(pluginID)) { - // Was never set, so probably received data from older node. - return true; - } - return pluginID == deviceNumber; -} - -bool C013_SensorDataStruct::matchesSensorType(Sensor_VType sensor_type) const -{ - if (deviceNumber.value == 255 || sensorType == Sensor_VType::SENSOR_TYPE_NONE) { - // Was never set, so probably received data from older node. - return true; - } - return sensorType == sensor_type; -} - -#endif // ifdef USES_C013 diff --git a/src/src/DataStructs/C013_p2p_dataStructs.h b/src/src/DataStructs/C013_p2p_dataStructs.h deleted file mode 100644 index fc316f93be..0000000000 --- a/src/src/DataStructs/C013_p2p_dataStructs.h +++ /dev/null @@ -1,64 +0,0 @@ -#ifndef DATASTRUCTS_C013_P2P_DATASTRUCTS_H -#define DATASTRUCTS_C013_P2P_DATASTRUCTS_H - -#include "../../ESPEasy_common.h" - -#ifdef USES_C013 - - -# include "../CustomBuild/ESPEasyLimits.h" -# include "../DataStructs/DeviceStruct.h" -# include "../DataTypes/TaskIndex.h" -# include "../DataTypes/TaskValues_Data.h" -# include "../DataTypes/PluginID.h" - -// These structs are sent to other nodes, so make sure not to change order or offset in struct. - -struct __attribute__((__packed__)) C013_SensorInfoStruct -{ - C013_SensorInfoStruct() = default; - - bool isValid() const; - - uint8_t header = 255; - uint8_t ID = 3; - uint8_t sourceUnit = 0; - uint8_t destUnit = 0; - taskIndex_t sourceTaskIndex = INVALID_TASK_INDEX; - taskIndex_t destTaskIndex = INVALID_TASK_INDEX; - pluginID_t deviceNumber = INVALID_PLUGIN_ID; - char taskName[26]{}; - char ValueNames[VARS_PER_TASK][26]{}; - Sensor_VType sensorType = Sensor_VType::SENSOR_TYPE_NONE; -}; - -struct C013_SensorDataStruct -{ - C013_SensorDataStruct() = default; - - bool isValid() const; - - bool matchesPluginID(pluginID_t pluginID) const; - - bool matchesSensorType(Sensor_VType sensor_type) const; - - uint8_t header = 255; - uint8_t ID = 5; - uint8_t sourceUnit = 0; - uint8_t destUnit = 0; - taskIndex_t sourceTaskIndex = INVALID_TASK_INDEX; - taskIndex_t destTaskIndex = INVALID_TASK_INDEX; - - // deviceNumber and sensorType were not present before build 2023-05-05. (build NR 20460) - // See: https://github.com/letscontrolit/ESPEasy/commit/cf791527eeaf31ca98b07c45c1b64e2561a7b041#diff-86b42dd78398b103e272503f05f55ee0870ae5fb907d713c2505d63279bb0321 - // Thus should not be checked - pluginID_t deviceNumber = INVALID_PLUGIN_ID; - Sensor_VType sensorType = Sensor_VType::SENSOR_TYPE_NONE; - TaskValues_Data_t values{}; -}; - -constexpr unsigned int size = sizeof(C013_SensorDataStruct); - -#endif // ifdef USES_C013 - -#endif // DATASTRUCTS_C013_P2P_DATASTRUCTS_H diff --git a/src/src/DataStructs/ChecksumType.cpp b/src/src/DataStructs/ChecksumType.cpp index 7c998362f8..9c894fb9fb 100644 --- a/src/src/DataStructs/ChecksumType.cpp +++ b/src/src/DataStructs/ChecksumType.cpp @@ -1,107 +1,107 @@ -#include "../DataStructs/ChecksumType.h" - -#include "../Helpers/StringConverter.h" - -#include - -ChecksumType::ChecksumType(const ChecksumType& rhs) -{ - memcpy(_checksum, rhs._checksum, 16); -} - -ChecksumType::ChecksumType(uint8_t checksum[16]) -{ - memcpy(_checksum, checksum, 16); -} - -ChecksumType::ChecksumType(const uint8_t *data, - size_t data_length) -{ - computeChecksum(_checksum, data, data_length, data_length, true); -} - -ChecksumType::ChecksumType(const uint8_t *data, - size_t data_length, - size_t len_upto_md5) -{ - computeChecksum(_checksum, data, data_length, len_upto_md5, true); -} - -ChecksumType::ChecksumType(const String strings[], size_t nrStrings) -{ - MD5Builder md5; - - md5.begin(); - - for (size_t i = 0; i < nrStrings; ++i) { - md5.add(strings[i].c_str()); - } - md5.calculate(); - md5.getBytes(_checksum); -} - -bool ChecksumType::computeChecksum( - uint8_t checksum[16], - const uint8_t *data, - size_t data_length, - size_t len_upto_md5, - bool updateChecksum) -{ - if (len_upto_md5 > data_length) { len_upto_md5 = data_length; } - MD5Builder md5; - - md5.begin(); - - if (len_upto_md5 > 0) { - // MD5Builder::add has non-const argument - md5.add(const_cast(data), len_upto_md5); - } - - if ((len_upto_md5 + 16) < data_length) { - data += len_upto_md5 + 16; - const int len_after_md5 = data_length - 16 - len_upto_md5; - - if (len_after_md5 > 0) { - // MD5Builder::add has non-const argument - md5.add(const_cast(data), len_after_md5); - } - } - md5.calculate(); - uint8_t tmp_md5[16] = { 0 }; - - md5.getBytes(tmp_md5); - - if (memcmp(tmp_md5, checksum, 16) != 0) { - // Data has changed, copy computed checksum - if (updateChecksum) { - memcpy(checksum, tmp_md5, 16); - } - return false; - } - return true; -} - -void ChecksumType::getChecksum(uint8_t checksum[16]) const { - memcpy(checksum, _checksum, 16); -} - -void ChecksumType::setChecksum(const uint8_t checksum[16]) { - memcpy(_checksum, checksum, 16); -} - -bool ChecksumType::matchChecksum(const uint8_t checksum[16]) const { - return memcmp(_checksum, checksum, 16) == 0; -} - -bool ChecksumType::operator==(const ChecksumType& rhs) const { - return memcmp(_checksum, rhs._checksum, 16) == 0; -} - -ChecksumType& ChecksumType::operator=(const ChecksumType& rhs) { - memcpy(_checksum, rhs._checksum, 16); - return *this; -} - -String ChecksumType::toString() const { - return formatToHex_array(_checksum, 16); +#include "../DataStructs/ChecksumType.h" + +#include "../Helpers/StringConverter.h" + +#include + +ChecksumType::ChecksumType(const ChecksumType& rhs) +{ + memcpy(_checksum, rhs._checksum, 16); +} + +ChecksumType::ChecksumType(uint8_t checksum[16]) +{ + memcpy(_checksum, checksum, 16); +} + +ChecksumType::ChecksumType(const uint8_t *data, + size_t data_length) +{ + computeChecksum(_checksum, data, data_length, data_length, true); +} + +ChecksumType::ChecksumType(const uint8_t *data, + size_t data_length, + size_t len_upto_md5) +{ + computeChecksum(_checksum, data, data_length, len_upto_md5, true); +} + +ChecksumType::ChecksumType(const String strings[], size_t nrStrings) +{ + MD5Builder md5; + + md5.begin(); + + for (size_t i = 0; i < nrStrings; ++i) { + md5.add(strings[i].c_str()); + } + md5.calculate(); + md5.getBytes(_checksum); +} + +bool ChecksumType::computeChecksum( + uint8_t checksum[16], + const uint8_t *data, + size_t data_length, + size_t len_upto_md5, + bool updateChecksum) +{ + if (len_upto_md5 > data_length) { len_upto_md5 = data_length; } + MD5Builder md5; + + md5.begin(); + + if (len_upto_md5 > 0) { + // MD5Builder::add has non-const argument + md5.add(const_cast(data), len_upto_md5); + } + + if ((len_upto_md5 + 16) < data_length) { + data += len_upto_md5 + 16; + const int len_after_md5 = data_length - 16 - len_upto_md5; + + if (len_after_md5 > 0) { + // MD5Builder::add has non-const argument + md5.add(const_cast(data), len_after_md5); + } + } + md5.calculate(); + uint8_t tmp_md5[16] = { 0 }; + + md5.getBytes(tmp_md5); + + if (memcmp(tmp_md5, checksum, 16) != 0) { + // Data has changed, copy computed checksum + if (updateChecksum) { + memcpy(checksum, tmp_md5, 16); + } + return false; + } + return true; +} + +void ChecksumType::getChecksum(uint8_t checksum[16]) const { + memcpy(checksum, _checksum, 16); +} + +void ChecksumType::setChecksum(const uint8_t checksum[16]) { + memcpy(_checksum, checksum, 16); +} + +bool ChecksumType::matchChecksum(const uint8_t checksum[16]) const { + return memcmp(_checksum, checksum, 16) == 0; +} + +bool ChecksumType::operator==(const ChecksumType& rhs) const { + return memcmp(_checksum, rhs._checksum, 16) == 0; +} + +ChecksumType& ChecksumType::operator=(const ChecksumType& rhs) { + memcpy(_checksum, rhs._checksum, 16); + return *this; +} + +String ChecksumType::toString() const { + return formatToHex_array(_checksum, 16); } \ No newline at end of file diff --git a/src/src/DataStructs/ControllerSettingsStruct.h b/src/src/DataStructs/ControllerSettingsStruct.h index d9a91c7455..3a42135399 100644 --- a/src/src/DataStructs/ControllerSettingsStruct.h +++ b/src/src/DataStructs/ControllerSettingsStruct.h @@ -157,6 +157,7 @@ struct ControllerSettingsStruct bool UseDNS; uint8_t IP[4]; + uint8_t UNUSED_1[3]; unsigned int Port; char HostName[65]; char Publish[129]; @@ -164,13 +165,16 @@ struct ControllerSettingsStruct char MQTTLwtTopic[129]; char LWTMessageConnect[129]; char LWTMessageDisconnect[129]; + uint8_t UNUSED_2[2]; unsigned int MinimalTimeBetweenMessages; unsigned int MaxQueueDepth; unsigned int MaxRetry; bool DeleteOldest; // Action to perform when buffer full, delete oldest, or ignore newest. + uint8_t UNUSED_3[3]; unsigned int ClientTimeout; bool MustCheckReply; // When set to false, a sent message is considered always successful. taskIndex_t SampleSetInitiator; // The first task to start a sample set. + uint8_t UNUSED_4[2]; struct { uint32_t unused_00 : 1; // Bit 00 diff --git a/src/src/DataStructs/ESPEasy_EventStruct.cpp b/src/src/DataStructs/ESPEasy_EventStruct.cpp index 4e36d79f32..9d1cea0035 100644 --- a/src/src/DataStructs/ESPEasy_EventStruct.cpp +++ b/src/src/DataStructs/ESPEasy_EventStruct.cpp @@ -1,50 +1,75 @@ -#include "../DataStructs/ESPEasy_EventStruct.h" - -#include "../../ESPEasy_common.h" - -#include "../CustomBuild/ESPEasyLimits.h" -#include "../DataTypes/EventValueSource.h" -#include "../Globals/Plugins.h" -#include "../Globals/CPlugins.h" -#include "../Globals/NPlugins.h" - -#include "../../_Plugin_Helper.h" - -EventStruct::EventStruct(taskIndex_t taskIndex) : - TaskIndex(taskIndex), BaseVarIndex(taskIndex * VARS_PER_TASK) -{ - if (taskIndex >= INVALID_TASK_INDEX) { - BaseVarIndex = 0; - } -} - -void EventStruct::deep_copy(const struct EventStruct& other) { - this->operator=(other); -} - -void EventStruct::deep_copy(const struct EventStruct *other) { - if (other != nullptr) { - deep_copy(*other); - } -} - -void EventStruct::setTaskIndex(taskIndex_t taskIndex) { - TaskIndex = taskIndex; - - if (TaskIndex < INVALID_TASK_INDEX) { - BaseVarIndex = taskIndex * VARS_PER_TASK; - } - sensorType = Sensor_VType::SENSOR_TYPE_NOT_SET; -} - -void EventStruct::clear() { - *this = EventStruct(); -} - -Sensor_VType EventStruct::getSensorType() { - const int tmp_idx = idx; - - checkDeviceVTypeForTask(this); - idx = tmp_idx; - return sensorType; -} +#include "../DataStructs/ESPEasy_EventStruct.h" + +#include "../../ESPEasy_common.h" + +#include "../CustomBuild/ESPEasyLimits.h" +#include "../DataTypes/EventValueSource.h" +#include "../Globals/Plugins.h" +#include "../Globals/CPlugins.h" +#include "../Globals/NPlugins.h" + +#include "../../_Plugin_Helper.h" + +EventStruct::EventStruct(taskIndex_t taskIndex) : + TaskIndex(taskIndex), BaseVarIndex(taskIndex * VARS_PER_TASK) +{ + if (taskIndex >= INVALID_TASK_INDEX) { + BaseVarIndex = 0; + } +} + +void EventStruct::deep_copy(const struct EventStruct& other) { + this->operator=(other); +} + +void EventStruct::deep_copy(const struct EventStruct *other) { + if (other != nullptr) { + deep_copy(*other); + } +} + +void EventStruct::setTaskIndex(taskIndex_t taskIndex) { + TaskIndex = taskIndex; + + if (TaskIndex < INVALID_TASK_INDEX) { + BaseVarIndex = taskIndex * VARS_PER_TASK; + } + sensorType = Sensor_VType::SENSOR_TYPE_NOT_SET; +} + +void EventStruct::clear() { + *this = EventStruct(); +} + +Sensor_VType EventStruct::getSensorType() { + const int tmp_idx = idx; + + checkDeviceVTypeForTask(this); + idx = tmp_idx; + return sensorType; +} + +int64_t EventStruct::getTimestamp_as_systemMicros() const +{ + if (timestamp_sec == 0) + return getMicros64(); + + // FIXME TD-er: What to do when system time has not been set? + int64_t res = node_time.Unixtime_to_systemMicros(timestamp_sec, timestamp_frac); + if (res < 0) { + // Unix time was from before we booted + // FIXME TD-er: What to do now? + return getMicros64(); + } + return res; +} + +void EventStruct::setUnixTimeTimestamp() +{ + timestamp_sec = node_time.getUnixTime(timestamp_frac); +} + +void EventStruct::setLocalTimeTimestamp() +{ + timestamp_sec = node_time.getLocalUnixTime(timestamp_frac); +} \ No newline at end of file diff --git a/src/src/DataStructs/ESPEasy_EventStruct.h b/src/src/DataStructs/ESPEasy_EventStruct.h index 6197d007d6..97f36068ce 100644 --- a/src/src/DataStructs/ESPEasy_EventStruct.h +++ b/src/src/DataStructs/ESPEasy_EventStruct.h @@ -1,74 +1,81 @@ -#ifndef DATASTRUCTS_ESPEASY_EVENTSTRUCT_H -#define DATASTRUCTS_ESPEASY_EVENTSTRUCT_H - -#include "../../ESPEasy_common.h" - -#include "../DataTypes/ControllerIndex.h" -#include "../DataTypes/EventValueSource.h" -#include "../DataTypes/TaskIndex.h" -#include "../DataTypes/NotifierIndex.h" -#include "../DataStructs/DeviceStruct.h" - - -/*********************************************************************************************\ -* EventStruct -* This should not be copied, only moved. -* When copy is really needed, use deep_copy -\*********************************************************************************************/ -struct EventStruct -{ - EventStruct() = default; - // Delete the copy constructor - EventStruct(const struct EventStruct& event) = delete; -private: - // Hide the copy assignment operator by making it private - EventStruct& operator=(const EventStruct&) = default; - -public: - EventStruct(struct EventStruct&& event) = default; - EventStruct& operator=(struct EventStruct&& other) = default; - - explicit EventStruct(taskIndex_t taskIndex); - - // Explicit deep_copy function to make sure this object is not accidentally copied using the copy-constructor - // Copy constructor and assignment operator should not be used. - void deep_copy(const struct EventStruct& other); - void deep_copy(const struct EventStruct* other); - // explicit EventStruct(const struct EventStruct& event); - // EventStruct& operator=(const struct EventStruct& other); - - - void setTaskIndex(taskIndex_t taskIndex); - - void clear(); - - // Check (and update) sensorType if not set, plus return (corrected) sensorType - Sensor_VType getSensorType(); - - String String1; - String String2; - String String3; - String String4; - String String5; - unsigned long timestamp = 0u; - uint8_t *Data = nullptr; - int idx = 0; - int Par1 = 0; - int Par2 = 0; - int Par3 = 0; - int Par4 = 0; - int Par5 = 0; - - // The origin of the values in the event. See EventValueSource.h - EventValueSource::Enum Source = EventValueSource::Enum::VALUE_SOURCE_NOT_SET; - taskIndex_t TaskIndex = INVALID_TASK_INDEX; // index position in TaskSettings array, 0-11 - controllerIndex_t ControllerIndex = INVALID_CONTROLLER_INDEX; // index position in Settings.Controller, 0-3 -#if FEATURE_NOTIFIER - notifierIndex_t NotificationIndex = INVALID_NOTIFIER_INDEX; // index position in Settings.Notification, 0-3 -#endif - uint8_t BaseVarIndex = 0; - Sensor_VType sensorType = Sensor_VType::SENSOR_TYPE_NOT_SET; - uint8_t OriginTaskIndex = 0; -}; - -#endif // DATASTRUCTS_ESPEASY_EVENTSTRUCT_H +#ifndef DATASTRUCTS_ESPEASY_EVENTSTRUCT_H +#define DATASTRUCTS_ESPEASY_EVENTSTRUCT_H + +#include "../../ESPEasy_common.h" + +#include "../DataTypes/ControllerIndex.h" +#include "../DataTypes/EventValueSource.h" +#include "../DataTypes/TaskIndex.h" +#include "../DataTypes/NotifierIndex.h" +#include "../DataStructs/DeviceStruct.h" + + +/*********************************************************************************************\ +* EventStruct +* This should not be copied, only moved. +* When copy is really needed, use deep_copy +\*********************************************************************************************/ +struct EventStruct +{ + EventStruct() = default; + // Delete the copy constructor + EventStruct(const struct EventStruct& event) = delete; +private: + // Hide the copy assignment operator by making it private + EventStruct& operator=(const EventStruct&) = default; + +public: + EventStruct(struct EventStruct&& event) = default; + EventStruct& operator=(struct EventStruct&& other) = default; + + explicit EventStruct(taskIndex_t taskIndex); + + // Explicit deep_copy function to make sure this object is not accidentally copied using the copy-constructor + // Copy constructor and assignment operator should not be used. + void deep_copy(const struct EventStruct& other); + void deep_copy(const struct EventStruct* other); + // explicit EventStruct(const struct EventStruct& event); + // EventStruct& operator=(const struct EventStruct& other); + + + void setTaskIndex(taskIndex_t taskIndex); + + void clear(); + + // Check (and update) sensorType if not set, plus return (corrected) sensorType + Sensor_VType getSensorType(); + + int64_t getTimestamp_as_systemMicros() const; + void setUnixTimeTimestamp(); + void setLocalTimeTimestamp(); + + String String1; + String String2; + String String3; + String String4; + String String5; + + + uint32_t timestamp_sec = 0u; + uint32_t timestamp_frac = 0u; + uint8_t *Data = nullptr; + int idx = 0; + int Par1 = 0; + int Par2 = 0; + int Par3 = 0; + int Par4 = 0; + int Par5 = 0; + + // The origin of the values in the event. See EventValueSource.h + EventValueSource::Enum Source = EventValueSource::Enum::VALUE_SOURCE_NOT_SET; + taskIndex_t TaskIndex = INVALID_TASK_INDEX; // index position in TaskSettings array, 0-11 + controllerIndex_t ControllerIndex = INVALID_CONTROLLER_INDEX; // index position in Settings.Controller, 0-3 +#if FEATURE_NOTIFIER + notifierIndex_t NotificationIndex = INVALID_NOTIFIER_INDEX; // index position in Settings.Notification, 0-3 +#endif + uint8_t BaseVarIndex = 0; + Sensor_VType sensorType = Sensor_VType::SENSOR_TYPE_NOT_SET; + uint8_t OriginTaskIndex = 0; +}; + +#endif // DATASTRUCTS_ESPEASY_EVENTSTRUCT_H diff --git a/src/src/DataStructs/ExtraTaskSettingsStruct.cpp b/src/src/DataStructs/ExtraTaskSettingsStruct.cpp index 37d82f4acc..3c42d964d1 100644 --- a/src/src/DataStructs/ExtraTaskSettingsStruct.cpp +++ b/src/src/DataStructs/ExtraTaskSettingsStruct.cpp @@ -20,6 +20,7 @@ void ExtraTaskSettingsStruct::clear() { version = EXTRA_TASK_SETTINGS_VERSION; for (int i = 0; i < VARS_PER_TASK; ++i) { TaskDeviceValueDecimals[i] = 2; + TaskDeviceErrorValue[i] = NAN; } } @@ -43,7 +44,7 @@ void ExtraTaskSettingsStruct::validate() { // Need to initialize the newly added fields for (uint8_t i = 0; i < VARS_PER_TASK; ++i) { setIgnoreRangeCheck(i); - TaskDeviceErrorValue[i] = 0.0f; + TaskDeviceErrorValue[i] = NAN; VariousBits[i] = 0u; } } @@ -74,7 +75,7 @@ void ExtraTaskSettingsStruct::clearUnusedValueNames(uint8_t usedVars) { ZERO_FILL(TaskDeviceValueNames[i]); TaskDeviceValueDecimals[i] = 2; setIgnoreRangeCheck(i); - TaskDeviceErrorValue[i] = 0.0f; + TaskDeviceErrorValue[i] = NAN; VariousBits[i] = 0; } } diff --git a/src/src/DataStructs/FactoryDefault_WiFi_NVS.cpp b/src/src/DataStructs/FactoryDefault_WiFi_NVS.cpp index b97559841d..8cc486e544 100644 --- a/src/src/DataStructs/FactoryDefault_WiFi_NVS.cpp +++ b/src/src/DataStructs/FactoryDefault_WiFi_NVS.cpp @@ -1,109 +1,111 @@ -#include "../DataStructs/FactoryDefault_WiFi_NVS.h" - -#ifdef ESP32 - -# include "../Globals/Settings.h" -# include "../Globals/SecuritySettings.h" -# include "../Helpers/StringConverter.h" - -// Max. 15 char keys for ESPEasy Factory Default marked keys -# define FACTORY_DEFAULT_NVS_SSID1_KEY "WIFI_SSID1" -# define FACTORY_DEFAULT_NVS_WPA_PASS1_KEY "WIFI_PASS1" -# define FACTORY_DEFAULT_NVS_SSID2_KEY "WIFI_SSID2" -# define FACTORY_DEFAULT_NVS_WPA_PASS2_KEY "WIFI_PASS2" -# define FACTORY_DEFAULT_NVS_AP_PASS_KEY "WIFI_AP_PASS" -# define FACTORY_DEFAULT_NVS_WIFI_FLAGS_KEY "WIFI_Flags" - - -void FactoryDefault_WiFi_NVS::fromSettings() { - bits.IncludeHiddenSSID = Settings.IncludeHiddenSSID(); - bits.ApDontForceSetup = Settings.ApDontForceSetup(); - bits.DoNotStartAP = Settings.DoNotStartAP(); - bits.ForceWiFi_bg_mode = Settings.ForceWiFi_bg_mode(); - bits.WiFiRestart_connection_lost = Settings.WiFiRestart_connection_lost(); - bits.WifiNoneSleep = Settings.WifiNoneSleep(); - bits.gratuitousARP = Settings.gratuitousARP(); - bits.UseMaxTXpowerForSending = Settings.UseMaxTXpowerForSending(); - bits.UseLastWiFiFromRTC = Settings.UseLastWiFiFromRTC(); - bits.WaitWiFiConnect = Settings.WaitWiFiConnect(); - bits.SDK_WiFi_autoreconnect = Settings.SDK_WiFi_autoreconnect(); - bits.HiddenSSID_SlowConnectPerBSSID = Settings.HiddenSSID_SlowConnectPerBSSID(); - bits.EnableIPv6 = Settings.EnableIPv6(); -} - -void FactoryDefault_WiFi_NVS::applyToSettings() const { - Settings.IncludeHiddenSSID(bits.IncludeHiddenSSID); - Settings.ApDontForceSetup(bits.ApDontForceSetup); - Settings.DoNotStartAP(bits.DoNotStartAP); - Settings.ForceWiFi_bg_mode(bits.ForceWiFi_bg_mode); - Settings.WiFiRestart_connection_lost(bits.WiFiRestart_connection_lost); - Settings.WifiNoneSleep(bits.WifiNoneSleep); - Settings.gratuitousARP(bits.gratuitousARP); - Settings.UseMaxTXpowerForSending(bits.UseMaxTXpowerForSending); - Settings.UseLastWiFiFromRTC(bits.UseLastWiFiFromRTC); - Settings.WaitWiFiConnect(bits.WaitWiFiConnect); - Settings.SDK_WiFi_autoreconnect(bits.SDK_WiFi_autoreconnect); - Settings.HiddenSSID_SlowConnectPerBSSID(bits.HiddenSSID_SlowConnectPerBSSID); - Settings.EnableIPv6(bits.EnableIPv6); -} - -struct FactoryDefault_WiFi_NVS_securityPrefs { - FactoryDefault_WiFi_NVS_securityPrefs(const __FlashStringHelper *pref, - char *dest, - size_t size) - : _pref(pref), _dest(dest), _size(size) {} - - const __FlashStringHelper *_pref; - char *_dest; - size_t _size; -}; - -const FactoryDefault_WiFi_NVS_securityPrefs _WiFi_NVS_securityPrefs_values[] = { - { F(FACTORY_DEFAULT_NVS_SSID1_KEY), SecuritySettings.WifiSSID, sizeof(SecuritySettings.WifiSSID) }, - { F(FACTORY_DEFAULT_NVS_WPA_PASS1_KEY), SecuritySettings.WifiKey, sizeof(SecuritySettings.WifiKey) }, - { F(FACTORY_DEFAULT_NVS_SSID2_KEY), SecuritySettings.WifiSSID2, sizeof(SecuritySettings.WifiSSID2) }, - { F(FACTORY_DEFAULT_NVS_WPA_PASS2_KEY), SecuritySettings.WifiKey2, sizeof(SecuritySettings.WifiKey2) }, - { F(FACTORY_DEFAULT_NVS_AP_PASS_KEY), SecuritySettings.WifiAPKey, sizeof(SecuritySettings.WifiAPKey) } -}; - - -bool FactoryDefault_WiFi_NVS::applyToSettings_from_NVS(ESPEasy_NVS_Helper& preferences) { - String tmp; - constexpr unsigned nr__WiFi_NVS_securityPrefs_values = NR_ELEMENTS(_WiFi_NVS_securityPrefs_values); - - for (unsigned i = 0; i < nr__WiFi_NVS_securityPrefs_values; ++i) { - if (preferences.getPreference(_WiFi_NVS_securityPrefs_values[i]._pref, tmp)) { - safe_strncpy(_WiFi_NVS_securityPrefs_values[i]._dest, tmp, _WiFi_NVS_securityPrefs_values[i]._size); - } - } - - if (!preferences.getPreference(F(FACTORY_DEFAULT_NVS_WIFI_FLAGS_KEY), data)) { - return false; - } - - applyToSettings(); - return true; -} - -void FactoryDefault_WiFi_NVS::fromSettings_to_NVS(ESPEasy_NVS_Helper& preferences) { - fromSettings(); - preferences.setPreference(F(FACTORY_DEFAULT_NVS_WIFI_FLAGS_KEY), data); - - // Store WiFi credentials - constexpr unsigned nr__WiFi_NVS_securityPrefs_values = NR_ELEMENTS(_WiFi_NVS_securityPrefs_values); - - for (unsigned i = 0; i < nr__WiFi_NVS_securityPrefs_values; ++i) { - preferences.setPreference(_WiFi_NVS_securityPrefs_values[i]._pref, String(_WiFi_NVS_securityPrefs_values[i]._dest)); - } -} - -void FactoryDefault_WiFi_NVS::clear_from_NVS(ESPEasy_NVS_Helper& preferences) { - constexpr unsigned nr__WiFi_NVS_securityPrefs_values = NR_ELEMENTS(_WiFi_NVS_securityPrefs_values); - - for (unsigned i = 0; i < nr__WiFi_NVS_securityPrefs_values; ++i) { - preferences.remove(_WiFi_NVS_securityPrefs_values[i]._pref); - } - preferences.remove(F(FACTORY_DEFAULT_NVS_WIFI_FLAGS_KEY)); -} - -#endif // ifdef ESP32 +#include "../DataStructs/FactoryDefault_WiFi_NVS.h" + +#ifdef ESP32 + +# include "../Globals/Settings.h" +# include "../Globals/SecuritySettings.h" +# include "../Helpers/StringConverter.h" + +// Max. 15 char keys for ESPEasy Factory Default marked keys +# define FACTORY_DEFAULT_NVS_SSID1_KEY "WIFI_SSID1" +# define FACTORY_DEFAULT_NVS_WPA_PASS1_KEY "WIFI_PASS1" +# define FACTORY_DEFAULT_NVS_SSID2_KEY "WIFI_SSID2" +# define FACTORY_DEFAULT_NVS_WPA_PASS2_KEY "WIFI_PASS2" +# define FACTORY_DEFAULT_NVS_AP_PASS_KEY "WIFI_AP_PASS" +# define FACTORY_DEFAULT_NVS_WIFI_FLAGS_KEY "WIFI_Flags" + + +void FactoryDefault_WiFi_NVS::fromSettings() { + bits.IncludeHiddenSSID = Settings.IncludeHiddenSSID(); + bits.ApDontForceSetup = Settings.ApDontForceSetup(); + bits.DoNotStartAP = Settings.DoNotStartAP(); + bits.ForceWiFi_bg_mode = Settings.ForceWiFi_bg_mode(); + bits.WiFiRestart_connection_lost = Settings.WiFiRestart_connection_lost(); + bits.WifiNoneSleep = Settings.WifiNoneSleep(); + bits.gratuitousARP = Settings.gratuitousARP(); + bits.UseMaxTXpowerForSending = Settings.UseMaxTXpowerForSending(); + bits.UseLastWiFiFromRTC = Settings.UseLastWiFiFromRTC(); + bits.WaitWiFiConnect = Settings.WaitWiFiConnect(); + bits.SDK_WiFi_autoreconnect = Settings.SDK_WiFi_autoreconnect(); + bits.HiddenSSID_SlowConnectPerBSSID = Settings.HiddenSSID_SlowConnectPerBSSID(); + bits.EnableIPv6 = Settings.EnableIPv6(); + bits.PassiveWiFiScan = Settings.PassiveWiFiScan(); +} + +void FactoryDefault_WiFi_NVS::applyToSettings() const { + Settings.IncludeHiddenSSID(bits.IncludeHiddenSSID); + Settings.ApDontForceSetup(bits.ApDontForceSetup); + Settings.DoNotStartAP(bits.DoNotStartAP); + Settings.ForceWiFi_bg_mode(bits.ForceWiFi_bg_mode); + Settings.WiFiRestart_connection_lost(bits.WiFiRestart_connection_lost); + Settings.WifiNoneSleep(bits.WifiNoneSleep); + Settings.gratuitousARP(bits.gratuitousARP); + Settings.UseMaxTXpowerForSending(bits.UseMaxTXpowerForSending); + Settings.UseLastWiFiFromRTC(bits.UseLastWiFiFromRTC); + Settings.WaitWiFiConnect(bits.WaitWiFiConnect); + Settings.SDK_WiFi_autoreconnect(bits.SDK_WiFi_autoreconnect); + Settings.HiddenSSID_SlowConnectPerBSSID(bits.HiddenSSID_SlowConnectPerBSSID); + Settings.EnableIPv6(bits.EnableIPv6); + Settings.PassiveWiFiScan(bits.PassiveWiFiScan); +} + +struct FactoryDefault_WiFi_NVS_securityPrefs { + FactoryDefault_WiFi_NVS_securityPrefs(const __FlashStringHelper *pref, + char *dest, + size_t size) + : _pref(pref), _dest(dest), _size(size) {} + + const __FlashStringHelper *_pref; + char *_dest; + size_t _size; +}; + +const FactoryDefault_WiFi_NVS_securityPrefs _WiFi_NVS_securityPrefs_values[] = { + { F(FACTORY_DEFAULT_NVS_SSID1_KEY), SecuritySettings.WifiSSID, sizeof(SecuritySettings.WifiSSID) }, + { F(FACTORY_DEFAULT_NVS_WPA_PASS1_KEY), SecuritySettings.WifiKey, sizeof(SecuritySettings.WifiKey) }, + { F(FACTORY_DEFAULT_NVS_SSID2_KEY), SecuritySettings.WifiSSID2, sizeof(SecuritySettings.WifiSSID2) }, + { F(FACTORY_DEFAULT_NVS_WPA_PASS2_KEY), SecuritySettings.WifiKey2, sizeof(SecuritySettings.WifiKey2) }, + { F(FACTORY_DEFAULT_NVS_AP_PASS_KEY), SecuritySettings.WifiAPKey, sizeof(SecuritySettings.WifiAPKey) } +}; + + +bool FactoryDefault_WiFi_NVS::applyToSettings_from_NVS(ESPEasy_NVS_Helper& preferences) { + String tmp; + constexpr unsigned nr__WiFi_NVS_securityPrefs_values = NR_ELEMENTS(_WiFi_NVS_securityPrefs_values); + + for (unsigned i = 0; i < nr__WiFi_NVS_securityPrefs_values; ++i) { + if (preferences.getPreference(_WiFi_NVS_securityPrefs_values[i]._pref, tmp)) { + safe_strncpy(_WiFi_NVS_securityPrefs_values[i]._dest, tmp, _WiFi_NVS_securityPrefs_values[i]._size); + } + } + + if (!preferences.getPreference(F(FACTORY_DEFAULT_NVS_WIFI_FLAGS_KEY), data)) { + return false; + } + + applyToSettings(); + return true; +} + +void FactoryDefault_WiFi_NVS::fromSettings_to_NVS(ESPEasy_NVS_Helper& preferences) { + fromSettings(); + preferences.setPreference(F(FACTORY_DEFAULT_NVS_WIFI_FLAGS_KEY), data); + + // Store WiFi credentials + constexpr unsigned nr__WiFi_NVS_securityPrefs_values = NR_ELEMENTS(_WiFi_NVS_securityPrefs_values); + + for (unsigned i = 0; i < nr__WiFi_NVS_securityPrefs_values; ++i) { + preferences.setPreference(_WiFi_NVS_securityPrefs_values[i]._pref, String(_WiFi_NVS_securityPrefs_values[i]._dest)); + } +} + +void FactoryDefault_WiFi_NVS::clear_from_NVS(ESPEasy_NVS_Helper& preferences) { + constexpr unsigned nr__WiFi_NVS_securityPrefs_values = NR_ELEMENTS(_WiFi_NVS_securityPrefs_values); + + for (unsigned i = 0; i < nr__WiFi_NVS_securityPrefs_values; ++i) { + preferences.remove(_WiFi_NVS_securityPrefs_values[i]._pref); + } + preferences.remove(F(FACTORY_DEFAULT_NVS_WIFI_FLAGS_KEY)); +} + +#endif // ifdef ESP32 diff --git a/src/src/DataStructs/FactoryDefault_WiFi_NVS.h b/src/src/DataStructs/FactoryDefault_WiFi_NVS.h index 59a8d3b92c..e3638b084f 100644 --- a/src/src/DataStructs/FactoryDefault_WiFi_NVS.h +++ b/src/src/DataStructs/FactoryDefault_WiFi_NVS.h @@ -1,56 +1,57 @@ -#ifndef DATASTRUCTS_FACTORYDEFAULT_WIFI_NVS_H -#define DATASTRUCTS_FACTORYDEFAULT_WIFI_NVS_H - - -#include "../../ESPEasy_common.h" - -#ifdef ESP32 - -# include "../Helpers/ESPEasy_NVS_Helper.h" - - -class FactoryDefault_WiFi_NVS { -private: - - void fromSettings(); - - void applyToSettings() const; - -public: - - bool applyToSettings_from_NVS(ESPEasy_NVS_Helper& preferences); - - void fromSettings_to_NVS(ESPEasy_NVS_Helper& preferences); - - void clear_from_NVS(ESPEasy_NVS_Helper& preferences); - -private: - - union { - struct { - uint64_t IncludeHiddenSSID : 1; - uint64_t ApDontForceSetup : 1; - uint64_t DoNotStartAP : 1; - uint64_t ForceWiFi_bg_mode : 1; - uint64_t WiFiRestart_connection_lost : 1; - uint64_t WifiNoneSleep : 1; - uint64_t gratuitousARP : 1; - uint64_t UseMaxTXpowerForSending : 1; - uint64_t UseLastWiFiFromRTC : 1; - uint64_t WaitWiFiConnect : 1; - uint64_t SDK_WiFi_autoreconnect : 1; - uint64_t HiddenSSID_SlowConnectPerBSSID : 1; - uint64_t EnableIPv6 : 1; - - uint64_t unused : 51; - } bits; - - uint64_t data{}; - }; -}; - - -#endif // ifdef ESP32 - - -#endif // ifndef DATASTRUCTS_FACTORYDEFAULT_WIFI_NVS_H +#ifndef DATASTRUCTS_FACTORYDEFAULT_WIFI_NVS_H +#define DATASTRUCTS_FACTORYDEFAULT_WIFI_NVS_H + + +#include "../../ESPEasy_common.h" + +#ifdef ESP32 + +# include "../Helpers/ESPEasy_NVS_Helper.h" + + +class FactoryDefault_WiFi_NVS { +private: + + void fromSettings(); + + void applyToSettings() const; + +public: + + bool applyToSettings_from_NVS(ESPEasy_NVS_Helper& preferences); + + void fromSettings_to_NVS(ESPEasy_NVS_Helper& preferences); + + void clear_from_NVS(ESPEasy_NVS_Helper& preferences); + +private: + + union { + struct { + uint64_t IncludeHiddenSSID : 1; + uint64_t ApDontForceSetup : 1; + uint64_t DoNotStartAP : 1; + uint64_t ForceWiFi_bg_mode : 1; + uint64_t WiFiRestart_connection_lost : 1; + uint64_t WifiNoneSleep : 1; + uint64_t gratuitousARP : 1; + uint64_t UseMaxTXpowerForSending : 1; + uint64_t UseLastWiFiFromRTC : 1; + uint64_t WaitWiFiConnect : 1; + uint64_t SDK_WiFi_autoreconnect : 1; + uint64_t HiddenSSID_SlowConnectPerBSSID : 1; + uint64_t EnableIPv6 : 1; + uint64_t PassiveWiFiScan : 1; + + uint64_t unused : 50; + } bits; + + uint64_t data{}; + }; +}; + + +#endif // ifdef ESP32 + + +#endif // ifndef DATASTRUCTS_FACTORYDEFAULT_WIFI_NVS_H diff --git a/src/src/DataStructs/NTP_packet.cpp b/src/src/DataStructs/NTP_packet.cpp new file mode 100644 index 0000000000..6e943b7419 --- /dev/null +++ b/src/src/DataStructs/NTP_packet.cpp @@ -0,0 +1,222 @@ +#include "../DataStructs/NTP_packet.h" + +#include "../Helpers/ESPEasy_time_calc.h" + +#include "../Helpers/StringConverter.h" + +NTP_packet::NTP_packet() +{ + // li, vn, and mode: + // - li. 2 bits. Leap indicator. + // 0 = no warning + // 1 = last minute of the day has 61 seconds + // 2 = last minute of the day has 59 seconds + // 3 = unknown (clock unsynchronized) + // - vn. 3 bits. Version number of the protocol. (0b100 = v4) + // - mode. 3 bits. Client will pick mode 3 for client. + // 0 = reserved + // 1 = symmetric active + // 2 = symmetric passive + // 3 = client + // 4 = server + // 5 = broadcast + // 6 = NTP control message + // 7 = reserved for private use + data[0] = 0b11100011; // Unsynchronized, V4, client mode + + // Stratum level of the local clock. + // 0 = unspecified or invalid + // 1 = primary server (e.g., equipped with a GPS receiver) + // 2-15 = secondary server (via NTP) + // 16 = unsynchronized + // 17-255 = reserved + data[1] = 0u; + + // Poll: 8-bit signed integer representing the maximum interval between + // successive messages, in log2 seconds. Suggested default limits for + // minimum and maximum poll intervals are 6 and 10, respectively. + data[2] = 6u; + + // Precision: 8-bit signed integer representing the precision of the + // system clock, in log2 seconds. For instance, a value of -18 + // corresponds to a precision of about one microsecond. The precision + // can be determined when the service first starts up as the minimum + // time of several iterations to read the system clock. + data[3] = 0xEC; // -20 -> 2^-20 sec -> microsec precision. + + constexpr int8_t precision = 0xEC; + + // Reference clock identifier. ASCII: "1N14" + data[12] = 0x31; + data[13] = 0x4E; + data[14] = 0x31; + data[15] = 0x34; +} + +uint32_t NTP_packet::readWord(uint8_t startIndex) const +{ + uint32_t res{}; + + res = (uint32_t)data[startIndex] << 24; + res |= (uint32_t)data[startIndex + 1] << 16; + res |= (uint32_t)data[startIndex + 2] << 8; + res |= (uint32_t)data[startIndex + 3]; + return res; +} + +uint64_t NTP_packet::ntp_timestamp_to_Unix_time(uint8_t startIndex) const { + // Apply offset from 1900/01/01 to 1970/01/01 + constexpr uint64_t offset_since_1900 = 2208988800ULL * 1000000ull; + + const uint32_t Tm_s = readWord(startIndex); + const uint32_t Tm_f = readWord(startIndex + 4); + uint64_t usec_since_1900 = sec_time_frac_to_Micros(Tm_s, Tm_f); + + if (usec_since_1900 < offset_since_1900) { + // Fix overflow which will occur in 2036 + usec_since_1900 += (4294967296ull * 1000000ull); + } + return usec_since_1900 - offset_since_1900; +} + +void NTP_packet::writeWord(uint32_t value, uint8_t startIndex) +{ + data[startIndex] = (value >> 24) & 0xFF; + data[startIndex + 1] = (value >> 16) & 0xFF; + data[startIndex + 2] = (value >> 8) & 0xFF; + data[startIndex + 3] = (value) & 0xFF; +} + +bool NTP_packet::isUnsynchronized() const +{ + return (data[0] /*li_vn_mode*/ & 0b11000000) == 0b11000000; +} + +uint64_t NTP_packet::getReferenceTimestamp_usec() const +{ + return ntp_timestamp_to_Unix_time(16); +} + +uint64_t NTP_packet::getOriginTimestamp_usec() const +{ + return ntp_timestamp_to_Unix_time(24); +} + +uint64_t NTP_packet::getReceiveTimestamp_usec() const +{ + return ntp_timestamp_to_Unix_time(32); +} + +uint64_t NTP_packet::getTransmitTimestamp_usec() const +{ + return ntp_timestamp_to_Unix_time(40); +} + +void NTP_packet::setTxTimestamp(uint64_t micros) +{ + constexpr uint64_t offset_since_1900 = 2208988800ULL * 1000000ull; + + micros += offset_since_1900; + uint32_t tmp_origTm_f{}; + + writeWord(micros_to_sec_time_frac(micros, tmp_origTm_f), 40); + writeWord(tmp_origTm_f, 44); +} + +bool NTP_packet::compute_usec( + uint64_t localTXTimestamp_usec, + uint64_t localRxTimestamp_usec, + uint64_t& offset_usec, + uint64_t& roundtripDelay_usec) const +{ + int64_t t1 = getOriginTimestamp_usec(); + + if (t1 == 0) { + t1 = localTXTimestamp_usec; + } + + const int64_t t2 = getReceiveTimestamp_usec(); + const int64_t t3 = getTransmitTimestamp_usec(); + + if ((t3 == 0) || (t3 < t2)) { + // No time stamp received + return false; + } + const int64_t t4 = localRxTimestamp_usec; + + offset_usec = (t2 - t1) + (t3 - t4); + offset_usec /= 2; + + roundtripDelay_usec = (t4 - t1) - (t3 - t2); + return true; +} + +String NTP_packet::getRefID_str(bool& isError) const +{ + String refID; + + const uint8_t stratum = data[1]; + + isError = false; + + if ((stratum == 0) || (stratum == 1)) { + refID = strformat(F("%c%c%c%c"), + static_cast(data[12] & 0x7F), + static_cast(data[13] & 0x7F), + static_cast(data[14] & 0x7F), + static_cast(data[15] & 0x7F)); + + if (stratum == 0) { + if (refID.equals(F("DENY")) || + refID.equals(F("RSTR"))) { + // For kiss codes DENY and RSTR, the client MUST + // demobilize any associations to that server and + // stop sending packets to that server; + // DENY = Access denied by remote server. + // RSTR = Access denied due to local policy. + isError = true; + } else if (refID.equals(F("RATE"))) { + // For kiss code RATE, the client MUST immediately reduce its + // polling interval to that server and continue to reduce it each + // time it receives a RATE kiss code. + } + } + } else { + const IPAddress addrv4(readWord(12)); + refID = addrv4.toString(); + } + return refID; +} + +#ifndef BUILD_NO_DEBUG +String NTP_packet::toDebugString() const +{ + const uint8_t li = (data[0] >> 6) & 0x3; + const uint8_t ver = (data[0] >> 3) & 0x7; + const uint8_t mode = data[0] & 0x7; + + bool isError{}; + + return strformat( + F(" li: %u ver: %u mode: %u\n" + " strat: %u poll: %d prec: %d\n" + " del: %u disp: %u refID: '%s'\n" + " refTm_s : %u refTm_f : %u\n" + " origTm_s: %u origTm_f: %u\n" + " rxTm_s : %u rxTm_f : %u\n" + " txTm_s : %u txTm_f : %u\n"), + li, ver, mode, // li_vn_mode + data[1], // stratum, + (int8_t)(data[2]), // poll in log2 seconds + (int8_t)(data[3]), // precision in log2 seconds + readWord(4), readWord(8), // rootDelay, rootDispersion, + getRefID_str(isError).c_str(), + readWord(16), unix_time_frac_to_micros(readWord(20)), // refTm_s, unix_time_frac_to_micros(refTm_f), + readWord(24), unix_time_frac_to_micros(readWord(28)), // origTm_s, unix_time_frac_to_micros(origTm_f), + readWord(32), unix_time_frac_to_micros(readWord(36)), // rxTm_s, unix_time_frac_to_micros(rxTm_f), + readWord(40), unix_time_frac_to_micros(readWord(44)) // txTm_s, unix_time_frac_to_micros(txTm_f) + + ); +} + +#endif // ifndef BUILD_NO_DEBUG diff --git a/src/src/DataStructs/NTP_packet.h b/src/src/DataStructs/NTP_packet.h new file mode 100644 index 0000000000..8852772d34 --- /dev/null +++ b/src/src/DataStructs/NTP_packet.h @@ -0,0 +1,57 @@ +#ifndef DATASTRUCTS_NTP_PACKET_H +#define DATASTRUCTS_NTP_PACKET_H + +#include + +struct __attribute__((__packed__)) NTP_packet +{ + NTP_packet(); + bool isUnsynchronized() const; + + // Reference Timestamp: Time when the system clock was last set or corrected, in NTP timestamp format. + // Timestamp is Unixtime in microseconds + uint64_t getReferenceTimestamp_usec() const; + + // Origin Timestamp (org): Time at the client when the request departed for the server, in NTP timestamp format. + // Timestamp is Unixtime in microseconds + uint64_t getOriginTimestamp_usec() const; + + // Receive Timestamp (rec): Time at the server when the request arrived from the client, in NTP timestamp format. + // Timestamp is Unixtime in microseconds + uint64_t getReceiveTimestamp_usec() const; + + // Transmit Timestamp (xmt): Time at the server when the response left for the client, in NTP timestamp format. + // The most important field the client cares about. + // Timestamp is Unixtime in microseconds + uint64_t getTransmitTimestamp_usec() const; + + + // Before sending, the TX-timestamp of the local machine must be set. + // This will be returned in the reply as "Origin" timestamp + void setTxTimestamp(uint64_t micros); + + // The "Offset", the time difference of the two computer clocks + // The "Delay", the time that was needed to transfer the packet in the network + bool compute_usec( + uint64_t localTXTimestamp_usec, + uint64_t localRxTimestamp_usec, + uint64_t& offset_usec, + uint64_t& roundtripDelay_usec) const; + + String getRefID_str(bool& isError) const; + +#ifndef BUILD_NO_DEBUG + String toDebugString() const; +#endif // ifndef BUILD_NO_DEBUG + + uint8_t data[48]{}; + +private: + + uint32_t readWord(uint8_t startIndex) const; + void writeWord(uint32_t value, + uint8_t startIndex); + uint64_t ntp_timestamp_to_Unix_time(uint8_t startIndex) const; +}; + +#endif // ifndef DATASTRUCTS_NTP_PACKET_H diff --git a/src/src/DataStructs/NodesHandler.cpp b/src/src/DataStructs/NodesHandler.cpp index db3c5e90cc..c75f83744e 100644 --- a/src/src/DataStructs/NodesHandler.cpp +++ b/src/src/DataStructs/NodesHandler.cpp @@ -504,7 +504,7 @@ void NodesHandler::updateThisNode() { } const NodeStruct * NodesHandler::getThisNode() { - node_time.now(); +// node_time.now(); updateThisNode(); MAC_address this_mac = NetworkMacAddress(); return getNodeByMac(this_mac.mac); diff --git a/src/src/DataStructs/PluginStats.cpp b/src/src/DataStructs/PluginStats.cpp index d21780dd88..cd2ee0adeb 100644 --- a/src/src/DataStructs/PluginStats.cpp +++ b/src/src/DataStructs/PluginStats.cpp @@ -3,52 +3,164 @@ #if FEATURE_PLUGIN_STATS # include "../../_Plugin_Helper.h" +# include "../Globals/TimeZone.h" + # include "../Helpers/ESPEasy_math.h" +# include "../Helpers/Memory.h" # include "../WebServer/Chart_JS.h" + PluginStats::PluginStats(uint8_t nrDecimals, float errorValue) : _errorValue(errorValue), - _nrDecimals(nrDecimals) + _nrDecimals(nrDecimals), + _plugin_stats_timestamps(nullptr) + +{ + // Try to allocate in PSRAM if possible + void *ptr = special_calloc(1, sizeof(PluginStatsBuffer_t)); + + if (ptr == nullptr) { _samples = nullptr; } + else { + _samples = new (ptr) PluginStatsBuffer_t(); + } + _errorValueIsNaN = isnan(_errorValue); + _minValue = std::numeric_limits::max(); + _maxValue = std::numeric_limits::lowest(); + _minValueTimestamp = 0; + _maxValueTimestamp = 0; +} +PluginStats::~PluginStats() { - _errorValueIsNaN = isnan(_errorValue); - _minValue = std::numeric_limits::max(); - _maxValue = std::numeric_limits::lowest(); + if (_samples != nullptr) { + free(_samples); + + // delete _samples; + } + _samples = nullptr; + _plugin_stats_timestamps = nullptr; +} + +void PluginStats::processTimeSet(const double& time_offset) +{ + // Check to see if there was a unix time set before the system time was set + // For example when receiving data from a p2p node + const uint64_t cur_micros = getMicros64(); + const uint64_t offset_micros = time_offset * 1000000ull; + + if ((_maxValueTimestamp > cur_micros) && (_maxValueTimestamp > offset_micros)) { + _maxValueTimestamp -= offset_micros; + } + + if ((_minValueTimestamp > cur_micros) && (_minValueTimestamp > offset_micros)) { + _minValueTimestamp -= offset_micros; + } } bool PluginStats::push(float value) { - return _samples.push(value); + if (_samples == nullptr) { return false; } + return _samples->push(value); } -void PluginStats::trackPeak(float value) +bool PluginStats::matchesLastTwoEntries(float value) const { - if (value > _maxValue) { _maxValue = value; } + const size_t nrSamples = getNrSamples(); + + if (nrSamples < 2) { return false; } + + const float last = (*_samples)[nrSamples - 1]; + const float beforeLast = (*_samples)[nrSamples - 2]; + + const String value_str = toString(value, _nrDecimals); + + return + toString(last, _nrDecimals).equals(value_str) && + toString(beforeLast, _nrDecimals).equals(value_str); + - if (value < _minValue) { _minValue = value; } + /* + const bool value_valid = isValidFloat(value); + const bool last_valid = isValidFloat(last); + + if (value_valid != last_valid) { + return false; + } + const bool beforeLast_valid = isValidFloat(beforeLast); + + if (value_valid != beforeLast_valid) { + return false; + } + + if (value_valid) { + return + approximatelyEqual(value, last) && + approximatelyEqual(value, beforeLast); + } + return true; + */ +} + +void PluginStats::trackPeak(float value, int64_t timestamp) +{ + if ((value > _maxValue) || (value < _minValue)) { + if (timestamp == 0) { + // Make sure both extremes are flagged with the same timestamp. + timestamp = getMicros64(); + } + + if (value > _maxValue) { + _maxValueTimestamp = timestamp; + _maxValue = value; + } + + if (value < _minValue) { + _minValueTimestamp = timestamp; + _minValue = value; + } + } } void PluginStats::resetPeaks() { - _minValue = std::numeric_limits::max(); - _maxValue = std::numeric_limits::lowest(); + _minValue = std::numeric_limits::max(); + _maxValue = std::numeric_limits::lowest(); + _minValueTimestamp = 0; + _maxValueTimestamp = 0; +} + +void PluginStats::clearSamples() { + if (_samples != nullptr) { + _samples->clear(); + } +} + +size_t PluginStats::getNrSamples() const { + if (_samples == nullptr) { return 0u; } + return _samples->size(); +} + +float PluginStats::getSampleAvg() const { + return getSampleAvg(getNrSamples()); } float PluginStats::getSampleAvg(PluginStatsBuffer_t::index_t lastNrSamples) const { - if (_samples.size() == 0) { return _errorValue; } + const size_t nrSamples = getNrSamples(); + + if (nrSamples == 0) { return _errorValue; } float sum = 0.0f; PluginStatsBuffer_t::index_t i = 0; - if (lastNrSamples < _samples.size()) { - i = _samples.size() - lastNrSamples; + if (lastNrSamples < nrSamples) { + i = nrSamples - lastNrSamples; } PluginStatsBuffer_t::index_t samplesUsed = 0; - for (; i < _samples.size(); ++i) { - const float sample(_samples[i]); + for (; i < nrSamples; ++i) { + const float sample((*_samples)[i]); if (usableValue(sample)) { ++samplesUsed; @@ -60,22 +172,71 @@ float PluginStats::getSampleAvg(PluginStatsBuffer_t::index_t lastNrSamples) cons return sum / samplesUsed; } +float PluginStats::getSampleAvg_time(PluginStatsBuffer_t::index_t lastNrSamples, uint64_t& totalDuration_usec) const +{ + const size_t nrSamples = getNrSamples(); + + totalDuration_usec = 0u; + + if ((nrSamples == 0) || (_plugin_stats_timestamps == nullptr)) { + return _errorValue; + } + + PluginStatsBuffer_t::index_t i = 0; + + if (lastNrSamples < nrSamples) { + i = nrSamples - lastNrSamples; + } + + int64_t lastTimestamp = 0; + float lastValue = 0.0f; + bool lastValueUsable = false; + float sum = 0.0f; + + for (; i < nrSamples; ++i) { + const float sample((*_samples)[i]); + const int64_t curTimestamp = (*_plugin_stats_timestamps)[i]; + const bool curValueUsable = usableValue(sample); + + if ((lastTimestamp != 0) && lastValueUsable) { + const int64_t duration_usec = abs(timeDiff64(lastTimestamp, curTimestamp)); + + if (curValueUsable) { + // Old and new value usable, take average of this period. + sum += ((lastValue + sample) / 2.0f) * duration_usec; + } else { + // New value is not usable, so just add the last value for the duration. + sum += lastValue * duration_usec; + } + totalDuration_usec += duration_usec; + } + + lastValueUsable = curValueUsable; + lastTimestamp = curTimestamp; + lastValue = sample; + } + + if (totalDuration_usec == 0) { return _errorValue; } + return sum / totalDuration_usec; +} + float PluginStats::getSampleStdDev(PluginStatsBuffer_t::index_t lastNrSamples) const { - float variance = 0.0f; - const float average = getSampleAvg(lastNrSamples); + const size_t nrSamples = getNrSamples(); + float variance = 0.0f; + const float average = getSampleAvg(lastNrSamples); if (!usableValue(average)) { return 0.0f; } PluginStatsBuffer_t::index_t i = 0; - if (lastNrSamples < _samples.size()) { - i = _samples.size() - lastNrSamples; + if (lastNrSamples < nrSamples) { + i = nrSamples - lastNrSamples; } PluginStatsBuffer_t::index_t samplesUsed = 0; - for (; i < _samples.size(); ++i) { - const float sample(_samples[i]); + for (; i < nrSamples; ++i) { + const float sample((*_samples)[i]); if (usableValue(sample)) { ++samplesUsed; @@ -92,20 +253,22 @@ float PluginStats::getSampleStdDev(PluginStatsBuffer_t::index_t lastNrSamples) c float PluginStats::getSampleExtreme(PluginStatsBuffer_t::index_t lastNrSamples, bool getMax) const { - if (_samples.size() == 0) { return _errorValue; } + const size_t nrSamples = getNrSamples(); + + if (nrSamples == 0) { return _errorValue; } PluginStatsBuffer_t::index_t i = 0; - if (lastNrSamples < _samples.size()) { - i = _samples.size() - lastNrSamples; + if (lastNrSamples < nrSamples) { + i = nrSamples - lastNrSamples; } bool changed = false; float res = getMax ? INT_MIN : INT_MAX; - for (; i < _samples.size(); ++i) { - const float sample(_samples[i]); + for (; i < nrSamples; ++i) { + const float sample((*_samples)[i]); if (usableValue(sample)) { if ((getMax && (sample > res)) || @@ -123,25 +286,29 @@ float PluginStats::getSampleExtreme(PluginStatsBuffer_t::index_t lastNrSamples, float PluginStats::getSample(int lastNrSamples) const { - if ((_samples.size() == 0) || (_samples.size() < abs(lastNrSamples))) { return _errorValue; } + const size_t nrSamples = getNrSamples(); + + if ((nrSamples == 0) || (nrSamples < abs(lastNrSamples))) { return _errorValue; } PluginStatsBuffer_t::index_t i = 0; if (lastNrSamples > 0) { - i = _samples.size() - lastNrSamples; + i = nrSamples - lastNrSamples; } else if (lastNrSamples < 0) { i = abs(lastNrSamples) - 1; } - if (i < _samples.size()) { - return _samples[i]; + if (i < nrSamples) { + return (*_samples)[i]; } return _errorValue; } float PluginStats::operator[](PluginStatsBuffer_t::index_t index) const { - if (index < _samples.size()) { return _samples[index]; } + const size_t nrSamples = getNrSamples(); + + if (index < nrSamples) { return (*_samples)[index]; } return _errorValue; } @@ -240,14 +407,14 @@ bool PluginStats::plugin_get_config_value_base(struct EventStruct *event, String } } else if (matchedCommand(command, F("size"), nrSamples)) { // [taskname#valuename.size] Number of samples in memory - value = _samples.size(); + value = getNrSamples(); success = true; } else if (matchedCommand(command, F("sample"), nrSamples)) { success = nrSamples != 0; if (nrSamples == INT_MIN) { // [taskname#valuename.sample] Number of samples in memory. - value = _samples.size(); + value = getNrSamples(); success = true; } else { if (nrSamples != 0) { @@ -292,9 +459,20 @@ bool PluginStats::webformLoad_show_stats(struct EventStruct *event) const bool PluginStats::webformLoad_show_avg(struct EventStruct *event) const { if (getNrSamples() > 0) { - addRowLabel(concat(getLabel(), F(" Average"))); - addHtmlFloat(getSampleAvg(), _nrDecimals); + addRowLabel(concat(getLabel(), F(" Average / sample"))); + addHtmlFloat(getSampleAvg(), (_nrDecimals == 0) ? 1 : _nrDecimals); addHtml(strformat(F(" (%u samples)"), getNrSamples())); + + if (_plugin_stats_timestamps != nullptr) { + uint64_t totalDuration_usec = 0u; + const float avg_per_sec = getSampleAvg_time(totalDuration_usec); + + if (totalDuration_usec > 0) { + addRowLabel(concat(getLabel(), F(" Average / sec"))); + addHtmlFloat(avg_per_sec, (_nrDecimals == 0) ? 1 : _nrDecimals); + addHtml(strformat(F(" (%s duration)"), secondsToDayHourMinuteSecond_ms(totalDuration_usec).c_str())); + } + } return true; } return false; @@ -306,7 +484,7 @@ bool PluginStats::webformLoad_show_stdev(struct EventStruct *event) const if (usableValue(stdDev) && (getNrSamples() > 1)) { addRowLabel(concat(getLabel(), F(" std. dev"))); - addHtmlFloat(stdDev, _nrDecimals); + addHtmlFloat(stdDev, (_nrDecimals == 0) ? 1 : _nrDecimals); addHtml(strformat(F(" (%u samples)"), getNrSamples())); return true; } @@ -316,10 +494,53 @@ bool PluginStats::webformLoad_show_stdev(struct EventStruct *event) const bool PluginStats::webformLoad_show_peaks(struct EventStruct *event, bool include_peak_to_peak) const { if (hasPeaks() && (getNrSamples() > 1)) { - addRowLabel(concat(getLabel(), F(" Peak Low/High"))); - addHtmlFloat(getPeakLow(), _nrDecimals); - addHtml('/'); - addHtmlFloat(getPeakHigh(), _nrDecimals); + return webformLoad_show_peaks( + event, + getLabel(), + toString(getPeakLow(), _nrDecimals), + toString(getPeakHigh(), _nrDecimals), + include_peak_to_peak); + } + return false; +} + +bool PluginStats::webformLoad_show_peaks(struct EventStruct *event, + const String & label, + const String & lowValue, + const String & highValue, + bool include_peak_to_peak) const +{ + if (hasPeaks() && (getNrSamples() > 1)) { + uint32_t peakLow_frac{}; + uint32_t peakHigh_frac{}; + const uint32_t peakLow = node_time.systemMicros_to_Unixtime(getPeakLowTimestamp(), peakLow_frac); + const uint32_t peakHigh = node_time.systemMicros_to_Unixtime(getPeakHighTimestamp(), peakHigh_frac); + const uint32_t current = node_time.getUnixTime(); + const bool useTimeOnly = (current - peakLow) < 86400 && (current - peakHigh) < 86400; + struct tm ts; + breakTime(time_zone.toLocal(peakLow), ts); + + + addRowLabel(concat(label, F(" Peak Low"))); + addHtml(strformat( + F("%s @ %s.%03u"), + lowValue.c_str(), + useTimeOnly + ? formatTimeString(ts).c_str() + : formatDateTimeString(ts).c_str(), + unix_time_frac_to_millis(peakLow_frac))); + + + breakTime(time_zone.toLocal(peakHigh), ts); + + addRowLabel(concat(label, F(" Peak High"))); + addHtml(strformat( + F("%s @ %s.%03u"), + highValue.c_str(), + useTimeOnly + ? formatTimeString(ts).c_str() + : formatDateTimeString(ts).c_str(), + unix_time_frac_to_millis(peakHigh_frac))); if (include_peak_to_peak) { addRowLabel(concat(getLabel(), F(" Peak-to-peak"))); @@ -350,14 +571,15 @@ void PluginStats::plot_ChartJS_dataset() const add_ChartJS_dataset_header(_ChartJS_dataset_config); PluginStatsBuffer_t::index_t i = 0; + const size_t nrSamples = getNrSamples(); - for (; i < _samples.size(); ++i) { + for (; i < nrSamples; ++i) { if (i != 0) { addHtml(','); } - if (!isnan(_samples[i])) { - addHtmlFloat(_samples[i], _nrDecimals); + if (!isnan((*_samples)[i])) { + addHtmlFloat((*_samples)[i], _nrDecimals); } else { addHtml(F("null")); @@ -378,339 +600,4 @@ bool PluginStats::usableValue(float value) const return false; } -PluginStats_array::~PluginStats_array() -{ - for (size_t i = 0; i < VARS_PER_TASK; ++i) { - if (_plugin_stats[i] != nullptr) { - delete _plugin_stats[i]; - _plugin_stats[i] = nullptr; - } - } -} - -void PluginStats_array::initPluginStats(taskVarIndex_t taskVarIndex) -{ - if (taskVarIndex < VARS_PER_TASK) { - delete _plugin_stats[taskVarIndex]; - _plugin_stats[taskVarIndex] = nullptr; - - if (ExtraTaskSettings.enabledPluginStats(taskVarIndex)) { - # ifdef USE_SECOND_HEAP - HeapSelectIram ephemeral; - # endif // ifdef USE_SECOND_HEAP - - _plugin_stats[taskVarIndex] = new (std::nothrow) PluginStats( - ExtraTaskSettings.TaskDeviceValueDecimals[taskVarIndex], - ExtraTaskSettings.TaskDeviceErrorValue[taskVarIndex]); - - if (_plugin_stats[taskVarIndex] != nullptr) { - _plugin_stats[taskVarIndex]->setLabel(ExtraTaskSettings.TaskDeviceValueNames[taskVarIndex]); - # if FEATURE_CHART_JS - const __FlashStringHelper *colors[] = { F("#A52422"), F("#BEA57D"), F("#0F4C5C"), F("#A4BAB7") }; - _plugin_stats[taskVarIndex]->_ChartJS_dataset_config.color = colors[taskVarIndex]; - _plugin_stats[taskVarIndex]->_ChartJS_dataset_config.displayConfig = ExtraTaskSettings.getPluginStatsConfig(taskVarIndex); - # endif // if FEATURE_CHART_JS - } - } - } -} - -void PluginStats_array::clearPluginStats(taskVarIndex_t taskVarIndex) -{ - if (taskVarIndex < VARS_PER_TASK) { - if (_plugin_stats[taskVarIndex] != nullptr) { - delete _plugin_stats[taskVarIndex]; - _plugin_stats[taskVarIndex] = nullptr; - } - } -} - -bool PluginStats_array::hasStats() const -{ - for (size_t i = 0; i < VARS_PER_TASK; ++i) { - if (_plugin_stats[i] != nullptr) { return true; } - } - return false; -} - -bool PluginStats_array::hasPeaks() const -{ - for (size_t i = 0; i < VARS_PER_TASK; ++i) { - if ((_plugin_stats[i] != nullptr) && _plugin_stats[i]->hasPeaks()) { - return true; - } - } - return false; -} - -size_t PluginStats_array::nrSamplesPresent() const -{ - for (size_t i = 0; i < VARS_PER_TASK; ++i) { - if (_plugin_stats[i] != nullptr) { - return _plugin_stats[i]->getNrSamples(); - } - } - return 0; -} - -size_t PluginStats_array::nrPluginStats() const -{ - size_t res{}; - - for (size_t i = 0; i < VARS_PER_TASK; ++i) { - if (_plugin_stats[i] != nullptr) { - ++res; - } - } - return res; -} - -void PluginStats_array::pushPluginStatsValues(struct EventStruct *event, bool trackPeaks) -{ - if (validTaskIndex(event->TaskIndex)) { - const uint8_t valueCount = getValueCountForTask(event->TaskIndex); - const Sensor_VType sensorType = event->getSensorType(); - - for (size_t i = 0; i < valueCount; ++i) { - if (_plugin_stats[i] != nullptr) { - const float value = UserVar.getAsDouble(event->TaskIndex, i, sensorType); - _plugin_stats[i]->push(value); - - if (trackPeaks) { - _plugin_stats[i]->trackPeak(value); - } - } - } - } -} - -bool PluginStats_array::plugin_get_config_value_base(struct EventStruct *event, - String & string) const -{ - // Full value name is something like "taskvaluename.avg" - const String fullValueName = parseString(string, 1); - const String valueName = parseString(fullValueName, 1, '.'); - - for (taskVarIndex_t i = 0; i < VARS_PER_TASK; i++) - { - if (_plugin_stats[i] != nullptr) { - // Check case insensitive, since the user entered value name can have any case. - if (valueName.equalsIgnoreCase(getTaskValueName(event->TaskIndex, i))) - { - return _plugin_stats[i]->plugin_get_config_value_base(event, string); - } - } - } - return false; -} - -bool PluginStats_array::plugin_write_base(struct EventStruct *event, const String& string) -{ - bool success = false; - const String cmd = parseString(string, 1); // command - - const bool resetPeaks = equals(cmd, F("resetpeaks")); // Command: "taskname.resetPeaks" - const bool clearSamples = equals(cmd, F("clearsamples")); // Command: "taskname.clearSamples" - - if (resetPeaks || clearSamples) { - for (size_t i = 0; i < VARS_PER_TASK; ++i) { - if (_plugin_stats[i] != nullptr) { - if (resetPeaks) { - success = true; - _plugin_stats[i]->resetPeaks(); - } - - if (clearSamples) { - success = true; - _plugin_stats[i]->clearSamples(); - } - } - } - } - return success; -} - -bool PluginStats_array::webformLoad_show_stats(struct EventStruct *event) const -{ - bool somethingAdded = false; - - for (size_t i = 0; i < VARS_PER_TASK; ++i) { - if (_plugin_stats[i] != nullptr) { - if (_plugin_stats[i]->webformLoad_show_stats(event)) { - somethingAdded = true; - } - } - } - return somethingAdded; -} - -# if FEATURE_CHART_JS -void PluginStats_array::plot_ChartJS(bool onlyJSON) const -{ - const size_t nrSamples = nrSamplesPresent(); - - if (nrSamples == 0) { return; } - - // Chart Header - { - ChartJS_options_scales scales; - scales.add({ F("x") }); - - for (size_t i = 0; i < VARS_PER_TASK; ++i) { - if (_plugin_stats[i] != nullptr) { - ChartJS_options_scale scaleOption( - _plugin_stats[i]->_ChartJS_dataset_config.displayConfig, - _plugin_stats[i]->getLabel()); - scaleOption.axisTitle.color = _plugin_stats[i]->_ChartJS_dataset_config.color; - scales.add(scaleOption); - - _plugin_stats[i]->_ChartJS_dataset_config.axisID = scaleOption.axisID; - } - } - - scales.update_Yaxis_TickCount(); - - add_ChartJS_chart_header( - F("line"), - F("TaskStatsChart"), - {}, - 500 + (70 * (scales.nr_Y_scales() - 1)), - 500, - scales.toString(), - nrSamples, - onlyJSON); - } - - - // Add labels - addHtml(F("\"labels\":[")); - - for (size_t i = 0; i < nrSamples; ++i) { - if (i != 0) { - addHtml(','); - } - addHtmlInt(i); - } - addHtml(F("],\n\"datasets\":[")); - - - // Data sets - bool first = true; - for (size_t i = 0; i < VARS_PER_TASK; ++i) { - if (_plugin_stats[i] != nullptr) { - if (!first) { - addHtml(','); - } - first = false; - _plugin_stats[i]->plot_ChartJS_dataset(); - } - } - add_ChartJS_chart_footer(onlyJSON); -} - -void PluginStats_array::plot_ChartJS_scatter( - taskVarIndex_t values_X_axis_index, - taskVarIndex_t values_Y_axis_index, - const __FlashStringHelper *id, - const ChartJS_title & chartTitle, - const ChartJS_dataset_config& datasetConfig, - int width, - int height, - bool showAverage, - const String & options, - bool onlyJSON) const -{ - const PluginStats *stats_X = getPluginStats(values_X_axis_index); - const PluginStats *stats_Y = getPluginStats(values_Y_axis_index); - - if ((stats_X == nullptr) || (stats_Y == nullptr)) { - return; - } - - if ((stats_X->getNrSamples() < 2) || (stats_Y->getNrSamples() < 2)) { - return; - } - - String axisOptions; - - { - ChartJS_options_scales scales; - scales.add({ F("x"), stats_X->getLabel() }); - scales.add({ F("y"), stats_Y->getLabel() }); - axisOptions = scales.toString(); - } - - - const size_t nrSamples = stats_X->getNrSamples(); - - add_ChartJS_chart_header( - F("scatter"), - id, - chartTitle, - width, - height, - axisOptions, - nrSamples, - onlyJSON); - - // Add labels, which will be shown in a tooltip when hovering with the mouse over a point. - addHtml(F("\"labels\":[")); - - for (size_t i = 0; i < nrSamples; ++i) { - if (i != 0) { - addHtml(','); - } - addHtmlInt(i); - } - addHtml(F("],\n\"datasets\":[")); - - // Long/Lat Coordinates - add_ChartJS_dataset_header(datasetConfig); - - // Add scatter data - for (size_t i = 0; i < nrSamples; ++i) { - const float valX = (*stats_X)[i]; - const float valY = (*stats_Y)[i]; - add_ChartJS_scatter_data_point(valX, valY, 6); - } - - add_ChartJS_dataset_footer(F("\"showLine\":true")); - - if (showAverage) { - // Add single point showing the average - addHtml(','); - add_ChartJS_dataset_header( - { - F("Average"), - F("#0F4C5C") }); - - { - const float valX = stats_X->getSampleAvg(); - const float valY = stats_Y->getSampleAvg(); - add_ChartJS_scatter_data_point(valX, valY, 6); - } - add_ChartJS_dataset_footer(F("\"pointRadius\":6,\"pointHoverRadius\":10")); - } - add_ChartJS_chart_footer(onlyJSON); -} - -# endif // if FEATURE_CHART_JS - - -PluginStats * PluginStats_array::getPluginStats(taskVarIndex_t taskVarIndex) const -{ - if ((taskVarIndex < VARS_PER_TASK)) { - return _plugin_stats[taskVarIndex]; - } - return nullptr; -} - -PluginStats * PluginStats_array::getPluginStats(taskVarIndex_t taskVarIndex) -{ - if ((taskVarIndex < VARS_PER_TASK)) { - return _plugin_stats[taskVarIndex]; - } - return nullptr; -} - #endif // if FEATURE_PLUGIN_STATS diff --git a/src/src/DataStructs/PluginStats.h b/src/src/DataStructs/PluginStats.h index ebf7563bd6..2a320eb809 100644 --- a/src/src/DataStructs/PluginStats.h +++ b/src/src/DataStructs/PluginStats.h @@ -6,6 +6,8 @@ #if FEATURE_PLUGIN_STATS # include "../DataStructs/ChartJS_dataset_config.h" +# include "../DataStructs/PluginStats_size.h" +# include "../DataStructs/PluginStats_timestamp.h" # include "../DataTypes/TaskIndex.h" @@ -13,21 +15,6 @@ # include "../WebServer/Chart_JS_title.h" # endif // if FEATURE_CHART_JS -# include - -# ifndef PLUGIN_STATS_NR_ELEMENTS -# ifdef ESP8266 -# ifdef USE_SECOND_HEAP -# define PLUGIN_STATS_NR_ELEMENTS 50 -# else // ifdef USE_SECOND_HEAP -# define PLUGIN_STATS_NR_ELEMENTS 16 -# endif // ifdef USE_SECOND_HEAP -# endif // ifdef ESP8266 -# ifdef ESP32 -# define PLUGIN_STATS_NR_ELEMENTS 250 -# endif // ifdef ESP32 -# endif // ifndef PLUGIN_STATS_NR_ELEMENTS - class PluginStats { public: @@ -37,15 +24,26 @@ class PluginStats { PluginStats(uint8_t nrDecimals, float errorValue); + ~PluginStats(); + + void processTimeSet(const double& time_offset); + + void setPluginStats_timestamp(PluginStats_timestamp *plugin_stats_timestamps) + { + _plugin_stats_timestamps = plugin_stats_timestamps; + } // Add a sample to the _sample buffer // This does not also track peaks as the peaks could be raw sensor data and the samples processed data. bool push(float value); + // When only updating the timestamp of the last entry, we should look at the last + bool matchesLastTwoEntries(float value) const; + // Keep track of peaks. // Use this for sensors that need to take several samples before actually output a task value. // For example the ADC with oversampling - void trackPeak(float value); + void trackPeak(float value, int64_t timestamp = 0u); // Get lowest recorded value since reset float getPeakLow() const { @@ -57,34 +55,47 @@ class PluginStats { return hasPeaks() ? _maxValue : _errorValue; } - bool hasPeaks() const { + int64_t getPeakLowTimestamp() const { + return hasPeaks() ? _minValueTimestamp : 0; + } + + int64_t getPeakHighTimestamp() const { + return hasPeaks() ? _maxValueTimestamp : 0; + } + + bool hasPeaks() const { return _maxValue >= _minValue; } // Set the peaks to unset values - void resetPeaks(); + void resetPeaks(); - void clearSamples() { - _samples.clear(); - } + void clearSamples(); - size_t getNrSamples() const { - return _samples.size(); - } + size_t getNrSamples() const; // Compute average over all stored values - float getSampleAvg() const { - return getSampleAvg(_samples.size()); - } + float getSampleAvg() const; // Compute average over last N stored values - float getSampleAvg(PluginStatsBuffer_t::index_t lastNrSamples) const; + float getSampleAvg(PluginStatsBuffer_t::index_t lastNrSamples) const; // Compute the standard deviation over all stored values - float getSampleStdDev() const { - return getSampleStdDev(_samples.size()); + float getSampleStdDev() const { + return getSampleStdDev(getNrSamples()); + } + + // Compute average over all stored values, taking timestamp into account. + // Returns average per second. + float getSampleAvg_time(uint64_t& totalDuration_usec) const { + return getSampleAvg_time(getNrSamples(), totalDuration_usec); } + // Compute average over last N stored values, taking timestamp into account. + // Returns average per second. + float getSampleAvg_time(PluginStatsBuffer_t::index_t lastNrSamples, + uint64_t & totalDuration_usec) const; + // Compute the standard deviation over last N stored values float getSampleStdDev(PluginStatsBuffer_t::index_t lastNrSamples) const; @@ -116,6 +127,12 @@ class PluginStats { bool webformLoad_show_stdev(struct EventStruct *event) const; bool webformLoad_show_peaks(struct EventStruct *event, bool include_peak_to_peak = true) const; + bool webformLoad_show_peaks(struct EventStruct *event, + const String& label, + const String& lowValue, + const String& highValue, + bool include_peak_to_peak = true) const; + void webformLoad_show_val( struct EventStruct *event, const String & label, @@ -164,67 +181,18 @@ class PluginStats { float _minValue; float _maxValue; + int64_t _minValueTimestamp; + int64_t _maxValueTimestamp; - PluginStatsBuffer_t _samples; + PluginStatsBuffer_t *_samples = nullptr; float _errorValue; bool _errorValueIsNaN; uint8_t _nrDecimals = 3u; -}; - -class PluginStats_array { -public: - PluginStats_array() = default; - ~PluginStats_array(); - - void initPluginStats(taskVarIndex_t taskVarIndex); - void clearPluginStats(taskVarIndex_t taskVarIndex); - - bool hasStats() const; - bool hasPeaks() const; - - size_t nrSamplesPresent() const; - size_t nrPluginStats() const; - - void pushPluginStatsValues(struct EventStruct *event, - bool trackPeaks); - - bool plugin_get_config_value_base(struct EventStruct *event, - String & string) const; - - bool plugin_write_base(struct EventStruct *event, - const String & string); - - bool webformLoad_show_stats(struct EventStruct *event) const; - -# if FEATURE_CHART_JS - void plot_ChartJS(bool onlyJSON = false) const; - - void plot_ChartJS_scatter( - taskVarIndex_t values_X_axis_index, - taskVarIndex_t values_Y_axis_index, - const __FlashStringHelper *id, - const ChartJS_title & chartTitle, - const ChartJS_dataset_config& datasetConfig, - int width, - int height, - bool showAverage = true, - const String & options = EMPTY_STRING, - bool onlyJSON = false) const; - - -# endif // if FEATURE_CHART_JS - - - PluginStats* getPluginStats(taskVarIndex_t taskVarIndex) const; - - PluginStats* getPluginStats(taskVarIndex_t taskVarIndex); - -private: - - PluginStats *_plugin_stats[VARS_PER_TASK] = {}; + PluginStats_timestamp *_plugin_stats_timestamps = nullptr; }; + #endif // if FEATURE_PLUGIN_STATS #endif // ifndef HELPERS_PLUGINSTATS_H diff --git a/src/src/DataStructs/PluginStats_Config.h b/src/src/DataStructs/PluginStats_Config.h index 00ce1a05dd..16dcff1d01 100644 --- a/src/src/DataStructs/PluginStats_Config.h +++ b/src/src/DataStructs/PluginStats_Config.h @@ -26,7 +26,9 @@ struct PluginStats_Config_t { return static_cast(bits.chartAxisPosition); } - bool isLeft() const { return AxisPosition::Left == getAxisPosition(); } + bool isLeft() const { + return AxisPosition::Left == getAxisPosition(); + } void setAxisPosition(AxisPosition position) { bits.chartAxisPosition = static_cast(position); @@ -81,8 +83,7 @@ struct PluginStats_Config_t { uint8_t chartAxisPosition : 1; // Bit 05 uint8_t unused_06 : 1; // Bit 06 uint8_t unused_07 : 1; // Bit 07 - } bits; - + } bits; }; #endif // if FEATURE_PLUGIN_STATS diff --git a/src/src/DataStructs/PluginStats_array.cpp b/src/src/DataStructs/PluginStats_array.cpp new file mode 100644 index 0000000000..f23524f53e --- /dev/null +++ b/src/src/DataStructs/PluginStats_array.cpp @@ -0,0 +1,511 @@ +#include "../DataStructs/PluginStats_array.h" + +#if FEATURE_PLUGIN_STATS + +# include "../../_Plugin_Helper.h" + +# include "../Globals/TimeZone.h" + +# include "../Helpers/ESPEasy_math.h" +# include "../Helpers/Memory.h" + +# include "../WebServer/Chart_JS.h" + +PluginStats_array::~PluginStats_array() +{ + for (size_t i = 0; i < VARS_PER_TASK; ++i) { + if (_plugin_stats[i] != nullptr) { + delete _plugin_stats[i]; + _plugin_stats[i] = nullptr; + } + } + + if (_plugin_stats_timestamps != nullptr) { + free(_plugin_stats_timestamps); + _plugin_stats_timestamps = nullptr; + } +} + +void PluginStats_array::initPluginStats(taskIndex_t taskIndex, taskVarIndex_t taskVarIndex) +{ + if (taskVarIndex < VARS_PER_TASK) { + delete _plugin_stats[taskVarIndex]; + _plugin_stats[taskVarIndex] = nullptr; + + if (!hasStats()) { + if (_plugin_stats_timestamps != nullptr) { + free(_plugin_stats_timestamps); + _plugin_stats_timestamps = nullptr; + } + } + + if (ExtraTaskSettings.enabledPluginStats(taskVarIndex)) { + # ifdef USE_SECOND_HEAP + HeapSelectIram ephemeral; + # endif // ifdef USE_SECOND_HEAP + + // Try to allocate in PSRAM if possible + constexpr unsigned size = sizeof(PluginStats); + void *ptr = special_calloc(1, size); + + if (ptr == nullptr) { _plugin_stats[taskVarIndex] = nullptr; } + else { + _plugin_stats[taskVarIndex] = new (ptr) PluginStats( + ExtraTaskSettings.TaskDeviceValueDecimals[taskVarIndex], + ExtraTaskSettings.TaskDeviceErrorValue[taskVarIndex]); + } + + + if (_plugin_stats[taskVarIndex] != nullptr) { + _plugin_stats[taskVarIndex]->setLabel(ExtraTaskSettings.TaskDeviceValueNames[taskVarIndex]); + # if FEATURE_CHART_JS + const __FlashStringHelper *colors[] = { F("#A52422"), F("#BEA57D"), F("#0F4C5C"), F("#A4BAB7") }; + _plugin_stats[taskVarIndex]->_ChartJS_dataset_config.color = colors[taskVarIndex]; + _plugin_stats[taskVarIndex]->_ChartJS_dataset_config.displayConfig = ExtraTaskSettings.getPluginStatsConfig(taskVarIndex); + # endif // if FEATURE_CHART_JS + + if (_plugin_stats_timestamps != nullptr) { + _plugin_stats[taskVarIndex]->setPluginStats_timestamp(_plugin_stats_timestamps); + } + } + } + } + + if (hasStats()) { + if (_plugin_stats_timestamps == nullptr) { + // Try to allocate in PSRAM if possible + constexpr unsigned size = sizeof(PluginStats_timestamp); + void *ptr = special_calloc(1, size); + + if (ptr != nullptr) { + // TODO TD-er: Let the task decide whether we need 1/50 sec resolution or 1/10 + // 1/10 sec resolution allows for ~13.6 years without overflow + _plugin_stats_timestamps = new (ptr) PluginStats_timestamp(true); + } + + for (size_t i = 0; i < VARS_PER_TASK; ++i) { + _plugin_stats[taskVarIndex]->setPluginStats_timestamp(_plugin_stats_timestamps); + } + } + } +} + +void PluginStats_array::clearPluginStats(taskVarIndex_t taskVarIndex) +{ + if (taskVarIndex < VARS_PER_TASK) { + if (_plugin_stats[taskVarIndex] != nullptr) { + delete _plugin_stats[taskVarIndex]; + _plugin_stats[taskVarIndex] = nullptr; + } + } + + if (!hasStats()) { + if (_plugin_stats_timestamps != nullptr) { + free(_plugin_stats_timestamps); + _plugin_stats_timestamps = nullptr; + } + } +} + +void PluginStats_array::processTimeSet(const double& time_offset) +{ + if (_plugin_stats_timestamps != nullptr) { + _plugin_stats_timestamps->processTimeSet(time_offset); + } + + // Also update timestamps of peaks + for (taskVarIndex_t taskVarIndex = 0; taskVarIndex < VARS_PER_TASK; ++taskVarIndex) { + PluginStats *stats = getPluginStats(taskVarIndex); + + if (stats != nullptr) { + stats->processTimeSet(time_offset); + } + } +} + +bool PluginStats_array::hasStats() const +{ + for (size_t i = 0; i < VARS_PER_TASK; ++i) { + if (_plugin_stats[i] != nullptr) { return true; } + } + return false; +} + +bool PluginStats_array::hasPeaks() const +{ + for (size_t i = 0; i < VARS_PER_TASK; ++i) { + if ((_plugin_stats[i] != nullptr) && _plugin_stats[i]->hasPeaks()) { + return true; + } + } + return false; +} + +size_t PluginStats_array::nrSamplesPresent() const +{ + for (size_t i = 0; i < VARS_PER_TASK; ++i) { + if (_plugin_stats[i] != nullptr) { + return _plugin_stats[i]->getNrSamples(); + } + } + return 0; +} + +size_t PluginStats_array::nrPluginStats() const +{ + size_t res{}; + + for (size_t i = 0; i < VARS_PER_TASK; ++i) { + if (_plugin_stats[i] != nullptr) { + ++res; + } + } + return res; +} + +uint32_t PluginStats_array::getFullPeriodInSec(uint32_t& time_frac) const +{ + if (_plugin_stats_timestamps == nullptr) { + time_frac = 0u; + return 0u; + } + return _plugin_stats_timestamps->getFullPeriodInSec(time_frac); +} + +void PluginStats_array::pushPluginStatsValues( + struct EventStruct *event, + bool trackPeaks, + bool onlyUpdateTimestampWhenSame) +{ + if (validTaskIndex(event->TaskIndex)) { + const uint8_t valueCount = getValueCountForTask(event->TaskIndex); + + if (valueCount > 0) { + const Sensor_VType sensorType = event->getSensorType(); + + const int64_t timestamp_sysmicros = event->getTimestamp_as_systemMicros(); + + if (onlyUpdateTimestampWhenSame && (_plugin_stats_timestamps != nullptr)) { + // When only updating the timestamp of the last entry, + // we should look at the last 2 entries to see if they are the same. + bool isSame = true; + size_t i = 0; + + while (isSame && i < valueCount) { + if (_plugin_stats[i] != nullptr) { + const float value = UserVar.getAsDouble(event->TaskIndex, i, sensorType); + + if (!_plugin_stats[i]->matchesLastTwoEntries(value)) { + isSame = false; + } + } + ++i; + } + + if (isSame) { + _plugin_stats_timestamps->updateLast(timestamp_sysmicros); + return; + } + } + + if (_plugin_stats_timestamps != nullptr) { + _plugin_stats_timestamps->push(timestamp_sysmicros); + } + + for (size_t i = 0; i < valueCount; ++i) { + if (_plugin_stats[i] != nullptr) { + const float value = UserVar.getAsDouble(event->TaskIndex, i, sensorType); + _plugin_stats[i]->push(value); + + if (trackPeaks) { + _plugin_stats[i]->trackPeak(value, timestamp_sysmicros); + } + } + } + } + } +} + +bool PluginStats_array::plugin_get_config_value_base(struct EventStruct *event, + String & string) const +{ + // Full value name is something like "taskvaluename.avg" + const String fullValueName = parseString(string, 1); + const String valueName = parseString(fullValueName, 1, '.'); + + const uint8_t valueCount = getValueCountForTask(event->TaskIndex); + + for (taskVarIndex_t i = 0; i < valueCount; i++) + { + if (_plugin_stats[i] != nullptr) { + // Check case insensitive, since the user entered value name can have any case. + if (valueName.equalsIgnoreCase(Cache.getTaskDeviceValueName(event->TaskIndex, i))) + { + return _plugin_stats[i]->plugin_get_config_value_base(event, string); + } + } + } + return false; +} + +bool PluginStats_array::plugin_write_base(struct EventStruct *event, const String& string) +{ + bool success = false; + const String cmd = parseString(string, 1); // command + + const bool resetPeaks = equals(cmd, F("resetpeaks")); // Command: "taskname.resetPeaks" + const bool clearSamples = equals(cmd, F("clearsamples")); // Command: "taskname.clearSamples" + + if (resetPeaks || clearSamples) { + for (size_t i = 0; i < VARS_PER_TASK; ++i) { + if (_plugin_stats[i] != nullptr) { + if (resetPeaks) { + success = true; + _plugin_stats[i]->resetPeaks(); + } + + if (clearSamples) { + success = true; + _plugin_stats[i]->clearSamples(); + } + } + } + } + return success; +} + +bool PluginStats_array::webformLoad_show_stats(struct EventStruct *event, bool showTaskValues) const +{ + bool somethingAdded = false; + + uint32_t time_frac{}; + const uint32_t duration = getFullPeriodInSec(time_frac); + const uint32_t nrSamples = nrSamplesPresent(); + + if ((duration > 0) && (nrSamples > 1)) { + const uint32_t duration_millis = unix_time_frac_to_millis(time_frac); + addRowLabel(F("Total Duration")); + addHtml(strformat( + F("%s.%03u (%u.%03u sec)"), + secondsToDayHourMinuteSecond(duration).c_str(), + duration_millis, + duration, + duration_millis)); + const float duration_f = static_cast(duration) + (duration_millis / 1000.0f); + addRowLabel(F("Total Nr Samples")); + addHtmlInt(nrSamples); + addRowLabel(F("Avg Rate")); + addHtmlFloat(duration_f / static_cast(nrSamples - 1), 2); + addUnit(F("sec/sample")); + addFormSeparator(4); + somethingAdded = true; + } + + if (showTaskValues) { + for (size_t i = 0; i < VARS_PER_TASK; ++i) { + if (_plugin_stats[i] != nullptr) { + if (_plugin_stats[i]->webformLoad_show_stats(event)) { + somethingAdded = true; + } + } + } + } + return somethingAdded; +} + +# if FEATURE_CHART_JS +void PluginStats_array::plot_ChartJS(bool onlyJSON) const +{ + const size_t nrSamples = nrSamplesPresent(); + + if (nrSamples == 0) { return; } + + // Chart Header + { + ChartJS_options_scales scales; + { + ChartJS_options_scale scaleOption(F("x")); + + if (_plugin_stats_timestamps != nullptr) { + scaleOption.scaleType = F("time"); + } + scales.add(scaleOption); + } + + for (size_t i = 0; i < VARS_PER_TASK; ++i) { + if (_plugin_stats[i] != nullptr) { + ChartJS_options_scale scaleOption( + _plugin_stats[i]->_ChartJS_dataset_config.displayConfig, + _plugin_stats[i]->getLabel()); + scaleOption.axisTitle.color = _plugin_stats[i]->_ChartJS_dataset_config.color; + scales.add(scaleOption); + + _plugin_stats[i]->_ChartJS_dataset_config.axisID = scaleOption.axisID; + } + } + + scales.update_Yaxis_TickCount(); + + const bool enableZoom = true; + + add_ChartJS_chart_header( + F("line"), + F("TaskStatsChart"), + {}, + 500 + (70 * (scales.nr_Y_scales() - 1)), + 500, + scales.toString(), + enableZoom, + nrSamples, + onlyJSON); + } + + + // Add labels + addHtml(F("\"labels\":[")); + + for (size_t i = 0; i < nrSamples; ++i) { + if (i != 0) { + addHtml(','); + } + + if (_plugin_stats_timestamps != nullptr) { + struct tm ts; + uint32_t unix_time_frac{}; + const uint32_t uinxtime_sec = node_time.systemMicros_to_Unixtime((*_plugin_stats_timestamps)[i], unix_time_frac); + const uint32_t local_timestamp = time_zone.toLocal(uinxtime_sec); + breakTime(local_timestamp, ts); + addHtml('"'); + addHtml(formatDateTimeString(ts)); + addHtml(strformat(F(".%03u"), unix_time_frac_to_millis(unix_time_frac))); + addHtml('"'); + } else { + addHtmlInt(i); + } + } + addHtml(F("],\n\"datasets\":[")); + + + // Data sets + bool first = true; + + for (size_t i = 0; i < VARS_PER_TASK; ++i) { + if (_plugin_stats[i] != nullptr) { + if (!first) { + addHtml(','); + } + first = false; + _plugin_stats[i]->plot_ChartJS_dataset(); + } + } + add_ChartJS_chart_footer(onlyJSON); +} + +void PluginStats_array::plot_ChartJS_scatter( + taskVarIndex_t values_X_axis_index, + taskVarIndex_t values_Y_axis_index, + const __FlashStringHelper *id, + const ChartJS_title & chartTitle, + const ChartJS_dataset_config& datasetConfig, + int width, + int height, + bool showAverage, + const String & options, + bool onlyJSON) const +{ + const PluginStats *stats_X = getPluginStats(values_X_axis_index); + const PluginStats *stats_Y = getPluginStats(values_Y_axis_index); + + if ((stats_X == nullptr) || (stats_Y == nullptr)) { + return; + } + + if ((stats_X->getNrSamples() < 2) || (stats_Y->getNrSamples() < 2)) { + return; + } + + String axisOptions; + + { + ChartJS_options_scales scales; + scales.add({ F("x"), stats_X->getLabel() }); + scales.add({ F("y"), stats_Y->getLabel() }); + axisOptions = scales.toString(); + } + + + const size_t nrSamples = stats_X->getNrSamples(); + const bool enableZoom = false; + + add_ChartJS_chart_header( + F("scatter"), + id, + chartTitle, + width, + height, + axisOptions, + enableZoom, + nrSamples, + onlyJSON); + + // Add labels, which will be shown in a tooltip when hovering with the mouse over a point. + addHtml(F("\"labels\":[")); + + for (size_t i = 0; i < nrSamples; ++i) { + if (i != 0) { + addHtml(','); + } + addHtmlInt(i); + } + addHtml(F("],\n\"datasets\":[")); + + // Long/Lat Coordinates + add_ChartJS_dataset_header(datasetConfig); + + // Add scatter data + for (size_t i = 0; i < nrSamples; ++i) { + const float valX = (*stats_X)[i]; + const float valY = (*stats_Y)[i]; + add_ChartJS_scatter_data_point(valX, valY, 6); + } + + add_ChartJS_dataset_footer(F("\"showLine\":true")); + + if (showAverage) { + // Add single point showing the average + addHtml(','); + add_ChartJS_dataset_header( + { + F("Average"), + F("#0F4C5C") }); + + { + const float valX = stats_X->getSampleAvg(); + const float valY = stats_Y->getSampleAvg(); + add_ChartJS_scatter_data_point(valX, valY, 6); + } + add_ChartJS_dataset_footer(F("\"pointRadius\":6,\"pointHoverRadius\":10")); + } + add_ChartJS_chart_footer(onlyJSON); +} + +# endif // if FEATURE_CHART_JS + + +PluginStats * PluginStats_array::getPluginStats(taskVarIndex_t taskVarIndex) const +{ + if ((taskVarIndex < VARS_PER_TASK)) { + return _plugin_stats[taskVarIndex]; + } + return nullptr; +} + +PluginStats * PluginStats_array::getPluginStats(taskVarIndex_t taskVarIndex) +{ + if ((taskVarIndex < VARS_PER_TASK)) { + return _plugin_stats[taskVarIndex]; + } + return nullptr; +} + +#endif // if FEATURE_PLUGIN_STATS diff --git a/src/src/DataStructs/PluginStats_array.h b/src/src/DataStructs/PluginStats_array.h new file mode 100644 index 0000000000..ec3e1df712 --- /dev/null +++ b/src/src/DataStructs/PluginStats_array.h @@ -0,0 +1,86 @@ +#ifndef HELPERS_PLUGINSTATS_ARRAY_H +#define HELPERS_PLUGINSTATS_ARRAY_H + +#include "../../ESPEasy_common.h" + +#if FEATURE_PLUGIN_STATS + +# include "../DataStructs/PluginStats.h" +# include "../DataStructs/PluginStats_timestamp.h" + +# include "../DataStructs/ChartJS_dataset_config.h" +# include "../DataTypes/TaskIndex.h" + + +# if FEATURE_CHART_JS +# include "../WebServer/Chart_JS_title.h" +# endif // if FEATURE_CHART_JS + + +class PluginStats_array { +public: + + PluginStats_array() = default; + ~PluginStats_array(); + + void initPluginStats(taskIndex_t taskIndex, + taskVarIndex_t taskVarIndex); + void clearPluginStats(taskVarIndex_t taskVarIndex); + + // Update any logged timestamp with this newly set system time. + void processTimeSet(const double& time_offset); + + bool hasStats() const; + bool hasPeaks() const; + + size_t nrSamplesPresent() const; + size_t nrPluginStats() const; + + // Compute the duration between first and last sample in seconds + // For 0 or 1 samples, the period will be 0 seconds. + uint32_t getFullPeriodInSec(uint32_t& time_frac) const; + + void pushPluginStatsValues(struct EventStruct *event, + bool trackPeaks, + bool onlyUpdateTimestampWhenSame); + + bool plugin_get_config_value_base(struct EventStruct *event, + String & string) const; + + bool plugin_write_base(struct EventStruct *event, + const String & string); + + bool webformLoad_show_stats(struct EventStruct *event, + bool showTaskValues = true) const; + +# if FEATURE_CHART_JS + void plot_ChartJS(bool onlyJSON = false) const; + + void plot_ChartJS_scatter( + taskVarIndex_t values_X_axis_index, + taskVarIndex_t values_Y_axis_index, + const __FlashStringHelper *id, + const ChartJS_title & chartTitle, + const ChartJS_dataset_config& datasetConfig, + int width, + int height, + bool showAverage = true, + const String & options = EMPTY_STRING, + bool onlyJSON = false) const; + + +# endif // if FEATURE_CHART_JS + + + PluginStats* getPluginStats(taskVarIndex_t taskVarIndex) const; + + PluginStats* getPluginStats(taskVarIndex_t taskVarIndex); + +private: + + PluginStats *_plugin_stats[VARS_PER_TASK] = {}; + PluginStats_timestamp *_plugin_stats_timestamps = nullptr; +}; + +#endif // if FEATURE_PLUGIN_STATS +#endif // ifndef HELPERS_PLUGINSTATS_ARRAY_H diff --git a/src/src/DataStructs/PluginStats_size.h b/src/src/DataStructs/PluginStats_size.h new file mode 100644 index 0000000000..b501d7fdef --- /dev/null +++ b/src/src/DataStructs/PluginStats_size.h @@ -0,0 +1,25 @@ +#ifndef HELPERS_PLUGINSTATS_SIZE_H +#define HELPERS_PLUGINSTATS_SIZE_H + +#include "../../ESPEasy_common.h" + +#if FEATURE_PLUGIN_STATS + +# include + +# ifndef PLUGIN_STATS_NR_ELEMENTS +# ifdef ESP8266 +# ifdef USE_SECOND_HEAP +# define PLUGIN_STATS_NR_ELEMENTS 50 +# else // ifdef USE_SECOND_HEAP +# define PLUGIN_STATS_NR_ELEMENTS 16 +# endif // ifdef USE_SECOND_HEAP +# endif // ifdef ESP8266 +# ifdef ESP32 +# define PLUGIN_STATS_NR_ELEMENTS 250 +# endif // ifdef ESP32 +# endif // ifndef PLUGIN_STATS_NR_ELEMENTS + + +#endif // if FEATURE_PLUGIN_STATS +#endif // ifndef HELPERS_PLUGINSTATS_SIZE_H diff --git a/src/src/DataStructs/PluginStats_timestamp.cpp b/src/src/DataStructs/PluginStats_timestamp.cpp new file mode 100644 index 0000000000..f71f05873b --- /dev/null +++ b/src/src/DataStructs/PluginStats_timestamp.cpp @@ -0,0 +1,120 @@ +#include "../DataStructs/PluginStats_timestamp.h" + +#if FEATURE_PLUGIN_STATS + +# include "../Globals/ESPEasy_time.h" +# include "../Helpers/ESPEasy_time_calc.h" + +PluginStats_timestamp::PluginStats_timestamp(bool useHighRes) + : _internal_to_micros_ratio(useHighRes +? 20000ull // 1/50 sec resolution +: 100000ull) // 1/10 sec resolution +{} + +PluginStats_timestamp::~PluginStats_timestamp() +{} + +bool PluginStats_timestamp::push(const int64_t& timestamp_sysmicros) +{ + return _timestamps.push(systemMicros_to_internalTimestamp(timestamp_sysmicros)); +} + +bool PluginStats_timestamp::updateLast(const int64_t& timestamp_sysmicros) +{ + const size_t nrElements = _timestamps.size(); + + if (nrElements == 0) { return false; } + return _timestamps.set(nrElements - 1, systemMicros_to_internalTimestamp(timestamp_sysmicros)); +} + +void PluginStats_timestamp::clear() +{ + _timestamps.clear(); +} + +void PluginStats_timestamp::processTimeSet(const double& time_offset) +{ + // Check to see if there was a unix time set before the system time was set + // For example when receiving data from a p2p node + + /* + const uint64_t cur_micros = getMicros64(); + const uint64_t offset_micros = time_offset * 1000000ull; + const size_t nrSamples = _timestamps.size(); + + // GMT Wed Jan 01 2020 00:00:00 GMT+0000 + const int64_t unixTime_20200101 = 1577836800ll * _internal_to_micros_ratio; + + for (PluginStatsTimestamps_t::index_t i = 0; i < nrSamples; ++i) { + if (_timestamps[i] < unixTime_20200101) { + _timestamps.set(i, _timestamps[i] + time_offset); + } + } + */ +} + +int64_t PluginStats_timestamp::getTimestamp(int lastNrSamples) const +{ + if ((_timestamps.size() == 0) || (_timestamps.size() < abs(lastNrSamples))) { return 0u; } + + PluginStatsTimestamps_t::index_t i = 0; + + if (lastNrSamples > 0) { + i = _timestamps.size() - lastNrSamples; + } else if (lastNrSamples < 0) { + i = abs(lastNrSamples) - 1; + } + + if (i < _timestamps.size()) { + return internalTimestamp_to_systemMicros(_timestamps[i]); + } + return 0u; +} + +uint32_t PluginStats_timestamp::getFullPeriodInSec(uint32_t& time_frac) const +{ + const size_t nrSamples = _timestamps.size(); + + time_frac = 0u; + + if (nrSamples <= 1) { + return 0u; + } + + const int64_t start = internalTimestamp_to_systemMicros(_timestamps[0]); + const int64_t end = internalTimestamp_to_systemMicros(_timestamps[nrSamples - 1]); + + const int64_t period_usec = (end < start) ? (start - end) : (end - start); + + return micros_to_sec_time_frac(period_usec, time_frac); +} + +int64_t PluginStats_timestamp::operator[](PluginStatsTimestamps_t::index_t index) const +{ + if (index < _timestamps.size()) { + return internalTimestamp_to_systemMicros(_timestamps[index]); + } + return 0u; +} + +uint32_t PluginStats_timestamp::systemMicros_to_internalTimestamp(const int64_t& timestamp_sysmicros) const +{ + return static_cast(timestamp_sysmicros / _internal_to_micros_ratio); +} + +int64_t PluginStats_timestamp::internalTimestamp_to_systemMicros(const uint32_t& internalTimestamp) const +{ + const uint64_t cur_micros = getMicros64(); + const uint64_t overflow_step = 4294967296ull * _internal_to_micros_ratio; + + uint64_t sysMicros = static_cast(internalTimestamp) * _internal_to_micros_ratio; + + // Try to get in the range of the current system micros + // This only does play a role in high res mode, when uptime is over 994 days. + while ((sysMicros + overflow_step) < cur_micros) { + sysMicros += overflow_step; + } + return sysMicros; +} + +#endif // if FEATURE_PLUGIN_STATS diff --git a/src/src/DataStructs/PluginStats_timestamp.h b/src/src/DataStructs/PluginStats_timestamp.h new file mode 100644 index 0000000000..96e97457b9 --- /dev/null +++ b/src/src/DataStructs/PluginStats_timestamp.h @@ -0,0 +1,56 @@ +#ifndef HELPERS_PLUGINSTATS_TIMESTAMP_H +#define HELPERS_PLUGINSTATS_TIMESTAMP_H + +#include "../../ESPEasy_common.h" + +#if FEATURE_PLUGIN_STATS + +# include "../DataStructs/PluginStats_size.h" + +// When using 'high res', the timestamps are stored internally +// with 0.02 sec resolution. (1/50 sec) Default is 0.1 sec resolution +// Stored timestamp will be based on the system micros +// This also implies there might be overflow issues when the +// full period exceeds (2^32 / 50) seconds (~ 1000 days) +// or (2^32 / 10) seconds (~13.6 years) +class PluginStats_timestamp { +public: + + typedef CircularBuffer PluginStatsTimestamps_t; + + PluginStats_timestamp() = delete; + + PluginStats_timestamp(bool useHighRes); + ~PluginStats_timestamp(); + + bool push(const int64_t& timestamp_sysmicros); + + bool updateLast(const int64_t& timestamp_sysmicros); + + void clear(); + + // Update any logged timestamp with this newly set system time. + void processTimeSet(const double& time_offset); + + int64_t getTimestamp(int lastNrSamples) const; + + // Compute the duration between first and last sample in seconds + // For 0 or 1 samples, the period will be 0 seconds. + uint32_t getFullPeriodInSec(uint32_t& time_frac) const; + + int64_t operator[](PluginStatsTimestamps_t::index_t index) const; + +private: + + // Conversion from system micros to internal timestamp + uint32_t systemMicros_to_internalTimestamp(const int64_t& timestamp_sysmicros) const; + + // Conversion from internal timestamp to system micros + int64_t internalTimestamp_to_systemMicros(const uint32_t& internalTimestamp) const; + + PluginStatsTimestamps_t _timestamps; + const uint32_t _internal_to_micros_ratio = 20000ul; // Default to 1/50 sec +}; + +#endif // if FEATURE_PLUGIN_STATS +#endif // ifndef HELPERS_PLUGINSTATS_TIMESTAMP_H diff --git a/src/src/DataStructs/PluginTaskData_base.cpp b/src/src/DataStructs/PluginTaskData_base.cpp index 2a02a53bfc..28428100fd 100644 --- a/src/src/DataStructs/PluginTaskData_base.cpp +++ b/src/src/DataStructs/PluginTaskData_base.cpp @@ -56,7 +56,7 @@ size_t PluginTaskData_base::nrSamplesPresent() const { } #if FEATURE_PLUGIN_STATS -void PluginTaskData_base::initPluginStats(taskVarIndex_t taskVarIndex) +void PluginTaskData_base::initPluginStats(taskIndex_t taskIndex, taskVarIndex_t taskVarIndex) { if (taskVarIndex < VARS_PER_TASK) { if (_plugin_stats_array == nullptr) { @@ -64,7 +64,7 @@ void PluginTaskData_base::initPluginStats(taskVarIndex_t taskVarIndex) } if (_plugin_stats_array != nullptr) { - _plugin_stats_array->initPluginStats(taskVarIndex); + _plugin_stats_array->initPluginStats(taskIndex, taskVarIndex); } } } @@ -81,15 +81,23 @@ void PluginTaskData_base::clearPluginStats(taskVarIndex_t taskVarIndex) } } +void PluginTaskData_base::processTimeSet(const double& time_offset) +{ + if (_plugin_stats_array != nullptr) { + _plugin_stats_array->processTimeSet(time_offset); + } +} + #endif // if FEATURE_PLUGIN_STATS void PluginTaskData_base::pushPluginStatsValues(struct EventStruct *event, - bool trackPeaks) + bool trackPeaks, + bool onlyUpdateTimestampWhenSame) { #if FEATURE_PLUGIN_STATS if (_plugin_stats_array != nullptr) { - _plugin_stats_array->pushPluginStatsValues(event, trackPeaks); + _plugin_stats_array->pushPluginStatsValues(event, trackPeaks, onlyUpdateTimestampWhenSame); } #endif // if FEATURE_PLUGIN_STATS } diff --git a/src/src/DataStructs/PluginTaskData_base.h b/src/src/DataStructs/PluginTaskData_base.h index ba2da51bbd..f8fa2f2870 100644 --- a/src/src/DataStructs/PluginTaskData_base.h +++ b/src/src/DataStructs/PluginTaskData_base.h @@ -4,7 +4,7 @@ #include "../../ESPEasy_common.h" -#include "../DataStructs/PluginStats.h" +#include "../DataStructs/PluginStats_array.h" #include "../DataTypes/PluginID.h" #include "../DataTypes/TaskIndex.h" @@ -25,20 +25,24 @@ struct PluginTaskData_base { return _baseClassOnly; } - bool hasPluginStats() const; + bool hasPluginStats() const; - bool hasPeaks() const; + bool hasPeaks() const; size_t nrSamplesPresent() const; #if FEATURE_PLUGIN_STATS - void initPluginStats(taskVarIndex_t taskVarIndex); - void clearPluginStats(taskVarIndex_t taskVarIndex); + void initPluginStats(taskIndex_t taskIndex, taskVarIndex_t taskVarIndex); + void clearPluginStats(taskVarIndex_t taskVarIndex); + + // Update any logged timestamp with this newly set system time. + void processTimeSet(const double& time_offset); #endif // if FEATURE_PLUGIN_STATS // Called right after successful PLUGIN_READ to store task values void pushPluginStatsValues(struct EventStruct *event, - bool trackPeaks); + bool trackPeaks, + bool onlyUpdateTimestampWhenSame); // Support task value notation to 'get' statistics // Notations like [taskname#taskvalue.avg] can then be used to compute the average over a number of samples. @@ -80,7 +84,7 @@ struct PluginTaskData_base { PluginStats* getPluginStats(taskVarIndex_t taskVarIndex); -private: +protected: // Array of pointers to PluginStats. One per task value. PluginStats_array *_plugin_stats_array = nullptr; diff --git a/src/src/DataStructs/SettingsStruct.h b/src/src/DataStructs/SettingsStruct.h index 17d2550891..45fade5f86 100644 --- a/src/src/DataStructs/SettingsStruct.h +++ b/src/src/DataStructs/SettingsStruct.h @@ -1,577 +1,583 @@ - -#ifndef DATASTRUCTS_SETTINGSSTRUCT_H -#define DATASTRUCTS_SETTINGSSTRUCT_H - -#include "../../ESPEasy_common.h" - -#include "../CustomBuild/ESPEasyLimits.h" -#include "../DataStructs/ChecksumType.h" -#include "../DataStructs/DeviceStruct.h" -#include "../DataTypes/EthernetParameters.h" -#include "../DataTypes/NetworkMedium.h" -#include "../DataTypes/NPluginID.h" -#include "../DataTypes/PluginID.h" -//#include "../DataTypes/TaskEnabledState.h" -#include "../DataTypes/TimeSource.h" -#include "../Globals/Plugins.h" - -#ifdef ESP32 -#include -#endif - -//we disable SPI if not defined -#ifndef DEFAULT_SPI - #define DEFAULT_SPI 0 -#endif - - -// FIXME TD-er: Move this PinBootState to DataTypes folder - -// State is stored, so don't change order -enum class PinBootState { - Default_state = 0, - Output_low = 1, - Output_high = 2, - Input_pullup = 3, - Input_pulldown = 4, // Only on ESP32 and GPIO16 on ESP82xx - Input = 5, - - // Options for later: - // ANALOG (only on ESP32) - // WAKEUP_PULLUP (only on ESP8266) - // WAKEUP_PULLDOWN (only on ESP8266) - // SPECIAL - // FUNCTION_0 (only on ESP8266) - // FUNCTION_1 - // FUNCTION_2 - // FUNCTION_3 - // FUNCTION_4 - // FUNCTION_5 (only on ESP32) - // FUNCTION_6 (only on ESP32) - -}; - - - - -/*********************************************************************************************\ - * SettingsStruct -\*********************************************************************************************/ -template -class SettingsStruct_tmpl -{ - public: - -// SettingsStruct_tmpl() = default; - - // VariousBits1 defaults to 0, keep in mind when adding bit lookups. - bool appendUnitToHostname() const { return !VariousBits_1.appendUnitToHostname; } - void appendUnitToHostname(bool value) { VariousBits_1.appendUnitToHostname = !value;} - - bool uniqueMQTTclientIdReconnect_unused() const { return VariousBits_1.unused_02; } - void uniqueMQTTclientIdReconnect_unused(bool value) { VariousBits_1.unused_02 = value; } - - bool OldRulesEngine() const { -#ifdef WEBSERVER_NEW_RULES - return !VariousBits_1.OldRulesEngine; -#else - return true; -#endif - } - void OldRulesEngine(bool value) { VariousBits_1.OldRulesEngine = !value; } - - bool ForceWiFi_bg_mode() const { return VariousBits_1.ForceWiFi_bg_mode; } - void ForceWiFi_bg_mode(bool value) { VariousBits_1.ForceWiFi_bg_mode = value; } - - bool WiFiRestart_connection_lost() const { return VariousBits_1.WiFiRestart_connection_lost; } - void WiFiRestart_connection_lost(bool value) { VariousBits_1.WiFiRestart_connection_lost = value; } - - bool EcoPowerMode() const { return VariousBits_1.EcoPowerMode; } - void EcoPowerMode(bool value) { VariousBits_1.EcoPowerMode = value; } - - bool WifiNoneSleep() const { return VariousBits_1.WifiNoneSleep; } - void WifiNoneSleep(bool value) { VariousBits_1.WifiNoneSleep = value; } - - // Enable send gratuitous ARP by default, so invert the values (default = 0) - bool gratuitousARP() const { return !VariousBits_1.gratuitousARP; } - void gratuitousARP(bool value) { VariousBits_1.gratuitousARP = !value; } - - // Be a bit more tolerant when parsing the last argument of a command. - // See: https://github.com/letscontrolit/ESPEasy/issues/2724 - bool TolerantLastArgParse() const { return VariousBits_1.TolerantLastArgParse; } - void TolerantLastArgParse(bool value) { VariousBits_1.TolerantLastArgParse = value; } - - // SendToHttp command does not wait for ack, with this flag it does wait. - bool SendToHttp_ack() const { return VariousBits_1.SendToHttp_ack; } - void SendToHttp_ack(bool value) { VariousBits_1.SendToHttp_ack = value; } - - // Enable/disable ESPEasyNow protocol - bool UseESPEasyNow() const { -#ifdef USES_ESPEASY_NOW - return VariousBits_1.UseESPEasyNow; -#else - return false; -#endif - } - void UseESPEasyNow(bool value) { -#ifdef USES_ESPEASY_NOW - VariousBits_1.UseESPEasyNow = value; -#endif - } - - // Whether to try to connect to a hidden SSID network - bool IncludeHiddenSSID() const { return VariousBits_1.IncludeHiddenSSID; } - void IncludeHiddenSSID(bool value) { VariousBits_1.IncludeHiddenSSID = value; } - - // When sending, the TX power may be boosted to max TX power. - bool UseMaxTXpowerForSending() const { return VariousBits_1.UseMaxTXpowerForSending; } - void UseMaxTXpowerForSending(bool value) { VariousBits_1.UseMaxTXpowerForSending = value; } - - // When set you can use the Sensor in AP-Mode without beeing forced to /setup - bool ApDontForceSetup() const { return VariousBits_1.ApDontForceSetup; } - void ApDontForceSetup(bool value) { VariousBits_1.ApDontForceSetup = value; } - - // When outputting JSON bools use quoted values (on, backward compatible) or use official JSON true/false unquoted - bool JSONBoolWithoutQuotes() const { return VariousBits_1.JSONBoolWithoutQuotes; } - void JSONBoolWithoutQuotes(bool value) { VariousBits_1.JSONBoolWithoutQuotes = value; } - - // Enable timing statistics (may consume a few kB of RAM) - bool EnableTimingStats() const { return VariousBits_1.EnableTimingStats; } - void EnableTimingStats(bool value) { VariousBits_1.EnableTimingStats = value; } - - // Allow to actively reset I2C bus if it appears to be hanging. - bool EnableClearHangingI2Cbus() const { -#if FEATURE_CLEAR_I2C_STUCK - return VariousBits_1.EnableClearHangingI2Cbus; -#else - return false; -#endif -} - void EnableClearHangingI2Cbus(bool value) { VariousBits_1.EnableClearHangingI2Cbus = value; } - - // Enable RAM Tracking (may consume a few kB of RAM and cause some performance hit) - bool EnableRAMTracking() const { return VariousBits_1.EnableRAMTracking; } - void EnableRAMTracking(bool value) { VariousBits_1.EnableRAMTracking = value; } - - // Enable caching of rules, to speed up rules processing - bool EnableRulesCaching() const { return !VariousBits_1.EnableRulesCaching; } - void EnableRulesCaching(bool value) { VariousBits_1.EnableRulesCaching = !value; } - - // Allow the cached event entries to be sorted based on how frequent they occur. - // This may speed up rules processing, especially on large rule sets with lots of rules blocks. - bool EnableRulesEventReorder() const { return !VariousBits_1.EnableRulesEventReorder; } - void EnableRulesEventReorder(bool value) { VariousBits_1.EnableRulesEventReorder = !value; } - - // Allow OTA to use 'unlimited' bin sized files, possibly overwriting the file-system, and trashing files - // Can be used if the configuration is later retrieved/restored manually - bool AllowOTAUnlimited() const { return VariousBits_1.AllowOTAUnlimited; } - void AllowOTAUnlimited(bool value) { VariousBits_1.AllowOTAUnlimited = value; } - - // Default behavior is to not allow following redirects - bool SendToHTTP_follow_redirects() const { return VariousBits_1.SendToHTTP_follow_redirects; } - void SendToHTTP_follow_redirects(bool value) { VariousBits_1.SendToHTTP_follow_redirects = value; } - - #if FEATURE_I2C_DEVICE_CHECK - // Check if an I2C device is found at configured address at plugin_INIT and plugin_READ - bool CheckI2Cdevice() const { return !VariousBits_1.CheckI2Cdevice; } - void CheckI2Cdevice(bool value) { VariousBits_1.CheckI2Cdevice = !value; } - #endif // if FEATURE_I2C_DEVICE_CHECK - - // Wait for a second after calling WiFi.begin() - // Especially useful for some FritzBox routers. - bool WaitWiFiConnect() const { return VariousBits_2.WaitWiFiConnect; } - void WaitWiFiConnect(bool value) { VariousBits_2.WaitWiFiConnect = value; } - - // Connect to Hidden SSID using channel and BSSID - // This is much slower, but appears to be needed for some access points - // like MikroTik. - bool HiddenSSID_SlowConnectPerBSSID() const { return !VariousBits_2.HiddenSSID_SlowConnectPerBSSID; } - void HiddenSSID_SlowConnectPerBSSID(bool value) { VariousBits_2.HiddenSSID_SlowConnectPerBSSID = !value; } - - bool EnableIPv6() const { return !VariousBits_2.EnableIPv6; } - void EnableIPv6(bool value) { VariousBits_2.EnableIPv6 = !value; } - - // Use Espressif's auto reconnect. - bool SDK_WiFi_autoreconnect() const { return VariousBits_2.SDK_WiFi_autoreconnect; } - void SDK_WiFi_autoreconnect(bool value) { VariousBits_2.SDK_WiFi_autoreconnect = value; } - - #if FEATURE_RULES_EASY_COLOR_CODE - // Inhibit RulesCodeCompletion - bool DisableRulesCodeCompletion() const { return VariousBits_2.DisableRulesCodeCompletion; } - void DisableRulesCodeCompletion(bool value) { VariousBits_2.DisableRulesCodeCompletion = value; } - #endif // if FEATURE_RULES_EASY_COLOR_CODE - - #if FEATURE_TARSTREAM_SUPPORT - bool DisableSaveConfigAsTar() const { return VariousBits_2.DisableSaveConfigAsTar; } - void DisableSaveConfigAsTar(bool value) { VariousBits_2.DisableSaveConfigAsTar = value; } - #endif // if FEATURE_TARSTREAM_SUPPORT - - // Flag indicating whether all task values should be sent in a single event or one event per task value (default behavior) - bool CombineTaskValues_SingleEvent(taskIndex_t taskIndex) const; - void CombineTaskValues_SingleEvent(taskIndex_t taskIndex, bool value); - - bool DoNotStartAP() const { return VariousBits_1.DoNotStartAP; } - void DoNotStartAP(bool value) { VariousBits_1.DoNotStartAP = value; } - - bool UseAlternativeDeepSleep() const { return VariousBits_1.UseAlternativeDeepSleep; } - void UseAlternativeDeepSleep(bool value) { VariousBits_1.UseAlternativeDeepSleep = value; } - - bool UseLastWiFiFromRTC() const { return VariousBits_1.UseLastWiFiFromRTC; } - void UseLastWiFiFromRTC(bool value) { VariousBits_1.UseLastWiFiFromRTC = value; } - - ExtTimeSource_e ExtTimeSource() const; - void ExtTimeSource(ExtTimeSource_e value); - - bool UseNTP() const; - void UseNTP(bool value); - - bool AllowTaskValueSetAllPlugins() const { return VariousBits_1.AllowTaskValueSetAllPlugins; } - void AllowTaskValueSetAllPlugins(bool value) { VariousBits_1.AllowTaskValueSetAllPlugins = value; } - - #if FEATURE_AUTO_DARK_MODE - uint8_t getCssMode() const { return VariousBits_1.CssMode; } - void setCssMode(uint8_t value) { VariousBits_1.CssMode = value; } - #endif // FEATURE_AUTO_DARK_MODE - - bool isTaskEnableReadonly(taskIndex_t taskIndex) const; - void setTaskEnableReadonly(taskIndex_t taskIndex, bool value); - - #if FEATURE_PLUGIN_PRIORITY - bool isPowerManagerTask(taskIndex_t taskIndex) const; - void setPowerManagerTask(taskIndex_t taskIndex, bool value); - - bool isPriorityTask(taskIndex_t taskIndex) const; - #endif // if FEATURE_PLUGIN_PRIORITY - - void validate(); - - bool networkSettingsEmpty() const; - - void clearNetworkSettings(); - - void clearTimeSettings(); - - void clearNotifications(); - - void clearControllers(); - - void clearTasks(); - - void clearLogSettings(); - - void clearUnitNameSettings(); - - void clearMisc(); - - void clearTask(taskIndex_t task); - - // Return hostname + unit when selected to add unit. - String getHostname() const; - - // Return hostname with explicit set append unit. - String getHostname(bool appendUnit) const; - - // Return the name of the unit, without unitnr appended, with template parsing applied, replacement for Settings.Name in most places - String getName() const; - -private: - - // Compute the index in either - // - PinBootStates array (index_low) or - // - PinBootStates_ESP32 (index_high) - // Returns whether it is a valid index - bool getPinBootStateIndex( - int8_t gpio_pin, - int8_t& index_low - #ifdef ESP32 - , int8_t& index_high - #endif - ) const; - -public: - - PinBootState getPinBootState(int8_t gpio_pin) const; - void setPinBootState(int8_t gpio_pin, PinBootState state); - - bool getSPI_pins(int8_t spi_gpios[3]) const; - - #ifdef ESP32 - spi_host_device_t getSPI_host() const; - #endif - - // Return true when pin is one of the SPI pins and SPI is enabled - bool isSPI_pin(int8_t pin) const; - - // Return true when SPI enabled and opt. user defined pins valid. - bool isSPI_valid() const; - - // Return true when pin is one of the configured I2C pins. - bool isI2C_pin(int8_t pin) const; - - // Return true if I2C settings are correct - bool isI2CEnabled() const; - - // Return true when pin is one of the fixed Ethernet pins and Ethernet is enabled - bool isEthernetPin(int8_t pin) const; - - // Return true when pin is one of the optional Ethernet pins and Ethernet is enabled - bool isEthernetPinOptional(int8_t pin) const; - - // Access to TaskDevicePin1 ... TaskDevicePin3 - // @param pinnr 1 = TaskDevicePin1, ..., 3 = TaskDevicePin3 - int8_t getTaskDevicePin(taskIndex_t taskIndex, uint8_t pinnr) const; - - float getWiFi_TX_power() const; - void setWiFi_TX_power(float dBm); - - pluginID_t getPluginID_for_task(taskIndex_t taskIndex) const; - - void forceSave() { memset(md5, 0, 16); } - - uint32_t getVariousBits1() const { - uint32_t res; - memcpy(&res, &VariousBits_1, sizeof(VariousBits_1)); - return res; - } - - void setVariousBits1(uint32_t value) { - memcpy(&VariousBits_1, &value, sizeof(VariousBits_1)); - } - - uint32_t getVariousBits2() const { - uint32_t res; - memcpy(&res, &VariousBits_2, sizeof(VariousBits_2)); - return res; - } - - void setVariousBits2(uint32_t value) { - memcpy(&VariousBits_2, &value, sizeof(VariousBits_2)); - } - - - unsigned long PID = 0; - int Version = 0; - int16_t Build = 0; - uint8_t IP[4] = {0}; - uint8_t Gateway[4] = {0}; - uint8_t Subnet[4] = {0}; - uint8_t DNS[4] = {0}; - uint8_t IP_Octet = 0; - uint8_t Unit = 0; - char Name[26] = {0}; - char NTPHost[64] = {0}; - // FIXME TD-er: Issue #2690 - unsigned long Delay = 0; // Sleep time in seconds - int8_t Pin_i2c_sda = DEFAULT_PIN_I2C_SDA; - int8_t Pin_i2c_scl = DEFAULT_PIN_I2C_SCL; - int8_t Pin_status_led = DEFAULT_PIN_STATUS_LED; - int8_t Pin_sd_cs = -1; - int8_t PinBootStates[17] = {0}; // Only use getPinBootState and setPinBootState as multiple pins are packed for ESP32 - uint8_t Syslog_IP[4] = {0}; - unsigned int UDPPort = 8266; - uint8_t SyslogLevel = 0; - uint8_t SerialLogLevel = 0; - uint8_t WebLogLevel = 0; - uint8_t SDLogLevel = 0; - unsigned long BaudRate = 115200; - unsigned long MessageDelay_unused = 0; // MQTT settings now moved to the controller settings. - uint8_t deepSleep_wakeTime = 0; // 0 = Sleep Disabled, else time awake from sleep in seconds - boolean CustomCSS = false; - boolean DST = false; - uint8_t WDI2CAddress = 0; - boolean UseRules = false; - boolean UseSerial = false; - boolean UseSSDP = false; - uint8_t ExternalTimeSource = 0; - unsigned long WireClockStretchLimit = 0; - boolean GlobalSync = false; - unsigned long ConnectionFailuresThreshold = 0; - int16_t TimeZone = 0; - boolean MQTTRetainFlag_unused = false; - uint8_t InitSPI = 0; //0 = disabled, 1= enabled but for ESP32 there is option 2= SPI2 9 = User defined, see src/src/WebServer/HardwarePage.h enum SPI_Options_e - // FIXME TD-er: Must change to cpluginID_t, but then also another check must be added since changing the pluginID_t will also render settings incompatible - uint8_t Protocol[CONTROLLER_MAX] = {0}; - uint8_t Notification[NOTIFICATION_MAX] = {0}; //notifications, point to a NPLUGIN id - // FIXME TD-er: Must change to pluginID_t, but then also another check must be added since changing the pluginID_t will also render settings incompatible - uint8_t TaskDeviceNumber[N_TASKS] = {0}; // The "plugin number" set at as task (e.g. 4 for P004_dallas) - unsigned int OLD_TaskDeviceID[N_TASKS] = {0}; //UNUSED: this can be reused - - // FIXME TD-er: When used on ESP8266, this conversion union may not work - // It might work as it is 32-bit in size. - union { - struct { - int8_t TaskDevicePin1[N_TASKS]; - int8_t TaskDevicePin2[N_TASKS]; - int8_t TaskDevicePin3[N_TASKS]; - uint8_t TaskDevicePort[N_TASKS]; - }; - int8_t TaskDevicePin[4][N_TASKS]{}; - }; - boolean TaskDevicePin1PullUp[N_TASKS] = {0}; - int16_t TaskDevicePluginConfig[N_TASKS][PLUGIN_CONFIGVAR_MAX]{}; - boolean TaskDevicePin1Inversed[N_TASKS] = {0}; - float TaskDevicePluginConfigFloat[N_TASKS][PLUGIN_CONFIGFLOATVAR_MAX]{}; - - // FIXME TD-er: When used on ESP8266, this conversion union may not work - // It might work as it is 32-bit in size. - union { - int32_t TaskDevicePluginConfigLong[N_TASKS][PLUGIN_CONFIGLONGVAR_MAX]; - uint32_t TaskDevicePluginConfigULong[N_TASKS][PLUGIN_CONFIGLONGVAR_MAX]{}; - }; - uint8_t TaskDeviceSendDataFlags[N_TASKS] = {0}; - uint8_t VariousTaskBits[N_TASKS] = {0}; - uint8_t TaskDeviceDataFeed[N_TASKS] = {0}; // When set to 0, only read local connected sensorsfeeds - unsigned long TaskDeviceTimer[N_TASKS] = {0}; - boolean TaskDeviceEnabled[N_TASKS] = {0}; - boolean ControllerEnabled[CONTROLLER_MAX] = {0}; - boolean NotificationEnabled[NOTIFICATION_MAX] = {0}; - unsigned int TaskDeviceID[CONTROLLER_MAX][N_TASKS]{}; // IDX number (mainly used by Domoticz) - boolean TaskDeviceSendData[CONTROLLER_MAX][N_TASKS]{}; - boolean Pin_status_led_Inversed = false; - boolean deepSleepOnFail = false; - boolean UseValueLogger = false; - boolean ArduinoOTAEnable = false; - uint16_t DST_Start = 0; - uint16_t DST_End = 0; - boolean UseRTOSMultitasking = false; - int8_t Pin_Reset = -1; - uint8_t SyslogFacility = 0; - uint32_t StructSize = 0; // Forced to be 32 bit, to make sure alignment is clear. - boolean MQTTUseUnitNameAsClientId_unused = false; - - //its safe to extend this struct, up to several bytes, default values in config are 0 - //look in misc.ino how config.dat is used because also other stuff is stored in it at different offsets. - //TODO: document config.dat somewhere here - float Latitude = 0.0f; - float Longitude = 0.0f; - - // VariousBits_1 defaults to 0, keep in mind when adding bit lookups. - struct { - uint32_t unused_00 : 1; // Bit 00 - uint32_t appendUnitToHostname : 1; // Bit 01 Inverted - uint32_t unused_02 : 1; // Bit 02 uniqueMQTTclientIdReconnect_unused - uint32_t OldRulesEngine : 1; // Bit 03 Inverted - uint32_t ForceWiFi_bg_mode : 1; // Bit 04 - uint32_t WiFiRestart_connection_lost : 1; // Bit 05 - uint32_t EcoPowerMode : 1; // Bit 06 - uint32_t WifiNoneSleep : 1; // Bit 07 - uint32_t gratuitousARP : 1; // Bit 08 Inverted - uint32_t TolerantLastArgParse : 1; // Bit 09 - uint32_t SendToHttp_ack : 1; // Bit 10 - uint32_t UseESPEasyNow : 1; // Bit 11 - uint32_t IncludeHiddenSSID : 1; // Bit 12 - uint32_t UseMaxTXpowerForSending : 1; // Bit 13 - uint32_t ApDontForceSetup : 1; // Bit 14 - uint32_t unused_15 : 1; // Bit 15 was used by PeriodicalScanWiFi - uint32_t JSONBoolWithoutQuotes : 1; // Bit 16 - uint32_t DoNotStartAP : 1; // Bit 17 - uint32_t UseAlternativeDeepSleep : 1; // Bit 18 - uint32_t UseLastWiFiFromRTC : 1; // Bit 19 - uint32_t EnableTimingStats : 1; // Bit 20 - uint32_t AllowTaskValueSetAllPlugins : 1; // Bit 21 - uint32_t EnableClearHangingI2Cbus : 1; // Bit 22 - uint32_t EnableRAMTracking : 1; // Bit 23 - uint32_t EnableRulesCaching : 1; // Bit 24 Inverted - uint32_t EnableRulesEventReorder : 1; // Bit 25 Inverted - uint32_t AllowOTAUnlimited : 1; // Bit 26 - uint32_t SendToHTTP_follow_redirects : 1; // Bit 27 - uint32_t CssMode : 2; // Bit 28 -// uint32_t unused_29 : 1; // Bit 29 - uint32_t CheckI2Cdevice : 1; // Bit 30 Inverted - uint32_t DoNotUse_31 : 1; // Bit 31 Was used to detect whether various bits were even set - - } VariousBits_1; - - uint32_t ResetFactoryDefaultPreference = 0; // Do not clear this one in the clearAll() - uint32_t I2C_clockSpeed = 400000; - uint16_t WebserverPort = 80; - uint16_t SyslogPort = DEFAULT_SYSLOG_PORT; - - int8_t ETH_Phy_Addr = -1; - int8_t ETH_Pin_mdc_cs = -1; - int8_t ETH_Pin_mdio_irq = -1; - int8_t ETH_Pin_power_rst = -1; - EthPhyType_t ETH_Phy_Type = EthPhyType_t::notSet; - EthClockMode_t ETH_Clock_Mode = EthClockMode_t::Ext_crystal_osc; - uint8_t ETH_IP[4] = {0}; - uint8_t ETH_Gateway[4] = {0}; - uint8_t ETH_Subnet[4] = {0}; - uint8_t ETH_DNS[4] = {0}; - NetworkMedium_t NetworkMedium = NetworkMedium_t::WIFI; - int8_t I2C_Multiplexer_Type = I2C_MULTIPLEXER_NONE; - int8_t I2C_Multiplexer_Addr = -1; - int8_t I2C_Multiplexer_Channel[N_TASKS]{}; - uint8_t I2C_Flags[N_TASKS] = {0}; - uint32_t I2C_clockSpeed_Slow = 100000; - int8_t I2C_Multiplexer_ResetPin = -1; - - #ifdef ESP32 - int8_t PinBootStates_ESP32[24] = {0}; // pins 17 ... 39 - #endif - uint8_t WiFi_TX_power = 70; // 70 = 17.5dBm. unit: 0.25 dBm - int8_t WiFi_sensitivity_margin = 3; // Margin in dBm on top of sensitivity. - uint8_t NumberExtraWiFiScans = 0; - int8_t SPI_SCLK_pin = -1; - int8_t SPI_MISO_pin = -1; - int8_t SPI_MOSI_pin = -1; - int8_t ForceESPEasyNOWchannel = 0; - - // Do not rename or move this checksum. - // Checksum calculation will work "around" this - uint8_t md5[16]{}; // Store checksum of the settings. - - // VariousBits_2 defaults to 0, keep in mind when adding bit lookups. - struct { - uint32_t WaitWiFiConnect : 1; // Bit 00 - uint32_t SDK_WiFi_autoreconnect : 1; // Bit 01 - uint32_t DisableRulesCodeCompletion : 1; // Bit 02 - uint32_t HiddenSSID_SlowConnectPerBSSID : 1; // Bit 03 // inverted - uint32_t EnableIPv6 : 1; // Bit 04 // inverted - uint32_t DisableSaveConfigAsTar : 1; // Bit 05 - uint32_t unused_06 : 1; // Bit 06 - uint32_t unused_07 : 1; // Bit 07 - uint32_t unused_08 : 1; // Bit 08 - uint32_t unused_09 : 1; // Bit 09 - uint32_t unused_10 : 1; // Bit 10 - uint32_t unused_11 : 1; // Bit 11 - uint32_t unused_12 : 1; // Bit 12 - uint32_t unused_13 : 1; // Bit 13 - uint32_t unused_14 : 1; // Bit 14 - uint32_t unused_15 : 1; // Bit 15 - uint32_t unused_16 : 1; // Bit 16 - uint32_t unused_17 : 1; // Bit 17 - uint32_t unused_18 : 1; // Bit 18 - uint32_t unused_19 : 1; // Bit 19 - uint32_t unused_20 : 1; // Bit 20 - uint32_t unused_21 : 1; // Bit 21 - uint32_t unused_22 : 1; // Bit 22 - uint32_t unused_23 : 1; // Bit 23 - uint32_t unused_24 : 1; // Bit 24 - uint32_t unused_25 : 1; // Bit 25 - uint32_t unused_26 : 1; // Bit 26 - uint32_t unused_27 : 1; // Bit 27 - uint32_t unused_28 : 1; // Bit 28 - uint32_t unused_29 : 1; // Bit 29 - uint32_t unused_30 : 1; // Bit 30 - uint32_t unused_31 : 1; // Bit 31 - - } VariousBits_2; - - uint8_t console_serial_port = DEFAULT_CONSOLE_PORT; - int8_t console_serial_rxpin = DEFAULT_CONSOLE_PORT_RXPIN; - int8_t console_serial_txpin = DEFAULT_CONSOLE_PORT_TXPIN; - uint8_t console_serial0_fallback = DEFAULT_CONSOLE_SER0_FALLBACK; - - // Try to extend settings to make the checksum 4-uint8_t aligned. -}; - -/* -SettingsStruct* SettingsStruct_ptr = new (std::nothrow) SettingsStruct; -SettingsStruct& Settings = *SettingsStruct_ptr; -*/ - - - -typedef SettingsStruct_tmpl SettingsStruct; - -#endif // DATASTRUCTS_SETTINGSSTRUCT_H + +#ifndef DATASTRUCTS_SETTINGSSTRUCT_H +#define DATASTRUCTS_SETTINGSSTRUCT_H + +#include "../../ESPEasy_common.h" + +#include "../CustomBuild/ESPEasyLimits.h" +#include "../DataStructs/ChecksumType.h" +#include "../DataStructs/DeviceStruct.h" +#include "../DataTypes/EthernetParameters.h" +#include "../DataTypes/NetworkMedium.h" +#include "../DataTypes/NPluginID.h" +#include "../DataTypes/PluginID.h" +//#include "../DataTypes/TaskEnabledState.h" +#include "../DataTypes/TimeSource.h" +#include "../Globals/Plugins.h" + +#ifdef ESP32 +#include +#endif + +//we disable SPI if not defined +#ifndef DEFAULT_SPI + #define DEFAULT_SPI 0 +#endif + + +// FIXME TD-er: Move this PinBootState to DataTypes folder + +// State is stored, so don't change order +enum class PinBootState { + Default_state = 0, + Output_low = 1, + Output_high = 2, + Input_pullup = 3, + Input_pulldown = 4, // Only on ESP32 and GPIO16 on ESP82xx + Input = 5, + + // Options for later: + // ANALOG (only on ESP32) + // WAKEUP_PULLUP (only on ESP8266) + // WAKEUP_PULLDOWN (only on ESP8266) + // SPECIAL + // FUNCTION_0 (only on ESP8266) + // FUNCTION_1 + // FUNCTION_2 + // FUNCTION_3 + // FUNCTION_4 + // FUNCTION_5 (only on ESP32) + // FUNCTION_6 (only on ESP32) + +}; + + + + +/*********************************************************************************************\ + * SettingsStruct +\*********************************************************************************************/ +template +class SettingsStruct_tmpl +{ + public: + +// SettingsStruct_tmpl() = default; + + // VariousBits1 defaults to 0, keep in mind when adding bit lookups. + bool appendUnitToHostname() const { return !VariousBits_1.appendUnitToHostname; } + void appendUnitToHostname(bool value) { VariousBits_1.appendUnitToHostname = !value;} + + bool uniqueMQTTclientIdReconnect_unused() const { return VariousBits_1.unused_02; } + void uniqueMQTTclientIdReconnect_unused(bool value) { VariousBits_1.unused_02 = value; } + + bool OldRulesEngine() const { +#ifdef WEBSERVER_NEW_RULES + return !VariousBits_1.OldRulesEngine; +#else + return true; +#endif + } + void OldRulesEngine(bool value) { VariousBits_1.OldRulesEngine = !value; } + + bool ForceWiFi_bg_mode() const { return VariousBits_1.ForceWiFi_bg_mode; } + void ForceWiFi_bg_mode(bool value) { VariousBits_1.ForceWiFi_bg_mode = value; } + + bool WiFiRestart_connection_lost() const { return VariousBits_1.WiFiRestart_connection_lost; } + void WiFiRestart_connection_lost(bool value) { VariousBits_1.WiFiRestart_connection_lost = value; } + + bool EcoPowerMode() const { return VariousBits_1.EcoPowerMode; } + void EcoPowerMode(bool value) { VariousBits_1.EcoPowerMode = value; } + + bool WifiNoneSleep() const { return VariousBits_1.WifiNoneSleep; } + void WifiNoneSleep(bool value) { VariousBits_1.WifiNoneSleep = value; } + + // Enable send gratuitous ARP by default, so invert the values (default = 0) + bool gratuitousARP() const { return !VariousBits_1.gratuitousARP; } + void gratuitousARP(bool value) { VariousBits_1.gratuitousARP = !value; } + + // Be a bit more tolerant when parsing the last argument of a command. + // See: https://github.com/letscontrolit/ESPEasy/issues/2724 + bool TolerantLastArgParse() const { return VariousBits_1.TolerantLastArgParse; } + void TolerantLastArgParse(bool value) { VariousBits_1.TolerantLastArgParse = value; } + + // SendToHttp command does not wait for ack, with this flag it does wait. + bool SendToHttp_ack() const { return VariousBits_1.SendToHttp_ack; } + void SendToHttp_ack(bool value) { VariousBits_1.SendToHttp_ack = value; } + + // Enable/disable ESPEasyNow protocol + bool UseESPEasyNow() const { +#ifdef USES_ESPEASY_NOW + return VariousBits_1.UseESPEasyNow; +#else + return false; +#endif + } + void UseESPEasyNow(bool value) { +#ifdef USES_ESPEASY_NOW + VariousBits_1.UseESPEasyNow = value; +#endif + } + + // Whether to try to connect to a hidden SSID network + bool IncludeHiddenSSID() const { return VariousBits_1.IncludeHiddenSSID; } + void IncludeHiddenSSID(bool value) { VariousBits_1.IncludeHiddenSSID = value; } + + // When sending, the TX power may be boosted to max TX power. + bool UseMaxTXpowerForSending() const { return VariousBits_1.UseMaxTXpowerForSending; } + void UseMaxTXpowerForSending(bool value) { VariousBits_1.UseMaxTXpowerForSending = value; } + + // When set you can use the Sensor in AP-Mode without beeing forced to /setup + bool ApDontForceSetup() const { return VariousBits_1.ApDontForceSetup; } + void ApDontForceSetup(bool value) { VariousBits_1.ApDontForceSetup = value; } + + // When outputting JSON bools use quoted values (on, backward compatible) or use official JSON true/false unquoted + bool JSONBoolWithoutQuotes() const { return VariousBits_1.JSONBoolWithoutQuotes; } + void JSONBoolWithoutQuotes(bool value) { VariousBits_1.JSONBoolWithoutQuotes = value; } + + // Enable timing statistics (may consume a few kB of RAM) + bool EnableTimingStats() const { return VariousBits_1.EnableTimingStats; } + void EnableTimingStats(bool value) { VariousBits_1.EnableTimingStats = value; } + + // Allow to actively reset I2C bus if it appears to be hanging. + bool EnableClearHangingI2Cbus() const { +#if FEATURE_CLEAR_I2C_STUCK + return VariousBits_1.EnableClearHangingI2Cbus; +#else + return false; +#endif +} + void EnableClearHangingI2Cbus(bool value) { VariousBits_1.EnableClearHangingI2Cbus = value; } + + // Enable RAM Tracking (may consume a few kB of RAM and cause some performance hit) + bool EnableRAMTracking() const { return VariousBits_1.EnableRAMTracking; } + void EnableRAMTracking(bool value) { VariousBits_1.EnableRAMTracking = value; } + + // Enable caching of rules, to speed up rules processing + bool EnableRulesCaching() const { return !VariousBits_1.EnableRulesCaching; } + void EnableRulesCaching(bool value) { VariousBits_1.EnableRulesCaching = !value; } + + // Allow the cached event entries to be sorted based on how frequent they occur. + // This may speed up rules processing, especially on large rule sets with lots of rules blocks. + bool EnableRulesEventReorder() const { return !VariousBits_1.EnableRulesEventReorder; } + void EnableRulesEventReorder(bool value) { VariousBits_1.EnableRulesEventReorder = !value; } + + // Allow OTA to use 'unlimited' bin sized files, possibly overwriting the file-system, and trashing files + // Can be used if the configuration is later retrieved/restored manually + bool AllowOTAUnlimited() const { return VariousBits_1.AllowOTAUnlimited; } + void AllowOTAUnlimited(bool value) { VariousBits_1.AllowOTAUnlimited = value; } + + // Default behavior is to not allow following redirects + bool SendToHTTP_follow_redirects() const { return VariousBits_1.SendToHTTP_follow_redirects; } + void SendToHTTP_follow_redirects(bool value) { VariousBits_1.SendToHTTP_follow_redirects = value; } + + #if FEATURE_I2C_DEVICE_CHECK + // Check if an I2C device is found at configured address at plugin_INIT and plugin_READ + bool CheckI2Cdevice() const { return !VariousBits_1.CheckI2Cdevice; } + void CheckI2Cdevice(bool value) { VariousBits_1.CheckI2Cdevice = !value; } + #endif // if FEATURE_I2C_DEVICE_CHECK + + // Wait for a second after calling WiFi.begin() + // Especially useful for some FritzBox routers. + bool WaitWiFiConnect() const { return VariousBits_2.WaitWiFiConnect; } + void WaitWiFiConnect(bool value) { VariousBits_2.WaitWiFiConnect = value; } + +#ifdef ESP32 + // Toggle between passive/active WiFi scan. + bool PassiveWiFiScan() const { return !VariousBits_2.PassiveWiFiScan; } + void PassiveWiFiScan(bool value) { VariousBits_2.PassiveWiFiScan = !value; } +#endif + + // Connect to Hidden SSID using channel and BSSID + // This is much slower, but appears to be needed for some access points + // like MikroTik. + bool HiddenSSID_SlowConnectPerBSSID() const { return !VariousBits_2.HiddenSSID_SlowConnectPerBSSID; } + void HiddenSSID_SlowConnectPerBSSID(bool value) { VariousBits_2.HiddenSSID_SlowConnectPerBSSID = !value; } + + bool EnableIPv6() const { return !VariousBits_2.EnableIPv6; } + void EnableIPv6(bool value) { VariousBits_2.EnableIPv6 = !value; } + + // Use Espressif's auto reconnect. + bool SDK_WiFi_autoreconnect() const { return VariousBits_2.SDK_WiFi_autoreconnect; } + void SDK_WiFi_autoreconnect(bool value) { VariousBits_2.SDK_WiFi_autoreconnect = value; } + + #if FEATURE_RULES_EASY_COLOR_CODE + // Inhibit RulesCodeCompletion + bool DisableRulesCodeCompletion() const { return VariousBits_2.DisableRulesCodeCompletion; } + void DisableRulesCodeCompletion(bool value) { VariousBits_2.DisableRulesCodeCompletion = value; } + #endif // if FEATURE_RULES_EASY_COLOR_CODE + + #if FEATURE_TARSTREAM_SUPPORT + bool DisableSaveConfigAsTar() const { return VariousBits_2.DisableSaveConfigAsTar; } + void DisableSaveConfigAsTar(bool value) { VariousBits_2.DisableSaveConfigAsTar = value; } + #endif // if FEATURE_TARSTREAM_SUPPORT + + // Flag indicating whether all task values should be sent in a single event or one event per task value (default behavior) + bool CombineTaskValues_SingleEvent(taskIndex_t taskIndex) const; + void CombineTaskValues_SingleEvent(taskIndex_t taskIndex, bool value); + + bool DoNotStartAP() const { return VariousBits_1.DoNotStartAP; } + void DoNotStartAP(bool value) { VariousBits_1.DoNotStartAP = value; } + + bool UseAlternativeDeepSleep() const { return VariousBits_1.UseAlternativeDeepSleep; } + void UseAlternativeDeepSleep(bool value) { VariousBits_1.UseAlternativeDeepSleep = value; } + + bool UseLastWiFiFromRTC() const { return VariousBits_1.UseLastWiFiFromRTC; } + void UseLastWiFiFromRTC(bool value) { VariousBits_1.UseLastWiFiFromRTC = value; } + + ExtTimeSource_e ExtTimeSource() const; + void ExtTimeSource(ExtTimeSource_e value); + + bool UseNTP() const; + void UseNTP(bool value); + + bool AllowTaskValueSetAllPlugins() const { return VariousBits_1.AllowTaskValueSetAllPlugins; } + void AllowTaskValueSetAllPlugins(bool value) { VariousBits_1.AllowTaskValueSetAllPlugins = value; } + + #if FEATURE_AUTO_DARK_MODE + uint8_t getCssMode() const { return VariousBits_1.CssMode; } + void setCssMode(uint8_t value) { VariousBits_1.CssMode = value; } + #endif // FEATURE_AUTO_DARK_MODE + + bool isTaskEnableReadonly(taskIndex_t taskIndex) const; + void setTaskEnableReadonly(taskIndex_t taskIndex, bool value); + + #if FEATURE_PLUGIN_PRIORITY + bool isPowerManagerTask(taskIndex_t taskIndex) const; + void setPowerManagerTask(taskIndex_t taskIndex, bool value); + + bool isPriorityTask(taskIndex_t taskIndex) const; + #endif // if FEATURE_PLUGIN_PRIORITY + + void validate(); + + bool networkSettingsEmpty() const; + + void clearNetworkSettings(); + + void clearTimeSettings(); + + void clearNotifications(); + + void clearControllers(); + + void clearTasks(); + + void clearLogSettings(); + + void clearUnitNameSettings(); + + void clearMisc(); + + void clearTask(taskIndex_t task); + + // Return hostname + unit when selected to add unit. + String getHostname() const; + + // Return hostname with explicit set append unit. + String getHostname(bool appendUnit) const; + + // Return the name of the unit, without unitnr appended, with template parsing applied, replacement for Settings.Name in most places + String getName() const; + +private: + + // Compute the index in either + // - PinBootStates array (index_low) or + // - PinBootStates_ESP32 (index_high) + // Returns whether it is a valid index + bool getPinBootStateIndex( + int8_t gpio_pin, + int8_t& index_low + #ifdef ESP32 + , int8_t& index_high + #endif + ) const; + +public: + + PinBootState getPinBootState(int8_t gpio_pin) const; + void setPinBootState(int8_t gpio_pin, PinBootState state); + + bool getSPI_pins(int8_t spi_gpios[3]) const; + + #ifdef ESP32 + spi_host_device_t getSPI_host() const; + #endif + + // Return true when pin is one of the SPI pins and SPI is enabled + bool isSPI_pin(int8_t pin) const; + + // Return true when SPI enabled and opt. user defined pins valid. + bool isSPI_valid() const; + + // Return true when pin is one of the configured I2C pins. + bool isI2C_pin(int8_t pin) const; + + // Return true if I2C settings are correct + bool isI2CEnabled() const; + + // Return true when pin is one of the fixed Ethernet pins and Ethernet is enabled + bool isEthernetPin(int8_t pin) const; + + // Return true when pin is one of the optional Ethernet pins and Ethernet is enabled + bool isEthernetPinOptional(int8_t pin) const; + + // Access to TaskDevicePin1 ... TaskDevicePin3 + // @param pinnr 1 = TaskDevicePin1, ..., 3 = TaskDevicePin3 + int8_t getTaskDevicePin(taskIndex_t taskIndex, uint8_t pinnr) const; + + float getWiFi_TX_power() const; + void setWiFi_TX_power(float dBm); + + pluginID_t getPluginID_for_task(taskIndex_t taskIndex) const; + + void forceSave() { memset(md5, 0, 16); } + + uint32_t getVariousBits1() const { + uint32_t res; + memcpy(&res, &VariousBits_1, sizeof(VariousBits_1)); + return res; + } + + void setVariousBits1(uint32_t value) { + memcpy(&VariousBits_1, &value, sizeof(VariousBits_1)); + } + + uint32_t getVariousBits2() const { + uint32_t res; + memcpy(&res, &VariousBits_2, sizeof(VariousBits_2)); + return res; + } + + void setVariousBits2(uint32_t value) { + memcpy(&VariousBits_2, &value, sizeof(VariousBits_2)); + } + + + unsigned long PID = 0; + int Version = 0; + int16_t Build = 0; + uint8_t IP[4] = {0}; + uint8_t Gateway[4] = {0}; + uint8_t Subnet[4] = {0}; + uint8_t DNS[4] = {0}; + uint8_t IP_Octet = 0; + uint8_t Unit = 0; + char Name[26] = {0}; + char NTPHost[64] = {0}; + // FIXME TD-er: Issue #2690 + unsigned long Delay = 0; // Sleep time in seconds + int8_t Pin_i2c_sda = DEFAULT_PIN_I2C_SDA; + int8_t Pin_i2c_scl = DEFAULT_PIN_I2C_SCL; + int8_t Pin_status_led = DEFAULT_PIN_STATUS_LED; + int8_t Pin_sd_cs = -1; + int8_t PinBootStates[17] = {0}; // Only use getPinBootState and setPinBootState as multiple pins are packed for ESP32 + uint8_t Syslog_IP[4] = {0}; + unsigned int UDPPort = 8266; + uint8_t SyslogLevel = 0; + uint8_t SerialLogLevel = 0; + uint8_t WebLogLevel = 0; + uint8_t SDLogLevel = 0; + unsigned long BaudRate = 115200; + unsigned long MessageDelay_unused = 0; // MQTT settings now moved to the controller settings. + uint8_t deepSleep_wakeTime = 0; // 0 = Sleep Disabled, else time awake from sleep in seconds + boolean CustomCSS = false; + boolean DST = false; + uint8_t WDI2CAddress = 0; + boolean UseRules = false; + boolean UseSerial = false; + boolean UseSSDP = false; + uint8_t ExternalTimeSource = 0; + unsigned long WireClockStretchLimit = 0; + boolean GlobalSync = false; + unsigned long ConnectionFailuresThreshold = 0; + int16_t TimeZone = 0; + boolean MQTTRetainFlag_unused = false; + uint8_t InitSPI = 0; //0 = disabled, 1= enabled but for ESP32 there is option 2= SPI2 9 = User defined, see src/src/WebServer/HardwarePage.h enum SPI_Options_e + // FIXME TD-er: Must change to cpluginID_t, but then also another check must be added since changing the pluginID_t will also render settings incompatible + uint8_t Protocol[CONTROLLER_MAX] = {0}; + uint8_t Notification[NOTIFICATION_MAX] = {0}; //notifications, point to a NPLUGIN id + // FIXME TD-er: Must change to pluginID_t, but then also another check must be added since changing the pluginID_t will also render settings incompatible + uint8_t TaskDeviceNumber[N_TASKS] = {0}; // The "plugin number" set at as task (e.g. 4 for P004_dallas) + unsigned int OLD_TaskDeviceID[N_TASKS] = {0}; //UNUSED: this can be reused + + // FIXME TD-er: When used on ESP8266, this conversion union may not work + // It might work as it is 32-bit in size. + union { + struct { + int8_t TaskDevicePin1[N_TASKS]; + int8_t TaskDevicePin2[N_TASKS]; + int8_t TaskDevicePin3[N_TASKS]; + uint8_t TaskDevicePort[N_TASKS]; + }; + int8_t TaskDevicePin[4][N_TASKS]{}; + }; + boolean TaskDevicePin1PullUp[N_TASKS] = {0}; + int16_t TaskDevicePluginConfig[N_TASKS][PLUGIN_CONFIGVAR_MAX]{}; + boolean TaskDevicePin1Inversed[N_TASKS] = {0}; + float TaskDevicePluginConfigFloat[N_TASKS][PLUGIN_CONFIGFLOATVAR_MAX]{}; + + // FIXME TD-er: When used on ESP8266, this conversion union may not work + // It might work as it is 32-bit in size. + union { + int32_t TaskDevicePluginConfigLong[N_TASKS][PLUGIN_CONFIGLONGVAR_MAX]; + uint32_t TaskDevicePluginConfigULong[N_TASKS][PLUGIN_CONFIGLONGVAR_MAX]{}; + }; + uint8_t TaskDeviceSendDataFlags[N_TASKS] = {0}; + uint8_t VariousTaskBits[N_TASKS] = {0}; + uint8_t TaskDeviceDataFeed[N_TASKS] = {0}; // When set to 0, only read local connected sensorsfeeds + unsigned long TaskDeviceTimer[N_TASKS] = {0}; + boolean TaskDeviceEnabled[N_TASKS] = {0}; + boolean ControllerEnabled[CONTROLLER_MAX] = {0}; + boolean NotificationEnabled[NOTIFICATION_MAX] = {0}; + unsigned int TaskDeviceID[CONTROLLER_MAX][N_TASKS]{}; // IDX number (mainly used by Domoticz) + boolean TaskDeviceSendData[CONTROLLER_MAX][N_TASKS]{}; + boolean Pin_status_led_Inversed = false; + boolean deepSleepOnFail = false; + boolean UseValueLogger = false; + boolean ArduinoOTAEnable = false; + uint16_t DST_Start = 0; + uint16_t DST_End = 0; + boolean UseRTOSMultitasking = false; + int8_t Pin_Reset = -1; + uint8_t SyslogFacility = 0; + uint32_t StructSize = 0; // Forced to be 32 bit, to make sure alignment is clear. + boolean MQTTUseUnitNameAsClientId_unused = false; + + //its safe to extend this struct, up to several bytes, default values in config are 0 + //look in misc.ino how config.dat is used because also other stuff is stored in it at different offsets. + //TODO: document config.dat somewhere here + float Latitude = 0.0f; + float Longitude = 0.0f; + + // VariousBits_1 defaults to 0, keep in mind when adding bit lookups. + struct { + uint32_t unused_00 : 1; // Bit 00 + uint32_t appendUnitToHostname : 1; // Bit 01 Inverted + uint32_t unused_02 : 1; // Bit 02 uniqueMQTTclientIdReconnect_unused + uint32_t OldRulesEngine : 1; // Bit 03 Inverted + uint32_t ForceWiFi_bg_mode : 1; // Bit 04 + uint32_t WiFiRestart_connection_lost : 1; // Bit 05 + uint32_t EcoPowerMode : 1; // Bit 06 + uint32_t WifiNoneSleep : 1; // Bit 07 + uint32_t gratuitousARP : 1; // Bit 08 Inverted + uint32_t TolerantLastArgParse : 1; // Bit 09 + uint32_t SendToHttp_ack : 1; // Bit 10 + uint32_t UseESPEasyNow : 1; // Bit 11 + uint32_t IncludeHiddenSSID : 1; // Bit 12 + uint32_t UseMaxTXpowerForSending : 1; // Bit 13 + uint32_t ApDontForceSetup : 1; // Bit 14 + uint32_t unused_15 : 1; // Bit 15 was used by PeriodicalScanWiFi + uint32_t JSONBoolWithoutQuotes : 1; // Bit 16 + uint32_t DoNotStartAP : 1; // Bit 17 + uint32_t UseAlternativeDeepSleep : 1; // Bit 18 + uint32_t UseLastWiFiFromRTC : 1; // Bit 19 + uint32_t EnableTimingStats : 1; // Bit 20 + uint32_t AllowTaskValueSetAllPlugins : 1; // Bit 21 + uint32_t EnableClearHangingI2Cbus : 1; // Bit 22 + uint32_t EnableRAMTracking : 1; // Bit 23 + uint32_t EnableRulesCaching : 1; // Bit 24 Inverted + uint32_t EnableRulesEventReorder : 1; // Bit 25 Inverted + uint32_t AllowOTAUnlimited : 1; // Bit 26 + uint32_t SendToHTTP_follow_redirects : 1; // Bit 27 + uint32_t CssMode : 2; // Bit 28 +// uint32_t unused_29 : 1; // Bit 29 + uint32_t CheckI2Cdevice : 1; // Bit 30 Inverted + uint32_t DoNotUse_31 : 1; // Bit 31 Was used to detect whether various bits were even set + + } VariousBits_1; + + uint32_t ResetFactoryDefaultPreference = 0; // Do not clear this one in the clearAll() + uint32_t I2C_clockSpeed = 400000; + uint16_t WebserverPort = 80; + uint16_t SyslogPort = DEFAULT_SYSLOG_PORT; + + int8_t ETH_Phy_Addr = -1; + int8_t ETH_Pin_mdc_cs = -1; + int8_t ETH_Pin_mdio_irq = -1; + int8_t ETH_Pin_power_rst = -1; + EthPhyType_t ETH_Phy_Type = EthPhyType_t::notSet; + EthClockMode_t ETH_Clock_Mode = EthClockMode_t::Ext_crystal_osc; + uint8_t ETH_IP[4] = {0}; + uint8_t ETH_Gateway[4] = {0}; + uint8_t ETH_Subnet[4] = {0}; + uint8_t ETH_DNS[4] = {0}; + NetworkMedium_t NetworkMedium = NetworkMedium_t::WIFI; + int8_t I2C_Multiplexer_Type = I2C_MULTIPLEXER_NONE; + int8_t I2C_Multiplexer_Addr = -1; + int8_t I2C_Multiplexer_Channel[N_TASKS]{}; + uint8_t I2C_Flags[N_TASKS] = {0}; + uint32_t I2C_clockSpeed_Slow = 100000; + int8_t I2C_Multiplexer_ResetPin = -1; + + #ifdef ESP32 + int8_t PinBootStates_ESP32[24] = {0}; // pins 17 ... 39 + #endif + uint8_t WiFi_TX_power = 70; // 70 = 17.5dBm. unit: 0.25 dBm + int8_t WiFi_sensitivity_margin = 3; // Margin in dBm on top of sensitivity. + uint8_t NumberExtraWiFiScans = 0; + int8_t SPI_SCLK_pin = -1; + int8_t SPI_MISO_pin = -1; + int8_t SPI_MOSI_pin = -1; + int8_t ForceESPEasyNOWchannel = 0; + + // Do not rename or move this checksum. + // Checksum calculation will work "around" this + uint8_t md5[16]{}; // Store checksum of the settings. + + // VariousBits_2 defaults to 0, keep in mind when adding bit lookups. + struct { + uint32_t WaitWiFiConnect : 1; // Bit 00 + uint32_t SDK_WiFi_autoreconnect : 1; // Bit 01 + uint32_t DisableRulesCodeCompletion : 1; // Bit 02 + uint32_t HiddenSSID_SlowConnectPerBSSID : 1; // Bit 03 // inverted + uint32_t EnableIPv6 : 1; // Bit 04 // inverted + uint32_t DisableSaveConfigAsTar : 1; // Bit 05 + uint32_t PassiveWiFiScan : 1; // Bit 06 // inverted + uint32_t unused_07 : 1; // Bit 07 + uint32_t unused_08 : 1; // Bit 08 + uint32_t unused_09 : 1; // Bit 09 + uint32_t unused_10 : 1; // Bit 10 + uint32_t unused_11 : 1; // Bit 11 + uint32_t unused_12 : 1; // Bit 12 + uint32_t unused_13 : 1; // Bit 13 + uint32_t unused_14 : 1; // Bit 14 + uint32_t unused_15 : 1; // Bit 15 + uint32_t unused_16 : 1; // Bit 16 + uint32_t unused_17 : 1; // Bit 17 + uint32_t unused_18 : 1; // Bit 18 + uint32_t unused_19 : 1; // Bit 19 + uint32_t unused_20 : 1; // Bit 20 + uint32_t unused_21 : 1; // Bit 21 + uint32_t unused_22 : 1; // Bit 22 + uint32_t unused_23 : 1; // Bit 23 + uint32_t unused_24 : 1; // Bit 24 + uint32_t unused_25 : 1; // Bit 25 + uint32_t unused_26 : 1; // Bit 26 + uint32_t unused_27 : 1; // Bit 27 + uint32_t unused_28 : 1; // Bit 28 + uint32_t unused_29 : 1; // Bit 29 + uint32_t unused_30 : 1; // Bit 30 + uint32_t unused_31 : 1; // Bit 31 + + } VariousBits_2; + + uint8_t console_serial_port = DEFAULT_CONSOLE_PORT; + int8_t console_serial_rxpin = DEFAULT_CONSOLE_PORT_RXPIN; + int8_t console_serial_txpin = DEFAULT_CONSOLE_PORT_TXPIN; + uint8_t console_serial0_fallback = DEFAULT_CONSOLE_SER0_FALLBACK; + + // Try to extend settings to make the checksum 4-uint8_t aligned. +}; + +/* +SettingsStruct* SettingsStruct_ptr = new (std::nothrow) SettingsStruct; +SettingsStruct& Settings = *SettingsStruct_ptr; +*/ + + + +typedef SettingsStruct_tmpl SettingsStruct; + +#endif // DATASTRUCTS_SETTINGSSTRUCT_H diff --git a/src/src/DataStructs/ShortChecksumType.cpp b/src/src/DataStructs/ShortChecksumType.cpp new file mode 100644 index 0000000000..b605779d7c --- /dev/null +++ b/src/src/DataStructs/ShortChecksumType.cpp @@ -0,0 +1,137 @@ +#include "../DataStructs/ShortChecksumType.h" + +#include "../Helpers/StringConverter.h" + +#include + +void ShortChecksumType::md5sumToShortChecksum(const uint8_t md5[16], uint8_t shortChecksum[4]) +{ + memset(shortChecksum, 0, 4); + + for (uint8_t i = 0; i < 16; ++i) { + // shortChecksum is XOR per 32 bit + shortChecksum[i % 4] ^= md5[i]; + } +} + +ShortChecksumType::ShortChecksumType(const ShortChecksumType& rhs) +{ + memcpy(_checksum, rhs._checksum, 4); +} + +ShortChecksumType::ShortChecksumType(uint8_t checksum[4]) +{ + memcpy(_checksum, checksum, 4); +} + +ShortChecksumType::ShortChecksumType(const uint8_t *data, + size_t data_length) +{ + computeChecksum(_checksum, data, data_length, data_length, true); +} + +ShortChecksumType::ShortChecksumType(const uint8_t *data, + size_t data_length, + size_t len_upto_checksum) +{ + computeChecksum(_checksum, data, data_length, len_upto_checksum, true); +} + +ShortChecksumType::ShortChecksumType(const String strings[], size_t nrStrings) +{ + MD5Builder md5; + + md5.begin(); + + for (size_t i = 0; i < nrStrings; ++i) { + md5.add(strings[i].c_str()); + } + md5.calculate(); + uint8_t tmp_md5[16] = { 0 }; + + md5.getBytes(tmp_md5); + md5sumToShortChecksum(tmp_md5, _checksum); +} + +bool ShortChecksumType::computeChecksum( + uint8_t checksum[4], + const uint8_t *data, + size_t data_length, + size_t len_upto_checksum, + bool updateChecksum) +{ + if (len_upto_checksum > data_length) { len_upto_checksum = data_length; } + MD5Builder md5; + + md5.begin(); + + if (len_upto_checksum > 0) { + // MD5Builder::add has non-const argument + md5.add(const_cast(data), len_upto_checksum); + } + + if ((len_upto_checksum + 4) < data_length) { + data += len_upto_checksum + 4; + const int len_after_checksum = data_length - 4 - len_upto_checksum; + + if (len_after_checksum > 0) { + // MD5Builder::add has non-const argument + md5.add(const_cast(data), len_after_checksum); + } + } + md5.calculate(); + uint8_t tmp_checksum[4] = { 0 }; + + { + uint8_t tmp_md5[16] = { 0 }; + md5.getBytes(tmp_md5); + md5sumToShortChecksum(tmp_md5, tmp_checksum); + } + + if (memcmp(tmp_checksum, checksum, 4) != 0) { + // Data has changed, copy computed checksum + if (updateChecksum) { + memcpy(checksum, tmp_checksum, 4); + } + return false; + } + return true; +} + +void ShortChecksumType::getChecksum(uint8_t checksum[4]) const { + memcpy(checksum, _checksum, 4); +} + +void ShortChecksumType::setChecksum(const uint8_t checksum[4]) { + memcpy(_checksum, checksum, 4); +} + +bool ShortChecksumType::matchChecksum(const uint8_t checksum[4]) const { + return memcmp(_checksum, checksum, 4) == 0; +} + +bool ShortChecksumType::operator==(const ShortChecksumType& rhs) const { + return memcmp(_checksum, rhs._checksum, 4) == 0; +} + +ShortChecksumType& ShortChecksumType::operator=(const ShortChecksumType& rhs) { + memcpy(_checksum, rhs._checksum, 4); + return *this; +} + +String ShortChecksumType::toString() const { + return formatToHex_array(_checksum, 4); +} + +bool ShortChecksumType::isSet() const { + return + _checksum[0] != 0 || + _checksum[1] != 0 || + _checksum[2] != 0 || + _checksum[3] != 0; +} + +void ShortChecksumType::clear() +{ + memset(_checksum, 0, 4); +} diff --git a/src/src/DataStructs/ShortChecksumType.h b/src/src/DataStructs/ShortChecksumType.h new file mode 100644 index 0000000000..cae8526047 --- /dev/null +++ b/src/src/DataStructs/ShortChecksumType.h @@ -0,0 +1,57 @@ +#ifndef DATASTRUCTS_SHORTCHECKSUMTYPE_H +#define DATASTRUCTS_SHORTCHECKSUMTYPE_H + +#include "../../ESPEasy_common.h" + +// Short (4 byte) version of ChecksumType +struct __attribute__((__packed__)) ShortChecksumType { + // Empty checksum + ShortChecksumType() = default; + + ShortChecksumType(const ShortChecksumType& rhs); + + ShortChecksumType(uint8_t checksum[4]); + + // Construct with checksum over entire range of given data + ShortChecksumType(const uint8_t *data, + size_t data_length); + + ShortChecksumType(const uint8_t *data, + size_t data_length, + size_t len_upto_checksum); + + ShortChecksumType(const String strings[], + size_t nrStrings); + + // Compute checksum of the data. + // Skip the part where the checksum may be located in the data + // @param checksum The expected checksum. Will contain checksum after call finished. + // @retval true when checksum matches + static bool computeChecksum( + uint8_t checksum[4], + const uint8_t *data, + size_t data_length, + size_t len_upto_checksum, + bool updateChecksum = true); + + void getChecksum(uint8_t checksum[4]) const; + void setChecksum(const uint8_t checksum[4]); + bool matchChecksum(const uint8_t checksum[4]) const; + bool operator==(const ShortChecksumType& rhs) const; + ShortChecksumType& operator=(const ShortChecksumType& rhs); + + String toString() const; + + bool isSet() const; + + void clear(); + +private: + + static void md5sumToShortChecksum(const uint8_t md5[16], + uint8_t shortChecksum[4]); + + uint8_t _checksum[4] = { 0 }; +}; + +#endif // ifndef DATASTRUCTS_SHORTCHECKSUMTYPE_H diff --git a/src/src/DataStructs/TimingStats.cpp b/src/src/DataStructs/TimingStats.cpp index eb45f2274f..fdf9431194 100644 --- a/src/src/DataStructs/TimingStats.cpp +++ b/src/src/DataStructs/TimingStats.cpp @@ -96,6 +96,7 @@ const __FlashStringHelper* getPluginFunctionName(int function) { case PLUGIN_REQUEST: return F("REQUEST"); case PLUGIN_PROCESS_CONTROLLER_DATA: return F("PROCESS_CONTROLLER_DATA"); case PLUGIN_I2C_GET_ADDRESS: return F("I2C_CHECK_DEVICE"); + case PLUGIN_READ_ERROR_OCCURED: return F("PLUGIN_READ_ERROR_OCCURED"); } return F("Unknown"); } @@ -134,6 +135,7 @@ bool mustLogFunction(int function) { case PLUGIN_REQUEST: return true; case PLUGIN_I2C_GET_ADDRESS: return true; case PLUGIN_PROCESS_CONTROLLER_DATA: return true; + case PLUGIN_READ_ERROR_OCCURED: return true; } return false; } @@ -141,6 +143,8 @@ bool mustLogFunction(int function) { const __FlashStringHelper* getCPluginCFunctionName(CPlugin::Function function) { switch (function) { case CPlugin::Function::CPLUGIN_PROTOCOL_ADD: return F("CPLUGIN_PROTOCOL_ADD"); + case CPlugin::Function::CPLUGIN_CONNECT_SUCCESS: return F("CPLUGIN_CONNECT_SUCCESS"); + case CPlugin::Function::CPLUGIN_CONNECT_FAIL: return F("CPLUGIN_CONNECT_FAIL"); case CPlugin::Function::CPLUGIN_PROTOCOL_TEMPLATE: return F("CPLUGIN_PROTOCOL_TEMPLATE"); case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: return F("CPLUGIN_PROTOCOL_SEND"); case CPlugin::Function::CPLUGIN_PROTOCOL_RECV: return F("CPLUGIN_PROTOCOL_RECV"); @@ -173,6 +177,8 @@ bool mustLogCFunction(CPlugin::Function function) { switch (function) { case CPlugin::Function::CPLUGIN_PROTOCOL_ADD: return false; + case CPlugin::Function::CPLUGIN_CONNECT_SUCCESS: return true; + case CPlugin::Function::CPLUGIN_CONNECT_FAIL: return true; case CPlugin::Function::CPLUGIN_PROTOCOL_TEMPLATE: return false; case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: return true; case CPlugin::Function::CPLUGIN_PROTOCOL_RECV: return true; @@ -247,6 +253,12 @@ const __FlashStringHelper* getMiscStatsName_F(TimingStatsElements stat) { case TimingStatsElements::GRAT_ARP_STATS: return F("sendGratuitousARP()"); case TimingStatsElements::SAVE_TO_RTC: return F("saveToRTC()"); case TimingStatsElements::BACKGROUND_TASKS: return F("backgroundtasks()"); + case TimingStatsElements::UPDATE_RTTTL: return F("update_rtttl()"); + case TimingStatsElements::CHECK_UDP: return F("checkUDP()"); + case TimingStatsElements::C013_SEND_UDP: return F("C013_sendUDP() SUCCESS"); + case TimingStatsElements::C013_SEND_UDP_FAIL: return F("C013_sendUDP() FAIL"); + case TimingStatsElements::C013_RECEIVE_SENSOR_DATA: return F("C013 Receive sensor data"); + case TimingStatsElements::WEBSERVER_HANDLE_CLIENT: return F("web_server.handleClient()"); case TimingStatsElements::PROCESS_SYSTEM_EVENT_QUEUE: return F("process_system_event_queue()"); case TimingStatsElements::FORMAT_USER_VAR: return F("doFormatUserVar()"); case TimingStatsElements::IS_NUMERICAL: return F("isNumerical()"); diff --git a/src/src/DataStructs/TimingStats.h b/src/src/DataStructs/TimingStats.h index e10de2fe77..41126efef2 100644 --- a/src/src/DataStructs/TimingStats.h +++ b/src/src/DataStructs/TimingStats.h @@ -106,6 +106,12 @@ enum class TimingStatsElements { HANDLE_SCHEDULER_TASK, HANDLE_SCHEDULER_IDLE, BACKGROUND_TASKS, + CHECK_UDP, + C013_SEND_UDP, + C013_SEND_UDP_FAIL, + C013_RECEIVE_SENSOR_DATA, + WEBSERVER_HANDLE_CLIENT, + UPDATE_RTTTL, // Web serving HANDLE_SERVING_WEBPAGE, diff --git a/src/src/DataStructs/UserVarStruct.cpp b/src/src/DataStructs/UserVarStruct.cpp index 96d4bb333f..1c97650ada 100644 --- a/src/src/DataStructs/UserVarStruct.cpp +++ b/src/src/DataStructs/UserVarStruct.cpp @@ -263,7 +263,6 @@ ESPEASY_RULES_FLOAT_TYPE UserVarStruct::getAsDouble(taskIndex_t taskIndex, String UserVarStruct::getAsString(taskIndex_t taskIndex, taskVarIndex_t varNr, Sensor_VType sensorType, uint8_t nrDecimals, bool raw) const { - START_TIMER const TaskValues_Data_t *data = getRawOrComputed(taskIndex, varNr, sensorType, raw); if (data != nullptr) { diff --git a/src/src/DataStructs/WiFi_AP_Candidate.cpp b/src/src/DataStructs/WiFi_AP_Candidate.cpp index e328d7e79d..ddb96812cc 100644 --- a/src/src/DataStructs/WiFi_AP_Candidate.cpp +++ b/src/src/DataStructs/WiFi_AP_Candidate.cpp @@ -1,259 +1,259 @@ -#include "../DataStructs/WiFi_AP_Candidate.h" - -#include "../Globals/ESPEasyWiFiEvent.h" -#include "../Globals/SecuritySettings.h" -#include "../Globals/Statistics.h" -#include "../Helpers/ESPEasy_time_calc.h" -#include "../Helpers/Misc.h" -#include "../Helpers/StringConverter.h" -#include "../Helpers/StringGenerator_WiFi.h" -#include "../../ESPEasy_common.h" - -#if defined(ESP8266) - # include -#endif // if defined(ESP8266) -#if defined(ESP32) - # include -#endif // if defined(ESP32) - -#define WIFI_AP_CANDIDATE_MAX_AGE 300000 // 5 minutes in msec - - -WiFi_AP_Candidate::WiFi_AP_Candidate() : -#ifdef ESP32 -# if ESP_IDF_VERSION_MAJOR >= 5 -country({ - .cc = "01", - .schan = 1, - .nchan = 11, - .policy = WIFI_COUNTRY_POLICY_AUTO, -}), -#endif -#endif - last_seen(0), rssi(0), channel(0), index(0), enc_type(0) -{ - memset(&bits, 0, sizeof(bits)); -} - -WiFi_AP_Candidate::WiFi_AP_Candidate(uint8_t index_c, const String& ssid_c) : - last_seen(0), rssi(0), channel(0), index(index_c), enc_type(0) -{ - memset(&bits, 0, sizeof(bits)); - - const size_t ssid_length = ssid_c.length(); - - if ((ssid_length == 0) || equals(ssid_c, F("ssid"))) { - return; - } - - if (ssid_length > 32) { return; } - - ssid = ssid_c; -} - -WiFi_AP_Candidate::WiFi_AP_Candidate(uint8_t networkItem) : index(0) { - // Need to make sure the phy isn't known as we can't get this information from the AP - // See: https://github.com/letscontrolit/ESPEasy/issues/4996 - // Not sure why this makes any difference as the flags should already have been set to 0. - memset(&bits, 0, sizeof(bits)); - - ssid = WiFi.SSID(networkItem); - rssi = WiFi.RSSI(networkItem); - channel = WiFi.channel(networkItem); - bssid = WiFi.BSSID(networkItem); - enc_type = WiFi.encryptionType(networkItem); - #ifdef ESP8266 - bits.isHidden = WiFi.isHidden(networkItem); - # ifdef CORE_POST_3_0_0 - const bss_info *it = reinterpret_cast(WiFi.getScanInfoByIndex(networkItem)); - - if (it) { - bits.phy_11b = it->phy_11b; - bits.phy_11g = it->phy_11g; - bits.phy_11n = it->phy_11n; - bits.wps = it->wps; - } - # endif // ifdef CORE_POST_3_0_0 - #endif // ifdef ESP8266 - #ifdef ESP32 - bits.isHidden = ssid.isEmpty(); - wifi_ap_record_t *it = reinterpret_cast(WiFi.getScanInfoByIndex(networkItem)); - - if (it) { - bits.phy_11b = it->phy_11b; - bits.phy_11g = it->phy_11g; - bits.phy_11n = it->phy_11n; - bits.phy_lr = it->phy_lr; -# if ESP_IDF_VERSION_MAJOR >= 5 - bits.phy_11ax = it->phy_11ax; - bits.ftm_initiator = it->ftm_initiator; - bits.ftm_responder = it->ftm_responder; -# endif // if ESP_IDF_VERSION_MAJOR >= 5 - bits.wps = it->wps; - - // FIXME TD-er: Maybe also add other info like 2nd channel, ftm and phy_lr support? -# if ESP_IDF_VERSION_MAJOR >= 5 - memcpy(&country, &(it->country), sizeof(wifi_country_t)); -#endif - } - #endif // ifdef ESP32 - last_seen = millis(); -} - -#ifdef ESP8266 -# if FEATURE_ESP8266_DIRECT_WIFI_SCAN -WiFi_AP_Candidate::WiFi_AP_Candidate(const bss_info& ap) : - rssi(ap.rssi), channel(ap.channel), bssid(ap.bssid), - index(0), enc_type(0), isHidden(ap.is_hidden), - phy_11b(ap.phy_11b), phy_11g(ap.phy_11g), phy_11n(ap.phy_11n), - wps(ap.wps) -{ - memset(&bits, 0, sizeof(bits)); - - last_seen = millis(); - - switch (ap.authmode) { - case AUTH_OPEN: enc_type = ENC_TYPE_NONE; break; - case AUTH_WEP: enc_type = ENC_TYPE_WEP; break; - case AUTH_WPA_PSK: enc_type = ENC_TYPE_TKIP; break; - case AUTH_WPA2_PSK: enc_type = ENC_TYPE_CCMP; break; - case AUTH_WPA_WPA2_PSK: enc_type = ENC_TYPE_AUTO; break; - case AUTH_MAX: break; - } - - char tmp[33]; // ssid can be up to 32chars, => plus null term - const size_t ssid_len = std::min(static_cast(ap.ssid_len), sizeof(ap.ssid)); - - memcpy(tmp, ap.ssid, ssid_len); - tmp[ssid_len] = 0; // nullterm marking end of string - - ssid = String(reinterpret_cast(tmp)); -} - -# endif // if FEATURE_ESP8266_DIRECT_WIFI_SCAN -#endif // ifdef ESP8266 - - -bool WiFi_AP_Candidate::operator<(const WiFi_AP_Candidate& other) const { - if (bits.isEmergencyFallback != other.bits.isEmergencyFallback) { - return bits.isEmergencyFallback; - } - - if (bits.lowPriority != other.bits.lowPriority) { - return !bits.lowPriority; - } - - // Prefer non hidden over hidden. - if (bits.isHidden != other.bits.isHidden) { - return !bits.isHidden; - } - - // RSSI values >= 0 are invalid - if (rssi >= 0) { return false; } - - if (other.rssi >= 0) { return true; } - - // RSSI values are negative, so the larger value is the better one. - return rssi > other.rssi; -} - -bool WiFi_AP_Candidate::usable() const { - // Allow for empty pass - // if (key.isEmpty()) return false; - if (bits.isEmergencyFallback) { - int allowedUptimeMinutes = 10; - #ifdef CUSTOM_EMERGENCY_FALLBACK_ALLOW_MINUTES_UPTIME - allowedUptimeMinutes = CUSTOM_EMERGENCY_FALLBACK_ALLOW_MINUTES_UPTIME; - #endif // ifdef CUSTOM_EMERGENCY_FALLBACK_ALLOW_MINUTES_UPTIME - - if ((getUptimeMinutes() > allowedUptimeMinutes) || - !SecuritySettings.hasWiFiCredentials() || - WiFiEventData.performedClearWiFiCredentials || - (lastBootCause != BOOT_CAUSE_COLD_BOOT)) { - return false; - } - } - - if (!bits.isHidden && (ssid.isEmpty())) { return false; } - return !expired(); -} - -bool WiFi_AP_Candidate::expired() const { - if (last_seen == 0) { - // Not set, so cannot expire - return false; - } - return timePassedSince(last_seen) > WIFI_AP_CANDIDATE_MAX_AGE; -} - -String WiFi_AP_Candidate::toString(const String& separator) const { - String result = ssid; - - htmlEscape(result); - - if (bits.isHidden) { - result += F("#Hidden#"); - } - result += strformat( - F("%s%s%sCh:%u"), - separator.c_str(), - bssid.toString().c_str(), - separator.c_str(), - channel); - - if (rssi == -1) { - result += F(" (RTC) "); - } else { - result += strformat(F(" (%ddBm)"), rssi); - } - - result += encryption_type(); - -#ifdef ESP32 -# if ESP_IDF_VERSION_MAJOR >= 5 - // Country code string - if (country.cc[0] != '\0' && country.cc[1] != '\0') { - result += strformat(F(" '%c%c'"), country.cc[0], country.cc[1]); - switch (country.cc[2]) { - case 'O': // Outdoor - case 'I': // Indoor - case 'X': // "non-country" - result += strformat(F("(%c)"), country.cc[2]); - break; - } - } - if (country.nchan > 0) { - result += strformat(F(" ch: %d..%d"), country.schan, country.schan + country.nchan - 1); - } -#endif -#endif - - if (phy_known()) { - String phy_str; - - if (bits.phy_11b) { phy_str += 'b'; } - - if (bits.phy_11g) { phy_str += 'g'; } - - if (bits.phy_11n) { phy_str += 'n'; } -#ifdef ESP32 - - if (bits.phy_11ax) { phy_str += F("/ax"); } - - if (bits.phy_lr) { phy_str += F("/lr"); } - - if (bits.ftm_initiator) { phy_str += F("/FTM_i"); } - - if (bits.ftm_responder) { phy_str += F("/FTM_r"); } -#endif // ifdef ESP32 - - if (phy_str.length()) { - result += strformat(F(" (%s)"), phy_str.c_str()); - } - } - return result; -} - -String WiFi_AP_Candidate::encryption_type() const { - return WiFi_encryptionType(enc_type); -} +#include "../DataStructs/WiFi_AP_Candidate.h" + +#include "../Globals/ESPEasyWiFiEvent.h" +#include "../Globals/SecuritySettings.h" +#include "../Globals/Statistics.h" +#include "../Helpers/ESPEasy_time_calc.h" +#include "../Helpers/Misc.h" +#include "../Helpers/StringConverter.h" +#include "../Helpers/StringGenerator_WiFi.h" +#include "../../ESPEasy_common.h" + +#if defined(ESP8266) + # include +#endif // if defined(ESP8266) +#if defined(ESP32) + # include +#endif // if defined(ESP32) + +#define WIFI_AP_CANDIDATE_MAX_AGE 300000 // 5 minutes in msec + + +WiFi_AP_Candidate::WiFi_AP_Candidate() : +#ifdef ESP32 +# if ESP_IDF_VERSION_MAJOR >= 5 +country({ + .cc = "01", + .schan = 1, + .nchan = 11, + .policy = WIFI_COUNTRY_POLICY_AUTO, +}), +#endif +#endif + last_seen(0), rssi(0), channel(0), index(0), enc_type(0) +{ + memset(&bits, 0, sizeof(bits)); +} + +WiFi_AP_Candidate::WiFi_AP_Candidate(uint8_t index_c, const String& ssid_c) : + last_seen(0), rssi(0), channel(0), index(index_c), enc_type(0) +{ + memset(&bits, 0, sizeof(bits)); + + const size_t ssid_length = ssid_c.length(); + + if ((ssid_length == 0) || equals(ssid_c, F("ssid"))) { + return; + } + + if (ssid_length > 32) { return; } + + ssid = ssid_c; +} + +WiFi_AP_Candidate::WiFi_AP_Candidate(uint8_t networkItem) : index(0) { + // Need to make sure the phy isn't known as we can't get this information from the AP + // See: https://github.com/letscontrolit/ESPEasy/issues/4996 + // Not sure why this makes any difference as the flags should already have been set to 0. + memset(&bits, 0, sizeof(bits)); + + ssid = WiFi.SSID(networkItem); + rssi = WiFi.RSSI(networkItem); + channel = WiFi.channel(networkItem); + bssid = WiFi.BSSID(networkItem); + enc_type = WiFi.encryptionType(networkItem); + #ifdef ESP8266 + bits.isHidden = WiFi.isHidden(networkItem); + # ifdef CORE_POST_3_0_0 + const bss_info *it = reinterpret_cast(WiFi.getScanInfoByIndex(networkItem)); + + if (it) { + bits.phy_11b = it->phy_11b; + bits.phy_11g = it->phy_11g; + bits.phy_11n = it->phy_11n; + bits.wps = it->wps; + } + # endif // ifdef CORE_POST_3_0_0 + #endif // ifdef ESP8266 + #ifdef ESP32 + bits.isHidden = ssid.isEmpty(); + wifi_ap_record_t *it = reinterpret_cast(WiFi.getScanInfoByIndex(networkItem)); + + if (it) { + bits.phy_11b = it->phy_11b; + bits.phy_11g = it->phy_11g; + bits.phy_11n = it->phy_11n; + bits.phy_lr = it->phy_lr; +# if ESP_IDF_VERSION_MAJOR >= 5 + bits.phy_11ax = it->phy_11ax; + bits.ftm_initiator = it->ftm_initiator; + bits.ftm_responder = it->ftm_responder; +# endif // if ESP_IDF_VERSION_MAJOR >= 5 + bits.wps = it->wps; + + // FIXME TD-er: Maybe also add other info like 2nd channel, ftm and phy_lr support? +# if ESP_IDF_VERSION_MAJOR >= 5 + memcpy(&country, &(it->country), sizeof(wifi_country_t)); +#endif + } + #endif // ifdef ESP32 + last_seen = millis(); +} + +#ifdef ESP8266 +# if FEATURE_ESP8266_DIRECT_WIFI_SCAN +WiFi_AP_Candidate::WiFi_AP_Candidate(const bss_info& ap) : + rssi(ap.rssi), channel(ap.channel), bssid(ap.bssid), + index(0), enc_type(0), isHidden(ap.is_hidden), + phy_11b(ap.phy_11b), phy_11g(ap.phy_11g), phy_11n(ap.phy_11n), + wps(ap.wps) +{ + memset(&bits, 0, sizeof(bits)); + + last_seen = millis(); + + switch (ap.authmode) { + case AUTH_OPEN: enc_type = ENC_TYPE_NONE; break; + case AUTH_WEP: enc_type = ENC_TYPE_WEP; break; + case AUTH_WPA_PSK: enc_type = ENC_TYPE_TKIP; break; + case AUTH_WPA2_PSK: enc_type = ENC_TYPE_CCMP; break; + case AUTH_WPA_WPA2_PSK: enc_type = ENC_TYPE_AUTO; break; + case AUTH_MAX: break; + } + + char tmp[33]; // ssid can be up to 32chars, => plus null term + const size_t ssid_len = std::min(static_cast(ap.ssid_len), sizeof(ap.ssid)); + + memcpy(tmp, ap.ssid, ssid_len); + tmp[ssid_len] = 0; // nullterm marking end of string + + ssid = String(reinterpret_cast(tmp)); +} + +# endif // if FEATURE_ESP8266_DIRECT_WIFI_SCAN +#endif // ifdef ESP8266 + + +bool WiFi_AP_Candidate::operator<(const WiFi_AP_Candidate& other) const { + if (bits.isEmergencyFallback != other.bits.isEmergencyFallback) { + return bits.isEmergencyFallback; + } + + if (bits.lowPriority != other.bits.lowPriority) { + return !bits.lowPriority; + } + + // Prefer non hidden over hidden. + if (bits.isHidden != other.bits.isHidden) { + return !bits.isHidden; + } + + // RSSI values >= 0 are invalid + if (rssi >= 0) { return false; } + + if (other.rssi >= 0) { return true; } + + // RSSI values are negative, so the larger value is the better one. + return rssi > other.rssi; +} + +bool WiFi_AP_Candidate::usable() const { + // Allow for empty pass + // if (key.isEmpty()) return false; + if (bits.isEmergencyFallback) { + int allowedUptimeMinutes = 10; + #ifdef CUSTOM_EMERGENCY_FALLBACK_ALLOW_MINUTES_UPTIME + allowedUptimeMinutes = CUSTOM_EMERGENCY_FALLBACK_ALLOW_MINUTES_UPTIME; + #endif // ifdef CUSTOM_EMERGENCY_FALLBACK_ALLOW_MINUTES_UPTIME + + if ((getUptimeMinutes() > allowedUptimeMinutes) || + !SecuritySettings.hasWiFiCredentials() || + WiFiEventData.performedClearWiFiCredentials || + (lastBootCause != BOOT_CAUSE_COLD_BOOT)) { + return false; + } + } + + if (!bits.isHidden && (ssid.isEmpty())) { return false; } + return !expired(); +} + +bool WiFi_AP_Candidate::expired() const { + if (last_seen == 0) { + // Not set, so cannot expire + return false; + } + return timePassedSince(last_seen) > WIFI_AP_CANDIDATE_MAX_AGE; +} + +String WiFi_AP_Candidate::toString(const String& separator) const { + String result = ssid; + + htmlEscape(result); + + if (bits.isHidden) { + result += F("#Hidden#"); + } + result += strformat( + F("%s%s%sCh:%u"), + separator.c_str(), + bssid.toString().c_str(), + separator.c_str(), + channel); + + if (rssi == -1) { + result += F(" (RTC) "); + } else { + result += strformat(F(" (%ddBm) "), rssi); + } + + result += encryption_type(); + +#ifdef ESP32 +# if ESP_IDF_VERSION_MAJOR >= 5 + // Country code string + if (country.cc[0] != '\0' && country.cc[1] != '\0') { + result += strformat(F(" '%c%c'"), country.cc[0], country.cc[1]); + switch (country.cc[2]) { + case 'O': // Outdoor + case 'I': // Indoor + case 'X': // "non-country" + result += strformat(F("(%c)"), country.cc[2]); + break; + } + } + if (country.nchan > 0) { + result += strformat(F(" ch: %d..%d"), country.schan, country.schan + country.nchan - 1); + } +#endif +#endif + + if (phy_known()) { + String phy_str; + + if (bits.phy_11b) { phy_str += 'b'; } + + if (bits.phy_11g) { phy_str += 'g'; } + + if (bits.phy_11n) { phy_str += 'n'; } +#ifdef ESP32 + + if (bits.phy_11ax) { phy_str += F("/ax"); } + + if (bits.phy_lr) { phy_str += F("/lr"); } + + if (bits.ftm_initiator) { phy_str += F("/FTM_i"); } + + if (bits.ftm_responder) { phy_str += F("/FTM_r"); } +#endif // ifdef ESP32 + + if (phy_str.length()) { + result += strformat(F(" (%s)"), phy_str.c_str()); + } + } + return result; +} + +String WiFi_AP_Candidate::encryption_type() const { + return WiFi_encryptionType(enc_type); +} diff --git a/src/src/DataTypes/ESPEasy_plugin_functions.h b/src/src/DataTypes/ESPEasy_plugin_functions.h index 37f05eb238..267ea867a0 100644 --- a/src/src/DataTypes/ESPEasy_plugin_functions.h +++ b/src/src/DataTypes/ESPEasy_plugin_functions.h @@ -84,6 +84,8 @@ class CPlugin { enum class Function { CPLUGIN_PROTOCOL_ADD = 127, // Called at boot for letting a controller adding itself to list of available controllers + CPLUGIN_CONNECT_SUCCESS, // Only used for timing stats + CPLUGIN_CONNECT_FAIL, // Only used for timing stats CPLUGIN_PROTOCOL_TEMPLATE, CPLUGIN_PROTOCOL_SEND, CPLUGIN_PROTOCOL_RECV, diff --git a/src/src/DataTypes/TaskValues_Data.cpp b/src/src/DataTypes/TaskValues_Data.cpp index 62136f37b4..961b630b40 100644 --- a/src/src/DataTypes/TaskValues_Data.cpp +++ b/src/src/DataTypes/TaskValues_Data.cpp @@ -251,7 +251,6 @@ bool TaskValues_Data_t::isValid(uint8_t varNr, Sensor_VType sensorType) const String TaskValues_Data_t::getAsString(uint8_t varNr, Sensor_VType sensorType, uint8_t nrDecimals) const { String result; - START_TIMER; if (isFloatOutputDataType(sensorType)) { result = toString(getFloat(varNr), nrDecimals); @@ -275,5 +274,5 @@ String TaskValues_Data_t::getAsString(uint8_t varNr, Sensor_VType sensorType, u #endif } result.trim(); - return std::move(result); + return result; } diff --git a/src/src/ESPEasyCore/Controller.cpp b/src/src/ESPEasyCore/Controller.cpp index 14f6ca1297..b56c14e4af 100644 --- a/src/src/ESPEasyCore/Controller.cpp +++ b/src/src/ESPEasyCore/Controller.cpp @@ -210,6 +210,8 @@ bool MQTTConnect(controllerIndex_t controller_idx) #endif MQTTclient.setClient(mqtt); + MQTTclient.setKeepAlive(10); + MQTTclient.setSocketTimeout(timeout); if (ControllerSettings->UseDNS) { MQTTclient.setServer(ControllerSettings->getHost().c_str(), ControllerSettings->Port); @@ -232,7 +234,7 @@ bool MQTTConnect(controllerIndex_t controller_idx) addLog(LOG_LEVEL_ERROR, F("MQTT : Intentional reconnect")); } - const unsigned long connect_start_time = millis(); + const uint64_t statisticsTimerStart(getMicros64()); // https://github.com/knolleary/pubsubclient/issues/458#issuecomment-493875150 if (hasControllerCredentialsSet(controller_idx, *ControllerSettings)) { @@ -257,7 +259,11 @@ bool MQTTConnect(controllerIndex_t controller_idx) } delay(0); - count_connection_results(MQTTresult, F("MQTT : Broker "), Settings.Protocol[controller_idx], connect_start_time); + count_connection_results( + MQTTresult, + F("MQTT : Broker "), + Settings.Protocol[controller_idx], + statisticsTimerStart); if (!MQTTresult) { MQTTclient.disconnect(); @@ -632,7 +638,7 @@ void SensorSendTask(struct EventStruct *event, unsigned long timestampUnixTime, struct EventStruct TempEvent(event->TaskIndex); TempEvent.Source = event->Source; - TempEvent.timestamp = timestampUnixTime; + TempEvent.timestamp_sec = timestampUnixTime; checkDeviceVTypeForTask(&TempEvent); String dummy; diff --git a/src/src/ESPEasyCore/ESPEasyRules.cpp b/src/src/ESPEasyCore/ESPEasyRules.cpp index 5abb96df35..f85db9df64 100644 --- a/src/src/ESPEasyCore/ESPEasyRules.cpp +++ b/src/src/ESPEasyCore/ESPEasyRules.cpp @@ -1,1344 +1,1344 @@ -#include "../ESPEasyCore/ESPEasyRules.h" - -#include "../../_Plugin_Helper.h" - -#include "../Commands/ExecuteCommand.h" -#include "../DataStructs/TimingStats.h" -#include "../DataTypes/EventValueSource.h" -#include "../ESPEasyCore/ESPEasy_backgroundtasks.h" -#include "../ESPEasyCore/Serial.h" -#include "../Globals/Cache.h" -#include "../Globals/Device.h" -#include "../Globals/EventQueue.h" -#include "../Globals/Plugins.h" -#include "../Globals/Plugins_other.h" -#include "../Globals/RulesCalculate.h" -#include "../Globals/Settings.h" -#include "../Helpers/ESPEasy_Storage.h" -#include "../Helpers/ESPEasy_time_calc.h" -#include "../Helpers/FS_Helper.h" -#include "../Helpers/Misc.h" -#include "../Helpers/Numerical.h" -#include "../Helpers/RulesHelper.h" -#include "../Helpers/RulesMatcher.h" -#include "../Helpers/StringConverter.h" -#include "../Helpers/StringParser.h" - - -#include -#include - -#ifdef WEBSERVER_NEW_RULES -String EventToFileName(const String& eventName) { - int size = eventName.length(); - int index = eventName.indexOf('='); - - if (index > -1) { - size = index; - } -#if defined(ESP8266) - String fileName = F("rules/"); -#endif // if defined(ESP8266) -#if defined(ESP32) - String fileName = F("/rules/"); -#endif // if defined(ESP32) - fileName += eventName.substring(0, size); - fileName.replace('#', RULE_FILE_SEPARAROR); - fileName.toLowerCase(); - return fileName; -} - -String FileNameToEvent(const String& fileName) { -#if defined(ESP8266) - String eventName = fileName.substring(6); -#endif // if defined(ESP8266) -#if defined(ESP32) - String eventName = fileName.substring(7); -#endif // if defined(ESP32) - eventName.replace(RULE_FILE_SEPARAROR, '#'); - return eventName; -} -#endif - -void checkRuleSets() { - Cache.rulesHelper.closeAllFiles(); -} - -/********************************************************************************************\ - Process next event from event queue - \*********************************************************************************************/ -bool processNextEvent() { - if (Settings.UseRules) - { - String nextEvent; - - if (eventQueue.getNext(nextEvent)) { - rulesProcessing(nextEvent); - return true; - } - } - - // Just make sure any (accidentally) added or remaining events are not kept. - eventQueue.clear(); - return false; -} - -/********************************************************************************************\ - Rules processing - \*********************************************************************************************/ -void rulesProcessing(const String& event) { - if (!Settings.UseRules) { - return; - } - START_TIMER - #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("rulesProcessing")); - #endif // ifndef BUILD_NO_RAM_TRACKER -#ifndef BUILD_NO_DEBUG - const unsigned long timer = millis(); -#endif // ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, concat(F("EVENT: "), event)); - } - - if (Settings.OldRulesEngine()) { - bool eventHandled = false; - - if (Settings.EnableRulesCaching()) { - String filename; - size_t pos = 0; - if (Cache.rulesHelper.findMatchingRule(event, filename, pos)) { - const bool startOnMatched = true; // We already matched the event - eventHandled = rulesProcessingFile(filename, event, pos, startOnMatched); - } - } else { - for (uint8_t x = 0; x < RULESETS_MAX && !eventHandled; x++) { - eventHandled = rulesProcessingFile(getRulesFileName(x), event); - } - } - } else { - #ifdef WEBSERVER_NEW_RULES - String fileName = EventToFileName(event); - - // if exists processed the rule file - if (fileExists(fileName)) { - rulesProcessingFile(fileName, event); - } - # ifndef BUILD_NO_DEBUG - else { - addLog(LOG_LEVEL_DEBUG, strformat(F("EVENT: %s is ingnored. File %s not found."), - event.c_str(), fileName.c_str())); - } - # endif // ifndef BUILD_NO_DEBUG - #endif // WEBSERVER_NEW_RULES - } - -#ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - addLogMove(LOG_LEVEL_DEBUG, strformat(F("EVENT: %s Processing: %d ms"), event.c_str(), timePassedSince(timer))); - } -#endif // ifndef BUILD_NO_DEBUG - STOP_TIMER(RULES_PROCESSING); - backgroundtasks(); -} - -/********************************************************************************************\ - Rules processing - \*********************************************************************************************/ -bool rulesProcessingFile(const String& fileName, - const String& event, - size_t pos, - bool startOnMatched) { - if (!Settings.UseRules || !fileExists(fileName)) { - return false; - } - #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("rulesProcessingFile")); - #endif // ifndef BUILD_NO_RAM_TRACKER -#ifndef BUILD_NO_DEBUG - - if (Settings.SerialLogLevel == LOG_LEVEL_DEBUG_DEV) { - serialPrint(F("RuleDebug Processing:")); - serialPrintln(fileName); - serialPrintln(F(" flags CMI parse output:")); - } -#endif // ifndef BUILD_NO_DEBUG - - static uint8_t nestingLevel = 0; - - nestingLevel++; - - if (nestingLevel > RULES_MAX_NESTING_LEVEL) { - addLog(LOG_LEVEL_ERROR, F("EVENT: Error: Nesting level exceeded!")); - nestingLevel--; - return false; - } - - - bool match = false; - bool codeBlock = false; - bool isCommand = false; - bool condition[RULES_IF_MAX_NESTING_LEVEL]; - bool ifBranche[RULES_IF_MAX_NESTING_LEVEL]; - uint8_t ifBlock = 0; - uint8_t fakeIfBlock = 0; - - - bool moreAvailable = true; - bool eventHandled = false; - while (moreAvailable && !eventHandled) { - const bool searchNextOnBlock = !codeBlock && !match; - String line = Cache.rulesHelper.readLn(fileName, pos, moreAvailable, searchNextOnBlock); - - // Parse the line and extract the action (if there is any) - String action; - { - START_TIMER - const bool matched_before_parse = match; - bool isOneLiner = false; - parseCompleteNonCommentLine(line, event, action, match, codeBlock, - isCommand, isOneLiner, condition, ifBranche, ifBlock, - fakeIfBlock, startOnMatched); - if ((matched_before_parse && !match) || isOneLiner) { - // We were processing a matching event and now crossed the "endon" - // Or just dealing with a oneliner. - // So we're done processing - eventHandled = true; - backgroundtasks(); - } - STOP_TIMER(RULES_PARSE_LINE); - } - - if (match) // rule matched for one action or a block of actions - { - START_TIMER - processMatchedRule(action, event, - isCommand, condition, - ifBranche, ifBlock, fakeIfBlock); - STOP_TIMER(RULES_PROCESS_MATCHED); - } - } - -/* - if (f) { - f.close(); - } -*/ - - nestingLevel--; - #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("rulesProcessingFile2")); - #endif // ifndef BUILD_NO_RAM_TRACKER - backgroundtasks(); - return eventHandled; // && nestingLevel == 0; -} - - -/********************************************************************************************\ - Parse string commands - \*********************************************************************************************/ -bool get_next_inner_bracket(const String& line, int& startIndex, int& closingIndex, char closingBracket) -{ - if (line.length() <= 1) { - // Not possible to have opening and closing bracket on a line this short. - return false; - } - char openingBracket = closingBracket; - - switch (closingBracket) { - case ']': openingBracket = '['; break; - case '}': openingBracket = '{'; break; - case ')': openingBracket = '('; break; - default: - // unknown bracket type - return false; - } - // Closing bracket should not be found on the first position. - closingIndex = line.indexOf(closingBracket, startIndex + 1); - - if (closingIndex == -1) { - // not found - return false; - } - - for (int i = (closingIndex - 1); i > startIndex; --i) { - if (line[i] == openingBracket) { - startIndex = i; - return true; - } - } - return false; -} - -bool get_next_argument(const String& fullCommand, int& index, String& argument, char separator) -{ - if (index == -1) { - return false; - } - int newIndex = fullCommand.indexOf(separator, index); - - if (newIndex == -1) { - argument = fullCommand.substring(index); - } else { - argument = fullCommand.substring(index, newIndex); - } - - if (argument.startsWith(String(separator))) { - argument = argument.substring(1); - } - - // addLog(LOG_LEVEL_INFO, String("get_next_argument: ") + String(index) + " " + fullCommand + " " + argument); - index = newIndex; - - if (index != -1) { - ++index; - } - return argument.length() > 0; -} - -const char bitwise_functions[] PROGMEM = "bitread|bitset|bitclear|bitwrite|xor|and|or"; -enum class bitwise_functions_e { - bitread, - bitset, - bitclear, - bitwrite, - xor_e, // protected keywords, thus appended _e - and_e, - or_e -}; - - -bool parse_bitwise_functions(const String& cmd_s_lower, const String& arg1, const String& arg2, const String& arg3, int64_t& result) { - #ifndef BUILD_NO_DEBUG - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = F("Bitwise: {"); - log += wrapIfContains(cmd_s_lower, ':', '\"'); - log += ':'; - log += wrapIfContains(arg1, ':', '\"'); - - if (arg2.length() > 0) { - log += ':'; - log += wrapIfContains(arg2, ':', '\"'); - - if (arg3.length() > 0) { - log += ':'; - log += wrapIfContains(arg3, ':', '\"'); - } - } - log += '}'; - addLogMove(LOG_LEVEL_DEBUG, log); - } - #endif - - if (cmd_s_lower.length() < 2) { - return false; - } - - int command_i = GetCommandCode(cmd_s_lower.c_str(), bitwise_functions); - if (command_i == -1) { - // No matching function found - return false; - } - - if (cmd_s_lower.startsWith(F("bit"))) { - uint32_t bitnr = 0; - uint64_t iarg2 = 0; - - if (!validUIntFromString(arg1, bitnr) || !validUInt64FromString(arg2, iarg2)) { - return false; - } - - switch(static_cast(command_i)) { - case bitwise_functions_e::bitread: - // Syntax like {bitread:0:123} to get a single decimal '1' - result = bitRead(iarg2, bitnr); - break; - case bitwise_functions_e::bitset: - // Syntax like {bitset:0:122} to set least significant bit of the given nr '122' to '1' => '123' - result = iarg2; - bitSetULL(result, bitnr); - break; - case bitwise_functions_e::bitclear: - // Syntax like {bitclear:0:123} to set least significant bit of the given nr '123' to '0' => '122' - result = iarg2; - bitClearULL(result, bitnr); - break; - case bitwise_functions_e::bitwrite: - { - uint32_t iarg3 = 0; - // Syntax like {bitwrite:0:122:1} to set least significant bit of the given nr '122' to '1' => '123' - if (validUIntFromString(arg3, iarg3)) { - const int bitvalue = (iarg3 & 1); // Only use the last bit of the given parameter - result = iarg2; - bitWriteULL(result, bitnr, bitvalue); - } else { - // Need 3 parameters, but 3rd one is not a valid uint - return false; - } - break; - } - default: - return false; - } - - // all functions starting with "bit" are checked - return true; - } - - uint64_t iarg1, iarg2 = 0; - - if (!validUInt64FromString(arg1, iarg1) || !validUInt64FromString(arg2, iarg2)) { - return false; - } - - switch(static_cast(command_i)) { - case bitwise_functions_e::xor_e: - // Syntax like {xor:127:15} to XOR the binary values 1111111 and 1111 => 1110000 - result = iarg1 ^ iarg2; - break; - case bitwise_functions_e::and_e: - // Syntax like {and:254:15} to AND the binary values 11111110 and 1111 => 1110 - result = iarg1 & iarg2; - break; - case bitwise_functions_e::or_e: - // Syntax like {or:254:15} to OR the binary values 11111110 and 1111 => 11111111 - result = iarg1 | iarg2; - break; - default: - return false; - - } - return true; -} - -bool parse_math_functions(const String& cmd_s_lower, const String& arg1, const String& arg2, const String& arg3, ESPEASY_RULES_FLOAT_TYPE& result) { - ESPEASY_RULES_FLOAT_TYPE farg1; - float farg2, farg3 = 0.0f; - - if (!validDoubleFromString(arg1, farg1)) { - return false; - } - - if (equals(cmd_s_lower, F("constrain"))) { - // Contrain a value X to be within range of A to B - // Syntax like {constrain:x:a:b} to constrain x in range a...b - if (validFloatFromString(arg2, farg2) && validFloatFromString(arg3, farg3)) { - if (farg2 > farg3) { - const float tmp = farg2; - farg2 = farg3; - farg3 = tmp; - } - result = constrain(farg1, farg2, farg3); - } else { - return false; - } - } else { - // No matching function found - return false; - } - return true; -} - -const char string_commands[] PROGMEM = "substring|indexof|indexof_ci|equals|equals_ci|timetomin|timetosec|strtol|tobin|tohex|ord|urlencode"; -enum class string_commands_e { - substring, - indexof, - indexof_ci, - equals, - equals_ci, - timetomin, - timetosec, - strtol, - tobin, - tohex, - ord, - urlencode -}; - - -void parse_string_commands(String& line) { - int startIndex = 0; - int closingIndex; - - bool mustReplaceMaskedChars = false; - - while (get_next_inner_bracket(line, startIndex, closingIndex, '}')) { - // Command without opening and closing brackets. - const String fullCommand = line.substring(startIndex + 1, closingIndex); - const String cmd_s_lower = parseString(fullCommand, 1, ':'); - const String arg1 = parseStringKeepCaseNoTrim(fullCommand, 2, ':'); - const String arg2 = parseStringKeepCaseNoTrim(fullCommand, 3, ':'); - const String arg3 = parseStringKeepCaseNoTrim(fullCommand, 4, ':'); - - if (cmd_s_lower.length() > 0) { - String replacement; // maybe just replace with empty to avoid looping? - uint64_t iarg1, iarg2 = 0; - ESPEASY_RULES_FLOAT_TYPE fresult{}; - int64_t iresult = 0; - int32_t startpos, endpos = -1; - const bool arg1valid = validIntFromString(arg1, startpos); - const bool arg2valid = validIntFromString(arg2, endpos); - - if (parse_math_functions(cmd_s_lower, arg1, arg2, arg3, fresult)) { - const bool trimTrailingZeros = true; - #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - replacement = doubleToString(fresult, maxNrDecimals_fpType(fresult), trimTrailingZeros); - #else - replacement = floatToString(fresult, maxNrDecimals_fpType(fresult), trimTrailingZeros); - #endif - } else if (parse_bitwise_functions(cmd_s_lower, arg1, arg2, arg3, iresult)) { - replacement = ull2String(iresult); - } else { - - int command_i = GetCommandCode(cmd_s_lower.c_str(), string_commands); - if (command_i != -1) { - const string_commands_e command = static_cast(command_i); - - // addLog(LOG_LEVEL_INFO, strformat(F("parse_string_commands cmd: %s %s %s %s"), - // cmd_s_lower.c_str(), arg1.c_str(), arg2.c_str(), arg3.c_str())); - - switch (command) { - case string_commands_e::substring: - // substring arduino style (first char included, last char excluded) - // Syntax like 12345{substring:8:12:ANOTHER HELLO WORLD}67890 - - if (arg1valid - && arg2valid) { - replacement = arg3.substring(startpos, endpos); - } - break; - case string_commands_e::indexof: - case string_commands_e::indexof_ci: - // indexOf arduino style (0-based position of first char returned, -1 if not found, case sensitive), 3rd argument is search-offset - // indexOf_ci : case-insensitive - // Syntax like {indexof:HELLO:"ANOTHER HELLO WORLD"} => 8, {indexof:hello:"ANOTHER HELLO WORLD"} => -1, {indexof_ci:Hello:"ANOTHER HELLO WORLD"} => 8 - // or like {indexof_ci:hello:"ANOTHER HELLO WORLD":10} => -1 - - if (!arg1.isEmpty() - && !arg2.isEmpty()) { - uint32_t offset = 0; - validUIntFromString(arg3, offset); - if (command == string_commands_e::indexof_ci) { - String arg1copy(arg1); - String arg2copy(arg2); - arg1copy.toLowerCase(); - arg2copy.toLowerCase(); - replacement = arg2copy.indexOf(arg1copy, offset); - } else { - replacement = arg2.indexOf(arg1, offset); - } - } - break; - case string_commands_e::equals: - case string_commands_e::equals_ci: - // equals: compare strings 1 = equal, 0 = unequal (case sensitive) - // equals_ci: case-insensitive compare - // Syntax like {equals:HELLO:HELLO} => 1, {equals:hello:HELLO} => 0, {equals_ci:hello:HELLO} => 1, {equals_ci:hello:BLA} => 0 - - if (!arg1.isEmpty() - && !arg2.isEmpty()) { - if (command == string_commands_e::equals_ci) { - replacement = arg2.equalsIgnoreCase(arg1); - } else { - replacement = arg2.equals(arg1); - } - } - break; - case string_commands_e::timetomin: - case string_commands_e::timetosec: - // time to minutes, transform a substring hh:mm to minutes - // time to seconds, transform a substring hh:mm:ss to seconds - // syntax similar to substring - - if (arg1valid - && arg2valid) { - int timeSeconds = 0; - String timeString; - if(timeStringToSeconds(arg3.substring(startpos, endpos), timeSeconds, timeString)) { - if (command == string_commands_e::timetosec) { - replacement = timeSeconds; - } else { // timetomin - replacement = timeSeconds / 60; - } - } - } - break; - case string_commands_e::strtol: - // string to long integer (from cstdlib) - // Syntax like 1234{strtol:16:38}7890 - if (validUInt64FromString(arg1, iarg1) - && validUInt64FromString(arg2, iarg2)) { - replacement = String(strtoul(arg2.c_str(), nullptr, iarg1)); - } - break; - case string_commands_e::tobin: - // Convert to binary string - // Syntax like 1234{tobin:15}7890 - if (validUInt64FromString(arg1, iarg1)) { - replacement = ull2String(iarg1, BIN); - } - break; - case string_commands_e::tohex: - // Convert to HEX string - // Syntax like 1234{tohex:15[,minHexDigits]}7890 - if (validUInt64FromString(arg1, iarg1)) { - if (!validUInt64FromString(arg2, iarg2)) { - iarg2 = 0; - } - replacement = formatToHex_no_prefix(iarg1, iarg2); - } - break; - case string_commands_e::ord: - { - // Give the ordinal/integer value of the first character of a string - // Syntax like let 1,{ord:B} - uint8_t uval = arg1.c_str()[0]; - replacement = String(uval); - } - break; - case string_commands_e::urlencode: - // Convert to url-encoded string - // Syntax like {urlencode:"string to/encode"} - if (!arg1.isEmpty()) { - replacement = URLEncode(arg1); - } - break; - } - } - } - - if (replacement.isEmpty()) { - // part in braces is not a supported command. - // replace the {} with other characters to mask the braces so we can continue parsing. - // We have to unmask then after we're finished. - // See: https://github.com/letscontrolit/ESPEasy/issues/2932#issuecomment-596139096 - replacement = line.substring(startIndex, closingIndex + 1); - replacement.replace('{', static_cast(0x02)); - replacement.replace('}', static_cast(0x03)); - mustReplaceMaskedChars = true; - } - - // Replace the full command including opening and closing brackets. - line.replace(line.substring(startIndex, closingIndex + 1), replacement); - - /* - if (replacement.length() > 0) { - addLog(LOG_LEVEL_INFO, strformat(F("parse_string_commands cmd: %s -> %s"), fullCommand.c_str(), replacement.c_str()); - } - */ - } - } - - if (mustReplaceMaskedChars) { - // We now have to check if we did mask some parts and unmask them. - // Let's hope we don't mess up any Unicode here. - line.replace(static_cast(0x02), '{'); - line.replace(static_cast(0x03), '}'); - } -} - -void substitute_eventvalue(String& line, const String& event) { - if (substitute_eventvalue_CallBack_ptr != nullptr) { - substitute_eventvalue_CallBack_ptr(line, event); - } - - if (line.indexOf(F("%event")) != -1) { - if (event.charAt(0) == '!') { - line.replace(F("%eventvalue%"), event); // substitute %eventvalue% with - // literal event string if - // starting with '!' - } else { - const int equalsPos = event.indexOf('='); - - String argString; - - if (equalsPos > 0) { - argString = event.substring(equalsPos + 1); - } - - // Replace %eventvalueX% with the actual value of the event. - // For compatibility reasons also replace %eventvalue% (argc = 0) - line.replace(F("%eventvalue%"), F("%eventvalue1%")); - int eventvalue_pos = line.indexOf(F("%eventvalue")); - - while (eventvalue_pos != -1) { - const int percent_pos = line.indexOf('%', eventvalue_pos + 1); - - if (percent_pos == -1) { - // Found "%eventvalue" without closing % - if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - String log = F("Rules : Syntax error, missing '%' in '%eventvalue%': '"); - log += line; - log += F("' %-pos: "); - log += percent_pos; - addLog(LOG_LEVEL_ERROR, log); - } - line.replace(F("%eventvalue"), F("")); - } else { - // Find the optional part for a default value when the asked for eventvalue does not exist. - // Syntax: %eventvalueX|Y% - // With: X = event value nr, Y = default value when eventvalue does not exist. - String defaultValue('0'); - int or_else_pos = line.indexOf('|', eventvalue_pos); - if ((or_else_pos == -1) || (or_else_pos > percent_pos)) { - or_else_pos = percent_pos; - } else { - defaultValue = line.substring(or_else_pos + 1, percent_pos); - } - const String nr = line.substring(eventvalue_pos + 11, or_else_pos); - const String eventvalue = line.substring(eventvalue_pos, percent_pos + 1); - int argc = -1; - - if (equals(nr, '0')) { - // Replace %eventvalue0% with the entire list of arguments. - line.replace(eventvalue, argString); - } else { - argc = nr.toInt(); - - // argc will be 0 on invalid int (0 was already handled) - if (argc > 0) { - String tmpParam; - - if (!GetArgv(argString.c_str(), tmpParam, argc)) { - // Replace with default value for non existing event values - tmpParam = parseTemplate(defaultValue); - } - line.replace(eventvalue, tmpParam); - } else { - // Just remove the invalid eventvalue variable - if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - addLog(LOG_LEVEL_ERROR, concat(F("Rules : Syntax error, invalid variable: "), eventvalue)); - } - line.replace(eventvalue, EMPTY_STRING); - } - } - } - eventvalue_pos = line.indexOf(F("%eventvalue")); - } - - if ((line.indexOf(F("%eventname%")) != -1) || - (line.indexOf(F("%eventpar%")) != -1)) { - const String eventName = equalsPos == -1 ? event : event.substring(0, equalsPos); - - // Replace %eventname% with the literal event - line.replace(F("%eventname%"), eventName); - - // Part of %eventname% after the # char - const int hash_pos = eventName.indexOf('#'); - line.replace(F("%eventpar%"), hash_pos == -1 ? EMPTY_STRING : eventName.substring(hash_pos + 1)); - } - } - } -} - -void parseCompleteNonCommentLine(String& line, const String& event, - String& action, bool& match, - bool& codeBlock, bool& isCommand, bool& isOneLiner, - bool condition[], bool ifBranche[], - uint8_t& ifBlock, uint8_t& fakeIfBlock, - bool startOnMatched) { - if (line.length() == 0) { - return; - } - const bool lineStartsWith_on = line.substring(0, 3).equalsIgnoreCase(F("on ")); - - if (!codeBlock && !match) { - // We're looking for a new code block. - // Continue with next line if none was found on current line. - if (!lineStartsWith_on) { - return; - } - } - - if (line.equalsIgnoreCase(F("endon"))) // Check if action block has ended, then we will - // wait for a new "on" rule - { - isCommand = false; - codeBlock = false; - match = false; - ifBlock = 0; - fakeIfBlock = 0; - return; - } - - const bool lineStartsWith_pct_event = line.startsWith(F("%event"));; - - isCommand = true; - - if (match || !codeBlock) { - // only parse [xxx#yyy] if we have a matching ruleblock or need to eval the - // "on" (no codeBlock) - // This to avoid wasting CPU time... - if (match && !fakeIfBlock) { - // substitution of %eventvalue% is made here so it can be used on if - // statement too - substitute_eventvalue(line, event); - } - - if (match || lineStartsWith_on) { - // Only parseTemplate when we are actually doing something with the line. - // When still looking for the "on ... do" part, do not change it before we found the block. - line = parseTemplate(line); - } - } - - - if (!codeBlock) // do not check "on" rules if a block of actions is to be - // processed - { - action.clear(); - if (lineStartsWith_on) { - ifBlock = 0; - fakeIfBlock = 0; - - String ruleEvent; - if (getEventFromRulesLine(line, ruleEvent, action)) { - START_TIMER - match = startOnMatched || ruleMatch(event, ruleEvent); - STOP_TIMER(RULES_MATCH); - } else { - match = false; - } - - if (action.length() > 0) // single on/do/action line, no block - { - isCommand = true; - isOneLiner = true; - codeBlock = false; - } else { - isCommand = false; - codeBlock = true; - } - } - } else { - #ifndef BUILD_NO_DEBUG - if (loglevelActiveFor(LOG_LEVEL_DEBUG_DEV)) { - // keep the line for the log - action = line; - } else { - action = std::move(line); - } - #else - action = std::move(line); - #endif - } - - if (isCommand && lineStartsWith_pct_event) { - action = concat(F("restrict,"), action); - if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - addLogMove(LOG_LEVEL_ERROR, - concat(F("Rules : Prefix command with 'restrict': "), action)); - } - } - -#ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG_DEV)) { - String log = F("RuleDebug: "); - log += codeBlock ? 0 : 1; - log += match ? 0 : 1; - log += isCommand ? 0 : 1; - log += F(": "); - log += line; - addLogMove(LOG_LEVEL_DEBUG_DEV, log); - } -#endif // ifndef BUILD_NO_DEBUG -} - -void processMatchedRule(String& action, const String& event, - bool& isCommand, bool condition[], bool ifBranche[], - uint8_t& ifBlock, uint8_t& fakeIfBlock) { - String lcAction = action; - - lcAction.toLowerCase(); - lcAction.trim(); - - if (fakeIfBlock) { - isCommand = false; - } - else if (ifBlock) { - if (condition[ifBlock - 1] != ifBranche[ifBlock - 1]) { - isCommand = false; - } - } - int split = - lcAction.startsWith(F("elseif ")) ? 0 : -1; // check for optional "elseif" condition - - if (split != -1) { - // Found "elseif" condition - isCommand = false; - - if (ifBlock && !fakeIfBlock) { - if (ifBranche[ifBlock - 1]) { - if (condition[ifBlock - 1]) { - ifBranche[ifBlock - 1] = false; - } - else { - String check = lcAction.substring(split + 7); - check.trim(); - condition[ifBlock - 1] = conditionMatchExtended(check); -#ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = F("Lev."); - log += String(ifBlock); - log += F(": [elseif "); - log += check; - log += F("]="); - log += boolToString(condition[ifBlock - 1]); - addLogMove(LOG_LEVEL_DEBUG, log); - } -#endif // ifndef BUILD_NO_DEBUG - } - } - } - } else { - // check for optional "if" condition - split = lcAction.startsWith(F("if ")) ? 0 : -1; - - if (split != -1) { - if (ifBlock < RULES_IF_MAX_NESTING_LEVEL) { - if (isCommand) { - ifBlock++; - String check = lcAction.substring(split + 3); - check.trim(); - condition[ifBlock - 1] = conditionMatchExtended(check); - ifBranche[ifBlock - 1] = true; -#ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = F("Lev."); - log += String(ifBlock); - log += F(": [if "); - log += check; - log += F("]="); - log += boolToString(condition[ifBlock - 1]); - addLogMove(LOG_LEVEL_DEBUG, log); - } -#endif // ifndef BUILD_NO_DEBUG - } else { - fakeIfBlock++; - } - } else { - fakeIfBlock++; - - if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - String log = F("Lev."); - log += String(ifBlock); - log += F(": Error: IF Nesting level exceeded!"); - addLogMove(LOG_LEVEL_ERROR, log); - } - } - isCommand = false; - } - } - - if ((equals(lcAction, F("else"))) && !fakeIfBlock) // in case of an "else" block of - // actions, set ifBranche to - // false - { - ifBranche[ifBlock - 1] = false; - isCommand = false; -#ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = F("Lev."); - log += String(ifBlock); - log += F(": [else]="); - log += boolToString(condition[ifBlock - 1] == ifBranche[ifBlock - 1]); - addLogMove(LOG_LEVEL_DEBUG, log); - } -#endif // ifndef BUILD_NO_DEBUG - } - - if (equals(lcAction, F("endif"))) // conditional block ends here - { - if (fakeIfBlock) { - fakeIfBlock--; - } - else if (ifBlock) { - ifBlock--; - } - isCommand = false; - } - - // process the action if it's a command and unconditional, or conditional and - // the condition matches the if or else block. - if (isCommand) { - substitute_eventvalue(action, event); - - const bool executeRestricted = equals(parseString(action, 1), F("restrict")); - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String actionlog = executeRestricted ? F("ACT : (restricted) ") : F("ACT : "); - actionlog += action; - addLogMove(LOG_LEVEL_INFO, actionlog); - } - - if (executeRestricted) { - ExecuteCommand_all({EventValueSource::Enum::VALUE_SOURCE_RULES_RESTRICTED, parseStringToEndKeepCase(action, 2)}); - } else { - // Use action.c_str() here as we need to preserve the action string. - ExecuteCommand_all({EventValueSource::Enum::VALUE_SOURCE_RULES, action.c_str()}); - } - delay(0); - } -} - -/********************************************************************************************\ - Check if an event matches to a given rule - \*********************************************************************************************/ - - -/********************************************************************************************\ - Check expression - \*********************************************************************************************/ -bool conditionMatchExtended(String& check) { - int condAnd = -1; - int condOr = -1; - bool rightcond = false; - bool leftcond = conditionMatch(check); // initial check - - #ifndef BUILD_NO_DEBUG - String debugstr; - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - debugstr += boolToString(leftcond); - } - #endif // ifndef BUILD_NO_DEBUG - - do { - #ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = F("conditionMatchExtended: "); - log += debugstr; - log += ' '; - log += wrap_String(check, '"'); - addLogMove(LOG_LEVEL_DEBUG, log); - } - #endif // ifndef BUILD_NO_DEBUG - condAnd = check.indexOf(F(" and ")); - condOr = check.indexOf(F(" or ")); - - if ((condAnd > 0) || (condOr > 0)) { // we got AND/OR - if ((condAnd > 0) && (((condOr < 0) /*&& (condOr < condAnd)*/) || - ((condOr > 0) && (condOr > condAnd)))) { // AND is first - check = check.substring(condAnd + 5); - rightcond = conditionMatch(check); - leftcond = (leftcond && rightcond); - - #ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - debugstr += F(" && "); - } - #endif // ifndef BUILD_NO_DEBUG - } else { // OR is first - check = check.substring(condOr + 4); - rightcond = conditionMatch(check); - leftcond = (leftcond || rightcond); - - #ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - debugstr += F(" || "); - } - #endif // ifndef BUILD_NO_DEBUG - } - - #ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - debugstr += boolToString(rightcond); - } - #endif // ifndef BUILD_NO_DEBUG - } - } while (condAnd > 0 || condOr > 0); - - #ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - check = debugstr; - } - #endif // ifndef BUILD_NO_DEBUG - return leftcond; -} - - -void logtimeStringToSeconds(const String& tBuf, int hours, int minutes, int seconds, bool valid) -{ - #ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log; - log = F("timeStringToSeconds: "); - log += wrap_String(tBuf, '"'); - log += F(" --> "); - if (valid) { - log += formatIntLeadingZeroes(hours, 2); - log += ':'; - log += formatIntLeadingZeroes(minutes, 2); - log += ':'; - log += formatIntLeadingZeroes(seconds, 2); - } else { - log += F("invalid"); - } - addLogMove(LOG_LEVEL_DEBUG, log); - } - - #endif // ifndef BUILD_NO_DEBUG -} - -// convert old and new time string to nr of seconds -// return whether it should be considered a time string. -bool timeStringToSeconds(const String& tBuf, int& time_seconds, String& timeString) { - { - // Make sure we only check for expected characters - // e.g. if 10:11:12 > 7:07 and 10:11:12 < 20:09:04 - // Should only try to match "7:07", not "7:07 and 10:11:12" - // Or else it will find "7:07:11" - bool done = false; - for (uint8_t pos = 0; !done && timeString.length() < 8 && pos < tBuf.length(); ++pos) { - char c = tBuf[pos]; - if (isdigit(c) || c == ':') { - timeString += c; - } else { - done = true; - } - } - } - - time_seconds = -1; - int32_t hours = 0; - int32_t minutes = 0; - int32_t seconds = 0; - - int tmpIndex = 0; - String hours_str, minutes_str, seconds_str; - bool validTime = false; - - if (get_next_argument(timeString, tmpIndex, hours_str, ':')) { - if (validIntFromString(hours_str, hours)) { - validTime = true; - - if ((hours < 0) || (hours > 24)) { - validTime = false; - } else { - time_seconds = hours * 60 * 60; - } - - if (validTime && get_next_argument(timeString, tmpIndex, minutes_str, ':')) { - if (validIntFromString(minutes_str, minutes)) { - if ((minutes < 0) || (minutes > 59)) { - validTime = false; - } else { - time_seconds += minutes * 60; - } - - if (validTime && get_next_argument(timeString, tmpIndex, seconds_str, ':')) { - // New format, only HH:MM:SS - if (validIntFromString(seconds_str, seconds)) { - if ((seconds < 0) || (seconds > 59)) { - validTime = false; - } else { - time_seconds += seconds; - } - } - } else { - // Old format, only HH:MM - } - } - } else { - // It is a valid time string, but could also be just a numerical. - // We mark it here as invalid, meaning the 'other' time to compare it to must contain more than just the hour. - validTime = false; - } - } - } - logtimeStringToSeconds(timeString, hours, minutes, seconds, validTime); - return validTime; -} - -// Balance the count of parentheses (aka round braces) by adding the missing left or right parentheses, if any -// Returns the number of added parentheses, < 0 is left parentheses added, > 0 is right parentheses added -int balanceParentheses(String& string) { - int left = 0; - int right = 0; - for (unsigned int i = 0; i < string.length(); i++) { - if (string[i] == '(') { - left++; - } else if (string[i] == ')') { - right++; - } - } - if (left != right) { - string.reserve(string.length() + abs(right - left)); // Re-allocate max. once - } - if (left > right) { - for (int i = 0; i < left - right; i++) { - string += ')'; - } - } else if (right > left) { - for (int i = 0; i < right - left; i++) { - string = String('(') + string; // This is quite 'expensive' - } - } - return left - right; -} - -bool conditionMatch(const String& check) { - int posStart, posEnd; - char compare; - - if (!findCompareCondition(check, compare, posStart, posEnd)) { - return false; - } - - String tmpCheck1 = check.substring(0, posStart); - String tmpCheck2 = check.substring(posEnd); - - tmpCheck1.trim(); - tmpCheck2.trim(); - ESPEASY_RULES_FLOAT_TYPE Value1{}; - ESPEASY_RULES_FLOAT_TYPE Value2{}; - - int timeInSec1 = 0; - int timeInSec2 = 0; - String timeString1, timeString2; - bool validTime1 = timeStringToSeconds(tmpCheck1, timeInSec1, timeString1); - bool validTime2 = timeStringToSeconds(tmpCheck2, timeInSec2, timeString2); - bool result = false; - - bool compareTimes = false; - - if ((validTime1 || validTime2) && (timeInSec1 != -1) && (timeInSec2 != -1)) - { - // At least one is a time containing ':' separator - // AND both can be considered a time, so use it as a time and compare seconds. - compareTimes = true; - result = compareIntValues(compare, timeInSec1, timeInSec2); - tmpCheck1 = timeString1; - tmpCheck2 = timeString2; - } else { - int condAnd = tmpCheck2.indexOf(F(" and ")); - int condOr = tmpCheck2.indexOf(F(" or ")); - if (condAnd > -1 || condOr > -1) { // Only parse first condition, rest will be parsed 'later' - if (condAnd > -1 && (condOr == -1 || condAnd < condOr)) { - tmpCheck2 = tmpCheck2.substring(0, condAnd); - } else if (condOr > -1) { - tmpCheck2 = tmpCheck2.substring(0, condOr); - } - tmpCheck2.trim(); - } - balanceParentheses(tmpCheck1); - balanceParentheses(tmpCheck2); - if (isError(Calculate(tmpCheck1, Value1)) || - isError(Calculate(tmpCheck2, Value2))) - { - return false; - } - result = compareDoubleValues(compare, Value1, Value2); - } - - #ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = F("conditionMatch: "); - log += wrap_String(check, '"'); - log += F(" --> "); - - log += wrap_String(tmpCheck1, '"'); - log += wrap_String(check.substring(posStart, posEnd), ' '); // Compare - log += wrap_String(tmpCheck2, '"'); - - log += F(" --> "); - log += boolToString(result); - log += ' '; - - log += '('; - const bool trimTrailingZeros = true; - log += compareTimes ? String(timeInSec1) : -#if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - doubleToString(Value1, 6, trimTrailingZeros); -#else - floatToString(Value1, 6, trimTrailingZeros); -#endif - log += wrap_String(check.substring(posStart, posEnd), ' '); // Compare - log += compareTimes ? String(timeInSec2) : -#if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - doubleToString(Value2, 6, trimTrailingZeros); -#else - floatToString(Value2, 6, trimTrailingZeros); -#endif - log += ')'; - addLogMove(LOG_LEVEL_DEBUG, log); - } - #else // ifndef BUILD_NO_DEBUG - (void)compareTimes; // To avoid compiler warning - #endif // ifndef BUILD_NO_DEBUG - return result; -} - -/********************************************************************************************\ - Generate rule events based on task refresh - \*********************************************************************************************/ -void createRuleEvents(struct EventStruct *event) { - if (!Settings.UseRules) { - return; - } - const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(event->TaskIndex); - - if (!validDeviceIndex(DeviceIndex)) { return; } - - const uint8_t valueCount = getValueCountForTask(event->TaskIndex); - - // Small optimization as sensor type string may result in large strings - // These also only yield a single value, so no need to check for combining task values. - if (event->getSensorType() == Sensor_VType::SENSOR_TYPE_STRING) { - size_t expectedSize = 2 + getTaskDeviceName(event->TaskIndex).length(); - expectedSize += getTaskValueName(event->TaskIndex, 0).length(); - - bool appendCompleteStringvalue = false; - - String eventString; - if (reserve_special(eventString, expectedSize + event->String2.length())) { - appendCompleteStringvalue = true; - } else if (!reserve_special(eventString, expectedSize + 24)) { - // No need to continue as we can't even allocate the event, we probably also cannot process it - addLog(LOG_LEVEL_ERROR, F("Not enough memory for event")); - return; - } - eventString += getTaskDeviceName(event->TaskIndex); - eventString += '#'; - eventString += getTaskValueName(event->TaskIndex, 0); - eventString += '='; - eventString += '`'; - if (appendCompleteStringvalue) { - eventString += event->String2; - } else { - eventString += event->String2.substring(0, 10); - eventString += F("..."); - eventString += event->String2.substring(event->String2.length() - 10); - } - eventString += '`'; - eventQueue.addMove(std::move(eventString)); - } else if (Settings.CombineTaskValues_SingleEvent(event->TaskIndex)) { - String eventvalues; - reserve_special(eventvalues, 32); // Enough for most use cases, prevent lots of memory allocations. - - for (uint8_t varNr = 0; varNr < valueCount; varNr++) { - if (varNr != 0) { - eventvalues += ','; - } - eventvalues += formatUserVarNoCheck(event, varNr); - } - eventQueue.add(event->TaskIndex, F("All"), eventvalues); - } else { - for (uint8_t varNr = 0; varNr < valueCount; varNr++) { - eventQueue.add(event->TaskIndex, getTaskValueName(event->TaskIndex, varNr), formatUserVarNoCheck(event, varNr)); - } - } -} +#include "../ESPEasyCore/ESPEasyRules.h" + +#include "../../_Plugin_Helper.h" + +#include "../Commands/ExecuteCommand.h" +#include "../DataStructs/TimingStats.h" +#include "../DataTypes/EventValueSource.h" +#include "../ESPEasyCore/ESPEasy_backgroundtasks.h" +#include "../ESPEasyCore/Serial.h" +#include "../Globals/Cache.h" +#include "../Globals/Device.h" +#include "../Globals/EventQueue.h" +#include "../Globals/Plugins.h" +#include "../Globals/Plugins_other.h" +#include "../Globals/RulesCalculate.h" +#include "../Globals/Settings.h" +#include "../Helpers/ESPEasy_Storage.h" +#include "../Helpers/ESPEasy_time_calc.h" +#include "../Helpers/FS_Helper.h" +#include "../Helpers/Misc.h" +#include "../Helpers/Numerical.h" +#include "../Helpers/RulesHelper.h" +#include "../Helpers/RulesMatcher.h" +#include "../Helpers/StringConverter.h" +#include "../Helpers/StringParser.h" + + +#include +#include + +#ifdef WEBSERVER_NEW_RULES +String EventToFileName(const String& eventName) { + int size = eventName.length(); + int index = eventName.indexOf('='); + + if (index > -1) { + size = index; + } +#if defined(ESP8266) + String fileName = F("rules/"); +#endif // if defined(ESP8266) +#if defined(ESP32) + String fileName = F("/rules/"); +#endif // if defined(ESP32) + fileName += eventName.substring(0, size); + fileName.replace('#', RULE_FILE_SEPARAROR); + fileName.toLowerCase(); + return fileName; +} + +String FileNameToEvent(const String& fileName) { +#if defined(ESP8266) + String eventName = fileName.substring(6); +#endif // if defined(ESP8266) +#if defined(ESP32) + String eventName = fileName.substring(7); +#endif // if defined(ESP32) + eventName.replace(RULE_FILE_SEPARAROR, '#'); + return eventName; +} +#endif + +void checkRuleSets() { + Cache.rulesHelper.closeAllFiles(); +} + +/********************************************************************************************\ + Process next event from event queue + \*********************************************************************************************/ +bool processNextEvent() { + if (Settings.UseRules) + { + String nextEvent; + + if (eventQueue.getNext(nextEvent)) { + rulesProcessing(nextEvent); + return true; + } + } + + // Just make sure any (accidentally) added or remaining events are not kept. + eventQueue.clear(); + return false; +} + +/********************************************************************************************\ + Rules processing + \*********************************************************************************************/ +void rulesProcessing(const String& event) { + if (!Settings.UseRules) { + return; + } + START_TIMER + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("rulesProcessing")); + #endif // ifndef BUILD_NO_RAM_TRACKER +#ifndef BUILD_NO_DEBUG + const unsigned long timer = millis(); +#endif // ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F("EVENT: "), event)); + } + + if (Settings.OldRulesEngine()) { + bool eventHandled = false; + + if (Settings.EnableRulesCaching()) { + String filename; + size_t pos = 0; + if (Cache.rulesHelper.findMatchingRule(event, filename, pos)) { + const bool startOnMatched = true; // We already matched the event + eventHandled = rulesProcessingFile(filename, event, pos, startOnMatched); + } + } else { + for (uint8_t x = 0; x < RULESETS_MAX && !eventHandled; x++) { + eventHandled = rulesProcessingFile(getRulesFileName(x), event); + } + } + } else { + #ifdef WEBSERVER_NEW_RULES + String fileName = EventToFileName(event); + + // if exists processed the rule file + if (fileExists(fileName)) { + rulesProcessingFile(fileName, event); + } + # ifndef BUILD_NO_DEBUG + else { + addLog(LOG_LEVEL_DEBUG, strformat(F("EVENT: %s is ingnored. File %s not found."), + event.c_str(), fileName.c_str())); + } + # endif // ifndef BUILD_NO_DEBUG + #endif // WEBSERVER_NEW_RULES + } + +#ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLogMove(LOG_LEVEL_DEBUG, strformat(F("EVENT: %s Processing: %d ms"), event.c_str(), timePassedSince(timer))); + } +#endif // ifndef BUILD_NO_DEBUG + STOP_TIMER(RULES_PROCESSING); + backgroundtasks(); +} + +/********************************************************************************************\ + Rules processing + \*********************************************************************************************/ +bool rulesProcessingFile(const String& fileName, + const String& event, + size_t pos, + bool startOnMatched) { + if (!Settings.UseRules || !fileExists(fileName)) { + return false; + } + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("rulesProcessingFile")); + #endif // ifndef BUILD_NO_RAM_TRACKER +#ifndef BUILD_NO_DEBUG + + if (Settings.SerialLogLevel == LOG_LEVEL_DEBUG_DEV) { + serialPrint(F("RuleDebug Processing:")); + serialPrintln(fileName); + serialPrintln(F(" flags CMI parse output:")); + } +#endif // ifndef BUILD_NO_DEBUG + + static uint8_t nestingLevel = 0; + + nestingLevel++; + + if (nestingLevel > RULES_MAX_NESTING_LEVEL) { + addLog(LOG_LEVEL_ERROR, F("EVENT: Error: Nesting level exceeded!")); + nestingLevel--; + return false; + } + + + bool match = false; + bool codeBlock = false; + bool isCommand = false; + bool condition[RULES_IF_MAX_NESTING_LEVEL]; + bool ifBranche[RULES_IF_MAX_NESTING_LEVEL]; + uint8_t ifBlock = 0; + uint8_t fakeIfBlock = 0; + + + bool moreAvailable = true; + bool eventHandled = false; + while (moreAvailable && !eventHandled) { + const bool searchNextOnBlock = !codeBlock && !match; + String line = Cache.rulesHelper.readLn(fileName, pos, moreAvailable, searchNextOnBlock); + + // Parse the line and extract the action (if there is any) + String action; + { + START_TIMER + const bool matched_before_parse = match; + bool isOneLiner = false; + parseCompleteNonCommentLine(line, event, action, match, codeBlock, + isCommand, isOneLiner, condition, ifBranche, ifBlock, + fakeIfBlock, startOnMatched); + if ((matched_before_parse && !match) || isOneLiner) { + // We were processing a matching event and now crossed the "endon" + // Or just dealing with a oneliner. + // So we're done processing + eventHandled = true; + backgroundtasks(); + } + STOP_TIMER(RULES_PARSE_LINE); + } + + if (match) // rule matched for one action or a block of actions + { + START_TIMER + processMatchedRule(action, event, + isCommand, condition, + ifBranche, ifBlock, fakeIfBlock); + STOP_TIMER(RULES_PROCESS_MATCHED); + } + } + +/* + if (f) { + f.close(); + } +*/ + + nestingLevel--; + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("rulesProcessingFile2")); + #endif // ifndef BUILD_NO_RAM_TRACKER + backgroundtasks(); + return eventHandled; // && nestingLevel == 0; +} + + +/********************************************************************************************\ + Parse string commands + \*********************************************************************************************/ +bool get_next_inner_bracket(const String& line, int& startIndex, int& closingIndex, char closingBracket) +{ + if (line.length() <= 1) { + // Not possible to have opening and closing bracket on a line this short. + return false; + } + char openingBracket = closingBracket; + + switch (closingBracket) { + case ']': openingBracket = '['; break; + case '}': openingBracket = '{'; break; + case ')': openingBracket = '('; break; + default: + // unknown bracket type + return false; + } + // Closing bracket should not be found on the first position. + closingIndex = line.indexOf(closingBracket, startIndex + 1); + + if (closingIndex == -1) { + // not found + return false; + } + + for (int i = (closingIndex - 1); i > startIndex; --i) { + if (line[i] == openingBracket) { + startIndex = i; + return true; + } + } + return false; +} + +bool get_next_argument(const String& fullCommand, int& index, String& argument, char separator) +{ + if (index == -1) { + return false; + } + int newIndex = fullCommand.indexOf(separator, index); + + if (newIndex == -1) { + argument = fullCommand.substring(index); + } else { + argument = fullCommand.substring(index, newIndex); + } + + if (argument.startsWith(String(separator))) { + argument = argument.substring(1); + } + + // addLog(LOG_LEVEL_INFO, String("get_next_argument: ") + String(index) + " " + fullCommand + " " + argument); + index = newIndex; + + if (index != -1) { + ++index; + } + return argument.length() > 0; +} + +const char bitwise_functions[] PROGMEM = "bitread|bitset|bitclear|bitwrite|xor|and|or"; +enum class bitwise_functions_e { + bitread, + bitset, + bitclear, + bitwrite, + xor_e, // protected keywords, thus appended _e + and_e, + or_e +}; + + +bool parse_bitwise_functions(const String& cmd_s_lower, const String& arg1, const String& arg2, const String& arg3, int64_t& result) { + #ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String log = F("Bitwise: {"); + log += wrapIfContains(cmd_s_lower, ':', '\"'); + log += ':'; + log += wrapIfContains(arg1, ':', '\"'); + + if (arg2.length() > 0) { + log += ':'; + log += wrapIfContains(arg2, ':', '\"'); + + if (arg3.length() > 0) { + log += ':'; + log += wrapIfContains(arg3, ':', '\"'); + } + } + log += '}'; + addLogMove(LOG_LEVEL_DEBUG, log); + } + #endif + + if (cmd_s_lower.length() < 2) { + return false; + } + + int command_i = GetCommandCode(cmd_s_lower.c_str(), bitwise_functions); + if (command_i == -1) { + // No matching function found + return false; + } + + if (cmd_s_lower.startsWith(F("bit"))) { + uint32_t bitnr = 0; + uint64_t iarg2 = 0; + + if (!validUIntFromString(arg1, bitnr) || !validUInt64FromString(arg2, iarg2)) { + return false; + } + + switch(static_cast(command_i)) { + case bitwise_functions_e::bitread: + // Syntax like {bitread:0:123} to get a single decimal '1' + result = bitRead(iarg2, bitnr); + break; + case bitwise_functions_e::bitset: + // Syntax like {bitset:0:122} to set least significant bit of the given nr '122' to '1' => '123' + result = iarg2; + bitSetULL(result, bitnr); + break; + case bitwise_functions_e::bitclear: + // Syntax like {bitclear:0:123} to set least significant bit of the given nr '123' to '0' => '122' + result = iarg2; + bitClearULL(result, bitnr); + break; + case bitwise_functions_e::bitwrite: + { + uint32_t iarg3 = 0; + // Syntax like {bitwrite:0:122:1} to set least significant bit of the given nr '122' to '1' => '123' + if (validUIntFromString(arg3, iarg3)) { + const int bitvalue = (iarg3 & 1); // Only use the last bit of the given parameter + result = iarg2; + bitWriteULL(result, bitnr, bitvalue); + } else { + // Need 3 parameters, but 3rd one is not a valid uint + return false; + } + break; + } + default: + return false; + } + + // all functions starting with "bit" are checked + return true; + } + + uint64_t iarg1, iarg2 = 0; + + if (!validUInt64FromString(arg1, iarg1) || !validUInt64FromString(arg2, iarg2)) { + return false; + } + + switch(static_cast(command_i)) { + case bitwise_functions_e::xor_e: + // Syntax like {xor:127:15} to XOR the binary values 1111111 and 1111 => 1110000 + result = iarg1 ^ iarg2; + break; + case bitwise_functions_e::and_e: + // Syntax like {and:254:15} to AND the binary values 11111110 and 1111 => 1110 + result = iarg1 & iarg2; + break; + case bitwise_functions_e::or_e: + // Syntax like {or:254:15} to OR the binary values 11111110 and 1111 => 11111111 + result = iarg1 | iarg2; + break; + default: + return false; + + } + return true; +} + +bool parse_math_functions(const String& cmd_s_lower, const String& arg1, const String& arg2, const String& arg3, ESPEASY_RULES_FLOAT_TYPE& result) { + ESPEASY_RULES_FLOAT_TYPE farg1; + float farg2, farg3 = 0.0f; + + if (!validDoubleFromString(arg1, farg1)) { + return false; + } + + if (equals(cmd_s_lower, F("constrain"))) { + // Contrain a value X to be within range of A to B + // Syntax like {constrain:x:a:b} to constrain x in range a...b + if (validFloatFromString(arg2, farg2) && validFloatFromString(arg3, farg3)) { + if (farg2 > farg3) { + const float tmp = farg2; + farg2 = farg3; + farg3 = tmp; + } + result = constrain(farg1, farg2, farg3); + } else { + return false; + } + } else { + // No matching function found + return false; + } + return true; +} + +const char string_commands[] PROGMEM = "substring|indexof|indexof_ci|equals|equals_ci|timetomin|timetosec|strtol|tobin|tohex|ord|urlencode"; +enum class string_commands_e { + substring, + indexof, + indexof_ci, + equals, + equals_ci, + timetomin, + timetosec, + strtol, + tobin, + tohex, + ord, + urlencode +}; + + +void parse_string_commands(String& line) { + int startIndex = 0; + int closingIndex; + + bool mustReplaceMaskedChars = false; + + while (get_next_inner_bracket(line, startIndex, closingIndex, '}')) { + // Command without opening and closing brackets. + const String fullCommand = line.substring(startIndex + 1, closingIndex); + const String cmd_s_lower = parseString(fullCommand, 1, ':'); + const String arg1 = parseStringKeepCaseNoTrim(fullCommand, 2, ':'); + const String arg2 = parseStringKeepCaseNoTrim(fullCommand, 3, ':'); + const String arg3 = parseStringKeepCaseNoTrim(fullCommand, 4, ':'); + + if (cmd_s_lower.length() > 0) { + String replacement; // maybe just replace with empty to avoid looping? + uint64_t iarg1, iarg2 = 0; + ESPEASY_RULES_FLOAT_TYPE fresult{}; + int64_t iresult = 0; + int32_t startpos, endpos = -1; + const bool arg1valid = validIntFromString(arg1, startpos); + const bool arg2valid = validIntFromString(arg2, endpos); + + if (parse_math_functions(cmd_s_lower, arg1, arg2, arg3, fresult)) { + const bool trimTrailingZeros = true; + #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + replacement = doubleToString(fresult, maxNrDecimals_fpType(fresult), trimTrailingZeros); + #else + replacement = floatToString(fresult, maxNrDecimals_fpType(fresult), trimTrailingZeros); + #endif + } else if (parse_bitwise_functions(cmd_s_lower, arg1, arg2, arg3, iresult)) { + replacement = ull2String(iresult); + } else { + + int command_i = GetCommandCode(cmd_s_lower.c_str(), string_commands); + if (command_i != -1) { + const string_commands_e command = static_cast(command_i); + + // addLog(LOG_LEVEL_INFO, strformat(F("parse_string_commands cmd: %s %s %s %s"), + // cmd_s_lower.c_str(), arg1.c_str(), arg2.c_str(), arg3.c_str())); + + switch (command) { + case string_commands_e::substring: + // substring arduino style (first char included, last char excluded) + // Syntax like 12345{substring:8:12:ANOTHER HELLO WORLD}67890 + + if (arg1valid + && arg2valid) { + replacement = arg3.substring(startpos, endpos); + } + break; + case string_commands_e::indexof: + case string_commands_e::indexof_ci: + // indexOf arduino style (0-based position of first char returned, -1 if not found, case sensitive), 3rd argument is search-offset + // indexOf_ci : case-insensitive + // Syntax like {indexof:HELLO:"ANOTHER HELLO WORLD"} => 8, {indexof:hello:"ANOTHER HELLO WORLD"} => -1, {indexof_ci:Hello:"ANOTHER HELLO WORLD"} => 8 + // or like {indexof_ci:hello:"ANOTHER HELLO WORLD":10} => -1 + + if (!arg1.isEmpty() + && !arg2.isEmpty()) { + uint32_t offset = 0; + validUIntFromString(arg3, offset); + if (command == string_commands_e::indexof_ci) { + String arg1copy(arg1); + String arg2copy(arg2); + arg1copy.toLowerCase(); + arg2copy.toLowerCase(); + replacement = arg2copy.indexOf(arg1copy, offset); + } else { + replacement = arg2.indexOf(arg1, offset); + } + } + break; + case string_commands_e::equals: + case string_commands_e::equals_ci: + // equals: compare strings 1 = equal, 0 = unequal (case sensitive) + // equals_ci: case-insensitive compare + // Syntax like {equals:HELLO:HELLO} => 1, {equals:hello:HELLO} => 0, {equals_ci:hello:HELLO} => 1, {equals_ci:hello:BLA} => 0 + + if (!arg1.isEmpty() + && !arg2.isEmpty()) { + if (command == string_commands_e::equals_ci) { + replacement = arg2.equalsIgnoreCase(arg1); + } else { + replacement = arg2.equals(arg1); + } + } + break; + case string_commands_e::timetomin: + case string_commands_e::timetosec: + // time to minutes, transform a substring hh:mm to minutes + // time to seconds, transform a substring hh:mm:ss to seconds + // syntax similar to substring + + if (arg1valid + && arg2valid) { + int timeSeconds = 0; + String timeString; + if(timeStringToSeconds(arg3.substring(startpos, endpos), timeSeconds, timeString)) { + if (command == string_commands_e::timetosec) { + replacement = timeSeconds; + } else { // timetomin + replacement = timeSeconds / 60; + } + } + } + break; + case string_commands_e::strtol: + // string to long integer (from cstdlib) + // Syntax like 1234{strtol:16:38}7890 + if (validUInt64FromString(arg1, iarg1) + && validUInt64FromString(arg2, iarg2)) { + replacement = String(strtoul(arg2.c_str(), nullptr, iarg1)); + } + break; + case string_commands_e::tobin: + // Convert to binary string + // Syntax like 1234{tobin:15}7890 + if (validUInt64FromString(arg1, iarg1)) { + replacement = ull2String(iarg1, BIN); + } + break; + case string_commands_e::tohex: + // Convert to HEX string + // Syntax like 1234{tohex:15[,minHexDigits]}7890 + if (validUInt64FromString(arg1, iarg1)) { + if (!validUInt64FromString(arg2, iarg2)) { + iarg2 = 0; + } + replacement = formatToHex_no_prefix(iarg1, iarg2); + } + break; + case string_commands_e::ord: + { + // Give the ordinal/integer value of the first character of a string + // Syntax like let 1,{ord:B} + uint8_t uval = arg1.c_str()[0]; + replacement = String(uval); + } + break; + case string_commands_e::urlencode: + // Convert to url-encoded string + // Syntax like {urlencode:"string to/encode"} + if (!arg1.isEmpty()) { + replacement = URLEncode(arg1); + } + break; + } + } + } + + if (replacement.isEmpty()) { + // part in braces is not a supported command. + // replace the {} with other characters to mask the braces so we can continue parsing. + // We have to unmask then after we're finished. + // See: https://github.com/letscontrolit/ESPEasy/issues/2932#issuecomment-596139096 + replacement = line.substring(startIndex, closingIndex + 1); + replacement.replace('{', static_cast(0x02)); + replacement.replace('}', static_cast(0x03)); + mustReplaceMaskedChars = true; + } + + // Replace the full command including opening and closing brackets. + line.replace(line.substring(startIndex, closingIndex + 1), replacement); + + /* + if (replacement.length() > 0) { + addLog(LOG_LEVEL_INFO, strformat(F("parse_string_commands cmd: %s -> %s"), fullCommand.c_str(), replacement.c_str()); + } + */ + } + } + + if (mustReplaceMaskedChars) { + // We now have to check if we did mask some parts and unmask them. + // Let's hope we don't mess up any Unicode here. + line.replace(static_cast(0x02), '{'); + line.replace(static_cast(0x03), '}'); + } +} + +void substitute_eventvalue(String& line, const String& event) { + if (substitute_eventvalue_CallBack_ptr != nullptr) { + substitute_eventvalue_CallBack_ptr(line, event); + } + + if (line.indexOf(F("%event")) != -1) { + if (event.charAt(0) == '!') { + line.replace(F("%eventvalue%"), event); // substitute %eventvalue% with + // literal event string if + // starting with '!' + } else { + const int equalsPos = event.indexOf('='); + + String argString; + + if (equalsPos > 0) { + argString = event.substring(equalsPos + 1); + } + + // Replace %eventvalueX% with the actual value of the event. + // For compatibility reasons also replace %eventvalue% (argc = 0) + line.replace(F("%eventvalue%"), F("%eventvalue1%")); + int eventvalue_pos = line.indexOf(F("%eventvalue")); + + while (eventvalue_pos != -1) { + const int percent_pos = line.indexOf('%', eventvalue_pos + 1); + + if (percent_pos == -1) { + // Found "%eventvalue" without closing % + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + String log = F("Rules : Syntax error, missing '%' in '%eventvalue%': '"); + log += line; + log += F("' %-pos: "); + log += percent_pos; + addLog(LOG_LEVEL_ERROR, log); + } + line.replace(F("%eventvalue"), F("")); + } else { + // Find the optional part for a default value when the asked for eventvalue does not exist. + // Syntax: %eventvalueX|Y% + // With: X = event value nr, Y = default value when eventvalue does not exist. + String defaultValue('0'); + int or_else_pos = line.indexOf('|', eventvalue_pos); + if ((or_else_pos == -1) || (or_else_pos > percent_pos)) { + or_else_pos = percent_pos; + } else { + defaultValue = line.substring(or_else_pos + 1, percent_pos); + } + const String nr = line.substring(eventvalue_pos + 11, or_else_pos); + const String eventvalue = line.substring(eventvalue_pos, percent_pos + 1); + int argc = -1; + + if (equals(nr, '0')) { + // Replace %eventvalue0% with the entire list of arguments. + line.replace(eventvalue, argString); + } else { + argc = nr.toInt(); + + // argc will be 0 on invalid int (0 was already handled) + if (argc > 0) { + String tmpParam; + + if (!GetArgv(argString.c_str(), tmpParam, argc)) { + // Replace with default value for non existing event values + tmpParam = parseTemplate(defaultValue); + } + line.replace(eventvalue, tmpParam); + } else { + // Just remove the invalid eventvalue variable + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + addLog(LOG_LEVEL_ERROR, concat(F("Rules : Syntax error, invalid variable: "), eventvalue)); + } + line.replace(eventvalue, EMPTY_STRING); + } + } + } + eventvalue_pos = line.indexOf(F("%eventvalue")); + } + + if ((line.indexOf(F("%eventname%")) != -1) || + (line.indexOf(F("%eventpar%")) != -1)) { + const String eventName = equalsPos == -1 ? event : event.substring(0, equalsPos); + + // Replace %eventname% with the literal event + line.replace(F("%eventname%"), eventName); + + // Part of %eventname% after the # char + const int hash_pos = eventName.indexOf('#'); + line.replace(F("%eventpar%"), hash_pos == -1 ? EMPTY_STRING : eventName.substring(hash_pos + 1)); + } + } + } +} + +void parseCompleteNonCommentLine(String& line, const String& event, + String& action, bool& match, + bool& codeBlock, bool& isCommand, bool& isOneLiner, + bool condition[], bool ifBranche[], + uint8_t& ifBlock, uint8_t& fakeIfBlock, + bool startOnMatched) { + if (line.length() == 0) { + return; + } + const bool lineStartsWith_on = line.substring(0, 3).equalsIgnoreCase(F("on ")); + + if (!codeBlock && !match) { + // We're looking for a new code block. + // Continue with next line if none was found on current line. + if (!lineStartsWith_on) { + return; + } + } + + if (line.equalsIgnoreCase(F("endon"))) // Check if action block has ended, then we will + // wait for a new "on" rule + { + isCommand = false; + codeBlock = false; + match = false; + ifBlock = 0; + fakeIfBlock = 0; + return; + } + + const bool lineStartsWith_pct_event = line.startsWith(F("%event"));; + + isCommand = true; + + if (match || !codeBlock) { + // only parse [xxx#yyy] if we have a matching ruleblock or need to eval the + // "on" (no codeBlock) + // This to avoid wasting CPU time... + if (match && !fakeIfBlock) { + // substitution of %eventvalue% is made here so it can be used on if + // statement too + substitute_eventvalue(line, event); + } + + if (match || lineStartsWith_on) { + // Only parseTemplate when we are actually doing something with the line. + // When still looking for the "on ... do" part, do not change it before we found the block. + line = parseTemplate(line); + } + } + + + if (!codeBlock) // do not check "on" rules if a block of actions is to be + // processed + { + action.clear(); + if (lineStartsWith_on) { + ifBlock = 0; + fakeIfBlock = 0; + + String ruleEvent; + if (getEventFromRulesLine(line, ruleEvent, action)) { + START_TIMER + match = startOnMatched || ruleMatch(event, ruleEvent); + STOP_TIMER(RULES_MATCH); + } else { + match = false; + } + + if (action.length() > 0) // single on/do/action line, no block + { + isCommand = true; + isOneLiner = true; + codeBlock = false; + } else { + isCommand = false; + codeBlock = true; + } + } + } else { + #ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG_DEV)) { + // keep the line for the log + action = line; + } else { + action = std::move(line); + } + #else + action = std::move(line); + #endif + } + + if (isCommand && lineStartsWith_pct_event) { + action = concat(F("restrict,"), action); + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + addLogMove(LOG_LEVEL_ERROR, + concat(F("Rules : Prefix command with 'restrict': "), action)); + } + } + +#ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG_DEV)) { + String log = F("RuleDebug: "); + log += codeBlock ? 0 : 1; + log += match ? 0 : 1; + log += isCommand ? 0 : 1; + log += F(": "); + log += line; + addLogMove(LOG_LEVEL_DEBUG_DEV, log); + } +#endif // ifndef BUILD_NO_DEBUG +} + +void processMatchedRule(String& action, const String& event, + bool& isCommand, bool condition[], bool ifBranche[], + uint8_t& ifBlock, uint8_t& fakeIfBlock) { + String lcAction = action; + + lcAction.toLowerCase(); + lcAction.trim(); + + if (fakeIfBlock) { + isCommand = false; + } + else if (ifBlock) { + if (condition[ifBlock - 1] != ifBranche[ifBlock - 1]) { + isCommand = false; + } + } + int split = + lcAction.startsWith(F("elseif ")) ? 0 : -1; // check for optional "elseif" condition + + if (split != -1) { + // Found "elseif" condition + isCommand = false; + + if (ifBlock && !fakeIfBlock) { + if (ifBranche[ifBlock - 1]) { + if (condition[ifBlock - 1]) { + ifBranche[ifBlock - 1] = false; + } + else { + String check = lcAction.substring(split + 7); + check.trim(); + condition[ifBlock - 1] = conditionMatchExtended(check); +#ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String log = F("Lev."); + log += String(ifBlock); + log += F(": [elseif "); + log += check; + log += F("]="); + log += boolToString(condition[ifBlock - 1]); + addLogMove(LOG_LEVEL_DEBUG, log); + } +#endif // ifndef BUILD_NO_DEBUG + } + } + } + } else { + // check for optional "if" condition + split = lcAction.startsWith(F("if ")) ? 0 : -1; + + if (split != -1) { + if (ifBlock < RULES_IF_MAX_NESTING_LEVEL) { + if (isCommand) { + ifBlock++; + String check = lcAction.substring(split + 3); + check.trim(); + condition[ifBlock - 1] = conditionMatchExtended(check); + ifBranche[ifBlock - 1] = true; +#ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String log = F("Lev."); + log += String(ifBlock); + log += F(": [if "); + log += check; + log += F("]="); + log += boolToString(condition[ifBlock - 1]); + addLogMove(LOG_LEVEL_DEBUG, log); + } +#endif // ifndef BUILD_NO_DEBUG + } else { + fakeIfBlock++; + } + } else { + fakeIfBlock++; + + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + String log = F("Lev."); + log += String(ifBlock); + log += F(": Error: IF Nesting level exceeded!"); + addLogMove(LOG_LEVEL_ERROR, log); + } + } + isCommand = false; + } + } + + if ((equals(lcAction, F("else"))) && !fakeIfBlock) // in case of an "else" block of + // actions, set ifBranche to + // false + { + ifBranche[ifBlock - 1] = false; + isCommand = false; +#ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String log = F("Lev."); + log += String(ifBlock); + log += F(": [else]="); + log += boolToString(condition[ifBlock - 1] == ifBranche[ifBlock - 1]); + addLogMove(LOG_LEVEL_DEBUG, log); + } +#endif // ifndef BUILD_NO_DEBUG + } + + if (equals(lcAction, F("endif"))) // conditional block ends here + { + if (fakeIfBlock) { + fakeIfBlock--; + } + else if (ifBlock) { + ifBlock--; + } + isCommand = false; + } + + // process the action if it's a command and unconditional, or conditional and + // the condition matches the if or else block. + if (isCommand) { + substitute_eventvalue(action, event); + + const bool executeRestricted = equals(parseString(action, 1), F("restrict")); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String actionlog = executeRestricted ? F("ACT : (restricted) ") : F("ACT : "); + actionlog += action; + addLogMove(LOG_LEVEL_INFO, actionlog); + } + + if (executeRestricted) { + ExecuteCommand_all({EventValueSource::Enum::VALUE_SOURCE_RULES_RESTRICTED, parseStringToEndKeepCase(action, 2)}); + } else { + // Use action.c_str() here as we need to preserve the action string. + ExecuteCommand_all({EventValueSource::Enum::VALUE_SOURCE_RULES, action.c_str()}); + } + delay(0); + } +} + +/********************************************************************************************\ + Check if an event matches to a given rule + \*********************************************************************************************/ + + +/********************************************************************************************\ + Check expression + \*********************************************************************************************/ +bool conditionMatchExtended(String& check) { + int condAnd = -1; + int condOr = -1; + bool rightcond = false; + bool leftcond = conditionMatch(check); // initial check + + #ifndef BUILD_NO_DEBUG + String debugstr; + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + debugstr += boolToString(leftcond); + } + #endif // ifndef BUILD_NO_DEBUG + + do { + #ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String log = F("conditionMatchExtended: "); + log += debugstr; + log += ' '; + log += wrap_String(check, '"'); + addLogMove(LOG_LEVEL_DEBUG, log); + } + #endif // ifndef BUILD_NO_DEBUG + condAnd = check.indexOf(F(" and ")); + condOr = check.indexOf(F(" or ")); + + if ((condAnd > 0) || (condOr > 0)) { // we got AND/OR + if ((condAnd > 0) && (((condOr < 0) /*&& (condOr < condAnd)*/) || + ((condOr > 0) && (condOr > condAnd)))) { // AND is first + check = check.substring(condAnd + 5); + rightcond = conditionMatch(check); + leftcond = (leftcond && rightcond); + + #ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + debugstr += F(" && "); + } + #endif // ifndef BUILD_NO_DEBUG + } else { // OR is first + check = check.substring(condOr + 4); + rightcond = conditionMatch(check); + leftcond = (leftcond || rightcond); + + #ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + debugstr += F(" || "); + } + #endif // ifndef BUILD_NO_DEBUG + } + + #ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + debugstr += boolToString(rightcond); + } + #endif // ifndef BUILD_NO_DEBUG + } + } while (condAnd > 0 || condOr > 0); + + #ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + check = debugstr; + } + #endif // ifndef BUILD_NO_DEBUG + return leftcond; +} + + +void logtimeStringToSeconds(const String& tBuf, int hours, int minutes, int seconds, bool valid) +{ + #ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String log; + log = F("timeStringToSeconds: "); + log += wrap_String(tBuf, '"'); + log += F(" --> "); + if (valid) { + log += formatIntLeadingZeroes(hours, 2); + log += ':'; + log += formatIntLeadingZeroes(minutes, 2); + log += ':'; + log += formatIntLeadingZeroes(seconds, 2); + } else { + log += F("invalid"); + } + addLogMove(LOG_LEVEL_DEBUG, log); + } + + #endif // ifndef BUILD_NO_DEBUG +} + +// convert old and new time string to nr of seconds +// return whether it should be considered a time string. +bool timeStringToSeconds(const String& tBuf, int& time_seconds, String& timeString) { + { + // Make sure we only check for expected characters + // e.g. if 10:11:12 > 7:07 and 10:11:12 < 20:09:04 + // Should only try to match "7:07", not "7:07 and 10:11:12" + // Or else it will find "7:07:11" + bool done = false; + for (uint8_t pos = 0; !done && timeString.length() < 8 && pos < tBuf.length(); ++pos) { + char c = tBuf[pos]; + if (isdigit(c) || c == ':') { + timeString += c; + } else { + done = true; + } + } + } + + time_seconds = -1; + int32_t hours = 0; + int32_t minutes = 0; + int32_t seconds = 0; + + int tmpIndex = 0; + String hours_str, minutes_str, seconds_str; + bool validTime = false; + + if (get_next_argument(timeString, tmpIndex, hours_str, ':')) { + if (validIntFromString(hours_str, hours)) { + validTime = true; + + if ((hours < 0) || (hours > 24)) { + validTime = false; + } else { + time_seconds = hours * 60 * 60; + } + + if (validTime && get_next_argument(timeString, tmpIndex, minutes_str, ':')) { + if (validIntFromString(minutes_str, minutes)) { + if ((minutes < 0) || (minutes > 59)) { + validTime = false; + } else { + time_seconds += minutes * 60; + } + + if (validTime && get_next_argument(timeString, tmpIndex, seconds_str, ':')) { + // New format, only HH:MM:SS + if (validIntFromString(seconds_str, seconds)) { + if ((seconds < 0) || (seconds > 59)) { + validTime = false; + } else { + time_seconds += seconds; + } + } + } else { + // Old format, only HH:MM + } + } + } else { + // It is a valid time string, but could also be just a numerical. + // We mark it here as invalid, meaning the 'other' time to compare it to must contain more than just the hour. + validTime = false; + } + } + } + logtimeStringToSeconds(timeString, hours, minutes, seconds, validTime); + return validTime; +} + +// Balance the count of parentheses (aka round braces) by adding the missing left or right parentheses, if any +// Returns the number of added parentheses, < 0 is left parentheses added, > 0 is right parentheses added +int balanceParentheses(String& string) { + int left = 0; + int right = 0; + for (unsigned int i = 0; i < string.length(); i++) { + if (string[i] == '(') { + left++; + } else if (string[i] == ')') { + right++; + } + } + if (left != right) { + string.reserve(string.length() + abs(right - left)); // Re-allocate max. once + } + if (left > right) { + for (int i = 0; i < left - right; i++) { + string += ')'; + } + } else if (right > left) { + for (int i = 0; i < right - left; i++) { + string = String('(') + string; // This is quite 'expensive' + } + } + return left - right; +} + +bool conditionMatch(const String& check) { + int posStart, posEnd; + char compare; + + if (!findCompareCondition(check, compare, posStart, posEnd)) { + return false; + } + + String tmpCheck1 = check.substring(0, posStart); + String tmpCheck2 = check.substring(posEnd); + + tmpCheck1.trim(); + tmpCheck2.trim(); + ESPEASY_RULES_FLOAT_TYPE Value1{}; + ESPEASY_RULES_FLOAT_TYPE Value2{}; + + int timeInSec1 = 0; + int timeInSec2 = 0; + String timeString1, timeString2; + bool validTime1 = timeStringToSeconds(tmpCheck1, timeInSec1, timeString1); + bool validTime2 = timeStringToSeconds(tmpCheck2, timeInSec2, timeString2); + bool result = false; + + bool compareTimes = false; + + if ((validTime1 || validTime2) && (timeInSec1 != -1) && (timeInSec2 != -1)) + { + // At least one is a time containing ':' separator + // AND both can be considered a time, so use it as a time and compare seconds. + compareTimes = true; + result = compareIntValues(compare, timeInSec1, timeInSec2); + tmpCheck1 = timeString1; + tmpCheck2 = timeString2; + } else { + int condAnd = tmpCheck2.indexOf(F(" and ")); + int condOr = tmpCheck2.indexOf(F(" or ")); + if (condAnd > -1 || condOr > -1) { // Only parse first condition, rest will be parsed 'later' + if (condAnd > -1 && (condOr == -1 || condAnd < condOr)) { + tmpCheck2 = tmpCheck2.substring(0, condAnd); + } else if (condOr > -1) { + tmpCheck2 = tmpCheck2.substring(0, condOr); + } + tmpCheck2.trim(); + } + balanceParentheses(tmpCheck1); + balanceParentheses(tmpCheck2); + if (isError(Calculate(tmpCheck1, Value1)) || + isError(Calculate(tmpCheck2, Value2))) + { + return false; + } + result = compareDoubleValues(compare, Value1, Value2); + } + + #ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String log = F("conditionMatch: "); + log += wrap_String(check, '"'); + log += F(" --> "); + + log += wrap_String(tmpCheck1, '"'); + log += wrap_String(check.substring(posStart, posEnd), ' '); // Compare + log += wrap_String(tmpCheck2, '"'); + + log += F(" --> "); + log += boolToString(result); + log += ' '; + + log += '('; + const bool trimTrailingZeros = true; + log += compareTimes ? String(timeInSec1) : +#if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + doubleToString(Value1, 6, trimTrailingZeros); +#else + floatToString(Value1, 6, trimTrailingZeros); +#endif + log += wrap_String(check.substring(posStart, posEnd), ' '); // Compare + log += compareTimes ? String(timeInSec2) : +#if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + doubleToString(Value2, 6, trimTrailingZeros); +#else + floatToString(Value2, 6, trimTrailingZeros); +#endif + log += ')'; + addLogMove(LOG_LEVEL_DEBUG, log); + } + #else // ifndef BUILD_NO_DEBUG + (void)compareTimes; // To avoid compiler warning + #endif // ifndef BUILD_NO_DEBUG + return result; +} + +/********************************************************************************************\ + Generate rule events based on task refresh + \*********************************************************************************************/ +void createRuleEvents(struct EventStruct *event) { + if (!Settings.UseRules) { + return; + } + const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(event->TaskIndex); + + if (!validDeviceIndex(DeviceIndex)) { return; } + + const uint8_t valueCount = getValueCountForTask(event->TaskIndex); + + // Small optimization as sensor type string may result in large strings + // These also only yield a single value, so no need to check for combining task values. + if (event->getSensorType() == Sensor_VType::SENSOR_TYPE_STRING) { + size_t expectedSize = 2 + getTaskDeviceName(event->TaskIndex).length(); + expectedSize += Cache.getTaskDeviceValueName(event->TaskIndex, 0).length(); + + bool appendCompleteStringvalue = false; + + String eventString; + if (reserve_special(eventString, expectedSize + event->String2.length())) { + appendCompleteStringvalue = true; + } else if (!reserve_special(eventString, expectedSize + 24)) { + // No need to continue as we can't even allocate the event, we probably also cannot process it + addLog(LOG_LEVEL_ERROR, F("Not enough memory for event")); + return; + } + eventString += getTaskDeviceName(event->TaskIndex); + eventString += '#'; + eventString += Cache.getTaskDeviceValueName(event->TaskIndex, 0); + eventString += '='; + eventString += '`'; + if (appendCompleteStringvalue) { + eventString += event->String2; + } else { + eventString += event->String2.substring(0, 10); + eventString += F("..."); + eventString += event->String2.substring(event->String2.length() - 10); + } + eventString += '`'; + eventQueue.addMove(std::move(eventString)); + } else if (Settings.CombineTaskValues_SingleEvent(event->TaskIndex)) { + String eventvalues; + reserve_special(eventvalues, 32); // Enough for most use cases, prevent lots of memory allocations. + + for (uint8_t varNr = 0; varNr < valueCount; varNr++) { + if (varNr != 0) { + eventvalues += ','; + } + eventvalues += formatUserVarNoCheck(event, varNr); + } + eventQueue.add(event->TaskIndex, F("All"), eventvalues); + } else { + for (uint8_t varNr = 0; varNr < valueCount; varNr++) { + eventQueue.add(event->TaskIndex, Cache.getTaskDeviceValueName(event->TaskIndex, varNr), formatUserVarNoCheck(event, varNr)); + } + } +} diff --git a/src/src/ESPEasyCore/ESPEasyWifi.cpp b/src/src/ESPEasyCore/ESPEasyWifi.cpp index d62f6460a1..296f660aeb 100644 --- a/src/src/ESPEasyCore/ESPEasyWifi.cpp +++ b/src/src/ESPEasyCore/ESPEasyWifi.cpp @@ -241,11 +241,18 @@ bool WiFiConnected() { } - if (lastCheckedTime != 0 && timePassedSince(lastCheckedTime) < 100) { - if (WiFiEventData.lastDisconnectMoment.isSet() && - WiFiEventData.lastDisconnectMoment.millisPassedSince() > timePassedSince(lastCheckedTime)) - { - // Try to rate-limit the nr of calls to this function or else it will be called 1000's of times a second. + const int32_t timePassed = timePassedSince(lastCheckedTime); + if (lastCheckedTime != 0) { + if (timePassed < 100) { + if (WiFiEventData.lastDisconnectMoment.isSet() && + WiFiEventData.lastDisconnectMoment.millisPassedSince() > timePassed) + { + // Try to rate-limit the nr of calls to this function or else it will be called 1000's of times a second. + return lastState; + } + } + if (timePassed < 10) { + // Rate limit time spent in WiFiConnected() to max. 100x per sec to process the rest of this function return lastState; } } @@ -367,6 +374,9 @@ bool WiFiConnected() { } void WiFiConnectRelaxed() { + if (!WiFiEventData.processedDisconnect) { + processDisconnect(); + } if (!WiFiEventData.WiFiConnectAllowed() || WiFiEventData.wifiConnectInProgress) { if (WiFiEventData.wifiConnectInProgress) { if (WiFiEventData.last_wifi_connect_attempt_moment.isSet()) { @@ -462,6 +472,7 @@ void AttemptWiFiConnect() { if (WiFiEventData.unprocessedWifiEvents()) { return; } + setSTA(false); setSTA(true); @@ -518,10 +529,15 @@ void AttemptWiFiConnect() { } else { WiFi.begin(candidate.ssid.c_str(), key.c_str()); } +#ifdef ESP32 + // Always wait for a second on ESP32 + WiFi.waitForConnectResult(1000); // https://github.com/arendst/Tasmota/issues/14985 +#else if (Settings.WaitWiFiConnect() || candidate.bits.isHidden) { // WiFi.waitForConnectResult(candidate.isHidden ? 3000 : 1000); // https://github.com/arendst/Tasmota/issues/14985 WiFi.waitForConnectResult(1000); // https://github.com/arendst/Tasmota/issues/14985 } +#endif delay(1); } else { WiFiEventData.wifiConnectInProgress = false; @@ -910,6 +926,9 @@ void WifiDisconnect() WiFiEventData.processingDisconnect.isSet()) { return; } + if (WiFi.status() == WL_DISCONNECTED) { + return; + } // Prevent recursion static LongTermTimer processingDisconnectTimer; if (processingDisconnectTimer.isSet() && @@ -1115,8 +1134,8 @@ void WifiScan(bool async, uint8_t channel) { #endif #endif #ifdef ESP32 - const bool passive = false; - const uint32_t max_ms_per_chan = 300; + const bool passive = Settings.PassiveWiFiScan(); + const uint32_t max_ms_per_chan = 120; WiFi.scanNetworks(async, show_hidden, passive, max_ms_per_chan /*, channel */); #endif if (!async) { diff --git a/src/src/ESPEasyCore/ESPEasyWifi_ProcessEvent.cpp b/src/src/ESPEasyCore/ESPEasyWifi_ProcessEvent.cpp index 88480a7fd5..ba47615978 100644 --- a/src/src/ESPEasyCore/ESPEasyWifi_ProcessEvent.cpp +++ b/src/src/ESPEasyCore/ESPEasyWifi_ProcessEvent.cpp @@ -77,7 +77,9 @@ void handle_unprocessedNetworkEvents() WiFiEventData.setWiFiServicesInitialized(); //#ifdef ESP32 setWebserverRunning(false); + delay(1); setWebserverRunning(true); + delay(1); /* #else CheckRunningServices(); diff --git a/src/src/ESPEasyCore/ESPEasy_backgroundtasks.cpp b/src/src/ESPEasyCore/ESPEasy_backgroundtasks.cpp index a820c68014..ca21573cb3 100644 --- a/src/src/ESPEasyCore/ESPEasy_backgroundtasks.cpp +++ b/src/src/ESPEasyCore/ESPEasy_backgroundtasks.cpp @@ -79,8 +79,11 @@ void backgroundtasks() serial(); // if (webserverRunning) { + { + START_TIMER web_server.handleClient(); -// } + STOP_TIMER(WEBSERVER_HANDLE_CLIENT); + } #if FEATURE_ESPEASY_P2P if (networkConnected) { checkUDP(); diff --git a/src/src/Globals/CPlugins.cpp b/src/src/Globals/CPlugins.cpp index c72e7f3445..083044c842 100644 --- a/src/src/Globals/CPlugins.cpp +++ b/src/src/Globals/CPlugins.cpp @@ -132,6 +132,9 @@ bool CPluginCall(CPlugin::Function Function, struct EventStruct *event, String& } } return true; + case CPlugin::Function::CPLUGIN_CONNECT_SUCCESS: + case CPlugin::Function::CPLUGIN_CONNECT_FAIL: + break; } return false; diff --git a/src/src/Globals/Plugins.cpp b/src/src/Globals/Plugins.cpp index 8ef29e2713..956b38d56a 100644 --- a/src/src/Globals/Plugins.cpp +++ b/src/src/Globals/Plugins.cpp @@ -18,6 +18,7 @@ #include "../Globals/ExtraTaskSettings.h" #include "../Globals/EventQueue.h" #include "../Globals/GlobalMapPortStatus.h" +#include "../Globals/NetworkState.h" #include "../Globals/Settings.h" #include "../Globals/Statistics.h" @@ -320,12 +321,12 @@ bool PluginCallForTask(taskIndex_t taskIndex, uint8_t Function, EventStruct *Tem const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(taskIndex); if (validDeviceIndex(DeviceIndex)) { + if (Function == PLUGIN_INIT) { + UserVar.clear_computed(taskIndex); + LoadTaskSettings(taskIndex); + } if (Settings.TaskDeviceDataFeed[taskIndex] == 0) // these calls only to tasks with local feed { - if (Function == PLUGIN_INIT) { - UserVar.clear_computed(taskIndex); - LoadTaskSettings(taskIndex); - } TempEvent->setTaskIndex(taskIndex); // Need to 'clear' the sensorType first, before calling getSensorType() TempEvent->sensorType = Sensor_VType::SENSOR_TYPE_NOT_SET; @@ -811,7 +812,7 @@ bool PluginCall(uint8_t Function, struct EventStruct *event, String& str) } bool retval = false; const bool performPluginCall = - (Function != PLUGIN_READ) || + (Function != PLUGIN_READ && Function != PLUGIN_INIT) || (Settings.TaskDeviceDataFeed[event->TaskIndex] == 0); #if FEATURE_I2C_DEVICE_CHECK bool i2cStatusOk = true; @@ -890,7 +891,16 @@ bool PluginCall(uint8_t Function, struct EventStruct *event, String& str) PluginTaskData_base *taskData = getPluginTaskDataBaseClassOnly(event->TaskIndex); if (taskData != nullptr) { - taskData->pushPluginStatsValues(event, !Device[DeviceIndex].TaskLogsOwnPeaks); + // FIXME TD-er: Must make this flag configurable + const bool onlyUpdateTimestampWhenSame = true; + const bool trackpeaks = + Settings.TaskDeviceDataFeed[event->TaskIndex] != 0 || // Receive data from remote node + !Device[DeviceIndex].TaskLogsOwnPeaks; + + taskData->pushPluginStatsValues( + event, + trackpeaks, + onlyUpdateTimestampWhenSame); } #endif // if FEATURE_PLUGIN_STATS saveUserVarToRTC(); diff --git a/src/src/Helpers/Audio.cpp b/src/src/Helpers/Audio.cpp index 6135957f4a..2501f68bd4 100644 --- a/src/src/Helpers/Audio.cpp +++ b/src/src/Helpers/Audio.cpp @@ -1,5 +1,6 @@ #include "../Helpers/Audio.h" +#include "../DataStructs/TimingStats.h" #include "../ESPEasyCore/ESPEasyGPIO.h" #include "../Globals/RamTracker.h" #include "../Helpers/Hardware_GPIO.h" @@ -99,6 +100,7 @@ bool play_rtttl(int8_t _pin, const char *p) { # if FEATURE_ANYRTTTL_ASYNC void update_rtttl() { + START_TIMER if (anyrtttl::nonblocking::isPlaying()) { anyrtttl::nonblocking::play(); } else { @@ -113,6 +115,7 @@ void update_rtttl() { # endif // if FEATURE_RTTTL_EVENTS clear_rtttl_melody(); // Release memory } + STOP_TIMER(UPDATE_RTTTL); } # endif // if FEATURE_ANYRTTTL_ASYNC diff --git a/src/src/Helpers/CUL_stats.cpp b/src/src/Helpers/CUL_stats.cpp index b857bda1dc..4e790ae397 100644 --- a/src/src/Helpers/CUL_stats.cpp +++ b/src/src/Helpers/CUL_stats.cpp @@ -1,135 +1,135 @@ -#include "../Helpers/CUL_stats.h" - -#ifdef USES_P094 - -# include "../ESPEasyCore/ESPEasy_Log.h" -# include "../Globals/ESPEasy_time.h" -# include "../Helpers/CRC_functions.h" -# include "../Helpers/ESPEasy_time_calc.h" -# include "../Helpers/StringConverter.h" - -# include "../WebServer/Markup.h" -# include "../WebServer/HTML_wrappers.h" - -String CUL_Stats::toString(const CUL_Stats_struct& element) const -{ - uint8_t LQI = 0; - const int16_t rssi = mBusPacket_t::decode_LQI_RSSI(element._lqi_rssi, LQI); - - // e.g.: THC.02.12345678;1674030412;1674031412;123;101,-36 - static size_t estimated_length = 52; - - String res; - - res.reserve(estimated_length); - { - auto it = _mBusStatsSourceMap.find(element._sourceHash); - if (element._sourceHash != 0u && it != _mBusStatsSourceMap.end()) { - res += it->second; - } else { - res += '-'; - } - } - res += ';'; - - if (element._id1 != 0u) { - mBusPacket_header_t deviceID; - deviceID.decode_fromUint64(element._id1); - res += deviceID.toString(); - } else { - res += '-'; - } - res += ';'; - - if (element._id2 != 0u) { - mBusPacket_header_t deviceID; - deviceID.decode_fromUint64(element._id2); - res += deviceID.toString(); - } else { - res += '-'; - } - res += ';'; - - res += element._UnixTimeFirstSeen; - res += ';'; - res += element._UnixTimeLastSeen; - res += ';'; - res += element._count; - res += ';'; - res += LQI; - res += ';'; - res += rssi; - - if (res.length() > estimated_length) { - estimated_length = res.length(); - } - return res; -} - -bool CUL_Stats::add(const mBusPacket_t& packet) -{ - const CUL_stats_hash sourceHash{}; - return add(packet, packet.deviceID_to_map_key_no_length(), sourceHash); -} - - -bool CUL_Stats::add(const mBusPacket_t& packet, const String& source) -{ - CUL_stats_hash key = packet.deviceID_to_map_key_no_length(); - CUL_stats_hash sourceHash{}; - - if (!source.isEmpty()) { - sourceHash = calc_CRC32((const uint8_t *)(source.c_str()), source.length()); - _mBusStatsSourceMap[sourceHash] = source; - key ^= sourceHash; - } - - return add(packet, key, sourceHash); -} - -bool CUL_Stats::add(const mBusPacket_t& packet, CUL_stats_hash key, CUL_stats_hash sourceHash) -{ - if (key == 0) { return false; } - - auto it = _mBusStatsMap.find(key); - - if (it == _mBusStatsMap.end()) { - CUL_Stats_struct tmp; - tmp._count = 1; - tmp._id1 = packet._deviceId1.encode_toUInt64(); - tmp._id2 = packet._deviceId2.encode_toUInt64(); - tmp._lqi_rssi = packet._lqi_rssi; - tmp._UnixTimeFirstSeen = node_time.now(); - tmp._UnixTimeLastSeen = tmp._UnixTimeFirstSeen; - tmp._sourceHash = sourceHash; - _mBusStatsMap[key] = tmp; - return true; - } - it->second._count++; - it->second._lqi_rssi = packet._lqi_rssi; - it->second._UnixTimeLastSeen = node_time.now(); - return false; -} - -String CUL_Stats::getFront() -{ - auto it = _mBusStatsMap.begin(); - - if (it == _mBusStatsMap.end()) { return EMPTY_STRING; } - const String res = toString(it->second); - - _mBusStatsMap.erase(it); - return res; -} - -void CUL_Stats::toHtml() const -{ - addRowLabel(F("CUL stats")); - - for (auto it = _mBusStatsMap.begin(); it != _mBusStatsMap.end(); ++it) { - addHtml(toString(it->second)); - addHtml(F("
")); - } -} - -#endif // ifdef USES_P094 +#include "../Helpers/CUL_stats.h" + +#ifdef USES_P094 + +# include "../ESPEasyCore/ESPEasy_Log.h" +# include "../Globals/ESPEasy_time.h" +# include "../Helpers/CRC_functions.h" +# include "../Helpers/ESPEasy_time_calc.h" +# include "../Helpers/StringConverter.h" + +# include "../WebServer/Markup.h" +# include "../WebServer/HTML_wrappers.h" + +String CUL_Stats::toString(const CUL_Stats_struct& element) const +{ + uint8_t LQI = 0; + const int16_t rssi = mBusPacket_t::decode_LQI_RSSI(element._lqi_rssi, LQI); + + // e.g.: THC.02.12345678;1674030412;1674031412;123;101,-36 + static size_t estimated_length = 52; + + String res; + + res.reserve(estimated_length); + { + auto it = _mBusStatsSourceMap.find(element._sourceHash); + if (element._sourceHash != 0u && it != _mBusStatsSourceMap.end()) { + res += it->second; + } else { + res += '-'; + } + } + res += ';'; + + if (element._id1 != 0u) { + mBusPacket_header_t deviceID; + deviceID.decode_fromUint64(element._id1); + res += deviceID.toString(); + } else { + res += '-'; + } + res += ';'; + + if (element._id2 != 0u) { + mBusPacket_header_t deviceID; + deviceID.decode_fromUint64(element._id2); + res += deviceID.toString(); + } else { + res += '-'; + } + res += ';'; + + res += element._UnixTimeFirstSeen; + res += ';'; + res += element._UnixTimeLastSeen; + res += ';'; + res += element._count; + res += ';'; + res += LQI; + res += ';'; + res += rssi; + + if (res.length() > estimated_length) { + estimated_length = res.length(); + } + return res; +} + +bool CUL_Stats::add(const mBusPacket_t& packet) +{ + const CUL_stats_hash sourceHash{}; + return add(packet, packet.deviceID_to_map_key_no_length(), sourceHash); +} + + +bool CUL_Stats::add(const mBusPacket_t& packet, const String& source) +{ + CUL_stats_hash key = packet.deviceID_to_map_key_no_length(); + CUL_stats_hash sourceHash{}; + + if (!source.isEmpty()) { + sourceHash = calc_CRC32((const uint8_t *)(source.c_str()), source.length()); + _mBusStatsSourceMap[sourceHash] = source; + key ^= sourceHash; + } + + return add(packet, key, sourceHash); +} + +bool CUL_Stats::add(const mBusPacket_t& packet, CUL_stats_hash key, CUL_stats_hash sourceHash) +{ + if (key == 0) { return false; } + + auto it = _mBusStatsMap.find(key); + + if (it == _mBusStatsMap.end()) { + CUL_Stats_struct tmp; + tmp._count = 1; + tmp._id1 = packet._deviceId1.encode_toUInt64(); + tmp._id2 = packet._deviceId2.encode_toUInt64(); + tmp._lqi_rssi = packet._lqi_rssi; + tmp._UnixTimeFirstSeen = node_time.getLocalUnixTime(); + tmp._UnixTimeLastSeen = tmp._UnixTimeFirstSeen; + tmp._sourceHash = sourceHash; + _mBusStatsMap[key] = tmp; + return true; + } + it->second._count++; + it->second._lqi_rssi = packet._lqi_rssi; + it->second._UnixTimeLastSeen = node_time.getLocalUnixTime(); + return false; +} + +String CUL_Stats::getFront() +{ + auto it = _mBusStatsMap.begin(); + + if (it == _mBusStatsMap.end()) { return EMPTY_STRING; } + const String res = toString(it->second); + + _mBusStatsMap.erase(it); + return res; +} + +void CUL_Stats::toHtml() const +{ + addRowLabel(F("CUL stats")); + + for (auto it = _mBusStatsMap.begin(); it != _mBusStatsMap.end(); ++it) { + addHtml(toString(it->second)); + addHtml(F("
")); + } +} + +#endif // ifdef USES_P094 diff --git a/src/src/Helpers/Convert.cpp b/src/src/Helpers/Convert.cpp index de20e6ce12..1127858b19 100644 --- a/src/src/Helpers/Convert.cpp +++ b/src/src/Helpers/Convert.cpp @@ -1,205 +1,220 @@ -#include "../Helpers/Convert.h" - -#include "../Helpers/StringConverter.h" - -/*********************************************************************************************\ - Convert bearing in degree to bearing string -\*********************************************************************************************/ -const __FlashStringHelper * getBearing(int degrees) -{ - const __FlashStringHelper* directions[] { - F("N"), - F("NNE"), - F("NE"), - F("ENE"), - F("E"), - F("ESE"), - F("SE"), - F("SSE"), - F("S"), - F("SSW"), - F("SW"), - F("WSW"), - F("W"), - F("WNW"), - F("NW"), - F("NNW") - }; - constexpr size_t nrDirections = NR_ELEMENTS(directions); - const float stepsize = (360.0f / nrDirections); - - if (degrees < 0) { degrees += 360; } // Allow for bearing -360 .. 359 - const size_t bearing_idx = int((degrees + (stepsize / 2.0f)) / stepsize) % nrDirections; - - if (bearing_idx < nrDirections) { - return directions[bearing_idx]; - } - return F(""); -} - -float CelsiusToFahrenheit(float celsius) { - constexpr float ratio = 9.0f / 5.0f; - return celsius * ratio + 32; -} - -int m_secToBeaufort(float m_per_sec) { - // Use ints wit 0.1 m/sec resolution to reduce size. - const uint16_t dm_per_sec = 10 * m_per_sec; - const uint16_t speeds[]{3, 16, 34, 55, 80, 108, 139, 172, 208, 245, 285, 326}; - constexpr int nrElements = NR_ELEMENTS(speeds); - - for (int bft = 0; bft < nrElements; ++bft) { - if (dm_per_sec < speeds[bft]) return bft; - } - return nrElements; -} - -String centimeterToImperialLength(float cm) { - return millimeterToImperialLength(cm * 10.0f); -} - -String millimeterToImperialLength(float mm) { - float inches = mm / 25.4f; - int feet = inches / 12.0f; - - inches = inches - (feet * 12); - String result; - result.reserve(10); - - if (feet != 0) { - result += feet; - result += '\''; - } - result += toString(inches, 1); - result += '"'; - return result; -} - -float minutesToDay(int minutes) { - return minutes / 1440.0f; -} - -String minutesToDayHour(int minutes) { - const int days = minutes / 1440; - const int hours = (minutes % 1440) / 60; - return strformat(F("%dd%02dh"), days, hours); -} - -String minutesToDayHourMinute(int minutes) { - const int days = minutes / 1440; - const int hours = (minutes % 1440) / 60; - const int mins = (minutes % 1440) % 60; - if (days == 0) { - return strformat(F("%02dh%02dm"), hours, mins); - } - return strformat(F("%dd%02dh%02dm"), days, hours, mins); -} - -String minutesToHourColonMinute(int minutes) { - const int hours = (minutes % 1440) / 60; - const int mins = (minutes % 1440) % 60; - - return strformat(F("%02d:%02d"), hours, mins); -} - -String secondsToDayHourMinuteSecond(int seconds) { - const int sec = seconds % 60; - const int minutes = seconds / 60; - const int days = minutes / 1440; - const int min_day = (minutes % 1440); - const int hours = min_day / 60; - const int mins = min_day % 60; - if (days == 0) { - return strformat(F("%02d:%02d:%02d"), hours, mins, sec); - } - return strformat(F("%dT%02d:%02d:%02d"), days, hours, mins, sec); -} - -String format_msec_duration(int64_t duration) { - if (duration < 0ll) { - return concat('-', format_msec_duration(-1ll*duration)); - } - const uint32_t duration_s = duration / 1000ll; - const int32_t duration_ms = duration % 1000ll; - - if (duration_s < 60) { - return strformat( - F("%02d.%03d"), - duration_s, - duration_ms); - } - return strformat( - F("%s.%03d"), - secondsToDayHourMinuteSecond(duration_s).c_str(), - duration_ms); -} - - -// Compute the dew point temperature, given temperature and humidity (temp in Celsius) -// Formula: http://www.ajdesigner.com/phphumidity/dewpoint_equation_dewpoint_temperature.php -// Td = (f/100)^(1/8) * (112 + 0.9*T) + 0.1*T - 112 -float compute_dew_point_temp(float temperature, float humidity_percentage) { - return powf(humidity_percentage / 100.0f, 0.125f) * - (112.0f + 0.9f*temperature) + 0.1f*temperature - 112.0f; -} - -// Compute the humidity given temperature and dew point temperature (temp in Celsius) -// Formula: http://www.ajdesigner.com/phphumidity/dewpoint_equation_relative_humidity.php -// f = 100 * ((112 - 0.1*T + Td) / (112 + 0.9 * T))^8 -float compute_humidity_from_dewpoint(float temperature, float dew_temperature) { - return 100.0f * powf((112.0f - 0.1f * temperature + dew_temperature) / - (112.0f + 0.9f * temperature), 8); -} - - - -/********************************************************************************************\ - Compensate air pressure for given altitude (in meters) - \*********************************************************************************************/ -float pressureElevation(float atmospheric, float altitude) { - // Equation taken from BMP180 datasheet (page 16): - // http://www.adafruit.com/datasheets/BST-BMP180-DS000-09.pdf - - // Note that using the equation from wikipedia can give bad results - // at high altitude. See this thread for more information: - // http://forums.adafruit.com/viewtopic.php?f=22&t=58064 - return atmospheric / powf(1.0f - (altitude / 44330.0f), 5.255f); -} - -float altitudeFromPressure(float atmospheric, float seaLevel) -{ - // Equation taken from BMP180 datasheet (page 16): - // http://www.adafruit.com/datasheets/BST-BMP180-DS000-09.pdf - - // Note that using the equation from wikipedia can give bad results - // at high altitude. See this thread for more information: - // http://forums.adafruit.com/viewtopic.php?f=22&t=58064 - return 44330.0f * (1.0f - powf(atmospheric / seaLevel, 0.1903f)); -} - - - - -/********************************************************************************************\ - In memory convert float to long - \*********************************************************************************************/ -unsigned long float2ul(float f) -{ - unsigned long ul; - - memcpy(&ul, &f, 4); - return ul; -} - -/********************************************************************************************\ - In memory convert long to float - \*********************************************************************************************/ -float ul2float(unsigned long ul) -{ - float f; - - memcpy(&f, &ul, 4); - return f; -} - - +#include "../Helpers/Convert.h" + +#include "../Helpers/StringConverter.h" +#include "../Helpers/ESPEasy_time_calc.h" + +/*********************************************************************************************\ + Convert bearing in degree to bearing string +\*********************************************************************************************/ +const __FlashStringHelper * getBearing(int degrees) +{ + const __FlashStringHelper* directions[] { + F("N"), + F("NNE"), + F("NE"), + F("ENE"), + F("E"), + F("ESE"), + F("SE"), + F("SSE"), + F("S"), + F("SSW"), + F("SW"), + F("WSW"), + F("W"), + F("WNW"), + F("NW"), + F("NNW") + }; + constexpr size_t nrDirections = NR_ELEMENTS(directions); + const float stepsize = (360.0f / nrDirections); + + if (degrees < 0) { degrees += 360; } // Allow for bearing -360 .. 359 + const size_t bearing_idx = int((degrees + (stepsize / 2.0f)) / stepsize) % nrDirections; + + if (bearing_idx < nrDirections) { + return directions[bearing_idx]; + } + return F(""); +} + +float CelsiusToFahrenheit(float celsius) { + constexpr float ratio = 9.0f / 5.0f; + return celsius * ratio + 32; +} + +int m_secToBeaufort(float m_per_sec) { + // Use ints wit 0.1 m/sec resolution to reduce size. + const uint16_t dm_per_sec = 10 * m_per_sec; + const uint16_t speeds[]{3, 16, 34, 55, 80, 108, 139, 172, 208, 245, 285, 326}; + constexpr int nrElements = NR_ELEMENTS(speeds); + + for (int bft = 0; bft < nrElements; ++bft) { + if (dm_per_sec < speeds[bft]) return bft; + } + return nrElements; +} + +String centimeterToImperialLength(float cm) { + return millimeterToImperialLength(cm * 10.0f); +} + +String millimeterToImperialLength(float mm) { + float inches = mm / 25.4f; + int feet = inches / 12.0f; + + inches = inches - (feet * 12); + String result; + result.reserve(10); + + if (feet != 0) { + result += feet; + result += '\''; + } + result += toString(inches, 1); + result += '"'; + return result; +} + +float minutesToDay(int minutes) { + return minutes / 1440.0f; +} + +String minutesToDayHour(int minutes) { + const int days = minutes / 1440; + const int hours = (minutes % 1440) / 60; + return strformat(F("%dd%02dh"), days, hours); +} + +String minutesToDayHourMinute(int minutes) { + const int days = minutes / 1440; + const int hours = (minutes % 1440) / 60; + const int mins = (minutes % 1440) % 60; + if (days == 0) { + return strformat(F("%02dh%02dm"), hours, mins); + } + return strformat(F("%dd%02dh%02dm"), days, hours, mins); +} + +String minutesToHourColonMinute(int minutes) { + const int hours = (minutes % 1440) / 60; + const int mins = (minutes % 1440) % 60; + + return strformat(F("%02d:%02d"), hours, mins); +} + +String secondsToDayHourMinuteSecond(int seconds) { + const int sec = seconds % 60; + const int minutes = seconds / 60; + const int days = minutes / 1440; + const int min_day = (minutes % 1440); + const int hours = min_day / 60; + const int mins = min_day % 60; + if (days == 0) { + return strformat(F("%02d:%02d:%02d"), hours, mins, sec); + } + return strformat(F("%dT%02d:%02d:%02d"), days, hours, mins, sec); +} + +String secondsToDayHourMinuteSecond_ms(int64_t systemMicros) +{ + if (systemMicros < 0ll) { + return concat('-', secondsToDayHourMinuteSecond_ms(-1ll*systemMicros)); + } + + uint32_t usec{}; + const uint32_t seconds = micros_to_sec_usec(systemMicros, usec); + return strformat( + F("%s.%03u"), + secondsToDayHourMinuteSecond(seconds).c_str(), + usec / 1000ul); +} + +String format_msec_duration(int64_t duration) { + if (duration < 0ll) { + return concat('-', format_msec_duration(-1ll*duration)); + } + const uint32_t duration_s = duration / 1000ll; + const int32_t duration_ms = duration % 1000ll; + + if (duration_s < 60) { + return strformat( + F("%02d.%03d"), + duration_s, + duration_ms); + } + return strformat( + F("%s.%03d"), + secondsToDayHourMinuteSecond(duration_s).c_str(), + duration_ms); +} + + +// Compute the dew point temperature, given temperature and humidity (temp in Celsius) +// Formula: http://www.ajdesigner.com/phphumidity/dewpoint_equation_dewpoint_temperature.php +// Td = (f/100)^(1/8) * (112 + 0.9*T) + 0.1*T - 112 +float compute_dew_point_temp(float temperature, float humidity_percentage) { + return powf(humidity_percentage / 100.0f, 0.125f) * + (112.0f + 0.9f*temperature) + 0.1f*temperature - 112.0f; +} + +// Compute the humidity given temperature and dew point temperature (temp in Celsius) +// Formula: http://www.ajdesigner.com/phphumidity/dewpoint_equation_relative_humidity.php +// f = 100 * ((112 - 0.1*T + Td) / (112 + 0.9 * T))^8 +float compute_humidity_from_dewpoint(float temperature, float dew_temperature) { + return 100.0f * powf((112.0f - 0.1f * temperature + dew_temperature) / + (112.0f + 0.9f * temperature), 8); +} + + + +/********************************************************************************************\ + Compensate air pressure for given altitude (in meters) + \*********************************************************************************************/ +float pressureElevation(float atmospheric, float altitude) { + // Equation taken from BMP180 datasheet (page 16): + // http://www.adafruit.com/datasheets/BST-BMP180-DS000-09.pdf + + // Note that using the equation from wikipedia can give bad results + // at high altitude. See this thread for more information: + // http://forums.adafruit.com/viewtopic.php?f=22&t=58064 + return atmospheric / powf(1.0f - (altitude / 44330.0f), 5.255f); +} + +float altitudeFromPressure(float atmospheric, float seaLevel) +{ + // Equation taken from BMP180 datasheet (page 16): + // http://www.adafruit.com/datasheets/BST-BMP180-DS000-09.pdf + + // Note that using the equation from wikipedia can give bad results + // at high altitude. See this thread for more information: + // http://forums.adafruit.com/viewtopic.php?f=22&t=58064 + return 44330.0f * (1.0f - powf(atmospheric / seaLevel, 0.1903f)); +} + + + + +/********************************************************************************************\ + In memory convert float to long + \*********************************************************************************************/ +unsigned long float2ul(float f) +{ + unsigned long ul; + + memcpy(&ul, &f, 4); + return ul; +} + +/********************************************************************************************\ + In memory convert long to float + \*********************************************************************************************/ +float ul2float(unsigned long ul) +{ + float f; + + memcpy(&f, &ul, 4); + return f; +} + + diff --git a/src/src/Helpers/Convert.h b/src/src/Helpers/Convert.h index becdc43e32..276c36ad5c 100644 --- a/src/src/Helpers/Convert.h +++ b/src/src/Helpers/Convert.h @@ -1,67 +1,68 @@ -#ifndef HELPERS_CONVERT_H -#define HELPERS_CONVERT_H - -#include "../../ESPEasy_common.h" - -/*********************************************************************************************\ - Convert bearing in degree to bearing string -\*********************************************************************************************/ -const __FlashStringHelper * getBearing(int degrees); - -float CelsiusToFahrenheit(float celsius); - -int m_secToBeaufort(float m_per_sec); - -String centimeterToImperialLength(float cm); - -String millimeterToImperialLength(float mm); - -float minutesToDay(int minutes); - -String minutesToDayHour(int minutes); - -String minutesToDayHourMinute(int minutes); - -String minutesToHourColonMinute(int minutes); - -String secondsToDayHourMinuteSecond(int seconds); - -String format_msec_duration(int64_t duration); - -// Compute the dew point temperature, given temperature and humidity (temp in Celsius) -// Formula: http://www.ajdesigner.com/phphumidity/dewpoint_equation_dewpoint_temperature.php -// Td = (f/100)^(1/8) * (112 + 0.9*T) + 0.1*T - 112 -float compute_dew_point_temp(float temperature, float humidity_percentage); - -// Compute the humidity given temperature and dew point temperature (temp in Celsius) -// Formula: http://www.ajdesigner.com/phphumidity/dewpoint_equation_relative_humidity.php -// f = 100 * ((112 - 0.1*T + Td) / (112 + 0.9 * T))^8 -float compute_humidity_from_dewpoint(float temperature, float dew_temperature); - -/********************************************************************************************\ - Compensate air pressure for measured atmospheric - pressure (in hPa) and given altitude (in meters) - \*********************************************************************************************/ -float pressureElevation(float atmospheric, float altitude); - -/********************************************************************************************\ - Calculates the altitude (in meters) from the specified atmospheric - pressure (in hPa), and sea-level pressure (in hPa). - @param seaLevel Sea-level pressure in hPa - @param atmospheric Atmospheric pressure in hPa - \*********************************************************************************************/ -float altitudeFromPressure(float atmospheric, float seaLevel); - -/********************************************************************************************\ - In memory convert float to long - \*********************************************************************************************/ -unsigned long float2ul(float f); - -/********************************************************************************************\ - In memory convert long to float - \*******************************************************************************************/ -float ul2float(unsigned long ul); - - - +#ifndef HELPERS_CONVERT_H +#define HELPERS_CONVERT_H + +#include "../../ESPEasy_common.h" + +/*********************************************************************************************\ + Convert bearing in degree to bearing string +\*********************************************************************************************/ +const __FlashStringHelper * getBearing(int degrees); + +float CelsiusToFahrenheit(float celsius); + +int m_secToBeaufort(float m_per_sec); + +String centimeterToImperialLength(float cm); + +String millimeterToImperialLength(float mm); + +float minutesToDay(int minutes); + +String minutesToDayHour(int minutes); + +String minutesToDayHourMinute(int minutes); + +String minutesToHourColonMinute(int minutes); + +String secondsToDayHourMinuteSecond(int seconds); +String secondsToDayHourMinuteSecond_ms(int64_t systemMicros); + +String format_msec_duration(int64_t duration); + +// Compute the dew point temperature, given temperature and humidity (temp in Celsius) +// Formula: http://www.ajdesigner.com/phphumidity/dewpoint_equation_dewpoint_temperature.php +// Td = (f/100)^(1/8) * (112 + 0.9*T) + 0.1*T - 112 +float compute_dew_point_temp(float temperature, float humidity_percentage); + +// Compute the humidity given temperature and dew point temperature (temp in Celsius) +// Formula: http://www.ajdesigner.com/phphumidity/dewpoint_equation_relative_humidity.php +// f = 100 * ((112 - 0.1*T + Td) / (112 + 0.9 * T))^8 +float compute_humidity_from_dewpoint(float temperature, float dew_temperature); + +/********************************************************************************************\ + Compensate air pressure for measured atmospheric + pressure (in hPa) and given altitude (in meters) + \*********************************************************************************************/ +float pressureElevation(float atmospheric, float altitude); + +/********************************************************************************************\ + Calculates the altitude (in meters) from the specified atmospheric + pressure (in hPa), and sea-level pressure (in hPa). + @param seaLevel Sea-level pressure in hPa + @param atmospheric Atmospheric pressure in hPa + \*********************************************************************************************/ +float altitudeFromPressure(float atmospheric, float seaLevel); + +/********************************************************************************************\ + In memory convert float to long + \*********************************************************************************************/ +unsigned long float2ul(float f); + +/********************************************************************************************\ + In memory convert long to float + \*******************************************************************************************/ +float ul2float(unsigned long ul); + + + #endif // HELPERS_CONVERT_H \ No newline at end of file diff --git a/src/src/Helpers/Dallas1WireHelper.cpp b/src/src/Helpers/Dallas1WireHelper.cpp index 9fd762f5bd..fb10602265 100644 --- a/src/src/Helpers/Dallas1WireHelper.cpp +++ b/src/src/Helpers/Dallas1WireHelper.cpp @@ -114,7 +114,9 @@ void Dallas_addr_selector_webform_load(taskIndex_t TaskIndex, int8_t gpio_pin_rx if (Dallas_plugin(Settings.getPluginID_for_task(task))) { uint8_t tmpAddress[8] = { 0 }; - for (uint8_t var_index = 0; var_index < VARS_PER_TASK; ++var_index) { + const uint8_t valueCount = getValueCountForTask(task); + + for (uint8_t var_index = 0; var_index < valueCount; ++var_index) { Dallas_plugin_get_addr(tmpAddress, task, var_index); uint64_t tmpAddr_64 = Dallas_addr_to_uint64(tmpAddress); @@ -126,7 +128,7 @@ void Dallas_addr_selector_webform_load(taskIndex_t TaskIndex, int8_t gpio_pin_rx F(" (task %d [%s#%s])") , task + 1 , getTaskDeviceName(task).c_str() - , getTaskValueName(task, var_index).c_str()) + , Cache.getTaskDeviceValueName(task, var_index).c_str()) )); } } diff --git a/src/src/Helpers/ESPEasy_Storage.cpp b/src/src/Helpers/ESPEasy_Storage.cpp index 15145557a9..a3e2705e5e 100644 --- a/src/src/Helpers/ESPEasy_Storage.cpp +++ b/src/src/Helpers/ESPEasy_Storage.cpp @@ -169,7 +169,7 @@ bool fileExists(const String& fname) { // use some random value as we don't have a time yet Cache.fileCacheClearMoment = HwRandom(); } else { - Cache.fileCacheClearMoment = node_time.now(); + Cache.fileCacheClearMoment = node_time.getLocalUnixTime(); } } return res; @@ -769,6 +769,7 @@ void afterloadSettings() { if (!Settings.UseRules) { eventQueue.clear(); } + node_time.applyTimeZone(); CheckRunningServices(); // To update changes in hostname. } @@ -1612,7 +1613,7 @@ String doSaveToFile(const char *fname, int index, const uint8_t *memAddress, int // return log; // FIXME TD-er: Should this be considered a breaking error? } # endif // ifndef ESP32 -#endif // ifndef BUILD_NO_DEBUG +#endif // ifndef BUILD_NO_DEBUG if (index < 0) { #ifndef BUILD_NO_DEBUG @@ -2340,9 +2341,10 @@ bool validateUploadConfigDat(const uint8_t *buf) { memcpy(reinterpret_cast(&Temp) + x, &buf[x], 1); } #ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_INFO, strformat(F("Validate config.dat, Version: %d = %d, PID: %d = %d"), + addLog(LOG_LEVEL_INFO, strformat(F("Validate config.dat, Version: %d = %d, PID: %d = %d"), Temp.Version, VERSION, Temp.PID, ESP_PROJECT_PID)); - #endif + #endif // ifndef BUILD_NO_DEBUG + if ((Temp.Version == VERSION) && (Temp.PID == ESP_PROJECT_PID)) { result = true; } diff --git a/src/src/Helpers/ESPEasy_checks.cpp b/src/src/Helpers/ESPEasy_checks.cpp index e0c5a8b4f2..31b1556faf 100644 --- a/src/src/Helpers/ESPEasy_checks.cpp +++ b/src/src/Helpers/ESPEasy_checks.cpp @@ -34,7 +34,8 @@ #include #ifdef USES_C013 -#include "../DataStructs/C013_p2p_dataStructs.h" +#include "../DataStructs/C013_p2p_SensorDataStruct.h" +#include "../DataStructs/C013_p2p_SensorInfoStruct.h" #endif #ifdef USES_C016 @@ -92,9 +93,9 @@ void run_compiletime_checks() { check_size(); #if ESP_IDF_VERSION_MAJOR > 3 // String class has increased with 4 bytes - check_size(); // Is not stored + check_size(); // Is not stored #else - check_size(); // Is not stored + check_size(); // Is not stored #endif @@ -124,8 +125,8 @@ void run_compiletime_checks() { check_size(); check_size(); #ifdef USES_C013 - check_size(); - check_size(); + check_size(); + check_size(); #endif #ifdef USES_C016 check_size(); diff --git a/src/src/Helpers/ESPEasy_time.cpp b/src/src/Helpers/ESPEasy_time.cpp index f490a05c1a..53398006f7 100644 --- a/src/src/Helpers/ESPEasy_time.cpp +++ b/src/src/Helpers/ESPEasy_time.cpp @@ -2,9 +2,13 @@ #include "../../ESPEasy_common.h" +#include "../../_Plugin_Helper.h" + #include "../CustomBuild/CompiletimeDefines.h" +#include "../DataStructs/NTP_packet.h" #include "../DataStructs/TimingStats.h" + #include "../DataTypes/TimeSource.h" #include "../ESPEasyCore/ESPEasy_Log.h" @@ -18,8 +22,8 @@ #include "../Globals/TimeZone.h" #ifdef USES_ESPEASY_NOW -#include "../Globals/ESPEasy_now_handler.h" -#endif +# include "../Globals/ESPEasy_now_handler.h" +#endif // ifdef USES_ESPEASY_NOW #include "../Helpers/Convert.h" @@ -70,13 +74,14 @@ void ESPEasy_time::restoreFromRTC() static bool firstCall = true; #if FEATURE_EXT_RTC - uint32_t unixtime = 0; + uint32_t unixtime = 0; + if (ExtRTC_get(unixtime)) { setExternalTimeSource(unixtime, timeSource_t::External_RTC_time_source); firstCall = false; return; } -#endif +#endif // if FEATURE_EXT_RTC if (firstCall && (RTC.lastSysTime != 0) && (RTC.deepSleepState != 1)) { firstCall = false; @@ -126,72 +131,116 @@ void ESPEasy_time::setExternalTimeSource(double time, timeSource_t new_timeSourc addLogMove(LOG_LEVEL_INFO, log); } #endif // ifndef BUILD_NO_DEBUG - extTimeSource = new_timeSource; - externalUnixTime_d = time; - lastSyncTime_ms = millis(); - timeSource_p2p_unit = unitnr; + extTimeSource = new_timeSource; + externalUnixTime_d = time; + externalUnixTime_received_micros = getMicros64(); + lastSyncTime_ms = millis(); + timeSource_p2p_unit = unitnr; initTime(); } } +uint32_t ESPEasy_time::getUptime_in_sec() const { + return getMicros64() / 1000000ull; +} + uint32_t ESPEasy_time::getUnixTime() const { - return static_cast(sysTime); + const uint64_t unixtime_usec = getMicros64() + unixTime_usec_uptime_offset; + + return static_cast(unixtime_usec / 1000000ull); } uint32_t ESPEasy_time::getUnixTime(uint32_t& unix_time_frac) const { - const uint32_t seconds(getUnixTime()); - double tmp(sysTime); + return systemMicros_to_Unixtime(getMicros64(), unix_time_frac); +} + +int64_t ESPEasy_time::Unixtime_to_systemMicros(const uint32_t& unix_time_sec, uint32_t unix_time_frac) const +{ + const int64_t res = + (static_cast(unix_time_sec) * 1000000ll) + + unix_time_frac_to_micros(unix_time_frac); - tmp -= seconds; - tmp *= 4294967295.0; - unix_time_frac = tmp; - return seconds; + if (unixTime_usec_uptime_offset == 0) { + // Time has not been set + return res; + } + return res - unixTime_usec_uptime_offset; +} + +uint32_t ESPEasy_time::systemMicros_to_Unixtime(const int64_t& systemMicros, uint32_t& unix_time_frac) const +{ + return micros_to_sec_time_frac(systemMicros + unixTime_usec_uptime_offset, unix_time_frac); +} + +uint32_t ESPEasy_time::systemMicros_to_Localtime(const int64_t& systemMicros, uint32_t& unix_time_frac) const +{ + return time_zone.toLocal(systemMicros_to_Unixtime(systemMicros, unix_time_frac)); } void ESPEasy_time::initTime() { nextSyncTime = 0; - now(); + now_(); } -unsigned long ESPEasy_time::now() { - // calculate number of seconds passed since last call to now() - bool timeSynced = false; - const long msec_passed = timePassedSince(prevMillis); +unsigned long ESPEasy_time::getLocalUnixTime() const +{ + return time_zone.toLocal(getUnixTime()); +} - sysTime += static_cast(msec_passed) / 1000.0; - prevMillis += msec_passed; +unsigned long ESPEasy_time::getLocalUnixTime(uint32_t& unix_time_frac) const +{ + return time_zone.toLocal(getUnixTime(unix_time_frac)); +} - if (nextSyncTime <= sysTime) { - // nextSyncTime & sysTime are in seconds +unsigned long ESPEasy_time::now_() { + // calculate number of seconds passed since last call to now() + bool timeSynced = false; + + if (nextSyncTime <= getUptime_in_sec()) { + // nextSyncTime is in seconds double unixTime_d = -1.0; bool updatedTime = false; + uint64_t micros_received = getMicros64(); + if (externalUnixTime_d > 0.0) { unixTime_d = externalUnixTime_d; + if (externalUnixTime_received_micros != 0) { + micros_received = externalUnixTime_received_micros; + } + // Correct for the delay between the last received external time and applying it - unixTime_d += (timePassedSince(lastSyncTime_ms) / 1000.0); - externalUnixTime_d = -1.0; - syncInterval = EXT_TIME_SOURCE_MIN_UPDATE_INTERVAL_SEC; - updatedTime = true; - timeSource = extTimeSource; + unixTime_d += (timePassedSince(lastSyncTime_ms) / 1000.0); + externalUnixTime_d = -1.0; + externalUnixTime_received_micros = 0; + syncInterval = EXT_TIME_SOURCE_MIN_UPDATE_INTERVAL_SEC; + updatedTime = true; + timeSource = extTimeSource; } if (!isExternalTimeSource(timeSource) || (extTimeSource <= timeSource) || (timePassedSince(lastSyncTime_ms) > static_cast(1000 * syncInterval))) { if (getNtpTime(unixTime_d)) { - updatedTime = true; + updatedTime = true; + micros_received = getMicros64(); + + if (externalUnixTime_received_micros != 0) { + micros_received = externalUnixTime_received_micros; + } } else { #if FEATURE_ESPEASY_P2P double tmp_unixtime_d; if (!updatedTime && Nodes.getUnixTime(tmp_unixtime_d, timeSource_p2p_unit)) { - unixTime_d = tmp_unixtime_d; + unixTime_d = tmp_unixtime_d; + micros_received = getMicros64(); // When calling Nodes.getUnixTime the unixtime has been patched with the time passed since packet + // was received timeSource = timeSource_t::ESPEASY_p2p_UDP; updatedTime = true; syncInterval = EXT_TIME_SOURCE_MIN_UPDATE_INTERVAL_SEC; @@ -200,47 +249,88 @@ unsigned long ESPEasy_time::now() { #if FEATURE_EXT_RTC uint32_t tmp_unixtime = 0; + if (!updatedTime && (timeSource > timeSource_t::External_RTC_time_source) && // No need to set from ext RTC more than once. ExtRTC_get(tmp_unixtime)) { - unixTime_d = tmp_unixtime; - timeSource = timeSource_t::External_RTC_time_source; - updatedTime = true; - syncInterval = 120; // Allow sync in 2 minutes to see if we get some better options from p2p nodes. + unixTime_d = tmp_unixtime; + micros_received = getMicros64(); + timeSource = timeSource_t::External_RTC_time_source; + updatedTime = true; + syncInterval = 120; // Allow sync in 2 minutes to see if we get some better options from p2p nodes. } - #endif + #endif // if FEATURE_EXT_RTC } } // Clear the external time source so it has to be set again with updated values. - extTimeSource = timeSource_t::No_time_source; + extTimeSource = timeSource_t::No_time_source; + externalUnixTime_received_micros = 0; + externalUnixTime_d = -1.0; if (timeSource != timeSource_t::ESPEASY_p2p_UDP) { timeSource_p2p_unit = 0; } if (updatedTime) { START_TIMER; - const double time_offset = unixTime_d - sysTime - (timePassedSince(prevMillis) / 1000.0); - if (statusNTPInitialized && (time_offset < 1.0)) { - // Clock instability in ppm - timeWander = ((time_offset * 1000000.0) / timePassedSince(lastTimeWanderCalculation_ms)); - timeWander *= 1000.0f; + double time_offset_sec = 0.0; + + // Update offset + const uint64_t tmp_unixTime_usec_uptime_offset = static_cast(unixTime_d * 1000000.0); + + if (micros_received < tmp_unixTime_usec_uptime_offset) { + const uint64_t new_unixTime_usec_uptime_offset = tmp_unixTime_usec_uptime_offset - micros_received; + time_offset_sec = unixTime_usec_uptime_offset; + time_offset_sec -= new_unixTime_usec_uptime_offset; + time_offset_sec /= 1000000.0; + unixTime_usec_uptime_offset = new_unixTime_usec_uptime_offset; + + if ((lastTimeWanderCalculation_ms != 0) && + statusNTPInitialized && + (std::abs(time_offset_sec) < 10.0)) { + // Clock instability in ppm + timeWander = ((time_offset_sec * 1000000.0) / timePassedSince(lastTimeWanderCalculation_ms)); + timeWander *= 1000.0f; + } + + lastTimeWanderCalculation_ms = static_cast(micros_received / 1000); + } else { + // Should not happen, so what to do now???? + // unixTime_usec_uptime_offset = 0; } - prevMillis = millis(); // restart counting from now (thanks to Korman for this fix) - lastTimeWanderCalculation_ms = prevMillis; timeSynced = true; - sysTime = unixTime_d; + #if FEATURE_PLUGIN_STATS + + // GMT Wed Jan 01 2020 00:00:00 GMT+0000 + const uint32_t unixTime_20200101 = 1577836800; + + if (!statusNTPInitialized && (time_offset_sec > unixTime_20200101)) { + if (static_cast(unixTime_d) > unixTime_20200101) { + // Update recorded plugin stats timestamps + for (taskIndex_t taskIndex = 0; taskIndex < TASKS_MAX; taskIndex++) + { + PluginTaskData_base *taskData = getPluginTaskDataBaseClassOnly(taskIndex); + + if (taskData != nullptr) { + taskData->processTimeSet(time_offset_sec); + } + } + } + } + + #endif // if FEATURE_PLUGIN_STATS #if FEATURE_EXT_RTC + // External RTC only stores with second resolution. // Thus to limit the error to +/- 500 ms, round the sysTime instead of just casting it. - ExtRTC_set(static_cast(sysTime + 0.5)); - #endif + ExtRTC_set(static_cast(unixTime_d + 0.5)); + #endif // if FEATURE_EXT_RTC { - const unsigned long abs_time_offset_ms = std::abs(time_offset) * 1000; + const unsigned long abs_time_offset_ms = std::abs(time_offset_sec) * 1000; if (timeSource == timeSource_t::NTP_time_source) { // May need to lessen the load on the NTP servers, randomize the sync interval @@ -271,18 +361,16 @@ unsigned long ESPEasy_time::now() { String log = F("Time set to "); #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE log += doubleToString(unixTime_d, 3); - #else + #else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE log += static_cast(unixTime_d); - #endif + #endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - if ((-86400 < time_offset) && (time_offset < 86400)) { + if (std::abs(time_offset_sec) < 86400) { // Only useful to show adjustment if it is less than a day. - log += F(" Time adjusted by "); - log += static_cast(time_offset * 1000.0); - log += F(" msec. Wander: "); - log += floatToString(timeWander, 1); - log += F(" ppm"); - log += F(" Source: "); + log += strformat( + F(" Time adjusted by %d msec. Wander: %.1f ppm Source: "), + static_cast(time_offset_sec * 1000.0), + timeWander); log += toString(timeSource); } addLogMove(LOG_LEVEL_INFO, log); @@ -290,7 +378,7 @@ unsigned long ESPEasy_time::now() { time_zone.applyTimeZone(unixTime_d); lastSyncTime_ms = millis(); - nextSyncTime = (uint32_t)unixTime_d + syncInterval; + nextSyncTime = getUptime_in_sec() + syncInterval; if (isExternalTimeSource(timeSource)) { #ifdef USES_ESPEASY_NOW @@ -300,16 +388,17 @@ unsigned long ESPEasy_time::now() { STOP_TIMER(SYSTIME_UPDATED); } } - RTC.lastSysTime = static_cast(sysTime); - uint32_t localSystime = time_zone.toLocal(sysTime); + RTC.lastSysTime = getUnixTime(); + uint32_t localSystime = time_zone.toLocal(RTC.lastSysTime); breakTime(localSystime, local_tm); calcSunRiseAndSet(timeSynced); + if (timeSynced) { if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLog(LOG_LEVEL_INFO, strformat( - F("Local time: %s"), - getDateTimeString('-', ':', ' ').c_str())); + F("Local time: %s"), + getDateTimeString('-', ':', ' ').c_str())); } { // Notify plugins the time has been set. @@ -331,7 +420,7 @@ unsigned long ESPEasy_time::now() { bool ESPEasy_time::reportNewMinute() { - now(); + now_(); int cur_min = local_tm.tm_min; @@ -389,7 +478,7 @@ bool ESPEasy_time::getNtpTime(double& unixTime_d) log += Settings.NTPHost; // When single set host fails, retry again in 20 seconds - nextSyncTime = sysTime + HwRandom(20, 60); + nextSyncTime = getUptime_in_sec() + HwRandom(20, 60); } else { // Have to do a lookup each time, since the NTP pool always returns another IP const String ntpServerName = strformat( @@ -398,7 +487,7 @@ bool ESPEasy_time::getNtpTime(double& unixTime_d) log += ntpServerName; // When pool host fails, retry can be much sooner - nextSyncTime = sysTime + HwRandom(5, 20); + nextSyncTime = getUptime_in_sec() + HwRandom(5, 20); useNTPpool = true; } @@ -419,8 +508,7 @@ bool ESPEasy_time::getNtpTime(double& unixTime_d) return false; } - const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message - uint8_t packetBuffer[NTP_PACKET_SIZE]{}; // buffer to hold incoming & outgoing packets + NTP_packet ntp_packet; log += F(" queried"); #ifndef BUILD_NO_DEBUG @@ -429,14 +517,6 @@ bool ESPEasy_time::getNtpTime(double& unixTime_d) while (udp.parsePacket() > 0) { // discard any previously received packets } - packetBuffer[0] = 0b11100011; // LI, Version, Mode - packetBuffer[1] = 0; // Stratum, or type of clock - packetBuffer[2] = 6; // Polling Interval - packetBuffer[3] = 0xEC; // Peer Clock Precision - packetBuffer[12] = 49; - packetBuffer[13] = 0x4E; - packetBuffer[14] = 49; - packetBuffer[15] = 52; FeedSW_watchdog(); @@ -446,115 +526,137 @@ bool ESPEasy_time::getNtpTime(double& unixTime_d) STOP_TIMER(NTP_FAIL); return false; } - udp.write(packetBuffer, NTP_PACKET_SIZE); + constexpr int NTP_packet_size = sizeof(NTP_packet); + const uint64_t txMicros = getMicros64() + unixTime_usec_uptime_offset; + ntp_packet.setTxTimestamp(txMicros); + udp.write(ntp_packet.data, NTP_packet_size); udp.endPacket(); - const uint32_t beginWait = millis(); +#ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, concat(F("NTP : before\n"), ntp_packet.toDebugString())); +#endif // ifndef BUILD_NO_DEBUG + while (!timeOutReached(beginWait + 1000)) { - int size = udp.parsePacket(); - int remotePort = udp.remotePort(); + const int size = udp.parsePacket(); + const int remotePort = udp.remotePort(); + + if (size >= NTP_packet_size) { + if (remotePort != 123) { +#ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG_MORE, concat(F("NTP : Reply from wrong port: "), remotePort)); +#endif // ifndef BUILD_NO_DEBUG + udp.stop(); + STOP_TIMER(NTP_FAIL); + return false; + } + udp.read(ntp_packet.data, NTP_packet_size); // read packet into the buffer + const uint64_t receivedMicros = getMicros64(); + + udp.stop(); - if ((size >= NTP_PACKET_SIZE) && (remotePort == 123)) { - udp.read(packetBuffer, NTP_PACKET_SIZE); // read packet into the buffer +#ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, concat(F("NTP : after\n"), ntp_packet.toDebugString())); +#endif // ifndef BUILD_NO_DEBUG - if ((packetBuffer[0] & 0b11000000) == 0b11000000) { + if (ntp_packet.isUnsynchronized()) { // Leap-Indicator: unknown (clock unsynchronized) // See: https://github.com/letscontrolit/ESPEasy/issues/2886#issuecomment-586656384 if (loglevelActiveFor(LOG_LEVEL_ERROR)) { addLog(LOG_LEVEL_ERROR, strformat( - F("NTP : NTP host (%s) unsynchronized"), - formatIP(timeServerIP).c_str())); + F("NTP : NTP host (%s) unsynchronized"), + formatIP(timeServerIP).c_str())); } if (!useNTPpool) { // Does not make sense to try it very often if a single host is used which is not synchronized. - nextSyncTime = sysTime + 120; + nextSyncTime = getUptime_in_sec() + 120; } - udp.stop(); STOP_TIMER(NTP_FAIL); return false; } // For more detailed info on improving accuracy, see: // https://github.com/lettier/ntpclient/issues/4#issuecomment-360703503 - // For now, we simply use half the reply time as delay compensation. - unsigned long secsSince1900; + uint64_t offset_usec{}; + uint64_t roundtripDelay_usec{}; - // convert four bytes starting at location 40 to a long integer - // TX time is used here. - secsSince1900 = (unsigned long)packetBuffer[40] << 24; - secsSince1900 |= (unsigned long)packetBuffer[41] << 16; - secsSince1900 |= (unsigned long)packetBuffer[42] << 8; - secsSince1900 |= (unsigned long)packetBuffer[43]; + if (!ntp_packet.compute_usec( + txMicros, + receivedMicros + unixTime_usec_uptime_offset, + offset_usec, roundtripDelay_usec)) + { +#ifndef BUILD_NO_DEBUG + addLogMove(LOG_LEVEL_ERROR, strformat( + F("NTP : NTP error: round-trip delay: %d ms offset: %s,\n t0: %s,\n t1: %s,\n t2: %s,\n t3: %s"), + static_cast(roundtripDelay_usec / 1000), + secondsToDayHourMinuteSecond_ms(offset_usec).c_str(), + doubleToString(ntp_packet.getReferenceTimestamp_usec() / 1000000.0, 3).c_str(), + doubleToString(ntp_packet.getOriginTimestamp_usec() / 1000000.0, 3).c_str(), + doubleToString(ntp_packet.getReceiveTimestamp_usec() / 1000000.0, 3).c_str(), + doubleToString(ntp_packet.getTransmitTimestamp_usec() / 1000000.0, 3).c_str() + )); +#else // ifndef BUILD_NO_DEBUG + addLogMove(LOG_LEVEL_ERROR, strformat( + F("NTP : NTP error: round-trip delay: %d ms offset: %s"), + static_cast(roundtripDelay_usec / 1000), + secondsToDayHourMinuteSecond_ms(offset_usec).c_str() + )); - if (secsSince1900 == 0) { - // No time stamp received +#endif // ifndef BUILD_NO_DEBUG - if (!useNTPpool) { - // Retry again in a minute. - nextSyncTime = sysTime + 60; - } - udp.stop(); + // Apparently this is not a valid packet + // as the received timestamp is before the origin timestamp + // or no valid timestamps from the NTP server. + nextSyncTime = getUptime_in_sec() + 60; STOP_TIMER(NTP_FAIL); return false; } - uint32_t txTm = secsSince1900 - 2208988800UL; - - unsigned long txTm_f; - txTm_f = (unsigned long)packetBuffer[44] << 24; - txTm_f |= (unsigned long)packetBuffer[45] << 16; - txTm_f |= (unsigned long)packetBuffer[46] << 8; - txTm_f |= (unsigned long)packetBuffer[47]; - // Convert seconds to double - unixTime_d = static_cast(txTm); - - // Add fractional part. - unixTime_d += (static_cast(txTm_f) / 4294967295.0); - - long total_delay = timePassedSince(beginWait); - lastSyncTime_ms = millis(); + unixTime_d = + static_cast(ntp_packet.getTransmitTimestamp_usec()) / + 1000000.0; + externalUnixTime_received_micros = + receivedMicros - (roundtripDelay_usec / 2); - // compensate for the delay by adding half the total delay - // N.B. unixTime_d is in seconds and delay in msec. - double delay_compensation = static_cast(total_delay) / 2000.0; - unixTime_d += delay_compensation; + timeSource = timeSource_t::NTP_time_source; + lastSyncTime_ms = millis(); + lastNTPSyncTime_ms = lastSyncTime_ms; if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("NTP : NTP replied: delay "); - log += total_delay; - log += F(" mSec"); -#ifndef LIMIT_BUILD_SIZE - log += F(" Accuracy increased by "); - double fractpart, intpart; - fractpart = modf(unixTime_d, &intpart); - - if (fractpart < delay_compensation) { - // We gained more than 1 second in accuracy - fractpart += 1.0; - } - log += static_cast(fractpart * 1000.0); - log += F(" msec"); -#endif - addLogMove(LOG_LEVEL_INFO, log); +#ifndef BUILD_NO_DEBUG + addLogMove(LOG_LEVEL_INFO, strformat( + F("NTP : NTP replied: delay %d ms round-trip delay: %u ms offset: %s,\n t0: %s,\n t1: %s,\n t2: %s,\n t3: %s"), + timePassedSince(beginWait), + static_cast(roundtripDelay_usec / 1000), + secondsToDayHourMinuteSecond_ms(offset_usec).c_str(), + doubleToString(ntp_packet.getReferenceTimestamp_usec() / 1000000.0, 3).c_str(), + doubleToString(ntp_packet.getOriginTimestamp_usec() / 1000000.0, 3).c_str(), + doubleToString(ntp_packet.getReceiveTimestamp_usec() / 1000000.0, 3).c_str(), + doubleToString(ntp_packet.getTransmitTimestamp_usec() / 1000000.0, 3).c_str() + )); +#else // ifndef BUILD_NO_DEBUG + addLogMove(LOG_LEVEL_INFO, strformat( + F("NTP : NTP replied: delay %d ms round-trip delay: %u ms offset: %s"), + timePassedSince(beginWait), + static_cast(roundtripDelay_usec / 1000), + secondsToDayHourMinuteSecond_ms(offset_usec).c_str() + )); +#endif // ifndef BUILD_NO_DEBUG } - udp.stop(); - timeSource = timeSource_t::NTP_time_source; - lastNTPSyncTime_ms = millis(); CheckRunningServices(); // FIXME TD-er: Sometimes services can only be started after NTP is successful STOP_TIMER(NTP_SUCCESS); return true; } - delay(10); + delay(1); } // Timeout. if (!useNTPpool) { // Retry again in a minute. - nextSyncTime = sysTime + 60; + nextSyncTime = getUptime_in_sec() + 60; } #ifndef BUILD_NO_DEBUG @@ -569,8 +671,8 @@ bool ESPEasy_time::getNtpTime(double& unixTime_d) * get the timezone-offset string in +/-0000 format **************************************************/ String ESPEasy_time::getTimeZoneOffsetString() { - int dif = static_cast((static_cast(now()) - static_cast(getUnixTime())) / 60); // Minutes - char valueString[6] = { 0 }; + int dif = static_cast((static_cast(getLocalUnixTime()) - static_cast(getUnixTime())) / 60); // Minutes + char valueString[6] = { 0 }; String tzoffset; // Formatting the timezone-offset string as [+|-]HHMM @@ -585,6 +687,12 @@ String ESPEasy_time::getTimeZoneOffsetString() { tzoffset += String(valueString); return tzoffset; } + +void ESPEasy_time::applyTimeZone() +{ + time_zone.applyTimeZone(getUnixTime()); +} + /********************************************************************************************\ Date/Time string formatters \*********************************************************************************************/ @@ -749,7 +857,7 @@ int ESPEasy_time::dayOfYear(int year, int month, int day) { } void ESPEasy_time::calcSunRiseAndSet(bool timeSynced) { - if (!timeSynced && + if (!timeSynced && (tsSet.tm_mday == local_tm.tm_mday)) { // No need to recalculate if already calculated for this day return; @@ -866,14 +974,15 @@ bool ESPEasy_time::ExtRTC_get(uint32_t& unixtime) if (unixtime != 0) { addLogMove(LOG_LEVEL_INFO, concat( - F("ExtRTC: Read external time source: "), - unixtime)); + F("ExtRTC: Read external time source: "), + unixtime)); return true; } addLog(LOG_LEVEL_ERROR, F("ExtRTC: Cannot get time from external time source")); return false; } -#endif + +#endif // if FEATURE_EXT_RTC #if FEATURE_EXT_RTC bool ESPEasy_time::ExtRTC_set(uint32_t unixtime) @@ -943,4 +1052,5 @@ bool ESPEasy_time::ExtRTC_set(uint32_t unixtime) addLog(LOG_LEVEL_ERROR, F("ExtRTC: Cannot set time to external time source")); return false; } -#endif \ No newline at end of file + +#endif // if FEATURE_EXT_RTC diff --git a/src/src/Helpers/ESPEasy_time.h b/src/src/Helpers/ESPEasy_time.h index d9fd7d7a16..694b906ec4 100644 --- a/src/src/Helpers/ESPEasy_time.h +++ b/src/src/Helpers/ESPEasy_time.h @@ -37,17 +37,41 @@ class ESPEasy_time { timeSource_t source, uint8_t unitnr = 0); + uint32_t getUptime_in_sec() const; + // Get unix time in seconds uint32_t getUnixTime() const; // Get unix time in seconds // @param unix_time_frac The fractional part - uint32_t getUnixTime(uint32_t& unix_time_frac) const; + uint32_t getUnixTime(uint32_t& unix_time_frac) const; + + // Convert the UnixTime to systemMicros + // Returns converted system micros when system time has been set. + // Returns UnixTime in usec when system time has not yet been set, so it can be easily identified. + // Return value is negative if Unix timestamp was before system boot. + int64_t Unixtime_to_systemMicros(const uint32_t& unix_time_sec, + uint32_t unix_time_frac = 0) const; + + // Convert the system micros() to Unix Time. + // Return value is in seconds. + // Returns UnixTime when time has been set, otherwise the seconds part of system micros + uint32_t systemMicros_to_Unixtime(const int64_t& systemMicros, + uint32_t & unix_time_frac) const; + + // Convert the system micros() to Unix Time. + // Return value is in seconds. + // Returns LocalTime when time has been set, otherwise the seconds part of system micros + uint32_t systemMicros_to_Localtime(const int64_t& systemMicros, + uint32_t & unix_time_frac) const; void initTime(); + unsigned long getLocalUnixTime() const; + unsigned long getLocalUnixTime(uint32_t& unix_time_frac) const; + // Update and get the current systime - unsigned long now(); + unsigned long now_(); // Update time and return whether the minute has changed since last check. bool reportNewMinute(); @@ -58,6 +82,8 @@ class ESPEasy_time { String getTimeZoneOffsetString(); + void applyTimeZone(); + /********************************************************************************************\ Date/Time string formatters \*********************************************************************************************/ @@ -181,6 +207,7 @@ class ESPEasy_time { struct tm getSunSet(int secOffset) const; #if FEATURE_EXT_RTC + public: bool ExtRTC_get(uint32_t& unixtime); @@ -188,18 +215,19 @@ class ESPEasy_time { private: bool ExtRTC_set(uint32_t unixtime); -#endif +#endif // if FEATURE_EXT_RTC public: - struct tm local_tm; // local time - uint32_t syncInterval = 3600; // time sync will be attempted after this many seconds - double sysTime = 0.0; // Use high resolution double to get better sync between nodes when using NTP - uint32_t prevMillis = 0; - uint32_t nextSyncTime = 0; // Next time to allow time sync against UNIX time (thus seconds) - uint32_t lastSyncTime_ms = 0; - uint32_t lastNTPSyncTime_ms = 0; - double externalUnixTime_d = -1.0; // Used to set time from a source other than NTP. + struct tm local_tm; // local time + uint32_t syncInterval = 3600; // time sync will be attempted after this many seconds + + uint64_t unixTime_usec_uptime_offset = 0.0; // Use usec resolution to get better sync between nodes when using NTP + uint32_t nextSyncTime = 0; // Next time to allow time sync against UNIX time (thus seconds) + uint32_t lastSyncTime_ms = 0; + uint32_t lastNTPSyncTime_ms = 0; + double externalUnixTime_d = -1.0; // Used to set time from a source other than NTP. + uint64_t externalUnixTime_received_micros{}; struct tm tsRise, tsSet; struct tm sunRise; struct tm sunSet; diff --git a/src/src/Helpers/ESPEasy_time_calc.cpp b/src/src/Helpers/ESPEasy_time_calc.cpp index 843d25f7a2..253b2e4ffb 100644 --- a/src/src/Helpers/ESPEasy_time_calc.cpp +++ b/src/src/Helpers/ESPEasy_time_calc.cpp @@ -13,15 +13,62 @@ #define SECS_PER_DAY (SECS_PER_HOUR * 24UL) +uint32_t unix_time_frac_to_millis(uint32_t unix_time_frac) +{ + return static_cast(unix_time_frac) / 4294967.0f; +} + +uint32_t unix_time_frac_to_micros(uint32_t unix_time_frac) +{ + return static_cast(unix_time_frac) / 4294.967f; +} + +uint32_t millis_to_unix_time_frac(uint32_t millis) +{ + return static_cast(millis) * 4294967.0f; +} + +uint32_t micros_to_unix_time_frac(uint32_t micros) +{ + return static_cast(micros) * 4294.967f; +} + +uint32_t micros_to_sec_time_frac(int64_t micros, uint32_t& unix_time_frac) +{ + const uint64_t seconds = static_cast(micros / 1000000ull); + + // Compute modulo usec + unix_time_frac = micros_to_unix_time_frac(micros - (1000000ull * seconds)); + return static_cast(seconds); +} + +uint64_t sec_time_frac_to_Micros(uint32_t seconds, uint32_t time_frac) +{ + return + (static_cast(seconds) * 1000000ull) + + unix_time_frac_to_micros(time_frac); +} + +uint32_t micros_to_sec_usec(int64_t micros, uint32_t& usec) +{ + const uint64_t seconds = static_cast(micros / 1000000ull); + + // Compute modulo usec + usec = static_cast(micros - (1000000ull * seconds)); + return static_cast(seconds); +} + bool isLeapYear(int year) { - return ((year > 0) && !(year % 4) && ((year % 100) || !(year % 400))); + return (year > 0) && !(year % 4) && ((year % 100) || !(year % 400)); } uint8_t getMonthDays(int year, uint8_t month) { const uint8_t monthDays[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; - if (month == 1 && isLeapYear(year)) { + + if ((month == 1) && isLeapYear(year)) { return 29; } + if (month > 11) { return 0; } @@ -35,7 +82,6 @@ uint8_t getMonthDays(const struct tm& tm) { /********************************************************************************************\ Unix Time computations \*********************************************************************************************/ - uint32_t makeTime(const struct tm& tm) { // assemble time elements into uint32_t // note year argument is offset from 1970 (see macros in time.h to convert to other formats) @@ -45,15 +91,17 @@ uint32_t makeTime(const struct tm& tm) { // seconds from 1970 till 1 jan 00:00:00 of the given year // tm_year starts at 1900 uint32_t seconds = 1577836800; // 01/01/2020 @ 12:00am (UTC) - int year = 2020; + int year = 2020; + if (tm_year < year) { // Just in case this function is called on old dates - year = 1970; + year = 1970; seconds = 0; } for (; year < tm_year; ++year) { seconds += SECS_PER_DAY * 365; + if (isLeapYear(year)) { seconds += SECS_PER_DAY; // add extra days for leap years } @@ -72,6 +120,7 @@ uint32_t makeTime(const struct tm& tm) { void breakTime(unsigned long timeInput, struct tm& tm) { uint32_t time = (uint32_t)timeInput; + tm.tm_sec = time % 60; time /= 60; // now it is minutes tm.tm_min = time % 60; @@ -80,34 +129,37 @@ void breakTime(unsigned long timeInput, struct tm& tm) { time /= 24; // now it is days tm.tm_wday = ((time + 4) % 7) + 1; // Sunday is day 1 - int year = 1970; + int year = 1970; unsigned long days = 0; + while ((unsigned)(days += (isLeapYear(year) ? 366 : 365)) <= time) { year++; } tm.tm_year = year - 1900; // tm_year starts at 1900 days -= isLeapYear(year) ? 366 : 365; - time -= days; // now it is days in this year, starting at 0 + time -= days; // now it is days in this year, starting at 0 uint8_t month = 0; + for (month = 0; month < 12; month++) { const uint8_t monthLength = getMonthDays(year, month); + if (time >= monthLength) { time -= monthLength; } else { break; } } - tm.tm_mon = month; // Jan is month 0 - tm.tm_mday = time + 1; // day of month start at 1 + tm.tm_mon = month; // Jan is month 0 + tm.tm_mday = time + 1; // day of month start at 1 } - String formatDateString(const struct tm& ts, char delimiter) { // time format example with ':' delimiter: 23:59:59 (HH:MM:SS) char DateString[20]; // 19 digits plus the null char const int year = 1900 + ts.tm_year; + if (delimiter == '\0') { sprintf_P(DateString, PSTR("%4d%02d%02d"), year, ts.tm_mon + 1, ts.tm_mday); } else { @@ -116,6 +168,10 @@ String formatDateString(const struct tm& ts, char delimiter) { return DateString; } +String formatTimeString(const struct tm& ts) +{ + return formatTimeString(ts, ':', false, true); +} // returns the current Time separated by the given delimiter // time format example with ':' delimiter: 23:59:59 (HH:MM:SS) @@ -129,6 +185,7 @@ String formatTimeString(const struct tm& ts, char delimiter, bool am_pm, bool sh if (hour == 0) { hour = 12; } const char a_or_p = ts.tm_hour < 12 ? 'A' : 'P'; + if (hour < 10) { hour_prefix_s[0] = hour_prefix; } if (show_seconds) { @@ -159,6 +216,7 @@ String formatTimeString(const struct tm& ts, char delimiter, bool am_pm, bool sh } } else { if (ts.tm_hour < 10) { hour_prefix_s[0] = hour_prefix; } + if (delimiter == '\0') { sprintf_P(TimeString, PSTR("%s%d%02d"), hour_prefix_s, ts.tm_hour, ts.tm_min); @@ -171,7 +229,6 @@ String formatTimeString(const struct tm& ts, char delimiter, bool am_pm, bool sh return TimeString; } - String formatDateTimeString(const struct tm& ts, char dateDelimiter, char timeDelimiter, char dateTimeDelimiter, bool am_pm) { // if called like this: getDateTimeString('\0', '\0', '\0'); @@ -188,7 +245,6 @@ String formatDateTimeString(const struct tm& ts, char dateDelimiter, char timeDe /********************************************************************************************\ Time computations for rules. \*********************************************************************************************/ - String timeLong2String(unsigned long lngTime) { unsigned long x = 0; @@ -210,7 +266,7 @@ String timeLong2String(unsigned long lngTime) time += SystemVariables::toString(bitRead(lngTime, 29) ? SystemVariables::Enum::SUNRISE : SystemVariables::Enum::SUNSET); if ((lngTime & 0xffff) > 0) { - time += bitRead(lngTime, 30) ? '-' : '+'; // Sign + time += bitRead(lngTime, 30) ? '-' : '+'; // Sign time += lngTime & 0xffff; const String hms = F("smh"); const uint8_t idx = (lngTime >> 26) & 0x3; // 0/1/2 = s/m/h @@ -274,12 +330,12 @@ String timeLong2String(unsigned long lngTime) return time; } - unsigned long string2TimeLong(const String& str) { // format 0NRSHM000000WWWWAAAABBBBCCCCDDDD // WWWW=weekday, AAAA=hours tens digit, BBBB=hours, CCCC=minutes tens digit DDDD=minutes - // N = Negative offset, R = sunRise, S = sunSet, H = offset hours, M = offset minutes -> AAAA..DDDD = offset (default: seconds, binary stored, not bcd) + // N = Negative offset, R = sunRise, S = sunSet, H = offset hours, M = offset minutes -> AAAA..DDDD = offset (default: seconds, binary + // stored, not bcd) char command[20]; int w, x, y; @@ -374,8 +430,6 @@ unsigned long string2TimeLong(const String& str) return lngTime; } - - /********************************************************************************************\ Match clock event \*********************************************************************************************/ @@ -393,23 +447,23 @@ bool matchClockEvent(unsigned long clockEvent, unsigned long clockSet) } } - if (((clockSet >> (16)) & 0xf) == 0x8) { // if weekday nibble has the wildcard value 0x8 (workdays) - if (node_time.weekday() >= 2 && node_time.weekday() <= 6) // and we have a working day today... + if (((clockSet >> (16)) & 0xf) == 0x8) { // if weekday nibble has the wildcard value 0x8 (workdays) + if ((node_time.weekday() >= 2) && (node_time.weekday() <= 6)) // and we have a working day today... { - Mask = 0xffffffff ^ (0xFUL << (16)); // Mask to wipe nibble position. - clockEvent &= Mask; // clear nibble - clockEvent |= (0x8UL << (16)); // fill with wildcard value 0x8 + Mask = 0xffffffff ^ (0xFUL << (16)); // Mask to wipe nibble position. + clockEvent &= Mask; // clear nibble + clockEvent |= (0x8UL << (16)); // fill with wildcard value 0x8 } } - if (((clockSet >> (16)) & 0xf) == 0x9) { // if weekday nibble has the wildcard value 0x9 (weekends) - if (node_time.weekday() == 1 || node_time.weekday() == 7) // and we have a weekend day today... + if (((clockSet >> (16)) & 0xf) == 0x9) { // if weekday nibble has the wildcard value 0x9 (weekends) + if ((node_time.weekday() == 1) || (node_time.weekday() == 7)) // and we have a weekend day today... { - Mask = 0xffffffff ^ (0xFUL << (16)); // Mask to wipe nibble position. - clockEvent &= Mask; // clear nibble - clockEvent |= (0x9UL << (16)); // fill with wildcard value 0x9 + Mask = 0xffffffff ^ (0xFUL << (16)); // Mask to wipe nibble position. + clockEvent &= Mask; // clear nibble + clockEvent |= (0x9UL << (16)); // fill with wildcard value 0x9 } } - return (clockEvent == clockSet); -} \ No newline at end of file + return clockEvent == clockSet; +} diff --git a/src/src/Helpers/ESPEasy_time_calc.h b/src/src/Helpers/ESPEasy_time_calc.h index affb25388c..83426d60b9 100644 --- a/src/src/Helpers/ESPEasy_time_calc.h +++ b/src/src/Helpers/ESPEasy_time_calc.h @@ -55,7 +55,15 @@ inline bool usecTimeOutReached(const uint64_t& timer) { return usecPassedSince(timer) >= 0; } +uint32_t unix_time_frac_to_millis(uint32_t unix_time_frac); +uint32_t unix_time_frac_to_micros(uint32_t unix_time_frac); +uint32_t millis_to_unix_time_frac(uint32_t millis); +uint32_t micros_to_unix_time_frac(uint32_t micros); +uint32_t micros_to_sec_time_frac(int64_t micros, uint32_t& unix_time_frac); +uint64_t sec_time_frac_to_Micros(uint32_t seconds, uint32_t time_frac); + +uint32_t micros_to_sec_usec(int64_t micros, uint32_t& usec); /********************************************************************************************\ Unix Time computations @@ -79,11 +87,14 @@ void breakTime(unsigned long timeInput, struct tm& tm); // date format example with '-' delimiter: 2016-12-31 (YYYY-MM-DD) String formatDateString(const struct tm& ts, char delimiter); -// returns the current Time separated by the given delimiter +// returns the given Time formatted like this 23:59:59 (HH:MM:SS) +String formatTimeString(const struct tm& ts); + +// returns the given Time separated by the given delimiter // time format example with ':' delimiter: 23:59:59 (HH:MM:SS) String formatTimeString(const struct tm& ts, char delimiter, bool am_pm, bool show_seconds, char hour_prefix = '\0'); -// returns the current Date and Time separated by the given delimiter +// returns the given Date and Time separated by the given delimiter // if called like this: getDateTimeString('\0', '\0', '\0'); // it will give back this: 20161231235959 (YYYYMMDDHHMMSS) String formatDateTimeString(const struct tm& ts, char dateDelimiter = '-', char timeDelimiter = ':', char dateTimeDelimiter = ' ', bool am_pm = false); diff --git a/src/src/Helpers/Hardware.cpp b/src/src/Helpers/Hardware.cpp index ae3c2385ec..45c31875e9 100644 --- a/src/src/Helpers/Hardware.cpp +++ b/src/src/Helpers/Hardware.cpp @@ -358,9 +358,9 @@ float mapADCtoFloat(float float_value, // FIXME TD-er: For now keep a local array of the adc calibration #if ESP_IDF_VERSION_MAJOR < 5 -Hardware_ADC_cali_t ESP32_ADC_cali[ADC_ATTEN_MAX]; +Hardware_ADC_cali_t ESP32_ADC_cali[ADC_ATTEN_MAX]{}; #else -Hardware_ADC_cali_t ESP32_ADC_cali[ADC_ATTENDB_MAX]; +Hardware_ADC_cali_t ESP32_ADC_cali[ADC_ATTENDB_MAX]{}; #endif diff --git a/src/src/Helpers/Hardware_ADC_cali.cpp b/src/src/Helpers/Hardware_ADC_cali.cpp index 913e799793..d097e7e886 100644 --- a/src/src/Helpers/Hardware_ADC_cali.cpp +++ b/src/src/Helpers/Hardware_ADC_cali.cpp @@ -1,211 +1,230 @@ -#include "../Helpers/Hardware_ADC_cali.h" - -#ifdef ESP32 - -//# include "../Helpers/ESPEasy_math.h" -# include "../Helpers/Hardware.h" - - -Hardware_ADC_cali_t::~Hardware_ADC_cali_t() -{ -# if ESP_IDF_VERSION_MAJOR >= 5 - - if (_useFactoryCalibration) { -# if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED - adc_cali_delete_scheme_curve_fitting(_adc_cali_handle); - -# elif ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED - adc_cali_delete_scheme_line_fitting(_adc_cali_handle); -# endif // if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED - } - -# endif // if ESP_IDF_VERSION_MAJOR >= 5 -} - -bool Hardware_ADC_cali_t::init(int pin, - adc_atten_t attenuation) -{ -# if ESP_IDF_VERSION_MAJOR >= 5 && ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED - _useHighResInterpolation = false; -# elif ESP_IDF_VERSION_MAJOR >= 5 - _useHighResInterpolation = attenuation != adc_atten_t::ADC_ATTEN_DB_12; -# else // if ESP_IDF_VERSION_MAJOR >= 5 && ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED - _useHighResInterpolation = attenuation != adc_atten_t::ADC_ATTEN_DB_11; -# endif // if ESP_IDF_VERSION_MAJOR >= 5 && ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED - -# if ESP_IDF_VERSION_MAJOR >= 5 - - if (_adc_cali_handle != nullptr) { -# if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED - adc_cali_delete_scheme_curve_fitting(_adc_cali_handle); - -# elif ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED - adc_cali_delete_scheme_line_fitting(_adc_cali_handle); - -# endif // if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED - } - - _useFactoryCalibration = Hardware_ADC_cali_t::adc_calibration_init( - pin, - attenuation, - &_adc_cali_handle); - - if (_useFactoryCalibration) { - int tmp{}; - adc_cali_raw_to_voltage(_adc_cali_handle, 0, &tmp); - _min_out = tmp; - adc_cali_raw_to_voltage(_adc_cali_handle, MAX_ADC_VALUE, &tmp); - _max_out = tmp; - } - -# else // if ESP_IDF_VERSION_MAJOR >= 5 - # ifndef DEFAULT_VREF - # define DEFAULT_VREF 1100 - # endif // ifndef DEFAULT_VREF - constexpr adc_bits_width_t adc_bit_width = static_cast(ADC_WIDTH_MAX - 1); - _adc_calibration_type = - esp_adc_cal_characterize((getADC_num_for_gpio(pin) == 1) ? ADC_UNIT_1 : ADC_UNIT_2, - static_cast(attenuation), - adc_bit_width, - DEFAULT_VREF, - &_adc_chars); - _useFactoryCalibration = esp_adc_cal_check_efuse(_adc_calibration_type) == ESP_OK; - - if (_useFactoryCalibration) { - _min_out = esp_adc_cal_raw_to_voltage(0, &_adc_chars); - _max_out = esp_adc_cal_raw_to_voltage(MAX_ADC_VALUE, &_adc_chars); - } -# endif // if ESP_IDF_VERSION_MAJOR >= 5 - - _initialized = true; - - return _useFactoryCalibration; -} - -float Hardware_ADC_cali_t::applyFactoryCalibration(float rawValue) const { - if (!_useFactoryCalibration) { - return rawValue; - } - - if (!_useHighResInterpolation) { - const int raw = rawValue; -# if ESP_IDF_VERSION_MAJOR >= 5 - int res{}; - adc_cali_raw_to_voltage(_adc_cali_handle, raw, &res); - return res; -# else // if ESP_IDF_VERSION_MAJOR >= 5 - return esp_adc_cal_raw_to_voltage(raw, &_adc_chars); -# endif // if ESP_IDF_VERSION_MAJOR >= 5 - } - - // All other attenuations do appear to have a straight calibration curve. - // But applying the factory calibration then reduces resolution. - // So we interpolate using the calibrated extremes - - return mapADCtoFloat( - rawValue, - 0, - MAX_ADC_VALUE, - _min_out, - _max_out); -} - -const __FlashStringHelper * Hardware_ADC_cali_t::getADC_factory_calibration_type() const { -# if ESP_IDF_VERSION_MAJOR >= 5 - - if (_useFactoryCalibration) { - # if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED - return F("Calibration Curve Fitting"); - # endif // if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED - # if ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED - return F("Calibration Line Fitting"); - # endif // if ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED - } - -# else // if ESP_IDF_VERSION_MAJOR >= 5 - - switch (_adc_calibration_type) { - case ESP_ADC_CAL_VAL_EFUSE_VREF: return F("V_ref in eFuse"); - case ESP_ADC_CAL_VAL_EFUSE_TP: return F("Two Point values in eFuse"); - case ESP_ADC_CAL_VAL_DEFAULT_VREF: return F("Default reference voltage"); - case ESP_ADC_CAL_VAL_EFUSE_TP_FIT: return F("Two Point values and fitting curve in eFuse"); - case ESP_ADC_CAL_VAL_NOT_SUPPORTED: - break; - } -# endif // if ESP_IDF_VERSION_MAJOR >= 5 - return F("Unknown"); -} - -# if ESP_IDF_VERSION_MAJOR >= 5 -bool Hardware_ADC_cali_t::adc_calibration_init( - int pin, - adc_atten_t atten, - adc_cali_handle_t *out_handle) -{ - int ch{}; - const int adc = getADC_num_for_gpio(pin, ch); - const adc_channel_t channel = static_cast(ch); - -# if HAS_ADC2 - const adc_unit_t unit = (adc == 1) ? ADC_UNIT_1 : ADC_UNIT_2; -# else // if HAS_ADC2 - const adc_unit_t unit = ADC_UNIT_1; -# endif // if HAS_ADC2 - - adc_cali_handle_t handle = NULL; - esp_err_t ret = ESP_FAIL; - bool calibrated = false; - -# if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED - - if (!calibrated) { - // calibration scheme version: Curve Fitting - adc_cali_curve_fitting_config_t cali_config = { - .unit_id = unit, - .chan = channel, - .atten = atten, - .bitwidth = ADC_BITWIDTH_DEFAULT, - }; - ret = adc_cali_create_scheme_curve_fitting(&cali_config, &handle); - - if (ret == ESP_OK) { - calibrated = true; - } - } -# endif // if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED - -# if ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED - - if (!calibrated) { - // calibration scheme version: Line Fitting - adc_cali_line_fitting_config_t cali_config = { - .unit_id = unit, - .atten = atten, - .bitwidth = ADC_BITWIDTH_DEFAULT, - }; - ret = adc_cali_create_scheme_line_fitting(&cali_config, &handle); - - if (ret == ESP_OK) { - calibrated = true; - } - } -# endif // if ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED - - *out_handle = handle; - - /* - if (ret == ESP_OK) { - // Calibration Success - } else if (ret == ESP_ERR_NOT_SUPPORTED || !calibrated) { - // eFuse not burnt, skip software calibration - } else { - // Invalid arg or no memory - } - */ - - return calibrated; -} - -# endif // if ESP_IDF_VERSION_MAJOR >= 5 - -#endif // ifdef ESP32 +#include "../Helpers/Hardware_ADC_cali.h" + +#ifdef ESP32 + +// # include "../Helpers/ESPEasy_math.h" +# include "../Helpers/Hardware.h" + + +Hardware_ADC_cali_t::~Hardware_ADC_cali_t() +{ +# if ESP_IDF_VERSION_MAJOR >= 5 + + if (_useFactoryCalibration) { +# if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED + adc_cali_delete_scheme_curve_fitting(_adc_cali_handle); + +# elif ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED + adc_cali_delete_scheme_line_fitting(_adc_cali_handle); +# endif // if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED + } + +# endif // if ESP_IDF_VERSION_MAJOR >= 5 +} + +bool Hardware_ADC_cali_t::init(int pin, + adc_atten_t attenuation) +{ +# if ESP_IDF_VERSION_MAJOR >= 5 && ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED + _useHighResInterpolation = false; +# elif ESP_IDF_VERSION_MAJOR >= 5 + _useHighResInterpolation = attenuation != adc_atten_t::ADC_ATTEN_DB_12; +# else // if ESP_IDF_VERSION_MAJOR >= 5 && ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED + _useHighResInterpolation = attenuation != adc_atten_t::ADC_ATTEN_DB_11; +# endif // if ESP_IDF_VERSION_MAJOR >= 5 && ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED + +# if ESP_IDF_VERSION_MAJOR >= 5 + + if (_adc_cali_handle != nullptr) { +# if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED + adc_cali_delete_scheme_curve_fitting(_adc_cali_handle); + _adc_cali_handle = nullptr; + +# elif ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED + adc_cali_delete_scheme_line_fitting(_adc_cali_handle); + _adc_cali_handle = nullptr; + +# endif // if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED + } + + _useFactoryCalibration = Hardware_ADC_cali_t::adc_calibration_init( + pin, + attenuation, + &_adc_cali_handle); + + if (_useFactoryCalibration) { + int tmp{}; + adc_cali_raw_to_voltage(_adc_cali_handle, 0, &tmp); + _min_out = tmp; + adc_cali_raw_to_voltage(_adc_cali_handle, MAX_ADC_VALUE, &tmp); + _max_out = tmp; + } + +# else // if ESP_IDF_VERSION_MAJOR >= 5 + # ifndef DEFAULT_VREF + # define DEFAULT_VREF 1100 + # endif // ifndef DEFAULT_VREF + constexpr adc_bits_width_t adc_bit_width = static_cast(ADC_WIDTH_MAX - 1); + _adc_calibration_type = + esp_adc_cal_characterize((getADC_num_for_gpio(pin) == 1) ? ADC_UNIT_1 : ADC_UNIT_2, + static_cast(attenuation), + adc_bit_width, + DEFAULT_VREF, + &_adc_chars); + _useFactoryCalibration = esp_adc_cal_check_efuse(_adc_calibration_type) == ESP_OK; + + if (_useFactoryCalibration) { + _min_out = esp_adc_cal_raw_to_voltage(0, &_adc_chars); + _max_out = esp_adc_cal_raw_to_voltage(MAX_ADC_VALUE, &_adc_chars); + } +# endif // if ESP_IDF_VERSION_MAJOR >= 5 + + _initialized = true; + + return _useFactoryCalibration; +} + +float Hardware_ADC_cali_t::applyFactoryCalibration(float rawValue) const { + if (!_useFactoryCalibration) { + return rawValue; + } + + if (!_useHighResInterpolation) { +# if ESP_IDF_VERSION_MAJOR >= 5 + int adc_low = (static_cast(rawValue) - 128) & 0xFFFFFF80; + int adc_high = (static_cast(rawValue) + 128) & 0xFFFFFF80; + + if (adc_low < 0) { adc_low = 0; } + + if (adc_high > MAX_ADC_VALUE) { adc_high = MAX_ADC_VALUE; } + + int volt_low{}; + int volt_high{}; + + if ( + (adc_cali_raw_to_voltage(_adc_cali_handle, adc_low, &volt_low) == ESP_OK) && + (adc_cali_raw_to_voltage(_adc_cali_handle, adc_high, &volt_high) == ESP_OK)) { + return mapADCtoFloat( + rawValue, + adc_low, + adc_high, + volt_low, + volt_high); + } +# else // if ESP_IDF_VERSION_MAJOR >= 5 + const int raw = rawValue; + return esp_adc_cal_raw_to_voltage(raw, &_adc_chars); +# endif // if ESP_IDF_VERSION_MAJOR >= 5 + } + + // All other attenuations do appear to have a straight calibration curve. + // But applying the factory calibration then reduces resolution. + // So we interpolate using the calibrated extremes + + return mapADCtoFloat( + rawValue, + 0, + MAX_ADC_VALUE, + _min_out, + _max_out); +} + +const __FlashStringHelper * Hardware_ADC_cali_t::getADC_factory_calibration_type() const { +# if ESP_IDF_VERSION_MAJOR >= 5 + + if (_useFactoryCalibration) { + # if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED + return F("Calibration Curve Fitting"); + # endif // if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED + # if ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED + return F("Calibration Line Fitting"); + # endif // if ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED + } + +# else // if ESP_IDF_VERSION_MAJOR >= 5 + + switch (_adc_calibration_type) { + case ESP_ADC_CAL_VAL_EFUSE_VREF: return F("V_ref in eFuse"); + case ESP_ADC_CAL_VAL_EFUSE_TP: return F("Two Point values in eFuse"); + case ESP_ADC_CAL_VAL_DEFAULT_VREF: return F("Default reference voltage"); + case ESP_ADC_CAL_VAL_EFUSE_TP_FIT: return F("Two Point values and fitting curve in eFuse"); + case ESP_ADC_CAL_VAL_NOT_SUPPORTED: + break; + } +# endif // if ESP_IDF_VERSION_MAJOR >= 5 + return F("Unknown"); +} + +# if ESP_IDF_VERSION_MAJOR >= 5 +bool Hardware_ADC_cali_t::adc_calibration_init( + int pin, + adc_atten_t atten, + adc_cali_handle_t *out_handle) +{ + int ch{}; + const int adc = getADC_num_for_gpio(pin, ch); + const adc_channel_t channel = static_cast(ch); + +# if HAS_ADC2 + const adc_unit_t unit = (adc == 1) ? ADC_UNIT_1 : ADC_UNIT_2; +# else // if HAS_ADC2 + const adc_unit_t unit = ADC_UNIT_1; +# endif // if HAS_ADC2 + + adc_cali_handle_t handle = NULL; + esp_err_t ret = ESP_FAIL; + bool calibrated = false; + +# if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED + + if (!calibrated) { + // calibration scheme version: Curve Fitting + adc_cali_curve_fitting_config_t cali_config = { + .unit_id = unit, + .chan = channel, + .atten = atten, + .bitwidth = ADC_BITWIDTH_DEFAULT, + }; + ret = adc_cali_create_scheme_curve_fitting(&cali_config, &handle); + + if (ret == ESP_OK) { + calibrated = true; + } + } +# endif // if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED + +# if ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED + + if (!calibrated) { + // calibration scheme version: Line Fitting + adc_cali_line_fitting_config_t cali_config = { + .unit_id = unit, + .atten = atten, + .bitwidth = ADC_BITWIDTH_DEFAULT, + }; + ret = adc_cali_create_scheme_line_fitting(&cali_config, &handle); + + if (ret == ESP_OK) { + calibrated = true; + } + } +# endif // if ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED + + *out_handle = handle; + + /* + if (ret == ESP_OK) { + // Calibration Success + } else if (ret == ESP_ERR_NOT_SUPPORTED || !calibrated) { + // eFuse not burnt, skip software calibration + } else { + // Invalid arg or no memory + } + */ + + return calibrated; +} + +# endif // if ESP_IDF_VERSION_MAJOR >= 5 + +#endif // ifdef ESP32 diff --git a/src/src/Helpers/Hardware_temperature_sensor.cpp b/src/src/Helpers/Hardware_temperature_sensor.cpp index 4cd04f086f..f46e08c029 100644 --- a/src/src/Helpers/Hardware_temperature_sensor.cpp +++ b/src/src/Helpers/Hardware_temperature_sensor.cpp @@ -1,219 +1,222 @@ -#include "../Helpers/Hardware_temperature_sensor.h" - - -#if FEATURE_INTERNAL_TEMPERATURE - -# include "../Helpers/StringConverter.h" - -/** - * Code based on: - * https://github.com/esphome/esphome/blob/518ecb4cc4489c8a76b899bfda7576b05d84c226/esphome/components/internal_temperature/internal_temperature.cpp#L40 - */ - -# ifdef ESP32 -# if defined(ESP32_CLASSIC) - -// there is no official API available on the original ESP32 -extern "C" { -uint8_t temprature_sens_read(); -} -# elif defined(ESP32C2) || defined(ESP32C3) || defined(ESP32C6) || defined(ESP32S2) || defined(ESP32S3) -# if ESP_IDF_VERSION_MAJOR < 5 -# include - -// Work-around for bug in ESP-IDF < 5.0 -# if defined(ESP32S3) || defined(ESP32C3) -# include -# elif defined(ESP32S2) -# include -# endif // if defined(ESP32S3) || defined(ESP32C3) -# else // if ESP_IDF_VERSION_MAJOR < 5 -# include -# endif // if ESP_IDF_VERSION_MAJOR < 5 -# endif // ESP32_CLASSIC -# endif // ESP32 - - -# ifdef ESP32 -# if defined(ESP32_CLASSIC) - - -esp_err_t do_read_internal_temperature(float& celsius) { - esp_err_t result = ESP_FAIL; - uint8_t raw = 128u; - int8_t retries = 2; - - while ((128u == raw) && (0 != retries)) { - delay(0); - raw = temprature_sens_read(); // Each reading takes about 112 microseconds - --retries; - } -# ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, concat(F("ESP32: Raw temperature value: "), raw)); -# endif // ifndef BUILD_NO_DEBUG - - if (raw != 128) { - result = ESP_OK; - - // Raw value is in Fahrenheit - celsius = (raw - 32) / 1.8f; - } - return result; -} - -# elif defined(ESP32C2) || defined(ESP32C3) || defined(ESP32C6) || defined(ESP32S2) || defined(ESP32S3) - -esp_err_t do_read_internal_temperature(float& celsius) { - esp_err_t result = ESP_FAIL; - - celsius = 0.0f; // Make sure it is initialized and within the default range. - -# if ESP_IDF_VERSION_MAJOR < 5 - - temp_sensor_config_t tsens = TSENS_CONFIG_DEFAULT(); - - temp_sensor_set_config(tsens); - temp_sensor_start(); - - // Work-around for bug in ESP-IDF < 5.0 - // Seems to be fixed in ESP_IDF5.1 - // temp_sensor_get_config always returns ESP_OK - // Thus dac_offset can be just about anything - // dac_offset is used as index in an array without bounds checking - { -# if defined(ESP32S3) || defined(ESP32C2) || defined(ESP32C3) || defined(ESP32C6) - static float s_deltaT = (esp_efuse_rtc_calib_get_ver() == 1) ? - (esp_efuse_rtc_calib_get_cal_temp(1) / 10.0f) : - 0.0f; -# elif defined(ESP32S2) - static uint32_t version = esp_efuse_rtc_table_read_calib_version(); - static float s_deltaT = (version == 1 || version == 2) ? - (esp_efuse_rtc_table_get_parsed_efuse_value(RTCCALIB_IDX_TMPSENSOR, false) / 10.0f) : - 0.0f; -# endif // if defined(ESP32S3) || defined(ESP32C3) - - - /* - if (isnan(s_deltaT)) { //suggests that the value is not initialized - uint32_t version = esp_efuse_rtc_calib_get_ver(); - if (version == 1) { - // fetch calibration value for temp sensor from eFuse - s_deltaT = esp_efuse_rtc_calib_get_cal_temp(version); - } else { - // no value to fetch, use 0. - s_deltaT = 0; - } - } - */ -# ifndef TSENS_ADC_FACTOR -# define TSENS_ADC_FACTOR (0.4386) -# endif // ifndef TSENS_ADC_FACTOR -# ifndef TSENS_DAC_FACTOR -# define TSENS_DAC_FACTOR (27.88) -# endif // ifndef TSENS_DAC_FACTOR -# ifndef TSENS_SYS_OFFSET -# define TSENS_SYS_OFFSET (20.52) -# endif // ifndef TSENS_SYS_OFFSET - uint32_t tsens_raw{}; - temp_sensor_read_raw(&tsens_raw); - celsius = (TSENS_ADC_FACTOR * tsens_raw) - s_deltaT - TSENS_SYS_OFFSET; - result = ESP_OK; - } - -# else // if ESP_IDF_VERSION_MAJOR < 5 - - // result = temp_sensor_read_celsius(&celsius); - - - static temperature_sensor_handle_t temp_sensor = nullptr; - - // Use range which seems to have the smallest error - // See: https://docs.espressif.com/projects/esp-idf/en/stable/esp32c3/api-reference/peripherals/temp_sensor.html - static int range_min = -10; - static int range_max = 80; - - bool must_reinstall = false; - - if (temp_sensor == nullptr) { - temperature_sensor_config_t temp_sensor_config = TEMPERATURE_SENSOR_CONFIG_DEFAULT(range_min, range_max); - result = temperature_sensor_install(&temp_sensor_config, &temp_sensor); - } else { - result = ESP_OK; - } - - if (ESP_OK == result) { - result = temperature_sensor_enable(temp_sensor); - - if (result == ESP_ERR_INVALID_STATE) { - // Sensor reports to be not enabled - must_reinstall = true; - } - } - - if (ESP_OK == result) { - result = temperature_sensor_get_celsius(temp_sensor, &celsius); - - // FIXME TD-er: What to do when result == ESP_FAIL (can be indication of out-of-range) - if (celsius < (range_min + 10)) { - range_min -= 10; - - if (range_min > celsius) { - range_min = celsius - 10; - } - must_reinstall = true; - } - - if (celsius > (range_max - 10)) { - range_max += 10; - - if (range_max < celsius) { - range_max = celsius + 10; - } - must_reinstall = true; - } - } - - temperature_sensor_disable(temp_sensor); - - - if (must_reinstall) { - temperature_sensor_uninstall(temp_sensor); - temp_sensor = nullptr; - } - - -# endif // if ESP_IDF_VERSION_MAJOR < 5 - - return result; -} - -# endif // if defined(ESP32_CLASSIC) -# endif // ifdef ESP32 - - -bool getInternalTemperature(float& temperatureCelsius) { - static float temperature_filtered = NAN; // Improbable value - float celsius{}; - esp_err_t result = do_read_internal_temperature(celsius); - - if (ESP_OK == result) { - if (isnanf(temperature_filtered)) { - temperature_filtered = celsius; - } else { - constexpr float IIR_FACTOR = 5.0f; - constexpr float IIR_DIVIDER = IIR_FACTOR + 1.0f; - temperature_filtered = ((IIR_FACTOR * temperature_filtered) + celsius) / IIR_DIVIDER; - } - } - temperatureCelsius = temperature_filtered; - return ESP_OK == result; -} - -float getInternalTemperature() { - float temperatureCelsius{}; - - getInternalTemperature(temperatureCelsius); - return temperatureCelsius; -} - -#endif // if FEATURE_INTERNAL_TEMPERATURE +#include "../Helpers/Hardware_temperature_sensor.h" + + +#if FEATURE_INTERNAL_TEMPERATURE + +# include "../Helpers/StringConverter.h" + +/** + * Code based on: + * https://github.com/esphome/esphome/blob/518ecb4cc4489c8a76b899bfda7576b05d84c226/esphome/components/internal_temperature/internal_temperature.cpp#L40 + */ + +# ifdef ESP32 +# if defined(ESP32_CLASSIC) + +// there is no official API available on the original ESP32 +extern "C" { +uint8_t temprature_sens_read(); +} +# elif defined(ESP32C2) || defined(ESP32C3) || defined(ESP32C6) || defined(ESP32S2) || defined(ESP32S3) +# if ESP_IDF_VERSION_MAJOR < 5 +# include + +// Work-around for bug in ESP-IDF < 5.0 +# if defined(ESP32S3) || defined(ESP32C3) +# include +# elif defined(ESP32S2) +# include +# endif // if defined(ESP32S3) || defined(ESP32C3) +# else // if ESP_IDF_VERSION_MAJOR < 5 +# include +# endif // if ESP_IDF_VERSION_MAJOR < 5 +# endif // ESP32_CLASSIC +# endif // ESP32 + + +# ifdef ESP32 +# if defined(ESP32_CLASSIC) + + +esp_err_t do_read_internal_temperature(float& celsius) { + esp_err_t result = ESP_FAIL; + uint8_t raw = 128u; + int8_t retries = 2; + + while ((128u == raw) && (0 != retries)) { + delay(0); + raw = temprature_sens_read(); // Each reading takes about 112 microseconds + --retries; + } +# ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, concat(F("ESP32: Raw temperature value: "), raw)); +# endif // ifndef BUILD_NO_DEBUG + + if (raw != 128) { + result = ESP_OK; + + // Raw value is in Fahrenheit + celsius = (raw - 32) / 1.8f; + } + return result; +} + +# elif defined(ESP32C2) || defined(ESP32C3) || defined(ESP32C6) || defined(ESP32S2) || defined(ESP32S3) + +esp_err_t do_read_internal_temperature(float& celsius) { + esp_err_t result = ESP_FAIL; + + celsius = 0.0f; // Make sure it is initialized and within the default range. + +# if ESP_IDF_VERSION_MAJOR < 5 + + temp_sensor_config_t tsens = TSENS_CONFIG_DEFAULT(); + + temp_sensor_set_config(tsens); + temp_sensor_start(); + + // Work-around for bug in ESP-IDF < 5.0 + // Seems to be fixed in ESP_IDF5.1 + // temp_sensor_get_config always returns ESP_OK + // Thus dac_offset can be just about anything + // dac_offset is used as index in an array without bounds checking + { +# if defined(ESP32S3) || defined(ESP32C2) || defined(ESP32C3) || defined(ESP32C6) + static float s_deltaT = (esp_efuse_rtc_calib_get_ver() == 1) ? + (esp_efuse_rtc_calib_get_cal_temp(1) / 10.0f) : + 0.0f; +# elif defined(ESP32S2) + static uint32_t version = esp_efuse_rtc_table_read_calib_version(); + static float s_deltaT = (version == 1 || version == 2) ? + (esp_efuse_rtc_table_get_parsed_efuse_value(RTCCALIB_IDX_TMPSENSOR, false) / 10.0f) : + 0.0f; +# endif // if defined(ESP32S3) || defined(ESP32C3) + + + /* + if (isnan(s_deltaT)) { //suggests that the value is not initialized + uint32_t version = esp_efuse_rtc_calib_get_ver(); + if (version == 1) { + // fetch calibration value for temp sensor from eFuse + s_deltaT = esp_efuse_rtc_calib_get_cal_temp(version); + } else { + // no value to fetch, use 0. + s_deltaT = 0; + } + } + */ +# ifndef TSENS_ADC_FACTOR +# define TSENS_ADC_FACTOR (0.4386) +# endif // ifndef TSENS_ADC_FACTOR +# ifndef TSENS_DAC_FACTOR +# define TSENS_DAC_FACTOR (27.88) +# endif // ifndef TSENS_DAC_FACTOR +# ifndef TSENS_SYS_OFFSET +# define TSENS_SYS_OFFSET (20.52) +# endif // ifndef TSENS_SYS_OFFSET + uint32_t tsens_raw{}; + temp_sensor_read_raw(&tsens_raw); + celsius = (TSENS_ADC_FACTOR * tsens_raw) - s_deltaT - TSENS_SYS_OFFSET; + result = ESP_OK; + } + +# else // if ESP_IDF_VERSION_MAJOR < 5 + + // result = temp_sensor_read_celsius(&celsius); + + + static temperature_sensor_handle_t temp_sensor = nullptr; + + // Use range which seems to have the smallest error + // See: https://docs.espressif.com/projects/esp-idf/en/stable/esp32c3/api-reference/peripherals/temp_sensor.html + static int range_min = -10; + static int range_max = 80; + + bool must_reinstall = false; + + if (temp_sensor == nullptr) { + temperature_sensor_config_t temp_sensor_config = TEMPERATURE_SENSOR_CONFIG_DEFAULT(range_min, range_max); + result = temperature_sensor_install(&temp_sensor_config, &temp_sensor); + } else { + result = ESP_OK; + } + + if (ESP_OK == result) { + result = temperature_sensor_enable(temp_sensor); + + if (result == ESP_ERR_INVALID_STATE) { + // Sensor reports to be not enabled + must_reinstall = true; + } + } + + if (ESP_OK == result) { + result = temperature_sensor_get_celsius(temp_sensor, &celsius); + if (result == ESP_FAIL) { + must_reinstall = true; + } + + // FIXME TD-er: What to do when result == ESP_FAIL (can be indication of out-of-range) + if (celsius < (range_min + 10)) { + range_min -= 10; + + if (range_min > celsius) { + range_min = celsius - 10; + } + must_reinstall = true; + } + + if (celsius > (range_max - 10)) { + range_max += 10; + + if (range_max < celsius) { + range_max = celsius + 10; + } + must_reinstall = true; + } + } + + temperature_sensor_disable(temp_sensor); + + + if (must_reinstall) { + temperature_sensor_uninstall(temp_sensor); + temp_sensor = nullptr; + } + + +# endif // if ESP_IDF_VERSION_MAJOR < 5 + + return result; +} + +# endif // if defined(ESP32_CLASSIC) +# endif // ifdef ESP32 + + +bool getInternalTemperature(float& temperatureCelsius) { + static float temperature_filtered = NAN; // Improbable value + float celsius{}; + esp_err_t result = do_read_internal_temperature(celsius); + + if (ESP_OK == result) { + if (isnanf(temperature_filtered)) { + temperature_filtered = celsius; + } else { + constexpr float IIR_FACTOR = 5.0f; + constexpr float IIR_DIVIDER = IIR_FACTOR + 1.0f; + temperature_filtered = ((IIR_FACTOR * temperature_filtered) + celsius) / IIR_DIVIDER; + } + } + temperatureCelsius = temperature_filtered; + return ESP_OK == result; +} + +float getInternalTemperature() { + float temperatureCelsius{}; + + getInternalTemperature(temperatureCelsius); + return temperatureCelsius; +} + +#endif // if FEATURE_INTERNAL_TEMPERATURE diff --git a/src/src/Helpers/Memory.cpp b/src/src/Helpers/Memory.cpp index 412230893c..b4749f11cc 100644 --- a/src/src/Helpers/Memory.cpp +++ b/src/src/Helpers/Memory.cpp @@ -1,138 +1,199 @@ -#include "../Helpers/Memory.h" - - -#ifdef ESP8266 -extern "C" { -#include -} -#endif - -#include "../../ESPEasy_common.h" - - -#ifdef ESP32 -#if ESP_IDF_VERSION_MAJOR < 5 -#include -#endif -#endif - -#include "../Helpers/Hardware_device_info.h" - -/*********************************************************************************************\ - Memory management -\*********************************************************************************************/ - - -// For keeping track of 'cont' stack -// See: https://github.com/esp8266/Arduino/issues/2557 -// https://github.com/esp8266/Arduino/issues/5148#issuecomment-424329183 -// https://github.com/letscontrolit/ESPEasy/issues/1824 -#ifdef ESP32 - -// FIXME TD-er: For ESP32 you need to provide the task number, or nullptr to get from the calling task. -uint32_t getCurrentFreeStack() { - return ((uint8_t*)esp_cpu_get_sp()) - pxTaskGetStackStart(nullptr); -} - -uint32_t getFreeStackWatermark() { - return uxTaskGetStackHighWaterMark(nullptr); -} - -#else // ifdef ESP32 - -uint32_t getCurrentFreeStack() { - // https://github.com/esp8266/Arduino/issues/2557 - register uint32_t *sp asm ("a1"); - - return 4 * (sp - g_pcont->stack); -} - -uint32_t getFreeStackWatermark() { - return cont_get_free_stack(g_pcont); -} - -bool allocatedOnStack(const void *address) { - register uint32_t *sp asm ("a1"); - - if (sp < address) { return false; } - return g_pcont->stack < address; -} - -#endif // ESP32 - - -/********************************************************************************************\ - Get free system mem - \*********************************************************************************************/ -unsigned long FreeMem() -{ - #if defined(ESP8266) - return system_get_free_heap_size(); - #endif // if defined(ESP8266) - #if defined(ESP32) - return ESP.getFreeHeap(); - #endif // if defined(ESP32) -} - -#ifdef USE_SECOND_HEAP -unsigned long FreeMem2ndHeap() -{ - HeapSelectIram ephemeral; - return ESP.getFreeHeap(); -} -#endif - - -unsigned long getMaxFreeBlock() -{ - const unsigned long freemem = FreeMem(); - // computing max free block is a rather extensive operation, so only perform when free memory is already low. - if (freemem < 6144) { - #if defined(ESP32) - return ESP.getMaxAllocHeap(); - #endif // if defined(ESP32) - #ifdef CORE_POST_2_5_0 - return ESP.getMaxFreeBlockSize(); - #endif // ifdef CORE_POST_2_5_0 - } - return freemem; -} - -/********************************************************************************************\ - Special alloc functions to allocate in PSRAM if available - \*********************************************************************************************/ - -void *special_malloc(uint32_t size) { - #ifdef ESP32 - if (UsePSRAM()) { - return heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); - } else { - return malloc(size); - } - #else - return malloc(size); - #endif -} - -void *special_realloc(void *ptr, size_t size) { - #ifdef ESP32 - if (UsePSRAM()) { - return heap_caps_realloc(ptr, size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); - } else { - return realloc(ptr, size); - } - #else - return realloc(ptr, size); - #endif -} -void *special_calloc(size_t num, size_t size) { - #ifdef ESP32 - if (UsePSRAM()) { - return heap_caps_calloc(num, size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); - } else { - return calloc(num, size); - } - #else - return calloc(num, size); - #endif -} \ No newline at end of file +#include "../Helpers/Memory.h" + + +#ifdef ESP8266 +extern "C" { +# include +} +#endif // ifdef ESP8266 + +#include "../../ESPEasy_common.h" + + +#ifdef ESP32 +# if ESP_IDF_VERSION_MAJOR < 5 +# include +# endif // if ESP_IDF_VERSION_MAJOR < 5 +#endif // ifdef ESP32 + +#include "../Helpers/Hardware_device_info.h" + +/*********************************************************************************************\ + Memory management +\*********************************************************************************************/ + + +// For keeping track of 'cont' stack +// See: https://github.com/esp8266/Arduino/issues/2557 +// https://github.com/esp8266/Arduino/issues/5148#issuecomment-424329183 +// https://github.com/letscontrolit/ESPEasy/issues/1824 +#ifdef ESP32 + +// FIXME TD-er: For ESP32 you need to provide the task number, or nullptr to get from the calling task. +uint32_t getCurrentFreeStack() { + return ((uint8_t *)esp_cpu_get_sp()) - pxTaskGetStackStart(nullptr); +} + +uint32_t getFreeStackWatermark() { + return uxTaskGetStackHighWaterMark(nullptr); +} + +#else // ifdef ESP32 + +uint32_t getCurrentFreeStack() { + // https://github.com/esp8266/Arduino/issues/2557 + register uint32_t *sp asm ("a1"); + + return 4 * (sp - g_pcont->stack); +} + +uint32_t getFreeStackWatermark() { + return cont_get_free_stack(g_pcont); +} + +bool allocatedOnStack(const void *address) { + register uint32_t *sp asm ("a1"); + + if (sp < address) { return false; } + return g_pcont->stack < address; +} + +#endif // ESP32 + + +/********************************************************************************************\ + Get free system mem + \*********************************************************************************************/ +unsigned long FreeMem() +{ + #if defined(ESP8266) + return system_get_free_heap_size(); + #endif // if defined(ESP8266) + #if defined(ESP32) + return ESP.getFreeHeap(); + #endif // if defined(ESP32) +} + +#ifdef USE_SECOND_HEAP +unsigned long FreeMem2ndHeap() +{ + HeapSelectIram ephemeral; + + return ESP.getFreeHeap(); +} + +#endif // ifdef USE_SECOND_HEAP + + +unsigned long getMaxFreeBlock() +{ + const unsigned long freemem = FreeMem(); + + // computing max free block is a rather extensive operation, so only perform when free memory is already low. + if (freemem < 6144) { + #if defined(ESP32) + return ESP.getMaxAllocHeap(); + #endif // if defined(ESP32) + #ifdef CORE_POST_2_5_0 + return ESP.getMaxFreeBlockSize(); + #endif // ifdef CORE_POST_2_5_0 + } + return freemem; +} + +/********************************************************************************************\ + Special alloc functions to allocate in PSRAM if available + See: https://github.com/espressif/esp-idf/blob/master/components/heap/port/esp32s3/memory_layout.c + \*********************************************************************************************/ +void* special_malloc(uint32_t size) { + void *res = nullptr; + +#ifdef ESP32 + + if (UsePSRAM()) { + res = heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); + } +#else // ifdef ESP32 + { +# ifdef USE_SECOND_HEAP + + // Try allocating on ESP8266 2nd heap + HeapSelectIram ephemeral; +# endif // ifdef USE_SECOND_HEAP + res = malloc(size); + } +#endif // ifdef ESP32 + + if (res == nullptr) { +#ifdef USE_SECOND_HEAP + + // Not successful, try allocating on (ESP8266) main heap + HeapSelectDram ephemeral; +#endif // ifdef USE_SECOND_HEAP + res = malloc(size); + } + + return res; +} + +void* special_realloc(void *ptr, size_t size) { + void *res = nullptr; + +#ifdef ESP32 + + if (UsePSRAM()) { + res = heap_caps_realloc(ptr, size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); + } +#else // ifdef ESP32 + { +# ifdef USE_SECOND_HEAP + + // Try allocating on ESP8266 2nd heap + HeapSelectIram ephemeral; +# endif // ifdef USE_SECOND_HEAP + res = realloc(ptr, size); + } +#endif // ifdef ESP32 + + if (res == nullptr) { +#ifdef USE_SECOND_HEAP + + // Not successful, try allocating on (ESP8266) main heap + HeapSelectDram ephemeral; +#endif // ifdef USE_SECOND_HEAP + res = realloc(ptr, size); + } + + return res; +} + +void* special_calloc(size_t num, size_t size) { + void *res = nullptr; + +#ifdef ESP32 + + if (UsePSRAM()) { + res = heap_caps_calloc(num, size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); + } +#else // ifdef ESP32 + { +# ifdef USE_SECOND_HEAP + + // Try allocating on ESP8266 2nd heap + HeapSelectIram ephemeral; +# endif // ifdef USE_SECOND_HEAP + res = calloc(num, size); + } +#endif // ifdef ESP32 + + if (res == nullptr) { +#ifdef USE_SECOND_HEAP + + // Not successful, try allocating on (ESP8266) main heap + HeapSelectDram ephemeral; +#endif // ifdef USE_SECOND_HEAP + res = calloc(num, size); + } + + return res; +} diff --git a/src/src/Helpers/Misc.cpp b/src/src/Helpers/Misc.cpp index 2f2858a94e..946c12a239 100644 --- a/src/src/Helpers/Misc.cpp +++ b/src/src/Helpers/Misc.cpp @@ -1,538 +1,547 @@ -#include "../Helpers/Misc.h" - -#include "../../ESPEasy-Globals.h" -#include "../../ESPEasy_common.h" -#include "../../_Plugin_Helper.h" -#include "../ESPEasyCore/ESPEasy_backgroundtasks.h" -#include "../ESPEasyCore/Serial.h" -#include "../Globals/ESPEasy_time.h" -#include "../Globals/Statistics.h" -#include "../Helpers/ESPEasy_FactoryDefault.h" -#include "../Helpers/ESPEasy_Storage.h" -#include "../Helpers/Numerical.h" -#include "../Helpers/PeriodicalActions.h" -#include "../Helpers/StringConverter.h" -#include "../Helpers/StringParser.h" - -#if FEATURE_SD -#include -#endif - - -bool remoteConfig(struct EventStruct *event, const String& string) -{ - // FIXME TD-er: Why have an event here as argument? It is not used. - #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("remoteConfig")); - #endif // ifndef BUILD_NO_RAM_TRACKER - bool success = false; - String command = parseString(string, 1); - - if (equals(command, F("config"))) - { - // Command: "config,task,," - if (equals(parseString(string, 2), F("task"))) - { - String configTaskName = parseStringKeepCase(string, 3); - - // FIXME TD-er: This command is not using the tolerance setting - // tolerantParseStringKeepCase(Line, 4); - String configCommand = parseStringToEndKeepCase(string, 4); - - if ((configTaskName.isEmpty()) || (configCommand.isEmpty())) { - return success; - } - taskIndex_t index = findTaskIndexByName(configTaskName); - - if (validTaskIndex(index)) - { - event->setTaskIndex(index); - success = PluginCall(PLUGIN_SET_CONFIG, event, configCommand); - } - } else { - addLog(LOG_LEVEL_ERROR, F("Expected syntax: config,task,,")); - } - } - return success; -} - -/********************************************************************************************\ - delay in milliseconds with background processing - \*********************************************************************************************/ -void delayBackground(unsigned long dsdelay) -{ - unsigned long timer = millis() + dsdelay; - - while (!timeOutReached(timer)) { - backgroundtasks(); - } -} - -/********************************************************************************************\ - Toggle controller enabled state - \*********************************************************************************************/ -bool setControllerEnableStatus(controllerIndex_t controllerIndex, bool enabled) -{ - if (!validControllerIndex(controllerIndex)) { return false; } - #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("setControllerEnableStatus")); - #endif // ifndef BUILD_NO_RAM_TRACKER - - // Only enable controller if it has a protocol configured - if ((Settings.Protocol[controllerIndex] != 0) || !enabled) { - Settings.ControllerEnabled[controllerIndex] = enabled; - return true; - } - return false; -} - -/********************************************************************************************\ - Toggle task enabled state - \*********************************************************************************************/ -bool setTaskEnableStatus(struct EventStruct *event, bool enabled) -{ - if (!validTaskIndex(event->TaskIndex)) { return false; } - #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("setTaskEnableStatus")); - #endif // ifndef BUILD_NO_RAM_TRACKER - - // Only enable task if it has a Plugin configured - if (validPluginID(Settings.getPluginID_for_task(event->TaskIndex)) || !enabled) { - String dummy; - - if (!enabled) { - PluginCall(PLUGIN_EXIT, event, dummy); - } - // Toggle enable/disable state via command - // FIXME TD-er: Should this be a 'runtime' change, or actually change the intended state? - //Settings.TaskDeviceEnabled[event->TaskIndex].enabled = enabled; - Settings.TaskDeviceEnabled[event->TaskIndex] = enabled; - - if (enabled) { - // Schedule the plugin to be read. - // Do this before actual init, to allow the plugin to schedule a specific first read. - Scheduler.schedule_task_device_timer(event->TaskIndex, millis() + 10); - - if (!PluginCall(PLUGIN_INIT, event, dummy)) { - return false; - } - } - return true; - } - return false; -} - -/********************************************************************************************\ - Clear task settings for given task - \*********************************************************************************************/ -void taskClear(taskIndex_t taskIndex, bool save) -{ - if (!validTaskIndex(taskIndex)) { return; } - #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("taskClear")); - #endif // ifndef BUILD_NO_RAM_TRACKER - Settings.clearTask(taskIndex); - clearTaskCache(taskIndex); // Invalidate any cached values. - ExtraTaskSettings.clear(); - ExtraTaskSettings.TaskIndex = taskIndex; - - if (save) { - addLog(LOG_LEVEL_INFO, F("taskClear() save settings")); - SaveTaskSettings(taskIndex); - SaveSettings(); - } -} - -/********************************************************************************************\ - check the program memory hash - The const MD5_MD5_MD5_MD5_BoundariesOfTheSegmentsGoHere... needs to remain unchanged as it will be replaced by - - 16 bytes md5 hash, followed by - - 4 * uint32_t start of memory segment 1-4 - - 4 * uint32_t end of memory segment 1-4 - currently there are only two segemts included in the hash. Unused segments have start adress 0. - Execution time 520kb @80Mhz: 236ms - Returns: 0 if hash compare fails, number of checked bytes otherwise. - The reference hash is calculated by a .py file and injected into the binary. - Caution: currently the hash sits in an unchecked segment. If it ever moves to a checked segment, make sure - it is excluded from the calculation ! - \*********************************************************************************************/ -#if defined(ARDUINO_ESP8266_RELEASE_2_3_0) -void dump(uint32_t addr) { // Seems already included in core 2.4 ... - serialPrint(String(addr, HEX)); - serialPrint(": "); - - for (uint32_t a = addr; a < addr + 16; a++) - { - serialPrint(String(pgm_read_byte(a), HEX)); - serialPrint(" "); - } - serialPrintln(); -} - -#endif // if defined(ARDUINO_ESP8266_RELEASE_2_3_0) - -/* - uint32_t progMemMD5check(){ - checkRAM(F("progMemMD5check")); - #define BufSize 10 - uint32_t calcBuffer[BufSize]; - CRCValues.numberOfCRCBytes = 0; - memcpy (calcBuffer,CRCValues.compileTimeMD5,16); // is there still the dummy in memory - ? - the dummy needs to be replaced by the real md5 after linking. - if( memcmp (calcBuffer, "MD5_MD5_MD5_",12)==0){ // do not memcmp with CRCdummy - directly or it will get optimized away. - addLog(LOG_LEVEL_INFO, F("CRC : No program memory checksum found. Check output of crc2.py")); - return 0; - } - MD5Builder md5; - md5.begin(); - for (int l = 0; l<4; l++){ // check max segments, if the - pointer is not 0 - uint32_t *ptrStart = (uint32_t *)&CRCValues.compileTimeMD5[16+l*4]; - uint32_t *ptrEnd = (uint32_t *)&CRCValues.compileTimeMD5[16+4*4+l*4]; - if ((*ptrStart) == 0) break; // segment not used. - for (uint32_t i = *ptrStart; i< (*ptrEnd) ; i=i+sizeof(calcBuffer)){ // "<" includes last byte - for (int buf = 0; buf < BufSize; buf ++){ - calcBuffer[buf] = pgm_read_dword((uint32_t*)i+buf); // read 4 bytes - CRCValues.numberOfCRCBytes+=sizeof(calcBuffer[0]); - } - md5.add(reinterpret_cast(&calcBuffer[0]),(*ptrEnd-i) 0 ? (S < 1 ? S : 1) : 0; // clamp S and I to interval [0,1] - I = I / 100; - I = I > 0 ? (I < 1 ? I : 1) : 0; - - // Math! Thanks in part to Kyle Miller. - if (H < 2.09439f) { - r = 255 * I / 3 * (1 + S * cosf(H) / cosf(1.047196667f - H)); - g = 255 * I / 3 * (1 + S * (1 - cosf(H) / cosf(1.047196667f - H))); - b = 255 * I / 3 * (1 - S); - } else if (H < 4.188787f) { - H = H - 2.09439f; - g = 255 * I / 3 * (1 + S * cosf(H) / cosf(1.047196667f - H)); - b = 255 * I / 3 * (1 + S * (1 - cosf(H) / cosf(1.047196667f - H))); - r = 255 * I / 3 * (1 - S); - } else { - H = H - 4.188787f; - b = 255 * I / 3 * (1 + S * cosf(H) / cosf(1.047196667f - H)); - r = 255 * I / 3 * (1 + S * (1 - cosf(H) / cosf(1.047196667f - H))); - g = 255 * I / 3 * (1 - S); - } - rgb[0] = r; - rgb[1] = g; - rgb[2] = b; - */ -} - -// uses H 0..360 S 1..100 I/V 1..100 (according to homie convention) -// Source https://blog.saikoled.com/post/44677718712/how-to-convert-from-hsi-to-rgb-white -void HSV2RGBW(float H, float S, float I, int rgbw[4]) { - H = fmod(H, 360); // cycle H around to 0-360 degrees - constexpr float deg2rad = 3.14159f / 180.0f; - H *= deg2rad; // Convert to radians. - S = S / 100; - S = S > 0 ? (S < 1 ? S : 1) : 0; // clamp S and I to interval [0,1] - I = I / 100; - I = I > 0 ? (I < 1 ? I : 1) : 0; - - #define RGB_ORDER 0 - #define GBR_ORDER 1 - #define BRG_ORDER 2 - - int order = RGB_ORDER; - - constexpr float ANGLE_120_DEG = 120.0f * deg2rad; - constexpr float ANGLE_240_DEG = 240.0f * deg2rad; - constexpr float ANGLE_60_DEG = 60.0f * deg2rad; - - if (H < ANGLE_120_DEG) { - order = RGB_ORDER; - } else if (H < ANGLE_240_DEG) { - H = H - ANGLE_120_DEG; - order = GBR_ORDER; - } else { - H = H - ANGLE_240_DEG; - order = BRG_ORDER; - } - const float cos_h = cosf(H); - const float cos_1047_h = cosf(ANGLE_60_DEG - H); - - const int r = S * 255 * I / 3 * (1 + cos_h / cos_1047_h); - const int g = S * 255 * I / 3 * (1 + (1 - cos_h / cos_1047_h)); - const int b = 0; - rgbw[3] = 255 * (1 - S) * I; - - if (RGB_ORDER == order) { - rgbw[0] = r; - rgbw[1] = g; - rgbw[2] = b; - } else if (GBR_ORDER == order) { - rgbw[0] = g; - rgbw[1] = b; - rgbw[2] = r; - } else if (BRG_ORDER == order) { - rgbw[0] = b; - rgbw[1] = r; - rgbw[2] = g; - } -} - -// Convert RGB Color to HSV Color -void RGB2HSV(uint8_t r, uint8_t g, uint8_t b, float hsv[3]) { - const float rf = static_cast(r) / 255.0f; - const float gf = static_cast(g) / 255.0f; - const float bf = static_cast(b) / 255.0f; - float maxval = rf; - - if (gf > maxval) { maxval = gf; } - - if (bf > maxval) { maxval = bf; } - float minval = rf; - - if (gf < minval) { minval = gf; } - - if (bf < minval) { minval = bf; } - float h = 0.0f, s, v = maxval; - float f = maxval - minval; - - s = maxval == 0.0f ? 0.0f : f / maxval; - - if (maxval == minval) { - h = 0.0f; // achromatic - } else { - if (maxval == rf) { - h = (gf - bf) / f + (gf < bf ? 6.0f : 0.0f); - } else if (maxval == gf) { - h = (bf - rf) / f + 2.0f; - } else if (maxval == bf) { - h = (rf - gf) / f + 4.0f; - } - h /= 6.0f; - } - - hsv[0] = h * 360.0f; - hsv[1] = s * 255.0f; - hsv[2] = v * 255.0f; -} - - - -float getCPUload() { - return 100.0f - Scheduler.getIdleTimePct(); -} - -int getLoopCountPerSec() { - return loopCounterLast / 30; -} - -int getUptimeMinutes() { - return wdcounter / 2; -} - -/****************************************************************************** - * scan an int array of specified size for a value - *****************************************************************************/ -bool intArrayContains(const int arraySize, const int array[], const int& value) { - for (int i = 0; i < arraySize; i++) { - if (array[i] == value) { return true; } - } - return false; -} - -bool intArrayContains(const int arraySize, const uint8_t array[], const uint8_t& value) { - for (int i = 0; i < arraySize; i++) { - if (array[i] == value) { return true; } - } - return false; -} - -#ifndef BUILD_NO_RAM_TRACKER -void logMemUsageAfter(const __FlashStringHelper *function, int value) { - // Store free memory in an int, as subtracting may sometimes result in negative value. - // The recorded used memory is not an exact value, as background (or interrupt) tasks may also allocate or free heap memory. - static int last_freemem = ESP.getFreeHeap(); - const int freemem_end = ESP.getFreeHeap(); - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log; - if (log.reserve(128)) { - log = F("After "); - log += function; - - if (value >= 0) { - log += value; - } - - while (log.length() < 30) { log += ' '; } - log += F("Free mem after: "); - log += freemem_end; - - while (log.length() < 55) { log += ' '; } - log += F("diff: "); - log += last_freemem - freemem_end; - addLogMove(LOG_LEVEL_DEBUG, log); - } - } - - last_freemem = freemem_end; -} - -#endif // ifndef BUILD_NO_RAM_TRACKER +#include "../Helpers/Misc.h" + +#include "../../ESPEasy-Globals.h" +#include "../../ESPEasy_common.h" +#include "../../_Plugin_Helper.h" +#include "../ESPEasyCore/ESPEasy_backgroundtasks.h" +#include "../ESPEasyCore/Serial.h" +#include "../Globals/ESPEasy_time.h" +#include "../Globals/Statistics.h" +#include "../Helpers/ESPEasy_FactoryDefault.h" +#include "../Helpers/ESPEasy_Storage.h" +#include "../Helpers/Numerical.h" +#include "../Helpers/PeriodicalActions.h" +#include "../Helpers/StringConverter.h" +#include "../Helpers/StringParser.h" + +#if FEATURE_SD +#include +#endif + + +bool remoteConfig(struct EventStruct *event, const String& string) +{ + // FIXME TD-er: Why have an event here as argument? It is not used. + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("remoteConfig")); + #endif // ifndef BUILD_NO_RAM_TRACKER + bool success = false; + String command = parseString(string, 1); + + if (equals(command, F("config"))) + { + // Command: "config,task,," + if (equals(parseString(string, 2), F("task"))) + { + String configTaskName = parseStringKeepCase(string, 3); + + // FIXME TD-er: This command is not using the tolerance setting + // tolerantParseStringKeepCase(Line, 4); + String configCommand = parseStringToEndKeepCase(string, 4); + + if ((configTaskName.isEmpty()) || (configCommand.isEmpty())) { + return success; + } + taskIndex_t index = findTaskIndexByName(configTaskName); + + if (validTaskIndex(index)) + { + event->setTaskIndex(index); + success = PluginCall(PLUGIN_SET_CONFIG, event, configCommand); + } + } else { + addLog(LOG_LEVEL_ERROR, F("Expected syntax: config,task,,")); + } + } + return success; +} + +/********************************************************************************************\ + delay in milliseconds with background processing + \*********************************************************************************************/ +void delayBackground(unsigned long dsdelay) +{ + unsigned long timer = millis() + dsdelay; + + while (!timeOutReached(timer)) { + backgroundtasks(); + } +} + +/********************************************************************************************\ + Toggle controller enabled state + \*********************************************************************************************/ +bool setControllerEnableStatus(controllerIndex_t controllerIndex, bool enabled) +{ + if (!validControllerIndex(controllerIndex)) { return false; } + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("setControllerEnableStatus")); + #endif // ifndef BUILD_NO_RAM_TRACKER + + // Only enable controller if it has a protocol configured + if ((Settings.Protocol[controllerIndex] != 0) || !enabled) { + Settings.ControllerEnabled[controllerIndex] = enabled; + return true; + } + return false; +} + +/********************************************************************************************\ + Toggle task enabled state + \*********************************************************************************************/ +bool setTaskEnableStatus(struct EventStruct *event, bool enabled) +{ + if (!validTaskIndex(event->TaskIndex)) { return false; } + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("setTaskEnableStatus")); + #endif // ifndef BUILD_NO_RAM_TRACKER + + // Only enable task if it has a Plugin configured + if (validPluginID(Settings.getPluginID_for_task(event->TaskIndex)) || !enabled) { + String dummy; + + if (!enabled) { + PluginCall(PLUGIN_EXIT, event, dummy); + } + // Toggle enable/disable state via command + // FIXME TD-er: Should this be a 'runtime' change, or actually change the intended state? + //Settings.TaskDeviceEnabled[event->TaskIndex].enabled = enabled; + Settings.TaskDeviceEnabled[event->TaskIndex] = enabled; + + if (enabled) { + // Schedule the plugin to be read. + // Do this before actual init, to allow the plugin to schedule a specific first read. + Scheduler.schedule_task_device_timer(event->TaskIndex, millis() + 10); + + if (!PluginCall(PLUGIN_INIT, event, dummy)) { + return false; + } + } + return true; + } + return false; +} + +/********************************************************************************************\ + Clear task settings for given task + \*********************************************************************************************/ +void taskClear(taskIndex_t taskIndex, bool save) +{ + if (!validTaskIndex(taskIndex)) { return; } + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("taskClear")); + #endif // ifndef BUILD_NO_RAM_TRACKER + if (Settings.TaskDeviceEnabled[taskIndex]) { + struct EventStruct TempEvent(taskIndex); + String dummy; + PluginCall(PLUGIN_EXIT, &TempEvent, dummy); + } + Settings.clearTask(taskIndex); + clearTaskCache(taskIndex); // Invalidate any cached values. + ExtraTaskSettings.clear(); + ExtraTaskSettings.TaskIndex = taskIndex; + + if (save) { + addLog(LOG_LEVEL_INFO, F("taskClear() save settings")); + SaveTaskSettings(taskIndex); + SaveSettings(); + } +} + +/********************************************************************************************\ + check the program memory hash + The const MD5_MD5_MD5_MD5_BoundariesOfTheSegmentsGoHere... needs to remain unchanged as it will be replaced by + - 16 bytes md5 hash, followed by + - 4 * uint32_t start of memory segment 1-4 + - 4 * uint32_t end of memory segment 1-4 + currently there are only two segemts included in the hash. Unused segments have start adress 0. + Execution time 520kb @80Mhz: 236ms + Returns: 0 if hash compare fails, number of checked bytes otherwise. + The reference hash is calculated by a .py file and injected into the binary. + Caution: currently the hash sits in an unchecked segment. If it ever moves to a checked segment, make sure + it is excluded from the calculation ! + \*********************************************************************************************/ +#if defined(ARDUINO_ESP8266_RELEASE_2_3_0) +void dump(uint32_t addr) { // Seems already included in core 2.4 ... + serialPrint(String(addr, HEX)); + serialPrint(": "); + + for (uint32_t a = addr; a < addr + 16; a++) + { + serialPrint(String(pgm_read_byte(a), HEX)); + serialPrint(" "); + } + serialPrintln(); +} + +#endif // if defined(ARDUINO_ESP8266_RELEASE_2_3_0) + +/* + uint32_t progMemMD5check(){ + checkRAM(F("progMemMD5check")); + #define BufSize 10 + uint32_t calcBuffer[BufSize]; + CRCValues.numberOfCRCBytes = 0; + memcpy (calcBuffer,CRCValues.compileTimeMD5,16); // is there still the dummy in memory + ? - the dummy needs to be replaced by the real md5 after linking. + if( memcmp (calcBuffer, "MD5_MD5_MD5_",12)==0){ // do not memcmp with CRCdummy + directly or it will get optimized away. + addLog(LOG_LEVEL_INFO, F("CRC : No program memory checksum found. Check output of crc2.py")); + return 0; + } + MD5Builder md5; + md5.begin(); + for (int l = 0; l<4; l++){ // check max segments, if the + pointer is not 0 + uint32_t *ptrStart = (uint32_t *)&CRCValues.compileTimeMD5[16+l*4]; + uint32_t *ptrEnd = (uint32_t *)&CRCValues.compileTimeMD5[16+4*4+l*4]; + if ((*ptrStart) == 0) break; // segment not used. + for (uint32_t i = *ptrStart; i< (*ptrEnd) ; i=i+sizeof(calcBuffer)){ // "<" includes last byte + for (int buf = 0; buf < BufSize; buf ++){ + calcBuffer[buf] = pgm_read_dword((uint32_t*)i+buf); // read 4 bytes + CRCValues.numberOfCRCBytes+=sizeof(calcBuffer[0]); + } + md5.add(reinterpret_cast(&calcBuffer[0]),(*ptrEnd-i) 0 ? (S < 1 ? S : 1) : 0; // clamp S and I to interval [0,1] + I = I / 100; + I = I > 0 ? (I < 1 ? I : 1) : 0; + + // Math! Thanks in part to Kyle Miller. + if (H < 2.09439f) { + r = 255 * I / 3 * (1 + S * cosf(H) / cosf(1.047196667f - H)); + g = 255 * I / 3 * (1 + S * (1 - cosf(H) / cosf(1.047196667f - H))); + b = 255 * I / 3 * (1 - S); + } else if (H < 4.188787f) { + H = H - 2.09439f; + g = 255 * I / 3 * (1 + S * cosf(H) / cosf(1.047196667f - H)); + b = 255 * I / 3 * (1 + S * (1 - cosf(H) / cosf(1.047196667f - H))); + r = 255 * I / 3 * (1 - S); + } else { + H = H - 4.188787f; + b = 255 * I / 3 * (1 + S * cosf(H) / cosf(1.047196667f - H)); + r = 255 * I / 3 * (1 + S * (1 - cosf(H) / cosf(1.047196667f - H))); + g = 255 * I / 3 * (1 - S); + } + rgb[0] = r; + rgb[1] = g; + rgb[2] = b; + */ +} + +// uses H 0..360 S 1..100 I/V 1..100 (according to homie convention) +// Source https://blog.saikoled.com/post/44677718712/how-to-convert-from-hsi-to-rgb-white +void HSV2RGBW(float H, float S, float I, int rgbw[4]) { + H = fmod(H, 360); // cycle H around to 0-360 degrees + constexpr float deg2rad = 3.14159f / 180.0f; + H *= deg2rad; // Convert to radians. + S = S / 100; + S = S > 0 ? (S < 1 ? S : 1) : 0; // clamp S and I to interval [0,1] + I = I / 100; + I = I > 0 ? (I < 1 ? I : 1) : 0; + + #define RGB_ORDER 0 + #define GBR_ORDER 1 + #define BRG_ORDER 2 + + int order = RGB_ORDER; + + constexpr float ANGLE_120_DEG = 120.0f * deg2rad; + constexpr float ANGLE_240_DEG = 240.0f * deg2rad; + constexpr float ANGLE_60_DEG = 60.0f * deg2rad; + + if (H < ANGLE_120_DEG) { + order = RGB_ORDER; + } else if (H < ANGLE_240_DEG) { + H = H - ANGLE_120_DEG; + order = GBR_ORDER; + } else { + H = H - ANGLE_240_DEG; + order = BRG_ORDER; + } + const float cos_h = cosf(H); + const float cos_1047_h = cosf(ANGLE_60_DEG - H); + + const int r = S * 255 * I / 3 * (1 + cos_h / cos_1047_h); + const int g = S * 255 * I / 3 * (1 + (1 - cos_h / cos_1047_h)); + const int b = 0; + rgbw[3] = 255 * (1 - S) * I; + + if (RGB_ORDER == order) { + rgbw[0] = r; + rgbw[1] = g; + rgbw[2] = b; + } else if (GBR_ORDER == order) { + rgbw[0] = g; + rgbw[1] = b; + rgbw[2] = r; + } else if (BRG_ORDER == order) { + rgbw[0] = b; + rgbw[1] = r; + rgbw[2] = g; + } +} + +// Convert RGB Color to HSV Color +void RGB2HSV(uint8_t r, uint8_t g, uint8_t b, float hsv[3]) { + const float rf = static_cast(r) / 255.0f; + const float gf = static_cast(g) / 255.0f; + const float bf = static_cast(b) / 255.0f; + float maxval = rf; + + if (gf > maxval) { maxval = gf; } + + if (bf > maxval) { maxval = bf; } + float minval = rf; + + if (gf < minval) { minval = gf; } + + if (bf < minval) { minval = bf; } + float h = 0.0f, s, v = maxval; + float f = maxval - minval; + + s = maxval == 0.0f ? 0.0f : f / maxval; + + if (maxval == minval) { + h = 0.0f; // achromatic + } else { + if (maxval == rf) { + h = (gf - bf) / f + (gf < bf ? 6.0f : 0.0f); + } else if (maxval == gf) { + h = (bf - rf) / f + 2.0f; + } else if (maxval == bf) { + h = (rf - gf) / f + 4.0f; + } + h /= 6.0f; + } + + hsv[0] = h * 360.0f; + hsv[1] = s * 255.0f; + hsv[2] = v * 255.0f; +} + + + +float getCPUload() { + return 100.0f - Scheduler.getIdleTimePct(); +} + +int getLoopCountPerSec() { + return loopCounterLast / 30; +} + +int getUptimeMinutes() { + return wdcounter / 2; +} + +/****************************************************************************** + * scan an int array of specified size for a value + *****************************************************************************/ +bool intArrayContains(const int arraySize, const int array[], const int& value) { + for (int i = 0; i < arraySize; i++) { + if (array[i] == value) { return true; } + } + return false; +} + +bool intArrayContains(const int arraySize, const uint8_t array[], const uint8_t& value) { + for (int i = 0; i < arraySize; i++) { + if (array[i] == value) { return true; } + } + return false; +} + +#ifndef BUILD_NO_RAM_TRACKER +void logMemUsageAfter(const __FlashStringHelper *function, int value) { + // Store free memory in an int, as subtracting may sometimes result in negative value. + // The recorded used memory is not an exact value, as background (or interrupt) tasks may also allocate or free heap memory. + static int last_freemem = ESP.getFreeHeap(); + const int freemem_end = ESP.getFreeHeap(); + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String log; + if (log.reserve(128)) { + log = F("After "); + log += function; + + if (value >= 0) { + log += value; + } + + while (log.length() < 30) { log += ' '; } + log += F("Free mem after: "); + log += freemem_end; + + while (log.length() < 55) { log += ' '; } + log += F("diff: "); + log += last_freemem - freemem_end; + addLogMove(LOG_LEVEL_DEBUG, log); + } + } + + last_freemem = freemem_end; +} + +#endif // ifndef BUILD_NO_RAM_TRACKER diff --git a/src/src/Helpers/Networking.cpp b/src/src/Helpers/Networking.cpp index 11308e9d09..668872220e 100644 --- a/src/src/Helpers/Networking.cpp +++ b/src/src/Helpers/Networking.cpp @@ -264,6 +264,7 @@ void checkUDP() if (runningUPDCheck) { return; } + START_TIMER runningUPDCheck = true; @@ -279,6 +280,11 @@ void checkUDP() if (portUDP.remotePort() == 123) { // unexpected NTP reply, drop for now... + while (portUDP.available()) { + // Do not call portUDP.flush() as that's meant to sending the packet (on ESP8266) + portUDP.read(); + } + runningUPDCheck = false; return; } @@ -289,6 +295,8 @@ void checkUDP() // and then crash due to memory allocation failures if ((packetSize >= 2) && (packetSize < UDP_PACKETSIZE_MAX)) { // Allocate buffer to process packet. + // Resize it to be 1 byte larger so we can 0-terminate it + // in case it is some plain text string std::vector packetBuffer; packetBuffer.resize(packetSize + 1); @@ -380,6 +388,7 @@ void checkUDP() portUDP.read(); } runningUPDCheck = false; + STOP_TIMER(CHECK_UDP); } /*********************************************************************************************\ diff --git a/src/src/Helpers/PeriodicalActions.cpp b/src/src/Helpers/PeriodicalActions.cpp index f3625ade40..6e1f90b967 100644 --- a/src/src/Helpers/PeriodicalActions.cpp +++ b/src/src/Helpers/PeriodicalActions.cpp @@ -491,7 +491,7 @@ void prepareShutdown(IntendedRebootReason_e reason) ESPEASY_FS.end(); process_serialWriteBuffer(); delay(100); // give the node time to flush all before reboot or sleep - node_time.now(); + node_time.now_(); Scheduler.markIntendedReboot(reason); saveToRTC(); } diff --git a/src/src/Helpers/StringConverter.cpp b/src/src/Helpers/StringConverter.cpp index b68e0bebad..617b2aafbd 100644 --- a/src/src/Helpers/StringConverter.cpp +++ b/src/src/Helpers/StringConverter.cpp @@ -1343,12 +1343,13 @@ void parseEventVariables(String& s, struct EventStruct *event, bool useURLencode const bool vname_found = s.indexOf(F("%vname")) != -1; if (vname_found) { - for (uint8_t i = 0; i < 4; ++i) { + const uint8_t valueCount = getValueCountForTask(event->TaskIndex); + for (uint8_t i = 0; i < valueCount; ++i) { String vname = F("%vname"); vname += (i + 1); vname += '%'; - SMART_REPL(vname, getTaskValueName(event->TaskIndex, i)); + SMART_REPL(vname, Cache.getTaskDeviceValueName(event->TaskIndex, i)); } } } diff --git a/src/src/Helpers/StringConverter_Numerical.cpp b/src/src/Helpers/StringConverter_Numerical.cpp index 4c8a0e13a7..9cf1eeff3a 100644 --- a/src/src/Helpers/StringConverter_Numerical.cpp +++ b/src/src/Helpers/StringConverter_Numerical.cpp @@ -6,6 +6,7 @@ #include "../Helpers/StringConverter.h" + /********************************************************************************************\ Convert a char string to integer \*********************************************************************************************/ @@ -25,11 +26,10 @@ unsigned long str2int(const char *string) \*********************************************************************************************/ String toString(const float& value, unsigned int decimalPlaces) { - START_TIMER String sValue; #ifndef LIMIT_BUILD_SIZE - if (decimalPlaces == 0) { + if (decimalPlaces == 0 && isValidFloat(value)) { if ((value > -2e9f) && (value < 2e9f)) { const int32_t l_value = static_cast(roundf(value)); sValue = l_value; @@ -39,7 +39,7 @@ String toString(const float& value, unsigned int decimalPlaces) sValue = ll2String(ll_value); } if (sValue.length() > 0) { - return std::move(sValue); + return sValue; } } #endif // ifndef LIMIT_BUILD_SIZE @@ -61,7 +61,7 @@ String toString(const float& value, unsigned int decimalPlaces) #endif */ sValue.trim(); - return std::move(sValue); + return sValue; } String ull2String(uint64_t value, uint8_t base) { @@ -69,7 +69,7 @@ String ull2String(uint64_t value, uint8_t base) { if (value == 0) { res = '0'; - return std::move(res); + return res; } while (value > 0) { @@ -88,7 +88,7 @@ String ull2String(uint64_t value, uint8_t base) { --endpos; } - return std::move(res); + return res; } String ll2String(int64_t value, uint8_t base) { @@ -120,7 +120,7 @@ String trimTrailingZeros(const String& value) { res.trim(); } } - return std::move(res); + return res; } @@ -165,7 +165,7 @@ String doubleToString(const double& value, unsigned int decimalPlaces, bool trim if (trimTrailingZeros_b) { return trimTrailingZeros(res); } - return std::move(res); + return res; } #endif @@ -178,7 +178,7 @@ String floatToString(const float& value, if (trimTrailingZeros_b) { return trimTrailingZeros(res); } - return std::move(res); + return res; } diff --git a/src/src/Helpers/StringParser.cpp b/src/src/Helpers/StringParser.cpp index d1ca5bc60c..9d7ba42bbc 100644 --- a/src/src/Helpers/StringParser.cpp +++ b/src/src/Helpers/StringParser.cpp @@ -1,787 +1,787 @@ -#include "../Helpers/StringParser.h" - -#include "../../_Plugin_Helper.h" - -#include "../Commands/GPIO.h" - -#include "../DataStructs/TimingStats.h" - -#include "../ESPEasyCore/ESPEasyRules.h" - -#include "../Globals/Cache.h" -#include "../Globals/Plugins_other.h" -#include "../Globals/RulesCalculate.h" -#include "../Globals/RuntimeData.h" - -#include "../Helpers/_CPlugin_init.h" -#include "../Helpers/ESPEasy_math.h" -#include "../Helpers/ESPEasy_Storage.h" -#include "../Helpers/Misc.h" -#include "../Helpers/Numerical.h" -#include "../Helpers/StringConverter.h" -#include "../Helpers/StringGenerator_GPIO.h" - - - -/********************************************************************************************\ - Parse string template - \*********************************************************************************************/ -String parseTemplate(String& tmpString) -{ - return parseTemplate(tmpString, false); -} - -String parseTemplate(String& tmpString, bool useURLencode) -{ - return parseTemplate_padded(tmpString, 0, useURLencode); -} - -String parseTemplate_padded(String& tmpString, uint8_t minimal_lineSize) -{ - return parseTemplate_padded(tmpString, minimal_lineSize, false); -} - -String parseTemplate_padded(String& tmpString, uint8_t minimal_lineSize, bool useURLencode) -{ - #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("parseTemplate_padded")); - #endif // ifndef BUILD_NO_RAM_TRACKER - START_TIMER; - - // Keep current loaded taskSettings to restore at the end. - const taskIndex_t currentTaskIndex = ExtraTaskSettings.TaskIndex; - String newString; - newString.reserve(minimal_lineSize); // Our best guess of the new size. - - if (parseTemplate_CallBack_ptr != nullptr) { - parseTemplate_CallBack_ptr(tmpString, useURLencode); - } - parseSystemVariables(tmpString, useURLencode); - - - int startpos = 0; - int lastStartpos = 0; - int endpos = 0; - { - String deviceName, valueName, format; - - while (findNextDevValNameInString(tmpString, startpos, endpos, deviceName, valueName, format)) { - // First copy all upto the start of the [...#...] part to be replaced. - newString += tmpString.substring(lastStartpos, startpos); - - // deviceName is lower case, so we can compare literal string (no need for equalsIgnoreCase) - const bool devNameEqInt = equals(deviceName, F("int")); - if (devNameEqInt || equals(deviceName, F("var"))) - { - // Address an internal variable either as float or as int - // For example: Let,10,[VAR#9] - uint32_t varNum; - - if (validUIntFromString(valueName, varNum)) { - unsigned char nr_decimals = maxNrDecimals_fpType(getCustomFloatVar(varNum)); - bool trimTrailingZeros = true; - - if (devNameEqInt) { - nr_decimals = 0; - } else if (!format.isEmpty()) - { - // There is some formatting here, so do not throw away decimals - trimTrailingZeros = false; - } - #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - String value = doubleToString(getCustomFloatVar(varNum), nr_decimals, trimTrailingZeros); - #else - String value = floatToString(getCustomFloatVar(varNum), nr_decimals, trimTrailingZeros); - #endif - transformValue( - newString, - minimal_lineSize, - std::move(value), - format, - tmpString); - } - } - else if (equals(deviceName, F("plugin"))) - { - // Handle a plugin request. - // For example: "[Plugin#GPIO#Pinstate#N]" - // The command is stored in valueName & format - String command = strformat(F("%s#%s"), valueName.c_str(), format.c_str()); - command.replace('#', ','); - - if (getGPIOPinStateValues(command)) { - newString += command; - } - /* @giig1967g - if (PluginCall(PLUGIN_REQUEST, 0, command)) - { - // Do not call transformValue here. - // The "format" is not empty so must not call the formatter function. - newString += command; - } - */ - } - else - { - // Address a value from a plugin. - // For example: "[bme#temp]" - // If value name is unknown, run a PLUGIN_GET_CONFIG_VALUE command. - // For example: "[#getLevel]" - taskIndex_t taskIndex = findTaskIndexByName(deviceName, true); // Check for enabled/disabled is done separately - - if (validTaskIndex(taskIndex)) { - bool isHandled = false; - if (Settings.TaskDeviceEnabled[taskIndex]) { - uint8_t valueNr = findDeviceValueIndexByName(valueName, taskIndex); - - if (valueNr != VARS_PER_TASK) { - // here we know the task and value, so find the uservar - // Try to format and transform the values - bool isvalid; - String value = formatUserVar(taskIndex, valueNr, isvalid); - - if (isvalid) { - transformValue(newString, minimal_lineSize, std::move(value), format, tmpString); - isHandled = true; - } - } else { - // try if this is a get config request - struct EventStruct TempEvent(taskIndex); - String tmpName = valueName; - - if (PluginCall(PLUGIN_GET_CONFIG_VALUE, &TempEvent, tmpName)) - { - transformValue(newString, minimal_lineSize, std::move(tmpName), format, tmpString); - isHandled = true; - } - } - } - if (!isHandled && valueName.startsWith(F("settings."))) { // Task settings values - String value; - if (valueName.endsWith(F(".enabled"))) { // Task state - value = Settings.TaskDeviceEnabled[taskIndex] ? '1' : '0'; - } else if (valueName.endsWith(F(".interval"))) { // Task interval - value = Settings.TaskDeviceTimer[taskIndex]; - } else if (valueName.endsWith(F(".valuecount"))) { // Task value count - value = getValueCountForTask(taskIndex); - } else if ((valueName.indexOf(F(".controller")) == 8) && valueName.length() >= 20) { // Task controller values - String ctrl = valueName.substring(19, 20); - int32_t ctrlNr = 0; - if (validIntFromString(ctrl, ctrlNr) && (ctrlNr >= 1) && (ctrlNr <= CONTROLLER_MAX) && - Settings.ControllerEnabled[ctrlNr - 1]) { // Controller nr. valid and enabled - if (valueName.endsWith(F(".enabled"))) { // Task-controller enabled - value = Settings.TaskDeviceSendData[ctrlNr - 1][taskIndex]; - } else if (valueName.endsWith(F(".idx"))) { // Task-controller idx value - protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(ctrlNr - 1); - - if (validProtocolIndex(ProtocolIndex) && - getProtocolStruct(ProtocolIndex).usesID && (Settings.Protocol[ctrlNr - 1] != 0)) { - value = Settings.TaskDeviceID[ctrlNr - 1][taskIndex]; - } - } - } - } - if (!value.isEmpty()) { - transformValue(newString, minimal_lineSize, std::move(value), format, tmpString); - // isHandled = true; - } - } - } - } - - - // Conversion is done (or impossible) for the found "[...#...]" - // Continue with the next one. - lastStartpos = endpos + 1; - startpos = endpos + 1; - - // This may have taken some time, so call delay() - delay(0); - } - } - - // Copy the rest of the string (or all if no replacements were done) - newString += tmpString.substring(lastStartpos); - #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("parseTemplate2")); - #endif // ifndef BUILD_NO_RAM_TRACKER - - // Restore previous loaded taskSettings - if (validTaskIndex(currentTaskIndex)) - { - LoadTaskSettings(currentTaskIndex); - } - - parseStandardConversions(newString, useURLencode); - - // process other markups as well - parse_string_commands(newString); - - // padding spaces - while (newString.length() < minimal_lineSize) { - newString += ' '; - } - - STOP_TIMER(PARSE_TEMPLATE_PADDED); - #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("parseTemplate3")); - #endif // ifndef BUILD_NO_RAM_TRACKER - return newString; -} - -/********************************************************************************************\ - Transform values - \*********************************************************************************************/ - -bool isTransformString(char c, bool logicVal, String& strValue) -{ - const __FlashStringHelper * value = F(""); - char value_ch = '\0'; - switch (c) { - case 'O': - value = logicVal == 0 ? F("OFF") : F(" ON"); // (equivalent to XOR operator) - break; - case 'C': - value = logicVal == 0 ? F("CLOSE") : F(" OPEN"); - break; - case 'c': - value = logicVal == 0 ? F("CLOSED") : F(" OPEN"); - break; - case 'M': - value = logicVal == 0 ? F("AUTO") : F(" MAN"); - break; - case 'm': - value_ch = logicVal == 0 ? 'A' : 'M'; - break; - case 'H': - value = logicVal == 0 ? F("COLD") : F(" HOT"); - break; - case 'U': - value = logicVal == 0 ? F("DOWN") : F(" UP"); - break; - case 'u': - value_ch = logicVal == 0 ? 'D' : 'U'; - break; - case 'Y': - value = logicVal == 0 ? F(" NO") : F("YES"); - break; - case 'y': - value_ch = logicVal == 0 ? 'N' : 'Y'; - break; - case 'X': - value_ch = logicVal == 0 ? 'O' : 'X'; - break; - case 'I': - value = logicVal == 0 ? F("OUT") : F(" IN"); - break; - case 'L': - value = logicVal == 0 ? F(" LEFT") : F("RIGHT"); - break; - case 'l': - value_ch = logicVal == 0 ? 'L' : 'R'; - break; - case 'Z': // return "0" or "1" - value_ch = logicVal == 0 ? '0' : '1'; - break; - default: - return false; - } - if (value_ch != '\0') { - strValue = value_ch; - } else { - strValue = value; - } - return true; -} - - -// Syntax: [task#value#transformation#justification] -// valueFormat="transformation#justification" -void transformValue( - String & newString, - uint8_t lineSize, - String value, - String & valueFormat, - const String& tmpString) -{ - // FIXME TD-er: This function does append to newString and uses its length to perform right aling. - // Is this the way it is intended to use? - #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("transformValue")); - #endif // ifndef BUILD_NO_RAM_TRACKER - - // start changes by giig1967g - 2018-04-20 - // Syntax: [task#value#transformation#justification] - // valueFormat="transformation#justification" - if (valueFormat.length() > 0) // do the checks only if a Format is defined to optimize loop - { - String valueJust; - - int hashtagIndex = valueFormat.indexOf('#'); - - if (hashtagIndex >= 0) - { - valueJust = valueFormat.substring(hashtagIndex + 1); // Justification part - valueFormat = valueFormat.substring(0, hashtagIndex); // Transformation part - } - - // valueFormat="transformation" - // valueJust="justification" - if (valueFormat.length() > 0) // do the checks only if a Format is defined to optimize loop - { - int logicVal = 0; - ESPEASY_RULES_FLOAT_TYPE valFloat{}; - - if (validDoubleFromString(value, valFloat)) - { - // to be used for binary values (0 or 1) - logicVal = lround(static_cast(valFloat)) == 0 ? 0 : 1; - } else { - if (value.length() > 0) { - logicVal = 1; - } - } - String tempValueFormat = valueFormat; - { - const int invertedIndex = tempValueFormat.indexOf('!'); - - if (invertedIndex != -1) { - // We must invert the value. - logicVal = (logicVal == 0) ? 1 : 0; - - // Remove the '!' from the string. - tempValueFormat.remove(invertedIndex, 1); - } - } - - const int rightJustifyIndex = tempValueFormat.indexOf('R'); - const bool rightJustify = rightJustifyIndex >= 0 ? 1 : 0; - - if (rightJustify) { - tempValueFormat.remove(rightJustifyIndex, 1); - } - - const int tempValueFormatLength = tempValueFormat.length(); - - // Check Transformation syntax - if (tempValueFormatLength > 0) - { - if (!isTransformString(tempValueFormat[0], logicVal, value)) { - switch (tempValueFormat[0]) - { - case 'V': // value = value without transformations - break; - case 'p': // Password hide using asterisks or custom character: pc - { - char maskChar = '*'; - - if (tempValueFormatLength > 1) - { - maskChar = tempValueFormat[1]; - } - - if (equals(value, '0')) { - value = String(); - } else { - const int valueLength = value.length(); - - for (int i = 0; i < valueLength; i++) { - value[i] = maskChar; - } - } - break; - } - case 'D': // Dx.y min 'x' digits zero filled & 'y' decimal fixed digits - case 'd': // like above but with spaces padding - { - int x = 0; - int y = 0; - - switch (tempValueFormatLength) - { - case 2: // Dx - - if (isDigit(tempValueFormat[1])) - { - x = static_cast(tempValueFormat[1]) - '0'; - } - break; - case 3: // D.y - - if ((tempValueFormat[1] == '.') && isDigit(tempValueFormat[2])) - { - y = static_cast(tempValueFormat[2]) - '0'; - } - break; - case 4: // Dx.y - - if (isDigit(tempValueFormat[1]) && (tempValueFormat[2] == '.') && isDigit(tempValueFormat[3])) - { - x = static_cast(tempValueFormat[1]) - '0'; - y = static_cast(tempValueFormat[3]) - '0'; - } - break; - case 1: // D - default: // any other combination x=0; y=0; - break; - } - bool trimTrailingZeros = false; -#if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - value = doubleToString(valFloat, y, trimTrailingZeros); -#else - value = floatToString(valFloat, y, trimTrailingZeros); -#endif - int indexDot = value.indexOf('.'); - - if (indexDot == -1) { - indexDot = value.length(); - } - - for (uint8_t f = 0; f < (x - indexDot); f++) { - value = (tempValueFormat[0] == 'd' ? ' ' : '0') + value; - } - break; - } - case 'F': // FLOOR (round down) - #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - value = static_cast(floor(valFloat)); - #else - value = static_cast(floorf(valFloat)); - #endif - break; - case 'E': // CEILING (round up) - #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - value = static_cast(ceil(valFloat)); - #else - value = static_cast(ceilf(valFloat)); - #endif - break; - default: - value = F("ERR"); - break; - } - } - - // Check Justification syntax - const int valueJustLength = valueJust.length(); - - if (valueJustLength > 0) // do the checks only if a Justification is defined to optimize loop - { - value.trim(); // remove right justification spaces for backward compatibility - - switch (valueJust[0]) - { - case 'P': // Prefix Fill with n spaces: Pn - - if (valueJustLength > 1) - { - if (isDigit(valueJust[1])) // Check Pn where n is between 0 and 9 - { - int filler = valueJust[1] - value.length() - '0'; // char '0' = 48; char '9' = 58 - - for (uint8_t f = 0; f < filler; f++) { - newString += ' '; - } - } - } - break; - case 'S': // Suffix Fill with n spaces: Sn - - if (valueJustLength > 1) - { - if (isDigit(valueJust[1])) // Check Sn where n is between 0 and 9 - { - int filler = valueJust[1] - value.length() - '0'; // 48 - - for (uint8_t f = 0; f < filler; f++) { - value += ' '; - } - } - } - break; - case 'L': // left part of the string - - if (valueJustLength > 1) - { - if (isDigit(valueJust[1])) // Check n where n is between 0 and 9 - { - value = value.substring(0, static_cast(valueJust[1]) - '0'); - } - } - break; - case 'R': // Right part of the string - - if (valueJustLength > 1) - { - if (isDigit(valueJust[1])) // Check n where n is between 0 and 9 - { - value = value.substring(std::max(0, static_cast(value.length()) - (static_cast(valueJust[1]) - '0'))); - } - } - break; - case 'U': // Substring Ux.y where x=firstChar and y=number of characters - - if (valueJustLength > 1) - { - if (isDigit(valueJust[1]) && (valueJust[2] == '.') && isDigit(valueJust[3]) && (valueJust[1] > '0') && (valueJust[3] > '0')) - { - value = value.substring(std::min(static_cast(value.length()), static_cast(valueJust[1]) - '0' - 1), - static_cast(valueJust[1]) - '0' - 1 + static_cast(valueJust[3]) - '0'); - } - else - { - newString += F("ERR"); - } - } - break; - case 'C': // Capitalize First Word-Character value (space/period are checked) - - if (value.length() > 0) { - value.toLowerCase(); - bool nextCapital = true; - - for (uint8_t i = 0; i < value.length(); i++) { - if (nextCapital) { - value[i] = toupper(value[i]); - } - nextCapital = (value[i] == ' ' || value[i] == '.'); // Very simple, capitalize-first-after-space/period - } - } - break; - case 'u': // Uppercase - value.toUpperCase(); - break; - case 'l': // Lowercase - value.toLowerCase(); - break; - default: - newString += F("ERR"); - break; - } - } - } - - if (rightJustify) - { - int filler = lineSize - newString.length() - value.length() - tmpString.length(); - - for (uint8_t f = 0; f < filler; f++) { - newString += ' '; - } - } - { -#ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String logFormatted = F("DEBUG: Formatted String='"); - logFormatted += newString; - logFormatted += value; - logFormatted += '\''; - addLogMove(LOG_LEVEL_DEBUG, logFormatted); - } -#endif // ifndef BUILD_NO_DEBUG - } - } - } - - // end of changes by giig1967g - 2018-04-18 - - newString += value; - { -#ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG_DEV)) { - String logParsed = F("DEBUG DEV: Parsed String='"); - logParsed += newString; - logParsed += '\''; - addLogMove(LOG_LEVEL_DEBUG_DEV, logParsed); - } -#endif // ifndef BUILD_NO_DEBUG - } - #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("transformValue2")); - #endif // ifndef BUILD_NO_RAM_TRACKER -} - -// Find the first (enabled) task with given name -// Return INVALID_TASK_INDEX when not found, else return taskIndex -taskIndex_t findTaskIndexByName(String deviceName, bool allowDisabled) -{ - deviceName.toLowerCase(); - // cache this, since LoadTaskSettings does take some time. - auto result = Cache.taskIndexName.find(deviceName); - - if (result != Cache.taskIndexName.end()) { - return result->second; - } - - for (taskIndex_t taskIndex = 0; taskIndex < TASKS_MAX; taskIndex++) - { - if (Settings.TaskDeviceEnabled[taskIndex] || allowDisabled) { - String taskDeviceName = getTaskDeviceName(taskIndex); - - if (!taskDeviceName.isEmpty()) - { - // Use entered taskDeviceName can have any case, so compare case insensitive. - if (deviceName.equalsIgnoreCase(taskDeviceName)) - { - Cache.taskIndexName.emplace( - std::make_pair( - std::move(deviceName), - taskIndex)); - return taskIndex; - } - } - } - } - return INVALID_TASK_INDEX; -} - -// Find the first device value index of a taskIndex. -// Return VARS_PER_TASK if none found. -uint8_t findDeviceValueIndexByName(const String& valueName, taskIndex_t taskIndex) -{ - const deviceIndex_t deviceIndex = getDeviceIndex_from_TaskIndex(taskIndex); - - if (!validDeviceIndex(deviceIndex)) { return VARS_PER_TASK; } - - #ifdef USE_SECOND_HEAP - HeapSelectDram ephemeral; - #endif - - - // cache this, since LoadTaskSettings does take some time. - // We need to use a cache search key including the taskIndex, - // to allow several tasks to have the same value names. - String cache_valueName = strformat( - F("%s#%d"), // The '#' cannot exist in a value name, use it in the cache key. - valueName.c_str(), - static_cast(taskIndex)); - cache_valueName.toLowerCase(); // No need to store multiple versions of the same entry with only different case. - - auto result = Cache.taskIndexValueName.find(cache_valueName); - - if (result != Cache.taskIndexValueName.end()) { - return result->second; - } - const uint8_t valCount = getValueCountForTask(taskIndex); - - for (uint8_t valueNr = 0; valueNr < valCount; valueNr++) - { - // Check case insensitive, since the user entered value name can have any case. - if (valueName.equalsIgnoreCase(getTaskValueName(taskIndex, valueNr))) - { - Cache.taskIndexValueName.emplace( - std::make_pair( - std::move(cache_valueName), - valueNr)); - return valueNr; - } - } - return VARS_PER_TASK; -} - -// Find positions of [...#...] in the given string. -// Only update pos values on success. -// Return true when found. -bool findNextValMarkInString(const String& input, int& startpos, int& hashpos, int& endpos) { - int tmpStartpos = input.indexOf('[', startpos); - - if (tmpStartpos == -1) { return false; } - const int tmpHashpos = input.indexOf('#', tmpStartpos); - - if (tmpHashpos == -1) { return false; } - - // We found a hash position, check if there is another '[' inbetween. - for (int i = tmpStartpos; i < tmpHashpos; ++i) { - if (input[i] == '[') { - tmpStartpos = i; - } - } - - const int tmpEndpos = input.indexOf(']', tmpStartpos); - - if (tmpEndpos == -1) { return false; } - - if (tmpHashpos >= tmpEndpos) { - return false; - } - - hashpos = tmpHashpos; - startpos = tmpStartpos; - endpos = tmpEndpos; - return true; -} - -// Find [deviceName#valueName] or [deviceName#valueName#format] -// DeviceName and valueName will be returned in lower case. -// Format may contain case sensitive formatting syntax. -bool findNextDevValNameInString(const String& input, int& startpos, int& endpos, String& deviceName, String& valueName, String& format) { - int hashpos; - - if (!findNextValMarkInString(input, startpos, hashpos, endpos)) { return false; } - - move_special(deviceName, input.substring(startpos + 1, hashpos)); - move_special(valueName , input.substring(hashpos + 1, endpos)); - hashpos = valueName.indexOf('#'); - - if (hashpos != -1) { - // Found an extra '#' in the valueName, will split valueName and format. - move_special(format, valueName.substring(hashpos + 1)); - move_special(valueName, valueName.substring(0, hashpos)); - } else { - format = String(); - } - deviceName.toLowerCase(); - valueName.toLowerCase(); - return true; -} - -/********************************************************************************************\ - Check to see if a given argument is a valid taskIndex (argc = 0 => command) - \*********************************************************************************************/ -taskIndex_t parseCommandArgumentTaskIndex(const String& string, unsigned int argc) -{ - taskIndex_t taskIndex = INVALID_TASK_INDEX; - const int ti = parseCommandArgumentInt(string, argc); - - if (ti > 0) { - // Task Index used as argument in commands start at 1. - taskIndex = static_cast(ti - 1); - } - return taskIndex; -} - -/********************************************************************************************\ - Get int from command argument (argc = 0 => command) - \*********************************************************************************************/ -int parseCommandArgumentInt(const String& string, unsigned int argc, - int errorValue) -{ - int value = 0; - - if (argc > 0) { - // No need to check for the command (argc == 0) - String TmpStr; - - if (GetArgv(string.c_str(), TmpStr, argc + 1)) { - value = CalculateParam(TmpStr, errorValue); - } - } - return value; -} - -/********************************************************************************************\ - Parse a command string to event struct - \*********************************************************************************************/ -void parseCommandString(struct EventStruct *event, const String& string) -{ - #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("parseCommandString")); - #endif // ifndef BUILD_NO_RAM_TRACKER - event->Par1 = parseCommandArgumentInt(string, 1); - event->Par2 = parseCommandArgumentInt(string, 2); - event->Par3 = parseCommandArgumentInt(string, 3); - event->Par4 = parseCommandArgumentInt(string, 4); - event->Par5 = parseCommandArgumentInt(string, 5); -} +#include "../Helpers/StringParser.h" + +#include "../../_Plugin_Helper.h" + +#include "../Commands/GPIO.h" + +#include "../DataStructs/TimingStats.h" + +#include "../ESPEasyCore/ESPEasyRules.h" + +#include "../Globals/Cache.h" +#include "../Globals/Plugins_other.h" +#include "../Globals/RulesCalculate.h" +#include "../Globals/RuntimeData.h" + +#include "../Helpers/_CPlugin_init.h" +#include "../Helpers/ESPEasy_math.h" +#include "../Helpers/ESPEasy_Storage.h" +#include "../Helpers/Misc.h" +#include "../Helpers/Numerical.h" +#include "../Helpers/StringConverter.h" +#include "../Helpers/StringGenerator_GPIO.h" + + + +/********************************************************************************************\ + Parse string template + \*********************************************************************************************/ +String parseTemplate(String& tmpString) +{ + return parseTemplate(tmpString, false); +} + +String parseTemplate(String& tmpString, bool useURLencode) +{ + return parseTemplate_padded(tmpString, 0, useURLencode); +} + +String parseTemplate_padded(String& tmpString, uint8_t minimal_lineSize) +{ + return parseTemplate_padded(tmpString, minimal_lineSize, false); +} + +String parseTemplate_padded(String& tmpString, uint8_t minimal_lineSize, bool useURLencode) +{ + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("parseTemplate_padded")); + #endif // ifndef BUILD_NO_RAM_TRACKER + START_TIMER; + + // Keep current loaded taskSettings to restore at the end. + const taskIndex_t currentTaskIndex = ExtraTaskSettings.TaskIndex; + String newString; + newString.reserve(minimal_lineSize); // Our best guess of the new size. + + if (parseTemplate_CallBack_ptr != nullptr) { + parseTemplate_CallBack_ptr(tmpString, useURLencode); + } + parseSystemVariables(tmpString, useURLencode); + + + int startpos = 0; + int lastStartpos = 0; + int endpos = 0; + { + String deviceName, valueName, format; + + while (findNextDevValNameInString(tmpString, startpos, endpos, deviceName, valueName, format)) { + // First copy all upto the start of the [...#...] part to be replaced. + newString += tmpString.substring(lastStartpos, startpos); + + // deviceName is lower case, so we can compare literal string (no need for equalsIgnoreCase) + const bool devNameEqInt = equals(deviceName, F("int")); + if (devNameEqInt || equals(deviceName, F("var"))) + { + // Address an internal variable either as float or as int + // For example: Let,10,[VAR#9] + uint32_t varNum; + + if (validUIntFromString(valueName, varNum)) { + unsigned char nr_decimals = maxNrDecimals_fpType(getCustomFloatVar(varNum)); + bool trimTrailingZeros = true; + + if (devNameEqInt) { + nr_decimals = 0; + } else if (!format.isEmpty()) + { + // There is some formatting here, so do not throw away decimals + trimTrailingZeros = false; + } + #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + String value = doubleToString(getCustomFloatVar(varNum), nr_decimals, trimTrailingZeros); + #else + String value = floatToString(getCustomFloatVar(varNum), nr_decimals, trimTrailingZeros); + #endif + transformValue( + newString, + minimal_lineSize, + std::move(value), + format, + tmpString); + } + } + else if (equals(deviceName, F("plugin"))) + { + // Handle a plugin request. + // For example: "[Plugin#GPIO#Pinstate#N]" + // The command is stored in valueName & format + String command = strformat(F("%s#%s"), valueName.c_str(), format.c_str()); + command.replace('#', ','); + + if (getGPIOPinStateValues(command)) { + newString += command; + } + /* @giig1967g + if (PluginCall(PLUGIN_REQUEST, 0, command)) + { + // Do not call transformValue here. + // The "format" is not empty so must not call the formatter function. + newString += command; + } + */ + } + else + { + // Address a value from a plugin. + // For example: "[bme#temp]" + // If value name is unknown, run a PLUGIN_GET_CONFIG_VALUE command. + // For example: "[#getLevel]" + taskIndex_t taskIndex = findTaskIndexByName(deviceName, true); // Check for enabled/disabled is done separately + + if (validTaskIndex(taskIndex)) { + bool isHandled = false; + if (Settings.TaskDeviceEnabled[taskIndex]) { + uint8_t valueNr = findDeviceValueIndexByName(valueName, taskIndex); + + if (valueNr != VARS_PER_TASK) { + // here we know the task and value, so find the uservar + // Try to format and transform the values + bool isvalid; + String value = formatUserVar(taskIndex, valueNr, isvalid); + + if (isvalid) { + transformValue(newString, minimal_lineSize, std::move(value), format, tmpString); + isHandled = true; + } + } else { + // try if this is a get config request + struct EventStruct TempEvent(taskIndex); + String tmpName = valueName; + + if (PluginCall(PLUGIN_GET_CONFIG_VALUE, &TempEvent, tmpName)) + { + transformValue(newString, minimal_lineSize, std::move(tmpName), format, tmpString); + isHandled = true; + } + } + } + if (!isHandled && valueName.startsWith(F("settings."))) { // Task settings values + String value; + if (valueName.endsWith(F(".enabled"))) { // Task state + value = Settings.TaskDeviceEnabled[taskIndex] ? '1' : '0'; + } else if (valueName.endsWith(F(".interval"))) { // Task interval + value = Settings.TaskDeviceTimer[taskIndex]; + } else if (valueName.endsWith(F(".valuecount"))) { // Task value count + value = getValueCountForTask(taskIndex); + } else if ((valueName.indexOf(F(".controller")) == 8) && valueName.length() >= 20) { // Task controller values + String ctrl = valueName.substring(19, 20); + int32_t ctrlNr = 0; + if (validIntFromString(ctrl, ctrlNr) && (ctrlNr >= 1) && (ctrlNr <= CONTROLLER_MAX) && + Settings.ControllerEnabled[ctrlNr - 1]) { // Controller nr. valid and enabled + if (valueName.endsWith(F(".enabled"))) { // Task-controller enabled + value = Settings.TaskDeviceSendData[ctrlNr - 1][taskIndex]; + } else if (valueName.endsWith(F(".idx"))) { // Task-controller idx value + protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(ctrlNr - 1); + + if (validProtocolIndex(ProtocolIndex) && + getProtocolStruct(ProtocolIndex).usesID && (Settings.Protocol[ctrlNr - 1] != 0)) { + value = Settings.TaskDeviceID[ctrlNr - 1][taskIndex]; + } + } + } + } + if (!value.isEmpty()) { + transformValue(newString, minimal_lineSize, std::move(value), format, tmpString); + // isHandled = true; + } + } + } + } + + + // Conversion is done (or impossible) for the found "[...#...]" + // Continue with the next one. + lastStartpos = endpos + 1; + startpos = endpos + 1; + + // This may have taken some time, so call delay() + delay(0); + } + } + + // Copy the rest of the string (or all if no replacements were done) + newString += tmpString.substring(lastStartpos); + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("parseTemplate2")); + #endif // ifndef BUILD_NO_RAM_TRACKER + + // Restore previous loaded taskSettings + if (validTaskIndex(currentTaskIndex)) + { + LoadTaskSettings(currentTaskIndex); + } + + parseStandardConversions(newString, useURLencode); + + // process other markups as well + parse_string_commands(newString); + + // padding spaces + while (newString.length() < minimal_lineSize) { + newString += ' '; + } + + STOP_TIMER(PARSE_TEMPLATE_PADDED); + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("parseTemplate3")); + #endif // ifndef BUILD_NO_RAM_TRACKER + return newString; +} + +/********************************************************************************************\ + Transform values + \*********************************************************************************************/ + +bool isTransformString(char c, bool logicVal, String& strValue) +{ + const __FlashStringHelper * value = F(""); + char value_ch = '\0'; + switch (c) { + case 'O': + value = logicVal == 0 ? F("OFF") : F(" ON"); // (equivalent to XOR operator) + break; + case 'C': + value = logicVal == 0 ? F("CLOSE") : F(" OPEN"); + break; + case 'c': + value = logicVal == 0 ? F("CLOSED") : F(" OPEN"); + break; + case 'M': + value = logicVal == 0 ? F("AUTO") : F(" MAN"); + break; + case 'm': + value_ch = logicVal == 0 ? 'A' : 'M'; + break; + case 'H': + value = logicVal == 0 ? F("COLD") : F(" HOT"); + break; + case 'U': + value = logicVal == 0 ? F("DOWN") : F(" UP"); + break; + case 'u': + value_ch = logicVal == 0 ? 'D' : 'U'; + break; + case 'Y': + value = logicVal == 0 ? F(" NO") : F("YES"); + break; + case 'y': + value_ch = logicVal == 0 ? 'N' : 'Y'; + break; + case 'X': + value_ch = logicVal == 0 ? 'O' : 'X'; + break; + case 'I': + value = logicVal == 0 ? F("OUT") : F(" IN"); + break; + case 'L': + value = logicVal == 0 ? F(" LEFT") : F("RIGHT"); + break; + case 'l': + value_ch = logicVal == 0 ? 'L' : 'R'; + break; + case 'Z': // return "0" or "1" + value_ch = logicVal == 0 ? '0' : '1'; + break; + default: + return false; + } + if (value_ch != '\0') { + strValue = value_ch; + } else { + strValue = value; + } + return true; +} + + +// Syntax: [task#value#transformation#justification] +// valueFormat="transformation#justification" +void transformValue( + String & newString, + uint8_t lineSize, + String value, + String & valueFormat, + const String& tmpString) +{ + // FIXME TD-er: This function does append to newString and uses its length to perform right aling. + // Is this the way it is intended to use? + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("transformValue")); + #endif // ifndef BUILD_NO_RAM_TRACKER + + // start changes by giig1967g - 2018-04-20 + // Syntax: [task#value#transformation#justification] + // valueFormat="transformation#justification" + if (valueFormat.length() > 0) // do the checks only if a Format is defined to optimize loop + { + String valueJust; + + int hashtagIndex = valueFormat.indexOf('#'); + + if (hashtagIndex >= 0) + { + valueJust = valueFormat.substring(hashtagIndex + 1); // Justification part + valueFormat = valueFormat.substring(0, hashtagIndex); // Transformation part + } + + // valueFormat="transformation" + // valueJust="justification" + if (valueFormat.length() > 0) // do the checks only if a Format is defined to optimize loop + { + int logicVal = 0; + ESPEASY_RULES_FLOAT_TYPE valFloat{}; + + if (validDoubleFromString(value, valFloat)) + { + // to be used for binary values (0 or 1) + logicVal = lround(static_cast(valFloat)) == 0 ? 0 : 1; + } else { + if (value.length() > 0) { + logicVal = 1; + } + } + String tempValueFormat = valueFormat; + { + const int invertedIndex = tempValueFormat.indexOf('!'); + + if (invertedIndex != -1) { + // We must invert the value. + logicVal = (logicVal == 0) ? 1 : 0; + + // Remove the '!' from the string. + tempValueFormat.remove(invertedIndex, 1); + } + } + + const int rightJustifyIndex = tempValueFormat.indexOf('R'); + const bool rightJustify = rightJustifyIndex >= 0 ? 1 : 0; + + if (rightJustify) { + tempValueFormat.remove(rightJustifyIndex, 1); + } + + const int tempValueFormatLength = tempValueFormat.length(); + + // Check Transformation syntax + if (tempValueFormatLength > 0) + { + if (!isTransformString(tempValueFormat[0], logicVal, value)) { + switch (tempValueFormat[0]) + { + case 'V': // value = value without transformations + break; + case 'p': // Password hide using asterisks or custom character: pc + { + char maskChar = '*'; + + if (tempValueFormatLength > 1) + { + maskChar = tempValueFormat[1]; + } + + if (equals(value, '0')) { + value = String(); + } else { + const int valueLength = value.length(); + + for (int i = 0; i < valueLength; i++) { + value[i] = maskChar; + } + } + break; + } + case 'D': // Dx.y min 'x' digits zero filled & 'y' decimal fixed digits + case 'd': // like above but with spaces padding + { + int x = 0; + int y = 0; + + switch (tempValueFormatLength) + { + case 2: // Dx + + if (isDigit(tempValueFormat[1])) + { + x = static_cast(tempValueFormat[1]) - '0'; + } + break; + case 3: // D.y + + if ((tempValueFormat[1] == '.') && isDigit(tempValueFormat[2])) + { + y = static_cast(tempValueFormat[2]) - '0'; + } + break; + case 4: // Dx.y + + if (isDigit(tempValueFormat[1]) && (tempValueFormat[2] == '.') && isDigit(tempValueFormat[3])) + { + x = static_cast(tempValueFormat[1]) - '0'; + y = static_cast(tempValueFormat[3]) - '0'; + } + break; + case 1: // D + default: // any other combination x=0; y=0; + break; + } + bool trimTrailingZeros = false; +#if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + value = doubleToString(valFloat, y, trimTrailingZeros); +#else + value = floatToString(valFloat, y, trimTrailingZeros); +#endif + int indexDot = value.indexOf('.'); + + if (indexDot == -1) { + indexDot = value.length(); + } + + for (uint8_t f = 0; f < (x - indexDot); f++) { + value = (tempValueFormat[0] == 'd' ? ' ' : '0') + value; + } + break; + } + case 'F': // FLOOR (round down) + #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + value = static_cast(floor(valFloat)); + #else + value = static_cast(floorf(valFloat)); + #endif + break; + case 'E': // CEILING (round up) + #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + value = static_cast(ceil(valFloat)); + #else + value = static_cast(ceilf(valFloat)); + #endif + break; + default: + value = F("ERR"); + break; + } + } + + // Check Justification syntax + const int valueJustLength = valueJust.length(); + + if (valueJustLength > 0) // do the checks only if a Justification is defined to optimize loop + { + value.trim(); // remove right justification spaces for backward compatibility + + switch (valueJust[0]) + { + case 'P': // Prefix Fill with n spaces: Pn + + if (valueJustLength > 1) + { + if (isDigit(valueJust[1])) // Check Pn where n is between 0 and 9 + { + int filler = valueJust[1] - value.length() - '0'; // char '0' = 48; char '9' = 58 + + for (uint8_t f = 0; f < filler; f++) { + newString += ' '; + } + } + } + break; + case 'S': // Suffix Fill with n spaces: Sn + + if (valueJustLength > 1) + { + if (isDigit(valueJust[1])) // Check Sn where n is between 0 and 9 + { + int filler = valueJust[1] - value.length() - '0'; // 48 + + for (uint8_t f = 0; f < filler; f++) { + value += ' '; + } + } + } + break; + case 'L': // left part of the string + + if (valueJustLength > 1) + { + if (isDigit(valueJust[1])) // Check n where n is between 0 and 9 + { + value = value.substring(0, static_cast(valueJust[1]) - '0'); + } + } + break; + case 'R': // Right part of the string + + if (valueJustLength > 1) + { + if (isDigit(valueJust[1])) // Check n where n is between 0 and 9 + { + value = value.substring(std::max(0, static_cast(value.length()) - (static_cast(valueJust[1]) - '0'))); + } + } + break; + case 'U': // Substring Ux.y where x=firstChar and y=number of characters + + if (valueJustLength > 1) + { + if (isDigit(valueJust[1]) && (valueJust[2] == '.') && isDigit(valueJust[3]) && (valueJust[1] > '0') && (valueJust[3] > '0')) + { + value = value.substring(std::min(static_cast(value.length()), static_cast(valueJust[1]) - '0' - 1), + static_cast(valueJust[1]) - '0' - 1 + static_cast(valueJust[3]) - '0'); + } + else + { + newString += F("ERR"); + } + } + break; + case 'C': // Capitalize First Word-Character value (space/period are checked) + + if (value.length() > 0) { + value.toLowerCase(); + bool nextCapital = true; + + for (uint8_t i = 0; i < value.length(); i++) { + if (nextCapital) { + value[i] = toupper(value[i]); + } + nextCapital = (value[i] == ' ' || value[i] == '.'); // Very simple, capitalize-first-after-space/period + } + } + break; + case 'u': // Uppercase + value.toUpperCase(); + break; + case 'l': // Lowercase + value.toLowerCase(); + break; + default: + newString += F("ERR"); + break; + } + } + } + + if (rightJustify) + { + int filler = lineSize - newString.length() - value.length() - tmpString.length(); + + for (uint8_t f = 0; f < filler; f++) { + newString += ' '; + } + } + { +#ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String logFormatted = F("DEBUG: Formatted String='"); + logFormatted += newString; + logFormatted += value; + logFormatted += '\''; + addLogMove(LOG_LEVEL_DEBUG, logFormatted); + } +#endif // ifndef BUILD_NO_DEBUG + } + } + } + + // end of changes by giig1967g - 2018-04-18 + + newString += value; + { +#ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG_DEV)) { + String logParsed = F("DEBUG DEV: Parsed String='"); + logParsed += newString; + logParsed += '\''; + addLogMove(LOG_LEVEL_DEBUG_DEV, logParsed); + } +#endif // ifndef BUILD_NO_DEBUG + } + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("transformValue2")); + #endif // ifndef BUILD_NO_RAM_TRACKER +} + +// Find the first (enabled) task with given name +// Return INVALID_TASK_INDEX when not found, else return taskIndex +taskIndex_t findTaskIndexByName(String deviceName, bool allowDisabled) +{ + deviceName.toLowerCase(); + // cache this, since LoadTaskSettings does take some time. + auto result = Cache.taskIndexName.find(deviceName); + + if (result != Cache.taskIndexName.end()) { + return result->second; + } + + for (taskIndex_t taskIndex = 0; taskIndex < TASKS_MAX; taskIndex++) + { + if (Settings.TaskDeviceEnabled[taskIndex] || allowDisabled) { + String taskDeviceName = getTaskDeviceName(taskIndex); + + if (!taskDeviceName.isEmpty()) + { + // Use entered taskDeviceName can have any case, so compare case insensitive. + if (deviceName.equalsIgnoreCase(taskDeviceName)) + { + Cache.taskIndexName.emplace( + std::make_pair( + std::move(deviceName), + taskIndex)); + return taskIndex; + } + } + } + } + return INVALID_TASK_INDEX; +} + +// Find the first device value index of a taskIndex. +// Return VARS_PER_TASK if none found. +uint8_t findDeviceValueIndexByName(const String& valueName, taskIndex_t taskIndex) +{ + const deviceIndex_t deviceIndex = getDeviceIndex_from_TaskIndex(taskIndex); + + if (!validDeviceIndex(deviceIndex)) { return VARS_PER_TASK; } + + #ifdef USE_SECOND_HEAP + HeapSelectDram ephemeral; + #endif + + + // cache this, since LoadTaskSettings does take some time. + // We need to use a cache search key including the taskIndex, + // to allow several tasks to have the same value names. + String cache_valueName = strformat( + F("%s#%d"), // The '#' cannot exist in a value name, use it in the cache key. + valueName.c_str(), + static_cast(taskIndex)); + cache_valueName.toLowerCase(); // No need to store multiple versions of the same entry with only different case. + + auto result = Cache.taskIndexValueName.find(cache_valueName); + + if (result != Cache.taskIndexValueName.end()) { + return result->second; + } + const uint8_t valCount = getValueCountForTask(taskIndex); + + for (uint8_t valueNr = 0; valueNr < valCount; valueNr++) + { + // Check case insensitive, since the user entered value name can have any case. + if (valueName.equalsIgnoreCase(Cache.getTaskDeviceValueName(taskIndex, valueNr))) + { + Cache.taskIndexValueName.emplace( + std::make_pair( + std::move(cache_valueName), + valueNr)); + return valueNr; + } + } + return VARS_PER_TASK; +} + +// Find positions of [...#...] in the given string. +// Only update pos values on success. +// Return true when found. +bool findNextValMarkInString(const String& input, int& startpos, int& hashpos, int& endpos) { + int tmpStartpos = input.indexOf('[', startpos); + + if (tmpStartpos == -1) { return false; } + const int tmpHashpos = input.indexOf('#', tmpStartpos); + + if (tmpHashpos == -1) { return false; } + + // We found a hash position, check if there is another '[' inbetween. + for (int i = tmpStartpos; i < tmpHashpos; ++i) { + if (input[i] == '[') { + tmpStartpos = i; + } + } + + const int tmpEndpos = input.indexOf(']', tmpStartpos); + + if (tmpEndpos == -1) { return false; } + + if (tmpHashpos >= tmpEndpos) { + return false; + } + + hashpos = tmpHashpos; + startpos = tmpStartpos; + endpos = tmpEndpos; + return true; +} + +// Find [deviceName#valueName] or [deviceName#valueName#format] +// DeviceName and valueName will be returned in lower case. +// Format may contain case sensitive formatting syntax. +bool findNextDevValNameInString(const String& input, int& startpos, int& endpos, String& deviceName, String& valueName, String& format) { + int hashpos; + + if (!findNextValMarkInString(input, startpos, hashpos, endpos)) { return false; } + + move_special(deviceName, input.substring(startpos + 1, hashpos)); + move_special(valueName , input.substring(hashpos + 1, endpos)); + hashpos = valueName.indexOf('#'); + + if (hashpos != -1) { + // Found an extra '#' in the valueName, will split valueName and format. + move_special(format, valueName.substring(hashpos + 1)); + move_special(valueName, valueName.substring(0, hashpos)); + } else { + format = String(); + } + deviceName.toLowerCase(); + valueName.toLowerCase(); + return true; +} + +/********************************************************************************************\ + Check to see if a given argument is a valid taskIndex (argc = 0 => command) + \*********************************************************************************************/ +taskIndex_t parseCommandArgumentTaskIndex(const String& string, unsigned int argc) +{ + taskIndex_t taskIndex = INVALID_TASK_INDEX; + const int ti = parseCommandArgumentInt(string, argc); + + if (ti > 0) { + // Task Index used as argument in commands start at 1. + taskIndex = static_cast(ti - 1); + } + return taskIndex; +} + +/********************************************************************************************\ + Get int from command argument (argc = 0 => command) + \*********************************************************************************************/ +int parseCommandArgumentInt(const String& string, unsigned int argc, + int errorValue) +{ + int value = 0; + + if (argc > 0) { + // No need to check for the command (argc == 0) + String TmpStr; + + if (GetArgv(string.c_str(), TmpStr, argc + 1)) { + value = CalculateParam(TmpStr, errorValue); + } + } + return value; +} + +/********************************************************************************************\ + Parse a command string to event struct + \*********************************************************************************************/ +void parseCommandString(struct EventStruct *event, const String& string) +{ + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("parseCommandString")); + #endif // ifndef BUILD_NO_RAM_TRACKER + event->Par1 = parseCommandArgumentInt(string, 1); + event->Par2 = parseCommandArgumentInt(string, 2); + event->Par3 = parseCommandArgumentInt(string, 3); + event->Par4 = parseCommandArgumentInt(string, 4); + event->Par5 = parseCommandArgumentInt(string, 5); +} diff --git a/src/src/Helpers/StringProvider.cpp b/src/src/Helpers/StringProvider.cpp index 6e8701de32..8eca804e0f 100644 --- a/src/src/Helpers/StringProvider.cpp +++ b/src/src/Helpers/StringProvider.cpp @@ -193,8 +193,13 @@ const __FlashStringHelper * getLabel(LabelType::Enum label) { case LabelType::FORCE_WIFI_NOSLEEP: return F("Force WiFi No Sleep"); case LabelType::PERIODICAL_GRAT_ARP: return F("Periodical send Gratuitous ARP"); case LabelType::CONNECTION_FAIL_THRESH: return F("Connection Failure Threshold"); +#ifndef ESP32 case LabelType::WAIT_WIFI_CONNECT: return F("Extra Wait WiFi Connect"); +#endif case LabelType::CONNECT_HIDDEN_SSID: return F("Include Hidden SSID"); +#ifdef ESP32 + case LabelType::WIFI_PASSIVE_SCAN: return F("Passive WiFi Scan"); +#endif case LabelType::HIDDEN_SSID_SLOW_CONNECT: return F("Hidden SSID Slow Connect"); case LabelType::SDK_WIFI_AUTORECONNECT: return F("Enable SDK WiFi Auto Reconnect"); #if FEATURE_USE_IPV6 @@ -259,7 +264,7 @@ const __FlashStringHelper * getLabel(LabelType::Enum label) { case LabelType::OTA_2STEP: return F("OTA 2-step Needed"); case LabelType::OTA_POSSIBLE: return F("OTA possible"); #if FEATURE_INTERNAL_TEMPERATURE - case LabelType::INTERNAL_TEMPERATURE: return F("Internal temperature (ESP32)"); + case LabelType::INTERNAL_TEMPERATURE: return F("Internal Temperature"); #endif // if FEATURE_INTERNAL_TEMPERATURE #if FEATURE_ETHERNET case LabelType::ETH_IP_ADDRESS: return F("Eth IP Address"); @@ -328,7 +333,7 @@ String getValue(LabelType::Enum label) { } return timeSource_str; } - case LabelType::TIME_WANDER: return String(node_time.timeWander, 1); + case LabelType::TIME_WANDER: return String(node_time.timeWander, 3); #if FEATURE_EXT_RTC case LabelType::EXT_RTC_UTC_TIME: { @@ -515,8 +520,13 @@ String getValue(LabelType::Enum label) { case LabelType::FORCE_WIFI_NOSLEEP: return jsonBool(Settings.WifiNoneSleep()); case LabelType::PERIODICAL_GRAT_ARP: return jsonBool(Settings.gratuitousARP()); case LabelType::CONNECTION_FAIL_THRESH: retval = Settings.ConnectionFailuresThreshold; break; +#ifndef ESP32 case LabelType::WAIT_WIFI_CONNECT: return jsonBool(Settings.WaitWiFiConnect()); +#endif case LabelType::CONNECT_HIDDEN_SSID: return jsonBool(Settings.IncludeHiddenSSID()); +#ifdef ESP32 + case LabelType::WIFI_PASSIVE_SCAN: return jsonBool(Settings.PassiveWiFiScan()); +#endif case LabelType::HIDDEN_SSID_SLOW_CONNECT: return jsonBool(Settings.HiddenSSID_SlowConnectPerBSSID()); case LabelType::SDK_WIFI_AUTORECONNECT: return jsonBool(Settings.SDK_WiFi_autoreconnect()); #if FEATURE_USE_IPV6 @@ -624,7 +634,7 @@ String getValue(LabelType::Enum label) { #if FEATURE_ETHERNET String getEthSpeed() { - return strformat(F("%dMbps"), EthLinkSpeed()); + return strformat(F("%d [Mbps]"), EthLinkSpeed()); } String getEthLinkSpeedState() { @@ -652,3 +662,165 @@ String getExtendedValue(LabelType::Enum label) { } return EMPTY_STRING; } + +String getFormNote(LabelType::Enum label) +{ + // Keep flash string till the end of the function, to reduce build size + // Otherwise lots of calls to String() constructor are included. + const __FlashStringHelper *flash_str = F(""); + + switch (label) { +#ifndef MINIMAL_OTA + case LabelType::CONNECT_HIDDEN_SSID: + flash_str = F("Must be checked to connect to a hidden SSID"); + break; +#ifdef ESP32 + case LabelType::WIFI_PASSIVE_SCAN: + flash_str = F("Passive scan listens for WiFi beacons, Active scan probes for AP. Passive scan is typically faster."); + break; +#endif // ifdef ESP32 + case LabelType::HIDDEN_SSID_SLOW_CONNECT: + flash_str = F("Required for some AP brands like Mikrotik to connect to hidden SSID"); + break; +#if FEATURE_USE_IPV6 + case LabelType::ENABLE_IPV6: + flash_str = F("Toggling IPv6 requires reboot"); + break; +#endif // if FEATURE_USE_IPV6 +#ifndef NO_HTTP_UPDATER + case LabelType::ALLOW_OTA_UNLIMITED: + flash_str = F("When enabled, OTA updating can overwrite the filesystem and settings!
Requires reboot to activate"); + break; +#endif // ifndef NO_HTTP_UPDATER +#if FEATURE_RULES_EASY_COLOR_CODE + case LabelType::DISABLE_RULES_AUTOCOMPLETE: + flash_str = F("Also disables Rules syntax highlighting!"); + break; +#endif // if FEATURE_RULES_EASY_COLOR_CODE + + case LabelType::FORCE_WIFI_NOSLEEP: + flash_str = F("Change WiFi sleep settings requires reboot to activate"); + break; + + case LabelType::CPU_ECO_MODE: + flash_str = F("Node may miss receiving packets with Eco mode enabled"); + break; + + case LabelType::WIFI_NR_EXTRA_SCANS: + flash_str = F("Number of extra times to scan all channels to have higher chance of finding the desired AP"); + break; +#ifndef ESP32 + case LabelType::WAIT_WIFI_CONNECT: + flash_str = F("Wait for 1000 msec right after connecting to WiFi.
May improve success on some APs like Fritz!Box"); + break; +#endif + +#endif + +#if FEATURE_SET_WIFI_TX_PWR + case LabelType::WIFI_TX_MAX_PWR: + case LabelType::WIFI_SENS_MARGIN: + { + float maxTXpwr; + float sensitivity = GetRSSIthreshold(maxTXpwr); + if (LabelType::WIFI_TX_MAX_PWR == label) { + return strformat( + F("Current max: %.2f dBm"), maxTXpwr); + } + return strformat( + F("Adjust TX power to target the AP with (sensitivity + margin) dBm signal strength. Current sensitivity: %.2f dBm"), + sensitivity); + } +#endif // if FEATURE_SET_WIFI_TX_PWR + + default: + return EMPTY_STRING; + } + + return flash_str; +} + + +String getFormUnit(LabelType::Enum label) +{ + const __FlashStringHelper *flash_str = F(""); + + switch (label) { +#if FEATURE_SET_WIFI_TX_PWR + case LabelType::WIFI_TX_MAX_PWR: + case LabelType::WIFI_CUR_TX_PWR: + case LabelType::WIFI_RSSI: + flash_str = F("dBm"); + break; + case LabelType::WIFI_SENS_MARGIN: + flash_str = F("dB"); + break; +#endif + case LabelType::TIME_WANDER: + flash_str = F("ppm"); + break; +#ifdef ESP32 + case LabelType::HEAP_SIZE: + case LabelType::HEAP_MIN_FREE: + #ifdef BOARD_HAS_PSRAM + case LabelType::PSRAM_SIZE: + case LabelType::PSRAM_FREE: + case LabelType::PSRAM_MIN_FREE: + case LabelType::PSRAM_MAX_FREE_BLOCK: + #endif // BOARD_HAS_PSRAM +#endif // ifdef ESP32 + case LabelType::FREE_MEM: + case LabelType::FREE_STACK: +#ifdef USE_SECOND_HEAP + case LabelType::FREE_HEAP_IRAM: +#endif +#if defined(CORE_POST_2_5_0) || defined(ESP32) + #ifndef LIMIT_BUILD_SIZE + case LabelType::HEAP_MAX_FREE_BLOCK: + #endif +#endif // if defined(CORE_POST_2_5_0) || defined(ESP32) + + flash_str = F("byte"); + break; + case LabelType::FLASH_CHIP_REAL_SIZE: + case LabelType::FLASH_IDE_SIZE: + flash_str = F("kB"); + break; +/* + case LabelType::UPTIME: + flash_str = F("min"); + break; +*/ + case LabelType::LOAD_PCT: +#if defined(CORE_POST_2_5_0) + #ifndef LIMIT_BUILD_SIZE + case LabelType::HEAP_FRAGMENTATION: + #endif +#endif // if defined(CORE_POST_2_5_0) + + flash_str = F("%"); + break; + + case LabelType::ESP_CHIP_FREQ: +#ifdef ESP32 + case LabelType::ESP_CHIP_XTAL_FREQ: + case LabelType::ESP_CHIP_APB_FREQ: +#endif + case LabelType::FLASH_CHIP_SPEED: + case LabelType::FLASH_IDE_SPEED: + flash_str = F("MHz"); + break; +#if FEATURE_INTERNAL_TEMPERATURE + case LabelType::INTERNAL_TEMPERATURE: + flash_str = F("°C"); + break; +#endif // if FEATURE_INTERNAL_TEMPERATURE + + + + default: + return EMPTY_STRING; + } + + return flash_str; +} \ No newline at end of file diff --git a/src/src/Helpers/StringProvider.h b/src/src/Helpers/StringProvider.h index 8c5efaa686..0b8e6b4b6f 100644 --- a/src/src/Helpers/StringProvider.h +++ b/src/src/Helpers/StringProvider.h @@ -141,9 +141,14 @@ struct LabelType { FORCE_WIFI_NOSLEEP, PERIODICAL_GRAT_ARP, CONNECTION_FAIL_THRESH, +#ifndef ESP32 WAIT_WIFI_CONNECT, +#endif HIDDEN_SSID_SLOW_CONNECT, CONNECT_HIDDEN_SSID, +#ifdef ESP32 + WIFI_PASSIVE_SCAN, +#endif SDK_WIFI_AUTORECONNECT, #if FEATURE_USE_IPV6 ENABLE_IPV6, @@ -253,5 +258,8 @@ const __FlashStringHelper * getLabel(LabelType::Enum label); String getValue(LabelType::Enum label); String getExtendedValue(LabelType::Enum label); +String getFormNote(LabelType::Enum label); +String getFormUnit(LabelType::Enum label); + #endif // STRING_PROVIDER_TYPES_H diff --git a/src/src/Helpers/_CPlugin_Helper.cpp b/src/src/Helpers/_CPlugin_Helper.cpp index d6ab2347cd..14120d5833 100644 --- a/src/src/Helpers/_CPlugin_Helper.cpp +++ b/src/src/Helpers/_CPlugin_Helper.cpp @@ -1,332 +1,331 @@ -#include "../Helpers/_CPlugin_Helper.h" - -#include "../../ESPEasy_common.h" - -#include "../CustomBuild/CompiletimeDefines.h" -#include "../CustomBuild/ESPEasyLimits.h" - -#include "../DataStructs/SecurityStruct.h" -#include "../DataStructs/SettingsStruct.h" - -#include "../DataStructs/ControllerSettingsStruct.h" -#include "../DataStructs/TimingStats.h" - -#include "../ESPEasyCore/ESPEasy_backgroundtasks.h" -#include "../ESPEasyCore/ESPEasy_Log.h" -#include "../ESPEasyCore/ESPEasyEth.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" -#include "../ESPEasyCore/ESPEasyWifi.h" - -#include "../Globals/Settings.h" -#include "../Globals/SecuritySettings.h" -#include "../Globals/ESPEasyWiFiEvent.h" - -#include "../Helpers/ESPEasy_time_calc.h" -#include "../Helpers/Misc.h" -#include "../Helpers/Network.h" -#include "../Helpers/Networking.h" -#include "../Helpers/StringConverter.h" - -#include -#include - - -bool safeReadStringUntil(Stream & input, - String & str, - char terminator, - unsigned int maxSize, - unsigned int timeout) -{ - int c; - const unsigned long start = millis(); - const unsigned long timer = start + timeout; - unsigned long backgroundtasks_timer = start + 10; - - str = String(); - - do { - // read character - if (input.available()) { - c = input.read(); - - if (c >= 0) { - // found terminator, we're ok - if (c == terminator) { - return true; - } - - // found character, add to string - str += char(c); - - // string at max size? - if (str.length() >= maxSize) { - addLog(LOG_LEVEL_ERROR, F("Not enough bufferspace to read all input data!")); - return false; - } - } - - // We must run the backgroundtasks every now and then. - if (timeOutReached(backgroundtasks_timer)) { - backgroundtasks_timer += 10; - backgroundtasks(); - } else { - delay(0); - } - } else { - delay(0); - } - } while (!timeOutReached(timer)); - - addLog(LOG_LEVEL_ERROR, strformat(F("Timeout while reading input data! str: `%s`"), str.c_str())); - return false; -} - -#ifndef BUILD_NO_DEBUG -void log_connecting_to(const __FlashStringHelper *prefix, int controller_number, ControllerSettingsStruct& ControllerSettings) { - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - addLogMove(LOG_LEVEL_DEBUG, - strformat(F("%s%s connecting to %s"), - prefix, - get_formatted_Controller_number(controller_number).c_str(), - ControllerSettings.getHostPortString().c_str())); - } -} - -#endif // ifndef BUILD_NO_DEBUG - -void log_connecting_fail(const __FlashStringHelper *prefix, int controller_number) { - if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - addLogMove(LOG_LEVEL_ERROR, - strformat(F("%s%s connection failed (%d/%d)"), - prefix, - get_formatted_Controller_number(controller_number).c_str(), - WiFiEventData.connectionFailures, - Settings.ConnectionFailuresThreshold)); - } -} - -bool count_connection_results(bool success, const __FlashStringHelper *prefix, int controller_number, unsigned long connect_start_time) { - WiFiEventData.connectDurations[controller_number] = timePassedSince(connect_start_time); - - if (!success) - { - ++WiFiEventData.connectionFailures; - log_connecting_fail(prefix, controller_number); - return false; - } - statusLED(true); - - if (WiFiEventData.connectionFailures > 0) { - --WiFiEventData.connectionFailures; - } - return true; -} - -bool try_connect_host(int controller_number, WiFiUDP& client, ControllerSettingsStruct& ControllerSettings) { - START_TIMER; - - if (!NetworkConnected()) { - client.stop(); - return false; - } - - // Ignoring the ACK from the server is probably set for a reason. - // For example because the server does not give an acknowledgement. - // This way, we always need the set amount of timeout to handle the request. - // Thus we should not make the timeout dynamic here if set to ignore ack. - const uint32_t timeout = ControllerSettings.MustCheckReply - ? WiFiEventData.getSuggestedTimeout(controller_number, ControllerSettings.ClientTimeout) - : ControllerSettings.ClientTimeout; - - client.setTimeout(timeout); // in msec as it should be! - delay(0); -#ifndef BUILD_NO_DEBUG - log_connecting_to(F("UDP : "), controller_number, ControllerSettings); -#endif // ifndef BUILD_NO_DEBUG - - const unsigned long connect_start_time = millis(); - bool success = ControllerSettings.beginPacket(client); - - if (!success) { - client.stop(); - } - const bool result = count_connection_results( - success, - F("UDP : "), - controller_number, - connect_start_time); - STOP_TIMER(TRY_CONNECT_HOST_UDP); - return result; -} - -#if FEATURE_HTTP_CLIENT -bool try_connect_host(int controller_number, WiFiClient& client, ControllerSettingsStruct& ControllerSettings) { - return try_connect_host(controller_number, client, ControllerSettings, F("HTTP : ")); -} - -bool try_connect_host(int controller_number, - WiFiClient & client, - ControllerSettingsStruct & ControllerSettings, - const __FlashStringHelper *loglabel) { - START_TIMER; - - if (!NetworkConnected()) { - client.stop(); - return false; - } - - // Use WiFiClient class to create TCP connections - delay(0); - - // Ignoring the ACK from the server is probably set for a reason. - // For example because the server does not give an acknowledgement. - // This way, we always need the set amount of timeout to handle the request. - // Thus we should not make the timeout dynamic here if set to ignore ack. - const uint32_t timeout = ControllerSettings.MustCheckReply - ? WiFiEventData.getSuggestedTimeout(controller_number, ControllerSettings.ClientTimeout) - : ControllerSettings.ClientTimeout; - - # ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS - - // See: https://github.com/espressif/arduino-esp32/pull/6676 - client.setTimeout((timeout + 500) / 1000); // in seconds!!!! - Client *pClient = &client; - pClient->setTimeout(timeout); - # else // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS - client.setTimeout(timeout); // in msec as it should be! - # endif // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS - -# ifndef BUILD_NO_DEBUG - log_connecting_to(loglabel, controller_number, ControllerSettings); -# endif // ifndef BUILD_NO_DEBUG - - const unsigned long connect_start_time = millis(); - - const bool success = ControllerSettings.connectToHost(client); - - if (!success) { - client.stop(); - } - const bool result = count_connection_results( - success, - loglabel, - controller_number, - connect_start_time); - STOP_TIMER(TRY_CONNECT_HOST_TCP); - return result; -} - -// Use "client.available() || client.connected()" to read all lines from slow servers. -// See: https://github.com/esp8266/Arduino/pull/5113 -// https://github.com/esp8266/Arduino/pull/1829 -bool client_available(WiFiClient& client) { - delay(0); - return (client.available() != 0) || (client.connected() != 0); -} - -String send_via_http(int controller_number, - const ControllerSettingsStruct& ControllerSettings, - controllerIndex_t controller_idx, - const String & uri, - const String & HttpMethod, - const String & header, - const String & postStr, - int & httpCode) { - // Ignoring the ACK from the HTTP server is probably set for a reason. - // For example because the server does not give an acknowledgement. - // This way, we always need the set amount of timeout to handle the request. - // Thus we should not make the timeout dynamic here if set to ignore ack. - const uint32_t timeout = ControllerSettings.MustCheckReply - ? WiFiEventData.getSuggestedTimeout(controller_number, ControllerSettings.ClientTimeout) - : ControllerSettings.ClientTimeout; - - const unsigned long connect_start_time = millis(); - const String result = send_via_http( - get_formatted_Controller_number(controller_number), - timeout, - getControllerUser(controller_idx, ControllerSettings), - getControllerPass(controller_idx, ControllerSettings), - ControllerSettings.getHost(), - ControllerSettings.Port, - uri, - HttpMethod, - header, - postStr, - httpCode, - ControllerSettings.MustCheckReply); - - // FIXME TD-er: Shouldn't this be: success = (httpCode >= 100) && (httpCode < 300) - // or is reachability of the host the important factor here? - const bool success = httpCode > 0; - - count_connection_results( - success, - F("HTTP : "), - controller_number, - connect_start_time); - - return result; -} - -#endif // FEATURE_HTTP_CLIENT - - -String getControllerUser(controllerIndex_t controller_idx, const ControllerSettingsStruct& ControllerSettings, bool doParseTemplate) -{ - if (!validControllerIndex(controller_idx)) { return EMPTY_STRING; } - - String res; - - if (ControllerSettings.useExtendedCredentials()) { - res = ExtendedControllerCredentials.getControllerUser(controller_idx); - } else { - res = String(SecuritySettings.ControllerUser[controller_idx]); - } - res.trim(); - - if (doParseTemplate) { - res = parseTemplate(res); - } - return res; -} - -String getControllerPass(controllerIndex_t controller_idx, const ControllerSettingsStruct& ControllerSettings) -{ - if (!validControllerIndex(controller_idx)) { return EMPTY_STRING; } - - if (ControllerSettings.useExtendedCredentials()) { - return ExtendedControllerCredentials.getControllerPass(controller_idx); - } - String res(SecuritySettings.ControllerPassword[controller_idx]); - - res.trim(); - return res; -} - -void setControllerUser(controllerIndex_t controller_idx, const ControllerSettingsStruct& ControllerSettings, const String& value) -{ - if (!validControllerIndex(controller_idx)) { return; } - - if (ControllerSettings.useExtendedCredentials()) { - ExtendedControllerCredentials.setControllerUser(controller_idx, value); - } else { - safe_strncpy(SecuritySettings.ControllerUser[controller_idx], value, sizeof(SecuritySettings.ControllerUser[0])); - } -} - -void setControllerPass(controllerIndex_t controller_idx, const ControllerSettingsStruct& ControllerSettings, const String& value) -{ - if (!validControllerIndex(controller_idx)) { return; } - - if (ControllerSettings.useExtendedCredentials()) { - ExtendedControllerCredentials.setControllerPass(controller_idx, value); - } else { - safe_strncpy(SecuritySettings.ControllerPassword[controller_idx], value, sizeof(SecuritySettings.ControllerPassword[0])); - } -} - -bool hasControllerCredentialsSet(controllerIndex_t controller_idx, const ControllerSettingsStruct& ControllerSettings) -{ - return !getControllerUser(controller_idx, ControllerSettings, false).isEmpty() && - !getControllerPass(controller_idx, ControllerSettings).isEmpty(); -} +#include "../Helpers/_CPlugin_Helper.h" + +#include "../../ESPEasy_common.h" + +#include "../CustomBuild/CompiletimeDefines.h" +#include "../CustomBuild/ESPEasyLimits.h" + +#include "../DataStructs/SecurityStruct.h" +#include "../DataStructs/SettingsStruct.h" + +#include "../DataStructs/ControllerSettingsStruct.h" +#include "../DataStructs/TimingStats.h" + +#include "../ESPEasyCore/ESPEasy_backgroundtasks.h" +#include "../ESPEasyCore/ESPEasy_Log.h" +#include "../ESPEasyCore/ESPEasyEth.h" +#include "../ESPEasyCore/ESPEasyNetwork.h" +#include "../ESPEasyCore/ESPEasyWifi.h" + +#include "../Globals/Settings.h" +#include "../Globals/SecuritySettings.h" +#include "../Globals/ESPEasyWiFiEvent.h" + +#include "../Helpers/ESPEasy_time_calc.h" +#include "../Helpers/Misc.h" +#include "../Helpers/Network.h" +#include "../Helpers/Networking.h" +#include "../Helpers/StringConverter.h" + +#include +#include + + +bool safeReadStringUntil(Stream & input, + String & str, + char terminator, + unsigned int maxSize, + unsigned int timeout) +{ + int c; + const unsigned long start = millis(); + const unsigned long timer = start + timeout; + unsigned long backgroundtasks_timer = start + 10; + + str = String(); + + do { + // read character + if (input.available()) { + c = input.read(); + + if (c >= 0) { + // found terminator, we're ok + if (c == terminator) { + return true; + } + + // found character, add to string + str += char(c); + + // string at max size? + if (str.length() >= maxSize) { + addLog(LOG_LEVEL_ERROR, F("Not enough bufferspace to read all input data!")); + return false; + } + } + + // We must run the backgroundtasks every now and then. + if (timeOutReached(backgroundtasks_timer)) { + backgroundtasks_timer += 10; + backgroundtasks(); + } else { + delay(0); + } + } else { + delay(0); + } + } while (!timeOutReached(timer)); + + addLog(LOG_LEVEL_ERROR, strformat(F("Timeout while reading input data! str: `%s`"), str.c_str())); + return false; +} + +#ifndef BUILD_NO_DEBUG +void log_connecting_to(const __FlashStringHelper *prefix, cpluginID_t cpluginID, ControllerSettingsStruct& ControllerSettings) { + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLogMove(LOG_LEVEL_DEBUG, + strformat(F("%s%s connecting to %s"), + prefix, + get_formatted_Controller_number(cpluginID).c_str(), + ControllerSettings.getHostPortString().c_str())); + } +} + +#endif // ifndef BUILD_NO_DEBUG + +void log_connecting_fail(const __FlashStringHelper *prefix, cpluginID_t cpluginID) { + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + addLogMove(LOG_LEVEL_ERROR, + strformat(F("%s%s connection failed (%d/%d)"), + prefix, + get_formatted_Controller_number(cpluginID).c_str(), + WiFiEventData.connectionFailures, + Settings.ConnectionFailuresThreshold)); + } +} + +bool count_connection_results(bool success, const __FlashStringHelper *prefix, cpluginID_t cpluginID, uint64_t statisticsTimerStart) { + protocolIndex_t protocolIndex = getProtocolIndex_from_CPluginID(cpluginID); + if (!success) + { + ++WiFiEventData.connectionFailures; + log_connecting_fail(prefix, cpluginID); + STOP_TIMER_CONTROLLER(protocolIndex, CPlugin::Function::CPLUGIN_CONNECT_FAIL); + return false; + } + WiFiEventData.connectDurations[cpluginID] = usecPassedSince(statisticsTimerStart) / 1000ul; + STOP_TIMER_CONTROLLER(protocolIndex, CPlugin::Function::CPLUGIN_CONNECT_SUCCESS); + statusLED(true); + + if (WiFiEventData.connectionFailures > 0) { + --WiFiEventData.connectionFailures; + } + return true; +} + +bool try_connect_host(cpluginID_t cpluginID, WiFiUDP& client, ControllerSettingsStruct& ControllerSettings) { + const uint64_t statisticsTimerStart(getMicros64()); // START_TIMER; + + if (!NetworkConnected()) { + client.stop(); + return false; + } + + // Ignoring the ACK from the server is probably set for a reason. + // For example because the server does not give an acknowledgement. + // This way, we always need the set amount of timeout to handle the request. + // Thus we should not make the timeout dynamic here if set to ignore ack. + const uint32_t timeout = ControllerSettings.MustCheckReply + ? WiFiEventData.getSuggestedTimeout(cpluginID, ControllerSettings.ClientTimeout) + : ControllerSettings.ClientTimeout; + + client.setTimeout(timeout); // in msec as it should be! + delay(0); +#ifndef BUILD_NO_DEBUG + log_connecting_to(F("UDP : "), cpluginID, ControllerSettings); +#endif // ifndef BUILD_NO_DEBUG + + bool success = ControllerSettings.beginPacket(client); + + if (!success) { + client.stop(); + } + const bool result = count_connection_results( + success, + F("UDP : "), + cpluginID, + statisticsTimerStart); + STOP_TIMER(TRY_CONNECT_HOST_UDP); + return result; +} + +#if FEATURE_HTTP_CLIENT +bool try_connect_host(cpluginID_t cpluginID, WiFiClient& client, ControllerSettingsStruct& ControllerSettings) { + return try_connect_host(cpluginID, client, ControllerSettings, F("HTTP : ")); +} + +bool try_connect_host(cpluginID_t cpluginID, + WiFiClient & client, + ControllerSettingsStruct & ControllerSettings, + const __FlashStringHelper *loglabel) { + const uint64_t statisticsTimerStart(getMicros64()); // START_TIMER; + + if (!NetworkConnected()) { + client.stop(); + return false; + } + + // Use WiFiClient class to create TCP connections + delay(0); + + // Ignoring the ACK from the server is probably set for a reason. + // For example because the server does not give an acknowledgement. + // This way, we always need the set amount of timeout to handle the request. + // Thus we should not make the timeout dynamic here if set to ignore ack. + const uint32_t timeout = ControllerSettings.MustCheckReply + ? WiFiEventData.getSuggestedTimeout(cpluginID, ControllerSettings.ClientTimeout) + : ControllerSettings.ClientTimeout; + + # ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS + + // See: https://github.com/espressif/arduino-esp32/pull/6676 + client.setTimeout((timeout + 500) / 1000); // in seconds!!!! + Client *pClient = &client; + pClient->setTimeout(timeout); + # else // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS + client.setTimeout(timeout); // in msec as it should be! + # endif // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS + +# ifndef BUILD_NO_DEBUG + log_connecting_to(loglabel, cpluginID, ControllerSettings); +# endif // ifndef BUILD_NO_DEBUG + + const bool success = ControllerSettings.connectToHost(client); + + if (!success) { + client.stop(); + } + const bool result = count_connection_results( + success, + loglabel, + cpluginID, + statisticsTimerStart); + STOP_TIMER(TRY_CONNECT_HOST_TCP); + return result; +} + +// Use "client.available() || client.connected()" to read all lines from slow servers. +// See: https://github.com/esp8266/Arduino/pull/5113 +// https://github.com/esp8266/Arduino/pull/1829 +bool client_available(WiFiClient& client) { + delay(0); + return (client.available() != 0) || (client.connected() != 0); +} + +String send_via_http(int cpluginID, + const ControllerSettingsStruct& ControllerSettings, + controllerIndex_t controller_idx, + const String & uri, + const String & HttpMethod, + const String & header, + const String & postStr, + int & httpCode) { + // Ignoring the ACK from the HTTP server is probably set for a reason. + // For example because the server does not give an acknowledgement. + // This way, we always need the set amount of timeout to handle the request. + // Thus we should not make the timeout dynamic here if set to ignore ack. + const uint32_t timeout = ControllerSettings.MustCheckReply + ? WiFiEventData.getSuggestedTimeout(cpluginID, ControllerSettings.ClientTimeout) + : ControllerSettings.ClientTimeout; + + const uint64_t statisticsTimerStart(getMicros64()); + const String result = send_via_http( + get_formatted_Controller_number(cpluginID), + timeout, + getControllerUser(controller_idx, ControllerSettings), + getControllerPass(controller_idx, ControllerSettings), + ControllerSettings.getHost(), + ControllerSettings.Port, + uri, + HttpMethod, + header, + postStr, + httpCode, + ControllerSettings.MustCheckReply); + + // FIXME TD-er: Shouldn't this be: success = (httpCode >= 100) && (httpCode < 300) + // or is reachability of the host the important factor here? + const bool success = httpCode > 0; + + count_connection_results( + success, + F("HTTP : "), + cpluginID, + statisticsTimerStart); + + return result; +} + +#endif // FEATURE_HTTP_CLIENT + + +String getControllerUser(controllerIndex_t controller_idx, const ControllerSettingsStruct& ControllerSettings, bool doParseTemplate) +{ + if (!validControllerIndex(controller_idx)) { return EMPTY_STRING; } + + String res; + + if (ControllerSettings.useExtendedCredentials()) { + res = ExtendedControllerCredentials.getControllerUser(controller_idx); + } else { + res = String(SecuritySettings.ControllerUser[controller_idx]); + } + res.trim(); + + if (doParseTemplate) { + res = parseTemplate(res); + } + return res; +} + +String getControllerPass(controllerIndex_t controller_idx, const ControllerSettingsStruct& ControllerSettings) +{ + if (!validControllerIndex(controller_idx)) { return EMPTY_STRING; } + + if (ControllerSettings.useExtendedCredentials()) { + return ExtendedControllerCredentials.getControllerPass(controller_idx); + } + String res(SecuritySettings.ControllerPassword[controller_idx]); + + res.trim(); + return res; +} + +void setControllerUser(controllerIndex_t controller_idx, const ControllerSettingsStruct& ControllerSettings, const String& value) +{ + if (!validControllerIndex(controller_idx)) { return; } + + if (ControllerSettings.useExtendedCredentials()) { + ExtendedControllerCredentials.setControllerUser(controller_idx, value); + } else { + safe_strncpy(SecuritySettings.ControllerUser[controller_idx], value, sizeof(SecuritySettings.ControllerUser[0])); + } +} + +void setControllerPass(controllerIndex_t controller_idx, const ControllerSettingsStruct& ControllerSettings, const String& value) +{ + if (!validControllerIndex(controller_idx)) { return; } + + if (ControllerSettings.useExtendedCredentials()) { + ExtendedControllerCredentials.setControllerPass(controller_idx, value); + } else { + safe_strncpy(SecuritySettings.ControllerPassword[controller_idx], value, sizeof(SecuritySettings.ControllerPassword[0])); + } +} + +bool hasControllerCredentialsSet(controllerIndex_t controller_idx, const ControllerSettingsStruct& ControllerSettings) +{ + return !getControllerUser(controller_idx, ControllerSettings, false).isEmpty() && + !getControllerPass(controller_idx, ControllerSettings).isEmpty(); +} diff --git a/src/src/Helpers/_CPlugin_Helper.h b/src/src/Helpers/_CPlugin_Helper.h index c6ffae680f..0b08d4f01a 100644 --- a/src/src/Helpers/_CPlugin_Helper.h +++ b/src/src/Helpers/_CPlugin_Helper.h @@ -1,76 +1,76 @@ -#ifndef CPLUGIN_HELPER_H -#define CPLUGIN_HELPER_H - -#include "../../ESPEasy_common.h" -#include "../../_Plugin_Helper.h" - - -#include "../ControllerQueue/DelayQueueElements.h" // Also forward declaring the do_process_cNNN_delay_queue -#include "../DataStructs/ControllerSettingsStruct.h" -#include "../ESPEasyCore/Controller.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" -#include "../Globals/CPlugins.h" -#include "../Globals/ESPEasy_Scheduler.h" -#include "../Globals/Services.h" -#include "../Helpers/_CPlugin_init.h" -#include "../Helpers/Misc.h" -#include "../Helpers/Network.h" -#include "../Helpers/Networking.h" -#include "../Helpers/Numerical.h" -#include "../Helpers/StringConverter.h" -#include "../Helpers/_CPlugin_Helper_webform.h" - - - -/*********************************************************************************************\ -* Helper functions used in a number of controllers -\*********************************************************************************************/ -bool safeReadStringUntil(Stream & input, - String & str, - char terminator, - unsigned int maxSize = 1024, - unsigned int timeout = 1000); - - -#ifndef BUILD_NO_DEBUG -void log_connecting_to(const __FlashStringHelper * prefix, int controller_number, ControllerSettingsStruct& ControllerSettings); -#endif // ifndef BUILD_NO_DEBUG - -void log_connecting_fail(const __FlashStringHelper * prefix, int controller_number); - -bool count_connection_results(bool success, const __FlashStringHelper * prefix, int controller_number, unsigned long connect_start_time); - -#if FEATURE_HTTP_CLIENT -bool try_connect_host(int controller_number, WiFiUDP& client, ControllerSettingsStruct& ControllerSettings); - -bool try_connect_host(int controller_number, WiFiClient& client, ControllerSettingsStruct& ControllerSettings); - -bool try_connect_host(int controller_number, WiFiClient& client, ControllerSettingsStruct& ControllerSettings, const __FlashStringHelper * loglabel); - -// Use "client.available() || client.connected()" to read all lines from slow servers. -// See: https://github.com/esp8266/Arduino/pull/5113 -// https://github.com/esp8266/Arduino/pull/1829 -bool client_available(WiFiClient& client); - - - -String send_via_http(int controller_number, - const ControllerSettingsStruct& ControllerSettings, - controllerIndex_t controller_idx, - const String & uri, - const String & HttpMethod, - const String & header, - const String & postStr, - int & httpCode); -#endif // FEATURE_HTTP_CLIENT - - -String getControllerUser(controllerIndex_t controller_idx, const ControllerSettingsStruct& ControllerSettings, bool parseTemplate = true); -String getControllerPass(controllerIndex_t controller_idx, const ControllerSettingsStruct& ControllerSettings); -void setControllerUser(controllerIndex_t controller_idx, const ControllerSettingsStruct& ControllerSettings, const String& value); -void setControllerPass(controllerIndex_t controller_idx, const ControllerSettingsStruct& ControllerSettings, const String& value); - -bool hasControllerCredentialsSet(controllerIndex_t controller_idx, const ControllerSettingsStruct& ControllerSettings); - - -#endif // CPLUGIN_HELPER_H +#ifndef CPLUGIN_HELPER_H +#define CPLUGIN_HELPER_H + +#include "../../ESPEasy_common.h" +#include "../../_Plugin_Helper.h" + + +#include "../ControllerQueue/DelayQueueElements.h" // Also forward declaring the do_process_cNNN_delay_queue +#include "../DataStructs/ControllerSettingsStruct.h" +#include "../ESPEasyCore/Controller.h" +#include "../ESPEasyCore/ESPEasyNetwork.h" +#include "../Globals/CPlugins.h" +#include "../Globals/ESPEasy_Scheduler.h" +#include "../Globals/Services.h" +#include "../Helpers/_CPlugin_init.h" +#include "../Helpers/Misc.h" +#include "../Helpers/Network.h" +#include "../Helpers/Networking.h" +#include "../Helpers/Numerical.h" +#include "../Helpers/StringConverter.h" +#include "../Helpers/_CPlugin_Helper_webform.h" + + + +/*********************************************************************************************\ +* Helper functions used in a number of controllers +\*********************************************************************************************/ +bool safeReadStringUntil(Stream & input, + String & str, + char terminator, + unsigned int maxSize = 1024, + unsigned int timeout = 1000); + + +#ifndef BUILD_NO_DEBUG +void log_connecting_to(const __FlashStringHelper * prefix, cpluginID_t cpluginID, ControllerSettingsStruct& ControllerSettings); +#endif // ifndef BUILD_NO_DEBUG + +void log_connecting_fail(const __FlashStringHelper * prefix, cpluginID_t cpluginID); + +bool count_connection_results(bool success, const __FlashStringHelper * prefix, cpluginID_t cpluginID, uint64_t statisticsTimerStart); + +#if FEATURE_HTTP_CLIENT +bool try_connect_host(cpluginID_t cpluginID, WiFiUDP& client, ControllerSettingsStruct& ControllerSettings); + +bool try_connect_host(cpluginID_t cpluginID, WiFiClient& client, ControllerSettingsStruct& ControllerSettings); + +bool try_connect_host(cpluginID_t cpluginID, WiFiClient& client, ControllerSettingsStruct& ControllerSettings, const __FlashStringHelper * loglabel); + +// Use "client.available() || client.connected()" to read all lines from slow servers. +// See: https://github.com/esp8266/Arduino/pull/5113 +// https://github.com/esp8266/Arduino/pull/1829 +bool client_available(WiFiClient& client); + + + +String send_via_http(int cpluginID, + const ControllerSettingsStruct& ControllerSettings, + controllerIndex_t controller_idx, + const String & uri, + const String & HttpMethod, + const String & header, + const String & postStr, + int & httpCode); +#endif // FEATURE_HTTP_CLIENT + + +String getControllerUser(controllerIndex_t controller_idx, const ControllerSettingsStruct& ControllerSettings, bool parseTemplate = true); +String getControllerPass(controllerIndex_t controller_idx, const ControllerSettingsStruct& ControllerSettings); +void setControllerUser(controllerIndex_t controller_idx, const ControllerSettingsStruct& ControllerSettings, const String& value); +void setControllerPass(controllerIndex_t controller_idx, const ControllerSettingsStruct& ControllerSettings, const String& value); + +bool hasControllerCredentialsSet(controllerIndex_t controller_idx, const ControllerSettingsStruct& ControllerSettings); + + +#endif // CPLUGIN_HELPER_H diff --git a/src/src/Helpers/_Plugin_init.h b/src/src/Helpers/_Plugin_init.h index 06360224f9..c288c557a4 100644 --- a/src/src/Helpers/_Plugin_init.h +++ b/src/src/Helpers/_Plugin_init.h @@ -717,7 +717,7 @@ void PluginInit(bool priorityOnly = false); #ifdef USES_P169 ADDPLUGIN_H(169) #endif - + #ifdef USES_P170 ADDPLUGIN_H(170) #endif diff --git a/src/src/PluginStructs/P002_data_struct.cpp b/src/src/PluginStructs/P002_data_struct.cpp index 801cfe1bbd..c3d0b4be84 100644 --- a/src/src/PluginStructs/P002_data_struct.cpp +++ b/src/src/PluginStructs/P002_data_struct.cpp @@ -332,6 +332,10 @@ bool P002_data_struct::webformLoad_show_stats(struct EventStruct *event) { bool somethingAdded = false; + if (_plugin_stats_array != nullptr) { + somethingAdded = _plugin_stats_array->webformLoad_show_stats(event, false); + } + const PluginStats *stats = getPluginStats(0); if (stats != nullptr) { @@ -340,9 +344,19 @@ bool P002_data_struct::webformLoad_show_stats(struct EventStruct *event) if (stats->webformLoad_show_stdev(event)) { somethingAdded = true; } if (stats->hasPeaks()) { - formatADC_statistics(F("ADC Peak Low"), stats->getPeakLow(), true); - formatADC_statistics(F("ADC Peak High"), stats->getPeakHigh(), true); - somethingAdded = true; + float floatvalue_low, floatvalue_high; + + if (stats->webformLoad_show_peaks( + event, + stats->getLabel(), + formatADC_statistics_to_str(stats->getPeakLow(), floatvalue_low, true), + formatADC_statistics_to_str(stats->getPeakHigh(), floatvalue_high, true), + false)) + { + addRowLabel(concat(stats->getLabel(), F(" Peak-to-peak"))); + addHtmlFloat(floatvalue_high - floatvalue_low, _nrDecimals); + somethingAdded = true; + } } } return somethingAdded; @@ -528,24 +542,34 @@ void P002_data_struct::webformLoad_2pt_calibrationCurve(struct EventStruct *even void P002_data_struct::formatADC_statistics(const __FlashStringHelper *label, int raw, bool includeOutputValue) const { addRowLabel(label); - addHtmlInt(raw); + float float_value{}; + + addHtml(formatADC_statistics_to_str(raw, float_value, includeOutputValue)); +} - float float_value = raw; +String P002_data_struct::formatADC_statistics_to_str( + int raw, + float& float_value, + bool includeOutputValue) const +{ + String res; + + res += raw; + + float_value = raw; # ifdef ESP32 if (_useFactoryCalibration) { float_value = applyADCFactoryCalibration(raw, _attenuation); - - html_add_estimate_symbol(); - addHtmlFloat(float_value, _nrDecimals); - addUnit(F("mV")); + res += F(" ≙ "); + res += toString(float_value, _nrDecimals); + res += (F(" [mV]")); } # endif // ifdef ESP32 if (includeOutputValue) { - addHtml(' '); - addHtml(F("→ ")); + res += F(" → "); float_value = applyCalibration(float_value); # ifndef LIMIT_BUILD_SIZE @@ -566,8 +590,10 @@ void P002_data_struct::formatADC_statistics(const __FlashStringHelper *label, in } } # endif // ifndef LIMIT_BUILD_SIZE - addHtmlFloat(float_value, _nrDecimals); + res += toString(float_value, _nrDecimals); } + + return res; } void P002_data_struct::format_2point_calib_statistics(const __FlashStringHelper *label, int raw, float float_value) const diff --git a/src/src/PluginStructs/P002_data_struct.h b/src/src/PluginStructs/P002_data_struct.h index 18d529811f..5db2570242 100644 --- a/src/src/PluginStructs/P002_data_struct.h +++ b/src/src/PluginStructs/P002_data_struct.h @@ -1,282 +1,285 @@ -#ifndef PLUGINSTRUCTS_P002_DATA_STRUCT_H -#define PLUGINSTRUCTS_P002_DATA_STRUCT_H - -#include "../../_Plugin_Helper.h" - -#include "../Helpers/OversamplingHelper.h" - -#ifdef USES_P002 - -# include - -# ifdef ESP32 -// Needed to get ADC Vref -#if ESP_IDF_VERSION_MAJOR >= 5 - #include - -#else - # include - # include -#endif -# endif // ifdef ESP32 - - -# define P002_OVERSAMPLING PCONFIG(0) -# ifdef ESP32 -# define P002_APPLY_FACTORY_CALIB PCONFIG(1) -# define P002_ATTENUATION PCONFIG(2) -# endif // ifdef ESP32 -# define P002_CALIBRATION_ENABLED PCONFIG(3) -# define P002_CALIBRATION_POINT1 PCONFIG_LONG(0) -# define P002_CALIBRATION_POINT2 PCONFIG_LONG(1) -# define P002_CALIBRATION_VALUE1 PCONFIG_FLOAT(0) -# define P002_CALIBRATION_VALUE2 PCONFIG_FLOAT(1) - -# define P002_MULTIPOINT_ENABLED PCONFIG(4) -# define P002_NR_MULTIPOINT_ITEMS PCONFIG(5) - -# define P002_USE_CURENT_SAMPLE 0 -# define P002_USE_OVERSAMPLING 1 -# define P002_USE_BINNING 2 - -// FIXME TD-er: Must test if HTML POST on ESP8266 will not take too much ram on save -# define P002_MAX_NR_MP_ITEMS 64 - -// We store the multipoint values and formula in a number of strings -// These will be stored in CustomTaskSettings -# define P002_SAVED_NR_LINES 0 -# define P002_LINE_INDEX_FORMULA 1 - -# define P002_LINE_IDX_FIRST_MP 6 // Leave some room for extra lines in the settings later -# define P002_STRINGS_PER_MP 2 // Nr of items per multi-point set -# define P002_Nlines (P002_LINE_IDX_FIRST_MP + (P002_STRINGS_PER_MP * (P002_NR_MULTIPOINT_ITEMS))) -# define P002_MAX_FORMULA_LENGTH 64 - -// Need to define the attenuation values to make sure no old or uninitialized value may be setting this to the wrong value. -# define P002_ADC_0db (ADC_ATTEN_DB_0 + 10) -# define P002_ADC_2_5db (ADC_ATTEN_DB_2_5 + 10) -# define P002_ADC_6db (ADC_ATTEN_DB_6 + 10) -#if ESP_IDF_VERSION_MAJOR >= 5 -# define P002_ADC_11db (ADC_ATTEN_DB_12 + 10) -#else -# define P002_ADC_11db (ADC_ATTEN_DB_11 + 10) -#endif - - -struct P002_ADC_Value_pair { - P002_ADC_Value_pair(float adc, float value) : _adc(adc), _value(value) {} - - P002_ADC_Value_pair(const P002_ADC_Value_pair&) = default; - - P002_ADC_Value_pair& operator=(const P002_ADC_Value_pair&) = default; - - P002_ADC_Value_pair& operator=(P002_ADC_Value_pair&&) = default; - - - // Needed to sort based on ADC value - bool operator<(const P002_ADC_Value_pair& other) const { - return this->_adc < other._adc; - } - - float _adc; - float _value; -}; - -struct P002_binningRange { - void set(int currentValue) { - if (currentValue > _maxADC) { - _maxADC = currentValue; - } - - if (currentValue < _minADC) { - _minADC = currentValue; - } - } - - bool inRange(int currentValue) const { - return _minADC <= currentValue && currentValue <= _maxADC; - } - - int _minADC = INT_MAX; - int _maxADC = INT_MIN; -}; - -struct P002_data_struct : public PluginTaskData_base { - P002_data_struct() = default; - virtual ~P002_data_struct() = default; - - void init(struct EventStruct *event); - -private: - -# ifndef LIMIT_BUILD_SIZE - void load(struct EventStruct *event); -# endif // ifndef LIMIT_BUILD_SIZE - - void webformLoad_2p_calibPoint( - const __FlashStringHelper *label, - const __FlashStringHelper *id_point, - const __FlashStringHelper *id_value, - int point, - float value) const; - -public: - - void webformLoad(struct EventStruct *event); - -# if FEATURE_PLUGIN_STATS - bool webformLoad_show_stats(struct EventStruct *event); -# endif // if FEATURE_PLUGIN_STATS - -private: - - void formatADC_statistics(const __FlashStringHelper *label, - int raw, - bool includeOutputValue = false) const; - void format_2point_calib_statistics(const __FlashStringHelper *label, - int raw, - float float_value) const; - -# ifdef ESP32 - static adc_atten_t getAttenuation(struct EventStruct *event); - static const __FlashStringHelper* AttenuationToString(adc_atten_t attenuation); - # if FEATURE_CHART_JS - static void webformLoad_calibrationCurve(struct EventStruct *event); - # endif // if FEATURE_CHART_JS -# endif // ifdef ESP32 - -# if FEATURE_CHART_JS - static const __FlashStringHelper* getChartXaxisLabel(struct EventStruct *event); -# endif // if FEATURE_CHART_JS - static void getInputRange(struct EventStruct *event, - int & min_value, - int & max_value, - bool ignoreCalibration = false); -# if FEATURE_CHART_JS - static void getChartRange(struct EventStruct *event, - int values[], - int count, - bool ignoreCalibration = false); - - static void webformLoad_2pt_calibrationCurve(struct EventStruct *event); - - void webformLoad_multipointCurve(struct EventStruct *event) const; -# endif // if FEATURE_CHART_JS - -public: - - static String webformSave(struct EventStruct *event); - - void takeSample(); - - bool getValue(float& float_value, - int & raw_value) const; - - void reset(); - - uint32_t getOversamplingCount() const; - -private: - - void resetOversampling(); - - void addOversamplingValue(int currentValue); - - bool getOversamplingValue(float& float_value, - int & raw_value) const; - -private: - -# ifndef LIMIT_BUILD_SIZE - - // Get index of the bin to match. - // Return -1 if no bin matched. - int getBinIndex(float currentValue) const; - - int computeADC_to_bin(const int& currentValue) const; - - void addBinningValue(int currentValue); - - bool getBinnedValue(float& float_value, - int & raw_value) const; -# endif // ifndef LIMIT_BUILD_SIZE - -public: - - // This needs to be a static function, as the object may not exist if the task is not enabled. - static float applyCalibration(struct EventStruct *event, - float float_value, - bool force = false); - - static float getCurrentValue(struct EventStruct *event, - int & raw_value); - - float applyCalibration(float float_value) const; - -# ifdef ESP32 - static bool useFactoryCalibration(struct EventStruct *event); - -# endif // ifdef ESP32 - -private: - -# ifndef LIMIT_BUILD_SIZE - float applyMultiPointInterpolation(float float_value, bool force = false) const; -# endif // ifndef LIMIT_BUILD_SIZE - - - // Map the input "point" values to the nearest int. - static void setTwoPointCalibration(struct EventStruct *event, - float adc1, - float adc2, - float out1, - float out2); - -public: - - bool plugin_set_config(struct EventStruct *event, String& string); - - -private: - - - OversamplingHelper OverSampling; - - int _calib_adc1 = 0; - int _calib_adc2 = 0; - float _calib_out1 = 0.0f; - float _calib_out2 = 0.0f; - - bool _use2pointCalibration = false; -# ifndef LIMIT_BUILD_SIZE - std::vector_multipoint; - std::vector _binning; - std::vector _binningRange; - bool _useMultipoint = false; -# endif // ifndef LIMIT_BUILD_SIZE - - int _pin_analogRead = -1; - - uint8_t _sampleMode = P002_USE_CURENT_SAMPLE; - - uint8_t _nrDecimals = 0; -# ifndef LIMIT_BUILD_SIZE - uint8_t _nrMultiPointItems = 0; - String _formula; - String _formula_preprocessed; -# endif // ifndef LIMIT_BUILD_SIZE -# ifdef ESP32 - bool _useFactoryCalibration = false; - -#if ESP_IDF_VERSION_MAJOR >= 5 - adc_atten_t _attenuation = ADC_ATTEN_DB_12; -#else - adc_atten_t _attenuation = ADC_ATTEN_DB_11; -#endif -# endif // ifdef ESP32 - -}; - - -#endif // ifdef USES_P002 -#endif // ifndef PLUGINSTRUCTS_P002_DATA_STRUCT_H +#ifndef PLUGINSTRUCTS_P002_DATA_STRUCT_H +#define PLUGINSTRUCTS_P002_DATA_STRUCT_H + +#include "../../_Plugin_Helper.h" + +#include "../Helpers/OversamplingHelper.h" + +#ifdef USES_P002 + +# include + +# ifdef ESP32 + +// Needed to get ADC Vref +# if ESP_IDF_VERSION_MAJOR >= 5 + # include + +# else // if ESP_IDF_VERSION_MAJOR >= 5 + # include + # include +# endif // if ESP_IDF_VERSION_MAJOR >= 5 +# endif // ifdef ESP32 + + +# define P002_OVERSAMPLING PCONFIG(0) +# ifdef ESP32 +# define P002_APPLY_FACTORY_CALIB PCONFIG(1) +# define P002_ATTENUATION PCONFIG(2) +# endif // ifdef ESP32 +# define P002_CALIBRATION_ENABLED PCONFIG(3) +# define P002_CALIBRATION_POINT1 PCONFIG_LONG(0) +# define P002_CALIBRATION_POINT2 PCONFIG_LONG(1) +# define P002_CALIBRATION_VALUE1 PCONFIG_FLOAT(0) +# define P002_CALIBRATION_VALUE2 PCONFIG_FLOAT(1) + +# define P002_MULTIPOINT_ENABLED PCONFIG(4) +# define P002_NR_MULTIPOINT_ITEMS PCONFIG(5) + +# define P002_USE_CURENT_SAMPLE 0 +# define P002_USE_OVERSAMPLING 1 +# define P002_USE_BINNING 2 + +// FIXME TD-er: Must test if HTML POST on ESP8266 will not take too much ram on save +# define P002_MAX_NR_MP_ITEMS 64 + +// We store the multipoint values and formula in a number of strings +// These will be stored in CustomTaskSettings +# define P002_SAVED_NR_LINES 0 +# define P002_LINE_INDEX_FORMULA 1 + +# define P002_LINE_IDX_FIRST_MP 6 // Leave some room for extra lines in the settings later +# define P002_STRINGS_PER_MP 2 // Nr of items per multi-point set +# define P002_Nlines (P002_LINE_IDX_FIRST_MP + (P002_STRINGS_PER_MP * (P002_NR_MULTIPOINT_ITEMS))) +# define P002_MAX_FORMULA_LENGTH 64 + +// Need to define the attenuation values to make sure no old or uninitialized value may be setting this to the wrong value. +# define P002_ADC_0db (ADC_ATTEN_DB_0 + 10) +# define P002_ADC_2_5db (ADC_ATTEN_DB_2_5 + 10) +# define P002_ADC_6db (ADC_ATTEN_DB_6 + 10) +# if ESP_IDF_VERSION_MAJOR >= 5 +# define P002_ADC_11db (ADC_ATTEN_DB_12 + 10) +# else // if ESP_IDF_VERSION_MAJOR >= 5 +# define P002_ADC_11db (ADC_ATTEN_DB_11 + 10) +# endif // if ESP_IDF_VERSION_MAJOR >= 5 + + +struct P002_ADC_Value_pair { + P002_ADC_Value_pair(float adc, float value) : _adc(adc), _value(value) {} + + P002_ADC_Value_pair(const P002_ADC_Value_pair&) = default; + + P002_ADC_Value_pair& operator=(const P002_ADC_Value_pair&) = default; + + P002_ADC_Value_pair& operator=(P002_ADC_Value_pair&&) = default; + + + // Needed to sort based on ADC value + bool operator<(const P002_ADC_Value_pair& other) const { + return this->_adc < other._adc; + } + + float _adc; + float _value; +}; + +struct P002_binningRange { + void set(int currentValue) { + if (currentValue > _maxADC) { + _maxADC = currentValue; + } + + if (currentValue < _minADC) { + _minADC = currentValue; + } + } + + bool inRange(int currentValue) const { + return _minADC <= currentValue && currentValue <= _maxADC; + } + + int _minADC = INT_MAX; + int _maxADC = INT_MIN; +}; + +struct P002_data_struct : public PluginTaskData_base { + P002_data_struct() = default; + virtual ~P002_data_struct() = default; + + void init(struct EventStruct *event); + +private: + +# ifndef LIMIT_BUILD_SIZE + void load(struct EventStruct *event); +# endif // ifndef LIMIT_BUILD_SIZE + + void webformLoad_2p_calibPoint( + const __FlashStringHelper *label, + const __FlashStringHelper *id_point, + const __FlashStringHelper *id_value, + int point, + float value) const; + +public: + + void webformLoad(struct EventStruct *event); + +# if FEATURE_PLUGIN_STATS + bool webformLoad_show_stats(struct EventStruct *event); +# endif // if FEATURE_PLUGIN_STATS + +private: + + void formatADC_statistics(const __FlashStringHelper *label, + int raw, + bool includeOutputValue = false) const; + String formatADC_statistics_to_str(int raw, + float& float_value, + bool includeOutputValue = false) const; + void format_2point_calib_statistics(const __FlashStringHelper *label, + int raw, + float float_value) const; + +# ifdef ESP32 + static adc_atten_t getAttenuation(struct EventStruct *event); + static const __FlashStringHelper* AttenuationToString(adc_atten_t attenuation); + # if FEATURE_CHART_JS + static void webformLoad_calibrationCurve(struct EventStruct *event); + # endif // if FEATURE_CHART_JS +# endif // ifdef ESP32 + +# if FEATURE_CHART_JS + static const __FlashStringHelper* getChartXaxisLabel(struct EventStruct *event); +# endif // if FEATURE_CHART_JS + static void getInputRange(struct EventStruct *event, + int & min_value, + int & max_value, + bool ignoreCalibration = false); +# if FEATURE_CHART_JS + static void getChartRange(struct EventStruct *event, + int values[], + int count, + bool ignoreCalibration = false); + + static void webformLoad_2pt_calibrationCurve(struct EventStruct *event); + + void webformLoad_multipointCurve(struct EventStruct *event) const; +# endif // if FEATURE_CHART_JS + +public: + + static String webformSave(struct EventStruct *event); + + void takeSample(); + + bool getValue(float& float_value, + int & raw_value) const; + + void reset(); + + uint32_t getOversamplingCount() const; + +private: + + void resetOversampling(); + + void addOversamplingValue(int currentValue); + + bool getOversamplingValue(float& float_value, + int & raw_value) const; + +private: + +# ifndef LIMIT_BUILD_SIZE + + // Get index of the bin to match. + // Return -1 if no bin matched. + int getBinIndex(float currentValue) const; + + int computeADC_to_bin(const int& currentValue) const; + + void addBinningValue(int currentValue); + + bool getBinnedValue(float& float_value, + int & raw_value) const; +# endif // ifndef LIMIT_BUILD_SIZE + +public: + + // This needs to be a static function, as the object may not exist if the task is not enabled. + static float applyCalibration(struct EventStruct *event, + float float_value, + bool force = false); + + static float getCurrentValue(struct EventStruct *event, + int & raw_value); + + float applyCalibration(float float_value) const; + +# ifdef ESP32 + static bool useFactoryCalibration(struct EventStruct *event); + +# endif // ifdef ESP32 + +private: + +# ifndef LIMIT_BUILD_SIZE + float applyMultiPointInterpolation(float float_value, + bool force = false) const; +# endif // ifndef LIMIT_BUILD_SIZE + + + // Map the input "point" values to the nearest int. + static void setTwoPointCalibration(struct EventStruct *event, + float adc1, + float adc2, + float out1, + float out2); + +public: + + bool plugin_set_config(struct EventStruct *event, + String & string); + +private: + + OversamplingHelperOverSampling; + + int _calib_adc1 = 0; + int _calib_adc2 = 0; + float _calib_out1 = 0.0f; + float _calib_out2 = 0.0f; + + bool _use2pointCalibration = false; +# ifndef LIMIT_BUILD_SIZE + std::vector_multipoint; + std::vector _binning; + std::vector _binningRange; + bool _useMultipoint = false; +# endif // ifndef LIMIT_BUILD_SIZE + + int _pin_analogRead = -1; + + uint8_t _sampleMode = P002_USE_CURENT_SAMPLE; + + uint8_t _nrDecimals = 0; +# ifndef LIMIT_BUILD_SIZE + uint8_t _nrMultiPointItems = 0; + String _formula; + String _formula_preprocessed; +# endif // ifndef LIMIT_BUILD_SIZE +# ifdef ESP32 + bool _useFactoryCalibration = false; + +# if ESP_IDF_VERSION_MAJOR >= 5 + adc_atten_t _attenuation = ADC_ATTEN_DB_12; +# else // if ESP_IDF_VERSION_MAJOR >= 5 + adc_atten_t _attenuation = ADC_ATTEN_DB_11; +# endif // if ESP_IDF_VERSION_MAJOR >= 5 +# endif // ifdef ESP32 +}; + + +#endif // ifdef USES_P002 +#endif // ifndef PLUGINSTRUCTS_P002_DATA_STRUCT_H diff --git a/src/src/PluginStructs/P026_data_struct.cpp b/src/src/PluginStructs/P026_data_struct.cpp index d235f12707..280ef56e34 100644 --- a/src/src/PluginStructs/P026_data_struct.cpp +++ b/src/src/PluginStructs/P026_data_struct.cpp @@ -128,7 +128,7 @@ float P026_get_value(uint8_t type) case P026_VALUETYPE_ip2: case P026_VALUETYPE_ip3: case P026_VALUETYPE_ip4: - res = NetworkLocalIP()[type - 5]; + res = NetworkLocalIP()[type - P026_VALUETYPE_ip1]; break; case P026_VALUETYPE_web: res = timePassedSince(lastWeb) / 1000.0f; diff --git a/src/src/PluginStructs/P047_data_struct.cpp b/src/src/PluginStructs/P047_data_struct.cpp index 0eb1a6978a..fc1c16bf61 100644 --- a/src/src/PluginStructs/P047_data_struct.cpp +++ b/src/src/PluginStructs/P047_data_struct.cpp @@ -173,7 +173,7 @@ float P047_data_struct::readTemperature() { I2C_write8_reg(_address, P047_ADAFRUIT_STATUS_BASE, P047_ADAFRUIT_GET_TEMPERATURE); delayMicroseconds(1000); - if (Wire.requestFrom(_address, 4) == 4) { + if (Wire.requestFrom(_address, 4u) == 4) { for (int b = 0; b < 4; ++b) { buf[b] = Wire.read(); } @@ -244,7 +244,7 @@ uint32_t P047_data_struct::getVersion() { I2C_write8_reg(_address, P047_ADAFRUIT_STATUS_BASE, P047_ADAFRUIT_GET_VERSION); - if (Wire.requestFrom(_address, 4) == 4) { + if (Wire.requestFrom(_address, 4u) == 4) { for (int b = 0; b < 4; ++b) { buf[b] = Wire.read(); } diff --git a/src/src/PluginStructs/P081_data_struct.cpp b/src/src/PluginStructs/P081_data_struct.cpp index e9af991e6e..7d0cd67af1 100644 --- a/src/src/PluginStructs/P081_data_struct.cpp +++ b/src/src/PluginStructs/P081_data_struct.cpp @@ -1,202 +1,202 @@ -#include "../PluginStructs/P081_data_struct.h" - -#ifdef USES_P081 - -P081_data_struct::P081_data_struct(const String& expression) -{ - const char *error; - - memset(&_expr, 0, sizeof(_expr)); - cron_parse_expr(expression.c_str(), &_expr, &error); - - if (!error) { - _initialized = true; - } else { - _error = String(error); - } -} - -bool P081_data_struct::hasError(String& error) const { - if (_initialized) { return false; } - error = _error; - return true; -} - -time_t P081_data_struct::get_cron_next(time_t date) const { - if (!_initialized) { return CRON_INVALID_INSTANT; } - return cron_next((cron_expr *)&_expr, date); -} - -time_t P081_data_struct::get_cron_prev(time_t date) const { - if (!_initialized) { return CRON_INVALID_INSTANT; } - return cron_prev((cron_expr *)&_expr, date); -} - -String P081_getCronExpr(taskIndex_t taskIndex) -{ - char expression[PLUGIN_081_EXPRESSION_SIZE + 1]; - - ZERO_FILL(expression); - LoadCustomTaskSettings(taskIndex, reinterpret_cast(&expression), PLUGIN_081_EXPRESSION_SIZE); - String res(expression); - - res.trim(); - return res; -} - -time_t P081_computeNextCronTime(taskIndex_t taskIndex, time_t last) -{ - P081_data_struct *P081_data = - static_cast(getPluginTaskData(taskIndex)); - - if ((nullptr != P081_data) && P081_data->isInitialized()) { - // int32_t freeHeapStart = ESP.getFreeHeap(); - - time_t res = P081_data->get_cron_next(last); - - /* - int32_t freeHeapEnd = ESP.getFreeHeap(); - - if (freeHeapEnd < freeHeapStart) { - String log = F("Cron: Free Heap Decreased: "); - log += String(freeHeapStart - freeHeapEnd); - log += F(" ("); - log += freeHeapStart; - log += F(" -> "); - log += freeHeapEnd; - addLog(LOG_LEVEL_INFO, log); - } - */ - return res; - } - return CRON_INVALID_INSTANT; -} - -time_t P081_getCronExecTime(taskIndex_t taskIndex, uint8_t varNr) -{ - return static_cast(UserVar.getUint32(taskIndex, varNr)); -} - -void P081_setCronExecTimes(struct EventStruct *event, time_t lastExecTime, time_t nextExecTime) { - UserVar.setUint32(event->TaskIndex, LASTEXECUTION, static_cast(lastExecTime)); - UserVar.setUint32(event->TaskIndex, NEXTEXECUTION, static_cast(nextExecTime)); -} - -time_t P081_getCurrentTime() -{ - node_time.now(); - - // FIXME TD-er: Why work on a deepcopy of tm? - struct tm current = node_time.local_tm; - - return mktime((struct tm *)¤t); -} - -void P081_check_or_init(struct EventStruct *event) -{ - if (node_time.systemTimePresent()) { - const time_t current_time = P081_getCurrentTime(); - time_t last_exec_time = P081_getCronExecTime(event->TaskIndex, LASTEXECUTION); - time_t next_exec_time = P081_getCronExecTime(event->TaskIndex, NEXTEXECUTION); - - // Must check if the values of LASTEXECUTION and NEXTEXECUTION make sense. - // These can be invalid values from a reboot, or simply contain uninitialized values. - if ((last_exec_time > current_time) || (last_exec_time == CRON_INVALID_INSTANT) || (next_exec_time == CRON_INVALID_INSTANT)) { - // Last execution time cannot be correct. - last_exec_time = CRON_INVALID_INSTANT; - const time_t tmp_next = P081_computeNextCronTime(event->TaskIndex, current_time); - - if ((tmp_next < next_exec_time) || (next_exec_time == CRON_INVALID_INSTANT)) { - next_exec_time = tmp_next; - } - P081_setCronExecTimes(event, CRON_INVALID_INSTANT, next_exec_time); - } - } -} - -# if PLUGIN_081_DEBUG -void PrintCronExp(struct cron_expr_t e) { - serialPrintln(F("===DUMP Cron Expression===")); - serialPrint(F("Seconds:")); - - for (int i = 0; i < 8; i++) - { - serialPrint(e.seconds[i]); - serialPrint(","); - } - serialPrintln(); - serialPrint(F("Minutes:")); - - for (int i = 0; i < 8; i++) - { - serialPrint(e.minutes[i]); - serialPrint(","); - } - serialPrintln(); - serialPrint(F("hours:")); - - for (int i = 0; i < 3; i++) - { - serialPrint(e.hours[i]); - serialPrint(","); - } - serialPrintln(); - serialPrint(F("months:")); - - for (int i = 0; i < 2; i++) - { - serialPrint(e.months[i]); - serialPrint(","); - } - serialPrintln(); - serialPrint(F("days_of_week:")); - - for (int i = 0; i < 1; i++) - { - serialPrint(e.days_of_week[i]); - serialPrint(","); - } - serialPrintln(); - serialPrint(F("days_of_month:")); - - for (int i = 0; i < 4; i++) - { - serialPrint(e.days_of_month[i]); - serialPrint(","); - } - serialPrintln(); - serialPrintln(F("END=DUMP Cron Expression===")); -} - -# endif // if PLUGIN_081_DEBUG - - -String P081_formatExecTime(taskIndex_t taskIndex, uint8_t varNr) { - time_t exec_time = P081_getCronExecTime(taskIndex, varNr); - - if (exec_time != CRON_INVALID_INSTANT) { - return formatDateTimeString(*gmtime(&exec_time)); - } - return F("-"); -} - -void P081_html_show_cron_expr(struct EventStruct *event) { - P081_data_struct *P081_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if ((nullptr != P081_data) && P081_data->isInitialized()) { - String error; - - if (P081_data->hasError(error)) { - addRowLabel(F("Error")); - addHtml(error); - } else { - addRowLabel(F("Last Exec Time")); - addHtml(P081_formatExecTime(event->TaskIndex, LASTEXECUTION)); - addRowLabel(F("Next Exec Time")); - addHtml(P081_formatExecTime(event->TaskIndex, NEXTEXECUTION)); - } - } -} - -#endif // ifdef USES_P081 +#include "../PluginStructs/P081_data_struct.h" + +#ifdef USES_P081 + +P081_data_struct::P081_data_struct(const String& expression) +{ + const char *error; + + memset(&_expr, 0, sizeof(_expr)); + cron_parse_expr(expression.c_str(), &_expr, &error); + + if (!error) { + _initialized = true; + } else { + _error = String(error); + } +} + +bool P081_data_struct::hasError(String& error) const { + if (_initialized) { return false; } + error = _error; + return true; +} + +time_t P081_data_struct::get_cron_next(time_t date) const { + if (!_initialized) { return CRON_INVALID_INSTANT; } + return cron_next((cron_expr *)&_expr, date); +} + +time_t P081_data_struct::get_cron_prev(time_t date) const { + if (!_initialized) { return CRON_INVALID_INSTANT; } + return cron_prev((cron_expr *)&_expr, date); +} + +String P081_getCronExpr(taskIndex_t taskIndex) +{ + char expression[PLUGIN_081_EXPRESSION_SIZE + 1]; + + ZERO_FILL(expression); + LoadCustomTaskSettings(taskIndex, reinterpret_cast(&expression), PLUGIN_081_EXPRESSION_SIZE); + String res(expression); + + res.trim(); + return res; +} + +time_t P081_computeNextCronTime(taskIndex_t taskIndex, time_t last) +{ + P081_data_struct *P081_data = + static_cast(getPluginTaskData(taskIndex)); + + if ((nullptr != P081_data) && P081_data->isInitialized()) { + // int32_t freeHeapStart = ESP.getFreeHeap(); + + time_t res = P081_data->get_cron_next(last); + + /* + int32_t freeHeapEnd = ESP.getFreeHeap(); + + if (freeHeapEnd < freeHeapStart) { + String log = F("Cron: Free Heap Decreased: "); + log += String(freeHeapStart - freeHeapEnd); + log += F(" ("); + log += freeHeapStart; + log += F(" -> "); + log += freeHeapEnd; + addLog(LOG_LEVEL_INFO, log); + } + */ + return res; + } + return CRON_INVALID_INSTANT; +} + +time_t P081_getCronExecTime(taskIndex_t taskIndex, uint8_t varNr) +{ + return static_cast(UserVar.getUint32(taskIndex, varNr)); +} + +void P081_setCronExecTimes(struct EventStruct *event, time_t lastExecTime, time_t nextExecTime) { + UserVar.setUint32(event->TaskIndex, LASTEXECUTION, static_cast(lastExecTime)); + UserVar.setUint32(event->TaskIndex, NEXTEXECUTION, static_cast(nextExecTime)); +} + +time_t P081_getCurrentTime() +{ + node_time.now_(); + + // FIXME TD-er: Why work on a deepcopy of tm? + struct tm current = node_time.local_tm; + + return mktime((struct tm *)¤t); +} + +void P081_check_or_init(struct EventStruct *event) +{ + if (node_time.systemTimePresent()) { + const time_t current_time = P081_getCurrentTime(); + time_t last_exec_time = P081_getCronExecTime(event->TaskIndex, LASTEXECUTION); + time_t next_exec_time = P081_getCronExecTime(event->TaskIndex, NEXTEXECUTION); + + // Must check if the values of LASTEXECUTION and NEXTEXECUTION make sense. + // These can be invalid values from a reboot, or simply contain uninitialized values. + if ((last_exec_time > current_time) || (last_exec_time == CRON_INVALID_INSTANT) || (next_exec_time == CRON_INVALID_INSTANT)) { + // Last execution time cannot be correct. + last_exec_time = CRON_INVALID_INSTANT; + const time_t tmp_next = P081_computeNextCronTime(event->TaskIndex, current_time); + + if ((tmp_next < next_exec_time) || (next_exec_time == CRON_INVALID_INSTANT)) { + next_exec_time = tmp_next; + } + P081_setCronExecTimes(event, CRON_INVALID_INSTANT, next_exec_time); + } + } +} + +# if PLUGIN_081_DEBUG +void PrintCronExp(struct cron_expr_t e) { + serialPrintln(F("===DUMP Cron Expression===")); + serialPrint(F("Seconds:")); + + for (int i = 0; i < 8; i++) + { + serialPrint(e.seconds[i]); + serialPrint(","); + } + serialPrintln(); + serialPrint(F("Minutes:")); + + for (int i = 0; i < 8; i++) + { + serialPrint(e.minutes[i]); + serialPrint(","); + } + serialPrintln(); + serialPrint(F("hours:")); + + for (int i = 0; i < 3; i++) + { + serialPrint(e.hours[i]); + serialPrint(","); + } + serialPrintln(); + serialPrint(F("months:")); + + for (int i = 0; i < 2; i++) + { + serialPrint(e.months[i]); + serialPrint(","); + } + serialPrintln(); + serialPrint(F("days_of_week:")); + + for (int i = 0; i < 1; i++) + { + serialPrint(e.days_of_week[i]); + serialPrint(","); + } + serialPrintln(); + serialPrint(F("days_of_month:")); + + for (int i = 0; i < 4; i++) + { + serialPrint(e.days_of_month[i]); + serialPrint(","); + } + serialPrintln(); + serialPrintln(F("END=DUMP Cron Expression===")); +} + +# endif // if PLUGIN_081_DEBUG + + +String P081_formatExecTime(taskIndex_t taskIndex, uint8_t varNr) { + time_t exec_time = P081_getCronExecTime(taskIndex, varNr); + + if (exec_time != CRON_INVALID_INSTANT) { + return formatDateTimeString(*gmtime(&exec_time)); + } + return F("-"); +} + +void P081_html_show_cron_expr(struct EventStruct *event) { + P081_data_struct *P081_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if ((nullptr != P081_data) && P081_data->isInitialized()) { + String error; + + if (P081_data->hasError(error)) { + addRowLabel(F("Error")); + addHtml(error); + } else { + addRowLabel(F("Last Exec Time")); + addHtml(P081_formatExecTime(event->TaskIndex, LASTEXECUTION)); + addRowLabel(F("Next Exec Time")); + addHtml(P081_formatExecTime(event->TaskIndex, NEXTEXECUTION)); + } + } +} + +#endif // ifdef USES_P081 diff --git a/src/src/PluginStructs/P110_data_struct.cpp b/src/src/PluginStructs/P110_data_struct.cpp index 4ef40dcd91..7b908aeecf 100644 --- a/src/src/PluginStructs/P110_data_struct.cpp +++ b/src/src/PluginStructs/P110_data_struct.cpp @@ -2,28 +2,30 @@ #ifdef USES_P110 -P110_data_struct::P110_data_struct(uint8_t i2c_addr, int timing, bool range) : i2cAddress(i2c_addr), timing(timing), range(range) {} +P110_data_struct::P110_data_struct(uint8_t i2c_addr, int timing, bool range) : + _i2cAddress(i2c_addr), + _timing(timing), + _range(range) {} // **************************************************************************/ // Initialize VL53L0X // **************************************************************************/ -bool P110_data_struct::begin() { - initState = true; - - sensor.setAddress(i2cAddress); // Initialize for configured address +bool P110_data_struct::begin(uint32_t interval_ms) { + _timeToWait = 0; + _initPhase = P110_initPhases::Undefined; + sensor.setAddress(_i2cAddress); // Initialize for configured address if (!sensor.init()) { if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, strformat(F("VL53L0X: Sensor not found, init failed for 0x%02x"), i2cAddress)); + addLogMove(LOG_LEVEL_INFO, strformat(F("VL53L0X: Sensor not found, init failed for 0x%02x"), _i2cAddress)); addLog(LOG_LEVEL_INFO, sensor.getInitResult()); } - initState = false; - return initState; + return false; } sensor.setTimeout(500); - if (range) { + if (_range) { // lower the return signal rate limit (default is 0.25 MCPS) sensor.setSignalRateLimit(0.1); @@ -32,78 +34,149 @@ bool P110_data_struct::begin() { sensor.setVcselPulsePeriod(VL53L0X::VcselPeriodFinalRange, 14); } - sensor.setMeasurementTimingBudget(timing * 1000); + sensor.setMeasurementTimingBudget(_timing * 1000); + + _initPhase = P110_initPhases::InitDelay; + _timeToWait = millis() + _timing + 50; + + sensor.startContinuous(interval_ms); - initPhase = P110_initPhases::InitDelay; - timeToWait = timing + 50; + return true; +} - return initState; +bool P110_data_struct::check_reading_ready(struct EventStruct *event) { + if (_initPhase == P110_initPhases::InitDelay) { + if ((_timeToWait != 0) && timeOutReached(_timeToWait)) { + _timeToWait = 0; + _initPhase = P110_initPhases::Ready; + } + } else { + if (readDistance() >= 0) { + Scheduler.schedule_task_device_timer(event->TaskIndex, millis()); + } + } + return true; } -bool P110_data_struct::plugin_fifty_per_second() { - if (initPhase == P110_initPhases::InitDelay) { - timeToWait -= 20; // milliseconds +bool P110_data_struct::plugin_read(struct EventStruct *event) { + bool success = false; + + if (isReadSuccessful()) { + const float new_distance = getDistance(); + const float prev_distance = _prev_distance; + + const bool first_sample = (prev_distance < 0.0f); + + const float estimator = (_filtered < 0.0f) + ? new_distance + : _filtered; + + const float ratio_filtered = 16; + const float ratio_newVal = _prev_newval_ratio; + _prev_newval_ratio = std::abs(estimator - new_distance); + + _filtered = + ((ratio_filtered * estimator) + (ratio_newVal * new_distance)) + / (ratio_filtered + ratio_newVal); + + const float dist = _filtered; + const float p_dist = prev_distance; + + // Check trend: + // 0 = equal + // -1 = move closer + // 1 = move away + + const int16_t displacement = first_sample ? 0 : roundf(dist - p_dist); + const int16_t disp_dir = (displacement == 0) + ? 0 + : (displacement > 0) ? 1 : -1; + + //const bool direction_changed = disp_dir != static_cast(UserVar.getFloat(event->TaskIndex, 1)); + const bool triggered = + // direction_changed || + (std::abs(displacement) > P110_DELTA); - // String log = F("VL53L0X: remaining wait: "); - // log += timeToWait; - // addLogMove(LOG_LEVEL_INFO, log); + # ifdef P110_INFO_LOG - if (timeToWait <= 0) { - timeToWait = 0; - initPhase = P110_initPhases::Ready; + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat(F("VL53L0x: Perform read: trig: %d, prev: %d, dist: %d"), triggered, p_dist, dist)); + } + # endif // ifdef P110_INFO_LOG + // Value is classified as invalid when > 8190, so no conversion or 'split' needed + UserVar.setFloat(event->TaskIndex, 0, _filtered); + UserVar.setFloat(event->TaskIndex, 1, disp_dir); // Trend of value + + if (first_sample || triggered || (P110_SEND_ALWAYS == 1)) { + // Update the "previous" distance. + _prev_distance = _filtered; + success = true; } } - return true; + return success; } -uint16_t P110_data_struct::getDistance() { - return _distance; +int16_t P110_data_struct::getDistance() { + const int res = _distance; + + _distance = P110_DISTANCE_WAITING; + return res; } -uint16_t P110_data_struct::readDistance() { - uint16_t dist = 0xFFFF; // Invalid +int16_t P110_data_struct::readDistance() { + if (_initPhase != P110_initPhases::Ready) { + return P110_DISTANCE_UNINITIALIZED; + } - if (initPhase != P110_initPhases::Ready) { return dist; } + int16_t dist{}; - # ifdef P110_DEBUG_LOG + if (sensor.asyncReadRangeContinuousMillimeters(dist)) { + if ((dist >= 0) && (dist < 8192)) { + // Only keep a copy of valid distance readings. + // Since the distance reading is later called from PLUGIN_READ, + // we might have had a new reading inbetween which could be a "still waiting" + // value and then we lost the actual reading. - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - addLogMove(LOG_LEVEL_DEBUG, strformat(F("VL53L0X: idx: 0x%02x init: %d"), - i2cAddress, initState)); - } - # endif // P110_DEBUG_LOG - - if (initState) { - success = true; - dist = sensor.readRangeSingleMillimeters(); - - if (sensor.timeoutOccurred()) { - # ifdef P110_DEBUG_LOG - addLog(LOG_LEVEL_DEBUG, F("VL53L0X: TIMEOUT")); - # endif // P110_DEBUG_LOG - success = false; - } else if (dist >= 8190u) { - # ifdef P110_DEBUG_LOG - addLog(LOG_LEVEL_DEBUG, concat(F("VL53L0X: NO MEASUREMENT: "), dist)); - # endif // P110_DEBUG_LOG - success = false; - } else { _distance = dist; + return _distance; } + } - # ifdef P110_INFO_LOG + if (dist == VL53L0X_WAITING) { + // Just waiting + // No need to keep sending many logs per second + return P110_DISTANCE_WAITING; + } - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, strformat(F("VL53L0X: Addr: 0x%02x / Timing: %d / Long Range: %d / success: %d / Distance: %d"), - i2cAddress, timing, range, success, dist)); - } - # endif // P110_INFO_LOG + +# ifdef P110_DEBUG_LOG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLogMove(LOG_LEVEL_DEBUG, strformat(F("VL53L0X: idx: 0x%02x init: %u"), + _i2cAddress, static_cast(_initPhase))); + } +# endif // P110_DEBUG_LOG + + if (sensor.timeoutOccurred()) { +# ifdef P110_DEBUG_LOG + addLog(LOG_LEVEL_DEBUG, F("VL53L0X: TIMEOUT")); +# endif // P110_DEBUG_LOG + return P110_DISTANCE_READ_TIMEOUT; + } else if (dist >= 8190u) { +# ifdef P110_DEBUG_LOG + addLog(LOG_LEVEL_DEBUG, concat(F("VL53L0X: NO MEASUREMENT: "), dist)); +# endif // P110_DEBUG_LOG + return P110_DISTANCE_OUT_OF_RANGE; } - return dist; + +# ifdef P110_DEBUG_LOG + addLog(LOG_LEVEL_DEBUG, F("VL53L0X: NO MEASUREMENT: 0xFFFF")); +# endif // P110_DEBUG_LOG + return P110_DISTANCE_READ_ERROR; } -bool P110_data_struct::isReadSuccessful() { - return success; +bool P110_data_struct::isReadSuccessful() const { + return _distance >= 0; } #endif // ifdef USES_P110 diff --git a/src/src/PluginStructs/P110_data_struct.h b/src/src/PluginStructs/P110_data_struct.h index 46cb07401f..d59d02dd26 100644 --- a/src/src/PluginStructs/P110_data_struct.h +++ b/src/src/PluginStructs/P110_data_struct.h @@ -11,7 +11,7 @@ # ifdef P110_DEBUG_LOG # undef P110_DEBUG_LOG # endif // ifdef P110_DEBUG_LOG -# endif // if defined(LIMIT_BUILD_SIZE) || defined(BUILD_NO_DEBUG) +# endif // if defined(LIMIT_BUILD_SIZE) || defined(BUILD_NO_DEBUG) # include @@ -21,10 +21,18 @@ # define P110_SEND_ALWAYS PCONFIG(3) # define P110_DELTA PCONFIG(4) +# define P110_DISTANCE_UNINITIALIZED -1 +# define P110_DISTANCE_READ_TIMEOUT -2 +# define P110_DISTANCE_READ_ERROR -3 +# define P110_DISTANCE_OUT_OF_RANGE -4 +# define P110_DISTANCE_WAITING -5 + + enum class P110_initPhases : uint8_t { - Ready = 0x00, - InitDelay = 0x01, - Undefined = 0xFF + Undefined = 0xFF, + InitDelay = 0x00, + Ready = 0x01, + WaitMeasurement = 0x02 }; struct P110_data_struct : public PluginTaskData_base { @@ -36,25 +44,32 @@ struct P110_data_struct : public PluginTaskData_base { P110_data_struct() = delete; virtual ~P110_data_struct() = default; - bool begin(); - uint16_t readDistance(); - uint16_t getDistance(); - bool isReadSuccessful(); - bool plugin_fifty_per_second(); + bool begin(uint32_t interval_ms); + int16_t readDistance(); + + // Return last reading and clear the cached _distance value + // This way we know if there was a new successful reading since last call of getDistance() + int16_t getDistance(); + bool isReadSuccessful() const; + bool check_reading_ready(struct EventStruct *event); + + bool plugin_read(struct EventStruct *event); private: VL53L0X sensor; - uint16_t _distance = 0xFFFF; - uint8_t i2cAddress; - int timing; - bool range; + float _prev_distance = -1.0f; + float _filtered = -1.0f; + float _prev_newval_ratio = 1.0f; + + const uint8_t _i2cAddress; + const int _timing; + const bool _range; - int32_t timeToWait = 0; - P110_initPhases initPhase = P110_initPhases::Undefined; - bool initState = false; - bool success = false; + int16_t _distance = P110_DISTANCE_UNINITIALIZED; + int32_t _timeToWait = 0; + P110_initPhases _initPhase = P110_initPhases::Undefined; }; #endif // ifdef USES_P110 #endif // ifndef PLUGINSTRUCTS_P110_DATA_STRUCT_H diff --git a/src/src/PluginStructs/P143_data_struct.cpp b/src/src/PluginStructs/P143_data_struct.cpp index 70fb098732..f1b7e36a8b 100644 --- a/src/src/PluginStructs/P143_data_struct.cpp +++ b/src/src/PluginStructs/P143_data_struct.cpp @@ -1,886 +1,886 @@ -#include "../PluginStructs/P143_data_struct.h" - -#ifdef USES_P143 - -/************************************************************************** - * toString for P143_DeviceType_e - *************************************************************************/ -const __FlashStringHelper* toString(P143_DeviceType_e device) { - switch (device) { - case P143_DeviceType_e::AdafruitEncoder: return F("Adafruit"); - # if P143_FEATURE_INCLUDE_M5STACK - case P143_DeviceType_e::M5StackEncoder: return F("M5Stack"); - # endif // if P143_FEATURE_INCLUDE_M5STACK - # if P143_FEATURE_INCLUDE_DFROBOT - case P143_DeviceType_e::DFRobotEncoder: return F("DFRobot"); - # endif // if P143_FEATURE_INCLUDE_DFROBOT - } - return F(""); -} - -# if P143_FEATURE_COUNTER_COLORMAPPING - -/************************************************************************** - * toString for P143_CounterMapping_e - *************************************************************************/ -const __FlashStringHelper* toString(P143_CounterMapping_e counter) { - switch (counter) { - case P143_CounterMapping_e::None: return F("None"); - case P143_CounterMapping_e::ColorMapping: return F("Color mapping"); - case P143_CounterMapping_e::ColorGradient: return F("Color gradient"); - } - return F(""); -} - -# endif // if P143_FEATURE_COUNTER_COLORMAPPING - -/************************************************************************** - * toString for P143_ButtonAction_e - *************************************************************************/ -const __FlashStringHelper* toString(P143_ButtonAction_e action) { - switch (action) { - case P143_ButtonAction_e::PushButton: return F("Pushbutton"); - case P143_ButtonAction_e::PushButtonInverted: return F("Pushbutton (inverted)"); - case P143_ButtonAction_e::ToggleSwitch: return F("Toggle switch"); - } - return F(""); -} - -/******************************************************************* - * P143_CheckEncoderDefaultSettings: Helper to set config defaults after changing the Encoder type - ******************************************************************/ -void P143_CheckEncoderDefaultSettings(struct EventStruct *event) { - if (P143_ENCODER_TYPE != P143_PREVIOUS_TYPE) { - switch (static_cast(P143_ENCODER_TYPE)) { - case P143_DeviceType_e::AdafruitEncoder: - P143_ADAFRUIT_COLOR_AND_BRIGHTNESS = 0x0000001E; // Black, with 30 (0x1E) brightness (1..255) - P143_OFFSET_POSITION = 0; - break; - # if P143_FEATURE_INCLUDE_M5STACK - case P143_DeviceType_e::M5StackEncoder: - P143_ADAFRUIT_COLOR_AND_BRIGHTNESS = 0x0000001E; // Black, with 30 (0x1E) brightness (1..255) - P143_M5STACK_COLOR_AND_SELECTION = 0x00000000; // Black, with both Leds using Color mapping - P143_OFFSET_POSITION = 0; - break; - # endif // if P143_FEATURE_INCLUDE_M5STACK - # if P143_FEATURE_INCLUDE_DFROBOT - case P143_DeviceType_e::DFRobotEncoder: - P143_DFROBOT_LED_GAIN = P143_DFROBOT_MAX_GAIN; - P143_OFFSET_POSITION = 0; - break; - # endif // if P143_FEATURE_INCLUDE_DFROBOT - } - P143_PREVIOUS_TYPE = P143_ENCODER_TYPE; // It's now up to date - } -} - -/************************************************************************** - * Constructor - *************************************************************************/ -P143_data_struct::P143_data_struct(struct EventStruct *event) { - _device = static_cast(P143_ENCODER_TYPE); - _i2cAddress = P143_I2C_ADDR; - _encoderPosition = P143_INITIAL_POSITION; - _encoderMin = P143_MINIMAL_POSITION; - _encoderMax = P143_MAXIMAL_POSITION; - _brightness = P143_NEOPIXEL_BRIGHTNESS; - _buttonLongPress = P143_GET_LONGPRESS_INTERVAL; - _enableLongPress = P143_PLUGIN_ENABLE_LONGPRESS; - # if P143_FEATURE_INCLUDE_DFROBOT - _initialOffset = P143_OFFSET_POSITION; - # endif // if P143_FEATURE_INCLUDE_DFROBOT -} - -/***************************************************** - * Destructor - ****************************************************/ -P143_data_struct::~P143_data_struct() { - delete Adafruit_Seesaw; - delete Adafruit_Spixel; -} - -/************************************************************************** - * plugin_init Initialize sensor and prepare for reading - *************************************************************************/ -bool P143_data_struct::plugin_init(struct EventStruct *event) { - if (!_initialized) { - switch (_device) { - case P143_DeviceType_e::AdafruitEncoder: - { - Adafruit_Seesaw = new (std::nothrow) Adafruit_seesaw(); - Adafruit_Spixel = new (std::nothrow) seesaw_NeoPixel(1, P143_SEESAW_NEOPIX, NEO_GRB + NEO_KHZ800); - - if ((nullptr != Adafruit_Seesaw) && (nullptr != Adafruit_Spixel)) { - _initialized = Adafruit_Seesaw->begin(_i2cAddress) && Adafruit_Spixel->begin(_i2cAddress); - uint32_t version = ((Adafruit_Seesaw->getVersion() >> 16) & 0xFFFF); - - if (_initialized && (version != P143_ADAFRUIT_ENCODER_PRODUCTID)) { // Check Adafruit product ID - _initialized = false; - } - - if (_initialized) { - // use a pin for the built in encoder switch - Adafruit_Seesaw->pinMode(P143_SEESAW_SWITCH, INPUT_PULLUP); - - // set starting position - Adafruit_Seesaw->setEncoderPosition(_encoderPosition); - - // Enable interrupts on Switch pin - Adafruit_Seesaw->setGPIOInterrupts((uint32_t)1 << P143_SEESAW_SWITCH, 1); - Adafruit_Seesaw->enableEncoderInterrupt(); - - // We only have 1 pixel available... - Adafruit_Spixel->setBrightness(_brightness); // Set brightness before color! - _red = P143_ADAFRUIT_COLOR_RED; - _green = P143_ADAFRUIT_COLOR_GREEN; - _blue = P143_ADAFRUIT_COLOR_BLUE; - Adafruit_Spixel->setPixelColor(0, _red, _green, _blue); - Adafruit_Spixel->show(); - } - } - break; - } - # if P143_FEATURE_INCLUDE_M5STACK - case P143_DeviceType_e::M5StackEncoder: - { - // Reset, only actually supported with upgraded firmware - I2C_write8_reg(_i2cAddress, P143_M5STACK_REG_MODE, 0x00); - - # if P143_FEATURE_M5STACK_V1_1 - - // Check if we need to use the offset method - // - Read current counter - // - Write incremented value, only works with upgraded firmware - // - re-read and if not changed we use an offset to handle passing the set limits - int16_t encoderCount = I2C_readS16_LE_reg(_i2cAddress, P143_M5STACK_REG_ENCODER); - encoderCount++; - I2C_write16_LE_reg(_i2cAddress, P143_M5STACK_REG_ENCODER, encoderCount); - - if (encoderCount != I2C_readS16_LE_reg(_i2cAddress, P143_M5STACK_REG_ENCODER)) { - _useOffset = true; - _previousEncoder = encoderCount - 1; - _offsetEncoder = _previousEncoder - _encoderPosition; - } else { - // Don't need to use offset, set configured initial value - encoderCount = _encoderPosition; - I2C_write16_LE_reg(_i2cAddress, P143_M5STACK_REG_ENCODER, encoderCount); - } - # else // if P143_FEATURE_M5STACK_V1_1 - _useOffset = true; // No check needed, we need to use the offset method - # endif // if P143_FEATURE_M5STACK_V1_1 - - _red = P143_ADAFRUIT_COLOR_RED; // Also used for M5Stack Led 1 - _green = P143_ADAFRUIT_COLOR_GREEN; - _blue = P143_ADAFRUIT_COLOR_BLUE; - - // Set LED initial state - m5stack_setPixelColor(1, _red, _green, _blue); - m5stack_setPixelColor(2, P143_M5STACK2_COLOR_RED, P143_M5STACK2_COLOR_GREEN, P143_M5STACK2_COLOR_BLUE); - _initialized = true; - break; - } - # endif // if P143_FEATURE_INCLUDE_M5STACK - # if P143_FEATURE_INCLUDE_DFROBOT - case P143_DeviceType_e::DFRobotEncoder: - { - _initialized = P143_DFROBOT_ENCODER_PID == I2C_read16_reg(_i2cAddress, P143_DFROBOT_ENCODER_PID_MSB_REG); - - if (_initialized) { - // Set encoder position - I2C_write16_reg(_i2cAddress, P143_DFROBOT_ENCODER_COUNT_MSB_REG, _initialOffset + _encoderPosition); - - // Set led gain - I2C_write8_reg(_i2cAddress, P143_DFROBOT_ENCODER_GAIN_REG, P143_DFROBOT_LED_GAIN & 0xFF); - } - break; - } - # endif // if P143_FEATURE_INCLUDE_DFROBOT - } - - // Set initial button state - _buttonState = (P143_ButtonAction_e::PushButtonInverted == static_cast(P143_PLUGIN_BUTTON_ACTION)) ? 0 : 1; - - UserVar.setFloat(event->TaskIndex, 1, _buttonState); - - # if P143_FEATURE_COUNTER_COLORMAPPING - - _mapping = static_cast(P143_PLUGIN_COUNTER_MAPPING); - - // Load color mapping data - LoadCustomTaskSettings(event->TaskIndex, _colorMapping, P143_STRINGS, 0); - - for (int i = P143_STRINGS - 1; i >= 0; --i) { - _colorMapping[i].trim(); - - if ((_colorMaps == -1) && !_colorMapping[i].isEmpty()) { - _colorMaps = i; - } - } - - counterToColorMapping(event); // Update color - # endif // if P143_FEATURE_COUNTER_COLORMAPPING - - if (loglevelActiveFor(_initialized ? LOG_LEVEL_INFO : LOG_LEVEL_ERROR)) { - String log = concat(F("I2CEncoders: INIT "), toString(_device)); - log += F(", "); - - # if P143_FEATURE_INCLUDE_M5STACK - - if (_useOffset) { - log += F("using Offset method, "); - } - # endif // if P143_FEATURE_INCLUDE_M5STACK - - if (!_initialized) { - log += F("FAILED."); - } else { - log += F("Success."); - } - addLogMove(_initialized ? LOG_LEVEL_INFO : LOG_LEVEL_ERROR, log); - } - } - - return _initialized; -} - -/************************************************************************** - * plugin_exit De-initialize and prepare for destructor - *************************************************************************/ -bool P143_data_struct::plugin_exit(struct EventStruct *event) { - if (_initialized) { - _initialized = false; // We don't want any unexpected events - - switch (_device) { - case P143_DeviceType_e::AdafruitEncoder: - { - // Stop interrupthandler - if (nullptr != Adafruit_Seesaw) { - Adafruit_Seesaw->disableEncoderInterrupt(); - } - - if ((nullptr != Adafruit_Spixel) && P143_PLUGIN_EXIT_LED_OFF) { - // Turn off Neopixel 0 by setting the R/G/B color to black - Adafruit_Spixel->setPixelColor(0, 0, 0, 0); - Adafruit_Spixel->show(); - } - break; - } - # if P143_FEATURE_INCLUDE_M5STACK - case P143_DeviceType_e::M5StackEncoder: - { - if (P143_PLUGIN_EXIT_LED_OFF) { - // Turn off both LEDs - m5stack_setPixelColor(0, 0, 0, 0); - } - break; - } - # endif // if P143_FEATURE_INCLUDE_M5STACK - # if P143_FEATURE_INCLUDE_DFROBOT - case P143_DeviceType_e::DFRobotEncoder: - - if (P143_PLUGIN_EXIT_LED_OFF) { - // Set encoder position to 0 will effectively turn off all LEDs - I2C_write16_reg(_i2cAddress, P143_DFROBOT_ENCODER_COUNT_MSB_REG, 0); - } - break; - # endif // if P143_FEATURE_INCLUDE_DFROBOT - } - } - return true; -} - -/***************************************************** - * plugin_read - ****************************************************/ -bool P143_data_struct::plugin_read(struct EventStruct *event) { - if (_initialized) { - // Last obtained values - UserVar.setFloat(event->TaskIndex, 0, _encoderPosition); - UserVar.setFloat(event->TaskIndex, 1, _buttonState); - return true; - } - return false; -} - -/***************************************************** - * plugin_write - ****************************************************/ -bool P143_data_struct::plugin_write(struct EventStruct *event, - String & string) { - bool success = false; - const String cmd = parseString(string, 1); - - if (_initialized && equals(cmd, F("i2cencoder"))) { - const String sub = parseString(string, 2); - const bool led1 = equals(sub, F("led1")); - - if ((led1 || equals(sub, F("led2"))) // led1,,, (Adafruit and M5Stack) - && (event->Par2 >= 0) && (event->Par2 <= 255) // led2,,, (M5Stack only) - && (event->Par3 >= 0) && (event->Par3 <= 255) && - (event->Par4 >= 0) && (event->Par4 <= 255) - # if P143_FEATURE_INCLUDE_DFROBOT - && _device != P143_DeviceType_e::DFRobotEncoder - # endif // if P143_FEATURE_INCLUDE_DFROBOT - ) { - uint32_t lSettings = 0u; - - if (led1) { - _red = event->Par2; - _green = event->Par3; - _blue = event->Par4; - lSettings = P143_ADAFRUIT_COLOR_AND_BRIGHTNESS; - set8BitToUL(lSettings, P143_ADAFRUIT_OFFSET_RED, _red); - set8BitToUL(lSettings, P143_ADAFRUIT_OFFSET_GREEN, _green); - set8BitToUL(lSettings, P143_ADAFRUIT_OFFSET_BLUE, _blue); - P143_ADAFRUIT_COLOR_AND_BRIGHTNESS = lSettings; - } - - switch (_device) { - case P143_DeviceType_e::AdafruitEncoder: - { - if (led1 && (nullptr != Adafruit_Spixel)) { - Adafruit_Spixel->setPixelColor(0, _red, _green, _blue); - Adafruit_Spixel->show(); - success = true; - } - break; - } - # if P143_FEATURE_INCLUDE_M5STACK - case P143_DeviceType_e::M5StackEncoder: - { - if (!led1) { - lSettings = P143_M5STACK_COLOR_AND_SELECTION; - set8BitToUL(lSettings, P143_M5STACK2_OFFSET_RED, event->Par2 & 0xFF); - set8BitToUL(lSettings, P143_M5STACK2_OFFSET_GREEN, event->Par3 & 0xFF); - set8BitToUL(lSettings, P143_M5STACK2_OFFSET_BLUE, event->Par4 & 0xFF); - P143_M5STACK_COLOR_AND_SELECTION = lSettings; - } - m5stack_setPixelColor(led1 ? 1 : 2, event->Par2 & 0xFF, event->Par3 & 0xFF, event->Par4 & 0xFF); - success = true; - break; - } - # endif // if P143_FEATURE_INCLUDE_M5STACK - # if P143_FEATURE_INCLUDE_DFROBOT - case P143_DeviceType_e::DFRobotEncoder: - break; - # endif // if P143_FEATURE_INCLUDE_DFROBOT - } - } else - if (equals(sub, F("bright")) // bright, (range 1..255, Adafruit and M5Stack only) - && (event->Par2 >= 1) && (event->Par2 <= 255) - # if P143_FEATURE_INCLUDE_DFROBOT - && _device != P143_DeviceType_e::DFRobotEncoder - # endif // if P143_FEATURE_INCLUDE_DFROBOT - ) { - _brightness = event->Par2; - uint32_t lSettings = P143_ADAFRUIT_COLOR_AND_BRIGHTNESS; - set8BitToUL(lSettings, P143_ADAFRUIT_OFFSET_BRIGHTNESS, _brightness); - P143_ADAFRUIT_COLOR_AND_BRIGHTNESS = lSettings; - - switch (_device) { - case P143_DeviceType_e::AdafruitEncoder: - { - if (nullptr != Adafruit_Spixel) { - Adafruit_Spixel->setBrightness(_brightness); - - // Update with new brightness - Adafruit_Spixel->setPixelColor(0, _red, _green, _blue); - Adafruit_Spixel->show(); - } - success = true; - break; - } - # if P143_FEATURE_INCLUDE_M5STACK - case P143_DeviceType_e::M5StackEncoder: - { - // Update with new brightness - m5stack_setPixelColor(1, _red, _green, _blue); - m5stack_setPixelColor(2, P143_M5STACK2_COLOR_RED, P143_M5STACK2_COLOR_GREEN, P143_M5STACK2_COLOR_BLUE); - success = true; - break; - } - # endif // if P143_FEATURE_INCLUDE_M5STACK - # if P143_FEATURE_INCLUDE_DFROBOT - case P143_DeviceType_e::DFRobotEncoder: - break; - # endif // if P143_FEATURE_INCLUDE_DFROBOT - } - # if P143_FEATURE_INCLUDE_DFROBOT - } else - if (equals(sub, F("gain")) // gain, (Range 1..51, DFRobot only) - && (event->Par2 >= P143_DFROBOT_MIN_GAIN) && (event->Par2 <= P143_DFROBOT_MAX_GAIN) - && (_device == P143_DeviceType_e::DFRobotEncoder) - ) { - P143_DFROBOT_LED_GAIN = event->Par2; - success = true; - # endif // if P143_FEATURE_INCLUDE_DFROBOT - } else - if (equals(sub, F("set"))) { // set,[,] (initial offset only for DFRobot) - _encoderPosition = event->Par2; - - switch (_device) { - case P143_DeviceType_e::AdafruitEncoder: - { - if (nullptr != Adafruit_Seesaw) { - Adafruit_Seesaw->setEncoderPosition(_encoderPosition); - } - break; - } - # if P143_FEATURE_INCLUDE_M5STACK - case P143_DeviceType_e::M5StackEncoder: - { - if (_useOffset) { // Adjust offset - int16_t encoderCount = I2C_readS16_LE_reg(_i2cAddress, P143_M5STACK_REG_ENCODER); - _offsetEncoder = encoderCount - _encoderPosition; - # if P143_FEATURE_M5STACK_V1_1 - } else { // Set position using upgraded firmware - I2C_write16_LE_reg(_i2cAddress, P143_M5STACK_REG_ENCODER, _encoderPosition); - # endif // if P143_FEATURE_M5STACK_V1_1 - } - break; - } - # endif // if P143_FEATURE_INCLUDE_M5STACK - # if P143_FEATURE_INCLUDE_DFROBOT - case P143_DeviceType_e::DFRobotEncoder: - { - if (!parseString(string, 4).isEmpty() && (event->Par3 >= P143_DFROBOT_MIN_OFFSET) && (event->Par3 <= P143_DFROBOT_MAX_OFFSET)) { - _initialOffset = event->Par3; - P143_OFFSET_POSITION = _initialOffset; - } - I2C_write16_reg(_i2cAddress, P143_DFROBOT_ENCODER_COUNT_MSB_REG, _initialOffset + _encoderPosition); - break; - } - # endif // if P143_FEATURE_INCLUDE_DFROBOT - } - } - } - - return success; -} - -/***************************************************** - * plugin_ten_per_second - ****************************************************/ -bool P143_data_struct::plugin_ten_per_second(struct EventStruct *event) { - bool result = false; - - if (_initialized) { - int32_t current = _encoderPosition; - # if PLUGIN_143_DEBUG - _oldPosition = _encoderPosition; - # endif // if PLUGIN_143_DEBUG - - // Read encoder - switch (_device) { - case P143_DeviceType_e::AdafruitEncoder: - - if (nullptr != Adafruit_Seesaw) { - current = Adafruit_Seesaw->getEncoderPosition(); - } - break; - # if P143_FEATURE_INCLUDE_M5STACK - case P143_DeviceType_e::M5StackEncoder: - current = I2C_readS16_LE_reg(_i2cAddress, P143_M5STACK_REG_ENCODER); - break; - # endif // if P143_FEATURE_INCLUDE_M5STACK - # if P143_FEATURE_INCLUDE_DFROBOT - case P143_DeviceType_e::DFRobotEncoder: - current = static_cast(I2C_read16_reg(_i2cAddress, P143_DFROBOT_ENCODER_COUNT_MSB_REG)) - _initialOffset; - break; - # endif // if P143_FEATURE_INCLUDE_DFROBOT - } - # if P143_FEATURE_INCLUDE_M5STACK - const int32_t rawCurrent = current; - - if (_useOffset) { - current -= _offsetEncoder; - } - # endif // if P143_FEATURE_INCLUDE_M5STACK - const int32_t orgCurrent = current; - - // Check limits - if (_encoderMin != _encoderMax) { - if ((current < _encoderMin) || (current > _encoderMax)) { - // Have to check separately, as it's possible to move multiple steps within 1/10th second - if (current < _encoderMin) { - # if P143_FEATURE_INCLUDE_M5STACK - - if (_useOffset) { - _offsetEncoder = rawCurrent - _encoderMin; - } - # endif // if P143_FEATURE_INCLUDE_M5STACK - current = _encoderMin; // keep minimal value - } - else if (current > _encoderMax) { - # if P143_FEATURE_INCLUDE_M5STACK - - if (_useOffset) { - _offsetEncoder = rawCurrent - _encoderMax; - } - # endif // if P143_FEATURE_INCLUDE_M5STACK - current = _encoderMax; // keep maximal value - } - _previousEncoder = current; - - // (Re)Set Encoder to current position if outside the set boundaries - if (current != orgCurrent) { - switch (_device) { - case P143_DeviceType_e::AdafruitEncoder: - - if (nullptr != Adafruit_Seesaw) { - Adafruit_Seesaw->setEncoderPosition(current); - } - break; - # if P143_FEATURE_INCLUDE_M5STACK - case P143_DeviceType_e::M5StackEncoder: - - # if P143_FEATURE_M5STACK_V1_1 - - // Set encoder position. NB: will only work if the encoder firmware is updated to v1.1 - if (!_useOffset) { - I2C_write16_LE_reg(_i2cAddress, P143_M5STACK_REG_ENCODER, current); - } - # endif // if P143_FEATURE_M5STACK_V1_1 - break; - # endif // if P143_FEATURE_INCLUDE_M5STACK - # if P143_FEATURE_INCLUDE_DFROBOT - case P143_DeviceType_e::DFRobotEncoder: - I2C_write16_reg(_i2cAddress, P143_DFROBOT_ENCODER_COUNT_MSB_REG, _initialOffset + _encoderPosition); - break; - # endif // if P143_FEATURE_INCLUDE_DFROBOT - } - } - } - } - - if (current != _encoderPosition) { - // Generate event - if (Settings.UseRules) { - eventQueue.add(event->TaskIndex, getTaskValueName(event->TaskIndex, 0), - strformat(F("%d,%d"), current, current - _encoderPosition)); // Position, Delta (positive = clock-wise) - } - - // Set task value - _encoderPosition = current; - - UserVar.setFloat(event->TaskIndex, 0, _encoderPosition); - - result = true; - - // Calculate colormapping - # if P143_FEATURE_COUNTER_COLORMAPPING - counterToColorMapping(event); - # endif // if P143_FEATURE_COUNTER_COLORMAPPING - - # if PLUGIN_143_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = concat(F("I2CEncoder : "), toString(_device)); - log += strformat(F(", Changed: %d to: %d, delta: %d"), - _oldPosition, _encoderPosition, _encoderPosition - _oldPosition); - addLogMove(LOG_LEVEL_INFO, log); - } - # endif // if PLUGIN_143_DEBUG - } - } - - return result; -} - -# if P143_FEATURE_COUNTER_COLORMAPPING - -/***************************************************** - * counterToColorMapping - ****************************************************/ -void P143_data_struct::counterToColorMapping(struct EventStruct *event) { - int16_t iRed = -1; - int16_t iGreen = -1; - int16_t iBlue = -1; - int16_t pRed = -1; - int16_t pGreen = -1; - int16_t pBlue = -1; - int32_t iCount = INT32_MIN; - int32_t pCount = INT32_MIN; - - switch (_mapping) { - case P143_CounterMapping_e::ColorMapping: - { - for (int i = 0; i <= _colorMaps; ++i) { - if ((!_colorMapping[i].isEmpty()) && - (iCount == INT32_MIN) && - (parseColorMapLine(_colorMapping[i], iCount, iRed, iGreen, iBlue)) && - (iCount < _encoderPosition)) { // Reset, out of range - iRed = -1; - iGreen = -1; - iBlue = -1; - iCount = INT32_MIN; - } - } - break; - } - - case P143_CounterMapping_e::ColorGradient: - { - for (int i = 0; i <= _colorMaps; ++i) { - if (!_colorMapping[i].isEmpty()) { - if ((iCount == INT32_MIN) && - (parseColorMapLine(_colorMapping[i], iCount, iRed, iGreen, iBlue)) && - ((iCount > _encoderPosition) || !rangeCheck(iCount, _encoderMin, _encoderMax))) { - iRed = -1; - iGreen = -1; - iBlue = -1; - iCount = INT32_MIN; - } - - if ((pCount == INT32_MIN) && - (parseColorMapLine(_colorMapping[i], pCount, pRed, pGreen, pBlue)) && - ((pCount < _encoderPosition) || !rangeCheck(pCount, _encoderMin, _encoderMax))) { - pRed = -1; - pGreen = -1; - pBlue = -1; - pCount = INT32_MIN; - } - } - } - - // Calculate R/G/B gradient-values for current Counter within upper and lower range - if ((pCount != iCount) && (iRed > -1) && (iGreen > -1) && (iBlue > -1) && (pRed > -1) && (pGreen > -1) && (pBlue > -1)) { - iRed = map(_encoderPosition, iCount, pCount, iRed, pRed); - iGreen = map(_encoderPosition, iCount, pCount, iGreen, pGreen); - iBlue = map(_encoderPosition, iCount, pCount, iBlue, pBlue); - } - - break; - } - case P143_CounterMapping_e::None: - // Do nothing - break; - } - - // Updated Led color? - if ((iRed > -1) && (iGreen > -1) && (iBlue > -1)) { - # if !defined(BUILD_NO_DEBUG) && PLUGIN_143_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = F("P143: Change color R:"); - log += iRed; - log += F(", G:"); - log += iGreen; - log += F(", B:"); - log += iBlue; - # ifdef LOG_LEVEL_DEBUG_DEV - log += F(", pR:"); // DEV-only from here - log += pRed; - log += F(", pG:"); - log += pGreen; - log += F(", pB:"); - log += pBlue; - log += F(", iC:"); - log += iCount; - log += F(", pC:"); - log += pCount; - # endif // ifdef LOG_LEVEL_DEBUG_DEV - addLog(LOG_LEVEL_DEBUG, log); - } - # endif // if !defined(BUILD_NO_DEBUG) && PLUGIN_143_DEBUG - - switch (_device) { - case P143_DeviceType_e::AdafruitEncoder: - { - _red = iRed; - _green = iGreen; - _blue = iBlue; - - if (nullptr != Adafruit_Spixel) { - Adafruit_Spixel->setPixelColor(0, _red, _green, _blue); - Adafruit_Spixel->show(); - } - break; - } - # if P143_FEATURE_INCLUDE_M5STACK - case P143_DeviceType_e::M5StackEncoder: - { - _red = iRed; - _green = iGreen; - _blue = iBlue; - m5stack_setPixelColor(static_cast(P143_M5STACK_SELECTION), _red, _green, _blue); - break; - } - # endif // if P143_FEATURE_INCLUDE_M5STACK - # if P143_FEATURE_INCLUDE_DFROBOT - case P143_DeviceType_e::DFRobotEncoder: - break; // N/A - # endif // if P143_FEATURE_INCLUDE_DFROBOT - } - } -} - -/***************************************************** - * parseColorMapLine, line prefixed with # is comment/disabled - ****************************************************/ -bool P143_data_struct::parseColorMapLine(const String& line, - int32_t & count, - int16_t & red, - int16_t & green, - int16_t & blue) { - bool result = false; - String tmp = parseString(line, 1); - - if (!tmp.isEmpty() && !tmp.startsWith(F("#"))) { - count = tmp.toInt(); - - tmp = parseString(line, 2); - - if (!tmp.isEmpty()) { - red = tmp.toInt(); - - tmp = parseString(line, 3); - - if (!tmp.isEmpty()) { - green = tmp.toInt(); - - tmp = parseString(line, 4); - - if (!tmp.isEmpty()) { - blue = tmp.toInt(); - result = true; - } - } - } - } - - return result; -} - -/***************************************************** - * rangeCheck - ****************************************************/ -bool P143_data_struct::rangeCheck(int32_t count, - int32_t min, - int32_t max) { - return !((min != max) && - ((count < min) || (count > max))); -} - -# endif // if P143_FEATURE_COUNTER_COLORMAPPING - -/***************************************************** - * plugin_fifty_per_second, handle button actions - ****************************************************/ -bool P143_data_struct::plugin_fifty_per_second(struct EventStruct *event) { - if (_initialized) { - const uint8_t pressedState = 0; // button has this value if pressed - uint8_t button = _buttonLast; - uint8_t state = _buttonState; - - // Read button - switch (_device) { - case P143_DeviceType_e::AdafruitEncoder: - - if (nullptr != Adafruit_Seesaw) { - button = Adafruit_Seesaw->digitalRead(P143_SEESAW_SWITCH); - } - break; - # if P143_FEATURE_INCLUDE_M5STACK - case P143_DeviceType_e::M5StackEncoder: - button = I2C_read8_reg(_i2cAddress, P143_M5STACK_REG_BUTTON); - break; - # endif // if P143_FEATURE_INCLUDE_M5STACK - # if P143_FEATURE_INCLUDE_DFROBOT - case P143_DeviceType_e::DFRobotEncoder: - uint8_t press = I2C_read8_reg(_i2cAddress, P143_DFROBOT_ENCODER_KEY_STATUS_REG); - - if ((press & 0x01) != 0) { - I2C_write8_reg(_i2cAddress, P143_DFROBOT_ENCODER_KEY_STATUS_REG, 0x00); // Reset - // Trigger immediately - button = button ? 0 : 1; - _buttonDown = true; - _buttonTime = (60 / 20); - } - - break; - # endif // if P143_FEATURE_INCLUDE_DFROBOT - } - - if ((button == pressedState) && _buttonDown) { - _buttonTime++; // increment 20 msec - } else if (button == pressedState) { - _buttonDown = true; - _buttonTime = 0; - } - - if ((button != _buttonLast) && (_buttonTime >= (60 / 20))) { // Short-press = 60 msec - _buttonLast = button; - _buttonDown = false; - - switch (static_cast(P143_PLUGIN_BUTTON_ACTION)) { - case P143_ButtonAction_e::PushButton: - state = button; - break; - case P143_ButtonAction_e::PushButtonInverted: - state = button == 0 ? 1 : 0; - _buttonState = state == 0 ? 1 : 0; // Changed - break; - case P143_ButtonAction_e::ToggleSwitch: - - if (button == pressedState) { - state = _buttonState == 0 ? 1 : 0; // Toggle - } - _buttonTime = 0; // Ignore long-press - break; - } - } - - if (state != _buttonState) { - if (_enableLongPress && (_buttonTime >= (_buttonLongPress / 20))) { // Long-press - state += 10; // Eventvalues similar to Switch plugin - } - - // Generate event - if (Settings.UseRules) { - eventQueue.add(event->TaskIndex, getTaskValueName(event->TaskIndex, 1), state); - } - - // Set task value - _buttonState = state; - - UserVar.setFloat(event->TaskIndex, 1, _buttonState); - - return true; - } - } - return false; -} - -# if P143_FEATURE_INCLUDE_M5STACK - -/***************************************************** - * applyBrightness, calculate new color based on brightness, based on NeoPixel library setBrightness() - ****************************************************/ -uint8_t P143_data_struct::applyBrightness(uint8_t color) { - if (_brightness) { - color = (color * _brightness) >> 8; - } - - return color; -} - -/***************************************************** - * m5stack_setPixelColor - ****************************************************/ -void P143_data_struct::m5stack_setPixelColor(uint8_t pixel, - uint8_t red, - uint8_t green, - uint8_t blue) { - uint8_t data[4] = { - pixel, - applyBrightness(red), - applyBrightness(green), - applyBrightness(blue) }; - - I2C_writeBytes_reg(_i2cAddress, P143_M5STACK_REG_LED, data, 4); -} - -# endif // if P143_FEATURE_INCLUDE_M5STACK - -#endif // ifdef USES_P143 +#include "../PluginStructs/P143_data_struct.h" + +#ifdef USES_P143 + +/************************************************************************** + * toString for P143_DeviceType_e + *************************************************************************/ +const __FlashStringHelper* toString(P143_DeviceType_e device) { + switch (device) { + case P143_DeviceType_e::AdafruitEncoder: return F("Adafruit"); + # if P143_FEATURE_INCLUDE_M5STACK + case P143_DeviceType_e::M5StackEncoder: return F("M5Stack"); + # endif // if P143_FEATURE_INCLUDE_M5STACK + # if P143_FEATURE_INCLUDE_DFROBOT + case P143_DeviceType_e::DFRobotEncoder: return F("DFRobot"); + # endif // if P143_FEATURE_INCLUDE_DFROBOT + } + return F(""); +} + +# if P143_FEATURE_COUNTER_COLORMAPPING + +/************************************************************************** + * toString for P143_CounterMapping_e + *************************************************************************/ +const __FlashStringHelper* toString(P143_CounterMapping_e counter) { + switch (counter) { + case P143_CounterMapping_e::None: return F("None"); + case P143_CounterMapping_e::ColorMapping: return F("Color mapping"); + case P143_CounterMapping_e::ColorGradient: return F("Color gradient"); + } + return F(""); +} + +# endif // if P143_FEATURE_COUNTER_COLORMAPPING + +/************************************************************************** + * toString for P143_ButtonAction_e + *************************************************************************/ +const __FlashStringHelper* toString(P143_ButtonAction_e action) { + switch (action) { + case P143_ButtonAction_e::PushButton: return F("Pushbutton"); + case P143_ButtonAction_e::PushButtonInverted: return F("Pushbutton (inverted)"); + case P143_ButtonAction_e::ToggleSwitch: return F("Toggle switch"); + } + return F(""); +} + +/******************************************************************* + * P143_CheckEncoderDefaultSettings: Helper to set config defaults after changing the Encoder type + ******************************************************************/ +void P143_CheckEncoderDefaultSettings(struct EventStruct *event) { + if (P143_ENCODER_TYPE != P143_PREVIOUS_TYPE) { + switch (static_cast(P143_ENCODER_TYPE)) { + case P143_DeviceType_e::AdafruitEncoder: + P143_ADAFRUIT_COLOR_AND_BRIGHTNESS = 0x0000001E; // Black, with 30 (0x1E) brightness (1..255) + P143_OFFSET_POSITION = 0; + break; + # if P143_FEATURE_INCLUDE_M5STACK + case P143_DeviceType_e::M5StackEncoder: + P143_ADAFRUIT_COLOR_AND_BRIGHTNESS = 0x0000001E; // Black, with 30 (0x1E) brightness (1..255) + P143_M5STACK_COLOR_AND_SELECTION = 0x00000000; // Black, with both Leds using Color mapping + P143_OFFSET_POSITION = 0; + break; + # endif // if P143_FEATURE_INCLUDE_M5STACK + # if P143_FEATURE_INCLUDE_DFROBOT + case P143_DeviceType_e::DFRobotEncoder: + P143_DFROBOT_LED_GAIN = P143_DFROBOT_MAX_GAIN; + P143_OFFSET_POSITION = 0; + break; + # endif // if P143_FEATURE_INCLUDE_DFROBOT + } + P143_PREVIOUS_TYPE = P143_ENCODER_TYPE; // It's now up to date + } +} + +/************************************************************************** + * Constructor + *************************************************************************/ +P143_data_struct::P143_data_struct(struct EventStruct *event) { + _device = static_cast(P143_ENCODER_TYPE); + _i2cAddress = P143_I2C_ADDR; + _encoderPosition = P143_INITIAL_POSITION; + _encoderMin = P143_MINIMAL_POSITION; + _encoderMax = P143_MAXIMAL_POSITION; + _brightness = P143_NEOPIXEL_BRIGHTNESS; + _buttonLongPress = P143_GET_LONGPRESS_INTERVAL; + _enableLongPress = P143_PLUGIN_ENABLE_LONGPRESS; + # if P143_FEATURE_INCLUDE_DFROBOT + _initialOffset = P143_OFFSET_POSITION; + # endif // if P143_FEATURE_INCLUDE_DFROBOT +} + +/***************************************************** + * Destructor + ****************************************************/ +P143_data_struct::~P143_data_struct() { + delete Adafruit_Seesaw; + delete Adafruit_Spixel; +} + +/************************************************************************** + * plugin_init Initialize sensor and prepare for reading + *************************************************************************/ +bool P143_data_struct::plugin_init(struct EventStruct *event) { + if (!_initialized) { + switch (_device) { + case P143_DeviceType_e::AdafruitEncoder: + { + Adafruit_Seesaw = new (std::nothrow) Adafruit_seesaw(); + Adafruit_Spixel = new (std::nothrow) seesaw_NeoPixel(1, P143_SEESAW_NEOPIX, NEO_GRB + NEO_KHZ800); + + if ((nullptr != Adafruit_Seesaw) && (nullptr != Adafruit_Spixel)) { + _initialized = Adafruit_Seesaw->begin(_i2cAddress) && Adafruit_Spixel->begin(_i2cAddress); + uint32_t version = ((Adafruit_Seesaw->getVersion() >> 16) & 0xFFFF); + + if (_initialized && (version != P143_ADAFRUIT_ENCODER_PRODUCTID)) { // Check Adafruit product ID + _initialized = false; + } + + if (_initialized) { + // use a pin for the built in encoder switch + Adafruit_Seesaw->pinMode(P143_SEESAW_SWITCH, INPUT_PULLUP); + + // set starting position + Adafruit_Seesaw->setEncoderPosition(_encoderPosition); + + // Enable interrupts on Switch pin + Adafruit_Seesaw->setGPIOInterrupts((uint32_t)1 << P143_SEESAW_SWITCH, 1); + Adafruit_Seesaw->enableEncoderInterrupt(); + + // We only have 1 pixel available... + Adafruit_Spixel->setBrightness(_brightness); // Set brightness before color! + _red = P143_ADAFRUIT_COLOR_RED; + _green = P143_ADAFRUIT_COLOR_GREEN; + _blue = P143_ADAFRUIT_COLOR_BLUE; + Adafruit_Spixel->setPixelColor(0, _red, _green, _blue); + Adafruit_Spixel->show(); + } + } + break; + } + # if P143_FEATURE_INCLUDE_M5STACK + case P143_DeviceType_e::M5StackEncoder: + { + // Reset, only actually supported with upgraded firmware + I2C_write8_reg(_i2cAddress, P143_M5STACK_REG_MODE, 0x00); + + # if P143_FEATURE_M5STACK_V1_1 + + // Check if we need to use the offset method + // - Read current counter + // - Write incremented value, only works with upgraded firmware + // - re-read and if not changed we use an offset to handle passing the set limits + int16_t encoderCount = I2C_readS16_LE_reg(_i2cAddress, P143_M5STACK_REG_ENCODER); + encoderCount++; + I2C_write16_LE_reg(_i2cAddress, P143_M5STACK_REG_ENCODER, encoderCount); + + if (encoderCount != I2C_readS16_LE_reg(_i2cAddress, P143_M5STACK_REG_ENCODER)) { + _useOffset = true; + _previousEncoder = encoderCount - 1; + _offsetEncoder = _previousEncoder - _encoderPosition; + } else { + // Don't need to use offset, set configured initial value + encoderCount = _encoderPosition; + I2C_write16_LE_reg(_i2cAddress, P143_M5STACK_REG_ENCODER, encoderCount); + } + # else // if P143_FEATURE_M5STACK_V1_1 + _useOffset = true; // No check needed, we need to use the offset method + # endif // if P143_FEATURE_M5STACK_V1_1 + + _red = P143_ADAFRUIT_COLOR_RED; // Also used for M5Stack Led 1 + _green = P143_ADAFRUIT_COLOR_GREEN; + _blue = P143_ADAFRUIT_COLOR_BLUE; + + // Set LED initial state + m5stack_setPixelColor(1, _red, _green, _blue); + m5stack_setPixelColor(2, P143_M5STACK2_COLOR_RED, P143_M5STACK2_COLOR_GREEN, P143_M5STACK2_COLOR_BLUE); + _initialized = true; + break; + } + # endif // if P143_FEATURE_INCLUDE_M5STACK + # if P143_FEATURE_INCLUDE_DFROBOT + case P143_DeviceType_e::DFRobotEncoder: + { + _initialized = P143_DFROBOT_ENCODER_PID == I2C_read16_reg(_i2cAddress, P143_DFROBOT_ENCODER_PID_MSB_REG); + + if (_initialized) { + // Set encoder position + I2C_write16_reg(_i2cAddress, P143_DFROBOT_ENCODER_COUNT_MSB_REG, _initialOffset + _encoderPosition); + + // Set led gain + I2C_write8_reg(_i2cAddress, P143_DFROBOT_ENCODER_GAIN_REG, P143_DFROBOT_LED_GAIN & 0xFF); + } + break; + } + # endif // if P143_FEATURE_INCLUDE_DFROBOT + } + + // Set initial button state + _buttonState = (P143_ButtonAction_e::PushButtonInverted == static_cast(P143_PLUGIN_BUTTON_ACTION)) ? 0 : 1; + + UserVar.setFloat(event->TaskIndex, 1, _buttonState); + + # if P143_FEATURE_COUNTER_COLORMAPPING + + _mapping = static_cast(P143_PLUGIN_COUNTER_MAPPING); + + // Load color mapping data + LoadCustomTaskSettings(event->TaskIndex, _colorMapping, P143_STRINGS, 0); + + for (int i = P143_STRINGS - 1; i >= 0; --i) { + _colorMapping[i].trim(); + + if ((_colorMaps == -1) && !_colorMapping[i].isEmpty()) { + _colorMaps = i; + } + } + + counterToColorMapping(event); // Update color + # endif // if P143_FEATURE_COUNTER_COLORMAPPING + + if (loglevelActiveFor(_initialized ? LOG_LEVEL_INFO : LOG_LEVEL_ERROR)) { + String log = concat(F("I2CEncoders: INIT "), toString(_device)); + log += F(", "); + + # if P143_FEATURE_INCLUDE_M5STACK + + if (_useOffset) { + log += F("using Offset method, "); + } + # endif // if P143_FEATURE_INCLUDE_M5STACK + + if (!_initialized) { + log += F("FAILED."); + } else { + log += F("Success."); + } + addLogMove(_initialized ? LOG_LEVEL_INFO : LOG_LEVEL_ERROR, log); + } + } + + return _initialized; +} + +/************************************************************************** + * plugin_exit De-initialize and prepare for destructor + *************************************************************************/ +bool P143_data_struct::plugin_exit(struct EventStruct *event) { + if (_initialized) { + _initialized = false; // We don't want any unexpected events + + switch (_device) { + case P143_DeviceType_e::AdafruitEncoder: + { + // Stop interrupthandler + if (nullptr != Adafruit_Seesaw) { + Adafruit_Seesaw->disableEncoderInterrupt(); + } + + if ((nullptr != Adafruit_Spixel) && P143_PLUGIN_EXIT_LED_OFF) { + // Turn off Neopixel 0 by setting the R/G/B color to black + Adafruit_Spixel->setPixelColor(0, 0, 0, 0); + Adafruit_Spixel->show(); + } + break; + } + # if P143_FEATURE_INCLUDE_M5STACK + case P143_DeviceType_e::M5StackEncoder: + { + if (P143_PLUGIN_EXIT_LED_OFF) { + // Turn off both LEDs + m5stack_setPixelColor(0, 0, 0, 0); + } + break; + } + # endif // if P143_FEATURE_INCLUDE_M5STACK + # if P143_FEATURE_INCLUDE_DFROBOT + case P143_DeviceType_e::DFRobotEncoder: + + if (P143_PLUGIN_EXIT_LED_OFF) { + // Set encoder position to 0 will effectively turn off all LEDs + I2C_write16_reg(_i2cAddress, P143_DFROBOT_ENCODER_COUNT_MSB_REG, 0); + } + break; + # endif // if P143_FEATURE_INCLUDE_DFROBOT + } + } + return true; +} + +/***************************************************** + * plugin_read + ****************************************************/ +bool P143_data_struct::plugin_read(struct EventStruct *event) { + if (_initialized) { + // Last obtained values + UserVar.setFloat(event->TaskIndex, 0, _encoderPosition); + UserVar.setFloat(event->TaskIndex, 1, _buttonState); + return true; + } + return false; +} + +/***************************************************** + * plugin_write + ****************************************************/ +bool P143_data_struct::plugin_write(struct EventStruct *event, + String & string) { + bool success = false; + const String cmd = parseString(string, 1); + + if (_initialized && equals(cmd, F("i2cencoder"))) { + const String sub = parseString(string, 2); + const bool led1 = equals(sub, F("led1")); + + if ((led1 || equals(sub, F("led2"))) // led1,,, (Adafruit and M5Stack) + && (event->Par2 >= 0) && (event->Par2 <= 255) // led2,,, (M5Stack only) + && (event->Par3 >= 0) && (event->Par3 <= 255) && + (event->Par4 >= 0) && (event->Par4 <= 255) + # if P143_FEATURE_INCLUDE_DFROBOT + && _device != P143_DeviceType_e::DFRobotEncoder + # endif // if P143_FEATURE_INCLUDE_DFROBOT + ) { + uint32_t lSettings = 0u; + + if (led1) { + _red = event->Par2; + _green = event->Par3; + _blue = event->Par4; + lSettings = P143_ADAFRUIT_COLOR_AND_BRIGHTNESS; + set8BitToUL(lSettings, P143_ADAFRUIT_OFFSET_RED, _red); + set8BitToUL(lSettings, P143_ADAFRUIT_OFFSET_GREEN, _green); + set8BitToUL(lSettings, P143_ADAFRUIT_OFFSET_BLUE, _blue); + P143_ADAFRUIT_COLOR_AND_BRIGHTNESS = lSettings; + } + + switch (_device) { + case P143_DeviceType_e::AdafruitEncoder: + { + if (led1 && (nullptr != Adafruit_Spixel)) { + Adafruit_Spixel->setPixelColor(0, _red, _green, _blue); + Adafruit_Spixel->show(); + success = true; + } + break; + } + # if P143_FEATURE_INCLUDE_M5STACK + case P143_DeviceType_e::M5StackEncoder: + { + if (!led1) { + lSettings = P143_M5STACK_COLOR_AND_SELECTION; + set8BitToUL(lSettings, P143_M5STACK2_OFFSET_RED, event->Par2 & 0xFF); + set8BitToUL(lSettings, P143_M5STACK2_OFFSET_GREEN, event->Par3 & 0xFF); + set8BitToUL(lSettings, P143_M5STACK2_OFFSET_BLUE, event->Par4 & 0xFF); + P143_M5STACK_COLOR_AND_SELECTION = lSettings; + } + m5stack_setPixelColor(led1 ? 1 : 2, event->Par2 & 0xFF, event->Par3 & 0xFF, event->Par4 & 0xFF); + success = true; + break; + } + # endif // if P143_FEATURE_INCLUDE_M5STACK + # if P143_FEATURE_INCLUDE_DFROBOT + case P143_DeviceType_e::DFRobotEncoder: + break; + # endif // if P143_FEATURE_INCLUDE_DFROBOT + } + } else + if (equals(sub, F("bright")) // bright, (range 1..255, Adafruit and M5Stack only) + && (event->Par2 >= 1) && (event->Par2 <= 255) + # if P143_FEATURE_INCLUDE_DFROBOT + && _device != P143_DeviceType_e::DFRobotEncoder + # endif // if P143_FEATURE_INCLUDE_DFROBOT + ) { + _brightness = event->Par2; + uint32_t lSettings = P143_ADAFRUIT_COLOR_AND_BRIGHTNESS; + set8BitToUL(lSettings, P143_ADAFRUIT_OFFSET_BRIGHTNESS, _brightness); + P143_ADAFRUIT_COLOR_AND_BRIGHTNESS = lSettings; + + switch (_device) { + case P143_DeviceType_e::AdafruitEncoder: + { + if (nullptr != Adafruit_Spixel) { + Adafruit_Spixel->setBrightness(_brightness); + + // Update with new brightness + Adafruit_Spixel->setPixelColor(0, _red, _green, _blue); + Adafruit_Spixel->show(); + } + success = true; + break; + } + # if P143_FEATURE_INCLUDE_M5STACK + case P143_DeviceType_e::M5StackEncoder: + { + // Update with new brightness + m5stack_setPixelColor(1, _red, _green, _blue); + m5stack_setPixelColor(2, P143_M5STACK2_COLOR_RED, P143_M5STACK2_COLOR_GREEN, P143_M5STACK2_COLOR_BLUE); + success = true; + break; + } + # endif // if P143_FEATURE_INCLUDE_M5STACK + # if P143_FEATURE_INCLUDE_DFROBOT + case P143_DeviceType_e::DFRobotEncoder: + break; + # endif // if P143_FEATURE_INCLUDE_DFROBOT + } + # if P143_FEATURE_INCLUDE_DFROBOT + } else + if (equals(sub, F("gain")) // gain, (Range 1..51, DFRobot only) + && (event->Par2 >= P143_DFROBOT_MIN_GAIN) && (event->Par2 <= P143_DFROBOT_MAX_GAIN) + && (_device == P143_DeviceType_e::DFRobotEncoder) + ) { + P143_DFROBOT_LED_GAIN = event->Par2; + success = true; + # endif // if P143_FEATURE_INCLUDE_DFROBOT + } else + if (equals(sub, F("set"))) { // set,[,] (initial offset only for DFRobot) + _encoderPosition = event->Par2; + + switch (_device) { + case P143_DeviceType_e::AdafruitEncoder: + { + if (nullptr != Adafruit_Seesaw) { + Adafruit_Seesaw->setEncoderPosition(_encoderPosition); + } + break; + } + # if P143_FEATURE_INCLUDE_M5STACK + case P143_DeviceType_e::M5StackEncoder: + { + if (_useOffset) { // Adjust offset + int16_t encoderCount = I2C_readS16_LE_reg(_i2cAddress, P143_M5STACK_REG_ENCODER); + _offsetEncoder = encoderCount - _encoderPosition; + # if P143_FEATURE_M5STACK_V1_1 + } else { // Set position using upgraded firmware + I2C_write16_LE_reg(_i2cAddress, P143_M5STACK_REG_ENCODER, _encoderPosition); + # endif // if P143_FEATURE_M5STACK_V1_1 + } + break; + } + # endif // if P143_FEATURE_INCLUDE_M5STACK + # if P143_FEATURE_INCLUDE_DFROBOT + case P143_DeviceType_e::DFRobotEncoder: + { + if (!parseString(string, 4).isEmpty() && (event->Par3 >= P143_DFROBOT_MIN_OFFSET) && (event->Par3 <= P143_DFROBOT_MAX_OFFSET)) { + _initialOffset = event->Par3; + P143_OFFSET_POSITION = _initialOffset; + } + I2C_write16_reg(_i2cAddress, P143_DFROBOT_ENCODER_COUNT_MSB_REG, _initialOffset + _encoderPosition); + break; + } + # endif // if P143_FEATURE_INCLUDE_DFROBOT + } + } + } + + return success; +} + +/***************************************************** + * plugin_ten_per_second + ****************************************************/ +bool P143_data_struct::plugin_ten_per_second(struct EventStruct *event) { + bool result = false; + + if (_initialized) { + int32_t current = _encoderPosition; + # if PLUGIN_143_DEBUG + _oldPosition = _encoderPosition; + # endif // if PLUGIN_143_DEBUG + + // Read encoder + switch (_device) { + case P143_DeviceType_e::AdafruitEncoder: + + if (nullptr != Adafruit_Seesaw) { + current = Adafruit_Seesaw->getEncoderPosition(); + } + break; + # if P143_FEATURE_INCLUDE_M5STACK + case P143_DeviceType_e::M5StackEncoder: + current = I2C_readS16_LE_reg(_i2cAddress, P143_M5STACK_REG_ENCODER); + break; + # endif // if P143_FEATURE_INCLUDE_M5STACK + # if P143_FEATURE_INCLUDE_DFROBOT + case P143_DeviceType_e::DFRobotEncoder: + current = static_cast(I2C_read16_reg(_i2cAddress, P143_DFROBOT_ENCODER_COUNT_MSB_REG)) - _initialOffset; + break; + # endif // if P143_FEATURE_INCLUDE_DFROBOT + } + # if P143_FEATURE_INCLUDE_M5STACK + const int32_t rawCurrent = current; + + if (_useOffset) { + current -= _offsetEncoder; + } + # endif // if P143_FEATURE_INCLUDE_M5STACK + const int32_t orgCurrent = current; + + // Check limits + if (_encoderMin != _encoderMax) { + if ((current < _encoderMin) || (current > _encoderMax)) { + // Have to check separately, as it's possible to move multiple steps within 1/10th second + if (current < _encoderMin) { + # if P143_FEATURE_INCLUDE_M5STACK + + if (_useOffset) { + _offsetEncoder = rawCurrent - _encoderMin; + } + # endif // if P143_FEATURE_INCLUDE_M5STACK + current = _encoderMin; // keep minimal value + } + else if (current > _encoderMax) { + # if P143_FEATURE_INCLUDE_M5STACK + + if (_useOffset) { + _offsetEncoder = rawCurrent - _encoderMax; + } + # endif // if P143_FEATURE_INCLUDE_M5STACK + current = _encoderMax; // keep maximal value + } + _previousEncoder = current; + + // (Re)Set Encoder to current position if outside the set boundaries + if (current != orgCurrent) { + switch (_device) { + case P143_DeviceType_e::AdafruitEncoder: + + if (nullptr != Adafruit_Seesaw) { + Adafruit_Seesaw->setEncoderPosition(current); + } + break; + # if P143_FEATURE_INCLUDE_M5STACK + case P143_DeviceType_e::M5StackEncoder: + + # if P143_FEATURE_M5STACK_V1_1 + + // Set encoder position. NB: will only work if the encoder firmware is updated to v1.1 + if (!_useOffset) { + I2C_write16_LE_reg(_i2cAddress, P143_M5STACK_REG_ENCODER, current); + } + # endif // if P143_FEATURE_M5STACK_V1_1 + break; + # endif // if P143_FEATURE_INCLUDE_M5STACK + # if P143_FEATURE_INCLUDE_DFROBOT + case P143_DeviceType_e::DFRobotEncoder: + I2C_write16_reg(_i2cAddress, P143_DFROBOT_ENCODER_COUNT_MSB_REG, _initialOffset + _encoderPosition); + break; + # endif // if P143_FEATURE_INCLUDE_DFROBOT + } + } + } + } + + if (current != _encoderPosition) { + // Generate event + if (Settings.UseRules) { + eventQueue.add(event->TaskIndex, Cache.getTaskDeviceValueName(event->TaskIndex, 0), + strformat(F("%d,%d"), current, current - _encoderPosition)); // Position, Delta (positive = clock-wise) + } + + // Set task value + _encoderPosition = current; + + UserVar.setFloat(event->TaskIndex, 0, _encoderPosition); + + result = true; + + // Calculate colormapping + # if P143_FEATURE_COUNTER_COLORMAPPING + counterToColorMapping(event); + # endif // if P143_FEATURE_COUNTER_COLORMAPPING + + # if PLUGIN_143_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = concat(F("I2CEncoder : "), toString(_device)); + log += strformat(F(", Changed: %d to: %d, delta: %d"), + _oldPosition, _encoderPosition, _encoderPosition - _oldPosition); + addLogMove(LOG_LEVEL_INFO, log); + } + # endif // if PLUGIN_143_DEBUG + } + } + + return result; +} + +# if P143_FEATURE_COUNTER_COLORMAPPING + +/***************************************************** + * counterToColorMapping + ****************************************************/ +void P143_data_struct::counterToColorMapping(struct EventStruct *event) { + int16_t iRed = -1; + int16_t iGreen = -1; + int16_t iBlue = -1; + int16_t pRed = -1; + int16_t pGreen = -1; + int16_t pBlue = -1; + int32_t iCount = INT32_MIN; + int32_t pCount = INT32_MIN; + + switch (_mapping) { + case P143_CounterMapping_e::ColorMapping: + { + for (int i = 0; i <= _colorMaps; ++i) { + if ((!_colorMapping[i].isEmpty()) && + (iCount == INT32_MIN) && + (parseColorMapLine(_colorMapping[i], iCount, iRed, iGreen, iBlue)) && + (iCount < _encoderPosition)) { // Reset, out of range + iRed = -1; + iGreen = -1; + iBlue = -1; + iCount = INT32_MIN; + } + } + break; + } + + case P143_CounterMapping_e::ColorGradient: + { + for (int i = 0; i <= _colorMaps; ++i) { + if (!_colorMapping[i].isEmpty()) { + if ((iCount == INT32_MIN) && + (parseColorMapLine(_colorMapping[i], iCount, iRed, iGreen, iBlue)) && + ((iCount > _encoderPosition) || !rangeCheck(iCount, _encoderMin, _encoderMax))) { + iRed = -1; + iGreen = -1; + iBlue = -1; + iCount = INT32_MIN; + } + + if ((pCount == INT32_MIN) && + (parseColorMapLine(_colorMapping[i], pCount, pRed, pGreen, pBlue)) && + ((pCount < _encoderPosition) || !rangeCheck(pCount, _encoderMin, _encoderMax))) { + pRed = -1; + pGreen = -1; + pBlue = -1; + pCount = INT32_MIN; + } + } + } + + // Calculate R/G/B gradient-values for current Counter within upper and lower range + if ((pCount != iCount) && (iRed > -1) && (iGreen > -1) && (iBlue > -1) && (pRed > -1) && (pGreen > -1) && (pBlue > -1)) { + iRed = map(_encoderPosition, iCount, pCount, iRed, pRed); + iGreen = map(_encoderPosition, iCount, pCount, iGreen, pGreen); + iBlue = map(_encoderPosition, iCount, pCount, iBlue, pBlue); + } + + break; + } + case P143_CounterMapping_e::None: + // Do nothing + break; + } + + // Updated Led color? + if ((iRed > -1) && (iGreen > -1) && (iBlue > -1)) { + # if !defined(BUILD_NO_DEBUG) && PLUGIN_143_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String log = F("P143: Change color R:"); + log += iRed; + log += F(", G:"); + log += iGreen; + log += F(", B:"); + log += iBlue; + # ifdef LOG_LEVEL_DEBUG_DEV + log += F(", pR:"); // DEV-only from here + log += pRed; + log += F(", pG:"); + log += pGreen; + log += F(", pB:"); + log += pBlue; + log += F(", iC:"); + log += iCount; + log += F(", pC:"); + log += pCount; + # endif // ifdef LOG_LEVEL_DEBUG_DEV + addLog(LOG_LEVEL_DEBUG, log); + } + # endif // if !defined(BUILD_NO_DEBUG) && PLUGIN_143_DEBUG + + switch (_device) { + case P143_DeviceType_e::AdafruitEncoder: + { + _red = iRed; + _green = iGreen; + _blue = iBlue; + + if (nullptr != Adafruit_Spixel) { + Adafruit_Spixel->setPixelColor(0, _red, _green, _blue); + Adafruit_Spixel->show(); + } + break; + } + # if P143_FEATURE_INCLUDE_M5STACK + case P143_DeviceType_e::M5StackEncoder: + { + _red = iRed; + _green = iGreen; + _blue = iBlue; + m5stack_setPixelColor(static_cast(P143_M5STACK_SELECTION), _red, _green, _blue); + break; + } + # endif // if P143_FEATURE_INCLUDE_M5STACK + # if P143_FEATURE_INCLUDE_DFROBOT + case P143_DeviceType_e::DFRobotEncoder: + break; // N/A + # endif // if P143_FEATURE_INCLUDE_DFROBOT + } + } +} + +/***************************************************** + * parseColorMapLine, line prefixed with # is comment/disabled + ****************************************************/ +bool P143_data_struct::parseColorMapLine(const String& line, + int32_t & count, + int16_t & red, + int16_t & green, + int16_t & blue) { + bool result = false; + String tmp = parseString(line, 1); + + if (!tmp.isEmpty() && !tmp.startsWith(F("#"))) { + count = tmp.toInt(); + + tmp = parseString(line, 2); + + if (!tmp.isEmpty()) { + red = tmp.toInt(); + + tmp = parseString(line, 3); + + if (!tmp.isEmpty()) { + green = tmp.toInt(); + + tmp = parseString(line, 4); + + if (!tmp.isEmpty()) { + blue = tmp.toInt(); + result = true; + } + } + } + } + + return result; +} + +/***************************************************** + * rangeCheck + ****************************************************/ +bool P143_data_struct::rangeCheck(int32_t count, + int32_t min, + int32_t max) { + return !((min != max) && + ((count < min) || (count > max))); +} + +# endif // if P143_FEATURE_COUNTER_COLORMAPPING + +/***************************************************** + * plugin_fifty_per_second, handle button actions + ****************************************************/ +bool P143_data_struct::plugin_fifty_per_second(struct EventStruct *event) { + if (_initialized) { + const uint8_t pressedState = 0; // button has this value if pressed + uint8_t button = _buttonLast; + uint8_t state = _buttonState; + + // Read button + switch (_device) { + case P143_DeviceType_e::AdafruitEncoder: + + if (nullptr != Adafruit_Seesaw) { + button = Adafruit_Seesaw->digitalRead(P143_SEESAW_SWITCH); + } + break; + # if P143_FEATURE_INCLUDE_M5STACK + case P143_DeviceType_e::M5StackEncoder: + button = I2C_read8_reg(_i2cAddress, P143_M5STACK_REG_BUTTON); + break; + # endif // if P143_FEATURE_INCLUDE_M5STACK + # if P143_FEATURE_INCLUDE_DFROBOT + case P143_DeviceType_e::DFRobotEncoder: + uint8_t press = I2C_read8_reg(_i2cAddress, P143_DFROBOT_ENCODER_KEY_STATUS_REG); + + if ((press & 0x01) != 0) { + I2C_write8_reg(_i2cAddress, P143_DFROBOT_ENCODER_KEY_STATUS_REG, 0x00); // Reset + // Trigger immediately + button = button ? 0 : 1; + _buttonDown = true; + _buttonTime = (60 / 20); + } + + break; + # endif // if P143_FEATURE_INCLUDE_DFROBOT + } + + if ((button == pressedState) && _buttonDown) { + _buttonTime++; // increment 20 msec + } else if (button == pressedState) { + _buttonDown = true; + _buttonTime = 0; + } + + if ((button != _buttonLast) && (_buttonTime >= (60 / 20))) { // Short-press = 60 msec + _buttonLast = button; + _buttonDown = false; + + switch (static_cast(P143_PLUGIN_BUTTON_ACTION)) { + case P143_ButtonAction_e::PushButton: + state = button; + break; + case P143_ButtonAction_e::PushButtonInverted: + state = button == 0 ? 1 : 0; + _buttonState = state == 0 ? 1 : 0; // Changed + break; + case P143_ButtonAction_e::ToggleSwitch: + + if (button == pressedState) { + state = _buttonState == 0 ? 1 : 0; // Toggle + } + _buttonTime = 0; // Ignore long-press + break; + } + } + + if (state != _buttonState) { + if (_enableLongPress && (_buttonTime >= (_buttonLongPress / 20))) { // Long-press + state += 10; // Eventvalues similar to Switch plugin + } + + // Generate event + if (Settings.UseRules) { + eventQueue.add(event->TaskIndex, Cache.getTaskDeviceValueName(event->TaskIndex, 1), state); + } + + // Set task value + _buttonState = state; + + UserVar.setFloat(event->TaskIndex, 1, _buttonState); + + return true; + } + } + return false; +} + +# if P143_FEATURE_INCLUDE_M5STACK + +/***************************************************** + * applyBrightness, calculate new color based on brightness, based on NeoPixel library setBrightness() + ****************************************************/ +uint8_t P143_data_struct::applyBrightness(uint8_t color) { + if (_brightness) { + color = (color * _brightness) >> 8; + } + + return color; +} + +/***************************************************** + * m5stack_setPixelColor + ****************************************************/ +void P143_data_struct::m5stack_setPixelColor(uint8_t pixel, + uint8_t red, + uint8_t green, + uint8_t blue) { + uint8_t data[4] = { + pixel, + applyBrightness(red), + applyBrightness(green), + applyBrightness(blue) }; + + I2C_writeBytes_reg(_i2cAddress, P143_M5STACK_REG_LED, data, 4); +} + +# endif // if P143_FEATURE_INCLUDE_M5STACK + +#endif // ifdef USES_P143 diff --git a/src/src/PluginStructs/P166_data_struct.cpp b/src/src/PluginStructs/P166_data_struct.cpp index 1a2680f330..93287637b5 100644 --- a/src/src/PluginStructs/P166_data_struct.cpp +++ b/src/src/PluginStructs/P166_data_struct.cpp @@ -61,9 +61,9 @@ bool P166_data_struct::plugin_read(struct EventStruct *event) { return isInitialized(); } -const char subcommands[] PROGMEM = "volt|mvolt|range|preset|init"; +const char P166_subcommands[] PROGMEM = "volt|mvolt|range|preset|init"; -enum class subcmd_e : int8_t { +enum class P166_subcmd_e : int8_t { invalid = -1, volt = 0, mvolt, @@ -84,33 +84,33 @@ bool P166_data_struct::plugin_write(struct EventStruct *event, if (isInitialized() && equals(command, F("gp8403"))) { const String subcommand = parseString(string, 2); const String sValue = parseString(string, 4); - const int subcommand_i = GetCommandCode(subcommand.c_str(), subcommands); + const int subcommand_i = GetCommandCode(subcommand.c_str(), P166_subcommands); if (subcommand_i < 0) { return false; } // Fail fast - const subcmd_e subcmd = static_cast(subcommand_i); + const P166_subcmd_e subcmd = static_cast(subcommand_i); uint32_t nChannel{}; const bool hasChannel = validUIntFromString(parseString(string, 3), nChannel); switch (subcmd) { - case subcmd_e::invalid: + case P166_subcmd_e::invalid: break; - case subcmd_e::volt: - case subcmd_e::mvolt: - case subcmd_e::preset: + case P166_subcmd_e::volt: + case P166_subcmd_e::mvolt: + case P166_subcmd_e::preset: if (hasChannel && (nChannel <= 2)) { // Channel range check float fValue{}; bool isValid = false; - if (subcmd_e::preset == subcmd) { + if (P166_subcmd_e::preset == subcmd) { isValid = validPresetValue(sValue, fValue); } else { isValid = validFloatFromString(sValue, fValue); } if (isValid) { // Value valid check - const bool voltValue = (subcmd_e::volt == subcmd || subcmd_e::preset == subcmd); + const bool voltValue = (P166_subcmd_e::volt == subcmd || P166_subcmd_e::preset == subcmd); // Calculate mV from requested voltage const int iValue = static_cast(roundf(voltValue ? fValue * P166_FACTOR_mV : fValue)); @@ -135,7 +135,7 @@ bool P166_data_struct::plugin_write(struct EventStruct *event, } break; - case subcmd_e::init: + case P166_subcmd_e::init: if (hasChannel && (nChannel <= 2)) { // Channel range check for (uint8_t i = 0; i < P166_MAX_OUTPUTS; ++i) { @@ -162,7 +162,7 @@ bool P166_data_struct::plugin_write(struct EventStruct *event, } break; - case subcmd_e::range: + case P166_subcmd_e::range: if ((event->Par2 == 5) || (event->Par2 == 10)) { // Value options check _range = (event->Par2 == 5 diff --git a/src/src/PluginStructs/P167_data_struct.h b/src/src/PluginStructs/P167_data_struct.h index 6c88949f76..dbb79ac0fc 100644 --- a/src/src/PluginStructs/P167_data_struct.h +++ b/src/src/PluginStructs/P167_data_struct.h @@ -1,270 +1,276 @@ -////////////////////////////////////////////////////////////////////////////////////////////////// -// P167 device class for IKEA Vindstyrka SEN54 temperature , humidity and air quality sensors -// See datasheet https://sensirion.com/media/documents/6791EFA0/62A1F68F/Sensirion_Datasheet_Environmental_Node_SEN5x.pdf -// and info about extra request https://sensirion.com/media/documents/2B6FC1F3/6409E74A/PS_AN_Read_RHT_VOC_and_NOx_RAW_signals_D1.pdf -// Based upon code from Rob Tillaart, Viktor Balint, https://github.com/RobTillaart/SHT2x -// Rewritten and adapted for ESPeasy by andibaciu and tonhuisman -// changelog in _P167_Vindstyrka.ino -////////////////////////////////////////////////////////////////////////////////////////////////// - -#include "../../_Plugin_Helper.h" -#include "../ESPEasyCore/ESPEasyGPIO.h" -#ifdef USES_P167 - -# ifdef LIMIT_BUILD_SIZE -# define PLUGIN_167_DEBUG false -# else // ifdef LIMIT_BUILD_SIZE -# define PLUGIN_167_DEBUG true // set to true for extra log info in the debug -# endif // ifdef LIMIT_BUILD_SIZE - - -// ------------------------------------------------------------------------------ -# define VIND_ERR_NO_ERROR 0 // no error -# define VIND_ERR_CRC_ERROR 1 // crc error -# define VIND_ERR_WRONG_BYTES 2 // bytes b0,b1 or b2 wrong -# define VIND_ERR_NOT_ENOUGHT_BYTES 3 // not enough bytes from sdm -# define VIND_ERR_TIMEOUT 4 // timeout -// ------------------------------------------------------------------------------ - -// Make accessing specific parameters more readable in the code -# define P167_ENABLE_LOG PCONFIG(0) -# define P167_ENABLE_LOG_LABEL F("enlg") -# define P167_MODEL PCONFIG(1) -# define P167_MODEL_LABEL F("mdl") -# define P167_MON_SCL_PIN PCONFIG(2) -# define P167_QUERY1 PCONFIG(3) -# define P167_QUERY2 PCONFIG(4) -# define P167_QUERY3 PCONFIG(5) -# define P167_QUERY4 PCONFIG(6) - - -# define P167_I2C_ADDRESS_DFLT 0x69 -# define P167_MON_SCL_PIN_DFLT 13 -# define P167_MODEL_DFLT P167_MODEL_VINDSTYRKA // Vindstyrka or SEN54 or SEN55 -# define P167_QUERY1_DFLT 0 // Temperature (C) -# define P167_QUERY2_DFLT 1 // Humidity (%) -# define P167_QUERY3_DFLT 5 // PM2.5 (ug/m3) -# define P167_QUERY4_DFLT 2 // tVOC (index) - - -# define P167_NR_OUTPUT_OPTIONS 9 -# define P167_QUERY1_CONFIG_POS 3 -# define P167_SENSOR_TYPE_INDEX (P167_QUERY1_CONFIG_POS + VARS_PER_TASK) -# define P167_NR_OUTPUT_VALUES getValueCountFromSensorType(static_cast(PCONFIG(P167_SENSOR_TYPE_INDEX))) -# define P167_MAX_ATTEMPT 3 // Number of tentative before declaring NAN value -# define P167_VALUE_COUNT 9 // Number of available values - - -////////////////////////////////////////////////////////////////////////////////////////////////// -// Access to the Vindstyrka device is mainly by sequencing a Final State Machine -enum class P167_state : uint8_t { - Uninitialized = 0, // Initial state, unknown status of sensor device - Wait_for_reset, // Reset being performed - Read_firm_version, // Reading firmware version - Read_prod_name, // Reading production - Read_serial_no, // Reading serial number - Write_user_reg, // Write the user register - Initialized, // Initialization completed - Ready, // Aquisition request is pending, ready to measure - Wait_for_start_meas, // Start measurement started - Wait_for_read_flag, // Read meas flag started - Wait_for_read_meas, // Read meas started - Wait_for_read_raw_meas, // RAW Read meas started - Wait_for_read_raw_MYS_meas, // RAW Read meas MYSTERY started - Wait_for_read_status, // Read status - cmdSTARTmeas, // send command START meas to leave SEN5x ready flag for Vindstyrka - IDLE, // Sensor device in IDLE mode - New_Values_Available, // Acqusition finished, new data available - Error // Sensor device cannot be accessed or in error -}; - - -enum class P167_statusinfo : uint8_t { - sensor_speed = 0, - sensor_autoclean, - sensor_gas, - sensor_rht, - sensor_laser, - sensor_fan -}; - -enum class P167_model : uint8_t { - Vindstyrka = 0u, - SEN54 = 1u, - SEN55 = 2u, -}; - - -# define P167_MODEL_VINDSTYRKA static_cast(P167_model::Vindstyrka) -# define P167_MODEL_SEN54 static_cast(P167_model::SEN54) -# define P167_MODEL_SEN55 static_cast(P167_model::SEN55) - -const __FlashStringHelper* toString(P167_model model); - -const __FlashStringHelper* P167_getQueryString(uint8_t query); -const __FlashStringHelper* P167_getQueryValueString(uint8_t query); - - -////////////////////////////////////////////////////////////////////////////////////////////////// -// ESPeasy standard PluginTaskData structure for this plugin -struct P167_data_struct : public PluginTaskData_base { -public: - - P167_data_struct(); - - ~P167_data_struct(); - - void IRAM_ATTR checkPin_interrupt(void); - static void IRAM_ATTR Plugin_167_interrupt(P167_data_struct *self); - - ///////////////////////////////////////////////////////// - // This method runs the FSM step by step on each call - // Returns true when a stable state is reached - bool update(); - bool monitorSCL(); - - ///////////////////////////////////////////////////////// - // (re)configure the device properties - // This will result in resetting and reloading the device - bool setupDevice(uint8_t i2caddr); - bool setupModel(P167_model model); - bool setupMonPin(int16_t monpin); - void disableInterrupt_monpin(void); - - void setLogging(bool logStatus); - - ///////////////////////////////////////////////////////// - // check sensor is reachable over I2C - bool isConnected() const; - - ///////////////////////////////////////////////////////// - bool newValues() const; - - ///////////////////////////////////////////////////////// - bool inError() const; - - ///////////////////////////////////////////////////////// - // Reset the FSM to initial state - bool reset(); - - ///////////////////////////////////////////////////////// - // Trigger a measurement cycle - // Only perform the measurements with big interval to prevent the sensor from warming up. - bool startMeasurements(); - - bool getStatusInfo(P167_statusinfo param); - - ///////////////////////////////////////////////////////// - // Electronic Identification Code - // Sensirion_Humidity_SHT2x_Electronic_Identification_Code_V1.1.pdf - // Electronic ID bytes - bool getEID(String & eid_productname, - String & eid_serialnumber, - uint8_t& firmware) const; - - ///////////////////////////////////////////////////////// - // Temperature, humidity, DewPoint, PMxpy retrieval - // Note: values are fetched from memory and reflect latest succesful read cycle - float getRequestedValue(uint8_t request) const; - - - uint16_t getErrCode(bool _clear = false); // return last errorcode (optional clear this value, default false) - uint16_t getErrCount(bool _clear = false); // return total errors count (optional clear this value, default false) - uint16_t getSuccCount(bool _clear = false); // return total success count (optional clear this value, default false) - void clearErrCode(); // clear last errorcode - void clearErrCount(); // clear total errors count - void clearSuccCount(); // clear total success count - void startCleaning(); // Start a fan cleaning session. - -private: - - union devicestatus - { - uint32_t val; - struct - { - uint32_t dummy4 : 4; // bit 0..3 - uint32_t fan : 1; // bit 4 - uint32_t laser : 1; // bit 5 - uint32_t rht : 1; // bit 6 - uint32_t gas : 1; // bit 7 - uint32_t dummy3 : 11; // bit 8..18 - uint32_t autoclean : 1; // bit 19 - uint32_t dummy2 : 1; // bit 20 - uint32_t speed : 1; // bit 21 - uint32_t dummy1 : 10; // bit 22..31 - }; - }; - - devicestatus _devicestatus; - P167_state _state; - - bool writeCmd(uint16_t cmd); - bool writeCmd(uint16_t cmd, - uint8_t value); - bool writeCmd(uint16_t cmd, - uint8_t length, - uint8_t *buffer); - bool readBytes(uint8_t n, - uint8_t *val, - uint8_t maxDuration); - - bool readMeasValue(); - bool readMeasRawValue(); - bool readMeasRawMYSValue(); - bool readDataRdyFlag(); - bool readDeviceStatus(); - bool calculateValue(); - - bool getProductName(); - bool getSerialNumber(); - bool getFirmwareVersion(); - - - float _Humidity = 0.0f; // Humidity as fetched from the device [bits] - float _HumidityX = 0.0f; // Humidity as calculated - float _Temperature = 0.0f; // Temperature as fetched from the device [bits] - float _TemperatureX = 0.0f; // Temperature as calculated - float _DewPoint = 0.0f; // DewPoint as calculated - float _rawHumidity = 0.0f; // Humidity as fetched from the device without compensation[bits] - float _rawTemperature = 0.0f; // Temperature as fetched from the device without compensation[bits] - float _mysHumidity = 0.0f; // Humidity as fetched from the device without compensation[bits] - float _mysTemperature = 0.0f; // Temperature as fetched from the device without compensation[bits] - float _tVOC = 0.0f; // tVOC as fetched from the device[bits] - float _NOx = 0.0f; // NOx as fetched from the device[bits] - float _rawtVOC = 0.0f; // tVOC as fetched from the device without compensation[bits] - float _rawNOx = 0.0f; // NOx as fetched from the device without compensation[bits] - float _mysOffset = 0.0f; // Temperature Offset fetched from the device[bits] - float _PM1p0 = 0.0f; // PM1.0 as fetched from the device[bits] - float _PM2p5 = 0.0f; // PM2.5 as fetched from the device[bits] - float _PM4p0 = 0.0f; // PM4.0 as fetched from the device[bits] - float _PM10p0 = 0.0f; // PM10.0 as fetched from the device[bits] - P167_model _model = P167_model::Vindstyrka; // Selected sensor model - uint8_t _i2caddr = 0; // Programmed I2C address - uint8_t _monpin = 0; // Pin to monitor I2C SCL to find when VindStyrka finish i2c communication - unsigned long _last_action_started = 0; // Timestamp for last action that takes processing time - uint16_t _errCount = 0; // Number of errors since last successful access - String _eid_productname; // Electronic Device ID - Product Name, read at initialization - String _eid_serialnumber; // Electronic Device ID - Serial Number, read at initialization - uint8_t _firmware = 0; // Firmware version numer, read at initialization - uint8_t _userreg = 0; // TODO debugging only - uint16_t _readingerrcode = VIND_ERR_NO_ERROR; // 4 = timeout; 3 = not enough bytes; 2 = number of bytes OK but bytes b0,b1 - // or b2 wrong, 1 = crc error - uint16_t _readingerrcount = 0; // total errors couter - uint32_t _readingsuccesscount = 0; // total success couter - uint8_t stepMonitoring = 0; // step for Monitorin SCL pin algorithm - bool _errmeas = false; - bool _errmeasraw = false; - bool _errmeasrawmys = false; - bool _errdevicestatus = false; - bool startMonitoringFlag = false; // flag to START/STOP Monitoring algorithm - bool statusMonitoring = false; // flag for status return from Monitoring algorithm - bool _enableLogging = false; // flag for enabling some technical logging - unsigned long lastSCLLowTransitionMonitoringTime = 0; // last time when SCL i2c pin rising - - volatile uint32_t monpinValue = 0; - volatile uint32_t monpinValuelast = 0; - volatile uint8_t monpinChanged = 0; - volatile uint64_t monpinLastTransitionTime = 0; -}; -#endif // USES_P167 +#ifndef PLUGINSTRUCTS_P167_DATA_STRUCT_H +#define PLUGINSTRUCTS_P167_DATA_STRUCT_H + + +////////////////////////////////////////////////////////////////////////////////////////////////// +// P167 device class for IKEA Vindstyrka SEN54 temperature , humidity and air quality sensors +// See datasheet https://sensirion.com/media/documents/6791EFA0/62A1F68F/Sensirion_Datasheet_Environmental_Node_SEN5x.pdf +// and info about extra request https://sensirion.com/media/documents/2B6FC1F3/6409E74A/PS_AN_Read_RHT_VOC_and_NOx_RAW_signals_D1.pdf +// Based upon code from Rob Tillaart, Viktor Balint, https://github.com/RobTillaart/SHT2x +// Rewritten and adapted for ESPeasy by andibaciu and tonhuisman +// changelog in _P167_Vindstyrka.ino +////////////////////////////////////////////////////////////////////////////////////////////////// + +#include "../../_Plugin_Helper.h" +#include "../ESPEasyCore/ESPEasyGPIO.h" +#ifdef USES_P167 + +# ifdef LIMIT_BUILD_SIZE +# define PLUGIN_167_DEBUG false +# else // ifdef LIMIT_BUILD_SIZE +# define PLUGIN_167_DEBUG true // set to true for extra log info in the debug +# endif // ifdef LIMIT_BUILD_SIZE + + +// ------------------------------------------------------------------------------ +# define VIND_ERR_NO_ERROR 0 // no error +# define VIND_ERR_CRC_ERROR 1 // crc error +# define VIND_ERR_WRONG_BYTES 2 // bytes b0,b1 or b2 wrong +# define VIND_ERR_NOT_ENOUGHT_BYTES 3 // not enough bytes from sdm +# define VIND_ERR_TIMEOUT 4 // timeout +// ------------------------------------------------------------------------------ + +// Make accessing specific parameters more readable in the code +# define P167_ENABLE_LOG PCONFIG(0) +# define P167_ENABLE_LOG_LABEL F("enlg") +# define P167_MODEL PCONFIG(1) +# define P167_MODEL_LABEL F("mdl") +# define P167_MON_SCL_PIN PCONFIG(2) +# define P167_QUERY1 PCONFIG(3) +# define P167_QUERY2 PCONFIG(4) +# define P167_QUERY3 PCONFIG(5) +# define P167_QUERY4 PCONFIG(6) + + +# define P167_I2C_ADDRESS_DFLT 0x69 +# define P167_MON_SCL_PIN_DFLT 13 +# define P167_MODEL_DFLT P167_MODEL_VINDSTYRKA // Vindstyrka or SEN54 or SEN55 +# define P167_QUERY1_DFLT 0 // Temperature (C) +# define P167_QUERY2_DFLT 1 // Humidity (%) +# define P167_QUERY3_DFLT 5 // PM2.5 (ug/m3) +# define P167_QUERY4_DFLT 2 // tVOC (index) + + +# define P167_NR_OUTPUT_OPTIONS 9 +# define P167_QUERY1_CONFIG_POS 3 +# define P167_SENSOR_TYPE_INDEX (P167_QUERY1_CONFIG_POS + VARS_PER_TASK) +# define P167_NR_OUTPUT_VALUES getValueCountFromSensorType(static_cast(PCONFIG(P167_SENSOR_TYPE_INDEX))) +# define P167_MAX_ATTEMPT 3 // Number of tentative before declaring NAN value +# define P167_VALUE_COUNT 9 // Number of available values + + +////////////////////////////////////////////////////////////////////////////////////////////////// +// Access to the Vindstyrka device is mainly by sequencing a Final State Machine +enum class P167_state : uint8_t { + Uninitialized = 0, // Initial state, unknown status of sensor device + Wait_for_reset, // Reset being performed + Read_firm_version, // Reading firmware version + Read_prod_name, // Reading production + Read_serial_no, // Reading serial number + Write_user_reg, // Write the user register + Initialized, // Initialization completed + Ready, // Aquisition request is pending, ready to measure + Wait_for_start_meas, // Start measurement started + Wait_for_read_flag, // Read meas flag started + Wait_for_read_meas, // Read meas started + Wait_for_read_raw_meas, // RAW Read meas started + Wait_for_read_raw_MYS_meas, // RAW Read meas MYSTERY started + Wait_for_read_status, // Read status + cmdSTARTmeas, // send command START meas to leave SEN5x ready flag for Vindstyrka + IDLE, // Sensor device in IDLE mode + New_Values_Available, // Acqusition finished, new data available + Error // Sensor device cannot be accessed or in error +}; + + +enum class P167_statusinfo : uint8_t { + sensor_speed = 0, + sensor_autoclean, + sensor_gas, + sensor_rht, + sensor_laser, + sensor_fan +}; + +enum class P167_model : uint8_t { + Vindstyrka = 0u, + SEN54 = 1u, + SEN55 = 2u, +}; + + +# define P167_MODEL_VINDSTYRKA static_cast(P167_model::Vindstyrka) +# define P167_MODEL_SEN54 static_cast(P167_model::SEN54) +# define P167_MODEL_SEN55 static_cast(P167_model::SEN55) + +const __FlashStringHelper* toString(P167_model model); + +const __FlashStringHelper* P167_getQueryString(uint8_t query); +const __FlashStringHelper* P167_getQueryValueString(uint8_t query); + + +////////////////////////////////////////////////////////////////////////////////////////////////// +// ESPeasy standard PluginTaskData structure for this plugin +struct P167_data_struct : public PluginTaskData_base { +public: + + P167_data_struct(); + + ~P167_data_struct(); + + void IRAM_ATTR checkPin_interrupt(void); + static void IRAM_ATTR Plugin_167_interrupt(P167_data_struct *self); + + ///////////////////////////////////////////////////////// + // This method runs the FSM step by step on each call + // Returns true when a stable state is reached + bool update(); + bool monitorSCL(); + + ///////////////////////////////////////////////////////// + // (re)configure the device properties + // This will result in resetting and reloading the device + bool setupDevice(uint8_t i2caddr); + bool setupModel(P167_model model); + bool setupMonPin(int16_t monpin); + void disableInterrupt_monpin(void); + + void setLogging(bool logStatus); + + ///////////////////////////////////////////////////////// + // check sensor is reachable over I2C + bool isConnected() const; + + ///////////////////////////////////////////////////////// + bool newValues() const; + + ///////////////////////////////////////////////////////// + bool inError() const; + + ///////////////////////////////////////////////////////// + // Reset the FSM to initial state + bool reset(); + + ///////////////////////////////////////////////////////// + // Trigger a measurement cycle + // Only perform the measurements with big interval to prevent the sensor from warming up. + bool startMeasurements(); + + bool getStatusInfo(P167_statusinfo param); + + ///////////////////////////////////////////////////////// + // Electronic Identification Code + // Sensirion_Humidity_SHT2x_Electronic_Identification_Code_V1.1.pdf + // Electronic ID bytes + bool getEID(String & eid_productname, + String & eid_serialnumber, + uint8_t& firmware) const; + + ///////////////////////////////////////////////////////// + // Temperature, humidity, DewPoint, PMxpy retrieval + // Note: values are fetched from memory and reflect latest succesful read cycle + float getRequestedValue(uint8_t request) const; + + + uint16_t getErrCode(bool _clear = false); // return last errorcode (optional clear this value, default false) + uint16_t getErrCount(bool _clear = false); // return total errors count (optional clear this value, default false) + uint16_t getSuccCount(bool _clear = false); // return total success count (optional clear this value, default false) + void clearErrCode(); // clear last errorcode + void clearErrCount(); // clear total errors count + void clearSuccCount(); // clear total success count + void startCleaning(); // Start a fan cleaning session. + +private: + + union devicestatus + { + uint32_t val; + struct + { + uint32_t dummy4 : 4; // bit 0..3 + uint32_t fan : 1; // bit 4 + uint32_t laser : 1; // bit 5 + uint32_t rht : 1; // bit 6 + uint32_t gas : 1; // bit 7 + uint32_t dummy3 : 11; // bit 8..18 + uint32_t autoclean : 1; // bit 19 + uint32_t dummy2 : 1; // bit 20 + uint32_t speed : 1; // bit 21 + uint32_t dummy1 : 10; // bit 22..31 + }; + }; + + devicestatus _devicestatus; + P167_state _state; + + bool writeCmd(uint16_t cmd); + bool writeCmd(uint16_t cmd, + uint8_t value); + bool writeCmd(uint16_t cmd, + uint8_t length, + uint8_t *buffer); + bool readBytes(uint8_t n, + uint8_t *val, + uint8_t maxDuration); + + bool readMeasValue(); + bool readMeasRawValue(); + bool readMeasRawMYSValue(); + bool readDataRdyFlag(); + bool readDeviceStatus(); + bool calculateValue(); + + bool getProductName(); + bool getSerialNumber(); + bool getFirmwareVersion(); + + + float _Humidity = 0.0f; // Humidity as fetched from the device [bits] + float _HumidityX = 0.0f; // Humidity as calculated + float _Temperature = 0.0f; // Temperature as fetched from the device [bits] + float _TemperatureX = 0.0f; // Temperature as calculated + float _DewPoint = 0.0f; // DewPoint as calculated + float _rawHumidity = 0.0f; // Humidity as fetched from the device without compensation[bits] + float _rawTemperature = 0.0f; // Temperature as fetched from the device without compensation[bits] + float _mysHumidity = 0.0f; // Humidity as fetched from the device without compensation[bits] + float _mysTemperature = 0.0f; // Temperature as fetched from the device without compensation[bits] + float _tVOC = 0.0f; // tVOC as fetched from the device[bits] + float _NOx = 0.0f; // NOx as fetched from the device[bits] + float _rawtVOC = 0.0f; // tVOC as fetched from the device without compensation[bits] + float _rawNOx = 0.0f; // NOx as fetched from the device without compensation[bits] + float _mysOffset = 0.0f; // Temperature Offset fetched from the device[bits] + float _PM1p0 = 0.0f; // PM1.0 as fetched from the device[bits] + float _PM2p5 = 0.0f; // PM2.5 as fetched from the device[bits] + float _PM4p0 = 0.0f; // PM4.0 as fetched from the device[bits] + float _PM10p0 = 0.0f; // PM10.0 as fetched from the device[bits] + P167_model _model = P167_model::Vindstyrka; // Selected sensor model + uint8_t _i2caddr = 0; // Programmed I2C address + uint8_t _monpin = 0; // Pin to monitor I2C SCL to find when VindStyrka finish i2c communication + unsigned long _last_action_started = 0; // Timestamp for last action that takes processing time + uint16_t _errCount = 0; // Number of errors since last successful access + String _eid_productname; // Electronic Device ID - Product Name, read at initialization + String _eid_serialnumber; // Electronic Device ID - Serial Number, read at initialization + uint8_t _firmware = 0; // Firmware version numer, read at initialization + uint8_t _userreg = 0; // TODO debugging only + uint16_t _readingerrcode = VIND_ERR_NO_ERROR; // 4 = timeout; 3 = not enough bytes; 2 = number of bytes OK but bytes b0,b1 + // or b2 wrong, 1 = crc error + uint16_t _readingerrcount = 0; // total errors couter + uint32_t _readingsuccesscount = 0; // total success couter + uint8_t stepMonitoring = 0; // step for Monitorin SCL pin algorithm + bool _errmeas = false; + bool _errmeasraw = false; + bool _errmeasrawmys = false; + bool _errdevicestatus = false; + bool startMonitoringFlag = false; // flag to START/STOP Monitoring algorithm + bool statusMonitoring = false; // flag for status return from Monitoring algorithm + bool _enableLogging = false; // flag for enabling some technical logging + unsigned long lastSCLLowTransitionMonitoringTime = 0; // last time when SCL i2c pin rising + + volatile uint32_t monpinValue = 0; + volatile uint32_t monpinValuelast = 0; + volatile uint8_t monpinChanged = 0; + volatile uint64_t monpinLastTransitionTime = 0; +}; +#endif // USES_P167 + +#endif // ifndef PLUGINSTRUCTS_P167_DATA_STRUCT_H diff --git a/src/src/PluginStructs/P169_data_struct.cpp b/src/src/PluginStructs/P169_data_struct.cpp new file mode 100644 index 0000000000..9f29c7efba --- /dev/null +++ b/src/src/PluginStructs/P169_data_struct.cpp @@ -0,0 +1,943 @@ +#include "../PluginStructs/P169_data_struct.h" + +#ifdef USES_P169 + +# include "../ESPEasyCore/ESPEasyGPIO.h" + +# include + + +# ifndef CORE_POST_3_0_0 +# ifdef ESP8266 +# define IRAM_ATTR ICACHE_RAM_ATTR +# endif // ifdef ESP8266 +# endif // ifndef CORE_POST_3_0_0 + +# define P169_AS3935_TIMEOUT_USEC 2000 + + +P169_data_struct::P169_data_struct(struct EventStruct *event) : + _irqPin(P169_IRQ_PIN) +{ + // Do not try to construct the sensor if not needed as it will set the pinmode of the pin + if (_irqPin >= 0 && Settings.TaskDeviceDataFeed[event->TaskIndex] == 0) { + _sensor = new (std::nothrow) AS3935I2C(P169_I2C_ADDRESS, P169_IRQ_PIN); + } +} + +P169_data_struct::~P169_data_struct() +{ + if (_sensor != nullptr) { + _sensor->writePowerDown(true); + delete _sensor; + _sensor = nullptr; + } +} + +bool P169_data_struct::loop(struct EventStruct *event) +{ + if (_sensor == nullptr) { + return false; + } + + if (_sensor->getInterruptMode() == AS3935MI::AS3935_INTERRUPT_NORMAL) { + // FIXME TD-er: Should also check for state of IRQ pin as it may still be high if the interrupt souce isn't checked. + const uint32_t timestamp = _sensor->getInterruptTimestamp(); + + if ((timestamp != 0ul) || DIRECT_pinRead(_irqPin)) { + if ((timestamp != 0ul) && (timePassedSince(timestamp) < 2)) { + // Check to make sure the sensor isn't still outputting a high freq. signal to the interrupt pin. + // Count should be 1 at most. + if (!_sensor->checkProperlySetToListenMode()) { + // Sensor not yet ready to report some data + addLog(LOG_LEVEL_ERROR, F("AS3935: Sensor was still showing LCO frequency on IRQ pin")); + return false; + } + + // Wait for the sensor to be ready to read the interrupt source + if (timePassedSince(_sensor->getInterruptTimestamp()) < 2) { + delay(1); + } + } + + // query the interrupt source from the AS3935 + switch (_sensor->readInterruptSource()) { + case AS3935MI::AS3935_INT_NH: + + // Noise floor too high + adjustForNoise(event); + break; + case AS3935MI::AS3935_INT_D: + + // Disturbance detected + // N.B. can be disabled with _sensor->writeMaskDisturbers(true); + adjustForDisturbances(event); + break; + case AS3935MI::AS3935_INT_L: + { + // Lightning detected + ++_lightningCount; + + // FIXME TD-er: What to do with the "Lightning Threshold" ? + // If it was > 15 minutes ago since the last detected lightning, + // or cleared statistics, then we should set _lightningCount to this + // threshold value and also increment the total counter accordingly. + + const int totalStrikes = UserVar.getFloat(event->TaskIndex, 3) + 1; + const uint32_t energy = getEnergy(); + + if (energy > _highestEnergy) { _highestEnergy = energy; } + + if (energy < _lowestEnergy) { _lowestEnergy = energy; } + UserVar.setFloat(event->TaskIndex, 0, computeDistanceFromEnergy(_highestEnergy, NAN)); + UserVar.setFloat(event->TaskIndex, 1, computeDistanceFromEnergy(_lowestEnergy, NAN)); + UserVar.setFloat(event->TaskIndex, 2, _lightningCount); + UserVar.setFloat(event->TaskIndex, 3, totalStrikes); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat( + F("AS3935: Lightning detected. DistNear: %.1f, DistFar: %.1f, Count: %u, Total: %d"), + computeDistanceFromEnergy(_highestEnergy, -1.0f), + computeDistanceFromEnergy(_lowestEnergy, -1.0f), + _lightningCount, + totalStrikes)); + } + + if (Settings.UseRules) { + // Lightning detected, Send event + // Eventvalues: + // - Distance + // - Energy + // - Lightning count since last PLUGIN_READ + // - Total Lightning count since this was reset (or power cycle of ESP) + eventQueue.addMove( + strformat( + F("%s#LightningDetected=%.1f,%.1f,%u,%d"), + getTaskDeviceName(event->TaskIndex).c_str(), + computeDistanceFromEnergy(_highestEnergy, -1.0f), + computeDistanceFromEnergy(_lowestEnergy, -1.0f), + _lightningCount, + totalStrikes)); + } + + return true; + } + case AS3935MI::AS3935_INT_DUPDATE: + { + // FIXME TD-er: No longer needed? + + + // Distance updated + const float distance = getDistance(); + + if (_lightningCount > 0) { + // Do not update until after this task interval or else the reported distance will be too low. + UserVar.setFloat(event->TaskIndex, 0, distance); + } else { + _sensor->clearStatistics(); + } + + if (Settings.UseRules) { + // Distance updated, Send event + // Eventvalue: + // - Distance + eventQueue.addMove( + strformat( + F("%s#DistanceUpdated=%.2f"), + getTaskDeviceName(event->TaskIndex).c_str(), + distance)); + } + break; + } + } + } + tryIncreasedSensitivity(event); + } + return false; +} + +void P169_data_struct::html_show_sensor_info(struct EventStruct *event) +{ + if (_sensor == nullptr) { + return; + } + + addFormSubHeader(F("Current Sensor Data")); + addRowLabel(F("Calibration")); + const int8_t ant_cap = _sensor->getCalibratedAntCap(); + + if (ant_cap != -1) { + const float deviation_pct = computeDeviationPct(_sensor->getAntCapFrequency(ant_cap)); + + if (fabs(deviation_pct) < (100 * AS3935MI_ALLOWED_DEVIATION)) { + addEnabled(true); + } else { + if (P169_GET_TOLERANT_CALIBRATION_RANGE) { + addEnabled(true); + addHtml(F(HTML_SYMBOL_WARNING)); + } else { + addEnabled(false); + } + } + + addRowLabel(F("Best Antenna cap")); + addHtml(strformat(F("%d (%.2f%%)"), ant_cap, deviation_pct)); + } else { + addEnabled(false); + } + + addRowLabel(F("Error % per cap")); +# if FEATURE_CHART_JS + addCalibrationChart(event); +# else // if FEATURE_CHART_JS + + for (uint8_t i = 0; i < 16; ++i) { + const int32_t freq = _sensor->getAntCapFrequency(i); + + if (i != 0) { + addHtml(','); + addHtml(' '); + } + + if (freq > 0) { + addHtml(strformat(F("%.2f%%"), computeDeviationPct(freq))); + } else { + addHtml('-'); + } + } +# endif // if FEATURE_CHART_JS + + addRowLabel(F("Current AFE gain")); + addHtmlFloat(_afeGain, 2); + addHtml('x'); + + addRowLabel(F("Current Noise Floor Threshold")); + addHtmlInt(_sensor->readNoiseFloorThreshold()); + + addRowLabel(F("Current Watchdog Threshold")); + addHtmlInt(_sensor->readWatchdogThreshold()); + + addRowLabel(F("Current Spike Rejection")); + addHtmlInt(_sensor->readSpikeRejection()); +} + +bool P169_data_struct::plugin_init(struct EventStruct *event) +{ + if (_sensor == nullptr) { + return false; + } + + _sensor->setInterruptMode(AS3935MI::AS3935_INTERRUPT_DETACHED); + + if (!(_sensor->begin() && _sensor->checkConnection())) + { + addLog(LOG_LEVEL_ERROR, F("AS3935: Sensor not detected")); + return false; + } + addLog(LOG_LEVEL_INFO, F("AS3935: Sensor detected")); + + /* + if (!_sensor->checkIRQ()) + { + addLog(LOG_LEVEL_ERROR, F("AS3935: IRQ pin connection check failed")); + + // return false; + } + */ + + calibrate(event); + +# ifdef ESP32 + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) + { + // Short test checking effect of nr samples during calibration + { + String log = F("AS3935: Calibration test: "); + + for (size_t i = 0; i < 7; ++i) { + const uint32_t nrSamples = 2048 >> i; + log += strformat(F(",%d samples"), nrSamples); + } + addLogMove(LOG_LEVEL_DEBUG, log); + } + + for (int antcap = 0; antcap < 16; ++antcap) { + float deviation_pct[7]{}; + + for (size_t i = 0; i < 7; ++i) { + const uint32_t nrSamples = 2048 >> i; + _sensor->setFrequencyMeasureNrSamples(nrSamples); + const uint32_t freq = _sensor->measureResonanceFrequency(AS3935MI::display_frequency_source_t::LCO, antcap); + + if (freq > 0) { + deviation_pct[i] = computeDeviationPct(freq); + } else { + deviation_pct[i] = 0.0f; + } + } + String log = strformat(F("AS3935: LCO: cap %d "), antcap); + + for (size_t i = 0; i < 7; ++i) { + log += strformat(F(",%.2f%%"), deviation_pct[i]); + } + addLogMove(LOG_LEVEL_DEBUG, log); + } + } +# endif // ifdef ESP32 + + + // set the analog front end gain + _sensor->writeNoiseFloorThreshold(AS3935MI::AS3935_NFL_2); + _sensor->writeWatchdogThreshold(AS3935MI::AS3935_WDTH_2); + _sensor->writeSpikeRejection(AS3935MI::AS3935_SREJ_2); + setAFE_gain(event, P169_AFE_GAIN_LOW); + { + AS3935MI::min_num_lightnings_t min_num_lightnings = AS3935MI::AS3935_MNL_1; + + if ((P169_LIGHTNING_THRESHOLD >= AS3935MI::AS3935_MNL_1) && (P169_LIGHTNING_THRESHOLD <= AS3935MI::AS3935_MNL_16)) { + min_num_lightnings = static_cast(P169_LIGHTNING_THRESHOLD); + } + _sensor->writeMinLightnings(min_num_lightnings); + } + + _sensor->writeMaskDisturbers(P169_GET_MASK_DISTURBANCE); + + _sensor->setInterruptMode(AS3935MI::AS3935_INTERRUPT_NORMAL); + return true; +} + +const char P169_subcommands[] PROGMEM = "clearstats|calibrate|setgain|setnf|setwd|setsrej"; + +enum class P169_subcmd_e : int8_t { + invalid = -1, + clearstats = 0, + calibrate, + setgain, + setnf, // Set noise floor + setwd, // Set Watchdog Threshold + setsrej // Set Spike Rejection +}; + +/***************************************************** +* plugin_write +*****************************************************/ +bool P169_data_struct::plugin_write(struct EventStruct *event, + String & string) { + if (_sensor == nullptr) { + return false; + } + + bool success = false; + + const String command = parseString(string, 1); + + if (equals(command, F("as3935"))) { + const String subcommand = parseString(string, 2); + const int subcommand_i = GetCommandCode(subcommand.c_str(), P169_subcommands); + + if (subcommand_i < 0) { return false; } // Fail fast + + const P169_subcmd_e subcmd = static_cast(subcommand_i); + uint32_t value{}; + const bool hasValue = validUIntFromString(parseString(string, 3), value); + + switch (subcmd) { + case P169_subcmd_e::invalid: + break; + case P169_subcmd_e::clearstats: + clearStatistics(); + success = true; + break; + case P169_subcmd_e::calibrate: + calibrate(event); + _sensor->setInterruptMode(AS3935MI::AS3935_INTERRUPT_NORMAL); + + success = true; + break; + case P169_subcmd_e::setgain: + + if (hasValue) { + success = true; + + // First check if it is a register value or gain factor. + setAFE_gain(event, AFE_gain_to_regValue(value)); + } + break; + case P169_subcmd_e::setnf: + + if (hasValue) { + success = true; + setNoiseFloorThreshold(event, value); + } + break; + case P169_subcmd_e::setwd: + + if (hasValue) { + success = true; + _sensor->writeWatchdogThreshold(value); + sendChangeEvent(event); + } + break; + case P169_subcmd_e::setsrej: + + if (hasValue) { + success = true; + _sensor->writeSpikeRejection(value); + sendChangeEvent(event); + } + break; + } + } + return success; +} + +const char P169_get_config[] PROGMEM = "noisefloor|watchdog|srej|gain"; + +enum class P169_get_config_e : int8_t { + invalid = -1, + noisefloor = 0, // [#noisefloor] + watchdog, // [#watchdog] + srej, // [#srej] = current spike rejection + gain // [#gain] +}; + + +/***************************************************** +* plugin_get_config_value +*****************************************************/ +bool P169_data_struct::plugin_get_config_value(struct EventStruct *event, + String & string) { + if (_sensor == nullptr) { + return false; + } + + const String var = parseString(string, 1); + const int config_i = GetCommandCode(var.c_str(), P169_get_config); + + if (config_i < 0) { return false; } // Fail fast + const P169_get_config_e config = static_cast(config_i); + + switch (config) + { + case P169_get_config_e::invalid: + return false; + case P169_get_config_e::noisefloor: + string = _sensor->readNoiseFloorThreshold(); + break; + case P169_get_config_e::watchdog: + string = _sensor->readWatchdogThreshold(); + break; + case P169_get_config_e::srej: + string = _sensor->readSpikeRejection(); + break; + case P169_get_config_e::gain: + string = toString(_afeGain, 2); + break; + } + return true; +} + +float P169_data_struct::getDistance() +{ + return computeDistanceFromEnergy(getEnergy(), -1.0f); +} + +uint32_t P169_data_struct::getEnergy() +{ + if (_sensor == nullptr) { + return 0u; + } + + const uint32_t rawEnergy = _sensor->readEnergy(); + + if ((rawEnergy == 0) || (rawEnergy == 0xFFFFFFFF)) { + return 0u; + } + + const int8_t ant_cap = _sensor->getCalibratedAntCap(); + + if (ant_cap != -1) { + const float deviation_pct = computeDeviationPct(_sensor->getAntCapFrequency(ant_cap)); + + // Compute correction factor for loss in reported energy due to offset from perfect calibration. + // Formula derived by TD-er using chart on this site: (section "Is Tuning Important?") + // https://sites.google.com/view/as3935workbook/home#h.n9qonjaydsbd + const float loss = + (0.0321f * deviation_pct * deviation_pct) + + (0.0279f * deviation_pct) + + 1.0f; + + return static_cast((rawEnergy * loss) / _afeGain); + } + + // No antenna calibration present, so no compensation possible + return static_cast(rawEnergy / _afeGain); +} + +uint32_t P169_data_struct::getAndClearLightningCount() +{ + const uint32_t res = _lightningCount; + + _lightningCount = 0; + _highestEnergy = 0; + _lowestEnergy = 0xFFFFFFFF; + return res; +} + +void P169_data_struct::clearStatistics() +{ + if (_sensor != nullptr) { + _sensor->clearStatistics(); + } +} + +float P169_data_struct::computeDeviationPct(uint32_t LCO_freq) +{ + return (LCO_freq / 5000.0f) - 100.0f; +} + +float P169_data_struct::computeDistanceFromEnergy(uint32_t energy, float errorValue) +{ + if ((energy == 0) || (energy == 0xFFFFFFFF)) { return errorValue; } + + // TD-er: Distance vs Energy attenuation is roughly X / sqrt(energy) for some factor X. + // Factor of 2100 was determined experimentally evaluating a number of thunder storms + // by Michael Gasperi, the author of this site: https://sites.google.com/view/as3935workbook/home + // Verified by TD-er comparing live data mapped on https://map.blitzortung.org/ and the sensor. + // LCO calibration offset was taken into account. + return 2100.0f / sqrtf(energy); +} + +bool P169_data_struct::calibrate(struct EventStruct *event) +{ + if (_sensor == nullptr) { + return false; + } + + _sensor->setInterruptMode(AS3935MI::AS3935_INTERRUPT_DETACHED); + + + // calibrate the resonance frequency. failing the resonance frequency could indicate an issue + // of the sensor. + int32_t frequency = 0; + + _sensor->setCalibrateAllAntCap(P169_GET_SLOW_LCO_CALIBRATION); + + _sensor->setFrequencyMeasureNrSamples(P169_GET_SLOW_LCO_CALIBRATION ? AS3935MI_NR_CALIBRATION_SAMPLES : (AS3935MI_NR_CALIBRATION_SAMPLES / + 2)); + + if (!_sensor->calibrateResonanceFrequency(frequency)) + { + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + addLog(LOG_LEVEL_ERROR, + strformat(F("AS3935: Resonance Frequency Calibration failed: %d Hz not in range 482500 Hz ... 517500 Hz"), frequency)); + } + } else { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, + strformat(F("AS3935: Resonance Frequency Calibration passed: ant_cap: %d, %d Hz, deviation: %.2f%%"), + _sensor->readAntennaTuning(), + frequency, computeDeviationPct(frequency))); + } + } + + // calibrate the RCO. + if (!_sensor->calibrateRCO()) + { + addLog(LOG_LEVEL_ERROR, F("AS3935: RCO Calibration failed.")); + } else { + addLog(LOG_LEVEL_INFO, F("AS3935: RCO Calibration passed.")); + } + + // stop displaying LCO on IRQ + _sensor->displayLcoOnIrq(false); + + return frequency != 0; +} + +void P169_data_struct::adjustForNoise(struct EventStruct *event) +{ + if (_sensor == nullptr) { + return; + } + + // if the noise floor threshold setting is not yet maxed out, increase the setting. + // note that noise floor threshold events can also be triggered by an incorrect + // analog front end setting. + uint8_t nf_lev{}; + + if (_sensor->increaseNoiseFloorThreshold(nf_lev)) { + addLog(LOG_LEVEL_INFO, strformat(F("AS3935: Increased noise floor threshold to: %u"), nf_lev)); + sendChangeEvent(event); + } + else { + addLog(LOG_LEVEL_ERROR, F("AS3935: Noise floor threshold already at maximum")); + } +} + +void P169_data_struct::adjustForDisturbances(struct EventStruct *event) +{ + if (_sensor == nullptr) { + return; + } + + // increasing the Watchdog Threshold and / or Spike Rejection setting improves the AS3935s resistance + // against disturbers but also decrease the lightning detection efficiency (see AS3935 datasheet) + const uint8_t wdth = _sensor->readWatchdogThreshold(); + const uint8_t srej = _sensor->readSpikeRejection(); + const uint8_t noise = _sensor->readNoiseFloorThreshold(); + + if ((wdth == AS3935MI::AS3935_WDTH_5) || + (srej == AS3935MI::AS3935_SREJ_5) || + (noise == AS3935MI::AS3935_NFL_5)) + { + int32_t frequency{}; + const bool valid = _sensor->validateCurrentResonanceFrequency(frequency); + + if (valid || P169_GET_TOLERANT_CALIBRATION_RANGE) { + // Resonance frequency is still OK, try lowering gain + uint8_t curGain = _sensor->readAFE(); + + if (curGain > P169_AFE_GAIN_LOW) { + --curGain; + + // Since we change the gain, reset the other values to default + _sensor->writeNoiseFloorThreshold(AS3935MI::AS3935_NFL_2); + _sensor->writeWatchdogThreshold(AS3935MI::AS3935_WDTH_2); + _sensor->writeSpikeRejection(AS3935MI::AS3935_SREJ_2); + setAFE_gain(event, curGain); + _sense_adj_last = millis(); + } else + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + addLog(LOG_LEVEL_ERROR, strformat( + F("AS3935: Watchdog Threshold and Spike Rejection settings are already maxed out. Freq = %d"), + frequency)); + } + } else if (timePassedSince(_sense_adj_last) > static_cast(_sense_increase_interval)) + { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat( + F("AS3935: Calibrate Resonance freq. Current frequency: %d"), + frequency)); + } + + if (_sensor->calibrateResonanceFrequency(frequency)) { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat( + F("AS3935: Calibrate Resonance freq. Current frequency: %d"), + frequency)); + } + + // calibrate the RCO. + if (!_sensor->calibrateRCO()) + { + addLog(LOG_LEVEL_ERROR, F("AS3935: RCO Calibration failed.")); + } else { + addLog(LOG_LEVEL_INFO, F("AS3935: RCO Calibration passed.")); + } + } + + // FIXME TD-er: Should we do anything else here? + } + _sensor->setInterruptMode(AS3935MI::AS3935_INTERRUPT_NORMAL); + } + + // FIXME TD-er: Is this a good threshold for auto adjust algorithm? + if ((wdth < AS3935MI::AS3935_WDTH_5) || + (srej < AS3935MI::AS3935_SREJ_5) + + // || (noise < AS3935MI::AS3935_NFL_5) + ) + { + _sense_adj_last = millis(); + + // alternatively increase spike rejection and watchdog threshold + if (srej < wdth) + { + if (_sensor->increaseSpikeRejection()) { + sendChangeEvent(event); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat(F("AS3935: Increased spike rejection ratio to: %d"), (srej + 1))); + } + } + else { + addLog(LOG_LEVEL_ERROR, F("AS3935: Spike rejection ratio already at maximum")); + } + } + else + { + if (_sensor->increaseWatchdogThreshold()) { + sendChangeEvent(event); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat(F("AS3935: Increased watchdog threshold to %d"), (wdth + 1))); + } + } + else { + addLog(LOG_LEVEL_ERROR, F("AS3935: Watchdog threshold already at maximum")); + } + } + } +} + +void P169_data_struct::tryIncreasedSensitivity(struct EventStruct *event) +{ + if (_sensor == nullptr) { + return; + } + + // increase sensor sensitivity every once in a while. _sense_increase_interval controls how quickly the code + // attempts to increase sensitivity. + if (timePassedSince(_sense_adj_last) > static_cast(_sense_increase_interval)) + { + _sense_adj_last = millis(); + + addLog(LOG_LEVEL_INFO, F("AS3935: No disturber detected, attempting to decrease noise floor threshold.")); + + const uint8_t wdth = _sensor->readWatchdogThreshold(); + const uint8_t srej = _sensor->readSpikeRejection(); + const uint8_t noise = _sensor->readNoiseFloorThreshold(); + + if ((wdth == AS3935MI::AS3935_WDTH_0) || + (srej == AS3935MI::AS3935_SREJ_0) || + (noise == AS3935MI::AS3935_NFL_0)) + { + uint8_t curGain = _sensor->readAFE(); + + if (curGain < P169_AFE_GAIN_HIGH) { + ++curGain; + + // Since we change the gain, reset the other values to default + _sensor->writeNoiseFloorThreshold(AS3935MI::AS3935_NFL_2); + _sensor->writeWatchdogThreshold(AS3935MI::AS3935_WDTH_2); + _sensor->writeSpikeRejection(AS3935MI::AS3935_SREJ_2); + + setAFE_gain(event, curGain); + return; + } + } + + if ((noise > srej) && (noise > wdth) && _sensor->decreaseNoiseFloorThreshold()) { + sendChangeEvent(event); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat(F("AS3935: Decreased noise floor to %d"), (noise - 1))); + } + } + + // alternatively decrease spike rejection and watchdog threshold + if (srej > wdth) + { + if (_sensor->decreaseSpikeRejection()) { + sendChangeEvent(event); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat(F("AS3935: Decreased spike rejection ratio to %d"), (srej - 1))); + } + } + # ifndef BUILD_NO_DEBUG + else { + addLog(LOG_LEVEL_DEBUG, F("AS3935: Spike rejection ratio already at minimum")); + } + # endif // ifndef BUILD_NO_DEBUG + } + else + { + if (_sensor->decreaseWatchdogThreshold()) { + sendChangeEvent(event); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat(F("AS3935: Decreased watchdog threshold to: %d"), (wdth - 1))); + } + } + # ifndef BUILD_NO_DEBUG + else { + addLog(LOG_LEVEL_DEBUG, F("AS3935: Watchdog threshold already at minimum")); + } + # endif // ifndef BUILD_NO_DEBUG + } + } +} + +void P169_data_struct::setAFE_gain(struct EventStruct *event, uint8_t gain) +{ + if (_sensor == nullptr) { + return; + } + + _afeGain = regValue_AFE_gain_toFloat(gain); + _afeGainRegval = gain; + _sensor->writeAFE(gain); + + sendChangeEvent(event); +} + +void P169_data_struct::setNoiseFloorThreshold(struct EventStruct *event, uint8_t noiseFloor) +{ + if (_sensor == nullptr) { + return; + } + + _sensor->writeNoiseFloorThreshold(noiseFloor); + sendChangeEvent(event); +} + +float P169_data_struct::regValue_AFE_gain_toFloat(uint8_t gain) +{ + // Source: https://sites.google.com/view/as3935workbook/home + float afeGain = 1.0f; + + switch (gain) + { + case 10: afeGain = 0.30f; break; + case 11: afeGain = 0.40f; break; + case 12: afeGain = 0.55f; break; + case 13: afeGain = 0.74f; break; + case 14: afeGain = 1.00f; break; // Datasheet: "Outdoor" + case 15: afeGain = 1.35f; break; + case 16: afeGain = 1.83f; break; + case 17: afeGain = 2.47f; break; + case 18: afeGain = 3.34f; break; // Datasheet: "Indoor" + } + return afeGain; +} + +uint8_t P169_data_struct::AFE_gain_to_regValue(float gain) +{ + { + uint8_t regval = static_cast(roundf(gain)); + + if (regval >= 10) { + if (regval <= 18) { + return regval; + } + return AS3935MI::AS3935_OUTDOORS; + } + } + + float prevAFE_Gain = regValue_AFE_gain_toFloat(10); + + if (gain < prevAFE_Gain) { + return 10; + } + + for (uint8_t regval = 11; regval <= 18; ++regval) + { + const float afeGain = regValue_AFE_gain_toFloat(regval); + + if (gain < afeGain) { + // See which is closest, prev or current + if ((gain - prevAFE_Gain) < (afeGain - gain)) { + return regval - 1; + } + return regval; + } + prevAFE_Gain = afeGain; + } + + return AS3935MI::AS3935_INDOORS; +} + +void P169_data_struct::sendChangeEvent(struct EventStruct *event) +{ + if (_sensor == nullptr) { + return; + } + + if (Settings.UseRules) { + const uint8_t noiseFloor = _sensor->readNoiseFloorThreshold(); + const uint8_t watchdog = _sensor->readWatchdogThreshold(); + const uint8_t srej = _sensor->readSpikeRejection(); + + if (((_lastEvent_noiseFloor != noiseFloor) && ((_lastEvent_noiseFloor > 1) || (noiseFloor > 1))) || + ((_lastEvent_watchdog != watchdog) && ((_lastEvent_watchdog > 1) || (watchdog > 1))) || + (_lastEvent_srej != srej) || + (_lastEvent_gain != _afeGainRegval)) { + // Some value was updated, send event + // Eventvalue: + // - Gain + // - Noise Level + // - Watchdog Threshold + // - Spike Rejection + eventQueue.addMove( + strformat( + F("%s#ParamUpdate=%.2f,%u,%u,%u"), + getTaskDeviceName(event->TaskIndex).c_str(), + _afeGain, + noiseFloor, + watchdog, + srej)); + } + + _lastEvent_noiseFloor = noiseFloor; + _lastEvent_watchdog = watchdog; + _lastEvent_srej = srej; + _lastEvent_gain = _afeGainRegval; + } +} + +# if FEATURE_CHART_JS +void P169_data_struct::addCalibrationChart(struct EventStruct *event) +{ + if (_sensor == nullptr) { + return; + } + + const int valueCount = 16; + int xAxisValues[valueCount]{}; + float values[valueCount]{}; + + int actualValueCount = 0; + + for (int i = 0; i < valueCount; ++i) { + const int32_t freq = _sensor->getAntCapFrequency(i); + + if (freq > 0) { + values[actualValueCount] = computeDeviationPct(freq); + xAxisValues[actualValueCount] = i; + ++actualValueCount; + } + } + + String axisOptions; + + { + ChartJS_options_scales scales; + scales.add({ F("x"), F("Antenna capacitor") }); + scales.add({ F("y"), F("Error (%)") }); + axisOptions = scales.toString(); + } + + add_ChartJS_chart_header( + F("line"), + F("lcoCapErrorCurve"), + { F("LCO Resonance Frequency") }, + 500, + 500, + axisOptions); + + add_ChartJS_chart_labels( + actualValueCount, + xAxisValues); + + { + const ChartJS_dataset_config config( + F("Error %"), + F("rgb(255, 99, 132)")); + + + add_ChartJS_dataset( + config, + values, + actualValueCount, + 2); + } + add_ChartJS_chart_footer(); +} + +# endif // if FEATURE_CHART_JS + + +#endif // ifdef USES_P169 diff --git a/src/src/PluginStructs/P169_data_struct.h b/src/src/PluginStructs/P169_data_struct.h new file mode 100644 index 0000000000..381e4da185 --- /dev/null +++ b/src/src/PluginStructs/P169_data_struct.h @@ -0,0 +1,158 @@ +#ifndef PLUGINSTRUCTS_P169_DATA_STRUCT_H +#define PLUGINSTRUCTS_P169_DATA_STRUCT_H + +////////////////////////////////////////////////////////////////////////////////////////////////// +// +// +// Using AS3935MI library written by Gregor Christandl +// https://bitbucket.org/christandlg/as3935mi/issues +////////////////////////////////////////////////////////////////////////////////////////////////// + +#include "../../_Plugin_Helper.h" +#ifdef USES_P169 + +# include "../ESPEasyCore/ESPEasyGPIO.h" + +# include + +# define DEFAULT_SENSE_INCREASE_INTERVAL 15000 // 15 s sensitivity increase interval + +# define P169_IRQ_PIN CONFIG_PIN1 +# define P169_IRQ_PIN_LABEL "taskdevicepin1" + +# define P169_I2C_ADDRESS PCONFIG(0) +# define P169_I2C_ADDRESS_LABEL PCONFIG_LABEL(0) + +# define P169_LIGHTNING_THRESHOLD PCONFIG(1) +# define P169_LIGHTNING_THRESHOLD_LABEL PCONFIG_LABEL(1) + +# define P169_AFE_GAIN_LOW PCONFIG(3) +# define P169_AFE_GAIN_LOW_LABEL PCONFIG_LABEL(3) +# define P169_AFE_GAIN_HIGH PCONFIG(4) +# define P169_AFE_GAIN_HIGH_LABEL PCONFIG_LABEL(4) + + +// # define P169_GET_INDOOR bitRead(PCONFIG(2), 0) +// # define P169_SET_INDOOR(X) bitWrite(PCONFIG(2), 0, X) +// # define P169_INDOOR_LABEL "mode" + +# define P169_GET_MASK_DISTURBANCE bitRead(PCONFIG(2), 1) +# define P169_SET_MASK_DISTURBANCE(X) bitWrite(PCONFIG(2), 1, X) +# define P169_MASK_DISTURBANCE_LABEL "maskdist" + +# define P169_GET_SEND_ONLY_ON_LIGHTNING bitRead(PCONFIG(2), 2) +# define P169_SET_SEND_ONLY_ON_LIGHTNING(X) bitWrite(PCONFIG(2), 2, X) +# define P169_SEND_ONLY_ON_LIGHTNING_LABEL "sendonlightning" + +# define P169_GET_TOLERANT_CALIBRATION_RANGE bitRead(PCONFIG(2), 3) +# define P169_SET_TOLERANT_CALIBRATION_RANGE(X) bitWrite(PCONFIG(2), 3, X) +# define P169_TOLERANT_CALIBRATION_RANGE_LABEL "tolerantcalib" + +# define P169_GET_SLOW_LCO_CALIBRATION bitRead(PCONFIG(2), 4) +# define P169_SET_SLOW_LCO_CALIBRATION(X) bitWrite(PCONFIG(2), 4, X) +# define P169_SLOW_LCO_CALIBRATION_LABEL "slowcalib" + +// The device addresses for the AS3935 in read or write mode are defined by: +// 0-0-0-0-0-a1-a0-0: write mode device address (DW) +// 0-0-0-0-0-a1-a0-1: read mode device address (DR) +// Where a0 and a1 are defined by the pins 5 (ADD0) and 6 (ADD1). +// The combination a0 = 0 (low) and a1 =0 (low) is explicitly not allowed for I²C communication. +# define P169_I2C_ADDRESS_DFLT 0x03 + +// Franklin AS3935 has 10k pull-up on the SDA line. +// When no other I2C devices used: Max 400 kHz I2C clock, add 10k as pull-up on SCL. +// Along with upto 3 other I2C devices: Max 100 kHz I2C clock, add 10k on SDA and add 4k7 pull-up on SCL. + + +struct P169_data_struct : public PluginTaskData_base +{ +public: + + P169_data_struct(struct EventStruct *event); + virtual ~P169_data_struct(); + + bool loop(struct EventStruct *event); + + bool plugin_init(struct EventStruct *event); + bool plugin_write(struct EventStruct *event, + String & string); + + bool plugin_get_config_value(struct EventStruct *event, + String & string); + + void html_show_sensor_info(struct EventStruct *event); + + // Read distance in km + float getDistance(); + + // Get lightning strike energy in some raw value (no unit) + uint32_t getEnergy(); + + uint32_t getLightningCount() const { + return _lightningCount; + } + + uint32_t getAndClearLightningCount(); + + + // Clear lightning distance estimation statistics + void clearStatistics(); + +private: + + static float computeDeviationPct(uint32_t LCO_freq); + + static float computeDistanceFromEnergy(uint32_t energy, + float errorValue); + + bool calibrate(struct EventStruct *event); + + void adjustForNoise(struct EventStruct *event); + + void adjustForDisturbances(struct EventStruct *event); + + void tryIncreasedSensitivity(struct EventStruct *event); + + void setAFE_gain(struct EventStruct *event, uint8_t gain); + + void setNoiseFloorThreshold(struct EventStruct *event, uint8_t noiseFloor); + + // Convert internal register value for AFE gain to gain factor + static float regValue_AFE_gain_toFloat(uint8_t gain); + + // Convert AFE gain factor to internal register value. + // Register values range from 10 .. 18, gain factor from 0.3x .. 3.34x + // If given value is in range 10 .. 18, this value wil be returned. + // For out of range values, the default of gain factor 1.0x will be used. + static uint8_t AFE_gain_to_regValue(float gain); + + void sendChangeEvent(struct EventStruct *event); + +# if FEATURE_CHART_JS + void addCalibrationChart(struct EventStruct *event); +# endif // if FEATURE_CHART_JS + + + AS3935I2C *_sensor = nullptr; + int8_t _irqPin; + float _afeGain = 1.0f; + uint8_t _afeGainRegval = 0; + + uint32_t _sense_adj_last = 0; + + uint32_t _sense_increase_interval = DEFAULT_SENSE_INCREASE_INTERVAL; + + uint32_t _lightningCount = 0; + uint32_t _highestEnergy = 0; + uint32_t _lowestEnergy = 0xFFFFFFFF; + + // Keep track of previous value to only send #ParamUpdate events when changed. + uint8_t _lastEvent_noiseFloor = 255; + uint8_t _lastEvent_watchdog = 255; + uint8_t _lastEvent_srej = 255; + uint8_t _lastEvent_gain = 0; + +}; + +#endif // ifdef USES_P169 +#endif // ifndef PLUGINSTRUCTS_P169_DATA_STRUCT_H diff --git a/src/src/WebServer/AdvancedConfigPage.cpp b/src/src/WebServer/AdvancedConfigPage.cpp index 06b895d095..4009769e7e 100644 --- a/src/src/WebServer/AdvancedConfigPage.cpp +++ b/src/src/WebServer/AdvancedConfigPage.cpp @@ -130,13 +130,16 @@ void handle_advanced() { #if FEATURE_I2C_DEVICE_CHECK Settings.CheckI2Cdevice(isFormItemChecked(LabelType::ENABLE_I2C_DEVICE_CHECK)); #endif // if FEATURE_I2C_DEVICE_CHECK - +#ifndef ESP32 Settings.WaitWiFiConnect(isFormItemChecked(LabelType::WAIT_WIFI_CONNECT)); +#endif Settings.HiddenSSID_SlowConnectPerBSSID(isFormItemChecked(LabelType::HIDDEN_SSID_SLOW_CONNECT)); Settings.SDK_WiFi_autoreconnect(isFormItemChecked(LabelType::SDK_WIFI_AUTORECONNECT)); +#ifdef ESP32 + Settings.PassiveWiFiScan(isFormItemChecked(LabelType::WIFI_PASSIVE_SCAN)); +#endif #if FEATURE_USE_IPV6 Settings.EnableIPv6(isFormItemChecked(LabelType::ENABLE_IPV6)); - addFormNote(F("Toggling IPv6 requires reboot")); #endif @@ -323,8 +326,6 @@ void handle_advanced() { # ifndef NO_HTTP_UPDATER addFormCheckBox(LabelType::ALLOW_OTA_UNLIMITED, Settings.AllowOTAUnlimited()); - addFormNote(F("When enabled, OTA updating can overwrite the filesystem and settings!")); - addFormNote(F("Requires reboot to activate")); # endif // ifndef NO_HTTP_UPDATER #if FEATURE_AUTO_DARK_MODE const __FlashStringHelper * cssModeNames[] = { @@ -344,7 +345,6 @@ void handle_advanced() { #if FEATURE_RULES_EASY_COLOR_CODE addFormCheckBox(LabelType::DISABLE_RULES_AUTOCOMPLETE, Settings.DisableRulesCodeCompletion()); - addFormNote(F("Also disables Rules syntax highlighting!")); #endif // if FEATURE_RULES_EASY_COLOR_CODE #if FEATURE_TARSTREAM_SUPPORT addFormCheckBox(LabelType::DISABLE_SAVE_CONFIG_AS_TAR, Settings.DisableSaveConfigAsTar()); @@ -364,40 +364,28 @@ void handle_advanced() { addFormCheckBox(LabelType::RESTART_WIFI_LOST_CONN, Settings.WiFiRestart_connection_lost()); addFormCheckBox(LabelType::FORCE_WIFI_NOSLEEP, Settings.WifiNoneSleep()); - addFormNote(F("Change WiFi sleep settings requires reboot to activate")); #ifdef SUPPORT_ARP addFormCheckBox(LabelType::PERIODICAL_GRAT_ARP, Settings.gratuitousARP()); #endif // ifdef SUPPORT_ARP addFormCheckBox(LabelType::CPU_ECO_MODE, Settings.EcoPowerMode()); - addFormNote(F("Node may miss receiving packets with Eco mode enabled")); #if FEATURE_SET_WIFI_TX_PWR - { - float maxTXpwr; - float sensitivity = GetRSSIthreshold(maxTXpwr); - - addFormFloatNumberBox(LabelType::WIFI_TX_MAX_PWR, Settings.getWiFi_TX_power(), 0.0f, MAX_TX_PWR_DBM_11b, 2, 0.25f); - addUnit(F("dBm")); - addFormNote(strformat( - F("Current max: %.2f dBm"), maxTXpwr)); - - addFormNumericBox(LabelType::WIFI_SENS_MARGIN, Settings.WiFi_sensitivity_margin, -20, 30); - addUnit(F("dB")); // Relative, thus the unit is dB, not dBm - addFormNote(strformat( - F("Adjust TX power to target the AP with (sensitivity + margin) dBm signal strength. Current sensitivity: %.2f dBm"), - sensitivity)); - } + addFormFloatNumberBox(LabelType::WIFI_TX_MAX_PWR, Settings.getWiFi_TX_power(), 0.0f, MAX_TX_PWR_DBM_11b, 2, 0.25f); + addFormNumericBox(LabelType::WIFI_SENS_MARGIN, Settings.WiFi_sensitivity_margin, -20, 30); addFormCheckBox(LabelType::WIFI_SEND_AT_MAX_TX_PWR, Settings.UseMaxTXpowerForSending()); #endif { addFormNumericBox(LabelType::WIFI_NR_EXTRA_SCANS, Settings.NumberExtraWiFiScans, 0, 5); - addFormNote(F("Number of extra times to scan all channels to have higher chance of finding the desired AP")); } addFormCheckBox(LabelType::WIFI_USE_LAST_CONN_FROM_RTC, Settings.UseLastWiFiFromRTC()); - - addFormCheckBox(LabelType::WAIT_WIFI_CONNECT, Settings.WaitWiFiConnect()); - addFormCheckBox(LabelType::SDK_WIFI_AUTORECONNECT, Settings.SDK_WiFi_autoreconnect()); - addFormCheckBox(LabelType::HIDDEN_SSID_SLOW_CONNECT, Settings.HiddenSSID_SlowConnectPerBSSID()); +#ifndef ESP32 + addFormCheckBox(LabelType::WAIT_WIFI_CONNECT, Settings.WaitWiFiConnect()); +#endif + addFormCheckBox(LabelType::SDK_WIFI_AUTORECONNECT, Settings.SDK_WiFi_autoreconnect()); + addFormCheckBox(LabelType::HIDDEN_SSID_SLOW_CONNECT, Settings.HiddenSSID_SlowConnectPerBSSID()); +#ifdef ESP32 + addFormCheckBox(LabelType::WIFI_PASSIVE_SCAN, Settings.PassiveWiFiScan()); +#endif #if FEATURE_USE_IPV6 addFormCheckBox(LabelType::ENABLE_IPV6, Settings.EnableIPv6()); #endif diff --git a/src/src/WebServer/Chart_JS.cpp b/src/src/WebServer/Chart_JS.cpp index bd8eab578f..e82c43e704 100644 --- a/src/src/WebServer/Chart_JS.cpp +++ b/src/src/WebServer/Chart_JS.cpp @@ -47,10 +47,20 @@ void add_ChartJS_chart_header( int width, int height, const String & options, + bool enableZoom, size_t nrSamples, bool onlyJSON) { - add_ChartJS_chart_header(chartType, String(id), chartTitle, width, height, options, nrSamples, onlyJSON); + add_ChartJS_chart_header( + chartType, + String(id), + chartTitle, + width, + height, + options, + enableZoom, + nrSamples, + onlyJSON); } void add_ChartJS_chart_header( @@ -60,6 +70,7 @@ void add_ChartJS_chart_header( int width, int height, const String & options, + bool enableZoom, size_t nrSamples, bool onlyJSON) { @@ -79,11 +90,46 @@ void add_ChartJS_chart_header( id_c_str, id_c_str)); } - add_ChartJS_chart_JSON_header(chartType, chartTitle, options, nrSamples); + String plugins; + + if (enableZoom) { + plugins = F( + "\"zoom\":" + "{" + "\"limits\":{" + "\"x\":{\"min\":\"original\",\"max\":\"original\",\"minRange\":1000}," // 1 sec min range + "}," + "\"pan\":{" + "\"enabled\":true," + "\"mode\":\"x\"," + "\"modifierKey\":\"ctrl\"," + "}," + "\"zoom\":{" + "\"wheel\":{" + "\"enabled\":true," + "}," + "\"drag\":{" + "\"enabled\":true," + "}," + "\"pinch\":{" + "\"enabled\":true" + "}," + "\"mode\":\"x\"," + "}" + "}" + ); + } + add_ChartJS_chart_JSON_header( + chartType, + plugins, + chartTitle, + options, + nrSamples); } void add_ChartJS_chart_JSON_header( const __FlashStringHelper *chartType, + const String & plugins, const ChartJS_title & chartTitle, const String & options, size_t nrSamples) @@ -91,18 +137,26 @@ void add_ChartJS_chart_JSON_header( addHtml(F("{\"type\":\"")); addHtml(chartType); addHtml(F("\",\"options\":{" - "\"responsive\":false,\"plugins\":{" - "\"legend\":{" + "\"responsive\":false,\"plugins\":{")); + addHtml(F("\"legend\":{" "\"position\":\"top\"" "},\"title\":")); addHtml(chartTitle.toString()); + + if (plugins.length() > 0) { + addHtml(','); + } + addHtml(plugins); addHtml('}'); // end plugins if (nrSamples >= 60) { // Default point radius = 3 // Typically when having > 64 samples, these points become really cluttered - // Thus it is best to remove them by setting the radius to 0. - addHtml(F(",\"elements\":{\"point\":{\"radius\":0}}")); + // Thus it is best to reduce their radius. + const float radius = (plugins.length() > 0) ? 2.5f : 2.0f; + addHtml(strformat( + F(",\"elements\":{\"point\":{\"radius\":%.1f}}"), + radius)); } if (!options.isEmpty()) { diff --git a/src/src/WebServer/Chart_JS.h b/src/src/WebServer/Chart_JS.h index b1be95df53..73ae441b4c 100644 --- a/src/src/WebServer/Chart_JS.h +++ b/src/src/WebServer/Chart_JS.h @@ -29,6 +29,7 @@ void add_ChartJS_chart_header( int width, int height, const String & options = EMPTY_STRING, + bool enableZoom = false, size_t nrSamples = 0, bool onlyJSON = false); @@ -39,11 +40,13 @@ void add_ChartJS_chart_header( int width, int height, const String & options = EMPTY_STRING, + bool enableZoom = false, size_t nrSamples = 0, bool onlyJSON = false); void add_ChartJS_chart_JSON_header( const __FlashStringHelper *chartType, + const String & plugins, const ChartJS_title & chartTitle, const String & options, size_t nrSamples); diff --git a/src/src/WebServer/Chart_JS_scale.cpp b/src/src/WebServer/Chart_JS_scale.cpp index 0ed34e7f4f..010b74abe0 100644 --- a/src/src/WebServer/Chart_JS_scale.cpp +++ b/src/src/WebServer/Chart_JS_scale.cpp @@ -54,10 +54,14 @@ String ChartJS_options_scale::toString() const case Position::Left: positionStr = F("left"); break; } - String ticksStr; + String extraOptions; + if (typeStr.equalsIgnoreCase(F("time")) || typeStr.equalsIgnoreCase(F("timeseries"))) { + // Make sure to use 24h time notation. + extraOptions += F(",\"time\":{\"displayFormats\":{\"millisecond\":\"HH:mm:ss.SSS\",\"second\":\"HH:mm:ss\",\"minute\":\"HH:mm:ss\",\"hour\":\"HH:mm\",\"day\":\"dd-MMM\",\"month\":\"MMM-yyyy\",\"year\":\"yyyy\"},\"tooltipFormat\":\"yyyy-MM-dd HH:mm:ss\"}"); + } if (tickCount > 0) { - ticksStr = strformat(F(",\"ticks\":{\"count\":%d}"), tickCount); + extraOptions += strformat(F(",\"ticks\":{\"count\":%d}"), tickCount); } return strformat( F("\"%s\":{\"display\":%s,\"type\":\"%s\",\"position\":\"%s\",\"title\":%s,\"weight\":%d%s}"), @@ -67,7 +71,7 @@ String ChartJS_options_scale::toString() const positionStr.c_str(), axisTitle.toString().c_str(), weight, - ticksStr.c_str()); + extraOptions.c_str()); } return EMPTY_STRING; } diff --git a/src/src/WebServer/ConfigPage.cpp b/src/src/WebServer/ConfigPage.cpp index 55b25ec93b..7bd36fd8fe 100644 --- a/src/src/WebServer/ConfigPage.cpp +++ b/src/src/WebServer/ConfigPage.cpp @@ -1,288 +1,294 @@ -#include "../WebServer/ConfigPage.h" - -#ifdef WEBSERVER_CONFIG - -#include "../WebServer/HTML_wrappers.h" -#include "../WebServer/AccessControl.h" -#include "../WebServer/Markup.h" -#include "../WebServer/Markup_Buttons.h" -#include "../WebServer/Markup_Forms.h" -#include "../WebServer/ESPEasy_WebServer.h" - -#ifdef USES_ESPEASY_NOW -#include "../DataStructs/MAC_address.h" -#include "../DataStructs/NodeStruct.h" -#endif - -#include "../ESPEasyCore/Controller.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" - -#include "../Globals/MQTT.h" -#include "../Globals/Nodes.h" -#include "../Globals/SecuritySettings.h" -#include "../Globals/Settings.h" - -#include "../Helpers/DeepSleep.h" -#include "../Helpers/ESPEasy_Storage.h" -#include "../Helpers/Networking.h" -#include "../Helpers/StringConverter.h" - - -// ******************************************************************************** -// Web Interface config page -// ******************************************************************************** -void handle_config() { - #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("handle_config")); - #endif - - if (!isLoggedIn()) { return; } - - navMenuIndex = MENU_INDEX_CONFIG; - TXBuffer.startStream(); - sendHeadandTail_stdtemplate(_HEAD); - - if (web_server.args() != 0) - { - String name = webArg(F("name")); - name.trim(); - - Settings.Delay = getFormItemInt(F("delay"), Settings.Delay); - Settings.deepSleep_wakeTime = getFormItemInt(F("awaketime"), Settings.deepSleep_wakeTime); - Settings.Unit = getFormItemInt(F("unit"), Settings.Unit); - - if (strcmp(Settings.Name, name.c_str()) != 0) { - addLog(LOG_LEVEL_INFO, F("Unit Name changed.")); - - if (CPluginCall(CPlugin::Function::CPLUGIN_GOT_INVALID, 0)) { // inform controllers that the old name will be invalid from now on. -#if FEATURE_MQTT - MQTTDisconnect(); // disconnect form MQTT Server if invalid message was sent succesfull. -#endif // if FEATURE_MQTT - } -#if FEATURE_MQTT - MQTTclient_should_reconnect = true; -#endif // if FEATURE_MQTT - } - - // Unit name - safe_strncpy(Settings.Name, name.c_str(), sizeof(Settings.Name)); - Settings.appendUnitToHostname(isFormItemChecked(F("appendunittohostname"))); - - // Password - copyFormPassword(F("password"), SecuritySettings.Password, sizeof(SecuritySettings.Password)); - - // SSID 1 - safe_strncpy(SecuritySettings.WifiSSID, webArg(F("ssid")).c_str(), sizeof(SecuritySettings.WifiSSID)); - copyFormPassword(F("key"), SecuritySettings.WifiKey, sizeof(SecuritySettings.WifiKey)); - - // SSID 2 - strncpy_webserver_arg(SecuritySettings.WifiSSID2, F("ssid2")); - copyFormPassword(F("key2"), SecuritySettings.WifiKey2, sizeof(SecuritySettings.WifiKey2)); - - // Hidden SSID - Settings.IncludeHiddenSSID(isFormItemChecked(LabelType::CONNECT_HIDDEN_SSID)); - Settings.HiddenSSID_SlowConnectPerBSSID(isFormItemChecked(LabelType::HIDDEN_SSID_SLOW_CONNECT)); - - // Access point password. - copyFormPassword(F("apkey"), SecuritySettings.WifiAPKey, sizeof(SecuritySettings.WifiAPKey)); - - // When set you can use the Sensor in AP-Mode without being forced to /setup - Settings.ApDontForceSetup(isFormItemChecked(F("ApDontForceSetup"))); - - // Usually the AP will be started when no WiFi is defined, or the defined one cannot be found. This flag may prevent it. - Settings.DoNotStartAP(isFormItemChecked(F("DoNotStartAP"))); - - - // TD-er Read access control from form. - SecuritySettings.IPblockLevel = getFormItemInt(F("ipblocklevel")); - - switch (SecuritySettings.IPblockLevel) { - case LOCAL_SUBNET_ALLOWED: - { - IPAddress low, high; - getSubnetRange(low, high); - - for (uint8_t i = 0; i < 4; ++i) { - SecuritySettings.AllowedIPrangeLow[i] = low[i]; - SecuritySettings.AllowedIPrangeHigh[i] = high[i]; - } - break; - } - case ONLY_IP_RANGE_ALLOWED: - case ALL_ALLOWED: - - webArg2ip(F("iprangelow"), SecuritySettings.AllowedIPrangeLow); - webArg2ip(F("iprangehigh"), SecuritySettings.AllowedIPrangeHigh); - break; - } - - #ifdef USES_ESPEASY_NOW - for (int peer = 0; peer < ESPEASY_NOW_PEER_MAX; ++peer) { - String peer_mac = webArg(concat(F("peer"), peer)); - if (peer_mac.length() == 0) { - peer_mac = F("00:00:00:00:00:00"); - } - MAC_address mac; - if (mac.set(peer_mac.c_str())) { - mac.get(SecuritySettings.EspEasyNowPeerMAC[peer]); - } - /* - String log = F("MAC decoding "); - log += peer_mac; - log += F(" => "); - log += mac.toString(); - addLog(LOG_LEVEL_INFO, log); - */ - } - #endif - - Settings.deepSleepOnFail = isFormItemChecked(F("deepsleeponfail")); - webArg2ip(F("espip"), Settings.IP); - webArg2ip(F("espgateway"), Settings.Gateway); - webArg2ip(F("espsubnet"), Settings.Subnet); - webArg2ip(F("espdns"), Settings.DNS); -#if FEATURE_ETHERNET - webArg2ip(F("espethip"), Settings.ETH_IP); - webArg2ip(F("espethgateway"), Settings.ETH_Gateway); - webArg2ip(F("espethsubnet"), Settings.ETH_Subnet); - webArg2ip(F("espethdns"), Settings.ETH_DNS); -#endif // if FEATURE_ETHERNET - #if FEATURE_ALTERNATIVE_CDN_URL - set_CDN_url_custom(webArg(F("alturl"))); - #endif // if FEATURE_ALTERNATIVE_CDN_URL - addHtmlError(SaveSettings()); - } - - html_add_form(); - html_table_class_normal(); - - addFormHeader(F("Main Settings")); - - Settings.Name[25] = 0; - SecuritySettings.Password[25] = 0; - addFormTextBox(F("Unit Name"), F("name"), Settings.Name, 25); - addFormNote(concat(F("Hostname: "), NetworkCreateRFCCompliantHostname())); - addFormNumericBox(F("Unit Number"), F("unit"), Settings.Unit, 0, UNIT_NUMBER_MAX); - addFormCheckBox(F("Append Unit Number to hostname"), F("appendunittohostname"), Settings.appendUnitToHostname()); - addFormPasswordBox(F("Admin Password"), F("password"), SecuritySettings.Password, 25); - - addFormSubHeader(F("Wifi Settings")); - - addFormTextBox(getLabel(LabelType::SSID), F("ssid"), SecuritySettings.WifiSSID, 31); - addFormPasswordBox(F("WPA Key"), F("key"), SecuritySettings.WifiKey, 63); - addFormTextBox(F("Fallback SSID"), F("ssid2"), SecuritySettings.WifiSSID2, 31); - addFormPasswordBox(F("Fallback WPA Key"), F("key2"), SecuritySettings.WifiKey2, 63); - addFormNote(F("WPA Key must be at least 8 characters long")); - - addFormCheckBox(LabelType::CONNECT_HIDDEN_SSID, Settings.IncludeHiddenSSID()); - addFormNote(F("Must be checked to connect to a hidden SSID")); - - addFormCheckBox(LabelType::HIDDEN_SSID_SLOW_CONNECT, Settings.HiddenSSID_SlowConnectPerBSSID()); - addFormNote(F("Required for some AP brands like Mikrotik to connect to hidden SSID")); - - addFormSeparator(2); - addFormPasswordBox(F("WPA AP Mode Key"), F("apkey"), SecuritySettings.WifiAPKey, 63); - addFormNote(F("WPA Key must be at least 8 characters long")); - - addFormCheckBox(F("Don't force /setup in AP-Mode"), F("ApDontForceSetup"), Settings.ApDontForceSetup()); - addFormNote(F("When set you can use the Sensor in AP-Mode without being forced to /setup. /setup can still be called.")); - - addFormCheckBox(F("Do Not Start AP"), F("DoNotStartAP"), Settings.DoNotStartAP()); - #if FEATURE_ETHERNET - addFormNote(F("Do not allow to start an AP when unable to connect to configured LAN/WiFi")); - #else // if FEATURE_ETHERNET - addFormNote(F("Do not allow to start an AP when configured WiFi cannot be found")); - #endif // if FEATURE_ETHERNET - - - // TD-er add IP access box F("ipblocklevel") - addFormSubHeader(F("Client IP filtering")); - { - IPAddress low, high; - getIPallowedRange(low, high); - uint8_t iplow[4]; - uint8_t iphigh[4]; - - for (uint8_t i = 0; i < 4; ++i) { - iplow[i] = low[i]; - iphigh[i] = high[i]; - } - addFormIPaccessControlSelect(F("Client IP block level"), F("ipblocklevel"), SecuritySettings.IPblockLevel); - addFormIPBox(F("Access IP lower range"), F("iprangelow"), iplow); - addFormIPBox(F("Access IP upper range"), F("iprangehigh"), iphigh); - } - - addFormSubHeader(F("WiFi IP Settings")); - - addFormIPBox(F("ESP WiFi IP"), F("espip"), Settings.IP); - addFormIPBox(F("ESP WiFi Gateway"), F("espgateway"), Settings.Gateway); - addFormIPBox(F("ESP WiFi Subnetmask"), F("espsubnet"), Settings.Subnet); - addFormIPBox(F("ESP WiFi DNS"), F("espdns"), Settings.DNS); - addFormNote(F("Leave empty for DHCP")); - -#if FEATURE_ETHERNET - addFormSubHeader(F("Ethernet IP Settings")); - - addFormIPBox(F("ESP Ethernet IP"), F("espethip"), Settings.ETH_IP); - addFormIPBox(F("ESP Ethernet Gateway"), F("espethgateway"), Settings.ETH_Gateway); - addFormIPBox(F("ESP Ethernet Subnetmask"), F("espethsubnet"), Settings.ETH_Subnet); - addFormIPBox(F("ESP Ethernet DNS"), F("espethdns"), Settings.ETH_DNS); - addFormNote(F("Leave empty for DHCP")); -#endif // if FEATURE_ETHERNET - -#ifdef USES_ESPEASY_NOW - addFormSubHeader(F("ESPEasy-NOW")); - for (int peer = 0; peer < ESPEASY_NOW_PEER_MAX; ++peer) { - addFormMACBox(concat(F("Peer "), peer + 1), - concat(F("peer"), peer), - SecuritySettings.EspEasyNowPeerMAC[peer]); - - bool match_STA; - const NodeStruct* nodeInfo = Nodes.getNodeByMac(SecuritySettings.EspEasyNowPeerMAC[peer], match_STA); - if (nodeInfo != nullptr) - { - String summary = nodeInfo->getSummary(); - summary += match_STA ? F(" (STA)") : F(" (AP)"); - addFormNote(summary); - } - - } -#endif - - addFormSubHeader(F("Sleep Mode")); - - addFormNumericBox(F("Sleep awake time"), F("awaketime"), Settings.deepSleep_wakeTime, 0, 255); - addUnit(F("sec")); - addHelpButton(F("SleepMode")); - addFormNote(F("0 = Sleep Disabled, else time awake from sleep")); - - int dsmax = getDeepSleepMax(); - addFormNumericBox(F("Sleep time"), F("delay"), Settings.Delay, 0, dsmax); // limited by hardware - { - addUnit(concat(F("sec (max: "), dsmax) + ')'); - } - - addFormCheckBox(F("Sleep on connection failure"), F("deepsleeponfail"), Settings.deepSleepOnFail); - - addFormSeparator(2); - - #if FEATURE_ALTERNATIVE_CDN_URL - addFormSubHeader(F("CDN (Content delivery network)")); - - addFormTextBox(F("Custom CDN URL"), F("alturl"), get_CDN_url_custom(), 255); - addFormNote(concat(F("Leave empty for default CDN url: "), get_CDN_url_prefix())); - - addFormSeparator(2); - #endif // if FEATURE_ALTERNATIVE_CDN_URL - - html_TR_TD(); - html_TD(); - addSubmitButton(); - html_end_table(); - html_end_form(); - - sendHeadandTail_stdtemplate(_TAIL); - TXBuffer.endStream(); -} - +#include "../WebServer/ConfigPage.h" + +#ifdef WEBSERVER_CONFIG + +#include "../WebServer/HTML_wrappers.h" +#include "../WebServer/AccessControl.h" +#include "../WebServer/Markup.h" +#include "../WebServer/Markup_Buttons.h" +#include "../WebServer/Markup_Forms.h" +#include "../WebServer/ESPEasy_WebServer.h" + +#ifdef USES_ESPEASY_NOW +#include "../DataStructs/MAC_address.h" +#include "../DataStructs/NodeStruct.h" +#endif + +#include "../ESPEasyCore/Controller.h" +#include "../ESPEasyCore/ESPEasyNetwork.h" + +#include "../Globals/MQTT.h" +#include "../Globals/Nodes.h" +#include "../Globals/SecuritySettings.h" +#include "../Globals/Settings.h" + +#include "../Helpers/DeepSleep.h" +#include "../Helpers/ESPEasy_Storage.h" +#include "../Helpers/Networking.h" +#include "../Helpers/StringConverter.h" + + +// ******************************************************************************** +// Web Interface config page +// ******************************************************************************** +void handle_config() { + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("handle_config")); + #endif + + if (!isLoggedIn()) { return; } + + navMenuIndex = MENU_INDEX_CONFIG; + TXBuffer.startStream(); + sendHeadandTail_stdtemplate(_HEAD); + + if (web_server.args() != 0) + { + String name = webArg(F("name")); + name.trim(); + + Settings.Delay = getFormItemInt(F("delay"), Settings.Delay); + Settings.deepSleep_wakeTime = getFormItemInt(F("awaketime"), Settings.deepSleep_wakeTime); + Settings.Unit = getFormItemInt(F("unit"), Settings.Unit); + + if (strcmp(Settings.Name, name.c_str()) != 0) { + addLog(LOG_LEVEL_INFO, F("Unit Name changed.")); + + if (CPluginCall(CPlugin::Function::CPLUGIN_GOT_INVALID, 0)) { // inform controllers that the old name will be invalid from now on. +#if FEATURE_MQTT + MQTTDisconnect(); // disconnect form MQTT Server if invalid message was sent succesfull. +#endif // if FEATURE_MQTT + } +#if FEATURE_MQTT + MQTTclient_should_reconnect = true; +#endif // if FEATURE_MQTT + } + + // Unit name + safe_strncpy(Settings.Name, name.c_str(), sizeof(Settings.Name)); + Settings.appendUnitToHostname(isFormItemChecked(F("appendunittohostname"))); + + // Password + copyFormPassword(F("password"), SecuritySettings.Password, sizeof(SecuritySettings.Password)); + + // SSID 1 + safe_strncpy(SecuritySettings.WifiSSID, webArg(F("ssid")).c_str(), sizeof(SecuritySettings.WifiSSID)); + copyFormPassword(F("key"), SecuritySettings.WifiKey, sizeof(SecuritySettings.WifiKey)); + + // SSID 2 + strncpy_webserver_arg(SecuritySettings.WifiSSID2, F("ssid2")); + copyFormPassword(F("key2"), SecuritySettings.WifiKey2, sizeof(SecuritySettings.WifiKey2)); + + // Hidden SSID + Settings.IncludeHiddenSSID(isFormItemChecked(LabelType::CONNECT_HIDDEN_SSID)); + Settings.HiddenSSID_SlowConnectPerBSSID(isFormItemChecked(LabelType::HIDDEN_SSID_SLOW_CONNECT)); + +#ifdef ESP32 + Settings.PassiveWiFiScan(isFormItemChecked(LabelType::WIFI_PASSIVE_SCAN)); +#endif + + // Access point password. + copyFormPassword(F("apkey"), SecuritySettings.WifiAPKey, sizeof(SecuritySettings.WifiAPKey)); + + // When set you can use the Sensor in AP-Mode without being forced to /setup + Settings.ApDontForceSetup(isFormItemChecked(F("ApDontForceSetup"))); + + // Usually the AP will be started when no WiFi is defined, or the defined one cannot be found. This flag may prevent it. + Settings.DoNotStartAP(isFormItemChecked(F("DoNotStartAP"))); + + + // TD-er Read access control from form. + SecuritySettings.IPblockLevel = getFormItemInt(F("ipblocklevel")); + + switch (SecuritySettings.IPblockLevel) { + case LOCAL_SUBNET_ALLOWED: + { + IPAddress low, high; + getSubnetRange(low, high); + + for (uint8_t i = 0; i < 4; ++i) { + SecuritySettings.AllowedIPrangeLow[i] = low[i]; + SecuritySettings.AllowedIPrangeHigh[i] = high[i]; + } + break; + } + case ONLY_IP_RANGE_ALLOWED: + case ALL_ALLOWED: + + webArg2ip(F("iprangelow"), SecuritySettings.AllowedIPrangeLow); + webArg2ip(F("iprangehigh"), SecuritySettings.AllowedIPrangeHigh); + break; + } + + #ifdef USES_ESPEASY_NOW + for (int peer = 0; peer < ESPEASY_NOW_PEER_MAX; ++peer) { + String peer_mac = webArg(concat(F("peer"), peer)); + if (peer_mac.length() == 0) { + peer_mac = F("00:00:00:00:00:00"); + } + MAC_address mac; + if (mac.set(peer_mac.c_str())) { + mac.get(SecuritySettings.EspEasyNowPeerMAC[peer]); + } + /* + String log = F("MAC decoding "); + log += peer_mac; + log += F(" => "); + log += mac.toString(); + addLog(LOG_LEVEL_INFO, log); + */ + } + #endif + + Settings.deepSleepOnFail = isFormItemChecked(F("deepsleeponfail")); + webArg2ip(F("espip"), Settings.IP); + webArg2ip(F("espgateway"), Settings.Gateway); + webArg2ip(F("espsubnet"), Settings.Subnet); + webArg2ip(F("espdns"), Settings.DNS); +#if FEATURE_ETHERNET + webArg2ip(F("espethip"), Settings.ETH_IP); + webArg2ip(F("espethgateway"), Settings.ETH_Gateway); + webArg2ip(F("espethsubnet"), Settings.ETH_Subnet); + webArg2ip(F("espethdns"), Settings.ETH_DNS); +#endif // if FEATURE_ETHERNET + #if FEATURE_ALTERNATIVE_CDN_URL + set_CDN_url_custom(webArg(F("alturl"))); + #endif // if FEATURE_ALTERNATIVE_CDN_URL + addHtmlError(SaveSettings()); + } + + html_add_form(); + html_table_class_normal(); + + addFormHeader(F("Main Settings")); + + Settings.Name[25] = 0; + SecuritySettings.Password[25] = 0; + addFormTextBox(F("Unit Name"), F("name"), Settings.Name, 25); + addFormNote(concat(F("Hostname: "), NetworkCreateRFCCompliantHostname())); + addFormNumericBox(F("Unit Number"), F("unit"), Settings.Unit, 0, UNIT_NUMBER_MAX); + addFormCheckBox(F("Append Unit Number to hostname"), F("appendunittohostname"), Settings.appendUnitToHostname()); + addFormPasswordBox(F("Admin Password"), F("password"), SecuritySettings.Password, 25); + + addFormSubHeader(F("Wifi Settings")); + + addFormTextBox(getLabel(LabelType::SSID), F("ssid"), SecuritySettings.WifiSSID, 31); + addFormPasswordBox(F("WPA Key"), F("key"), SecuritySettings.WifiKey, 63); + addFormTextBox(F("Fallback SSID"), F("ssid2"), SecuritySettings.WifiSSID2, 31); + addFormPasswordBox(F("Fallback WPA Key"), F("key2"), SecuritySettings.WifiKey2, 63); + addFormNote(F("WPA Key must be at least 8 characters long")); + + addFormCheckBox(LabelType::CONNECT_HIDDEN_SSID, Settings.IncludeHiddenSSID()); + +#ifdef ESP32 + addFormCheckBox(LabelType::WIFI_PASSIVE_SCAN, Settings.PassiveWiFiScan()); +#endif + + addFormCheckBox(LabelType::HIDDEN_SSID_SLOW_CONNECT, Settings.HiddenSSID_SlowConnectPerBSSID()); + + addFormSeparator(2); + addFormPasswordBox(F("WPA AP Mode Key"), F("apkey"), SecuritySettings.WifiAPKey, 63); + addFormNote(F("WPA Key must be at least 8 characters long")); + + addFormCheckBox(F("Don't force /setup in AP-Mode"), F("ApDontForceSetup"), Settings.ApDontForceSetup()); + addFormNote(F("When set you can use the Sensor in AP-Mode without being forced to /setup. /setup can still be called.")); + + addFormCheckBox(F("Do Not Start AP"), F("DoNotStartAP"), Settings.DoNotStartAP()); + #if FEATURE_ETHERNET + addFormNote(F("Do not allow to start an AP when unable to connect to configured LAN/WiFi")); + #else // if FEATURE_ETHERNET + addFormNote(F("Do not allow to start an AP when configured WiFi cannot be found")); + #endif // if FEATURE_ETHERNET + + + // TD-er add IP access box F("ipblocklevel") + addFormSubHeader(F("Client IP filtering")); + { + IPAddress low, high; + getIPallowedRange(low, high); + uint8_t iplow[4]; + uint8_t iphigh[4]; + + for (uint8_t i = 0; i < 4; ++i) { + iplow[i] = low[i]; + iphigh[i] = high[i]; + } + addFormIPaccessControlSelect(F("Client IP block level"), F("ipblocklevel"), SecuritySettings.IPblockLevel); + addFormIPBox(F("Access IP lower range"), F("iprangelow"), iplow); + addFormIPBox(F("Access IP upper range"), F("iprangehigh"), iphigh); + } + + addFormSubHeader(F("WiFi IP Settings")); + + addFormIPBox(F("ESP WiFi IP"), F("espip"), Settings.IP); + addFormIPBox(F("ESP WiFi Gateway"), F("espgateway"), Settings.Gateway); + addFormIPBox(F("ESP WiFi Subnetmask"), F("espsubnet"), Settings.Subnet); + addFormIPBox(F("ESP WiFi DNS"), F("espdns"), Settings.DNS); + addFormNote(F("Leave empty for DHCP")); + +#if FEATURE_ETHERNET + addFormSubHeader(F("Ethernet IP Settings")); + + addFormIPBox(F("ESP Ethernet IP"), F("espethip"), Settings.ETH_IP); + addFormIPBox(F("ESP Ethernet Gateway"), F("espethgateway"), Settings.ETH_Gateway); + addFormIPBox(F("ESP Ethernet Subnetmask"), F("espethsubnet"), Settings.ETH_Subnet); + addFormIPBox(F("ESP Ethernet DNS"), F("espethdns"), Settings.ETH_DNS); + addFormNote(F("Leave empty for DHCP")); +#endif // if FEATURE_ETHERNET + +#ifdef USES_ESPEASY_NOW + addFormSubHeader(F("ESPEasy-NOW")); + for (int peer = 0; peer < ESPEASY_NOW_PEER_MAX; ++peer) { + addFormMACBox(concat(F("Peer "), peer + 1), + concat(F("peer"), peer), + SecuritySettings.EspEasyNowPeerMAC[peer]); + + bool match_STA; + const NodeStruct* nodeInfo = Nodes.getNodeByMac(SecuritySettings.EspEasyNowPeerMAC[peer], match_STA); + if (nodeInfo != nullptr) + { + String summary = nodeInfo->getSummary(); + summary += match_STA ? F(" (STA)") : F(" (AP)"); + addFormNote(summary); + } + + } +#endif + + addFormSubHeader(F("Sleep Mode")); + + addFormNumericBox(F("Sleep awake time"), F("awaketime"), Settings.deepSleep_wakeTime, 0, 255); + addUnit(F("sec")); + addHelpButton(F("SleepMode")); + addFormNote(F("0 = Sleep Disabled, else time awake from sleep")); + + int dsmax = getDeepSleepMax(); + addFormNumericBox(F("Sleep time"), F("delay"), Settings.Delay, 0, dsmax); // limited by hardware + { + addUnit(concat(F("sec (max: "), dsmax) + ')'); + } + + addFormCheckBox(F("Sleep on connection failure"), F("deepsleeponfail"), Settings.deepSleepOnFail); + + addFormSeparator(2); + + #if FEATURE_ALTERNATIVE_CDN_URL + addFormSubHeader(F("CDN (Content delivery network)")); + + addFormTextBox(F("Custom CDN URL"), F("alturl"), get_CDN_url_custom(), 255); + addFormNote(concat(F("Leave empty for default CDN url: "), get_CDN_url_prefix())); + + addFormSeparator(2); + #endif // if FEATURE_ALTERNATIVE_CDN_URL + + html_TR_TD(); + html_TD(); + addSubmitButton(); + html_end_table(); + html_end_form(); + + sendHeadandTail_stdtemplate(_TAIL); + TXBuffer.endStream(); +} + #endif // ifdef WEBSERVER_CONFIG \ No newline at end of file diff --git a/src/src/WebServer/ControllerPage.cpp b/src/src/WebServer/ControllerPage.cpp index 0f2fef5e56..1b8327d234 100644 --- a/src/src/WebServer/ControllerPage.cpp +++ b/src/src/WebServer/ControllerPage.cpp @@ -1,462 +1,465 @@ -#include "../WebServer/ControllerPage.h" - - -#ifdef WEBSERVER_CONTROLLERS - -# include "../WebServer/ESPEasy_WebServer.h" -# include "../WebServer/HTML_wrappers.h" -# include "../WebServer/Markup.h" -# include "../WebServer/Markup_Buttons.h" -# include "../WebServer/Markup_Forms.h" - -# include "../DataStructs/ESPEasy_EventStruct.h" - -# include "../ESPEasyCore/Controller.h" - -# include "../Globals/CPlugins.h" -# include "../Globals/Settings.h" - -# include "../Helpers/_CPlugin_init.h" -# include "../Helpers/_CPlugin_Helper_webform.h" -# include "../Helpers/_Plugin_SensorTypeHelper.h" -# include "../Helpers/ESPEasy_Storage.h" -# include "../Helpers/StringConverter.h" - - -// ******************************************************************************** -// Web Interface controller page -// ******************************************************************************** -void handle_controllers() { - # ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("handle_controllers")); - # endif // ifndef BUILD_NO_RAM_TRACKER - - if (!isLoggedIn()) { return; } - navMenuIndex = MENU_INDEX_CONTROLLERS; - TXBuffer.startStream(); - sendHeadandTail_stdtemplate(_HEAD); - - // 'index' value in the URL - uint8_t controllerindex = getFormItemInt(F("index"), 0); - boolean controllerNotSet = controllerindex == 0; - --controllerindex; // Index in URL is starting from 1, but starting from 0 in the array. - - const int protocol_webarg_value = getFormItemInt(F("protocol"), -1); - - // submitted data - if ((protocol_webarg_value != -1) && !controllerNotSet) - { - const protocolIndex_t protocolIndex = protocol_webarg_value; - bool mustInit = false; - bool mustCallCpluginSave = false; - { - // Place in a scope to free ControllerSettings memory ASAP - MakeControllerSettings(ControllerSettings); //-V522 - - if (!AllocatedControllerSettings()) { - addHtmlError(F("Not enough free memory to save settings")); - } else { - // Need to make sure every byte between the members is also zero - // Otherwise the checksum will fail and settings will be saved too often. - ControllerSettings->reset(); - - if (Settings.Protocol[controllerindex] != protocolIndex) - { - // Protocol has changed. - Settings.Protocol[controllerindex] = protocolIndex; - - // there is a protocolIndex selected? - if (protocolIndex != 0) - { - mustInit = true; - handle_controllers_clearLoadDefaults(controllerindex, *ControllerSettings); - } - } - - // subitted same protocolIndex - else - { - // there is a protocolIndex selected - if (protocolIndex != 0) - { - mustInit = true; - handle_controllers_CopySubmittedSettings(controllerindex, *ControllerSettings); - mustCallCpluginSave = true; - } - } - addHtmlError(SaveControllerSettings(controllerindex, *ControllerSettings)); - } - } - - if (mustCallCpluginSave) { - // Call CPLUGIN_WEBFORM_SAVE after destructing ControllerSettings object to reduce RAM usage. - // Controller plugin almost only deals with custom controller settings. - // Even if they need to save things to the ControllerSettings, then the changes must - // already be saved first as the CPluginCall does not have the ControllerSettings as argument. - handle_controllers_CopySubmittedSettings_CPluginCall(controllerindex); - } - addHtmlError(SaveSettings()); - - if (mustInit) { - // Init controller plugin using the new settings. - protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(controllerindex); - - if (validProtocolIndex(ProtocolIndex)) { - struct EventStruct TempEvent; - TempEvent.ControllerIndex = controllerindex; - String dummy; - CPlugin::Function cfunction = - Settings.ControllerEnabled[controllerindex] ? CPlugin::Function::CPLUGIN_INIT : CPlugin::Function::CPLUGIN_EXIT; - CPluginCall(ProtocolIndex, cfunction, &TempEvent, dummy); - } - } - } - - html_add_form(); - - if (controllerNotSet) - { - handle_controllers_ShowAllControllersTable(); - } - else - { - handle_controllers_ControllerSettingsPage(controllerindex); - } - - sendHeadandTail_stdtemplate(_TAIL); - TXBuffer.endStream(); -} - -// ******************************************************************************** -// Selected controller has changed. -// Clear all Controller settings and load some defaults -// ******************************************************************************** -void handle_controllers_clearLoadDefaults(uint8_t controllerindex, ControllerSettingsStruct& ControllerSettings) -{ - // Protocol has changed and it was not an empty one. - // reset (some) default-settings - protocolIndex_t ProtocolIndex = getProtocolIndex_from_CPluginID(Settings.Protocol[controllerindex]); - - if (!validProtocolIndex(ProtocolIndex)) { - return; - } - - const ProtocolStruct& proto = getProtocolStruct(ProtocolIndex); - - ControllerSettings.reset(); - ControllerSettings.Port = proto.defaultPort; - - // Load some templates from the controller. - struct EventStruct TempEvent; - - // Hand over the controller settings in the Data pointer, so the controller can set some defaults. - TempEvent.Data = (uint8_t *)(&ControllerSettings); - - if (proto.usesTemplate) { - String dummy; - CPluginCall(ProtocolIndex, CPlugin::Function::CPLUGIN_PROTOCOL_TEMPLATE, &TempEvent, dummy); - } - safe_strncpy(ControllerSettings.Subscribe, TempEvent.String1.c_str(), sizeof(ControllerSettings.Subscribe)); - safe_strncpy(ControllerSettings.Publish, TempEvent.String2.c_str(), sizeof(ControllerSettings.Publish)); - safe_strncpy(ControllerSettings.MQTTLwtTopic, TempEvent.String3.c_str(), sizeof(ControllerSettings.MQTTLwtTopic)); - safe_strncpy(ControllerSettings.LWTMessageConnect, TempEvent.String4.c_str(), sizeof(ControllerSettings.LWTMessageConnect)); - safe_strncpy(ControllerSettings.LWTMessageDisconnect, TempEvent.String5.c_str(), sizeof(ControllerSettings.LWTMessageDisconnect)); - - // NOTE: do not enable controller by default, give user a change to enter sensible values first - Settings.ControllerEnabled[controllerindex] = false; - - // not resetted to default (for convenience) - // SecuritySettings.ControllerUser[controllerindex] - // SecuritySettings.ControllerPassword[controllerindex] - - ClearCustomControllerSettings(controllerindex); -} - -// ******************************************************************************** -// Collect all submitted form data and store in the ControllerSettings -// ******************************************************************************** -void handle_controllers_CopySubmittedSettings(uint8_t controllerindex, ControllerSettingsStruct& ControllerSettings) -{ - // copy all settings to controller settings struct - for (int parameterIdx = 0; parameterIdx <= ControllerSettingsStruct::CONTROLLER_ENABLED; ++parameterIdx) { - ControllerSettingsStruct::VarType varType = static_cast(parameterIdx); - saveControllerParameterForm(ControllerSettings, controllerindex, varType); - } -} - -void handle_controllers_CopySubmittedSettings_CPluginCall(uint8_t controllerindex) { - protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(controllerindex); - - if (validProtocolIndex(ProtocolIndex)) { - struct EventStruct TempEvent; - TempEvent.ControllerIndex = controllerindex; - - // Call controller plugin to save CustomControllerSettings - String dummy; - CPluginCall(ProtocolIndex, CPlugin::Function::CPLUGIN_WEBFORM_SAVE, &TempEvent, dummy); - } -} - -// ******************************************************************************** -// Show table with all selected controllers -// ******************************************************************************** -void handle_controllers_ShowAllControllersTable() -{ - html_table_class_multirow(); - html_TR(); - html_table_header(F(""), 70); - html_table_header(F("Nr"), 50); - html_table_header(F("Enabled"), 100); - html_table_header(F("Protocol")); - html_table_header(F("Host")); - html_table_header(F("Port")); - - MakeControllerSettings(ControllerSettings); //-V522 - - if (AllocatedControllerSettings()) { - for (controllerIndex_t x = 0; x < CONTROLLER_MAX; x++) - { - const bool cplugin_set = Settings.Protocol[x] != INVALID_C_PLUGIN_ID; - - - LoadControllerSettings(x, *ControllerSettings); - html_TR_TD(); - - if (cplugin_set && !supportedCPluginID(Settings.Protocol[x])) { - html_add_button_prefix(F("red"), true); - } else { - html_add_button_prefix(); - } - { - addHtml(F("controllers?index=")); - addHtmlInt(x + 1); - addHtml(F("'>")); - - if (cplugin_set) { - addHtml(F("Edit")); - } else { - addHtml(F("Add")); - } - addHtml(F("")); - addHtml(getControllerSymbol(x)); - } - html_TD(); - - if (cplugin_set) - { - addEnabled(Settings.ControllerEnabled[x]); - - html_TD(); - addHtml(getCPluginNameFromCPluginID(Settings.Protocol[x])); - html_TD(); - const protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(x); - { - String hostDescription; - CPluginCall(ProtocolIndex, CPlugin::Function::CPLUGIN_WEBFORM_SHOW_HOST_CONFIG, 0, hostDescription); - - if (!hostDescription.isEmpty()) { - addHtml(hostDescription); - } else { - addHtml(ControllerSettings->getHost()); - } - } - - html_TD(); - const ProtocolStruct& proto = getProtocolStruct(ProtocolIndex); - if ((INVALID_PROTOCOL_INDEX == ProtocolIndex) || proto.usesPort) { - addHtmlInt(13 == Settings.Protocol[x] ? Settings.UDPPort : ControllerSettings->Port); // P2P/C013 exception - } - } - else { - html_TD(3); - } - } - } - html_end_table(); - html_end_form(); -} - -// ******************************************************************************** -// Show the controller settings page -// ******************************************************************************** -void handle_controllers_ControllerSettingsPage(controllerIndex_t controllerindex) -{ - if (!validControllerIndex(controllerindex)) { - return; - } - - // Show controller settings page - html_table_class_normal(); - addFormHeader(F("Controller Settings")); - addRowLabel(F("Protocol")); - uint8_t choice = Settings.Protocol[controllerindex]; - - addSelector_Head_reloadOnChange(F("protocol")); - addSelector_Item(F("- Standalone -"), 0, false, false, EMPTY_STRING); - - protocolIndex_t protocolIndex = 0; - while (validProtocolIndex(protocolIndex)) - { - const cpluginID_t number = getCPluginID_from_ProtocolIndex(protocolIndex); - boolean disabled = false; // !((controllerindex == 0) || !Protocol[x].usesMQTT); - addSelector_Item(getCPluginNameFromProtocolIndex(protocolIndex), - number, - choice == number, - disabled); - ++protocolIndex; - } - addSelector_Foot(); - - addHelpButton(F("EasyProtocols")); - - const protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(controllerindex); - const ProtocolStruct& proto = getProtocolStruct(ProtocolIndex); - - # ifndef LIMIT_BUILD_SIZE - addRTDControllerButton(getCPluginID_from_ProtocolIndex(ProtocolIndex)); - # endif // ifndef LIMIT_BUILD_SIZE - - if (Settings.Protocol[controllerindex]) - { - { - MakeControllerSettings(ControllerSettings); //-V522 - - if (!AllocatedControllerSettings()) { - addHtmlError(F("Out of memory, cannot load page")); - } else { - LoadControllerSettings(controllerindex, *ControllerSettings); - - if (!proto.Custom) - { - if (proto.usesHost) { - addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_USE_DNS); - - if (ControllerSettings->UseDNS) - { - addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_HOSTNAME); - } - else - { - addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_IP); - } - } - if (proto.usesPort) { - addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_PORT); - } - # ifdef USES_ESPEASY_NOW - - if (proto.usesMQTT) { - // FIXME TD-er: Currently only enabled for MQTT protocols, later for more - addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_ENABLE_ESPEASY_NOW_FALLBACK); - } - # endif // ifdef USES_ESPEASY_NOW - - if (proto.usesQueue) { - addTableSeparator(F("Controller Queue"), 2, 3); - addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_MIN_SEND_INTERVAL); - addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_MAX_QUEUE_DEPTH); - addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_MAX_RETRIES); - addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_FULL_QUEUE_ACTION); - - if (proto.allowsExpire) { - addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_ALLOW_EXPIRE); - } - addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_DEDUPLICATE); - } - - if (proto.usesCheckReply) { - addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_CHECK_REPLY); - } - - if (proto.usesTimeout) { - addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_TIMEOUT); - } - - if (proto.usesSampleSets) { - addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_SAMPLE_SET_INITIATOR); - } - if (proto.allowLocalSystemTime) { - addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_USE_LOCAL_SYSTEM_TIME); - } - - - if (proto.useCredentials()) { - addTableSeparator(F("Credentials"), 2, 3); - } - - if (proto.useExtendedCredentials()) { - addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_USE_EXTENDED_CREDENTIALS); - } - - if (proto.usesAccount) - { - addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_USER); - } - - if (proto.usesPassword) - { - addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_PASS); - } - #if FEATURE_MQTT - if (proto.usesMQTT) { - addTableSeparator(F("MQTT"), 2, 3); - - addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_CLIENT_ID); - addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_UNIQUE_CLIENT_ID_RECONNECT); - addRowLabel(F("Current Client ID")); - addHtml(getMQTTclientID(*ControllerSettings)); - addFormNote(F("Updated on load of this page")); - addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_RETAINFLAG); - } - # endif // if FEATURE_MQTT - - - if (proto.usesTemplate || proto.usesMQTT) - { - addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_SUBSCRIBE); - addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_PUBLISH); - } - #if FEATURE_MQTT - if (proto.usesMQTT) - { - addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_LWT_TOPIC); - addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_LWT_CONNECT_MESSAGE); - addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_LWT_DISCONNECT_MESSAGE); - addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_SEND_LWT); - addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_WILL_RETAIN); - addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_CLEAN_SESSION); - } - # endif // if FEATURE_MQTT - } - } - - // End of scope for ControllerSettings, destruct it to save memory. - } - { - // Load controller specific settings - struct EventStruct TempEvent; - TempEvent.ControllerIndex = controllerindex; - - String webformLoadString; - CPluginCall(ProtocolIndex, CPlugin::Function::CPLUGIN_WEBFORM_LOAD, &TempEvent, webformLoadString); - - if (webformLoadString.length() > 0) { - addHtmlError(F("Bug in CPlugin::Function::CPLUGIN_WEBFORM_LOAD, should not append to string, use addHtml() instead")); - } - } - - // Separate enabled checkbox as it doesn't need to use the ControllerSettings. - // So ControllerSettings object can be destructed before controller specific settings are loaded. - addControllerEnabledForm(controllerindex); - } - - addFormSeparator(2); - html_TR_TD(); - html_TD(); - addButton(F("controllers"), F("Close")); - addSubmitButton(); - html_end_table(); - html_end_form(); -} - -#endif // ifdef WEBSERVER_CONTROLLERS +#include "../WebServer/ControllerPage.h" + + +#ifdef WEBSERVER_CONTROLLERS + +# include "../WebServer/ESPEasy_WebServer.h" +# include "../WebServer/HTML_wrappers.h" +# include "../WebServer/Markup.h" +# include "../WebServer/Markup_Buttons.h" +# include "../WebServer/Markup_Forms.h" + +# include "../DataStructs/ESPEasy_EventStruct.h" + +# include "../ESPEasyCore/Controller.h" + +# include "../Globals/CPlugins.h" +# include "../Globals/Settings.h" + +# include "../Helpers/_CPlugin_init.h" +# include "../Helpers/_CPlugin_Helper_webform.h" +# include "../Helpers/_Plugin_SensorTypeHelper.h" +# include "../Helpers/ESPEasy_Storage.h" +# include "../Helpers/StringConverter.h" + + +// ******************************************************************************** +// Web Interface controller page +// ******************************************************************************** +void handle_controllers() { + # ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("handle_controllers")); + # endif // ifndef BUILD_NO_RAM_TRACKER + + if (!isLoggedIn()) { return; } + navMenuIndex = MENU_INDEX_CONTROLLERS; + TXBuffer.startStream(); + sendHeadandTail_stdtemplate(_HEAD); + + // 'index' value in the URL + uint8_t controllerindex = getFormItemInt(F("index"), 0); + boolean controllerNotSet = controllerindex == 0; + --controllerindex; // Index in URL is starting from 1, but starting from 0 in the array. + + const int protocol_webarg_value = getFormItemInt(F("protocol"), -1); + + // submitted data + if ((protocol_webarg_value != -1) && !controllerNotSet) + { + const protocolIndex_t protocolIndex = protocol_webarg_value; + bool mustInit = false; + bool mustCallCpluginSave = false; + { + // Place in a scope to free ControllerSettings memory ASAP + MakeControllerSettings(ControllerSettings); //-V522 + + if (!AllocatedControllerSettings()) { + addHtmlError(F("Not enough free memory to save settings")); + } else { + // Need to make sure every byte between the members is also zero + // Otherwise the checksum will fail and settings will be saved too often. + ControllerSettings->reset(); + + if (Settings.Protocol[controllerindex] != protocolIndex) + { + // Protocol has changed. + Settings.Protocol[controllerindex] = protocolIndex; + + // there is a protocolIndex selected? + if (protocolIndex != 0) + { + mustInit = true; + handle_controllers_clearLoadDefaults(controllerindex, *ControllerSettings); + } + } + + // subitted same protocolIndex + else + { + // there is a protocolIndex selected + if (protocolIndex != 0) + { + mustInit = true; + handle_controllers_CopySubmittedSettings(controllerindex, *ControllerSettings); + mustCallCpluginSave = true; + } + } + addHtmlError(SaveControllerSettings(controllerindex, *ControllerSettings)); + } + } + + if (mustCallCpluginSave) { + // Call CPLUGIN_WEBFORM_SAVE after destructing ControllerSettings object to reduce RAM usage. + // Controller plugin almost only deals with custom controller settings. + // Even if they need to save things to the ControllerSettings, then the changes must + // already be saved first as the CPluginCall does not have the ControllerSettings as argument. + handle_controllers_CopySubmittedSettings_CPluginCall(controllerindex); + } + addHtmlError(SaveSettings()); + + if (mustInit) { + // Init controller plugin using the new settings. + protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(controllerindex); + + if (validProtocolIndex(ProtocolIndex)) { + struct EventStruct TempEvent; + TempEvent.ControllerIndex = controllerindex; + String dummy; + CPlugin::Function cfunction = + Settings.ControllerEnabled[controllerindex] ? CPlugin::Function::CPLUGIN_INIT : CPlugin::Function::CPLUGIN_EXIT; + CPluginCall(ProtocolIndex, cfunction, &TempEvent, dummy); + } + } + } + + html_add_form(); + + if (controllerNotSet) + { + handle_controllers_ShowAllControllersTable(); + } + else + { + handle_controllers_ControllerSettingsPage(controllerindex); + } + + sendHeadandTail_stdtemplate(_TAIL); + TXBuffer.endStream(); +} + +// ******************************************************************************** +// Selected controller has changed. +// Clear all Controller settings and load some defaults +// ******************************************************************************** +void handle_controllers_clearLoadDefaults(uint8_t controllerindex, ControllerSettingsStruct& ControllerSettings) +{ + // Protocol has changed and it was not an empty one. + // reset (some) default-settings + protocolIndex_t ProtocolIndex = getProtocolIndex_from_CPluginID(Settings.Protocol[controllerindex]); + + if (!validProtocolIndex(ProtocolIndex)) { + return; + } + + const ProtocolStruct& proto = getProtocolStruct(ProtocolIndex); + + ControllerSettings.reset(); + ControllerSettings.Port = proto.defaultPort; + + // Load some templates from the controller. + struct EventStruct TempEvent; + + // Hand over the controller settings in the Data pointer, so the controller can set some defaults. + TempEvent.Data = (uint8_t *)(&ControllerSettings); + + if (proto.usesTemplate) { + String dummy; + CPluginCall(ProtocolIndex, CPlugin::Function::CPLUGIN_PROTOCOL_TEMPLATE, &TempEvent, dummy); + } + safe_strncpy(ControllerSettings.Subscribe, TempEvent.String1.c_str(), sizeof(ControllerSettings.Subscribe)); + safe_strncpy(ControllerSettings.Publish, TempEvent.String2.c_str(), sizeof(ControllerSettings.Publish)); + safe_strncpy(ControllerSettings.MQTTLwtTopic, TempEvent.String3.c_str(), sizeof(ControllerSettings.MQTTLwtTopic)); + safe_strncpy(ControllerSettings.LWTMessageConnect, TempEvent.String4.c_str(), sizeof(ControllerSettings.LWTMessageConnect)); + safe_strncpy(ControllerSettings.LWTMessageDisconnect, TempEvent.String5.c_str(), sizeof(ControllerSettings.LWTMessageDisconnect)); + + // NOTE: do not enable controller by default, give user a change to enter sensible values first + Settings.ControllerEnabled[controllerindex] = false; + + // not resetted to default (for convenience) + // SecuritySettings.ControllerUser[controllerindex] + // SecuritySettings.ControllerPassword[controllerindex] + + ClearCustomControllerSettings(controllerindex); +} + +// ******************************************************************************** +// Collect all submitted form data and store in the ControllerSettings +// ******************************************************************************** +void handle_controllers_CopySubmittedSettings(uint8_t controllerindex, ControllerSettingsStruct& ControllerSettings) +{ + // copy all settings to controller settings struct + for (int parameterIdx = 0; parameterIdx <= ControllerSettingsStruct::CONTROLLER_ENABLED; ++parameterIdx) { + ControllerSettingsStruct::VarType varType = static_cast(parameterIdx); + saveControllerParameterForm(ControllerSettings, controllerindex, varType); + } +} + +void handle_controllers_CopySubmittedSettings_CPluginCall(uint8_t controllerindex) { + protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(controllerindex); + + if (validProtocolIndex(ProtocolIndex)) { + struct EventStruct TempEvent; + TempEvent.ControllerIndex = controllerindex; + + // Call controller plugin to save CustomControllerSettings + String dummy; + CPluginCall(ProtocolIndex, CPlugin::Function::CPLUGIN_WEBFORM_SAVE, &TempEvent, dummy); + } +} + +// ******************************************************************************** +// Show table with all selected controllers +// ******************************************************************************** +void handle_controllers_ShowAllControllersTable() +{ + html_table_class_multirow(); + html_TR(); + html_table_header(F(""), 70); + html_table_header(F("Nr"), 50); + html_table_header(F("Enabled"), 100); + html_table_header(F("Protocol")); + html_table_header(F("Host")); + html_table_header(F("Port")); + + MakeControllerSettings(ControllerSettings); //-V522 + + if (AllocatedControllerSettings()) { + for (controllerIndex_t x = 0; x < CONTROLLER_MAX; x++) + { + const bool cplugin_set = Settings.Protocol[x] != INVALID_C_PLUGIN_ID; + + + LoadControllerSettings(x, *ControllerSettings); + html_TR_TD(); + + if (cplugin_set && !supportedCPluginID(Settings.Protocol[x])) { + html_add_button_prefix(F("red"), true); + } else { + html_add_button_prefix(); + } + { + addHtml(F("controllers?index=")); + addHtmlInt(x + 1); + addHtml(F("'>")); + + if (cplugin_set) { + addHtml(F("Edit")); + } else { + addHtml(F("Add")); + } + addHtml(F("")); + addHtml(getControllerSymbol(x)); + } + html_TD(); + + if (cplugin_set) + { + addEnabled(Settings.ControllerEnabled[x]); + + html_TD(); + addHtml(getCPluginNameFromCPluginID(Settings.Protocol[x])); + html_TD(); + const protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(x); + { + String hostDescription; + CPluginCall(ProtocolIndex, CPlugin::Function::CPLUGIN_WEBFORM_SHOW_HOST_CONFIG, 0, hostDescription); + + if (!hostDescription.isEmpty()) { + addHtml(hostDescription); + } else { + addHtml(ControllerSettings->getHost()); + } + } + + html_TD(); + const ProtocolStruct& proto = getProtocolStruct(ProtocolIndex); + if ((INVALID_PROTOCOL_INDEX == ProtocolIndex) || proto.usesPort) { + addHtmlInt(13 == Settings.Protocol[x] ? Settings.UDPPort : ControllerSettings->Port); // P2P/C013 exception + } + } + else { + html_TD(3); + } + } + } + html_end_table(); + html_end_form(); +} + +// ******************************************************************************** +// Show the controller settings page +// ******************************************************************************** +void handle_controllers_ControllerSettingsPage(controllerIndex_t controllerindex) +{ + if (!validControllerIndex(controllerindex)) { + return; + } + + // Show controller settings page + html_table_class_normal(); + addFormHeader(F("Controller Settings")); + addRowLabel(F("Protocol")); + uint8_t choice = Settings.Protocol[controllerindex]; + + addSelector_Head_reloadOnChange(F("protocol")); + addSelector_Item(F("- Standalone -"), 0, false, false, EMPTY_STRING); + + protocolIndex_t protocolIndex = 0; + while (validProtocolIndex(protocolIndex)) + { + const cpluginID_t number = getCPluginID_from_ProtocolIndex(protocolIndex); + boolean disabled = false; // !((controllerindex == 0) || !Protocol[x].usesMQTT); + addSelector_Item(getCPluginNameFromProtocolIndex(protocolIndex), + number, + choice == number, + disabled); + ++protocolIndex; + } + addSelector_Foot(); + + addHelpButton(F("EasyProtocols")); + + const protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(controllerindex); + const ProtocolStruct& proto = getProtocolStruct(ProtocolIndex); + + # ifndef LIMIT_BUILD_SIZE + addRTDControllerButton(getCPluginID_from_ProtocolIndex(ProtocolIndex)); + # endif // ifndef LIMIT_BUILD_SIZE + + if (Settings.Protocol[controllerindex]) + { + { + MakeControllerSettings(ControllerSettings); //-V522 + + if (!AllocatedControllerSettings()) { + addHtmlError(F("Out of memory, cannot load page")); + } else { + LoadControllerSettings(controllerindex, *ControllerSettings); + + if (!proto.Custom) + { + if (proto.usesHost) { + addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_USE_DNS); + + if (ControllerSettings->UseDNS) + { + addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_HOSTNAME); + } + else + { + addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_IP); + } + } + if (proto.usesPort) { + addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_PORT); + } + # ifdef USES_ESPEASY_NOW + + if (proto.usesMQTT) { + // FIXME TD-er: Currently only enabled for MQTT protocols, later for more + addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_ENABLE_ESPEASY_NOW_FALLBACK); + } + # endif // ifdef USES_ESPEASY_NOW + + if (proto.usesQueue) { + addTableSeparator(F("Controller Queue"), 2, 3); + addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_MIN_SEND_INTERVAL); + addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_MAX_QUEUE_DEPTH); + addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_MAX_RETRIES); + addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_FULL_QUEUE_ACTION); + + if (proto.allowsExpire) { + addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_ALLOW_EXPIRE); + } + addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_DEDUPLICATE); + } + + if (proto.usesCheckReply) { + addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_CHECK_REPLY); + } + + if (proto.usesTimeout) { + addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_TIMEOUT); + if (proto.usesHost) { + addFormNote(F("Typical timeout: 100...300 msec for local host, >500 msec for internet hosts")); + } + } + + if (proto.usesSampleSets) { + addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_SAMPLE_SET_INITIATOR); + } + if (proto.allowLocalSystemTime) { + addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_USE_LOCAL_SYSTEM_TIME); + } + + + if (proto.useCredentials()) { + addTableSeparator(F("Credentials"), 2, 3); + } + + if (proto.useExtendedCredentials()) { + addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_USE_EXTENDED_CREDENTIALS); + } + + if (proto.usesAccount) + { + addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_USER); + } + + if (proto.usesPassword) + { + addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_PASS); + } + #if FEATURE_MQTT + if (proto.usesMQTT) { + addTableSeparator(F("MQTT"), 2, 3); + + addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_CLIENT_ID); + addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_UNIQUE_CLIENT_ID_RECONNECT); + addRowLabel(F("Current Client ID")); + addHtml(getMQTTclientID(*ControllerSettings)); + addFormNote(F("Updated on load of this page")); + addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_RETAINFLAG); + } + # endif // if FEATURE_MQTT + + + if (proto.usesTemplate || proto.usesMQTT) + { + addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_SUBSCRIBE); + addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_PUBLISH); + } + #if FEATURE_MQTT + if (proto.usesMQTT) + { + addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_LWT_TOPIC); + addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_LWT_CONNECT_MESSAGE); + addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_LWT_DISCONNECT_MESSAGE); + addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_SEND_LWT); + addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_WILL_RETAIN); + addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_CLEAN_SESSION); + } + # endif // if FEATURE_MQTT + } + } + + // End of scope for ControllerSettings, destruct it to save memory. + } + { + // Load controller specific settings + struct EventStruct TempEvent; + TempEvent.ControllerIndex = controllerindex; + + String webformLoadString; + CPluginCall(ProtocolIndex, CPlugin::Function::CPLUGIN_WEBFORM_LOAD, &TempEvent, webformLoadString); + + if (webformLoadString.length() > 0) { + addHtmlError(F("Bug in CPlugin::Function::CPLUGIN_WEBFORM_LOAD, should not append to string, use addHtml() instead")); + } + } + + // Separate enabled checkbox as it doesn't need to use the ControllerSettings. + // So ControllerSettings object can be destructed before controller specific settings are loaded. + addControllerEnabledForm(controllerindex); + } + + addFormSeparator(2); + html_TR_TD(); + html_TD(); + addButton(F("controllers"), F("Close")); + addSubmitButton(); + html_end_table(); + html_end_form(); +} + +#endif // ifdef WEBSERVER_CONTROLLERS diff --git a/src/src/WebServer/CustomPage.cpp b/src/src/WebServer/CustomPage.cpp index f241162625..859e91be41 100644 --- a/src/src/WebServer/CustomPage.cpp +++ b/src/src/WebServer/CustomPage.cpp @@ -201,11 +201,10 @@ bool handle_custom(const String& path) { const uint8_t valueCount = getValueCountForTask(x); struct EventStruct TempEvent(x); - for (uint8_t varNr = 0; varNr < VARS_PER_TASK; varNr++) + for (uint8_t varNr = 0; varNr < valueCount; varNr++) { - const String taskValueName = getTaskValueName(x, varNr); - if ((varNr < valueCount) && - (!taskValueName.isEmpty())) + const String taskValueName = Cache.getTaskDeviceValueName(x, varNr); + if (!taskValueName.isEmpty()) { if (varNr > 0) { html_TR_TD(); diff --git a/src/src/WebServer/DevicesPage.cpp b/src/src/WebServer/DevicesPage.cpp index 2d76ebd4b0..3e24c13b41 100644 --- a/src/src/WebServer/DevicesPage.cpp +++ b/src/src/WebServer/DevicesPage.cpp @@ -756,7 +756,7 @@ void handle_devicess_ShowAllTasksTable(uint8_t page) pluginWebformShowValue( x, varNr, - getTaskValueName(x, varNr), + Cache.getTaskDeviceValueName(x, varNr), formatUserVarNoCheck(&TempEvent, varNr)); } } @@ -932,6 +932,27 @@ void handle_devices_TaskSettingsPage(taskIndex_t taskIndex, uint8_t page) } #endif // if FEATURE_PLUGIN_PRIORITY + const uint8_t remoteUnit = Settings.TaskDeviceDataFeed[taskIndex]; + #if FEATURE_ESPEASY_P2P + if (device.SendDataOption) + { + // Show remote feed information. + addFormSubHeader(F("Data Source")); + addFormNumericBox(F("Remote Unit"), F("remoteFeed"), remoteUnit, 0, 255); + + if (remoteUnit != 255) { + const NodeStruct* node = Nodes.getNode(remoteUnit); + + if (node != nullptr) { + addUnit(node->getNodeName()); + } else { + addUnit(F("Unknown Unit Name")); + } + } + addFormNote(F("0 = disable remote feed, 255 = broadcast")); // FIXME TD-er: Must verify if broadcast can be set. + } + #endif + bool addPinConfig = false; // section: Sensor / Actuator @@ -975,13 +996,15 @@ void handle_devices_TaskSettingsPage(taskIndex_t taskIndex, uint8_t page) devicePage_show_pin_config(taskIndex, DeviceIndex); } } - if (DEVICE_TYPE_DUMMY != device.Type) { - addFormSubHeader(F("Device Settings")); - } String webformLoadString; struct EventStruct TempEvent(taskIndex); + + // add plugins content + if (DEVICE_TYPE_DUMMY != device.Type && remoteUnit == 0) { + addFormSubHeader(F("Device Settings")); + } if (Settings.TaskDeviceDataFeed[taskIndex] == 0) { // only show additional config for local connected sensors PluginCall(PLUGIN_WEBFORM_LOAD, &TempEvent, webformLoadString); #ifndef BUILD_NO_DEBUG @@ -992,30 +1015,8 @@ void handle_devices_TaskSettingsPage(taskIndex_t taskIndex, uint8_t page) addHtmlError(errorMessage); } #endif - - PluginCall(PLUGIN_WEBFORM_LOAD_ALWAYS, &TempEvent, webformLoadString); // Load settings also useful for remote-datafeed devices - } - else { - #if FEATURE_ESPEASY_P2P - // Show remote feed information. - addFormSubHeader(F("Data Source")); - const uint8_t remoteUnit = Settings.TaskDeviceDataFeed[taskIndex]; - addFormNumericBox(F("Remote Unit"), F("RemoteUnit"), remoteUnit, 0, 255); - - if (remoteUnit != 255) { - const NodeStruct* node = Nodes.getNode(remoteUnit); - - if (node != nullptr) { - addUnit(node->getNodeName()); - } else { - addUnit(F("Unknown Unit Name")); - } - } - addFormNote(F("0 = disable remote feed, 255 = broadcast")); // FIXME TD-er: Must verify if broadcast can be set. - #endif - - PluginCall(PLUGIN_WEBFORM_LOAD_ALWAYS, &TempEvent, webformLoadString); // Load settings also useful for remote-datafeed devices } + PluginCall(PLUGIN_WEBFORM_LOAD_ALWAYS, &TempEvent, webformLoadString); // Load settings also useful for remote-datafeed devices devicePage_show_output_data_type(taskIndex, DeviceIndex); @@ -1295,6 +1296,7 @@ void devicePage_show_task_statistics(taskIndex_t taskIndex, deviceIndex_t Device if (taskData->nrSamplesPresent() > 0) { addRowLabel(F("Historic data")); taskData->plot_ChartJS(); + } #endif // if FEATURE_CHART_JS diff --git a/src/src/WebServer/ESPEasy_WebServer.cpp b/src/src/WebServer/ESPEasy_WebServer.cpp index fe1440cd6c..8071ddf7a4 100644 --- a/src/src/WebServer/ESPEasy_WebServer.cpp +++ b/src/src/WebServer/ESPEasy_WebServer.cpp @@ -735,7 +735,7 @@ void addTaskValueSelect(const String& name, int choice, taskIndex_t TaskIndex) addHtml(F(" selected")); } addHtml('>'); - addHtml(getTaskValueName(TaskIndex, x)); + addHtml(Cache.getTaskDeviceValueName(TaskIndex, x)); addHtml(F("")); } } diff --git a/src/src/WebServer/HTML_wrappers.cpp b/src/src/WebServer/HTML_wrappers.cpp index df05381141..43ea53a9c2 100644 --- a/src/src/WebServer/HTML_wrappers.cpp +++ b/src/src/WebServer/HTML_wrappers.cpp @@ -281,9 +281,26 @@ void html_add_ChartJS_script() { // - Select the chart.js file (may be called chart.umd.min.js) and copy the url // - Replace the url in below script src element, keeping the quotes #ifndef CDN_URL_CHART_JS - #define CDN_URL_CHART_JS "https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js" + #define CDN_URL_CHART_JS "https://cdn.jsdelivr.net/npm/chart.js@4.4.3/dist/chart.umd.min.js" #endif // ifndef CDN_URL_CHART_JS + + #ifndef CDN_URL_CHART_JS_ADAPTER_DATE + #define CDN_URL_CHART_JS_ADAPTER_DATE "https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns/dist/chartjs-adapter-date-fns.bundle.min.js" + #endif + + #ifndef CDN_URL_CHART_JS_HAMMERJS + #define CDN_URL_CHART_JS_HAMMERJS "https://cdn.jsdelivr.net/npm/hammerjs@2.0.8" + #endif + + #ifndef CDN_URL_CHART_JS_PLUGIN_ZOOM + #define CDN_URL_CHART_JS_PLUGIN_ZOOM "https://cdn.jsdelivr.net/npm/chartjs-plugin-zoom@2.0.1/dist/chartjs-plugin-zoom.min.js" + #endif + + addHtml(F("")); + addHtml(F("")); + addHtml(F("")); + addHtml(F("")); } #endif // if FEATURE_CHART_JS diff --git a/src/src/WebServer/JSON.cpp b/src/src/WebServer/JSON.cpp index d4c0656786..527245c336 100644 --- a/src/src/WebServer/JSON.cpp +++ b/src/src/WebServer/JSON.cpp @@ -74,7 +74,7 @@ void handle_csvval() { if (valNr == INVALID_VALUE_NUM || valNr == x) { - addHtml(getTaskValueName(taskNr, x)); + addHtml(Cache.getTaskDeviceValueName(taskNr, x)); if (x != taskValCount - 1) { addHtml(';'); @@ -124,7 +124,7 @@ void handle_json() bool showNodes = true; #endif #if FEATURE_PLUGIN_STATS - bool showPluginStats = isFormItemChecked(F("showpluginstats")); + bool showPluginStats = getFormItemInt(F("showpluginstats"), 0) != 0; #endif if (equals(webArg(F("view")), F("sensorupdate"))) { @@ -139,7 +139,7 @@ void handle_json() showNodes = false; #endif #if FEATURE_PLUGIN_STATS - showPluginStats = false; + showPluginStats = hasArg(F("showpluginstats")); #endif } @@ -178,6 +178,9 @@ void handle_json() LabelType::UNIT_NAME, LabelType::UPTIME, LabelType::UPTIME_MS, +#if FEATURE_INTERNAL_TEMPERATURE + LabelType::INTERNAL_TEMPERATURE, +#endif LabelType::BOOT_TYPE, LabelType::RESET_REASON, LabelType::CPU_ECO_MODE, @@ -288,10 +291,14 @@ void handle_json() LabelType::WIFI_SEND_AT_MAX_TX_PWR, #endif LabelType::WIFI_NR_EXTRA_SCANS, +#ifdef ESP32 + LabelType::WIFI_PASSIVE_SCAN, +#endif LabelType::WIFI_USE_LAST_CONN_FROM_RTC, LabelType::WIFI_RSSI, - +#ifndef ESP32 LabelType::WAIT_WIFI_CONNECT, +#endif LabelType::HIDDEN_SSID_SLOW_CONNECT, LabelType::CONNECT_HIDDEN_SSID, LabelType::SDK_WIFI_AUTORECONNECT, diff --git a/src/src/WebServer/Markup.cpp b/src/src/WebServer/Markup.cpp index 1ab2374547..965d8dde35 100644 --- a/src/src/WebServer/Markup.cpp +++ b/src/src/WebServer/Markup.cpp @@ -1,1078 +1,1081 @@ - -#include "../WebServer/Markup.h" - -#include "../WebServer/HTML_wrappers.h" - -#include "../CustomBuild/ESPEasyLimits.h" - -#include "../Globals/Settings.h" - -#include "../Helpers/Convert.h" -#include "../Helpers/Hardware_GPIO.h" -#include "../Helpers/StringConverter_Numerical.h" -#include "../Helpers/StringConverter.h" -#include "../Helpers/StringGenerator_GPIO.h" - -#include "../../ESPEasy_common.h" - -// ******************************************************************************** -// Add Selector -// ******************************************************************************** -void addSelector(const __FlashStringHelper *id, - int optionCount, - const __FlashStringHelper *options[], - const int indices[], - const String attr[], - int selectedIndex, - bool reloadonchange, - bool enabled) -{ - addSelector(String(id), optionCount, options, indices, attr, selectedIndex, reloadonchange, enabled, F("wide")); -} - -void addSelector(const String & id, - int optionCount, - const __FlashStringHelper *options[], - const int indices[], - const String attr[], - int selectedIndex, - bool reloadonchange, - bool enabled) -{ - addSelector(id, optionCount, options, indices, attr, selectedIndex, reloadonchange, enabled, F("wide")); -} - -void addSelector(const String& id, - int optionCount, - const String options[], - const int indices[], - const String attr[], - int selectedIndex, - bool reloadonchange, - bool enabled) -{ - addSelector(id, optionCount, options, indices, attr, selectedIndex, reloadonchange, enabled, F("wide")); -} - -void addSelector(const String & id, - int optionCount, - const __FlashStringHelper *options[], - const int indices[], - const String attr[], - int selectedIndex, - bool reloadonchange, - bool enabled, - const __FlashStringHelper * classname - #if FEATURE_TOOLTIPS - , const String & tooltip - #endif // if FEATURE_TOOLTIPS - ) -{ - // FIXME TD-er Change bool to disabled - if (reloadonchange) - { - addSelector_Head_reloadOnChange(id, classname, !enabled - #if FEATURE_TOOLTIPS - , tooltip - #endif // if FEATURE_TOOLTIPS - ); - } else { - do_addSelector_Head(id, classname, EMPTY_STRING, !enabled - #if FEATURE_TOOLTIPS - , tooltip - #endif // if FEATURE_TOOLTIPS - ); - } - addSelector_options(optionCount, options, indices, attr, selectedIndex); - addSelector_Foot(); -} - -void addSelector_reloadOnChange( - const String& id, - int optionCount, - const String options[], - const int indices[], - const String attr[], - int selectedIndex, - const String& onChangeCall, - bool enabled, - const __FlashStringHelper * classname - #if FEATURE_TOOLTIPS - , - const String& tooltip - #endif // if FEATURE_TOOLTIPS - ) -{ - // FIXME TD-er Change bool to disabled - do_addSelector_Head(id, classname, onChangeCall, !enabled - #if FEATURE_TOOLTIPS - , tooltip - #endif // if FEATURE_TOOLTIPS - ); - addSelector_options(optionCount, options, indices, attr, selectedIndex); - addSelector_Foot(); -} - - -void addSelector(const String & id, - int optionCount, - const String options[], - const int indices[], - const String attr[], - int selectedIndex, - bool reloadonchange, - bool enabled, - const __FlashStringHelper * classname - #if FEATURE_TOOLTIPS - , const String& tooltip - #endif // if FEATURE_TOOLTIPS - ) -{ - // FIXME TD-er Change bool to disabled - if (reloadonchange) - { - addSelector_Head_reloadOnChange(id, classname, !enabled - #if FEATURE_TOOLTIPS - , tooltip - #endif // if FEATURE_TOOLTIPS - ); - } else { - do_addSelector_Head(id, classname, EMPTY_STRING, !enabled - #if FEATURE_TOOLTIPS - , tooltip - #endif // if FEATURE_TOOLTIPS - ); - } - addSelector_options(optionCount, options, indices, attr, selectedIndex); - addSelector_Foot(); -} - -void addSelector_options(int optionCount, const __FlashStringHelper *options[], const int indices[], const String attr[], int selectedIndex) -{ - for (uint8_t x = 0; x < optionCount; ++x) - { - const int index = indices ? indices[x] : x; - addSelector_Item( - options[x], - index, - selectedIndex == index, - false, - attr ? attr[x] : EMPTY_STRING); - if ((x & 0x07) == 0) delay(0); - } -} - -void addSelector_options(int optionCount, const String options[], const int indices[], const String attr[], int selectedIndex) -{ - for (uint8_t x = 0; x < optionCount; ++x) - { - const int index = indices ? indices[x] : x; - addSelector_Item( - options[x], - index, - selectedIndex == index, - false, - attr ? attr[x] : EMPTY_STRING); - if ((x & 0x07) == 0) delay(0); - } -} - -void addSelector_Head(const String& id) { - do_addSelector_Head(id, F("wide"), EMPTY_STRING, false - #if FEATURE_TOOLTIPS - , F("") - #endif // if FEATURE_TOOLTIPS - ); -} - -void addSelector_Head_reloadOnChange(const __FlashStringHelper * id) { - addSelector_Head_reloadOnChange(String(id), F("wide"), false); -} - -/* -void addSelector_Head_reloadOnChange(const String& id) { - addSelector_Head_reloadOnChange(id, F("wide"), false); -} -*/ - -void addSelector_Head_reloadOnChange(const String& id, - const __FlashStringHelper * classname, - bool disabled - #if FEATURE_TOOLTIPS - , const String& tooltip - #endif // if FEATURE_TOOLTIPS - ) { - do_addSelector_Head(id, classname, F("return dept_onchange(frmselect)"), disabled - #if FEATURE_TOOLTIPS - , tooltip - #endif // if FEATURE_TOOLTIPS - ); -} - -void addSelector_Head_reloadOnChange(const String& id, const __FlashStringHelper * classname, const String& onChangeCall, bool disabled - #if FEATURE_TOOLTIPS - , const String& tooltip - #endif // if FEATURE_TOOLTIPS - ) { - do_addSelector_Head(id, classname, onChangeCall, disabled - #if FEATURE_TOOLTIPS - , tooltip - #endif // if FEATURE_TOOLTIPS - ); -} - - -void do_addSelector_Head(const String& id, const __FlashStringHelper * classname, const String& onChangeCall, const bool& disabled - #if FEATURE_TOOLTIPS - , const String& tooltip - #endif // if FEATURE_TOOLTIPS - ) -{ - addHtml(F("")); -} - -void addUnit(const __FlashStringHelper *unit) -{ - addHtml(F(" [")); - addHtml(unit); - addHtml(']'); -} - -void addUnit(const String& unit) -{ - addHtml(F(" [")); - addHtml(unit); - addHtml(']'); -} - -void addUnit(char unit) -{ - addHtml(F(" [")); - addHtml(unit); - addHtml(']'); -} - -void addRowLabel_tr_id(const __FlashStringHelper *label, const __FlashStringHelper *id) -{ - addRowLabel_tr_id(label, String(id)); -} - -void addRowLabel_tr_id(const __FlashStringHelper *label, const String& id) -{ - if (id.isEmpty()) { - addRowLabel(label); - } else { - addRowLabel_tr_id(String(label), id); - } -} - -void addRowLabel_tr_id(const String& label, const String& id) -{ - if (id.isEmpty()) { - addRowLabel(label); - } else { - addRowLabel(label, concat(F("tr_"), id)); - } -} - -void addRowLabel(const __FlashStringHelper *label) -{ - html_TR_TD(); - addHtml(concat(label, F(":"))); - html_TD(); -} - -void addRowLabel(const String& label, const String& id) -{ - if (id.length() > 0) { - addHtml(F("")); - } else { - html_TR_TD(); - } - - if (!label.isEmpty()) { - addHtml(label); - addHtml(':'); - } - addHtml(F("")); - html_TD(); -} - -// Add a row label and mark it with copy markers to copy it to clipboard. -void addRowLabel_copy(const __FlashStringHelper *label) { - addHtml(F("")); - html_copyText_TD(); - addHtml(label); - addHtml(':'); - html_copyText_marker(); - html_copyText_TD(); -} - -void addRowLabel_copy(const String& label) { - addHtml(F("")); - html_copyText_TD(); - addHtml(label); - addHtml(':'); - html_copyText_marker(); - html_copyText_TD(); -} - -void addRowLabel(LabelType::Enum label) { - addRowLabel(getLabel(label)); -} - -void addRowLabelValue(LabelType::Enum label) { - addRowLabel(getLabel(label)); - addHtml(getValue(label)); -} - -void addRowLabelValues(const LabelType::Enum labels[]) { - size_t i = 0; - LabelType::Enum cur = static_cast(pgm_read_byte(labels + i)); - - while (true) { - const LabelType::Enum next = static_cast(pgm_read_byte(labels + i + 1)); - addRowLabelValue(cur); - if (next == LabelType::MAX_LABEL) { - return; - } - ++i; - cur = next; - } -} - -void addRowLabelValue_copy(LabelType::Enum label) { - addRowLabel_copy(getLabel(label)); - addHtml(getValue(label)); -} - -// ******************************************************************************** -// Add a header -// ******************************************************************************** -void addTableSeparator(const __FlashStringHelper *label, int colspan, int h_size) -{ - addHtml(strformat( - F(""), - colspan, h_size)); - addHtml(label); - addHtml(strformat( - F(""), - h_size)); -} - -void addTableSeparator(const __FlashStringHelper *label, int colspan, int h_size, const __FlashStringHelper *helpButton) -{ - addTableSeparator(String(label), colspan, h_size, String(helpButton)); -} - -void addTableSeparator(const String& label, int colspan, int h_size, const String& helpButton) { - addHtml(strformat( - F(""), - colspan, h_size)); - addHtml(label); - - if (!helpButton.isEmpty()) { - addHelpButton(helpButton); - } - addHtml(strformat( - F(""), - h_size)); -} - -void addFormHeader(const __FlashStringHelper *header) { - addFormHeader(header, F(""), F("")); -} - -void addFormHeader(const __FlashStringHelper *header, - const __FlashStringHelper *helpButton) -{ - addFormHeader(header, helpButton, F("")); -} - -void addFormHeader(const __FlashStringHelper *header, - const __FlashStringHelper *helpButton, - const __FlashStringHelper *rtdHelpButton) -{ - html_TR(); - html_table_header(header, helpButton, rtdHelpButton, 300); - html_table_header(F("")); -} - -/* -void addFormHeader(const String& header, const String& helpButton) { - addFormHeader(header, helpButton, EMPTY_STRING); -} - -void addFormHeader(const String& header, const String& helpButton, const String& rtdHelpButton) -{ - html_TR(); - html_table_header(header, helpButton, rtdHelpButton, 225); - html_table_header(F("")); -} -*/ - -// ******************************************************************************** -// Add a sub header -// ******************************************************************************** -void addFormSubHeader(const __FlashStringHelper *header) { - addTableSeparator(header, 2, 3); -} - -void addFormSubHeader(const String& header) -{ - addTableSeparator(header, 2, 3); -} - -// ******************************************************************************** -// Add a checkbox -// ******************************************************************************** -void addCheckBox(const __FlashStringHelper *id, bool checked, bool disabled) -{ - addCheckBox(String(id), checked, disabled); -} - -void addCheckBox(const String& id, bool checked, bool disabled - #if FEATURE_TOOLTIPS - , const String& tooltip - #endif // if FEATURE_TOOLTIPS - ) -{ - addHtml(F("")); -} - -// ******************************************************************************** -// Add a numeric box -// ******************************************************************************** -void addNumericBox(const __FlashStringHelper *id, int value, int min, int max, bool disabled) -{ - addNumericBox(String(id), value, min, max, disabled); -} - -void addNumericBox(const String& id, int value, int min, int max - #if FEATURE_TOOLTIPS - , const __FlashStringHelper * classname, const String& tooltip - #endif // if FEATURE_TOOLTIPS - , bool disabled - ) -{ - addHtml(F(" 0) { - addHtmlAttribute(F("title"), tooltip); - } - #endif // if FEATURE_TOOLTIPS - - if (disabled) { - addDisabled(); - } - - if (value < min) { - value = min; - } - - if (value > max) { - value = max; - } - - if (min != INT_MIN) - { - addHtmlAttribute(F("min"), min); - } - - if (max != INT_MAX) - { - addHtmlAttribute(F("max"), max); - } - addHtmlAttribute(F("value"), value); - addHtml('>'); -} - -#if FEATURE_TOOLTIPS -void addNumericBox(const String& id, int value, int min, int max, bool disabled) -{ - addNumericBox(id, value, min, max, F("widenumber"), EMPTY_STRING, disabled); -} - -#endif // if FEATURE_TOOLTIPS - -void addFloatNumberBox(const String& id, float value, float min, float max, unsigned int nrDecimals, float stepsize - #if FEATURE_TOOLTIPS - , const String& tooltip - #endif // if FEATURE_TOOLTIPS - ) -{ - addHtml(strformat( - F("'); -} - -// ******************************************************************************** -// Add Textbox -// ******************************************************************************** -void addTextBox(const __FlashStringHelper * id, const String& value, int maxlength, bool readonly, bool required, const String& pattern) { - addTextBox(id, value, maxlength, readonly, required, pattern, F("wide")); -} - -void addTextBox(const String& id, const String& value, int maxlength, bool readonly, bool required, const String& pattern) { - addTextBox(id, value, maxlength, readonly, required, pattern, F("wide")); -} - -void addTextBox(const String & id, - const String & value, - int maxlength, - bool readonly, - bool required, - const String & pattern, - const __FlashStringHelper * classname - #if FEATURE_TOOLTIPS - , const String& tooltip - #endif // if FEATURE_TOOLTIPS - , - const String& datalist - ) -{ - addHtml(F(" 0) { - addHtmlAttribute(F("maxlength"), maxlength); - } - if (!datalist.isEmpty()) { - addHtmlAttribute(F("list"), datalist); - } - addHtmlAttribute(F("value"), value); - - if (readonly) { - addHtml(F(" readonly ")); - } - - if (required) { - addHtml(F(" required ")); - } - - if (pattern.length() > 0) { - addHtmlAttribute(F("pattern"), pattern); - } - - #if FEATURE_TOOLTIPS - - if (tooltip.length() > 0) { - addHtmlAttribute(F("title"), tooltip); - } - #endif // if FEATURE_TOOLTIPS - addHtml('>'); -} - - - -// ******************************************************************************** -// Add Textarea -// ******************************************************************************** -void addTextArea(const String & id, - const String & value, - int maxlength, - int rows, - int columns, - bool readonly, - bool required - #if FEATURE_TOOLTIPS - , const String& tooltip - #endif // if FEATURE_TOOLTIPS - ) -{ - addHtml(F("")); -} - -// ******************************************************************************** -// Add Help Buttons -// ******************************************************************************** - -// adds a Help Button with points to the the given Wiki Subpage -// If url starts with "RTD", it will be considered as a Read-the-docs link -void addHelpButton(const __FlashStringHelper *url) { - addHelpButton(String(url)); -} - -void addHelpButton(const String& url) { -#ifndef WEBPAGE_TEMPLATE_HIDE_HELP_BUTTON - - if (url.startsWith("RTD")) { - addRTDHelpButton(url.substring(3)); - } else { - addHelpButton(url, false); - } -#endif // ifndef WEBPAGE_TEMPLATE_HIDE_HELP_BUTTON -} - -void addRTDHelpButton(const String& url) -{ - addHelpButton(url, true); -} - -void addHelpButton(const String& url, bool isRTD) -{ - #ifndef WEBPAGE_TEMPLATE_HIDE_HELP_BUTTON - addHtmlLink( - F("button help"), - makeDocLink(url, isRTD), - isRTD ? F("i") : F("?")); - #endif // ifndef WEBPAGE_TEMPLATE_HIDE_HELP_BUTTON -} - -void addRTDPluginButton(pluginID_t pluginID) { - addRTDHelpButton( - strformat( - F("Plugin/%s.html"), - get_formatted_Plugin_number(pluginID).c_str())); - - constexpr pluginID_t PLUGIN_ID_P076_HLW8012(76); - constexpr pluginID_t PLUGIN_ID_P077_CSE7766(77); - - if ((pluginID == PLUGIN_ID_P076_HLW8012) || - (pluginID == PLUGIN_ID_P077_CSE7766)) { - addHtmlLink( - F("button help"), - makeDocLink(F("Reference/Safety.html"), true), - F("⚡")); // High voltage sign - } -} - -# ifndef LIMIT_BUILD_SIZE -void addRTDControllerButton(cpluginID_t cpluginID) { - addRTDHelpButton( - strformat( - F("Controller/%s.html"), - get_formatted_Controller_number(cpluginID).c_str())); -} -# endif // ifndef LIMIT_BUILD_SIZE - -String makeDocLink(const String& url, bool isRTD) { - String result; - - if (!url.startsWith(F("http"))) { - if (isRTD) { - result += F("https://espeasy.readthedocs.io/en/latest/"); - } else { - result += F("http://www.letscontrolit.com/wiki/index.php/"); - } - } - result += url; - return result; -} - -void addPinSelect(PinSelectPurpose purpose, const __FlashStringHelper *id, int choice) -{ - addPinSelect(purpose, String(id), choice); -} - -void addPinSelect(PinSelectPurpose purpose, const String& id, int choice) -{ - addSelector_Head(id); - - // At i == 0 && gpio == -1, add the "- None -" option first - int i = 0; - int gpio = -1; - - while (gpio <= MAX_GPIO) { - int pinnr = -1; - bool input, output, warning = false; - - // Make sure getGpioInfo is called (compiler may optimize it away if (i == 0)) - const bool UsableGPIO = getGpioInfo(gpio, pinnr, input, output, warning); - - if (UsableGPIO || (i == 0)) { - addPinSelector_Item( - purpose, - concat( - createGPIO_label(gpio, pinnr, input, output, warning), - getConflictingUse_wrapped(gpio, purpose)), - gpio, - choice == gpio); - - ++i; - } - ++gpio; - } - addSelector_Foot(); -} - -#ifdef ESP32 -void addADC_PinSelect(AdcPinSelectPurpose purpose, const String& id, int choice) -{ - addSelector_Head(id); - - // At i == 0 && gpio == -1, add the "Hall Effect" option first - int i = 0; - int gpio = -1; - - if ( -#if HAS_HALL_EFFECT_SENSOR - (purpose == AdcPinSelectPurpose::ADC_Touch_HallEffect) || -#endif - (purpose == AdcPinSelectPurpose::ADC_Touch_Optional)) { - addPinSelector_Item( - PinSelectPurpose::Generic, - purpose == AdcPinSelectPurpose::ADC_Touch_Optional ? F("- None -") : formatGpioName_ADC(gpio), - gpio, - choice == gpio); - } - - while (i <= MAX_GPIO && gpio <= MAX_GPIO) { - int pinnr = -1; - bool input, output, warning; - - if (purpose == AdcPinSelectPurpose::TouchOnly) { - // For touch only list, sort based on touch number - // Default sort is on GPIO number. - gpio = touchPinToGpio(i); - } else { - ++gpio; - } - - if (getGpioInfo(gpio, pinnr, input, output, warning)) { - int adc, ch, t; - - if (getADC_gpio_info(gpio, adc, ch, t)) { - if ((purpose != AdcPinSelectPurpose::TouchOnly) || (t >= 0)) { - String gpio_label; - gpio_label = formatGpioName_ADC(gpio); - - if (adc != 0) { - gpio_label += F(" / "); - gpio_label += createGPIO_label(gpio, pinnr, input, output, warning); - gpio_label += getConflictingUse_wrapped(gpio); - } - addPinSelector_Item( - PinSelectPurpose::Generic, - gpio_label, - gpio, - choice == gpio); - } - } - } - ++i; - } - addSelector_Foot(); -} - -void addDAC_PinSelect(const String& id, int choice) -{ - addSelector_Head(id); - - // At i == 0 && gpio == -1, add the "- None -" option first - int i = 0; - int gpio = -1; - - while (gpio <= MAX_GPIO) { - int pinnr = -1; - bool input = false; - bool output = false; - bool warning = false; - int dac = 0; - - // Make sure getGpioInfo is called (compiler may optimize it away if (i == 0)) - const bool UsableGPIO = getDAC_gpio_info(gpio, dac); // getGpioInfo(gpio, pinnr, input, output, warning); - - if (UsableGPIO || (i == 0)) { - if (getGpioInfo(gpio, pinnr, input, output, warning) || (i == 0)) { - String gpio_label = formatGpioName_DAC(gpio); - - if (dac != 0) { - gpio_label += F(" / "); - gpio_label += createGPIO_label(gpio, pinnr, input, output, warning); - gpio_label += getConflictingUse_wrapped(gpio, PinSelectPurpose::DAC); - } - addPinSelector_Item( - PinSelectPurpose::DAC, - gpio_label, - gpio, - choice == gpio); - } - ++i; - } - ++gpio; - } - addSelector_Foot(); -} - -#endif // ifdef ESP32 + +#include "../WebServer/Markup.h" + +#include "../WebServer/HTML_wrappers.h" + +#include "../CustomBuild/ESPEasyLimits.h" + +#include "../Globals/Settings.h" + +#include "../Helpers/Convert.h" +#include "../Helpers/Hardware_GPIO.h" +#include "../Helpers/StringConverter_Numerical.h" +#include "../Helpers/StringConverter.h" +#include "../Helpers/StringGenerator_GPIO.h" + +#include "../../ESPEasy_common.h" + +// ******************************************************************************** +// Add Selector +// ******************************************************************************** +void addSelector(const __FlashStringHelper *id, + int optionCount, + const __FlashStringHelper *options[], + const int indices[], + const String attr[], + int selectedIndex, + bool reloadonchange, + bool enabled) +{ + addSelector(String(id), optionCount, options, indices, attr, selectedIndex, reloadonchange, enabled, F("wide")); +} + +void addSelector(const String & id, + int optionCount, + const __FlashStringHelper *options[], + const int indices[], + const String attr[], + int selectedIndex, + bool reloadonchange, + bool enabled) +{ + addSelector(id, optionCount, options, indices, attr, selectedIndex, reloadonchange, enabled, F("wide")); +} + +void addSelector(const String& id, + int optionCount, + const String options[], + const int indices[], + const String attr[], + int selectedIndex, + bool reloadonchange, + bool enabled) +{ + addSelector(id, optionCount, options, indices, attr, selectedIndex, reloadonchange, enabled, F("wide")); +} + +void addSelector(const String & id, + int optionCount, + const __FlashStringHelper *options[], + const int indices[], + const String attr[], + int selectedIndex, + bool reloadonchange, + bool enabled, + const __FlashStringHelper * classname + #if FEATURE_TOOLTIPS + , const String & tooltip + #endif // if FEATURE_TOOLTIPS + ) +{ + // FIXME TD-er Change bool to disabled + if (reloadonchange) + { + addSelector_Head_reloadOnChange(id, classname, !enabled + #if FEATURE_TOOLTIPS + , tooltip + #endif // if FEATURE_TOOLTIPS + ); + } else { + do_addSelector_Head(id, classname, EMPTY_STRING, !enabled + #if FEATURE_TOOLTIPS + , tooltip + #endif // if FEATURE_TOOLTIPS + ); + } + addSelector_options(optionCount, options, indices, attr, selectedIndex); + addSelector_Foot(); +} + +void addSelector_reloadOnChange( + const String& id, + int optionCount, + const String options[], + const int indices[], + const String attr[], + int selectedIndex, + const String& onChangeCall, + bool enabled, + const __FlashStringHelper * classname + #if FEATURE_TOOLTIPS + , + const String& tooltip + #endif // if FEATURE_TOOLTIPS + ) +{ + // FIXME TD-er Change bool to disabled + do_addSelector_Head(id, classname, onChangeCall, !enabled + #if FEATURE_TOOLTIPS + , tooltip + #endif // if FEATURE_TOOLTIPS + ); + addSelector_options(optionCount, options, indices, attr, selectedIndex); + addSelector_Foot(); +} + + +void addSelector(const String & id, + int optionCount, + const String options[], + const int indices[], + const String attr[], + int selectedIndex, + bool reloadonchange, + bool enabled, + const __FlashStringHelper * classname + #if FEATURE_TOOLTIPS + , const String& tooltip + #endif // if FEATURE_TOOLTIPS + ) +{ + // FIXME TD-er Change bool to disabled + if (reloadonchange) + { + addSelector_Head_reloadOnChange(id, classname, !enabled + #if FEATURE_TOOLTIPS + , tooltip + #endif // if FEATURE_TOOLTIPS + ); + } else { + do_addSelector_Head(id, classname, EMPTY_STRING, !enabled + #if FEATURE_TOOLTIPS + , tooltip + #endif // if FEATURE_TOOLTIPS + ); + } + addSelector_options(optionCount, options, indices, attr, selectedIndex); + addSelector_Foot(); +} + +void addSelector_options(int optionCount, const __FlashStringHelper *options[], const int indices[], const String attr[], int selectedIndex) +{ + for (uint8_t x = 0; x < optionCount; ++x) + { + const int index = indices ? indices[x] : x; + addSelector_Item( + options[x], + index, + selectedIndex == index, + false, + attr ? attr[x] : EMPTY_STRING); + if ((x & 0x07) == 0) delay(0); + } +} + +void addSelector_options(int optionCount, const String options[], const int indices[], const String attr[], int selectedIndex) +{ + for (uint8_t x = 0; x < optionCount; ++x) + { + const int index = indices ? indices[x] : x; + addSelector_Item( + options[x], + index, + selectedIndex == index, + false, + attr ? attr[x] : EMPTY_STRING); + if ((x & 0x07) == 0) delay(0); + } +} + +void addSelector_Head(const String& id) { + do_addSelector_Head(id, F("wide"), EMPTY_STRING, false + #if FEATURE_TOOLTIPS + , F("") + #endif // if FEATURE_TOOLTIPS + ); +} + +void addSelector_Head_reloadOnChange(const __FlashStringHelper * id) { + addSelector_Head_reloadOnChange(String(id), F("wide"), false); +} + +/* +void addSelector_Head_reloadOnChange(const String& id) { + addSelector_Head_reloadOnChange(id, F("wide"), false); +} +*/ + +void addSelector_Head_reloadOnChange(const String& id, + const __FlashStringHelper * classname, + bool disabled + #if FEATURE_TOOLTIPS + , const String& tooltip + #endif // if FEATURE_TOOLTIPS + ) { + do_addSelector_Head(id, classname, F("return dept_onchange(frmselect)"), disabled + #if FEATURE_TOOLTIPS + , tooltip + #endif // if FEATURE_TOOLTIPS + ); +} + +void addSelector_Head_reloadOnChange(const String& id, const __FlashStringHelper * classname, const String& onChangeCall, bool disabled + #if FEATURE_TOOLTIPS + , const String& tooltip + #endif // if FEATURE_TOOLTIPS + ) { + do_addSelector_Head(id, classname, onChangeCall, disabled + #if FEATURE_TOOLTIPS + , tooltip + #endif // if FEATURE_TOOLTIPS + ); +} + + +void do_addSelector_Head(const String& id, const __FlashStringHelper * classname, const String& onChangeCall, const bool& disabled + #if FEATURE_TOOLTIPS + , const String& tooltip + #endif // if FEATURE_TOOLTIPS + ) +{ + addHtml(F("")); +} + +void addUnit(const __FlashStringHelper *unit) +{ + addHtml(F(" [")); + addHtml(unit); + addHtml(']'); +} + +void addUnit(const String& unit) +{ + if (unit.isEmpty()) return; + addHtml(F(" [")); + addHtml(unit); + addHtml(']'); +} + +void addUnit(char unit) +{ + addHtml(F(" [")); + addHtml(unit); + addHtml(']'); +} + +void addRowLabel_tr_id(const __FlashStringHelper *label, const __FlashStringHelper *id) +{ + addRowLabel_tr_id(label, String(id)); +} + +void addRowLabel_tr_id(const __FlashStringHelper *label, const String& id) +{ + if (id.isEmpty()) { + addRowLabel(label); + } else { + addRowLabel_tr_id(String(label), id); + } +} + +void addRowLabel_tr_id(const String& label, const String& id) +{ + if (id.isEmpty()) { + addRowLabel(label); + } else { + addRowLabel(label, concat(F("tr_"), id)); + } +} + +void addRowLabel(const __FlashStringHelper *label) +{ + html_TR_TD(); + addHtml(concat(label, F(":"))); + html_TD(); +} + +void addRowLabel(const String& label, const String& id) +{ + if (id.length() > 0) { + addHtml(F("")); + } else { + html_TR_TD(); + } + + if (!label.isEmpty()) { + addHtml(label); + addHtml(':'); + } + addHtml(F("")); + html_TD(); +} + +// Add a row label and mark it with copy markers to copy it to clipboard. +void addRowLabel_copy(const __FlashStringHelper *label) { + addHtml(F("")); + html_copyText_TD(); + addHtml(label); + addHtml(':'); + html_copyText_marker(); + html_copyText_TD(); +} + +void addRowLabel_copy(const String& label) { + addHtml(F("")); + html_copyText_TD(); + addHtml(label); + addHtml(':'); + html_copyText_marker(); + html_copyText_TD(); +} + +void addRowLabel(LabelType::Enum label) { + addRowLabel(getLabel(label)); +} + +void addRowLabelValue(LabelType::Enum label) { + addRowLabel(getLabel(label)); + addHtml(getValue(label)); + addUnit(getFormUnit(label)); +} + +void addRowLabelValues(const LabelType::Enum labels[]) { + size_t i = 0; + LabelType::Enum cur = static_cast(pgm_read_byte(labels + i)); + + while (true) { + const LabelType::Enum next = static_cast(pgm_read_byte(labels + i + 1)); + addRowLabelValue(cur); + if (next == LabelType::MAX_LABEL) { + return; + } + ++i; + cur = next; + } +} + +void addRowLabelValue_copy(LabelType::Enum label) { + addRowLabel_copy(getLabel(label)); + addHtml(getValue(label)); + addUnit(getFormUnit(label)); +} + +// ******************************************************************************** +// Add a header +// ******************************************************************************** +void addTableSeparator(const __FlashStringHelper *label, int colspan, int h_size) +{ + addHtml(strformat( + F(""), + colspan, h_size)); + addHtml(label); + addHtml(strformat( + F(""), + h_size)); +} + +void addTableSeparator(const __FlashStringHelper *label, int colspan, int h_size, const __FlashStringHelper *helpButton) +{ + addTableSeparator(String(label), colspan, h_size, String(helpButton)); +} + +void addTableSeparator(const String& label, int colspan, int h_size, const String& helpButton) { + addHtml(strformat( + F(""), + colspan, h_size)); + addHtml(label); + + if (!helpButton.isEmpty()) { + addHelpButton(helpButton); + } + addHtml(strformat( + F(""), + h_size)); +} + +void addFormHeader(const __FlashStringHelper *header) { + addFormHeader(header, F(""), F("")); +} + +void addFormHeader(const __FlashStringHelper *header, + const __FlashStringHelper *helpButton) +{ + addFormHeader(header, helpButton, F("")); +} + +void addFormHeader(const __FlashStringHelper *header, + const __FlashStringHelper *helpButton, + const __FlashStringHelper *rtdHelpButton) +{ + html_TR(); + html_table_header(header, helpButton, rtdHelpButton, 300); + html_table_header(F("")); +} + +/* +void addFormHeader(const String& header, const String& helpButton) { + addFormHeader(header, helpButton, EMPTY_STRING); +} + +void addFormHeader(const String& header, const String& helpButton, const String& rtdHelpButton) +{ + html_TR(); + html_table_header(header, helpButton, rtdHelpButton, 225); + html_table_header(F("")); +} +*/ + +// ******************************************************************************** +// Add a sub header +// ******************************************************************************** +void addFormSubHeader(const __FlashStringHelper *header) { + addTableSeparator(header, 2, 3); +} + +void addFormSubHeader(const String& header) +{ + addTableSeparator(header, 2, 3); +} + +// ******************************************************************************** +// Add a checkbox +// ******************************************************************************** +void addCheckBox(const __FlashStringHelper *id, bool checked, bool disabled) +{ + addCheckBox(String(id), checked, disabled); +} + +void addCheckBox(const String& id, bool checked, bool disabled + #if FEATURE_TOOLTIPS + , const String& tooltip + #endif // if FEATURE_TOOLTIPS + ) +{ + addHtml(F("")); +} + +// ******************************************************************************** +// Add a numeric box +// ******************************************************************************** +void addNumericBox(const __FlashStringHelper *id, int value, int min, int max, bool disabled) +{ + addNumericBox(String(id), value, min, max, disabled); +} + +void addNumericBox(const String& id, int value, int min, int max + #if FEATURE_TOOLTIPS + , const __FlashStringHelper * classname, const String& tooltip + #endif // if FEATURE_TOOLTIPS + , bool disabled + ) +{ + addHtml(F(" 0) { + addHtmlAttribute(F("title"), tooltip); + } + #endif // if FEATURE_TOOLTIPS + + if (disabled) { + addDisabled(); + } + + if (value < min) { + value = min; + } + + if (value > max) { + value = max; + } + + if (min != INT_MIN) + { + addHtmlAttribute(F("min"), min); + } + + if (max != INT_MAX) + { + addHtmlAttribute(F("max"), max); + } + addHtmlAttribute(F("value"), value); + addHtml('>'); +} + +#if FEATURE_TOOLTIPS +void addNumericBox(const String& id, int value, int min, int max, bool disabled) +{ + addNumericBox(id, value, min, max, F("widenumber"), EMPTY_STRING, disabled); +} + +#endif // if FEATURE_TOOLTIPS + +void addFloatNumberBox(const String& id, float value, float min, float max, unsigned int nrDecimals, float stepsize + #if FEATURE_TOOLTIPS + , const String& tooltip + #endif // if FEATURE_TOOLTIPS + ) +{ + addHtml(strformat( + F("'); +} + +// ******************************************************************************** +// Add Textbox +// ******************************************************************************** +void addTextBox(const __FlashStringHelper * id, const String& value, int maxlength, bool readonly, bool required, const String& pattern) { + addTextBox(id, value, maxlength, readonly, required, pattern, F("wide")); +} + +void addTextBox(const String& id, const String& value, int maxlength, bool readonly, bool required, const String& pattern) { + addTextBox(id, value, maxlength, readonly, required, pattern, F("wide")); +} + +void addTextBox(const String & id, + const String & value, + int maxlength, + bool readonly, + bool required, + const String & pattern, + const __FlashStringHelper * classname + #if FEATURE_TOOLTIPS + , const String& tooltip + #endif // if FEATURE_TOOLTIPS + , + const String& datalist + ) +{ + addHtml(F(" 0) { + addHtmlAttribute(F("maxlength"), maxlength); + } + if (!datalist.isEmpty()) { + addHtmlAttribute(F("list"), datalist); + } + addHtmlAttribute(F("value"), value); + + if (readonly) { + addHtml(F(" readonly ")); + } + + if (required) { + addHtml(F(" required ")); + } + + if (pattern.length() > 0) { + addHtmlAttribute(F("pattern"), pattern); + } + + #if FEATURE_TOOLTIPS + + if (tooltip.length() > 0) { + addHtmlAttribute(F("title"), tooltip); + } + #endif // if FEATURE_TOOLTIPS + addHtml('>'); +} + + + +// ******************************************************************************** +// Add Textarea +// ******************************************************************************** +void addTextArea(const String & id, + const String & value, + int maxlength, + int rows, + int columns, + bool readonly, + bool required + #if FEATURE_TOOLTIPS + , const String& tooltip + #endif // if FEATURE_TOOLTIPS + ) +{ + addHtml(F("")); +} + +// ******************************************************************************** +// Add Help Buttons +// ******************************************************************************** + +// adds a Help Button with points to the the given Wiki Subpage +// If url starts with "RTD", it will be considered as a Read-the-docs link +void addHelpButton(const __FlashStringHelper *url) { + addHelpButton(String(url)); +} + +void addHelpButton(const String& url) { +#ifndef WEBPAGE_TEMPLATE_HIDE_HELP_BUTTON + + if (url.startsWith("RTD")) { + addRTDHelpButton(url.substring(3)); + } else { + addHelpButton(url, false); + } +#endif // ifndef WEBPAGE_TEMPLATE_HIDE_HELP_BUTTON +} + +void addRTDHelpButton(const String& url) +{ + addHelpButton(url, true); +} + +void addHelpButton(const String& url, bool isRTD) +{ + #ifndef WEBPAGE_TEMPLATE_HIDE_HELP_BUTTON + addHtmlLink( + F("button help"), + makeDocLink(url, isRTD), + isRTD ? F("i") : F("?")); + #endif // ifndef WEBPAGE_TEMPLATE_HIDE_HELP_BUTTON +} + +void addRTDPluginButton(pluginID_t pluginID) { + addRTDHelpButton( + strformat( + F("Plugin/%s.html"), + get_formatted_Plugin_number(pluginID).c_str())); + + constexpr pluginID_t PLUGIN_ID_P076_HLW8012(76); + constexpr pluginID_t PLUGIN_ID_P077_CSE7766(77); + + if ((pluginID == PLUGIN_ID_P076_HLW8012) || + (pluginID == PLUGIN_ID_P077_CSE7766)) { + addHtmlLink( + F("button help"), + makeDocLink(F("Reference/Safety.html"), true), + F("⚡")); // High voltage sign + } +} + +# ifndef LIMIT_BUILD_SIZE +void addRTDControllerButton(cpluginID_t cpluginID) { + addRTDHelpButton( + strformat( + F("Controller/%s.html"), + get_formatted_Controller_number(cpluginID).c_str())); +} +# endif // ifndef LIMIT_BUILD_SIZE + +String makeDocLink(const String& url, bool isRTD) { + String result; + + if (!url.startsWith(F("http"))) { + if (isRTD) { + result += F("https://espeasy.readthedocs.io/en/latest/"); + } else { + result += F("http://www.letscontrolit.com/wiki/index.php/"); + } + } + result += url; + return result; +} + +void addPinSelect(PinSelectPurpose purpose, const __FlashStringHelper *id, int choice) +{ + addPinSelect(purpose, String(id), choice); +} + +void addPinSelect(PinSelectPurpose purpose, const String& id, int choice) +{ + addSelector_Head(id); + + // At i == 0 && gpio == -1, add the "- None -" option first + int i = 0; + int gpio = -1; + + while (gpio <= MAX_GPIO) { + int pinnr = -1; + bool input, output, warning = false; + + // Make sure getGpioInfo is called (compiler may optimize it away if (i == 0)) + const bool UsableGPIO = getGpioInfo(gpio, pinnr, input, output, warning); + + if (UsableGPIO || (i == 0)) { + addPinSelector_Item( + purpose, + concat( + createGPIO_label(gpio, pinnr, input, output, warning), + getConflictingUse_wrapped(gpio, purpose)), + gpio, + choice == gpio); + + ++i; + } + ++gpio; + } + addSelector_Foot(); +} + +#ifdef ESP32 +void addADC_PinSelect(AdcPinSelectPurpose purpose, const String& id, int choice) +{ + addSelector_Head(id); + + // At i == 0 && gpio == -1, add the "Hall Effect" option first + int i = 0; + int gpio = -1; + + if ( +#if HAS_HALL_EFFECT_SENSOR + (purpose == AdcPinSelectPurpose::ADC_Touch_HallEffect) || +#endif + (purpose == AdcPinSelectPurpose::ADC_Touch_Optional)) { + addPinSelector_Item( + PinSelectPurpose::Generic, + purpose == AdcPinSelectPurpose::ADC_Touch_Optional ? F("- None -") : formatGpioName_ADC(gpio), + gpio, + choice == gpio); + } + + while (i <= MAX_GPIO && gpio <= MAX_GPIO) { + int pinnr = -1; + bool input, output, warning; + + if (purpose == AdcPinSelectPurpose::TouchOnly) { + // For touch only list, sort based on touch number + // Default sort is on GPIO number. + gpio = touchPinToGpio(i); + } else { + ++gpio; + } + + if (getGpioInfo(gpio, pinnr, input, output, warning)) { + int adc, ch, t; + + if (getADC_gpio_info(gpio, adc, ch, t)) { + if ((purpose != AdcPinSelectPurpose::TouchOnly) || (t >= 0)) { + String gpio_label; + gpio_label = formatGpioName_ADC(gpio); + + if (adc != 0) { + gpio_label += F(" / "); + gpio_label += createGPIO_label(gpio, pinnr, input, output, warning); + gpio_label += getConflictingUse_wrapped(gpio); + } + addPinSelector_Item( + PinSelectPurpose::Generic, + gpio_label, + gpio, + choice == gpio); + } + } + } + ++i; + } + addSelector_Foot(); +} + +void addDAC_PinSelect(const String& id, int choice) +{ + addSelector_Head(id); + + // At i == 0 && gpio == -1, add the "- None -" option first + int i = 0; + int gpio = -1; + + while (gpio <= MAX_GPIO) { + int pinnr = -1; + bool input = false; + bool output = false; + bool warning = false; + int dac = 0; + + // Make sure getGpioInfo is called (compiler may optimize it away if (i == 0)) + const bool UsableGPIO = getDAC_gpio_info(gpio, dac); // getGpioInfo(gpio, pinnr, input, output, warning); + + if (UsableGPIO || (i == 0)) { + if (getGpioInfo(gpio, pinnr, input, output, warning) || (i == 0)) { + String gpio_label = formatGpioName_DAC(gpio); + + if (dac != 0) { + gpio_label += F(" / "); + gpio_label += createGPIO_label(gpio, pinnr, input, output, warning); + gpio_label += getConflictingUse_wrapped(gpio, PinSelectPurpose::DAC); + } + addPinSelector_Item( + PinSelectPurpose::DAC, + gpio_label, + gpio, + choice == gpio); + } + ++i; + } + ++gpio; + } + addSelector_Foot(); +} + +#endif // ifdef ESP32 diff --git a/src/src/WebServer/Markup_Forms.cpp b/src/src/WebServer/Markup_Forms.cpp index 30f8ff05ca..ac47d98e1b 100644 --- a/src/src/WebServer/Markup_Forms.cpp +++ b/src/src/WebServer/Markup_Forms.cpp @@ -1,856 +1,865 @@ -#include "../WebServer/Markup_Forms.h" - -#include "../WebServer/ESPEasy_WebServer.h" -#include "../WebServer/AccessControl.h" -#include "../WebServer/Markup.h" -#include "../WebServer/HTML_wrappers.h" - -#include "../Globals/Settings.h" - -#include "../Helpers/Hardware_GPIO.h" -#include "../Helpers/Numerical.h" -#include "../Helpers/StringConverter.h" -#include "../Helpers/StringGenerator_GPIO.h" - -// ******************************************************************************** -// Add a separator as row start -// ******************************************************************************** -void addFormSeparator(int clspan) -{ - addHtml(strformat( - F("
"), - clspan)); -} - -// ******************************************************************************** -// Add a note as row start -// ******************************************************************************** -void addFormNote(const __FlashStringHelper * text) -{ - addRowLabel_tr_id(EMPTY_STRING, EMPTY_STRING); - addHtml(F("
Note: ")); - addHtml(text); - addHtml(F("
")); -} - -void addFormNote(const String& text, const String& id) -{ - if (text.isEmpty()) return; - addRowLabel_tr_id(EMPTY_STRING, id); - addHtmlDiv(F("note"), concat(F("Note: "), text)); -} - -// ******************************************************************************** -// Create Forms -// ******************************************************************************** - - -// ******************************************************************************** -// Add a checkbox Form -// ******************************************************************************** - -void addFormCheckBox_disabled(const String& label, const String& id, bool checked - #if FEATURE_TOOLTIPS - , const String& tooltip - #endif // if FEATURE_TOOLTIPS - ) { - addFormCheckBox(label, id, checked, true - #if FEATURE_TOOLTIPS - , tooltip - #endif // if FEATURE_TOOLTIPS - ); -} - -void addFormCheckBox(const __FlashStringHelper * label, const __FlashStringHelper * id, bool checked, bool disabled) -{ - addRowLabel_tr_id(label, id); - addCheckBox(id, checked, disabled); -} - -void addFormCheckBox(const __FlashStringHelper * label, const String& id, bool checked, bool disabled) -{ - addRowLabel_tr_id(label, id); - addCheckBox(id, checked, disabled); -} - -void addFormCheckBox(const String& label, const String& id, bool checked, bool disabled - #if FEATURE_TOOLTIPS - , const String& tooltip - #endif // if FEATURE_TOOLTIPS - ) -{ - addRowLabel_tr_id(label, id); - addCheckBox(id, checked, disabled - #if FEATURE_TOOLTIPS - , tooltip - #endif // if FEATURE_TOOLTIPS - ); -} - -void addFormCheckBox(LabelType::Enum label, bool checked, bool disabled - #if FEATURE_TOOLTIPS - , const String& tooltip - #endif // if FEATURE_TOOLTIPS - ) { - addFormCheckBox(getLabel(label), getInternalLabel(label), checked, disabled - #if FEATURE_TOOLTIPS - , tooltip - #endif // if FEATURE_TOOLTIPS - ); -} - -void addFormCheckBox_disabled(LabelType::Enum label, bool checked) { - addFormCheckBox(label, checked, true); -} - -// ******************************************************************************** -// Add a Numeric Box form -// ******************************************************************************** -void addFormNumericBox(LabelType::Enum label, int value, int min, int max - #if FEATURE_TOOLTIPS - , const String& tooltip - #endif // if FEATURE_TOOLTIPS - , bool disabled - ) -{ - addFormNumericBox(getLabel(label), getInternalLabel(label), value, min, max - #if FEATURE_TOOLTIPS - , tooltip - #endif // if FEATURE_TOOLTIPS - , disabled - ); -} - -void addFormNumericBox(const __FlashStringHelper * label, - const __FlashStringHelper * id, - int value, - int min, - int max - #if FEATURE_TOOLTIPS - , - const String& tooltip - #endif // if FEATURE_TOOLTIPS - , - bool disabled - ) -{ - addFormNumericBox(String(label), String(id), value, min, max - #if FEATURE_TOOLTIPS - , tooltip - #endif // if FEATURE_TOOLTIPS - , disabled - ); - -} - -void addFormNumericBox(const String& label, const String& id, int value, int min, int max - #if FEATURE_TOOLTIPS - , const String& tooltip - #endif // if FEATURE_TOOLTIPS - , bool disabled - ) -{ - addRowLabel_tr_id(label, id); - addNumericBox(id, value, min, max - #if FEATURE_TOOLTIPS - , F("widenumber"), tooltip - #endif // if FEATURE_TOOLTIPS - , disabled - ); -} - -void addFormFloatNumberBox(LabelType::Enum label, float value, float min, float max, uint8_t nrDecimals, float stepsize - #if FEATURE_TOOLTIPS - , const String& tooltip - #endif // if FEATURE_TOOLTIPS - ) { - addFormFloatNumberBox(getLabel(label), getInternalLabel(label), value, min, max, nrDecimals, stepsize - #if FEATURE_TOOLTIPS - , tooltip - #endif // if FEATURE_TOOLTIPS - ); -} - -void addFormFloatNumberBox(const String& label, - const String& id, - float value, - float min, - float max, - uint8_t nrDecimals, - float stepsize - #if FEATURE_TOOLTIPS - , - const String& tooltip - #endif // if FEATURE_TOOLTIPS - ) -{ - addRowLabel_tr_id(label, id); - addFloatNumberBox(id, value, min, max, nrDecimals, stepsize - #if FEATURE_TOOLTIPS - , tooltip - #endif // if FEATURE_TOOLTIPS - ); -} - -void addFormFloatNumberBox(const __FlashStringHelper * label, - const __FlashStringHelper * id, - float value, - float min, - float max, - uint8_t nrDecimals, - float stepsize - #if FEATURE_TOOLTIPS - , - const String& tooltip - #endif // if FEATURE_TOOLTIPS - ) -{ - addRowLabel_tr_id(label, id); - addFloatNumberBox(id, value, min, max, nrDecimals, stepsize - #if FEATURE_TOOLTIPS - , tooltip - #endif // if FEATURE_TOOLTIPS - ); -} - - - -// ******************************************************************************** -// Add a task selector form -// ******************************************************************************** -void addTaskSelectBox(const String& label, const String& id, taskIndex_t choice) -{ - addRowLabel_tr_id(label, id); - addTaskSelect(id, choice); -} - -// ******************************************************************************** -// Add a Text Box form -// ******************************************************************************** -void addFormTextBox(const __FlashStringHelper * label, - const __FlashStringHelper * id, - const String& value, - int maxlength, - bool readonly, - bool required, - const String& pattern) -{ - addRowLabel_tr_id(label, id); - addTextBox(id, value, maxlength, readonly, required, pattern); -} - -void addFormTextBox(const String & label, - const String & id, - const String & value, - int maxlength, - bool readonly, - bool required, - const String& pattern - #if FEATURE_TOOLTIPS - , const String& tooltip - #endif // if FEATURE_TOOLTIPS - , - const String& datalist - ) -{ - addRowLabel_tr_id(label, id); - addTextBox(id, value, maxlength, readonly, required, pattern, F("wide") - #if FEATURE_TOOLTIPS - , tooltip - #endif // if FEATURE_TOOLTIPS - , datalist - ); -} - -void addFormTextBox(const __FlashStringHelper * classname, - const String& label, - const String& id, - const String& value, - int maxlength, - bool readonly , - bool required , - const String& pattern - #if FEATURE_TOOLTIPS - , - const String& tooltip - #endif // if FEATURE_TOOLTIPS - , - const String& datalist - ) -{ - addRowLabel_tr_id(label, id); - addTextBox(id, value, maxlength, readonly, required, pattern, classname - #if FEATURE_TOOLTIPS - , tooltip - #endif // if FEATURE_TOOLTIPS - , datalist - ); -} - - - -void addFormTextArea(const String & label, - const String & id, - const String & value, - int maxlength, - int rows, - int columns, - bool readonly, - bool required - #if FEATURE_TOOLTIPS - , const String& tooltip - #endif // if FEATURE_TOOLTIPS - ) -{ - addRowLabel_tr_id(label, id); - addTextArea(id, value, maxlength, rows, columns, readonly, required - #if FEATURE_TOOLTIPS - , tooltip - #endif // if FEATURE_TOOLTIPS - ); -} - -// ******************************************************************************** -// Add a Password Box form -// ******************************************************************************** - -void addFormPasswordBox(const String& label, const String& id, const String& password, int maxlength - #if FEATURE_TOOLTIPS - , const String& tooltip - #endif // if FEATURE_TOOLTIPS - ) -{ - addRowLabel_tr_id(label, id); - - addHtml(F(" 0) { - addHtmlAttribute(F("title"), tooltip); - } - #endif // if FEATURE_TOOLTIPS - addHtmlAttribute(F("value"), (password.length() == 0) ? F("") : F("*****")); - addHtml('>'); -} - -bool getFormPassword(const String& id, String& password) -{ - password = webArg(id); - return !equals(password, F("*****")); -} - -// ******************************************************************************** -// Add a IP Box form -// ******************************************************************************** -void addFormIPBox(const __FlashStringHelper *label, - const __FlashStringHelper *id, - const uint8_t ip[4]) -{ - addFormIPBox(String(label), String(id), ip); -} - -void addFormTextBox(const String& label, const String& id, const String& value) -{ - addRowLabel_tr_id(label, id); - - addHtml(strformat( - F(""), - id.c_str(), - id.c_str(), - value.c_str() - )); -} - -void addFormIPBox(const String& label, const String& id, const uint8_t ip[4]) -{ - const bool empty_IP = (ip[0] == 0 && ip[1] == 0 && ip[2] == 0 && ip[3] == 0); - - addFormTextBox(label, id, (empty_IP) ? EMPTY_STRING : formatIP(ip)); -} - -// ******************************************************************************** -// Add a MAC Box form -// ******************************************************************************** -void addFormMACBox(const String& label, const String& id, const MAC_address mac) -{ - addFormTextBox( - label, - id, - mac.all_zero() ? EMPTY_STRING.c_str() : mac.toString()); -} - -// ******************************************************************************** -// Add a IP Access Control select dropdown list -// ******************************************************************************** -void addFormIPaccessControlSelect(const __FlashStringHelper * label, const __FlashStringHelper * id, int choice) -{ - addRowLabel_tr_id(label, id); - addIPaccessControlSelect(id, choice); -} - -// ******************************************************************************** -// a Separator character selector -// ******************************************************************************** -void addFormSeparatorCharInput(const __FlashStringHelper *rowLabel, - const __FlashStringHelper *id, - int value, - const String & charset, - const __FlashStringHelper *additionalText) { - const int len = charset.length() + 1; - String charList[len]; - int charOpts[len]; - - charList[0] = F("None"); - charOpts[0] = 0; - - for (uint16_t i = 0; i < charset.length(); i++) { - charList[i + 1] = charset[i]; - charOpts[i + 1] = static_cast(charset[i]); - } - addFormSelector(rowLabel, id, len, charList, charOpts, value); - - if (!String(additionalText).isEmpty()) { - addUnit(additionalText); - } -} - -// ******************************************************************************** -// Add a selector form -// ******************************************************************************** -void addFormPinSelect(PinSelectPurpose purpose, const String& label, const __FlashStringHelper * id, int choice) -{ - addRowLabel_tr_id(label, id); - addPinSelect(purpose, id, choice); -} - -void addFormPinSelect(PinSelectPurpose purpose, const __FlashStringHelper * label, const __FlashStringHelper * id, int choice) -{ - addRowLabel_tr_id(label, id); - addPinSelect(purpose, id, choice); -} - -/* -void addFormPinSelect(const String& label, const __FlashStringHelper * id, int choice) -{ - addRowLabel_tr_id(label, id); - addPinSelect(PinSelectPurpose::Generic, id, choice); -} - -void addFormPinSelect(const __FlashStringHelper * label, - const __FlashStringHelper * id, - int choice) -{ - addRowLabel_tr_id(label, id); - addPinSelect(PinSelectPurpose::Generic, id, choice); -} - -void addFormPinSelect(const String& label, const String & id, int choice) -{ - addRowLabel_tr_id(label, id); - addPinSelect(PinSelectPurpose::Generic, id, choice); -} -*/ - -void addFormPinSelectI2C(const String& label, const String& id, int choice) -{ - addRowLabel_tr_id(label, id); - addPinSelect(PinSelectPurpose::I2C, id, choice); -} - -void addFormSelectorI2C(const String& id, - int addressCount, - const uint8_t addresses[], - int selectedIndex, - uint8_t defaultAddress - #if FEATURE_TOOLTIPS - , const String& tooltip - #endif // if FEATURE_TOOLTIPS - ) -{ - addRowLabel_tr_id(F("I2C Address"), id); - do_addSelector_Head(id, F(""), EMPTY_STRING, false - #if FEATURE_TOOLTIPS - , tooltip - #endif // if FEATURE_TOOLTIPS - ); - - for (int x = 0; x < addressCount; x++) - { - String option = formatToHex_decimal(addresses[x]); - - if (((x == 0) && (defaultAddress == 0)) || (defaultAddress == addresses[x])) { - option += F(" - (default)"); - } - addSelector_Item(option, addresses[x], addresses[x] == selectedIndex); - } - addSelector_Foot(); -} - -void addFormSelector(const __FlashStringHelper * label, const __FlashStringHelper * id, int optionCount, const __FlashStringHelper * options[], const int indices[], int selectedIndex, bool reloadonchange) -{ - addFormSelector(String(label), String(id), optionCount, options, indices, nullptr, selectedIndex, reloadonchange); -} - -void addFormSelector(const __FlashStringHelper * label, const String& id, int optionCount, const __FlashStringHelper * options[], const int indices[], int selectedIndex, bool reloadonchange) -{ - addFormSelector(String(label), id, optionCount, options, indices, nullptr, selectedIndex, reloadonchange); -} - -void addFormSelector(const String& label, const String& id, int optionCount, const __FlashStringHelper * options[], const int indices[], int selectedIndex) -{ - addFormSelector(label, id, optionCount, options, indices, nullptr, selectedIndex, false); -} - -void addFormSelector(const __FlashStringHelper * label, const __FlashStringHelper * id, int optionCount, const String options[], const int indices[], int selectedIndex) -{ - addFormSelector(String(label), String(id), optionCount, options, indices, nullptr, selectedIndex, false); -} - -void addFormSelector(const String & label, - const String & id, - int optionCount, - const String options[], - const int indices[], - int selectedIndex - #if FEATURE_TOOLTIPS - , const String& tooltip - #endif // if FEATURE_TOOLTIPS - ) -{ - addFormSelector(label, id, optionCount, options, indices, nullptr, selectedIndex, false - #if FEATURE_TOOLTIPS - , tooltip - #endif // if FEATURE_TOOLTIPS - ); -} - -void addFormSelector(const String& label, - const String& id, - int optionCount, - const __FlashStringHelper * options[], - const int indices[], - int selectedIndex, - bool reloadonchange) -{ - addFormSelector(label, id, optionCount, options, indices, nullptr, selectedIndex, reloadonchange); -} - -void addFormSelector(const String& label, - const String& id, - int optionCount, - const __FlashStringHelper * options[], - const int indices[], - const String attr[], - int selectedIndex, - bool reloadonchange) -{ - addRowLabel_tr_id(label, id); - addSelector(id, optionCount, options, indices, attr, selectedIndex, reloadonchange, true); -} - -void addFormSelector(const String& label, - const String& id, - int optionCount, - const String options[], - const int indices[], - int selectedIndex, - bool reloadonchange - #if FEATURE_TOOLTIPS - , const String& tooltip - #endif // if FEATURE_TOOLTIPS - ) -{ - addFormSelector(label, id, optionCount, options, indices, nullptr, selectedIndex, reloadonchange - #if FEATURE_TOOLTIPS - , tooltip - #endif // if FEATURE_TOOLTIPS - ); -} - -void addFormSelector(const String & label, - const String & id, - int optionCount, - const String options[], - const int indices[], - const String attr[], - int selectedIndex, - bool reloadonchange - #if FEATURE_TOOLTIPS - , const String& tooltip - #endif // if FEATURE_TOOLTIPS - ) -{ - addRowLabel_tr_id(label, id); - addSelector(id, optionCount, options, indices, attr, selectedIndex, reloadonchange, true, F("wide") - #if FEATURE_TOOLTIPS - , tooltip - #endif // if FEATURE_TOOLTIPS - ); -} - -void addFormSelector_script(const __FlashStringHelper * label, - const __FlashStringHelper * id, - int optionCount, - const __FlashStringHelper * options[], - const int indices[], - const String attr[], - int selectedIndex, - const __FlashStringHelper * onChangeCall - #if FEATURE_TOOLTIPS - , const String& tooltip - #endif // if FEATURE_TOOLTIPS - ) -{ - addRowLabel_tr_id(label, id); - do_addSelector_Head(id, F("wide"), onChangeCall, false - #if FEATURE_TOOLTIPS - , tooltip - #endif // if FEATURE_TOOLTIPS - ); - addSelector_options(optionCount, options, indices, attr, selectedIndex); - addSelector_Foot(); -} - -void addFormSelector_script(const __FlashStringHelper * label, - const __FlashStringHelper * id, - int optionCount, - const String options[], - const int indices[], - const String attr[], - int selectedIndex, - const __FlashStringHelper * onChangeCall - #if FEATURE_TOOLTIPS - , const String& tooltip - #endif // if FEATURE_TOOLTIPS - ) -{ - addRowLabel_tr_id(label, id); - do_addSelector_Head(id, F("wide"), onChangeCall, false - #if FEATURE_TOOLTIPS - , tooltip - #endif // if FEATURE_TOOLTIPS - ); - addSelector_options(optionCount, options, indices, attr, selectedIndex); - addSelector_Foot(); -} - -void addFormSelector_YesNo(const __FlashStringHelper * label, - const __FlashStringHelper * id, - int selectedIndex, - bool reloadonchange) -{ - addFormSelector_YesNo(label, String(id), selectedIndex, reloadonchange); -} - -void addFormSelector_YesNo(const __FlashStringHelper * label, - const String& id, - int selectedIndex, - bool reloadonchange) -{ - const __FlashStringHelper *optionsNoYes[] = { F("No"), F("Yes") }; - int optionValuesNoYes[] = { 0, 1 }; - addFormSelector(label, id, NR_ELEMENTS(optionValuesNoYes), optionsNoYes, optionValuesNoYes, selectedIndex, reloadonchange); -} - - - -// ******************************************************************************** -// Add a GPIO pin select dropdown list -// ******************************************************************************** -void addFormPinStateSelect(int gpio, int choice) -{ - bool enabled = true; - - if (isSerialConsolePin(gpio)) { - // do not add the pin state select for these pins. - enabled = false; - } - if (Settings.isEthernetPin(gpio)) { - // do not add the pin state select for non-optional Ethernet pins - enabled = false; - } - int pinnr = -1; - bool input, output, warning; - - if (getGpioInfo(gpio, pinnr, input, output, warning)) { - const String id = String('p') + gpio; - addRowLabel_tr_id( - concat( - F("Pin mode "), - createGPIO_label(gpio, pinnr, input, output, warning)), - id); - bool hasPullUp, hasPullDown; - getGpioPullResistor(gpio, hasPullUp, hasPullDown); - int nr_options = 0; - const __FlashStringHelper * options[6]; - int option_val[6]; - options[nr_options] = F("Default"); - option_val[nr_options] = static_cast(PinBootState::Default_state); - ++nr_options; - - if (output) { - options[nr_options] = F("Output Low"); - option_val[nr_options] = static_cast(PinBootState::Output_low); - ++nr_options; - options[nr_options] = F("Output High"); - option_val[nr_options] = static_cast(PinBootState::Output_high); - ++nr_options; - } - - if (input) { - if (hasPullUp) { - options[nr_options] = F("Input pullup"); - option_val[nr_options] = static_cast(PinBootState::Input_pullup); - ++nr_options; - } - - if (hasPullDown) { - options[nr_options] = F("Input pulldown"); - option_val[nr_options] = static_cast(PinBootState::Input_pulldown); - ++nr_options; - } - - if (!hasPullUp && !hasPullDown) { - options[nr_options] = F("Input"); - option_val[nr_options] = static_cast(PinBootState::Input); - ++nr_options; - } - } - addSelector(id, nr_options, options, option_val, nullptr, choice, false, enabled); - { - const String conflict = getConflictingUse(gpio); - if (!conflict.isEmpty()) { - addUnit(conflict); - } - } - } -} - -// ******************************************************************************** -// Retrieve return values from form/checkbox. -// ******************************************************************************** -int getFormItemInt(const __FlashStringHelper * key, int defaultValue) { - return getFormItemInt(String(key), defaultValue); -} - -int getFormItemInt(const String& key, int defaultValue) { - int value = defaultValue; - - getCheckWebserverArg_int(key, value); - return value; -} - -bool getCheckWebserverArg_int(const String& key, int& value) { - const String valueStr = webArg(key); - if (valueStr.isEmpty()) return false; - // FIXME TD-er: Since ESP_IDF 5.1 int32_t != int - int32_t tmp{}; - const bool res = validIntFromString(valueStr, tmp); - value = tmp; - return res; -} - -bool update_whenset_FormItemInt(const __FlashStringHelper * key, - int & value) -{ - return update_whenset_FormItemInt(String(key), value); -} - -bool update_whenset_FormItemInt(const String& key, int& value) { - int tmpVal; - - if (getCheckWebserverArg_int(key, tmpVal)) { - value = tmpVal; - return true; - } - return false; -} - -bool update_whenset_FormItemInt(const __FlashStringHelper * key, - uint8_t& value) -{ - return update_whenset_FormItemInt(String(key), value); -} - - -bool update_whenset_FormItemInt(const String& key, uint8_t& value) { - int tmpVal; - - if (getCheckWebserverArg_int(key, tmpVal)) { - value = tmpVal; - return true; - } - return false; -} - -// Note: Checkbox values will not appear in POST Form data if unchecked. -// So if webserver does not have an argument for a checkbox form, it means it should be considered unchecked. -bool isFormItemChecked(const __FlashStringHelper * id) -{ - return isFormItemChecked(String(id)); -} - -bool isFormItemChecked(const String& id) -{ - return equals(webArg(id), F("on")); -} - -bool isFormItemChecked(const LabelType::Enum& id) -{ - return isFormItemChecked(getInternalLabel(id)); -} - -int getFormItemInt(const __FlashStringHelper * id) -{ - return getFormItemInt(String(id), 0); -} - -int getFormItemInt(const String& id) -{ - return getFormItemInt(id, 0); -} - -int getFormItemInt(const LabelType::Enum& id) -{ - return getFormItemInt(getInternalLabel(id), 0); -} - -float getFormItemFloat(const __FlashStringHelper * id) -{ - return getFormItemFloat(String(id)); -} - -float getFormItemFloat(const String& id) -{ - const String val = webArg(id); - float res = 0.0; - if (val.length() > 0) { - validFloatFromString(val, res); - } - return res; -} - -float getFormItemFloat(const LabelType::Enum& id) -{ - return getFormItemFloat(getInternalLabel(id)); -} - -bool isFormItem(const String& id) -{ - return !webArg(id).isEmpty(); -} - -void copyFormPassword(const __FlashStringHelper * id, char *pPassword, int maxlength) -{ - String password; - - if (getFormPassword(id, password)) { - safe_strncpy(pPassword, password.c_str(), maxlength); - } -} +#include "../WebServer/Markup_Forms.h" + +#include "../WebServer/ESPEasy_WebServer.h" +#include "../WebServer/AccessControl.h" +#include "../WebServer/Markup.h" +#include "../WebServer/HTML_wrappers.h" + +#include "../Globals/Settings.h" + +#include "../Helpers/Hardware_GPIO.h" +#include "../Helpers/Numerical.h" +#include "../Helpers/StringConverter.h" +#include "../Helpers/StringGenerator_GPIO.h" + +// ******************************************************************************** +// Add a separator as row start +// ******************************************************************************** +void addFormSeparator(int clspan) +{ + addHtml(strformat( + F("
"), + clspan)); +} + +// ******************************************************************************** +// Add a note as row start +// ******************************************************************************** +void addFormNote(const __FlashStringHelper * text) +{ + addRowLabel_tr_id(EMPTY_STRING, EMPTY_STRING); + addHtml(F("
Note: ")); + addHtml(text); + addHtml(F("
")); +} + +void addFormNote(const String& text, const String& id) +{ + if (text.isEmpty()) return; + addRowLabel_tr_id(EMPTY_STRING, id); + addHtmlDiv(F("note"), concat(F("Note: "), text)); +} + +void addFormNote(const LabelType::Enum& label) +{ + addUnit(getFormUnit(label)); + addFormNote(getFormNote(label)); +} + +// ******************************************************************************** +// Create Forms +// ******************************************************************************** + + +// ******************************************************************************** +// Add a checkbox Form +// ******************************************************************************** + +void addFormCheckBox_disabled(const String& label, const String& id, bool checked + #if FEATURE_TOOLTIPS + , const String& tooltip + #endif // if FEATURE_TOOLTIPS + ) { + addFormCheckBox(label, id, checked, true + #if FEATURE_TOOLTIPS + , tooltip + #endif // if FEATURE_TOOLTIPS + ); +} + +void addFormCheckBox(const __FlashStringHelper * label, const __FlashStringHelper * id, bool checked, bool disabled) +{ + addRowLabel_tr_id(label, id); + addCheckBox(id, checked, disabled); +} + +void addFormCheckBox(const __FlashStringHelper * label, const String& id, bool checked, bool disabled) +{ + addRowLabel_tr_id(label, id); + addCheckBox(id, checked, disabled); +} + +void addFormCheckBox(const String& label, const String& id, bool checked, bool disabled + #if FEATURE_TOOLTIPS + , const String& tooltip + #endif // if FEATURE_TOOLTIPS + ) +{ + addRowLabel_tr_id(label, id); + addCheckBox(id, checked, disabled + #if FEATURE_TOOLTIPS + , tooltip + #endif // if FEATURE_TOOLTIPS + ); +} + +void addFormCheckBox(LabelType::Enum label, bool checked, bool disabled + #if FEATURE_TOOLTIPS + , const String& tooltip + #endif // if FEATURE_TOOLTIPS + ) { + addFormCheckBox(getLabel(label), getInternalLabel(label), checked, disabled + #if FEATURE_TOOLTIPS + , tooltip + #endif // if FEATURE_TOOLTIPS + ); + addFormNote(label); +} + +void addFormCheckBox_disabled(LabelType::Enum label, bool checked) { + addFormCheckBox(label, checked, true); +} + +// ******************************************************************************** +// Add a Numeric Box form +// ******************************************************************************** +void addFormNumericBox(LabelType::Enum label, int value, int min, int max + #if FEATURE_TOOLTIPS + , const String& tooltip + #endif // if FEATURE_TOOLTIPS + , bool disabled + ) +{ + addFormNumericBox(getLabel(label), getInternalLabel(label), value, min, max + #if FEATURE_TOOLTIPS + , tooltip + #endif // if FEATURE_TOOLTIPS + , disabled + ); + addFormNote(label); +} + +void addFormNumericBox(const __FlashStringHelper * label, + const __FlashStringHelper * id, + int value, + int min, + int max + #if FEATURE_TOOLTIPS + , + const String& tooltip + #endif // if FEATURE_TOOLTIPS + , + bool disabled + ) +{ + addFormNumericBox(String(label), String(id), value, min, max + #if FEATURE_TOOLTIPS + , tooltip + #endif // if FEATURE_TOOLTIPS + , disabled + ); + +} + +void addFormNumericBox(const String& label, const String& id, int value, int min, int max + #if FEATURE_TOOLTIPS + , const String& tooltip + #endif // if FEATURE_TOOLTIPS + , bool disabled + ) +{ + addRowLabel_tr_id(label, id); + addNumericBox(id, value, min, max + #if FEATURE_TOOLTIPS + , F("widenumber"), tooltip + #endif // if FEATURE_TOOLTIPS + , disabled + ); +} + +void addFormFloatNumberBox(LabelType::Enum label, float value, float min, float max, uint8_t nrDecimals, float stepsize + #if FEATURE_TOOLTIPS + , const String& tooltip + #endif // if FEATURE_TOOLTIPS + ) { + addFormFloatNumberBox(getLabel(label), getInternalLabel(label), value, min, max, nrDecimals, stepsize + #if FEATURE_TOOLTIPS + , tooltip + #endif // if FEATURE_TOOLTIPS + ); + addFormNote(label); +} + +void addFormFloatNumberBox(const String& label, + const String& id, + float value, + float min, + float max, + uint8_t nrDecimals, + float stepsize + #if FEATURE_TOOLTIPS + , + const String& tooltip + #endif // if FEATURE_TOOLTIPS + ) +{ + addRowLabel_tr_id(label, id); + addFloatNumberBox(id, value, min, max, nrDecimals, stepsize + #if FEATURE_TOOLTIPS + , tooltip + #endif // if FEATURE_TOOLTIPS + ); +} + +void addFormFloatNumberBox(const __FlashStringHelper * label, + const __FlashStringHelper * id, + float value, + float min, + float max, + uint8_t nrDecimals, + float stepsize + #if FEATURE_TOOLTIPS + , + const String& tooltip + #endif // if FEATURE_TOOLTIPS + ) +{ + addRowLabel_tr_id(label, id); + addFloatNumberBox(id, value, min, max, nrDecimals, stepsize + #if FEATURE_TOOLTIPS + , tooltip + #endif // if FEATURE_TOOLTIPS + ); +} + + + +// ******************************************************************************** +// Add a task selector form +// ******************************************************************************** +void addTaskSelectBox(const String& label, const String& id, taskIndex_t choice) +{ + addRowLabel_tr_id(label, id); + addTaskSelect(id, choice); +} + +// ******************************************************************************** +// Add a Text Box form +// ******************************************************************************** +void addFormTextBox(const __FlashStringHelper * label, + const __FlashStringHelper * id, + const String& value, + int maxlength, + bool readonly, + bool required, + const String& pattern) +{ + addRowLabel_tr_id(label, id); + addTextBox(id, value, maxlength, readonly, required, pattern); +} + +void addFormTextBox(const String & label, + const String & id, + const String & value, + int maxlength, + bool readonly, + bool required, + const String& pattern + #if FEATURE_TOOLTIPS + , const String& tooltip + #endif // if FEATURE_TOOLTIPS + , + const String& datalist + ) +{ + addRowLabel_tr_id(label, id); + addTextBox(id, value, maxlength, readonly, required, pattern, F("wide") + #if FEATURE_TOOLTIPS + , tooltip + #endif // if FEATURE_TOOLTIPS + , datalist + ); +} + +void addFormTextBox(const __FlashStringHelper * classname, + const String& label, + const String& id, + const String& value, + int maxlength, + bool readonly , + bool required , + const String& pattern + #if FEATURE_TOOLTIPS + , + const String& tooltip + #endif // if FEATURE_TOOLTIPS + , + const String& datalist + ) +{ + addRowLabel_tr_id(label, id); + addTextBox(id, value, maxlength, readonly, required, pattern, classname + #if FEATURE_TOOLTIPS + , tooltip + #endif // if FEATURE_TOOLTIPS + , datalist + ); +} + + + +void addFormTextArea(const String & label, + const String & id, + const String & value, + int maxlength, + int rows, + int columns, + bool readonly, + bool required + #if FEATURE_TOOLTIPS + , const String& tooltip + #endif // if FEATURE_TOOLTIPS + ) +{ + addRowLabel_tr_id(label, id); + addTextArea(id, value, maxlength, rows, columns, readonly, required + #if FEATURE_TOOLTIPS + , tooltip + #endif // if FEATURE_TOOLTIPS + ); +} + +// ******************************************************************************** +// Add a Password Box form +// ******************************************************************************** + +void addFormPasswordBox(const String& label, const String& id, const String& password, int maxlength + #if FEATURE_TOOLTIPS + , const String& tooltip + #endif // if FEATURE_TOOLTIPS + ) +{ + addRowLabel_tr_id(label, id); + + addHtml(F(" 0) { + addHtmlAttribute(F("title"), tooltip); + } + #endif // if FEATURE_TOOLTIPS + addHtmlAttribute(F("value"), (password.length() == 0) ? F("") : F("*****")); + addHtml('>'); +} + +bool getFormPassword(const String& id, String& password) +{ + password = webArg(id); + return !equals(password, F("*****")); +} + +// ******************************************************************************** +// Add a IP Box form +// ******************************************************************************** +void addFormIPBox(const __FlashStringHelper *label, + const __FlashStringHelper *id, + const uint8_t ip[4]) +{ + addFormIPBox(String(label), String(id), ip); +} + +void addFormTextBox(const String& label, const String& id, const String& value) +{ + addRowLabel_tr_id(label, id); + + addHtml(strformat( + F(""), + id.c_str(), + id.c_str(), + value.c_str() + )); +} + +void addFormIPBox(const String& label, const String& id, const uint8_t ip[4]) +{ + const bool empty_IP = (ip[0] == 0 && ip[1] == 0 && ip[2] == 0 && ip[3] == 0); + + addFormTextBox(label, id, (empty_IP) ? EMPTY_STRING : formatIP(ip)); +} + +// ******************************************************************************** +// Add a MAC Box form +// ******************************************************************************** +void addFormMACBox(const String& label, const String& id, const MAC_address mac) +{ + addFormTextBox( + label, + id, + mac.all_zero() ? EMPTY_STRING.c_str() : mac.toString()); +} + +// ******************************************************************************** +// Add a IP Access Control select dropdown list +// ******************************************************************************** +void addFormIPaccessControlSelect(const __FlashStringHelper * label, const __FlashStringHelper * id, int choice) +{ + addRowLabel_tr_id(label, id); + addIPaccessControlSelect(id, choice); +} + +// ******************************************************************************** +// a Separator character selector +// ******************************************************************************** +void addFormSeparatorCharInput(const __FlashStringHelper *rowLabel, + const __FlashStringHelper *id, + int value, + const String & charset, + const __FlashStringHelper *additionalText) { + const int len = charset.length() + 1; + String charList[len]; + int charOpts[len]; + + charList[0] = F("None"); + charOpts[0] = 0; + + for (uint16_t i = 0; i < charset.length(); i++) { + charList[i + 1] = charset[i]; + charOpts[i + 1] = static_cast(charset[i]); + } + addFormSelector(rowLabel, id, len, charList, charOpts, value); + + if (!String(additionalText).isEmpty()) { + addUnit(additionalText); + } +} + +// ******************************************************************************** +// Add a selector form +// ******************************************************************************** +void addFormPinSelect(PinSelectPurpose purpose, const String& label, const __FlashStringHelper * id, int choice) +{ + addRowLabel_tr_id(label, id); + addPinSelect(purpose, id, choice); +} + +void addFormPinSelect(PinSelectPurpose purpose, const __FlashStringHelper * label, const __FlashStringHelper * id, int choice) +{ + addRowLabel_tr_id(label, id); + addPinSelect(purpose, id, choice); +} + +/* +void addFormPinSelect(const String& label, const __FlashStringHelper * id, int choice) +{ + addRowLabel_tr_id(label, id); + addPinSelect(PinSelectPurpose::Generic, id, choice); +} + +void addFormPinSelect(const __FlashStringHelper * label, + const __FlashStringHelper * id, + int choice) +{ + addRowLabel_tr_id(label, id); + addPinSelect(PinSelectPurpose::Generic, id, choice); +} + +void addFormPinSelect(const String& label, const String & id, int choice) +{ + addRowLabel_tr_id(label, id); + addPinSelect(PinSelectPurpose::Generic, id, choice); +} +*/ + +void addFormPinSelectI2C(const String& label, const String& id, int choice) +{ + addRowLabel_tr_id(label, id); + addPinSelect(PinSelectPurpose::I2C, id, choice); +} + +void addFormSelectorI2C(const String& id, + int addressCount, + const uint8_t addresses[], + int selectedIndex, + uint8_t defaultAddress + #if FEATURE_TOOLTIPS + , const String& tooltip + #endif // if FEATURE_TOOLTIPS + ) +{ + addRowLabel_tr_id(F("I2C Address"), id); + do_addSelector_Head(id, F(""), EMPTY_STRING, false + #if FEATURE_TOOLTIPS + , tooltip + #endif // if FEATURE_TOOLTIPS + ); + + for (int x = 0; x < addressCount; x++) + { + String option = formatToHex_decimal(addresses[x]); + + if (((x == 0) && (defaultAddress == 0)) || (defaultAddress == addresses[x])) { + option += F(" - (default)"); + } + addSelector_Item(option, addresses[x], addresses[x] == selectedIndex); + } + addSelector_Foot(); +} + +void addFormSelector(const __FlashStringHelper * label, const __FlashStringHelper * id, int optionCount, const __FlashStringHelper * options[], const int indices[], int selectedIndex, bool reloadonchange) +{ + addFormSelector(String(label), String(id), optionCount, options, indices, nullptr, selectedIndex, reloadonchange); +} + +void addFormSelector(const __FlashStringHelper * label, const String& id, int optionCount, const __FlashStringHelper * options[], const int indices[], int selectedIndex, bool reloadonchange) +{ + addFormSelector(String(label), id, optionCount, options, indices, nullptr, selectedIndex, reloadonchange); +} + +void addFormSelector(const String& label, const String& id, int optionCount, const __FlashStringHelper * options[], const int indices[], int selectedIndex) +{ + addFormSelector(label, id, optionCount, options, indices, nullptr, selectedIndex, false); +} + +void addFormSelector(const __FlashStringHelper * label, const __FlashStringHelper * id, int optionCount, const String options[], const int indices[], int selectedIndex) +{ + addFormSelector(String(label), String(id), optionCount, options, indices, nullptr, selectedIndex, false); +} + +void addFormSelector(const String & label, + const String & id, + int optionCount, + const String options[], + const int indices[], + int selectedIndex + #if FEATURE_TOOLTIPS + , const String& tooltip + #endif // if FEATURE_TOOLTIPS + ) +{ + addFormSelector(label, id, optionCount, options, indices, nullptr, selectedIndex, false + #if FEATURE_TOOLTIPS + , tooltip + #endif // if FEATURE_TOOLTIPS + ); +} + +void addFormSelector(const String& label, + const String& id, + int optionCount, + const __FlashStringHelper * options[], + const int indices[], + int selectedIndex, + bool reloadonchange) +{ + addFormSelector(label, id, optionCount, options, indices, nullptr, selectedIndex, reloadonchange); +} + +void addFormSelector(const String& label, + const String& id, + int optionCount, + const __FlashStringHelper * options[], + const int indices[], + const String attr[], + int selectedIndex, + bool reloadonchange) +{ + addRowLabel_tr_id(label, id); + addSelector(id, optionCount, options, indices, attr, selectedIndex, reloadonchange, true); +} + +void addFormSelector(const String& label, + const String& id, + int optionCount, + const String options[], + const int indices[], + int selectedIndex, + bool reloadonchange + #if FEATURE_TOOLTIPS + , const String& tooltip + #endif // if FEATURE_TOOLTIPS + ) +{ + addFormSelector(label, id, optionCount, options, indices, nullptr, selectedIndex, reloadonchange + #if FEATURE_TOOLTIPS + , tooltip + #endif // if FEATURE_TOOLTIPS + ); +} + +void addFormSelector(const String & label, + const String & id, + int optionCount, + const String options[], + const int indices[], + const String attr[], + int selectedIndex, + bool reloadonchange + #if FEATURE_TOOLTIPS + , const String& tooltip + #endif // if FEATURE_TOOLTIPS + ) +{ + addRowLabel_tr_id(label, id); + addSelector(id, optionCount, options, indices, attr, selectedIndex, reloadonchange, true, F("wide") + #if FEATURE_TOOLTIPS + , tooltip + #endif // if FEATURE_TOOLTIPS + ); +} + +void addFormSelector_script(const __FlashStringHelper * label, + const __FlashStringHelper * id, + int optionCount, + const __FlashStringHelper * options[], + const int indices[], + const String attr[], + int selectedIndex, + const __FlashStringHelper * onChangeCall + #if FEATURE_TOOLTIPS + , const String& tooltip + #endif // if FEATURE_TOOLTIPS + ) +{ + addRowLabel_tr_id(label, id); + do_addSelector_Head(id, F("wide"), onChangeCall, false + #if FEATURE_TOOLTIPS + , tooltip + #endif // if FEATURE_TOOLTIPS + ); + addSelector_options(optionCount, options, indices, attr, selectedIndex); + addSelector_Foot(); +} + +void addFormSelector_script(const __FlashStringHelper * label, + const __FlashStringHelper * id, + int optionCount, + const String options[], + const int indices[], + const String attr[], + int selectedIndex, + const __FlashStringHelper * onChangeCall + #if FEATURE_TOOLTIPS + , const String& tooltip + #endif // if FEATURE_TOOLTIPS + ) +{ + addRowLabel_tr_id(label, id); + do_addSelector_Head(id, F("wide"), onChangeCall, false + #if FEATURE_TOOLTIPS + , tooltip + #endif // if FEATURE_TOOLTIPS + ); + addSelector_options(optionCount, options, indices, attr, selectedIndex); + addSelector_Foot(); +} + +void addFormSelector_YesNo(const __FlashStringHelper * label, + const __FlashStringHelper * id, + int selectedIndex, + bool reloadonchange) +{ + addFormSelector_YesNo(label, String(id), selectedIndex, reloadonchange); +} + +void addFormSelector_YesNo(const __FlashStringHelper * label, + const String& id, + int selectedIndex, + bool reloadonchange) +{ + const __FlashStringHelper *optionsNoYes[] = { F("No"), F("Yes") }; + int optionValuesNoYes[] = { 0, 1 }; + addFormSelector(label, id, NR_ELEMENTS(optionValuesNoYes), optionsNoYes, optionValuesNoYes, selectedIndex, reloadonchange); +} + + + +// ******************************************************************************** +// Add a GPIO pin select dropdown list +// ******************************************************************************** +void addFormPinStateSelect(int gpio, int choice) +{ + bool enabled = true; + + if (isSerialConsolePin(gpio)) { + // do not add the pin state select for these pins. + enabled = false; + } + if (Settings.isEthernetPin(gpio)) { + // do not add the pin state select for non-optional Ethernet pins + enabled = false; + } + int pinnr = -1; + bool input, output, warning; + + if (getGpioInfo(gpio, pinnr, input, output, warning)) { + const String id = String('p') + gpio; + addRowLabel_tr_id( + concat( + F("Pin mode "), + createGPIO_label(gpio, pinnr, input, output, warning)), + id); + bool hasPullUp, hasPullDown; + getGpioPullResistor(gpio, hasPullUp, hasPullDown); + int nr_options = 0; + const __FlashStringHelper * options[6]; + int option_val[6]; + options[nr_options] = F("Default"); + option_val[nr_options] = static_cast(PinBootState::Default_state); + ++nr_options; + + if (output) { + options[nr_options] = F("Output Low"); + option_val[nr_options] = static_cast(PinBootState::Output_low); + ++nr_options; + options[nr_options] = F("Output High"); + option_val[nr_options] = static_cast(PinBootState::Output_high); + ++nr_options; + } + + if (input) { + if (hasPullUp) { + options[nr_options] = F("Input pullup"); + option_val[nr_options] = static_cast(PinBootState::Input_pullup); + ++nr_options; + } + + if (hasPullDown) { + options[nr_options] = F("Input pulldown"); + option_val[nr_options] = static_cast(PinBootState::Input_pulldown); + ++nr_options; + } + + if (!hasPullUp && !hasPullDown) { + options[nr_options] = F("Input"); + option_val[nr_options] = static_cast(PinBootState::Input); + ++nr_options; + } + } + addSelector(id, nr_options, options, option_val, nullptr, choice, false, enabled); + { + const String conflict = getConflictingUse(gpio); + if (!conflict.isEmpty()) { + addUnit(conflict); + } + } + } +} + +// ******************************************************************************** +// Retrieve return values from form/checkbox. +// ******************************************************************************** +int getFormItemInt(const __FlashStringHelper * key, int defaultValue) { + return getFormItemInt(String(key), defaultValue); +} + +int getFormItemInt(const String& key, int defaultValue) { + int value = defaultValue; + + getCheckWebserverArg_int(key, value); + return value; +} + +bool getCheckWebserverArg_int(const String& key, int& value) { + const String valueStr = webArg(key); + if (valueStr.isEmpty()) return false; + // FIXME TD-er: Since ESP_IDF 5.1 int32_t != int + int32_t tmp{}; + const bool res = validIntFromString(valueStr, tmp); + value = tmp; + return res; +} + +bool update_whenset_FormItemInt(const __FlashStringHelper * key, + int & value) +{ + return update_whenset_FormItemInt(String(key), value); +} + +bool update_whenset_FormItemInt(const String& key, int& value) { + int tmpVal; + + if (getCheckWebserverArg_int(key, tmpVal)) { + value = tmpVal; + return true; + } + return false; +} + +bool update_whenset_FormItemInt(const __FlashStringHelper * key, + uint8_t& value) +{ + return update_whenset_FormItemInt(String(key), value); +} + + +bool update_whenset_FormItemInt(const String& key, uint8_t& value) { + int tmpVal; + + if (getCheckWebserverArg_int(key, tmpVal)) { + value = tmpVal; + return true; + } + return false; +} + +// Note: Checkbox values will not appear in POST Form data if unchecked. +// So if webserver does not have an argument for a checkbox form, it means it should be considered unchecked. +bool isFormItemChecked(const __FlashStringHelper * id) +{ + return isFormItemChecked(String(id)); +} + +bool isFormItemChecked(const String& id) +{ + return equals(webArg(id), F("on")); +} + +bool isFormItemChecked(const LabelType::Enum& id) +{ + return isFormItemChecked(getInternalLabel(id)); +} + +int getFormItemInt(const __FlashStringHelper * id) +{ + return getFormItemInt(String(id), 0); +} + +int getFormItemInt(const String& id) +{ + return getFormItemInt(id, 0); +} + +int getFormItemInt(const LabelType::Enum& id) +{ + return getFormItemInt(getInternalLabel(id), 0); +} + +float getFormItemFloat(const __FlashStringHelper * id) +{ + return getFormItemFloat(String(id)); +} + +float getFormItemFloat(const String& id) +{ + const String val = webArg(id); + float res = 0.0; + if (val.length() > 0) { + validFloatFromString(val, res); + } + return res; +} + +float getFormItemFloat(const LabelType::Enum& id) +{ + return getFormItemFloat(getInternalLabel(id)); +} + +bool isFormItem(const String& id) +{ + return !webArg(id).isEmpty(); +} + +void copyFormPassword(const __FlashStringHelper * id, char *pPassword, int maxlength) +{ + String password; + + if (getFormPassword(id, password)) { + safe_strncpy(pPassword, password.c_str(), maxlength); + } +} diff --git a/src/src/WebServer/Markup_Forms.h b/src/src/WebServer/Markup_Forms.h index 6c40f9e1aa..71a5ea34a9 100644 --- a/src/src/WebServer/Markup_Forms.h +++ b/src/src/WebServer/Markup_Forms.h @@ -1,444 +1,445 @@ -#ifndef WEBSERVER_WEBSERVER_MARKUP_FORMS_H -#define WEBSERVER_WEBSERVER_MARKUP_FORMS_H - -#include "../WebServer/common.h" - -#include "../DataStructs/MAC_address.h" -#include "../Globals/Plugins.h" -#include "../Helpers/StringGenerator_GPIO.h" - - -// ******************************************************************************** -// Add a separator as row start -// ******************************************************************************** -void addFormSeparator(int clspan); - -// ******************************************************************************** -// Add a note as row start -// ******************************************************************************** -void addFormNote(const __FlashStringHelper * text); -void addFormNote(const String& text, const String& id = EMPTY_STRING); - -// ******************************************************************************** -// Create Forms -// ******************************************************************************** - - -// ******************************************************************************** -// Add a checkbox Form -// ******************************************************************************** - -void addFormCheckBox_disabled(const String& label, - const String& id, - bool checked - #if FEATURE_TOOLTIPS - , - const String& tooltip = EMPTY_STRING - #endif // if FEATURE_TOOLTIPS - ); - -void addFormCheckBox(const String& label, - const String& id, - bool checked, - bool disabled = false - #if FEATURE_TOOLTIPS - , - const String& tooltip = EMPTY_STRING - #endif // if FEATURE_TOOLTIPS - ); - -void addFormCheckBox(LabelType::Enum label, - bool checked, - bool disabled = false - #if FEATURE_TOOLTIPS - , - const String & tooltip = EMPTY_STRING - #endif // if FEATURE_TOOLTIPS - ); - -void addFormCheckBox_disabled(LabelType::Enum label, - bool checked); -void addFormCheckBox(const __FlashStringHelper * label, const __FlashStringHelper * id, bool checked, bool disabled = false); -void addFormCheckBox(const __FlashStringHelper * label, const String& id, bool checked, bool disabled = false); - -// ******************************************************************************** -// Add a Numeric Box form -// ******************************************************************************** -void addFormNumericBox(LabelType::Enum label, - int value, - int min = INT_MIN, - int max = INT_MAX - #if FEATURE_TOOLTIPS - , - const String & tooltip = EMPTY_STRING - #endif // if FEATURE_TOOLTIPS - , - bool disabled = false - ); - -void addFormNumericBox(const __FlashStringHelper * label, - const __FlashStringHelper * id, - int value, - int min = INT_MIN, - int max = INT_MAX - #if FEATURE_TOOLTIPS - , - const String& tooltip = EMPTY_STRING - #endif // if FEATURE_TOOLTIPS - , - bool disabled = false - ); - -void addFormNumericBox(const String& label, - const String& id, - int value, - int min = INT_MIN, - int max = INT_MAX - #if FEATURE_TOOLTIPS - , - const String& tooltip = EMPTY_STRING - #endif // if FEATURE_TOOLTIPS - , - bool disabled = false - ); - - -void addFormFloatNumberBox(LabelType::Enum label, - float value, - float min, - float max, - uint8_t nrDecimals = 6, - float stepsize = 0.0f - #if FEATURE_TOOLTIPS - , - const String& tooltip = EMPTY_STRING - #endif // if FEATURE_TOOLTIPS - ); - -void addFormFloatNumberBox(const String& label, - const String& id, - float value, - float min, - float max, - uint8_t nrDecimals = 6, - float stepsize = 0.0f - #if FEATURE_TOOLTIPS - , - const String& tooltip = EMPTY_STRING - #endif // if FEATURE_TOOLTIPS - ); - -void addFormFloatNumberBox(const __FlashStringHelper * label, - const __FlashStringHelper * id, - float value, - float min, - float max, - uint8_t nrDecimals = 6, - float stepsize = 0.0f - #if FEATURE_TOOLTIPS - , - const String& tooltip = EMPTY_STRING - #endif // if FEATURE_TOOLTIPS - ); - - -// ******************************************************************************** -// Add a task selector form -// ******************************************************************************** -void addTaskSelectBox(const String& label, - const String& id, - taskIndex_t choice); - -// ******************************************************************************** -// Add a Text Box form -// ******************************************************************************** -void addFormTextBox(const __FlashStringHelper * label, - const __FlashStringHelper * id, - const String& value, - int maxlength, - bool readonly = false, - bool required = false, - const String& pattern = EMPTY_STRING); - -void addFormTextBox(const String& label, - const String& id, - const String& value, - int maxlength, - bool readonly = false, - bool required = false, - const String& pattern = EMPTY_STRING - #if FEATURE_TOOLTIPS - , - const String& tooltip = EMPTY_STRING - #endif // if FEATURE_TOOLTIPS - , - const String& datalist = EMPTY_STRING - ); - -void addFormTextBox(const __FlashStringHelper * classname, - const String& label, - const String& id, - const String& value, - int maxlength, - bool readonly = false, - bool required = false, - const String& pattern = EMPTY_STRING - #if FEATURE_TOOLTIPS - , - const String& tooltip = EMPTY_STRING - #endif // if FEATURE_TOOLTIPS - , - const String& datalist = EMPTY_STRING - ); - - -void addFormTextArea(const String& label, - const String& id, - const String& value, - int maxlength, - int rows, - int columns, - bool readonly = false, - bool required = false - #if FEATURE_TOOLTIPS - , - const String& tooltip = EMPTY_STRING - #endif // if FEATURE_TOOLTIPS - ); - -// ******************************************************************************** -// Add a Password Box form -// ******************************************************************************** - -void addFormPasswordBox(const String& label, - const String& id, - const String& password, - int maxlength - #if FEATURE_TOOLTIPS - , - const String& tooltip = EMPTY_STRING - #endif // if FEATURE_TOOLTIPS - ); - -bool getFormPassword(const String& id, - String & password); - -// ******************************************************************************** -// Add a IP Box form -// ******************************************************************************** - -void addFormIPBox(const __FlashStringHelper *label, - const __FlashStringHelper *id, - const uint8_t ip[4]); - -void addFormIPBox(const String& label, - const String& id, - const uint8_t ip[4]); - -// ******************************************************************************** -// Add a MAC address Box form -// ******************************************************************************** -void addFormMACBox(const String& label, const String& id, const MAC_address mac); - -// ******************************************************************************** -// Add a IP Access Control select dropdown list -// ******************************************************************************** -void addFormIPaccessControlSelect(const __FlashStringHelper * label, - const __FlashStringHelper * id, - int choice); - -// ******************************************************************************** -// a Separator character selector -// ******************************************************************************** -void addFormSeparatorCharInput(const __FlashStringHelper *rowLabel, - const __FlashStringHelper *id, - int value, - const String & charset, - const __FlashStringHelper *additionalText); - -// ******************************************************************************** -// Add a selector form -// ******************************************************************************** - -/* -void addFormPinSelect(const String& label, - const String& id, - int choice); -void addFormPinSelect(const String& label, - const __FlashStringHelper * id, - int choice); -void addFormPinSelect(const __FlashStringHelper * label, - const __FlashStringHelper * id, - int choice); -*/ -void addFormPinSelect(PinSelectPurpose purpose, const String& label, const __FlashStringHelper * id, int choice); - -void addFormPinSelect(PinSelectPurpose purpose, const __FlashStringHelper * label, const __FlashStringHelper * id, int choice); - -void addFormPinSelectI2C(const String& label, - const String& id, - int choice); - -void addFormSelectorI2C(const String& id, - int addressCount, - const uint8_t addresses[], - int selectedIndex, - uint8_t defaultAddress = 0 // Address 0 is invalid - #if FEATURE_TOOLTIPS - , - const String& tooltip = EMPTY_STRING - #endif - ); - -void addFormSelector(const String& label, - const String& id, - int optionCount, - const String options[], - const int indices[], - int selectedIndex - #if FEATURE_TOOLTIPS - , - const String& tooltip = EMPTY_STRING - #endif - ); - -void addFormSelector(const __FlashStringHelper * label, const __FlashStringHelper * id, int optionCount, const __FlashStringHelper * options[], const int indices[], int selectedIndex, bool reloadonchange = false); -void addFormSelector(const __FlashStringHelper * label, const String& id, int optionCount, const __FlashStringHelper * options[], const int indices[], int selectedIndex, bool reloadonchange = false); -void addFormSelector(const String& label, const String& id, int optionCount, const __FlashStringHelper * options[], const int indices[], int selectedIndex); -void addFormSelector(const __FlashStringHelper * label, const __FlashStringHelper * id, int optionCount, const String options[], const int indices[], int selectedIndex); - -void addFormSelector(const String& label, - const String& id, - int optionCount, - const __FlashStringHelper * options[], - const int indices[], - int selectedIndex, - bool reloadonchange); - -void addFormSelector(const String& label, - const String& id, - int optionCount, - const __FlashStringHelper * options[], - const int indices[], - const String attr[], - int selectedIndex, - bool reloadonchange); - - -void addFormSelector(const String& label, - const String& id, - int optionCount, - const String options[], - const int indices[], - int selectedIndex, - bool reloadonchange - #if FEATURE_TOOLTIPS - , - const String& tooltip = EMPTY_STRING - #endif - ); - -void addFormSelector(const String& label, - const String& id, - int optionCount, - const String options[], - const int indices[], - const String attr[], - int selectedIndex, - bool reloadonchange - #if FEATURE_TOOLTIPS - , - const String& tooltip = EMPTY_STRING - #endif - ); - -void addFormSelector_script(const __FlashStringHelper * label, - const __FlashStringHelper * id, - int optionCount, - const __FlashStringHelper * options[], - const int indices[], - const String attr[], - int selectedIndex, - const __FlashStringHelper * onChangeCall - #if FEATURE_TOOLTIPS - , - const String& tooltip = EMPTY_STRING - #endif - ); - - -void addFormSelector_script(const __FlashStringHelper * label, - const __FlashStringHelper * id, - int optionCount, - const String options[], - const int indices[], - const String attr[], - int selectedIndex, - const __FlashStringHelper * onChangeCall - #if FEATURE_TOOLTIPS - , - const String& tooltip = EMPTY_STRING - #endif - ); - -void addFormSelector_YesNo(const __FlashStringHelper * label, - const __FlashStringHelper * id, - int selectedIndex, - bool reloadonchange); - -void addFormSelector_YesNo(const __FlashStringHelper * label, - const String& id, - int selectedIndex, - bool reloadonchange); - -// ******************************************************************************** -// Add a GPIO pin select dropdown list -// ******************************************************************************** -void addFormPinStateSelect(int gpio, - int choice); - -// ******************************************************************************** -// Retrieve return values from form/checkbox. -// ******************************************************************************** - - -int getFormItemInt(const __FlashStringHelper * key, int defaultValue); -int getFormItemInt(const String& key, int defaultValue); - -bool getCheckWebserverArg_int(const String& key, - int & value); - -bool update_whenset_FormItemInt(const __FlashStringHelper * key, - int & value); - -bool update_whenset_FormItemInt(const String& key, - int & value); - -bool update_whenset_FormItemInt(const __FlashStringHelper * key, - uint8_t & value); - -bool update_whenset_FormItemInt(const String& key, - uint8_t & value); - -// Note: Checkbox values will not appear in POST Form data if unchecked. -// So if webserver does not have an argument for a checkbox form, it means it should be considered unchecked. -bool isFormItemChecked(const __FlashStringHelper * id); -bool isFormItemChecked(const String& id); -bool isFormItemChecked(const LabelType::Enum& id); - -int getFormItemInt(const __FlashStringHelper * id); -int getFormItemInt(const String& id); -int getFormItemInt(const LabelType::Enum& id); - -float getFormItemFloat(const __FlashStringHelper * id); -float getFormItemFloat(const String& id); -float getFormItemFloat(const LabelType::Enum& id); - -bool isFormItem(const String& id); - -void copyFormPassword(const __FlashStringHelper * id, - char *pPassword, - int maxlength); - - -#endif // ifndef WEBSERVER_WEBSERVER_MARKUP_FORMS_H +#ifndef WEBSERVER_WEBSERVER_MARKUP_FORMS_H +#define WEBSERVER_WEBSERVER_MARKUP_FORMS_H + +#include "../WebServer/common.h" + +#include "../DataStructs/MAC_address.h" +#include "../Globals/Plugins.h" +#include "../Helpers/StringGenerator_GPIO.h" + + +// ******************************************************************************** +// Add a separator as row start +// ******************************************************************************** +void addFormSeparator(int clspan); + +// ******************************************************************************** +// Add a note as row start +// ******************************************************************************** +void addFormNote(const __FlashStringHelper * text); +void addFormNote(const String& text, const String& id = EMPTY_STRING); +void addFormNote(const LabelType::Enum& label); + +// ******************************************************************************** +// Create Forms +// ******************************************************************************** + + +// ******************************************************************************** +// Add a checkbox Form +// ******************************************************************************** + +void addFormCheckBox_disabled(const String& label, + const String& id, + bool checked + #if FEATURE_TOOLTIPS + , + const String& tooltip = EMPTY_STRING + #endif // if FEATURE_TOOLTIPS + ); + +void addFormCheckBox(const String& label, + const String& id, + bool checked, + bool disabled = false + #if FEATURE_TOOLTIPS + , + const String& tooltip = EMPTY_STRING + #endif // if FEATURE_TOOLTIPS + ); + +void addFormCheckBox(LabelType::Enum label, + bool checked, + bool disabled = false + #if FEATURE_TOOLTIPS + , + const String & tooltip = EMPTY_STRING + #endif // if FEATURE_TOOLTIPS + ); + +void addFormCheckBox_disabled(LabelType::Enum label, + bool checked); +void addFormCheckBox(const __FlashStringHelper * label, const __FlashStringHelper * id, bool checked, bool disabled = false); +void addFormCheckBox(const __FlashStringHelper * label, const String& id, bool checked, bool disabled = false); + +// ******************************************************************************** +// Add a Numeric Box form +// ******************************************************************************** +void addFormNumericBox(LabelType::Enum label, + int value, + int min = INT_MIN, + int max = INT_MAX + #if FEATURE_TOOLTIPS + , + const String & tooltip = EMPTY_STRING + #endif // if FEATURE_TOOLTIPS + , + bool disabled = false + ); + +void addFormNumericBox(const __FlashStringHelper * label, + const __FlashStringHelper * id, + int value, + int min = INT_MIN, + int max = INT_MAX + #if FEATURE_TOOLTIPS + , + const String& tooltip = EMPTY_STRING + #endif // if FEATURE_TOOLTIPS + , + bool disabled = false + ); + +void addFormNumericBox(const String& label, + const String& id, + int value, + int min = INT_MIN, + int max = INT_MAX + #if FEATURE_TOOLTIPS + , + const String& tooltip = EMPTY_STRING + #endif // if FEATURE_TOOLTIPS + , + bool disabled = false + ); + + +void addFormFloatNumberBox(LabelType::Enum label, + float value, + float min, + float max, + uint8_t nrDecimals = 6, + float stepsize = 0.0f + #if FEATURE_TOOLTIPS + , + const String& tooltip = EMPTY_STRING + #endif // if FEATURE_TOOLTIPS + ); + +void addFormFloatNumberBox(const String& label, + const String& id, + float value, + float min, + float max, + uint8_t nrDecimals = 6, + float stepsize = 0.0f + #if FEATURE_TOOLTIPS + , + const String& tooltip = EMPTY_STRING + #endif // if FEATURE_TOOLTIPS + ); + +void addFormFloatNumberBox(const __FlashStringHelper * label, + const __FlashStringHelper * id, + float value, + float min, + float max, + uint8_t nrDecimals = 6, + float stepsize = 0.0f + #if FEATURE_TOOLTIPS + , + const String& tooltip = EMPTY_STRING + #endif // if FEATURE_TOOLTIPS + ); + + +// ******************************************************************************** +// Add a task selector form +// ******************************************************************************** +void addTaskSelectBox(const String& label, + const String& id, + taskIndex_t choice); + +// ******************************************************************************** +// Add a Text Box form +// ******************************************************************************** +void addFormTextBox(const __FlashStringHelper * label, + const __FlashStringHelper * id, + const String& value, + int maxlength, + bool readonly = false, + bool required = false, + const String& pattern = EMPTY_STRING); + +void addFormTextBox(const String& label, + const String& id, + const String& value, + int maxlength, + bool readonly = false, + bool required = false, + const String& pattern = EMPTY_STRING + #if FEATURE_TOOLTIPS + , + const String& tooltip = EMPTY_STRING + #endif // if FEATURE_TOOLTIPS + , + const String& datalist = EMPTY_STRING + ); + +void addFormTextBox(const __FlashStringHelper * classname, + const String& label, + const String& id, + const String& value, + int maxlength, + bool readonly = false, + bool required = false, + const String& pattern = EMPTY_STRING + #if FEATURE_TOOLTIPS + , + const String& tooltip = EMPTY_STRING + #endif // if FEATURE_TOOLTIPS + , + const String& datalist = EMPTY_STRING + ); + + +void addFormTextArea(const String& label, + const String& id, + const String& value, + int maxlength, + int rows, + int columns, + bool readonly = false, + bool required = false + #if FEATURE_TOOLTIPS + , + const String& tooltip = EMPTY_STRING + #endif // if FEATURE_TOOLTIPS + ); + +// ******************************************************************************** +// Add a Password Box form +// ******************************************************************************** + +void addFormPasswordBox(const String& label, + const String& id, + const String& password, + int maxlength + #if FEATURE_TOOLTIPS + , + const String& tooltip = EMPTY_STRING + #endif // if FEATURE_TOOLTIPS + ); + +bool getFormPassword(const String& id, + String & password); + +// ******************************************************************************** +// Add a IP Box form +// ******************************************************************************** + +void addFormIPBox(const __FlashStringHelper *label, + const __FlashStringHelper *id, + const uint8_t ip[4]); + +void addFormIPBox(const String& label, + const String& id, + const uint8_t ip[4]); + +// ******************************************************************************** +// Add a MAC address Box form +// ******************************************************************************** +void addFormMACBox(const String& label, const String& id, const MAC_address mac); + +// ******************************************************************************** +// Add a IP Access Control select dropdown list +// ******************************************************************************** +void addFormIPaccessControlSelect(const __FlashStringHelper * label, + const __FlashStringHelper * id, + int choice); + +// ******************************************************************************** +// a Separator character selector +// ******************************************************************************** +void addFormSeparatorCharInput(const __FlashStringHelper *rowLabel, + const __FlashStringHelper *id, + int value, + const String & charset, + const __FlashStringHelper *additionalText); + +// ******************************************************************************** +// Add a selector form +// ******************************************************************************** + +/* +void addFormPinSelect(const String& label, + const String& id, + int choice); +void addFormPinSelect(const String& label, + const __FlashStringHelper * id, + int choice); +void addFormPinSelect(const __FlashStringHelper * label, + const __FlashStringHelper * id, + int choice); +*/ +void addFormPinSelect(PinSelectPurpose purpose, const String& label, const __FlashStringHelper * id, int choice); + +void addFormPinSelect(PinSelectPurpose purpose, const __FlashStringHelper * label, const __FlashStringHelper * id, int choice); + +void addFormPinSelectI2C(const String& label, + const String& id, + int choice); + +void addFormSelectorI2C(const String& id, + int addressCount, + const uint8_t addresses[], + int selectedIndex, + uint8_t defaultAddress = 0 // Address 0 is invalid + #if FEATURE_TOOLTIPS + , + const String& tooltip = EMPTY_STRING + #endif + ); + +void addFormSelector(const String& label, + const String& id, + int optionCount, + const String options[], + const int indices[], + int selectedIndex + #if FEATURE_TOOLTIPS + , + const String& tooltip = EMPTY_STRING + #endif + ); + +void addFormSelector(const __FlashStringHelper * label, const __FlashStringHelper * id, int optionCount, const __FlashStringHelper * options[], const int indices[], int selectedIndex, bool reloadonchange = false); +void addFormSelector(const __FlashStringHelper * label, const String& id, int optionCount, const __FlashStringHelper * options[], const int indices[], int selectedIndex, bool reloadonchange = false); +void addFormSelector(const String& label, const String& id, int optionCount, const __FlashStringHelper * options[], const int indices[], int selectedIndex); +void addFormSelector(const __FlashStringHelper * label, const __FlashStringHelper * id, int optionCount, const String options[], const int indices[], int selectedIndex); + +void addFormSelector(const String& label, + const String& id, + int optionCount, + const __FlashStringHelper * options[], + const int indices[], + int selectedIndex, + bool reloadonchange); + +void addFormSelector(const String& label, + const String& id, + int optionCount, + const __FlashStringHelper * options[], + const int indices[], + const String attr[], + int selectedIndex, + bool reloadonchange); + + +void addFormSelector(const String& label, + const String& id, + int optionCount, + const String options[], + const int indices[], + int selectedIndex, + bool reloadonchange + #if FEATURE_TOOLTIPS + , + const String& tooltip = EMPTY_STRING + #endif + ); + +void addFormSelector(const String& label, + const String& id, + int optionCount, + const String options[], + const int indices[], + const String attr[], + int selectedIndex, + bool reloadonchange + #if FEATURE_TOOLTIPS + , + const String& tooltip = EMPTY_STRING + #endif + ); + +void addFormSelector_script(const __FlashStringHelper * label, + const __FlashStringHelper * id, + int optionCount, + const __FlashStringHelper * options[], + const int indices[], + const String attr[], + int selectedIndex, + const __FlashStringHelper * onChangeCall + #if FEATURE_TOOLTIPS + , + const String& tooltip = EMPTY_STRING + #endif + ); + + +void addFormSelector_script(const __FlashStringHelper * label, + const __FlashStringHelper * id, + int optionCount, + const String options[], + const int indices[], + const String attr[], + int selectedIndex, + const __FlashStringHelper * onChangeCall + #if FEATURE_TOOLTIPS + , + const String& tooltip = EMPTY_STRING + #endif + ); + +void addFormSelector_YesNo(const __FlashStringHelper * label, + const __FlashStringHelper * id, + int selectedIndex, + bool reloadonchange); + +void addFormSelector_YesNo(const __FlashStringHelper * label, + const String& id, + int selectedIndex, + bool reloadonchange); + +// ******************************************************************************** +// Add a GPIO pin select dropdown list +// ******************************************************************************** +void addFormPinStateSelect(int gpio, + int choice); + +// ******************************************************************************** +// Retrieve return values from form/checkbox. +// ******************************************************************************** + + +int getFormItemInt(const __FlashStringHelper * key, int defaultValue); +int getFormItemInt(const String& key, int defaultValue); + +bool getCheckWebserverArg_int(const String& key, + int & value); + +bool update_whenset_FormItemInt(const __FlashStringHelper * key, + int & value); + +bool update_whenset_FormItemInt(const String& key, + int & value); + +bool update_whenset_FormItemInt(const __FlashStringHelper * key, + uint8_t & value); + +bool update_whenset_FormItemInt(const String& key, + uint8_t & value); + +// Note: Checkbox values will not appear in POST Form data if unchecked. +// So if webserver does not have an argument for a checkbox form, it means it should be considered unchecked. +bool isFormItemChecked(const __FlashStringHelper * id); +bool isFormItemChecked(const String& id); +bool isFormItemChecked(const LabelType::Enum& id); + +int getFormItemInt(const __FlashStringHelper * id); +int getFormItemInt(const String& id); +int getFormItemInt(const LabelType::Enum& id); + +float getFormItemFloat(const __FlashStringHelper * id); +float getFormItemFloat(const String& id); +float getFormItemFloat(const LabelType::Enum& id); + +bool isFormItem(const String& id); + +void copyFormPassword(const __FlashStringHelper * id, + char *pPassword, + int maxlength); + + +#endif // ifndef WEBSERVER_WEBSERVER_MARKUP_FORMS_H diff --git a/src/src/WebServer/Metrics.cpp b/src/src/WebServer/Metrics.cpp index 23b0c341d3..5bf219e719 100644 --- a/src/src/WebServer/Metrics.cpp +++ b/src/src/WebServer/Metrics.cpp @@ -123,7 +123,7 @@ void handle_metrics_devices() { addHtml(F("espeasy_device_")); addHtml(deviceName); addHtml(F("{valueName=\"")); - addHtml(getTaskValueName(x, varNr)); + addHtml(Cache.getTaskDeviceValueName(x, varNr)); addHtml(F("\"} ")); addHtml(formatUserVarNoCheck(&TempEvent, varNr)); addHtml('\n'); diff --git a/src/src/WebServer/RootPage.cpp b/src/src/WebServer/RootPage.cpp index 1517a7c4d7..a489a1e4f1 100644 --- a/src/src/WebServer/RootPage.cpp +++ b/src/src/WebServer/RootPage.cpp @@ -158,14 +158,19 @@ void handle_root() { if (wdcounter > 0) { - addHtmlFloat(getCPUload()); - addHtml(F("% (LC=")); - addHtmlInt(getLoopCountPerSec()); - addHtml(')'); + addHtml(strformat( + F("%.2f [%%] (LC=%d)"), + getCPUload(), + getLoopCountPerSec())); } + +#if FEATURE_INTERNAL_TEMPERATURE + addRowLabelValue(LabelType::INTERNAL_TEMPERATURE); +#endif { addRowLabel(LabelType::FREE_MEM); addHtmlInt(freeMem); + addUnit(getFormUnit(LabelType::FREE_MEM)); # ifndef BUILD_NO_RAM_TRACKER addHtml(strformat( F(" (%d - %s)"), @@ -174,13 +179,14 @@ void handle_root() { # endif // ifndef BUILD_NO_RAM_TRACKER } { - # ifdef USE_SECOND_HEAP +# ifdef USE_SECOND_HEAP addRowLabelValue(LabelType::FREE_HEAP_IRAM); - # endif // ifdef USE_SECOND_HEAP +# endif // ifdef USE_SECOND_HEAP } { addRowLabel(LabelType::FREE_STACK); addHtmlInt(getCurrentFreeStack()); + addUnit(getFormUnit(LabelType::FREE_STACK)); # ifndef BUILD_NO_RAM_TRACKER addHtml(strformat( F(" (%d - %s)"), @@ -204,7 +210,7 @@ void handle_root() { #endif addRowLabel(LabelType::WIFI_RSSI); addHtml(strformat( - F("%d dBm (%s)"), + F("%d [dBm] (%s)"), WiFi.RSSI(), WiFi.SSID().c_str())); } diff --git a/src/src/WebServer/SetupPage.cpp b/src/src/WebServer/SetupPage.cpp index ef94bf0f14..373f986348 100644 --- a/src/src/WebServer/SetupPage.cpp +++ b/src/src/WebServer/SetupPage.cpp @@ -1,412 +1,421 @@ -#include "../WebServer/SetupPage.h" - - -#ifdef WEBSERVER_SETUP - -# include "../WebServer/ESPEasy_WebServer.h" -# include "../WebServer/AccessControl.h" -# include "../WebServer/HTML_wrappers.h" -# include "../WebServer/Markup.h" -# include "../WebServer/Markup_Buttons.h" -# include "../WebServer/Markup_Forms.h" -# include "../WebServer/SysInfoPage.h" - -# include "../ESPEasyCore/ESPEasyNetwork.h" -# include "../ESPEasyCore/ESPEasyWifi.h" - -# include "../Globals/ESPEasyWiFiEvent.h" -# include "../Globals/NetworkState.h" -# include "../Globals/RTC.h" -# include "../Globals/Settings.h" -# include "../Globals/SecuritySettings.h" -# include "../Globals/WiFi_AP_Candidates.h" - -# include "../Helpers/Misc.h" -# include "../Helpers/Networking.h" -# include "../Helpers/ESPEasy_Storage.h" -# include "../Helpers/StringConverter.h" - - - -#ifndef SETUP_PAGE_SHOW_CONFIG_BUTTON - #define SETUP_PAGE_SHOW_CONFIG_BUTTON true -#endif - - - -// ******************************************************************************** -// Web Interface Setup Wizard -// ******************************************************************************** - -# define HANDLE_SETUP_SCAN_STAGE 0 -# define HANDLE_SETUP_CONNECTING_STAGE 1 - -void handle_setup() { - # ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("handle_setup")); - # endif // ifndef BUILD_NO_RAM_TRACKER - - // Do not check client IP range allowed. - TXBuffer.startStream(); - - const bool connected = NetworkConnected(); - - -// if (connected) { - navMenuIndex = MENU_INDEX_SETUP; - sendHeadandTail_stdtemplate(_HEAD); -/* } else { - sendHeadandTail(F("TmplAP")); - } - */ - - const bool clearButtonPressed = hasArg(F("performclearcredentials")); - const bool clearWiFiCredentials = - isFormItemChecked(F("clearcredentials")) && clearButtonPressed; - - { - if (clearWiFiCredentials) { - SecuritySettings.clearWiFiCredentials(); - addHtmlError(SaveSecuritySettings()); - - html_add_form(); - html_table_class_normal(); - - addFormHeader(F("WiFi credentials cleared, reboot now")); - html_end_table(); - } else { - // if (active_network_medium == NetworkMedium_t::WIFI) - // { - static uint8_t status = HANDLE_SETUP_SCAN_STAGE; - static uint8_t refreshCount = 0; - - String ssid = webArg(F("ssid")); - String other = webArg(F("other")); - String password; - bool passwordGiven = getFormPassword(F("pass"), password); - if (passwordGiven) { - passwordGiven = !password.isEmpty(); - } - const bool emptyPassAllowed = isFormItemChecked(F("emptypass")); - const bool performRescan = hasArg(F("performrescan")); - if (performRescan) { - WiFiEventData.lastScanMoment.clear(); - WifiScan(false); - } - - if (!other.isEmpty()) - { - ssid = other; - } - - if (!performRescan) { - // if ssid config not set and params are both provided - if ((status == HANDLE_SETUP_SCAN_STAGE) && (!ssid.isEmpty()) /*&& strcasecmp(SecuritySettings.WifiSSID, "ssid") == 0 */) - { - if (clearButtonPressed) { - addHtmlError(F("Warning: Need to confirm to clear WiFi credentials")); - } else if (!passwordGiven && !emptyPassAllowed) { - addHtmlError(F("No password entered")); - } else { - safe_strncpy(SecuritySettings.WifiKey, password.c_str(), sizeof(SecuritySettings.WifiKey)); - safe_strncpy(SecuritySettings.WifiSSID, ssid.c_str(), sizeof(SecuritySettings.WifiSSID)); - // Hidden SSID - Settings.IncludeHiddenSSID(isFormItemChecked(LabelType::CONNECT_HIDDEN_SSID)); - Settings.HiddenSSID_SlowConnectPerBSSID(isFormItemChecked(LabelType::HIDDEN_SSID_SLOW_CONNECT)); - addHtmlError(SaveSettings()); - WiFiEventData.wifiSetupConnect = true; - WiFiEventData.wifiConnectAttemptNeeded = true; - WiFi_AP_Candidates.force_reload(); // Force reload of the credentials and found APs from the last scan - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String reconnectlog = F("WIFI : Credentials Changed, retry connection. SSID: "); - reconnectlog += ssid; - addLogMove(LOG_LEVEL_INFO, reconnectlog); - } - status = HANDLE_SETUP_CONNECTING_STAGE; - refreshCount = 0; - AttemptWiFiConnect(); - } - } - } - html_BR(); - wrap_html_tag(F("h1"), connected ? F("Connected to a network") : F("Wifi Setup wizard")); - html_add_form(); - - switch (status) { - case HANDLE_SETUP_SCAN_STAGE: - { - // first step, scan and show access points within reach... - handle_setup_scan_and_show(ssid, other, password); - break; - } - case HANDLE_SETUP_CONNECTING_STAGE: - { - if (!handle_setup_connectingStage(refreshCount)) { - status = HANDLE_SETUP_SCAN_STAGE; - } - ++refreshCount; - break; - } - } - /* - } else { - html_add_form(); - addFormHeader(F("Ethernet Setup Complete")); - - } - */ - - html_table_class_normal(); - html_TR(); -#if defined(WEBSERVER_SYSINFO) && !defined(WEBSERVER_SYSINFO_MINIMAL) - handle_sysinfo_NetworkServices(); -#endif - if (connected) { - - //addFormHeader(F("Current network configuration")); - -#ifdef WEBSERVER_SYSINFO - handle_sysinfo_Network(); -#endif - - addFormSeparator(2); - - html_TR_TD(); - html_TD(); - - #if SETUP_PAGE_SHOW_CONFIG_BUTTON - if (!clientIPinSubnet()) { - String host = formatIP(NetworkLocalIP()); - String url = F("http://"); - url += host; - url += F("/config"); - addButton(url, host); - } - #endif - - WiFiEventData.wifiSetup = false; - } - html_end_table(); - - html_BR(); - html_BR(); - html_BR(); - html_BR(); - html_BR(); - html_BR(); - html_BR(); - - html_table_class_normal(); - - addFormHeader(F("Advanced WiFi settings")); - - addFormCheckBox(LabelType::CONNECT_HIDDEN_SSID, Settings.IncludeHiddenSSID()); - addFormNote(F("Must be checked to connect to a hidden SSID")); - - addFormCheckBox(LabelType::HIDDEN_SSID_SLOW_CONNECT, Settings.HiddenSSID_SlowConnectPerBSSID()); - addFormNote(F("Required for some AP brands like Mikrotik to connect to hidden SSID")); - - html_BR(); - html_BR(); - - addFormHeader(F("Clear WiFi credentials")); - addFormCheckBox(F("Confirm clear"), F("clearcredentials"), false); - - html_TR_TD(); - html_TD(); - addSubmitButton(F("Clear and Reboot"), F("performclearcredentials"), F("red")); - html_end_table(); - } - - html_end_form(); - } -// if (connected) { - sendHeadandTail_stdtemplate(_TAIL); -/* } else { - sendHeadandTail(F("TmplAP"), true); - } -*/ - TXBuffer.endStream(); - delay(10); - if (clearWiFiCredentials) { - reboot(IntendedRebootReason_e::RestoreSettings); - } -} - -void handle_setup_scan_and_show(const String& ssid, const String& other, const String& password) { - int8_t scanCompleteStatus = WiFi_AP_Candidates.scanComplete(); - const bool needsRescan = scanCompleteStatus <= 0 || WiFiScanAllowed(); - if (needsRescan) { - WiFiMode_t cur_wifimode = WiFi.getMode(); - WifiScan(false); - scanCompleteStatus = WiFi_AP_Candidates.scanComplete(); - setWifiMode(cur_wifimode); - } - - - if (scanCompleteStatus <= 0) { - addHtml(F("No Access Points found")); - } - else - { - html_table_class_multirow(); - html_TR(); - html_table_header(F("Pick"), 50); - html_table_header(F("Network info")); - html_table_header(F("RSSI"), 50); - - for (auto it = WiFi_AP_Candidates.scanned_begin(); it != WiFi_AP_Candidates.scanned_end(); ++it) - { - html_TR_TD(); - const String id = it->toString(""); - addHtml(F("")); - - html_TD(); - addHtml(F("")); - - html_TD(); - addHtml(F("")); - } - html_end_table(); - } - - html_BR(); - - addSubmitButton(F("Rescan"), F("performrescan")); - - html_BR(); - - html_table_class_normal(); - html_TR_TD(); - - addHtml(F("")); - - html_TD(); - - addHtml(F("")); - - - html_TR(); - - html_BR(); - html_BR(); - - addFormSeparator(2); - - html_BR(); - - addFormPasswordBox(F("Password"), F("pass"), password, 63); - addFormCheckBox(F("Allow Empty Password"), F("emptypass"), false); - -/* - if (SecuritySettings.hasWiFiCredentials(SecurityStruct::WiFiCredentialsSlot::first)) { - addFormCheckBox(F("Clear Stored SSID1"), F("clearssid1"), false); - addFormNote(String(F("Current: ")) + getValue(LabelType::WIFI_STORED_SSID1)); - } - if (SecuritySettings.hasWiFiCredentials(SecurityStruct::WiFiCredentialsSlot::second)) { - addFormCheckBox(F("Clear Stored SSID2"), F("clearssid2"), false); - addFormNote(String(F("Current: ")) + getValue(LabelType::WIFI_STORED_SSID2)); - } - */ - - html_TR_TD(); - html_TD(); - html_BR(); - addSubmitButton(F("Connect"), EMPTY_STRING); - - html_end_table(); -} - -bool handle_setup_connectingStage(uint8_t refreshCount) { - if (refreshCount > 0) - { - // safe_strncpy(SecuritySettings.WifiSSID, "ssid", sizeof(SecuritySettings.WifiSSID)); - // SecuritySettings.WifiKey[0] = 0; - addButton(F("/setup"), F("Back to Setup")); - html_TR_TD(); - html_BR(); - WiFiEventData.wifiSetupConnect = false; - return false; - } - int wait = WIFI_RECONNECT_WAIT / 1000; - - if (refreshCount != 0) { - wait = 3; - } - addHtml(F("Please wait for

20..

" - "