Merge branch 'mega' of https://github.com/letscontrolit/ESPEasy into feature/p124-i2c-multi-relay-plugin

This commit is contained in:
Ton Huisman
2021-12-12 17:13:08 +01:00
47 changed files with 2488 additions and 642 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

+307 -5
View File
@@ -45,7 +45,9 @@ PlatformIO does use a PowerShell script to activate the Python virtual environme
Default Windows security settings prevent execution of a PowerShell script.
Enter in the PowerShell terminal window in VScode::
Enter in the PowerShell terminal window in VScode:
.. code-block:: none
Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Scope CurrentUser
@@ -71,15 +73,16 @@ For development of ESPeasy, a number of extensions has to be installed in VS-Cod
* Arduino for Visual Studio Code (by Microsoft)
* Uncrustify (by Zachary Flower, originally by Laurent Tréguier)
Optional:
Optional, but highly recommended:
* Bookmarks (by Alessandro Fragnani)
* Bracket Pair Colorizer 2 (by CoenraadS)
* GitLens - Git supercharged (by Eric Amodio)
* Todo Tree (by Gruntfuggly)
* All Autocomplete (by Atishay Jain)
* Excel Viewer (by GrapeCity)
* esbonio - An extension for editing sphinx projects (by Swyddfa)
* reStructuredText - for .rst language support (by LeXtudio Inc.)
* reStructuredText Syntax highlighting (by Trond Snekvik)
* Markdown All in One (by Yu Zhang)
@@ -97,6 +100,8 @@ To do so:
The first time (after installing uncrustify) it must be confirmed to use Uncrustify as formatter and using the default suggested config file.
After setting it as the default formatter, the hotkey Alt-Shift-F (Cmd-Shift-F on MacOS) can be used to format the entire document.
Load a project using PlatformIO
-------------------------------
@@ -129,7 +134,8 @@ For example:
* ..._ESP8266_4Mnn -> ESP8266 has external flash, which can vary in size from 512 kB to 16 MB, with nn configured as filesystem.
* ..._ESP8285_1M -> ESP8285 has the flash internal, so is always 1 MB.
* ..._ESP32_4M316k -> ESP32 with 4 MB flash and a 1.8 MB partition for the sketch. (316k SPIFFS)
* ..._ESP32_16M2M -> ESP32 with 16 MB flash and a 4 MB partition for the sketch. (2MB LittleFS)
* ..._ESP32_16M2M_LittleFS -> ESP32 with 16 MB flash and a 4 MB partition for the sketch. (2MB LittleFS)
* ..._ESP32_16M1M_ETH -> ESP32 with 16 MB flash and a 4 MB partition for the sketch. (1MB SPIFFS, Wired ethernet support)
Make a custom build using PlatformIO
------------------------------------
@@ -161,4 +167,300 @@ Upload to ESP
Linux
-----
For Linux, you may need to install 99-platformio-udev.rules to make PlatformIO upload tools work in vscode.
For Linux, you may need to install 99-platformio-udev.rules to make PlatformIO upload tools work in vscode.
.. highlight::sh
Starter guide for (local) development on ESPEasy
================================================
For those with less development experience, or less experience in using Github, this chapter is intended as a **How To** guide to get started with development on ESPEasy.
It tries to help setting up Visual Studio Code (VSCode) with the PlatformIO development environment and additional VSCode plugins that aid in easier working on code and documentation.
The global steps described here are:
- Creating a private copy on Github
- Getting VSCode and PlatformIO set up
- Getting the source code from Github onto your system
- Compiling the source code
- Creating a branch to make your changes
- Modify the source code, compile & test
- Add a plugin (optional), compile & test
- Write documentation on the changes you made
- Commit your code and create a pull request on Github to publish your changes to the world
- Regular maintenance of your fork (housekeeping)
Let's get started!
Github account
--------------
First requirement is to have a Github account. You can either use an existing account or create one (it's free), by opening a browser on https://github.com, and following the steps after clicking the Sign up (for Github) button.
The Github flow
---------------
When you want to extend some function of the software at hand, ESPEasy in this case, there is a general flow, or 'how things are done here', that is common for Github, but most likely somewhat different from other software development flows or processes.
The usual Github flow or way of working is described nicely on this page: `Understanding the GitHub flow <https://guides.github.com/introduction/flow/>`_ If you have not used git or github before, or have little knowledge on how all this works, this explanation can be very helpful.
Fork the ESPEasy repository
---------------------------
ESPEasy uses the 'Fork and Pull' development method. This is probably the most used method for open source projects. This involves creating a copy (the fork) of the project, and request to incorporate changes into the original project by means of pull requests.
`Reasons for forking <https://docs.github.com/en/get-started/quickstart/fork-a-repo>`_ Quote: "A fork is a copy of a repository. Forking a repository allows you to freely experiment with changes without affecting the original project."
As an 'external' developer, no (direct) write-access is granted to the ESPEasy repository. To experiment with the code, and still be able to later have your development work included in the project, a 'fork' has to be made to your own account. So, log into your Github account from a webbrowser, browse to https://github.com/letscontrolit/ESPEasy and click the Fork button to create that copy:
.. image:: Github_fork_button.png
:alt: Github fork button
After this completes, you can view the fork in your Github dashboard at https://github.com/[your_github_handle]
(You have to replace [your_github_handle] with the name you selected during the Github sign-up procedure)
Install VSCode and PlatformIO
------------------------------
Earlier on this page, a complete description has been given on how to install **PlatformIO with VSCode** with the required and advised optional extensions and the git command-line tools.
NB: PlatformIO is often shortened to PIO.
Clone your forked repository to your computer
---------------------------------------------
To get the ESPEasy sources on your computer for compilation and making modifications, a 'clone' has to be made, using the ``git clone`` command
`Cloning a repository <https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository>`_ Quote: "You can clone your repository to create a local copy on your computer and sync between the two locations."
.. note::
If available, a clone can of course also be made using GUI tools like `Github Desktop <https://desktop.github.com/>`_, `GitKraken <https://www.gitkraken.com/>`_, `SourceTree <https://www.sourcetreeapp.com/>`_ or `TortoiseGIT <https://tortoisegit.org/>`_, etc., but, as the ``git`` command-line tools have been installed as part of setting up the development environment, that is used in the steps here.
Open a Command prompt (Windows) or Terminal session (MacOS or Linux), and ``cd`` to a folder where the ESPEasy project can/should be a subfolder of.
Then type this command to create the clone:
.. code-block::
git clone https://github.com/[your_github_handle]/ESPEasy.git
This will create a new folder called ``ESPEasy``, and download all files that make up the project into that folder.
Working on it:
.. image:: Github_clone_working.png
:alt: Github clone working
Completed:
.. image:: Github_clone_completed.png
:alt: Github clone completed
To be able to get the latest changes from the original project into your local copy, and to bring your changes as a 'pull request' (git terminology, often referred to as a 'PR', further explained below) to the ESPEasy repository, a connection has to be made from your local clone to the 'upstream' source (git terminology, pointer to the repository the fork was taken from). This command needs to be issued **only once** after cloning the repository into a folder on your computer, and should be executed from the ``ESPEasy`` folder that was just created:
.. code-block::
git remote add upstream https://github.com/letscontrolit/ESPEasy
Now this Command prompt / terminal (or GUI tool) can be closed.
Open the folder with ESPEasy project
------------------------------------
Start VSCode, and open the ESPEasy folder that was just created. In Windows you can right-click the ESPEasy folder and select the 'Open with Code' option. First thing when opening a git repository, VSCode will ask you if you trust the authors of the files. The easiest option is to respond by clicking the 'Yes, I trust the authors' button, as that is the only way to get unrestricted access to the sources. After that confirmation, VSCode will take a little time to initialize all plugins.
Depending on your usual workflow, the current VSCode environment can be saved as a 'Workspace' (VSCode terminology), so it can be easily re-opened. This is especially useful if you also use VSCode for other projects/editing work.
Compile an ESPEasy PIO environment
----------------------------------
ESPEasy supports several different configurations of ESP units, ESP8266, ESP8285 and ESP32, and also some predefined hardware configurations and sets of plugins & controllers. This has been turned into several different PlatformIO environments, to make managing the different builds as easy as possible.
To compile such 'environment' (PIO terminology), select the PIO button (it looks like an alien) in VSCode:
.. image:: VSCode_PIO_Environments.png
:alt: VSCode Platform IO environments
Expand an environment from the list, so the PIO options become visible (this will take some time for PIO to scan the configuration of that environment).
.. image:: VSCode_PIO_custom_ESP8266_4M1M.png
:alt: VSCode Platform IO custom ESP8266 4M 1M expanded
Now, the ``Build`` option is visible, and clicking that will build the project for the selected environment (configuration).
The first build will take some extra time, as PIO needs to first install some of its tooling and other required components and libraries, but as you haven't changed any files yet, the build should be successful:
.. image:: VSCode_build_success.png
:alt: VSCode build success
(NB: For this build all tools and libraries where already installed, and the computer isn't that slow, so total execution didn't take too much time.)
Create a new branch
-------------------
As shown above, the git workflow starts by creating a new branch to do the development work in. This will record all changes to the sourcecode you make, and can be put in as a pull request (explained below) for ESPEasy.
A new branch is created by clicking on the 'mega' branch name (lower left in the status bar of VSCode) and selecting the option 'Create new branch...' from the list presented at the mille-top of the VSCode window. Then a new braanch name should be typed. Branch naming does use some conventions. New features are often named like 'feature/purpose-of-the-feature', and bugfixes are usually named like 'bugfix/what-is-to-be-fixed'. For the addition of this documentation, I've created a branch named 'feature/how-to-guide-for-new-developers':
.. image:: VSCode_create_branch.png
:alt: VSCode create branch
.. image:: VSCode_type_branch_name.png
:alt: VSCode type the branch name
.. image:: VSCode_statusbar_new_branch.png
:alt: VSCode statusbar with new branch name
As an alternative, a new branch can also be created using command-line commands, you can type these after opening a Terminal in VSCode:
.. code-block::
git checkout -b feature/how-to-guide-for-new-developers
The nett result of this command is the same as from using the UI flow shown above.
Change code of ESPEasy
----------------------
To improve or extend an existing plugin or other code of ESPEasy, after creating a new branch for it, open the source file and modify code the as needed. Then compile and see if it all is according to the requirements of the compiler. Errors (showing as red text messages) will abort the compilation process, warnings, yellow messages, allow to continue, but should be resolved as much as possible before committing the code.
Testing is done by uploading the generated .bin file to an ESPEasy unit, testing the changed functionality to ensure no errors or undesired behavior remain in the code.
This uploading can be done in 2 ways:
* *Use the Upload feature of PIO*: If the ESP unit is connected to the computer via USB and the serial chip of the unit is recognized by the OS, the Upload option can be selected to compile the sources (only what was changed since the last compilation) and start the upload procedure. After uploading the ESP will restart.
* *Use the Update Firmware option of ESPEasy*: On the Tools tab of ESPEasy, there is a button Update Firmware available (on units that have enough free Flash space) so a new .bin file can be uploaded. The latest successful compiled file can be found in the ``build_output/bin`` subfolder of your ``ESPEasy`` folder.
Add a plugin to ESPEasy
-----------------------
Instead of just changing an existing plugin or some other feature of ESPEasy, also, new plugins can be added. Plugins can be created from scratch, starting with the template ``_Pxxx_PluginTemplate.ino`` that includes instructions what each section is supposed to do, take a proposed plugin from the ESPEasyPlayground repository at https://github.com/letscontrolit/ESPEasyPluginPlayground, or from other sources (some plugins are in personal Github repositories, but never submitted to the ESPEasyPluginPlayground).
It requires sufficient testing, and analysis of the runtime behavior, of that piece of code, before it should be submitted for a pull request.
Especially for new plugins, it is highly recommended to write documentation, as explained in the next paragraph.
Writing documentation
---------------------
Updating, or adding if it does not yet exist, the documentation is a useful activity that should be part of changing or adding to the ESPEasy code. Some of the optional VSCode extensions are specifically aimed at that task.
The documentation is created in the reStructuredText format, using mostly a ``.rst`` extension, and can be built locally by installing the sphinx tool. This can be installed manually by opening a Terminal window in VSCode (an already open Terminal can also be used) and issuing these commands:
.. code-block::
cd docs
pip install -r requirements.txt
The python tool ``pip`` will read the file ``requirements.txt`` and install all tools mentioned in the file. Depending on what is already installed, more or less of the modules will be installed. This should be a 1-time process, though sometimes updates to the tooling are made, and re-running these commands will then update all to the latest, possibly required, version.
The sources for the documentation are in the repository in the ``docs`` folder and its subfolders.
When adding screenshots it is advised to use the ``.png`` file format, as that usually has the best visible result for screenshots. When adding photos, the ``.jpg`` file format will do nicely.
Documentation for writing in reStructuredtext format can be found on the `Sphinx website <https://www.sphinx-doc.org/en/master/>`_
Building the documentation into html files, for reviewing locally, can be done by running this command from the ``docs`` folder:
On Windows:
.. highlight::bat
.. code-block::
.\make.bat html
On Linux or MacOS:
.. highlight::sh
.. code-block::
make html
The resulting output can be found in this folder with the ESPEasy folder: ``docs/build/html`` and can be viewed by opening the file ``index.html`` in a browser. Then the normal navigation within the documentation is available.
TODO: Add documentation about the (file) structure of the documentation.
Commit and create a pull request
--------------------------------
After changing and testing your changed source code, using builds uploaded to an actual ESP unit, the time has come to present the changed code to ESPEasy to be included in the regular build. This is called a 'pull request', and is explained in this Github documentation `About pull requests <https://docs.github.com/en/github/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests>`_ Quote: "Pull requests let you tell others about changes you've pushed to a branch in a repository on GitHub."
To make changes available for others they have to be 'staged' and 'committed' (git terminology) before it can be uploaded (pushed) to the repository. This stage and commit is a 2 step process, and easiest done from the VSCode UI. First select the GitLens plugin, and select the files that need to be staged and committed:
.. image:: VSCode_stage_changes.png
:alt: VSCode stage changed files
Selecting multiple files and clicking one of the ``+`` buttons next to the selected files, will put the files in the staging area, so they can be committed. Every commit will need a useful commit message, that describes what the commit is all about:
.. image:: VSCode_staged_files.png
:alt: VSCode list of staged files and commit message
Clicking the marked check button, or using the Ctrl-Enter key combination, will commit the staged files, using the commit message just typed.
After the commit is completed, more commits can be added, if desired. It is good practice to commit separate functional changes in separate commits. That will make the review process, as explained in the Github flow documentation, easier.
To have the commit(s) to be presented as a pull request, they must be published, and the easiest way to accomplish that is to use the Publish Changes button in VSCode:
.. image:: VSCode_Publish_changes.png
:alt: VSCode publish change button
After clicking that button, you have to select the source the changes should be published to. As we don't have (direct) write access to the upstream ESPEasy repository, we can only publish to the 'origin' (git terminology), our own fork of the repository, so that option should be selected by clicking it, or pressing the Enter key:
.. image:: VSCode_select_publish_source.png
:alt: VSCode select publish source
If this is the first time you try to push any changes to your repository, VSCode, or actually the GitLens plugin, will ask for your Github credentials, and will switch back and forth a few times between your webbrowser and VSCode to complete the authentication process. This is as intended.
Now that the Publish Changes is done, the pull request can be created. We have to switch to the ESPEasy repository on Github to complete that task. The Github website will show the options for that, assuming you are still logged in to your Github account from that browser:
When opening the https://github.com/letscontrolit/ESPEasy page (or refreshing it if it was already open), a message is shown that you have committed something to your forked repository, that can be pull-requested into the ESPEasy repository:
.. image:: Github_start_new_pull_request.png
:alt: Github start new pull rquest
After clicking the 'Compare and pull request' button, a description for the PR can be given, the title can be updated, and the pull request created. Helpful information can be found in `Creating a pull request from a fork <https://docs.github.com/en/github/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork>`_
If needed, or requested during the review process, more changes can be made to files, or files added or deleted, then staged and committed, after which these can be pushed to Github, and the changes will be automatically added to the PR.
Regular maintenance of your fork
--------------------------------
If you have forked ESPEasy before (or some time ago), and want to start (new) work on the code, it is required to update your fork with the latest state of affairs of ESPEasy, to avoid surprises, or difficulties when trying to merge, after submitting a PR.
This expects the currently selected 'branch' to be ``mega``, as is visible in the VSCode statusbar:
.. image:: VSCode_statusbar_mega.png
:alt: VSCode statusbar current branch mega
The desired branch can be selected by clicking the currently selected branch name as shown in the VSCode status bar, or by typing this command from a VSCode terminal window:
.. code-block::
git checkout mega
The update is 'pulled' (git terminology) by getting the latest from the ``upstream`` source (we defined that source after the initial clone), by opening a terminal window in VSCode and issuing this command:
.. code-block::
git pull upstream mega
(NB: The current development branch of ESPEasy is called ``mega`` where other Github repos often use ``master``, or ``main``. ESPEasy *does* have a ``master`` branch, but it currently isn't actively maintained. The name of the 'main' branch of any repository can be chosen freely, the ``master`` or ``main`` name is just used by convention.)
Depending on the time passed since the last update, some files will be updated from the git pull command.
To update your fork on Github, these changes should be 'pushed' (git terminology) to your fork by using the command:
.. code-block::
git push
If this is the first time you try to push any changes to your repository, VSCode, or actually the GitLens plugin, will ask for your Github credentials, and will switch back and forth a few times between your webbrowser and VSCode to complete the authentication process. This is as intended.
Updating your fork this way should be done at least every time before you start new work, and can be done more often if desired. If kept up to date you will avoid starting with an out-dated state of the repository.
Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

+2 -2
View File
@@ -1,7 +1,7 @@
Introduction
------------
The Communication - TSOP4838 module can be used to receive IR remote control signals.
The Communication - IR Receive (TSOP4838) module can be used to receive IR remote control signals.
Connect a TSOP module, preferably a 38Khz one, preferably to GPIO with interrupt functionality.
@@ -24,7 +24,7 @@ Setup
Task settings
~~~~~~~~~~~~~
* **Device**: Communication - TSOP4838
* **Device**: Communication - IR Receive (TSOP4838)
* **Name**: Name of the task (example name **IR**).
* **Enable**: Should the task be enabled or not
+86
View File
@@ -0,0 +1,86 @@
.. include:: ../Plugin/_plugin_substitutions_p11x.repl
.. _P117_page:
|P117_typename|
==================================================
|P117_shortinfo|
Plugin details
--------------
Type: |P117_type|
Name: |P117_name|
Status: |P117_status|
GitHub: |P117_github|_
Maintainer: |P117_maintainer|
Used libraries: |P117_usedlibraries|
Description
-----------
The SCD30 is a CO2 sensor unit that also provides Humidity and Temperature measurement, supports auto-calibration, and has temperature and altitude compensation settings.
Configuration
-------------
.. image:: P117_DeviceConfiguration.png
:alt: Device configuration
* **Name** Required by ESPEasy, must be unique among the list of available devices/tasks.
* **Enabled** The device can be disabled or enabled. When not enabled the device should not use any resources.
I2C options
^^^^^^^^^^^
.. note:: According to manufacturer specification, the I2C ClockStretchLimit setting should be configured at a minimum value of 20 milliseconds, and can take up to 150 msec once a day. For ESP8266 and ESP32 there are somewhat different values to fill, ESP8266 expects microseconds (usec) there, 20000 to 150000, and ESP32 needs 1/80 usec, 1600000 to 12000000, so a factor 80 difference.
The available settings here depend on the build used. At least the **Force Slow I2C speed** option is available, but selections for the I2C Multiplexer can also be shown. For details see the :ref:`Hardware_page`
.. note:: The SCD30 sensor supports a max. I2C Clock Speed of 100 kHz, so **Force Slow I2C speed** should be checked! (ESPEasy has a default setting of 100 kHz for I2C Slow device Clock Speed).
Device Settings
^^^^^^^^^^^^^^^
* **Altitude** Set the altitude above sealevel in meters, where the sensor is placed. The range is 0..2000 meter.
* **Temp offset** Offset for temperature correction. Can be set to compensate any self-heating of the device, by comparing the temperature measurement with another calibrated temperature measured close to the device. The difference can be set here, and is (also) stored in the sensor (but re-written every time ESPEasy initializes the sensor).
The Data Acquisition, Send to Controller and Interval settings are standard available configuration items. Send to Controller only when one or more Controllers are configured.
**Interval** By default, Interval will be set to 60 sec. The data will be measured and optionally sent to any configured controllers using this interval.
Values
^^^^^^
The used library provides an averaged CO2 value of the last 5 measurements in value ``CO2``. The last single CO2 measurement is also available as ``CO2raw``.
``Humidity`` shows the relative humidity RH in a range of 0..100%.
``Temperature`` shows the temperature of the sensor in degrees Celcius, with the **Temp offset** applied.
Commands available
^^^^^^^^^^^^^^^^^^
.. include:: P117_commands.repl
Change log
----------
.. versionchanged:: 2.0
...
|added|
2021-11-20 Initial release version.
Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

+31
View File
@@ -0,0 +1,31 @@
.. csv-table::
:header: "Command Syntax", "Extra information"
:widths: 20, 30
"
| ``scdgetabc``
","
| Shows automatic calibration period in days, 0 = disable.
"
"
| ``scdgetalt``
","
| Shows altitude compensation configuration in meters above sea level.
"
"
| ``scdgettmp``
","
| Shows temperature offset in degrees C.
"
+1
View File
@@ -140,6 +140,7 @@ There are different released versions of ESP Easy:
":ref:`P113_page`","|P113_status|","P113"
":ref:`P114_page`","|P114_status|","P114"
":ref:`P115_page`","|P115_status|","P115"
":ref:`P117_page`","|P117_status|","P117"
":ref:`P124_page`","|P124_status|","P124"
+1 -1
View File
@@ -8,7 +8,7 @@
.. |Plugin_Energy_Heat| replace:: :ref:`P088_page`, :ref:`P093_page`
.. |Plugin_Environment| replace:: :ref:`P004_page`, :ref:`P005_page`, :ref:`P006_page`, :ref:`P014_page`, :ref:`P024_page`, :ref:`P028_page`, :ref:`P030_page`, :ref:`P031_page`, :ref:`P032_page`, :ref:`P034_page`, :ref:`P039_page`, :ref:`P047_page`, :ref:`P051_page`, :ref:`P068_page`, :ref:`P069_page`, :ref:`P072_page`, :ref:`P103_page`, :ref:`P104_page`, :ref:`P105_page`, :ref:`P106_page`
.. |Plugin_Extra_IO| replace:: :ref:`P011_page`, :ref:`P022_page`
.. |Plugin_Gases| replace:: :ref:`P049_page`, :ref:`P052_page`, :ref:`P083_page`, :ref:`P090_page`
.. |Plugin_Gases| replace:: :ref:`P049_page`, :ref:`P052_page`, :ref:`P083_page`, :ref:`P090_page`, :ref:`P117_page`
.. |Plugin_Generic| replace:: :ref:`P003_page`, :ref:`P026_page`, :ref:`P033_page`, :ref:`P037_page`, :ref:`P081_page`, :ref:`P100_page`
.. |Plugin_Gesture| replace:: :ref:`P064_page`
.. |Plugin_Gyro| replace:: :ref:`P045_page`
@@ -78,7 +78,7 @@
.. |P016_name| replace:: :cyan:`TSOP4838`
.. |P016_type| replace:: :cyan:`Communication`
.. |P016_typename| replace:: :cyan:`Communication - TSOP4838`
.. |P016_typename| replace:: :cyan:`Communication - IR Receive (TSOP4838)`
.. |P016_porttype| replace:: `.`
.. |P016_status| replace:: :yellow:`IR`
.. |P016_github| replace:: P016_IR.ino
@@ -62,3 +62,16 @@
.. |P115_maintainer| replace:: `TD-er`
.. |P115_compileinfo| replace:: `.`
.. |P115_usedlibraries| replace:: `https://github.com/sparkfun/SparkFun_MAX1704x_Fuel_Gauge_Arduino_Library`
.. |P117_name| replace:: :cyan:`SCD30`
.. |P117_type| replace:: :cyan:`Gases`
.. |P117_typename| replace:: :cyan:`Gases - SCD30 CO2, Humidity, Temperature`
.. |P117_porttype| replace:: `.`
.. |P117_status| replace:: :yellow:`TESTING D`
.. |P117_github| replace:: _P117_SCD30.ino
.. _P117_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P117_SCD30.ino
.. |P117_usedby| replace:: `.`
.. |P117_shortinfo| replace:: `SCD30 CO2 sensor`
.. |P117_maintainer| replace:: `tonhuisman`
.. |P117_compileinfo| replace:: `.`
.. |P117_usedlibraries| replace:: `https://github.com/Frogmore42/Sonoff-Tasmota/tree/development/lib/FrogmoreScd30 (corrected local copy)`
+4
View File
@@ -664,6 +664,10 @@ P115 :ref:`P115_page`
.. include:: ../Plugin/P115_commands.repl
P117 :ref:`P117_page`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. include:: ../Plugin/P117_commands.repl
P124 :ref:`P124_page`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+743
View File
@@ -0,0 +1,743 @@
/*
# Copyright (c) 2019 Frogmore42
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <Wire.h>
#include <math.h>
#include <stdio.h>
// #include <twi.h>
#include <FrogmoreScd30.h>
#define COMMAND_SCD30_CONTINUOUS_MEASUREMENT 0x0010
#define COMMAND_SCD30_MEASUREMENT_INTERVAL 0x4600
#define COMMAND_SCD30_GET_DATA_READY 0x0202
#define COMMAND_SCD30_READ_MEASUREMENT 0x0300
#define COMMAND_SCD30_CALIBRATION_TYPE 0x5306
#define COMMAND_SCD30_FORCED_RECALIBRATION_FACTOR 0x5204
#define COMMAND_SCD30_TEMPERATURE_OFFSET 0x5403
#define COMMAND_SCD30_ALTITUDE_COMPENSATION 0x5102
#define COMMAND_SCD30_SOFT_RESET 0xD304
#define COMMAND_SCD30_GET_FW_VERSION 0xD100
#define COMMAND_SCD30_STOP_MEASUREMENT 0x0104
#define SCD30_DATA_REGISTER_BYTES 2
#define SCD30_DATA_REGISTER_WITH_CRC 3
#define SCD30_MEAS_BYTES 18
#ifdef SCD30_DEBUG
enum LoggingLevels { LOG_LEVEL_NONE, LOG_LEVEL_ERROR, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG, LOG_LEVEL_DEBUG_MORE, LOG_LEVEL_ALL };
char scd30log_data[180];
#endif // ifdef SCD30_DEBUG
void FrogmoreScd30::begin(TwoWire *pWire, uint8_t i2cAddress)
{
this->i2cAddress = i2cAddress;
if (pWire == NULL)
{
this->pWire = &Wire;
}
else
{
this->pWire = pWire;
}
co2NewDataLocation = -1; // indicates there is no data, so the 1st data point needs to fill up the median filter
// this->pWire->setClockStretchLimit(200000); // should be set via Tools->Advanced->I2C ClockStretchLimit
this->ambientPressure = 0;
}
void FrogmoreScd30::begin(uint8_t i2cAddress)
{
begin(NULL, i2cAddress);
}
void FrogmoreScd30::begin(TwoWire *pWire)
{
begin(pWire, SCD30_ADDRESS);
}
void FrogmoreScd30::begin(void)
{
begin(NULL, SCD30_ADDRESS);
}
/*---------------------------------------------------------------------------
Function : opt_med5() In : pointer to array of 5 values
Out : a uint16_t which is the middle value of the sorted array
Job : optimized search of the median of 5 values
Notice : found on sci.image.processing cannot go faster unless assumptions are made on the nature of the input signal.
---------------------------------------------------------------------------*/
#define PIX_SORT(a, b) { if ((a) > (b)) PIX_SWAP((a), (b)); }
#define PIX_SWAP(a, b) { uint16_t temp = (a); (a) = (b); (b) = temp; }
uint16_t opt_med5(uint16_t *p)
{
PIX_SORT(p[0], p[1]);
PIX_SORT(p[3], p[4]);
PIX_SORT(p[0], p[3]);
PIX_SORT(p[1], p[4]);
PIX_SORT(p[1], p[2]);
PIX_SORT(p[2], p[3]);
PIX_SORT(p[1], p[2]);
return p[2];
}
// twi_status() attempts to read out any data left that is holding SDA low, so a new transaction can take place
// something like (http://www.forward.com.au/pfod/ArduinoProgramming/I2C_ClearBus/index.html)
// int FrogmoreScd30::clearI2CBus(void)
// {
// #ifdef SCD30_DEBUG
// snprintf_P(scd30log_data, sizeof(scd30log_data), "clearI2CBus");
// AddLog(LOG_LEVEL_DEBUG_MORE);
// #endif // ifdef SCD30_DEBUG
// return twi_status();
// }
#ifdef SCD30_DEBUG
void FrogmoreScd30::AddLog(uint8_t loglevel)
{
if (loglevel <= LOG_LEVEL_INFO)
{
Serial.printf("%s\r\n", scd30log_data);
}
}
#endif // ifdef SCD30_DEBUG
uint8_t FrogmoreScd30::computeCRC8(uint8_t data[], uint8_t len)
// Computes the CRC that the SCD30 uses
{
uint8_t crc = 0xFF; // Init with 0xFF
for (uint8_t x = 0; x < len; x++)
{
crc ^= data[x]; // XOR-in the next input byte
for (uint8_t i = 0; i < 8; i++)
{
if ((crc & 0x80) != 0) {
crc = (uint8_t)((crc << 1) ^ 0x31);
}
else {
crc <<= 1;
}
}
}
return crc; // No output reflection
}
// Sends stream of bytes to device
int FrogmoreScd30::sendBytes(void *pInput, uint8_t len)
{
uint8_t *pBytes = (uint8_t *)pInput;
int result;
uint8_t errorBytes = 0; // number of bytes that had an error in transmission
#ifdef SCD30_DEBUG
snprintf_P(scd30log_data,
sizeof(scd30log_data),
"Scd30SendBytes: data: 0x %02X %02X %02X | 0x %02X %02X %02X | 0x %02X %02X %02X",
pBytes[0],
pBytes[1],
pBytes[2],
pBytes[3],
pBytes[4],
pBytes[5],
pBytes[6],
pBytes[7],
pBytes[8]);
AddLog(LOG_LEVEL_DEBUG_MORE);
#endif // ifdef SCD30_DEBUG
pWire->beginTransmission(this->i2cAddress);
errorBytes = len - (pWire->write(pBytes, len));
result = pWire->endTransmission();
if (errorBytes || result)
{
#ifdef SCD30_DEBUG
snprintf_P(scd30log_data, sizeof(scd30log_data), "Scd30SendBytes: errorBytes: %d | Wire.end: %d", errorBytes, result);
AddLog(LOG_LEVEL_INFO);
#endif // ifdef SCD30_DEBUG
}
result <<= 8; // leave room for error bytes number
result |= errorBytes; // low byte has number of bytes that were not written correctly
return result;
}
// Gets a number of bytes from device
int FrogmoreScd30::getBytes(void *pOutput, uint8_t len)
{
uint8_t *pBytes = (uint8_t *)pOutput;
uint8_t result;
result = pWire->requestFrom(this->i2cAddress, len);
if (len != result)
{
#ifdef SCD30_DEBUG
snprintf_P(scd30log_data, sizeof(scd30log_data), "Scd30GetBytes: wire request expected %d got: %d", len, result);
AddLog(LOG_LEVEL_INFO);
#endif // ifdef SCD30_DEBUG
return ERROR_SCD30_NOT_ENOUGH_BYTES_ERROR;
}
if (pWire->available())
{
for (int x = 0; x < len; x++)
{
pBytes[x] = pWire->read();
}
#ifdef SCD30_DEBUG
snprintf_P(scd30log_data, sizeof(scd30log_data), "Scd30GetBytes: data: 0x %02X %02X %02X | 0x %02X %02X %02X | 0x %02X %02X %02X",
pBytes[0], pBytes[1], pBytes[2], pBytes[3], pBytes[4], pBytes[5], pBytes[6], pBytes[7], pBytes[8]);
AddLog(LOG_LEVEL_DEBUG_MORE);
#endif // ifdef SCD30_DEBUG
return ERROR_SCD30_NO_ERROR;
}
return ERROR_SCD30_UNKNOWN_ERROR;
}
// Sends just a command, no arguments, no CRC
int FrogmoreScd30::sendCommand(uint16_t command)
{
uint8_t data[2];
data[0] = command >> 8;
data[1] = command & 0xFF;
int error = sendBytes(data, sizeof(data));
if (error)
{
#ifdef SCD30_DEBUG
snprintf_P(scd30log_data, sizeof(scd30log_data), "Scd30SendCommand: Scd30SendBytes failed: 0x%lX", error);
AddLog(LOG_LEVEL_INFO);
#endif // ifdef SCD30_DEBUG
}
return error;
}
// Sends a command along with arguments and CRC
int FrogmoreScd30::sendCommandArguments(uint16_t command, uint16_t arguments)
{
uint8_t data[5];
data[0] = command >> 8;
data[1] = command & 0xFF;
data[2] = arguments >> 8;
data[3] = arguments & 0xFF;
data[4] = computeCRC8(&data[2], 2); // Calc CRC on the arguments only, not the command
int error = sendBytes(data, sizeof(data));
if (error)
{
#ifdef SCD30_DEBUG
snprintf_P(scd30log_data, sizeof(scd30log_data), "Scd30SendCommandArguments: Scd30SendBytes failed: 0x%lX", error);
AddLog(LOG_LEVEL_INFO);
#endif // ifdef SCD30_DEBUG
}
return error;
}
int FrogmoreScd30::get16BitRegCheckCRC(void *pInput, uint16_t *pData)
{
uint8_t *pBytes = (uint8_t *)pInput;
uint8_t expectedCRC = computeCRC8(pBytes, SCD30_DATA_REGISTER_BYTES);
if (expectedCRC != pBytes[SCD30_DATA_REGISTER_BYTES])
{
#ifdef SCD30_DEBUG
snprintf_P(scd30log_data, sizeof(scd30log_data), "Scd30get16BitRegCheckCRC: expected: 0x%02X, but got: 0x%02X", expectedCRC,
pBytes[SCD30_DATA_REGISTER_BYTES]);
AddLog(LOG_LEVEL_INFO);
snprintf_P(scd30log_data, sizeof(scd30log_data), "Scd30get16BitRegCheckCRC: data: 0x%02X, 0x%02X, 0x%02X", pBytes[0], pBytes[1],
pBytes[2]);
AddLog(LOG_LEVEL_INFO);
#endif // ifdef SCD30_DEBUG
return ERROR_SCD30_CRC_ERROR;
}
*pData = (uint16_t)pBytes[0] << 8 | pBytes[1]; // data from SCD30 is Big-Endian
return ERROR_SCD30_NO_ERROR;
}
// gets 32 bits, (2) 16-bit chunks, and validates the CRCs
//
int FrogmoreScd30::get32BitRegCheckCRC(void *pInput, float *pData)
{
uint16_t tempU16High;
uint16_t tempU16Low;
uint8_t *pBytes = (uint8_t *)pInput;
uint32_t rawInt = 0;
int error = get16BitRegCheckCRC(pBytes, &tempU16High);
if (error) {
return error;
}
error = get16BitRegCheckCRC(pBytes + SCD30_DATA_REGISTER_WITH_CRC, &tempU16Low);
if (error) {
return error;
}
// data from SCD is Big-Endian
rawInt |= tempU16High;
rawInt <<= 16;
rawInt |= tempU16Low;
*pData = *(float *)&rawInt;
#ifdef SCD30_DEBUG
snprintf_P(scd30log_data, sizeof(scd30log_data), "get32BitRegCheckCRC: got: tempUs 0x%lX, %lX", tempU16High, tempU16Low);
AddLog(LOG_LEVEL_DEBUG);
#endif // ifdef SCD30_DEBUG
if (isnan(*pData) || isinf(*pData))
{
#ifdef SCD30_DEBUG
snprintf_P(scd30log_data, sizeof(scd30log_data), "get32BitRegCheckCRC: not a floating point number: rawInt 0x%lX", rawInt);
AddLog(LOG_LEVEL_INFO);
#endif // ifdef SCD30_DEBUG
return ERROR_SCD30_NOT_A_NUMBER_ERROR;
}
return ERROR_SCD30_NO_ERROR;
}
// Gets two bytes (and check CRC) from SCD30
int FrogmoreScd30::readRegister(uint16_t registerAddress, uint16_t *pData)
{
int error = sendCommand(registerAddress);
if (error)
{
#ifdef SCD30_DEBUG
snprintf_P(scd30log_data, sizeof(scd30log_data), "Scd30ReadRegister: SendCommand error: 0x%lX", error);
AddLog(LOG_LEVEL_INFO);
#endif // ifdef SCD30_DEBUG
return error;
}
delay(1); // the SCD30 uses clock streching to give it time to prepare data, waiting here makes it work
uint8_t data[SCD30_DATA_REGISTER_WITH_CRC];
error = getBytes(data, sizeof(data));
if (error)
{
#ifdef SCD30_DEBUG
snprintf_P(scd30log_data, sizeof(scd30log_data), "Scd30ReadRegister: Scd30GetBytes error: 0x%lX", error);
AddLog(LOG_LEVEL_INFO);
#endif // ifdef SCD30_DEBUG
return error;
}
uint16_t regValue;
error = get16BitRegCheckCRC(data, &regValue);
if (error)
{
#ifdef SCD30_DEBUG
snprintf_P(scd30log_data, sizeof(scd30log_data), "Scd30ReadRegister: Scd30get16BitRegCheckCRC error: 0x%lX", error);
AddLog(LOG_LEVEL_INFO);
#endif // ifdef SCD30_DEBUG
return error;
}
*pData = regValue;
return ERROR_SCD30_NO_ERROR;
}
int FrogmoreScd30::softReset(void)
{
return sendCommand(COMMAND_SCD30_SOFT_RESET);
}
int FrogmoreScd30::getAltitudeCompensation(uint16_t *pHeight_meter)
{
return readRegister(COMMAND_SCD30_ALTITUDE_COMPENSATION, pHeight_meter);
}
int FrogmoreScd30::getAmbientPressure(uint16_t *pAirPressure_mbar)
{
*pAirPressure_mbar = ambientPressure;
return ERROR_SCD30_NO_ERROR;
}
int FrogmoreScd30::getCalibrationType(uint16_t *pIsAuto)
{
uint16_t value = 0;
int error = readRegister(COMMAND_SCD30_CALIBRATION_TYPE, &value);
if (!error)
{
*pIsAuto = value != 0;
}
return error;
}
int FrogmoreScd30::getFirmwareVersion(uint8_t *pMajor, uint8_t *pMinor)
{
uint16_t value;
int error = readRegister(COMMAND_SCD30_GET_FW_VERSION, &value);
if (!error)
{
*pMajor = value >> 8;
*pMinor = value & 0xFF;
}
return error;
}
int FrogmoreScd30::getForcedRecalibrationFactor(uint16_t *pCo2_ppm)
{
return readRegister(COMMAND_SCD30_FORCED_RECALIBRATION_FACTOR, pCo2_ppm);
}
int FrogmoreScd30::getMeasurementInterval(uint16_t *pTime_sec)
{
return readRegister(COMMAND_SCD30_MEASUREMENT_INTERVAL, pTime_sec);
}
int FrogmoreScd30::getTemperatureOffset(float *pOffset_degC)
{
uint16_t value;
int error = readRegister(COMMAND_SCD30_TEMPERATURE_OFFSET, &value);
if (!error)
{
// result is in centi-degrees, need to convert to degrees
*pOffset_degC = (float)value / 100.0;
}
return error;
}
int FrogmoreScd30::getTemperatureOffset(uint16_t *pOffset_centiDegC)
{
uint16_t value;
int error = readRegister(COMMAND_SCD30_TEMPERATURE_OFFSET, &value);
if (!error)
{
// result is in centi-degrees, need to convert to degrees
*pOffset_centiDegC = value;
}
return error;
}
int FrogmoreScd30::setAltitudeCompensation(uint16_t height_meter)
{
return sendCommandArguments(COMMAND_SCD30_ALTITUDE_COMPENSATION, height_meter);
}
int FrogmoreScd30::setAmbientPressure(uint16_t airPressure_mbar)
{
ambientPressure = airPressure_mbar;
return beginMeasuring(ambientPressure);
}
int FrogmoreScd30::setAutoSelfCalibration(void)
{
bool isAuto = true;
return setCalibrationType(isAuto);
}
int FrogmoreScd30::setCalibrationType(bool isAuto)
{
bool value = !!isAuto; // using NOT operator twice makes sure value is 0 or 1
return sendCommandArguments(COMMAND_SCD30_CALIBRATION_TYPE, value);
}
int FrogmoreScd30::setForcedRecalibrationFactor(uint16_t co2_ppm)
{
return sendCommandArguments(COMMAND_SCD30_FORCED_RECALIBRATION_FACTOR, co2_ppm);
}
int FrogmoreScd30::setManualCalibration(void)
{
bool isAuto = false;
return setCalibrationType(isAuto);
}
int FrogmoreScd30::setMeasurementInterval(uint16_t time_sec)
{
if (time_sec < 2) { time_sec = 2; }
if (time_sec > 1800) { time_sec = 1800; }
return sendCommandArguments(COMMAND_SCD30_MEASUREMENT_INTERVAL, time_sec);
}
int FrogmoreScd30::setTemperatureOffset(float offset_degC)
{
uint16_t offset_centiDegC;
if (offset_degC >= 0)
{
offset_centiDegC = (uint16_t)offset_degC * 100;
return sendCommandArguments(COMMAND_SCD30_TEMPERATURE_OFFSET, offset_centiDegC);
}
else
{
return ERROR_SCD30_INVALID_VALUE;
}
}
int FrogmoreScd30::setTemperatureOffset(uint16_t offset_centiDegC)
{
return sendCommandArguments(COMMAND_SCD30_TEMPERATURE_OFFSET, offset_centiDegC);
}
int FrogmoreScd30::beginMeasuring(void)
{
return beginMeasuring(ambientPressure);
}
int FrogmoreScd30::beginMeasuring(uint16_t airPressure_mbar)
{
ambientPressure = airPressure_mbar;
return sendCommandArguments(COMMAND_SCD30_CONTINUOUS_MEASUREMENT, ambientPressure);
}
int FrogmoreScd30::isDataAvailable(bool *pIsAvailable)
{
uint16_t isDataAvailable = false;
int error = readRegister(COMMAND_SCD30_GET_DATA_READY, &isDataAvailable);
if (!error)
{
*pIsAvailable = isDataAvailable != 0;
}
return error;
}
uint32_t FrogmoreScd30::readMeasurement(
uint16_t *pCO2_ppm,
uint16_t *pCO2EAvg_ppm,
float *pTemperature,
float *pHumidity
)
{
bool isAvailable = false;
int error = 0;
float tempCO2;
float tempHumidity;
float tempTemperature;
error = isDataAvailable(&isAvailable);
if (error)
{
return error;
}
if (!isAvailable)
{
return ERROR_SCD30_NO_DATA;
}
#ifdef SCD30_DEBUG
snprintf_P(scd30log_data, sizeof(scd30log_data), "Scd30ReadMeasurement: have data");
AddLog(LOG_LEVEL_DEBUG_MORE);
#endif // ifdef SCD30_DEBUG
error = sendCommand(COMMAND_SCD30_READ_MEASUREMENT);
if (error)
{
#ifdef SCD30_DEBUG
snprintf_P(scd30log_data, sizeof(scd30log_data), "Scd30ReadMeasurement: send command failed: 0x%lX", error);
AddLog(LOG_LEVEL_INFO);
#endif // ifdef SCD30_DEBUG
return error;
}
delay(1); // the SCD30 uses clock streching to give it time to prepare data, waiting here makes it work
uint8_t bytes[SCD30_MEAS_BYTES];
// there are (6) 16-bit values, each with a CRC in the measurement data
// the chip does not seem to like sending this data, except all at once
error = getBytes(bytes, SCD30_MEAS_BYTES);
if (error)
{
#ifdef SCD30_DEBUG
snprintf_P(scd30log_data, sizeof(scd30log_data), "Scd30ReadMeasurement: Scd30GetBytes command failed: 0x%lX", error);
AddLog(LOG_LEVEL_INFO);
#endif // ifdef SCD30_DEBUG
return error;
}
#ifdef SCD30_DEBUG
snprintf_P(scd30log_data,
sizeof(scd30log_data),
"Scd30ReadMeasurement: Scd30GetBytes data: 0x %02X %02X %02X | 0x %02X %02X %02X | 0x %02X %02X %02X",
bytes[0],
bytes[1],
bytes[2],
bytes[3],
bytes[4],
bytes[5],
bytes[6],
bytes[7],
bytes[8]);
AddLog(LOG_LEVEL_DEBUG_MORE);
snprintf_P(scd30log_data,
sizeof(scd30log_data),
"Scd30ReadMeasurement: Scd30GetBytes data: 0x %02X %02X %02X | 0x %02X %02X %02X | 0x %02X %02X %02X",
bytes[9],
bytes[10],
bytes[11],
bytes[12],
bytes[13],
bytes[14],
bytes[15],
bytes[16],
bytes[17]);
AddLog(LOG_LEVEL_DEBUG_MORE);
#endif // ifdef SCD30_DEBUG
error = get32BitRegCheckCRC(&bytes[0], &tempCO2);
if (error)
{
#ifdef SCD30_DEBUG
snprintf_P(scd30log_data, sizeof(scd30log_data), "Scd30ReadMeasurement: Scd30Get32BitsCheckCRC 1st command failed: 0x%lX", error);
AddLog(LOG_LEVEL_INFO);
#endif // ifdef SCD30_DEBUG
return error;
}
error = get32BitRegCheckCRC(&bytes[6], &tempTemperature);
if (error)
{
#ifdef SCD30_DEBUG
snprintf_P(scd30log_data, sizeof(scd30log_data), "Scd30ReadMeasurement: Scd30Get32BitsCheckCRC 2nd command failed: 0x%lX", error);
AddLog(LOG_LEVEL_INFO);
#endif // ifdef SCD30_DEBUG
return error;
}
error = get32BitRegCheckCRC(&bytes[12], &tempHumidity);
if (error)
{
#ifdef SCD30_DEBUG
snprintf_P(scd30log_data, sizeof(scd30log_data), "Scd30ReadMeasurement: Scd30Get32BitsCheckCRC 3rd command failed: 0x%lX", error);
AddLog(LOG_LEVEL_INFO);
#endif // ifdef SCD30_DEBUG
return error;
}
if (tempCO2 == 0)
{
return ERROR_SCD30_CO2_ZERO;
}
if (co2NewDataLocation < 0)
{
co2EAverage = tempCO2;
for (int x = 0; x < SCD30_MEDIAN_FILTER_SIZE; x++)
{
co2History[x] = tempCO2;
co2NewDataLocation = 1;
}
}
else
{
co2History[co2NewDataLocation++] = tempCO2;
if (co2NewDataLocation >= SCD30_MEDIAN_FILTER_SIZE)
{
co2NewDataLocation = 0;
}
}
#ifdef SCD30_DEBUG
snprintf_P(scd30log_data,
sizeof(scd30log_data),
"Scd30ReadMeasurement: co2History: %ld, %ld, %ld, %ld, %ld",
co2History[0],
co2History[1],
co2History[2],
co2History[3],
co2History[4]);
AddLog(LOG_LEVEL_DEBUG_MORE);
#endif // ifdef SCD30_DEBUG
// copy array since the median filter function will re-arrange it
uint16_t temp[SCD30_MEDIAN_FILTER_SIZE];
for (int x = 0; x < SCD30_MEDIAN_FILTER_SIZE; x++)
{
temp[x] = co2History[x];
}
#ifdef SCD30_DEBUG
snprintf_P(scd30log_data,
sizeof(scd30log_data),
"Scd30ReadMeasurement: temp: %ld, %ld, %ld, %ld, %ld",
temp[0],
temp[1],
temp[2],
temp[3],
temp[4]);
AddLog(LOG_LEVEL_DEBUG_MORE);
#endif // ifdef SCD30_DEBUG
*pCO2_ppm = opt_med5(temp);
#ifdef SCD30_DEBUG
snprintf_P(scd30log_data, sizeof(scd30log_data), "Scd30ReadMeasurement: CO2_ppm: %ld", *pCO2_ppm);
AddLog(LOG_LEVEL_DEBUG_MORE);
#endif // ifdef SCD30_DEBUG
if (pCO2EAvg_ppm)
{
int16_t delta = (int16_t)*pCO2_ppm - (int16_t)co2EAverage;
int16_t change = delta / 32;
co2EAverage += change;
#if 0
uint16_t remain = co2EAverage % 5;
uint16_t dividend = co2EAverage / 5;
uint16_t co2EAReported = dividend * 5;
if (remain > 2)
{
co2EAReported += 5;
}
*pCO2EAvg_ppm = co2EAReported;
#else // if 0
*pCO2EAvg_ppm = co2EAverage;
#endif // if 0
}
*pTemperature = tempTemperature;
*pHumidity = tempHumidity;
return ERROR_SCD30_NO_ERROR;
}
int FrogmoreScd30::stopMeasuring(void)
{
return sendCommand(COMMAND_SCD30_STOP_MEASUREMENT);
}
+115
View File
@@ -0,0 +1,115 @@
/*
# Copyright (c) 2019 Frogmore42
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#pragma once
#include "Arduino.h"
// #define SCD30_DEBUG
#define SCD30_ADDRESS 0x61
#define ERROR_SCD30_NO_ERROR 0
#define ERROR_SCD30_NO_DATA 0x80000000
#define ERROR_SCD30_CO2_ZERO 0x90000000
#define ERROR_SCD30_UNKNOWN_ERROR 0x1000000
#define ERROR_SCD30_CRC_ERROR 0x2000000
#define ERROR_SCD30_NOT_ENOUGH_BYTES_ERROR 0x3000000
#define ERROR_SCD30_NOT_FOUND_ERROR 0x4000000
#define ERROR_SCD30_NOT_A_NUMBER_ERROR 0x5000000
#define ERROR_SCD30_INVALID_VALUE 0x6000000
#define SCD30_MEDIAN_FILTER_SIZE 5
class FrogmoreScd30 {
public:
FrogmoreScd30() {}
// Constructors
// the SCD30 only lists a single i2c address, so not necesary to specify
//
void begin(void);
void begin(uint8_t _i2cAddress);
void begin(TwoWire *pWire);
void begin(TwoWire *pWire,
uint8_t _i2cAddress);
int softReset(void);
// int clearI2CBus(void); // this is a HARD reset of the IC2 bus to restore communication, it will disrupt the bus
int getAltitudeCompensation(uint16_t *pHeight_meter);
int getAmbientPressure(uint16_t *pAirPressure_mbar);
int getCalibrationType(uint16_t *pIsAuto);
int getFirmwareVersion(uint8_t *pMajor,
uint8_t *pMinor);
int getForcedRecalibrationFactor(uint16_t *pCo2_ppm);
int getMeasurementInterval(uint16_t *pTime_sec);
int getTemperatureOffset(float *pOffset_degC);
int getTemperatureOffset(uint16_t *pOffset_centiDegC);
int setAltitudeCompensation(uint16_t height_meter);
int setAmbientPressure(uint16_t airPressure_mbar);
int setAutoSelfCalibration(void);
int setCalibrationType(bool isAuto);
int setForcedRecalibrationFactor(uint16_t co2_ppm);
int setManualCalibration(void);
int setMeasurementInterval(uint16_t time_sec);
int setTemperatureOffset(float offset_degC);
int setTemperatureOffset(uint16_t offset_centiDegC);
int beginMeasuring(void);
int beginMeasuring(uint16_t airPressure_mbar); // also sets ambient pressure offset in mbar/hPascal
int isDataAvailable(bool *pIsAvailable);
uint32_t readMeasurement(uint16_t *pCO2_ppm,
uint16_t *pCO2EAvg_ppm,
float *pTemperature,
float *pHumidity);
int stopMeasuring(void);
private:
uint8_t i2cAddress;
TwoWire *pWire;
uint16_t ambientPressure;
uint16_t co2AvgExtra;
uint16_t co2History[SCD30_MEDIAN_FILTER_SIZE];
uint16_t co2EAverage;
int8_t co2NewDataLocation; // location to put new CO2 data for median filter
uint8_t computeCRC8(uint8_t data[],
uint8_t len);
int sendBytes(void *pInput,
uint8_t len);
int getBytes(void *pOutput,
uint8_t len);
int sendCommand(uint16_t command);
int sendCommandArguments(uint16_t command,
uint16_t arguments);
int get16BitRegCheckCRC(void *pInput,
uint16_t *pData);
int get32BitRegCheckCRC(void *pInput,
float *pData);
int readRegister(uint16_t registerAddress,
uint16_t *pData);
#ifdef SCD30_DEBUG
void AddLog(uint8_t loglevel);
#endif // ifdef SCD30_DEBUG
};
+9
View File
@@ -0,0 +1,9 @@
name=FrogmoreScd30
version=1.0
author=Frogmore
maintainer=Frogmore
sentence=
paragraph=
category=Environment sensor
url=
architectures=esp8266,esp32
+2 -1
View File
@@ -136,7 +136,8 @@ build_flags = ${esp82xx_2_6_x.build_flags}
-Wno-deprecated-declarations
platform_packages =
platformio/framework-arduinoespressif8266 @ https://github.com/esp8266/Arduino.git
platformio/toolchain-xtensa @ ~2.100300.210717
mcspr/toolchain-xtensa @ ~5.100300.211127
; Updated ESP-IDF to the latest stable 4.0.1
; See: https://github.com/platformio/platform-espressif32/releases
+1 -1
View File
@@ -10,7 +10,7 @@ lib_ignore = ESP8266WiFi, ESP8266Ping, ESP8266WebServer, ESP8266H
[esp32_common]
extends = common, core_esp32_3_3_0
lib_deps = td-er/ESPeasySerial @ 2.0.8, adafruit/Adafruit ILI9341 @ ^1.5.6, Adafruit GFX Library, LOLIN_EPD, Adafruit BusIO, VL53L0X @ 1.3.0, SparkFun VL53L1X 4m Laser Distance Sensor @ 1.2.9, td-er/SparkFun MAX1704x Fuel Gauge Arduino Library @ ^1.0.1, ArduinoOTA, ESP32HTTPUpdateServer, Multi Channel Relay Arduino Library
lib_deps = td-er/ESPeasySerial @ 2.0.8, adafruit/Adafruit ILI9341 @ ^1.5.6, Adafruit GFX Library, LOLIN_EPD, Adafruit BusIO, VL53L0X @ 1.3.0, SparkFun VL53L1X 4m Laser Distance Sensor @ 1.2.9, td-er/SparkFun MAX1704x Fuel Gauge Arduino Library @ ^1.0.1, ArduinoOTA, ESP32HTTPUpdateServer, FrogmoreScd30, Multi Channel Relay Arduino Library
lib_ignore = ${esp32_always.lib_ignore}, ESP32_ping, IRremoteESP8266, HeatpumpIR
board_build.f_flash = 80000000L
board_build.flash_mode = dout
+1 -1
View File
@@ -52,7 +52,7 @@ extends = common
board_build.f_cpu = 80000000L
build_flags = ${debug_flags.build_flags} ${mqtt_flags.build_flags} -DHTTPCLIENT_1_1_COMPATIBLE=0
build_unflags = -DDEBUG_ESP_PORT
lib_deps = td-er/ESPeasySerial @ 2.0.8, adafruit/Adafruit ILI9341 @ ^1.5.6, Adafruit GFX Library, LOLIN_EPD, Adafruit BusIO, bblanchon/ArduinoJson @ ^6.17.2, VL53L0X @ 1.3.0, SparkFun VL53L1X 4m Laser Distance Sensor @ 1.2.9, td-er/RABurton ESP8266 Mutex @ ^1.0.2, td-er/SparkFun MAX1704x Fuel Gauge Arduino Library @ ^1.0.1, ESP8266HTTPUpdateServer, Multi Channel Relay Arduino Library
lib_deps = td-er/ESPeasySerial @ 2.0.8, adafruit/Adafruit ILI9341 @ ^1.5.6, Adafruit GFX Library, LOLIN_EPD, Adafruit BusIO, bblanchon/ArduinoJson @ ^6.17.2, VL53L0X @ 1.3.0, SparkFun VL53L1X 4m Laser Distance Sensor @ 1.2.9, td-er/RABurton ESP8266 Mutex @ ^1.0.2, td-er/SparkFun MAX1704x Fuel Gauge Arduino Library @ ^1.0.1, ESP8266HTTPUpdateServer, FrogmoreScd30, Multi Channel Relay Arduino Library
lib_ignore = ${esp82xx_defaults.lib_ignore}, IRremoteESP8266, HeatpumpIR, LittleFS(esp8266), ServoESP32, TinyWireM
board = esp12e
monitor_filters = esp8266_exception_decoder
+1
View File
@@ -377,6 +377,7 @@ static const char DATA_ESPEASY_DEFAULT_MIN_CSS[] PROGMEM = {
// #define USES_P113 // VL53L1X ToF
// #define USES_P114 // VEML6075
// #define USES_P115 // MAX1704x
// #define USES_P117 // SCD30
// #define USES_P119 // ITG3205 Gyro
// #define USES_P120 // ADXL345 Acceleration / Gravity
// #define USES_P124 // I2C MultiRelay
+1 -1
View File
@@ -38,7 +38,7 @@
# define PLUGIN_016
# define PLUGIN_ID_016 16
# define PLUGIN_NAME_016 "Communication - TSOP4838"
# define PLUGIN_NAME_016 "Communication - IR Receive (TSOP4838)"
# define PLUGIN_VALUENAME1_016 "IR"
# define P016_CMDINHIBIT PCONFIG(1)
# define P016_SETTINGS_VERSION PCONFIG(7) // 0 = V1, 2 = V2
+66 -23
View File
@@ -1,16 +1,32 @@
#include "_Plugin_Helper.h"
#ifdef USES_P043
//#######################################################################################################
//#################################### Plugin 043: Clock Output #########################################
//#######################################################################################################
#define PLUGIN_043
#define PLUGIN_ID_043 43
#define PLUGIN_NAME_043 "Output - Clock"
#define PLUGIN_VALUENAME1_043 "Output"
# define PLUGIN_VALUENAME_043 "Output"
//#define PLUGIN_VALUENAME1_043 "Output"
//#define PLUGIN_VALUENAME2_043 "Output2"
#define PLUGIN_043_MAX_SETTINGS 8
# define P043_SENSOR_TYPE_INDEX 2
# define P043_NR_OUTPUT_VALUES getValueCountFromSensorType(static_cast<Sensor_VType>(PCONFIG(P043_SENSOR_TYPE_INDEX)))
String Plugin_043_valuename(byte value_nr, bool displayString) {
String name = F("Output");
if (value_nr != 0) {
name += String(value_nr + 1);
}
if (!displayString) {
name.toLowerCase();
}
return name;
}
boolean Plugin_043(uint8_t function, struct EventStruct *event, String& string)
{
@@ -28,8 +44,9 @@ boolean Plugin_043(uint8_t function, struct EventStruct *event, String& string)
Device[deviceCount].PullUpOption = false;
Device[deviceCount].InverseLogicOption = false;
Device[deviceCount].FormulaOption = false;
Device[deviceCount].ValueCount = 1;
Device[deviceCount].ValueCount = 2;
Device[deviceCount].SendDataOption = true;
Device[deviceCount].OutputDataType = Output_Data_type_t::Simple;
break;
}
@@ -40,10 +57,35 @@ boolean Plugin_043(uint8_t function, struct EventStruct *event, String& string)
}
case PLUGIN_GET_DEVICEVALUENAMES:
{
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_043));
break;
{
for (byte i = 0; i < VARS_PER_TASK; ++i) {
if (i < P043_NR_OUTPUT_VALUES) {
safe_strncpy(
ExtraTaskSettings.TaskDeviceValueNames[i],
Plugin_043_valuename(i, false),
sizeof(ExtraTaskSettings.TaskDeviceValueNames[i]));
ExtraTaskSettings.TaskDeviceValueDecimals[i] = 2;
} else {
ZERO_FILL(ExtraTaskSettings.TaskDeviceValueNames[i]);
}
}
break;
}
case PLUGIN_GET_DEVICEVALUECOUNT:
{
event->Par1 = P043_NR_OUTPUT_VALUES;
success = true;
break;
}
case PLUGIN_GET_DEVICEVTYPE:
{
event->sensorType = static_cast<Sensor_VType>(PCONFIG(P043_SENSOR_TYPE_INDEX));
event->idx = P043_SENSOR_TYPE_INDEX;
success = true;
break;
}
case PLUGIN_GET_DEVICEGPIONAMES:
{
@@ -57,21 +99,16 @@ boolean Plugin_043(uint8_t function, struct EventStruct *event, String& string)
options[0] = F("");
options[1] = F("Off");
options[2] = F("On");
for (uint8_t x = 0; x < PLUGIN_043_MAX_SETTINGS; x++)
{
addFormTextBox(String(F("Day,Time ")) + (x + 1), String(F("p043_clock")) + (x), timeLong2String(ExtraTaskSettings.TaskDevicePluginConfigLong[x]), 32);
// addHtml(F("<TR><TD>Day,Time "));
// addHtml(x+1);
// addHtml(F(":<TD><input type='text' name='plugin_043_clock"));
// addHtml(x);
// addHtml(F("' value='"));
// addHtml(timeLong2String(ExtraTaskSettings.TaskDevicePluginConfigLong[x]));
// addHtml("'>");
addHtml(' ');
uint8_t choice = ExtraTaskSettings.TaskDevicePluginConfig[x];
addSelector(String(F("p043_state")) + (x), 3, options, NULL, NULL, choice);
if (CONFIG_PIN1 >= 0) {
addHtml(' ');
const uint8_t choice = ExtraTaskSettings.TaskDevicePluginConfig[x];
addSelector(String(F("p043_state")) + (x), 3, options, NULL, NULL, choice);
}
else addFormNumericBox(String(F("Value")) + (x + 1), String(F("p043_state")) + (x), ExtraTaskSettings.TaskDevicePluginConfig[x]);
}
success = true;
break;
@@ -112,12 +149,18 @@ boolean Plugin_043(uint8_t function, struct EventStruct *event, String& string)
if (matchClockEvent(clockEvent,clockSet))
{
uint8_t state = ExtraTaskSettings.TaskDevicePluginConfig[x];
if (state != 0)
if (state != 0)
{
state--;
pinMode(CONFIG_PIN1, OUTPUT);
digitalWrite(CONFIG_PIN1, state);
UserVar[event->BaseVarIndex] = state;
if (CONFIG_PIN1 >= 0) { // if GPIO is specified, use the old behavior
state--;
pinMode(CONFIG_PIN1, OUTPUT);
digitalWrite(CONFIG_PIN1, state);
UserVar[event->BaseVarIndex] = state;
}
else {
UserVar[event->BaseVarIndex] = x+1;
UserVar[event->BaseVarIndex+1] = state;
}
if (loglevelActiveFor(LOG_LEVEL_INFO)) {
String log = F("TCLK : State ");
log += state;
+193 -282
View File
@@ -1,5 +1,8 @@
#include "_Plugin_Helper.h"
#ifdef USES_P095
#include "src/PluginStructs/P095_data_struct.h"
//#######################################################################################################
//#################################### Plugin 095: ILI9341 TFT 2.4inches display #################################
//#######################################################################################################
@@ -11,10 +14,6 @@
#define PLUGIN_095_MAX_DISPLAY 1
#if !defined(LIMIT_BUILD_SIZE) && !defined(PLUGIN_095_FONT_INCLUDED)
#define PLUGIN_095_FONT_INCLUDED // enable to use fonts in this plugin
#endif
/**
* Changelog:
* 2020-08-29 tonhuisman: Removed TS (Touchscreen) related stuff, XPT2046 will be a separate plugin
@@ -116,18 +115,6 @@ Examples:
http://<espeasy_ip>/control?cmd=tftcmd,clear,green
*/
//plugin dependency
#include <Adafruit_ILI9341.h>
#ifdef PLUGIN_095_FONT_INCLUDED
#include "src/Static/Fonts/Seven_Segment24pt7b.h"
#include "src/Static/Fonts/Seven_Segment18pt7b.h"
#include "Fonts/FreeSans9pt7b.h"
#endif
//declare functions for using default value parameters
void Plugin_095_printText(const char *string, int X, int Y, unsigned int textSize = 1, unsigned short color = ILI9341_WHITE, unsigned short bkcolor = ILI9341_BLACK);
//Define the default values for both ESP32/lolin32 and D1 Mini
#ifdef ESP32
@@ -157,8 +144,6 @@ struct Plugin_095_TFT_SettingStruct
uint8_t rotation;
} TFT_Settings;
//The display pointer
Adafruit_ILI9341 *tft = NULL;
boolean Plugin_095(uint8_t function, struct EventStruct *event, String& string)
{
@@ -262,12 +247,20 @@ boolean Plugin_095(uint8_t function, struct EventStruct *event, String& string)
TFT_Settings.address_tft_rst = PIN(2);
TFT_Settings.rotation = PCONFIG(1);
tft = new Adafruit_ILI9341(TFT_Settings.address_tft_cs, TFT_Settings.address_tft_dc, TFT_Settings.address_tft_rst);
tft->begin();
tft->setRotation(TFT_Settings.rotation);
tft->fillScreen(ILI9341_WHITE);
Plugin_095_printText("ESPEasy", 1, 1);
success = true;
initPluginTaskData(event->TaskIndex,
new (std::nothrow) P095_data_struct(
TFT_Settings.address_tft_cs,
TFT_Settings.address_tft_dc,
TFT_Settings.address_tft_rst));
P095_data_struct *P095_data =
static_cast<P095_data_struct *>(getPluginTaskData(event->TaskIndex));
if (nullptr != P095_data) {
P095_data->tft.setRotation(TFT_Settings.rotation);
P095_data->tft.fillScreen(ILI9341_WHITE);
P095_data->printText(F("ESPEasy"), 1, 1);
success = true;
}
break;
}
@@ -282,181 +275,194 @@ boolean Plugin_095(uint8_t function, struct EventStruct *event, String& string)
int argIndex = arguments.indexOf(',');
if (argIndex)
{
command = arguments.substring(0, argIndex);
arguments = arguments.substring(argIndex+1);
argIndex = arguments.indexOf(',');
subcommand = arguments.substring(0, argIndex);
success = true;
P095_data_struct *P095_data =
static_cast<P095_data_struct *>(getPluginTaskData(event->TaskIndex));
tmpString += "<br/> command= " + command;
tmpString += "<br/> arguments= " + arguments;
tmpString += "<br/> argIndex= " + String(argIndex);
tmpString += "<br/> subcommand= " + subcommand;
if (nullptr != P095_data) {
command = arguments.substring(0, argIndex);
arguments = arguments.substring(argIndex+1);
argIndex = arguments.indexOf(',');
subcommand = arguments.substring(0, argIndex);
success = true;
tmpString += F("<br/> command= ");
tmpString += command;
tmpString += F("<br/> arguments= ");
tmpString += arguments;
tmpString += F("<br/> argIndex= ");
tmpString += String(argIndex);
tmpString += F("<br/> subcommand= ");
tmpString += subcommand;
if (command.equalsIgnoreCase(F("TFTCMD")))
{
if(subcommand.equalsIgnoreCase(F("ON")))
if (command.equalsIgnoreCase(F("TFTCMD")))
{
tft->sendCommand(ILI9341_DISPON);
}
else if(subcommand.equalsIgnoreCase(F("OFF")))
{
tft->sendCommand(ILI9341_DISPOFF);
}
else if(subcommand.equalsIgnoreCase(F("CLEAR")))
{
arguments = arguments.substring(argIndex + 1);
tft->fillScreen(Plugin_095_ParseColor(arguments));
}
else if(subcommand.equalsIgnoreCase(F("INV")))
{
arguments = arguments.substring(argIndex + 1);
tft->invertDisplay(arguments.toInt() == 1);
}
else if(subcommand.equalsIgnoreCase(F("ROT")))
{
///control?cmd=tftcmd,rot,0
//not working to verify
arguments = arguments.substring(argIndex + 1);
tft->setRotation(arguments.toInt() % 4);
}
else
{
success = false;
}
}
else if (command.equalsIgnoreCase(F("TFT")))
{
tmpString += "<br/> TFT ";
arguments = arguments.substring(argIndex + 1);
String sParams[8];
int argCount = Plugin_095_StringSplit(arguments, ',', sParams, 8);
for(int a=0; a < argCount && a < 8; a++)
{
tmpString += "<br/> ARGS[" + String(a) + "]=" + sParams[a];
}
if(subcommand.equalsIgnoreCase(F("txt")))
{
tft->println(arguments); //write all pending cars
}
else if(subcommand.equalsIgnoreCase(F("txp")) && argCount == 2)
{
tft->setCursor(sParams[0].toInt(), sParams[1].toInt());
}
else if(subcommand.equalsIgnoreCase(F("txc")) && (argCount == 1 || argCount == 2) )
{
if(argCount == 1)
tft->setTextColor(Plugin_095_ParseColor(sParams[0]));
else //argCount=2
tft->setTextColor(Plugin_095_ParseColor(sParams[0]), Plugin_095_ParseColor(sParams[1]));
}
else if(subcommand.equalsIgnoreCase(F("txs")) && argCount == 1)
{
tft->setTextSize(sParams[0].toInt());
}
#ifdef PLUGIN_095_FONT_INCLUDED
else if(subcommand.equalsIgnoreCase(F("font")) && argCount == 1) {
if (sParams[0].equalsIgnoreCase(F("SEVENSEG24"))) {
tft->setFont(&Seven_Segment24pt7b);
} else if (sParams[0].equalsIgnoreCase(F("SEVENSEG18"))) {
tft->setFont(&Seven_Segment18pt7b);
} else if (sParams[0].equalsIgnoreCase(F("FREESANS"))) {
tft->setFont(&FreeSans9pt7b);
} else if (sParams[0].equalsIgnoreCase(F("DEFAULT"))) {
tft->setFont();
} else {
success = false;
}
}
#endif
else if(subcommand.equalsIgnoreCase(F("txtfull")) && argCount >= 3 && argCount <= 6)
{
switch (argCount)
if(subcommand.equalsIgnoreCase(F("ON")))
{
P095_data->tft.sendCommand(ILI9341_DISPON);
}
else if(subcommand.equalsIgnoreCase(F("OFF")))
{
P095_data->tft.sendCommand(ILI9341_DISPOFF);
}
else if(subcommand.equalsIgnoreCase(F("CLEAR")))
{
arguments = arguments.substring(argIndex + 1);
P095_data->tft.fillScreen(P095_data->ParseColor(arguments));
}
else if(subcommand.equalsIgnoreCase(F("INV")))
{
arguments = arguments.substring(argIndex + 1);
P095_data->tft.invertDisplay(arguments.toInt() == 1);
}
else if(subcommand.equalsIgnoreCase(F("ROT")))
{
///control?cmd=tftcmd,rot,0
//not working to verify
arguments = arguments.substring(argIndex + 1);
P095_data->tft.setRotation(arguments.toInt() % 4);
}
else
{
case 3: //single text
Plugin_095_printText(sParams[2].c_str(), sParams[0].toInt() - 1,sParams[1].toInt() - 1);
break;
case 4: //text + size
Plugin_095_printText(sParams[3].c_str(), sParams[0].toInt() - 1, sParams[1].toInt() - 1, sParams[2].toInt());
break;
case 5: //text + size + color
Plugin_095_printText(sParams[4].c_str(), sParams[0].toInt() - 1, sParams[1].toInt() - 1, sParams[2].toInt(), Plugin_095_ParseColor(sParams[3]));
break;
case 6: //text + size + color
Plugin_095_printText(sParams[5].c_str(), sParams[0].toInt() - 1, sParams[1].toInt() - 1, sParams[2].toInt(), Plugin_095_ParseColor(sParams[3]), Plugin_095_ParseColor(sParams[4]));
break;
default:
success = false;
break;
}
}
else if(subcommand.equalsIgnoreCase(F("l")) && argCount == 5)
else if (command.equalsIgnoreCase(F("TFT")))
{
tft->drawLine(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), sParams[3].toInt(), Plugin_095_ParseColor(sParams[4]));
tmpString += F("<br/> TFT ");
arguments = arguments.substring(argIndex + 1);
String sParams[8];
int argCount = P095_data->StringSplit(arguments, ',', sParams, 8);
for(int a=0; a < argCount && a < 8; a++)
{
tmpString += F("<br/> ARGS[");
tmpString += a;
tmpString += F("]=");
tmpString += sParams[a];
}
if(subcommand.equalsIgnoreCase(F("txt")))
{
P095_data->tft.println(arguments); //write all pending cars
}
else if(subcommand.equalsIgnoreCase(F("txp")) && argCount == 2)
{
P095_data->tft.setCursor(sParams[0].toInt(), sParams[1].toInt());
}
else if(subcommand.equalsIgnoreCase(F("txc")) && (argCount == 1 || argCount == 2) )
{
if(argCount == 1)
P095_data->tft.setTextColor(P095_data->ParseColor(sParams[0]));
else //argCount=2
P095_data->tft.setTextColor(P095_data->ParseColor(sParams[0]), P095_data->ParseColor(sParams[1]));
}
else if(subcommand.equalsIgnoreCase(F("txs")) && argCount == 1)
{
P095_data->tft.setTextSize(sParams[0].toInt());
}
#ifdef PLUGIN_095_FONT_INCLUDED
else if(subcommand.equalsIgnoreCase(F("font")) && argCount == 1) {
if (sParams[0].equalsIgnoreCase(F("SEVENSEG24"))) {
P095_data->tft.setFont(&Seven_Segment24pt7b);
} else if (sParams[0].equalsIgnoreCase(F("SEVENSEG18"))) {
P095_data->tft.setFont(&Seven_Segment18pt7b);
} else if (sParams[0].equalsIgnoreCase(F("FREESANS"))) {
P095_data->tft.setFont(&FreeSans9pt7b);
} else if (sParams[0].equalsIgnoreCase(F("DEFAULT"))) {
P095_data->tft.setFont();
} else {
success = false;
}
}
#endif
else if(subcommand.equalsIgnoreCase(F("txtfull")) && argCount >= 3 && argCount <= 6)
{
switch (argCount)
{
case 3: //single text
P095_data->printText(sParams[2], sParams[0].toInt() - 1,sParams[1].toInt() - 1);
break;
case 4: //text + size
P095_data->printText(sParams[3], sParams[0].toInt() - 1, sParams[1].toInt() - 1, sParams[2].toInt());
break;
case 5: //text + size + color
P095_data->printText(sParams[4], sParams[0].toInt() - 1, sParams[1].toInt() - 1, sParams[2].toInt(), P095_data->ParseColor(sParams[3]));
break;
case 6: //text + size + color
P095_data->printText(sParams[5], sParams[0].toInt() - 1, sParams[1].toInt() - 1, sParams[2].toInt(), P095_data->ParseColor(sParams[3]), P095_data->ParseColor(sParams[4]));
break;
default:
success = false;
break;
}
}
else if(subcommand.equalsIgnoreCase(F("l")) && argCount == 5)
{
P095_data->tft.drawLine(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), sParams[3].toInt(), P095_data->ParseColor(sParams[4]));
}
else if(subcommand.equalsIgnoreCase(F("lh")) && argCount == 3)
{
P095_data->tft.drawFastHLine(0, sParams[0].toInt(), sParams[1].toInt(), P095_data->ParseColor(sParams[2]));
}
else if(subcommand.equalsIgnoreCase(F("lv")) && argCount == 3)
{
P095_data->tft.drawFastVLine(sParams[0].toInt(), 0, sParams[1].toInt(), P095_data->ParseColor(sParams[2]));
}
else if(subcommand.equalsIgnoreCase(F("r")) && argCount == 5)
{
P095_data->tft.drawRect(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), sParams[3].toInt(), P095_data->ParseColor(sParams[4]));
}
else if(subcommand.equalsIgnoreCase(F("rf")) && argCount == 6)
{
P095_data->tft.fillRect(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), sParams[3].toInt(), P095_data->ParseColor(sParams[5]));
P095_data->tft.drawRect(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), sParams[3].toInt(), P095_data->ParseColor(sParams[4]));
}
else if(subcommand.equalsIgnoreCase(F("c")) && argCount == 4)
{
P095_data->tft.drawCircle(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), P095_data->ParseColor(sParams[3]));
}
else if(subcommand.equalsIgnoreCase(F("cf")) && argCount == 5)
{
P095_data->tft.fillCircle(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), P095_data->ParseColor(sParams[4]));
P095_data->tft.drawCircle(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), P095_data->ParseColor(sParams[3]));
}
else if(subcommand.equalsIgnoreCase(F("t")) && argCount == 7)
{
P095_data->tft.drawTriangle(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), sParams[3].toInt(), sParams[4].toInt(), sParams[5].toInt(), P095_data->ParseColor(sParams[6]));
}
else if(subcommand.equalsIgnoreCase(F("tf")) && argCount == 8)
{
P095_data->tft.fillTriangle(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), sParams[3].toInt(), sParams[4].toInt(), sParams[5].toInt(), P095_data->ParseColor(sParams[7]));
P095_data->tft.drawTriangle(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), sParams[3].toInt(), sParams[4].toInt(), sParams[5].toInt(), P095_data->ParseColor(sParams[6]));
}
else if(subcommand.equalsIgnoreCase(F("rr")) && argCount == 6)
{
P095_data->tft.drawRoundRect(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), sParams[3].toInt(), sParams[4].toInt(), P095_data->ParseColor(sParams[5]));
}
else if(subcommand.equalsIgnoreCase(F("rrf")) && argCount == 7)
{
P095_data->tft.fillRoundRect(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), sParams[3].toInt(), sParams[4].toInt(), P095_data->ParseColor(sParams[6]));
P095_data->tft.drawRoundRect(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), sParams[3].toInt(), sParams[4].toInt(), P095_data->ParseColor(sParams[5]));
}
else if(subcommand.equalsIgnoreCase(F("px")) && argCount == 3)
{
P095_data->tft.drawPixel(sParams[0].toInt(), sParams[1].toInt(), P095_data->ParseColor(sParams[2]));
}
else
{
success = false;
}
}
else if(subcommand.equalsIgnoreCase(F("lh")) && argCount == 3)
{
tft->drawFastHLine(0, sParams[0].toInt(), sParams[1].toInt(), Plugin_095_ParseColor(sParams[2]));
}
else if(subcommand.equalsIgnoreCase(F("lv")) && argCount == 3)
{
tft->drawFastVLine(sParams[0].toInt(), 0, sParams[1].toInt(), Plugin_095_ParseColor(sParams[2]));
}
else if(subcommand.equalsIgnoreCase(F("r")) && argCount == 5)
{
tft->drawRect(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), sParams[3].toInt(), Plugin_095_ParseColor(sParams[4]));
}
else if(subcommand.equalsIgnoreCase(F("rf")) && argCount == 6)
{
tft->fillRect(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), sParams[3].toInt(), Plugin_095_ParseColor(sParams[5]));
tft->drawRect(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), sParams[3].toInt(), Plugin_095_ParseColor(sParams[4]));
}
else if(subcommand.equalsIgnoreCase(F("c")) && argCount == 4)
{
tft->drawCircle(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), Plugin_095_ParseColor(sParams[3]));
}
else if(subcommand.equalsIgnoreCase(F("cf")) && argCount == 5)
{
tft->fillCircle(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), Plugin_095_ParseColor(sParams[4]));
tft->drawCircle(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), Plugin_095_ParseColor(sParams[3]));
}
else if(subcommand.equalsIgnoreCase(F("t")) && argCount == 7)
{
tft->drawTriangle(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), sParams[3].toInt(), sParams[4].toInt(), sParams[5].toInt(), Plugin_095_ParseColor(sParams[6]));
}
else if(subcommand.equalsIgnoreCase(F("tf")) && argCount == 8)
{
tft->fillTriangle(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), sParams[3].toInt(), sParams[4].toInt(), sParams[5].toInt(), Plugin_095_ParseColor(sParams[7]));
tft->drawTriangle(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), sParams[3].toInt(), sParams[4].toInt(), sParams[5].toInt(), Plugin_095_ParseColor(sParams[6]));
}
else if(subcommand.equalsIgnoreCase(F("rr")) && argCount == 6)
{
tft->drawRoundRect(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), sParams[3].toInt(), sParams[4].toInt(), Plugin_095_ParseColor(sParams[5]));
}
else if(subcommand.equalsIgnoreCase(F("rrf")) && argCount == 7)
{
tft->fillRoundRect(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), sParams[3].toInt(), sParams[4].toInt(), Plugin_095_ParseColor(sParams[6]));
tft->drawRoundRect(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), sParams[3].toInt(), sParams[4].toInt(), Plugin_095_ParseColor(sParams[5]));
}
else if(subcommand.equalsIgnoreCase(F("px")) && argCount == 3)
{
tft->drawPixel(sParams[0].toInt(), sParams[1].toInt(), Plugin_095_ParseColor(sParams[2]));
}
else
{
else {
success = false;
}
}
}
else {
success = false;
}
}
else
{
@@ -491,99 +497,4 @@ boolean Plugin_095(uint8_t function, struct EventStruct *event, String& string)
return success;
}
//Print some text
//param [in] string : The text to display
//param [in] X : The left position (X)
//param [in] Y : The top position (Y)
//param [in] textSize : The text size (default 1)
//param [in] color : The fore color (default ILI9341_WHITE)
//param [in] bkcolor : The background color (default ILI9341_BLACK)
void Plugin_095_printText(const char *string, int X, int Y, unsigned int textSize, unsigned short color, unsigned short bkcolor)
{
tft->setCursor(X, Y);
tft->setTextColor(color, bkcolor);
tft->setTextSize(textSize);
tft->println(string);
}
//Parse color string to ILI9341 color
//param [in] s : The color string (white, red, ...)
//return : color (default ILI9341_WHITE)
unsigned short Plugin_095_ParseColor(String & s)
{
if (s.equalsIgnoreCase(F("BLACK")))
return ILI9341_BLACK;
if (s.equalsIgnoreCase(F("NAVY")))
return ILI9341_NAVY;
if (s.equalsIgnoreCase(F("DARKGREEN")))
return ILI9341_DARKGREEN;
if (s.equalsIgnoreCase(F("DARKCYAN")))
return ILI9341_DARKCYAN;
if (s.equalsIgnoreCase(F("MAROON")))
return ILI9341_MAROON;
if (s.equalsIgnoreCase(F("PURPLE")))
return ILI9341_PURPLE;
if (s.equalsIgnoreCase(F("OLIVE")))
return ILI9341_OLIVE;
if (s.equalsIgnoreCase(F("LIGHTGREY")))
return ILI9341_LIGHTGREY;
if (s.equalsIgnoreCase(F("DARKGREY")))
return ILI9341_DARKGREY;
if (s.equalsIgnoreCase(F("BLUE")))
return ILI9341_BLUE;
if (s.equalsIgnoreCase(F("GREEN")))
return ILI9341_GREEN;
if (s.equalsIgnoreCase(F("CYAN")))
return ILI9341_CYAN;
if (s.equalsIgnoreCase(F("RED")))
return ILI9341_RED;
if (s.equalsIgnoreCase(F("MAGENTA")))
return ILI9341_MAGENTA;
if (s.equalsIgnoreCase(F("YELLOW")))
return ILI9341_YELLOW;
if (s.equalsIgnoreCase(F("WHITE")))
return ILI9341_WHITE;
if (s.equalsIgnoreCase(F("ORANGE")))
return ILI9341_ORANGE;
if (s.equalsIgnoreCase(F("GREENYELLOW")))
return ILI9341_GREENYELLOW;
if (s.equalsIgnoreCase(F("PINK")))
return ILI9341_PINK;
if(s.length() == 7 && s[0] == '#')
{
// convrt to long value in base16, then split up into r, g, b values
long long number = strtoll( &s[1], NULL, 16);
//long long r = number >> 16;
//long long g = number >> 8 & 0xFF;
//long long b = number & 0xFF;
//convert to color565 (used by adafruit lib)
return tft->color565(number >> 16, number >> 8 & 0xFF, number & 0xFF);
}
return ILI9341_WHITE; //fallback value
}
//Split a string by delimiter
//param [in] s : The input string
//param [in] c : The delimiter
//param [out] op : The resulting string array
//param [in] limit : The maximum strings to find
//return : The string count
int Plugin_095_StringSplit(String &s, char c, String op[], int limit)
{
int count = 0;
char * pch;
String d = String(c);
pch = strtok ((char*)(s.c_str()),d.c_str());
while (pch != NULL && count < limit)
{
op[count] = String(pch);
count++;
pch = strtok (NULL, ",");
}
return count;
}
#endif // USES_P095
+230 -317
View File
@@ -1,5 +1,8 @@
#include "_Plugin_Helper.h"
#ifdef USES_P096
#include "src/PluginStructs/P096_data_struct.h"
//#######################################################################################################
//#################################### Plugin 096: eInk display #################################
//#######################################################################################################
@@ -97,12 +100,6 @@ Examples:
*/
//plugin dependency
#include <LOLIN_EPD.h>
#include <Adafruit_GFX.h>
//declare functions for using default value parameters
void Plugin_096_printText(const char *string, int X, int Y, unsigned int textSize = 1, unsigned short color = EPD_WHITE, unsigned short bkcolor = EPD_BLACK);
//Define the default values for both ESP32/lolin32 and D1 Mini
#ifdef ESP32
@@ -138,9 +135,6 @@ struct Plugin_096_EPD_SettingStruct
int height;
} EPD_Settings;
//The display pointer
LOLIN_IL3897 *eInkScreen = NULL;
uint8_t plugin_096_sequence_in_progress = false;
boolean Plugin_096(uint8_t function, struct EventStruct *event, String& string)
{
@@ -285,20 +279,29 @@ boolean Plugin_096(uint8_t function, struct EventStruct *event, String& string)
EPD_Settings.width = PCONFIG(2);
EPD_Settings.height = PCONFIG(3);
eInkScreen = new LOLIN_IL3897(EPD_Settings.width, EPD_Settings.height, EPD_Settings.address_epd_dc, EPD_Settings.address_epd_rst, EPD_Settings.address_epd_cs, EPD_Settings.address_epd_busy); //hardware SPI
plugin_096_sequence_in_progress = false;
eInkScreen->begin();
eInkScreen->clearBuffer();
eInkScreen->setTextColor(EPD_BLACK);
eInkScreen->setTextSize(3);
eInkScreen->println("ESP Easy");
eInkScreen->setTextSize(2);
eInkScreen->println("eInk shield");
eInkScreen->display();
delay(100);
success = true;
initPluginTaskData(event->TaskIndex,
new (std::nothrow) P096_data_struct(
EPD_Settings.width,
EPD_Settings.height,
EPD_Settings.address_epd_dc,
EPD_Settings.address_epd_rst,
EPD_Settings.address_epd_cs,
EPD_Settings.address_epd_busy)); // hardware SPI
P096_data_struct *P096_data =
static_cast<P096_data_struct *>(getPluginTaskData(event->TaskIndex));
if (nullptr != P096_data) {
P096_data->eInkScreen.setTextColor(EPD_BLACK);
P096_data->eInkScreen.setTextSize(3);
P096_data->eInkScreen.println(F("ESP Easy"));
P096_data->eInkScreen.setTextSize(2);
P096_data->eInkScreen.println(F("eInk shield"));
P096_data->eInkScreen.display();
delay(100);
success = true;
}
break;
}
@@ -312,220 +315,232 @@ boolean Plugin_096(uint8_t function, struct EventStruct *event, String& string)
String command;
String subcommand;
int argIndex = arguments.indexOf(',');
if (argIndex)
{
command = arguments.substring(0, argIndex);
arguments = arguments.substring(argIndex+1);
argIndex = arguments.indexOf(',');
subcommand = arguments.substring(0, argIndex);
success = true;
P096_data_struct *P096_data =
static_cast<P096_data_struct *>(getPluginTaskData(event->TaskIndex));
#ifndef BUILD_NO_DEBUG
tmpString += "<br/> command= " + command;
tmpString += "<br/> arguments= " + arguments;
tmpString += "<br/> argIndex= " + String(argIndex);
tmpString += "<br/> subcommand= " + subcommand;
#endif
if (nullptr != P096_data) {
if (command.equalsIgnoreCase(F("EPDCMD")))
int argIndex = arguments.indexOf(',');
if (argIndex)
{
if(subcommand.equalsIgnoreCase(F("CLEAR")))
{
arguments = arguments.substring(argIndex + 1);
eInkScreen->clearBuffer();
eInkScreen->fillScreen(Plugin_096_ParseColor(arguments));
eInkScreen->display();
eInkScreen->clearBuffer();
}
else if(subcommand.equalsIgnoreCase(F("DEEPSLEEP")))
{
eInkScreen->deepSleep();
}
else if(subcommand.equalsIgnoreCase(F("SEQ_START")))
{
eInkScreen->clearBuffer();
eInkScreen->fillScreen(Plugin_096_ParseColor(arguments));
plugin_096_sequence_in_progress = true;
}
else if(subcommand.equalsIgnoreCase(F("SEQ_END")))
{
#ifndef BUILD_NO_DEBUG
TimingStats s;
const unsigned statisticsTimerStart(micros());
#endif
eInkScreen->display();
#ifndef BUILD_NO_DEBUG
s.add(usecPassedSince(statisticsTimerStart));
tmpString += "<br/> Display timings = " + String(s.getAvg());
#endif
eInkScreen->clearBuffer();
plugin_096_sequence_in_progress = false;
}
else if(subcommand.equalsIgnoreCase(F("INV")))
{
arguments = arguments.substring(argIndex + 1);
eInkScreen->invertDisplay(arguments.toInt() == 1);
eInkScreen->display();
}
else if(subcommand.equalsIgnoreCase(F("ROT")))
{
arguments = arguments.substring(argIndex + 1);
eInkScreen->setRotation(arguments.toInt() % 4);
eInkScreen->display();
}
else
{
success = false;
}
}
else if (command.equalsIgnoreCase(F("EPD")))
{
#ifndef BUILD_NO_DEBUG
tmpString += "<br/> EPD ";
#endif
arguments = arguments.substring(argIndex + 1);
String sParams[8];
int argCount = Plugin_096_StringSplit(arguments, ',', sParams, 8);
command = arguments.substring(0, argIndex);
arguments = arguments.substring(argIndex+1);
argIndex = arguments.indexOf(',');
subcommand = arguments.substring(0, argIndex);
success = true;
#ifndef BUILD_NO_DEBUG
for(int a=0; a < argCount && a < 8; a++)
{
tmpString += "<br/> ARGS[" + String(a) + "]=" + sParams[a];
}
#endif
#ifndef BUILD_NO_DEBUG
tmpString += F("<br/> command= ");
tmpString += command;
tmpString += F("<br/> arguments= ");
tmpString += arguments;
tmpString += F("<br/> argIndex= ");
tmpString += String(argIndex);
tmpString += F("<br/> subcommand= ");
tmpString += subcommand;
#endif
if(plugin_096_sequence_in_progress == false)
if (command.equalsIgnoreCase(F("EPDCMD")))
{
eInkScreen->clearBuffer();
eInkScreen->fillScreen(EPD_WHITE);
}
if(subcommand.equalsIgnoreCase(F("txt")))
{
Plugin_096_FixText(arguments);
eInkScreen->println(arguments); //write all pending cars
}
else if(subcommand.equalsIgnoreCase(F("txz")))
{
eInkScreen->setCursor(sParams[0].toInt(), sParams[1].toInt());
Plugin_096_FixText(sParams[2]);
eInkScreen->println(sParams[2]); //write all pending cars
}
else if(subcommand.equalsIgnoreCase(F("txp")) && argCount == 2)
{
eInkScreen->setCursor(sParams[0].toInt(), sParams[1].toInt());
}
else if(subcommand.equalsIgnoreCase(F("txc")) && (argCount == 1 || argCount == 2) )
{
if(argCount == 1)
eInkScreen->setTextColor(Plugin_096_ParseColor(sParams[0]));
else //argCount=2
eInkScreen->setTextColor(Plugin_096_ParseColor(sParams[0]), Plugin_096_ParseColor(sParams[1]));
}
else if(subcommand.equalsIgnoreCase(F("txs")) && argCount == 1)
{
eInkScreen->setTextSize(sParams[0].toInt());
}
else if(subcommand.equalsIgnoreCase(F("txtfull")) && argCount >= 3 && argCount <= 6)
{
switch (argCount)
if(subcommand.equalsIgnoreCase(F("CLEAR")))
{
arguments = arguments.substring(argIndex + 1);
P096_data->eInkScreen.clearBuffer();
P096_data->eInkScreen.fillScreen(P096_data->ParseColor(arguments));
P096_data->eInkScreen.display();
P096_data->eInkScreen.clearBuffer();
}
else if(subcommand.equalsIgnoreCase(F("DEEPSLEEP")))
{
P096_data->eInkScreen.deepSleep();
}
else if(subcommand.equalsIgnoreCase(F("SEQ_START")))
{
P096_data->eInkScreen.clearBuffer();
P096_data->eInkScreen.fillScreen(P096_data->ParseColor(arguments));
P096_data->plugin_096_sequence_in_progress = true;
}
else if(subcommand.equalsIgnoreCase(F("SEQ_END")))
{
#ifndef BUILD_NO_DEBUG
TimingStats s;
const unsigned statisticsTimerStart(micros());
#endif
P096_data->eInkScreen.display();
#ifndef BUILD_NO_DEBUG
s.add(usecPassedSince(statisticsTimerStart));
tmpString += "<br/> Display timings = " + String(s.getAvg());
#endif
P096_data->eInkScreen.clearBuffer();
P096_data->plugin_096_sequence_in_progress = false;
}
else if(subcommand.equalsIgnoreCase(F("INV")))
{
arguments = arguments.substring(argIndex + 1);
P096_data->eInkScreen.invertDisplay(arguments.toInt() == 1);
P096_data->eInkScreen.display();
}
else if(subcommand.equalsIgnoreCase(F("ROT")))
{
arguments = arguments.substring(argIndex + 1);
P096_data->eInkScreen.setRotation(arguments.toInt() % 4);
P096_data->eInkScreen.display();
}
else
{
case 3: //single text
Plugin_096_printText(sParams[2].c_str(), sParams[0].toInt() - 1,sParams[1].toInt() - 1);
break;
case 4: //text + size
Plugin_096_printText(sParams[3].c_str(), sParams[0].toInt() - 1, sParams[1].toInt() - 1, sParams[2].toInt());
break;
case 5: //text + size + color
Plugin_096_printText(sParams[4].c_str(), sParams[0].toInt() - 1, sParams[1].toInt() - 1, sParams[2].toInt(), Plugin_096_ParseColor(sParams[3]));
break;
case 6: //text + size + color
Plugin_096_printText(sParams[5].c_str(), sParams[0].toInt() - 1, sParams[1].toInt() - 1, sParams[2].toInt(), Plugin_096_ParseColor(sParams[3]), Plugin_096_ParseColor(sParams[4]));
break;
default:
success = false;
break;
}
}
}
else if(subcommand.equalsIgnoreCase(F("l")) && argCount == 5)
else if (command.equalsIgnoreCase(F("EPD")))
{
eInkScreen->drawLine(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), sParams[3].toInt(), Plugin_096_ParseColor(sParams[4]));
}
else if(subcommand.equalsIgnoreCase(F("lh")) && argCount == 3)
{
eInkScreen->drawFastHLine(0, sParams[0].toInt(), sParams[1].toInt(), Plugin_096_ParseColor(sParams[2]));
}
else if(subcommand.equalsIgnoreCase(F("lv")) && argCount == 3)
{
eInkScreen->drawFastVLine(sParams[0].toInt(), 0, sParams[1].toInt(), Plugin_096_ParseColor(sParams[2]));
}
else if(subcommand.equalsIgnoreCase(F("r")) && argCount == 5)
{
eInkScreen->drawRect(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), sParams[3].toInt(), Plugin_096_ParseColor(sParams[4]));
}
else if(subcommand.equalsIgnoreCase(F("rf")) && argCount == 6)
{
eInkScreen->fillRect(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), sParams[3].toInt(), Plugin_096_ParseColor(sParams[5]));
eInkScreen->drawRect(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), sParams[3].toInt(), Plugin_096_ParseColor(sParams[4]));
}
else if(subcommand.equalsIgnoreCase(F("c")) && argCount == 4)
{
eInkScreen->drawCircle(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), Plugin_096_ParseColor(sParams[3]));
}
else if(subcommand.equalsIgnoreCase(F("cf")) && argCount == 5)
{
eInkScreen->fillCircle(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), Plugin_096_ParseColor(sParams[4]));
eInkScreen->drawCircle(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), Plugin_096_ParseColor(sParams[3]));
#ifndef BUILD_NO_DEBUG
tmpString += F("<br/> EPD ");
#endif
arguments = arguments.substring(argIndex + 1);
String sParams[8];
int argCount = P096_data->StringSplit(arguments, ',', sParams, 8);
#ifndef BUILD_NO_DEBUG
for(int a=0; a < argCount && a < 8; a++)
{
tmpString += F("<br/> ARGS[");
tmpString += a;
tmpString += F("]=");
tmpString += sParams[a];
}
#endif
if(P096_data->plugin_096_sequence_in_progress == false)
{
P096_data->eInkScreen.clearBuffer();
P096_data->eInkScreen.fillScreen(EPD_WHITE);
}
if(subcommand.equalsIgnoreCase(F("txt")))
{
P096_data->FixText(arguments);
P096_data->eInkScreen.println(arguments); //write all pending cars
}
else if(subcommand.equalsIgnoreCase(F("txz")))
{
P096_data->eInkScreen.setCursor(sParams[0].toInt(), sParams[1].toInt());
P096_data->FixText(sParams[2]);
P096_data->eInkScreen.println(sParams[2]); //write all pending cars
}
else if(subcommand.equalsIgnoreCase(F("txp")) && argCount == 2)
{
P096_data->eInkScreen.setCursor(sParams[0].toInt(), sParams[1].toInt());
}
else if(subcommand.equalsIgnoreCase(F("txc")) && (argCount == 1 || argCount == 2) )
{
if(argCount == 1)
P096_data->eInkScreen.setTextColor(P096_data->ParseColor(sParams[0]));
else //argCount=2
P096_data->eInkScreen.setTextColor(P096_data->ParseColor(sParams[0]), P096_data->ParseColor(sParams[1]));
}
else if(subcommand.equalsIgnoreCase(F("txs")) && argCount == 1)
{
P096_data->eInkScreen.setTextSize(sParams[0].toInt());
}
else if(subcommand.equalsIgnoreCase(F("txtfull")) && argCount >= 3 && argCount <= 6)
{
switch (argCount)
{
case 3: //single text
P096_data->printText(sParams[2].c_str(), sParams[0].toInt() - 1,sParams[1].toInt() - 1);
break;
case 4: //text + size
P096_data->printText(sParams[3].c_str(), sParams[0].toInt() - 1, sParams[1].toInt() - 1, sParams[2].toInt());
break;
case 5: //text + size + color
P096_data->printText(sParams[4].c_str(), sParams[0].toInt() - 1, sParams[1].toInt() - 1, sParams[2].toInt(), P096_data->ParseColor(sParams[3]));
break;
case 6: //text + size + color
P096_data->printText(sParams[5].c_str(), sParams[0].toInt() - 1, sParams[1].toInt() - 1, sParams[2].toInt(), P096_data->ParseColor(sParams[3]), P096_data->ParseColor(sParams[4]));
break;
default:
success = false;
break;
}
}
else if(subcommand.equalsIgnoreCase(F("l")) && argCount == 5)
{
P096_data->eInkScreen.drawLine(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), sParams[3].toInt(), P096_data->ParseColor(sParams[4]));
}
else if(subcommand.equalsIgnoreCase(F("lh")) && argCount == 3)
{
P096_data->eInkScreen.drawFastHLine(0, sParams[0].toInt(), sParams[1].toInt(), P096_data->ParseColor(sParams[2]));
}
else if(subcommand.equalsIgnoreCase(F("lv")) && argCount == 3)
{
P096_data->eInkScreen.drawFastVLine(sParams[0].toInt(), 0, sParams[1].toInt(), P096_data->ParseColor(sParams[2]));
}
else if(subcommand.equalsIgnoreCase(F("r")) && argCount == 5)
{
P096_data->eInkScreen.drawRect(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), sParams[3].toInt(), P096_data->ParseColor(sParams[4]));
}
else if(subcommand.equalsIgnoreCase(F("rf")) && argCount == 6)
{
P096_data->eInkScreen.fillRect(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), sParams[3].toInt(), P096_data->ParseColor(sParams[5]));
P096_data->eInkScreen.drawRect(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), sParams[3].toInt(), P096_data->ParseColor(sParams[4]));
}
else if(subcommand.equalsIgnoreCase(F("c")) && argCount == 4)
{
P096_data->eInkScreen.drawCircle(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), P096_data->ParseColor(sParams[3]));
}
else if(subcommand.equalsIgnoreCase(F("cf")) && argCount == 5)
{
P096_data->eInkScreen.fillCircle(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), P096_data->ParseColor(sParams[4]));
P096_data->eInkScreen.drawCircle(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), P096_data->ParseColor(sParams[3]));
}
else if(subcommand.equalsIgnoreCase(F("t")) && argCount == 7)
{
P096_data->eInkScreen.drawTriangle(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), sParams[3].toInt(), sParams[4].toInt(), sParams[5].toInt(), P096_data->ParseColor(sParams[6]));
}
else if(subcommand.equalsIgnoreCase(F("tf")) && argCount == 8)
{
P096_data->eInkScreen.fillTriangle(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), sParams[3].toInt(), sParams[4].toInt(), sParams[5].toInt(), P096_data->ParseColor(sParams[7]));
P096_data->eInkScreen.drawTriangle(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), sParams[3].toInt(), sParams[4].toInt(), sParams[5].toInt(), P096_data->ParseColor(sParams[6]));
}
else if(subcommand.equalsIgnoreCase(F("rr")) && argCount == 6)
{
P096_data->eInkScreen.drawRoundRect(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), sParams[3].toInt(), sParams[4].toInt(), P096_data->ParseColor(sParams[5]));
}
else if(subcommand.equalsIgnoreCase(F("rrf")) && argCount == 7)
{
P096_data->eInkScreen.fillRoundRect(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), sParams[3].toInt(), sParams[4].toInt(), P096_data->ParseColor(sParams[6]));
P096_data->eInkScreen.drawRoundRect(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), sParams[3].toInt(), sParams[4].toInt(), P096_data->ParseColor(sParams[5]));
}
else if(subcommand.equalsIgnoreCase(F("px")) && argCount == 3)
{
P096_data->eInkScreen.drawPixel(sParams[0].toInt(), sParams[1].toInt(), P096_data->ParseColor(sParams[2]));
}
else
{
success = false;
}
}
else if(subcommand.equalsIgnoreCase(F("t")) && argCount == 7)
{
eInkScreen->drawTriangle(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), sParams[3].toInt(), sParams[4].toInt(), sParams[5].toInt(), Plugin_096_ParseColor(sParams[6]));
}
else if(subcommand.equalsIgnoreCase(F("tf")) && argCount == 8)
{
eInkScreen->fillTriangle(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), sParams[3].toInt(), sParams[4].toInt(), sParams[5].toInt(), Plugin_096_ParseColor(sParams[7]));
eInkScreen->drawTriangle(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), sParams[3].toInt(), sParams[4].toInt(), sParams[5].toInt(), Plugin_096_ParseColor(sParams[6]));
}
else if(subcommand.equalsIgnoreCase(F("rr")) && argCount == 6)
{
eInkScreen->drawRoundRect(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), sParams[3].toInt(), sParams[4].toInt(), Plugin_096_ParseColor(sParams[5]));
}
else if(subcommand.equalsIgnoreCase(F("rrf")) && argCount == 7)
{
eInkScreen->fillRoundRect(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), sParams[3].toInt(), sParams[4].toInt(), Plugin_096_ParseColor(sParams[6]));
eInkScreen->drawRoundRect(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), sParams[3].toInt(), sParams[4].toInt(), Plugin_096_ParseColor(sParams[5]));
}
else if(subcommand.equalsIgnoreCase(F("px")) && argCount == 3)
{
eInkScreen->drawPixel(sParams[0].toInt(), sParams[1].toInt(), Plugin_096_ParseColor(sParams[2]));
}
else
{
success = false;
}
}
else
else
{
//invalid arguments
success = false;
}
}
else
{
//invalid arguments
success = false;
}
//in case of command outside of sequence, then refresh screen
if(success && !plugin_096_sequence_in_progress)
{
eInkScreen->display();
//in case of command outside of sequence, then refresh screen
if(success && !P096_data->plugin_096_sequence_in_progress)
{
P096_data->eInkScreen.display();
}
}
#ifndef BUILD_NO_DEBUG
String log;
log.reserve(110); // Prevent re-allocation
@@ -541,108 +556,6 @@ boolean Plugin_096(uint8_t function, struct EventStruct *event, String& string)
}
//Print some text
//param [in] string : The text to display
//param [in] X : The left position (X)
//param [in] Y : The top position (Y)
//param [in] textSize : The text size (default 1)
//param [in] color : The fore color (default ILI9341_WHITE)
//param [in] bkcolor : The background color (default ILI9341_BLACK)
void Plugin_096_printText(const char *string, int X, int Y, unsigned int textSize, unsigned short color, unsigned short bkcolor)
{
eInkScreen->clearBuffer();
eInkScreen->clearDisplay();
eInkScreen->setCursor(X, Y);
eInkScreen->setTextColor(color, bkcolor);
eInkScreen->setTextSize(textSize);
String fixString = string;
Plugin_096_FixText(fixString);
eInkScreen->println(fixString);
eInkScreen->display();
}
//Parse color string to color
//param [in] colorString : The color string (white, red, ...)
//return : color (default EPD_WHITE)
unsigned short Plugin_096_ParseColor(const String & colorString)
{
//copy to local var and ensure lowercase
//this optimise the next equlaity checks
String s = colorString;
s.toLowerCase();
if (s.equals(F("black")))
return EPD_BLACK;
if (s.equals(F("white")))
return EPD_WHITE;
if (s.equals(F("inverse")))
return EPD_INVERSE;
if (s.equals(F("red")))
return EPD_RED;
if (s.equals(F("dark")))
return EPD_DARK;
if (s.equals(F("light")))
return EPD_LIGHT;
return EPD_WHITE;
}
//Fix text with handling special characters (degrees and main monetary symbols)
//This is specific case for current AdafruitGfx standard fontused for eink screen
//param [in/out] s : The string to fix
void Plugin_096_FixText(String & s)
{
const char degree[3] = {0xc2, 0xb0, 0}; // Unicode degree symbol
const char degree_eink[2] = {0xf7, 0}; // eink degree symbol
s.replace(degree, degree_eink);
s.replace(F("{D}"), degree_eink);
s.replace(F("&deg;"), degree_eink);
const char euro[4] = { 0xe2, 0x82, 0xac, 0 }; // Unicode euro symbol
const char euro_eink[2] = {0xED, 0}; // eink degree symbol
s.replace(euro, euro_eink);
s.replace(F("{E}"), euro_eink);
s.replace(F("&euro;"), euro_eink);
const char pound[3] = { 0xc2, 0xa3, 0 }; // Unicode pound symbol
const char pound_eink[2] = {0x9C, 0}; // eink pound symbol
s.replace(pound, pound_eink);
s.replace(F("{P}"), pound_eink);
s.replace(F("&pound;"), pound_eink);
const char yen[3] = { 0xc2, 0xa5, 0 }; // Unicode yen symbol
const char yen_eink[2] = {0x9D, 0}; // eink yen symbol
s.replace(yen, yen_eink);
s.replace(F("{Y}"), yen_eink);
s.replace(F("&yen;"), yen_eink);
const char cent[3] = { 0xc2, 0xa2, 0 }; // Unicode yen symbol
const char cent_eink[2] = {0x9B, 0}; // eink cent symbol
s.replace(cent, cent_eink);
s.replace(F("{c}"), cent_eink);
s.replace(F("&cent;"), cent_eink);
}
//Split a string by delimiter
//param [in] s : The input string
//param [in] c : The delimiter
//param [out] op : The resulting string array
//param [in] limit : The maximum strings to find
//return : The string count
int Plugin_096_StringSplit(const String &s, char c, String op[], int limit)
{
int count = 0;
char * pch;
String d = String(c);
pch = strtok ((char*)(s.c_str()),d.c_str());
while (pch != NULL && count < limit)
{
op[count] = String(pch);
count++;
pch = strtok (NULL, ",");
}
return count;
}
#endif // USES_P096
+203
View File
@@ -0,0 +1,203 @@
#include "_Plugin_Helper.h"
#ifdef USES_P117
// #######################################################################################################
// ############ Plugin 117 SCD30 I2C CO2, Humidity and Temperature Sensor ################################
// #######################################################################################################
// development version
// by: V0JT4
// this plugin is based on the Frogmore42 library
// written based code from https://github.com/Frogmore42/Sonoff-Tasmota/tree/development/lib/FrogmoreScd30
// Changelog:
//
// 2021-11-20 tonhuisman: Implement multi-instance support (using PluginStruct)
// 2021-09 tonhuisman: Moved from ESPEasyPluginPlayground to main repository
// Commands:
// SCDGETABC - shows automatic calibration period in days, 0 = disable
// SCDGETALT - shows altitude compensation configuration in meters above sea level
// SCDGETTMP - shows temperature offset in degrees C
# define PLUGIN_117
# define PLUGIN_ID_117 117
# define PLUGIN_NAME_117 "Gases - CO2 SCD30 [TESTING]"
# define PLUGIN_VALUENAME1_117 "CO2"
# define PLUGIN_VALUENAME2_117 "Humidity"
# define PLUGIN_VALUENAME3_117 "Temperature"
# define PLUGIN_VALUENAME4_117 "CO2raw"
# include "./src/PluginStructs/P117_data_struct.h"
boolean Plugin_117(uint8_t function, struct EventStruct *event, String& string)
{
boolean success = false;
switch (function)
{
case PLUGIN_DEVICE_ADD:
{
Device[++deviceCount].Number = PLUGIN_ID_117;
Device[deviceCount].Type = DEVICE_TYPE_I2C;
Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_QUAD;
Device[deviceCount].Ports = 0;
Device[deviceCount].PullUpOption = false;
Device[deviceCount].InverseLogicOption = false;
Device[deviceCount].FormulaOption = true;
Device[deviceCount].ValueCount = 4;
Device[deviceCount].SendDataOption = true;
Device[deviceCount].TimerOption = true;
Device[deviceCount].GlobalSyncOption = true;
break;
}
case PLUGIN_GET_DEVICENAME:
{
string = F(PLUGIN_NAME_117);
break;
}
case PLUGIN_GET_DEVICEVALUENAMES:
{
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_117));
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_117));
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_VALUENAME3_117));
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[3], PSTR(PLUGIN_VALUENAME4_117));
break;
}
case PLUGIN_I2C_HAS_ADDRESS:
{
success = event->Par1 == 0x61;
break;
}
case PLUGIN_SET_DEFAULTS:
{
ExtraTaskSettings.TaskDeviceValueDecimals[0] = 0; // CO2 values are integers
ExtraTaskSettings.TaskDeviceValueDecimals[3] = 0;
break;
}
case PLUGIN_WEBFORM_SHOW_I2C_PARAMS:
{
html_TR_TD();
html_TD();
addHtml(F("<span style=\"color:red\">Tools->Advanced->I2C ClockStretchLimit should be set in range 20 to 150 msec.</span>"));
break;
}
case PLUGIN_WEBFORM_LOAD:
{
addFormNumericBox(F("Altitude"), F("plugin_117_SCD30_alt"), PCONFIG(0), 0, 2000);
addUnit(F("0..2000 m"));
addFormTextBox(F("Temp offset"), F("plugin_117_SCD30_tmp"), String(PCONFIG_FLOAT(0), 2), 5);
addUnit(F("&deg;C"));
success = true;
break;
}
case PLUGIN_WEBFORM_SAVE:
{
uint16_t alt = getFormItemInt(F("plugin_117_SCD30_alt"));
if (alt > 2000) { alt = 2000; }
PCONFIG(0) = alt;
PCONFIG_FLOAT(0) = getFormItemFloat(F("plugin_117_SCD30_tmp"));
success = true;
break;
}
case PLUGIN_INIT:
{
initPluginTaskData(event->TaskIndex, new (std::nothrow) P117_data_struct(PCONFIG(0), PCONFIG_FLOAT(0)));
P117_data_struct *P117_data = static_cast<P117_data_struct *>(getPluginTaskData(event->TaskIndex));
if (nullptr == P117_data) {
return success;
}
success = true;
break;
}
case PLUGIN_READ:
{
P117_data_struct *P117_data = static_cast<P117_data_struct *>(getPluginTaskData(event->TaskIndex));
if (nullptr == P117_data) {
return success;
}
uint16_t scd30_CO2 = 0u;
uint16_t scd30_CO2EAvg = 0u;
float scd30_Humid = 0.0f;
float scd30_Temp = 0.0f;
switch (P117_data->read_sensor(&scd30_CO2, &scd30_CO2EAvg, &scd30_Temp, &scd30_Humid))
{
case ERROR_SCD30_NO_ERROR:
UserVar[event->BaseVarIndex] = scd30_CO2EAvg;
UserVar[event->BaseVarIndex + 1] = scd30_Humid;
UserVar[event->BaseVarIndex + 2] = scd30_Temp;
UserVar[event->BaseVarIndex + 3] = scd30_CO2;
if (scd30_CO2EAvg > 5000)
{
addLog(LOG_LEVEL_INFO, F("SCD30: Sensor saturated! > 5000 ppm"));
}
break;
case ERROR_SCD30_NO_DATA:
case ERROR_SCD30_CRC_ERROR:
case ERROR_SCD30_CO2_ZERO:
break;
default:
{
P117_data->softReset();
break;
}
}
success = true;
break;
}
case PLUGIN_WRITE:
{
P117_data_struct *P117_data = static_cast<P117_data_struct *>(getPluginTaskData(event->TaskIndex));
if (nullptr == P117_data) {
return success;
}
String command = parseString(string, 1);
uint16_t value = 0;
String log;
float temp;
if (command.equals(F("scdgetabc")))
{
P117_data->getCalibrationType(&value);
log += F("ABC: ");
log += value;
success = true;
} else if (command.equals(F("scdgetalt")))
{
P117_data->getAltitudeCompensation(&value);
log += F("Altitude: ");
log += value;
success = true;
} else if (command.equals(F("scdgettmp")))
{
P117_data->getTemperatureOffset(&temp);
log += F("Temp offset: ");
log += String(temp, 2);
success = true;
}
if (success) {
SendStatus(event, log);
} else {
addLog(LOG_LEVEL_ERROR, F("SCD30: Unknown command."));
}
break;
}
}
return success;
}
#endif // ifdef USES_P117
+2 -1
View File
@@ -1137,6 +1137,7 @@ To create/register a plugin, you have to :
#define USES_P094 // CUL Reader
#define USES_P114 // VEML6075 UVA/UVB sensor
#define USES_P115 // Fuel Gauge MAX1704x
#define USES_P117 // SCD30
#define USES_P124 // I2C MultiRelay
#endif
@@ -1420,7 +1421,7 @@ To create/register a plugin, you have to :
#define USES_P116 //
#endif
#ifndef USES_P117
#define USES_P117 //
#define USES_P117 // SCD30
#endif
#ifndef USES_P118
#define USES_P118 //
+14 -5
View File
@@ -373,7 +373,11 @@ bool check_rules_line_user_errors(String& line)
\*********************************************************************************************/
bool get_next_inner_bracket(const String& line, int& startIndex, int& closingIndex, char closingBracket)
{
char openingBracket = closingIndex;
if (line.length() <= 1) {
// Not possible to have opening and closing bracket on a line this short.
return false;
}
char openingBracket = closingBracket;
switch (closingBracket) {
case ']': openingBracket = '['; break;
@@ -383,11 +387,15 @@ bool get_next_inner_bracket(const String& line, int& startIndex, int& closingInd
// unknown bracket type
return false;
}
closingIndex = line.indexOf(closingBracket);
// Closing bracket should not be found on the first position.
closingIndex = line.indexOf(closingBracket, startIndex + 1);
if (closingIndex == -1) { return false; }
if (closingIndex == -1) {
// not found
return false;
}
for (int i = closingIndex; i >= 0; --i) {
for (int i = (closingIndex - 1); i > startIndex; --i) {
if (line[i] == openingBracket) {
startIndex = i;
return true;
@@ -542,7 +550,8 @@ bool parse_math_functions(const String& cmd_s_lower, const String& arg1, const S
}
void parse_string_commands(String& line) {
int startIndex, closingIndex;
int startIndex = 0;
int closingIndex;
while (get_next_inner_bracket(line, startIndex, closingIndex, '}')) {
// Command without opening and closing brackets.
+106
View File
@@ -0,0 +1,106 @@
#include "../PluginStructs/P095_data_struct.h"
#ifdef USES_P095
P095_data_struct::P095_data_struct(int8_t _CS, int8_t _DC, int8_t _RST)
: tft(_CS, _DC, _RST)
{
tft.begin();
}
//Print some text
//param [in] string : The text to display
//param [in] X : The left position (X)
//param [in] Y : The top position (Y)
//param [in] textSize : The text size (default 1)
//param [in] color : The fore color (default ILI9341_WHITE)
//param [in] bkcolor : The background color (default ILI9341_BLACK)
void P095_data_struct::printText(const String& string, int X, int Y, unsigned int textSize, unsigned short color, unsigned short bkcolor)
{
tft.setCursor(X, Y);
tft.setTextColor(color, bkcolor);
tft.setTextSize(textSize);
tft.println(string);
}
//Parse color string to ILI9341 color
//param [in] s : The color string (white, red, ...)
//return : color (default ILI9341_WHITE)
unsigned short P095_data_struct::ParseColor(String & s)
{
if (s.equalsIgnoreCase(F("BLACK")))
return ILI9341_BLACK;
if (s.equalsIgnoreCase(F("NAVY")))
return ILI9341_NAVY;
if (s.equalsIgnoreCase(F("DARKGREEN")))
return ILI9341_DARKGREEN;
if (s.equalsIgnoreCase(F("DARKCYAN")))
return ILI9341_DARKCYAN;
if (s.equalsIgnoreCase(F("MAROON")))
return ILI9341_MAROON;
if (s.equalsIgnoreCase(F("PURPLE")))
return ILI9341_PURPLE;
if (s.equalsIgnoreCase(F("OLIVE")))
return ILI9341_OLIVE;
if (s.equalsIgnoreCase(F("LIGHTGREY")))
return ILI9341_LIGHTGREY;
if (s.equalsIgnoreCase(F("DARKGREY")))
return ILI9341_DARKGREY;
if (s.equalsIgnoreCase(F("BLUE")))
return ILI9341_BLUE;
if (s.equalsIgnoreCase(F("GREEN")))
return ILI9341_GREEN;
if (s.equalsIgnoreCase(F("CYAN")))
return ILI9341_CYAN;
if (s.equalsIgnoreCase(F("RED")))
return ILI9341_RED;
if (s.equalsIgnoreCase(F("MAGENTA")))
return ILI9341_MAGENTA;
if (s.equalsIgnoreCase(F("YELLOW")))
return ILI9341_YELLOW;
if (s.equalsIgnoreCase(F("WHITE")))
return ILI9341_WHITE;
if (s.equalsIgnoreCase(F("ORANGE")))
return ILI9341_ORANGE;
if (s.equalsIgnoreCase(F("GREENYELLOW")))
return ILI9341_GREENYELLOW;
if (s.equalsIgnoreCase(F("PINK")))
return ILI9341_PINK;
if(s.length() == 7 && s[0] == '#')
{
// convrt to long value in base16, then split up into r, g, b values
long long number = strtoll( &s[1], NULL, 16);
//long long r = number >> 16;
//long long g = number >> 8 & 0xFF;
//long long b = number & 0xFF;
//convert to color565 (used by adafruit lib)
return tft.color565(number >> 16, number >> 8 & 0xFF, number & 0xFF);
}
return ILI9341_WHITE; //fallback value
}
//Split a string by delimiter
//param [in] s : The input string
//param [in] c : The delimiter
//param [out] op : The resulting string array
//param [in] limit : The maximum strings to find
//return : The string count
int P095_data_struct::StringSplit(String &s, char c, String op[], int limit)
{
int count = 0;
char * pch;
String d = String(c);
pch = strtok ((char*)(s.c_str()),d.c_str());
while (pch != NULL && count < limit)
{
op[count] = String(pch);
count++;
pch = strtok (NULL, ",");
}
return count;
}
#endif // ifdef USES_P095
+64
View File
@@ -0,0 +1,64 @@
#ifndef PLUGINSTRUCTS_P095_DATA_STRUCT_H
#define PLUGINSTRUCTS_P095_DATA_STRUCT_H
#include "../../_Plugin_Helper.h"
#ifdef USES_P095
# if !defined(LIMIT_BUILD_SIZE) && !defined(PLUGIN_095_FONT_INCLUDED)
# define PLUGIN_095_FONT_INCLUDED // enable to use fonts in this plugin
# endif // if !defined(LIMIT_BUILD_SIZE) && !defined(PLUGIN_095_FONT_INCLUDED)
# include <Adafruit_ILI9341.h>
# ifdef PLUGIN_095_FONT_INCLUDED
# include "../Static/Fonts/Seven_Segment24pt7b.h"
# include "../Static/Fonts/Seven_Segment18pt7b.h"
# include <Fonts/FreeSans9pt7b.h> // included in Adafruit-GFX-Library
# endif // ifdef PLUGIN_095_FONT_INCLUDED
struct P095_data_struct : public PluginTaskData_base {
public:
P095_data_struct(int8_t _CS,
int8_t _DC,
int8_t _RST = -1);
// Print some text
// param [in] string : The text to display
// param [in] X : The left position (X)
// param [in] Y : The top position (Y)
// param [in] textSize : The text size (default 1)
// param [in] color : The fore color (default ILI9341_WHITE)
// param [in] bkcolor : The background color (default ILI9341_BLACK)
void printText(const String & string,
int X,
int Y,
unsigned int textSize = 1,
unsigned short color = ILI9341_WHITE,
unsigned short bkcolor = ILI9341_BLACK);
// Parse color string to ILI9341 color
// param [in] s : The color string (white, red, ...)
// return : color (default ILI9341_WHITE)
unsigned short ParseColor(String& s);
// Split a string by delimiter
// param [in] s : The input string
// param [in] c : The delimiter
// param [out] op : The resulting string array
// param [in] limit : The maximum strings to find
// return : The string count
int StringSplit(String& s,
char c,
String op[],
int limit);
Adafruit_ILI9341 tft;
};
#endif // ifdef USES_P095
#endif // ifndef PLUGINSTRUCTS_P095_DATA_STRUCT_H
+119
View File
@@ -0,0 +1,119 @@
#include "../PluginStructs/P096_data_struct.h"
#ifdef USES_P096
P096_data_struct::P096_data_struct(int width, int height, int8_t DC, int8_t RST, int8_t CS, int8_t BUSY)
: eInkScreen(width, height, DC, RST, CS, BUSY),
plugin_096_sequence_in_progress(false)
{
eInkScreen.begin();
eInkScreen.clearBuffer();
}
//Print some text
//param [in] string : The text to display
//param [in] X : The left position (X)
//param [in] Y : The top position (Y)
//param [in] textSize : The text size (default 1)
//param [in] color : The fore color (default ILI9341_WHITE)
//param [in] bkcolor : The background color (default ILI9341_BLACK)
void P096_data_struct::printText(const char *string, int X, int Y, unsigned int textSize, unsigned short color, unsigned short bkcolor)
{
eInkScreen.clearBuffer();
eInkScreen.clearDisplay();
eInkScreen.setCursor(X, Y);
eInkScreen.setTextColor(color, bkcolor);
eInkScreen.setTextSize(textSize);
String fixString = string;
FixText(fixString);
eInkScreen.println(fixString);
eInkScreen.display();
}
//Parse color string to color
//param [in] colorString : The color string (white, red, ...)
//return : color (default EPD_WHITE)
unsigned short P096_data_struct::ParseColor(const String & colorString)
{
//copy to local var and ensure lowercase
//this optimise the next equlaity checks
String s = colorString;
s.toLowerCase();
if (s.equals(F("black")))
return EPD_BLACK;
if (s.equals(F("white")))
return EPD_WHITE;
if (s.equals(F("inverse")))
return EPD_INVERSE;
if (s.equals(F("red")))
return EPD_RED;
if (s.equals(F("dark")))
return EPD_DARK;
if (s.equals(F("light")))
return EPD_LIGHT;
return EPD_WHITE;
}
//Fix text with handling special characters (degrees and main monetary symbols)
//This is specific case for current AdafruitGfx standard fontused for eink screen
//param [in/out] s : The string to fix
void P096_data_struct::FixText(String & s)
{
const char degree[3] = {0xc2, 0xb0, 0}; // Unicode degree symbol
const char degree_eink[2] = {0xf7, 0}; // eink degree symbol
s.replace(degree, degree_eink);
s.replace(F("{D}"), degree_eink);
s.replace(F("&deg;"), degree_eink);
const char euro[4] = { 0xe2, 0x82, 0xac, 0 }; // Unicode euro symbol
const char euro_eink[2] = {0xED, 0}; // eink degree symbol
s.replace(euro, euro_eink);
s.replace(F("{E}"), euro_eink);
s.replace(F("&euro;"), euro_eink);
const char pound[3] = { 0xc2, 0xa3, 0 }; // Unicode pound symbol
const char pound_eink[2] = {0x9C, 0}; // eink pound symbol
s.replace(pound, pound_eink);
s.replace(F("{P}"), pound_eink);
s.replace(F("&pound;"), pound_eink);
const char yen[3] = { 0xc2, 0xa5, 0 }; // Unicode yen symbol
const char yen_eink[2] = {0x9D, 0}; // eink yen symbol
s.replace(yen, yen_eink);
s.replace(F("{Y}"), yen_eink);
s.replace(F("&yen;"), yen_eink);
const char cent[3] = { 0xc2, 0xa2, 0 }; // Unicode yen symbol
const char cent_eink[2] = {0x9B, 0}; // eink cent symbol
s.replace(cent, cent_eink);
s.replace(F("{c}"), cent_eink);
s.replace(F("&cent;"), cent_eink);
}
//Split a string by delimiter
//param [in] s : The input string
//param [in] c : The delimiter
//param [out] op : The resulting string array
//param [in] limit : The maximum strings to find
//return : The string count
int P096_data_struct::StringSplit(const String &s, char c, String op[], int limit)
{
int count = 0;
char * pch;
String d = String(c);
pch = strtok ((char*)(s.c_str()),d.c_str());
while (pch != NULL && count < limit)
{
op[count] = String(pch);
count++;
pch = strtok (NULL, ",");
}
return count;
}
#endif // ifdef USES_P096
+62
View File
@@ -0,0 +1,62 @@
#ifndef PLUGINSTRUCTS_P096_DATA_STRUCT_H
#define PLUGINSTRUCTS_P096_DATA_STRUCT_H
#include "../../_Plugin_Helper.h"
#ifdef USES_P096
# include <LOLIN_EPD.h>
# include <Adafruit_GFX.h>
struct P096_data_struct : public PluginTaskData_base {
public:
P096_data_struct(int width,
int height,
int8_t DC,
int8_t RST,
int8_t CS,
int8_t BUSY = -1);
// Print some text
// param [in] string : The text to display
// param [in] X : The left position (X)
// param [in] Y : The top position (Y)
// param [in] textSize : The text size (default 1)
// param [in] color : The fore color (default ILI9341_WHITE)
// param [in] bkcolor : The background color (default ILI9341_BLACK)
void printText(const char *string,
int X,
int Y,
unsigned int textSize = 1,
unsigned short color = EPD_WHITE,
unsigned short bkcolor = EPD_BLACK);
// Parse color string to color
// param [in] colorString : The color string (white, red, ...)
// return : color (default EPD_WHITE)
unsigned short ParseColor(const String& colorString);
// Fix text with handling special characters (degrees and main monetary symbols)
// This is specific case for current AdafruitGfx standard fontused for eink screen
// param [in/out] s : The string to fix
void FixText(String& s);
// Split a string by delimiter
// param [in] s : The input string
// param [in] c : The delimiter
// param [out] op : The resulting string array
// param [in] limit : The maximum strings to find
// return : The string count
int StringSplit(const String& s,
char c,
String op[],
int limit);
LOLIN_IL3897 eInkScreen;
uint8_t plugin_096_sequence_in_progress = false;
};
#endif // ifdef USES_P096
#endif // ifndef PLUGINSTRUCTS_P096_DATA_STRUCT_H
@@ -0,0 +1,57 @@
#include "../PluginStructs/P117_data_struct.h"
#ifdef USES_P117
// **************************************************************************/
// Constructor
// **************************************************************************/
P117_data_struct::P117_data_struct(uint16_t altitude, float temperatureOffset)
: _altitude(altitude), _temperatureOffset(temperatureOffset) {}
// **************************************************************************/
// Initialize sensor and read data from SCD30
// **************************************************************************/
uint32_t P117_data_struct::read_sensor(uint16_t *scd30_CO2, uint16_t *scd30_CO2EAvg, float *scd30_Temp, float *scd30_Humid) {
if (!initialised) {
initialised = init_sensor(); // Check id device is present
}
if (initialised) {
return scd30.readMeasurement(scd30_CO2, scd30_CO2EAvg, scd30_Temp, scd30_Humid);
}
return ERROR_SCD30_NO_DATA;
}
// **************************************************************************/
// Check SCD30 presence and initialize
// **************************************************************************/
bool P117_data_struct::softReset() {
if (initialised) {
scd30.softReset();
}
return initialised;
}
// **************************************************************************/
// Check SCD30 presence and initialize
// **************************************************************************/
bool P117_data_struct::init_sensor() {
if (!initialised) {
scd30.begin();
uint16_t calibration = 0;
scd30.getCalibrationType(&calibration);
if (calibration) {
scd30.setManualCalibration();
}
scd30.beginMeasuring();
scd30.setAltitudeCompensation(_altitude);
scd30.setTemperatureOffset(_temperatureOffset);
return true;
}
return initialised;
}
#endif // ifdef USES_P117
+52
View File
@@ -0,0 +1,52 @@
#ifndef PLUGINSTRUCTS_P117_DATA_STRUCT_H
#define PLUGINSTRUCTS_P117_DATA_STRUCT_H
#include "../../_Plugin_Helper.h"
#ifdef USES_P117
# include <FrogmoreScd30.h>
struct P117_data_struct : public PluginTaskData_base {
public:
P117_data_struct(uint16_t altitude,
float temperatureOffset);
P117_data_struct() = delete;
uint32_t read_sensor(uint16_t *scd30_CO2,
uint16_t *scd30_CO2EAvg,
float *scd30_Temp,
float *scd30_Humid);
bool softReset();
void getCalibrationType(uint16_t *abc) {
if (initialised) {
scd30.getCalibrationType(abc);
}
}
void getAltitudeCompensation(uint16_t *altitude) {
if (initialised) {
scd30.getAltitudeCompensation(altitude);
}
}
void getTemperatureOffset(float *temperature) {
if (initialised) {
scd30.getTemperatureOffset(temperature);
}
}
private:
FrogmoreScd30 scd30;
bool init_sensor();
uint16_t _altitude;
float _temperatureOffset;
bool initialised = false;
};
#endif // ifdef USES_P117
#endif // ifndef PLUGINSTRUCTS_P117_DATA_STRUCT_H
+1 -1
View File
@@ -280,7 +280,7 @@ String getKnownI2Cdevice(uint8_t address) {
result += F("Adafruit Motorshield v2,SI1145");
break;
case 0x61:
result += F("Atlas EZO DO");
result += F("Atlas EZO DO,SCD30");
break;
case 0x62:
result += F("Atlas EZO ORP");