Merge branch 'mega' of https://github.com/letscontrolit/ESPEasy into feature/SPI-add-support-for-multiple-buses

This commit is contained in:
Ton Huisman
2025-09-10 22:24:42 +02:00
73 changed files with 2691 additions and 763 deletions
+4 -4
View File
@@ -24,7 +24,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v5
- uses: actions/setup-python@v6
with:
python-version: '3.x'
- name: Build documentation
@@ -47,7 +47,7 @@ jobs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v5
- uses: actions/setup-python@v6
with:
python-version: '3.x'
- id: set-matrix
@@ -63,7 +63,7 @@ jobs:
matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }}
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v5
- uses: actions/setup-python@v6
with:
python-version: '3.x'
- name: Dependencies
@@ -102,7 +102,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v5
- uses: actions/setup-python@v6
with:
python-version: '3.x'
- name: Get current date
+5 -5
View File
@@ -22,7 +22,7 @@ jobs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v5
- uses: actions/setup-python@v6
with:
python-version: '3.x'
- id: set-matrix
@@ -38,7 +38,7 @@ jobs:
matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }}
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v5
- uses: actions/setup-python@v6
- name: Dependencies
run: |
sudo apt install binutils build-essential libffi-dev libgit2-dev
@@ -68,7 +68,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v5
- uses: actions/setup-python@v6
- name: Build documentation
run: |
cd docs
@@ -97,7 +97,7 @@ jobs:
notes: ${{ steps.release-notes.outputs.result }}
steps:
- id: release-notes
uses: actions/github-script@v7
uses: actions/github-script@v8
with:
result-encoding: string
script: |
@@ -119,7 +119,7 @@ jobs:
needs: [build, prepare-dist, prepare-notes]
runs-on: ubuntu-latest
steps:
- uses: actions/setup-python@v5
- uses: actions/setup-python@v6
with:
python-version: '3.x'
- name: Get current date
+2
View File
@@ -85,6 +85,8 @@ A group of MQTT specific settings:
* **Publish Retain Flag**: When publishing data accompanied by the Retain flag, the server will store the last sent data, and publish that when reconnecting.
This will override the **Retained** setting per Task that can be set for a MQTT Controller.
* **Controller Subscribe**: The topic(s) to subscribe to, so the MQTT Server can send data and optionally commands to this client-instance.
* **Controller Publish**: The topic to publish data to. This field supports variables so each value sent to the server can have a separate topic for its data.
+6 -4
View File
@@ -22,7 +22,7 @@ Depending on the controller capabilities, some configuration settings may be sho
.. image:: Task_config_page_Controllers_section.png
All configured Controllers are shown here, including their enabled or disabled state (only a single MQTT Controller can be enabled at one time).
All configured Controllers are shown here, including the enabled or disabled state (multiple Controllers can be enabled, only a single MQTT Controller can be enabled at one time!).
For each controller the user can select wether the data should be sent on each Interval (or explicit TaskRun).
@@ -30,12 +30,14 @@ For the Domoticz controllers the value index (IDX) has to be configured.
For some controllers, like Home Assistant/openHAB, there are extra options available.
* **Group**: This represents the group id to combine all values from multiple tasks into a single grouped-device during MQTT AutoDiscovery. Groups, by design, can span multiple ESPEasy devices, if desired, as long as the Task/Valuename combinations are unique. If a group should only combine Tasks from a single ESPEasy unit, the group id should be unique across multiple ESPEasy units. The group description, default *Group <n>*, can be adjusted in Home Assistant.
* **Group**: This represents the group id to combine all values from multiple tasks into a single grouped-device during MQTT AutoDiscovery. Groups, by design, can span multiple ESPEasy devices, if desired, as long as the Task/Valuename combinations are unique. If a group should only combine Tasks from a single ESPEasy unit, the group id should be unique across multiple ESPEasy units. The group description, default *Group <n>*, can be adjusted in Home Assistant. If the Group value matches the current Unit nr, the Unit name, ``%sysname%``, is used instead of Group <nr>.
* **Retained**: For MQTT Controllers, this setting can be enabled to send the values for the current task with the Retain flag set. The **Publish Retain flag** in the Controller settings will override this by sending *all* task values with Retain flag enabled.
* **Send derived**: This checkbox determines if any configured Derived values should also be sent to the controller (and included in the AutoDiscovery if that's available and enabled).
* **Resend MQTT Discovery**: When checked, will start a resend of the MQTT Discovery process for this task after a random delay, when Submit is clicked, so any chenged settings can be updated in the MQTT server. This setting is only available if the controller is enabled, the Auto Discovery feature is available and enabled for the controller. This setting is not stored.
* **Resend MQTT Discovery**: When checked, will start a resend of the MQTT Discovery process for this task after a random delay, when Submit is clicked, so any changed settings will be updated in the MQTT broker. This setting is only available if the controller is enabled, the Auto Discovery feature is available and enabled for the controller. This setting is not stored.
Other controllers, like FHEM HTTP, do not support additional settings besides the checkbox to enable sending the data.
Other controllers, like f.e. FHEM HTTP, do not support additional settings besides the checkbox to enable sending the data.
|
+1 -1
View File
@@ -179,7 +179,7 @@ The number of mappings is currently limited to 25, but only the number of used m
Option: Generate events for accepted topics
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* **Generate events for accepted topics**: Enabling this option will generate an event for every incoming, and accepted if Filtering is used, non-JSON payload even when that has a non-numeric value. The event generated is: ``<devicename>#<valuename>=<value>``, for example ``MQTT_Import#Value1=on``.
* **Generate events for accepted topics**: Enabling this option will generate an event for every incoming, and accepted if Filtering is used, non-JSON payload even when that has a non-numeric value. The event generated is: ``<devicename>#<valuename>=<value>``, for example ``MQTT_Import#Value1=on``. When the ``<value>`` is numeric, the configured number of decimals is applied to the value.
When this option is disabled it has the backward-compatible behavior of discarding that message as invalid.
+38 -15
View File
@@ -23,28 +23,52 @@ Maintainer: |P056_maintainer|
Used libraries: |P056_usedlibraries|
Supported hardware
------------------
Introduction
------------
|P056_usedby|
A dust sensor will measure the dust concentration of the surrounding air. Depending on the device, different particle sizes can be measured, and the SDS series provides values for up to 2.5 and up to 10 micrometer particle size.
Multiple sensors can be used independently on a single ESP unit, as long as the serial port setup is uniquely configured.
Device
------
.. image:: P056_DeviceConfiguration.png
Task settings
~~~~~~~~~~~~~
* **Name** The name of the task. This should be unique for all devices that are configured. (Initially empty)
* **Enabled** For the device to work it has to be enabled. When checked, the device will be started as soon as the ESP starts. If desired, the device can also be enabled from f.e. a rule by using the ``TaskEnable,<tasknr>`` or ``TaskEnable,<taskname>`` command, or disabled using the corresponding ``TaskDisable,<tasknr>|<taskname>`` commands.
Sensor
^^^^^^
~~~~~~
See: :ref:`SerialHelper_page`
Device Settings
~~~~~~~~~~~~~~~
**TODO**: Complete this documentation...
* **Sleep time**: Numeric value to set the sleep time between measurements. The laser used in the sensor has an aproximated life time of 8000 hours of on-time. When enabled continuously, this number will be reached in about 333 days, so less than a year! To prolong the lifetime, a sleep interval can be configured, during which the laser will be turned on, and then turned off for ``<sleeptime>*60-30`` seconds. Range = 0 (continuous) or 1 .. 30 seconds.
.. Commands available
.. ^^^^^^^^^^^^^^^^^^
This setting is only available if the optional **ESP TX GPIO** pin is configured correctly, as it involves sending a command to the sensor.
.. .. include:: P056_commands.repl
.. Events
.. ~~~~~~
.. include:: DataAcquisition.repl
.. .. include:: P056_events.repl
* **Interval**: By default, Interval will be set to 60 sec for this plugin. It is the frequency used to read sensor values and send these to any Controllers configured for this device. Values will *only* be sent if data is successfully received from the sensor.
Values
~~~~~~
The measured values are available in ``PM2.5`` and ``PM10``.
For a complete description of all available columns see the `Plugin - Values <_Plugin.html#values>`_ section.
|
Change log
----------
@@ -52,6 +76,9 @@ Change log
.. versionchanged:: 2.0
...
|changed|
2025-08-05: Enable multiple instances of the plugin, and update the documentation.
|added|
Major overhaul for 2.0 release.
@@ -61,7 +88,3 @@ Change log
|added|
Initial release version.
Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

+15 -1
View File
@@ -40,6 +40,16 @@ Device Settings
* **Hostname**: The hostname or IP-address for the device or host to monitor.
**ESP32 only:**
* **Ping count**: The number of pings that should be tried before reporting a failure. (Only available on ESP32 builds)
* **Available Values**: The default is to only have a **Fails** value available, but for ESP32 there is also the Average ping-time in msec. available.
.. image:: P089_AvailableValuesOptions.png
When changing this setting, the page will save and reload to adjust for the change in **Values**, below.
.. include:: DataAcquisition.repl
* **Interval** By default, Interval will be set to 60 sec. Every Interval setting, the host will be pinged and the state will be updated in Fails. If the ping responds as intended, then Fails is reset to 0.
@@ -47,7 +57,9 @@ Device Settings
Values
^^^^^^
The failure count value is available in ``Fails``. No other options are available for Values.
The failure count value is available in ``Fails``. On ESP32 there is an option to also show the average ping-time in msec. via ``Avg_ms``.
On ESP32, 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: <Task Value Statistics>`
@@ -64,6 +76,8 @@ Change log
.. versionchanged:: 2.0
...
|added| 2025-08-26 Enable support for ESP32
|changed| 2023-03-14 Extended command handling to not require the taskname argument.
|added| 2020-02-22
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 41 KiB

+2 -10
View File
@@ -53,15 +53,9 @@ Device Settings
.. image:: P168_LuxReadMethodOptions.png
:alt: Lux read-method options
* *Normal*: Normal read mode, will report the uncorrected ``Lux`` value.
* *Normal (no wait)*: Normal read mode, will report the uncorrected ``Lux`` value, doesn't wait for the sensor to complete a measurement, but retrieves it later after starting the read.
* *Corrected*: Corrected read mode, Raw calculated to ``Lux`` and corrected for non-linearity.
* *Auto*: Show the corrected value, auto-scaled with Gain factor and Integration time to avoid measuring errors because of over-exposing the sensor. This is the default setting.
* *Normal (no wait)*: Normal read mode, will report the uncorrected ``Lux`` value, doesn't wait for the sensor to complete a measurement.
* *Corrected (no wait)*: Corrected read mode, Raw calculated to ``Lux`` and corrected for non-linearity, doesn't wait for the sensor to complete a measurement.
* *Corrected (no wait)*: Corrected read mode, Raw calculated to ``Lux`` and corrected for non-linearity, doesn't wait for the sensor to complete a measurement, but retrieves it later after starting the read.
|
@@ -75,8 +69,6 @@ Device Settings
.. image:: P168_IntegrationTimeOptions.png
.. warning:: When the **Lux Read-method** is set to *Auto* the Gain factor and Integration time settings are ignored.
* **Power Save Mode**: The sensor can go into Power Save Mode on several levels, to conserve power when used in a battery operated device. When in power save mode, the integration time increases.
.. image:: P168_PowerSaveModeOptions.png
Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 18 KiB

+17 -7
View File
@@ -247,13 +247,13 @@ On selected builds (ESP32 only, can be enabled in ESP8266 Custom builds) per val
If set the UoM will be space-appended to the value when displayed on the Devices page, used in the labels for the Stats display, and later used in the MQTT AutoDiscovery messages so the receiving server can use that for presentation. When set it is also included in the JSON output as ``UoM`` per taskvalue, available at the ``/json`` endpoint of the ESP, to be used by external systems like EasyFetch.
A list of 150+ Unit of Measure values is available (derived of what's supported by Home Assistant):
A list of 165+ Unit of Measure values is available (derived of what's supported/expected by Home Assistant):
``°C, °F, K, %, Pa, hPa, bar, mbar, inHg, psi, W, kW, V, Wh, kWh, A, VA, mm, cm, m, km,`` ``L, mL, m³, ft³, m³/h, ft³/h, lx, UV index, µg/m³, mg/m³, p/m³, ppm, ppb,``
``°, €, $, ¢, µs, ms, s, min, h, d, w, m, y, in, ft, yd, mi, Hz, GHz, gal, fl. oz, m²,`` ``g, kg, mg, µg, oz, lb, µS/cm, W/m², mm/h, mm/s, in/s, m/s, in/h, km/h, mph, db, dBm,``
``bit, kbit, Mbit, Gbit, B, kB, MB, GB, TB, PB, EB, ZB, YB, KiB, MiB, GiB, TiB, PiB, EiB, ZiB, YiB,`` ``bit/s, kbit/s, Mbit/s, Gbit/s, B/s, kB/s, MB/s, GB/s, KiB/s, MiB/s, GiB/s,``
``ft/s, kn, mW, MW, GW, TW, BTU/(h·ft²), pH, cbar, mmHg, kPa, mA, µA, mV, µV, kV, cm², km², mm²,`` ``in², ft², yd², mi², ac, ha, kHz, MHz, mWh, MWh, GWh, TWh, cal, kcal, Mcal,``
``Gcal, J, kJ, MJ, GJ, var, kvar, varh, kvarh, st, mg/dL, mmol/L, µSv, µSv/h, m³/s, ft³/min,`` ``L/h, L/min, L/s, gal/min, mL/s``
``Gcal, J, kJ, MJ, GJ, var, kvar, varh, kvarh, st, mg/dL, mmol/L, µSv, µSv/h, m³/s, ft³/min,`` ``L/h, L/min, L/s, gal/min, mL/s, g/m³, kWh/100km, Wh/km, mi/kWh, km/kWh, in/d, mm/d``
Displaying the Unit of Measure in the Devices overview page can be disabled by unchecking the **Show Unit of Measure** checkbox on the Tools/Advanced page.
@@ -275,19 +275,19 @@ Available options, grouped per category:
**Basic**: ``Single``
**Environment**: ``Temp¹, Hum¹, Baro¹, Wind speed¹``
**Environment**: ``Temp¹, Hum¹, Baro¹, Wind speed¹, Absolute humidity¹, Atmospheric pressure¹, Precipitation¹,`` ``Precipitation intensity¹``
**Dust/Gases**: ``Dust PM2.5¹, Dust PM1.0¹, Dust PM10¹, (e)CO2¹, TVOC¹, AQI¹, NOx¹``
**Dust/Gases**: ``Dust PM2.5¹, Dust PM1.0¹, Dust PM10¹, (e)CO2¹, CO¹, TVOC¹, VOC parts¹, AQI¹, NOx¹,`` ``Gas¹, N2O¹, Ozone¹, SO2¹``
**Energy**: ``Voltage¹, Current¹, Power Usage¹, Power Factor¹, Apparent Power Usage¹, Reactive Power¹``
**Energy**: ``Voltage¹, Current¹, Power Usage¹, Power Factor¹, Apparent Power Usage¹,`` ``Reactive Power¹, Reactive Energy¹, Energy¹, Energy storage¹, Energy distance¹``
**Time**: ``Duration¹, Date¹, Timestamp¹``
**Size**: ``Analog, Distance¹, Direction¹, Moisture¹, GPS, Weight¹, Data rate¹, Data size¹, Sound pressure¹, Signal strength¹``
**Size**: ``Analog, Distance¹, Direction¹, Moisture¹, GPS, Weight¹, Data rate¹, Data size¹, Sound pressure¹, Signal strength¹,`` ``Volume¹, Volume flow rate¹, Volume storage¹, Water¹``
**Light**: ``Lux¹, UV¹, UV Index¹, IR¹, Red¹, Green¹, Blue¹, Color temperature¹``
**Other**: ``Switch¹, Switch (inv.)¹, Dimmer, String, UInt32 (1x), Int32 (1x), UInt64 (1x), Int64 (1x), Double (1x)``
**Other**: ``Switch¹, Switch (inv.)¹, Dimmer, String, UInt32 (1x), Int32 (1x), UInt64 (1x), Int64 (1x), Double (1x), Frequency¹``
Value Types marked with ``¹`` are supported for use in MQTT AutoDiscovery.
@@ -299,6 +299,16 @@ Here's a partial preview (the 'None' value is selected):
.. image:: Task_config_page_ValueType_selector_part.png
MQTT State Class
^^^^^^^^^^^^^^^^
For some Value Types a State Class can be provided in the MQTT Discovery configuration. By default this setting is empty, but for (mostly Energy related) this can be configured for:
* *Measurement*
* *Measurement-angle*
* *Total*
* *Total-increasing*
|
.. _Plugin List:
@@ -110,7 +110,7 @@
.. |P078_status| replace:: :yellow:`ENERGY`
.. |P078_github| replace:: P078_Eastron.ino
.. _P078_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P078_Eastron.ino
.. |P078_usedby| replace:: `Modbus variants of Eastron SDM120M, SDM120CT, SDM220M, SDM230M, SDM72M, SDM72CTM, SDM630, SDM630MCT, SDM630-EV, SDM54-M, SDM320Y, DDM18SD, SMART X-96, MCS-U22, TAiYEDQ TAC2100 `
.. |P078_usedby| replace:: `Modbus variants of Eastron SDM120M, SDM120CT, SDM220M, SDM230M, SDM72M, SDM72CTM, SDM630, SDM630MCT, SDM630-EV, SDM54-M, SDM320Y, DDM18SD, SMART X-96, MCS-U22, TAiYEDQ TAC2100`
.. |P078_shortinfo| replace:: `.`
.. |P078_maintainer| replace:: TD-er
.. |P078_compileinfo| replace:: `.`
@@ -119,11 +119,11 @@
.. |P089_type| replace:: :cyan:`Communication`
.. |P089_typename| replace:: :cyan:`Communication - Ping`
.. |P089_porttype| replace:: `.`
.. |P089_status| replace:: :yellow:`COLLECTION`
.. |P089_status| replace:: :yellow:`COLLECTION` :yellow:`CLIMATE` :yellow:`ENERGY` :yellow:`NEOPIXEL`
.. |P089_github| replace:: P089_Ping.ino
.. _P089_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P089_Ping.ino
.. |P089_usedby| replace:: `.`
.. |P089_shortinfo| replace:: `.`
.. |P089_maintainer| replace:: `TD-er`
.. |P089_maintainer| replace:: `TD-er tonhuisman`
.. |P089_compileinfo| replace:: `.`
.. |P089_usedlibraries| replace:: `.`
.. |P089_usedlibraries| replace:: `ESPping (ESP32)`
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+41
View File
@@ -0,0 +1,41 @@
# ESPping
Let the ESP32/ESP8266 ping a remote machine.
With this library an ESP32/ESP8266 can ping a remote machine and know if it's reachable.
It provide some basic measurements on ping messages (avg response time).
thanks to Daniele Colanardi and Marian Craciunescu
## Usage
First, include the library in your sketch along with WiFi library:
```Arduino
#include <ESPping.h>
```
Next, simply call the `Ping.ping()` function
```Arduino
IPAddress ip (192, 168, 0, 1); // The remote ip to ping
bool ret = Ping.ping(ip);
```
`ret` will be true if the remote responded to pings, false if not reachable.
The library supports hostname too, just pass a string instead of the ip address:
```Arduino
bool ret = Ping.ping("www.google.com");
```
Additionally, the function accept a second integer parameter `count` that specify how many pings has to be sent:
```Arduino
bool ret = Ping.ping(ip_or_host, 10);
```
After `Ping.ping()` has been called, the average response time (in milliseconds) can be retrieved with
```Arduino
float avg_time_ms = Ping.averageTime();
```
@@ -0,0 +1,61 @@
/******************************************************
test Ping
******************************************************/
#include <ESPping.h>
void setup()
{
Serial.begin(115200);
delay(100);
WiFi.begin("ssid","password");
// attente connexion
Serial.print("\nConnection");
while (WiFi.status() != WL_CONNECTED)
{
delay(500);
Serial.print(".");
}
Serial.print("\nConnection OK, IP: ");
Serial.print(WiFi.localIP());
Serial.print(" Gateway: ");
Serial.println(WiFi.gatewayIP());
Serial.println("You can try to ping me ;-)");
}
void loop()
{
// Ping IP
const IPAddress remote_ip(9,9,9,9);
Serial.print(remote_ip);
if (Ping.ping(remote_ip) > 0){
Serial.printf(" response time : %d/%.2f/%d ms\n", Ping.minTime(), Ping.averageTime(), Ping.maxTime());
} else {
Serial.println(" Error !");
}
delay(1000);
// Ping Host
const char* remote_host = "quad9.net";
Serial.print(remote_host);
if (Ping.ping(remote_host) > 0){
Serial.printf(" response time : %d/%.2f/%d ms\n", Ping.minTime(), Ping.averageTime(), Ping.maxTime());
} else {
Serial.println(" Ping Error !");
}
delay(1000);
// Ping local IP
Serial.print(WiFi.gatewayIP());
if (Ping.ping(WiFi.gatewayIP()) > 0){
Serial.printf(" response time : %d/%.2f/%d ms\n", Ping.minTime(), Ping.averageTime(), Ping.maxTime());
} else {
Serial.println(" Ping Error !");
}
delay(1000);
}
+29
View File
@@ -0,0 +1,29 @@
#######################################
# Syntax Coloring Map For ESPping
#######################################
#######################################
# Library (KEYWORD3)
#######################################
ESPping KEYWORD3
#######################################
# Class (KEYWORD1)
#######################################
Ping KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
#######################################
ping KEYWORD2
minTime KEYWORD2
averageTime KEYWORD2
maxTime KEYWORD2
#######################################
# Constants (LITERAL1)
#######################################
+9
View File
@@ -0,0 +1,9 @@
name=ESPping
version=1.0.5
author=dvarrel, Daniele Colanardi, Marian Craciunescu
maintainer=dvarrel
sentence=Let the ESP32/ESP8266 ping a remote machine.
paragraph=With this library an ESP32/ESP8266 can ping a remote machine and know if reachable. It provide some basic measurements on ping messages (avg response time).
category=Communication
url=https://github.com/dvarrel/ESPping.git
architectures=esp8266,esp32
+161
View File
@@ -0,0 +1,161 @@
/*
ESP8266Ping - Ping library for ESP8266
Copyright (c) 2015 Daniele Colanardi. All rights reserved.
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 St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "ESPping.h"
#include <stdint.h>
#ifdef ESP8266
extern "C" void esp_schedule();
extern "C" void esp_yield();
#endif
#ifdef ESP32
extern "C" void esp_schedule(void) {};
extern "C" void esp_yield(void) {};
#endif
PingClass::PingClass() {}
bool PingClass::ping(IPAddress dest, int16_t count) {
_expected_count = count;
_errors = 0;
_success = 0;
_min_time = INT_MAX;
_avg_time = 0.0f;
_max_time = 0;
memset(&_options, 0, sizeof(struct ping_option));
// Repeat count (how many time send a ping message to destination)
_options.count = count;
// Time interval between two ping (seconds??)
_options.coarse_time = 1;
// Destination machine
_options.ip = dest;
// Callbacks
_options.recv_function = reinterpret_cast<ping_recv_function>(&PingClass::_ping_recv_cb);
_options.sent_function = NULL; //reinterpret_cast<ping_sent_function>(&_ping_sent_cb);
// Let's go!
if(ping_start(&_options)) {
#ifdef ESP8266
//wait until finished
while((_success + _errors) < _expected_count){
delay(1);
esp_yield();
};
#else
// Suspend till the process end
esp_yield();
#endif
}
return (_success > 0);
}
bool PingClass::ping(const char* host, int16_t count) {
IPAddress remote_addr;
if (WiFi.hostByName(host, remote_addr))
return ping(remote_addr, count);
return false;
}
float PingClass::averageTime() {
return _avg_time;
}
float PingClass::minTime() {
return _min_time;
}
float PingClass::maxTime() {
return _max_time;
}
void PingClass::_ping_recv_cb(void *opt, void *resp) {
// Cast the parameters to get some usable info
ping_resp* ping_resp = reinterpret_cast<struct ping_resp*>(resp);
//ping_option* ping_opt = reinterpret_cast<struct ping_option*>(opt);
// Error or success?
#ifdef ESP8266
if (ping_resp->ping_err == -1)
_errors++;
else {
_success++;
_avg_time += ping_resp->resp_time;
if(ping_resp->resp_time < _min_time)
_min_time = ping_resp->resp_time;
if(ping_resp->resp_time > _max_time)
_max_time = ping_resp->resp_time;
}
#else
_errors = ping_resp->ping_err;
_success = _expected_count - _errors;
_avg_time = ping_resp->resp_time;
#endif
// Some debug info
DEBUG_PING(
"DEBUG: ping reply"
"\ttotal_count = %d"
"\tresp_time = %d"
"\tseqno = %d"
"\ttimeout_count = %d"
"\tbytes = %d"
"\ttotal_bytes = %d"
"\ttotal_time = %d"
"\tping_err = %d \n",
(int)ping_resp->total_count, (int)ping_resp->resp_time, (int)ping_resp->seqno,
(int)ping_resp->timeout_count, (int)ping_resp->bytes, (int)ping_resp->total_bytes,
(int)ping_resp->total_time, (int)ping_resp->ping_err
);
// Is it time to end?
// Don't using seqno because it does not increase on error
#ifdef ESP8266
if (_success + _errors == _expected_count ) {
_avg_time = _success > 0 ? _avg_time / _success : 0;
#else
if (ping_resp->total_count== _expected_count) {
#endif
DEBUG_PING("success %d errors %d\n", _success, _errors);
DEBUG_PING("Resp times min %d, avg %.2f, max %d ms\n", _min_time, _avg_time, _max_time);
#ifdef ESP32
// Done, return to main functiom
esp_schedule();
#endif
}
}
byte PingClass::_expected_count = 0;
byte PingClass::_errors = 0;
byte PingClass::_success = 0;
float PingClass::_avg_time = 0;
float PingClass::_min_time = std::numeric_limits<float>::max();
float PingClass::_max_time = 0;
PingClass Ping;
+74
View File
@@ -0,0 +1,74 @@
/*
ESP32Ping - Ping library for ESP32 or ESP8266
Copyright (c) 2018 Marian Craciunescu. All rights reserved.
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 St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef ESPping_H
#define ESPping_H
#include <stdint.h>
#include <Arduino.h>
#ifdef ESP32
#include <WiFi.h>
#include <ping32.h>
extern "C" {
void esp_schedule(void);
void esp_yield(void);
}
#endif
#ifdef ESP8266
#include <ESP8266WiFi.h>
extern "C" {
#include <ping.h>
}
#endif
//#define ENABLE_DEBUG_PING 0
#ifdef ENABLE_DEBUG_PING
#define DEBUG_PING(...) Serial.printf(__VA_ARGS__)
#else
#define DEBUG_PING(...)
#endif
class PingClass {
public:
PingClass();
bool ping(IPAddress dest, int16_t count = 5);
bool ping(const char* host, int16_t count = 5);
float averageTime();
float minTime();
float maxTime();
protected:
static void _ping_sent_cb(void *opt, void *pdata);
static void _ping_recv_cb(void *opt, void *pdata);
IPAddress _dest;
ping_option _options;
static byte _expected_count, _errors, _success;
static float _min_time, _max_time;
static float _avg_time;
};
extern PingClass Ping;
#endif
+384
View File
@@ -0,0 +1,384 @@
/*
* ESP32 Ping library
*
* All rights reserved.
*
* Permission to use, copy, modify, and distribute this software
* and its documentation for any purpose and without fee is hereby
* granted, provided that the above copyright notice appear in all
* copies and that both that the copyright notice and this
* permission notice and warranty disclaimer appear in supporting
* documentation, and that the name of the author not be used in
* advertising or publicity pertaining to distribution of the
* software without specific, written prior permission.
*
* The author disclaim all warranties with regard to this
* software, including all implied warranties of merchantability
* and fitness. In no event shall the author be liable for any
* special, indirect or consequential damages or any damages
* whatsoever resulting from loss of use, data or profits, whether
* in an action of contract, negligence or other tortious action,
* arising out of or in connection with the use or performance of
* this software.
*
* --------------------------------------------------------------------------------
* Ping Library is based on the following source code:
*
* Lua RTOS, ping utility
*
*
* Author: Jaume Oliv (jolive@iberoxarxa.com / jolive@whitecatboard.org)
*
* --------------------------------------------------------------------------------
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
* SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
* OF SUCH DAMAGE.
*
* This file is part of the lwIP TCP/IP stack.
*
*/
#ifdef ESP32
#include <Arduino.h>
#include <math.h>
#include <float.h>
#include <signal.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include "ping32.h"
#include "lwip/inet_chksum.h"
#include "lwip/ip.h"
#include "lwip/ip4.h"
#include "lwip/err.h"
#include "lwip/icmp.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include "lwip/netdb.h"
#include "lwip/dns.h"
static uint16_t ping_seq_num;
static uint8_t stopped = 0;
/*
* Statistics
*/
static uint32_t transmitted = 0;
static uint32_t received = 0;
static float min_time = 0;
static float max_time = 0;
static float mean_time = 0;
static float last_mean_time = 0;
static float var_time = 0;
#define PING_ID 0xAFAF
#ifndef PING_DEFAULT_COUNT
#define PING_DEFAULT_COUNT 10
#endif
#ifndef PING_DEFAULT_INTERVAL
#define PING_DEFAULT_INTERVAL 1
#endif
#ifndef PING_DEFAULT_SIZE
#define PING_DEFAULT_SIZE 32
#endif
#ifndef PING_DEFAULT_TIMEOUT
#define PING_DEFAULT_TIMEOUT 1
#endif
/*
* Helper functions
*
*/
static void ping_prepare_echo(struct icmp_echo_hdr *iecho, uint16_t len) {
size_t i;
size_t data_len = len - sizeof(struct icmp_echo_hdr);
ICMPH_TYPE_SET(iecho, ICMP_ECHO);
ICMPH_CODE_SET(iecho, 0);
iecho->chksum = 0;
iecho->id = PING_ID;
iecho->seqno = htons(++ping_seq_num);
/* fill the additional data buffer with some data */
for (i = 0; i < data_len; i++) {
((char*)iecho)[sizeof(struct icmp_echo_hdr) + i] = (char)i;
}
iecho->chksum = inet_chksum(iecho, len);
}
static err_t ping_send(int s, ip4_addr_t *addr, int size) {
struct icmp_echo_hdr *iecho;
struct sockaddr_in to;
size_t ping_size = sizeof(struct icmp_echo_hdr) + size;
int err;
iecho = (struct icmp_echo_hdr *)mem_malloc((mem_size_t)ping_size);
if (!iecho) {
mem_free(iecho);
return ERR_MEM;
}
ping_prepare_echo(iecho, (uint16_t)ping_size);
to.sin_len = sizeof(to);
to.sin_family = AF_INET;
inet_addr_from_ip4addr(&to.sin_addr, addr);
if ((err = sendto(s, iecho, ping_size, 0, (struct sockaddr*)&to, sizeof(to)))) {
transmitted++;
}
mem_free(iecho);
return (err ? ERR_OK : ERR_VAL);
}
static void ping_recv(int s) {
char buf[64];
int fromlen, len;
struct sockaddr_in from;
struct ip_hdr *iphdr;
struct icmp_echo_hdr *iecho = NULL;
char ipa[16];
struct timeval begin;
struct timeval end;
uint64_t micros_begin;
uint64_t micros_end;
float elapsed;
// Register begin time
gettimeofday(&begin, NULL);
// Send
while ((len = recvfrom(s, buf, sizeof(buf), 0, (struct sockaddr*)&from, (socklen_t*)&fromlen)) > 0) {
if (len >= (int)(sizeof(struct ip_hdr) + sizeof(struct icmp_echo_hdr))) {
// Register end time
gettimeofday(&end, NULL);
/// Get from IP address
ip4_addr_t fromaddr;
inet_addr_to_ip4addr(&fromaddr, &from.sin_addr);
strcpy(ipa, inet_ntoa(fromaddr));
// Get echo
iphdr = (struct ip_hdr *)buf;
iecho = (struct icmp_echo_hdr *)(buf + (IPH_HL(iphdr) * 4));
// Print ....
if ((iecho->id == PING_ID) && (iecho->seqno == htons(ping_seq_num))) {
received++;
// Get elapsed time in milliseconds
micros_begin = begin.tv_sec * 1000000;
micros_begin += begin.tv_usec;
micros_end = end.tv_sec * 1000000;
micros_end += end.tv_usec;
elapsed = (float)(micros_end - micros_begin) / (float)1000.0;
// Update statistics
// Mean and variance are computed in an incremental way
if (elapsed < min_time) {
min_time = elapsed;
}
if (elapsed > max_time) {
max_time = elapsed;
}
last_mean_time = mean_time;
mean_time = (((received - 1) * mean_time) + elapsed) / received;
if (received > 1) {
var_time = var_time + ((elapsed - last_mean_time) * (elapsed - mean_time));
}
// Print ...
log_d("%d bytes from %s: icmp_seq=%d time=%.3f ms", len, ipa,
ntohs(iecho->seqno), elapsed
);
return;
}
else {
// TODO
}
}
}
if (len < 0) {
log_d("Request timeout for icmp_seq %d", ping_seq_num);
}
}
/*
static void stop_action(int i) {
signal(i, SIG_DFL);
stopped = 1;
}
*/
/*
* Operation functions
*
*/
void ping(const char *name, int16_t count, int interval, int size, int timeout) {
// Resolve name
hostent * target = gethostbyname(name);
IPAddress adr = *target->h_addr_list[0];
if (target->h_length == 0) {
// TODO: error not found target?????
return;
}
ping_start(adr, count, interval, size, timeout);
}
bool ping_start(struct ping_option *ping_o) {
return ping_start(ping_o->ip,ping_o->count,0,0,0,ping_o);
}
bool ping_start(IPAddress adr, int16_t count=0, int interval=0, int size=0, int timeout=0, struct ping_option *ping_o) {
// driver_error_t *error;
struct sockaddr_in address;
ip4_addr_t ping_target;
int s;
// Get default values if argument are not provided
if (count == 0) {
count = PING_DEFAULT_COUNT;
}
if (interval == 0) {
interval = PING_DEFAULT_INTERVAL;
}
if (size == 0) {
size = PING_DEFAULT_SIZE;
}
if (timeout == 0) {
timeout = PING_DEFAULT_TIMEOUT;
}
// Create socket
if ((s = socket(AF_INET, SOCK_RAW, IP_PROTO_ICMP)) < 0) {
// TODO: error
return false;
}
address.sin_addr.s_addr = adr;
ping_target.addr = address.sin_addr.s_addr;
// Setup socket
struct timeval tout;
// Timeout
tout.tv_sec = timeout;
tout.tv_usec = 0;
if (setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tout, sizeof(tout)) < 0) {
closesocket(s);
// TODO: error
return false;
}
stopped = 0;
transmitted = 0;
received = 0;
min_time = 1.E+9;// FLT_MAX;
max_time = 0.0;
mean_time = 0.0;
var_time = 0.0;
// Register signal for stop ping
//signal(SIGINT, stop_action);
// Begin ping ...
char ipa[16];
strcpy(ipa, inet_ntoa(ping_target));
log_i("PING %s: %d data bytes", ipa, size);
ping_seq_num = 0;
unsigned long ping_started_time = millis();
while ((ping_seq_num < count) && (!stopped)) {
if (ping_send(s, &ping_target, size) == ERR_OK) {
ping_recv(s);
}
if(ping_seq_num < count){
delay( interval*1000L);
}
}
closesocket(s);
log_i("%d packets transmitted, %d packets received, %.1f%% packet loss",
transmitted,
received,
((((float)transmitted - (float)received) / (float)transmitted) * 100.0)
);
if (ping_o) {
ping_resp pingresp;
log_i("round-trip min/avg/max/stddev = %.3f/%.3f/%.3f/%.3f ms", min_time, mean_time, max_time, sqrt(var_time / received));
pingresp.total_count = count; //Number of pings
pingresp.resp_time = mean_time; //Average time for the pings
pingresp.seqno = 0; //not relevant
pingresp.timeout_count = transmitted - received; //number of pings which failed
pingresp.bytes = size; //number of bytes received for 1 ping
pingresp.total_bytes = size * count; //number of bytes for all pings
pingresp.total_time = (millis() - ping_started_time) / 1000.0; //Time consumed for all pings; it takes into account also timeout pings
pingresp.ping_err = transmitted - received; //number of pings failed
// Call the callback function
ping_o->recv_function(ping_o, &pingresp);
}
// Return true if at least one ping had a successfull "pong"
return (received > 0);
}
bool ping_regist_recv(struct ping_option *ping_opt, ping_recv_function ping_recv)
{
if (ping_opt == NULL)
return false;
ping_opt->recv_function = ping_recv;
return true;
}
bool ping_regist_sent(struct ping_option *ping_opt, ping_sent_function ping_sent)
{
if (ping_opt == NULL)
return false;
ping_opt->sent_function = ping_sent;
return true;
}
#endif
+37
View File
@@ -0,0 +1,37 @@
#ifdef ESP32
#ifndef PING32_H
#define PING32_H
#include <Arduino.h>
#include <stdint.h>
typedef void(*ping_recv_function)(void* arg, void *pdata);
typedef void(*ping_sent_function)(void* arg, void *pdata);
struct ping_option {
int16_t count;
uint32_t ip;
uint32_t coarse_time;
ping_recv_function recv_function;
ping_sent_function sent_function;
void* reverse;
};
struct ping_resp {
uint32_t total_count;
float resp_time;
uint32_t seqno;
uint32_t timeout_count;
uint32_t bytes;
uint32_t total_bytes;
float total_time;
int8_t ping_err;
};
bool ping_start(struct ping_option *ping_opt);
void ping(const char *name, int16_t count, int interval, int size, int timeout);
bool ping_start(IPAddress adr, int16_t count, int interval, int size, int timeout, struct ping_option *ping_o = NULL);
#endif
#endif // PING_H
+7 -1
View File
@@ -214,8 +214,14 @@ bool CPlugin_002(CPlugin::Function function, struct EventStruct *event, String&
String pubname = CPlugin_002_pubname;
parseControllerVariables(pubname, event, false);
const bool taskRetained = Settings.SendRetainedTaskValues(event->TaskIndex, event->ControllerIndex);
// Publish using move operator, thus pubname and json are empty after this call
success = MQTTpublish(event->ControllerIndex, event->TaskIndex, std::move(pubname), std::move(json), CPlugin_002_mqtt_retainFlag);
success = MQTTpublish(event->ControllerIndex,
event->TaskIndex,
std::move(pubname),
std::move(json),
CPlugin_002_mqtt_retainFlag || taskRetained);
} // if idx !=0
else
{
+2 -2
View File
@@ -191,9 +191,9 @@ bool CPlugin_005(CPlugin::Function function, struct EventStruct *event, String&
break;
}
// String pubname = CPlugin_005_pubname;
const bool taskRetained = Settings.SendRetainedTaskValues(event->TaskIndex, event->ControllerIndex);
success = MQTT_protocol_send(event, CPlugin_005_pubname, CPlugin_005_mqtt_retainFlag);
success = MQTT_protocol_send(event, CPlugin_005_pubname, CPlugin_005_mqtt_retainFlag || taskRetained);
break;
}
+7 -4
View File
@@ -11,6 +11,7 @@
* 2023-03-15 tonhuisman: Handle setting payload to (Dummy) Devices via topic SysName/TaskName/ValueName/set
* 2023-03 Changelog started
*/
// # include "src/Commands/InternalCommands.h"
# include "src/Commands/ExecuteCommand.h"
# include "src/ESPEasyCore/Controller.h"
@@ -44,9 +45,9 @@ bool CPlugin_006(CPlugin::Function function, struct EventStruct *event, String&
proto.usesExtCreds = true;
proto.defaultPort = 1883;
proto.usesID = false;
#if FEATURE_MQTT_TLS
proto.usesTLS = true;
#endif
# if FEATURE_MQTT_TLS
proto.usesTLS = true;
# endif // if FEATURE_MQTT_TLS
# if FEATURE_STRING_VARIABLES
proto.allowSendDerived = true;
# endif // if FEATURE_STRING_VARIABLES
@@ -130,7 +131,9 @@ bool CPlugin_006(CPlugin::Function function, struct EventStruct *event, String&
break;
}
success = MQTT_protocol_send(event, CPlugin_006_pubname, CPlugin_006_mqtt_retainFlag);
const bool taskRetained = Settings.SendRetainedTaskValues(event->TaskIndex, event->ControllerIndex);
success = MQTT_protocol_send(event, CPlugin_006_pubname, CPlugin_006_mqtt_retainFlag || taskRetained);
break;
}
+3 -1
View File
@@ -809,7 +809,9 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String&
break;
}
success = MQTT_protocol_send(event, CPlugin_014_pubname, CPlugin_014_mqtt_retainFlag);
const bool taskRetained = Settings.SendRetainedTaskValues(event->TaskIndex, event->ControllerIndex);
success = MQTT_protocol_send(event, CPlugin_014_pubname, CPlugin_014_mqtt_retainFlag || taskRetained);
break;
}
+1
View File
@@ -44,6 +44,7 @@ boolean Plugin_026(uint8_t function, struct EventStruct *event, String& string)
dev.OutputDataType = Output_Data_type_t::Simple;
dev.PluginStats = true;
dev.CustomVTypeVar = true;
dev.MqttStateClass = true;
break;
}
+1
View File
@@ -34,6 +34,7 @@ boolean Plugin_033(uint8_t function, struct EventStruct *event, String& string)
dev.OutputDataType = Output_Data_type_t::All;
dev.PluginStats = true;
dev.CustomVTypeVar = true;
dev.MqttStateClass = true;
break;
}
+4 -3
View File
@@ -11,6 +11,7 @@
// This task reads data from the MQTT Import input stream and saves the value
/**
* 2025-08-20 tonhuisman: Generate events with numeric values using the configured decimals setting.
* 2025-06-14 tonhuisman: Add support for Custom Value Type per task value
* 2025-01-12 tonhuisman: Add support for MQTT AutoDiscovery (not supported for MQTT Import)
* Update changelog
@@ -643,10 +644,10 @@ boolean Plugin_037(uint8_t function, struct EventStruct *event, String& string)
# if !defined(P037_LIMIT_BUILD_SIZE) || defined(P037_OVERRIDE)
if (loglevelActiveFor(LOG_LEVEL_INFO)) {
addLog(LOG_LEVEL_INFO, strformat(F("IMPT : [%s#%s] : %.3f"),
addLog(LOG_LEVEL_INFO, strformat(F("IMPT : [%s#%s] : %s"),
getTaskDeviceName(event->TaskIndex).c_str(),
checkJson ? key.c_str() : getTaskValueName(event->TaskIndex, x).c_str(),
doublePayload));
toString(doublePayload, ExtraTaskSettings.TaskDeviceValueDecimals[x]).c_str()));
}
# endif // if !defined(P037_LIMIT_BUILD_SIZE) || defined(P037_OVERRIDE)
@@ -687,7 +688,7 @@ boolean Plugin_037(uint8_t function, struct EventStruct *event, String& string)
RuleEvent += '=';
if (numericPayload) {
RuleEvent += doublePayload;
RuleEvent += toString(doublePayload, ExtraTaskSettings.TaskDeviceValueDecimals[x]);
} else {
RuleEvent += wrapWithQuotesIfContainsParameterSeparatorChar(Payload);
}
+32 -38
View File
@@ -11,26 +11,21 @@
This plugin reads the particle concentration from SDS011 Sensor
DevicePin1 - RX on ESP, TX on SDS
DevicePin2 - TX on ESP, RX on SDS, optional, for setting the sleep time
*/
/** Changelog:
* 2025-08-05 tonhuisman: Introduce multi-instance use
* 2025-01-12 tonhuisman: Add support for MQTT AutoDiscovery
*/
// #ifdef ESP8266 // Needed for precompile issues.
# define PLUGIN_056
# define PLUGIN_ID_056 56
# define PLUGIN_NAME_056 "Dust - SDS011/018/198"
# define PLUGIN_VALUENAME1_056 "PM2.5" // Dust <2.5µm in µg/m³ SDS198:<100µm in µg/m³
# define PLUGIN_VALUENAME2_056 "PM10" // Dust <10µm in µg/m³
# include <jkSDS011.h>
# include "ESPEasy-Globals.h"
CjkSDS011 *Plugin_056_SDS = nullptr;
# include "src/PluginStructs/P056_data_struct.h"
boolean Plugin_056(uint8_t function, struct EventStruct *event, String& string)
@@ -50,6 +45,9 @@ boolean Plugin_056(uint8_t function, struct EventStruct *event, String& string)
dev.SendDataOption = true;
dev.TimerOption = true;
dev.PluginStats = true;
// Keep alive for setting the sleep time on save, only when changed
dev.ExitTaskBeforeSave = false;
break;
}
@@ -109,7 +107,7 @@ boolean Plugin_056(uint8_t function, struct EventStruct *event, String& string)
if (PCONFIG(0) != newsleeptime) {
PCONFIG(0) = newsleeptime;
Plugin_056_setWorkingPeriod(newsleeptime);
Plugin_056_setWorkingPeriod(event, newsleeptime);
}
}
success = true;
@@ -118,45 +116,34 @@ boolean Plugin_056(uint8_t function, struct EventStruct *event, String& string)
case PLUGIN_INIT:
{
if (Plugin_056_SDS) {
delete Plugin_056_SDS;
}
initPluginTaskData(event->TaskIndex, new (std::nothrow) P056_data_struct(event));
P056_data_struct *P056_data =
static_cast<P056_data_struct *>(getPluginTaskData(event->TaskIndex));
success = (nullptr != P056_data && P056_data->isInitialized());
const ESPEasySerialPort port = static_cast<ESPEasySerialPort>(CONFIG_PORT);
Plugin_056_SDS = new (std::nothrow) CjkSDS011(port, CONFIG_PIN1, CONFIG_PIN2);
if (loglevelActiveFor(LOG_LEVEL_INFO)) {
addLog(LOG_LEVEL_INFO, strformat(F("SDS : Init OK ESP GPIO-pin RX:%d TX:%d"), CONFIG_PIN1, CONFIG_PIN2));
}
success = true;
break;
}
case PLUGIN_EXIT:
{
// //FIXME: if this plugin is used more than once at the same time, things go horribly wrong :)
// FIXME TD-er: Must implement plugin_data_struct for this
//
// if (Plugin_056_SDS)
// delete Plugin_056_SDS;
// addLog(LOG_LEVEL_INFO, F("SDS : Exit"));
shouldReboot = true;
break;
}
case PLUGIN_FIFTY_PER_SECOND:
{
if (!Plugin_056_SDS) {
P056_data_struct *P056_data =
static_cast<P056_data_struct *>(getPluginTaskData(event->TaskIndex));
if ((nullptr == P056_data) || !P056_data->isInitialized()) {
break;
}
Plugin_056_SDS->Process();
P056_data->Process();
if (Plugin_056_SDS->available())
if (P056_data->available())
{
const float pm2_5 = Plugin_056_SDS->GetPM2_5();
const float pm10 = Plugin_056_SDS->GetPM10_();
const float pm2_5 = P056_data->GetPM2_5();
const float pm10 = P056_data->GetPM10_();
# ifndef BUILD_NO_DEBUG
if (loglevelActiveFor(LOG_LEVEL_DEBUG)) {
@@ -179,13 +166,17 @@ boolean Plugin_056(uint8_t function, struct EventStruct *event, String& string)
case PLUGIN_READ:
{
if (!Plugin_056_SDS) {
P056_data_struct *P056_data =
static_cast<P056_data_struct *>(getPluginTaskData(event->TaskIndex));
if ((nullptr == P056_data) || !P056_data->isInitialized()) {
break;
}
float pm25, pm10;
float pm25{};
float pm10{};
if (Plugin_056_SDS->ReadAverage(pm25, pm10)) {
if (P056_data->ReadAverage(pm25, pm10)) {
UserVar.setFloat(event->TaskIndex, 0, pm25);
UserVar.setFloat(event->TaskIndex, 1, pm10);
success = true;
@@ -220,11 +211,14 @@ String Plugin_056_WorkingPeriodToString(int workingPeriod) {
return log;
}
void Plugin_056_setWorkingPeriod(int minutes) {
if (!Plugin_056_SDS) {
void Plugin_056_setWorkingPeriod(EventStruct *event, int minutes) {
P056_data_struct *P056_data =
static_cast<P056_data_struct *>(getPluginTaskData(event->TaskIndex));
if ((nullptr == P056_data) || !P056_data->isInitialized()) {
return;
}
Plugin_056_SDS->SetWorkingPeriod(minutes);
P056_data->SetWorkingPeriod(minutes);
if (loglevelActiveFor(LOG_LEVEL_INFO)) {
addLog(LOG_LEVEL_INFO, concat(F("SDS : Working Period set to: "), Plugin_056_WorkingPeriodToString(minutes)));
+2 -1
View File
@@ -230,6 +230,7 @@ boolean Plugin_076(uint8_t function, struct EventStruct *event, String& string)
dev.PluginStats = true;
dev.setPin1Direction(gpio_direction::gpio_output);
dev.OutputDataType = Output_Data_type_t::Simple;
dev.MqttStateClass = true;
// dev.TaskLogsOwnPeaks = true;
break;
@@ -953,7 +954,7 @@ int Plugin_076_QueryVType(uint8_t value_nr) {
case P076_INDEX_PF:
result = Sensor_VType::SENSOR_TYPE_POWER_FACT_ONLY; break;
case P076_INDEX_ENER:
result = Sensor_VType::SENSOR_TYPE_NONE; break; // FIXME Add support for Energy + Unit of Measure
result = Sensor_VType::SENSOR_TYPE_ENERGY; break;
}
return static_cast<int>(result);
}
+2 -1
View File
@@ -55,6 +55,7 @@ boolean Plugin_077(uint8_t function, struct EventStruct *event, String& string)
dev.TimerOptional = true;
dev.PluginStats = true;
dev.TaskLogsOwnPeaks = true;
dev.MqttStateClass = true;
break;
}
@@ -96,7 +97,7 @@ boolean Plugin_077(uint8_t function, struct EventStruct *event, String& string)
# if FEATURE_MQTT_DISCOVER
case PLUGIN_GET_DISCOVERY_VTYPES:
{
success = getDiscoveryVType(event, Plugin_077_QueryVType, P077_QUERY1_CONFIG_POS, event->Par5);;
success = getDiscoveryVType(event, Plugin_077_QueryVType, P077_QUERY1_CONFIG_POS, event->Par5);
break;
}
# endif // if FEATURE_MQTT_DISCOVER
+4 -3
View File
@@ -58,6 +58,7 @@ boolean Plugin_078(uint8_t function, struct EventStruct *event, String& string)
dev.TimerOption = true;
dev.PluginStats = true;
dev.TaskLogsOwnPeaks = true;
dev.MqttStateClass = true;
break;
}
@@ -194,12 +195,12 @@ boolean Plugin_078(uint8_t function, struct EventStruct *event, String& string)
F("SDM630"),
F("SDM72_V2"),
F("SDM320C"),
F("TAC2100")
F("TAC2100"),
};
constexpr size_t nrOptions = NR_ELEMENTS(options_model);
const FormSelectorOptions selector(nrOptions, options_model);
FormSelectorOptions selector(nrOptions, options_model);
selector.reloadonchange = true;
selector.addFormSelector(F("Model Type"), P078_MODEL_LABEL, P078_MODEL);
addFormNote(F("Submit after changing the modell to update Output Configuration."));
}
success = true;
break;
+2 -1
View File
@@ -43,6 +43,7 @@ boolean Plugin_085(uint8_t function, struct EventStruct *event, String& string)
dev.TimerOption = true;
dev.ExitTaskBeforeSave = false;
dev.PluginStats = true;
dev.MqttStateClass = true;
dev.setPin3Direction(gpio_direction::gpio_output);
break;
}
@@ -74,7 +75,7 @@ boolean Plugin_085(uint8_t function, struct EventStruct *event, String& string)
# if FEATURE_MQTT_DISCOVER
case PLUGIN_GET_DISCOVERY_VTYPES:
{
success = getDiscoveryVType(event, Plugin_085_QueryVType, P085_QUERY1_CONFIG_POS, event->Par5);;
success = getDiscoveryVType(event, Plugin_085_QueryVType, P085_QUERY1_CONFIG_POS, event->Par5);
break;
}
# endif // if FEATURE_MQTT_DISCOVER
+72 -4
View File
@@ -1,5 +1,5 @@
#include "_Plugin_Helper.h"
#if defined(USES_P089) && defined(ESP8266)
#ifdef USES_P089
# include "src/PluginStructs/P089_data_struct.h"
@@ -19,6 +19,12 @@
Maintainer: Denys Fedoryshchenko, denys AT nuclearcat.com
*/
/** Changelog:
* 2025-08-15 tonhuisman: Move actual ping action to a service, instantiated by the first task instance, and deleted by the last stopped
* task instance. Service is looped in 10 per second function.
* Enable PluginStats for ESP32, so the trend of average ping-time can be displayed.
* 2025-08-15 tonhuisman: Reduce max. ping count from 100 to 25
* 2025-08-14 tonhuisman: Add code to plugin to use dvarrel/ESPping library that works for ESP32.
* ESP32 handles the ping in an independent RTOS task, to make it non-blocking.
* 2025-01-12 tonhuisman: Add support for MQTT AutoDiscovery (not supported for Ping)
* 2023-03-19 tonhuisman: Show hostname in GPIO column of Devices page
* 2023-03-14 tonhuisman: Change command handling to not require the taskname as the second argument if no 3rd argument is given.
@@ -30,6 +36,7 @@
# define PLUGIN_089
# define PLUGIN_NAME_089 "Communication - Ping"
# define PLUGIN_VALUENAME1_089 "Fails"
# define PLUGIN_VALUENAME2_089 "Avg_ms"
boolean Plugin_089(uint8_t function, struct EventStruct *event, String& string)
@@ -48,6 +55,9 @@ boolean Plugin_089(uint8_t function, struct EventStruct *event, String& string)
dev.DecimalsOnly = true;
dev.SendDataOption = true;
dev.TimerOption = true;
# ifdef ESP32
dev.PluginStats = true;
# endif // ifdef ESP32
break;
}
@@ -60,14 +70,34 @@ boolean Plugin_089(uint8_t function, struct EventStruct *event, String& string)
case PLUGIN_GET_DEVICEVALUENAMES:
{
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_089));
# ifdef ESP32
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_089));
# endif // ifdef ESP32
break;
}
# ifdef ESP32
case PLUGIN_GET_DEVICEVALUECOUNT:
{
int vCount = P089_VALUE_COUNT;
if (0 == vCount) {
vCount = 1;
}
event->Par1 = vCount;
success = true;
break;
}
# endif // ifdef ESP32
# if FEATURE_MQTT_DISCOVER
case PLUGIN_GET_DISCOVERY_VTYPES:
{
event->Par1 = static_cast<int>(Sensor_VType::SENSOR_TYPE_NONE); // Not yet supported
success = true;
# ifdef ESP32
event->Par2 = static_cast<int>(Sensor_VType::SENSOR_TYPE_DURATION);
# endif // ifdef ESP32
success = true;
break;
}
# endif // if FEATURE_MQTT_DISCOVER
@@ -92,12 +122,35 @@ boolean Plugin_089(uint8_t function, struct EventStruct *event, String& string)
char hostname[PLUGIN_089_HOSTNAME_SIZE]{};
LoadCustomTaskSettings(event->TaskIndex, (uint8_t *)&hostname, PLUGIN_089_HOSTNAME_SIZE);
addFormTextBox(F("Hostname"), F("host"), hostname, PLUGIN_089_HOSTNAME_SIZE - 2);
# ifdef ESP32
if (P089_PING_COUNT < 1) {
P089_PING_COUNT = 5;
}
addFormNumericBox(F("Ping count"), F("pcount"), P089_PING_COUNT, 1, P089_MAX_PING_COUNT);
const __FlashStringHelper*countOptions[] = {
F("Fails"),
F("Fails, Avg_ms"),
};
const int countValues[] = { 1, 2 };
FormSelectorOptions selector(2, countOptions, countValues);
selector.reloadonchange = true;
selector.addFormSelector(F("Available Values"), F("pvals"), P089_VALUE_COUNT);
# endif // ifdef ESP32
success = true;
break;
}
case PLUGIN_WEBFORM_SAVE:
{
# ifdef ESP32
P089_PING_COUNT = getFormItemInt(F("pcount"), 5);
P089_VALUE_COUNT = getFormItemInt(F("pvals"), 1);
# endif // ifdef ESP32
char hostname[PLUGIN_089_HOSTNAME_SIZE]{};
// Reset "Fails" if settings updated
@@ -112,15 +165,30 @@ boolean Plugin_089(uint8_t function, struct EventStruct *event, String& string)
{
success = initPluginTaskData(event->TaskIndex, new (std::nothrow) P089_data_struct());
UserVar.setFloat(event->TaskIndex, 0, 0);
# ifdef ESP32
UserVar.setFloat(event->TaskIndex, 1, 0);
# endif // ifdef ESP32
break;
}
# ifdef ESP32
case PLUGIN_TEN_PER_SECOND:
{
P089_data_struct *P089_taskdata =
static_cast<P089_data_struct *>(getPluginTaskData(event->TaskIndex));
if ((nullptr != P089_taskdata) && P089_taskdata->isInitialized()) {
success = P089_taskdata->loop();
}
break;
}
# endif // ifdef ESP32
case PLUGIN_READ:
{
P089_data_struct *P089_taskdata =
static_cast<P089_data_struct *>(getPluginTaskData(event->TaskIndex));
if (nullptr == P089_taskdata) {
if ((nullptr == P089_taskdata) || !P089_taskdata->isInitialized()) {
break;
}
@@ -165,4 +233,4 @@ boolean Plugin_089(uint8_t function, struct EventStruct *event, String& string)
return success;
}
#endif // if defined(USES_P089) && defined(ESP8266)
#endif // ifdef USES_P089
+1
View File
@@ -55,6 +55,7 @@ boolean Plugin_093(uint8_t function, struct EventStruct *event, String& string)
dev.TimerOption = true;
dev.TimerOptional = true;
dev.CustomVTypeVar = true;
dev.MqttStateClass = true;
break;
}
+3 -2
View File
@@ -71,6 +71,7 @@ boolean Plugin_102(uint8_t function, struct EventStruct *even
dev.OutputDataType = Output_Data_type_t::Simple;
dev.SendDataOption = true;
dev.TimerOption = true;
dev.MqttStateClass = true;
break;
}
@@ -405,9 +406,9 @@ int Plugin_102_QueryVType(uint8_t query) {
case 0: result = Sensor_VType::SENSOR_TYPE_VOLTAGE_ONLY; break;
case 1: result = Sensor_VType::SENSOR_TYPE_CURRENT_ONLY; break;
case 2: result = Sensor_VType::SENSOR_TYPE_POWER_USG_ONLY; break;
case 3: result = Sensor_VType::SENSOR_TYPE_NONE; break; // FIXME
case 3: result = Sensor_VType::SENSOR_TYPE_ENERGY; break;
case 4: result = Sensor_VType::SENSOR_TYPE_POWER_FACT_ONLY; break;
case 5: result = Sensor_VType::SENSOR_TYPE_NONE; break;
case 5: result = Sensor_VType::SENSOR_TYPE_FREQUENCY; break;
}
return static_cast<int>(result);
}
+1
View File
@@ -51,6 +51,7 @@ boolean Plugin_108(uint8_t function, struct EventStruct *event, String& string)
dev.SendDataOption = true;
dev.TimerOption = true;
dev.PluginStats = true;
dev.MqttStateClass = true;
dev.setPin3Direction(gpio_direction::gpio_output);
break;
}
+20 -8
View File
@@ -6,6 +6,8 @@
// #######################################################################################################
/**
* 2025-08-21 tonhuisman: Remove Lux Read-Method options without 'no wait' as that causes massive delays, disturbing other ESPEasy
* operations
* 2025-01-12 tonhuisman: Add support for MQTT AutoDiscovery
* 2024-06-21 tonhuisman: Fix support for VEML6030, using by default the alternate I2C address, by modifying the VEML7700 library
* 2024-05-18 tonhuisman: Implement AutoLux feature, and Get Config Value for automatically determined gain and integration
@@ -95,7 +97,7 @@ boolean Plugin_168(uint8_t function, struct EventStruct *event, String& string)
case PLUGIN_SET_DEFAULTS:
{
P168_READLUX_MODE = VEML_LUX_AUTO;
P168_READLUX_MODE = VEML_LUX_CORRECTED_NOWAIT;
P168_PSM_MODE = static_cast<int>(P168_power_save_mode_e::Disabled);
ExtraTaskSettings.TaskDeviceValueDecimals[1] = 0; // White
@@ -108,24 +110,34 @@ boolean Plugin_168(uint8_t function, struct EventStruct *event, String& string)
case PLUGIN_WEBFORM_LOAD:
{
{
uint8_t _readMethod = P168_READLUX_MODE;
if (static_cast<uint8_t>(VEML_LUX_NORMAL) == _readMethod) {
_readMethod = static_cast<uint8_t>(VEML_LUX_NORMAL_NOWAIT);
} else if ((static_cast<uint8_t>(VEML_LUX_CORRECTED) == _readMethod) || (static_cast<uint8_t>(VEML_LUX_AUTO) == _readMethod)) {
_readMethod = static_cast<uint8_t>(VEML_LUX_CORRECTED_NOWAIT);
}
P168_READLUX_MODE = _readMethod;
const __FlashStringHelper *readMethod[] = {
F("Normal"),
F("Corrected"),
F("Auto"),
// F("Normal"),
// F("Corrected"),
// F("Auto"),
F("Normal (no wait)"),
F("Corrected (no wait)"),
};
const int readMethodOptions[] = {
VEML_LUX_NORMAL,
VEML_LUX_CORRECTED,
VEML_LUX_AUTO,
// VEML_LUX_NORMAL,
// VEML_LUX_CORRECTED,
// VEML_LUX_AUTO,
VEML_LUX_NORMAL_NOWAIT,
VEML_LUX_CORRECTED_NOWAIT,
};
constexpr size_t optionCount = NR_ELEMENTS(readMethodOptions);
const FormSelectorOptions selector(optionCount, readMethod, readMethodOptions);
selector.addFormSelector(F("Lux Read-method"), F("rmth"), P168_READLUX_MODE);
addFormNote(F("For 'Auto' Read-method, the Gain factor and Integration time settings are ignored."));
// addFormNote(F("For 'Auto' Read-method, the Gain factor and Integration time settings are ignored."));
}
{
const __FlashStringHelper *alsGain[] = {
+1
View File
@@ -58,6 +58,7 @@ boolean Plugin_176(uint8_t function, struct EventStruct *event, String& string)
dev.TimerOptional = true;
dev.PluginStats = true;
dev.CustomVTypeVar = true;
dev.MqttStateClass = true;
break;
}
+24
View File
@@ -1753,6 +1753,9 @@ To create/register a plugin, you have to :
#ifndef USES_P085
#define USES_P085 // AcuDC24x
#endif
#ifndef USES_P089
#define USES_P089 // Ping
#endif
#ifndef USES_P093
#define USES_P093 // Mitsubishi Heat Pump
#endif
@@ -1977,6 +1980,9 @@ To create/register a plugin, you have to :
#ifndef USES_P083
#define USES_P083 // SGP30
#endif
#ifndef USES_P089
#define USES_P089 // Ping
#endif
#ifndef USES_P090
#define USES_P090 // CCS811
#endif
@@ -2094,6 +2100,9 @@ To create/register a plugin, you have to :
#ifndef USES_P070
#define USES_P070 // NeoPixel_Clock
#endif
#ifndef USES_P089
#define USES_P089 // Ping
#endif
#ifndef USES_P128
#define USES_P128 // NeoPixelBusFX
#endif
@@ -2326,6 +2335,9 @@ To create/register a plugin, you have to :
#ifndef USES_P088
#define USES_P088 // ToniA IR plugin
#endif
#ifndef USES_P089
#define USES_P089 // Ping
#endif
#ifndef USES_P094
#define USES_P094 // CUL Reader
#endif
@@ -3136,6 +3148,18 @@ To create/register a plugin, you have to :
#define FEATURE_MQTT_DEVICECLASS 0
#endif
#if !defined(FEATURE_MQTT_STATE_CLASS) && FEATURE_MQTT
#if defined(LIMIT_BUILD_SIZE) || defined(ESP8266) // Must enable this explicitly for ESP8266 Custom build
#define FEATURE_MQTT_STATE_CLASS 0
#else
#define FEATURE_MQTT_STATE_CLASS 1
#endif
#endif
#if !FEATURE_MQTT_DISCOVER && FEATURE_MQTT_STATE_CLASS
#undef FEATURE_MQTT_STATE_CLASS
#define FEATURE_MQTT_STATE_CLASS 0
#endif
#ifndef FEATURE_CUSTOM_TASKVAR_VTYPE
#if defined(LIMIT_BUILD_SIZE) || defined(ESP8266) // Must enable this explicitly for ESP8266 Custom build
#define FEATURE_CUSTOM_TASKVAR_VTYPE 0
+18
View File
@@ -324,6 +324,21 @@ uint8_t Caches::getTaskVarCustomVType(taskIndex_t TaskIndex,
#endif // if FEATURE_CUSTOM_TASKVAR_VTYPE
#if FEATURE_MQTT_STATE_CLASS
uint8_t Caches::getTaskVarStateClass(taskIndex_t TaskIndex,
taskVarIndex_t taskVarIndex) {
if (validTaskIndex(TaskIndex) && (validTaskVarIndex(taskVarIndex))) {
auto it = getExtraTaskSettings(TaskIndex);
if (it != extraTaskSettings_cache.end()) {
return it->second.mqttStateClass[taskVarIndex];
}
}
return 0;
}
#endif // if FEATURE_MQTT_STATE_CLASS
void Caches::updateExtraTaskSettingsCache()
{
const taskIndex_t TaskIndex = ExtraTaskSettings.TaskIndex;
@@ -374,6 +389,9 @@ void Caches::updateExtraTaskSettingsCache()
#if FEATURE_CUSTOM_TASKVAR_VTYPE
tmp.customVType[i] = ExtraTaskSettings.getTaskVarCustomVType(i);
#endif // if FEATURE_CUSTOM_TASKVAR_VTYPE
#if FEATURE_MQTT_STATE_CLASS
tmp.mqttStateClass[i] = ExtraTaskSettings.getTaskVarStateClass(i);
#endif // if FEATURE_MQTT_STATE_CLASS
}
#ifdef ESP32
tmp.TaskDevicePluginConfigLong_index_used = 0;
+19 -9
View File
@@ -50,13 +50,16 @@ struct ExtraTaskSettings_cache_t {
#if FEATURE_PLUGIN_STATS
PluginStats_Config_t pluginStatsConfig[VARS_PER_TASK] = {};
#endif // if FEATURE_PLUGIN_STATS
uint8_t hasFormula = 0; // Bitmap which task value has formula and whether a formula needs previous value
uint8_t hasFormula = 0; // Bitmap which task value has formula and whether a formula needs previous value
#if FEATURE_TASKVALUE_UNIT_OF_MEASURE
uint8_t unitOfMeasure[VARS_PER_TASK] = { 0 }; // Value for unit of measure per taskValue
uint8_t unitOfMeasure[VARS_PER_TASK] = { 0 }; // Value for unit of measure per taskValue
#endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE
#if FEATURE_CUSTOM_TASKVAR_VTYPE
uint8_t customVType[VARS_PER_TASK] = { 0 }; // single-value VType per taskValue
uint8_t customVType[VARS_PER_TASK] = { 0 }; // single-value VType per taskValue
#endif // if FEATURE_CUSTOM_TASKVAR_VTYPE
#if FEATURE_MQTT_STATE_CLASS
uint8_t mqttStateClass[VARS_PER_TASK] = { 0 }; // state_class = None, Measurement, Measurement Angle, Total, Total Increasing
#endif // if FEATURE_MQTT_STATE_CLASS
};
typedef std::map<String, taskIndex_t> TaskIndexNameMap;
@@ -91,9 +94,11 @@ struct Caches {
uint8_t rel_index);
// Check to see if at least one of the taskvalues has a non-empty formula field.
bool hasFormula(taskIndex_t TaskIndex, uint8_t rel_index);
bool hasFormula(taskIndex_t TaskIndex,
uint8_t rel_index);
bool hasFormula(taskIndex_t TaskIndex);
bool hasFormula_with_prevValue(taskIndex_t TaskIndex, uint8_t rel_index);
bool hasFormula_with_prevValue(taskIndex_t TaskIndex,
uint8_t rel_index);
String getTaskDeviceFormula(taskIndex_t TaskIndex,
@@ -116,14 +121,19 @@ struct Caches {
#if FEATURE_TASKVALUE_UNIT_OF_MEASURE
uint8_t getTaskVarUnitOfMeasure(taskIndex_t taskIndex,
taskVarIndex_t taskVarIndex);
taskVarIndex_t taskVarIndex);
#endif // if FEATURE_TASKVALUE_UNIT_OF_MEASURE
#if FEATURE_CUSTOM_TASKVAR_VTYPE
uint8_t getTaskVarCustomVType(taskIndex_t taskIndex,
taskVarIndex_t taskVarIndex);
#endif // if FEATURE_CUSTOM_TASKVAR_VTYPE
#if FEATURE_MQTT_STATE_CLASS
uint8_t getTaskVarStateClass(taskIndex_t taskIndex,
taskVarIndex_t taskVarIndex);
#endif // if FEATURE_MQTT_STATE_CLASS
// Update all cached values, except the checksum.
void updateExtraTaskSettingsCache();
@@ -152,7 +162,7 @@ public:
TaskIndexNameMap taskIndexName;
TaskIndexValueNameMap taskIndexValueName;
FilePresenceMap fileExistsMap; // Filesize. -1 if not present
FilePresenceMap fileExistsMap; // Filesize. -1 if not present
RulesHelperClass rulesHelper;
private:
+1 -1
View File
@@ -120,8 +120,8 @@ struct DeviceStruct
uint32_t I2CNoBusSelection : 1; // Dis-allow I2C Bus selection in device configuration
uint32_t CustomVTypeVar : 1; // User-selectable VType per value
uint32_t Dummy22 : 1; // Dummy added to force alignment, can be re-used
uint32_t SpiBusSelect : 1; // Allow selection of the SPI bus, if multiple buses are configured
uint32_t MqttStateClass : 1; // MQTT StateClass setting in DevicesPage
uint32_t Dummy24 : 1; // Dummy added to force alignment, can be re-used
uint32_t Dummy25 : 1; // Dummy added to force alignment, can be re-used
uint32_t Dummy26 : 1; // Dummy added to force alignment, can be re-used
@@ -290,6 +290,20 @@ void ExtraTaskSettingsStruct::setTaskVarCustomVType(taskVarIndex_t taskVarIndex,
}
#endif // if FEATURE_CUSTOM_TASKVAR_VTYPE
#if FEATURE_MQTT_STATE_CLASS
uint8_t ExtraTaskSettingsStruct::getTaskVarStateClass(taskVarIndex_t taskVarIndex) const {
if (!validTaskVarIndex(taskVarIndex)) { return 0u; }
return get3BitFromUL(VariousBits[taskVarIndex], 24); // 3 Bits 24, 25, 26
}
void ExtraTaskSettingsStruct::setTaskVarStateClass(taskVarIndex_t taskVarIndex,
uint8_t stateClass) {
if (validTaskVarIndex(taskVarIndex)) {
set3BitToUL(VariousBits[taskVarIndex], 24, stateClass); // 3 Bits 24, 25, 26
}
}
#endif // if FEATURE_MQTT_STATE_CLASS
void ExtraTaskSettingsStruct::populateDeviceValueNamesSeq(
const __FlashStringHelper *valuename,
@@ -84,6 +84,12 @@ struct ExtraTaskSettingsStruct
uint8_t customVType);
#endif // if FEATURE_CUSTOM_TASKVAR_VTYPE
#if FEATURE_MQTT_STATE_CLASS
uint8_t getTaskVarStateClass(taskVarIndex_t taskVarIndex) const;
void setTaskVarStateClass(taskVarIndex_t taskVarIndex,
uint8_t stateClass);
#endif // if FEATURE_MQTT_STATE_CLASS
void populateDeviceValueNamesSeq(const __FlashStringHelper *valuename,
size_t nrValues,
uint8_t defaultDecimals,
+5
View File
@@ -264,6 +264,11 @@ class SettingsStruct_tmpl
bool isPriorityTask(taskIndex_t taskIndex) const;
#endif // if FEATURE_PLUGIN_PRIORITY
#if FEATURE_MQTT
bool SendRetainedTaskValues(taskIndex_t taskIndex, controllerIndex_t controllerIndex) const;
void SendRetainedTaskValues(taskIndex_t taskIndex, controllerIndex_t controllerIndex, bool value);
#endif // if FEATURE_MQTT
void validate();
bool networkSettingsEmpty() const;
@@ -479,6 +479,23 @@ bool SettingsStruct_tmpl<N_TASKS>::isPriorityTask(taskIndex_t taskIndex) const {
}
#endif // if FEATURE_PLUGIN_PRIORITY
#if FEATURE_MQTT
template<unsigned int N_TASKS>
bool SettingsStruct_tmpl<N_TASKS>::SendRetainedTaskValues(taskIndex_t taskIndex, controllerIndex_t controllerIndex) const {
if (validTaskIndex(taskIndex) && validControllerIndex(controllerIndex)) {
return bitRead(VariousTaskBits[taskIndex], 2 + controllerIndex); // ATTENTION: uses bits 2..5!!!
}
return false;
}
template<unsigned int N_TASKS>
void SettingsStruct_tmpl<N_TASKS>::SendRetainedTaskValues(taskIndex_t taskIndex, controllerIndex_t controllerIndex, bool value) {
if (validTaskIndex(taskIndex) && validControllerIndex(controllerIndex)) {
bitWrite(VariousTaskBits[taskIndex], 2 + controllerIndex, value); // ATTENTION: uses bits 2..5!!!
}
}
#endif // if FEATURE_MQTT
template<unsigned int N_TASKS>
ExtTimeSource_e SettingsStruct_tmpl<N_TASKS>::ExtTimeSource() const {
return static_cast<ExtTimeSource_e>(ExternalTimeSource >> 1);
+97 -7
View File
@@ -97,6 +97,26 @@ uint8_t getValueCountFromSensorType(Sensor_VType sensorType, bool log)
case Sensor_VType::SENSOR_TYPE_DATA_SIZE:
case Sensor_VType::SENSOR_TYPE_SOUND_PRESSURE:
case Sensor_VType::SENSOR_TYPE_SIGNAL_STRENGTH:
case Sensor_VType::SENSOR_TYPE_REACTIVE_ENERGY:
case Sensor_VType::SENSOR_TYPE_FREQUENCY:
case Sensor_VType::SENSOR_TYPE_ENERGY:
case Sensor_VType::SENSOR_TYPE_ENERGY_STORAGE:
case Sensor_VType::SENSOR_TYPE_ABS_HUMIDITY:
case Sensor_VType::SENSOR_TYPE_ATMOS_PRESSURE:
case Sensor_VType::SENSOR_TYPE_BLOOD_GLUCOSE_C:
case Sensor_VType::SENSOR_TYPE_CO_ONLY:
case Sensor_VType::SENSOR_TYPE_ENERGY_DISTANCE:
case Sensor_VType::SENSOR_TYPE_GAS_ONLY:
case Sensor_VType::SENSOR_TYPE_NITROUS_OXIDE:
case Sensor_VType::SENSOR_TYPE_OZONE_ONLY:
case Sensor_VType::SENSOR_TYPE_PRECIPITATION:
case Sensor_VType::SENSOR_TYPE_PRECIPITATION_INTEN:
case Sensor_VType::SENSOR_TYPE_SULPHUR_DIOXIDE:
case Sensor_VType::SENSOR_TYPE_VOC_PARTS:
case Sensor_VType::SENSOR_TYPE_VOLUME:
case Sensor_VType::SENSOR_TYPE_VOLUME_FLOW_RATE:
case Sensor_VType::SENSOR_TYPE_VOLUME_STORAGE:
case Sensor_VType::SENSOR_TYPE_WATER:
return 1;
}
#ifndef BUILD_NO_DEBUG
@@ -183,6 +203,26 @@ const __FlashStringHelper* getSensorTypeLabel(Sensor_VType sensorType) {
case Sensor_VType::SENSOR_TYPE_DATA_SIZE: return F("Data size");
case Sensor_VType::SENSOR_TYPE_SOUND_PRESSURE: return F("Sound pressure");
case Sensor_VType::SENSOR_TYPE_SIGNAL_STRENGTH: return F("Signal strength");
case Sensor_VType::SENSOR_TYPE_REACTIVE_ENERGY: return F("Reactive Energy");
case Sensor_VType::SENSOR_TYPE_FREQUENCY: return F("Frequency");
case Sensor_VType::SENSOR_TYPE_ENERGY: return F("Energy");
case Sensor_VType::SENSOR_TYPE_ENERGY_STORAGE: return F("Energy storage");
case Sensor_VType::SENSOR_TYPE_ABS_HUMIDITY: return F("Absolute humidity");
case Sensor_VType::SENSOR_TYPE_ATMOS_PRESSURE: return F("Atmospheric pressure");
case Sensor_VType::SENSOR_TYPE_BLOOD_GLUCOSE_C: return F("Blood glucose conc.");
case Sensor_VType::SENSOR_TYPE_CO_ONLY: return F("CO");
case Sensor_VType::SENSOR_TYPE_ENERGY_DISTANCE: return F("Energy distance");
case Sensor_VType::SENSOR_TYPE_GAS_ONLY: return F("Gas");
case Sensor_VType::SENSOR_TYPE_NITROUS_OXIDE: return F("N2O");
case Sensor_VType::SENSOR_TYPE_OZONE_ONLY: return F("Ozone");
case Sensor_VType::SENSOR_TYPE_PRECIPITATION: return F("Precipitation");
case Sensor_VType::SENSOR_TYPE_PRECIPITATION_INTEN: return F("Precipitation intensity");
case Sensor_VType::SENSOR_TYPE_SULPHUR_DIOXIDE: return F("SO2");
case Sensor_VType::SENSOR_TYPE_VOC_PARTS: return F("VOC parts");
case Sensor_VType::SENSOR_TYPE_VOLUME: return F("Volume");
case Sensor_VType::SENSOR_TYPE_VOLUME_FLOW_RATE: return F("Volume flow rate");
case Sensor_VType::SENSOR_TYPE_VOLUME_STORAGE: return F("Volume storage");
case Sensor_VType::SENSOR_TYPE_WATER: return F("Water cons.");
#else // if FEATURE_MQTT_DISCOVER || FEATURE_CUSTOM_TASKVAR_VTYPE
case Sensor_VType::SENSOR_TYPE_ANALOG_ONLY:
case Sensor_VType::SENSOR_TYPE_TEMP_ONLY:
@@ -223,6 +263,26 @@ const __FlashStringHelper* getSensorTypeLabel(Sensor_VType sensorType) {
case Sensor_VType::SENSOR_TYPE_DATA_SIZE:
case Sensor_VType::SENSOR_TYPE_SOUND_PRESSURE:
case Sensor_VType::SENSOR_TYPE_SIGNAL_STRENGTH:
case Sensor_VType::SENSOR_TYPE_REACTIVE_ENERGY:
case Sensor_VType::SENSOR_TYPE_FREQUENCY:
case Sensor_VType::SENSOR_TYPE_ENERGY:
case Sensor_VType::SENSOR_TYPE_ENERGY_STORAGE:
case Sensor_VType::SENSOR_TYPE_ABS_HUMIDITY:
case Sensor_VType::SENSOR_TYPE_ATMOS_PRESSURE:
case Sensor_VType::SENSOR_TYPE_BLOOD_GLUCOSE_C:
case Sensor_VType::SENSOR_TYPE_CO_ONLY:
case Sensor_VType::SENSOR_TYPE_ENERGY_DISTANCE:
case Sensor_VType::SENSOR_TYPE_GAS_ONLY:
case Sensor_VType::SENSOR_TYPE_NITROUS_OXIDE:
case Sensor_VType::SENSOR_TYPE_OZONE_ONLY:
case Sensor_VType::SENSOR_TYPE_PRECIPITATION:
case Sensor_VType::SENSOR_TYPE_PRECIPITATION_INTEN:
case Sensor_VType::SENSOR_TYPE_SULPHUR_DIOXIDE:
case Sensor_VType::SENSOR_TYPE_VOC_PARTS:
case Sensor_VType::SENSOR_TYPE_VOLUME:
case Sensor_VType::SENSOR_TYPE_VOLUME_FLOW_RATE:
case Sensor_VType::SENSOR_TYPE_VOLUME_STORAGE:
case Sensor_VType::SENSOR_TYPE_WATER:
break;
#endif // if FEATURE_MQTT_DISCOVER || FEATURE_CUSTOM_TASKVAR_VTYPE
}
@@ -312,6 +372,26 @@ bool isMQTTDiscoverySensorType(Sensor_VType sensorType)
case Sensor_VType::SENSOR_TYPE_DATA_SIZE:
case Sensor_VType::SENSOR_TYPE_SOUND_PRESSURE:
case Sensor_VType::SENSOR_TYPE_SIGNAL_STRENGTH:
case Sensor_VType::SENSOR_TYPE_REACTIVE_ENERGY:
case Sensor_VType::SENSOR_TYPE_FREQUENCY:
case Sensor_VType::SENSOR_TYPE_ENERGY:
case Sensor_VType::SENSOR_TYPE_ENERGY_STORAGE:
case Sensor_VType::SENSOR_TYPE_ABS_HUMIDITY:
case Sensor_VType::SENSOR_TYPE_ATMOS_PRESSURE:
case Sensor_VType::SENSOR_TYPE_BLOOD_GLUCOSE_C:
case Sensor_VType::SENSOR_TYPE_CO_ONLY:
case Sensor_VType::SENSOR_TYPE_ENERGY_DISTANCE:
case Sensor_VType::SENSOR_TYPE_GAS_ONLY:
case Sensor_VType::SENSOR_TYPE_NITROUS_OXIDE:
case Sensor_VType::SENSOR_TYPE_OZONE_ONLY:
case Sensor_VType::SENSOR_TYPE_PRECIPITATION:
case Sensor_VType::SENSOR_TYPE_PRECIPITATION_INTEN:
case Sensor_VType::SENSOR_TYPE_SULPHUR_DIOXIDE:
case Sensor_VType::SENSOR_TYPE_VOC_PARTS:
case Sensor_VType::SENSOR_TYPE_VOLUME:
case Sensor_VType::SENSOR_TYPE_VOLUME_FLOW_RATE:
case Sensor_VType::SENSOR_TYPE_VOLUME_STORAGE:
case Sensor_VType::SENSOR_TYPE_WATER:
return true;
}
return false;
@@ -410,21 +490,26 @@ const char mqtt_valueType_ha_deviceclass_names[] PROGMEM = // !! Offset, startin
"irradiance|irradiance|irradiance|mdi:scale|" // UV_ONLY .. WEIGHT_ONLY
"voltage|current|power|power_factor|power|" // VOLTAGE_ONLY .. APPRNT_POWER_USG_ONLY
"volatile_organic_compounds|pressure|mdi:palette|mdi:palette|mdi:palette|" // TVOC_ONLY .. COLOR_BLUE_ONLY
"mdi:temperature-kelvin|power|aqi|nitrogen_dioxide|" // COLOR_TEMP_ONLY .. NOX_ONLY
"mdi:temperature-kelvin|reactive_power|aqi|nitrogen_dioxide|" // COLOR_TEMP_ONLY .. NOX_ONLY
"|wind_speed|duration|date|" // SWITCH_INVERTED .. DATE
"timestamp|data_rate|data_size|sound_pressure|signal_strength|datetime|" // TIMESTAMP .. DATETIME
"timestamp|data_rate|data_size|sound_pressure|signal_strength|reactive_energy|" // TIMESTAMP .. REACTIVE_ENERGY
"frequency|energy|energy_storage|" // FREQUENCY .. ENERGY_STORAGE
"absolute_humidity|atmospheric_pressure|blood_glucose_concentration|" // ABS_HUMIDITY .. ATMOSPH_PRESSURE
"carbon_monoxide|energy_distance|gas|nitrous_oxide|ozone|" // CO_ONLY .. OZONE_ONLY
"precipitation|precipitation_intensity|sulphur_dioxide|volatile_organic_compounds_parts|" // PRECIPITATION .. VOC_PARTS
"volume|volume_flow_rate|volume_storage|water|" // VOLUME .. WATER
;
/**
* getValueType2HADeviceClass: Convert the ValueType to a HA Device Class
*/
String getValueType2HADeviceClass(Sensor_VType sensorType) {
char tmp[27]{}; // length: volatile_organic_compounds + \0
char tmp[33]{}; // length: volatile_organic_compounds_parts + \0
int devClassIndex = static_cast<int>(sensorType);
if (sensorType >= Sensor_VType::SENSOR_TYPE_ANALOG_ONLY) {
devClassIndex -= static_cast<int>(Sensor_VType::SENSOR_TYPE_ANALOG_ONLY); // Subtract offset
// } else if (sensorType == Sensor_VType::SENSOR_TYPE_SWITCH) {
// return F("");
// return EMPTY_STRING;
} else if (sensorType == Sensor_VType::SENSOR_TYPE_WIND) {
return F("wind_speed");
// } else if (sensorType == Sensor_VType::SENSOR_TYPE_DIMMER) {
@@ -442,11 +527,16 @@ const char mqtt_valueType_default_ha_uom_names[] PROGMEM = // !! Offset, startin
"|°C|%|lx|cm|°|" // ANALOG_ONLY .. DIRECTION_ONLY
"µg/m³|µg/m³|µg/m³|%|ppm||" // DUSTPM2_5_ONLY .. GPS_ONLY
"W/m²|UV Index|W/m²|kg|" // UV_ONLY .. WEIGHT_ONLY
"V|A|kWh|Cos φ|VA|" // VOLTAGE_ONLY .. APPRNT_POWER_USG_ONLY
"V|A|W|%|VA|" // VOLTAGE_ONLY .. APPRNT_POWER_USG_ONLY
"ppd|hPa|lx|lx|lx|" // TVOC_ONLY .. COLOR_BLUE_ONLY
"K|var||µg/m³|" // COLOR_TEMP_ONLY .. NOX_ONLY
"|m/s|min||" // SWITCH_INVERTED .. DATE
"|bit/s|B|dB|dBm||" // TIMESTAMP .. DATETIME
"|bit/s|B|dB|dBm|kvar|" // TIMESTAMP .. REACTIVE_ENERGY
"Hz|kWh|kWh|" // FREQUENCY .. ENERGY_STORAGE
"g/m³|mbar|mmol/L|" // ABS_HUMIDITY .. BLOOD_GLUCOSE_C
"ppm|kWh/100km|L|µg/m³|µg/m³|" // CO_ONLY .. OZONE_ONLY
"mm|mm/h|µg/m³|ppm|" // PRICIPIATION .. VOC_PARTS
"L|m³/h|L|L|" // VOLUME .. WATER
;
/**
@@ -462,7 +552,7 @@ String getValueType2DefaultHAUoM(Sensor_VType sensorType) {
} else if (sensorType == Sensor_VType::SENSOR_TYPE_WIND) {
return F("m/s");
// } else if (sensorType == Sensor_VType::SENSOR_TYPE_DIMMER) {
// return F("");
// return EMPTY_STRING;
} else {
return EMPTY_STRING;
}
+20
View File
@@ -75,6 +75,26 @@ enum class Sensor_VType : uint8_t {
SENSOR_TYPE_DATA_SIZE = 136,
SENSOR_TYPE_SOUND_PRESSURE = 137,
SENSOR_TYPE_SIGNAL_STRENGTH = 138,
SENSOR_TYPE_REACTIVE_ENERGY = 139,
SENSOR_TYPE_FREQUENCY = 140,
SENSOR_TYPE_ENERGY = 141,
SENSOR_TYPE_ENERGY_STORAGE = 142,
SENSOR_TYPE_ABS_HUMIDITY = 143,
SENSOR_TYPE_ATMOS_PRESSURE = 144,
SENSOR_TYPE_BLOOD_GLUCOSE_C = 145,
SENSOR_TYPE_CO_ONLY = 146,
SENSOR_TYPE_ENERGY_DISTANCE = 147,
SENSOR_TYPE_GAS_ONLY = 148,
SENSOR_TYPE_NITROUS_OXIDE = 149,
SENSOR_TYPE_OZONE_ONLY = 150,
SENSOR_TYPE_PRECIPITATION = 151,
SENSOR_TYPE_PRECIPITATION_INTEN = 152,
SENSOR_TYPE_SULPHUR_DIOXIDE = 153,
SENSOR_TYPE_VOC_PARTS = 154,
SENSOR_TYPE_VOLUME = 155,
SENSOR_TYPE_VOLUME_FLOW_RATE = 156,
SENSOR_TYPE_VOLUME_STORAGE = 157,
SENSOR_TYPE_WATER = 158,
SENSOR_TYPE_NOT_SET = 255
};
+75 -7
View File
@@ -448,6 +448,21 @@ bool MQTT_binary_deviceClassTwoWay(int devClassIndex) {
# endif // if FEATURE_MQTT_DEVICECLASS
# if FEATURE_MQTT_STATE_CLASS
const __FlashStringHelper* MQTT_sensor_StateClass(uint8_t index,
bool display) {
switch (index) {
case 0: return F("");
case 1: return display ? F("Measurement") : F("measurement");
case 2: return display ? F("Total") : F("total");
case 3: return display ? F("Total-increasing") : F("total_increasing");
case 4: return display ? F("Measurement-angle") : F("measurement_angle");
}
return F("");
}
# endif // if FEATURE_MQTT_STATE_CLASS
bool MQTT_SendAutoDiscovery(controllerIndex_t ControllerIndex, cpluginID_t CPluginID) {
bool success = true;
@@ -521,7 +536,7 @@ bool MQTT_HomeAssistant_SendAutoDiscovery(controllerIndex_t ControllerIn
if (loglevelActiveFor(LOG_LEVEL_INFO)) {
addLog(LOG_LEVEL_INFO, strformat(F("MQTT : Start AutoDiscovery for task %d, %s max. %d value%c"),
x + 1, taskName.c_str(), valueCount, 1 == valueCount ? 's' : ' '));
x + 1, taskName.c_str(), valueCount, 1 != valueCount ? 's' : ' '));
}
String pluginDeviceClass;
@@ -536,7 +551,7 @@ bool MQTT_HomeAssistant_SendAutoDiscovery(controllerIndex_t ControllerIn
const bool useGroupId = groupId != 0 && !usesControllerIDX;
const String elementName = useGroupId ?
strformat(F("Group %u"), groupId) :
(Settings.Unit == groupId ? hostName : strformat(F("Group %u"), groupId)) :
strformat(F("%s %s"), hostName.c_str(), taskName.c_str());
const String elementIds = useGroupId ?
strformat(F("group_%u"), groupId) :
@@ -616,6 +631,11 @@ bool MQTT_HomeAssistant_SendAutoDiscovery(controllerIndex_t ControllerIn
String valueDeviceClass = parseStringKeepCase(pluginDeviceClass, v + 1); // Device classes per value
if (valueDeviceClass.isEmpty()) { valueDeviceClass = F("power"); } // default
# if FEATURE_MQTT_STATE_CLASS
const String stateClass = MQTT_sensor_StateClass(Cache.getTaskVarStateClass(x, v), false);
# else // if FEATURE_MQTT_STATE_CLASS
const String stateClass = EMPTY_STRING;
# endif // if FEATURE_MQTT_STATE_CLASS
# if FEATURE_MQTT_DEVICECLASS
const bool twoWay = MQTT_binary_deviceClassTwoWay(MQTT_binary_deviceClassIndex(valueDeviceClass));
# else // if FEATURE_MQTT_DEVICECLASS
@@ -637,6 +657,7 @@ bool MQTT_HomeAssistant_SendAutoDiscovery(controllerIndex_t ControllerIn
EMPTY_STRING, // No unit of measure used
&TempEvent,
deviceElement,
EMPTY_STRING,
success,
true, false,
useGroupId ? elementName : EMPTY_STRING, elementIds,
@@ -650,6 +671,7 @@ bool MQTT_HomeAssistant_SendAutoDiscovery(controllerIndex_t ControllerIn
uom,
&TempEvent,
deviceElement,
stateClass,
success,
discoveryItems[s].canSet, false,
useGroupId ? elementName : EMPTY_STRING, elementIds);
@@ -680,6 +702,11 @@ bool MQTT_HomeAssistant_SendAutoDiscovery(controllerIndex_t ControllerIn
const String valuename = MQTT_DiscoveryHelperGetValueName(x, v, discoveryItems[s]);
const String uom = MQTT_DiscoveryHelperGetValueUoM(x, v, discoveryItems[s],
getValueType2DefaultHAUoM(Sensor_VType::SENSOR_TYPE_TEMP_ONLY));
# if FEATURE_MQTT_STATE_CLASS
const String stateClass = MQTT_sensor_StateClass(Cache.getTaskVarStateClass(x, v), false);
# else // if FEATURE_MQTT_STATE_CLASS
const String stateClass = EMPTY_STRING;
# endif // if FEATURE_MQTT_STATE_CLASS
success &= MQTT_DiscoveryPublishWithStatusAndSet(x, v, valuename,
ControllerIndex,
ControllerSettings,
@@ -688,6 +715,7 @@ bool MQTT_HomeAssistant_SendAutoDiscovery(controllerIndex_t ControllerIn
uom,
&TempEvent,
deviceElement,
stateClass,
success,
discoveryItems[s].canSet, false,
useGroupId ? elementName : EMPTY_STRING, elementIds);
@@ -709,6 +737,11 @@ bool MQTT_HomeAssistant_SendAutoDiscovery(controllerIndex_t ControllerIn
const String valuename = MQTT_DiscoveryHelperGetValueName(x, v, discoveryItems[s]);
const String uom = MQTT_DiscoveryHelperGetValueUoM(x, v, discoveryItems[s],
getValueType2DefaultHAUoM(Sensor_VType::SENSOR_TYPE_HUM_ONLY));
# if FEATURE_MQTT_STATE_CLASS
const String stateClass = MQTT_sensor_StateClass(Cache.getTaskVarStateClass(x, v), false);
# else // if FEATURE_MQTT_STATE_CLASS
const String stateClass = EMPTY_STRING;
# endif // if FEATURE_MQTT_STATE_CLASS
success &= MQTT_DiscoveryPublishWithStatusAndSet(x, v, valuename,
ControllerIndex,
ControllerSettings,
@@ -717,6 +750,7 @@ bool MQTT_HomeAssistant_SendAutoDiscovery(controllerIndex_t ControllerIn
uom,
&TempEvent,
deviceElement,
stateClass,
success,
discoveryItems[s].canSet, false,
useGroupId ? elementName : EMPTY_STRING, elementIds);
@@ -742,6 +776,11 @@ bool MQTT_HomeAssistant_SendAutoDiscovery(controllerIndex_t ControllerIn
const String valuename = MQTT_DiscoveryHelperGetValueName(x, v, discoveryItems[s]);
const String uom = MQTT_DiscoveryHelperGetValueUoM(x, v, discoveryItems[s],
getValueType2DefaultHAUoM(Sensor_VType::SENSOR_TYPE_BARO_ONLY));
# if FEATURE_MQTT_STATE_CLASS
const String stateClass = MQTT_sensor_StateClass(Cache.getTaskVarStateClass(x, v), false);
# else // if FEATURE_MQTT_STATE_CLASS
const String stateClass = EMPTY_STRING;
# endif // if FEATURE_MQTT_STATE_CLASS
success &= MQTT_DiscoveryPublishWithStatusAndSet(x, v, valuename,
ControllerIndex,
ControllerSettings,
@@ -750,6 +789,7 @@ bool MQTT_HomeAssistant_SendAutoDiscovery(controllerIndex_t ControllerIn
uom,
&TempEvent,
deviceElement,
stateClass,
success,
discoveryItems[s].canSet, false,
useGroupId ? elementName : EMPTY_STRING, elementIds);
@@ -792,6 +832,26 @@ bool MQTT_HomeAssistant_SendAutoDiscovery(controllerIndex_t ControllerIn
case Sensor_VType::SENSOR_TYPE_DATA_SIZE:
case Sensor_VType::SENSOR_TYPE_SOUND_PRESSURE:
case Sensor_VType::SENSOR_TYPE_SIGNAL_STRENGTH:
case Sensor_VType::SENSOR_TYPE_REACTIVE_ENERGY:
case Sensor_VType::SENSOR_TYPE_FREQUENCY:
case Sensor_VType::SENSOR_TYPE_ENERGY:
case Sensor_VType::SENSOR_TYPE_ENERGY_STORAGE:
case Sensor_VType::SENSOR_TYPE_ABS_HUMIDITY:
case Sensor_VType::SENSOR_TYPE_ATMOS_PRESSURE:
case Sensor_VType::SENSOR_TYPE_BLOOD_GLUCOSE_C:
case Sensor_VType::SENSOR_TYPE_CO_ONLY:
case Sensor_VType::SENSOR_TYPE_ENERGY_DISTANCE:
case Sensor_VType::SENSOR_TYPE_GAS_ONLY:
case Sensor_VType::SENSOR_TYPE_NITROUS_OXIDE:
case Sensor_VType::SENSOR_TYPE_OZONE_ONLY:
case Sensor_VType::SENSOR_TYPE_PRECIPITATION:
case Sensor_VType::SENSOR_TYPE_PRECIPITATION_INTEN:
case Sensor_VType::SENSOR_TYPE_SULPHUR_DIOXIDE:
case Sensor_VType::SENSOR_TYPE_VOC_PARTS:
case Sensor_VType::SENSOR_TYPE_VOLUME:
case Sensor_VType::SENSOR_TYPE_VOLUME_FLOW_RATE:
case Sensor_VType::SENSOR_TYPE_VOLUME_STORAGE:
case Sensor_VType::SENSOR_TYPE_WATER:
{
const String dev = getValueType2HADeviceClass(discoveryItems[s].VType);
const String uomDef = getValueType2DefaultHAUoM(discoveryItems[s].VType);
@@ -799,6 +859,11 @@ bool MQTT_HomeAssistant_SendAutoDiscovery(controllerIndex_t ControllerIn
for (uint8_t v = discoveryItems[s].varIndex; v < varCount; ++v) {
const String valuename = MQTT_DiscoveryHelperGetValueName(x, v, discoveryItems[s]);
const String uom = MQTT_DiscoveryHelperGetValueUoM(x, v, discoveryItems[s], uomDef);
# if FEATURE_MQTT_STATE_CLASS
const String stateClass = MQTT_sensor_StateClass(Cache.getTaskVarStateClass(x, v), false);
# else // if FEATURE_MQTT_STATE_CLASS
const String stateClass = EMPTY_STRING;
# endif // if FEATURE_MQTT_STATE_CLASS
success &= MQTT_DiscoveryPublishWithStatusAndSet(x, v, valuename,
ControllerIndex,
ControllerSettings,
@@ -807,6 +872,7 @@ bool MQTT_HomeAssistant_SendAutoDiscovery(controllerIndex_t ControllerIn
uom,
&TempEvent,
deviceElement,
stateClass,
success,
discoveryItems[s].canSet, false,
useGroupId ? elementName : EMPTY_STRING, elementIds);
@@ -1009,6 +1075,7 @@ bool MQTT_DiscoveryPublishWithStatusAndSet(taskIndex_t taskIndex,
String unitOfMeasure,
struct EventStruct *event,
const String deviceElement,
const String stateClass,
bool success,
bool hasSet,
bool hasIcon,
@@ -1019,9 +1086,10 @@ bool MQTT_DiscoveryPublishWithStatusAndSet(taskIndex_t taskIndex,
const String withSet = hasSet ? F(",\"cmd_t\":\"~/set\"") : EMPTY_STRING;
const String schema = hasSet ? EMPTY_STRING : "\"schema\":\"basic\",";
const String devOrIcon = hasIcon ? F("ic") : F("dev_cla");
const String withUoM = !unitOfMeasure.isEmpty() ?
strformat(F(",\"unit_of_meas\":\"%s\""), unitOfMeasure.c_str()) :
EMPTY_STRING;
const String withUoM = unitOfMeasure.isEmpty() ? EMPTY_STRING :
strformat(F(",\"unit_of_meas\":\"%s\""), unitOfMeasure.c_str());
const String stateJson = stateClass.isEmpty() ? EMPTY_STRING :
strformat(F(",\"stat_cla\":\"%s\""), stateClass.c_str());
const String taskName = makeHomeAssistantCompliantName(getTaskDeviceName(taskIndex));
const String valName = makeHomeAssistantCompliantName(valueName);
const bool retainDsc = ControllerSettings.mqtt_retainDiscovery();
@@ -1046,10 +1114,10 @@ bool MQTT_DiscoveryPublishWithStatusAndSet(taskIndex_t taskIndex,
elementId,
valName);
const String discoveryMessage = strformat(F("{\"~\":\"%s\",\"name\":\"%s %s\",\"uniq_id\":\"%s\",%s"
"\"%s\":\"%s\"%s%s,\"stat_t\":\"~\""
"\"%s\":\"%s\"%s%s%s,\"stat_t\":\"~\""
"%s}"), // deviceElement last
publish.c_str(), taskName.c_str(), valName.c_str(), uniqueId.c_str(), schema.c_str(),
devOrIcon.c_str(), deviceClass.c_str(), withUoM.c_str(), withSet.c_str(),
devOrIcon.c_str(), deviceClass.c_str(), withUoM.c_str(), stateJson.c_str(), withSet.c_str(),
deviceElement.c_str());
const String triggerMessage = strformat(F("{\"atype\":\"trigger\",\"t\":\"%s\","
"\"p\":\"device_automation\","
+18 -13
View File
@@ -23,23 +23,27 @@ bool getDiscoveryVType(struct EventStruct *event,
uint8_t pConfigOffset,
uint8_t nrVars);
int Plugin_QueryVType_BinarySensor(uint8_t value_nr);
int Plugin_QueryVType_BinarySensorInv(uint8_t value_nr);
int Plugin_QueryVType_Analog(uint8_t value_nr);
int Plugin_QueryVType_CO2(uint8_t value_nr);
int Plugin_QueryVType_Distance(uint8_t value_nr);
int Plugin_QueryVType_DustPM2_5(uint8_t value_nr);
int Plugin_QueryVType_Lux(uint8_t value_nr);
int Plugin_QueryVType_Temperature(uint8_t value_nr);
int Plugin_QueryVType_Weight(uint8_t value_nr);
int Plugin_QueryVType_BinarySensor(uint8_t value_nr);
int Plugin_QueryVType_BinarySensorInv(uint8_t value_nr);
int Plugin_QueryVType_Analog(uint8_t value_nr);
int Plugin_QueryVType_CO2(uint8_t value_nr);
int Plugin_QueryVType_Distance(uint8_t value_nr);
int Plugin_QueryVType_DustPM2_5(uint8_t value_nr);
int Plugin_QueryVType_Lux(uint8_t value_nr);
int Plugin_QueryVType_Temperature(uint8_t value_nr);
int Plugin_QueryVType_Weight(uint8_t value_nr);
String makeHomeAssistantCompliantName(const String& name);
String makeHomeAssistantCompliantName(const String& name);
# if FEATURE_MQTT_DEVICECLASS
String MQTT_binary_deviceClassName(int devClassIndex);
bool MQTT_binary_deviceClassTwoWay(int devClassIndex);
int MQTT_binary_deviceClassIndex(const String& deviceClassName);
String MQTT_binary_deviceClassName(int devClassIndex);
bool MQTT_binary_deviceClassTwoWay(int devClassIndex);
int MQTT_binary_deviceClassIndex(const String& deviceClassName);
# endif // if FEATURE_MQTT_DEVICECLASS
# if FEATURE_MQTT_STATE_CLASS
const __FlashStringHelper* MQTT_sensor_StateClass(uint8_t index,
bool display = true);
# endif // if FEATURE_MQTT_STATE_CLASS
struct DiscoveryItem {
DiscoveryItem(Sensor_VType _VType, int _valueCount, taskVarIndex_t _varIndex, const bool _canSet = false)
: VType(_VType), valueCount(_valueCount), varIndex(_varIndex), canSet(_canSet) {}
@@ -96,6 +100,7 @@ bool MQTT_DiscoveryPublishWithStatusAndSet(taskIndex_t taskIndex,
String unitOfMeasure,
struct EventStruct *event,
const String deviceElement,
const String stateClass,
bool success,
bool hasSet,
bool hasIcon,
+10 -9
View File
@@ -97,15 +97,16 @@ String toValueTypeCategory(const uint32_t valueType) {
/* *INDENT-OFF* */
const uint16_t value_type_map[] PROGMEM = {
0, // None
1024, 1, 5, 6, 7, // Basic
1025, 2, 3, 4, 8, 101, 102, 122, 21, 131, // Environment
1026, 106, 107, 108, 110, 121, 128, 129, // Gases
1027, 116, 117, 118, 119, 120, 127, // Energy
1028, 132, 133, 134, // Time
1029, 100, 104, 105, 109, 111, 115, 135, 136, 137, 138, // Size
1030, 103, 112, 113, 114, 123, 124, 125, 126, // Light
1031, 10, 130, 11, 22, 20, 31, 32, 33, 40, 41, 42, 43, 50, 51, 60, 61, 70, 71, // Other
0, // None
1024, 1, 5, 6, 7, // Basic
1025, 2, 3, 4, 8, 101, 102, 122, 21, 131, 143, 144, 151, 152, // Environment
1026, 106, 107, 108, 110, 146, 121, 154, 128, 129, 148, 149, 150, 153, // Gases
1027, 116, 117, 118, 119, 120, 127, 139, 141, 142, 147, // Energy
1028, 132, 133, 134, // Time
1029, 100, 104, 105, 109, 111, 115, 135, 136, 137, 138, 155, 156, 157, 158, // Size
1030, 103, 112, 113, 114, 123, 124, 125, 126, // Light
1031, 10, 130, 11, 22, 20, 31, 32, 33, 40, 41, 42, 43, 50, 51, 60, 61, 70, 71, 140, // Other
145, // Other
};
/* *INDENT-ON* */
-8
View File
@@ -371,11 +371,7 @@ constexpr /*pluginID_t*/ uint8_t DeviceIndex_to_Plugin_id[] PROGMEM =
#endif // ifdef USES_P088
#ifdef USES_P089
# ifdef ESP8266
// FIXME TD-er: Support Ping plugin for ESP32
89,
# endif // ifdef ESP8266
#endif // ifdef USES_P089
#ifdef USES_P090
@@ -1404,11 +1400,7 @@ constexpr const Plugin_ptr_t PROGMEM Plugin_ptr[] =
#endif // ifdef USES_P088
#ifdef USES_P089
# ifdef ESP8266
// FIXME TD-er: Support Ping plugin for ESP32
&Plugin_089,
# endif // ifdef ESP8266
#endif // ifdef USES_P089
#ifdef USES_P090
-3
View File
@@ -390,10 +390,7 @@ void PluginInit(bool priorityOnly = false);
#endif
#ifdef USES_P089
#ifdef ESP8266
// FIXME TD-er: Support Ping plugin for ESP32
ADDPLUGIN_H(089)
#endif
#endif
#ifdef USES_P090
@@ -0,0 +1,66 @@
#include "../PluginStructs/P056_data_struct.h"
#ifdef USES_P056
P056_data_struct::P056_data_struct(EventStruct *event) {
delete SDS;
const ESPEasySerialPort port = static_cast<ESPEasySerialPort>(CONFIG_PORT);
SDS = new (std::nothrow) CjkSDS011(port, CONFIG_PIN1, CONFIG_PIN2);
if (loglevelActiveFor(LOG_LEVEL_INFO)) {
addLog(LOG_LEVEL_INFO, strformat(F("SDS : Init OK ESP GPIO-pin RX:%d TX:%d"), CONFIG_PIN1, CONFIG_PIN2));
}
}
P056_data_struct::~P056_data_struct() {
delete SDS;
SDS = nullptr;
}
void P056_data_struct::Process() {
if (isInitialized()) {
this->SDS->Process();
}
}
bool P056_data_struct::available() {
if (isInitialized()) {
return this->SDS->available();
} else {
return false;
}
}
float P056_data_struct::GetPM2_5() {
if (isInitialized()) {
return this->SDS->GetPM2_5();
} else {
return 0.0f;
}
}
float P056_data_struct::GetPM10_() {
if (isInitialized()) {
return this->SDS->GetPM10_();
} else {
return 0.0f;
}
}
bool P056_data_struct::ReadAverage(float pm25, float pm10) {
if (isInitialized()) {
return this->SDS->ReadAverage(pm25, pm10);
} else {
return false;
}
}
void P056_data_struct::SetWorkingPeriod(int minutes) {
if (isInitialized()) {
this->SDS->SetWorkingPeriod(minutes);
}
}
#endif
+28
View File
@@ -0,0 +1,28 @@
#pragma once
#include "../../_Plugin_Helper.h"
#ifdef USES_P056
# include <jkSDS011.h>
struct P056_data_struct : public PluginTaskData_base {
P056_data_struct(EventStruct *event);
~P056_data_struct();
bool isInitialized() {
return nullptr != SDS;
}
void Process();
bool available();
float GetPM2_5();
float GetPM10_();
bool ReadAverage(float pm25,
float pm10);
void SetWorkingPeriod(int minutes);
private:
CjkSDS011 *SDS = nullptr;
};
#endif // ifdef USES_P056
+1 -1
View File
@@ -33,7 +33,7 @@ int Plugin_077_QueryVType(uint8_t value_nr) {
Sensor_VType::SENSOR_TYPE_POWER_USG_ONLY,
Sensor_VType::SENSOR_TYPE_CURRENT_ONLY,
Sensor_VType::SENSOR_TYPE_NONE,
Sensor_VType::SENSOR_TYPE_POWER_USG_ONLY,
Sensor_VType::SENSOR_TYPE_ENERGY,
Sensor_VType::SENSOR_TYPE_APPRNT_POWER_USG_ONLY,
Sensor_VType::SENSOR_TYPE_POWER_FACT_ONLY,
Sensor_VType::SENSOR_TYPE_REACTIVE_POWER_ONLY,
File diff suppressed because it is too large Load Diff
+6 -6
View File
@@ -38,12 +38,12 @@ int Plugin_085_QueryVType(uint8_t value_nr) {
case P085_QUERY_V: result = Sensor_VType::SENSOR_TYPE_VOLTAGE_ONLY; break;
case P085_QUERY_A: result = Sensor_VType::SENSOR_TYPE_CURRENT_ONLY; break;
case P085_QUERY_W: result = Sensor_VType::SENSOR_TYPE_POWER_USG_ONLY; break;
case P085_QUERY_Wh_imp: // return Sensor_VType::SENSOR_TYPE_NONE; // FIXME
case P085_QUERY_Wh_exp: // return Sensor_VType::SENSOR_TYPE_NONE; // FIXME
case P085_QUERY_Wh_tot: // return Sensor_VType::SENSOR_TYPE_NONE; // FIXME
case P085_QUERY_Wh_net: // return Sensor_VType::SENSOR_TYPE_NONE; // FIXME
case P085_QUERY_h_tot: // return Sensor_VType::SENSOR_TYPE_NONE; // FIXME
case P085_QUERY_h_load: result = Sensor_VType::SENSOR_TYPE_NONE; break; // FIXME
case P085_QUERY_Wh_imp:
case P085_QUERY_Wh_exp:
case P085_QUERY_Wh_tot: // Fall through
case P085_QUERY_Wh_net: result = Sensor_VType::SENSOR_TYPE_ENERGY; break;
case P085_QUERY_h_tot: // Fall through
case P085_QUERY_h_load: result = Sensor_VType::SENSOR_TYPE_DURATION; break;
}
return static_cast<int>(result);
}
+106 -4
View File
@@ -1,13 +1,114 @@
#include "../PluginStructs/P089_data_struct.h"
#if defined(USES_P089) && defined(ESP8266)
#ifdef USES_P089
#include "../Helpers/Networking.h"
# include "../Helpers/Networking.h"
#include "../Helpers/_Plugin_init.h"
# include "../Helpers/_Plugin_init.h"
# ifdef ESP32
P089_data_struct::P089_data_struct() {
if (nullptr == P089_ping_service) {
P089_ping_service = new (std::nothrow) P089_ping_service_struct();
addLog(LOG_LEVEL_INFO, F("PING : Starting ping service."));
}
if ((nullptr != P089_ping_service) && P089_ping_service->isInitialized()) {
addLog(LOG_LEVEL_INFO, F("PING : Increment task instance counter."));
P089_ping_service->increment();
}
}
P089_data_struct::~P089_data_struct() {
if (nullptr != P089_ping_service) {
addLog(LOG_LEVEL_INFO, F("PING : Decrement task instance counter."));
if (0 == P089_ping_service->decrement()) {
addLog(LOG_LEVEL_INFO, F("PING : Stopping ping service."));
delete P089_ping_service;
P089_ping_service = nullptr;
}
}
}
bool P089_data_struct::send_ping(struct EventStruct *event) {
/* This ping lost for sure */
if (!isInitialized() || !NetworkConnected()) {
return true;
}
if ((nullptr == P089_ping_service) ||
!P089_ping_service->isInitialized()) {
return true; // Service not available
}
if (P089_ping_service->getPingResult(event->TaskIndex, _ping_request)) {
// Get requested result
if (_ping_request.status == P089_request_status::Result) {
if (_ping_request.result) {
addLog(LOG_LEVEL_INFO, strformat(F("PING : Successfully pinged %s (%s) count: %d avg: %.03f ms for task: %d"),
_ping_request.hostname.c_str(),
_ping_request.ip.toString().c_str(),
_ping_request.count,
_ping_request.avgTime,
event->TaskIndex + 1));
} else {
addLog(LOG_LEVEL_ERROR, strformat(F("PING : Error pinging %s (%s) for task: %d"),
_ping_request.hostname.c_str(),
_ping_request.ip.toString().c_str(),
event->TaskIndex + 1));
}
UserVar.setFloat(event->TaskIndex, 1, _ping_request.avgTime); // Set always, even when not specifically enabled
_ping_request.status = P089_request_status::Finished; // Ready for new work
return !_ping_request.result; // Inverted result to report back!
}
}
if ((_ping_request.status > P089_request_status::Request) && (_ping_request.status < P089_request_status::Result)) {
// addLog(LOG_LEVEL_INFO, F("PING : Still working."));
return false; // Busy, but not a failure
}
// New request
IPAddress ip;
char hostname[PLUGIN_089_HOSTNAME_SIZE]{};
LoadCustomTaskSettings(event->TaskIndex, (uint8_t *)&hostname, PLUGIN_089_HOSTNAME_SIZE);
/* This one lost as well, DNS dead? */
if (!resolveHostByName(hostname, ip)) {
return true;
}
int16_t pingCount = P089_PING_COUNT;
if ((pingCount < 1) || (pingCount > P089_MAX_PING_COUNT)) {
pingCount = 5;
}
_ping_request.status = P089_request_status::Request;
_ping_request.count = pingCount;
_ping_request.ip = ip;
_ping_request.hostname = hostname; // Bring all data to the request
const bool result = !P089_ping_service->addPingRequest(event->TaskIndex, _ping_request);
P089_ping_service->loop(); // Kick-off if not already working
return result;
}
bool P089_data_struct::loop() {
if ((nullptr != P089_ping_service) && P089_ping_service->isInitialized()) {
return P089_ping_service->loop();
}
return false; // We did nothing
}
# endif // ifdef ESP32
# ifdef ESP8266
P089_data_struct::P089_data_struct() {
destIPAddress.addr = 0;
idseq = 0;
@@ -159,4 +260,5 @@ uint8_t PingReceiver(void *origin, struct raw_pcb *pcb, struct pbuf *packetBuffe
return 1;
}
#endif // if defined(USES_P089) && defined(ESP8266)
# endif // ifdef ESP8266
#endif // ifdef USES_P089
+45 -10
View File
@@ -2,7 +2,9 @@
#define PLUGINSTRUCTS_P089_DATA_STRUCT_H
#include "../../_Plugin_Helper.h"
#if defined(USES_P089) && defined(ESP8266)
#ifdef USES_P089
# include "src/ESPEasyCore/ESPEasyNetwork.h"
# define PLUGIN_ID_089 89
@@ -12,17 +14,46 @@
# define ICMP_PAYLOAD_LEN 32
# ifdef ESP32
# include "../PluginStructs/P089_ping_service_struct.h"
# define P089_PING_COUNT PCONFIG(0)
# define P089_VALUE_COUNT PCONFIG(1)
# define P089_MAX_PING_COUNT 25
struct P089_data_struct : public PluginTaskData_base {
public:
P089_data_struct();
virtual ~P089_data_struct();
bool isInitialized() const {
return true;
}
bool send_ping(struct EventStruct *event);
bool loop();
private:
P089_ping_request _ping_request;
};
# endif // ifdef ESP32
# ifdef ESP8266
extern "C"
{
# include <lwip/raw.h>
# include <lwip/icmp.h> // needed for icmp packet definitions
# include <lwip/inet_chksum.h> // needed for inet_chksum()
# include <lwip/sys.h> // needed for sys_now()
# include <lwip/netif.h>
# include <lwip/raw.h>
# include <lwip/icmp.h> // needed for icmp packet definitions
# include <lwip/inet_chksum.h> // needed for inet_chksum()
# include <lwip/sys.h> // needed for sys_now()
# include <lwip/netif.h>
}
# include "src/ESPEasyCore/ESPEasyNetwork.h"
struct P089_icmp_pcb {
P089_icmp_pcb() = default;
@@ -39,6 +70,10 @@ public:
bool send_ping(struct EventStruct *event);
bool isInitialized() const {
return nullptr != P089_data;
}
struct P089_icmp_pcb *P089_data = nullptr;
ip_addr_t destIPAddress;
uint32_t idseq;
@@ -50,6 +85,6 @@ uint8_t PingReceiver(void *origin,
struct pbuf *packetBuffer,
const ip_addr_t *addr);
#endif // if defined(USES_P089) && defined(ESP8266)
# endif // ifdef ESP8266
#endif // ifdef USES_P089
#endif // ifndef PLUGINSTRUCTS_P089_DATA_STRUCT_H
@@ -0,0 +1,142 @@
#include "../PluginStructs/P089_ping_service_struct.h"
#ifdef ESP32
P089_ping_service_struct*P089_ping_service = nullptr;
/**
* Ping execute handler task
*/
void P089_execute_ping_task(void *parameter)
{
P089_ping_task_data*ping_task_data = static_cast<P089_ping_task_data *>(parameter);
if ((ping_task_data->status == P089_task_status::Working) && (nullptr != ping_task_data->espPing)) {
// Blocking operation
ping_task_data->result = ping_task_data->espPing->ping(ping_task_data->ip, ping_task_data->count);
// Results are in
ping_task_data->avgTime = ping_task_data->espPing->averageTime();
ping_task_data->status = P089_task_status::Ready;
}
ping_task_data->taskHandle = NULL;
vTaskDelete(ping_task_data->taskHandle);
}
/**
* P089_ping_service_struct
*/
P089_ping_service_struct::P089_ping_service_struct() {
espPing = new (std::nothrow) PingClass();
}
P089_ping_service_struct::~P089_ping_service_struct() {
if (_ping_task_data.taskHandle) {
vTaskDelete(_ping_task_data.taskHandle);
_ping_task_data.taskHandle = NULL;
}
_requests.clear();
delete espPing;
espPing = nullptr;
}
bool P089_ping_service_struct::addPingRequest(taskIndex_t TaskIndex,
P089_ping_request request) {
bool result = false; // Something is not ok
auto it = _requests.find(TaskIndex);
if ((request.status == P089_request_status::Request) &&
((it == _requests.end()) ||
((it->first == TaskIndex) && (it->second.status == P089_request_status::Finished)))) { // No request or Finished for this task
_requests[TaskIndex] = request;
_requests[TaskIndex].status = P089_request_status::Accepted;
_requests[TaskIndex].lastStarted = millis();
result = true;
}
return result;
}
bool P089_ping_service_struct::getPingResult(taskIndex_t TaskIndex,
P089_ping_request& request) {
bool result = false;
auto it = _requests.find(TaskIndex);
if (it != _requests.end()) {
request = it->second;
if (it->second.status == P089_request_status::Result) {
it->second.status = P089_request_status::Finished; // Can be removed
result = true;
}
}
// Check if we need to clean outdated requests
it = _requests.begin();
while (it != _requests.end()) {
// Cleanup when counter reached
if (timePassedSince(it->second.lastStarted) >= P089_FINISHED_REQUEST_TIMEOUT_MS) {
addLog(LOG_LEVEL_INFO, strformat(F("PING : Clean last request for task %d (%u s)"),
it->first + 1, timePassedSince(it->second.lastStarted) / 1000));
_requests.erase(it); // Cleanup
it = _requests.begin(); // Restart loop
} else {
++it;
}
}
return result;
}
bool P089_ping_service_struct::loop() {
bool result = false;
if (timePassedSince(lastLooped) < 100) { // Some call/load throtteling
return result;
}
lastLooped = millis();
// addLog(LOG_LEVEL_INFO, F("PING : Running P089_ping_service->loop()")); // FIXME
if (_ping_task_data.status == P089_task_status::Ready) {
auto it = _requests.find(_ping_task_data.taskIndex);
if (it != _requests.end()) {
it->second.result = _ping_task_data.result;
it->second.avgTime = _ping_task_data.avgTime;
it->second.status = P089_request_status::Result; // Can be retrieved
result = true;
}
_ping_task_data.status = P089_task_status::Initial; // Available for new job
}
if (_ping_task_data.status == P089_task_status::Initial) { // Find next job
auto it = _requests.begin();
for (; it != _requests.end(); ++it) {
if (it->second.status == P089_request_status::Accepted) {
_ping_task_data.taskIndex = it->first;
_ping_task_data.status = P089_task_status::Working;
_ping_task_data.count = it->second.count;
_ping_task_data.ip = it->second.ip;
_ping_task_data.espPing = espPing;
xTaskCreatePinnedToCore(
P089_execute_ping_task, // Function that should be called
"PingClass.ping()", // Name of the task (for debugging)
4000, // Stack size (bytes)
&_ping_task_data, // Parameter to pass
1, // Task priority
&_ping_task_data.taskHandle, // Task handle
xPortGetCoreID() // Core you want to run the task on (0 or 1)
);
break;
}
}
}
return result;
}
#endif // ifdef ESP32
@@ -0,0 +1,88 @@
#pragma once
#include "../../_Plugin_Helper.h"
#ifdef ESP32
# include <ESPping.h>
# include <map>
constexpr uint32_t P089_FINISHED_REQUEST_TIMEOUT_MS = 1000 * 60 * 10; // Delete entry after unused for this number of milliseconds (10 min.)
struct P089_ping_service_struct; // Forward declaration
extern P089_ping_service_struct*P089_ping_service;
enum class P089_task_status : uint8_t {
Initial = 0, // Ready to be start a task
Working = 1, // Working on a task
Ready = 2, // Ready, data is available
};
enum class P089_request_status : uint8_t {
Request = 0, // Initial request
Accepted = 1, // Can be processed
Processing = 2, // Actively being processed
Ready = 3, // Results in
Result = 4, // Results retrieved
Finished = 5, // Can be removed or reused
};
struct P089_ping_task_data {
taskIndex_t taskIndex{};
P089_task_status status = P089_task_status::Initial;
IPAddress ip;
bool result{};
uint16_t count{};
float avgTime{};
PingClass *espPing = nullptr;
// This is C-code, so not set to nullptr, but to NULL
TaskHandle_t taskHandle = NULL;
};
struct P089_ping_request {
P089_request_status status = P089_request_status::Request;
IPAddress ip;
String hostname;
uint16_t count{};
bool result{};
float avgTime{};
uint32_t lastStarted{};
};
struct P089_ping_service_struct {
public:
P089_ping_service_struct();
virtual ~P089_ping_service_struct();
bool isInitialized() {
return nullptr != espPing;
}
bool addPingRequest(taskIndex_t TaskIndex,
P089_ping_request request);
bool getPingResult(taskIndex_t TaskIndex,
P089_ping_request& request);
bool loop();
uint8_t increment() {
return ++taskCounter; // No checks needed?
}
uint8_t decrement() {
return --taskCounter; // No checks needed?
}
private:
uint32_t lastLooped{};
uint8_t taskCounter{};
std::map<taskIndex_t, P089_ping_request>_requests;
PingClass *espPing = nullptr;
P089_ping_task_data _ping_task_data;
};
#endif // ifdef ESP32
+4 -4
View File
@@ -39,10 +39,10 @@ int Plugin_108_QueryVType(uint8_t value_nr) {
Sensor_VType::SENSOR_TYPE_POWER_USG_ONLY,
Sensor_VType::SENSOR_TYPE_REACTIVE_POWER_ONLY,
Sensor_VType::SENSOR_TYPE_POWER_FACT_ONLY,
Sensor_VType::SENSOR_TYPE_NONE, // FIXME To implement Frequency and Energy options
Sensor_VType::SENSOR_TYPE_NONE,
Sensor_VType::SENSOR_TYPE_NONE,
Sensor_VType::SENSOR_TYPE_NONE,
Sensor_VType::SENSOR_TYPE_FREQUENCY,
Sensor_VType::SENSOR_TYPE_ENERGY,
Sensor_VType::SENSOR_TYPE_ENERGY,
Sensor_VType::SENSOR_TYPE_ENERGY,
};
constexpr uint8_t valueCount = NR_ELEMENTS(vtypes);
Sensor_VType result = Sensor_VType::SENSOR_TYPE_NONE;
+13 -5
View File
@@ -12,7 +12,15 @@ P168_data_struct::P168_data_struct(uint8_t alsGain,
uint8_t psmMode,
uint8_t readMethod) :
_als_gain(alsGain), _als_integration(alsIntegration), _psm_mode(psmMode), _readMethod(readMethod), initialized(false)
{}
{
// We already apply the correct WAIT for a read, so no need or desire to wait any longer.
// AUTO enforces multiple WAIT options sequentially, so that's inhibited
if (static_cast<uint8_t>(VEML_LUX_NORMAL) == _readMethod) {
_readMethod = static_cast<uint8_t>(VEML_LUX_NORMAL_NOWAIT);
} else if ((static_cast<uint8_t>(VEML_LUX_CORRECTED) == _readMethod) || (static_cast<uint8_t>(VEML_LUX_AUTO) == _readMethod)) {
_readMethod = static_cast<uint8_t>(VEML_LUX_CORRECTED_NOWAIT);
}
}
P168_data_struct::~P168_data_struct() {
delete veml;
@@ -51,10 +59,10 @@ bool P168_data_struct::plugin_read(struct EventStruct *event) {
float lux = veml->readLux(static_cast<luxMethod>(_readMethod));
uint16_t whi = veml->readWhite();
if (luxMethod::VEML_LUX_AUTO == static_cast<luxMethod>(_readMethod)) {
addLog(LOG_LEVEL_INFO, strformat(F("VEML : 6030/7700 AutoLux, Lux: %.2f, Gain: %.3f, Integration: %d"),
lux, veml->getGainValue(), veml->getIntegrationTimeValue()));
}
// if (luxMethod::VEML_LUX_AUTO == static_cast<luxMethod>(_readMethod)) {
// addLog(LOG_LEVEL_INFO, strformat(F("VEML : 6030/7700 AutoLux, Lux: %.2f, Gain: %.3f, Integration: %d"),
// lux, veml->getGainValue(), veml->getIntegrationTimeValue()));
// }
UserVar.setFloat(event->TaskIndex, 0, lux);
UserVar.setFloat(event->TaskIndex, 1, whi);
UserVar.setFloat(event->TaskIndex, 2, amb);
+45
View File
@@ -415,6 +415,9 @@ void handle_devices_CopySubmittedSettings(taskIndex_t taskIndex, pluginID_t task
#if FEATURE_STRING_VARIABLES
Settings.SendDerivedTaskValues(taskIndex, controllerNr, isFormItemChecked(getPluginCustomArgName(F("TSND"), controllerNr)));
#endif // if FEATURE_STRING_VARIABLES
#if FEATURE_MQTT && FEATURE_MQTT_DISCOVER
Settings.SendRetainedTaskValues(taskIndex, controllerNr, isFormItemChecked(getPluginCustomArgName(F("TSRT"), controllerNr)));
#endif // if FEATURE_MQTT && FEATURE_MQTT_DISCOVER
}
if (device.PullUpOption) {
@@ -464,6 +467,10 @@ void handle_devices_CopySubmittedSettings(taskIndex_t taskIndex, pluginID_t task
#if FEATURE_CUSTOM_TASKVAR_VTYPE
ExtraTaskSettings.setTaskVarCustomVType(varNr, getFormItemInt(getPluginCustomArgName(F("TDTV"), varNr)));
#endif // if FEATURE_CUSTOM_TASKVAR_VTYPE
#if FEATURE_MQTT_STATE_CLASS
ExtraTaskSettings.setTaskVarStateClass(varNr, getFormItemInt(getPluginCustomArgName(F("TDSC"), varNr)));
#endif // if FEATURE_MQTT_STATE_CLASS
}
ExtraTaskSettings.clearUnusedValueNames(valueCount);
@@ -1636,6 +1643,18 @@ void devicePage_show_controller_config(taskIndex_t taskIndex, deviceIndex_t Devi
getPluginCustomArgName(F("TDID"), controllerNr), // ="taskdeviceid"
Settings.TaskDeviceID[controllerNr][taskIndex], 0, DOMOTICZ_MAX_IDX);
}
#if FEATURE_MQTT && FEATURE_MQTT_DISCOVER
if (showMqttGroup) {
html_TD();
addHtml(F("Retained:"));
html_TD();
addCheckBox(getPluginCustomArgName(F("TSRT"), controllerNr), Settings.SendRetainedTaskValues(taskIndex, controllerNr), false
# if FEATURE_TOOLTIPS
, F("Send values with Retain flag")
# endif // if FEATURE_TOOLTIPS
);
}
#endif // if FEATURE_MQTT && FEATURE_MQTT_DISCOVER
# if FEATURE_STRING_VARIABLES
if (allowSendDerived) {
html_TD();
@@ -1743,6 +1762,13 @@ void devicePage_show_task_values(taskIndex_t taskIndex, deviceIndex_t DeviceInde
}
#endif // if FEATURE_CUSTOM_TASKVAR_VTYPE
#if FEATURE_MQTT_STATE_CLASS
if (device.MqttStateClass) {
html_table_header(F("MQTT State Class"), 350);
++colCount;
}
#endif // if FEATURE_MQTT_STATE_CLASS
// placeholder header
html_table_header(F(""));
@@ -1845,6 +1871,25 @@ void devicePage_show_task_values(taskIndex_t taskIndex, deviceIndex_t DeviceInde
static_cast<Sensor_VType>(Cache.getTaskVarCustomVType(taskIndex, varNr)));
}
#endif // if FEATURE_CUSTOM_TASKVAR_VTYPE
#if FEATURE_MQTT_STATE_CLASS
if (device.MqttStateClass) {
html_TD();
const __FlashStringHelper *stateClasses[] = {
MQTT_sensor_StateClass(0),
MQTT_sensor_StateClass(1),
MQTT_sensor_StateClass(4),
MQTT_sensor_StateClass(2),
MQTT_sensor_StateClass(3),
};
constexpr size_t stateCount = NR_ELEMENTS(stateClasses);
const FormSelectorOptions selectorSC(stateCount, stateClasses);
selectorSC.addSelector(
getPluginCustomArgName(F("TDSC"), varNr),
Cache.getTaskVarStateClass(taskIndex, varNr));
}
#endif // if FEATURE_MQTT_STATE_CLASS
}
addFormSeparator(colCount);
}
+4 -1
View File
@@ -396,6 +396,8 @@ const char unit_of_measure_list[] PROGMEM = // *** DO NOT CHANGE ORDER, SAVED IN
"mg/dL|mmol/L|" // 150..151
"μSv|μSv/h|" // 152..153
"m³/s|ft³/min|L/h|L/min|L/s|gal/min|mL/s|" // 154..160
"g/m³|kWh/100km|Wh/km|mi/kWh|km/kWh|" // 161..165
"in/d|mm/d" // 166 .. 167
; // *** DO NOT CHANGE ORDER, SAVED IN TASK SETTINGS! ***
@@ -435,12 +437,13 @@ const uint16_t unit_of_measure_map[] PROGMEM = {
1054, 26, 27, 153, 154, 155, 156, 157, 158, 159, 160, // Volume flow rate
1032, 39, 40, 41, 42, 43, 44, 45, 46, 47, // Duration
1034, 14, 15, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, // Energy(-storage)
1033, 162, 163, 164, 165, // Energy distance
1050, 66, 67, 68, 69, 70, 71, 65, 106, 107, // Speed
1056, 35, // (Wind) direction
1038, 28, // Illuminance
1039, 64, 112, // Irradiance
1046, 152, 153, // Radiation
1057, 29, 63, // Various units
1057, 29, 63, 161, 166, 167, // Various units
1035, 52, 53, 131, 132, // Frequency
1043, 113, // Potential hydrogen
1026, 56, 122, 123, 124, 125, 126, 127, 128, 129, 130, // Area