From 965d9bb4e82feddfc9fdc5236e321dc58b31b760 Mon Sep 17 00:00:00 2001 From: tonhuisman Date: Sun, 2 Aug 2020 22:33:20 +0200 Subject: [PATCH 01/28] [Docs] Updated commands reference --- docs/source/Plugin/P000_commands.repl | 92 ++++++++++++++++++++++++++- 1 file changed, 91 insertions(+), 1 deletion(-) diff --git a/docs/source/Plugin/P000_commands.repl b/docs/source/Plugin/P000_commands.repl index 55c49e498..91be344fb 100644 --- a/docs/source/Plugin/P000_commands.repl +++ b/docs/source/Plugin/P000_commands.repl @@ -30,6 +30,12 @@ Example output: ``Allowed IP range : 192.168.10.0 - 192.168.10.255``" " + Background"," + :red:`Internal`"," + Process background tasks. + + ``Background``" + " Build"," :red:`Internal`"," Get or set build information. This will not be stored, so only valid until reboot. @@ -86,7 +92,7 @@ * ``Datetime,2020-02-29`` - set node time '2020-02-29 00:00:00' * ``Datetime`` - get current node time - Time source will be set to ``Manual_set`` until the time will be updated via another source. (e.g. GPS or NTP) + Time source will be set to ``Manual_set`` until the time will be updated via another source. (e.g. GPS or NTP)" " Debug"," :red:`Internal`"," @@ -201,6 +207,12 @@ ``LogEntry,``" " + LogPortStatus"," + :red:`Internal`"," + Display status information about all ports + + ``LogPortStatus``" + " meminfo"," :red:`Internal`"," Will send summary of struct sizes to the serial port. @@ -303,6 +315,16 @@ ``Settings``" " + Subnet"," + :red:`Internal`"," + Get or set the network subnet setting + + ``Subnet`` + + Example output: ``Subnet:255.255.255.0(DHCP)`` + + ``Subnet,255.255.255.0``" + " Subscribe"," :green:`Rules`"," Subscribes to a specific topic using MQTT broker service @@ -347,6 +369,24 @@ ``TaskValueSet,,,``" " + TaskValueSetAndRun"," + :green:`Rules`"," + Set values on a **Dummy Task** (device) and execute a TaskRun on it + + ``TaskValueSetAndRun,,,``" + " + TimerPause"," + :green:`Rules`"," + Pause a timer + + ``TimerPause,``" + " + TimerResume"," + :green:`Rules`"," + Resume a paused timer + + ``TimerResume,``" + " TimerSet"," :green:`Rules`"," Start a timed event @@ -355,12 +395,56 @@ ``TimerSet,,0`` disables the timer" " + TimeZone"," + :red:`Internal`"," + Get or set the TimeZone + + ``TimeZone`` + + Example output: ``TimeZone:60`` + + ``TimeZone,``" + " + UdpPort"," + :red:`Internal`"," + Set the udp port + + ``UdpPort,``" + " + UdpTest"," + :red:`Internal`"," + Send a test message to an udp port, for number of times + + ``UdpTest,,``" + " Unit"," :red:`Internal`"," Set the unit number ``Unit,``" " + UseNTP"," + :red:`Internal`"," + Get or set the status of NTP (Network Time Protocol) + + ``UseNTP`` + + Example output: ``UseNTP:true`` + + ``UseNTP,<0/1>``" + " + WdConfig"," + :red:`Internal`"," + Configure I2C device (Wire Device) + + ``WdConfig,
,,``" + " + WdRead"," + :red:`Internal`"," + Read from I2C device (Wire Device) + + ``WdRead,
,``" + " WifiAPKey"," :red:`Internal`"," Change AP WPA key @@ -414,6 +498,12 @@ Change SSID to connect as secondry WiFi ``WifiSSID2,``" + " + WifiSTAMode"," + :red:`Internal`"," + Force the unit into STA mode. + + ``WifiSTAMode``" From 0629084b4708d9d679543c160c9bad6ce59ee503 Mon Sep 17 00:00:00 2001 From: tonhuisman Date: Mon, 3 Aug 2020 10:54:57 +0200 Subject: [PATCH 02/28] [Docs] Fixed some typos and Sphinx syntax errors --- docs/source/Controller/C013.rst | 12 ++++++------ docs/source/Plugin/P000_commands.repl | 4 ++-- docs/source/Plugin/P093.rst | 2 +- docs/source/Reference/RTC.rst | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/source/Controller/C013.rst b/docs/source/Controller/C013.rst index f1d807630..4cead80df 100644 --- a/docs/source/Controller/C013.rst +++ b/docs/source/Controller/C013.rst @@ -163,10 +163,10 @@ Of each known node the following data is kept: The key to index this ``NodeStruct`` is the nodes unit number. ASCII Data -~~~~~~~~~~ +^^^^^^^^^^^^^^^^ Command Message -*************** +^^^^^^^^^^^^^^^^ First byte is not 0xFF. @@ -181,7 +181,7 @@ As can be seen, no checks for size, and it is just expected to be a valid ESPeas Also no check to see if the command is supported by the receiving end and no feedback to the sender. Binary Data -~~~~~~~~~~~ +^^^^^^^^^^^^^^^^ Binary data is marked with the first byte 0xFF. @@ -207,7 +207,7 @@ Message types supported, determined by the 2nd byte: * 5: Sensor data Sysinfo Message -*************** +^^^^^^^^^^^^^^^^ There are 2 types of Sysinfo messages, a standard and an extended message. The extended message starts with the same information as the standard one. @@ -234,7 +234,7 @@ The node type is defined as: * 81 = "Nano Easy" Sensor Info message -******************* +^^^^^^^^^^^^^^^^^^^^ Sensor Info messages are just a description of a shared sensor. It contains some information to setup a new sensor on the receiving end. @@ -258,7 +258,7 @@ These messages are just a serialized byte stream of ``struct C013_SensorInfoStru Sensor Data message -******************* +^^^^^^^^^^^^^^^^^^^^ These messages are just a serialized byte stream of ``struct C013_SensorDataStruct`` . diff --git a/docs/source/Plugin/P000_commands.repl b/docs/source/Plugin/P000_commands.repl index 91be344fb..48ddff51d 100644 --- a/docs/source/Plugin/P000_commands.repl +++ b/docs/source/Plugin/P000_commands.repl @@ -435,13 +435,13 @@ " WdConfig"," :red:`Internal`"," - Configure I2C device (Wire Device) + Configure external Watchdog device ``WdConfig,
,,``" " WdRead"," :red:`Internal`"," - Read from I2C device (Wire Device) + Read from external Watchdog device ``WdRead,
,``" " diff --git a/docs/source/Plugin/P093.rst b/docs/source/Plugin/P093.rst index dc3710482..f64b78d09 100644 --- a/docs/source/Plugin/P093.rst +++ b/docs/source/Plugin/P093.rst @@ -89,7 +89,7 @@ Commands .. Events .. ~~~~~~ -.. include:: P093_events.repl +.. This file is missing. (remove up to 'include' when fixed) include:: P093_events.repl Example ------- diff --git a/docs/source/Reference/RTC.rst b/docs/source/Reference/RTC.rst index 1c7629165..9b7dca611 100644 --- a/docs/source/Reference/RTC.rst +++ b/docs/source/Reference/RTC.rst @@ -4,7 +4,7 @@ RTC Reference The ESP microprocessors we use, have some special memory which will retain its information as long as the node remains powered. Espressif calls this _RTC_ RAM and thus we're using the same term for it. -The name may be a bit strange, as RTC usually refers to _Real Time Clock_. +The name may be a bit strange, as RTC usually refers to *Real Time Clock*. Currently we only support RTC functions on the ESP8266 and ESP8285. ESP32 does also have RTC memory, but that's organised a bit different. From e86736a423866ff69f348acac3a199c7339d1c8c Mon Sep 17 00:00:00 2001 From: Gijs Noorlander Date: Tue, 4 Aug 2020 13:16:55 +0200 Subject: [PATCH 03/28] [docs] Fix table for P095 --- docs/source/Plugin/P095_commands.repl | 118 +++++++++++++------------- 1 file changed, 59 insertions(+), 59 deletions(-) diff --git a/docs/source/Plugin/P095_commands.repl b/docs/source/Plugin/P095_commands.repl index e0aac87e0..0333daf36 100644 --- a/docs/source/Plugin/P095_commands.repl +++ b/docs/source/Plugin/P095_commands.repl @@ -1,69 +1,69 @@ .. csv-table:: - :header: "Command", "Extra information" - :widths: 20, 30 + :header: "Command", "Extra information" + :widths: 20, 30 - " - ``TFTCMD,`` - "," - Control the screen (on, off, clear,..) + " + ``TFTCMD,`` + "," + Control the screen (on, off, clear,..) - Examples: + Examples: - - ``tftcmd,on`` Switch display on. - - ``tftcmd,off`` Switch display off. - - ``tftcmd,clear`` Clear whole display. - - ``tftcmd,clear,green`` Clear whole display with green color. - - ``tftcmd,inv,1`` Invert the dispaly (value:0 normal display, 1 inverted display) - - ``tftcmd,rot,2`` Rotate display (value from 0 to 3 inclusive) - " - " - ``TFT,,....`` - "," - Draw line, rect, circle, triangle and text - - Subcommands: - - - ``tft,txt,`` Write simple text (use last position, color and size) - - ``tft,txp,,`` Set text position (move the cursor) - - ``tft,txc,,`` Set text color (background is transparent if not provided - - ``tft,txs,`` Set text size - - ``tft,txtfull,,,,,,`` Write text with all options - - ``tft,l,,,<2>,,`` Draw a simple line - - ``tft,lh,,,`` Draw an horizontal line (width = Line width in pixels (positive = right of first point, negative = point of first corner). - - ``tft,lv,,,`` Draw a vertical line (height= Line height in pixels (positive = below first point, negative = above first point). - - ``tft,r,,,,,`` Draw a rectangle - - ``tft,rf,,,,,,`` Draw a filled rectangle - - ``tft,c,,,,`` Draw a circle - - ``tft,cf,,,,,`` Draw a filled circle - - ``tft,t,,,,,,,| Draw a triangle - - ``tft,tf,,,,,,,,`` Draw a filled triangle - - ``tft,rr,,,,,,`` Draw a round rectangle - - ``tft,rrf,,,,,,,`` Draw a filled round rectangle - - ``tft,px,,,`` Print a single pixel - - ``tft,font, `` Switch to font - SEVENSEG24, SEVENSEG18, FREESANS, DEFAULT + - ``tftcmd,on`` Switch display on. + - ``tftcmd,off`` Switch display off. + - ``tftcmd,clear`` Clear whole display. + - ``tftcmd,clear,green`` Clear whole display with green color. + - ``tftcmd,inv,1`` Invert the dispaly (value:0 normal display, 1 inverted display) + - ``tftcmd,rot,2`` Rotate display (value from 0 to 3 inclusive) + " + " + ``TFT,,....`` + "," + Draw line, rect, circle, triangle and text + + Subcommands: + + - ``tft,txt,`` Write simple text (use last position, color and size) + - ``tft,txp,,`` Set text position (move the cursor) + - ``tft,txc,,`` Set text color (background is transparent if not provided + - ``tft,txs,`` Set text size + - ``tft,txtfull,,,,,,`` Write text with all options + - ``tft,l,,,<2>,,`` Draw a simple line + - ``tft,lh,,,`` Draw an horizontal line (width = Line width in pixels (positive = right of first point, negative = point of first corner). + - ``tft,lv,,,`` Draw a vertical line (height= Line height in pixels (positive = below first point, negative = above first point). + - ``tft,r,,,,,`` Draw a rectangle + - ``tft,rf,,,,,,`` Draw a filled rectangle + - ``tft,c,,,,`` Draw a circle + - ``tft,cf,,,,,`` Draw a filled circle + - ``tft,t,,,,,,,`` Draw a triangle + - ``tft,tf,,,,,,,,`` Draw a filled triangle + - ``tft,rr,,,,,,`` Draw a round rectangle + - ``tft,rrf,,,,,,,`` Draw a filled round rectangle + - ``tft,px,,,`` Print a single pixel + - ``tft,font,`` Switch to font - SEVENSEG24, SEVENSEG18, FREESANS, DEFAULT + " - Examples: +Examples: - Write Text : - ``tft,txtfull,0,0,HelloWorld`` - - Write Text another place: - ``tft,txtfull,100,40,HelloWorld`` +Write Text : + ``tft,txtfull,0,0,HelloWorld`` - Write bigger Text : - ``tft,txtfull,0,0,3,HelloWorld`` +Write Text another place: + ``tft,txtfull,100,40,HelloWorld`` - Write RED Text : - ``tft,txtfull,0,0,3,HelloWorld`` +Write bigger Text : + ``tft,txtfull,0,0,3,HelloWorld`` - Write RED Text (size is 1): - ``tft,txtfull,0,0,1,RED,HelloWorld`` +Write RED Text : + ``tft,txtfull,0,0,3,HelloWorld`` - Write RED Text on YELLOW background (size is 1): - ``tft,txtfull,0,0,1,RED,YELLOW,HelloWorld`` - - Select font : - ``tft,font,SEVENSEG24`` - ``tft,txtfull,0,0,1,RED,17.0 `` - " - \ No newline at end of file +Write RED Text (size is 1): + ``tft,txtfull,0,0,1,RED,HelloWorld`` + +Write RED Text on YELLOW background (size is 1): + ``tft,txtfull,0,0,1,RED,YELLOW,HelloWorld`` + +Select font : + +- ``tft,font,SEVENSEG24`` +- ``tft,txtfull,0,0,1,RED,17.0`` From bede949bef4b6949d87cee4d62389357ee8f14c0 Mon Sep 17 00:00:00 2001 From: Gijs Noorlander Date: Tue, 4 Aug 2020 13:18:36 +0200 Subject: [PATCH 04/28] [docs] Replace code-block:: html with code-block:: none The HTML parser does not parse our own rules which is where it is used. So until we have a rules highlighter, we use the ``none`` highlighter. --- docs/source/Controller/C014.rst | 2 +- docs/source/Plugin/P000_Buzzer_RTTTL.rst | 10 +- docs/source/Plugin/P000_Level_converter.rst | 4 +- .../Plugin/P000_Piezo_element_RTTTL.rst | 10 +- docs/source/Plugin/P000_Relay.rst | 4 +- docs/source/Plugin/P000_Servo_motor.rst | 4 +- docs/source/Plugin/P000_Speaker_RTTTL.rst | 10 +- docs/source/Plugin/P000_commands.repl | 10 +- docs/source/Plugin/P000_events.repl | 38 +-- docs/source/Plugin/P001_Door_switch.rst | 6 +- docs/source/Plugin/P001_PIR_sensor.rst | 6 +- docs/source/Plugin/P001_Switch.rst | 6 +- docs/source/Plugin/P002_ESP82XX_internal.rst | 4 +- docs/source/Plugin/P002_PCF8591.rst | 6 +- .../Plugin/P002_Photosensitive_resistor.rst | 6 +- docs/source/Plugin/P003_LJ12A3.rst | 4 +- docs/source/Plugin/P003_TCR5000.rst | 6 +- docs/source/Plugin/P003_YFS401.rst | 6 +- docs/source/Plugin/P004_DS18b20.rst | 6 +- docs/source/Plugin/P005_DHT11_DHT22.rst | 6 +- docs/source/Plugin/P006_BMP085.rst | 6 +- docs/source/Plugin/P006_BMP180.rst | 6 +- docs/source/Plugin/P008_Wiegand-26.rst | 6 +- docs/source/Plugin/P009_MCP23017.rst | 6 +- docs/source/Plugin/P010_BH1750.rst | 6 +- docs/source/Plugin/P012_LCD.rst | 6 +- docs/source/Plugin/P013_HC-SR04.rst | 30 +- docs/source/Plugin/P013_RCW-0001.rst | 6 +- docs/source/Plugin/P013_US-100.rst | 6 +- docs/source/Plugin/P014_SI7021_HTU21D.rst | 6 +- docs/source/Plugin/P015_TSL2561.rst | 6 +- docs/source/Plugin/P017_PN532.rst | 6 +- docs/source/Plugin/P018_GP2Y10.rst | 6 +- docs/source/Plugin/P019_PCF8574.rst | 6 +- docs/source/Plugin/P022_PCA9685.rst | 6 +- docs/source/Plugin/P023_OLED.rst | 6 +- docs/source/Plugin/P024_MLX90614.rst | 6 +- docs/source/Plugin/P025_ADS1115.rst | 6 +- docs/source/Plugin/P027_INA219.rst | 6 +- docs/source/Plugin/P028_BME280.rst | 6 +- docs/source/Plugin/P030_BMP280.rst | 6 +- docs/source/Plugin/P031_SHT1X.rst | 6 +- docs/source/Plugin/P032_MS5611.rst | 6 +- docs/source/Plugin/P034_DHT12.rst | 6 +- docs/source/Plugin/P035_IR_transmitter.rst | 6 +- docs/source/Plugin/P045_MPU6050.rst | 6 +- docs/source/Plugin/P046_Ventus_W266.rst | 8 +- .../Plugin/P047_Soil_moisture_sensor.rst | 6 +- .../Plugin/P048_Adafruit_Motor_Shield_v2.rst | 6 +- docs/source/Plugin/P049_MH-Z19.rst | 6 +- docs/source/Plugin/P050_TCS34725.rst | 6 +- docs/source/Plugin/P051_AM2320.rst | 6 +- docs/source/Plugin/P052_S11.rst | 4 +- docs/source/Plugin/P052_S8.rst | 6 +- docs/source/Plugin/P052_tSense_K70.rst | 6 +- docs/source/Plugin/P053_PMSx003.rst | 6 +- docs/source/Plugin/P059_Rotary_Encoder.rst | 6 +- docs/source/Plugin/P063_TTP229-B.rst | 6 +- docs/source/Plugin/P065_DFPlayer_Mini.rst | 6 +- docs/source/Plugin/P065_YX5300.rst | 6 +- docs/source/Plugin/P073_7_Segment.rst | 6 +- docs/source/Plugin/P075_Nextion.rst | 6 +- docs/source/Plugin/P081.rst | 4 +- docs/source/Plugin/P081_events.repl | 2 +- docs/source/Plugin/P082_Neo-6M.rst | 4 +- docs/source/Plugin/P082_Neo-7M.rst | 4 +- docs/source/Plugin/P082_Neo-M8n.rst | 4 +- docs/source/Plugin/P082_events.repl | 6 +- docs/source/Plugin/P086_Homie.rst | 2 +- docs/source/Plugin/P097_events.repl | 6 +- docs/source/Plugin/_plugin_hardware.template | 4 +- docs/source/Rules/Rules.rst | 296 +++++++++--------- 72 files changed, 381 insertions(+), 381 deletions(-) diff --git a/docs/source/Controller/C014.rst b/docs/source/Controller/C014.rst index 9662e9c37..3fbb1ea15 100644 --- a/docs/source/Controller/C014.rst +++ b/docs/source/Controller/C014.rst @@ -79,7 +79,7 @@ Setup - ``homie/%sysname%/$nodes`` enumerating all devices will be sent as final message of the auto-discover header. - The final event log should look like this: -.. code-block:: html +.. code-block:: none 5584 : EVENT: MQTT#Connected 5754 : C014 : autodiscover information of 4 Devices and 6 Nodes sent with no errors! (26 messages) diff --git a/docs/source/Plugin/P000_Buzzer_RTTTL.rst b/docs/source/Plugin/P000_Buzzer_RTTTL.rst index a3ac8a226..11c0d77da 100644 --- a/docs/source/Plugin/P000_Buzzer_RTTTL.rst +++ b/docs/source/Plugin/P000_Buzzer_RTTTL.rst @@ -22,7 +22,7 @@ RTTTL (Ring Tone [Text] Transfer Language) was developed by Nokia to play more " After that the actual tune is then created using standard notes (a, b, c, d, e, f, and g), before the note the duration is set, after the note the octave is set: -.. code-block:: html +.. code-block:: none @@ -35,7 +35,7 @@ Specifications: Wiring ------ -.. code-block:: html +.. code-block:: none ESP Buzzer GPIO <--> I/O @@ -55,7 +55,7 @@ Rules examples From ESP Easy v2.0.0 its possible to play melodies via `RTTTL `_ (don't forget to remove the spaces and replace # with -, this might be fixed in future releases): -.. code-block:: html +.. code-block:: none http:///control?cmd=rtttl,14:d=4,o=5,b=112:8a,8a,a,8a,8a,a,8a,8c6,8f.,16g,2a,8a-,8a-,8a-.,16a-,8a-,8a,8a.,16a,8a,8g,8g,8a,g,c6 @@ -65,7 +65,7 @@ You can also use these from rules. We use it to let our alarm system give feedba To make a boot-sound on startup, create a rule like this: -.. code-block:: html +.. code-block:: none On System#Boot do rtttl,14:d=10,o=6,b=180,c,e,g @@ -75,7 +75,7 @@ To make a boot-sound on startup, create a rule like this: Besides RTTTL it's also possible to play a single tone on a pin, via a buzzer, speaker or piezo element: -.. code-block:: html +.. code-block:: none http:///control?cmd=tone,14,1300,200 diff --git a/docs/source/Plugin/P000_Level_converter.rst b/docs/source/Plugin/P000_Level_converter.rst index 9695e8086..77e64641d 100644 --- a/docs/source/Plugin/P000_Level_converter.rst +++ b/docs/source/Plugin/P000_Level_converter.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p00x.repl +.. include:: ../Plugin/_plugin_substitutions_p00x.repl .. _P000_Level_converter_page: Level converter @@ -43,7 +43,7 @@ Wiring A direct connection by mistake from 5V to a GPIO may fry your ESP! Handle level-shifters with some care. The used FET transistors are somewhat sensible against electrostatic discharge. Use a anti-static mat. -.. code-block:: html +.. code-block:: none ESP Level converter Connected hardware GPIO (X) <--> 3A, LV1, A1 etc. <--> 5A, HV1, B2 etc. diff --git a/docs/source/Plugin/P000_Piezo_element_RTTTL.rst b/docs/source/Plugin/P000_Piezo_element_RTTTL.rst index 1c3daaa17..cd28215a0 100644 --- a/docs/source/Plugin/P000_Piezo_element_RTTTL.rst +++ b/docs/source/Plugin/P000_Piezo_element_RTTTL.rst @@ -22,7 +22,7 @@ RTTTL (Ring Tone [Text] Transfer Language) was developed by Nokia to play more " After that the actual tune is then created using standard notes (a, b, c, d, e, f, and g), before the note the duration is set, after the note the octave is set: -.. code-block:: html +.. code-block:: none @@ -35,7 +35,7 @@ Specifications: Wiring ------ -.. code-block:: html +.. code-block:: none ESP Piezo GPIO <--> Positive (red) @@ -52,7 +52,7 @@ Rules examples From ESP Easy v2.0.0 its possible to play melodies via `RTTTL `_ (don't forget to remove the spaces and replace # with -, this might be fixed in future releases): -.. code-block:: html +.. code-block:: none http:///control?cmd=rtttl,14:d=4,o=5,b=112:8a,8a,a,8a,8a,a,8a,8c6,8f.,16g,2a,8a-,8a-,8a-.,16a-,8a-,8a,8a.,16a,8a,8g,8g,8a,g,c6 @@ -62,7 +62,7 @@ You can also use these from rules. We use it to let our alarm system give feedba To make a boot-sound on startup, create a rule like this: -.. code-block:: html +.. code-block:: none On System#Boot do rtttl,14:d=10,o=6,b=180,c,e,g @@ -72,7 +72,7 @@ To make a boot-sound on startup, create a rule like this: Besides RTTTL it's also possible to play a single tone on a pin, via a buzzer, speaker or piezo element: -.. code-block:: html +.. code-block:: none http:///control?cmd=tone,14,1300,200 diff --git a/docs/source/Plugin/P000_Relay.rst b/docs/source/Plugin/P000_Relay.rst index 25829d3a0..5208426db 100644 --- a/docs/source/Plugin/P000_Relay.rst +++ b/docs/source/Plugin/P000_Relay.rst @@ -179,7 +179,7 @@ have to be figured into your project. It is also possible to use a small SSR to Wiring ------ -.. code-block:: html +.. code-block:: none ESP Relay (board) GPIO (X) <--> IN1 @@ -204,7 +204,7 @@ find more information on how to do this. Rules examples -------------- -.. code-block:: html +.. code-block:: none On Switch#State Do if [Switch#State]=1 diff --git a/docs/source/Plugin/P000_Servo_motor.rst b/docs/source/Plugin/P000_Servo_motor.rst index 5a7cfb410..8f813e3d1 100644 --- a/docs/source/Plugin/P000_Servo_motor.rst +++ b/docs/source/Plugin/P000_Servo_motor.rst @@ -29,7 +29,7 @@ Wiring ------ -.. code-block:: html +.. code-block:: none ESP Servo GPIO (X) <--> S (signal) @@ -48,7 +48,7 @@ find more information on how to do this. Rules examples -------------- -.. code-block:: html +.. code-block:: none On Switch#State Do if [Switch#State]=1 diff --git a/docs/source/Plugin/P000_Speaker_RTTTL.rst b/docs/source/Plugin/P000_Speaker_RTTTL.rst index d4fbad20d..67c860fda 100644 --- a/docs/source/Plugin/P000_Speaker_RTTTL.rst +++ b/docs/source/Plugin/P000_Speaker_RTTTL.rst @@ -22,7 +22,7 @@ RTTTL (Ring Tone [Text] Transfer Language) was developed by Nokia to play more " After that the actual tune is then created using standard notes (a, b, c, d, e, f, and g), before the note the duration is set, after the note the octave is set: -.. code-block:: html +.. code-block:: none @@ -35,7 +35,7 @@ Specifications: Wiring ------ -.. code-block:: html +.. code-block:: none ESP Speaker GPIO <--> Positive (red) @@ -52,7 +52,7 @@ Rules examples From ESP Easy v2.0.0 its possible to play melodies via `RTTTL `_ (don't forget to remove the spaces and replace # with -, this might be fixed in future releases): -.. code-block:: html +.. code-block:: none http:///control?cmd=rtttl,14:d=4,o=5,b=112:8a,8a,a,8a,8a,a,8a,8c6,8f.,16g,2a,8a-,8a-,8a-.,16a-,8a-,8a,8a.,16a,8a,8g,8g,8a,g,c6 @@ -62,7 +62,7 @@ You can also use these from rules. We use it to let our alarm system give feedba To make a boot-sound on startup, create a rule like this: -.. code-block:: html +.. code-block:: none On System#Boot do rtttl,14:d=10,o=6,b=180,c,e,g @@ -72,7 +72,7 @@ To make a boot-sound on startup, create a rule like this: Besides RTTTL it's also possible to play a single tone on a pin, via a buzzer, speaker or piezo element: -.. code-block:: html +.. code-block:: none http:///control?cmd=tone,14,1300,200 diff --git a/docs/source/Plugin/P000_commands.repl b/docs/source/Plugin/P000_commands.repl index 48ddff51d..3ed59d198 100644 --- a/docs/source/Plugin/P000_commands.repl +++ b/docs/source/Plugin/P000_commands.repl @@ -170,7 +170,7 @@ Example output: - .. code-block:: html + .. code-block:: none 4500043 : Info : Command: i2cscanner I2C : Found 0x3c @@ -221,7 +221,7 @@ Example output: - .. code-block:: html + .. code-block:: none 4306250 : Info : Command: meminfo SecurityStruct | 593 @@ -531,7 +531,7 @@ Try to use AsyncEvents where possible, to keep rules processing times low. **Rules example** - .. code-block:: html + .. code-block:: none on SingleEvent do Publish,%sysname%/Info,A single event has been triggered! @@ -544,7 +544,7 @@ Try to use AsyncEvents where possible, to keep rules processing times low. **Rules example** - .. code-block:: html + .. code-block:: none on SingleValue do Publish,%sysname%/Info,An event has been sent (%eventvalue%)! @@ -558,7 +558,7 @@ Try to use AsyncEvents where possible, to keep rules processing times low. **Rules example** - .. code-block:: html + .. code-block:: none on Multi do if %eventvalue4%=99 diff --git a/docs/source/Plugin/P000_events.repl b/docs/source/Plugin/P000_events.repl index 8b8c5d45b..6a661d132 100644 --- a/docs/source/Plugin/P000_events.repl +++ b/docs/source/Plugin/P000_events.repl @@ -7,7 +7,7 @@ As described already, each task can produced one or more events, one for each measured value. You should not name your devices and value names so that the combination equals to any of the below listed system events! "," - .. code-block:: html + .. code-block:: none on DHT11Outside#Temperature>20 do GPIO,2,1 @@ -19,7 +19,7 @@ Triggered after power on. "," - .. code-block:: html + .. code-block:: none on System#Wake do GPIO,15,1 @@ -31,7 +31,7 @@ Triggered at boot time. "," - .. code-block:: html + .. code-block:: none on System#Boot do GPIO,2,1 @@ -44,7 +44,7 @@ Triggered just before the ESP goes to deep sleep. "," - .. code-block:: html + .. code-block:: none on System#Sleep do GPIO,2,0 @@ -56,7 +56,7 @@ Triggered when the ESP has connected to broker. "," - .. code-block:: html + .. code-block:: none on MQTT#Connected do Publish,%sysname%/status,First message! @@ -68,7 +68,7 @@ Triggered when the ESP has disconnected from the broker. "," - .. code-block:: html + .. code-block:: none on MQTT#Disconnected do Reboot @@ -80,7 +80,7 @@ Triggered when the ESP has connected to broker (the MQTT Import plugin uses a separate connection than the generic one). "," - .. code-block:: html + .. code-block:: none on MQTTimport#Connected do Publish,%sysname%/status,MQTT Import is now operational @@ -92,7 +92,7 @@ Triggered when the ESP has disconnected from the broker (the MQTT Import plugin uses a separate connection than the generic one). "," - .. code-block:: html + .. code-block:: none on MQTTimport#Disconnected do Reboot @@ -104,7 +104,7 @@ Triggered when the ESP has connected to Wi-Fi. "," - .. code-block:: html + .. code-block:: none on WiFi#Connected do SendToHTTP,url.com,80,/report.php?hash=123abc456&t=[temp2#out] @@ -116,7 +116,7 @@ Triggered when the ESP has changed to access point, will also trigger first time the unit connects to the Wi-Fi. "," - .. code-block:: html + .. code-block:: none on WiFi#ChangedAccesspoint do Publish,%sysname%/status,AP changed @@ -129,7 +129,7 @@ This has been added in build mega-20190910 "," - .. code-block:: html + .. code-block:: none on WiFi#ChangedWiFichannel do Publish,%sysname%/status,channel changed @@ -143,7 +143,7 @@ N.B. Sending a publish command may not be very useful on this event, since this will mainly happen when there is no WiFi connection. "," - .. code-block:: html + .. code-block:: none on WiFi#APmodeEnabled do ... // Some command @@ -156,7 +156,7 @@ This can happen some time (default 60 seconds) after a WiFi connection has been made. Or disabled using some command. "," - .. code-block:: html + .. code-block:: none on WiFi#APmodeDisabled do Publish,%sysname%/status,AP disabled @@ -168,7 +168,7 @@ Triggered when (someone) has tried to login to a ESP unit with admin password enabled, but have failed to enter correct password. "," - .. code-block:: html + .. code-block:: none on Login#Failed do Publish,%sysname%/warning,Intruder alert! @@ -180,7 +180,7 @@ Triggered the first time (after boot) NTP is updating the unit. "," - .. code-block:: html + .. code-block:: none on Time#Initialized do Publish,%sysname%/Time,%systime% @@ -192,7 +192,7 @@ Triggered when the time is set by an update from NTP. "," - .. code-block:: html + .. code-block:: none on Time#Set do Publish,%sysname%/Time,%systime% @@ -205,7 +205,7 @@ As described already, triggered when a rules timer ends (setting a timer to 0 will disable the timer). "," - .. code-block:: html + .. code-block:: none on Rules#Timer=1 do GPIO,2,1 @@ -217,7 +217,7 @@ Triggered every minute with day and time like: Mon,12:30 or Tue,14:45. You can define triggers on specific days or all days using 'All' for days indicator. You can also use wildcards in the time setting like All,**:00 to run every hour. "," - .. code-block:: html + .. code-block:: none on Clock#Time=All,12:00 do //will run once a day at noon GPIO,2,1 @@ -237,7 +237,7 @@ If the command 'Monitor' is used to monitor a given pin you will receive an event for that GPIO as soon as it's state changes. As seen in the example you can always use the square brackets together with the task/value name of ``Plugin#GPIO#Pinstate#N`` to get the state, but to trigger events you need to add the monitor command (preferably at boot). "," - .. code-block:: html + .. code-block:: none on System#Boot do Monitor GPIO,15 diff --git a/docs/source/Plugin/P001_Door_switch.rst b/docs/source/Plugin/P001_Door_switch.rst index d4684df18..731803b69 100644 --- a/docs/source/Plugin/P001_Door_switch.rst +++ b/docs/source/Plugin/P001_Door_switch.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p00x.repl +.. include:: ../Plugin/_plugin_substitutions_p00x.repl .. _P001_Door_switch_page: Door switch @@ -25,7 +25,7 @@ Specifications: Wiring ------ -.. code-block:: html +.. code-block:: none ESP Door switch GPIO (X) <--> Signal @@ -111,7 +111,7 @@ Indicators/values (recommended settings) Rules examples -------------- -.. code-block:: html +.. code-block:: none on Door#State=1 do timerSet,1,1 diff --git a/docs/source/Plugin/P001_PIR_sensor.rst b/docs/source/Plugin/P001_PIR_sensor.rst index 3d2356f74..f1039485b 100644 --- a/docs/source/Plugin/P001_PIR_sensor.rst +++ b/docs/source/Plugin/P001_PIR_sensor.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p00x.repl +.. include:: ../Plugin/_plugin_substitutions_p00x.repl .. _P001_PIR_sensor_page: PIR sensor @@ -31,7 +31,7 @@ Specifications: Wiring ------ -.. code-block:: html +.. code-block:: none ESP PIR GPIO (X) <--> I/O or OUT @@ -117,7 +117,7 @@ Indicators/values (recommended settings) Rules examples -------------- -.. code-block:: html +.. code-block:: none on Motion#Detection=1 do timerSet,1,1 diff --git a/docs/source/Plugin/P001_Switch.rst b/docs/source/Plugin/P001_Switch.rst index f21726b6a..25d251447 100644 --- a/docs/source/Plugin/P001_Switch.rst +++ b/docs/source/Plugin/P001_Switch.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p00x.repl +.. include:: ../Plugin/_plugin_substitutions_p00x.repl .. _P001_Switch_page: Switch @@ -24,7 +24,7 @@ Specifications: Wiring ------ -.. code-block:: html +.. code-block:: none ESP Switch/button GPIO (X) <--> Signal @@ -128,7 +128,7 @@ Indicators/values (recommended settings) Rules examples -------------- -.. code-block:: html +.. code-block:: none on Switch#State do if [Switch#State]=3 diff --git a/docs/source/Plugin/P002_ESP82XX_internal.rst b/docs/source/Plugin/P002_ESP82XX_internal.rst index 170b8b783..f4aec11e0 100644 --- a/docs/source/Plugin/P002_ESP82XX_internal.rst +++ b/docs/source/Plugin/P002_ESP82XX_internal.rst @@ -21,7 +21,7 @@ Wiring ------ -.. code-block:: html +.. code-block:: none ESP S8 GPIO (X) <--> TX @@ -41,7 +41,7 @@ Setup Rules examples -------------- -.. code-block:: html +.. code-block:: none //Code below... diff --git a/docs/source/Plugin/P002_PCF8591.rst b/docs/source/Plugin/P002_PCF8591.rst index 0c4087234..ba455d43e 100644 --- a/docs/source/Plugin/P002_PCF8591.rst +++ b/docs/source/Plugin/P002_PCF8591.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p00x.repl +.. include:: ../Plugin/_plugin_substitutions_p00x.repl .. _P002_PCF8591_page: PCF8591 @@ -20,7 +20,7 @@ Wiring ------ -.. code-block:: html +.. code-block:: none ESP S8 GPIO (X) <--> TX @@ -40,7 +40,7 @@ Setup Rules examples -------------- -.. code-block:: html +.. code-block:: none //Code below... diff --git a/docs/source/Plugin/P002_Photosensitive_resistor.rst b/docs/source/Plugin/P002_Photosensitive_resistor.rst index f177bd5a0..672c4a68f 100644 --- a/docs/source/Plugin/P002_Photosensitive_resistor.rst +++ b/docs/source/Plugin/P002_Photosensitive_resistor.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p00x.repl +.. include:: ../Plugin/_plugin_substitutions_p00x.repl .. _P002_Photosensitive_resistor_page: Photosensitive resistor @@ -21,7 +21,7 @@ Wiring ------ -.. code-block:: html +.. code-block:: none ESP S8 GPIO (X) <--> TX @@ -41,7 +41,7 @@ Setup Rules examples -------------- -.. code-block:: html +.. code-block:: none //Code below... diff --git a/docs/source/Plugin/P003_LJ12A3.rst b/docs/source/Plugin/P003_LJ12A3.rst index 4046fcc63..cb6b5ebd5 100644 --- a/docs/source/Plugin/P003_LJ12A3.rst +++ b/docs/source/Plugin/P003_LJ12A3.rst @@ -28,7 +28,7 @@ Specifications: Wiring ------ -.. code-block:: html +.. code-block:: none ESP LJ12A3 GPIO (14) <--> Signal (black) @@ -125,7 +125,7 @@ Indicators (recommended settings) Rules examples -------------- -.. code-block:: html +.. code-block:: none On WaterMeter#mL Do If [WaterMeter#mL]=0 diff --git a/docs/source/Plugin/P003_TCR5000.rst b/docs/source/Plugin/P003_TCR5000.rst index e9f9c6487..f24d63512 100644 --- a/docs/source/Plugin/P003_TCR5000.rst +++ b/docs/source/Plugin/P003_TCR5000.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p00x.repl +.. include:: ../Plugin/_plugin_substitutions_p00x.repl .. _P003_TCR5000_page: TCR5000 @@ -25,7 +25,7 @@ Specifications: Wiring ------ -.. code-block:: html +.. code-block:: none ESP TCR5000 GPIO (14) <--> S @@ -97,7 +97,7 @@ Indicators (recommended settings) Rules examples -------------- -.. code-block:: html +.. code-block:: none On PowerMeter#W Do If [PowerMeter#W]>1500 diff --git a/docs/source/Plugin/P003_YFS401.rst b/docs/source/Plugin/P003_YFS401.rst index eef9fde94..7b744242b 100644 --- a/docs/source/Plugin/P003_YFS401.rst +++ b/docs/source/Plugin/P003_YFS401.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p00x.repl +.. include:: ../Plugin/_plugin_substitutions_p00x.repl .. _P003_YFS401_page: YFS401 @@ -27,7 +27,7 @@ Specifications: Wiring ------ -.. code-block:: html +.. code-block:: none ESP YFS401 GPIO (14) <--> Signal (yellow) @@ -104,7 +104,7 @@ Indicators (recommended settings) Rules examples -------------- -.. code-block:: html +.. code-block:: none On WaterMeter#mL Do If [WaterMeter#mL]=0 diff --git a/docs/source/Plugin/P004_DS18b20.rst b/docs/source/Plugin/P004_DS18b20.rst index 1ff49ee3c..7c24362b1 100644 --- a/docs/source/Plugin/P004_DS18b20.rst +++ b/docs/source/Plugin/P004_DS18b20.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p00x.repl +.. include:: ../Plugin/_plugin_substitutions_p00x.repl .. _P004_DS18b20_page: DS18b20 @@ -32,7 +32,7 @@ Wiring .. image:: P004_DS18b20_5.jpg -.. code-block:: html +.. code-block:: none ESP DS18b20 GPIO (14) <--> 1-wire/D (yellow) @@ -106,7 +106,7 @@ Indicators (recommended settings) Rules examples -------------- -.. code-block:: html +.. code-block:: none On Temp1#Celsius Do If [Temp1#Celsius]>37 diff --git a/docs/source/Plugin/P005_DHT11_DHT22.rst b/docs/source/Plugin/P005_DHT11_DHT22.rst index f4e7dbb40..395ad28f2 100644 --- a/docs/source/Plugin/P005_DHT11_DHT22.rst +++ b/docs/source/Plugin/P005_DHT11_DHT22.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p00x.repl +.. include:: ../Plugin/_plugin_substitutions_p00x.repl .. _P005_DHT11_DHT22_page: DHT11/DHT22 @@ -35,7 +35,7 @@ DHT11 (blue) DHT22 (white) -.. code-block:: html +.. code-block:: none ESP DHT11/DHT22 GPIO (X) <--> Data (2nd pin) @@ -89,7 +89,7 @@ Indicators (recommended settings) Rules examples -------------- -.. code-block:: html +.. code-block:: none On DHT#Celsius Do If [DHT#Celsius]>-15 diff --git a/docs/source/Plugin/P006_BMP085.rst b/docs/source/Plugin/P006_BMP085.rst index 758212b7a..7a153feba 100644 --- a/docs/source/Plugin/P006_BMP085.rst +++ b/docs/source/Plugin/P006_BMP085.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p00x.repl +.. include:: ../Plugin/_plugin_substitutions_p00x.repl .. _P006_BMP085_page: BMP085 @@ -20,7 +20,7 @@ Specifications: Wiring ------ -.. code-block:: html +.. code-block:: none ESP BMP085 GPIO (4) <--> DA (SDA) @@ -73,7 +73,7 @@ Indicators (recommended settings) Rules examples -------------- -.. code-block:: html +.. code-block:: none on BMP#hPa do if BMP#hPa>999 diff --git a/docs/source/Plugin/P006_BMP180.rst b/docs/source/Plugin/P006_BMP180.rst index 74d047f66..158696ae3 100644 --- a/docs/source/Plugin/P006_BMP180.rst +++ b/docs/source/Plugin/P006_BMP180.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p00x.repl +.. include:: ../Plugin/_plugin_substitutions_p00x.repl .. _P006_BMP180_page: BMP180 @@ -20,7 +20,7 @@ Specifications: Wiring ------ -.. code-block:: html +.. code-block:: none ESP BMP180 GPIO (4) <--> DA (SDA) @@ -73,7 +73,7 @@ Indicators (recommended settings) Rules examples -------------- -.. code-block:: html +.. code-block:: none on BMP#hPa do if BMP#hPa>999 diff --git a/docs/source/Plugin/P008_Wiegand-26.rst b/docs/source/Plugin/P008_Wiegand-26.rst index 4c1f19550..609732373 100644 --- a/docs/source/Plugin/P008_Wiegand-26.rst +++ b/docs/source/Plugin/P008_Wiegand-26.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p00x.repl +.. include:: ../Plugin/_plugin_substitutions_p00x.repl .. _P008_Wiegand-26_page: Wiegand-26 @@ -20,7 +20,7 @@ Wiring ------ -.. code-block:: html +.. code-block:: none ESP S8 GPIO (X) <--> TX @@ -40,7 +40,7 @@ Setup Rules examples -------------- -.. code-block:: html +.. code-block:: none //Code below... diff --git a/docs/source/Plugin/P009_MCP23017.rst b/docs/source/Plugin/P009_MCP23017.rst index 1644e84fc..89aab2628 100644 --- a/docs/source/Plugin/P009_MCP23017.rst +++ b/docs/source/Plugin/P009_MCP23017.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p00x.repl +.. include:: ../Plugin/_plugin_substitutions_p00x.repl .. _P009_MCP23017_page: MCP23017 @@ -20,7 +20,7 @@ Wiring ------ -.. code-block:: html +.. code-block:: none ESP S8 GPIO (X) <--> TX @@ -40,7 +40,7 @@ Setup Rules examples -------------- -.. code-block:: html +.. code-block:: none //Code below... diff --git a/docs/source/Plugin/P010_BH1750.rst b/docs/source/Plugin/P010_BH1750.rst index 78920a91f..b1a0adbe3 100644 --- a/docs/source/Plugin/P010_BH1750.rst +++ b/docs/source/Plugin/P010_BH1750.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p01x.repl +.. include:: ../Plugin/_plugin_substitutions_p01x.repl .. _P010_BH1750_page: BH1750 @@ -20,7 +20,7 @@ Wiring ------ -.. code-block:: html +.. code-block:: none ESP S8 GPIO (X) <--> TX @@ -40,7 +40,7 @@ Setup Rules examples -------------- -.. code-block:: html +.. code-block:: none //Code below... diff --git a/docs/source/Plugin/P012_LCD.rst b/docs/source/Plugin/P012_LCD.rst index f244eb79d..05884dcec 100644 --- a/docs/source/Plugin/P012_LCD.rst +++ b/docs/source/Plugin/P012_LCD.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p01x.repl +.. include:: ../Plugin/_plugin_substitutions_p01x.repl .. _P012_LCD_page: LCD @@ -20,7 +20,7 @@ Wiring ------ -.. code-block:: html +.. code-block:: none ESP S8 GPIO (X) <--> TX @@ -40,7 +40,7 @@ Setup Rules examples -------------- -.. code-block:: html +.. code-block:: none //Code below... diff --git a/docs/source/Plugin/P013_HC-SR04.rst b/docs/source/Plugin/P013_HC-SR04.rst index a1c467568..d00a57f99 100644 --- a/docs/source/Plugin/P013_HC-SR04.rst +++ b/docs/source/Plugin/P013_HC-SR04.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p01x.repl +.. include:: ../Plugin/_plugin_substitutions_p01x.repl .. _P013_HC-SR04_page: HC-SR04 @@ -26,15 +26,15 @@ Wiring The HC-SR04 must be run on 5V. Running it with 3.3V will show very weak results or nothing at all. -.. code-block:: html +.. code-block:: none - ESP HC-SR04 - GPIO (X) <--> Trigger - GPIO (X) <--> Echo (voltage divider!) - - Power - 5.0V <--> VCC - GND <--> GND + ESP HC-SR04 + GPIO (X) <--> Trigger + GPIO (X) <--> Echo (voltage divider!) + + Power + 5.0V <--> VCC + GND <--> GND .. warning:: Do not connect 5V directly to the ESP! Read more about :ref:`P000_Level_converter_page`. @@ -89,14 +89,14 @@ Indicators (recommended settings) Rules examples -------------- -.. code-block:: html +.. code-block:: on HCSR04#distance do - if [HCSR04#distance]<50 and [HCSR04#distance]>45 - GPIO,14,1 //light up LED to show that you're at the correct position - else - GPIO,14,0 - endif + if [HCSR04#distance]<50 and [HCSR04#distance]>45 + GPIO,14,1 //light up LED to show that you're at the correct position + else + GPIO,14,0 + endif endon .. Commands available diff --git a/docs/source/Plugin/P013_RCW-0001.rst b/docs/source/Plugin/P013_RCW-0001.rst index 53eabcbcd..715539758 100644 --- a/docs/source/Plugin/P013_RCW-0001.rst +++ b/docs/source/Plugin/P013_RCW-0001.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p01x.repl +.. include:: ../Plugin/_plugin_substitutions_p01x.repl .. _P013_RCW-0001_page: RCW-0001 @@ -26,7 +26,7 @@ Wiring The RCW-0001 must be run on 5V. Running it with 3.3V will show very weak results or nothing at all. -.. code-block:: html +.. code-block:: none ESP RCW-0001 GPIO (X) <--> Trigger @@ -89,7 +89,7 @@ Indicators (recommended settings) Rules examples -------------- -.. code-block:: html +.. code-block:: on RCW0001#distance do if [RCW0001#distance]<50 and [RCW0001#distance]>45 diff --git a/docs/source/Plugin/P013_US-100.rst b/docs/source/Plugin/P013_US-100.rst index d99cff0f9..b2594f805 100644 --- a/docs/source/Plugin/P013_US-100.rst +++ b/docs/source/Plugin/P013_US-100.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p01x.repl +.. include:: ../Plugin/_plugin_substitutions_p01x.repl .. _P013_US-100_page: US-100 @@ -26,7 +26,7 @@ Wiring The US-100 must be run on 5V. Running it with 3.3V will show very weak results or nothing at all. -.. code-block:: html +.. code-block:: none ESP US-100 GPIO (X) <--> Trigger @@ -89,7 +89,7 @@ Indicators (recommended settings) Rules examples -------------- -.. code-block:: html +.. code-block:: on US100#distance do if [US100#distance]<50 and [US100#distance]>45 diff --git a/docs/source/Plugin/P014_SI7021_HTU21D.rst b/docs/source/Plugin/P014_SI7021_HTU21D.rst index bced501ef..1e14966e2 100644 --- a/docs/source/Plugin/P014_SI7021_HTU21D.rst +++ b/docs/source/Plugin/P014_SI7021_HTU21D.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p01x.repl +.. include:: ../Plugin/_plugin_substitutions_p01x.repl .. _P014_SI7021_HTU21D_page: SI7021/HTU21D @@ -21,7 +21,7 @@ Wiring ------ -.. code-block:: html +.. code-block:: none ESP S8 GPIO (X) <--> TX @@ -41,7 +41,7 @@ Setup Rules examples -------------- -.. code-block:: html +.. code-block:: none //Code below... diff --git a/docs/source/Plugin/P015_TSL2561.rst b/docs/source/Plugin/P015_TSL2561.rst index 2c370ef50..9f91f2593 100644 --- a/docs/source/Plugin/P015_TSL2561.rst +++ b/docs/source/Plugin/P015_TSL2561.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p01x.repl +.. include:: ../Plugin/_plugin_substitutions_p01x.repl .. _P015_TSL2561_page: TSL2561 @@ -31,7 +31,7 @@ Wiring .. image:: P015_TSL2561_2.jpg -.. code-block:: html +.. code-block:: none ESP TSL2561 GPIO (5) <--> SCL @@ -94,7 +94,7 @@ Indicators (recommended settings) Rules examples -------------- -.. code-block:: html +.. code-block:: none on Light#VI_Lux do if Light#VI_Lux>100 diff --git a/docs/source/Plugin/P017_PN532.rst b/docs/source/Plugin/P017_PN532.rst index f27745ac2..dc27c1ca6 100644 --- a/docs/source/Plugin/P017_PN532.rst +++ b/docs/source/Plugin/P017_PN532.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p01x.repl +.. include:: ../Plugin/_plugin_substitutions_p01x.repl .. _P017_PN532_page: PN532 @@ -20,7 +20,7 @@ Wiring ------ -.. code-block:: html +.. code-block:: none ESP S8 GPIO (X) <--> TX @@ -40,7 +40,7 @@ Setup Rules examples -------------- -.. code-block:: html +.. code-block:: none //Code below... diff --git a/docs/source/Plugin/P018_GP2Y10.rst b/docs/source/Plugin/P018_GP2Y10.rst index 341d6b87f..1039d7b80 100644 --- a/docs/source/Plugin/P018_GP2Y10.rst +++ b/docs/source/Plugin/P018_GP2Y10.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p01x.repl +.. include:: ../Plugin/_plugin_substitutions_p01x.repl .. _P018_GP2Y10_page: GP2Y10 @@ -20,7 +20,7 @@ Wiring ------ -.. code-block:: html +.. code-block:: none ESP S8 GPIO (X) <--> TX @@ -40,7 +40,7 @@ Setup Rules examples -------------- -.. code-block:: html +.. code-block:: none //Code below... diff --git a/docs/source/Plugin/P019_PCF8574.rst b/docs/source/Plugin/P019_PCF8574.rst index fe66509eb..a7f1b91bb 100644 --- a/docs/source/Plugin/P019_PCF8574.rst +++ b/docs/source/Plugin/P019_PCF8574.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p01x.repl +.. include:: ../Plugin/_plugin_substitutions_p01x.repl .. _P019_PCF8574_page: PCF8574 @@ -20,7 +20,7 @@ Wiring ------ -.. code-block:: html +.. code-block:: none ESP S8 GPIO (X) <--> TX @@ -40,7 +40,7 @@ Setup Rules examples -------------- -.. code-block:: html +.. code-block:: none //Code below... diff --git a/docs/source/Plugin/P022_PCA9685.rst b/docs/source/Plugin/P022_PCA9685.rst index 4d07cb39e..a1bb62f83 100644 --- a/docs/source/Plugin/P022_PCA9685.rst +++ b/docs/source/Plugin/P022_PCA9685.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p02x.repl +.. include:: ../Plugin/_plugin_substitutions_p02x.repl .. _P022_PCA9685_page: PCA9685 @@ -20,7 +20,7 @@ Wiring ------ -.. code-block:: html +.. code-block:: none ESP S8 GPIO (X) <--> TX @@ -40,7 +40,7 @@ Setup Rules examples -------------- -.. code-block:: html +.. code-block:: none //Code below... diff --git a/docs/source/Plugin/P023_OLED.rst b/docs/source/Plugin/P023_OLED.rst index f002df965..48ffc1db0 100644 --- a/docs/source/Plugin/P023_OLED.rst +++ b/docs/source/Plugin/P023_OLED.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p02x.repl +.. include:: ../Plugin/_plugin_substitutions_p02x.repl .. _P023_OLED_page: OLED @@ -20,7 +20,7 @@ Wiring ------ -.. code-block:: html +.. code-block:: none ESP S8 GPIO (X) <--> TX @@ -40,7 +40,7 @@ Setup Rules examples -------------- -.. code-block:: html +.. code-block:: none //Code below... diff --git a/docs/source/Plugin/P024_MLX90614.rst b/docs/source/Plugin/P024_MLX90614.rst index 3cc5ab4cc..bc85d57c7 100644 --- a/docs/source/Plugin/P024_MLX90614.rst +++ b/docs/source/Plugin/P024_MLX90614.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p02x.repl +.. include:: ../Plugin/_plugin_substitutions_p02x.repl .. _P024_MLX90614_page: MLX90614 @@ -21,7 +21,7 @@ Specifications: Wiring ------ -.. code-block:: html +.. code-block:: none ESP MLX90614 GPIO (4) <--> SDA @@ -71,7 +71,7 @@ Indicators (recommended settings) Rules examples -------------- -.. code-block:: html +.. code-block:: none on Temp#C do if [Temp#C]>300 diff --git a/docs/source/Plugin/P025_ADS1115.rst b/docs/source/Plugin/P025_ADS1115.rst index 11303f6c9..ec730736c 100644 --- a/docs/source/Plugin/P025_ADS1115.rst +++ b/docs/source/Plugin/P025_ADS1115.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p02x.repl +.. include:: ../Plugin/_plugin_substitutions_p02x.repl .. _P025_ADS1115_page: ADS1115 @@ -20,7 +20,7 @@ Wiring ------ -.. code-block:: html +.. code-block:: none ESP S8 GPIO (X) <--> TX @@ -40,7 +40,7 @@ Setup Rules examples -------------- -.. code-block:: html +.. code-block:: none //Code below... diff --git a/docs/source/Plugin/P027_INA219.rst b/docs/source/Plugin/P027_INA219.rst index fcbf8eed7..4b1991746 100644 --- a/docs/source/Plugin/P027_INA219.rst +++ b/docs/source/Plugin/P027_INA219.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p02x.repl +.. include:: ../Plugin/_plugin_substitutions_p02x.repl .. _P027_INA219_page: INA219 @@ -21,7 +21,7 @@ Wiring ------ -.. code-block:: html +.. code-block:: none ESP S8 GPIO (X) <--> TX @@ -41,7 +41,7 @@ Setup Rules examples -------------- -.. code-block:: html +.. code-block:: none //Code below... diff --git a/docs/source/Plugin/P028_BME280.rst b/docs/source/Plugin/P028_BME280.rst index ebc539608..58a4e20b0 100644 --- a/docs/source/Plugin/P028_BME280.rst +++ b/docs/source/Plugin/P028_BME280.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p02x.repl +.. include:: ../Plugin/_plugin_substitutions_p02x.repl .. _P028_BME280_page: BME280 @@ -24,7 +24,7 @@ Specifications: Wiring ------ -.. code-block:: html +.. code-block:: none ESP BME280 GPIO (4) <--> SDA @@ -75,7 +75,7 @@ Indicators (recommended settings) Rules examples -------------- -.. code-block:: html +.. code-block:: none on BME#hPa do if BME#hPa>999 diff --git a/docs/source/Plugin/P030_BMP280.rst b/docs/source/Plugin/P030_BMP280.rst index 8f6e58801..467592d48 100644 --- a/docs/source/Plugin/P030_BMP280.rst +++ b/docs/source/Plugin/P030_BMP280.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p03x.repl +.. include:: ../Plugin/_plugin_substitutions_p03x.repl .. _P030_BMP280_page: BMP280 @@ -23,7 +23,7 @@ Specifications: Wiring ------ -.. code-block:: html +.. code-block:: none ESP BMP280 GPIO (4) <--> SDI (SDA) @@ -77,7 +77,7 @@ Indicators (recommended settings) Rules examples -------------- -.. code-block:: html +.. code-block:: none on BMP#hPa do if BMP#hPa>999 diff --git a/docs/source/Plugin/P031_SHT1X.rst b/docs/source/Plugin/P031_SHT1X.rst index 40923d120..77ee233d8 100644 --- a/docs/source/Plugin/P031_SHT1X.rst +++ b/docs/source/Plugin/P031_SHT1X.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p03x.repl +.. include:: ../Plugin/_plugin_substitutions_p03x.repl .. _P031_SHT1X_page: SHT1X @@ -21,7 +21,7 @@ Wiring ------ -.. code-block:: html +.. code-block:: none ESP S8 GPIO (X) <--> TX @@ -52,7 +52,7 @@ With a delay of 10, on the same node, the clock signal is about 40 kHz. Rules examples -------------- -.. code-block:: html +.. code-block:: none //Code below... diff --git a/docs/source/Plugin/P032_MS5611.rst b/docs/source/Plugin/P032_MS5611.rst index 057bbec09..7616bbd0e 100644 --- a/docs/source/Plugin/P032_MS5611.rst +++ b/docs/source/Plugin/P032_MS5611.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p03x.repl +.. include:: ../Plugin/_plugin_substitutions_p03x.repl .. _P032_MS5611_page: MS5611 @@ -21,7 +21,7 @@ Wiring ------ -.. code-block:: html +.. code-block:: none ESP S8 GPIO (X) <--> TX @@ -41,7 +41,7 @@ Setup Rules examples -------------- -.. code-block:: html +.. code-block:: none //Code below... diff --git a/docs/source/Plugin/P034_DHT12.rst b/docs/source/Plugin/P034_DHT12.rst index 75b1ff0be..1cf2e3044 100644 --- a/docs/source/Plugin/P034_DHT12.rst +++ b/docs/source/Plugin/P034_DHT12.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p03x.repl +.. include:: ../Plugin/_plugin_substitutions_p03x.repl .. _P034_DHT12_page: DHT12 @@ -21,7 +21,7 @@ Wiring ------ -.. code-block:: html +.. code-block:: none ESP S8 GPIO (X) <--> TX @@ -41,7 +41,7 @@ Setup Rules examples -------------- -.. code-block:: html +.. code-block:: none //Code below... diff --git a/docs/source/Plugin/P035_IR_transmitter.rst b/docs/source/Plugin/P035_IR_transmitter.rst index b57c4e404..6fb7c1464 100644 --- a/docs/source/Plugin/P035_IR_transmitter.rst +++ b/docs/source/Plugin/P035_IR_transmitter.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p03x.repl +.. include:: ../Plugin/_plugin_substitutions_p03x.repl .. _P035_IR_transmitter_page: IR transmitter @@ -20,7 +20,7 @@ Wiring ------ -.. code-block:: html +.. code-block:: none ESP S8 GPIO (X) <--> TX @@ -40,7 +40,7 @@ Setup Rules examples -------------- -.. code-block:: html +.. code-block:: none //Code below... diff --git a/docs/source/Plugin/P045_MPU6050.rst b/docs/source/Plugin/P045_MPU6050.rst index aa079aea2..c9a79ca26 100644 --- a/docs/source/Plugin/P045_MPU6050.rst +++ b/docs/source/Plugin/P045_MPU6050.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p04x.repl +.. include:: ../Plugin/_plugin_substitutions_p04x.repl .. _P045_MPU6050_page: MPU6050 @@ -20,7 +20,7 @@ Wiring ------ -.. code-block:: html +.. code-block:: none ESP S8 GPIO (X) <--> TX @@ -40,7 +40,7 @@ Setup Rules examples -------------- -.. code-block:: html +.. code-block:: none //Code below... diff --git a/docs/source/Plugin/P046_Ventus_W266.rst b/docs/source/Plugin/P046_Ventus_W266.rst index 50b49d131..9ca4f8a2d 100644 --- a/docs/source/Plugin/P046_Ventus_W266.rst +++ b/docs/source/Plugin/P046_Ventus_W266.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p04x.repl +.. include:: ../Plugin/_plugin_substitutions_p04x.repl .. _P046_Ventus_W266_page: Ventus W266 @@ -42,7 +42,7 @@ Specifications (indoor unit): of the normal builds. If you want to compile ESP Easy with this plugin enabled you can set the following line in your platformio.ini: - .. code-block:: html + .. code-block:: none [platformio] env_default = hard_Ventus_W266 @@ -70,7 +70,7 @@ Wiring .. image:: P046_Ventus_W266_2.jpg -.. code-block:: html +.. code-block:: none ESP Ventus GPIO (4) <--> MOSI @@ -169,7 +169,7 @@ Indicators (recommended settings) Rules examples -------------- -.. code-block:: html +.. code-block:: none on VentusW266#UV do if [VentusW266#UV]>9 diff --git a/docs/source/Plugin/P047_Soil_moisture_sensor.rst b/docs/source/Plugin/P047_Soil_moisture_sensor.rst index 7fe9f20d1..239822f9e 100644 --- a/docs/source/Plugin/P047_Soil_moisture_sensor.rst +++ b/docs/source/Plugin/P047_Soil_moisture_sensor.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p04x.repl +.. include:: ../Plugin/_plugin_substitutions_p04x.repl .. _P047_Soil_moisture_sensor_page: Soil moisture sensor @@ -22,7 +22,7 @@ Wiring ------ -.. code-block:: html +.. code-block:: none ESP S8 GPIO (X) <--> TX @@ -42,7 +42,7 @@ Setup Rules examples -------------- -.. code-block:: html +.. code-block:: none //Code below... diff --git a/docs/source/Plugin/P048_Adafruit_Motor_Shield_v2.rst b/docs/source/Plugin/P048_Adafruit_Motor_Shield_v2.rst index 8ae786b48..f077b9a10 100644 --- a/docs/source/Plugin/P048_Adafruit_Motor_Shield_v2.rst +++ b/docs/source/Plugin/P048_Adafruit_Motor_Shield_v2.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p04x.repl +.. include:: ../Plugin/_plugin_substitutions_p04x.repl .. _P048_Adafruit_Motor_Shield_v2_page: Adafruit Motor Shield v2 @@ -20,7 +20,7 @@ Wiring ------ -.. code-block:: html +.. code-block:: none ESP S8 GPIO (X) <--> TX @@ -40,7 +40,7 @@ Setup Rules examples -------------- -.. code-block:: html +.. code-block:: none //Code below... diff --git a/docs/source/Plugin/P049_MH-Z19.rst b/docs/source/Plugin/P049_MH-Z19.rst index 4c6790324..f60c17592 100644 --- a/docs/source/Plugin/P049_MH-Z19.rst +++ b/docs/source/Plugin/P049_MH-Z19.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p04x.repl +.. include:: ../Plugin/_plugin_substitutions_p04x.repl .. _P049_MH-Z19_page: MH-Z19 @@ -22,7 +22,7 @@ Wiring ------ -.. code-block:: html +.. code-block:: none ESP S8 GPIO (X) <--> TX @@ -42,7 +42,7 @@ Setup Rules examples -------------- -.. code-block:: html +.. code-block:: none //Code below... diff --git a/docs/source/Plugin/P050_TCS34725.rst b/docs/source/Plugin/P050_TCS34725.rst index 2d23e11a1..a4aebe38a 100644 --- a/docs/source/Plugin/P050_TCS34725.rst +++ b/docs/source/Plugin/P050_TCS34725.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p05x.repl +.. include:: ../Plugin/_plugin_substitutions_p05x.repl .. _P050_TCS34725_page: TCS34725 @@ -20,7 +20,7 @@ Wiring ------ -.. code-block:: html +.. code-block:: none ESP S8 GPIO (X) <--> TX @@ -40,7 +40,7 @@ Setup Rules examples -------------- -.. code-block:: html +.. code-block:: none //Code below... diff --git a/docs/source/Plugin/P051_AM2320.rst b/docs/source/Plugin/P051_AM2320.rst index 1111987ac..9da70752f 100644 --- a/docs/source/Plugin/P051_AM2320.rst +++ b/docs/source/Plugin/P051_AM2320.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p05x.repl +.. include:: ../Plugin/_plugin_substitutions_p05x.repl .. _P051_AM2320_page: AM2320 @@ -21,7 +21,7 @@ Wiring ------ -.. code-block:: html +.. code-block:: none ESP S8 GPIO (X) <--> TX @@ -41,7 +41,7 @@ Setup Rules examples -------------- -.. code-block:: html +.. code-block:: none //Code below... diff --git a/docs/source/Plugin/P052_S11.rst b/docs/source/Plugin/P052_S11.rst index f4a294817..b1041b4d3 100644 --- a/docs/source/Plugin/P052_S11.rst +++ b/docs/source/Plugin/P052_S11.rst @@ -28,7 +28,7 @@ Wiring Circuit wiring (S11 seen from top, with the 5 holes left and the 4 holes right): -.. code-block:: html +.. code-block:: none ESP S11 (UART) GPIO (14) <--> TX (5th hole of the 5 holes) @@ -106,7 +106,7 @@ Indicators (recommended settings) Rules examples -------------- -.. code-block:: html +.. code-block:: none on CO2#Level do if [CO2#Level]>2000 diff --git a/docs/source/Plugin/P052_S8.rst b/docs/source/Plugin/P052_S8.rst index 5f79afd56..f9239ddba 100644 --- a/docs/source/Plugin/P052_S8.rst +++ b/docs/source/Plugin/P052_S8.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p05x.repl +.. include:: ../Plugin/_plugin_substitutions_p05x.repl .. _P052_S8_page: S8 @@ -28,7 +28,7 @@ Wiring Circuit wiring (S8 seen from PCB side, with the 5 vias down and the 4 vias up): -.. code-block:: html +.. code-block:: none ESP S8 GPIO (7) <--> TX (3rd via [middle] of the 5 vias) @@ -95,7 +95,7 @@ Indicators (recommended settings) Rules examples -------------- -.. code-block:: html +.. code-block:: none on CO2#Level do if [CO2#Level]>2000 diff --git a/docs/source/Plugin/P052_tSense_K70.rst b/docs/source/Plugin/P052_tSense_K70.rst index fed48c56d..b70fb6110 100644 --- a/docs/source/Plugin/P052_tSense_K70.rst +++ b/docs/source/Plugin/P052_tSense_K70.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p05x.repl +.. include:: ../Plugin/_plugin_substitutions_p05x.repl .. _P052_tSense_K70_page: tSense (K70) @@ -36,7 +36,7 @@ Specifications: Wiring ------ -.. code-block:: html +.. code-block:: none ESP tSense GPIO (7) <--> TX (see pictures further down) @@ -140,7 +140,7 @@ Indicators (recommended settings) Rules examples -------------- -.. code-block:: html +.. code-block:: none on CO2#Level do if [CO2#Level]>2000 diff --git a/docs/source/Plugin/P053_PMSx003.rst b/docs/source/Plugin/P053_PMSx003.rst index 6b1b355ae..11adc4593 100644 --- a/docs/source/Plugin/P053_PMSx003.rst +++ b/docs/source/Plugin/P053_PMSx003.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p05x.repl +.. include:: ../Plugin/_plugin_substitutions_p05x.repl .. _P053_PMSx003_page: PMSx003 @@ -20,7 +20,7 @@ Wiring ------ -.. code-block:: html +.. code-block:: none ESP S8 GPIO (X) <--> TX @@ -40,7 +40,7 @@ Setup Rules examples -------------- -.. code-block:: html +.. code-block:: none //Code below... diff --git a/docs/source/Plugin/P059_Rotary_Encoder.rst b/docs/source/Plugin/P059_Rotary_Encoder.rst index 689009c45..5b6dea882 100644 --- a/docs/source/Plugin/P059_Rotary_Encoder.rst +++ b/docs/source/Plugin/P059_Rotary_Encoder.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p05x.repl +.. include:: ../Plugin/_plugin_substitutions_p05x.repl .. _P059_Rotary_Encoder_page: Rotary Encoder @@ -20,7 +20,7 @@ Wiring ------ -.. code-block:: html +.. code-block:: none ESP S8 GPIO (X) <--> TX @@ -40,7 +40,7 @@ Setup Rules examples -------------- -.. code-block:: html +.. code-block:: none //Code below... diff --git a/docs/source/Plugin/P063_TTP229-B.rst b/docs/source/Plugin/P063_TTP229-B.rst index 379dc07b7..c13b4efc3 100644 --- a/docs/source/Plugin/P063_TTP229-B.rst +++ b/docs/source/Plugin/P063_TTP229-B.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p06x.repl +.. include:: ../Plugin/_plugin_substitutions_p06x.repl .. _P063_TTP229-B_page: TTP229-B @@ -20,7 +20,7 @@ Wiring ------ -.. code-block:: html +.. code-block:: none ESP S8 GPIO (X) <--> TX @@ -40,7 +40,7 @@ Setup Rules examples -------------- -.. code-block:: html +.. code-block:: none //Code below... diff --git a/docs/source/Plugin/P065_DFPlayer_Mini.rst b/docs/source/Plugin/P065_DFPlayer_Mini.rst index 369b87667..a147534e4 100644 --- a/docs/source/Plugin/P065_DFPlayer_Mini.rst +++ b/docs/source/Plugin/P065_DFPlayer_Mini.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p06x.repl +.. include:: ../Plugin/_plugin_substitutions_p06x.repl .. _P065_DFPlayer_Mini_page: DFPlayer Mini @@ -20,7 +20,7 @@ Wiring ------ -.. code-block:: html +.. code-block:: none ESP S8 GPIO (X) <--> TX @@ -40,7 +40,7 @@ Setup Rules examples -------------- -.. code-block:: html +.. code-block:: none //Code below... diff --git a/docs/source/Plugin/P065_YX5300.rst b/docs/source/Plugin/P065_YX5300.rst index 441cb3c3b..c85cd6fd4 100644 --- a/docs/source/Plugin/P065_YX5300.rst +++ b/docs/source/Plugin/P065_YX5300.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p06x.repl +.. include:: ../Plugin/_plugin_substitutions_p06x.repl .. _P065_YX5300_page: YX5300 @@ -20,7 +20,7 @@ Wiring ------ -.. code-block:: html +.. code-block:: none ESP S8 GPIO (X) <--> TX @@ -40,7 +40,7 @@ Setup Rules examples -------------- -.. code-block:: html +.. code-block:: none //Code below... diff --git a/docs/source/Plugin/P073_7_Segment.rst b/docs/source/Plugin/P073_7_Segment.rst index 2852e1788..148cf3d0a 100644 --- a/docs/source/Plugin/P073_7_Segment.rst +++ b/docs/source/Plugin/P073_7_Segment.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p07x.repl +.. include:: ../Plugin/_plugin_substitutions_p07x.repl .. _P073_7_Segment_page: 7 Segment @@ -20,7 +20,7 @@ Wiring ------ -.. code-block:: html +.. code-block:: none ESP S8 GPIO (X) <--> TX @@ -40,7 +40,7 @@ Setup Rules examples -------------- -.. code-block:: html +.. code-block:: none //Code below... diff --git a/docs/source/Plugin/P075_Nextion.rst b/docs/source/Plugin/P075_Nextion.rst index 9346a0b97..2d819fdde 100644 --- a/docs/source/Plugin/P075_Nextion.rst +++ b/docs/source/Plugin/P075_Nextion.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p07x.repl +.. include:: ../Plugin/_plugin_substitutions_p07x.repl .. _P075_Nextion_page: Nextion @@ -20,7 +20,7 @@ Wiring ------ -.. code-block:: html +.. code-block:: none ESP S8 GPIO (X) <--> TX @@ -40,7 +40,7 @@ Setup Rules examples -------------- -.. code-block:: html +.. code-block:: none //Code below... diff --git a/docs/source/Plugin/P081.rst b/docs/source/Plugin/P081.rst index d1088d7df..232f6a703 100644 --- a/docs/source/Plugin/P081.rst +++ b/docs/source/Plugin/P081.rst @@ -46,7 +46,7 @@ The extra argument handles seconds. See also `Wikipedia - Cron `_ for more detailed information. -.. code-block:: html +.. code-block:: none ┌───────────── seconds (0 - 59) │ ┌───────────── minute (0 - 59) @@ -76,7 +76,7 @@ Examples of supported expressions Expression, input date, next date: -.. code-block:: html +.. code-block:: none "*/15 * 1-4 * * *", "2012-07-01_09:53:50", "2012-07-02_01:00:00" "0 */2 1-4 * * *", "2012-07-01_09:00:00", "2012-07-02_01:00:00" diff --git a/docs/source/Plugin/P081_events.repl b/docs/source/Plugin/P081_events.repl index d427bad2e..785b04053 100644 --- a/docs/source/Plugin/P081_events.repl +++ b/docs/source/Plugin/P081_events.repl @@ -7,7 +7,7 @@ Will trigger when the cron expression of task named ``foo`` matches the current time. "," - .. code-block:: html + .. code-block:: none on Cron#foo do GPIO,2,1 //LED on diff --git a/docs/source/Plugin/P082_Neo-6M.rst b/docs/source/Plugin/P082_Neo-6M.rst index 9b8498b18..4e2f1f1dd 100644 --- a/docs/source/Plugin/P082_Neo-6M.rst +++ b/docs/source/Plugin/P082_Neo-6M.rst @@ -27,7 +27,7 @@ The TX channel from the ESP to the GPS is not needed for this plugin. The used baudrate is 9600 bps. -.. code-block:: html +.. code-block:: none ESP Neo-6M (HW Serial0) GPIO (3/RX/RX0/D9) <--> TXD @@ -102,7 +102,7 @@ Indicators (recommended settings) Rules examples -------------- -.. code-block:: html +.. code-block:: none on GeoPos#Speed do if [GeoPos#Speed]>50 diff --git a/docs/source/Plugin/P082_Neo-7M.rst b/docs/source/Plugin/P082_Neo-7M.rst index b638cb149..3b13e522a 100644 --- a/docs/source/Plugin/P082_Neo-7M.rst +++ b/docs/source/Plugin/P082_Neo-7M.rst @@ -28,7 +28,7 @@ The TX channel from the ESP to the GPS is not needed for this plugin. The used baudrate is 9600 bps. -.. code-block:: html +.. code-block:: none ESP Neo-7M (HW Serial0) GPIO (3/RX/RX0/D9) <--> TXD @@ -103,7 +103,7 @@ Indicators (recommended settings) Rules examples -------------- -.. code-block:: html +.. code-block:: none on GeoPos#Speed do if [GeoPos#Speed]>50 diff --git a/docs/source/Plugin/P082_Neo-M8n.rst b/docs/source/Plugin/P082_Neo-M8n.rst index 736ace675..eb0487e88 100644 --- a/docs/source/Plugin/P082_Neo-M8n.rst +++ b/docs/source/Plugin/P082_Neo-M8n.rst @@ -30,7 +30,7 @@ The TX channel from the ESP to the GPS is not needed for this plugin. The used baudrate is 9600 bps. -.. code-block:: html +.. code-block:: none ESP Neo-M8n (HW Serial0) GPIO (3/RX/RX0/D9) <--> TXD @@ -105,7 +105,7 @@ Indicators (recommended settings) Rules examples -------------- -.. code-block:: html +.. code-block:: none on GeoPos#Speed do if [GeoPos#Speed]>50 diff --git a/docs/source/Plugin/P082_events.repl b/docs/source/Plugin/P082_events.repl index cf3a256be..ce0acea12 100644 --- a/docs/source/Plugin/P082_events.repl +++ b/docs/source/Plugin/P082_events.repl @@ -7,7 +7,7 @@ Will trigger when the GPS unit got a good position from the satellites. "," - .. code-block:: html + .. code-block:: none on GPS#GotFix do GPIO,2,1 //LED on @@ -19,7 +19,7 @@ Will trigger when the GPS unit is not having a good position from the satellites. "," - .. code-block:: html + .. code-block:: none on GPS#LostFix do GPIO,2,0 //LED off @@ -32,7 +32,7 @@ This means the total travel distance can be more if the GPS does not move in a straight line. "," - .. code-block:: html + .. code-block:: none on GPS#travelled do LogEntry,'Travelled %eventvalue% meter' diff --git a/docs/source/Plugin/P086_Homie.rst b/docs/source/Plugin/P086_Homie.rst index cbc33f847..9e9f2d7a0 100644 --- a/docs/source/Plugin/P086_Homie.rst +++ b/docs/source/Plugin/P086_Homie.rst @@ -41,7 +41,7 @@ For some controllers or dataset types it is not necessary to acknowledge the new Example Rules for every value / event type ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. code-block:: html +.. code-block:: none // The "homie controller" schedules the event defined in the plug-in. // Does NOT store string values or arrays in the "homie receiver" plug-in. diff --git a/docs/source/Plugin/P097_events.repl b/docs/source/Plugin/P097_events.repl index 992cb218f..eb5522b57 100644 --- a/docs/source/Plugin/P097_events.repl +++ b/docs/source/Plugin/P097_events.repl @@ -8,7 +8,7 @@ The regular events like in every other task. Only difference here is that you may have an event either when the touch pad is touched, released or both. "," - .. code-block:: html + .. code-block:: none on touch#Touch do GPIO,2,1 //LED on @@ -21,7 +21,7 @@ When enabled, this event will be sent on release of the touch pad. The event variable is the time in msec the pad had been touched. "," - .. code-block:: html + .. code-block:: none on touch#Duration do LogEntry,'Touch Duration %eventvalue% ms' @@ -29,7 +29,7 @@ Example in the log: - .. code-block:: html + .. code-block:: none 6553261 : Info : Touch : ADC2 ch5 (T5): 10 6553338 : Info : EVENT: touch#Touch=10.00 diff --git a/docs/source/Plugin/_plugin_hardware.template b/docs/source/Plugin/_plugin_hardware.template index e6dbb0395..963fd061d 100644 --- a/docs/source/Plugin/_plugin_hardware.template +++ b/docs/source/Plugin/_plugin_hardware.template @@ -20,7 +20,7 @@ Specifications: Wiring ------ -.. code-block:: html +.. code-block:: none ESP UNIT GPIO (X) <--> A @@ -73,7 +73,7 @@ Indicators (recommended settings) Rules examples -------------- -.. code-block:: html +.. code-block:: none on TASK#VALUE do if [TASK#VALUE]>999 diff --git a/docs/source/Rules/Rules.rst b/docs/source/Rules/Rules.rst index 3c59ab3a2..4588c7957 100644 --- a/docs/source/Rules/Rules.rst +++ b/docs/source/Rules/Rules.rst @@ -51,79 +51,79 @@ Syntax The syntax of a rule can be single line: -.. code-block:: html +.. code-block:: none - on do endon + on do endon or multi-line (need to be closed with an "endon"): -.. code-block:: html +.. code-block:: none - on do - - - - endon + on do + + + + endon IF/ELSE - IF/ELSEIF/ELSE ------------------------ Also simple if ... else ... statements are possible: -.. code-block:: html +.. code-block:: none - on do - if - - - else - - endif - endon + on do + if + + + else + + endif + endon If the "else" part is not needed it can be removed: -.. code-block:: html +.. code-block:: none - on do - if - - - endif - endon + on do + if + + + endif + endon Also more complex if ... elseif ... else statements are possible (multiple elseif's can be used) -.. code-block:: html +.. code-block:: none - on do - if - - - elseif - - - else - - endif - endon + on do + if + + + elseif + + + else + + endif + endon Again, if the "else" part is not needed it can be removed: -.. code-block:: html +.. code-block:: none - on do - if - - - elseif - - - elseif - - - endif - endon + on do + if + + + elseif + + + elseif + + + endif + endon AND/OR ------ @@ -135,55 +135,55 @@ consumption of stack space (IRAM). Depending on plug-ins in use this might lead to unpredictable, unreliable behavior, advice is not to exceed 3 levels of nesting. -.. code-block:: html +.. code-block:: none - on do - if - event, - endif - endon - - on do - if - - endif - endon + on do + if + event, + endif + endon + + on do + if + + endif + endon As of mega-201803.. we have the possibility to use AND/OR: -.. code-block:: html +.. code-block:: none - on test do - if [test#a]=0 or [test#b]=0 or [test#c]=0 - event,ok - else - event,not_ok - endif - endon - - on test2 do - if [test#a]=1 and [test#b]=1 and [test#c]=1 - event,ok - else - event,not_ok - endif - endon - - on test3 do - if [test#a]=1 and [test#b]=1 or [test#c]=0 - event,ok - else - event,not_ok - endif - endon - - on test4 do - if [test#a]=0 - event,ok - else - event,not_ok - endif - endon + on test do + if [test#a]=0 or [test#b]=0 or [test#c]=0 + event,ok + else + event,not_ok + endif + endon + + on test2 do + if [test#a]=1 and [test#b]=1 and [test#c]=1 + event,ok + else + event,not_ok + endif + endon + + on test3 do + if [test#a]=1 and [test#b]=1 or [test#c]=0 + event,ok + else + event,not_ok + endif + endon + + on test4 do + if [test#a]=0 + event,ok + else + event,not_ok + endif + endon Up to two AND/OR can be used per if statement, that means that you can test three float values and if the statement is true/false corresponding action will take place. @@ -191,13 +191,13 @@ three float values and if the statement is true/false corresponding action will Trigger ------- -.. code-block:: html +.. code-block:: none The trigger can be an device value being changed: -.. code-block:: html +.. code-block:: none DeviceName#ValueName @@ -206,13 +206,13 @@ Operator (inequality function) Or a inequality function: -.. code-block:: html +.. code-block:: none DeviceName#ValueName Where the "inequality function" is a simple check: -.. code-block:: html +.. code-block:: none equal (=) to less (<) than @@ -240,14 +240,14 @@ boot/reboot/time/sleep etc. of the unit: Test ---- -.. code-block:: html +.. code-block:: none As described in the trigger section the test is a check done by checking if the DeviceName#ValueName is meeting a criteria: -.. code-block:: html +.. code-block:: none [DeviceName#ValueName] @@ -257,7 +257,7 @@ DeviceName#ValueName is closed by (square) brackets "[" and "]". Action ------ -.. code-block:: html +.. code-block:: none @@ -272,7 +272,7 @@ Comment If you want you can add comments to any row in your rules code. Just remember to add them after the code and always begin with "//": -.. code-block:: html +.. code-block:: none on do //If this happens then do that... if @@ -356,13 +356,13 @@ In order to allow the comma or space in a parameter, you can wrap the parameter There are multiple quotes available for this, to be able to use "the other quote" in your parameter. For example in JSON, you need the double quote for string like values or keys. -.. code-block:: html +.. code-block:: none Publish domoticz/in,{"idx":26,"nvalue":0,"svalue":"[AQ#TVOC]"} This can be fixed by wrapping the last parameter with single quotes like this: -.. code-block:: html +.. code-block:: none Publish domoticz/in,'{"idx":26,"nvalue":0,"svalue":"[AQ#TVOC]"}' @@ -459,7 +459,7 @@ The position arguments are the same as in Arduino ``String::substring`` , meanin For example: -.. code-block:: html +.. code-block:: none on DS-1#Temperature do logentry,{substring:0:1:%eventvalue%} @@ -470,7 +470,7 @@ For example: The ``%eventvalue%`` may contain the value "23.06" The output in the log will then be: -.. code-block:: html +.. code-block:: none 1512372 : Info : EVENT: DS-1#Temperature=23.06 1512404 : Info : ACT : logentry,2 @@ -488,13 +488,13 @@ N.B. it is also possible to concatenate these and refer to ``{taskname#varname}` For example (bit useless example, just for illustrative purposes): -.. code-block:: html +.. code-block:: none on DS-1#Temperature do logentry,{substring:0:2:{strtol:16:{substring:0:2:[DS-1#Temperature]}{substring:3:5:[DS-1#Temperature]}}} endon -.. code-block:: html +.. code-block:: none 221313 : Info : EVENT: DS-1#Temperature=22.13 221346 : Info : parse_string_commands cmd: substring:0:2:22.13 -> 22 @@ -515,14 +515,14 @@ Usage: ``{strtol:16:}`` to convert HEX (base 16) into an integer value. Example of extracting sub strings from a value and interpreting as if they were HEX values: -.. code-block:: html +.. code-block:: none on DS-1#Temperature do logentry,{strtol:16:%eventvalue%} logentry,{strtol:16:{substring:3:5:%eventvalue%}} endon -.. code-block:: html +.. code-block:: none 1987550 : Info : EVENT: DS-1#Temperature=24.12 1987586 : Info : ACT : logentry,36 @@ -566,7 +566,7 @@ Last but not least the fraction is not correct, it needs to be divided by 256 (a Complete rule used to parse this and set a variable in a dummy device: -.. code-block:: html +.. code-block:: none // Room temperature on !Serial#T1018* do @@ -583,7 +583,7 @@ Usage: ``{ord:}`` For example: -.. code-block:: html +.. code-block:: none on DS-1#Temperature do logentry,{ord:A} // ASCII value of 'A' @@ -591,7 +591,7 @@ For example: endon -.. code-block:: html +.. code-block:: none 2982455 : Info : EVENT: DS-1#Temperature=23.12 2982487 : Info : ACT : logentry,65 @@ -626,7 +626,7 @@ Regarding spaces in names it is recommended to NOT use them as it makes bug testing rules a lot harder. Spaces between chunks of code is possible to make the code more readable: -.. code-block:: html +.. code-block:: none [DeviceName#ValueName]< //These work... [DeviceName#ValueName] < //the same... @@ -647,14 +647,14 @@ TaskValueSet Dummy Device is a single way to store and read value on variable. Just create Generic - Dummy Device and variables inside it. -.. code-block:: html +.. code-block:: none TaskValueSet,TASKnr,VARnr,Value This example for two switches that toggle one device (LED and Relay on GPIO 13 and 16). -.. code-block:: html +.. code-block:: none on sw1#state do if [dummy#var1]=0 @@ -688,7 +688,7 @@ For the Unix time there are now 2 variables included: Here some example used to store the Unix time in the dummy plugin to keep track of actions. The values stored in the Dummy variables will be kept and restored on a crash/reboot as long as the ESP remains powered. -.. code-block:: html +.. code-block:: none if [DT#YMD]=0 and %unixday%>0 taskvalueset,7,1,%unixday%-1 @@ -715,7 +715,7 @@ the '=' sign, up to four values are possible). Sample rules section: -.. code-block:: html +.. code-block:: none on remoteTimerControl do timerSet,1,%eventvalue% @@ -723,7 +723,7 @@ Sample rules section: Now send this command to the ESP: -.. code-block:: html +.. code-block:: none http:///control?cmd=event,remoteTimerControl=5 @@ -740,7 +740,7 @@ the value of the Dummy device as condition in if-statement within rules. Multiple event values: -.. code-block:: html +.. code-block:: none on ToggleGPIO do GPIO,%eventvalue1%,%eventvalue2% @@ -748,7 +748,7 @@ Multiple event values: You could then use the command "ToggleGPIO" with dynamic GPIO numbers and state. -.. code-block:: html +.. code-block:: none http:///control?cmd=event,ToggleGPIO=12,1 @@ -757,7 +757,7 @@ Internal variables A really great feature to use is the 16 internal variables. You set them like this: -.. code-block:: html +.. code-block:: none Let,, @@ -774,7 +774,7 @@ remove the jumpiness with hardware you might want to add a filter in the softwar A **10 value average**: -.. code-block:: html +.. code-block:: none On Temp#Value Do Let,10,[VAR#9] @@ -798,7 +798,7 @@ value instead of the sensor value. Another filter could be to just use the previous value and **dilute** the new value with that one: -.. code-block:: html +.. code-block:: none On Temp#Value Do Let,2,[VAR#1] @@ -809,7 +809,7 @@ Another filter could be to just use the previous value and **dilute** the new va Yet another filter could be to add the new value to a **summarized average**: -.. code-block:: html +.. code-block:: none On Temp#Value Do Let,1,[Temp#Value] @@ -823,7 +823,7 @@ one suits your actual scenario the best. PIR and LDR ----------- -.. code-block:: html +.. code-block:: none On PIR#State do if [LDR#Light]<500 @@ -836,7 +836,7 @@ PIR and LDR In other words: If the PIR switch is set (to either 1 or 0) and if the light value < 500, then set GPIO port 16 of the ESP. -.. code-block:: html +.. code-block:: none on PIR#State=1 do if [LDR#Light]<500 @@ -849,7 +849,7 @@ Now the event is only triggered when the PIR switches on. SR04 and LDR ------------ -.. code-block:: html +.. code-block:: none on SR04#range<100 do if [ldr#lux]<500 @@ -867,7 +867,7 @@ Timer There are 8 timers (1-8) you can use: -.. code-block:: html +.. code-block:: none On System#Boot do //When the ESP boots, do servo,1,12,0 @@ -891,7 +891,7 @@ Starting/stopping repeating timers with events To disable an existing timer, set it to 0. This is useful to make repeating timers for things like alarms or warnings: -.. code-block:: html +.. code-block:: none //start the warning signal when we receive a start_warning event: On start_warning do @@ -915,7 +915,7 @@ timers for things like alarms or warnings: To start or stop the warning signal use http: -.. code-block:: html +.. code-block:: none http:///control?cmd=event,start_warning http:///control?cmd=event,stop_warning @@ -925,14 +925,14 @@ HTTP call When you enter this first command with the correct IP address in the URL of your browser: -.. code-block:: html +.. code-block:: none http:///control?cmd=event,startwatering http:///control?cmd=event,stopwatering And have this rule in the addressed ESP: -.. code-block:: html +.. code-block:: none On startwatering do gpio,12,1 //start watering (open valve) @@ -966,7 +966,7 @@ SendTo: SendTo , Imagine you have two ESP Easy modules, ESP#1 and ESP#2 In the Rules section of ESP#1 you have this: -.. code-block:: html +.. code-block:: none on demoEvent do sendTo,2,event,startwatering //(to use the previous example.) @@ -976,7 +976,7 @@ And ESP#2 has the rules according to the previous example (givemesomewater) If you then enter this with the correct IP address in the URL of your browser: -.. code-block:: html +.. code-block:: none http:///control?cmd=event,demoEvent @@ -984,7 +984,7 @@ Then ESP#1 shall send the event 'startwatering ' to ESP#2. It is also possible to directly order GPIO changes, like: -.. code-block:: html +.. code-block:: none on demoEvent do sendTo,2,GPIO,2,1 @@ -993,7 +993,7 @@ It is also possible to directly order GPIO changes, like: Publish -.. code-block:: html +.. code-block:: none Publish,, @@ -1004,7 +1004,7 @@ Time With Rules you can also start or stop actions on a given day and time, or even on every day. -.. code-block:: html +.. code-block:: none On Clock#Time=All,18:25 do // every day at 18:25 hours do ... gpio,14,0 @@ -1012,7 +1012,7 @@ With Rules you can also start or stop actions on a given day and time, or even o Or for a specific day: -.. code-block:: html +.. code-block:: none On Clock#Time=Sun,18:25 do // for Sunday, but All, Sun, Mon, Tue, Wed, Thu, Fri, Sat will do. gpio,14,0 @@ -1021,7 +1021,7 @@ Or for a specific day: It is also possible to use the system value %systime% in rules conditions to make things happen during certain hours of the day: -.. code-block:: html +.. code-block:: none On Pir#State=1 do If %systime% < 07:00:00 @@ -1041,7 +1041,7 @@ SendToHTTP To send a message to another device, like a command to switch on a light to Domoticz -.. code-block:: html +.. code-block:: none On System#Boot do //When the ESP boots, do timerSet,1,10 //Set Timer 1 for the next event in 10 seconds @@ -1055,7 +1055,7 @@ Many users have reported problems with commands being truncated, particularly when trying to send commands to Domoticz. It seems to be a parsing error. There is the following workaround: -.. code-block:: html +.. code-block:: none SendToHTTP 192.168.0.243,8080,/json.htm?type=param=switchlight&command&idx=174&switchcmd=On @@ -1071,7 +1071,7 @@ different i2c addresses. For dew point on the 'outside': -.. code-block:: html +.. code-block:: none on TempHumidityPressure_OUTSIDE#%RH do TaskValueSet,7,1,[TempHumidityPressure_OUTSIDE#°C]-(100-[TempHumidityPressure_OUTSIDE#%RH])/5 // "7" is the number of the task that the dummy device is on, "1" is its first value where we dump our result @@ -1084,7 +1084,7 @@ For dew point on the 'outside': For dew point on the 'inside': -.. code-block:: html +.. code-block:: none on TempHumidityPressure_INSIDE#%RH do TaskValueSet,7,2,[TempHumidityPressure_INSIDE#°C]-(100-[TempHumidityPressure_INSIDE#%RH])/5 // "7" is the number of the task that the dummy device is on, "2" is its second value where we dump our result @@ -1102,7 +1102,7 @@ Report IP every 30 seconds using MQTT This rule also work as a ping or heart beat of the unit. If it has not published a IP number for 30+ seconds the unit is experiencing problems. -.. code-block:: html +.. code-block:: none On System#Boot do //When the ESP boots, do Publish,%sysname%/IP,%ip% @@ -1125,7 +1125,7 @@ just as an example we want to publish these as custom messages with a unique IDX *MQTT* -.. code-block:: html +.. code-block:: none on INA219#Amps do Publish domoticz/in,{"idx":123456,"nvalue":0,"svalue":"[INA219#Amps]"} //Own made up IDX 123456 @@ -1138,7 +1138,7 @@ just as an example we want to publish these as custom messages with a unique IDX *HTTP* -.. code-block:: html +.. code-block:: none on INA219#Amps do SendToHTTP 192.168.1.2,8080,/json.htm?type=command¶m=udevice&idx=123456&nvalue=0&svalue=[INA219#Amps] //Own made up IDX 123456 @@ -1159,7 +1159,7 @@ Using a "normal switch" device which is in this example normally set to low (0) you can make one of two actions when pressed. If you either release the button in less than a second or press it for more than a second: -.. code-block:: html +.. code-block:: none on Button#State=1 do timerSet,1,1 @@ -1178,7 +1178,7 @@ Calculating water consumption Using the pulse counter you can calculate and act on waterflow and changes like this: -.. code-block:: html +.. code-block:: none On System#Boot do // When the ESP boots, do TaskValueSet,3,1,0 // TaskValueSet TASKnr,VARnr,Value, Reset the Liters counter to 0 From 777c397b891c709430df85f97cb0cafcfe5a2bcb Mon Sep 17 00:00:00 2001 From: tonhuisman Date: Thu, 13 Aug 2020 20:53:45 +0200 Subject: [PATCH 05/28] [I2C] Implement I2C Multiplexer --- src/ESPEasy_buildinfo.h | 2 +- src/WebServer_DevicesPage.ino | 40 +++++ src/WebServer_HardwarePage.ino | 43 +++++ src/WebServer_I2C_Scanner.ino | 227 +++++++++++++++++-------- src/WebServer_JSON.ino | 14 ++ src/__Plugin.ino | 77 +++++++++ src/define_plugin_sets.h | 17 ++ src/src/Commands/Tasks.h | 4 + src/src/DataStructs/DeviceStruct.h | 6 + src/src/DataStructs/SettingsStruct.cpp | 35 ++-- src/src/DataStructs/SettingsStruct.h | 3 + src/src/Helpers/ESPEasy_Storage.cpp | 7 + src/src/Helpers/Hardware.cpp | 44 +++++ src/src/Helpers/Hardware.h | 8 + 14 files changed, 439 insertions(+), 88 deletions(-) diff --git a/src/ESPEasy_buildinfo.h b/src/ESPEasy_buildinfo.h index 65f1a9b5e..780c195dc 100644 --- a/src/ESPEasy_buildinfo.h +++ b/src/ESPEasy_buildinfo.h @@ -20,7 +20,7 @@ #endif // if defined(ESP32) -#define BUILD 20109 // git version e.g. "20103" can be read as "2.1.03" (stored in int16_t) +#define BUILD 20110 // git version e.g. "20103" can be read as "2.1.03" (stored in int16_t) #if defined(ESP8266) # define BUILD_NOTES " - Mega" #endif // if defined(ESP8266) diff --git a/src/WebServer_DevicesPage.ino b/src/WebServer_DevicesPage.ino index 53d3bfc43..09001dd39 100644 --- a/src/WebServer_DevicesPage.ino +++ b/src/WebServer_DevicesPage.ino @@ -220,6 +220,11 @@ void handle_devices_CopySubmittedSettings(taskIndex_t taskIndex, pluginID_t task Settings.TaskDeviceNumber[taskIndex] = taskdevicenumber; +#ifdef FEATURE_I2CMULTIPLEXER + if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && Settings.I2C_Multiplexer_Addr != -1) { + Settings.I2C_Multiplexer_Port[taskIndex] = getFormItemInt(F("taskdevicei2cmuxport"), 0); + } +#endif int pin1 = -1; int pin2 = -1; int pin3 = -1; @@ -430,6 +435,15 @@ void handle_devicess_ShowAllTasksTable(byte page) switch (Device[DeviceIndex].Type) { case DEVICE_TYPE_I2C: addHtml(F("I2C")); +#ifdef FEATURE_I2CMULTIPLEXER + if (Settings.I2C_Multiplexer_Addr != -1 && Settings.I2C_Multiplexer_Port[x] != -1) { + String mux = F("
Multiplexer SD"); + mux += String(Settings.I2C_Multiplexer_Port[x]); + mux += F("/SC"); + mux += String(Settings.I2C_Multiplexer_Port[x]); + addHtml(mux); + } +#endif break; case DEVICE_TYPE_SERIAL: case DEVICE_TYPE_SERIAL_PLUS1: @@ -704,6 +718,32 @@ void handle_devices_TaskSettingsPage(taskIndex_t taskIndex, byte page) } } +#ifdef FEATURE_I2CMULTIPLEXER + // Show selector for an I2C multiplexer port if a multiplexer is configured + if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && Settings.I2C_Multiplexer_Addr != -1) { + + addFormSubHeader(F("I2C Multiplexer")); + + int taskDeviceI2CMuxPort = Settings.I2C_Multiplexer_Port[taskIndex]; + String i2c_mux_portoptions[9]; + int i2c_mux_portchoices[9]; + uint8_t mux_opt = 0; + i2c_mux_portoptions[mux_opt] = F("(Not connected via multiplexer)"); + i2c_mux_portchoices[mux_opt] = -1; + uint8_t mux_max = I2CMultiplexerMaxChannels(); + for (int8_t x = 0; x < mux_max; x++) { + mux_opt++; + i2c_mux_portoptions[mux_opt] = F("SD"); + i2c_mux_portoptions[mux_opt] += String(x); + i2c_mux_portoptions[mux_opt] += F("/SC"); + i2c_mux_portoptions[mux_opt] += String(x); + + i2c_mux_portchoices[mux_opt] = x; + } + if (taskDeviceI2CMuxPort >= mux_max) { taskDeviceI2CMuxPort = -1; } // Reset if out of range + addFormSelector(F("Multiplexer port"), F("taskdevicei2cmuxport"), mux_opt + 1, i2c_mux_portoptions, i2c_mux_portchoices, taskDeviceI2CMuxPort); + } +#endif // add plugins content if (Settings.TaskDeviceDataFeed[taskIndex] == 0) { // only show additional config for local connected sensors String webformLoadString; diff --git a/src/WebServer_HardwarePage.ino b/src/WebServer_HardwarePage.ino index 9fc4170f2..d3c35baf7 100644 --- a/src/WebServer_HardwarePage.ino +++ b/src/WebServer_HardwarePage.ino @@ -19,6 +19,14 @@ void handle_hardware() { Settings.Pin_i2c_sda = getFormItemInt(F("psda")); Settings.Pin_i2c_scl = getFormItemInt(F("pscl")); Settings.I2C_clockSpeed = getFormItemInt(F("pi2csp"), DEFAULT_I2C_CLOCK_SPEED); +#ifdef FEATURE_I2CMULTIPLEXER + Settings.I2C_Multiplexer_Type = getFormItemInt(F("pi2cmuxtype")); + if (Settings.I2C_Multiplexer_Type != -1) { + Settings.I2C_Multiplexer_Addr = getFormItemInt(F("pi2cmuxaddr")); + } else { + Settings.I2C_Multiplexer_Type = -1; + } +#endif #ifdef ESP32 Settings.InitSPI = getFormItemInt(F("initspi"), 0); #else //for ESP8266 we keep the old UI @@ -79,6 +87,41 @@ void handle_hardware() { addFormNumericBox(F("Clock Speed"), F("pi2csp"), Settings.I2C_clockSpeed, 100, 3400000); addUnit(F("Hz")); addFormNote(F("Use 100 kHz for old I2C devices, 400 kHz is max for most.")); +#ifdef FEATURE_I2CMULTIPLEXER + // Select tye type of multiplexer to use + { + String i2c_muxtype_options[4]; + int i2c_muxtype_choices[4]; + i2c_muxtype_options[0] = F("- None -"); + i2c_muxtype_choices[0] = 0; + i2c_muxtype_options[1] = F("TCA9548a - 8 channel"); + i2c_muxtype_choices[1] = I2C_MULTIPLEXER_TCA9548A; + i2c_muxtype_options[2] = F("TCA9546a - 4 channel"); + i2c_muxtype_choices[2] = I2C_MULTIPLEXER_TCA9546A; + i2c_muxtype_options[3] = F("PCA9540 - 2 channel (experimental)"); + i2c_muxtype_choices[3] = I2C_MULTIPLEXER_PCA9540; + addFormSelector(F("I2C Multiplexer type"), F("pi2cmuxtype"), 4, i2c_muxtype_options, i2c_muxtype_choices, Settings.I2C_Multiplexer_Type); +} + // Select the I2C address for a port multiplexer + { + String i2c_mux_options[9]; + int i2c_mux_choices[9]; + uint8_t mux_opt = 0; + i2c_mux_options[mux_opt] = F("- None -"); + i2c_mux_choices[mux_opt] = -1; + for (int8_t x = 0; x < 8; x++) { + mux_opt++; + i2c_mux_options[mux_opt] = formatToHex_decimal(0x70 + x); + if (x == 0) { // PCA9540 has a fixed address 0f 0x70 + i2c_mux_options[mux_opt] += F(" [TCA9546/8a, PCA9540]"); + } else { + i2c_mux_options[mux_opt] += F(" [TCA9546/8a]"); + } + i2c_mux_choices[mux_opt] = 0x70 + x; + } + addFormSelector(F("I2C Multiplexer address"), F("pi2cmuxaddr"), mux_opt + 1, i2c_mux_options, i2c_mux_choices, Settings.I2C_Multiplexer_Addr); + } +#endif // SPI Init addFormSubHeader(F("SPI Interface")); diff --git a/src/WebServer_I2C_Scanner.ino b/src/WebServer_I2C_Scanner.ino index 9694fc819..c2d96e6af 100644 --- a/src/WebServer_I2C_Scanner.ino +++ b/src/WebServer_I2C_Scanner.ino @@ -14,59 +14,99 @@ void handle_i2cscanner_json() { json_init(); json_open(true); - byte error, address; int nDevices = 0; - for (address = 1; address <= 127; address++) - { - Wire.beginTransmission(address); - error = Wire.endTransmission(); +#ifdef FEATURE_I2CMULTIPLEXER + nDevices = scanI2CbusForDevices_json(Settings.I2C_Multiplexer_Addr, -1, nDevices); // Channel -1 = standard I2C bus +#else + nDevices = scanI2CbusForDevices_json(-1, -1, nDevices); // Standard scan +#endif - if ((error == 0) || (error == 4)) - { - json_open(); - json_prop(F("addr"), String(formatToHex(address))); - json_number(F("status"), String(error)); - - if (error == 4) { - json_prop(F("error"), F("Unknown error at address ")); - } else { - String description = getKnownI2Cdevice(address); - - if (description.length() > 0) { - json_open(true, F("known devices")); - int pos = 0; - - while (pos >= 0) { - int newpos = description.indexOf(',', pos); - - if (pos != 0) { - addHtml(","); - } - - if (newpos == -1) { - json_quote_val(description.substring(pos)); - } else { - json_quote_val(description.substring(pos, newpos)); - } - pos = newpos; - - if (newpos != -1) - { - ++pos; - } - } - json_close(true); - } - nDevices++; - } - json_close(); +#ifdef FEATURE_I2CMULTIPLEXER + if (Settings.I2C_Multiplexer_Addr != -1) { + uint8_t mux_max = I2CMultiplexerMaxChannels(); + for (uint8_t channel = 0; channel < mux_max; channel++) { + I2CMultiplexerSelect(channel); + nDevices += scanI2CbusForDevices_json(Settings.I2C_Multiplexer_Addr, channel, nDevices); // Specific channels } + I2CMultiplexerOff(); } +#endif + json_close(true); TXBuffer.endStream(); } +int scanI2CbusForDevices_json(int8_t muxAddr, int8_t channel, int nDevices) { + byte error, address; + + for (address = 1; address <= 127; address++) + { +#ifdef FEATURE_I2CMULTIPLEXER + if (muxAddr == -1 || channel == -1 || muxAddr != address) { // Ignore I2C multiplexer when scanning its channels +#endif + Wire.beginTransmission(address); + error = Wire.endTransmission(); + + if ((error == 0) || (error == 4)) + { + json_open(); + json_prop(F("addr"), String(formatToHex(address))); +#ifdef FEATURE_I2CMULTIPLEXER + if (muxAddr != -1) { + if (channel == -1){ + json_prop(F("I2Cbus"), F("Standard I2C bus")); + } else { + String i2cChannel = F("Multiplexer SD"); + i2cChannel += String(channel); + i2cChannel += F("/SC"); + i2cChannel += String(channel); + json_prop(F("I2Cbus"), i2cChannel); + } + } +#endif + json_number(F("status"), String(error)); + + if (error == 4) { + json_prop(F("error"), F("Unknown error at address ")); + } else { + String description = getKnownI2Cdevice(address); + + if (description.length() > 0) { + json_open(true, F("known devices")); + int pos = 0; + + while (pos >= 0) { + int newpos = description.indexOf(',', pos); + + if (pos != 0) { + addHtml(","); + } + + if (newpos == -1) { + json_quote_val(description.substring(pos)); + } else { + json_quote_val(description.substring(pos, newpos)); + } + pos = newpos; + + if (newpos != -1) { + ++pos; + } + } + json_close(true); + } + nDevices++; + } + json_close(); + addHtml("\n"); + } +#ifdef FEATURE_I2CMULTIPLEXER + } +#endif + } + return nDevices; +} #endif // WEBSERVER_NEW_UI @@ -154,20 +194,20 @@ String getKnownI2Cdevice(byte address) { result = F("Adafruit Motorshield v2,SI1145"); break; case 0x70: - result = F("Adafruit Motorshield v2 (Catchall),HT16K33"); + result = F("Adafruit Motorshield v2 (Catchall),HT16K33,TCA9546/8a I2C multiplexer,PCA9540 I2C multiplexer"); break; case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: - result = F("HT16K33"); + result = F("HT16K33,TCA9546/8A I2C multiplexer"); break; case 0x76: - result = F("BME280,BMP280,MS5607,MS5611,HT16K33"); + result = F("BME280,BMP280,MS5607,MS5611,HT16K33,TCA9546/8a I2C multiplexer"); break; case 0x77: - result = F("BMP085,BMP180,BME280,BMP280,MS5607,MS5611,HT16K33"); + result = F("BMP085,BMP180,BME280,BMP280,MS5607,MS5611,HT16K33,TCA9546/8a I2C multiplexer"); break; case 0x7f: result = F("Arduino PME"); @@ -185,37 +225,32 @@ void handle_i2cscanner() { sendHeadandTail_stdtemplate(_HEAD); html_table_class_multirow(); +#ifdef FEATURE_I2CMULTIPLEXER + if (Settings.I2C_Multiplexer_Addr != -1) { + html_table_header(F("I2C bus")); + } +#endif html_table_header(F("I2C Addresses in use")); html_table_header(F("Supported devices")); - byte error, address; int nDevices = 0; - for (address = 1; address <= 127; address++) - { - Wire.beginTransmission(address); - error = Wire.endTransmission(); +#ifdef FEATURE_I2CMULTIPLEXER + nDevices = scanI2CbusForDevices(Settings.I2C_Multiplexer_Addr, -1, nDevices); // Channel -1 = standard I2C bus +#else + nDevices = scanI2CbusForDevices(-1, -1, nDevices); // Standard scan +#endif - if (error == 0) - { - html_TR_TD(); - addHtml(formatToHex(address)); - html_TD(); - String description = getKnownI2Cdevice(address); - - if (description.length() > 0) { - description.replace(",", "
"); - addHtml(description); - } - nDevices++; - } - else if (error == 4) - { - html_TR_TD(); - addHtml(F("Unknown error at address ")); - addHtml(formatToHex(address)); +#ifdef FEATURE_I2CMULTIPLEXER + if (Settings.I2C_Multiplexer_Addr != -1) { + uint8_t mux_max = I2CMultiplexerMaxChannels(); + for (uint8_t channel = 0; channel < mux_max; channel++) { + I2CMultiplexerSelect(channel); + nDevices += scanI2CbusForDevices(Settings.I2C_Multiplexer_Addr, channel, nDevices); } + I2CMultiplexerOff(); } +#endif if (nDevices == 0) { addHtml(F("No I2C devices found")); @@ -226,4 +261,54 @@ void handle_i2cscanner() { TXBuffer.endStream(); } +int scanI2CbusForDevices(int8_t muxAddr, int8_t channel, int nDevices) { + byte error, address; + + for (address = 1; address <= 127; address++) + { +#ifdef FEATURE_I2CMULTIPLEXER + if (muxAddr == -1 || channel == -1 || muxAddr != address) { // Ignore I2C multiplexer when scanning its channels +#endif + Wire.beginTransmission(address); + error = Wire.endTransmission(); + + if (error == 0) + { + html_TR_TD(); +#ifdef FEATURE_I2CMULTIPLEXER + if (muxAddr != -1) { + if (channel == -1){ + addHtml(F("Standard I2C bus")); + } else { + String i2cChannel = F("Multiplexer SD"); + i2cChannel += String(channel); + i2cChannel += F("/SC"); + i2cChannel += String(channel); + addHtml(i2cChannel); + } + html_TD(); + } +#endif + addHtml(formatToHex(address)); + html_TD(); + String description = getKnownI2Cdevice(address); + + if (description.length() > 0) { + description.replace(",", "
"); + addHtml(description); + } + nDevices++; + } + else if (error == 4) + { + html_TR_TD(); + addHtml(F("Unknown error at address ")); + addHtml(formatToHex(address)); + } +#ifdef FEATURE_I2CMULTIPLEXER + } +#endif + } + return nDevices; +} #endif // WEBSERVER_I2C_SCANNER diff --git a/src/WebServer_JSON.ino b/src/WebServer_JSON.ino index 1e5e9ecaa..08f1ec998 100644 --- a/src/WebServer_JSON.ino +++ b/src/WebServer_JSON.ino @@ -324,6 +324,20 @@ void handle_json() stream_next_json_object_value(F("Type"), getPluginNameFromDeviceIndex(DeviceIndex)); stream_next_json_object_value(F("TaskName"), String(ExtraTaskSettings.TaskDeviceName)); stream_next_json_object_value(F("TaskDeviceNumber"), String(Settings.TaskDeviceNumber[TaskIndex])); +#ifdef FEATURE_I2CMULTIPLEXER + if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && Settings.I2C_Multiplexer_Addr != -1) { + int8_t channel = Settings.I2C_Multiplexer_Port[TaskIndex]; + if (channel == -1){ + stream_next_json_object_value(F("I2Cbus"), F("Standard I2C bus")); + } else { + String i2cChannel = F("Multiplexer SD"); + i2cChannel += String(channel); + i2cChannel += F("/SC"); + i2cChannel += String(channel); + stream_next_json_object_value(F("I2Cbus"), i2cChannel); + } + } +#endif } stream_next_json_object_value(F("TaskEnabled"), jsonBool(Settings.TaskDeviceEnabled[TaskIndex])); stream_last_json_object_value(F("TaskNumber"), String(TaskIndex + 1)); diff --git a/src/__Plugin.ino b/src/__Plugin.ino index 1f2e6c6fd..1050fd457 100644 --- a/src/__Plugin.ino +++ b/src/__Plugin.ino @@ -3,6 +3,7 @@ #include "src/Globals/GlobalMapPortStatus.h" #include "src/Globals/Plugins.h" #include "src/Globals/Settings.h" +#include "src/Commands/Tasks.h" #include "src/DataStructs/ESPEasy_EventStruct.h" #include "src/DataStructs/TimingStats.h" @@ -1115,9 +1116,27 @@ byte PluginCall(byte Function, struct EventStruct *event, String& str) // FIXME TD-er: Also resize DeviceIndex_to_Plugin_id ? } } +#ifdef FEATURE_I2CMULTIPLEXER + taskIndex_t taskIndex; + unsigned int varNr; + bool validTaskIndex = false; + + if (Function != PLUGIN_DEVICE_ADD && Device[x].Type == DEVICE_TYPE_I2C && Settings.I2C_Multiplexer_Addr != -1) { + validTaskIndex = validTaskVars(event, taskIndex, varNr); + + if (validTaskIndex && Settings.I2C_Multiplexer_Port[taskIndex] > -1) { + I2CMultiplexerSelect(Settings.I2C_Multiplexer_Port[taskIndex]); + } + } +#endif START_TIMER; Plugin_ptr[x](Function, event, str); STOP_TIMER_TASK(x, Function); +#ifdef FEATURE_I2CMULTIPLEXER + if (validTaskIndex && Settings.I2C_Multiplexer_Port[taskIndex] > -1) { + I2CMultiplexerOff(); + } +#endif delay(0); // SMY: call delay(0) unconditionally } } @@ -1135,9 +1154,27 @@ byte PluginCall(byte Function, struct EventStruct *event, String& str) const deviceIndex_t DeviceIndex = it->second.x; if (validDeviceIndex(DeviceIndex)) { +#ifdef FEATURE_I2CMULTIPLEXER + taskIndex_t taskIndex; + unsigned int varNr; + bool validTaskIndex = false; + + if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && Settings.I2C_Multiplexer_Addr != -1) { + validTaskIndex = validTaskVars(event, taskIndex, varNr); + + if (validTaskIndex && Settings.I2C_Multiplexer_Port[taskIndex] > -1) { + I2CMultiplexerSelect(Settings.I2C_Multiplexer_Port[taskIndex]); + } + } +#endif START_TIMER; Plugin_ptr[DeviceIndex](Function, &TempEvent, str); STOP_TIMER_TASK(DeviceIndex, Function); +#ifdef FEATURE_I2CMULTIPLEXER + if (validTaskIndex && Settings.I2C_Multiplexer_Port[taskIndex] > -1) { + I2CMultiplexerOff(); + } +#endif } } } @@ -1160,10 +1197,20 @@ byte PluginCall(byte Function, struct EventStruct *event, String& str) TempEvent.TaskIndex = task; TempEvent.BaseVarIndex = task * VARS_PER_TASK; TempEvent.sensorType = Device[DeviceIndex].VType; +#ifdef FEATURE_I2CMULTIPLEXER + if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && Settings.I2C_Multiplexer_Addr != -1 && Settings.I2C_Multiplexer_Port[task] > -1) { + I2CMultiplexerSelect(Settings.I2C_Multiplexer_Port[task]); + } +#endif checkRAM(F("PluginCall_s"), task); START_TIMER; bool retval = (Plugin_ptr[DeviceIndex](Function, &TempEvent, str)); STOP_TIMER_TASK(DeviceIndex, Function); +#ifdef FEATURE_I2CMULTIPLEXER + if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && Settings.I2C_Multiplexer_Addr != -1 && Settings.I2C_Multiplexer_Port[task] > -1) { + I2CMultiplexerOff(); + } +#endif delay(0); // SMY: call delay(0) unconditionally if (retval) { @@ -1204,9 +1251,19 @@ byte PluginCall(byte Function, struct EventStruct *event, String& str) // TempEvent.idx = Settings.TaskDeviceID[task]; todo check TempEvent.sensorType = Device[DeviceIndex].VType; +#ifdef FEATURE_I2CMULTIPLEXER + if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && Settings.I2C_Multiplexer_Addr != -1 && Settings.I2C_Multiplexer_Port[task] > -1) { + I2CMultiplexerSelect(Settings.I2C_Multiplexer_Port[task]); + } +#endif START_TIMER; bool retval = (Plugin_ptr[DeviceIndex](Function, &TempEvent, str)); STOP_TIMER_TASK(DeviceIndex, Function); +#ifdef FEATURE_I2CMULTIPLEXER + if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && Settings.I2C_Multiplexer_Addr != -1 && Settings.I2C_Multiplexer_Port[task] > -1) { + I2CMultiplexerOff(); + } +#endif delay(0); // SMY: call delay(0) unconditionally if (retval) { @@ -1254,9 +1311,19 @@ byte PluginCall(byte Function, struct EventStruct *event, String& str) // Schedule the plugin to be read. Scheduler.schedule_task_device_timer_at_init(TempEvent.TaskIndex); } +#ifdef FEATURE_I2CMULTIPLEXER + if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && Settings.I2C_Multiplexer_Addr != -1 && Settings.I2C_Multiplexer_Port[task] > -1) { + I2CMultiplexerSelect(Settings.I2C_Multiplexer_Port[task]); + } +#endif START_TIMER; Plugin_ptr[DeviceIndex](Function, &TempEvent, str); STOP_TIMER_TASK(DeviceIndex, Function); +#ifdef FEATURE_I2CMULTIPLEXER + if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && Settings.I2C_Multiplexer_Addr != -1 && Settings.I2C_Multiplexer_Port[task] > -1) { + I2CMultiplexerOff(); + } +#endif delay(0); // SMY: call delay(0) unconditionally } } @@ -1306,6 +1373,11 @@ byte PluginCall(byte Function, struct EventStruct *event, String& str) UserVar[event->BaseVarIndex + i] = 0.0; } } +#ifdef FEATURE_I2CMULTIPLEXER + if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && Settings.I2C_Multiplexer_Addr != -1 && Settings.I2C_Multiplexer_Port[event->TaskIndex] > -1) { + I2CMultiplexerSelect(Settings.I2C_Multiplexer_Port[event->TaskIndex]); + } +#endif START_TIMER; bool retval = Plugin_ptr[DeviceIndex](Function, event, str); @@ -1318,6 +1390,11 @@ byte PluginCall(byte Function, struct EventStruct *event, String& str) clearPluginTaskData(event->TaskIndex); } STOP_TIMER_TASK(DeviceIndex, Function); +#ifdef FEATURE_I2CMULTIPLEXER + if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && Settings.I2C_Multiplexer_Addr != -1 && Settings.I2C_Multiplexer_Port[event->TaskIndex] > -1) { + I2CMultiplexerOff(); + } +#endif delay(0); // SMY: call delay(0) unconditionally return retval; } diff --git a/src/define_plugin_sets.h b/src/define_plugin_sets.h index a8abcefdb..a6dc21ad5 100644 --- a/src/define_plugin_sets.h +++ b/src/define_plugin_sets.h @@ -23,6 +23,7 @@ To create/register a plugin, you have to : */ //#define FEATURE_SD +#define FEATURE_I2CMULTIPLEXER /******************************************************************************\ * WebServer pages ********************************************************** @@ -337,6 +338,10 @@ To create/register a plugin, you have to : #ifdef USES_SSDP #undef USES_SSDP #endif + + #ifdef FEATURE_I2CMULTIPLEXER + #undef FEATURE_I2CMULTIPLEXER + #endif #endif @@ -361,6 +366,9 @@ To create/register a plugin, you have to : #define P016_SEND_IR_TO_CONTROLLER false //IF true then the JSON replay solution is transmited back to the condroller. #define USES_P035 // IRTX #define P016_P035_USE_RAW_RAW2 //Use the RAW and RAW2 encodings, disabling it saves 3.7Kb + #ifdef FEATURE_I2CMULTIPLEXER + #undef FEATURE_I2CMULTIPLEXER + #endif #endif #ifdef PLUGIN_BUILD_IR_EXTENDED @@ -377,6 +385,9 @@ To create/register a plugin, you have to : #define PLUGIN_SET_ONLY_SWITCH #define USES_P029 // Output - Domoticz MQTT Helper #define PLUGIN_SET_ONLY_TEMP_HUM + #ifdef FEATURE_I2CMULTIPLEXER + #undef FEATURE_I2CMULTIPLEXER + #endif #endif #ifdef PLUGIN_BUILD_IR_EXTENDED_NO_RX @@ -388,6 +399,9 @@ To create/register a plugin, you have to : #define P016_P035_Extended_AC #define P016_P035_USE_RAW_RAW2 //Use the RAW and RAW2 encodings, disabling it saves 3.7Kb #define USES_P088 //ToniA IR plugin + #ifdef FEATURE_I2CMULTIPLEXER + #undef FEATURE_I2CMULTIPLEXER + #endif #endif /******************************************************************************\ @@ -576,6 +590,9 @@ To create/register a plugin, you have to : #ifndef USES_P037 #define USES_P037 // MQTTImport #endif + #ifdef FEATURE_I2CMULTIPLEXER + #undef FEATURE_I2CMULTIPLEXER + #endif #endif #ifdef PLUGIN_SET_ONLY_TEMP_HUM diff --git a/src/src/Commands/Tasks.h b/src/src/Commands/Tasks.h index 31f86f8d3..ae5b74163 100644 --- a/src/src/Commands/Tasks.h +++ b/src/src/Commands/Tasks.h @@ -1,6 +1,8 @@ #ifndef COMMAND_TASKS_H #define COMMAND_TASKS_H +#include "../Globals/Plugins.h" + class String; String Command_Task_Clear(struct EventStruct *event, const char* Line); @@ -13,4 +15,6 @@ String Command_Task_ValueSetAndRun(struct EventStruct *event, const char* Line); String Command_Task_Run(struct EventStruct *event, const char* Line); String Command_Task_RemoteConfig(struct EventStruct *event, const char* Line); +bool validTaskVars(struct EventStruct *event, taskIndex_t& taskIndex, unsigned int& varNr); + #endif // COMMAND_TASKS_H diff --git a/src/src/DataStructs/DeviceStruct.h b/src/src/DataStructs/DeviceStruct.h index 9e00d7cab..377f9a92c 100644 --- a/src/src/DataStructs/DeviceStruct.h +++ b/src/src/DataStructs/DeviceStruct.h @@ -15,6 +15,12 @@ #define DEVICE_TYPE_SPI 23 // connected through SPI #define DEVICE_TYPE_DUMMY 99 // Dummy device, has no physical connection +#ifdef FEATURE_I2CMULTIPLEXER +#define I2C_MULTIPLEXER_TCA9548A 0 // TCA9548a 8 channel I2C switch, addresses 0x70-0x77 +#define I2C_MULTIPLEXER_TCA9546A 1 // TCA9546a 4 channel I2C switch, addresses 0x70-0x77 +#define I2C_MULTIPLEXER_PCA9540 2 // PCA9540 2 channel I2C switch, address 0x70, different channel addressing +#endif + // Used for VType #define SENSOR_TYPE_NONE 0 #define SENSOR_TYPE_SINGLE 1 diff --git a/src/src/DataStructs/SettingsStruct.cpp b/src/src/DataStructs/SettingsStruct.cpp index a512c14d1..fc17a9a62 100644 --- a/src/src/DataStructs/SettingsStruct.cpp +++ b/src/src/DataStructs/SettingsStruct.cpp @@ -203,22 +203,24 @@ void SettingsStruct_tmpl::clearUnitNameSettings() { template void SettingsStruct_tmpl::clearMisc() { - PID = 0; - Version = 0; - Build = 0; - IP_Octet = 0; - Delay = 0; - Pin_i2c_sda = -1; - Pin_i2c_scl = -1; - Pin_status_led = -1; - Pin_sd_cs = -1; - ETH_Phy_Addr = 0; - ETH_Pin_mdc = -1; - ETH_Pin_mdio = -1; - ETH_Pin_power = -1; - ETH_Phy_Type = 0; - ETH_Clock_Mode = 0; - ETH_Wifi_Mode = 0; + PID = 0; + Version = 0; + Build = 0; + IP_Octet = 0; + Delay = 0; + Pin_i2c_sda = -1; + Pin_i2c_scl = -1; + Pin_status_led = -1; + Pin_sd_cs = -1; + ETH_Phy_Addr = 0; + ETH_Pin_mdc = -1; + ETH_Pin_mdio = -1; + ETH_Pin_power = -1; + ETH_Phy_Type = 0; + ETH_Clock_Mode = 0; + ETH_Wifi_Mode = 0; + I2C_Multiplexer_Type = -1; + I2C_Multiplexer_Addr = -1; for (byte i = 0; i < 17; ++i) { PinBootStates[i] = 0; } BaudRate = 0; @@ -301,6 +303,7 @@ void SettingsStruct_tmpl::clearTask(taskIndex_t task) { TaskDeviceDataFeed[task] = 0; TaskDeviceTimer[task] = 0; TaskDeviceEnabled[task] = false; + I2C_Multiplexer_Port[task] = -1; } template diff --git a/src/src/DataStructs/SettingsStruct.h b/src/src/DataStructs/SettingsStruct.h index eea7065fc..ea4fba3ce 100644 --- a/src/src/DataStructs/SettingsStruct.h +++ b/src/src/DataStructs/SettingsStruct.h @@ -194,6 +194,9 @@ class SettingsStruct_tmpl byte ETH_Subnet[4]; byte ETH_DNS[4]; uint8_t ETH_Wifi_Mode; + int8_t I2C_Multiplexer_Addr; + int8_t I2C_Multiplexer_Port[N_TASKS]; + int8_t I2C_Multiplexer_Type; }; /* diff --git a/src/src/Helpers/ESPEasy_Storage.cpp b/src/src/Helpers/ESPEasy_Storage.cpp index 51b4ba4c3..fa898272a 100644 --- a/src/src/Helpers/ESPEasy_Storage.cpp +++ b/src/src/Helpers/ESPEasy_Storage.cpp @@ -219,6 +219,13 @@ String BuildFixes() if (Settings.Build < 20109) { Settings.SyslogPort = 514; } + if (Settings.Build < 20110) { + Settings.I2C_Multiplexer_Type = -1; + Settings.I2C_Multiplexer_Addr = -1; + for (taskIndex_t x = 0; x < TASKS_MAX; x++) { + Settings.I2C_Multiplexer_Port[x] = -1; + } + } Settings.Build = BUILD; return SaveSettings(); diff --git a/src/src/Helpers/Hardware.cpp b/src/src/Helpers/Hardware.cpp index a8db784a7..dba8d5eef 100644 --- a/src/src/Helpers/Hardware.cpp +++ b/src/src/Helpers/Hardware.cpp @@ -147,6 +147,50 @@ void initI2C() { } } +#ifdef FEATURE_I2CMULTIPLEXER +// As initially constructed by krikk in PR#254, quite adapted +// utility method for the I2C multiplexer +// select the multiplexer port given as parameter +void I2CMultiplexerSelect(uint8_t i) { + if (i > 7) return; + + byte toWrite = 0; + switch (Settings.I2C_Multiplexer_Type) { + case I2C_MULTIPLEXER_TCA9546A: // TCA9546/8 addressing + case I2C_MULTIPLEXER_TCA9548A: + toWrite = (1 << i); + break; + case I2C_MULTIPLEXER_PCA9540: // PCA9540 needs bit 2 set to write the channel + toWrite = 0b00000100; + if (i == 1) { + toWrite |= 0b00000010; // And bit 0 not set when selecting channel 0... + } + break; + } + Wire.beginTransmission(Settings.I2C_Multiplexer_Addr); + Wire.write(toWrite); + Wire.endTransmission(); +} + +// utility method for the I2C multiplexer +// disable all channels on a multiplexer +void I2CMultiplexerOff() { + Wire.beginTransmission(Settings.I2C_Multiplexer_Addr); + Wire.write(0); // no channel selected + Wire.endTransmission(); +} + +byte I2CMultiplexerMaxChannels() { + uint channels = 0; + switch (Settings.I2C_Multiplexer_Type) { + case I2C_MULTIPLEXER_TCA9548A: channels = 8; break; // TCA9548A has 8 channels + case I2C_MULTIPLEXER_TCA9546A: channels = 4; break; // TCA9546A has 4 channels + case I2C_MULTIPLEXER_PCA9540: channels = 2; break; // PCA9540 has 2 channels + } + return channels; +} +#endif + void checkResetFactoryPin() { static byte factoryResetCounter = 0; diff --git a/src/src/Helpers/Hardware.h b/src/src/Helpers/Hardware.h index 7730cb056..775e9a6fe 100644 --- a/src/src/Helpers/Hardware.h +++ b/src/src/Helpers/Hardware.h @@ -15,6 +15,14 @@ void hardwareInit(); void initI2C(); +#ifdef FEATURE_I2CMULTIPLEXER +void I2CMultiplexerSelect(uint8_t i); + +void I2CMultiplexerOff(); + +byte I2CMultiplexerMaxChannels(); +#endif + void checkResetFactoryPin(); int espeasy_analogRead(int pin); From cfd52a2108e7a1499059bb5900637b6c569a0e68 Mon Sep 17 00:00:00 2001 From: Gijs Noorlander Date: Sat, 15 Aug 2020 14:20:04 +0200 Subject: [PATCH 06/28] [Build] Add normal_ESP8266_4M1M_VCC env. --- .travis.yml | 1 + platformio_esp82xx_envs.ini | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/.travis.yml b/.travis.yml index 347f0d72f..50becfcea 100644 --- a/.travis.yml +++ b/.travis.yml @@ -58,6 +58,7 @@ env: - ENV=normal_ESP8266_1M - ENV=normal_ESP8266_1M_VCC - ENV=normal_ESP8266_4M1M + - ENV=normal_ESP8266_4M1M_VCC - ENV=normal_ESP8285_1M - ENV=normal_IRext_no_rx_ESP8266_4M2M - ENV=normal_WROOM02_2M256 diff --git a/platformio_esp82xx_envs.ini b/platformio_esp82xx_envs.ini index fb23266df..aafd2c384 100644 --- a/platformio_esp82xx_envs.ini +++ b/platformio_esp82xx_envs.ini @@ -206,6 +206,14 @@ platform_packages = ${regular_platform.platform_packages} build_flags = ${regular_platform.build_flags} ${esp8266_4M1M.build_flags} +[env:normal_ESP8266_4M1M_VCC] +extends = esp8266_4M1M +platform = ${regular_platform.platform} +platform_packages = ${regular_platform.platform_packages} +build_flags = ${regular_platform.build_flags} + ${esp8266_4M1M.build_flags} + -D FEATURE_ADC_VCC=true + [env:normal_alt_wifi_ESP8266_4M1M] extends = esp8266_4M1M platform = ${regular_platform_alt_wifi.platform} From 6f11467b08cc417d719584096600990768ba216f Mon Sep 17 00:00:00 2001 From: tonhuisman Date: Sat, 15 Aug 2020 17:22:25 +0200 Subject: [PATCH 07/28] [I2C] Improve I2C Multiplexer support --- src/WebServer_DevicesPage.ino | 132 ++++++++++++---- src/WebServer_HardwarePage.ino | 51 ++++--- src/WebServer_I2C_Scanner.ino | 200 +++++++++++++++---------- src/WebServer_JSON.ino | 29 +++- src/__Plugin.ino | 109 ++++++++++---- src/define_plugin_sets.h | 17 +-- src/src/DataStructs/DeviceStruct.h | 3 + src/src/DataStructs/ESPEasyDefaults.h | 3 + src/src/DataStructs/SettingsStruct.cpp | 37 ++--- src/src/DataStructs/SettingsStruct.h | 5 +- src/src/Helpers/ESPEasy_Storage.cpp | 4 +- src/src/Helpers/ESPEasy_checks.cpp | 2 +- src/src/Helpers/Hardware.cpp | 76 +++++++++- src/src/Helpers/Hardware.h | 8 + 14 files changed, 473 insertions(+), 203 deletions(-) diff --git a/src/WebServer_DevicesPage.ino b/src/WebServer_DevicesPage.ino index 09001dd39..f7ffc7065 100644 --- a/src/WebServer_DevicesPage.ino +++ b/src/WebServer_DevicesPage.ino @@ -220,11 +220,31 @@ void handle_devices_CopySubmittedSettings(taskIndex_t taskIndex, pluginID_t task Settings.TaskDeviceNumber[taskIndex] = taskdevicenumber; + uint8_t flags = 0; + if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C) { + bitWrite(flags, 0, isFormItemChecked(F("taskdeviceflags0"))); + } #ifdef FEATURE_I2CMULTIPLEXER if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && Settings.I2C_Multiplexer_Addr != -1) { - Settings.I2C_Multiplexer_Port[taskIndex] = getFormItemInt(F("taskdevicei2cmuxport"), 0); + int multipleMuxPortsOption = getFormItemInt(F("taskdeviceflags1"), 0); + bitWrite(flags, 1, multipleMuxPortsOption == 1); + if (multipleMuxPortsOption == 1) { + uint8_t selectedPorts = 0; + for (uint8_t x = 0; x < I2CMultiplexerMaxChannels(); x++) { + String id = F("taskdeviceflag1ch"); + id += String(x); + bitWrite(selectedPorts, x, isFormItemChecked(id)); + } + Settings.I2C_Multiplexer_Port[taskIndex] = selectedPorts; + } else { + Settings.I2C_Multiplexer_Port[taskIndex] = getFormItemInt(F("taskdevicei2cmuxport"), 0); + } } #endif + if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C) { + Settings.I2C_Flags[taskIndex] = flags; + } + int pin1 = -1; int pin2 = -1; int pin3 = -1; @@ -436,11 +456,27 @@ void handle_devicess_ShowAllTasksTable(byte page) case DEVICE_TYPE_I2C: addHtml(F("I2C")); #ifdef FEATURE_I2CMULTIPLEXER - if (Settings.I2C_Multiplexer_Addr != -1 && Settings.I2C_Multiplexer_Port[x] != -1) { - String mux = F("
Multiplexer SD"); - mux += String(Settings.I2C_Multiplexer_Port[x]); - mux += F("/SC"); - mux += String(Settings.I2C_Multiplexer_Port[x]); + if (Settings.I2C_Multiplexer_Addr != -1 && I2CMultiplexerPortSelectedForTask(x)) { + String mux; + if (bitRead(Settings.I2C_Flags[x], I2C_FLAGS_MUX_MULTICHANNEL)) { // Multi-channel + mux = F("
Multiplexer channel(s)"); + uint8_t b = 0; // For adding lineBreaks + for (uint8_t c = 0; c < I2CMultiplexerMaxChannels(); c++) { + if (bitRead(Settings.I2C_Multiplexer_Port[x], c)) { + mux += b % 4 == 0 ? F("
") : F(" "); + b++; + mux += F("SD"); + mux += String(c); + mux += F("/SC"); + mux += String(c); + } + } + } else { // Single channel + mux = F("
Multiplexer SD"); + mux += String(Settings.I2C_Multiplexer_Port[x]); + mux += F("/SC"); + mux += String(Settings.I2C_Multiplexer_Port[x]); + } addHtml(mux); } #endif @@ -718,32 +754,78 @@ void handle_devices_TaskSettingsPage(taskIndex_t taskIndex, byte page) } } + if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C) { + + addFormSubHeader(F("I2C options")); + + addFormCheckBox(F("Force Slow I2C speed"), F("taskdeviceflags0"), bitRead(Settings.I2C_Flags[taskIndex], I2C_FLAGS_SLOW_SPEED)); + } #ifdef FEATURE_I2CMULTIPLEXER // Show selector for an I2C multiplexer port if a multiplexer is configured if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && Settings.I2C_Multiplexer_Addr != -1) { + bool multipleMuxPorts = bitRead(Settings.I2C_Flags[taskIndex], I2C_FLAGS_MUX_MULTICHANNEL); + { + String i2c_mux_channels[2]; + int i2c_mux_channelOptions[2]; + int i2c_mux_channelCount = 1; + i2c_mux_channels[0] = F("Single channel"); + i2c_mux_channelOptions[0] = 0; + if (Settings.I2C_Multiplexer_Type == I2C_MULTIPLEXER_PCA9540) { + multipleMuxPorts = false; // force off + } else { + i2c_mux_channels[1] = F("Multiple channels"); + i2c_mux_channelOptions[1] = 1; + i2c_mux_channelCount++; + } + addFormSelector(F("Multiplexer channels"),F("taskdeviceflags1"), i2c_mux_channelCount, i2c_mux_channels, i2c_mux_channelOptions, multipleMuxPorts ? 1 : 0, true); + } + if (multipleMuxPorts) { + addRowLabel(F("Select connections"), F("")); + html_table(F(""), false); // Sub-table + html_table_header(F("Channel")); + html_table_header(F("Enable")); + html_table_header(F("Channel")); + html_table_header(F("Enable")); + for (uint8_t x = 0; x < I2CMultiplexerMaxChannels(); x++) { + String label = F("SD"); + label += String(x); + label += F("/SC"); + label += String(x); + String id = F("taskdeviceflag1ch"); + id += String(x); + if (x % 2 == 0) { html_TR(); } // Start a new row for every 2 channels + html_TD(); + addHtml(label); + html_TD(); + addCheckBox(id, bitRead(Settings.I2C_Multiplexer_Port[taskIndex], x), false); + } + html_end_table(); + } else { + int taskDeviceI2CMuxPort = Settings.I2C_Multiplexer_Port[taskIndex]; + String i2c_mux_portoptions[9]; + int i2c_mux_portchoices[9]; + uint8_t mux_opt = 0; + i2c_mux_portoptions[mux_opt] = F("(Not connected via multiplexer)"); + i2c_mux_portchoices[mux_opt] = -1; + uint8_t mux_max = I2CMultiplexerMaxChannels(); + for (int8_t x = 0; x < mux_max; x++) { + mux_opt++; + i2c_mux_portoptions[mux_opt] = F("SD"); + i2c_mux_portoptions[mux_opt] += String(x); + i2c_mux_portoptions[mux_opt] += F("/SC"); + i2c_mux_portoptions[mux_opt] += String(x); - addFormSubHeader(F("I2C Multiplexer")); - - int taskDeviceI2CMuxPort = Settings.I2C_Multiplexer_Port[taskIndex]; - String i2c_mux_portoptions[9]; - int i2c_mux_portchoices[9]; - uint8_t mux_opt = 0; - i2c_mux_portoptions[mux_opt] = F("(Not connected via multiplexer)"); - i2c_mux_portchoices[mux_opt] = -1; - uint8_t mux_max = I2CMultiplexerMaxChannels(); - for (int8_t x = 0; x < mux_max; x++) { - mux_opt++; - i2c_mux_portoptions[mux_opt] = F("SD"); - i2c_mux_portoptions[mux_opt] += String(x); - i2c_mux_portoptions[mux_opt] += F("/SC"); - i2c_mux_portoptions[mux_opt] += String(x); - - i2c_mux_portchoices[mux_opt] = x; + i2c_mux_portchoices[mux_opt] = x; + } + if (taskDeviceI2CMuxPort >= mux_max) { taskDeviceI2CMuxPort = -1; } // Reset if out of range + addFormSelector(F("Connected to"), F("taskdevicei2cmuxport"), mux_opt + 1, i2c_mux_portoptions, i2c_mux_portchoices, taskDeviceI2CMuxPort); } - if (taskDeviceI2CMuxPort >= mux_max) { taskDeviceI2CMuxPort = -1; } // Reset if out of range - addFormSelector(F("Multiplexer port"), F("taskdevicei2cmuxport"), mux_opt + 1, i2c_mux_portoptions, i2c_mux_portchoices, taskDeviceI2CMuxPort); } #endif + + if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C) { + addFormSubHeader(F("Device settings")); + } // add plugins content if (Settings.TaskDeviceDataFeed[taskIndex] == 0) { // only show additional config for local connected sensors String webformLoadString; diff --git a/src/WebServer_HardwarePage.ino b/src/WebServer_HardwarePage.ino index d3c35baf7..e6b8f0eef 100644 --- a/src/WebServer_HardwarePage.ino +++ b/src/WebServer_HardwarePage.ino @@ -13,34 +13,36 @@ void handle_hardware() { if (isFormItem(F("psda"))) { - Settings.Pin_status_led = getFormItemInt(F("pled")); - Settings.Pin_status_led_Inversed = isFormItemChecked(F("pledi")); - Settings.Pin_Reset = getFormItemInt(F("pres")); - Settings.Pin_i2c_sda = getFormItemInt(F("psda")); - Settings.Pin_i2c_scl = getFormItemInt(F("pscl")); - Settings.I2C_clockSpeed = getFormItemInt(F("pi2csp"), DEFAULT_I2C_CLOCK_SPEED); + Settings.Pin_status_led = getFormItemInt(F("pled")); + Settings.Pin_status_led_Inversed = isFormItemChecked(F("pledi")); + Settings.Pin_Reset = getFormItemInt(F("pres")); + Settings.Pin_i2c_sda = getFormItemInt(F("psda")); + Settings.Pin_i2c_scl = getFormItemInt(F("pscl")); + Settings.I2C_clockSpeed = getFormItemInt(F("pi2csp"), DEFAULT_I2C_CLOCK_SPEED); + Settings.I2C_clockSpeed_Slow = getFormItemInt(F("pi2cspslow"), DEFAULT_I2C_CLOCK_SPEED_SLOW); #ifdef FEATURE_I2CMULTIPLEXER - Settings.I2C_Multiplexer_Type = getFormItemInt(F("pi2cmuxtype")); + Settings.I2C_Multiplexer_Type = getFormItemInt(F("pi2cmuxtype")); if (Settings.I2C_Multiplexer_Type != -1) { - Settings.I2C_Multiplexer_Addr = getFormItemInt(F("pi2cmuxaddr")); + Settings.I2C_Multiplexer_Addr = getFormItemInt(F("pi2cmuxaddr")); } else { - Settings.I2C_Multiplexer_Type = -1; + Settings.I2C_Multiplexer_Addr = -1; } + Settings.I2C_Multiplexer_ResetPin = getFormItemInt(F("pi2cmuxreset")); #endif #ifdef ESP32 - Settings.InitSPI = getFormItemInt(F("initspi"), 0); + Settings.InitSPI = getFormItemInt(F("initspi"), 0); #else //for ESP8266 we keep the old UI - Settings.InitSPI = isFormItemChecked(F("initspi")); // SPI Init + Settings.InitSPI = isFormItemChecked(F("initspi")); // SPI Init #endif - Settings.Pin_sd_cs = getFormItemInt(F("sd")); + Settings.Pin_sd_cs = getFormItemInt(F("sd")); #ifdef HAS_ETHERNET - Settings.ETH_Phy_Addr = getFormItemInt(F("ethphy")); - Settings.ETH_Pin_mdc = getFormItemInt(F("ethmdc")); - Settings.ETH_Pin_mdio = getFormItemInt(F("ethmdio")); - Settings.ETH_Pin_power = getFormItemInt(F("ethpower")); - Settings.ETH_Phy_Type = getFormItemInt(F("ethtype")); - Settings.ETH_Clock_Mode = getFormItemInt(F("ethclock")); - Settings.ETH_Wifi_Mode = getFormItemInt(F("ethwifi")); + Settings.ETH_Phy_Addr = getFormItemInt(F("ethphy")); + Settings.ETH_Pin_mdc = getFormItemInt(F("ethmdc")); + Settings.ETH_Pin_mdio = getFormItemInt(F("ethmdio")); + Settings.ETH_Pin_power = getFormItemInt(F("ethpower")); + Settings.ETH_Phy_Type = getFormItemInt(F("ethtype")); + Settings.ETH_Clock_Mode = getFormItemInt(F("ethclock")); + Settings.ETH_Wifi_Mode = getFormItemInt(F("ethwifi")); #endif int gpio = 0; @@ -87,13 +89,16 @@ void handle_hardware() { addFormNumericBox(F("Clock Speed"), F("pi2csp"), Settings.I2C_clockSpeed, 100, 3400000); addUnit(F("Hz")); addFormNote(F("Use 100 kHz for old I2C devices, 400 kHz is max for most.")); + addFormNumericBox(F("Slow device Clock Speed"), F("pi2cspslow"), Settings.I2C_clockSpeed_Slow, 100, 3400000); + addUnit(F("Hz")); #ifdef FEATURE_I2CMULTIPLEXER - // Select tye type of multiplexer to use + addFormSubHeader(F("I2C Multiplexer")); + // Select the type of multiplexer to use { String i2c_muxtype_options[4]; int i2c_muxtype_choices[4]; i2c_muxtype_options[0] = F("- None -"); - i2c_muxtype_choices[0] = 0; + i2c_muxtype_choices[0] = -1; i2c_muxtype_options[1] = F("TCA9548a - 8 channel"); i2c_muxtype_choices[1] = I2C_MULTIPLEXER_TCA9548A; i2c_muxtype_options[2] = F("TCA9546a - 4 channel"); @@ -101,7 +106,7 @@ void handle_hardware() { i2c_muxtype_options[3] = F("PCA9540 - 2 channel (experimental)"); i2c_muxtype_choices[3] = I2C_MULTIPLEXER_PCA9540; addFormSelector(F("I2C Multiplexer type"), F("pi2cmuxtype"), 4, i2c_muxtype_options, i2c_muxtype_choices, Settings.I2C_Multiplexer_Type); -} + } // Select the I2C address for a port multiplexer { String i2c_mux_options[9]; @@ -121,6 +126,8 @@ void handle_hardware() { } addFormSelector(F("I2C Multiplexer address"), F("pi2cmuxaddr"), mux_opt + 1, i2c_mux_options, i2c_mux_choices, Settings.I2C_Multiplexer_Addr); } + addFormPinSelect(formatGpioName_output_optional("Reset"), F("pi2cmuxreset"), Settings.I2C_Multiplexer_ResetPin); + addFormNote(F("Will be pulled low to force a reset. Reset is not available on PCA9540.")); #endif // SPI Init diff --git a/src/WebServer_I2C_Scanner.ino b/src/WebServer_I2C_Scanner.ino index c2d96e6af..50a38e00d 100644 --- a/src/WebServer_I2C_Scanner.ino +++ b/src/WebServer_I2C_Scanner.ino @@ -5,45 +5,30 @@ // ******************************************************************************** // Web Interface I2C scanner // ******************************************************************************** -void handle_i2cscanner_json() { - checkRAM(F("handle_i2cscanner")); - - if (!isLoggedIn()) { return; } - navMenuIndex = MENU_INDEX_TOOLS; - TXBuffer.startJsonStream(); - json_init(); - json_open(true); - - int nDevices = 0; +int scanI2CbusForDevices_json( // Utility function for scanning the I2C bus for valid devices, with JSON output + int8_t muxAddr + , int8_t channel + , int nDevices #ifdef FEATURE_I2CMULTIPLEXER - nDevices = scanI2CbusForDevices_json(Settings.I2C_Multiplexer_Addr, -1, nDevices); // Channel -1 = standard I2C bus -#else - nDevices = scanI2CbusForDevices_json(-1, -1, nDevices); // Standard scan + , String &excludeDevices #endif - -#ifdef FEATURE_I2CMULTIPLEXER - if (Settings.I2C_Multiplexer_Addr != -1) { - uint8_t mux_max = I2CMultiplexerMaxChannels(); - for (uint8_t channel = 0; channel < mux_max; channel++) { - I2CMultiplexerSelect(channel); - nDevices += scanI2CbusForDevices_json(Settings.I2C_Multiplexer_Addr, channel, nDevices); // Specific channels - } - I2CMultiplexerOff(); - } -#endif - - json_close(true); - TXBuffer.endStream(); -} - -int scanI2CbusForDevices_json(int8_t muxAddr, int8_t channel, int nDevices) { +) { byte error, address; +#ifdef FEATURE_I2CMULTIPLEXER + bool skipCheck = false; +#endif for (address = 1; address <= 127; address++) { #ifdef FEATURE_I2CMULTIPLEXER - if (muxAddr == -1 || channel == -1 || muxAddr != address) { // Ignore I2C multiplexer when scanning its channels + if (channel != -1 && excludeDevices.length() != 0) { + String toFind = F("|"); + toFind += String(address); + toFind += F("|"); + skipCheck = (excludeDevices.indexOf(toFind) != -1); + } + if (!skipCheck) { // Ignore I2C multiplexer and addresses to exclude when scanning its channels #endif Wire.beginTransmission(address); error = Wire.endTransmission(); @@ -56,6 +41,9 @@ int scanI2CbusForDevices_json(int8_t muxAddr, int8_t channel, int nDevices) { if (muxAddr != -1) { if (channel == -1){ json_prop(F("I2Cbus"), F("Standard I2C bus")); + excludeDevices += F("|"); + excludeDevices += String(address); + excludeDevices += F("|"); } else { String i2cChannel = F("Multiplexer SD"); i2cChannel += String(channel); @@ -107,10 +95,45 @@ int scanI2CbusForDevices_json(int8_t muxAddr, int8_t channel, int nDevices) { } return nDevices; } + +void handle_i2cscanner_json() { + checkRAM(F("handle_i2cscanner")); + + if (!isLoggedIn()) { return; } + navMenuIndex = MENU_INDEX_TOOLS; + TXBuffer.startJsonStream(); + json_init(); + json_open(true); + + int nDevices = 0; + + I2CSelectClockSpeed(true); // Always scan in low speed to also find old/slow devices +#ifdef FEATURE_I2CMULTIPLEXER + String mainBusDevices; + mainBusDevices.reserve(32); + nDevices = scanI2CbusForDevices_json(Settings.I2C_Multiplexer_Addr, -1, nDevices, mainBusDevices); // Channel -1 = standard I2C bus +#else + nDevices = scanI2CbusForDevices_json(-1, -1, nDevices); // Standard scan +#endif + +#ifdef FEATURE_I2CMULTIPLEXER + if (Settings.I2C_Multiplexer_Addr != -1) { + uint8_t mux_max = I2CMultiplexerMaxChannels(); + for (uint8_t channel = 0; channel < mux_max; channel++) { + I2CMultiplexerSelect(channel); + nDevices += scanI2CbusForDevices_json(Settings.I2C_Multiplexer_Addr, channel, nDevices, mainBusDevices); // Specific channels + } + I2CMultiplexerOff(); + } +#endif + I2CSelectClockSpeed(false); // Reset bus to standard speed + + json_close(true); + TXBuffer.endStream(); +} #endif // WEBSERVER_NEW_UI -// FIXME TD-er: Query all included plugins for their supported addresses (return name of plugin) String getKnownI2Cdevice(byte address) { String result; @@ -216,58 +239,29 @@ String getKnownI2Cdevice(byte address) { return result; } -void handle_i2cscanner() { - checkRAM(F("handle_i2cscanner")); - - if (!isLoggedIn()) { return; } - navMenuIndex = MENU_INDEX_TOOLS; - TXBuffer.startStream(); - sendHeadandTail_stdtemplate(_HEAD); - - html_table_class_multirow(); +int scanI2CbusForDevices( // Utility function for scanning the I2C bus for valid devices, with HTML table output + int8_t muxAddr + , int8_t channel + , int nDevices #ifdef FEATURE_I2CMULTIPLEXER - if (Settings.I2C_Multiplexer_Addr != -1) { - html_table_header(F("I2C bus")); - } + , String &excludeDevices #endif - html_table_header(F("I2C Addresses in use")); - html_table_header(F("Supported devices")); - - int nDevices = 0; - -#ifdef FEATURE_I2CMULTIPLEXER - nDevices = scanI2CbusForDevices(Settings.I2C_Multiplexer_Addr, -1, nDevices); // Channel -1 = standard I2C bus -#else - nDevices = scanI2CbusForDevices(-1, -1, nDevices); // Standard scan -#endif - -#ifdef FEATURE_I2CMULTIPLEXER - if (Settings.I2C_Multiplexer_Addr != -1) { - uint8_t mux_max = I2CMultiplexerMaxChannels(); - for (uint8_t channel = 0; channel < mux_max; channel++) { - I2CMultiplexerSelect(channel); - nDevices += scanI2CbusForDevices(Settings.I2C_Multiplexer_Addr, channel, nDevices); - } - I2CMultiplexerOff(); - } -#endif - - if (nDevices == 0) { - addHtml(F("No I2C devices found")); - } - - html_end_table(); - sendHeadandTail_stdtemplate(_TAIL); - TXBuffer.endStream(); -} - -int scanI2CbusForDevices(int8_t muxAddr, int8_t channel, int nDevices) { +) { byte error, address; +#ifdef FEATURE_I2CMULTIPLEXER + bool skipCheck = false; +#endif for (address = 1; address <= 127; address++) { #ifdef FEATURE_I2CMULTIPLEXER - if (muxAddr == -1 || channel == -1 || muxAddr != address) { // Ignore I2C multiplexer when scanning its channels + if (channel != -1 && excludeDevices.length() != 0) { + String toFind = F("|"); + toFind += String(address); + toFind += F("|"); + skipCheck = (excludeDevices.indexOf(toFind) != -1); + } + if (!skipCheck) { // Ignore I2C multiplexer and addresses to exclude when scanning its channels #endif Wire.beginTransmission(address); error = Wire.endTransmission(); @@ -279,6 +273,9 @@ int scanI2CbusForDevices(int8_t muxAddr, int8_t channel, int nDevices) { if (muxAddr != -1) { if (channel == -1){ addHtml(F("Standard I2C bus")); + excludeDevices += F("|"); + excludeDevices += String(address); + excludeDevices += F("|"); } else { String i2cChannel = F("Multiplexer SD"); i2cChannel += String(channel); @@ -311,4 +308,53 @@ int scanI2CbusForDevices(int8_t muxAddr, int8_t channel, int nDevices) { } return nDevices; } + +// FIXME TD-er: Query all included plugins for their supported addresses (return name of plugin) +void handle_i2cscanner() { + checkRAM(F("handle_i2cscanner")); + + if (!isLoggedIn()) { return; } + navMenuIndex = MENU_INDEX_TOOLS; + TXBuffer.startStream(); + sendHeadandTail_stdtemplate(_HEAD); + + html_table_class_multirow(); +#ifdef FEATURE_I2CMULTIPLEXER + if (Settings.I2C_Multiplexer_Addr != -1) { + html_table_header(F("I2C bus")); + } +#endif + html_table_header(F("I2C Addresses in use")); + html_table_header(F("Supported devices")); + + int nDevices = 0; + I2CSelectClockSpeed(true); // Scan bus using low speed +#ifdef FEATURE_I2CMULTIPLEXER + String mainBusDevices; + mainBusDevices.reserve(32); + nDevices = scanI2CbusForDevices(Settings.I2C_Multiplexer_Addr, -1, nDevices, mainBusDevices); // Channel -1 = standard I2C bus +#else + nDevices = scanI2CbusForDevices(-1, -1, nDevices); // Standard scan +#endif + +#ifdef FEATURE_I2CMULTIPLEXER + if (Settings.I2C_Multiplexer_Addr != -1) { + uint8_t mux_max = I2CMultiplexerMaxChannels(); + for (uint8_t channel = 0; channel < mux_max; channel++) { + I2CMultiplexerSelect(channel); + nDevices += scanI2CbusForDevices(Settings.I2C_Multiplexer_Addr, channel, nDevices, mainBusDevices); + } + I2CMultiplexerOff(); + } +#endif + I2CSelectClockSpeed(false); // By default the bus is in standard speed + + if (nDevices == 0) { + addHtml(F("No I2C devices found")); + } + + html_end_table(); + sendHeadandTail_stdtemplate(_TAIL); + TXBuffer.endStream(); +} #endif // WEBSERVER_I2C_SCANNER diff --git a/src/WebServer_JSON.ino b/src/WebServer_JSON.ino index 08f1ec998..767426cfc 100644 --- a/src/WebServer_JSON.ino +++ b/src/WebServer_JSON.ino @@ -327,14 +327,29 @@ void handle_json() #ifdef FEATURE_I2CMULTIPLEXER if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && Settings.I2C_Multiplexer_Addr != -1) { int8_t channel = Settings.I2C_Multiplexer_Port[TaskIndex]; - if (channel == -1){ - stream_next_json_object_value(F("I2Cbus"), F("Standard I2C bus")); + if (bitRead(Settings.I2C_Flags[TaskIndex], I2C_FLAGS_MUX_MULTICHANNEL)) { + addHtml(F("\"I2CBus\" : [\n")); + for (uint8_t c = 0; c < I2CMultiplexerMaxChannels(); c++) { + if (bitRead(channel, c)) { + String i2cChannel = F("Multiplexer SD"); + i2cChannel += String(c); + i2cChannel += F("/SC"); + i2cChannel += String(c); + addHtml(i2cChannel); + addHtml(F(",\n")); + } + } + addHtml(F("],\n")); } else { - String i2cChannel = F("Multiplexer SD"); - i2cChannel += String(channel); - i2cChannel += F("/SC"); - i2cChannel += String(channel); - stream_next_json_object_value(F("I2Cbus"), i2cChannel); + if (channel == -1){ + stream_next_json_object_value(F("I2Cbus"), F("Standard I2C bus")); + } else { + String i2cChannel = F("Multiplexer SD"); + i2cChannel += String(channel); + i2cChannel += F("/SC"); + i2cChannel += String(channel); + stream_next_json_object_value(F("I2Cbus"), i2cChannel); + } } } #endif diff --git a/src/__Plugin.ino b/src/__Plugin.ino index 1050fd457..db9fafb77 100644 --- a/src/__Plugin.ino +++ b/src/__Plugin.ino @@ -1116,26 +1116,34 @@ byte PluginCall(byte Function, struct EventStruct *event, String& str) // FIXME TD-er: Also resize DeviceIndex_to_Plugin_id ? } } -#ifdef FEATURE_I2CMULTIPLEXER taskIndex_t taskIndex; unsigned int varNr; bool validTaskIndex = false; - if (Function != PLUGIN_DEVICE_ADD && Device[x].Type == DEVICE_TYPE_I2C && Settings.I2C_Multiplexer_Addr != -1) { + if (Function != PLUGIN_DEVICE_ADD && Device[x].Type == DEVICE_TYPE_I2C) { validTaskIndex = validTaskVars(event, taskIndex, varNr); - if (validTaskIndex && Settings.I2C_Multiplexer_Port[taskIndex] > -1) { - I2CMultiplexerSelect(Settings.I2C_Multiplexer_Port[taskIndex]); +#ifdef FEATURE_I2CMULTIPLEXER + if (validTaskIndex && Settings.I2C_Multiplexer_Addr != -1 && I2CMultiplexerPortSelectedForTask(taskIndex)) { + I2CMultiplexerSelectByTaskIndex(taskIndex); + } +#else + if (validTaskIndex && bitRead(Settings.I2C_Flags[taskIndex], I2C_FLAGS_SLOW_SPEED)) { + I2CSelectClockSpeed(true); // Set to Slow } - } #endif + } START_TIMER; Plugin_ptr[x](Function, event, str); STOP_TIMER_TASK(x, Function); #ifdef FEATURE_I2CMULTIPLEXER - if (validTaskIndex && Settings.I2C_Multiplexer_Port[taskIndex] > -1) { - I2CMultiplexerOff(); + if (validTaskIndex && I2CMultiplexerPortSelectedForTask(taskIndex)) { + I2CMultiplexerOffByTaskIndex(taskIndex); } +#else + if (validTaskIndex && bitRead(Settings.I2C_Flags[taskIndex], I2C_FLAGS_SLOW_SPEED)) { + I2CSelectClockSpeed(false); // Reset + } #endif delay(0); // SMY: call delay(0) unconditionally } @@ -1154,25 +1162,32 @@ byte PluginCall(byte Function, struct EventStruct *event, String& str) const deviceIndex_t DeviceIndex = it->second.x; if (validDeviceIndex(DeviceIndex)) { -#ifdef FEATURE_I2CMULTIPLEXER taskIndex_t taskIndex; unsigned int varNr; bool validTaskIndex = false; - if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && Settings.I2C_Multiplexer_Addr != -1) { + if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C) { validTaskIndex = validTaskVars(event, taskIndex, varNr); - - if (validTaskIndex && Settings.I2C_Multiplexer_Port[taskIndex] > -1) { - I2CMultiplexerSelect(Settings.I2C_Multiplexer_Port[taskIndex]); +#ifdef FEATURE_I2CMULTIPLEXER + if (validTaskIndex && Settings.I2C_Multiplexer_Addr != -1 && I2CMultiplexerPortSelectedForTask(taskIndex)) { + I2CMultiplexerSelectByTaskIndex(taskIndex); + } +#else + if (validTaskIndex && bitRead(Settings.I2C_Flags[taskIndex], I2C_FLAGS_SLOW_SPEED)) { + I2CSelectClockSpeed(true); // Set to Slow } - } #endif + } START_TIMER; Plugin_ptr[DeviceIndex](Function, &TempEvent, str); STOP_TIMER_TASK(DeviceIndex, Function); #ifdef FEATURE_I2CMULTIPLEXER - if (validTaskIndex && Settings.I2C_Multiplexer_Port[taskIndex] > -1) { - I2CMultiplexerOff(); + if (validTaskIndex && I2CMultiplexerPortSelectedForTask(taskIndex)) { + I2CMultiplexerOffByTaskIndex(taskIndex); + } +#else + if (validTaskIndex && bitRead(Settings.I2C_Flags[taskIndex], I2C_FLAGS_SLOW_SPEED)) { + I2CSelectClockSpeed(false); // Reset } #endif } @@ -1198,18 +1213,26 @@ byte PluginCall(byte Function, struct EventStruct *event, String& str) TempEvent.BaseVarIndex = task * VARS_PER_TASK; TempEvent.sensorType = Device[DeviceIndex].VType; #ifdef FEATURE_I2CMULTIPLEXER - if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && Settings.I2C_Multiplexer_Addr != -1 && Settings.I2C_Multiplexer_Port[task] > -1) { - I2CMultiplexerSelect(Settings.I2C_Multiplexer_Port[task]); + if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && Settings.I2C_Multiplexer_Addr != -1 && I2CMultiplexerPortSelectedForTask(task)) { + I2CMultiplexerSelectByTaskIndex(task); } +#else + if (bitRead(Settings.I2C_Flags[task], I2C_FLAGS_SLOW_SPEED)) { + I2CSelectClockSpeed(true); // Set to Slow + } #endif checkRAM(F("PluginCall_s"), task); START_TIMER; bool retval = (Plugin_ptr[DeviceIndex](Function, &TempEvent, str)); STOP_TIMER_TASK(DeviceIndex, Function); #ifdef FEATURE_I2CMULTIPLEXER - if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && Settings.I2C_Multiplexer_Addr != -1 && Settings.I2C_Multiplexer_Port[task] > -1) { - I2CMultiplexerOff(); + if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && Settings.I2C_Multiplexer_Addr != -1 && I2CMultiplexerPortSelectedForTask(task)) { + I2CMultiplexerOffByTaskIndex(task); } +#else + if (bitRead(Settings.I2C_Flags[task], I2C_FLAGS_SLOW_SPEED)) { + I2CSelectClockSpeed(false); // Reset + } #endif delay(0); // SMY: call delay(0) unconditionally @@ -1252,16 +1275,24 @@ byte PluginCall(byte Function, struct EventStruct *event, String& str) // TempEvent.idx = Settings.TaskDeviceID[task]; todo check TempEvent.sensorType = Device[DeviceIndex].VType; #ifdef FEATURE_I2CMULTIPLEXER - if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && Settings.I2C_Multiplexer_Addr != -1 && Settings.I2C_Multiplexer_Port[task] > -1) { - I2CMultiplexerSelect(Settings.I2C_Multiplexer_Port[task]); + if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && Settings.I2C_Multiplexer_Addr != -1 && I2CMultiplexerPortSelectedForTask(task)) { + I2CMultiplexerSelectByTaskIndex(task); + } +#else + if (bitRead(Settings.I2C_Flags[task], I2C_FLAGS_SLOW_SPEED)) { + I2CSelectClockSpeed(true); // Set to Slow } #endif START_TIMER; bool retval = (Plugin_ptr[DeviceIndex](Function, &TempEvent, str)); STOP_TIMER_TASK(DeviceIndex, Function); #ifdef FEATURE_I2CMULTIPLEXER - if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && Settings.I2C_Multiplexer_Addr != -1 && Settings.I2C_Multiplexer_Port[task] > -1) { - I2CMultiplexerOff(); + if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && Settings.I2C_Multiplexer_Addr != -1 && I2CMultiplexerPortSelectedForTask(task)) { + I2CMultiplexerOffByTaskIndex(task); + } +#else + if (bitRead(Settings.I2C_Flags[task], I2C_FLAGS_SLOW_SPEED)) { + I2CSelectClockSpeed(false); // Reset } #endif delay(0); // SMY: call delay(0) unconditionally @@ -1312,16 +1343,24 @@ byte PluginCall(byte Function, struct EventStruct *event, String& str) Scheduler.schedule_task_device_timer_at_init(TempEvent.TaskIndex); } #ifdef FEATURE_I2CMULTIPLEXER - if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && Settings.I2C_Multiplexer_Addr != -1 && Settings.I2C_Multiplexer_Port[task] > -1) { - I2CMultiplexerSelect(Settings.I2C_Multiplexer_Port[task]); + if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && Settings.I2C_Multiplexer_Addr != -1 && I2CMultiplexerPortSelectedForTask(task)) { + I2CMultiplexerSelectByTaskIndex(task); + } +#else + if (bitRead(Settings.I2C_Flags[task], I2C_FLAGS_SLOW_SPEED)) { + I2CSelectClockSpeed(true); // Set to Slow } #endif START_TIMER; Plugin_ptr[DeviceIndex](Function, &TempEvent, str); STOP_TIMER_TASK(DeviceIndex, Function); #ifdef FEATURE_I2CMULTIPLEXER - if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && Settings.I2C_Multiplexer_Addr != -1 && Settings.I2C_Multiplexer_Port[task] > -1) { - I2CMultiplexerOff(); + if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && I2CMultiplexerPortSelectedForTask(task)) { + I2CMultiplexerOffByTaskIndex(task); + } +#else + if (bitRead(Settings.I2C_Flags[task], I2C_FLAGS_SLOW_SPEED)) { + I2CSelectClockSpeed(false); // Reset } #endif delay(0); // SMY: call delay(0) unconditionally @@ -1374,8 +1413,12 @@ byte PluginCall(byte Function, struct EventStruct *event, String& str) } } #ifdef FEATURE_I2CMULTIPLEXER - if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && Settings.I2C_Multiplexer_Addr != -1 && Settings.I2C_Multiplexer_Port[event->TaskIndex] > -1) { - I2CMultiplexerSelect(Settings.I2C_Multiplexer_Port[event->TaskIndex]); + if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && Settings.I2C_Multiplexer_Addr != -1 && I2CMultiplexerPortSelectedForTask(event->TaskIndex)) { + I2CMultiplexerSelectByTaskIndex(event->TaskIndex); + } +#else + if (bitRead(Settings.I2C_Flags[event->TaskIndex], I2C_FLAGS_SLOW_SPEED)) { + I2CSelectClockSpeed(true); // Set to Slow } #endif START_TIMER; @@ -1391,8 +1434,12 @@ byte PluginCall(byte Function, struct EventStruct *event, String& str) } STOP_TIMER_TASK(DeviceIndex, Function); #ifdef FEATURE_I2CMULTIPLEXER - if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && Settings.I2C_Multiplexer_Addr != -1 && Settings.I2C_Multiplexer_Port[event->TaskIndex] > -1) { - I2CMultiplexerOff(); + if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && Settings.I2C_Multiplexer_Addr != -1 && I2CMultiplexerPortSelectedForTask(event->TaskIndex)) { + I2CMultiplexerOffByTaskIndex(event->TaskIndex); + } +#else + if (bitRead(Settings.I2C_Flags[event->TaskIndex], I2C_FLAGS_SLOW_SPEED)) { + I2CSelectClockSpeed(false); // Reset } #endif delay(0); // SMY: call delay(0) unconditionally diff --git a/src/define_plugin_sets.h b/src/define_plugin_sets.h index a6dc21ad5..d5eccd816 100644 --- a/src/define_plugin_sets.h +++ b/src/define_plugin_sets.h @@ -23,7 +23,6 @@ To create/register a plugin, you have to : */ //#define FEATURE_SD -#define FEATURE_I2CMULTIPLEXER /******************************************************************************\ * WebServer pages ********************************************************** @@ -226,6 +225,10 @@ To create/register a plugin, you have to : #define CONTROLLER_SET_STABLE #define NOTIFIER_SET_STABLE + #ifndef FEATURE_I2CMULTIPLEXER + #define FEATURE_I2CMULTIPLEXER + #endif + #ifndef BUILD_NO_DEBUG #define BUILD_NO_DEBUG #endif @@ -366,9 +369,6 @@ To create/register a plugin, you have to : #define P016_SEND_IR_TO_CONTROLLER false //IF true then the JSON replay solution is transmited back to the condroller. #define USES_P035 // IRTX #define P016_P035_USE_RAW_RAW2 //Use the RAW and RAW2 encodings, disabling it saves 3.7Kb - #ifdef FEATURE_I2CMULTIPLEXER - #undef FEATURE_I2CMULTIPLEXER - #endif #endif #ifdef PLUGIN_BUILD_IR_EXTENDED @@ -385,9 +385,6 @@ To create/register a plugin, you have to : #define PLUGIN_SET_ONLY_SWITCH #define USES_P029 // Output - Domoticz MQTT Helper #define PLUGIN_SET_ONLY_TEMP_HUM - #ifdef FEATURE_I2CMULTIPLEXER - #undef FEATURE_I2CMULTIPLEXER - #endif #endif #ifdef PLUGIN_BUILD_IR_EXTENDED_NO_RX @@ -399,9 +396,6 @@ To create/register a plugin, you have to : #define P016_P035_Extended_AC #define P016_P035_USE_RAW_RAW2 //Use the RAW and RAW2 encodings, disabling it saves 3.7Kb #define USES_P088 //ToniA IR plugin - #ifdef FEATURE_I2CMULTIPLEXER - #undef FEATURE_I2CMULTIPLEXER - #endif #endif /******************************************************************************\ @@ -590,9 +584,6 @@ To create/register a plugin, you have to : #ifndef USES_P037 #define USES_P037 // MQTTImport #endif - #ifdef FEATURE_I2CMULTIPLEXER - #undef FEATURE_I2CMULTIPLEXER - #endif #endif #ifdef PLUGIN_SET_ONLY_TEMP_HUM diff --git a/src/src/DataStructs/DeviceStruct.h b/src/src/DataStructs/DeviceStruct.h index 377f9a92c..92237c636 100644 --- a/src/src/DataStructs/DeviceStruct.h +++ b/src/src/DataStructs/DeviceStruct.h @@ -21,6 +21,9 @@ #define I2C_MULTIPLEXER_PCA9540 2 // PCA9540 2 channel I2C switch, address 0x70, different channel addressing #endif +#define I2C_FLAGS_SLOW_SPEED 0 // Force slow speed when this flag is set +#define I2C_FLAGS_MUX_MULTICHANNEL 1 // Allow multiple multiplexer channels when set + // Used for VType #define SENSOR_TYPE_NONE 0 #define SENSOR_TYPE_SINGLE 1 diff --git a/src/src/DataStructs/ESPEasyDefaults.h b/src/src/DataStructs/ESPEasyDefaults.h index 8bf9fcf67..7159d1c72 100644 --- a/src/src/DataStructs/ESPEasyDefaults.h +++ b/src/src/DataStructs/ESPEasyDefaults.h @@ -160,6 +160,9 @@ #ifndef DEFAULT_I2C_CLOCK_SPEED #define DEFAULT_I2C_CLOCK_SPEED 400000 // Use 100 kHz if working with old I2C chips #endif +#ifndef DEFAULT_I2C_CLOCK_SPEED_SLOW +#define DEFAULT_I2C_CLOCK_SPEED_SLOW 100000 // Use 100 kHz for old/slow I2C chips +#endif #ifndef DEFAULT_PIN_STATUS_LED #define DEFAULT_PIN_STATUS_LED (-1) diff --git a/src/src/DataStructs/SettingsStruct.cpp b/src/src/DataStructs/SettingsStruct.cpp index fc17a9a62..283f8cdc9 100644 --- a/src/src/DataStructs/SettingsStruct.cpp +++ b/src/src/DataStructs/SettingsStruct.cpp @@ -203,24 +203,25 @@ void SettingsStruct_tmpl::clearUnitNameSettings() { template void SettingsStruct_tmpl::clearMisc() { - PID = 0; - Version = 0; - Build = 0; - IP_Octet = 0; - Delay = 0; - Pin_i2c_sda = -1; - Pin_i2c_scl = -1; - Pin_status_led = -1; - Pin_sd_cs = -1; - ETH_Phy_Addr = 0; - ETH_Pin_mdc = -1; - ETH_Pin_mdio = -1; - ETH_Pin_power = -1; - ETH_Phy_Type = 0; - ETH_Clock_Mode = 0; - ETH_Wifi_Mode = 0; - I2C_Multiplexer_Type = -1; - I2C_Multiplexer_Addr = -1; + PID = 0; + Version = 0; + Build = 0; + IP_Octet = 0; + Delay = 0; + Pin_i2c_sda = -1; + Pin_i2c_scl = -1; + Pin_status_led = -1; + Pin_sd_cs = -1; + ETH_Phy_Addr = 0; + ETH_Pin_mdc = -1; + ETH_Pin_mdio = -1; + ETH_Pin_power = -1; + ETH_Phy_Type = 0; + ETH_Clock_Mode = 0; + ETH_Wifi_Mode = 0; + I2C_Multiplexer_Type = -1; + I2C_Multiplexer_Addr = -1; + I2C_Multiplexer_ResetPin = -1; for (byte i = 0; i < 17; ++i) { PinBootStates[i] = 0; } BaudRate = 0; diff --git a/src/src/DataStructs/SettingsStruct.h b/src/src/DataStructs/SettingsStruct.h index ea4fba3ce..4bc01f76f 100644 --- a/src/src/DataStructs/SettingsStruct.h +++ b/src/src/DataStructs/SettingsStruct.h @@ -194,9 +194,12 @@ class SettingsStruct_tmpl byte ETH_Subnet[4]; byte ETH_DNS[4]; uint8_t ETH_Wifi_Mode; + int8_t I2C_Multiplexer_Type; int8_t I2C_Multiplexer_Addr; int8_t I2C_Multiplexer_Port[N_TASKS]; - int8_t I2C_Multiplexer_Type; + uint8_t I2C_Flags[N_TASKS]; + uint32_t I2C_clockSpeed_Slow; + uint8_t I2C_Multiplexer_ResetPin; }; /* diff --git a/src/src/Helpers/ESPEasy_Storage.cpp b/src/src/Helpers/ESPEasy_Storage.cpp index fa898272a..7e978aaef 100644 --- a/src/src/Helpers/ESPEasy_Storage.cpp +++ b/src/src/Helpers/ESPEasy_Storage.cpp @@ -176,7 +176,7 @@ String BuildFixes() Settings.OldRulesEngine(DEFAULT_RULES_OLDENGINE); } if (Settings.Build < 20105) { - Settings.I2C_clockSpeed = 400000; + Settings.I2C_clockSpeed = DEFAULT_I2C_CLOCK_SPEED; } if (Settings.Build <= 20106) { // ClientID is now defined in the controller settings. @@ -220,11 +220,13 @@ String BuildFixes() Settings.SyslogPort = 514; } if (Settings.Build < 20110) { + Settings.I2C_clockSpeed_Slow = DEFAULT_I2C_CLOCK_SPEED_SLOW; Settings.I2C_Multiplexer_Type = -1; Settings.I2C_Multiplexer_Addr = -1; for (taskIndex_t x = 0; x < TASKS_MAX; x++) { Settings.I2C_Multiplexer_Port[x] = -1; } + Settings.I2C_Multiplexer_ResetPin = -1; } Settings.Build = BUILD; diff --git a/src/src/Helpers/ESPEasy_checks.cpp b/src/src/Helpers/ESPEasy_checks.cpp index 6fa8eaeee..e4f85fe32 100644 --- a/src/src/Helpers/ESPEasy_checks.cpp +++ b/src/src/Helpers/ESPEasy_checks.cpp @@ -61,7 +61,7 @@ void run_compiletime_checks() { #ifndef LIMIT_BUILD_SIZE check_size(); check_size(); - const unsigned int SettingsStructSize = (276 + 82 * TASKS_MAX); + const unsigned int SettingsStructSize = (285 + 84 * TASKS_MAX); check_size(); check_size(); #ifdef USES_NOTIFIER diff --git a/src/src/Helpers/Hardware.cpp b/src/src/Helpers/Hardware.cpp index dba8d5eef..6af271176 100644 --- a/src/src/Helpers/Hardware.cpp +++ b/src/src/Helpers/Hardware.cpp @@ -121,6 +121,13 @@ void initI2C() { Wire.setClockStretchLimit(Settings.WireClockStretchLimit); #endif // if defined(ESP8266) } + +#ifdef FEATURE_I2CMULTIPLEXER + if (Settings.I2C_Multiplexer_ResetPin != -1) { // Initialize Reset pin to High if configured + pinMode(Settings.I2C_Multiplexer_ResetPin, OUTPUT); + digitalWrite(Settings.I2C_Multiplexer_ResetPin, HIGH); + } +#endif } // I2C Watchdog boot status check @@ -147,13 +154,22 @@ void initI2C() { } } -#ifdef FEATURE_I2CMULTIPLEXER -// As initially constructed by krikk in PR#254, quite adapted -// utility method for the I2C multiplexer -// select the multiplexer port given as parameter -void I2CMultiplexerSelect(uint8_t i) { - if (i > 7) return; +void I2CSelectClockSpeed(bool setLowSpeed) { + Wire.setClock(setLowSpeed ? Settings.I2C_clockSpeed_Slow : Settings.I2C_clockSpeed); +} +#ifdef FEATURE_I2CMULTIPLEXER +// Reset the I2C Multiplexer, if a pin is assigned for that. Pulled to low to force a reset. +void I2CMultiplexerReset() { + if (Settings.I2C_Multiplexer_ResetPin != -1) { + digitalWrite(Settings.I2C_Multiplexer_ResetPin, LOW); + delay(1); // minimum requirement of low for a proper reset seems to be about 6 nsec, so 1 msec should be more than sufficient + digitalWrite(Settings.I2C_Multiplexer_ResetPin, HIGH); + } +} + +// Shift the bit in the right position when selecting a single channel +byte I2CMultiplexerShiftBit(uint8_t i) { byte toWrite = 0; switch (Settings.I2C_Multiplexer_Type) { case I2C_MULTIPLEXER_TCA9546A: // TCA9546/8 addressing @@ -167,13 +183,52 @@ void I2CMultiplexerSelect(uint8_t i) { } break; } + return toWrite; +} + +// As initially constructed by krikk in PR#254, quite adapted +// utility method for the I2C multiplexer +// select the multiplexer port given as parameter, if taskIndex < 0 then take that abs value as the port to select (to allow I2C scanner) +void I2CMultiplexerSelectByTaskIndex(int8_t taskIndex) { + if (taskIndex >= INVALID_TASK_INDEX) return; + + byte toWrite = 0; + if (!bitRead(Settings.I2C_Flags[taskIndex], I2C_FLAGS_MUX_MULTICHANNEL)) { + uint8_t i = Settings.I2C_Multiplexer_Port[taskIndex]; + if (i > 7) return; + toWrite = I2CMultiplexerShiftBit(i); + } else { + toWrite = Settings.I2C_Multiplexer_Port[taskIndex]; // Bitpattern is already correctly stored + } + + if (bitRead(Settings.I2C_Flags[taskIndex], I2C_FLAGS_SLOW_SPEED)) { + I2CSelectClockSpeed(true); // Set to slow + } Wire.beginTransmission(Settings.I2C_Multiplexer_Addr); Wire.write(toWrite); Wire.endTransmission(); } +void I2CMultiplexerSelect(uint8_t i) { + if (i > 7) return; + + byte toWrite = I2CMultiplexerShiftBit(i); + + Wire.beginTransmission(Settings.I2C_Multiplexer_Addr); + Wire.write(toWrite); + Wire.endTransmission(); +} // utility method for the I2C multiplexer -// disable all channels on a multiplexer +// disable all channels on a multiplexer, and restore I2C speed if it was connecting a slow device +void I2CMultiplexerOffByTaskIndex(int8_t taskIndex) { + Wire.beginTransmission(Settings.I2C_Multiplexer_Addr); + Wire.write(0); // no channel selected + Wire.endTransmission(); + if (bitRead(Settings.I2C_Flags[taskIndex], I2C_FLAGS_SLOW_SPEED)) { + I2CSelectClockSpeed(false); // Reset clockspeed + } +} + void I2CMultiplexerOff() { Wire.beginTransmission(Settings.I2C_Multiplexer_Addr); Wire.write(0); // no channel selected @@ -189,6 +244,13 @@ byte I2CMultiplexerMaxChannels() { } return channels; } + +// Has this taskIndex a channel selected? Checks for both Single channel and Multiple channel mode +// taskIndex must already be validated! (0..MAX_TASKS) +bool I2CMultiplexerPortSelectedForTask(taskIndex_t taskIndex) { + return ( !bitRead(Settings.I2C_Flags[taskIndex], I2C_FLAGS_MUX_MULTICHANNEL) && Settings.I2C_Multiplexer_Port[taskIndex] != -1) + || (bitRead(Settings.I2C_Flags[taskIndex], I2C_FLAGS_MUX_MULTICHANNEL) && Settings.I2C_Multiplexer_Port[taskIndex] != 0); +} #endif void checkResetFactoryPin() { diff --git a/src/src/Helpers/Hardware.h b/src/src/Helpers/Hardware.h index 775e9a6fe..74f5bc04e 100644 --- a/src/src/Helpers/Hardware.h +++ b/src/src/Helpers/Hardware.h @@ -15,12 +15,20 @@ void hardwareInit(); void initI2C(); +void I2CSelectClockSpeed(bool setLowSpeed); + #ifdef FEATURE_I2CMULTIPLEXER +void I2CMultiplexerSelectByTaskIndex(int8_t taskIndex); void I2CMultiplexerSelect(uint8_t i); +void I2CMultiplexerOffByTaskIndex(int8_t taskIndex); void I2CMultiplexerOff(); byte I2CMultiplexerMaxChannels(); + +void I2CMultiplexerReset(); + +bool I2CMultiplexerPortSelectedForTask(taskIndex_t taskIndex); #endif void checkResetFactoryPin(); From 1578bf089dbfab2b9bff09d86a4ce7c495d60e67 Mon Sep 17 00:00:00 2001 From: tonhuisman Date: Sat, 15 Aug 2020 19:59:46 +0200 Subject: [PATCH 08/28] [I2C] Fix builderror on SettingsStructSize (again) --- src/src/Helpers/ESPEasy_checks.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/src/Helpers/ESPEasy_checks.cpp b/src/src/Helpers/ESPEasy_checks.cpp index e4f85fe32..9f4ed9a13 100644 --- a/src/src/Helpers/ESPEasy_checks.cpp +++ b/src/src/Helpers/ESPEasy_checks.cpp @@ -61,7 +61,7 @@ void run_compiletime_checks() { #ifndef LIMIT_BUILD_SIZE check_size(); check_size(); - const unsigned int SettingsStructSize = (285 + 84 * TASKS_MAX); + const unsigned int SettingsStructSize = (288 + 84 * TASKS_MAX); check_size(); check_size(); #ifdef USES_NOTIFIER From 0c5ec877b73dd9ff9f65866471a03feb74fad5ba Mon Sep 17 00:00:00 2001 From: tonhuisman Date: Sat, 15 Aug 2020 20:11:21 +0200 Subject: [PATCH 09/28] [P008] Fix saving/loading new setting --- src/_P008_RFID.ino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/_P008_RFID.ino b/src/_P008_RFID.ino index 578672b49..7bb428fdb 100644 --- a/src/_P008_RFID.ino +++ b/src/_P008_RFID.ino @@ -209,7 +209,7 @@ boolean Plugin_008(byte function, struct EventStruct *event, String& string) optionValues[0] = 26; optionValues[1] = 34; addFormSelector(F("Wiegand Type"), F("p008_type"), 2, options, optionValues, choice); - bool presentHexToDec = PCONFIG(1); + bool presentHexToDec = PCONFIG(1) == 1; addFormCheckBox(F("Present hex as decimal value"), F("p008_hexasdec"), presentHexToDec); addFormNote(F("Useful only for numeric keypad input!")); success = true; @@ -219,7 +219,7 @@ boolean Plugin_008(byte function, struct EventStruct *event, String& string) case PLUGIN_WEBFORM_SAVE: { PCONFIG(0) = getFormItemInt(F("p008_type")); - PCONFIG(1) = getFormItemInt(F("p008_hexasdec")); + PCONFIG(1) = isFormItemChecked(F("p008_hexasdec")) ? 1 : 0; success = true; break; } From a0f5a4d0d4785ab7c169b2f034176aff864d8fc9 Mon Sep 17 00:00:00 2001 From: tonhuisman Date: Sat, 15 Aug 2020 20:19:18 +0200 Subject: [PATCH 10/28] [P008] Fix saving/loading new setting --- src/_P008_RFID.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_P008_RFID.ino b/src/_P008_RFID.ino index 7bb428fdb..79188e98a 100644 --- a/src/_P008_RFID.ino +++ b/src/_P008_RFID.ino @@ -155,7 +155,7 @@ boolean Plugin_008(byte function, struct EventStruct *event, String& string) unsigned long old_key = ((uint32_t) UserVar[event->BaseVarIndex]) | ((uint32_t) UserVar[event->BaseVarIndex + 1])<<16; bool new_key = false; - if (PCONFIG(1)) { + if (PCONFIG(1) == 1) { Plugin_008_keyBuffer = castHexAsDec(Plugin_008_keyBuffer); } From 102bc2c40572aa71a4ca13b24dbb64f0588ba5fd Mon Sep 17 00:00:00 2001 From: tonhuisman Date: Sun, 16 Aug 2020 14:40:38 +0200 Subject: [PATCH 11/28] [I2C] Bugfixes and improvements for multiplexer implementation --- src/WebServer_DevicesPage.ino | 61 +++++++++++--------------- src/WebServer_I2C_Scanner.ino | 58 ++++++++++++------------ src/WebServer_JSON.ino | 10 ++--- src/src/DataStructs/SettingsStruct.cpp | 12 ++--- src/src/DataStructs/SettingsStruct.h | 2 +- src/src/Helpers/ESPEasy_Storage.cpp | 2 +- src/src/Helpers/Hardware.cpp | 8 ++-- 7 files changed, 69 insertions(+), 84 deletions(-) diff --git a/src/WebServer_DevicesPage.ino b/src/WebServer_DevicesPage.ino index f7ffc7065..259a633ca 100644 --- a/src/WebServer_DevicesPage.ino +++ b/src/WebServer_DevicesPage.ino @@ -235,9 +235,9 @@ void handle_devices_CopySubmittedSettings(taskIndex_t taskIndex, pluginID_t task id += String(x); bitWrite(selectedPorts, x, isFormItemChecked(id)); } - Settings.I2C_Multiplexer_Port[taskIndex] = selectedPorts; + Settings.I2C_Multiplexer_Channel[taskIndex] = selectedPorts; } else { - Settings.I2C_Multiplexer_Port[taskIndex] = getFormItemInt(F("taskdevicei2cmuxport"), 0); + Settings.I2C_Multiplexer_Channel[taskIndex] = getFormItemInt(F("taskdevicei2cmuxport"), 0); } } #endif @@ -462,20 +462,15 @@ void handle_devicess_ShowAllTasksTable(byte page) mux = F("
Multiplexer channel(s)"); uint8_t b = 0; // For adding lineBreaks for (uint8_t c = 0; c < I2CMultiplexerMaxChannels(); c++) { - if (bitRead(Settings.I2C_Multiplexer_Port[x], c)) { - mux += b % 4 == 0 ? F("
") : F(" "); + if (bitRead(Settings.I2C_Multiplexer_Channel[x], c)) { + mux += b == 0 ? F("
") : F(", "); b++; - mux += F("SD"); - mux += String(c); - mux += F("/SC"); mux += String(c); } } } else { // Single channel - mux = F("
Multiplexer SD"); - mux += String(Settings.I2C_Multiplexer_Port[x]); - mux += F("/SC"); - mux += String(Settings.I2C_Multiplexer_Port[x]); + mux = F("
Multiplexer channel "); + mux += String(Settings.I2C_Multiplexer_Channel[x]); } addHtml(mux); } @@ -780,28 +775,26 @@ void handle_devices_TaskSettingsPage(taskIndex_t taskIndex, byte page) addFormSelector(F("Multiplexer channels"),F("taskdeviceflags1"), i2c_mux_channelCount, i2c_mux_channels, i2c_mux_channelOptions, multipleMuxPorts ? 1 : 0, true); } if (multipleMuxPorts) { - addRowLabel(F("Select connections"), F("")); - html_table(F(""), false); // Sub-table - html_table_header(F("Channel")); - html_table_header(F("Enable")); - html_table_header(F("Channel")); - html_table_header(F("Enable")); - for (uint8_t x = 0; x < I2CMultiplexerMaxChannels(); x++) { - String label = F("SD"); - label += String(x); - label += F("/SC"); - label += String(x); - String id = F("taskdeviceflag1ch"); - id += String(x); - if (x % 2 == 0) { html_TR(); } // Start a new row for every 2 channels - html_TD(); - addHtml(label); - html_TD(); - addCheckBox(id, bitRead(Settings.I2C_Multiplexer_Port[taskIndex], x), false); - } - html_end_table(); + addRowLabel(F("Select connections"), F("")); + html_table(F(""), false); // Sub-table + html_table_header(F("Channel")); + html_table_header(F("Enable")); + html_table_header(F("Channel")); + html_table_header(F("Enable")); + for (uint8_t x = 0; x < I2CMultiplexerMaxChannels(); x++) { + String label = F("Channel "); + label += String(x); + String id = F("taskdeviceflag1ch"); + id += String(x); + if (x % 2 == 0) { html_TR(); } // Start a new row for every 2 channels + html_TD(); + addHtml(label); + html_TD(); + addCheckBox(id, bitRead(Settings.I2C_Multiplexer_Channel[taskIndex], x), false); + } + html_end_table(); } else { - int taskDeviceI2CMuxPort = Settings.I2C_Multiplexer_Port[taskIndex]; + int taskDeviceI2CMuxPort = Settings.I2C_Multiplexer_Channel[taskIndex]; String i2c_mux_portoptions[9]; int i2c_mux_portchoices[9]; uint8_t mux_opt = 0; @@ -810,9 +803,7 @@ void handle_devices_TaskSettingsPage(taskIndex_t taskIndex, byte page) uint8_t mux_max = I2CMultiplexerMaxChannels(); for (int8_t x = 0; x < mux_max; x++) { mux_opt++; - i2c_mux_portoptions[mux_opt] = F("SD"); - i2c_mux_portoptions[mux_opt] += String(x); - i2c_mux_portoptions[mux_opt] += F("/SC"); + i2c_mux_portoptions[mux_opt] = F("Channel "); i2c_mux_portoptions[mux_opt] += String(x); i2c_mux_portchoices[mux_opt] = x; diff --git a/src/WebServer_I2C_Scanner.ino b/src/WebServer_I2C_Scanner.ino index 50a38e00d..c9827a1d3 100644 --- a/src/WebServer_I2C_Scanner.ino +++ b/src/WebServer_I2C_Scanner.ino @@ -1,5 +1,9 @@ #ifdef WEBSERVER_I2C_SCANNER +#ifdef FEATURE_I2CMULTIPLEXER +typedef std::vector i2c_addresses_t; +#endif + #ifdef WEBSERVER_NEW_UI // ******************************************************************************** @@ -11,7 +15,7 @@ int scanI2CbusForDevices_json( // Utility function for scanning the I2C bus for , int8_t channel , int nDevices #ifdef FEATURE_I2CMULTIPLEXER - , String &excludeDevices + , i2c_addresses_t &excludeDevices #endif ) { byte error, address; @@ -22,16 +26,14 @@ int scanI2CbusForDevices_json( // Utility function for scanning the I2C bus for for (address = 1; address <= 127; address++) { #ifdef FEATURE_I2CMULTIPLEXER - if (channel != -1 && excludeDevices.length() != 0) { - String toFind = F("|"); - toFind += String(address); - toFind += F("|"); - skipCheck = (excludeDevices.indexOf(toFind) != -1); + if (channel != -1 && excludeDevices.size() != 0) { + skipCheck = excludeDevices[address]; } if (!skipCheck) { // Ignore I2C multiplexer and addresses to exclude when scanning its channels #endif Wire.beginTransmission(address); error = Wire.endTransmission(); + delay(1); if ((error == 0) || (error == 4)) { @@ -41,13 +43,9 @@ int scanI2CbusForDevices_json( // Utility function for scanning the I2C bus for if (muxAddr != -1) { if (channel == -1){ json_prop(F("I2Cbus"), F("Standard I2C bus")); - excludeDevices += F("|"); - excludeDevices += String(address); - excludeDevices += F("|"); + excludeDevices[address] = true; } else { - String i2cChannel = F("Multiplexer SD"); - i2cChannel += String(channel); - i2cChannel += F("/SC"); + String i2cChannel = F("Multiplexer channel "); i2cChannel += String(channel); json_prop(F("I2Cbus"), i2cChannel); } @@ -109,8 +107,11 @@ void handle_i2cscanner_json() { I2CSelectClockSpeed(true); // Always scan in low speed to also find old/slow devices #ifdef FEATURE_I2CMULTIPLEXER - String mainBusDevices; - mainBusDevices.reserve(32); + i2c_addresses_t mainBusDevices; + mainBusDevices.resize(128); + for (int i = 0; i < 128; i++) { + mainBusDevices[i] = false; + } nDevices = scanI2CbusForDevices_json(Settings.I2C_Multiplexer_Addr, -1, nDevices, mainBusDevices); // Channel -1 = standard I2C bus #else nDevices = scanI2CbusForDevices_json(-1, -1, nDevices); // Standard scan @@ -119,7 +120,7 @@ void handle_i2cscanner_json() { #ifdef FEATURE_I2CMULTIPLEXER if (Settings.I2C_Multiplexer_Addr != -1) { uint8_t mux_max = I2CMultiplexerMaxChannels(); - for (uint8_t channel = 0; channel < mux_max; channel++) { + for (int8_t channel = 0; channel < mux_max; channel++) { I2CMultiplexerSelect(channel); nDevices += scanI2CbusForDevices_json(Settings.I2C_Multiplexer_Addr, channel, nDevices, mainBusDevices); // Specific channels } @@ -244,7 +245,7 @@ int scanI2CbusForDevices( // Utility function for scanning the I2C bus for valid , int8_t channel , int nDevices #ifdef FEATURE_I2CMULTIPLEXER - , String &excludeDevices + , i2c_addresses_t &excludeDevices #endif ) { byte error, address; @@ -255,16 +256,14 @@ int scanI2CbusForDevices( // Utility function for scanning the I2C bus for valid for (address = 1; address <= 127; address++) { #ifdef FEATURE_I2CMULTIPLEXER - if (channel != -1 && excludeDevices.length() != 0) { - String toFind = F("|"); - toFind += String(address); - toFind += F("|"); - skipCheck = (excludeDevices.indexOf(toFind) != -1); + if (channel != -1 && excludeDevices.size() != 0) { + skipCheck = excludeDevices[address]; } if (!skipCheck) { // Ignore I2C multiplexer and addresses to exclude when scanning its channels #endif Wire.beginTransmission(address); error = Wire.endTransmission(); + delay(1); if (error == 0) { @@ -273,13 +272,9 @@ int scanI2CbusForDevices( // Utility function for scanning the I2C bus for valid if (muxAddr != -1) { if (channel == -1){ addHtml(F("Standard I2C bus")); - excludeDevices += F("|"); - excludeDevices += String(address); - excludeDevices += F("|"); + excludeDevices[address] = true; } else { - String i2cChannel = F("Multiplexer SD"); - i2cChannel += String(channel); - i2cChannel += F("/SC"); + String i2cChannel = F("Multiplexer channel "); i2cChannel += String(channel); addHtml(i2cChannel); } @@ -330,8 +325,11 @@ void handle_i2cscanner() { int nDevices = 0; I2CSelectClockSpeed(true); // Scan bus using low speed #ifdef FEATURE_I2CMULTIPLEXER - String mainBusDevices; - mainBusDevices.reserve(32); + i2c_addresses_t mainBusDevices; + mainBusDevices.resize(128); + for (int i = 0; i < 128; i++) { + mainBusDevices[i] = false; + } nDevices = scanI2CbusForDevices(Settings.I2C_Multiplexer_Addr, -1, nDevices, mainBusDevices); // Channel -1 = standard I2C bus #else nDevices = scanI2CbusForDevices(-1, -1, nDevices); // Standard scan @@ -340,7 +338,7 @@ void handle_i2cscanner() { #ifdef FEATURE_I2CMULTIPLEXER if (Settings.I2C_Multiplexer_Addr != -1) { uint8_t mux_max = I2CMultiplexerMaxChannels(); - for (uint8_t channel = 0; channel < mux_max; channel++) { + for (int8_t channel = 0; channel < mux_max; channel++) { I2CMultiplexerSelect(channel); nDevices += scanI2CbusForDevices(Settings.I2C_Multiplexer_Addr, channel, nDevices, mainBusDevices); } diff --git a/src/WebServer_JSON.ino b/src/WebServer_JSON.ino index 767426cfc..59dd0551f 100644 --- a/src/WebServer_JSON.ino +++ b/src/WebServer_JSON.ino @@ -326,14 +326,12 @@ void handle_json() stream_next_json_object_value(F("TaskDeviceNumber"), String(Settings.TaskDeviceNumber[TaskIndex])); #ifdef FEATURE_I2CMULTIPLEXER if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && Settings.I2C_Multiplexer_Addr != -1) { - int8_t channel = Settings.I2C_Multiplexer_Port[TaskIndex]; + int8_t channel = Settings.I2C_Multiplexer_Channel[TaskIndex]; if (bitRead(Settings.I2C_Flags[TaskIndex], I2C_FLAGS_MUX_MULTICHANNEL)) { addHtml(F("\"I2CBus\" : [\n")); for (uint8_t c = 0; c < I2CMultiplexerMaxChannels(); c++) { if (bitRead(channel, c)) { - String i2cChannel = F("Multiplexer SD"); - i2cChannel += String(c); - i2cChannel += F("/SC"); + String i2cChannel = F("Multiplexer channel "); i2cChannel += String(c); addHtml(i2cChannel); addHtml(F(",\n")); @@ -344,9 +342,7 @@ void handle_json() if (channel == -1){ stream_next_json_object_value(F("I2Cbus"), F("Standard I2C bus")); } else { - String i2cChannel = F("Multiplexer SD"); - i2cChannel += String(channel); - i2cChannel += F("/SC"); + String i2cChannel = F("Multiplexer channel "); i2cChannel += String(channel); stream_next_json_object_value(F("I2Cbus"), i2cChannel); } diff --git a/src/src/DataStructs/SettingsStruct.cpp b/src/src/DataStructs/SettingsStruct.cpp index 283f8cdc9..c19b87d34 100644 --- a/src/src/DataStructs/SettingsStruct.cpp +++ b/src/src/DataStructs/SettingsStruct.cpp @@ -299,12 +299,12 @@ void SettingsStruct_tmpl::clearTask(taskIndex_t task) { for (byte cv = 0; cv < PLUGIN_CONFIGLONGVAR_MAX; ++cv) { TaskDevicePluginConfigLong[task][cv] = 0; } - OLD_TaskDeviceSendData[task] = false; - TaskDeviceGlobalSync[task] = false; - TaskDeviceDataFeed[task] = 0; - TaskDeviceTimer[task] = 0; - TaskDeviceEnabled[task] = false; - I2C_Multiplexer_Port[task] = -1; + OLD_TaskDeviceSendData[task] = false; + TaskDeviceGlobalSync[task] = false; + TaskDeviceDataFeed[task] = 0; + TaskDeviceTimer[task] = 0; + TaskDeviceEnabled[task] = false; + I2C_Multiplexer_Channel[task] = -1; } template diff --git a/src/src/DataStructs/SettingsStruct.h b/src/src/DataStructs/SettingsStruct.h index 4bc01f76f..bd567117d 100644 --- a/src/src/DataStructs/SettingsStruct.h +++ b/src/src/DataStructs/SettingsStruct.h @@ -196,7 +196,7 @@ class SettingsStruct_tmpl uint8_t ETH_Wifi_Mode; int8_t I2C_Multiplexer_Type; int8_t I2C_Multiplexer_Addr; - int8_t I2C_Multiplexer_Port[N_TASKS]; + int8_t I2C_Multiplexer_Channel[N_TASKS]; uint8_t I2C_Flags[N_TASKS]; uint32_t I2C_clockSpeed_Slow; uint8_t I2C_Multiplexer_ResetPin; diff --git a/src/src/Helpers/ESPEasy_Storage.cpp b/src/src/Helpers/ESPEasy_Storage.cpp index 7e978aaef..2f74bd2fd 100644 --- a/src/src/Helpers/ESPEasy_Storage.cpp +++ b/src/src/Helpers/ESPEasy_Storage.cpp @@ -224,7 +224,7 @@ String BuildFixes() Settings.I2C_Multiplexer_Type = -1; Settings.I2C_Multiplexer_Addr = -1; for (taskIndex_t x = 0; x < TASKS_MAX; x++) { - Settings.I2C_Multiplexer_Port[x] = -1; + Settings.I2C_Multiplexer_Channel[x] = -1; } Settings.I2C_Multiplexer_ResetPin = -1; } diff --git a/src/src/Helpers/Hardware.cpp b/src/src/Helpers/Hardware.cpp index 6af271176..c675b1b9f 100644 --- a/src/src/Helpers/Hardware.cpp +++ b/src/src/Helpers/Hardware.cpp @@ -194,11 +194,11 @@ void I2CMultiplexerSelectByTaskIndex(int8_t taskIndex) { byte toWrite = 0; if (!bitRead(Settings.I2C_Flags[taskIndex], I2C_FLAGS_MUX_MULTICHANNEL)) { - uint8_t i = Settings.I2C_Multiplexer_Port[taskIndex]; + uint8_t i = Settings.I2C_Multiplexer_Channel[taskIndex]; if (i > 7) return; toWrite = I2CMultiplexerShiftBit(i); } else { - toWrite = Settings.I2C_Multiplexer_Port[taskIndex]; // Bitpattern is already correctly stored + toWrite = Settings.I2C_Multiplexer_Channel[taskIndex]; // Bitpattern is already correctly stored } if (bitRead(Settings.I2C_Flags[taskIndex], I2C_FLAGS_SLOW_SPEED)) { @@ -248,8 +248,8 @@ byte I2CMultiplexerMaxChannels() { // Has this taskIndex a channel selected? Checks for both Single channel and Multiple channel mode // taskIndex must already be validated! (0..MAX_TASKS) bool I2CMultiplexerPortSelectedForTask(taskIndex_t taskIndex) { - return ( !bitRead(Settings.I2C_Flags[taskIndex], I2C_FLAGS_MUX_MULTICHANNEL) && Settings.I2C_Multiplexer_Port[taskIndex] != -1) - || (bitRead(Settings.I2C_Flags[taskIndex], I2C_FLAGS_MUX_MULTICHANNEL) && Settings.I2C_Multiplexer_Port[taskIndex] != 0); + return ( !bitRead(Settings.I2C_Flags[taskIndex], I2C_FLAGS_MUX_MULTICHANNEL) && Settings.I2C_Multiplexer_Channel[taskIndex] != -1) + || (bitRead(Settings.I2C_Flags[taskIndex], I2C_FLAGS_MUX_MULTICHANNEL) && Settings.I2C_Multiplexer_Channel[taskIndex] != 0); } #endif From 0e68f3c9e196576153d21cc1e0b04a5d40a3e10c Mon Sep 17 00:00:00 2001 From: tonhuisman Date: Sun, 16 Aug 2020 17:38:59 +0200 Subject: [PATCH 12/28] [I2C] Optimizations and improvements --- src/WebServer_I2C_Scanner.ino | 12 ++++-------- src/WebServer_JSON.ino | 9 ++++++--- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/WebServer_I2C_Scanner.ino b/src/WebServer_I2C_Scanner.ino index c9827a1d3..574662b0e 100644 --- a/src/WebServer_I2C_Scanner.ino +++ b/src/WebServer_I2C_Scanner.ino @@ -19,14 +19,12 @@ int scanI2CbusForDevices_json( // Utility function for scanning the I2C bus for #endif ) { byte error, address; -#ifdef FEATURE_I2CMULTIPLEXER - bool skipCheck = false; -#endif for (address = 1; address <= 127; address++) { #ifdef FEATURE_I2CMULTIPLEXER - if (channel != -1 && excludeDevices.size() != 0) { + bool skipCheck = false; + if (channel != -1 && excludeDevices.size() > address) { skipCheck = excludeDevices[address]; } if (!skipCheck) { // Ignore I2C multiplexer and addresses to exclude when scanning its channels @@ -249,14 +247,12 @@ int scanI2CbusForDevices( // Utility function for scanning the I2C bus for valid #endif ) { byte error, address; -#ifdef FEATURE_I2CMULTIPLEXER - bool skipCheck = false; -#endif for (address = 1; address <= 127; address++) { #ifdef FEATURE_I2CMULTIPLEXER - if (channel != -1 && excludeDevices.size() != 0) { + bool skipCheck = false; + if (channel != -1 && excludeDevices.size() > address) { skipCheck = excludeDevices[address]; } if (!skipCheck) { // Ignore I2C multiplexer and addresses to exclude when scanning its channels diff --git a/src/WebServer_JSON.ino b/src/WebServer_JSON.ino index 59dd0551f..1e30019d5 100644 --- a/src/WebServer_JSON.ino +++ b/src/WebServer_JSON.ino @@ -328,13 +328,16 @@ void handle_json() if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && Settings.I2C_Multiplexer_Addr != -1) { int8_t channel = Settings.I2C_Multiplexer_Channel[TaskIndex]; if (bitRead(Settings.I2C_Flags[TaskIndex], I2C_FLAGS_MUX_MULTICHANNEL)) { - addHtml(F("\"I2CBus\" : [\n")); + addHtml(F("\"I2CBus\" : [")); + uint8_t b = 0; for (uint8_t c = 0; c < I2CMultiplexerMaxChannels(); c++) { if (bitRead(channel, c)) { - String i2cChannel = F("Multiplexer channel "); + if (b > 0) { addHtml(F(",\n")); } + b++; + String i2cChannel = F("\"Multiplexer channel "); i2cChannel += String(c); + i2cChannel += F("\""); addHtml(i2cChannel); - addHtml(F(",\n")); } } addHtml(F("],\n")); From e93013d6ba7d0086d187a8533260aab6f4701d15 Mon Sep 17 00:00:00 2001 From: Gijs Noorlander Date: Mon, 17 Aug 2020 11:29:39 +0200 Subject: [PATCH 13/28] [ESPEasy p2p] Init node data to 0 Node struct data was not initialized to 0, so when adding new data to these packets, you could receive uninitialized data from older nodes. --- src/Networking.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Networking.ino b/src/Networking.ino index 3677b2c81..9ebb9200c 100644 --- a/src/Networking.ino +++ b/src/Networking.ino @@ -422,7 +422,7 @@ void sendSysInfoUDP(byte repeats) uint8_t mac[] = { 0, 0, 0, 0, 0, 0 }; uint8_t *macread = NetworkMacAddressAsBytes(mac); - byte data[80]; + byte data[80] = {0}; data[0] = 255; data[1] = 1; From eae06e912c0c58d9822ca5f12b2396be8c4ba575 Mon Sep 17 00:00:00 2001 From: Gijs Noorlander Date: Mon, 17 Aug 2020 11:31:11 +0200 Subject: [PATCH 14/28] [Build size] Do not include I2C device names for minimal OTA builds These are shown on I2C scan result page. --- src/WebServer_I2C_Scanner.ino | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/WebServer_I2C_Scanner.ino b/src/WebServer_I2C_Scanner.ino index 9694fc819..298f1bfc9 100644 --- a/src/WebServer_I2C_Scanner.ino +++ b/src/WebServer_I2C_Scanner.ino @@ -73,6 +73,7 @@ void handle_i2cscanner_json() { // FIXME TD-er: Query all included plugins for their supported addresses (return name of plugin) String getKnownI2Cdevice(byte address) { String result; + #ifndef LIMIT_BUILD_SIZE switch (address) { @@ -173,6 +174,7 @@ String getKnownI2Cdevice(byte address) { result = F("Arduino PME"); break; } + #endif // LIMIT_BUILD_SIZE return result; } From 1147a2b151a6948caf89a09ef2c51b25aeb0fc67 Mon Sep 17 00:00:00 2001 From: Gijs Noorlander Date: Mon, 17 Aug 2020 14:15:53 +0200 Subject: [PATCH 15/28] [Controller] Convert controller delay handler to pointer To reduce memory usage when not used. --- src/Controller.ino | 12 +- src/Misc.ino | 28 +- src/WebServer_ControllerPage.ino | 275 +++++++++--------- src/_C001.ino | 17 +- src/_C002.ino | 16 +- src/_C003.ino | 18 +- src/_C004.ino | 17 +- src/_C005.ino | 12 +- src/_C006.ino | 12 +- src/_C007.ino | 18 +- src/_C008.ino | 20 +- src/_C009.ino | 23 +- src/_C010.ino | 23 +- src/_C011.ino | 26 +- src/_C012.ino | 25 +- src/_C013.ino | 2 +- src/_C014.ino | 12 +- src/_C015.ino | 26 +- src/_C016.ino | 18 +- src/_C017.ino | 24 +- src/_C018.ino | 26 +- .../ControllerDelayHandlerStruct.h | 41 ++- .../ControllerQueue/DelayQueueElements.cpp | 28 +- src/src/ControllerQueue/DelayQueueElements.h | 5 +- src/src/Helpers/PeriodicalActions.cpp | 14 +- 25 files changed, 491 insertions(+), 247 deletions(-) diff --git a/src/Controller.ino b/src/Controller.ino index 53b5693d0..162e74602 100644 --- a/src/Controller.ino +++ b/src/Controller.ino @@ -419,22 +419,28 @@ void SendStatus(EventValueSource::Enum source, const String& status) #ifdef USES_MQTT bool MQTT_queueFull(controllerIndex_t controller_idx) { + if (MQTTDelayHandler == nullptr) { + return true; + } MQTT_queue_element dummy_element; dummy_element.controller_idx = controller_idx; - if (MQTTDelayHandler.queueFull(dummy_element)) { + if (MQTTDelayHandler->queueFull(dummy_element)) { // The queue is full, try to make some room first. processMQTTdelayQueue(); - return MQTTDelayHandler.queueFull(dummy_element); + return MQTTDelayHandler->queueFull(dummy_element); } return false; } bool MQTTpublish(controllerIndex_t controller_idx, const char *topic, const char *payload, bool retained) { + if (MQTTDelayHandler == nullptr) { + return false; + } if (MQTT_queueFull(controller_idx)) { return false; } - const bool success = MQTTDelayHandler.addToQueue(MQTT_queue_element(controller_idx, topic, payload, retained)); + const bool success = MQTTDelayHandler->addToQueue(MQTT_queue_element(controller_idx, topic, payload, retained)); scheduleNextMQTTdelayQueue(); return success; } diff --git a/src/Misc.ino b/src/Misc.ino index 31edc73dd..3a0302b4b 100644 --- a/src/Misc.ino +++ b/src/Misc.ino @@ -1092,20 +1092,22 @@ void ResetFactory() { // Place in a scope to have its memory freed ASAP MakeControllerSettings(ControllerSettings); - safe_strncpy(ControllerSettings.Subscribe, F(DEFAULT_SUB), sizeof(ControllerSettings.Subscribe)); - safe_strncpy(ControllerSettings.Publish, F(DEFAULT_PUB), sizeof(ControllerSettings.Publish)); - safe_strncpy(ControllerSettings.MQTTLwtTopic, F(DEFAULT_MQTT_LWT_TOPIC), sizeof(ControllerSettings.MQTTLwtTopic)); - safe_strncpy(ControllerSettings.LWTMessageConnect, F(DEFAULT_MQTT_LWT_CONNECT_MESSAGE), sizeof(ControllerSettings.LWTMessageConnect)); - safe_strncpy(ControllerSettings.LWTMessageDisconnect, F(DEFAULT_MQTT_LWT_DISCONNECT_MESSAGE), sizeof(ControllerSettings.LWTMessageDisconnect)); - str2ip((char*)DEFAULT_SERVER, ControllerSettings.IP); - ControllerSettings.setHostname(F(DEFAULT_SERVER_HOST)); - ControllerSettings.UseDNS = DEFAULT_SERVER_USEDNS; - ControllerSettings.useExtendedCredentials(DEFAULT_USE_EXTD_CONTROLLER_CREDENTIALS); - ControllerSettings.Port = DEFAULT_PORT; - setControllerUser(0, ControllerSettings, F(DEFAULT_CONTROLLER_USER)); - setControllerPass(0, ControllerSettings, F(DEFAULT_CONTROLLER_PASS)); + if (AllocatedControllerSettings()) { + safe_strncpy(ControllerSettings.Subscribe, F(DEFAULT_SUB), sizeof(ControllerSettings.Subscribe)); + safe_strncpy(ControllerSettings.Publish, F(DEFAULT_PUB), sizeof(ControllerSettings.Publish)); + safe_strncpy(ControllerSettings.MQTTLwtTopic, F(DEFAULT_MQTT_LWT_TOPIC), sizeof(ControllerSettings.MQTTLwtTopic)); + safe_strncpy(ControllerSettings.LWTMessageConnect, F(DEFAULT_MQTT_LWT_CONNECT_MESSAGE), sizeof(ControllerSettings.LWTMessageConnect)); + safe_strncpy(ControllerSettings.LWTMessageDisconnect, F(DEFAULT_MQTT_LWT_DISCONNECT_MESSAGE), sizeof(ControllerSettings.LWTMessageDisconnect)); + str2ip((char*)DEFAULT_SERVER, ControllerSettings.IP); + ControllerSettings.setHostname(F(DEFAULT_SERVER_HOST)); + ControllerSettings.UseDNS = DEFAULT_SERVER_USEDNS; + ControllerSettings.useExtendedCredentials(DEFAULT_USE_EXTD_CONTROLLER_CREDENTIALS); + ControllerSettings.Port = DEFAULT_PORT; + setControllerUser(0, ControllerSettings, F(DEFAULT_CONTROLLER_USER)); + setControllerPass(0, ControllerSettings, F(DEFAULT_CONTROLLER_PASS)); - SaveControllerSettings(0, ControllerSettings); + SaveControllerSettings(0, ControllerSettings); + } } #endif diff --git a/src/WebServer_ControllerPage.ino b/src/WebServer_ControllerPage.ino index fc8eede2b..4b719b959 100644 --- a/src/WebServer_ControllerPage.ino +++ b/src/WebServer_ControllerPage.ino @@ -161,62 +161,63 @@ void handle_controllers_ShowAllControllersTable() html_table_header("Port"); MakeControllerSettings(ControllerSettings); - - for (controllerIndex_t x = 0; x < CONTROLLER_MAX; x++) - { - const bool cplugin_set = Settings.Protocol[x] != INVALID_C_PLUGIN_ID; - - - LoadControllerSettings(x, ControllerSettings); - html_TR_TD(); - - if (cplugin_set && !supportedCPluginID(Settings.Protocol[x])) { - html_add_button_prefix(F("red"), true); - } else { - html_add_button_prefix(); - } + if (AllocatedControllerSettings()) { + for (controllerIndex_t x = 0; x < CONTROLLER_MAX; x++) { - String html; - html.reserve(32); - html += F("controllers?index="); - html += x + 1; - html += F("'>"); + const bool cplugin_set = Settings.Protocol[x] != INVALID_C_PLUGIN_ID; - if (cplugin_set) { - html += F("Edit"); + + LoadControllerSettings(x, ControllerSettings); + html_TR_TD(); + + if (cplugin_set && !supportedCPluginID(Settings.Protocol[x])) { + html_add_button_prefix(F("red"), true); } else { - html += F("Add"); + html_add_button_prefix(); } - html += F(""); - html += getControllerSymbol(x); - addHtml(html); - } - html_TD(); - - if (cplugin_set) - { - addEnabled(Settings.ControllerEnabled[x]); - - html_TD(); - addHtml(getCPluginNameFromCPluginID(Settings.Protocol[x])); - html_TD(); { - const protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(x); - String hostDescription; - CPluginCall(ProtocolIndex, CPlugin::Function::CPLUGIN_WEBFORM_SHOW_HOST_CONFIG, 0, hostDescription); + String html; + html.reserve(32); + html += F("controllers?index="); + html += x + 1; + html += F("'>"); - if (hostDescription.length() != 0) { - addHtml(hostDescription); + if (cplugin_set) { + html += F("Edit"); } else { - addHtml(ControllerSettings.getHost()); + html += F("Add"); } + html += F(""); + html += getControllerSymbol(x); + addHtml(html); } - html_TD(); - addHtml(String(ControllerSettings.Port)); - } - else { - html_TD(3); + + if (cplugin_set) + { + addEnabled(Settings.ControllerEnabled[x]); + + html_TD(); + addHtml(getCPluginNameFromCPluginID(Settings.Protocol[x])); + html_TD(); + { + const protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(x); + String hostDescription; + CPluginCall(ProtocolIndex, CPlugin::Function::CPLUGIN_WEBFORM_SHOW_HOST_CONFIG, 0, hostDescription); + + if (hostDescription.length() != 0) { + addHtml(hostDescription); + } else { + addHtml(ControllerSettings.getHost()); + } + } + + html_TD(); + addHtml(String(ControllerSettings.Port)); + } + else { + html_TD(3); + } } } html_end_table(); @@ -258,102 +259,106 @@ void handle_controllers_ControllerSettingsPage(controllerIndex_t controllerindex if (Settings.Protocol[controllerindex]) { MakeControllerSettings(ControllerSettings); - LoadControllerSettings(controllerindex, ControllerSettings); + if (!AllocatedControllerSettings()) { + addHtmlError(F("Out of memory, cannot load page")); + } else { + LoadControllerSettings(controllerindex, ControllerSettings); - if (!Protocol[ProtocolIndex].Custom) - { - if (Protocol[ProtocolIndex].usesHost) { - addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_USE_DNS); + if (!Protocol[ProtocolIndex].Custom) + { + if (Protocol[ProtocolIndex].usesHost) { + addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_USE_DNS); - if (ControllerSettings.UseDNS) - { - addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_HOSTNAME); + if (ControllerSettings.UseDNS) + { + addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_HOSTNAME); + } + else + { + addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_IP); + } } - else + addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_PORT); + + if (Protocol[ProtocolIndex].usesQueue) { + addTableSeparator(F("Controller Queue"), 2, 3); + addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_MIN_SEND_INTERVAL); + addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_MAX_QUEUE_DEPTH); + addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_MAX_RETRIES); + addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_FULL_QUEUE_ACTION); + } + + if (Protocol[ProtocolIndex].usesCheckReply) { + addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_CHECK_REPLY); + } + + if (Protocol[ProtocolIndex].usesTimeout) { + addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_TIMEOUT); + } + + if (Protocol[ProtocolIndex].usesSampleSets) { + addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_SAMPLE_SET_INITIATOR); + } + + if (Protocol[ProtocolIndex].useExtendedCredentials()) { + addTableSeparator(F("Credentials"), 2, 3); + addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_USE_EXTENDED_CREDENTIALS); + } + + if (Protocol[ProtocolIndex].usesAccount) { - addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_IP); + addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_USER); + } + + if (Protocol[ProtocolIndex].usesPassword) + { + addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_PASS); + } + #ifdef USES_MQTT + if (Protocol[ProtocolIndex].usesMQTT) { + addTableSeparator(F("MQTT"), 2, 3); + + addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_CLIENT_ID); + addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_UNIQUE_CLIENT_ID_RECONNECT); + addRowLabel(F("Current Client ID")); + addHtml(getMQTTclientID(ControllerSettings)); + addFormNote(F("Updated on load of this page")); + addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_RETAINFLAG); + } + #endif // USES_MQTT + + + if (Protocol[ProtocolIndex].usesTemplate || Protocol[ProtocolIndex].usesMQTT) + { + addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_SUBSCRIBE); + addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_PUBLISH); + } + #ifdef USES_MQTT + if (Protocol[ProtocolIndex].usesMQTT) + { + addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_LWT_TOPIC); + addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_LWT_CONNECT_MESSAGE); + addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_LWT_DISCONNECT_MESSAGE); + addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_SEND_LWT); + addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_WILL_RETAIN); + addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_CLEAN_SESSION); + } + #endif // USES_MQTT + } + { + // Load controller specific settings + struct EventStruct TempEvent; + TempEvent.ControllerIndex = controllerindex; + + String webformLoadString; + CPluginCall(ProtocolIndex, CPlugin::Function::CPLUGIN_WEBFORM_LOAD, &TempEvent, webformLoadString); + + if (webformLoadString.length() > 0) { + addHtmlError(F("Bug in CPlugin::Function::CPLUGIN_WEBFORM_LOAD, should not append to string, use addHtml() instead")); } } - addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_PORT); - - if (Protocol[ProtocolIndex].usesQueue) { - addTableSeparator(F("Controller Queue"), 2, 3); - addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_MIN_SEND_INTERVAL); - addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_MAX_QUEUE_DEPTH); - addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_MAX_RETRIES); - addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_FULL_QUEUE_ACTION); - } - - if (Protocol[ProtocolIndex].usesCheckReply) { - addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_CHECK_REPLY); - } - - if (Protocol[ProtocolIndex].usesTimeout) { - addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_TIMEOUT); - } - - if (Protocol[ProtocolIndex].usesSampleSets) { - addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_SAMPLE_SET_INITIATOR); - } - - if (Protocol[ProtocolIndex].useExtendedCredentials()) { - addTableSeparator(F("Credentials"), 2, 3); - addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_USE_EXTENDED_CREDENTIALS); - } - - if (Protocol[ProtocolIndex].usesAccount) - { - addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_USER); - } - - if (Protocol[ProtocolIndex].usesPassword) - { - addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_PASS); - } - #ifdef USES_MQTT - if (Protocol[ProtocolIndex].usesMQTT) { - addTableSeparator(F("MQTT"), 2, 3); - - addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_CLIENT_ID); - addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_UNIQUE_CLIENT_ID_RECONNECT); - addRowLabel(F("Current Client ID")); - addHtml(getMQTTclientID(ControllerSettings)); - addFormNote(F("Updated on load of this page")); - addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_RETAINFLAG); - } - #endif // USES_MQTT - - - if (Protocol[ProtocolIndex].usesTemplate || Protocol[ProtocolIndex].usesMQTT) - { - addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_SUBSCRIBE); - addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_PUBLISH); - } - #ifdef USES_MQTT - if (Protocol[ProtocolIndex].usesMQTT) - { - addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_LWT_TOPIC); - addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_LWT_CONNECT_MESSAGE); - addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_LWT_DISCONNECT_MESSAGE); - addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_SEND_LWT); - addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_WILL_RETAIN); - addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_CLEAN_SESSION); - } - #endif // USES_MQTT + addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_ENABLED); } - { - // Load controller specific settings - struct EventStruct TempEvent; - TempEvent.ControllerIndex = controllerindex; - - String webformLoadString; - CPluginCall(ProtocolIndex, CPlugin::Function::CPLUGIN_WEBFORM_LOAD, &TempEvent, webformLoadString); - - if (webformLoadString.length() > 0) { - addHtmlError(F("Bug in CPlugin::Function::CPLUGIN_WEBFORM_LOAD, should not append to string, use addHtml() instead")); - } - } - addControllerParameterForm(ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_ENABLED); } addFormSeparator(2); diff --git a/src/_C001.ino b/src/_C001.ino index a831a2ba5..f3d9cc9fd 100644 --- a/src/_C001.ino +++ b/src/_C001.ino @@ -35,14 +35,21 @@ bool CPlugin_001(CPlugin::Function function, struct EventStruct *event, String& case CPlugin::Function::CPLUGIN_INIT: { - MakeControllerSettings(ControllerSettings); - LoadControllerSettings(event->ControllerIndex, ControllerSettings); - C001_DelayHandler.configureControllerSettings(ControllerSettings); + success = init_c001_delay_queue(event->ControllerIndex); + break; + } + + case CPlugin::Function::CPLUGIN_EXIT: + { + exit_c001_delay_queue(); break; } case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: { + if (C001_DelayHandler == nullptr) { + break; + } if (event->idx != 0) { // We now create a URI for the request @@ -98,8 +105,8 @@ bool CPlugin_001(CPlugin::Function function, struct EventStruct *event, String& url += mapVccToDomoticz(); #endif - success = C001_DelayHandler.addToQueue(C001_queue_element(event->ControllerIndex, url)); - Scheduler.scheduleNextDelayQueue(ESPEasy_Scheduler::IntervalTimer_e::TIMER_C001_DELAY_QUEUE, C001_DelayHandler.getNextScheduleTime()); + success = C001_DelayHandler->addToQueue(C001_queue_element(event->ControllerIndex, url)); + Scheduler.scheduleNextDelayQueue(ESPEasy_Scheduler::IntervalTimer_e::TIMER_C001_DELAY_QUEUE, C001_DelayHandler->getNextScheduleTime()); } // if ixd !=0 else { diff --git a/src/_C002.ino b/src/_C002.ino index 81b665b45..510804b85 100644 --- a/src/_C002.ino +++ b/src/_C002.ino @@ -13,7 +13,7 @@ #include String CPlugin_002_pubname; -bool CPlugin_002_retain = false; +bool CPlugin_002_mqtt_retainFlag = false; bool CPlugin_002(CPlugin::Function function, struct EventStruct *event, String& string) { @@ -42,11 +42,13 @@ bool CPlugin_002(CPlugin::Function function, struct EventStruct *event, String& case CPlugin::Function::CPLUGIN_INIT: { - MakeControllerSettings(ControllerSettings); - LoadControllerSettings(event->ControllerIndex, ControllerSettings); - MQTTDelayHandler.configureControllerSettings(ControllerSettings); - CPlugin_002_pubname = ControllerSettings.Publish; - CPlugin_002_retain = ControllerSettings.mqtt_retainFlag(); + success = init_mqtt_delay_queue(event->ControllerIndex, CPlugin_002_pubname, CPlugin_002_mqtt_retainFlag); + break; + } + + case CPlugin::Function::CPLUGIN_EXIT: + { + exit_mqtt_delay_queue(); break; } @@ -236,7 +238,7 @@ bool CPlugin_002(CPlugin::Function function, struct EventStruct *event, String& String pubname = CPlugin_002_pubname; parseControllerVariables(pubname, event, false); - success = MQTTpublish(event->ControllerIndex, pubname.c_str(), json.c_str(), CPlugin_002_retain); + success = MQTTpublish(event->ControllerIndex, pubname.c_str(), json.c_str(), CPlugin_002_mqtt_retainFlag); } // if ixd !=0 else { diff --git a/src/_C003.ino b/src/_C003.ino index 00e04c659..3a8fcd20e 100644 --- a/src/_C003.ino +++ b/src/_C003.ino @@ -33,22 +33,30 @@ bool CPlugin_003(CPlugin::Function function, struct EventStruct *event, String& case CPlugin::Function::CPLUGIN_INIT: { - MakeControllerSettings(ControllerSettings); - LoadControllerSettings(event->ControllerIndex, ControllerSettings); - C003_DelayHandler.configureControllerSettings(ControllerSettings); + success = init_c003_delay_queue(event->ControllerIndex); + break; + } + + case CPlugin::Function::CPLUGIN_EXIT: + { + exit_c003_delay_queue(); break; } case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: { + if (C003_DelayHandler == nullptr) { + break; + } + // We now create a URI for the request String url = F("variableset "); url += event->idx; url += ","; url += formatUserVarNoCheck(event, 0); url += "\n"; - success = C003_DelayHandler.addToQueue(C003_queue_element(event->ControllerIndex, url)); - Scheduler.scheduleNextDelayQueue(ESPEasy_Scheduler::IntervalTimer_e::TIMER_C003_DELAY_QUEUE, C003_DelayHandler.getNextScheduleTime()); + success = C003_DelayHandler->addToQueue(C003_queue_element(event->ControllerIndex, url)); + Scheduler.scheduleNextDelayQueue(ESPEasy_Scheduler::IntervalTimer_e::TIMER_C003_DELAY_QUEUE, C003_DelayHandler->getNextScheduleTime()); break; } diff --git a/src/_C004.ino b/src/_C004.ino index 932e61057..39976ec95 100644 --- a/src/_C004.ino +++ b/src/_C004.ino @@ -33,9 +33,13 @@ bool CPlugin_004(CPlugin::Function function, struct EventStruct *event, String& case CPlugin::Function::CPLUGIN_INIT: { - MakeControllerSettings(ControllerSettings); - LoadControllerSettings(event->ControllerIndex, ControllerSettings); - C004_DelayHandler.configureControllerSettings(ControllerSettings); + success = init_c004_delay_queue(event->ControllerIndex); + break; + } + + case CPlugin::Function::CPLUGIN_EXIT: + { + exit_c004_delay_queue(); break; } @@ -58,8 +62,11 @@ bool CPlugin_004(CPlugin::Function function, struct EventStruct *event, String& case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: { - success = C004_DelayHandler.addToQueue(C004_queue_element(event)); - Scheduler.scheduleNextDelayQueue(ESPEasy_Scheduler::IntervalTimer_e::TIMER_C004_DELAY_QUEUE, C004_DelayHandler.getNextScheduleTime()); + if (C004_DelayHandler == nullptr) { + break; + } + success = C004_DelayHandler->addToQueue(C004_queue_element(event)); + Scheduler.scheduleNextDelayQueue(ESPEasy_Scheduler::IntervalTimer_e::TIMER_C004_DELAY_QUEUE, C004_DelayHandler->getNextScheduleTime()); break; } diff --git a/src/_C005.ino b/src/_C005.ino index 30682961d..d0ca909b7 100644 --- a/src/_C005.ino +++ b/src/_C005.ino @@ -39,11 +39,13 @@ bool CPlugin_005(CPlugin::Function function, struct EventStruct *event, String& case CPlugin::Function::CPLUGIN_INIT: { - MakeControllerSettings(ControllerSettings); - LoadControllerSettings(event->ControllerIndex, ControllerSettings); - MQTTDelayHandler.configureControllerSettings(ControllerSettings); - CPlugin_005_pubname = ControllerSettings.Publish; - CPlugin_005_mqtt_retainFlag = ControllerSettings.mqtt_retainFlag(); + success = init_mqtt_delay_queue(event->ControllerIndex, CPlugin_005_pubname, CPlugin_005_mqtt_retainFlag); + break; + } + + case CPlugin::Function::CPLUGIN_EXIT: + { + exit_mqtt_delay_queue(); break; } diff --git a/src/_C006.ino b/src/_C006.ino index f03b86fec..73cb0dbc2 100644 --- a/src/_C006.ino +++ b/src/_C006.ino @@ -39,11 +39,13 @@ bool CPlugin_006(CPlugin::Function function, struct EventStruct *event, String& case CPlugin::Function::CPLUGIN_INIT: { - MakeControllerSettings(ControllerSettings); - LoadControllerSettings(event->ControllerIndex, ControllerSettings); - MQTTDelayHandler.configureControllerSettings(ControllerSettings); - CPlugin_006_pubname = ControllerSettings.Publish; - CPlugin_006_mqtt_retainFlag = ControllerSettings.mqtt_retainFlag(); + success = init_mqtt_delay_queue(event->ControllerIndex, CPlugin_006_pubname, CPlugin_006_mqtt_retainFlag); + break; + } + + case CPlugin::Function::CPLUGIN_EXIT: + { + exit_mqtt_delay_queue(); break; } diff --git a/src/_C007.ino b/src/_C007.ino index 13feffb17..59214505c 100644 --- a/src/_C007.ino +++ b/src/_C007.ino @@ -33,14 +33,22 @@ bool CPlugin_007(CPlugin::Function function, struct EventStruct *event, String& case CPlugin::Function::CPLUGIN_INIT: { - MakeControllerSettings(ControllerSettings); - LoadControllerSettings(event->ControllerIndex, ControllerSettings); - C007_DelayHandler.configureControllerSettings(ControllerSettings); + success = init_c004_delay_queue(event->ControllerIndex); + break; + } + + case CPlugin::Function::CPLUGIN_EXIT: + { + exit_c007_delay_queue(); break; } case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: { + if (C007_DelayHandler == nullptr) { + break; + } + if (event->sensorType == SENSOR_TYPE_STRING) { addLog(LOG_LEVEL_ERROR, F("emoncms : No support for SENSOR_TYPE_STRING")); break; @@ -50,8 +58,8 @@ bool CPlugin_007(CPlugin::Function function, struct EventStruct *event, String& addLog(LOG_LEVEL_ERROR, F("emoncms : Unknown sensortype or too many sensor values")); break; } - success = C007_DelayHandler.addToQueue(C007_queue_element(event)); - Scheduler.scheduleNextDelayQueue(ESPEasy_Scheduler::IntervalTimer_e::TIMER_C007_DELAY_QUEUE, C007_DelayHandler.getNextScheduleTime()); + success = C007_DelayHandler->addToQueue(C007_queue_element(event)); + Scheduler.scheduleNextDelayQueue(ESPEasy_Scheduler::IntervalTimer_e::TIMER_C007_DELAY_QUEUE, C007_DelayHandler->getNextScheduleTime()); break; } diff --git a/src/_C008.ino b/src/_C008.ino index 9e352e51d..b4a60bfb9 100644 --- a/src/_C008.ino +++ b/src/_C008.ino @@ -37,9 +37,13 @@ bool CPlugin_008(CPlugin::Function function, struct EventStruct *event, String& case CPlugin::Function::CPLUGIN_INIT: { - MakeControllerSettings(ControllerSettings); - LoadControllerSettings(event->ControllerIndex, ControllerSettings); - C008_DelayHandler.configureControllerSettings(ControllerSettings); + success = init_c008_delay_queue(event->ControllerIndex); + break; + } + + case CPlugin::Function::CPLUGIN_EXIT: + { + exit_c008_delay_queue(); break; } @@ -52,6 +56,10 @@ bool CPlugin_008(CPlugin::Function function, struct EventStruct *event, String& case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: { + if (C008_DelayHandler == nullptr) { + break; + } + String pubname; { // Place the ControllerSettings in a scope to free the memory as soon as we got all relevant information. @@ -66,12 +74,12 @@ bool CPlugin_008(CPlugin::Function function, struct EventStruct *event, String& // FIXME TD-er must define a proper move operator byte valueCount = getValueCountFromSensorType(event->sensorType); - success = C008_DelayHandler.addToQueue(C008_queue_element(event, valueCount)); + success = C008_DelayHandler->addToQueue(C008_queue_element(event, valueCount)); if (success) { // Element was added. // Now we try to append to the existing element // and thus preventing the need to create a long string only to copy it to a queue element. - C008_queue_element &element = C008_DelayHandler.sendQueue.back(); + C008_queue_element &element = C008_DelayHandler->sendQueue.back(); // Collect the values at the same run, to make sure all are from the same sample if (ExtraTaskSettings.TaskIndex != event->TaskIndex) { @@ -95,7 +103,7 @@ bool CPlugin_008(CPlugin::Function function, struct EventStruct *event, String& } } } - Scheduler.scheduleNextDelayQueue(ESPEasy_Scheduler::IntervalTimer_e::TIMER_C008_DELAY_QUEUE, C008_DelayHandler.getNextScheduleTime()); + Scheduler.scheduleNextDelayQueue(ESPEasy_Scheduler::IntervalTimer_e::TIMER_C008_DELAY_QUEUE, C008_DelayHandler->getNextScheduleTime()); break; } diff --git a/src/_C009.ino b/src/_C009.ino index 16ccaf08e..6c81bab40 100644 --- a/src/_C009.ino +++ b/src/_C009.ino @@ -57,12 +57,31 @@ bool CPlugin_009(CPlugin::Function function, struct EventStruct *event, String& break; } + case CPlugin::Function::CPLUGIN_INIT: + { + success = init_c009_delay_queue(event->ControllerIndex); + break; + } + + case CPlugin::Function::CPLUGIN_EXIT: + { + exit_c009_delay_queue(); + break; + } + case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: { + if (C009_DelayHandler == nullptr) { + break; + } + byte valueCount = getValueCountFromSensorType(event->sensorType); C009_queue_element element(event); MakeControllerSettings(ControllerSettings); + if (!AllocatedControllerSettings()) { + break; + } LoadControllerSettings(event->ControllerIndex, ControllerSettings); for (byte x = 0; x < valueCount; x++) @@ -70,8 +89,8 @@ bool CPlugin_009(CPlugin::Function function, struct EventStruct *event, String& element.txt[x] = formatUserVarNoCheck(event, x); } // FIXME TD-er must define a proper move operator - success = C009_DelayHandler.addToQueue(C009_queue_element(element)); - Scheduler.scheduleNextDelayQueue(ESPEasy_Scheduler::IntervalTimer_e::TIMER_C009_DELAY_QUEUE, C009_DelayHandler.getNextScheduleTime()); + success = C009_DelayHandler->addToQueue(C009_queue_element(element)); + Scheduler.scheduleNextDelayQueue(ESPEasy_Scheduler::IntervalTimer_e::TIMER_C009_DELAY_QUEUE, C009_DelayHandler->getNextScheduleTime()); break; } diff --git a/src/_C010.ino b/src/_C010.ino index b200952f9..e863b89ae 100644 --- a/src/_C010.ino +++ b/src/_C010.ino @@ -39,8 +39,24 @@ bool CPlugin_010(CPlugin::Function function, struct EventStruct *event, String& break; } + case CPlugin::Function::CPLUGIN_INIT: + { + success = init_c010_delay_queue(event->ControllerIndex); + break; + } + + case CPlugin::Function::CPLUGIN_EXIT: + { + exit_c010_delay_queue(); + break; + } + case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: { + if (C010_DelayHandler == nullptr) { + break; + } + byte valueCount = getValueCountFromSensorType(event->sensorType); C010_queue_element element(event, valueCount); if (ExtraTaskSettings.TaskIndex != event->TaskIndex) { @@ -49,6 +65,9 @@ bool CPlugin_010(CPlugin::Function function, struct EventStruct *event, String& } MakeControllerSettings(ControllerSettings); + if (!AllocatedControllerSettings()) { + break; + } LoadControllerSettings(event->ControllerIndex, ControllerSettings); for (byte x = 0; x < valueCount; x++) @@ -65,8 +84,8 @@ bool CPlugin_010(CPlugin::Function function, struct EventStruct *event, String& } } // FIXME TD-er must define a proper move operator - success = C010_DelayHandler.addToQueue(C010_queue_element(element)); - Scheduler.scheduleNextDelayQueue(ESPEasy_Scheduler::IntervalTimer_e::TIMER_C010_DELAY_QUEUE, C010_DelayHandler.getNextScheduleTime()); + success = C010_DelayHandler->addToQueue(C010_queue_element(element)); + Scheduler.scheduleNextDelayQueue(ESPEasy_Scheduler::IntervalTimer_e::TIMER_C010_DELAY_QUEUE, C010_DelayHandler->getNextScheduleTime()); break; } diff --git a/src/_C011.ino b/src/_C011.ino index b743610bb..db227415c 100644 --- a/src/_C011.ino +++ b/src/_C011.ino @@ -53,6 +53,18 @@ bool CPlugin_011(CPlugin::Function function, struct EventStruct *event, String& break; } + case CPlugin::Function::CPLUGIN_INIT: + { + success = init_c011_delay_queue(event->ControllerIndex); + break; + } + + case CPlugin::Function::CPLUGIN_EXIT: + { + exit_c011_delay_queue(); + break; + } + case CPlugin::Function::CPLUGIN_WEBFORM_LOAD: { String escapeBuffer; @@ -121,6 +133,10 @@ bool CPlugin_011(CPlugin::Function function, struct EventStruct *event, String& case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: { + if (C011_DelayHandler == nullptr) { + break; + } + success = Create_schedule_HTTP_C011(event); break; } @@ -162,6 +178,10 @@ bool do_process_c011_delay_queue(int controller_number, const C011_queue_element // ******************************************************************************** boolean Create_schedule_HTTP_C011(struct EventStruct *event) { + if (C011_DelayHandler == nullptr) { + return false; + } + String authHeader; String hostportString; @@ -208,14 +228,14 @@ boolean Create_schedule_HTTP_C011(struct EventStruct *event) removeExtraNewLine(payload); // Add a new element to the queue with the minimal payload - success = C011_DelayHandler.addToQueue(C011_queue_element(event->ControllerIndex, payload)); + success = C011_DelayHandler->addToQueue(C011_queue_element(event->ControllerIndex, payload)); } if (success) { // Element was added. // Now we try to append to the existing element // and thus preventing the need to create a long string only to copy it to a queue element. - C011_queue_element& element = C011_DelayHandler.sendQueue.back(); + C011_queue_element& element = C011_DelayHandler->sendQueue.back(); if (strlen(customConfig->HttpHeader) > 0) { element.txt += customConfig->HttpHeader; @@ -236,7 +256,7 @@ boolean Create_schedule_HTTP_C011(struct EventStruct *event) addNewLine(element.txt); } - Scheduler.scheduleNextDelayQueue(ESPEasy_Scheduler::IntervalTimer_e::TIMER_C011_DELAY_QUEUE, C011_DelayHandler.getNextScheduleTime()); + Scheduler.scheduleNextDelayQueue(ESPEasy_Scheduler::IntervalTimer_e::TIMER_C011_DELAY_QUEUE, C011_DelayHandler->getNextScheduleTime()); return success; } diff --git a/src/_C012.ino b/src/_C012.ino index ffd118b38..067296353 100644 --- a/src/_C012.ino +++ b/src/_C012.ino @@ -36,8 +36,23 @@ bool CPlugin_012(CPlugin::Function function, struct EventStruct *event, String& break; } - case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: + case CPlugin::Function::CPLUGIN_INIT: { + success = init_c012_delay_queue(event->ControllerIndex); + break; + } + + case CPlugin::Function::CPLUGIN_EXIT: + { + exit_c012_delay_queue(); + break; + } + + case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: + { + if (C012_DelayHandler == nullptr) { + break; + } // Collect the values at the same run, to make sure all are from the same sample byte valueCount = getValueCountFromSensorType(event->sensorType); C012_queue_element element(event, valueCount); @@ -47,6 +62,10 @@ bool CPlugin_012(CPlugin::Function function, struct EventStruct *event, String& } MakeControllerSettings(ControllerSettings); + if (!AllocatedControllerSettings()) { + break; + } + LoadControllerSettings(event->ControllerIndex, ControllerSettings); for (byte x = 0; x < valueCount; x++) @@ -62,8 +81,8 @@ bool CPlugin_012(CPlugin::Function function, struct EventStruct *event, String& } } // FIXME TD-er must define a proper move operator - success = C012_DelayHandler.addToQueue(C012_queue_element(element)); - Scheduler.scheduleNextDelayQueue(ESPEasy_Scheduler::IntervalTimer_e::TIMER_C012_DELAY_QUEUE, C012_DelayHandler.getNextScheduleTime()); + success = C012_DelayHandler->addToQueue(C012_queue_element(element)); + Scheduler.scheduleNextDelayQueue(ESPEasy_Scheduler::IntervalTimer_e::TIMER_C012_DELAY_QUEUE, C012_DelayHandler->getNextScheduleTime()); break; } diff --git a/src/_C013.ino b/src/_C013.ino index 6a69ea6db..756e10dc7 100644 --- a/src/_C013.ino +++ b/src/_C013.ino @@ -97,7 +97,7 @@ bool CPlugin_013(CPlugin::Function function, struct EventStruct *event, String& /* case CPlugin::Function::CPLUGIN_FLUSH: { - process_c013_delay_queue(); + process_c013_delay_queue(event->ControllerIndex); delay(0); break; } diff --git a/src/_C014.ino b/src/_C014.ino index 2ed2d7093..8a986dd22 100644 --- a/src/_C014.ino +++ b/src/_C014.ino @@ -158,11 +158,13 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& case CPlugin::Function::CPLUGIN_INIT: { - MakeControllerSettings(ControllerSettings); - LoadControllerSettings(event->ControllerIndex, ControllerSettings); - MQTTDelayHandler.configureControllerSettings(ControllerSettings); - CPlugin_014_pubname = ControllerSettings.Publish; - CPlugin_014_mqtt_retainFlag = ControllerSettings.mqtt_retainFlag(); + success = init_mqtt_delay_queue(event->ControllerIndex, CPlugin_014_pubname, CPlugin_014_mqtt_retainFlag); + break; + } + + case CPlugin::Function::CPLUGIN_EXIT: + { + exit_mqtt_delay_queue(); break; } diff --git a/src/_C015.ino b/src/_C015.ino index 24a59a5d2..6b9b9e5f9 100644 --- a/src/_C015.ino +++ b/src/_C015.ino @@ -88,12 +88,20 @@ bool CPlugin_015(CPlugin::Function function, struct EventStruct *event, String& case CPlugin::Function::CPLUGIN_INIT: { - // when connected to another server and user has changed settings - if (Blynk.connected()){ + success = init_c015_delay_queue(event->ControllerIndex); + + // when connected to another server and user has changed settings + if (success && Blynk.connected()){ addLog(LOG_LEVEL_INFO, F(C015_LOG_PREFIX "disconnect from server")); Blynk.disconnect(); - } - break; + } + break; + } + + case CPlugin::Function::CPLUGIN_EXIT: + { + exit_c015_delay_queue(); + break; } #ifdef CPLUGIN_015_SSL @@ -142,6 +150,10 @@ bool CPlugin_015(CPlugin::Function function, struct EventStruct *event, String& case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: { + if (C015_DelayHandler == nullptr) { + break; + } + if (!Settings.ControllerEnabled[event->ControllerIndex]) break; @@ -149,12 +161,12 @@ bool CPlugin_015(CPlugin::Function function, struct EventStruct *event, String& byte valueCount = getValueCountFromSensorType(event->sensorType); // FIXME TD-er must define a proper move operator - success = C015_DelayHandler.addToQueue(C015_queue_element(event, valueCount)); + success = C015_DelayHandler->addToQueue(C015_queue_element(event, valueCount)); if (success) { // Element was added. // Now we try to append to the existing element // and thus preventing the need to create a long string only to copy it to a queue element. - C015_queue_element &element = C015_DelayHandler.sendQueue.back(); + C015_queue_element &element = C015_DelayHandler->sendQueue.back(); if (ExtraTaskSettings.TaskIndex != event->TaskIndex) { String dummy; PluginCall(PLUGIN_GET_DEVICEVALUENAMES, event, dummy); @@ -196,7 +208,7 @@ bool CPlugin_015(CPlugin::Function function, struct EventStruct *event, String& element.txt[x] = formattedValue; } } - Scheduler.scheduleNextDelayQueue(ESPEasy_Scheduler::IntervalTimer_e::TIMER_C015_DELAY_QUEUE, C015_DelayHandler.getNextScheduleTime()); + Scheduler.scheduleNextDelayQueue(ESPEasy_Scheduler::IntervalTimer_e::TIMER_C015_DELAY_QUEUE, C015_DelayHandler->getNextScheduleTime()); break; } diff --git a/src/_C016.ino b/src/_C016.ino index 1852e4fb1..2b89b5429 100644 --- a/src/_C016.ino +++ b/src/_C016.ino @@ -62,13 +62,17 @@ bool CPlugin_016(CPlugin::Function function, struct EventStruct *event, String& case CPlugin::Function::CPLUGIN_INIT: { - MakeControllerSettings(ControllerSettings); - LoadControllerSettings(event->ControllerIndex, ControllerSettings); - C016_DelayHandler.configureControllerSettings(ControllerSettings); + success = init_c016_delay_queue(event->ControllerIndex); ControllerCache.init(); break; } + case CPlugin::Function::CPLUGIN_EXIT: + { + exit_c016_delay_queue(); + break; + } + case CPlugin::Function::CPLUGIN_WEBFORM_LOAD: { @@ -96,10 +100,14 @@ bool CPlugin_016(CPlugin::Function function, struct EventStruct *event, String& success = ControllerCache.write((uint8_t*)&element, sizeof(element)); /* + if (C016_DelayHandler == nullptr) { + break; + } + MakeControllerSettings(ControllerSettings); LoadControllerSettings(event->ControllerIndex, ControllerSettings); - success = C016_DelayHandler.addToQueue(element); - Scheduler.scheduleNextDelayQueue(ESPEasy_Scheduler::IntervalTimer_e::TIMER_C016_DELAY_QUEUE, C016_DelayHandler.getNextScheduleTime()); + success = C016_DelayHandler->addToQueue(element); + Scheduler.scheduleNextDelayQueue(ESPEasy_Scheduler::IntervalTimer_e::TIMER_C016_DELAY_QUEUE, C016_DelayHandler->getNextScheduleTime()); */ break; } diff --git a/src/_C017.ino b/src/_C017.ino index 396bb61e7..c2f35e6be 100644 --- a/src/_C017.ino +++ b/src/_C017.ino @@ -45,12 +45,30 @@ bool CPlugin_017(CPlugin::Function function, struct EventStruct *event, String & break; } + case CPlugin::Function::CPLUGIN_INIT: + { + success = init_c017_delay_queue(event->ControllerIndex); + break; + } + + case CPlugin::Function::CPLUGIN_EXIT: + { + exit_c017_delay_queue(); + break; + } + case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: - { + { + if (C017_DelayHandler == nullptr) { + break; + } byte valueCount = getValueCountFromSensorType(event->sensorType); C017_queue_element element(event); MakeControllerSettings(ControllerSettings); + if (!AllocatedControllerSettings()) { + break; + } LoadControllerSettings(event->ControllerIndex, ControllerSettings); for (byte x = 0; x < valueCount; x++) @@ -58,8 +76,8 @@ bool CPlugin_017(CPlugin::Function function, struct EventStruct *event, String & element.txt[x] = formatUserVarNoCheck(event, x); } // FIXME TD-er must define a proper move operator - success = C017_DelayHandler.addToQueue(C017_queue_element(element)); - Scheduler.scheduleNextDelayQueue(ESPEasy_Scheduler::IntervalTimer_e::TIMER_C017_DELAY_QUEUE, C017_DelayHandler.getNextScheduleTime()); + success = C017_DelayHandler->addToQueue(C017_queue_element(element)); + Scheduler.scheduleNextDelayQueue(ESPEasy_Scheduler::IntervalTimer_e::TIMER_C017_DELAY_QUEUE, C017_DelayHandler->getNextScheduleTime()); break; } diff --git a/src/_C018.ino b/src/_C018.ino index 215de284b..42aa4b28f 100644 --- a/src/_C018.ino +++ b/src/_C018.ino @@ -454,15 +454,19 @@ bool CPlugin_018(CPlugin::Function function, struct EventStruct *event, String& break; } - case CPlugin::Function::CPLUGIN_EXIT: + case CPlugin::Function::CPLUGIN_INIT: { - C018_data.reset(); + success = init_c018_delay_queue(event->ControllerIndex); + if (success) { + C018_init(event); + } break; } - case CPlugin::Function::CPLUGIN_INIT: + case CPlugin::Function::CPLUGIN_EXIT: { - C018_init(event); + C018_data.reset(); + exit_c018_delay_queue(); break; } @@ -635,9 +639,13 @@ bool CPlugin_018(CPlugin::Function function, struct EventStruct *event, String& case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: { - success = C018_DelayHandler.addToQueue( + if (C018_DelayHandler == nullptr) { + break; + } + + success = C018_DelayHandler->addToQueue( C018_queue_element(event, C018_data.getSampleSetCount(event->TaskIndex))); - Scheduler.scheduleNextDelayQueue(ESPEasy_Scheduler::IntervalTimer_e::TIMER_C018_DELAY_QUEUE, C018_DelayHandler.getNextScheduleTime()); + Scheduler.scheduleNextDelayQueue(ESPEasy_Scheduler::IntervalTimer_e::TIMER_C018_DELAY_QUEUE, C018_DelayHandler->getNextScheduleTime()); if (!C018_data.isInitialized()) { // Sometimes the module does need some time after power on to respond. // So it may not be initialized well at the call of CPLUGIN_INIT @@ -678,8 +686,12 @@ bool CPlugin_018(CPlugin::Function function, struct EventStruct *event, String& bool C018_init(struct EventStruct *event) { MakeControllerSettings(ControllerSettings); + if (!AllocatedControllerSettings()) { + return false; + } + LoadControllerSettings(event->ControllerIndex, ControllerSettings); - C018_DelayHandler.configureControllerSettings(ControllerSettings); + C018_DelayHandler->configureControllerSettings(ControllerSettings); C018_ConfigStruct customConfig; LoadCustomControllerSettings(event->ControllerIndex, (byte *)&customConfig, sizeof(customConfig)); diff --git a/src/src/ControllerQueue/ControllerDelayHandlerStruct.h b/src/src/ControllerQueue/ControllerDelayHandlerStruct.h index a951e1535..766d805ac 100644 --- a/src/src/ControllerQueue/ControllerDelayHandlerStruct.h +++ b/src/src/ControllerQueue/ControllerDelayHandlerStruct.h @@ -204,13 +204,17 @@ struct ControllerDelayHandlerStruct { bool do_process_c##NNN####M##_delay_queue(int controller_number, \ const C##NNN####M##_queue_element & element, \ ControllerSettingsStruct & ControllerSettings); \ - extern ControllerDelayHandlerStructC##NNN####M##_DelayHandler; \ + typedef ControllerDelayHandlerStruct C##NNN####M##_DelayHandler_t; \ + extern C##NNN####M##_DelayHandler_t *C##NNN####M##_DelayHandler; \ void process_c##NNN####M##_delay_queue(); \ + bool init_c##NNN####M##_delay_queue(controllerIndex_t ControllerIndex); \ + void exit_c##NNN####M##_delay_queue(); \ #define DEFINE_Cxxx_DELAY_QUEUE_MACRO_CPP(NNN, M) \ - ControllerDelayHandlerStructC##NNN####M##_DelayHandler; \ + C##NNN####M##_DelayHandler_t *C##NNN####M##_DelayHandler = nullptr; \ void process_c##NNN####M##_delay_queue() { \ - C##NNN####M##_queue_element *element(C##NNN####M##_DelayHandler.getNext()); \ + if (C##NNN####M##_DelayHandler == nullptr) return; \ + C##NNN####M##_queue_element *element(C##NNN####M##_DelayHandler->getNext()); \ if (element == NULL) return; \ MakeControllerSettings(ControllerSettings); \ bool ready = true; \ @@ -218,16 +222,37 @@ struct ControllerDelayHandlerStruct { ready = false; \ } else { \ LoadControllerSettings(element->controller_idx, ControllerSettings); \ - C##NNN####M##_DelayHandler.configureControllerSettings(ControllerSettings); \ - if (!C##NNN####M##_DelayHandler.readyToProcess(*element)) { ready = false; } \ + C##NNN####M##_DelayHandler->configureControllerSettings(ControllerSettings); \ + if (!C##NNN####M##_DelayHandler->readyToProcess(*element)) { ready = false; } \ } \ if (ready) { \ START_TIMER; \ - C##NNN####M##_DelayHandler.markProcessed(do_process_c##NNN####M##_delay_queue(M, *element, ControllerSettings)); \ + C##NNN####M##_DelayHandler->markProcessed(do_process_c##NNN####M##_delay_queue(M, *element, ControllerSettings)); \ STOP_TIMER(C##NNN####M##_DELAY_QUEUE); \ } \ - Scheduler.scheduleNextDelayQueue(ESPEasy_Scheduler::IntervalTimer_e::TIMER_C##NNN####M##_DELAY_QUEUE, C##NNN####M##_DelayHandler.getNextScheduleTime()); \ - } + Scheduler.scheduleNextDelayQueue(ESPEasy_Scheduler::IntervalTimer_e::TIMER_C##NNN####M##_DELAY_QUEUE, C##NNN####M##_DelayHandler->getNextScheduleTime()); \ + } \ + bool init_c##NNN####M##_delay_queue(controllerIndex_t ControllerIndex) { \ + if (C##NNN####M##_DelayHandler == nullptr) { \ + C##NNN####M##_DelayHandler = new C##NNN####M##_DelayHandler_t; \ + } \ + if (C##NNN####M##_DelayHandler == nullptr) { return false; } \ + MakeControllerSettings(ControllerSettings); \ + if (!AllocatedControllerSettings()) { \ + return false; \ + } \ + LoadControllerSettings(ControllerIndex, ControllerSettings); \ + C##NNN####M##_DelayHandler->configureControllerSettings(ControllerSettings); \ + return true; \ + } \ + void exit_c##NNN####M##_delay_queue() { \ + if (C##NNN####M##_DelayHandler != nullptr) { \ + delete C##NNN####M##_DelayHandler; \ + C##NNN####M##_DelayHandler = nullptr; \ + } \ + } \ + + // Uncrustify must not be used on macros, but we're now done, so turn Uncrustify on again. diff --git a/src/src/ControllerQueue/DelayQueueElements.cpp b/src/src/ControllerQueue/DelayQueueElements.cpp index 88fa72b5c..b7ea89cec 100644 --- a/src/src/ControllerQueue/DelayQueueElements.cpp +++ b/src/src/ControllerQueue/DelayQueueElements.cpp @@ -2,7 +2,33 @@ #ifdef USES_MQTT -ControllerDelayHandlerStruct MQTTDelayHandler; +ControllerDelayHandlerStruct *MQTTDelayHandler = nullptr; + +bool init_mqtt_delay_queue(controllerIndex_t ControllerIndex, String& pubname, bool& retainFlag) { + MakeControllerSettings(ControllerSettings); + if (!AllocatedControllerSettings()) { + return false; + } + LoadControllerSettings(ControllerIndex, ControllerSettings); + if (MQTTDelayHandler == nullptr) { + MQTTDelayHandler = new ControllerDelayHandlerStruct; + } + if (MQTTDelayHandler == nullptr) { + return false; + } + MQTTDelayHandler->configureControllerSettings(ControllerSettings); + pubname = ControllerSettings.Publish; + retainFlag = ControllerSettings.mqtt_retainFlag(); + return true; +} + +void exit_mqtt_delay_queue() { + if (MQTTDelayHandler != nullptr) { + delete MQTTDelayHandler; + MQTTDelayHandler = nullptr; + } +} + #endif // USES_MQTT diff --git a/src/src/ControllerQueue/DelayQueueElements.h b/src/src/ControllerQueue/DelayQueueElements.h index ebcbded66..8bc0935b7 100644 --- a/src/src/ControllerQueue/DelayQueueElements.h +++ b/src/src/ControllerQueue/DelayQueueElements.h @@ -28,7 +28,10 @@ #ifdef USES_MQTT # include "../ControllerQueue/MQTT_queue_element.h" -extern ControllerDelayHandlerStruct MQTTDelayHandler; +extern ControllerDelayHandlerStruct *MQTTDelayHandler; + +bool init_mqtt_delay_queue(controllerIndex_t ControllerIndex, String& pubname, bool& retainFlag); +void exit_mqtt_delay_queue(); #endif // USES_MQTT diff --git a/src/src/Helpers/PeriodicalActions.cpp b/src/src/Helpers/PeriodicalActions.cpp index ea6396f87..350e78ed0 100644 --- a/src/src/Helpers/PeriodicalActions.cpp +++ b/src/src/Helpers/PeriodicalActions.cpp @@ -220,7 +220,7 @@ void runEach30Seconds() void scheduleNextMQTTdelayQueue() { - Scheduler.scheduleNextDelayQueue(ESPEasy_Scheduler::IntervalTimer_e::TIMER_MQTT_DELAY_QUEUE, MQTTDelayHandler.getNextScheduleTime()); + Scheduler.scheduleNextDelayQueue(ESPEasy_Scheduler::IntervalTimer_e::TIMER_MQTT_DELAY_QUEUE, MQTTDelayHandler->getNextScheduleTime()); } void schedule_all_tasks_using_MQTT_controller() { @@ -239,8 +239,12 @@ void schedule_all_tasks_using_MQTT_controller() { } void processMQTTdelayQueue() { + if (MQTTDelayHandler == nullptr) { + return; + } + START_TIMER; - MQTT_queue_element *element(MQTTDelayHandler.getNext()); + MQTT_queue_element *element(MQTTDelayHandler->getNext()); if (element == NULL) { return; } @@ -248,14 +252,14 @@ void processMQTTdelayQueue() { if (connectionFailures > 0) { --connectionFailures; } - MQTTDelayHandler.markProcessed(true); + MQTTDelayHandler->markProcessed(true); } else { - MQTTDelayHandler.markProcessed(false); + MQTTDelayHandler->markProcessed(false); #ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { String log = F("MQTT : process MQTT queue not published, "); - log += MQTTDelayHandler.sendQueue.size(); + log += MQTTDelayHandler->sendQueue.size(); log += F(" items left in queue"); addLog(LOG_LEVEL_DEBUG, log); } From 135a26ebf3ac15416f6d399de96de898ed9ece07 Mon Sep 17 00:00:00 2001 From: Gijs Noorlander Date: Mon, 17 Aug 2020 16:50:31 +0200 Subject: [PATCH 16/28] [BME280] Allow to be used with I2C multiplexer This plugin was using plugin data per I2C address, which no longer will work when used with I2C multiplexer. --- src/_P028_BME280.ino | 734 ++++----------------- src/src/PluginStructs/P028_data_struct.cpp | 413 ++++++++++++ src/src/PluginStructs/P028_data_struct.h | 188 ++++++ 3 files changed, 719 insertions(+), 616 deletions(-) create mode 100644 src/src/PluginStructs/P028_data_struct.cpp create mode 100644 src/src/PluginStructs/P028_data_struct.h diff --git a/src/_P028_BME280.ino b/src/_P028_BME280.ino index 142f3ce9d..d110eda1c 100644 --- a/src/_P028_BME280.ino +++ b/src/_P028_BME280.ino @@ -1,12 +1,14 @@ #ifdef USES_P028 -//####################################################################################################### -//#################### Plugin 028 BME280 I2C Temp/Hum/Barometric Pressure Sensor ####################### -//####################################################################################################### + +// ####################################################################################################### +// #################### Plugin 028 BME280 I2C Temp/Hum/Barometric Pressure Sensor ####################### +// ####################################################################################################### #include "_Plugin_Helper.h" -//#include -#include +#include "src/PluginStructs/P028_data_struct.h" + +// #include #define PLUGIN_028 #define PLUGIN_ID_028 28 @@ -15,201 +17,7 @@ #define PLUGIN_VALUENAME2_028 "Humidity" #define PLUGIN_VALUENAME3_028 "Pressure" -#define PLUGIN_028_BME280_DEVICE "BME280" -#define PLUGIN_028_BMP280_DEVICE "BMP280" -#define BMx280_REGISTER_DIG_T1 0x88 -#define BMx280_REGISTER_DIG_T2 0x8A -#define BMx280_REGISTER_DIG_T3 0x8C - -#define BMx280_REGISTER_DIG_P1 0x8E -#define BMx280_REGISTER_DIG_P2 0x90 -#define BMx280_REGISTER_DIG_P3 0x92 -#define BMx280_REGISTER_DIG_P4 0x94 -#define BMx280_REGISTER_DIG_P5 0x96 -#define BMx280_REGISTER_DIG_P6 0x98 -#define BMx280_REGISTER_DIG_P7 0x9A -#define BMx280_REGISTER_DIG_P8 0x9C -#define BMx280_REGISTER_DIG_P9 0x9E - -#define BMx280_REGISTER_DIG_H1 0xA1 -#define BMx280_REGISTER_DIG_H2 0xE1 -#define BMx280_REGISTER_DIG_H3 0xE3 -#define BMx280_REGISTER_DIG_H4 0xE4 -#define BMx280_REGISTER_DIG_H5 0xE5 -#define BMx280_REGISTER_DIG_H6 0xE7 - -#define BMx280_REGISTER_CHIPID 0xD0 -#define BMx280_REGISTER_VERSION 0xD1 -#define BMx280_REGISTER_SOFTRESET 0xE0 - -#define BMx280_REGISTER_CAL26 0xE1 // R calibration stored in 0xE1-0xF0 - -#define BMx280_REGISTER_CONTROLHUMID 0xF2 -#define BMx280_REGISTER_STATUS 0xF3 -#define BMx280_REGISTER_CONTROL 0xF4 -#define BMx280_REGISTER_CONFIG 0xF5 -#define BMx280_REGISTER_PRESSUREDATA 0xF7 -#define BMx280_REGISTER_TEMPDATA 0xFA -#define BMx280_REGISTER_HUMIDDATA 0xFD - -#define BME280_CONTROL_SETTING_HUMIDITY 0x02 // Oversampling: 2x H - -#define BME280_TEMP_PRESS_CALIB_DATA_ADDR 0x88 -#define BME280_HUMIDITY_CALIB_DATA_ADDR 0xE1 -#define BME280_DATA_ADDR 0xF7 - -#define BME280_TEMP_PRESS_CALIB_DATA_LEN 26 -#define BME280_HUMIDITY_CALIB_DATA_LEN 7 -#define BME280_P_T_H_DATA_LEN 8 - -typedef struct -{ - uint16_t dig_T1 = 0; - int16_t dig_T2 = 0; - int16_t dig_T3 = 0; - - uint16_t dig_P1 = 0; - int16_t dig_P2 = 0; - int16_t dig_P3 = 0; - int16_t dig_P4 = 0; - int16_t dig_P5 = 0; - int16_t dig_P6 = 0; - int16_t dig_P7 = 0; - int16_t dig_P8 = 0; - int16_t dig_P9 = 0; - - uint8_t dig_H1 = 0; - int16_t dig_H2 = 0; - uint8_t dig_H3 = 0; - int16_t dig_H4 = 0; - int16_t dig_H5 = 0; - int8_t dig_H6 = 0; - int32_t t_fine = 0; -} bme280_calib_data; - -struct bme280_uncomp_data { - /*! un-compensated pressure */ - uint32_t pressure = 0; - /*! un-compensated temperature */ - uint32_t temperature = 0; - /*! un-compensated humidity */ - uint32_t humidity = 0; -}; - -enum BMx_ChipId { - Unknown_DEVICE = 0, - BMP280_DEVICE_SAMPLE1 = 0x56, - BMP280_DEVICE_SAMPLE2 = 0x57, - BMP280_DEVICE = 0x58, - BME280_DEVICE = 0x60 -}; - -enum BMx_state { - BMx_Uninitialized = 0, - BMx_Initialized, - BMx_Wait_for_samples, - BMx_New_values, - BMx_Values_read -}; - -struct P028_sensordata { - P028_sensordata() : - last_hum_val(0.0), - last_press_val(0.0), - last_temp_val(0.0), - last_dew_temp_val(0.0), - last_measurement(0), - sensorID(Unknown_DEVICE), - i2cAddress(0), - state(BMx_Uninitialized) {} - - byte get_config_settings() const { - switch (sensorID) { - case BMP280_DEVICE_SAMPLE1: - case BMP280_DEVICE_SAMPLE2: - case BMP280_DEVICE: - case BME280_DEVICE: return 0x28; // Tstandby 62.5ms, filter 4, 3-wire SPI Disable - default: return 0; - } - } - - byte get_control_settings() const { - switch (sensorID) { - case BMP280_DEVICE_SAMPLE1: - case BMP280_DEVICE_SAMPLE2: - case BMP280_DEVICE: - case BME280_DEVICE: return 0x93; // Oversampling: 8x P, 8x T, normal mode - default: return 0; - } - } - - String getFullDeviceName() const { - String devicename = getDeviceName(); - if (sensorID == BMP280_DEVICE_SAMPLE1 || - sensorID == BMP280_DEVICE_SAMPLE2) - { - devicename += " sample"; - } - return devicename; - } - - String getDeviceName() const { - switch (sensorID) { - case BMP280_DEVICE_SAMPLE1: - case BMP280_DEVICE_SAMPLE2: - case BMP280_DEVICE: return PLUGIN_028_BMP280_DEVICE; - case BME280_DEVICE: return PLUGIN_028_BME280_DEVICE; - default: return "Unknown"; - } - } - - boolean hasHumidity() const { - switch (sensorID) { - case BMP280_DEVICE_SAMPLE1: - case BMP280_DEVICE_SAMPLE2: - case BMP280_DEVICE: return false; - case BME280_DEVICE: return true; - default: return false; - } - } - - bool initialized() const { - return state != BMx_Uninitialized; - } - - void setUninitialized() { - state = BMx_Uninitialized; - } - - bme280_uncomp_data uncompensated; - bme280_calib_data calib; - float last_hum_val; - float last_press_val; - float last_temp_val; - float last_dew_temp_val; - unsigned long last_measurement; - BMx_ChipId sensorID; - uint8_t i2cAddress; - unsigned long moment_next_step = 0; - BMx_state state; -}; - -std::map P028_sensors; - -int Plugin_28_i2c_addresses[2] = { 0x76, 0x77 }; - -uint8_t Plugin_028_i2c_addr(struct EventStruct *event) { - uint8_t i2cAddress = static_cast(PCONFIG(0)); - if (i2cAddress != Plugin_28_i2c_addresses[0] && i2cAddress != Plugin_28_i2c_addresses[1]) { - // Set to default address - i2cAddress = Plugin_28_i2c_addresses[0]; - } - if (P028_sensors.count(i2cAddress) == 0) { - P028_sensors[i2cAddress] = P028_sensordata(); - } - return i2cAddress; -} boolean Plugin_028(byte function, struct EventStruct *event, String& string) { @@ -218,477 +26,171 @@ boolean Plugin_028(byte function, struct EventStruct *event, String& string) switch (function) { case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_028; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].VType = SENSOR_TYPE_TEMP_HUM_BARO; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = true; - Device[deviceCount].ValueCount = 3; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].GlobalSyncOption = true; - break; - } + { + Device[++deviceCount].Number = PLUGIN_ID_028; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = SENSOR_TYPE_TEMP_HUM_BARO; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 3; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].GlobalSyncOption = true; + break; + } case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_028); - break; - } + { + string = F(PLUGIN_NAME_028); + break; + } case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_028)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_028)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_VALUENAME3_028)); - break; + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_028)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_028)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_VALUENAME3_028)); + break; + } + + case PLUGIN_INIT: + { + initPluginTaskData(event->TaskIndex, new P028_data_struct(PCONFIG(0))); + P028_data_struct *P028_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr == P028_data) { + return success; } + success = true; + + break; + } + + case PLUGIN_EXIT: + { + clearPluginTaskData(event->TaskIndex); + success = true; + break; + } + case PLUGIN_WEBFORM_LOAD: - { - const uint8_t i2cAddress = Plugin_028_i2c_addr(event); - P028_sensordata& sensor = P028_sensors[i2cAddress]; - addFormSelectorI2C(F("p028_bme280_i2c"), 2, Plugin_28_i2c_addresses, i2cAddress); - if (sensor.sensorID != Unknown_DEVICE) { + { + int Plugin_28_i2c_addresses[2] = { 0x76, 0x77 }; + addFormSelectorI2C(F("p028_bme280_i2c"), 2, Plugin_28_i2c_addresses, PCONFIG(0)); + + P028_data_struct *P028_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P028_data) { + if (P028_data->sensorID != Unknown_DEVICE) { String detectedString = F("Detected: "); - detectedString += sensor.getFullDeviceName(); + detectedString += P028_data->getFullDeviceName(); addUnit(detectedString); } - addFormNote(F("SDO Low=0x76, High=0x77")); + } + addFormNote(F("SDO Low=0x76, High=0x77")); - addFormNumericBox(F("Altitude"), F("p028_bme280_elev"), PCONFIG(1)); - addUnit(F("m")); + addFormNumericBox(F("Altitude"), F("p028_bme280_elev"), PCONFIG(1)); + addUnit(F("m")); - addFormNumericBox(F("Temperature offset"), F("p028_bme280_tempoffset"), PCONFIG(2)); - addUnit(F("x 0.1C")); - String offsetNote = F("Offset in units of 0.1 degree Celcius"); - if (sensor.hasHumidity()) { + addFormNumericBox(F("Temperature offset"), F("p028_bme280_tempoffset"), PCONFIG(2)); + addUnit(F("x 0.1C")); + String offsetNote = F("Offset in units of 0.1 degree Celcius"); + + if (nullptr != P028_data) { + if (P028_data->hasHumidity()) { offsetNote += F(" (also correct humidity)"); } - addFormNote(offsetNote); - - success = true; - break; } + addFormNote(offsetNote); + + success = true; + break; + } case PLUGIN_WEBFORM_SAVE: - { - const uint8_t i2cAddress = getFormItemInt(F("p028_bme280_i2c")); - Plugin_028_check(i2cAddress); // Check id device is present - PCONFIG(0) = i2cAddress; - PCONFIG(1) = getFormItemInt(F("p028_bme280_elev")); - PCONFIG(2) = getFormItemInt(F("p028_bme280_tempoffset")); - success = true; - break; - } + { + PCONFIG(0) = getFormItemInt(F("p028_bme280_i2c")); + PCONFIG(1) = getFormItemInt(F("p028_bme280_elev")); + PCONFIG(2) = getFormItemInt(F("p028_bme280_tempoffset")); + success = true; + break; + } case PLUGIN_ONCE_A_SECOND: - { - const uint8_t i2cAddress = Plugin_028_i2c_addr(event); + { + P028_data_struct *P028_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P028_data) { const float tempOffset = PCONFIG(2) / 10.0; - if (Plugin_028_update_measurements(i2cAddress, tempOffset, event->TaskIndex)) { + + if (P028_data->updateMeasurements(tempOffset, event->TaskIndex)) { // Update was succesfull, schedule a read. Scheduler.schedule_task_device_timer(event->TaskIndex, millis() + 10); } - break; } + break; + } case PLUGIN_READ: - { - const uint8_t i2cAddress = Plugin_028_i2c_addr(event); - P028_sensordata& sensor = P028_sensors[i2cAddress]; - if (sensor.state != BMx_New_values) { + { + P028_data_struct *P028_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P028_data) { + if (P028_data->state != BMx_New_values) { success = false; break; } - sensor.state = BMx_Values_read; - if (!sensor.hasHumidity()) { + P028_data->state = BMx_Values_read; + + if (!P028_data->hasHumidity()) { // Patch the sensor type to output only the measured values. event->sensorType = SENSOR_TYPE_TEMP_EMPTY_BARO; } - UserVar[event->BaseVarIndex] = sensor.last_temp_val; - UserVar[event->BaseVarIndex + 1] = sensor.last_hum_val; + UserVar[event->BaseVarIndex] = P028_data->last_temp_val; + UserVar[event->BaseVarIndex + 1] = P028_data->last_hum_val; const int elev = PCONFIG(1); - if (elev) { - UserVar[event->BaseVarIndex + 2] = Plugin_028_pressureElevation(sensor.last_press_val, elev); + + if (elev != 0) { + UserVar[event->BaseVarIndex + 2] = P028_data->pressureElevation(elev); } else { - UserVar[event->BaseVarIndex + 2] = sensor.last_press_val; + UserVar[event->BaseVarIndex + 2] = P028_data->last_press_val; } + if (loglevelActiveFor(LOG_LEVEL_INFO)) { String log; log.reserve(40); // Prevent re-allocation - log = sensor.getDeviceName(); + log = P028_data->getDeviceName(); log += F(" : Address: 0x"); - log += String(i2cAddress,HEX); + log += String(PCONFIG(0), HEX); addLog(LOG_LEVEL_INFO, log); - log = sensor.getDeviceName(); + log = P028_data->getDeviceName(); log += F(" : Temperature: "); log += UserVar[event->BaseVarIndex]; addLog(LOG_LEVEL_INFO, log); - if (sensor.hasHumidity()) { - log = sensor.getDeviceName(); + + if (P028_data->hasHumidity()) { + log = P028_data->getDeviceName(); log += F(" : Humidity: "); log += UserVar[event->BaseVarIndex + 1]; addLog(LOG_LEVEL_INFO, log); } - log = sensor.getDeviceName(); + log = P028_data->getDeviceName(); log += F(" : Barometric Pressure: "); log += UserVar[event->BaseVarIndex + 2]; addLog(LOG_LEVEL_INFO, log); } success = true; - break; - } - case PLUGIN_EXIT: - { - const uint8_t i2cAddress = Plugin_028_i2c_addr(event); - P028_sensors.erase(i2cAddress); - break; } + break; + } } return success; } - -// Only perform the measurements with big interval to prevent the sensor from warming up. -bool Plugin_028_update_measurements(const uint8_t i2cAddress, float tempOffset, unsigned long task_index) { - P028_sensordata& sensor = P028_sensors[i2cAddress]; - const unsigned long current_time = millis(); - Plugin_028_check(i2cAddress); // Check id device is present - if (!sensor.initialized()) { - if (!Plugin_028_begin(i2cAddress)) { - return false; - } - sensor.state = BMx_Initialized; - sensor.last_measurement = 0; - } - if (sensor.state != BMx_Wait_for_samples) { - if (sensor.last_measurement != 0 && - !timeOutReached(sensor.last_measurement + (Settings.TaskDeviceTimer[task_index] * 1000))) { - // Timeout has not yet been reached. - return false; - } - - sensor.last_measurement = current_time; - // Set the Sensor in sleep to be make sure that the following configs will be stored - I2C_write8_reg(i2cAddress, BMx280_REGISTER_CONTROL, 0x00); - if (sensor.hasHumidity()) { - I2C_write8_reg(i2cAddress, BMx280_REGISTER_CONTROLHUMID, BME280_CONTROL_SETTING_HUMIDITY); - } - I2C_write8_reg(i2cAddress, BMx280_REGISTER_CONFIG, sensor.get_config_settings()); - I2C_write8_reg(i2cAddress, BMx280_REGISTER_CONTROL, sensor.get_control_settings()); - sensor.state = BMx_Wait_for_samples; - return false; - } - - // It takes at least 1.587 sec for valit measurements to complete. - // The datasheet names this the "T63" moment. - // 1 second = 63% of the time needed to perform a measurement. - if (!timeOutReached(sensor.last_measurement + 1587)) { - return false; - } - if (!Plugin_028_readUncompensatedData(i2cAddress)) { - return false; - } - // Set to sleep mode again to prevent the sensor from heating up. - I2C_write8_reg(i2cAddress, BMx280_REGISTER_CONTROL, 0x00); - - sensor.last_measurement = current_time; - sensor.state = BMx_New_values; - sensor.last_temp_val = Plugin_028_readTemperature(i2cAddress); - sensor.last_press_val = ((float)Plugin_028_readPressure(i2cAddress)) / 100; - sensor.last_hum_val = ((float)Plugin_028_readHumidity(i2cAddress)); - - - String log; - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log.reserve(120); // Prevent re-allocation - log = sensor.getDeviceName(); - log += F(":"); - } - boolean logAdded = false; - if (sensor.hasHumidity()) { - // Apply half of the temp offset, to correct the dew point offset. - // The sensor is warmer than the surrounding air, which has effect on the perceived humidity. - sensor.last_dew_temp_val = compute_dew_point_temp(sensor.last_temp_val + (tempOffset / 2.0), sensor.last_hum_val); - } else { - // No humidity measurement, thus set dew point equal to air temperature. - sensor.last_dew_temp_val = sensor.last_temp_val; - } - if (tempOffset > 0.1 || tempOffset < -0.1) { - // There is some offset to apply. - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log += F(" Apply temp offset "); - log += tempOffset; - log += F("C"); - } - if (sensor.hasHumidity()) { - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log += F(" humidity "); - log += sensor.last_hum_val; - } - sensor.last_hum_val = compute_humidity_from_dewpoint(sensor.last_temp_val + tempOffset, sensor.last_dew_temp_val); - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log += F("% => "); - log += sensor.last_hum_val; - log += F("%"); - } - } else { - sensor.last_hum_val = 0.0; - } - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log += F(" temperature "); - log += sensor.last_temp_val; - } - sensor.last_temp_val = sensor.last_temp_val + tempOffset; - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log += F("C => "); - log += sensor.last_temp_val; - log += F("C"); - logAdded = true; - } - } - if (sensor.hasHumidity()) { - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log += F(" dew point "); - log += sensor.last_dew_temp_val; - log += F("C"); - logAdded = true; - } - } - if (logAdded && loglevelActiveFor(LOG_LEVEL_INFO)) - addLog(LOG_LEVEL_INFO, log); - return true; -} - - -//**************************************************************************/ -// Check BME280 presence -//**************************************************************************/ -bool Plugin_028_check(uint8_t i2cAddress) { - bool wire_status = false; - const uint8_t chip_id = I2C_read8_reg(i2cAddress, BMx280_REGISTER_CHIPID, &wire_status); - P028_sensordata& sensor = P028_sensors[i2cAddress]; - if (!wire_status) sensor.setUninitialized(); - switch (chip_id) { - case BMP280_DEVICE_SAMPLE1: - case BMP280_DEVICE_SAMPLE2: - case BMP280_DEVICE: - case BME280_DEVICE: { - if (wire_status) { - // Store detected chip ID when chip found. - if (sensor.sensorID != chip_id) { - sensor.sensorID = static_cast(chip_id); - sensor.setUninitialized(); - String log = F("BMx280 : Detected "); - log += sensor.getFullDeviceName(); - addLog(LOG_LEVEL_INFO, log); - } - } else { - sensor.sensorID = Unknown_DEVICE; - } - break; - } - default: - sensor.sensorID = Unknown_DEVICE; - break; - } - if (sensor.sensorID == Unknown_DEVICE) { - String log = F("BMx280 : Unable to detect chip ID"); - addLog(LOG_LEVEL_INFO, log); - return false; - } - return wire_status; -} - -//**************************************************************************/ -// Initialize BME280 -//**************************************************************************/ -bool Plugin_028_begin(uint8_t i2cAddress) { - if (! Plugin_028_check(i2cAddress)) - return false; - // Perform soft reset - I2C_write8_reg(i2cAddress, BMx280_REGISTER_SOFTRESET, 0xB6); - delay(2); // Startup time is 2 ms (datasheet) - Plugin_028_readCoefficients(i2cAddress); -// delay(65); //May be needed here as well to fix first wrong measurement? - return true; -} - - -//**************************************************************************/ -// Reads the factory-set coefficients -//**************************************************************************/ -void Plugin_028_readCoefficients(uint8_t i2cAddress) -{ - P028_sensordata& sensor = P028_sensors[i2cAddress]; - sensor.calib.dig_T1 = I2C_read16_LE_reg(i2cAddress, BMx280_REGISTER_DIG_T1); - sensor.calib.dig_T2 = I2C_readS16_LE_reg(i2cAddress, BMx280_REGISTER_DIG_T2); - sensor.calib.dig_T3 = I2C_readS16_LE_reg(i2cAddress, BMx280_REGISTER_DIG_T3); - - sensor.calib.dig_P1 = I2C_read16_LE_reg(i2cAddress, BMx280_REGISTER_DIG_P1); - sensor.calib.dig_P2 = I2C_readS16_LE_reg(i2cAddress, BMx280_REGISTER_DIG_P2); - sensor.calib.dig_P3 = I2C_readS16_LE_reg(i2cAddress, BMx280_REGISTER_DIG_P3); - sensor.calib.dig_P4 = I2C_readS16_LE_reg(i2cAddress, BMx280_REGISTER_DIG_P4); - sensor.calib.dig_P5 = I2C_readS16_LE_reg(i2cAddress, BMx280_REGISTER_DIG_P5); - sensor.calib.dig_P6 = I2C_readS16_LE_reg(i2cAddress, BMx280_REGISTER_DIG_P6); - sensor.calib.dig_P7 = I2C_readS16_LE_reg(i2cAddress, BMx280_REGISTER_DIG_P7); - sensor.calib.dig_P8 = I2C_readS16_LE_reg(i2cAddress, BMx280_REGISTER_DIG_P8); - sensor.calib.dig_P9 = I2C_readS16_LE_reg(i2cAddress, BMx280_REGISTER_DIG_P9); - - if (sensor.hasHumidity()) { - sensor.calib.dig_H1 = I2C_read8_reg(i2cAddress, BMx280_REGISTER_DIG_H1); - sensor.calib.dig_H2 = I2C_readS16_LE_reg(i2cAddress, BMx280_REGISTER_DIG_H2); - sensor.calib.dig_H3 = I2C_read8_reg(i2cAddress, BMx280_REGISTER_DIG_H3); - sensor.calib.dig_H4 = (I2C_read8_reg(i2cAddress, BMx280_REGISTER_DIG_H4) << 4) | (I2C_read8_reg(i2cAddress, BMx280_REGISTER_DIG_H4 + 1) & 0xF); - sensor.calib.dig_H5 = (I2C_read8_reg(i2cAddress, BMx280_REGISTER_DIG_H5 + 1) << 4) | (I2C_read8_reg(i2cAddress, BMx280_REGISTER_DIG_H5) >> 4); - sensor.calib.dig_H6 = (int8_t)I2C_read8_reg(i2cAddress, BMx280_REGISTER_DIG_H6); - } -} - -bool Plugin_028_readUncompensatedData(uint8_t i2cAddress) { - // wait until measurement has been completed, otherwise we would read - // the values from the last measurement - if (I2C_read8_reg(i2cAddress, BMx280_REGISTER_STATUS) & 0x08) - return false; - - I2Cdata_bytes BME280_data(BME280_P_T_H_DATA_LEN, BME280_DATA_ADDR); - bool allDataRead = I2C_read_bytes(i2cAddress, BME280_data); - if (!allDataRead) { - return false; - } - /* Variables to store the sensor data */ - uint32_t data_xlsb; - uint32_t data_lsb; - uint32_t data_msb; - - P028_sensordata& sensor = P028_sensors[i2cAddress]; - - /* Store the parsed register values for pressure data */ - data_msb = (uint32_t)BME280_data[BME280_DATA_ADDR + 0] << 12; - data_lsb = (uint32_t)BME280_data[BME280_DATA_ADDR + 1] << 4; - data_xlsb = (uint32_t)BME280_data[BME280_DATA_ADDR + 2] >> 4; - sensor.uncompensated.pressure = data_msb | data_lsb | data_xlsb; - - /* Store the parsed register values for temperature data */ - data_msb = (uint32_t)BME280_data[BME280_DATA_ADDR + 3] << 12; - data_lsb = (uint32_t)BME280_data[BME280_DATA_ADDR + 4] << 4; - data_xlsb = (uint32_t)BME280_data[BME280_DATA_ADDR + 5] >> 4; - sensor.uncompensated.temperature = data_msb | data_lsb | data_xlsb; - - /* Store the parsed register values for temperature data */ - data_lsb = (uint32_t)BME280_data[BME280_DATA_ADDR + 6] << 8; - data_msb = (uint32_t)BME280_data[BME280_DATA_ADDR + 7]; - sensor.uncompensated.humidity = data_msb | data_lsb; - return true; -} - -//**************************************************************************/ -// Read temperature -//**************************************************************************/ -float Plugin_028_readTemperature(uint8_t i2cAddress) -{ - P028_sensordata& sensor = P028_sensors[i2cAddress]; - int32_t var1, var2; - int32_t adc_T = sensor.uncompensated.temperature; - var1 = ((((adc_T >> 3) - ((int32_t)sensor.calib.dig_T1 << 1))) * - ((int32_t)sensor.calib.dig_T2)) >> 11; - - var2 = (((((adc_T >> 4) - ((int32_t)sensor.calib.dig_T1)) * - ((adc_T >> 4) - ((int32_t)sensor.calib.dig_T1))) >> 12) * - ((int32_t)sensor.calib.dig_T3)) >> 14; - - sensor.calib.t_fine = var1 + var2; - - float T = (sensor.calib.t_fine * 5 + 128) >> 8; - return T / 100; -} - -//**************************************************************************/ -// Read pressure -//**************************************************************************/ -float Plugin_028_readPressure(uint8_t i2cAddress) -{ - P028_sensordata& sensor = P028_sensors[i2cAddress]; - int64_t var1, var2, p; - int32_t adc_P = sensor.uncompensated.pressure; - - var1 = ((int64_t)sensor.calib.t_fine) - 128000; - var2 = var1 * var1 * (int64_t)sensor.calib.dig_P6; - var2 = var2 + ((var1 * (int64_t)sensor.calib.dig_P5) << 17); - var2 = var2 + (((int64_t)sensor.calib.dig_P4) << 35); - var1 = ((var1 * var1 * (int64_t)sensor.calib.dig_P3) >> 8) + - ((var1 * (int64_t)sensor.calib.dig_P2) << 12); - var1 = (((((int64_t)1) << 47) + var1)) * ((int64_t)sensor.calib.dig_P1) >> 33; - - if (var1 == 0) { - return 0; // avoid exception caused by division by zero - } - p = 1048576 - adc_P; - p = (((p << 31) - var2) * 3125) / var1; - var1 = (((int64_t)sensor.calib.dig_P9) * (p >> 13) * (p >> 13)) >> 25; - var2 = (((int64_t)sensor.calib.dig_P8) * p) >> 19; - - p = ((p + var1 + var2) >> 8) + (((int64_t)sensor.calib.dig_P7) << 4); - return (float)p / 256; -} - -//**************************************************************************/ -// Read humidity -//**************************************************************************/ -float Plugin_028_readHumidity(uint8_t i2cAddress) -{ - P028_sensordata& sensor = P028_sensors[i2cAddress]; - if (!sensor.hasHumidity()) { - // No support for humidity - return 0.0; - } - int32_t adc_H = sensor.uncompensated.humidity; - - int32_t v_x1_u32r; - - v_x1_u32r = (sensor.calib.t_fine - ((int32_t)76800)); - - v_x1_u32r = (((((adc_H << 14) - (((int32_t)sensor.calib.dig_H4) << 20) - - (((int32_t)sensor.calib.dig_H5) * v_x1_u32r)) + ((int32_t)16384)) >> 15) * - (((((((v_x1_u32r * ((int32_t)sensor.calib.dig_H6)) >> 10) * - (((v_x1_u32r * ((int32_t)sensor.calib.dig_H3)) >> 11) + ((int32_t)32768))) >> 10) + - ((int32_t)2097152)) * ((int32_t)sensor.calib.dig_H2) + 8192) >> 14)); - - v_x1_u32r = (v_x1_u32r - (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7) * - ((int32_t)sensor.calib.dig_H1)) >> 4)); - - v_x1_u32r = (v_x1_u32r < 0) ? 0 : v_x1_u32r; - v_x1_u32r = (v_x1_u32r > 419430400) ? 419430400 : v_x1_u32r; - float h = (v_x1_u32r >> 12); - return h / 1024.0; -} - -//**************************************************************************/ -// Calculates the altitude (in meters) from the specified atmospheric -// pressure (in hPa), and sea-level pressure (in hPa). -// @param seaLevel Sea-level pressure in hPa -// @param atmospheric Atmospheric pressure in hPa -//**************************************************************************/ -float Plugin_028_readAltitude(uint8_t i2cAddress, float seaLevel) -{ - // Equation taken from BMP180 datasheet (page 16): - // http://www.adafruit.com/datasheets/BST-BMP180-DS000-09.pdf - - // Note that using the equation from wikipedia can give bad results - // at high altitude. See this thread for more information: - // http://forums.adafruit.com/viewtopic.php?f=22&t=58064 - - float atmospheric = Plugin_028_readPressure(i2cAddress) / 100.0F; - return 44330.0 * (1.0 - pow(atmospheric / seaLevel, 0.1903)); -} - -//**************************************************************************/ -// MSL pressure formula -//**************************************************************************/ -float Plugin_028_pressureElevation(float atmospheric, int altitude) { - return atmospheric / pow(1.0 - (altitude/44330.0), 5.255); -} #endif // USES_P028 diff --git a/src/src/PluginStructs/P028_data_struct.cpp b/src/src/PluginStructs/P028_data_struct.cpp new file mode 100644 index 000000000..69fb78182 --- /dev/null +++ b/src/src/PluginStructs/P028_data_struct.cpp @@ -0,0 +1,413 @@ +#include "P028_data_struct.h" + +#ifdef USES_P028 + +# include "../Helpers/Convert.h" + + +P028_data_struct::P028_data_struct(uint8_t addr) : + last_hum_val(0.0), + last_press_val(0.0), + last_temp_val(0.0), + last_dew_temp_val(0.0), + last_measurement(0), + sensorID(Unknown_DEVICE), + i2cAddress(addr), + state(BMx_Uninitialized) {} + + +byte P028_data_struct::get_config_settings() const { + switch (sensorID) { + case BMP280_DEVICE_SAMPLE1: + case BMP280_DEVICE_SAMPLE2: + case BMP280_DEVICE: + case BME280_DEVICE: return 0x28; // Tstandby 62.5ms, filter 4, 3-wire SPI Disable + default: return 0; + } +} + +byte P028_data_struct::get_control_settings() const { + switch (sensorID) { + case BMP280_DEVICE_SAMPLE1: + case BMP280_DEVICE_SAMPLE2: + case BMP280_DEVICE: + case BME280_DEVICE: return 0x93; // Oversampling: 8x P, 8x T, normal mode + default: return 0; + } +} + +String P028_data_struct::getFullDeviceName() const { + String devicename = getDeviceName(); + + if ((sensorID == BMP280_DEVICE_SAMPLE1) || + (sensorID == BMP280_DEVICE_SAMPLE2)) + { + devicename += F(" sample"); + } + return devicename; +} + +String P028_data_struct::getDeviceName() const { + switch (sensorID) { + case BMP280_DEVICE_SAMPLE1: + case BMP280_DEVICE_SAMPLE2: + case BMP280_DEVICE: return F("BMP280"); + case BME280_DEVICE: return F("BME280"); + default: return F("Unknown"); + } +} + +boolean P028_data_struct::hasHumidity() const { + switch (sensorID) { + case BMP280_DEVICE_SAMPLE1: + case BMP280_DEVICE_SAMPLE2: + case BMP280_DEVICE: return false; + case BME280_DEVICE: return true; + default: return false; + } +} + +bool P028_data_struct::initialized() const { + return state != BMx_Uninitialized; +} + +void P028_data_struct::setUninitialized() { + state = BMx_Uninitialized; +} + +// Only perform the measurements with big interval to prevent the sensor from warming up. +bool P028_data_struct::updateMeasurements(float tempOffset, unsigned long task_index) { + const unsigned long current_time = millis(); + + check(); // Check id device is present + + if (!initialized()) { + if (!begin()) { + return false; + } + state = BMx_Initialized; + last_measurement = 0; + } + + if (state != BMx_Wait_for_samples) { + if ((last_measurement != 0) && + !timeOutReached(last_measurement + (Settings.TaskDeviceTimer[task_index] * 1000))) { + // Timeout has not yet been reached. + return false; + } + + last_measurement = current_time; + + // Set the Sensor in sleep to be make sure that the following configs will be stored + I2C_write8_reg(i2cAddress, BMx280_REGISTER_CONTROL, 0x00); + + if (hasHumidity()) { + I2C_write8_reg(i2cAddress, BMx280_REGISTER_CONTROLHUMID, BME280_CONTROL_SETTING_HUMIDITY); + } + I2C_write8_reg(i2cAddress, BMx280_REGISTER_CONFIG, get_config_settings()); + I2C_write8_reg(i2cAddress, BMx280_REGISTER_CONTROL, get_control_settings()); + state = BMx_Wait_for_samples; + return false; + } + + // It takes at least 1.587 sec for valit measurements to complete. + // The datasheet names this the "T63" moment. + // 1 second = 63% of the time needed to perform a measurement. + if (!timeOutReached(last_measurement + 1587)) { + return false; + } + + if (!readUncompensatedData()) { + return false; + } + + // Set to sleep mode again to prevent the sensor from heating up. + I2C_write8_reg(i2cAddress, BMx280_REGISTER_CONTROL, 0x00); + + last_measurement = current_time; + state = BMx_New_values; + last_temp_val = readTemperature(); + last_press_val = ((float)readPressure()) / 100.0; + last_hum_val = ((float)readHumidity()); + + + String log; + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + log.reserve(120); // Prevent re-allocation + log = getDeviceName(); + log += F(":"); + } + boolean logAdded = false; + + if (hasHumidity()) { + // Apply half of the temp offset, to correct the dew point offset. + // The sensor is warmer than the surrounding air, which has effect on the perceived humidity. + last_dew_temp_val = compute_dew_point_temp(last_temp_val + (tempOffset / 2.0), last_hum_val); + } else { + // No humidity measurement, thus set dew point equal to air temperature. + last_dew_temp_val = last_temp_val; + } + + if ((tempOffset > 0.1) || (tempOffset < -0.1)) { + // There is some offset to apply. + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + log += F(" Apply temp offset "); + log += tempOffset; + log += F("C"); + } + + if (hasHumidity()) { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + log += F(" humidity "); + log += last_hum_val; + } + last_hum_val = compute_humidity_from_dewpoint(last_temp_val + tempOffset, last_dew_temp_val); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + log += F("% => "); + log += last_hum_val; + log += F("%"); + } + } else { + last_hum_val = 0.0; + } + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + log += F(" temperature "); + log += last_temp_val; + } + last_temp_val = last_temp_val + tempOffset; + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + log += F("C => "); + log += last_temp_val; + log += F("C"); + logAdded = true; + } + } + + if (hasHumidity()) { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + log += F(" dew point "); + log += last_dew_temp_val; + log += F("C"); + logAdded = true; + } + } + + if (logAdded && loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, log); + } + return true; +} + +// **************************************************************************/ +// Check BME280 presence +// **************************************************************************/ +bool P028_data_struct::check() { + bool wire_status = false; + const uint8_t chip_id = I2C_read8_reg(i2cAddress, BMx280_REGISTER_CHIPID, &wire_status); + if (!wire_status) { setUninitialized(); } + + switch (chip_id) { + case BMP280_DEVICE_SAMPLE1: + case BMP280_DEVICE_SAMPLE2: + case BMP280_DEVICE: + case BME280_DEVICE: { + if (wire_status) { + // Store detected chip ID when chip found. + if (sensorID != chip_id) { + sensorID = static_cast(chip_id); + setUninitialized(); + String log = F("BMx280 : Detected "); + log += getFullDeviceName(); + addLog(LOG_LEVEL_INFO, log); + } + } else { + sensorID = Unknown_DEVICE; + } + break; + } + default: + sensorID = Unknown_DEVICE; + break; + } + + if (sensorID == Unknown_DEVICE) { + String log = F("BMx280 : Unable to detect chip ID"); + addLog(LOG_LEVEL_INFO, log); + return false; + } + return wire_status; +} + +bool P028_data_struct::begin() { + if (!check()) { + return false; + } + + // Perform soft reset + I2C_write8_reg(i2cAddress, BMx280_REGISTER_SOFTRESET, 0xB6); + delay(2); // Startup time is 2 ms (datasheet) + readCoefficients(); + + // delay(65); //May be needed here as well to fix first wrong measurement? + return true; +} + +void P028_data_struct::readCoefficients() +{ + calib.dig_T1 = I2C_read16_LE_reg(i2cAddress, BMx280_REGISTER_DIG_T1); + calib.dig_T2 = I2C_readS16_LE_reg(i2cAddress, BMx280_REGISTER_DIG_T2); + calib.dig_T3 = I2C_readS16_LE_reg(i2cAddress, BMx280_REGISTER_DIG_T3); + + calib.dig_P1 = I2C_read16_LE_reg(i2cAddress, BMx280_REGISTER_DIG_P1); + calib.dig_P2 = I2C_readS16_LE_reg(i2cAddress, BMx280_REGISTER_DIG_P2); + calib.dig_P3 = I2C_readS16_LE_reg(i2cAddress, BMx280_REGISTER_DIG_P3); + calib.dig_P4 = I2C_readS16_LE_reg(i2cAddress, BMx280_REGISTER_DIG_P4); + calib.dig_P5 = I2C_readS16_LE_reg(i2cAddress, BMx280_REGISTER_DIG_P5); + calib.dig_P6 = I2C_readS16_LE_reg(i2cAddress, BMx280_REGISTER_DIG_P6); + calib.dig_P7 = I2C_readS16_LE_reg(i2cAddress, BMx280_REGISTER_DIG_P7); + calib.dig_P8 = I2C_readS16_LE_reg(i2cAddress, BMx280_REGISTER_DIG_P8); + calib.dig_P9 = I2C_readS16_LE_reg(i2cAddress, BMx280_REGISTER_DIG_P9); + + if (hasHumidity()) { + calib.dig_H1 = I2C_read8_reg(i2cAddress, BMx280_REGISTER_DIG_H1); + calib.dig_H2 = I2C_readS16_LE_reg(i2cAddress, BMx280_REGISTER_DIG_H2); + calib.dig_H3 = I2C_read8_reg(i2cAddress, BMx280_REGISTER_DIG_H3); + calib.dig_H4 = (I2C_read8_reg(i2cAddress, BMx280_REGISTER_DIG_H4) << 4) | (I2C_read8_reg(i2cAddress, BMx280_REGISTER_DIG_H4 + 1) & 0xF); + calib.dig_H5 = (I2C_read8_reg(i2cAddress, BMx280_REGISTER_DIG_H5 + 1) << 4) | (I2C_read8_reg(i2cAddress, BMx280_REGISTER_DIG_H5) >> 4); + calib.dig_H6 = (int8_t)I2C_read8_reg(i2cAddress, BMx280_REGISTER_DIG_H6); + } +} + +bool P028_data_struct::readUncompensatedData() { + // wait until measurement has been completed, otherwise we would read + // the values from the last measurement + if (I2C_read8_reg(i2cAddress, BMx280_REGISTER_STATUS) & 0x08) { + return false; + } + + I2Cdata_bytes BME280_data(BME280_P_T_H_DATA_LEN, BME280_DATA_ADDR); + bool allDataRead = I2C_read_bytes(i2cAddress, BME280_data); + + if (!allDataRead) { + return false; + } + + /* Variables to store the sensor data */ + uint32_t data_xlsb; + uint32_t data_lsb; + uint32_t data_msb; + + /* Store the parsed register values for pressure data */ + data_msb = (uint32_t)BME280_data[BME280_DATA_ADDR + 0] << 12; + data_lsb = (uint32_t)BME280_data[BME280_DATA_ADDR + 1] << 4; + data_xlsb = (uint32_t)BME280_data[BME280_DATA_ADDR + 2] >> 4; + uncompensated.pressure = data_msb | data_lsb | data_xlsb; + + /* Store the parsed register values for temperature data */ + data_msb = (uint32_t)BME280_data[BME280_DATA_ADDR + 3] << 12; + data_lsb = (uint32_t)BME280_data[BME280_DATA_ADDR + 4] << 4; + data_xlsb = (uint32_t)BME280_data[BME280_DATA_ADDR + 5] >> 4; + uncompensated.temperature = data_msb | data_lsb | data_xlsb; + + /* Store the parsed register values for temperature data */ + data_lsb = (uint32_t)BME280_data[BME280_DATA_ADDR + 6] << 8; + data_msb = (uint32_t)BME280_data[BME280_DATA_ADDR + 7]; + uncompensated.humidity = data_msb | data_lsb; + return true; +} + +float P028_data_struct::readTemperature() +{ + int32_t var1, var2; + int32_t adc_T = uncompensated.temperature; + + var1 = ((((adc_T >> 3) - ((int32_t)calib.dig_T1 << 1))) * + ((int32_t)calib.dig_T2)) >> 11; + + var2 = (((((adc_T >> 4) - ((int32_t)calib.dig_T1)) * + ((adc_T >> 4) - ((int32_t)calib.dig_T1))) >> 12) * + ((int32_t)calib.dig_T3)) >> 14; + + calib.t_fine = var1 + var2; + + float T = (calib.t_fine * 5 + 128) >> 8; + + return T / 100; +} + +float P028_data_struct::readPressure() +{ + int64_t var1, var2, p; + int32_t adc_P = uncompensated.pressure; + + var1 = ((int64_t)calib.t_fine) - 128000; + var2 = var1 * var1 * (int64_t)calib.dig_P6; + var2 = var2 + ((var1 * (int64_t)calib.dig_P5) << 17); + var2 = var2 + (((int64_t)calib.dig_P4) << 35); + var1 = ((var1 * var1 * (int64_t)calib.dig_P3) >> 8) + + ((var1 * (int64_t)calib.dig_P2) << 12); + var1 = (((((int64_t)1) << 47) + var1)) * ((int64_t)calib.dig_P1) >> 33; + + if (var1 == 0) { + return 0; // avoid exception caused by division by zero + } + p = 1048576 - adc_P; + p = (((p << 31) - var2) * 3125) / var1; + var1 = (((int64_t)calib.dig_P9) * (p >> 13) * (p >> 13)) >> 25; + var2 = (((int64_t)calib.dig_P8) * p) >> 19; + + p = ((p + var1 + var2) >> 8) + (((int64_t)calib.dig_P7) << 4); + return (float)p / 256; +} + +float P028_data_struct::readHumidity() +{ + if (!hasHumidity()) { + // No support for humidity + return 0.0; + } + int32_t adc_H = uncompensated.humidity; + + int32_t v_x1_u32r; + + v_x1_u32r = (calib.t_fine - ((int32_t)76800)); + + v_x1_u32r = (((((adc_H << 14) - (((int32_t)calib.dig_H4) << 20) - + (((int32_t)calib.dig_H5) * v_x1_u32r)) + ((int32_t)16384)) >> 15) * + (((((((v_x1_u32r * ((int32_t)calib.dig_H6)) >> 10) * + (((v_x1_u32r * ((int32_t)calib.dig_H3)) >> 11) + ((int32_t)32768))) >> 10) + + ((int32_t)2097152)) * ((int32_t)calib.dig_H2) + 8192) >> 14)); + + v_x1_u32r = (v_x1_u32r - (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7) * + ((int32_t)calib.dig_H1)) >> 4)); + + v_x1_u32r = (v_x1_u32r < 0) ? 0 : v_x1_u32r; + v_x1_u32r = (v_x1_u32r > 419430400) ? 419430400 : v_x1_u32r; + float h = (v_x1_u32r >> 12); + + return h / 1024.0; +} + +float P028_data_struct::Plugin_028_readAltitude(float seaLevel) +{ + // Equation taken from BMP180 datasheet (page 16): + // http://www.adafruit.com/datasheets/BST-BMP180-DS000-09.pdf + + // Note that using the equation from wikipedia can give bad results + // at high altitude. See this thread for more information: + // http://forums.adafruit.com/viewtopic.php?f=22&t=58064 + + float atmospheric = readPressure() / 100.0F; + + return 44330.0 * (1.0 - pow(atmospheric / seaLevel, 0.1903)); +} + +float P028_data_struct::pressureElevation(int altitude) { + return last_press_val / pow(1.0 - (altitude / 44330.0), 5.255); +} + +#endif // ifdef USES_P028 diff --git a/src/src/PluginStructs/P028_data_struct.h b/src/src/PluginStructs/P028_data_struct.h new file mode 100644 index 000000000..893e08e01 --- /dev/null +++ b/src/src/PluginStructs/P028_data_struct.h @@ -0,0 +1,188 @@ +#ifndef PLUGINSTRUCTS_P028_DATA_STRUCT_H +#define PLUGINSTRUCTS_P028_DATA_STRUCT_H + +#include "../../_Plugin_Helper.h" +#include "../../ESPEasy_common.h" + +#ifdef USES_P028 + +# define BMx280_REGISTER_DIG_T1 0x88 +# define BMx280_REGISTER_DIG_T2 0x8A +# define BMx280_REGISTER_DIG_T3 0x8C + +# define BMx280_REGISTER_DIG_P1 0x8E +# define BMx280_REGISTER_DIG_P2 0x90 +# define BMx280_REGISTER_DIG_P3 0x92 +# define BMx280_REGISTER_DIG_P4 0x94 +# define BMx280_REGISTER_DIG_P5 0x96 +# define BMx280_REGISTER_DIG_P6 0x98 +# define BMx280_REGISTER_DIG_P7 0x9A +# define BMx280_REGISTER_DIG_P8 0x9C +# define BMx280_REGISTER_DIG_P9 0x9E + +# define BMx280_REGISTER_DIG_H1 0xA1 +# define BMx280_REGISTER_DIG_H2 0xE1 +# define BMx280_REGISTER_DIG_H3 0xE3 +# define BMx280_REGISTER_DIG_H4 0xE4 +# define BMx280_REGISTER_DIG_H5 0xE5 +# define BMx280_REGISTER_DIG_H6 0xE7 + +# define BMx280_REGISTER_CHIPID 0xD0 +# define BMx280_REGISTER_VERSION 0xD1 +# define BMx280_REGISTER_SOFTRESET 0xE0 + +# define BMx280_REGISTER_CAL26 0xE1 // R calibration stored in 0xE1-0xF0 + +# define BMx280_REGISTER_CONTROLHUMID 0xF2 +# define BMx280_REGISTER_STATUS 0xF3 +# define BMx280_REGISTER_CONTROL 0xF4 +# define BMx280_REGISTER_CONFIG 0xF5 +# define BMx280_REGISTER_PRESSUREDATA 0xF7 +# define BMx280_REGISTER_TEMPDATA 0xFA +# define BMx280_REGISTER_HUMIDDATA 0xFD + +# define BME280_CONTROL_SETTING_HUMIDITY 0x02 // Oversampling: 2x H + +# define BME280_TEMP_PRESS_CALIB_DATA_ADDR 0x88 +# define BME280_HUMIDITY_CALIB_DATA_ADDR 0xE1 +# define BME280_DATA_ADDR 0xF7 + +# define BME280_TEMP_PRESS_CALIB_DATA_LEN 26 +# define BME280_HUMIDITY_CALIB_DATA_LEN 7 +# define BME280_P_T_H_DATA_LEN 8 + +typedef struct +{ + uint16_t dig_T1 = 0; + int16_t dig_T2 = 0; + int16_t dig_T3 = 0; + + uint16_t dig_P1 = 0; + int16_t dig_P2 = 0; + int16_t dig_P3 = 0; + int16_t dig_P4 = 0; + int16_t dig_P5 = 0; + int16_t dig_P6 = 0; + int16_t dig_P7 = 0; + int16_t dig_P8 = 0; + int16_t dig_P9 = 0; + + uint8_t dig_H1 = 0; + int16_t dig_H2 = 0; + uint8_t dig_H3 = 0; + int16_t dig_H4 = 0; + int16_t dig_H5 = 0; + int8_t dig_H6 = 0; + int32_t t_fine = 0; +} bme280_calib_data; + +struct bme280_uncomp_data { + /*! un-compensated pressure */ + uint32_t pressure = 0; + + /*! un-compensated temperature */ + uint32_t temperature = 0; + + /*! un-compensated humidity */ + uint32_t humidity = 0; +}; + +enum BMx_ChipId { + Unknown_DEVICE = 0, + BMP280_DEVICE_SAMPLE1 = 0x56, + BMP280_DEVICE_SAMPLE2 = 0x57, + BMP280_DEVICE = 0x58, + BME280_DEVICE = 0x60 +}; + +enum BMx_state { + BMx_Uninitialized = 0, + BMx_Initialized, + BMx_Wait_for_samples, + BMx_New_values, + BMx_Values_read +}; + + +struct P028_data_struct : public PluginTaskData_base { + P028_data_struct(uint8_t addr); + + byte get_config_settings() const; + + byte get_control_settings() const; + + String getFullDeviceName() const; + + String getDeviceName() const; + + boolean hasHumidity() const; + + bool initialized() const; + + void setUninitialized(); + + // Only perform the measurements with big interval to prevent the sensor from warming up. + bool updateMeasurements(float tempOffset, + unsigned long task_index); + + // **************************************************************************/ + // Check BME280 presence + // **************************************************************************/ + bool check(); + + // **************************************************************************/ + // Initialize BME280 + // **************************************************************************/ + bool begin(); + + // **************************************************************************/ + // Reads the factory-set coefficients + // **************************************************************************/ + void readCoefficients(); + + bool readUncompensatedData(); + + // **************************************************************************/ + // Read temperature + // **************************************************************************/ + float readTemperature(); + + // **************************************************************************/ + // Read pressure + // **************************************************************************/ + float readPressure(); + + // **************************************************************************/ + // Read humidity + // **************************************************************************/ + float readHumidity(); + + // **************************************************************************/ + // Calculates the altitude (in meters) from the specified atmospheric + // pressure (in hPa), and sea-level pressure (in hPa). + // @param seaLevel Sea-level pressure in hPa + // @param atmospheric Atmospheric pressure in hPa + // **************************************************************************/ + float Plugin_028_readAltitude(float seaLevel); + + // **************************************************************************/ + // MSL pressure formula + // **************************************************************************/ + float pressureElevation(int altitude); + + bme280_uncomp_data uncompensated; + bme280_calib_data calib; + float last_hum_val = 0.0; + float last_press_val = 0.0; + float last_temp_val = 0.0; + float last_dew_temp_val = 0.0; + unsigned long last_measurement = 0; + BMx_ChipId sensorID = Unknown_DEVICE; + uint8_t i2cAddress = 0; + unsigned long moment_next_step = 0; + BMx_state state = BMx_Uninitialized; +}; + +#endif // ifdef USES_P028 + +#endif // PLUGINSTRUCTS_P028_DATA_STRUCT_H From 4e2609ff890be979a576354dfd21660210c5b2ff Mon Sep 17 00:00:00 2001 From: tonhuisman Date: Mon, 17 Aug 2020 20:24:57 +0200 Subject: [PATCH 17/28] [I2C/Doc] Add Hardware page documentation, RTD help button --- ...ce_I2COptionsMultiplexerMultipleSelect.png | Bin 0 -> 12404 bytes .../Device_I2COptionsMultiplexerNone.png | Bin 0 -> 7883 bytes .../Device_I2COptionsMultiplexerSelect.png | Bin 0 -> 10041 bytes ...COptionsMultiplexerSelectSingleChannel.png | Bin 0 -> 23722 bytes .../Hardware/Device_I2COptionsShort.png | Bin 0 -> 3333 bytes docs/source/Hardware/Hardware.rst | 160 ++++++++++++++++++ .../Hardware/Hardware_GPIObootstatesESP32.png | Bin 0 -> 30516 bytes .../Hardware_GPIObootstatesESP8266.png | Bin 0 -> 25655 bytes .../source/Hardware/Hardware_I2CInterface.png | Bin 0 -> 10598 bytes .../Hardware/Hardware_I2CMultiplexerNone.png | Bin 0 -> 9905 bytes .../Hardware_I2CMultiplexer_Address.png | Bin 0 -> 22520 bytes .../Hardware/Hardware_I2CMultiplexer_Type.png | Bin 0 -> 14759 bytes docs/source/Hardware/Hardware_Resetpin.png | Bin 0 -> 4250 bytes .../Hardware/Hardware_SPIInterfaceESP32.png | Bin 0 -> 7410 bytes .../Hardware_SPIInterfaceESP32_Select.png | Bin 0 -> 9862 bytes .../Hardware/Hardware_SPIInterfaceESP8266.png | Bin 0 -> 7814 bytes .../Hardware/Hardware_Wifistatusled.png | Bin 0 -> 6679 bytes docs/source/index.rst | 1 + src/WebServer_HTML_wrappers.ino | 9 +- src/WebServer_HardwarePage.ino | 2 +- src/WebServer_Markup.ino | 7 +- 21 files changed, 175 insertions(+), 4 deletions(-) create mode 100644 docs/source/Hardware/Device_I2COptionsMultiplexerMultipleSelect.png create mode 100644 docs/source/Hardware/Device_I2COptionsMultiplexerNone.png create mode 100644 docs/source/Hardware/Device_I2COptionsMultiplexerSelect.png create mode 100644 docs/source/Hardware/Device_I2COptionsMultiplexerSelectSingleChannel.png create mode 100644 docs/source/Hardware/Device_I2COptionsShort.png create mode 100644 docs/source/Hardware/Hardware.rst create mode 100644 docs/source/Hardware/Hardware_GPIObootstatesESP32.png create mode 100644 docs/source/Hardware/Hardware_GPIObootstatesESP8266.png create mode 100644 docs/source/Hardware/Hardware_I2CInterface.png create mode 100644 docs/source/Hardware/Hardware_I2CMultiplexerNone.png create mode 100644 docs/source/Hardware/Hardware_I2CMultiplexer_Address.png create mode 100644 docs/source/Hardware/Hardware_I2CMultiplexer_Type.png create mode 100644 docs/source/Hardware/Hardware_Resetpin.png create mode 100644 docs/source/Hardware/Hardware_SPIInterfaceESP32.png create mode 100644 docs/source/Hardware/Hardware_SPIInterfaceESP32_Select.png create mode 100644 docs/source/Hardware/Hardware_SPIInterfaceESP8266.png create mode 100644 docs/source/Hardware/Hardware_Wifistatusled.png diff --git a/docs/source/Hardware/Device_I2COptionsMultiplexerMultipleSelect.png b/docs/source/Hardware/Device_I2COptionsMultiplexerMultipleSelect.png new file mode 100644 index 0000000000000000000000000000000000000000..e4839bc2afcaa59fcb1f5701cee02c699411bda9 GIT binary patch literal 12404 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 RgD!dKsKaq&DZ)Gce*rc8$Qb|t literal 0 HcmV?d00001 diff --git a/docs/source/Hardware/Device_I2COptionsMultiplexerSelect.png b/docs/source/Hardware/Device_I2COptionsMultiplexerSelect.png new file mode 100644 index 0000000000000000000000000000000000000000..4b60641fc1bdc691c210175248b1749f5e789775 GIT binary patch literal 10041 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 e*e@>=Li_TO6}yMs1ObuZ)%muiVIp7H92v zA2>1^Ih8K|jlKoY#>S6@g{P>1Eu*hA0*itqU^_RDb3j}$5=a2p$R{hIW+XH8rFO2? zd<(pUT}>s+>q*0@L=q8jeo3Q6tV?s8uDDhRKa23bspX6D#R}H_!6{ZAmNDVyV@}$E zIKV4~F$@eS*l{N3^sE_+#z>a}?5MDn`2$4!)gpRv>sqDU)gpc&so+eggMiF8@JZl= zof6Lom#5w|qjaq>zA9q3+MGF`hDkl$4`LP?*;%9jSSjI`BCnk`lDT}II_{@>zZd!hwVw=|Bd}QQfc#Dp9N?saGFIzbl31U%rNmUWZK@NHx zO8EFEU$d2Ox|}TIZwD#zE_ECdK@kIKeHCulGX_$WwMqu*8~{9|kh5L8xHP>DFP^`r49MztTRZUPl#%=Tu0B*#41sb~yh(6( z>L;%ij}tFAy;HPeO{=z!888R-f<(kM0eg<=IM{CIDqi{L2{GOUV8=>(*Koi-D5QBp z`|mL<8(Sn053Kf?r3$ts{ZoK~`?2()aKG6~Nxw3(g?C@53?1 zcmm0;HKTb0h`$oWrP*=#_ug^(Qr$DU))(5k)19|C0u^R1AzqPUQQeEOg=PuIhu3)2 zX5BA^6wH>Vs?2WthA~acsI>g}bMWRL=T8~wIggXxBIa#pF~=HMmX6z>S37$V@HStHCYL*qUaH?X#U)m{=(r|ra}y^ON-KnB z+6(T7NxkkF0_j{YtJ@A!MQ=a;w(E6Xzb0@81EqEKnM{Ur--*dsXWOCeXZFT~Tivv@ z@;3N@;u>xX*a)xm>};QrQ>T14h&ng*_p=gvUS5b#Mn0GC+vR6kVeFsN;BUAl-szy) zEH*<>QYuHzw^~ywFrrpt1o0S5oWBO$ud|5PiRfYHabfp6nzWuTP*}Qp*QiSA$uQF^ z^9=`zcp+BGMaI1;O*5}}&zX0AT%>bD?3%?M$mtzpePZG>%F7A)ad`XpNvz#_ku2Uq z@~=0XzuqW$VkBMekslC_@vKSuCI%j6Qr%l{!z@ z4Jf~l=TMW6AN`&&40l(5emo3WwC|DS+RYp}f0GGAn%DQ@!w2Js%U8+VZzGMTZa6&k z{;7&erhOPw*$G(BjdfyZ?)uJT`ZG8&m?X(@$m8CLv+13og<$CMB^hbcUVmkBj<-pl zA@h_0?1t-J zX_S0f6P`nghox*K=@9INy_^A9a@PgS|JK6)!E)&n-Cft8+5TPfcqZRhh`UW%RxFbB N#8B4+RfKem_#Y`aQ6B&R literal 0 HcmV?d00001 diff --git a/docs/source/Hardware/Device_I2COptionsMultiplexerSelectSingleChannel.png b/docs/source/Hardware/Device_I2COptionsMultiplexerSelectSingleChannel.png new file mode 100644 index 0000000000000000000000000000000000000000..4cba899bdc0b2e7c9bcfc11a46a0ab576666ed98 GIT binary patch literal 23722 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%0Dn!h(M4pf+Iil_Qws4-K+wk8%wdhxLRd<*cc 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 tZ;|`_) some pins have to be configured, and initialized during boot, for the SDA and SCL connections. This can be any unused pair of pins on the ESP board. + +The pins can be configured here, and will have default values initially (ESP8266: SDA: GPIO-4 (D2), SCL: GPIO-5 (D3), ESP32: SDA: GPIO-22, SCL: GPIO-23). When I2C is not used, these can be set to `- None -`, so the pins are available for other purposes. + +The default bus clock speed can also be set here. If only devices supporting 100 kHz are connected (Old/Slow devices), then the value can be set to 100 kHz, by default 400 kHz is configured, that is supported by newer devices, though there are many devices supporting higher frequencies. ESP8266 is able to achieve ca. 400 kHz, while ESP32 allows much higher speeds. + +Since build 20110, ESPEasy has a separate setting for Slow I2C devices, and per I2C device this slow clock speed can be selected in the Device edit page. This value is by default set to 100 kHz, but can be set lower or higher if desired. + +.. image:: Hardware_I2CInterface.png + +*Device specific Force Slow I2C speed selection:* + +.. image:: Device_I2COptionsShort.png + + +--------------- +I2C Multiplexer +--------------- + +Since build 20110, there is the option of using an I2C multiplexer. This option is not available in all builds, because of the size of the code. It is usually available in the normal, testing and custom builds, but ommitted from minimal, IR and hardware-specific builds. + +Possible use-cases for an I2C multiplexer are: + +* Connect multiple devices that have fixed or limited I2C addresses (For example, some OLED devices have a single fixed address but you need to connect 2 or more, or connect more than 3 TLS2561 devices, that support only 3 different addresses). +* Connect different devices that have the same I2C address (For example connecting a TSL2561 light/lux sensor and an APDS9960 proximity sensor). +* Connect slow and fast devices, where the speed of the fast device prohibits proper working of the slow device. + +There are a couple of I2C multiplexer chips available, currently there is support for: + +* TCA9548a (8 channels, multiple channel-connections, 8 I2C addresses, with reset) +* TCA9546a (4 channels, multiple channel-connections, 8 I2C addresses, with reset) +* PCA9540 (2 channels, fixed I2C address, no reset, experimental support) + +Both TCA9548a and TCA9546a support connecting multiple channels to the main I2C channel. This can be configured on the Device edit page for I2C devices, once the I2C Multiplexer configuration is enabled by selecting a multiplexer type and an I2C address for the multiplexer. + +Also, both the TCA9548a and TCA9546a chips have a connection for a reset signal available. This allows the chip to be reset if it gets stuck by some 'less compatible' or 'badly behaving' devices. Once connected and configured, the multiplexer can be reset from the software, if desired or required. This feature is not yet used in any I2C device plugin. + +.. image:: Hardware_I2CMultiplexerNone.png + +*Available multiplexer types:* + +.. image:: Hardware_I2CMultiplexer_Type.png + +*Select the I2C Address for the multiplexer:* + +.. image:: Hardware_I2CMultiplexer_Address.png + + +Device configuration +^^^^^^^^^^^^^^^^^^^^ + +If an I2C multiplexer is configured, every Device edit page for I2C devices will show extra options to select the channel the device is connected on. + +There is the default option of Single channel, or, when a TCA9548a or TCA9546a is configured, Multiple channels. + +*Example: A multiplexer is confgured, but the device is connected directly on the ESP board I2C channel:* + +.. image:: Device_I2COptionsMultiplexerNone.png + +*Configure a (single) multiplexer channel the device is connected on:* + +.. image:: Device_I2COptionsMultiplexerSelectSingleChannel.png + +NB: Only acceptable channels (0-7/0-3/0-1) will be available in the dropdown list, depending on the Multiplexer type configured. + +*Select Single channel or Multiple channels:* + +.. image:: Device_I2COptionsMultiplexerSelect.png + +*Configure multiple channels for a device, 8 channel multiplexer configured* + +.. image:: Device_I2COptionsMultiplexerMultipleSelect.png + +Above configuration results in channels 0, 4, 5, 6 and 7 being connected to the ESP board I2C bus when this sensor is active via I2C. + +For the 4 channel multiplexer, options 4-7 aren't shown. + + +------------- +SPI Interface +------------- + +When using devices that are connected via the SPI interface (`Wikipedia: SPI `_), the interface must be initialized during boot. This can be enabled here. For ESP32 there is the option to select either the Hardware SPI (HSPI) interface or the Virtual SPI (VSPI) interface (software controlled). + +The common SPI pins are shown here. + +Other SPI pins to be used are device specific, and need to be configured from the corresponding Device edit page. + +*For ESP8266:* + +.. image:: Hardware_SPIInterfaceESP8266.png + +*For ESP32, disabled:* + +.. image:: Hardware_SPIInterfaceESP32.png + +*For ESP32, select the desired interface:* + +.. image:: Hardware_SPIInterfaceESP32_Select.png + +NB: When using the VSPI interface and also the I2C interface is used, another pin has to be selected for I2C GPIO -> SCL, as its configuration is fixed for the VSPI setting. + + +------------------- +GPIO boot states +------------------- + +For some GPIO pins, the boot state (initial configuration after startup) can be configured. + +Some differences exist between ESP8266 and ESP32: + +* ESP8266 can't initialize GPIO's 6, 7 and 8 (technical limitation of ESP8266 chip) and 16 (always has pull-down setting) +* ESP32 can't initialize GPIO's from 16 and up (settings storage limitation, could be initialized from Rules in `System#Boot` event) + +*ESP8266 GPIO boot states:* + +.. image:: Hardware_GPIObootstatesESP8266.png + +*ESP32 GPIO boot states:* + +.. image:: Hardware_GPIObootstatesESP32.png diff --git a/docs/source/Hardware/Hardware_GPIObootstatesESP32.png b/docs/source/Hardware/Hardware_GPIObootstatesESP32.png new file mode 100644 index 0000000000000000000000000000000000000000..ad809aa3eb5fae8735184a1b67129fb83eb09a29 GIT binary patch literal 30516 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 Jz~AjS@;7y6m$Co= literal 0 HcmV?d00001 diff --git a/docs/source/Hardware/Hardware_GPIObootstatesESP8266.png b/docs/source/Hardware/Hardware_GPIObootstatesESP8266.png new file mode 100644 index 0000000000000000000000000000000000000000..d8c2d72008286a2c05bcdeb79dbeb593de3a583a GIT binary patch literal 25655 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 literal 0 HcmV?d00001 diff --git a/docs/source/Hardware/Hardware_I2CMultiplexerNone.png b/docs/source/Hardware/Hardware_I2CMultiplexerNone.png new file mode 100644 index 0000000000000000000000000000000000000000..89bf834a087f6a4c60beb6a26c2019395ee0c7e6 GIT binary patch literal 9905 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 QjD<+`i3YS($^7mA0YvRU6951J literal 0 HcmV?d00001 diff --git a/docs/source/Hardware/Hardware_Resetpin.png b/docs/source/Hardware/Hardware_Resetpin.png new file mode 100644 index 0000000000000000000000000000000000000000..392f966ba1d21570488c4aa2aab9171ed43dca1d GIT binary patch literal 4250 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 literal 0 HcmV?d00001 diff --git a/docs/source/Hardware/Hardware_SPIInterfaceESP32.png b/docs/source/Hardware/Hardware_SPIInterfaceESP32.png new file mode 100644 index 0000000000000000000000000000000000000000..044040372d17edb8cf1b0903efb0f7296b32963d GIT binary patch literal 7410 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 IY4h?w08)~X7ytkO literal 0 HcmV?d00001 diff --git a/docs/source/Hardware/Hardware_SPIInterfaceESP32_Select.png b/docs/source/Hardware/Hardware_SPIInterfaceESP32_Select.png new file mode 100644 index 0000000000000000000000000000000000000000..7d09a4812d3793a4f4accf9d944a1d5cd27bc663 GIT binary patch literal 9862 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 F{{_iMOcwwE literal 0 HcmV?d00001 diff --git a/docs/source/Hardware/Hardware_SPIInterfaceESP8266.png b/docs/source/Hardware/Hardware_SPIInterfaceESP8266.png new file mode 100644 index 0000000000000000000000000000000000000000..98b9bafa535d0e9f9c07a02b53c0b7f52b0e07ce GIT binary patch literal 7814 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 ot|W>gBl~~ 0) { addHelpButton(helpButton); } + if (rtdHelpButton.length() > 0) { + addRTDHelpButton(rtdHelpButton); + } addHtml(F("")); } diff --git a/src/WebServer_HardwarePage.ino b/src/WebServer_HardwarePage.ino index e6b8f0eef..23d62dc3a 100644 --- a/src/WebServer_HardwarePage.ino +++ b/src/WebServer_HardwarePage.ino @@ -72,7 +72,7 @@ void handle_hardware() { addHtml(F("
")); html_table_class_normal(); - addFormHeader(F("Hardware Settings"), F("ESPEasy#Hardware_page")); + addFormHeader(F("Hardware Settings"), F("ESPEasy#Hardware_page"), F("Hardware/Hardware.html")); addFormSubHeader(F("Wifi Status LED")); addFormPinSelect(formatGpioName_output("LED"), "pled", Settings.Pin_status_led); diff --git a/src/WebServer_Markup.ino b/src/WebServer_Markup.ino index 77427cb1b..a05489f76 100644 --- a/src/WebServer_Markup.ino +++ b/src/WebServer_Markup.ino @@ -259,10 +259,13 @@ void addTableSeparator(const String& label, int colspan, int h_size, const Strin } } -void addFormHeader(const String& header, const String& helpButton) +void addFormHeader(const String& header, const String& helpButton) { + addFormHeader(header, helpButton, F("")); +} +void addFormHeader(const String& header, const String& helpButton, const String& rtdHelpButton) { html_TR(); - html_table_header(header, helpButton, 225); + html_table_header(header, helpButton, rtdHelpButton, 225); html_table_header(""); } From 7fc64d8f0cc5f7b8c85ded34e4b6073c5dc44cce Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Tue, 18 Aug 2020 21:23:34 +0200 Subject: [PATCH 18/28] [I2C] Some fixes and improvements --- src/WebServer_DevicesPage.ino | 6 +++--- src/WebServer_HardwarePage.ino | 4 ++-- src/WebServer_I2C_Scanner.ino | 6 +++--- src/WebServer_JSON.ino | 2 +- src/WebServer_ToolsPage.ino | 1 + src/__Plugin.ino | 18 +++++++++--------- src/src/DataStructs/DeviceStruct.h | 1 + src/src/Helpers/ESPEasy_Storage.cpp | 2 +- src/src/Helpers/Hardware.cpp | 6 ++++++ src/src/Helpers/Hardware.h | 2 ++ 10 files changed, 29 insertions(+), 19 deletions(-) diff --git a/src/WebServer_DevicesPage.ino b/src/WebServer_DevicesPage.ino index 259a633ca..8d55bd118 100644 --- a/src/WebServer_DevicesPage.ino +++ b/src/WebServer_DevicesPage.ino @@ -225,7 +225,7 @@ void handle_devices_CopySubmittedSettings(taskIndex_t taskIndex, pluginID_t task bitWrite(flags, 0, isFormItemChecked(F("taskdeviceflags0"))); } #ifdef FEATURE_I2CMULTIPLEXER - if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && Settings.I2C_Multiplexer_Addr != -1) { + if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && isI2CMultiplexerEnabled()) { int multipleMuxPortsOption = getFormItemInt(F("taskdeviceflags1"), 0); bitWrite(flags, 1, multipleMuxPortsOption == 1); if (multipleMuxPortsOption == 1) { @@ -456,7 +456,7 @@ void handle_devicess_ShowAllTasksTable(byte page) case DEVICE_TYPE_I2C: addHtml(F("I2C")); #ifdef FEATURE_I2CMULTIPLEXER - if (Settings.I2C_Multiplexer_Addr != -1 && I2CMultiplexerPortSelectedForTask(x)) { + if (isI2CMultiplexerEnabled() && I2CMultiplexerPortSelectedForTask(x)) { String mux; if (bitRead(Settings.I2C_Flags[x], I2C_FLAGS_MUX_MULTICHANNEL)) { // Multi-channel mux = F("
Multiplexer channel(s)"); @@ -757,7 +757,7 @@ void handle_devices_TaskSettingsPage(taskIndex_t taskIndex, byte page) } #ifdef FEATURE_I2CMULTIPLEXER // Show selector for an I2C multiplexer port if a multiplexer is configured - if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && Settings.I2C_Multiplexer_Addr != -1) { + if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && isI2CMultiplexerEnabled()) { bool multipleMuxPorts = bitRead(Settings.I2C_Flags[taskIndex], I2C_FLAGS_MUX_MULTICHANNEL); { String i2c_mux_channels[2]; diff --git a/src/WebServer_HardwarePage.ino b/src/WebServer_HardwarePage.ino index 23d62dc3a..2be857b5d 100644 --- a/src/WebServer_HardwarePage.ino +++ b/src/WebServer_HardwarePage.ino @@ -22,7 +22,7 @@ void handle_hardware() { Settings.I2C_clockSpeed_Slow = getFormItemInt(F("pi2cspslow"), DEFAULT_I2C_CLOCK_SPEED_SLOW); #ifdef FEATURE_I2CMULTIPLEXER Settings.I2C_Multiplexer_Type = getFormItemInt(F("pi2cmuxtype")); - if (Settings.I2C_Multiplexer_Type != -1) { + if (Settings.I2C_Multiplexer_Type != I2C_MULTIPLEXER_NONE) { Settings.I2C_Multiplexer_Addr = getFormItemInt(F("pi2cmuxaddr")); } else { Settings.I2C_Multiplexer_Addr = -1; @@ -113,7 +113,7 @@ void handle_hardware() { int i2c_mux_choices[9]; uint8_t mux_opt = 0; i2c_mux_options[mux_opt] = F("- None -"); - i2c_mux_choices[mux_opt] = -1; + i2c_mux_choices[mux_opt] = I2C_MULTIPLEXER_NONE; for (int8_t x = 0; x < 8; x++) { mux_opt++; i2c_mux_options[mux_opt] = formatToHex_decimal(0x70 + x); diff --git a/src/WebServer_I2C_Scanner.ino b/src/WebServer_I2C_Scanner.ino index d16eabbf9..3a91d1718 100644 --- a/src/WebServer_I2C_Scanner.ino +++ b/src/WebServer_I2C_Scanner.ino @@ -116,7 +116,7 @@ void handle_i2cscanner_json() { #endif #ifdef FEATURE_I2CMULTIPLEXER - if (Settings.I2C_Multiplexer_Addr != -1) { + if (isI2CMultiplexerEnabled()) { uint8_t mux_max = I2CMultiplexerMaxChannels(); for (int8_t channel = 0; channel < mux_max; channel++) { I2CMultiplexerSelect(channel); @@ -313,7 +313,7 @@ void handle_i2cscanner() { html_table_class_multirow(); #ifdef FEATURE_I2CMULTIPLEXER - if (Settings.I2C_Multiplexer_Addr != -1) { + if (isI2CMultiplexerEnabled()) { html_table_header(F("I2C bus")); } #endif @@ -334,7 +334,7 @@ void handle_i2cscanner() { #endif #ifdef FEATURE_I2CMULTIPLEXER - if (Settings.I2C_Multiplexer_Addr != -1) { + if (isI2CMultiplexerEnabled()) { uint8_t mux_max = I2CMultiplexerMaxChannels(); for (int8_t channel = 0; channel < mux_max; channel++) { I2CMultiplexerSelect(channel); diff --git a/src/WebServer_JSON.ino b/src/WebServer_JSON.ino index 1e30019d5..491e29736 100644 --- a/src/WebServer_JSON.ino +++ b/src/WebServer_JSON.ino @@ -325,7 +325,7 @@ void handle_json() stream_next_json_object_value(F("TaskName"), String(ExtraTaskSettings.TaskDeviceName)); stream_next_json_object_value(F("TaskDeviceNumber"), String(Settings.TaskDeviceNumber[TaskIndex])); #ifdef FEATURE_I2CMULTIPLEXER - if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && Settings.I2C_Multiplexer_Addr != -1) { + if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && isI2CMultiplexerEnabled()) { int8_t channel = Settings.I2C_Multiplexer_Channel[TaskIndex]; if (bitRead(Settings.I2C_Flags[TaskIndex], I2C_FLAGS_MUX_MULTICHANNEL)) { addHtml(F("\"I2CBus\" : [")); diff --git a/src/WebServer_ToolsPage.ino b/src/WebServer_ToolsPage.ino index c24b2ba56..f820f6d79 100644 --- a/src/WebServer_ToolsPage.ino +++ b/src/WebServer_ToolsPage.ino @@ -26,6 +26,7 @@ void handle_tools() { html_TR_TD(); addSubmitButton(); addHelpButton(F("ESPEasy_Command_Reference")); + addRTDHelpButton(F("Reference/Command.html")); html_TR_TD(); printToWeb = true; diff --git a/src/__Plugin.ino b/src/__Plugin.ino index db9fafb77..634b1f9cc 100644 --- a/src/__Plugin.ino +++ b/src/__Plugin.ino @@ -1124,7 +1124,7 @@ byte PluginCall(byte Function, struct EventStruct *event, String& str) validTaskIndex = validTaskVars(event, taskIndex, varNr); #ifdef FEATURE_I2CMULTIPLEXER - if (validTaskIndex && Settings.I2C_Multiplexer_Addr != -1 && I2CMultiplexerPortSelectedForTask(taskIndex)) { + if (validTaskIndex && isI2CMultiplexerEnabled() && I2CMultiplexerPortSelectedForTask(taskIndex)) { I2CMultiplexerSelectByTaskIndex(taskIndex); } #else @@ -1169,7 +1169,7 @@ byte PluginCall(byte Function, struct EventStruct *event, String& str) if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C) { validTaskIndex = validTaskVars(event, taskIndex, varNr); #ifdef FEATURE_I2CMULTIPLEXER - if (validTaskIndex && Settings.I2C_Multiplexer_Addr != -1 && I2CMultiplexerPortSelectedForTask(taskIndex)) { + if (validTaskIndex && isI2CMultiplexerEnabled() && I2CMultiplexerPortSelectedForTask(taskIndex)) { I2CMultiplexerSelectByTaskIndex(taskIndex); } #else @@ -1213,7 +1213,7 @@ byte PluginCall(byte Function, struct EventStruct *event, String& str) TempEvent.BaseVarIndex = task * VARS_PER_TASK; TempEvent.sensorType = Device[DeviceIndex].VType; #ifdef FEATURE_I2CMULTIPLEXER - if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && Settings.I2C_Multiplexer_Addr != -1 && I2CMultiplexerPortSelectedForTask(task)) { + if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && isI2CMultiplexerEnabled() && I2CMultiplexerPortSelectedForTask(task)) { I2CMultiplexerSelectByTaskIndex(task); } #else @@ -1226,7 +1226,7 @@ byte PluginCall(byte Function, struct EventStruct *event, String& str) bool retval = (Plugin_ptr[DeviceIndex](Function, &TempEvent, str)); STOP_TIMER_TASK(DeviceIndex, Function); #ifdef FEATURE_I2CMULTIPLEXER - if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && Settings.I2C_Multiplexer_Addr != -1 && I2CMultiplexerPortSelectedForTask(task)) { + if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && isI2CMultiplexerEnabled() && I2CMultiplexerPortSelectedForTask(task)) { I2CMultiplexerOffByTaskIndex(task); } #else @@ -1275,7 +1275,7 @@ byte PluginCall(byte Function, struct EventStruct *event, String& str) // TempEvent.idx = Settings.TaskDeviceID[task]; todo check TempEvent.sensorType = Device[DeviceIndex].VType; #ifdef FEATURE_I2CMULTIPLEXER - if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && Settings.I2C_Multiplexer_Addr != -1 && I2CMultiplexerPortSelectedForTask(task)) { + if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && isI2CMultiplexerEnabled() && I2CMultiplexerPortSelectedForTask(task)) { I2CMultiplexerSelectByTaskIndex(task); } #else @@ -1287,7 +1287,7 @@ byte PluginCall(byte Function, struct EventStruct *event, String& str) bool retval = (Plugin_ptr[DeviceIndex](Function, &TempEvent, str)); STOP_TIMER_TASK(DeviceIndex, Function); #ifdef FEATURE_I2CMULTIPLEXER - if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && Settings.I2C_Multiplexer_Addr != -1 && I2CMultiplexerPortSelectedForTask(task)) { + if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && isI2CMultiplexerEnabled() && I2CMultiplexerPortSelectedForTask(task)) { I2CMultiplexerOffByTaskIndex(task); } #else @@ -1343,7 +1343,7 @@ byte PluginCall(byte Function, struct EventStruct *event, String& str) Scheduler.schedule_task_device_timer_at_init(TempEvent.TaskIndex); } #ifdef FEATURE_I2CMULTIPLEXER - if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && Settings.I2C_Multiplexer_Addr != -1 && I2CMultiplexerPortSelectedForTask(task)) { + if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && isI2CMultiplexerEnabled() && I2CMultiplexerPortSelectedForTask(task)) { I2CMultiplexerSelectByTaskIndex(task); } #else @@ -1413,7 +1413,7 @@ byte PluginCall(byte Function, struct EventStruct *event, String& str) } } #ifdef FEATURE_I2CMULTIPLEXER - if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && Settings.I2C_Multiplexer_Addr != -1 && I2CMultiplexerPortSelectedForTask(event->TaskIndex)) { + if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && isI2CMultiplexerEnabled() && I2CMultiplexerPortSelectedForTask(event->TaskIndex)) { I2CMultiplexerSelectByTaskIndex(event->TaskIndex); } #else @@ -1434,7 +1434,7 @@ byte PluginCall(byte Function, struct EventStruct *event, String& str) } STOP_TIMER_TASK(DeviceIndex, Function); #ifdef FEATURE_I2CMULTIPLEXER - if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && Settings.I2C_Multiplexer_Addr != -1 && I2CMultiplexerPortSelectedForTask(event->TaskIndex)) { + if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && isI2CMultiplexerEnabled() && I2CMultiplexerPortSelectedForTask(event->TaskIndex)) { I2CMultiplexerOffByTaskIndex(event->TaskIndex); } #else diff --git a/src/src/DataStructs/DeviceStruct.h b/src/src/DataStructs/DeviceStruct.h index 92237c636..ba6a0ccea 100644 --- a/src/src/DataStructs/DeviceStruct.h +++ b/src/src/DataStructs/DeviceStruct.h @@ -16,6 +16,7 @@ #define DEVICE_TYPE_DUMMY 99 // Dummy device, has no physical connection #ifdef FEATURE_I2CMULTIPLEXER +#define I2C_MULTIPLEXER_NONE -1 // None selected #define I2C_MULTIPLEXER_TCA9548A 0 // TCA9548a 8 channel I2C switch, addresses 0x70-0x77 #define I2C_MULTIPLEXER_TCA9546A 1 // TCA9546a 4 channel I2C switch, addresses 0x70-0x77 #define I2C_MULTIPLEXER_PCA9540 2 // PCA9540 2 channel I2C switch, address 0x70, different channel addressing diff --git a/src/src/Helpers/ESPEasy_Storage.cpp b/src/src/Helpers/ESPEasy_Storage.cpp index 2f74bd2fd..8b6b9db1c 100644 --- a/src/src/Helpers/ESPEasy_Storage.cpp +++ b/src/src/Helpers/ESPEasy_Storage.cpp @@ -221,7 +221,7 @@ String BuildFixes() } if (Settings.Build < 20110) { Settings.I2C_clockSpeed_Slow = DEFAULT_I2C_CLOCK_SPEED_SLOW; - Settings.I2C_Multiplexer_Type = -1; + Settings.I2C_Multiplexer_Type = I2C_MULTIPLEXER_NONE; Settings.I2C_Multiplexer_Addr = -1; for (taskIndex_t x = 0; x < TASKS_MAX; x++) { Settings.I2C_Multiplexer_Channel[x] = -1; diff --git a/src/src/Helpers/Hardware.cpp b/src/src/Helpers/Hardware.cpp index c675b1b9f..3e344c581 100644 --- a/src/src/Helpers/Hardware.cpp +++ b/src/src/Helpers/Hardware.cpp @@ -159,6 +159,12 @@ void I2CSelectClockSpeed(bool setLowSpeed) { } #ifdef FEATURE_I2CMULTIPLEXER +// Check if the I2C Multiplexer is enabled +bool isI2CMultiplexerEnabled() { + return Settings.I2C_Multiplexer_Type != I2C_MULTIPLEXER_NONE + && Settings.I2C_Multiplexer_Addr != -1; +} + // Reset the I2C Multiplexer, if a pin is assigned for that. Pulled to low to force a reset. void I2CMultiplexerReset() { if (Settings.I2C_Multiplexer_ResetPin != -1) { diff --git a/src/src/Helpers/Hardware.h b/src/src/Helpers/Hardware.h index 74f5bc04e..25eb63e8a 100644 --- a/src/src/Helpers/Hardware.h +++ b/src/src/Helpers/Hardware.h @@ -18,6 +18,8 @@ void initI2C(); void I2CSelectClockSpeed(bool setLowSpeed); #ifdef FEATURE_I2CMULTIPLEXER +bool isI2CMultiplexerEnabled(); + void I2CMultiplexerSelectByTaskIndex(int8_t taskIndex); void I2CMultiplexerSelect(uint8_t i); From d9d09c1239fcc61900bb2c0abe28688c3fadacdf Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Wed, 19 Aug 2020 15:19:40 +0200 Subject: [PATCH 19/28] [I2C] Add TCA9543a multiplexer, updated docs --- .../Device_I2COptionsMultiplexerSelect.png | Bin 10041 -> 9135 bytes docs/source/Hardware/Hardware.rst | 17 +++++++++++------ .../Hardware_I2CMultiplexer_Address.png | Bin 22520 -> 24282 bytes .../Hardware/Hardware_I2CMultiplexer_Type.png | Bin 14759 -> 16174 bytes src/WebServer_HardwarePage.ino | 18 +++++++++++------- src/WebServer_I2C_Scanner.ino | 10 ++++++---- src/src/DataStructs/DeviceStruct.h | 7 ++++--- src/src/Helpers/Hardware.cpp | 4 +++- 8 files changed, 35 insertions(+), 21 deletions(-) diff --git a/docs/source/Hardware/Device_I2COptionsMultiplexerSelect.png b/docs/source/Hardware/Device_I2COptionsMultiplexerSelect.png index 4b60641fc1bdc691c210175248b1749f5e789775..418f1db15d3a082dcc6d04d0a5c52020c5038afe 100644 GIT binary patch literal 9135 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 literal 10041 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 e*e@>=Li_TO6}yMs1ObuZ)%muiVIp7H92v zA2>1^Ih8K|jlKoY#>S6@g{P>1Eu*hA0*itqU^_RDb3j}$5=a2p$R{hIW+XH8rFO2? zd<(pUT}>s+>q*0@L=q8jeo3Q6tV?s8uDDhRKa23bspX6D#R}H_!6{ZAmNDVyV@}$E zIKV4~F$@eS*l{N3^sE_+#z>a}?5MDn`2$4!)gpRv>sqDU)gpc&so+eggMiF8@JZl= zof6Lom#5w|qjaq>zA9q3+MGF`hDkl$4`LP?*;%9jSSjI`BCnk`lDT}II_{@>zZd!hwVw=|Bd}QQfc#Dp9N?saGFIzbl31U%rNmUWZK@NHx zO8EFEU$d2Ox|}TIZwD#zE_ECdK@kIKeHCulGX_$WwMqu*8~{9|kh5L8xHP>DFP^`r49MztTRZUPl#%=Tu0B*#41sb~yh(6( z>L;%ij}tFAy;HPeO{=z!888R-f<(kM0eg<=IM{CIDqi{L2{GOUV8=>(*Koi-D5QBp z`|mL<8(Sn053Kf?r3$ts{ZoK~`?2()aKG6~Nxw3(g?C@53?1 zcmm0;HKTb0h`$oWrP*=#_ug^(Qr$DU))(5k)19|C0u^R1AzqPUQQeEOg=PuIhu3)2 zX5BA^6wH>Vs?2WthA~acsI>g}bMWRL=T8~wIggXxBIa#pF~=HMmX6z>S37$V@HStHCYL*qUaH?X#U)m{=(r|ra}y^ON-KnB z+6(T7NxkkF0_j{YtJ@A!MQ=a;w(E6Xzb0@81EqEKnM{Ur--*dsXWOCeXZFT~Tivv@ z@;3N@;u>xX*a)xm>};QrQ>T14h&ng*_p=gvUS5b#Mn0GC+vR6kVeFsN;BUAl-szy) zEH*<>QYuHzw^~ywFrrpt1o0S5oWBO$ud|5PiRfYHabfp6nzWuTP*}Qp*QiSA$uQF^ z^9=`zcp+BGMaI1;O*5}}&zX0AT%>bD?3%?M$mtzpePZG>%F7A)ad`XpNvz#_ku2Uq z@~=0XzuqW$VkBMekslC_@vKSuCI%j6Qr%l{!z@ z4Jf~l=TMW6AN`&&40l(5emo3WwC|DS+RYp}f0GGAn%DQ@!w2Js%U8+VZzGMTZa6&k z{;7&erhOPw*$G(BjdfyZ?)uJT`ZG8&m?X(@$m8CLv+13og<$CMB^hbcUVmkBj<-pl zA@h_0?1t-J zX_S0f6P`nghox*K=@9INy_^A9a@PgS|JK6)!E)&n-Cft8+5TPfcqZRhh`UW%RxFbB N#8B4+RfKem_#Y`aQ6B&R diff --git a/docs/source/Hardware/Hardware.rst b/docs/source/Hardware/Hardware.rst index eb2c1bd54..058db8101 100644 --- a/docs/source/Hardware/Hardware.rst +++ b/docs/source/Hardware/Hardware.rst @@ -67,12 +67,17 @@ Possible use-cases for an I2C multiplexer are: There are a couple of I2C multiplexer chips available, currently there is support for: * TCA9548a (8 channels, multiple channel-connections, 8 I2C addresses, with reset) -* TCA9546a (4 channels, multiple channel-connections, 8 I2C addresses, with reset) +* TCA9546a (4 channels, multiple channel-connections, 8 I2C addresses, with reset, also TCA9545a can be used, but no support for the Interrupt function though) +* TCA9543a (2 channels, multiple channel-connections, 4 I2C addresses, with reset) * PCA9540 (2 channels, fixed I2C address, no reset, experimental support) -Both TCA9548a and TCA9546a support connecting multiple channels to the main I2C channel. This can be configured on the Device edit page for I2C devices, once the I2C Multiplexer configuration is enabled by selecting a multiplexer type and an I2C address for the multiplexer. +The TCA9548a, TCA9546a and TCA9543a support connecting multiple channels to the main I2C channel. This can be configured on the Device edit page for I2C devices, once the I2C Multiplexer configuration is enabled by selecting a multiplexer type and an I2C address for the multiplexer. -Also, both the TCA9548a and TCA9546a chips have a connection for a reset signal available. This allows the chip to be reset if it gets stuck by some 'less compatible' or 'badly behaving' devices. Once connected and configured, the multiplexer can be reset from the software, if desired or required. This feature is not yet used in any I2C device plugin. +Also, the TCA9548a, TCA9546a and TCA9543a chips have a connection for a reset signal available. This allows the chip to be reset if it gets stuck by some 'less compatible' or 'badly behaving' devices. Once connected and configured, the multiplexer can be reset from the software, if desired or required. This feature is not yet used in any I2C device plugin. + +A TCA9543a board has the advantage of being quite a bit smaller than either TCA9546a or TCA9548a, while being digitally compatible. (But with less channels and only 4 I2C addresses). + +All these chips/boards can be found at Adafruit, Aliexpress, Banggood, EBay, etc. .. image:: Hardware_I2CMultiplexerNone.png @@ -90,9 +95,9 @@ Device configuration If an I2C multiplexer is configured, every Device edit page for I2C devices will show extra options to select the channel the device is connected on. -There is the default option of Single channel, or, when a TCA9548a or TCA9546a is configured, Multiple channels. +There is the default option of Single channel, or, when a TCA9548a, TCA9546a or TCA9543a is configured, Multiple channels. -*Example: A multiplexer is confgured, but the device is connected directly on the ESP board I2C channel:* +*Example: A multiplexer is configured, but the device is connected directly on the ESP board I2C channel:* .. image:: Device_I2COptionsMultiplexerNone.png @@ -112,7 +117,7 @@ NB: Only acceptable channels (0-7/0-3/0-1) will be available in the dropdown lis Above configuration results in channels 0, 4, 5, 6 and 7 being connected to the ESP board I2C bus when this sensor is active via I2C. -For the 4 channel multiplexer, options 4-7 aren't shown. +NB: Only acceptable channel checkboxes (0-7/0-3/0-1) will be shown, depending on the Multiplexer type configured. ------------- diff --git a/docs/source/Hardware/Hardware_I2CMultiplexer_Address.png b/docs/source/Hardware/Hardware_I2CMultiplexer_Address.png index ba623cc83240d9dbd9def707708206db252c8211..e4a21fb24c6aa49e85ce0f871b2f379f594186fe 100644 GIT binary patch literal 24282 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 Si#y;2u2Uz^7#12_4*frk-Tp=Z literal 22520 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 Djq-~u literal 14759 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 QjD<+`i3YS($^7mA0YvRU6951J diff --git a/src/WebServer_HardwarePage.ino b/src/WebServer_HardwarePage.ino index 2be857b5d..8891bee26 100644 --- a/src/WebServer_HardwarePage.ino +++ b/src/WebServer_HardwarePage.ino @@ -95,17 +95,19 @@ void handle_hardware() { addFormSubHeader(F("I2C Multiplexer")); // Select the type of multiplexer to use { - String i2c_muxtype_options[4]; - int i2c_muxtype_choices[4]; + String i2c_muxtype_options[5]; + int i2c_muxtype_choices[5]; i2c_muxtype_options[0] = F("- None -"); i2c_muxtype_choices[0] = -1; i2c_muxtype_options[1] = F("TCA9548a - 8 channel"); i2c_muxtype_choices[1] = I2C_MULTIPLEXER_TCA9548A; i2c_muxtype_options[2] = F("TCA9546a - 4 channel"); i2c_muxtype_choices[2] = I2C_MULTIPLEXER_TCA9546A; - i2c_muxtype_options[3] = F("PCA9540 - 2 channel (experimental)"); - i2c_muxtype_choices[3] = I2C_MULTIPLEXER_PCA9540; - addFormSelector(F("I2C Multiplexer type"), F("pi2cmuxtype"), 4, i2c_muxtype_options, i2c_muxtype_choices, Settings.I2C_Multiplexer_Type); + i2c_muxtype_options[3] = F("TCA9543a - 2 channel"); + i2c_muxtype_choices[3] = I2C_MULTIPLEXER_TCA9543A; + i2c_muxtype_options[4] = F("PCA9540 - 2 channel (experimental)"); + i2c_muxtype_choices[4] = I2C_MULTIPLEXER_PCA9540; + addFormSelector(F("I2C Multiplexer type"), F("pi2cmuxtype"), 5, i2c_muxtype_options, i2c_muxtype_choices, Settings.I2C_Multiplexer_Type); } // Select the I2C address for a port multiplexer { @@ -118,9 +120,11 @@ void handle_hardware() { mux_opt++; i2c_mux_options[mux_opt] = formatToHex_decimal(0x70 + x); if (x == 0) { // PCA9540 has a fixed address 0f 0x70 - i2c_mux_options[mux_opt] += F(" [TCA9546/8a, PCA9540]"); + i2c_mux_options[mux_opt] += F(" [TCA9543a/6a/8a, PCA9540]"); + } else if (x < 4) { + i2c_mux_options[mux_opt] += F(" [TCA9543a/6a/8a]"); } else { - i2c_mux_options[mux_opt] += F(" [TCA9546/8a]"); + i2c_mux_options[mux_opt] += F(" [TCA9546a/8a]"); } i2c_mux_choices[mux_opt] = 0x70 + x; } diff --git a/src/WebServer_I2C_Scanner.ino b/src/WebServer_I2C_Scanner.ino index 3a91d1718..21601b3cb 100644 --- a/src/WebServer_I2C_Scanner.ino +++ b/src/WebServer_I2C_Scanner.ino @@ -217,20 +217,22 @@ String getKnownI2Cdevice(byte address) { result = F("Adafruit Motorshield v2,SI1145"); break; case 0x70: - result = F("Adafruit Motorshield v2 (Catchall),HT16K33,TCA9546/8a I2C multiplexer,PCA9540 I2C multiplexer"); + result = F("Adafruit Motorshield v2 (Catchall),HT16K33,TCA9543a/6a/8a I2C multiplexer,PCA9540 I2C multiplexer"); break; case 0x71: case 0x72: case 0x73: + result = F("HT16K33,TCA9543a/6a/8a I2C multiplexer"); + break; case 0x74: case 0x75: - result = F("HT16K33,TCA9546/8A I2C multiplexer"); + result = F("HT16K33,TCA9546a/8a I2C multiplexer"); break; case 0x76: - result = F("BME280,BMP280,MS5607,MS5611,HT16K33,TCA9546/8a I2C multiplexer"); + result = F("BME280,BMP280,MS5607,MS5611,HT16K33,TCA9546a/8a I2C multiplexer"); break; case 0x77: - result = F("BMP085,BMP180,BME280,BMP280,MS5607,MS5611,HT16K33,TCA9546/8a I2C multiplexer"); + result = F("BMP085,BMP180,BME280,BMP280,MS5607,MS5611,HT16K33,TCA9546a/8a I2C multiplexer"); break; case 0x7f: result = F("Arduino PME"); diff --git a/src/src/DataStructs/DeviceStruct.h b/src/src/DataStructs/DeviceStruct.h index ba6a0ccea..fb41ed8d2 100644 --- a/src/src/DataStructs/DeviceStruct.h +++ b/src/src/DataStructs/DeviceStruct.h @@ -17,9 +17,10 @@ #ifdef FEATURE_I2CMULTIPLEXER #define I2C_MULTIPLEXER_NONE -1 // None selected -#define I2C_MULTIPLEXER_TCA9548A 0 // TCA9548a 8 channel I2C switch, addresses 0x70-0x77 -#define I2C_MULTIPLEXER_TCA9546A 1 // TCA9546a 4 channel I2C switch, addresses 0x70-0x77 -#define I2C_MULTIPLEXER_PCA9540 2 // PCA9540 2 channel I2C switch, address 0x70, different channel addressing +#define I2C_MULTIPLEXER_TCA9548A 0 // TCA9548a 8 channel I2C switch, with reset, addresses 0x70-0x77 +#define I2C_MULTIPLEXER_TCA9546A 1 // TCA9546a or TCA9545a 4 channel I2C switch, with reset, addresses 0x70-0x77 (no interrupt support on TCA9545a) +#define I2C_MULTIPLEXER_TCA9543A 2 // TCA9543a 2 channel I2C switch, with reset, addresses 0x70-0x73 +#define I2C_MULTIPLEXER_PCA9540 3 // PCA9540 2 channel I2C switch, no reset, address 0x70, different channel addressing #endif #define I2C_FLAGS_SLOW_SPEED 0 // Force slow speed when this flag is set diff --git a/src/src/Helpers/Hardware.cpp b/src/src/Helpers/Hardware.cpp index 3e344c581..cb16499ff 100644 --- a/src/src/Helpers/Hardware.cpp +++ b/src/src/Helpers/Hardware.cpp @@ -178,7 +178,8 @@ void I2CMultiplexerReset() { byte I2CMultiplexerShiftBit(uint8_t i) { byte toWrite = 0; switch (Settings.I2C_Multiplexer_Type) { - case I2C_MULTIPLEXER_TCA9546A: // TCA9546/8 addressing + case I2C_MULTIPLEXER_TCA9543A: // TCA9543/6/8 addressing + case I2C_MULTIPLEXER_TCA9546A: case I2C_MULTIPLEXER_TCA9548A: toWrite = (1 << i); break; @@ -247,6 +248,7 @@ byte I2CMultiplexerMaxChannels() { case I2C_MULTIPLEXER_TCA9548A: channels = 8; break; // TCA9548A has 8 channels case I2C_MULTIPLEXER_TCA9546A: channels = 4; break; // TCA9546A has 4 channels case I2C_MULTIPLEXER_PCA9540: channels = 2; break; // PCA9540 has 2 channels + case I2C_MULTIPLEXER_TCA9543A: channels = 2; break; // TCA9543A has 2 channels } return channels; } From 1e44f34495d83f0bb87e6db6b73f33e1b16714e0 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Wed, 19 Aug 2020 16:07:56 +0200 Subject: [PATCH 20/28] [I2C Docs] Emphasize configuration with multiplexer --- docs/source/Hardware/Hardware.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/source/Hardware/Hardware.rst b/docs/source/Hardware/Hardware.rst index 058db8101..0a3ac4d54 100644 --- a/docs/source/Hardware/Hardware.rst +++ b/docs/source/Hardware/Hardware.rst @@ -64,6 +64,14 @@ Possible use-cases for an I2C multiplexer are: * Connect different devices that have the same I2C address (For example connecting a TSL2561 light/lux sensor and an APDS9960 proximity sensor). * Connect slow and fast devices, where the speed of the fast device prohibits proper working of the slow device. +.. |br| raw:: html + +
+ +.. note:: + If devices with conflicting I2C addresses are to be used, then *none* of them can be connected to the ESP main I2C bus, but they should each be connected to a separate channel of the multiplexer. |br| + Devices that do not conflict with other devices *can* be connected to the ESP main I2C bus, this might improve the performance/responsiveness of these devices. + There are a couple of I2C multiplexer chips available, currently there is support for: * TCA9548a (8 channels, multiple channel-connections, 8 I2C addresses, with reset) From ded06f6c50e9892c85e05f43dcb0d9f7ba19f6e9 Mon Sep 17 00:00:00 2001 From: Ton Huisman Date: Wed, 19 Aug 2020 17:42:17 +0200 Subject: [PATCH 21/28] [I2C Docs] Add note about multiplexer address --- docs/source/Hardware/Hardware.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/source/Hardware/Hardware.rst b/docs/source/Hardware/Hardware.rst index 0a3ac4d54..82be3a846 100644 --- a/docs/source/Hardware/Hardware.rst +++ b/docs/source/Hardware/Hardware.rst @@ -72,6 +72,9 @@ Possible use-cases for an I2C multiplexer are: If devices with conflicting I2C addresses are to be used, then *none* of them can be connected to the ESP main I2C bus, but they should each be connected to a separate channel of the multiplexer. |br| Devices that do not conflict with other devices *can* be connected to the ESP main I2C bus, this might improve the performance/responsiveness of these devices. +.. note:: + When using an I2C Multiplexer, make sure there is no address conflict with any of the devices you intend to connect, f.e. when connecting BME280 sensors, don't set the address of the multiplexer to 0x76 or 0x77. + There are a couple of I2C multiplexer chips available, currently there is support for: * TCA9548a (8 channels, multiple channel-connections, 8 I2C addresses, with reset) From 4c9d62a74c45b6398c0abecfa58c66bc5aeaeeae Mon Sep 17 00:00:00 2001 From: Gijs Noorlander Date: Thu, 20 Aug 2020 09:32:25 +0200 Subject: [PATCH 22/28] [Docs] Add some description for P029 Output Domoticz MQTT helper --- docs/source/Plugin/P029.rst | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/source/Plugin/P029.rst b/docs/source/Plugin/P029.rst index 407090380..382d25849 100644 --- a/docs/source/Plugin/P029.rst +++ b/docs/source/Plugin/P029.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p02x.repl +.. include:: ../Plugin/_plugin_substitutions_p02x.repl .. _P029_page: |P029_typename| @@ -36,6 +36,17 @@ Supported hardware .. .. include:: P029_events.repl +Description +----------- + +Using the Domoticz MQTT protocol, it is not possible to configure direct commands to control output. +Instead, a json message is send by Domoticz on the Domoticz/Out channel. + +We need to configure the IDX value within ESP Easy and link this to a given output pin. +This plugin is a special 'helper' device to link IDX to GPIO. + +Only the IDX and the corresponding GPIO pin needs to be set. + Change log ---------- From f1d05a59af47028893e692e060a4dd1243d88991 Mon Sep 17 00:00:00 2001 From: Gijs Noorlander Date: Thu, 20 Aug 2020 11:51:45 +0200 Subject: [PATCH 23/28] [I2C] Add PLUGIN_WEBFORM_SHOW_I2C_PARAMS to separate I2C address setup Also made an example for P036 Framed OLED. --- src/ESPEasy_plugindefs.h | 2 ++ src/WebServer_DevicesPage.ino | 4 ++-- src/_P036_FrameOLED.ino | 19 ++++++++++--------- src/__Plugin.ino | 1 + src/src/PluginStructs/P028_data_struct.cpp | 7 ++++++- tools/pio/pre_custom_esp32.py | 1 + tools/pio/pre_custom_esp82xx.py | 1 + 7 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/ESPEasy_plugindefs.h b/src/ESPEasy_plugindefs.h index 0b2f3ddab..3066d159f 100644 --- a/src/ESPEasy_plugindefs.h +++ b/src/ESPEasy_plugindefs.h @@ -37,6 +37,8 @@ #define PLUGIN_GET_PACKED_RAW_DATA 30 // Return all data in a compact binary format specific for that plugin. // Needs USES_PACKED_RAW_DATA #define PLUGIN_ONLY_TIMER_IN 31 +#define PLUGIN_WEBFORM_SHOW_I2C_PARAMS 32 + // ******************************************************************************** diff --git a/src/WebServer_DevicesPage.ino b/src/WebServer_DevicesPage.ino index 8d55bd118..c51a94674 100644 --- a/src/WebServer_DevicesPage.ino +++ b/src/WebServer_DevicesPage.ino @@ -750,9 +750,9 @@ void handle_devices_TaskSettingsPage(taskIndex_t taskIndex, byte page) } if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C) { - addFormSubHeader(F("I2C options")); - + String dummy; + PluginCall(PLUGIN_WEBFORM_SHOW_I2C_PARAMS, &TempEvent, dummy); addFormCheckBox(F("Force Slow I2C speed"), F("taskdeviceflags0"), bitRead(Settings.I2C_Flags[taskIndex], I2C_FLAGS_SLOW_SPEED)); } #ifdef FEATURE_I2CMULTIPLEXER diff --git a/src/_P036_FrameOLED.ino b/src/_P036_FrameOLED.ino index 13cc3c466..4d8fd5f0f 100644 --- a/src/_P036_FrameOLED.ino +++ b/src/_P036_FrameOLED.ino @@ -128,6 +128,16 @@ boolean Plugin_036(uint8_t function, struct EventStruct *event, String& string) break; } + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: + { + uint8_t choice = P036_ADR; + int optionValues[2]; + optionValues[0] = 0x3C; + optionValues[1] = 0x3D; + addFormSelectorI2C(F("p036_adr"), 2, optionValues, choice); + break; + } + case PLUGIN_WEBFORM_LOAD: { #ifdef PLUGIN_036_DEBUG @@ -136,7 +146,6 @@ boolean Plugin_036(uint8_t function, struct EventStruct *event, String& string) // Use number 5 to remain compatible with existing configurations, // but the item should be one of the first choices. - addFormSubHeader(F("Display")); { uint8_t choice = P036_CONTROLLER; String options[2]; @@ -146,14 +155,6 @@ boolean Plugin_036(uint8_t function, struct EventStruct *event, String& string) addFormSelector(F("Controller"), F("p036_controller"), 2, options, optionValues, choice); } - { - uint8_t choice = P036_ADR; - int optionValues[2]; - optionValues[0] = 0x3C; - optionValues[1] = 0x3D; - addFormSelectorI2C(F("p036_adr"), 2, optionValues, choice); - } - { String options[P36_MaxSizesCount] = { F("128x64"), F("128x32"), F("64x48") }; int optionValues[P36_MaxSizesCount] = { pix128x64, pix128x32, pix64x48 }; diff --git a/src/__Plugin.ino b/src/__Plugin.ino index 634b1f9cc..400788748 100644 --- a/src/__Plugin.ino +++ b/src/__Plugin.ino @@ -1379,6 +1379,7 @@ byte PluginCall(byte Function, struct EventStruct *event, String& str) case PLUGIN_WEBFORM_SAVE: case PLUGIN_WEBFORM_SHOW_VALUES: case PLUGIN_WEBFORM_SHOW_CONFIG: + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: case PLUGIN_GET_DEVICEVALUENAMES: case PLUGIN_GET_DEVICEGPIONAMES: case PLUGIN_READ: diff --git a/src/src/PluginStructs/P028_data_struct.cpp b/src/src/PluginStructs/P028_data_struct.cpp index 69fb78182..8b022f423 100644 --- a/src/src/PluginStructs/P028_data_struct.cpp +++ b/src/src/PluginStructs/P028_data_struct.cpp @@ -235,7 +235,12 @@ bool P028_data_struct::check() { } if (sensorID == Unknown_DEVICE) { - String log = F("BMx280 : Unable to detect chip ID"); + String log = F("BMx280 : Unable to detect chip ID ("); + log += chip_id; + if (!wire_status) { + log += F(", failed"); + } + log += ')'; addLog(LOG_LEVEL_INFO, log); return false; } diff --git a/tools/pio/pre_custom_esp32.py b/tools/pio/pre_custom_esp32.py index 7f6b4ed02..283874a68 100644 --- a/tools/pio/pre_custom_esp32.py +++ b/tools/pio/pre_custom_esp32.py @@ -39,6 +39,7 @@ else: "USES_P097", # Touch (ESP32) "USE_SETTINGS_ARCHIVE", + "FEATURE_I2CMULTIPLEXER", "FEATURE_ARDUINO_OTA" ]) diff --git a/tools/pio/pre_custom_esp82xx.py b/tools/pio/pre_custom_esp82xx.py index 026abfbde..14d23c9df 100644 --- a/tools/pio/pre_custom_esp82xx.py +++ b/tools/pio/pre_custom_esp82xx.py @@ -47,6 +47,7 @@ else: "FEATURE_MDNS", "FEATURE_SD", + "FEATURE_I2CMULTIPLEXER", "USE_SETTINGS_ARCHIVE" ]) From b3f3d8ecab1778d46e0add3fee3c9e2f5f39cefa Mon Sep 17 00:00:00 2001 From: Gijs Noorlander Date: Thu, 20 Aug 2020 12:22:07 +0200 Subject: [PATCH 24/28] [I2C] Move I2C address selector for all I2C plugins with address field --- src/_P010_BH1750.ino | 29 +++++++++++++---------- src/_P012_LCD.ino | 9 +++++--- src/_P015_TSL2561.ino | 32 ++++++++++++++------------ src/_P022_PCA9685.ino | 21 +++++++++-------- src/_P023_OLED.ino | 15 +++++++----- src/_P025_ADS1115.ino | 16 +++++++------ src/_P027_INA219.ino | 11 +++++---- src/_P028_BME280.ino | 7 ++++-- src/_P030_BMP280.ino | 7 +++++- src/_P032_MS5611.ino | 7 +++++- src/_P045_MPU6050.ino | 31 ++++++++++++++----------- src/_P047_i2c-soil-moisture-sensor.ino | 11 ++++++--- src/_P048_Motorshield_v2.ino | 11 ++++++--- src/_P057_HT16K33_LED.ino | 6 ++++- src/_P058_HT16K33_KeyPad.ino | 6 ++++- src/_P060_MCP3221.ino | 6 ++++- src/_P061_KeyPad.ino | 9 ++++++-- src/_P062_MPR121_KeyPad.ino | 6 ++++- src/_P064_APDS9960.ino | 6 ++++- src/_P066_VEML6040.ino | 12 ++++++---- src/_P068_SHT3x.ino | 8 +++++-- src/_P069_LM75A.ino | 6 ++++- src/_P074_TSL2591.ino | 13 +++++++---- src/_P079_Wemos_Motorshield.ino | 12 ++++++---- src/_P090_CCS811.ino | 16 +++++++------ src/_P095_ILI9341.ino | 2 +- 26 files changed, 203 insertions(+), 112 deletions(-) diff --git a/src/_P010_BH1750.ino b/src/_P010_BH1750.ino index 524cf7708..d68e22891 100644 --- a/src/_P010_BH1750.ino +++ b/src/_P010_BH1750.ino @@ -51,20 +51,25 @@ boolean Plugin_010(byte function, struct EventStruct *event, String& string) break; } + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: + { + byte choice = PCONFIG(0); + /* + String options[2]; + options[0] = F("0x23 - default settings (ADDR Low)"); + options[1] = F("0x5c - alternate settings (ADDR High)"); + */ + int optionValues[2]; + optionValues[0] = BH1750_DEFAULT_I2CADDR; + optionValues[1] = BH1750_SECOND_I2CADDR; + addFormSelectorI2C(F("p010"), 2, optionValues, choice); + addFormNote(F("ADDR Low=0x23, High=0x5c")); + break; + } + + case PLUGIN_WEBFORM_LOAD: { - byte choice = PCONFIG(0); - /* - String options[2]; - options[0] = F("0x23 - default settings (ADDR Low)"); - options[1] = F("0x5c - alternate settings (ADDR High)"); - */ - int optionValues[2]; - optionValues[0] = BH1750_DEFAULT_I2CADDR; - optionValues[1] = BH1750_SECOND_I2CADDR; - addFormSelectorI2C(F("p010"), 2, optionValues, choice); - addFormNote(F("ADDR Low=0x23, High=0x5c")); - byte choiceMode = PCONFIG(1); String optionsMode[4]; optionsMode[0] = F("RESOLUTION_LOW"); diff --git a/src/_P012_LCD.ino b/src/_P012_LCD.ino index e5c0bab6e..f7b1d17b7 100644 --- a/src/_P012_LCD.ino +++ b/src/_P012_LCD.ino @@ -62,8 +62,8 @@ boolean Plugin_012(byte function, struct EventStruct *event, String& string) break; } - case PLUGIN_WEBFORM_LOAD: - { + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: + { byte choice = PCONFIG(0); //String options[16]; int optionValues[16]; @@ -77,8 +77,11 @@ boolean Plugin_012(byte function, struct EventStruct *event, String& string) //options[x] += String(optionValues[x], HEX); } addFormSelectorI2C(F("p012_adr"), 16, optionValues, choice); + break; + } - + case PLUGIN_WEBFORM_LOAD: + { byte choice2 = PCONFIG(1); String options2[2]; options2[0] = F("2 x 16"); diff --git a/src/_P015_TSL2561.ino b/src/_P015_TSL2561.ino index 511f1c664..b592687b2 100644 --- a/src/_P015_TSL2561.ino +++ b/src/_P015_TSL2561.ino @@ -350,23 +350,25 @@ boolean Plugin_015(byte function, struct EventStruct *event, String& string) break; } + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: + { + /* + String options1[3]; + options1[0] = F("0x39 - (default)"); + options1[1] = F("0x49"); + options1[2] = F("0x29"); + */ + int optionValues[3]; + optionValues[0] = TSL2561_ADDR; + optionValues[1] = TSL2561_ADDR_1; + optionValues[2] = TSL2561_ADDR_0; + addFormSelectorI2C(F("p015_tsl2561_i2c"), 3, optionValues, PCONFIG(0)); + break; + } + + case PLUGIN_WEBFORM_LOAD: { - { - /* - String options1[3]; - options1[0] = F("0x39 - (default)"); - options1[1] = F("0x49"); - options1[2] = F("0x29"); - */ - int optionValues[3]; - optionValues[0] = TSL2561_ADDR; - optionValues[1] = TSL2561_ADDR_1; - optionValues[2] = TSL2561_ADDR_0; - addFormSelectorI2C(F("p015_tsl2561_i2c"), 3, optionValues, PCONFIG(0)); - } - - { #define TSL2561_INTEGRATION_OPTION 3 String options[TSL2561_INTEGRATION_OPTION]; diff --git a/src/_P022_PCA9685.ino b/src/_P022_PCA9685.ino index 4e2436c84..50ffa7cdf 100644 --- a/src/_P022_PCA9685.ino +++ b/src/_P022_PCA9685.ino @@ -124,19 +124,22 @@ boolean Plugin_022(byte function, struct EventStruct *event, String& string) break; } + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: + { + int optionValues[PCA9685_NUMS_ADDRESS]; + + for (int i = 0; i < PCA9685_NUMS_ADDRESS; i++) + { + optionValues[i] = PCA9685_ADDRESS + i; + } + addFormSelectorI2C(F("i2c_addr"), PCA9685_NUMS_ADDRESS, optionValues, address); + break; + } + case PLUGIN_WEBFORM_LOAD: { // The options lists are quite long. // To prevent stack overflow issues, each selection has its own scope. - { - int optionValues[PCA9685_NUMS_ADDRESS]; - - for (int i = 0; i < PCA9685_NUMS_ADDRESS; i++) - { - optionValues[i] = PCA9685_ADDRESS + i; - } - addFormSelectorI2C(F("i2c_addr"), PCA9685_NUMS_ADDRESS, optionValues, address); - } { String m2Options[PCA9685_MODE2_VALUES]; int m2Values[PCA9685_MODE2_VALUES]; diff --git a/src/_P023_OLED.ino b/src/_P023_OLED.ino index bc24a92b6..7b7278103 100644 --- a/src/_P023_OLED.ino +++ b/src/_P023_OLED.ino @@ -77,14 +77,17 @@ boolean Plugin_023(byte function, struct EventStruct *event, String& string) break; } + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: + { + byte choice = PCONFIG(0); + /*String options[2] = { F("3C"), F("3D") };*/ + int optionValues[2] = { 0x3C, 0x3D }; + addFormSelectorI2C(F("p023_adr"), 2, optionValues, choice); + break; + } + case PLUGIN_WEBFORM_LOAD: { - { - byte choice = PCONFIG(0); - /*String options[2] = { F("3C"), F("3D") };*/ - int optionValues[2] = { 0x3C, 0x3D }; - addFormSelectorI2C(F("p023_adr"), 2, optionValues, choice); - } { byte choice2 = PCONFIG(1); String options2[2] = { F("Normal"), F("Rotated") }; diff --git a/src/_P025_ADS1115.ino b/src/_P025_ADS1115.ino index dfabba809..aa6807920 100644 --- a/src/_P025_ADS1115.ino +++ b/src/_P025_ADS1115.ino @@ -55,6 +55,15 @@ boolean Plugin_025(byte function, struct EventStruct *event, String& string) break; } + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: + { + #define ADS1115_I2C_OPTION 4 + byte addr = PCONFIG(0); + int optionValues[ADS1115_I2C_OPTION] = { 0x48, 0x49, 0x4A, 0x4B }; + addFormSelectorI2C(F("p025_i2c"), ADS1115_I2C_OPTION, optionValues, addr); + break; + } + case PLUGIN_WEBFORM_LOAD: { byte port = CONFIG_PORT; @@ -66,13 +75,6 @@ boolean Plugin_025(byte function, struct EventStruct *event, String& string) CONFIG_PORT = 0; } - { - #define ADS1115_I2C_OPTION 4 - byte addr = PCONFIG(0); - int optionValues[ADS1115_I2C_OPTION] = { 0x48, 0x49, 0x4A, 0x4B }; - addFormSelectorI2C(F("p025_i2c"), ADS1115_I2C_OPTION, optionValues, addr); - } - addFormSubHeader(F("Input")); { diff --git a/src/_P027_INA219.ino b/src/_P027_INA219.ino index 9ba564a3d..3a9eb7005 100644 --- a/src/_P027_INA219.ino +++ b/src/_P027_INA219.ino @@ -128,6 +128,13 @@ boolean Plugin_027(byte function, struct EventStruct *event, String& string) break; } + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: + { + int Plugin_27_i2c_addresses[4] = { INA219_ADDRESS, INA219_ADDRESS2, INA219_ADDRESS3, INA219_ADDRESS4 }; + addFormSelectorI2C(F("p027_i2c"), 4, Plugin_27_i2c_addresses, Plugin_027_i2c_addr(event)); + break; + } + case PLUGIN_WEBFORM_LOAD: { { @@ -142,10 +149,6 @@ boolean Plugin_027(byte function, struct EventStruct *event, String& string) optionValuesMode[2] = 2; addFormSelector(F("Measure range"), F("p027_range"), 3, optionsMode, optionValuesMode, choiceMode); } - { - int Plugin_27_i2c_addresses[4] = { INA219_ADDRESS, INA219_ADDRESS2, INA219_ADDRESS3, INA219_ADDRESS4 }; - addFormSelectorI2C(F("p027_i2c"), 4, Plugin_27_i2c_addresses, Plugin_027_i2c_addr(event)); - } { byte choiceMeasureType = PCONFIG(2); String options[4] = { F("Voltage"), F("Current"), F("Power"), F("Voltage/Current/Power") }; diff --git a/src/_P028_BME280.ino b/src/_P028_BME280.ino index d110eda1c..2ef0dca53 100644 --- a/src/_P028_BME280.ino +++ b/src/_P028_BME280.ino @@ -76,12 +76,15 @@ boolean Plugin_028(byte function, struct EventStruct *event, String& string) break; } - - case PLUGIN_WEBFORM_LOAD: + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: { int Plugin_28_i2c_addresses[2] = { 0x76, 0x77 }; addFormSelectorI2C(F("p028_bme280_i2c"), 2, Plugin_28_i2c_addresses, PCONFIG(0)); + break; + } + case PLUGIN_WEBFORM_LOAD: + { P028_data_struct *P028_data = static_cast(getPluginTaskData(event->TaskIndex)); diff --git a/src/_P030_BMP280.ino b/src/_P030_BMP280.ino index 7ae1d089a..422053025 100644 --- a/src/_P030_BMP280.ino +++ b/src/_P030_BMP280.ino @@ -101,14 +101,19 @@ boolean Plugin_030(byte function, struct EventStruct *event, String& string) strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_030)); break; } - case PLUGIN_WEBFORM_LOAD: + + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: { byte choice = PCONFIG(0); /*String options[2] = { F("0x76 - default settings (SDO Low)"), F("0x77 - alternate settings (SDO HIGH)") };*/ int optionValues[2] = { 0x76, 0x77 }; addFormSelectorI2C(F("p030_bmp280_i2c"), 2, optionValues, choice); addFormNote(F("SDO Low=0x76, High=0x77")); + break; + } + case PLUGIN_WEBFORM_LOAD: + { addFormNumericBox(F("Altitude"), F("p030_bmp280_elev"), PCONFIG(1)); addUnit(F("m")); diff --git a/src/_P032_MS5611.ino b/src/_P032_MS5611.ino index 6a3e8e233..3f5dc0608 100644 --- a/src/_P032_MS5611.ino +++ b/src/_P032_MS5611.ino @@ -70,13 +70,18 @@ boolean Plugin_032(byte function, struct EventStruct *event, String& string) strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_032)); break; } - case PLUGIN_WEBFORM_LOAD: + + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: { byte choice = PCONFIG(0); /*String options[2] = { F("0x77 - default I2C address"), F("0x76 - alternate I2C address") };*/ int optionValues[2] = { 0x77, 0x76 }; addFormSelectorI2C(F("p032_ms5611_i2c"), 2, optionValues, choice); + break; + } + case PLUGIN_WEBFORM_LOAD: + { addFormNumericBox(F("Altitude [m]"), F("p032_ms5611_elev"), PCONFIG(1)); success = true; diff --git a/src/_P045_MPU6050.ino b/src/_P045_MPU6050.ino index b7b8dcc12..4620f6532 100644 --- a/src/_P045_MPU6050.ino +++ b/src/_P045_MPU6050.ino @@ -118,23 +118,26 @@ boolean Plugin_045(byte function, struct EventStruct *event, String& string) break; } - case PLUGIN_WEBFORM_LOAD: + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: { byte choice = PCONFIG(0); // Setup webform for address selection - { - /* - String options[10]; - options[0] = F("0x68 - default settings (ADDR Low)"); - options[1] = F("0x69 - alternate settings (ADDR High)"); - */ - int optionValues[2]; - optionValues[0] = 0x68; - optionValues[1] = 0x69; - addFormSelectorI2C(F("p045_address"), 2, optionValues, choice); - addFormNote(F("ADDR Low=0x68, High=0x69")); - } - choice = PCONFIG(1); + /* + String options[10]; + options[0] = F("0x68 - default settings (ADDR Low)"); + options[1] = F("0x69 - alternate settings (ADDR High)"); + */ + int optionValues[2]; + optionValues[0] = 0x68; + optionValues[1] = 0x69; + addFormSelectorI2C(F("p045_address"), 2, optionValues, choice); + addFormNote(F("ADDR Low=0x68, High=0x69")); + break; + } + + case PLUGIN_WEBFORM_LOAD: + { + byte choice = PCONFIG(1); { String options[10]; options[0] = F("Movement detection"); diff --git a/src/_P047_i2c-soil-moisture-sensor.ino b/src/_P047_i2c-soil-moisture-sensor.ino index 0540cd2ff..6801d200f 100644 --- a/src/_P047_i2c-soil-moisture-sensor.ino +++ b/src/_P047_i2c-soil-moisture-sensor.ino @@ -74,11 +74,16 @@ boolean Plugin_047(byte function, struct EventStruct *event, String& string) break; } + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: + { + addFormTextBox(F("I2C Address (Hex)"), F("p047_i2cSoilMoisture_i2cAddress"), + formatToHex_decimal(PCONFIG(0)), 4); + // FIXME TD-er: Why not using addFormSelectorI2C here? + break; + } + case PLUGIN_WEBFORM_LOAD: { - addFormTextBox(F("I2C Address (Hex)"), F("p047_i2cSoilMoisture_i2cAddress"), - formatToHex_decimal(PCONFIG(0)), 4); - addFormCheckBox(F("Send sensor to sleep"), F("p047_sleep"), PCONFIG(1)); addFormCheckBox(F("Check sensor version") ,F("p047_version"), PCONFIG(2)); diff --git a/src/_P048_Motorshield_v2.ino b/src/_P048_Motorshield_v2.ino index 48037560d..6fb0cbcd8 100644 --- a/src/_P048_Motorshield_v2.ino +++ b/src/_P048_Motorshield_v2.ino @@ -57,11 +57,16 @@ boolean Plugin_048(byte function, struct EventStruct *event, String& string) { break; } - case PLUGIN_WEBFORM_LOAD: { - - addFormTextBox(F("I2C Address (Hex)"), F("p048_adr"), + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: + { + addFormTextBox(F("I2C Address (Hex)"), F("p048_adr"), formatToHex_decimal(PCONFIG(0)), 4); + // FIXME TD-er: Why not using addFormSelectorI2C here? + break; + } + + case PLUGIN_WEBFORM_LOAD: { addFormNumericBox(F("Stepper: steps per revolution"), F("p048_MotorStepsPerRevolution") , PCONFIG(1)); diff --git a/src/_P057_HT16K33_LED.ino b/src/_P057_HT16K33_LED.ino index 5512f38b1..c98c16611 100644 --- a/src/_P057_HT16K33_LED.ino +++ b/src/_P057_HT16K33_LED.ino @@ -102,14 +102,18 @@ boolean Plugin_057(byte function, struct EventStruct *event, String& string) break; } - case PLUGIN_WEBFORM_LOAD: + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: { byte addr = PCONFIG(0); int optionValues[8] = { 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77 }; addFormSelectorI2C(F("i2c_addr"), 8, optionValues, addr); + break; + } + case PLUGIN_WEBFORM_LOAD: + { addFormSubHeader(F("7-Seg. Clock")); int16_t choice = PCONFIG(1); diff --git a/src/_P058_HT16K33_KeyPad.ino b/src/_P058_HT16K33_KeyPad.ino index ad636c54e..047603ffc 100644 --- a/src/_P058_HT16K33_KeyPad.ino +++ b/src/_P058_HT16K33_KeyPad.ino @@ -74,13 +74,17 @@ boolean Plugin_058(byte function, struct EventStruct *event, String& string) break; } - case PLUGIN_WEBFORM_LOAD: + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: { byte addr = PCONFIG(0); int optionValues[8] = { 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77 }; addFormSelectorI2C(F("i2c_addr"), 8, optionValues, addr); + break; + } + case PLUGIN_WEBFORM_LOAD: + { success = true; break; } diff --git a/src/_P060_MCP3221.ino b/src/_P060_MCP3221.ino index f266406f2..fd5e9ec05 100644 --- a/src/_P060_MCP3221.ino +++ b/src/_P060_MCP3221.ino @@ -65,13 +65,17 @@ boolean Plugin_060(byte function, struct EventStruct *event, String& string) break; } - case PLUGIN_WEBFORM_LOAD: + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: { byte addr = PCONFIG(0); int optionValues[8] = { 0x4D, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4E, 0x4F }; addFormSelectorI2C(F("i2c_addr"), 8, optionValues, addr); + break; + } + case PLUGIN_WEBFORM_LOAD: + { addFormCheckBox(F("Oversampling"), F("p060_oversampling"), PCONFIG(1)); addFormSubHeader(F("Two Point Calibration")); diff --git a/src/_P061_KeyPad.ino b/src/_P061_KeyPad.ino index 03170caa1..9e48cedc8 100644 --- a/src/_P061_KeyPad.ino +++ b/src/_P061_KeyPad.ino @@ -96,15 +96,20 @@ boolean Plugin_061(byte function, struct EventStruct *event, String& string) break; } - case PLUGIN_WEBFORM_LOAD: - { + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: + { byte addr = PCONFIG(0); int optionValues[16] = { 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F }; addFormSelectorI2C(F("i2c_addr"), (PCONFIG(1) == 0) ? 8 : 16, optionValues, addr); if (PCONFIG(1) != 0) addFormNote(F("PCF8574 uses address 0x20+; PCF8574A uses address 0x38+")); + break; + } + + case PLUGIN_WEBFORM_LOAD: + { String options[3] = { F("MCP23017 (Matrix 9x8)"), F("PCF8574 (Matrix 5x4)"), F("PCF8574 (Direct 8)") }; addFormSelector(F("Chip (Mode)"), F("chip"), 3, options, NULL, PCONFIG(1)); diff --git a/src/_P062_MPR121_KeyPad.ino b/src/_P062_MPR121_KeyPad.ino index 199e4fa2f..28408e1a0 100644 --- a/src/_P062_MPR121_KeyPad.ino +++ b/src/_P062_MPR121_KeyPad.ino @@ -62,13 +62,17 @@ boolean Plugin_062(byte function, struct EventStruct *event, String& string) break; } - case PLUGIN_WEBFORM_LOAD: + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: { byte addr = PCONFIG(0); int optionValues[4] = { 0x5A, 0x5B, 0x5C, 0x5D }; addFormSelectorI2C(F("i2c_addr"), 4, optionValues, addr); + break; + } + case PLUGIN_WEBFORM_LOAD: + { addFormCheckBox(F("ScanCode"), F("scancode"), PCONFIG(1)); success = true; diff --git a/src/_P064_APDS9960.ino b/src/_P064_APDS9960.ino index 7c78cdb9b..198513543 100644 --- a/src/_P064_APDS9960.ino +++ b/src/_P064_APDS9960.ino @@ -95,13 +95,17 @@ boolean Plugin_064(byte function, struct EventStruct *event, String& string) break; } - case PLUGIN_WEBFORM_LOAD: + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: { byte addr = 0x39; // P064_ADDR; chip has only 1 address int optionValues[1] = { 0x39 }; addFormSelectorI2C(F("i2c_addr"), 1, optionValues, addr); //Only for display I2C address + break; + } + case PLUGIN_WEBFORM_LOAD: + { String optionsPluginMode[2]; optionsPluginMode[0] = F("Gesture/Proximity/Ambient Light Sensor"); optionsPluginMode[1] = F("R/G/B Colors"); diff --git a/src/_P066_VEML6040.ino b/src/_P066_VEML6040.ino index 81e84b053..26b8d3df9 100644 --- a/src/_P066_VEML6040.ino +++ b/src/_P066_VEML6040.ino @@ -63,13 +63,15 @@ boolean Plugin_066(byte function, struct EventStruct *event, String& string) break; } + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: + { + int optionValues[1] = { VEML6040_ADDR }; + addFormSelectorI2C(F("i2c_addr"), 1, optionValues, VEML6040_ADDR); //Only for display I2C address + break; + } + case PLUGIN_WEBFORM_LOAD: { - { - int optionValues[1] = { VEML6040_ADDR }; - addFormSelectorI2C(F("i2c_addr"), 1, optionValues, VEML6040_ADDR); //Only for display I2C address - } - { String optionsMode[6] = { F("40ms (16496)"), F("80ms (8248)"), F("160ms (4124)"), F("320ms (2062)"), F("640ms (1031)"), F("1280ms (515)") }; addFormSelector(F("Integration Time (Max Lux)"), F("itime"), 6, optionsMode, NULL, PCONFIG(1)); diff --git a/src/_P068_SHT3x.ino b/src/_P068_SHT3x.ino index 4e8c2367c..f9a0ffc0b 100644 --- a/src/_P068_SHT3x.ino +++ b/src/_P068_SHT3x.ino @@ -162,11 +162,15 @@ boolean Plugin_068(byte function, struct EventStruct *event, String& string) break; } - case PLUGIN_WEBFORM_LOAD: - { + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: + { int optionValues[2] = { 0x44, 0x45 }; addFormSelectorI2C(F("i2c_addr"), 2, optionValues, PCONFIG(0)); + break; + } + case PLUGIN_WEBFORM_LOAD: + { success = true; break; } diff --git a/src/_P069_LM75A.ino b/src/_P069_LM75A.ino index a14190234..1f379195f 100644 --- a/src/_P069_LM75A.ino +++ b/src/_P069_LM75A.ino @@ -150,11 +150,15 @@ boolean Plugin_069(byte function, struct EventStruct *event, String& string) break; } - case PLUGIN_WEBFORM_LOAD: + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: { int optionValues[8] = { 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F }; addFormSelectorI2C(F("i2c_addr"), 8, optionValues, PCONFIG(0)); + break; + } + case PLUGIN_WEBFORM_LOAD: + { success = true; break; } diff --git a/src/_P074_TSL2591.ino b/src/_P074_TSL2591.ino index f3168e7a4..43d973c8b 100644 --- a/src/_P074_TSL2591.ino +++ b/src/_P074_TSL2591.ino @@ -96,12 +96,15 @@ boolean Plugin_074(byte function, struct EventStruct *event, String &string) { break; } + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: + { + int optionValues[1] = {TSL2591_ADDR}; + addFormSelectorI2C(F("p074_i2c_addr"), 1, optionValues, + TSL2591_ADDR); // Only for display I2C address + break; + } + case PLUGIN_WEBFORM_LOAD: { - { - int optionValues[1] = {TSL2591_ADDR}; - addFormSelectorI2C(F("p074_i2c_addr"), 1, optionValues, - TSL2591_ADDR); // Only for display I2C address - } // P074_data_struct* P074_data = // static_cast(getPluginTaskData(event->TaskIndex)); // if (nullptr != P074_data) { diff --git a/src/_P079_Wemos_Motorshield.ino b/src/_P079_Wemos_Motorshield.ino index 88ad8ae88..4cd013ee0 100644 --- a/src/_P079_Wemos_Motorshield.ino +++ b/src/_P079_Wemos_Motorshield.ino @@ -93,11 +93,15 @@ boolean Plugin_079(byte function, struct EventStruct *event, String& string) break; } - case PLUGIN_WEBFORM_LOAD: { - String i2c_addres_string = formatToHex(PCONFIG(0)); - addFormTextBox(F("I2C Address (Hex)"), F("p079_adr"), i2c_addres_string, 4); - addFormNote(F("Make sure to update the Wemos Motorshield firmware, see wiki")); + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: + { + String i2c_addres_string = formatToHex(PCONFIG(0)); + addFormTextBox(F("I2C Address (Hex)"), F("p079_adr"), i2c_addres_string, 4); + addFormNote(F("Make sure to update the Wemos Motorshield firmware, see wiki")); + break; + } + case PLUGIN_WEBFORM_LOAD: { success = true; break; } diff --git a/src/_P090_CCS811.ino b/src/_P090_CCS811.ino index 969d14e4f..019ad1842 100644 --- a/src/_P090_CCS811.ino +++ b/src/_P090_CCS811.ino @@ -220,15 +220,17 @@ boolean Plugin_090(byte function, struct EventStruct *event, String& string) break; } + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: + { + // I2C address choice + String options[2] = { F("0x5A (ADDR pin is LOW)"), F("0x5B (ADDR pin is HIGH)") }; + int optionValues[2] = { 0x5A, 0x5B }; + addFormSelector(F("I2C Address"), F("p090_i2c_address"), 2, options, optionValues, P090_I2C_ADDR); + break; + } + case PLUGIN_WEBFORM_LOAD: { - { - // I2C address choice - String options[2] = { F("0x5A (ADDR pin is LOW)"), F("0x5B (ADDR pin is HIGH)") }; - int optionValues[2] = { 0x5A, 0x5B }; - addFormSelector(F("I2C Address"), F("p090_i2c_address"), 2, options, optionValues, P090_I2C_ADDR); - } - { // read frequency int frequencyChoice = (int)P090_READ_INTERVAL; diff --git a/src/_P095_ILI9341.ino b/src/_P095_ILI9341.ino index 68c5d2dc2..e91a9a376 100644 --- a/src/_P095_ILI9341.ino +++ b/src/_P095_ILI9341.ino @@ -164,7 +164,7 @@ boolean Plugin_095(byte function, struct EventStruct *event, String& string) case PLUGIN_DEVICE_ADD: { Device[++deviceCount].Number = PLUGIN_ID_095; - Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].Type = DEVICE_TYPE_I2C; // FIXME TD-er: This is no I2C, but looks like SPI. Device[deviceCount].VType = SENSOR_TYPE_NONE; Device[deviceCount].Ports = 0; Device[deviceCount].PullUpOption = false; From 723cad184741ff32ababc227be27107355c8e90b Mon Sep 17 00:00:00 2001 From: Gijs Noorlander Date: Thu, 20 Aug 2020 12:34:11 +0200 Subject: [PATCH 25/28] [Cleanup] Uncrustify all I2C plugins When adding the new webform I2C params function, it struck me how inconsistent the formatting of our code is. So how looks like a good moment to uncrustify all I2C plugins as their code is touched anyway. --- src/_P006_BMP085.ino | 184 ++-- src/_P007_PCF8591.ino | 93 +- src/_P009_MCP.ino | 1349 ++++++++++++----------- src/_P010_BH1750.ino | 165 +-- src/_P011_PME.ino | 420 ++++---- src/_P012_LCD.ino | 504 ++++----- src/_P015_TSL2561.ino | 2 +- src/_P017_PN532.ino | 336 +++--- src/_P019_PCF8574.ino | 1360 +++++++++++++----------- src/_P022_PCA9685.ino | 24 +- src/_P023_OLED.ino | 963 ++++++++--------- src/_P024_MLX90614.ino | 148 +-- src/_P025_ADS1115.ino | 323 +++--- src/_P027_INA219.ino | 445 ++++---- src/_P028_BME280.ino | 1 - src/_P030_BMP280.ino | 333 +++--- src/_P032_MS5611.ino | 265 ++--- src/_P034_DHT12.ino | 166 +-- src/_P036_FrameOLED.ino | 50 +- src/_P045_MPU6050.ino | 511 ++++----- src/_P047_i2c-soil-moisture-sensor.ino | 342 +++--- src/_P048_Motorshield_v2.ino | 462 ++++---- src/_P050_TCS34725.ino | 274 ++--- src/_P051_AM2320.ino | 118 +- src/_P057_HT16K33_LED.ino | 478 +++++---- src/_P058_HT16K33_KeyPad.ino | 154 ++- src/_P060_MCP3221.ino | 195 ++-- src/_P061_KeyPad.ino | 439 ++++---- src/_P062_MPR121_KeyPad.ino | 192 ++-- src/_P064_APDS9960.ino | 502 ++++----- src/_P066_VEML6040.ino | 294 ++--- src/_P068_SHT3x.ino | 320 +++--- src/_P069_LM75A.ino | 86 +- src/_P072_HDC1080.ino | 132 +-- src/_P074_TSL2591.ino | 80 +- src/_P079_Wemos_Motorshield.ino | 345 +++--- src/_P083_SGP30.ino | 10 +- src/_P084_VEML6070.ino | 183 ++-- src/_P090_CCS811.ino | 5 +- src/_P095_ILI9341.ino | 952 +++++++++-------- 40 files changed, 6882 insertions(+), 6323 deletions(-) diff --git a/src/_P006_BMP085.ino b/src/_P006_BMP085.ino index 110698a0f..2d1d37d04 100644 --- a/src/_P006_BMP085.ino +++ b/src/_P006_BMP085.ino @@ -1,7 +1,8 @@ #ifdef USES_P006 -//####################################################################################################### -//######################## Plugin 006 BMP0685 I2C Barometric Pressure Sensor ########################### -//####################################################################################################### + +// ####################################################################################################### +// ######################## Plugin 006 BMP0685 I2C Barometric Pressure Sensor ########################### +// ####################################################################################################### #include "_Plugin_Helper.h" @@ -22,109 +23,111 @@ boolean Plugin_006(byte function, struct EventStruct *event, String& string) switch (function) { case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_006; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].VType = SENSOR_TYPE_TEMP_BARO; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = true; - Device[deviceCount].ValueCount = 2; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].GlobalSyncOption = true; - break; - } + { + Device[++deviceCount].Number = PLUGIN_ID_006; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = SENSOR_TYPE_TEMP_BARO; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 2; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].GlobalSyncOption = true; + break; + } case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_006); - break; - } + { + string = F(PLUGIN_NAME_006); + break; + } case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_006)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_006)); - break; - } + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_006)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_006)); + break; + } case PLUGIN_WEBFORM_LOAD: - { - addFormNumericBox(F("Altitude [m]"), F("_p006_bmp085_elev"), PCONFIG(1)); - success = true; - break; - } + { + addFormNumericBox(F("Altitude [m]"), F("_p006_bmp085_elev"), PCONFIG(1)); + success = true; + break; + } case PLUGIN_WEBFORM_SAVE: - { - PCONFIG(1) = getFormItemInt(F("_p006_bmp085_elev")); - success = true; - break; - } + { + PCONFIG(1) = getFormItemInt(F("_p006_bmp085_elev")); + success = true; + break; + } case PLUGIN_READ: + { + if (!Plugin_006_init) { - if (!Plugin_006_init) - { - if (Plugin_006_bmp085_begin()) - Plugin_006_init = true; + if (Plugin_006_bmp085_begin()) { + Plugin_006_init = true; } - - if (Plugin_006_init) - { - UserVar[event->BaseVarIndex] = Plugin_006_bmp085_readTemperature(); - int elev = PCONFIG(1); - if (elev) - { - UserVar[event->BaseVarIndex + 1] = Plugin_006_pressureElevation((float)Plugin_006_bmp085_readPressure() / 100, elev); - } else { - UserVar[event->BaseVarIndex + 1] = ((float)Plugin_006_bmp085_readPressure()) / 100; - } - String log = F("BMP : Temperature: "); - log += UserVar[event->BaseVarIndex]; - addLog(LOG_LEVEL_INFO, log); - log = F("BMP : Barometric Pressure: "); - log += UserVar[event->BaseVarIndex + 1]; - addLog(LOG_LEVEL_INFO, log); - success = true; - } - break; } + if (Plugin_006_init) + { + UserVar[event->BaseVarIndex] = Plugin_006_bmp085_readTemperature(); + int elev = PCONFIG(1); + + if (elev) + { + UserVar[event->BaseVarIndex + 1] = Plugin_006_pressureElevation((float)Plugin_006_bmp085_readPressure() / 100, elev); + } else { + UserVar[event->BaseVarIndex + 1] = ((float)Plugin_006_bmp085_readPressure()) / 100; + } + String log = F("BMP : Temperature: "); + log += UserVar[event->BaseVarIndex]; + addLog(LOG_LEVEL_INFO, log); + log = F("BMP : Barometric Pressure: "); + log += UserVar[event->BaseVarIndex + 1]; + addLog(LOG_LEVEL_INFO, log); + success = true; + } + break; + } } return success; } #define BMP085_I2CADDR 0x77 #define BMP085_ULTRAHIGHRES 3 -#define BMP085_CAL_AC1 0xAA // R Calibration data (16 bits) -#define BMP085_CAL_AC2 0xAC // R Calibration data (16 bits) -#define BMP085_CAL_AC3 0xAE // R Calibration data (16 bits) -#define BMP085_CAL_AC4 0xB0 // R Calibration data (16 bits) -#define BMP085_CAL_AC5 0xB2 // R Calibration data (16 bits) -#define BMP085_CAL_AC6 0xB4 // R Calibration data (16 bits) -#define BMP085_CAL_B1 0xB6 // R Calibration data (16 bits) -#define BMP085_CAL_B2 0xB8 // R Calibration data (16 bits) -#define BMP085_CAL_MB 0xBA // R Calibration data (16 bits) -#define BMP085_CAL_MC 0xBC // R Calibration data (16 bits) -#define BMP085_CAL_MD 0xBE // R Calibration data (16 bits) +#define BMP085_CAL_AC1 0xAA // R Calibration data (16 bits) +#define BMP085_CAL_AC2 0xAC // R Calibration data (16 bits) +#define BMP085_CAL_AC3 0xAE // R Calibration data (16 bits) +#define BMP085_CAL_AC4 0xB0 // R Calibration data (16 bits) +#define BMP085_CAL_AC5 0xB2 // R Calibration data (16 bits) +#define BMP085_CAL_AC6 0xB4 // R Calibration data (16 bits) +#define BMP085_CAL_B1 0xB6 // R Calibration data (16 bits) +#define BMP085_CAL_B2 0xB8 // R Calibration data (16 bits) +#define BMP085_CAL_MB 0xBA // R Calibration data (16 bits) +#define BMP085_CAL_MC 0xBC // R Calibration data (16 bits) +#define BMP085_CAL_MD 0xBE // R Calibration data (16 bits) #define BMP085_CONTROL 0xF4 #define BMP085_TEMPDATA 0xF6 #define BMP085_PRESSUREDATA 0xF6 #define BMP085_READTEMPCMD 0x2E #define BMP085_READPRESSURECMD 0x34 -uint8_t oversampling = BMP085_ULTRAHIGHRES; -int16_t ac1, ac2, ac3, b1, b2, mb, mc, md; +uint8_t oversampling = BMP085_ULTRAHIGHRES; +int16_t ac1, ac2, ac3, b1, b2, mb, mc, md; uint16_t ac4, ac5, ac6; /*********************************************************************/ boolean Plugin_006_bmp085_begin() + /*********************************************************************/ { - if (I2C_read8_reg(BMP085_I2CADDR, 0xD0) != 0x55) return false; + if (I2C_read8_reg(BMP085_I2CADDR, 0xD0) != 0x55) { return false; } /* read calibration data */ ac1 = I2C_read16_reg(BMP085_I2CADDR, BMP085_CAL_AC1); @@ -141,11 +144,12 @@ boolean Plugin_006_bmp085_begin() mc = I2C_read16_reg(BMP085_I2CADDR, BMP085_CAL_MC); md = I2C_read16_reg(BMP085_I2CADDR, BMP085_CAL_MD); - return(true); + return true; } /*********************************************************************/ uint16_t Plugin_006_bmp085_readRawTemperature(void) + /*********************************************************************/ { I2C_write8_reg(BMP085_I2CADDR, BMP085_CONTROL, BMP085_READTEMPCMD); @@ -155,6 +159,7 @@ uint16_t Plugin_006_bmp085_readRawTemperature(void) /*********************************************************************/ uint32_t Plugin_006_bmp085_readRawPressure(void) + /*********************************************************************/ { uint32_t raw; @@ -163,9 +168,9 @@ uint32_t Plugin_006_bmp085_readRawPressure(void) delay(26); - raw = I2C_read16_reg(BMP085_I2CADDR, BMP085_PRESSUREDATA); + raw = I2C_read16_reg(BMP085_I2CADDR, BMP085_PRESSUREDATA); raw <<= 8; - raw |= I2C_read8_reg(BMP085_I2CADDR, BMP085_PRESSUREDATA + 2); + raw |= I2C_read8_reg(BMP085_I2CADDR, BMP085_PRESSUREDATA + 2); raw >>= (8 - oversampling); return raw; @@ -173,9 +178,10 @@ uint32_t Plugin_006_bmp085_readRawPressure(void) /*********************************************************************/ int32_t Plugin_006_bmp085_readPressure(void) + /*********************************************************************/ { - int32_t UT, UP, B3, B5, B6, X1, X2, X3, p; + int32_t UT, UP, B3, B5, B6, X1, X2, X3, p; uint32_t B4, B7; UT = Plugin_006_bmp085_readRawTemperature(); @@ -188,7 +194,7 @@ int32_t Plugin_006_bmp085_readPressure(void) // do pressure calcs B6 = B5 - 4000; - X1 = ((int32_t)b2 * ( (B6 * B6) >> 12 )) >> 11; + X1 = ((int32_t)b2 * ((B6 * B6) >> 12)) >> 11; X2 = ((int32_t)ac2 * B6) >> 11; X3 = X1 + X2; B3 = ((((int32_t)ac1 * 4 + X3) << oversampling) + 2) / 4; @@ -197,7 +203,7 @@ int32_t Plugin_006_bmp085_readPressure(void) X2 = ((int32_t)b1 * ((B6 * B6) >> 12)) >> 16; X3 = ((X1 + X2) + 2) >> 2; B4 = ((uint32_t)ac4 * (uint32_t)(X3 + 32768)) >> 15; - B7 = ((uint32_t)UP - B3) * (uint32_t)( 50000UL >> oversampling ); + B7 = ((uint32_t)UP - B3) * (uint32_t)(50000UL >> oversampling); if (B7 < 0x80000000) { @@ -217,18 +223,19 @@ int32_t Plugin_006_bmp085_readPressure(void) /*********************************************************************/ float Plugin_006_bmp085_readTemperature(void) + /*********************************************************************/ { - int32_t UT, X1, X2, B5; // following ds convention - float temp; + int32_t UT, X1, X2, B5; // following ds convention + float temp; UT = Plugin_006_bmp085_readRawTemperature(); // step 1 - X1 = (UT - (int32_t)ac6) * ((int32_t)ac5) / pow(2, 15); - X2 = ((int32_t)mc * pow(2, 11)) / (X1 + (int32_t)md); - B5 = X1 + X2; - temp = (B5 + 8) / pow(2, 4); + X1 = (UT - (int32_t)ac6) * ((int32_t)ac5) / pow(2, 15); + X2 = ((int32_t)mc * pow(2, 11)) / (X1 + (int32_t)md); + B5 = X1 + X2; + temp = (B5 + 8) / pow(2, 4); temp /= 10; return temp; @@ -236,7 +243,8 @@ float Plugin_006_bmp085_readTemperature(void) /*********************************************************************/ float Plugin_006_pressureElevation(float atmospheric, int altitude) { -/*********************************************************************/ - return atmospheric / pow(1.0 - (altitude/44330.0), 5.255); + /*********************************************************************/ + return atmospheric / pow(1.0 - (altitude / 44330.0), 5.255); } + #endif // USES_P006 diff --git a/src/_P007_PCF8591.ino b/src/_P007_PCF8591.ino index cf0c42737..7d4bc5c3d 100644 --- a/src/_P007_PCF8591.ino +++ b/src/_P007_PCF8591.ino @@ -1,7 +1,8 @@ #ifdef USES_P007 -//####################################################################################################### -//#################################### Plugin 007: ExtWiredAnalog ####################################### -//####################################################################################################### + +// ####################################################################################################### +// #################################### Plugin 007: ExtWiredAnalog ####################################### +// ####################################################################################################### #include "_Plugin_Helper.h" @@ -19,57 +20,59 @@ boolean Plugin_007(byte function, struct EventStruct *event, String& string) switch (function) { case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_007; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].VType = SENSOR_TYPE_SINGLE; - Device[deviceCount].Ports = 4; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = true; - Device[deviceCount].ValueCount = 1; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].GlobalSyncOption = true; - break; - } + { + Device[++deviceCount].Number = PLUGIN_ID_007; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = SENSOR_TYPE_SINGLE; + Device[deviceCount].Ports = 4; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 1; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].GlobalSyncOption = true; + break; + } case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_007); - break; - } + { + string = F(PLUGIN_NAME_007); + break; + } case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_007)); - break; - } + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_007)); + break; + } case PLUGIN_READ: + { + byte unit = (CONFIG_PORT - 1) / 4; + byte port = CONFIG_PORT - (unit * 4); + uint8_t address = 0x48 + unit; + + // get the current pin value + Wire.beginTransmission(address); + Wire.write(port - 1); + Wire.endTransmission(); + + Wire.requestFrom(address, (uint8_t)0x2); + + if (Wire.available()) { - byte unit = (CONFIG_PORT - 1) / 4; - byte port = CONFIG_PORT - (unit * 4); - uint8_t address = 0x48 + unit; - - // get the current pin value - Wire.beginTransmission(address); - Wire.write(port - 1); - Wire.endTransmission(); - - Wire.requestFrom(address, (uint8_t)0x2); - if (Wire.available()) - { - Wire.read(); // Read older value first (stored in chip) - UserVar[event->BaseVarIndex] = (float)Wire.read(); // now read actual value and store into Nodo var - String log = F("PCF : Analog value: "); - log += UserVar[event->BaseVarIndex]; - addLog(LOG_LEVEL_INFO,log); - success = true; - } - break; + Wire.read(); // Read older value first (stored in chip) + UserVar[event->BaseVarIndex] = (float)Wire.read(); // now read actual value and store into Nodo var + String log = F("PCF : Analog value: "); + log += UserVar[event->BaseVarIndex]; + addLog(LOG_LEVEL_INFO, log); + success = true; } + break; + } } return success; } + #endif // USES_P007 diff --git a/src/_P009_MCP.ino b/src/_P009_MCP.ino index 0742e9e13..378724069 100644 --- a/src/_P009_MCP.ino +++ b/src/_P009_MCP.ino @@ -3,33 +3,33 @@ #include "_Plugin_Helper.h" #include "src/DataStructs/PinMode.h" -//####################################################################################################### -//#################################### Plugin 009: MCP23017 input ####################################### -//####################################################################################################### +// ####################################################################################################### +// #################################### Plugin 009: MCP23017 input ####################################### +// ####################################################################################################### /**************************************************\ -CONFIG -TaskDevicePluginConfig settings: -0: send boot state (true,false) -1: -2: -3: -4: use doubleclick (0,1,2,3) -5: use longpress (0,1,2,3) -6: LP fired (true,false) -7: doubleclick counter (=0,1,2,3) + CONFIG + TaskDevicePluginConfig settings: + 0: send boot state (true,false) + 1: + 2: + 3: + 4: use doubleclick (0,1,2,3) + 5: use longpress (0,1,2,3) + 6: LP fired (true,false) + 7: doubleclick counter (=0,1,2,3) -TaskDevicePluginConfigFloat settings: -0: debounce interval ms -1: doubleclick interval ms -2: longpress interval ms -3: use safebutton (=0,1) + TaskDevicePluginConfigFloat settings: + 0: debounce interval ms + 1: doubleclick interval ms + 2: longpress interval ms + 3: use safebutton (=0,1) -TaskDevicePluginConfigLong settings: -0: clickTime debounce ms -1: clickTime doubleclick ms -2: clickTime longpress ms -3: safebutton counter (=0,1) + TaskDevicePluginConfigLong settings: + 0: clickTime debounce ms + 1: clickTime doubleclick ms + 2: clickTime longpress ms + 3: safebutton counter (=0,1) \**************************************************/ #define PLUGIN_009 @@ -52,700 +52,770 @@ TaskDevicePluginConfigLong settings: boolean Plugin_009(byte function, struct EventStruct *event, String& string) { boolean success = false; - //static int8_t switchstate[TASKS_MAX]; + + // static int8_t switchstate[TASKS_MAX]; switch (function) { - case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_009; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].VType = SENSOR_TYPE_SWITCH; - Device[deviceCount].Ports = 16; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = true; - Device[deviceCount].FormulaOption = false; - Device[deviceCount].ValueCount = 1; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].TimerOptional = true; - Device[deviceCount].GlobalSyncOption = true; - break; - } + { + Device[++deviceCount].Number = PLUGIN_ID_009; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = SENSOR_TYPE_SWITCH; + Device[deviceCount].Ports = 16; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = true; + Device[deviceCount].FormulaOption = false; + Device[deviceCount].ValueCount = 1; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].TimerOptional = true; + Device[deviceCount].GlobalSyncOption = true; + break; + } case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_009); - break; - } + { + string = F(PLUGIN_NAME_009); + break; + } case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_009)); - break; - } + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_009)); + break; + } case PLUGIN_WEBFORM_LOAD: - { - //@giig1967g: set current task value for taking actions after changes - const uint32_t key = createKey(PLUGIN_ID_009,CONFIG_PORT); - if (existPortStatus(key)) { - globalMapPortStatus[key].previousTask = event->TaskIndex; - } + { + // @giig1967g: set current task value for taking actions after changes + const uint32_t key = createKey(PLUGIN_ID_009, CONFIG_PORT); - addFormCheckBox(F("Send Boot state") ,F("p009_boot"), PCONFIG(0)); - - //@giig1967-20181022 - addFormSubHeader(F("Advanced event management")); - - addFormNumericBox(F("De-bounce (ms)"), F("p009_debounce"), round(PCONFIG_FLOAT(0)), 0, 250); - - //set minimum value for doubleclick MIN max speed - if (PCONFIG_FLOAT(1) < PLUGIN_009_DOUBLECLICK_MIN_INTERVAL) - PCONFIG_FLOAT(1) = PLUGIN_009_DOUBLECLICK_MIN_INTERVAL; - - byte choiceDC = PCONFIG(4); - String buttonDC[4]; - buttonDC[0] = F("Disabled"); - buttonDC[1] = F("Active only on LOW (EVENT=3)"); - buttonDC[2] = F("Active only on HIGH (EVENT=3)"); - buttonDC[3] = F("Active on LOW & HIGH (EVENT=3)"); - int buttonDCValues[4] = {PLUGIN_009_DC_DISABLED, PLUGIN_009_DC_LOW, PLUGIN_009_DC_HIGH,PLUGIN_009_DC_BOTH}; - addFormSelector(F("Doubleclick event"), F("p009_dc"), 4, buttonDC, buttonDCValues, choiceDC); - - addFormNumericBox(F("Doubleclick max. interval (ms)"), F("p009_dcmaxinterval"), round(PCONFIG_FLOAT(1)), PLUGIN_009_DOUBLECLICK_MIN_INTERVAL, PLUGIN_009_DOUBLECLICK_MAX_INTERVAL); - - //set minimum value for longpress MIN max speed - if (PCONFIG_FLOAT(2) < PLUGIN_009_LONGPRESS_MIN_INTERVAL) - PCONFIG_FLOAT(2) = PLUGIN_009_LONGPRESS_MIN_INTERVAL; - - byte choiceLP = PCONFIG(5); - String buttonLP[4]; - buttonLP[0] = F("Disabled"); - buttonLP[1] = F("Active only on LOW (EVENT= 10 [NORMAL] or 11 [INVERSED])"); - buttonLP[2] = F("Active only on HIGH (EVENT= 11 [NORMAL] or 10 [INVERSED])"); - buttonLP[3] = F("Active on LOW & HIGH (EVENT= 10 or 11)"); - - int buttonLPValues[4] = {PLUGIN_009_LONGPRESS_DISABLED, PLUGIN_009_LONGPRESS_LOW, PLUGIN_009_LONGPRESS_HIGH,PLUGIN_009_LONGPRESS_BOTH}; - addFormSelector(F("Longpress event"), F("p009_lp"), 4, buttonLP, buttonLPValues, choiceLP); - - addFormNumericBox(F("Longpress min. interval (ms)"), F("p009_lpmininterval"), round(PCONFIG_FLOAT(2)), PLUGIN_009_LONGPRESS_MIN_INTERVAL, PLUGIN_009_LONGPRESS_MAX_INTERVAL); - - addFormCheckBox(F("Use Safe Button (slower)"), F("p009_sb"), round(PCONFIG_FLOAT(3))); - - success = true; - break; + if (existPortStatus(key)) { + globalMapPortStatus[key].previousTask = event->TaskIndex; } + addFormCheckBox(F("Send Boot state"), F("p009_boot"), PCONFIG(0)); + + // @giig1967-20181022 + addFormSubHeader(F("Advanced event management")); + + addFormNumericBox(F("De-bounce (ms)"), F("p009_debounce"), round(PCONFIG_FLOAT(0)), 0, 250); + + // set minimum value for doubleclick MIN max speed + if (PCONFIG_FLOAT(1) < PLUGIN_009_DOUBLECLICK_MIN_INTERVAL) { + PCONFIG_FLOAT(1) = PLUGIN_009_DOUBLECLICK_MIN_INTERVAL; + } + + byte choiceDC = PCONFIG(4); + String buttonDC[4]; + buttonDC[0] = F("Disabled"); + buttonDC[1] = F("Active only on LOW (EVENT=3)"); + buttonDC[2] = F("Active only on HIGH (EVENT=3)"); + buttonDC[3] = F("Active on LOW & HIGH (EVENT=3)"); + int buttonDCValues[4] = { PLUGIN_009_DC_DISABLED, PLUGIN_009_DC_LOW, PLUGIN_009_DC_HIGH, PLUGIN_009_DC_BOTH }; + addFormSelector(F("Doubleclick event"), F("p009_dc"), 4, buttonDC, buttonDCValues, choiceDC); + + addFormNumericBox(F("Doubleclick max. interval (ms)"), + F("p009_dcmaxinterval"), + round(PCONFIG_FLOAT(1)), + PLUGIN_009_DOUBLECLICK_MIN_INTERVAL, + PLUGIN_009_DOUBLECLICK_MAX_INTERVAL); + + // set minimum value for longpress MIN max speed + if (PCONFIG_FLOAT(2) < PLUGIN_009_LONGPRESS_MIN_INTERVAL) { + PCONFIG_FLOAT(2) = PLUGIN_009_LONGPRESS_MIN_INTERVAL; + } + + byte choiceLP = PCONFIG(5); + String buttonLP[4]; + buttonLP[0] = F("Disabled"); + buttonLP[1] = F("Active only on LOW (EVENT= 10 [NORMAL] or 11 [INVERSED])"); + buttonLP[2] = F("Active only on HIGH (EVENT= 11 [NORMAL] or 10 [INVERSED])"); + buttonLP[3] = F("Active on LOW & HIGH (EVENT= 10 or 11)"); + + int buttonLPValues[4] = + { PLUGIN_009_LONGPRESS_DISABLED, PLUGIN_009_LONGPRESS_LOW, PLUGIN_009_LONGPRESS_HIGH, PLUGIN_009_LONGPRESS_BOTH }; + addFormSelector(F("Longpress event"), F("p009_lp"), 4, buttonLP, buttonLPValues, choiceLP); + + addFormNumericBox(F("Longpress min. interval (ms)"), + F("p009_lpmininterval"), + round(PCONFIG_FLOAT(2)), + PLUGIN_009_LONGPRESS_MIN_INTERVAL, + PLUGIN_009_LONGPRESS_MAX_INTERVAL); + + addFormCheckBox(F("Use Safe Button (slower)"), F("p009_sb"), round(PCONFIG_FLOAT(3))); + + success = true; + break; + } + case PLUGIN_WEBFORM_SAVE: - { - PCONFIG(0) = isFormItemChecked(F("p009_boot")); + { + PCONFIG(0) = isFormItemChecked(F("p009_boot")); - //@giig1967-20181022 - PCONFIG_FLOAT(0) = getFormItemInt(F("p009_debounce")); + // @giig1967-20181022 + PCONFIG_FLOAT(0) = getFormItemInt(F("p009_debounce")); - PCONFIG(4) = getFormItemInt(F("p009_dc")); - PCONFIG_FLOAT(1) = getFormItemInt(F("p009_dcmaxinterval")); + PCONFIG(4) = getFormItemInt(F("p009_dc")); + PCONFIG_FLOAT(1) = getFormItemInt(F("p009_dcmaxinterval")); - PCONFIG(5) = getFormItemInt(F("p009_lp")); - PCONFIG_FLOAT(2) = getFormItemInt(F("p009_lpmininterval")); + PCONFIG(5) = getFormItemInt(F("p009_lp")); + PCONFIG_FLOAT(2) = getFormItemInt(F("p009_lpmininterval")); - PCONFIG_FLOAT(3) = isFormItemChecked(F("p009_sb")); + PCONFIG_FLOAT(3) = isFormItemChecked(F("p009_sb")); - //check if a task has been edited and remove task flag from the previous pin - for (std::map::iterator it=globalMapPortStatus.begin(); it!=globalMapPortStatus.end(); ++it) { - if (it->second.previousTask == event->TaskIndex && getPluginFromKey(it->first)==PLUGIN_ID_009) { - globalMapPortStatus[it->first].previousTask = -1; - removeTaskFromPort(it->first); - break; - } + // check if a task has been edited and remove task flag from the previous pin + for (std::map::iterator it = globalMapPortStatus.begin(); it != globalMapPortStatus.end(); ++it) { + if ((it->second.previousTask == event->TaskIndex) && (getPluginFromKey(it->first) == PLUGIN_ID_009)) { + globalMapPortStatus[it->first].previousTask = -1; + removeTaskFromPort(it->first); + break; } - success = true; - break; } + success = true; + break; + } case PLUGIN_INIT: + { + // Turn on Pullup resistor + Plugin_009_Config(CONFIG_PORT, 1); + + // apply INIT only if PIN is in range. Do not start INIT if pin not set in the device page. + if (CONFIG_PORT >= 0) { - // Turn on Pullup resistor - Plugin_009_Config(CONFIG_PORT, 1); + portStatusStruct newStatus; + const uint32_t key = createKey(PLUGIN_ID_009, CONFIG_PORT); - //apply INIT only if PIN is in range. Do not start INIT if pin not set in the device page. - if (CONFIG_PORT >= 0) - { - portStatusStruct newStatus; - const uint32_t key = createKey(PLUGIN_ID_009,CONFIG_PORT); - //Read current status or create empty if it does not exist - newStatus = globalMapPortStatus[key]; + // Read current status or create empty if it does not exist + newStatus = globalMapPortStatus[key]; - // read and store current state to prevent switching at boot time - // "state" could be -1, 0 or 1 - newStatus.state = Plugin_009_Read(CONFIG_PORT); - newStatus.output = newStatus.state; - (newStatus.state == -1) ? newStatus.mode = PIN_MODE_OFFLINE : newStatus.mode = PIN_MODE_INPUT_PULLUP; // @giig1967g: if it is in the device list we assume it's an input pin - newStatus.task++; // add this GPIO/port as a task + // read and store current state to prevent switching at boot time + // "state" could be -1, 0 or 1 + newStatus.state = Plugin_009_Read(CONFIG_PORT); + newStatus.output = newStatus.state; + (newStatus.state == -1) ? newStatus.mode = PIN_MODE_OFFLINE : newStatus.mode = PIN_MODE_INPUT_PULLUP; // @giig1967g: if it is in the + // device list we assume it's + // an input pin + newStatus.task++; // add this GPIO/port as a + // task - // @giig1967g-20181022: set initial UserVar of the switch - if (newStatus.state != -1 && Settings.TaskDevicePin1Inversed[event->TaskIndex]) { - UserVar[event->BaseVarIndex] = !newStatus.state; - } else { - UserVar[event->BaseVarIndex] = newStatus.state; - } - - // if boot state must be send, inverse default state - // this is done to force the trigger in PLUGIN_TEN_PER_SECOND - if (PCONFIG(0)) - newStatus.state = !newStatus.state; - - // @giig1967g-20181022: doubleclick counter = 0 - PCONFIG(7)=0; //doubleclick counter - PCONFIG_LONG(3)=0; //safebutton counter - - // @giig1967g-20181022: used to track if LP has fired - PCONFIG(6)=false; - - // @giig1967g-20181022: store millis for debounce, doubleclick and long press - PCONFIG_LONG(0)=millis(); //debounce timer - PCONFIG_LONG(1)=millis(); //doubleclick timer - PCONFIG_LONG(2)=millis(); //longpress timer - - // @giig1967g-20181022: set minimum value for doubleclick MIN max speed - if (PCONFIG_FLOAT(1) < PLUGIN_009_DOUBLECLICK_MIN_INTERVAL) - PCONFIG_FLOAT(1) = PLUGIN_009_DOUBLECLICK_MIN_INTERVAL; - - // @giig1967g-20181022: set minimum value for longpress MIN max speed - if (PCONFIG_FLOAT(2) < PLUGIN_009_LONGPRESS_MIN_INTERVAL) - PCONFIG_FLOAT(2) = PLUGIN_009_LONGPRESS_MIN_INTERVAL; - - //setPinState(PLUGIN_ID_009, CONFIG_PORT, PIN_MODE_INPUT, switchstate[event->TaskIndex]); - savePortStatus(key,newStatus); + // @giig1967g-20181022: set initial UserVar of the switch + if ((newStatus.state != -1) && Settings.TaskDevicePin1Inversed[event->TaskIndex]) { + UserVar[event->BaseVarIndex] = !newStatus.state; + } else { + UserVar[event->BaseVarIndex] = newStatus.state; } - success = true; - break; + + // if boot state must be send, inverse default state + // this is done to force the trigger in PLUGIN_TEN_PER_SECOND + if (PCONFIG(0)) { + newStatus.state = !newStatus.state; + } + + // @giig1967g-20181022: doubleclick counter = 0 + PCONFIG(7) = 0; // doubleclick counter + PCONFIG_LONG(3) = 0; // safebutton counter + + // @giig1967g-20181022: used to track if LP has fired + PCONFIG(6) = false; + + // @giig1967g-20181022: store millis for debounce, doubleclick and long press + PCONFIG_LONG(0) = millis(); // debounce timer + PCONFIG_LONG(1) = millis(); // doubleclick timer + PCONFIG_LONG(2) = millis(); // longpress timer + + // @giig1967g-20181022: set minimum value for doubleclick MIN max speed + if (PCONFIG_FLOAT(1) < PLUGIN_009_DOUBLECLICK_MIN_INTERVAL) { + PCONFIG_FLOAT(1) = PLUGIN_009_DOUBLECLICK_MIN_INTERVAL; + } + + // @giig1967g-20181022: set minimum value for longpress MIN max speed + if (PCONFIG_FLOAT(2) < PLUGIN_009_LONGPRESS_MIN_INTERVAL) { + PCONFIG_FLOAT(2) = PLUGIN_009_LONGPRESS_MIN_INTERVAL; + } + + // setPinState(PLUGIN_ID_009, CONFIG_PORT, PIN_MODE_INPUT, switchstate[event->TaskIndex]); + savePortStatus(key, newStatus); } -/* - case PLUGIN_UNCONDITIONAL_POLL: - { - // port monitoring, generates an event by rule command 'monitor,pcf,port#' - for (std::map::iterator it=globalMapPortStatus.begin(); it!=globalMapPortStatus.end(); ++it) { - if ((it->second.monitor || it->second.command || it->second.init) && getPluginFromKey(it->first)==PLUGIN_ID_009) { - const uint16_t port = getPortFromKey(it->first); - int8_t state = Plugin_009_Read(port); - if (it->second.state != state || it->second.forceMonitor) { - if (it->second.mode == PIN_MODE_OFFLINE) it->second.mode=PIN_MODE_UNDEFINED; //changed from offline to online - if (state == -1) it->second.mode=PIN_MODE_OFFLINE; //changed from online to offline - if (!it->second.task) it->second.state = state; //do not update state if task flag=1 otherwise it will not be picked up by 10xSEC function - if (it->second.monitor) { - it->second.forceMonitor=0; //reset flag - String eventString = F("MCP#"); - eventString += port; - eventString += '='; - eventString += state; - rulesProcessing(eventString); + success = true; + break; + } + + /* + case PLUGIN_UNCONDITIONAL_POLL: + { + // port monitoring, generates an event by rule command 'monitor,pcf,port#' + for (std::map::iterator it=globalMapPortStatus.begin(); it!=globalMapPortStatus.end(); ++it) { + if ((it->second.monitor || it->second.command || it->second.init) && getPluginFromKey(it->first)==PLUGIN_ID_009) { + const uint16_t port = getPortFromKey(it->first); + int8_t state = Plugin_009_Read(port); + if (it->second.state != state || it->second.forceMonitor) { + if (it->second.mode == PIN_MODE_OFFLINE) it->second.mode=PIN_MODE_UNDEFINED; //changed from offline to online + if (state == -1) it->second.mode=PIN_MODE_OFFLINE; //changed from online to offline + if (!it->second.task) it->second.state = state; //do not update state if task flag=1 otherwise it will not be picked up + by 10xSEC function + if (it->second.monitor) { + it->second.forceMonitor=0; //reset flag + String eventString = F("MCP#"); + eventString += port; + eventString += '='; + eventString += state; + rulesProcessing(eventString); + } + } } } + break; } - } - break; - } -*/ - case PLUGIN_MONITOR: - { - // port monitoring, generates an event by rule command 'monitor,gpio,port#' - const uint32_t key = createKey(PLUGIN_ID_009,event->Par1); - const portStatusStruct currentStatus = globalMapPortStatus[key]; + */ + case PLUGIN_MONITOR: + { + // port monitoring, generates an event by rule command 'monitor,gpio,port#' + const uint32_t key = createKey(PLUGIN_ID_009, event->Par1); + const portStatusStruct currentStatus = globalMapPortStatus[key]; - //if (currentStatus.monitor || currentStatus.command || currentStatus.init) { - const int8_t state = Plugin_009_Read(event->Par1); - if (currentStatus.state != state || currentStatus.forceMonitor) { - if (!currentStatus.task) globalMapPortStatus[key].state = state; //do not update state if task flag=1 otherwise it will not be picked up by 10xSEC function - if (currentStatus.monitor) { - globalMapPortStatus[key].forceMonitor=0; //reset flag - String eventString = F("MCP#"); - eventString += event->Par1; - eventString += '='; - eventString += state; - rulesProcessing(eventString); - } - } - //} + // if (currentStatus.monitor || currentStatus.command || currentStatus.init) { + const int8_t state = Plugin_009_Read(event->Par1); - break; + if ((currentStatus.state != state) || currentStatus.forceMonitor) { + if (!currentStatus.task) { globalMapPortStatus[key].state = state; // do not update state if task flag=1 otherwise it will not be + // picked up by 10xSEC function } + if (currentStatus.monitor) { + globalMapPortStatus[key].forceMonitor = 0; // reset flag + String eventString = F("MCP#"); + eventString += event->Par1; + eventString += '='; + eventString += state; + rulesProcessing(eventString); + } + } + + // } + + break; + } + case PLUGIN_TEN_PER_SECOND: - { - const int8_t state = Plugin_009_Read(CONFIG_PORT); - /**************************************************************************\ - 20181022 - @giig1967g: new doubleclick logic is: - if there is a 'state' change, check debounce period. - Then if doubleclick interval exceeded, reset PCONFIG(7) to 0 - PCONFIG(7) contains the current status for doubleclick: - 0: start counting - 1: 1st click - 2: 2nd click - 3: 3rd click = doubleclick event if inside interval (calculated as: '3rd click time' minus '1st click time') + { + const int8_t state = Plugin_009_Read(CONFIG_PORT); - Returned EVENT value is = 3 always for doubleclick - In rules this can be checked: - on Button#State=3 do //will fire if doubleclick - \**************************************************************************/ - portStatusStruct currentStatus; - const uint32_t key = createKey(PLUGIN_ID_009,CONFIG_PORT); - //WARNING operator [],creates an entry in map if key doesn't exist: - currentStatus = globalMapPortStatus[key]; + /**************************************************************************\ + 20181022 - @giig1967g: new doubleclick logic is: + if there is a 'state' change, check debounce period. + Then if doubleclick interval exceeded, reset PCONFIG(7) to 0 + PCONFIG(7) contains the current status for doubleclick: + 0: start counting + 1: 1st click + 2: 2nd click + 3: 3rd click = doubleclick event if inside interval (calculated as: '3rd click time' minus '1st click time') - //Bug fixed: avoid 10xSEC in case of a non-fully configured device (no port defined yet) - if (state != -1 && CONFIG_PORT>=0) { + Returned EVENT value is = 3 always for doubleclick + In rules this can be checked: + on Button#State=3 do //will fire if doubleclick + \**************************************************************************/ + portStatusStruct currentStatus; + const uint32_t key = createKey(PLUGIN_ID_009, CONFIG_PORT); - //CASE 1: using SafeButton, so wait 1 more 100ms cycle to acknowledge the status change - if (round(PCONFIG_FLOAT(3)) && state != currentStatus.state && PCONFIG_LONG(3)==0) + // WARNING operator [],creates an entry in map if key doesn't exist: + currentStatus = globalMapPortStatus[key]; + + // Bug fixed: avoid 10xSEC in case of a non-fully configured device (no port defined yet) + if ((state != -1) && (CONFIG_PORT >= 0)) { + // CASE 1: using SafeButton, so wait 1 more 100ms cycle to acknowledge the status change + if (round(PCONFIG_FLOAT(3)) && (state != currentStatus.state) && (PCONFIG_LONG(3) == 0)) + { + addLog(LOG_LEVEL_DEBUG, F("MCP :SafeButton 1st click.")) + PCONFIG_LONG(3) = 1; + } + + // CASE 2: not using SafeButton, or already waited 1 more 100ms cycle, so proceed. + else if ((state != currentStatus.state) || currentStatus.forceEvent) + { + // Reset forceEvent + currentStatus.forceEvent = 0; + + // Reset SafeButton counter + PCONFIG_LONG(3) = 0; + + // @giig1967g20181022: reset timer for long press + PCONFIG_LONG(2) = millis(); + PCONFIG(6) = false; + + const unsigned long debounceTime = timePassedSince(PCONFIG_LONG(0)); + + if (debounceTime >= (unsigned long)lround(PCONFIG_FLOAT(0))) // de-bounce check { - addLog(LOG_LEVEL_DEBUG,F("MCP :SafeButton 1st click.")) - PCONFIG_LONG(3) = 1; - } - //CASE 2: not using SafeButton, or already waited 1 more 100ms cycle, so proceed. - else if (state != currentStatus.state || currentStatus.forceEvent) - { - //Reset forceEvent - currentStatus.forceEvent = 0; + const unsigned long deltaDC = timePassedSince(PCONFIG_LONG(1)); - // Reset SafeButton counter - PCONFIG_LONG(3) = 0; - - //@giig1967g20181022: reset timer for long press - PCONFIG_LONG(2)=millis(); - PCONFIG(6) = false; - - const unsigned long debounceTime = timePassedSince(PCONFIG_LONG(0)); - if (debounceTime >= (unsigned long)lround(PCONFIG_FLOAT(0))) //de-bounce check + if ((deltaDC >= (unsigned long)lround(PCONFIG_FLOAT(1))) || + (PCONFIG(7) == 3)) { - const unsigned long deltaDC = timePassedSince(PCONFIG_LONG(1)); - if ((deltaDC >= (unsigned long)lround(PCONFIG_FLOAT(1))) || - PCONFIG(7)==3) - { - //reset timer for doubleclick - PCONFIG(7)=0; - PCONFIG_LONG(1)=millis(); - } + // reset timer for doubleclick + PCONFIG(7) = 0; + PCONFIG_LONG(1) = millis(); + } -//just to simplify the reading of the code + // just to simplify the reading of the code #define COUNTER PCONFIG(7) #define DC PCONFIG(4) - //check settings for doubleclick according to the settings - if ( COUNTER!=0 || ( COUNTER==0 && (DC==3 || (DC==1 && state==0) || (DC==2 && state==1))) ) - PCONFIG(7)++; + // check settings for doubleclick according to the settings + if ((COUNTER != 0) || ((COUNTER == 0) && ((DC == 3) || ((DC == 1) && (state == 0)) || ((DC == 2) && (state == 1))))) { + PCONFIG(7)++; + } #undef DC #undef COUNTER - //switchstate[event->TaskIndex] = state; - if (currentStatus.mode == PIN_MODE_OFFLINE || currentStatus.mode == PIN_MODE_UNDEFINED) currentStatus.mode = PIN_MODE_INPUT_PULLUP; //changed from offline to online - currentStatus.state = state; - - byte output_value; - //boolean sendState = switchstate[event->TaskIndex]; - boolean sendState = currentStatus.state; - - if (Settings.TaskDevicePin1Inversed[event->TaskIndex]) - sendState = !sendState; - - if (PCONFIG(7)==3 && PCONFIG(4)>0) - { - output_value = 3; //double click - } else { - output_value = sendState ? 1 : 0; //single click - } - - UserVar[event->BaseVarIndex] = output_value; - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("MCP : Port="); - log += CONFIG_PORT; - log += F(" State="); - log += state; - log += output_value==3 ? F(" Doubleclick=") : F(" Output value="); - log += output_value; - addLog(LOG_LEVEL_INFO, log); - } - event->sensorType = SENSOR_TYPE_SWITCH; - sendData(event); - - //reset Userdata so it displays the correct state value in the web page - UserVar[event->BaseVarIndex] = sendState ? 1 : 0; - - PCONFIG_LONG(0) = millis(); + // switchstate[event->TaskIndex] = state; + if ((currentStatus.mode == PIN_MODE_OFFLINE) || + (currentStatus.mode == PIN_MODE_UNDEFINED)) { currentStatus.mode = PIN_MODE_INPUT_PULLUP; // changed from offline to online } - savePortStatus(key,currentStatus); - } + currentStatus.state = state; -//just to simplify the reading of the code + byte output_value; + + // boolean sendState = switchstate[event->TaskIndex]; + boolean sendState = currentStatus.state; + + if (Settings.TaskDevicePin1Inversed[event->TaskIndex]) { + sendState = !sendState; + } + + if ((PCONFIG(7) == 3) && (PCONFIG(4) > 0)) + { + output_value = 3; // double click + } else { + output_value = sendState ? 1 : 0; // single click + } + + UserVar[event->BaseVarIndex] = output_value; + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = F("MCP : Port="); + log += CONFIG_PORT; + log += F(" State="); + log += state; + log += output_value == 3 ? F(" Doubleclick=") : F(" Output value="); + log += output_value; + addLog(LOG_LEVEL_INFO, log); + } + event->sensorType = SENSOR_TYPE_SWITCH; + sendData(event); + + // reset Userdata so it displays the correct state value in the web page + UserVar[event->BaseVarIndex] = sendState ? 1 : 0; + + PCONFIG_LONG(0) = millis(); + } + savePortStatus(key, currentStatus); + } + + // just to simplify the reading of the code #define LP PCONFIG(5) #define FIRED PCONFIG(6) - //check if LP is enabled and if LP has not fired yet - else if (!FIRED && (LP==3 ||(LP==1 && state==0)||(LP==2 && state==1) ) ) { - + // check if LP is enabled and if LP has not fired yet + else if (!FIRED && ((LP == 3) || ((LP == 1) && (state == 0)) || ((LP == 2) && (state == 1)))) { #undef LP #undef FIRED - /**************************************************************************\ - 20181022 - @giig1967g: new longpress logic is: - if there is no 'state' change, check if longpress interval reached - When reached send longpress event. - Returned Event value = state + 10 - So if state = 0 => EVENT longpress = 10 - if state = 1 => EVENT longpress = 11 - So we can trigger longpress for high or low contact + /**************************************************************************\ + 20181022 - @giig1967g: new longpress logic is: + if there is no 'state' change, check if longpress interval reached + When reached send longpress event. + Returned Event value = state + 10 + So if state = 0 => EVENT longpress = 10 + if state = 1 => EVENT longpress = 11 + So we can trigger longpress for high or low contact - In rules this can be checked: - on Button#State=10 do //will fire if longpress when state = 0 - on Button#State=11 do //will fire if longpress when state = 1 - \**************************************************************************/ + In rules this can be checked: + on Button#State=10 do //will fire if longpress when state = 0 + on Button#State=11 do //will fire if longpress when state = 1 + \**************************************************************************/ + + // Reset SafeButton counter + PCONFIG_LONG(3) = 0; + + const unsigned long deltaLP = timePassedSince(PCONFIG_LONG(2)); + + if (deltaLP >= (unsigned long)lround(PCONFIG_FLOAT(2))) + { + byte output_value; + PCONFIG(6) = true; // fired = true + + boolean sendState = state; + + if (Settings.TaskDevicePin1Inversed[event->TaskIndex]) { + sendState = !sendState; + } + + output_value = sendState ? 1 : 0; + output_value = output_value + 10; + + UserVar[event->BaseVarIndex] = output_value; + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = F("MCP : LongPress: Port="); + log += CONFIG_PORT; + log += F(" State="); + log += state ? '1' : '0'; + log += F(" Output value="); + log += output_value; + addLog(LOG_LEVEL_INFO, log); + } + sendData(event); + + // reset Userdata so it displays the correct state value in the web page + UserVar[event->BaseVarIndex] = sendState ? 1 : 0; + } + } else { + if (PCONFIG_LONG(3) == 1) { // Safe Button detected. Send EVENT value = 4 // Reset SafeButton counter PCONFIG_LONG(3) = 0; - const unsigned long deltaLP = timePassedSince(PCONFIG_LONG(2)); - if (deltaLP >= (unsigned long)lround(PCONFIG_FLOAT(2))) - { - byte output_value; - PCONFIG(6) = true; //fired = true + // Create EVENT with value = 4 for SafeButton false positive detection + const int tempUserVar = round(UserVar[event->BaseVarIndex]); + UserVar[event->BaseVarIndex] = 4; - boolean sendState = state; - if (Settings.TaskDevicePin1Inversed[event->TaskIndex]) - sendState = !sendState; - - output_value = sendState ? 1 : 0; - output_value = output_value + 10; - - UserVar[event->BaseVarIndex] = output_value; - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("MCP : LongPress: Port="); - log += CONFIG_PORT; - log += F(" State="); - log += state ? '1' : '0'; - log += F(" Output value="); - log += output_value; - addLog(LOG_LEVEL_INFO, log); - } - sendData(event); - - //reset Userdata so it displays the correct state value in the web page - UserVar[event->BaseVarIndex] = sendState ? 1 : 0; + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = F("MCP : SafeButton: false positive detected. GPIO= "); + log += CONFIG_PIN1; + log += F(" State="); + log += tempUserVar; + addLog(LOG_LEVEL_INFO, log); } - } else { - if (PCONFIG_LONG(3)==1) { //Safe Button detected. Send EVENT value = 4 - // Reset SafeButton counter - PCONFIG_LONG(3) = 0; + sendData(event); - //Create EVENT with value = 4 for SafeButton false positive detection - const int tempUserVar = round(UserVar[event->BaseVarIndex]); - UserVar[event->BaseVarIndex] = 4; - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("MCP : SafeButton: false positive detected. GPIO= "); - log += CONFIG_PIN1; - log += F(" State="); - log += tempUserVar; - addLog(LOG_LEVEL_INFO, log); - } - sendData(event); - - //reset Userdata so it displays the correct state value in the web page - UserVar[event->BaseVarIndex] = tempUserVar; - } + // reset Userdata so it displays the correct state value in the web page + UserVar[event->BaseVarIndex] = tempUserVar; } - } else if (state != currentStatus.state && state == -1) { - //set UserVar and switchState = -1 and send EVENT to notify user - UserVar[event->BaseVarIndex] = state; - currentStatus.mode = PIN_MODE_OFFLINE; - //switchstate[event->TaskIndex] = state; - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("MCP : Port="); - log += CONFIG_PORT; - log += F(" is offline (EVENT= -1)"); - addLog(LOG_LEVEL_INFO, log); - } - sendData(event); - savePortStatus(key,currentStatus); } - success = true; - break; - } + } else if ((state != currentStatus.state) && (state == -1)) { + // set UserVar and switchState = -1 and send EVENT to notify user + UserVar[event->BaseVarIndex] = state; + currentStatus.mode = PIN_MODE_OFFLINE; - //giig1967g: Added EXIT function - case PLUGIN_EXIT: - { - removeTaskFromPort(createKey(PLUGIN_ID_009,CONFIG_PORT)); - break; - } - - case PLUGIN_READ: - { - // We do not actually read the pin state as this is already done 10x/second - // Instead we just send the last known state stored in Uservar + // switchstate[event->TaskIndex] = state; if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("MCP : Port="); + String log = F("MCP : Port="); log += CONFIG_PORT; - log += F(" State="); - log += UserVar[event->BaseVarIndex]; + log += F(" is offline (EVENT= -1)"); addLog(LOG_LEVEL_INFO, log); } - success = true; - break; + sendData(event); + savePortStatus(key, currentStatus); } + success = true; + break; + } + + // giig1967g: Added EXIT function + case PLUGIN_EXIT: + { + removeTaskFromPort(createKey(PLUGIN_ID_009, CONFIG_PORT)); + break; + } + + case PLUGIN_READ: + { + // We do not actually read the pin state as this is already done 10x/second + // Instead we just send the last known state stored in Uservar + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = F("MCP : Port="); + log += CONFIG_PORT; + log += F(" State="); + log += UserVar[event->BaseVarIndex]; + addLog(LOG_LEVEL_INFO, log); + } + success = true; + break; + } case PLUGIN_REQUEST: - { - //parseString(string, 1) = device - //parseString(string, 2) = command - //parseString(string, 3) = gpio number + { + // parseString(string, 1) = device + // parseString(string, 2) = command + // parseString(string, 3) = gpio number - // returns pin value using syntax: [plugin#mcpgpio#pinstate#xx] - if (string.length()>=16 && string.substring(0,16).equalsIgnoreCase(F("mcpgpio,pinstate"))) - { - int par1; - if (validIntFromString(parseString(string, 3), par1)) { - string = Plugin_009_Read(par1); - } - success = true; + // returns pin value using syntax: [plugin#mcpgpio#pinstate#xx] + if ((string.length() >= 16) && string.substring(0, 16).equalsIgnoreCase(F("mcpgpio,pinstate"))) + { + int par1; + + if (validIntFromString(parseString(string, 3), par1)) { + string = Plugin_009_Read(par1); } - break; + success = true; } + break; + } case PLUGIN_WRITE: + { + String log = ""; + String command = parseString(string, 1); + + if (command == F("mcpgpio")) { - String log = ""; - String command = parseString(string, 1); + success = true; - if (command == F("mcpgpio")) + if ((event->Par1 > 0) && (event->Par1 <= 128)) { - success = true; - if (event->Par1 > 0 && event->Par1 <= 128) - { - portStatusStruct tempStatus; - const uint32_t key = createKey(PLUGIN_ID_009,event->Par1); - // WARNING: operator [] creates an entry in the map if key does not exist - // So the next command should be part of each command: - tempStatus = globalMapPortStatus[key]; - - const int8_t currentState = Plugin_009_Read(event->Par1); - - if (currentState == -1) { - tempStatus.mode=PIN_MODE_OFFLINE; - tempStatus.state=-1; - log = String(F("MCP : GPIO ")) + String(event->Par1) + String(F(" is offline (-1). Cannot set value.")); - } else if (event->Par2 == 2) { //INPUT - // PCF8574 specific: only can read 0/low state, so we must send 1 - //setPinState(PLUGIN_ID_019, event->Par1, PIN_MODE_INPUT, 1); - tempStatus.mode=PIN_MODE_INPUT_PULLUP; - tempStatus.state = currentState; - Plugin_009_Write(event->Par1,1); - log = String(F("MCP : GPIO INPUT ")) + String(event->Par1) + String(F(" Set to 1")); - } else { // OUTPUT - //setPinState(PLUGIN_ID_019, event->Par1, PIN_MODE_OUTPUT, event->Par2); - Plugin_009_Write(event->Par1, event->Par2); - tempStatus.mode=PIN_MODE_OUTPUT; - tempStatus.state=event->Par2; - log = String(F("MCP : GPIO OUTPUT ")) + String(event->Par1) + String(F(" Set to ")) + String(event->Par2); - } - tempStatus.command=1; //set to 1 in order to display the status in the PinStatus page - tempStatus.forceEvent=1; - if (tempStatus.monitor) tempStatus.forceMonitor=1; //set to 1 in order to force an EVENT in case monitor is requested - - savePortStatus(key,tempStatus); - addLog(LOG_LEVEL_INFO, log); - SendStatusOnlyIfNeeded(event->Source, SEARCH_PIN_STATE, key, log, 0); - //SendStatus(event->Source, getPinStateJSON(SEARCH_PIN_STATE, PLUGIN_ID_019, event->Par1, log, 0)); - } - } else if (command == F("mcpgpiotoggle")) { - success = true; - if (event->Par1 > 0 && event->Par1 <= 128) - { - portStatusStruct tempStatus; - const uint32_t key = createKey(PLUGIN_ID_009,event->Par1); - // WARNING: operator [] creates an entry in the map if key does not exist - // So the next command should be part of each command: - tempStatus = globalMapPortStatus[key]; - const int8_t currentState = Plugin_009_Read(event->Par1); - bool needToSave = false; - - if (currentState == -1) { - tempStatus.mode=PIN_MODE_OFFLINE; - tempStatus.state=-1; - log = String(F("MCP : GPIO ")) + String(event->Par1) + String(F(" is offline (-1). Cannot set value.")); - needToSave = true; - } else if (tempStatus.mode == PIN_MODE_OUTPUT || tempStatus.mode == PIN_MODE_UNDEFINED) { //toggle only output pins - tempStatus.state = !currentState; //toggle current state value - tempStatus.mode = PIN_MODE_OUTPUT; - Plugin_009_Write(event->Par1, tempStatus.state); - log = String(F("MCP : Toggle GPIO ")) + String(event->Par1) + String(F(" Set to ")) + String(tempStatus.state); - needToSave = true; - } - if (needToSave) { - tempStatus.command=1; //set to 1 in order to display the status in the PinStatus page - tempStatus.forceEvent=1; - if (tempStatus.monitor) tempStatus.forceMonitor=1; //set to 1 in order to force an EVENT in case monitor is requested - - //setPinState(PLUGIN_ID_019, event->Par1, PIN_MODE_OUTPUT, !currentState); - savePortStatus(key,tempStatus); - addLog(LOG_LEVEL_INFO, log); - //SendStatus(event->Source, getPinStateJSON(SEARCH_PIN_STATE, PLUGIN_ID_019, event->Par1, log, 0)); - SendStatusOnlyIfNeeded(event->Source, SEARCH_PIN_STATE, key, log, 0); - } - } - } else if (command == F("mcppulse")) { - success = true; - if (event->Par1 > 0 && event->Par1 <= 128) - { - portStatusStruct tempStatus; - const uint32_t key = createKey(PLUGIN_ID_009,event->Par1); - // WARNING: operator [] creates an entry in the map if key does not exist - // So the next command should be part of each command: - tempStatus = globalMapPortStatus[key]; - - //setPinState(PLUGIN_ID_019, event->Par1, PIN_MODE_OUTPUT, event->Par2); - Plugin_009_Write(event->Par1, event->Par2); - delay(event->Par3); - Plugin_009_Write(event->Par1, !event->Par2); - //setPinState(PLUGIN_ID_019, event->Par1, PIN_MODE_OUTPUT, !event->Par2); - - tempStatus.mode = PIN_MODE_OUTPUT; - tempStatus.state = event->Par2; - tempStatus.command=1; //set to 1 in order to display the status in the PinStatus page - savePortStatus(key,tempStatus); - - log = String(F("MCP : GPIO ")) + String(event->Par1) + String(F(" Pulsed for ")) + String(event->Par3) + String(F(" mS")); - addLog(LOG_LEVEL_INFO, log); - //SendStatus(event->Source, getPinStateJSON(SEARCH_PIN_STATE, PLUGIN_ID_019, event->Par1, log, 0)); - SendStatusOnlyIfNeeded(event->Source, SEARCH_PIN_STATE, key, log, 0); - } - } else if (command == F("mcplongpulse")) { - success = true; - if (event->Par1 > 0 && event->Par1 <= 128) - { - portStatusStruct tempStatus; - const uint32_t key = createKey(PLUGIN_ID_009,event->Par1); - // WARNING: operator [] creates an entry in the map if key does not exist - // So the next command should be part of each command: - tempStatus = globalMapPortStatus[key]; - - //setPinState(PLUGIN_ID_019, event->Par1, PIN_MODE_OUTPUT, event->Par2); - Plugin_009_Write(event->Par1, event->Par2); - - tempStatus.mode = PIN_MODE_OUTPUT; - tempStatus.state = event->Par2; - tempStatus.command=1; //set to 1 in order to display the status in the PinStatus page - (tempStatus.monitor) ? tempStatus.forceMonitor = 1 : tempStatus.forceMonitor = 0; - savePortStatus(key,tempStatus); - - //Scheduler.setPluginTaskTimer(event->Par3 * 1000, event->TaskIndex, event->Par1, !event->Par2); - Scheduler.setPluginTimer(event->Par3 * 1000, PLUGIN_ID_009, event->Par1, !event->Par2); - - log = String(F("MCP : GPIO ")) + String(event->Par1) + String(F(" Pulse set for ")) + String(event->Par3) + String(F(" S")); - addLog(LOG_LEVEL_INFO, log); - //SendStatus(event->Source, getPinStateJSON(SEARCH_PIN_STATE, PLUGIN_ID_019, event->Par1, log, 0)); - SendStatusOnlyIfNeeded(event->Source, SEARCH_PIN_STATE, key, log, 0); - } - } else if (command == F("status")) { - if (parseString(string, 2) == F("mcp")) - { - success = true; - const uint32_t key = createKey(PLUGIN_ID_009,event->Par2); //WARNING: 'status' uses Par2 instead of Par1 - - if (existPortStatus(key)) - SendStatusOnlyIfNeeded(event->Source, SEARCH_PIN_STATE, key, dummyString, 0); - else - { - const int8_t state = Plugin_009_Read(event->Par2); // report as input - if (state != -1) - SendStatusOnlyIfNeeded(event->Source, NO_SEARCH_PIN_STATE, key, dummyString, state); - } - } - } else if (command == F("monitor")) { - if (parseString(string, 2) == F("mcp")) - { - success = true; - const uint32_t key = createKey(PLUGIN_ID_009,event->Par2); //WARNING: 'monitor' uses Par2 instead of Par1 - - addMonitorToPort(key); - //giig1967g: Comment next line to receive an EVENT just after calling the monitor command - globalMapPortStatus[key].state = Plugin_009_Read(event->Par2); //set initial value to avoid an event just after calling the command - - log = String(F("MCP : GPIO ")) + String(event->Par2) + String(F(" added to monitor list.")); - addLog(LOG_LEVEL_INFO, log); - SendStatusOnlyIfNeeded(event->Source, SEARCH_PIN_STATE, key, dummyString, 0); - } - } else if (command == F("unmonitor")) { - if (parseString(string, 2) == F("mcp")) - { - success = true; - const uint32_t key = createKey(PLUGIN_ID_009,event->Par2); //WARNING: 'monitor' uses Par2 instead of Par1 - SendStatusOnlyIfNeeded(event->Source, SEARCH_PIN_STATE, key, dummyString, 0); - - removeMonitorFromPort(key); - log = String(F("MCP : GPIO ")) + String(event->Par2) + String(F(" removed from monitor list.")); - addLog(LOG_LEVEL_INFO, log); - } - } - break; - } - - case PLUGIN_TIMER_IN: - { - Plugin_009_Write(event->Par1, event->Par2); - //setPinState(PLUGIN_ID_009, event->Par1, PIN_MODE_OUTPUT, event->Par2); - portStatusStruct tempStatus; - // WARNING: operator [] creates an entry in the map if key does not exist - const uint32_t key = createKey(PLUGIN_ID_009,event->Par1); - tempStatus = globalMapPortStatus[key]; - - tempStatus.state = event->Par2; - tempStatus.mode = PIN_MODE_OUTPUT; - (tempStatus.monitor) ? tempStatus.forceMonitor = 1 : tempStatus.forceMonitor = 0; //added to send event for longpulse command - savePortStatus(key,tempStatus); - break; - } - - case PLUGIN_ONLY_TIMER_IN: - { - Plugin_009_Write(event->Par1, event->Par2); - //setPinState(PLUGIN_ID_009, event->Par1, PIN_MODE_OUTPUT, event->Par2); portStatusStruct tempStatus; + const uint32_t key = createKey(PLUGIN_ID_009, event->Par1); + // WARNING: operator [] creates an entry in the map if key does not exist - const uint32_t key = createKey(PLUGIN_ID_009,event->Par1); + // So the next command should be part of each command: tempStatus = globalMapPortStatus[key]; - tempStatus.state = event->Par2; - tempStatus.mode = PIN_MODE_OUTPUT; - (tempStatus.monitor) ? tempStatus.forceMonitor = 1 : tempStatus.forceMonitor = 0; //added to send event for longpulse command - savePortStatus(key,tempStatus); - break; + const int8_t currentState = Plugin_009_Read(event->Par1); + + if (currentState == -1) { + tempStatus.mode = PIN_MODE_OFFLINE; + tempStatus.state = -1; + log = String(F("MCP : GPIO ")) + String(event->Par1) + String(F(" is offline (-1). Cannot set value.")); + } else if (event->Par2 == 2) { // INPUT + // PCF8574 specific: only can read 0/low state, so we must send 1 + // setPinState(PLUGIN_ID_019, event->Par1, PIN_MODE_INPUT, 1); + tempStatus.mode = PIN_MODE_INPUT_PULLUP; + tempStatus.state = currentState; + Plugin_009_Write(event->Par1, 1); + log = String(F("MCP : GPIO INPUT ")) + String(event->Par1) + String(F(" Set to 1")); + } else { // OUTPUT + // setPinState(PLUGIN_ID_019, event->Par1, PIN_MODE_OUTPUT, event->Par2); + Plugin_009_Write(event->Par1, event->Par2); + tempStatus.mode = PIN_MODE_OUTPUT; + tempStatus.state = event->Par2; + log = String(F("MCP : GPIO OUTPUT ")) + String(event->Par1) + String(F(" Set to ")) + String(event->Par2); + } + tempStatus.command = 1; // set to 1 in order to display the status in the PinStatus page + tempStatus.forceEvent = 1; + + if (tempStatus.monitor) { tempStatus.forceMonitor = 1; // set to 1 in order to force an EVENT in case monitor is requested + } + savePortStatus(key, tempStatus); + addLog(LOG_LEVEL_INFO, log); + SendStatusOnlyIfNeeded(event->Source, SEARCH_PIN_STATE, key, log, 0); + + // SendStatus(event->Source, getPinStateJSON(SEARCH_PIN_STATE, PLUGIN_ID_019, event->Par1, log, 0)); } + } else if (command == F("mcpgpiotoggle")) { + success = true; + + if ((event->Par1 > 0) && (event->Par1 <= 128)) + { + portStatusStruct tempStatus; + const uint32_t key = createKey(PLUGIN_ID_009, event->Par1); + + // WARNING: operator [] creates an entry in the map if key does not exist + // So the next command should be part of each command: + tempStatus = globalMapPortStatus[key]; + const int8_t currentState = Plugin_009_Read(event->Par1); + bool needToSave = false; + + if (currentState == -1) { + tempStatus.mode = PIN_MODE_OFFLINE; + tempStatus.state = -1; + log = String(F("MCP : GPIO ")) + String(event->Par1) + String(F(" is offline (-1). Cannot set value.")); + needToSave = true; + } else if ((tempStatus.mode == PIN_MODE_OUTPUT) || (tempStatus.mode == PIN_MODE_UNDEFINED)) { // toggle only output pins + tempStatus.state = !currentState; // toggle current state value + tempStatus.mode = PIN_MODE_OUTPUT; + Plugin_009_Write(event->Par1, tempStatus.state); + log = String(F("MCP : Toggle GPIO ")) + String(event->Par1) + String(F(" Set to ")) + String(tempStatus.state); + needToSave = true; + } + + if (needToSave) { + tempStatus.command = 1; // set to 1 in order to display the status in the PinStatus page + tempStatus.forceEvent = 1; + + if (tempStatus.monitor) { tempStatus.forceMonitor = 1; // set to 1 in order to force an EVENT in case monitor is requested + } + + // setPinState(PLUGIN_ID_019, event->Par1, PIN_MODE_OUTPUT, !currentState); + savePortStatus(key, tempStatus); + addLog(LOG_LEVEL_INFO, log); + + // SendStatus(event->Source, getPinStateJSON(SEARCH_PIN_STATE, PLUGIN_ID_019, event->Par1, log, 0)); + SendStatusOnlyIfNeeded(event->Source, SEARCH_PIN_STATE, key, log, 0); + } + } + } else if (command == F("mcppulse")) { + success = true; + + if ((event->Par1 > 0) && (event->Par1 <= 128)) + { + portStatusStruct tempStatus; + const uint32_t key = createKey(PLUGIN_ID_009, event->Par1); + + // WARNING: operator [] creates an entry in the map if key does not exist + // So the next command should be part of each command: + tempStatus = globalMapPortStatus[key]; + + // setPinState(PLUGIN_ID_019, event->Par1, PIN_MODE_OUTPUT, event->Par2); + Plugin_009_Write(event->Par1, event->Par2); + delay(event->Par3); + Plugin_009_Write(event->Par1, !event->Par2); + + // setPinState(PLUGIN_ID_019, event->Par1, PIN_MODE_OUTPUT, !event->Par2); + + tempStatus.mode = PIN_MODE_OUTPUT; + tempStatus.state = event->Par2; + tempStatus.command = 1; // set to 1 in order to display the status in the PinStatus page + savePortStatus(key, tempStatus); + + log = String(F("MCP : GPIO ")) + String(event->Par1) + String(F(" Pulsed for ")) + String(event->Par3) + String(F(" mS")); + addLog(LOG_LEVEL_INFO, log); + + // SendStatus(event->Source, getPinStateJSON(SEARCH_PIN_STATE, PLUGIN_ID_019, event->Par1, log, 0)); + SendStatusOnlyIfNeeded(event->Source, SEARCH_PIN_STATE, key, log, 0); + } + } else if (command == F("mcplongpulse")) { + success = true; + + if ((event->Par1 > 0) && (event->Par1 <= 128)) + { + portStatusStruct tempStatus; + const uint32_t key = createKey(PLUGIN_ID_009, event->Par1); + + // WARNING: operator [] creates an entry in the map if key does not exist + // So the next command should be part of each command: + tempStatus = globalMapPortStatus[key]; + + // setPinState(PLUGIN_ID_019, event->Par1, PIN_MODE_OUTPUT, event->Par2); + Plugin_009_Write(event->Par1, event->Par2); + + tempStatus.mode = PIN_MODE_OUTPUT; + tempStatus.state = event->Par2; + tempStatus.command = 1; // set to 1 in order to display the status in the PinStatus page + (tempStatus.monitor) ? tempStatus.forceMonitor = 1 : tempStatus.forceMonitor = 0; + savePortStatus(key, tempStatus); + + // Scheduler.setPluginTaskTimer(event->Par3 * 1000, event->TaskIndex, event->Par1, !event->Par2); + Scheduler.setPluginTimer(event->Par3 * 1000, PLUGIN_ID_009, event->Par1, !event->Par2); + + log = String(F("MCP : GPIO ")) + String(event->Par1) + String(F(" Pulse set for ")) + String(event->Par3) + String(F(" S")); + addLog(LOG_LEVEL_INFO, log); + + // SendStatus(event->Source, getPinStateJSON(SEARCH_PIN_STATE, PLUGIN_ID_019, event->Par1, log, 0)); + SendStatusOnlyIfNeeded(event->Source, SEARCH_PIN_STATE, key, log, 0); + } + } else if (command == F("status")) { + if (parseString(string, 2) == F("mcp")) + { + success = true; + const uint32_t key = createKey(PLUGIN_ID_009, event->Par2); // WARNING: 'status' uses Par2 instead of Par1 + + if (existPortStatus(key)) { + SendStatusOnlyIfNeeded(event->Source, SEARCH_PIN_STATE, key, dummyString, 0); + } + else + { + const int8_t state = Plugin_009_Read(event->Par2); // report as input + + if (state != -1) { + SendStatusOnlyIfNeeded(event->Source, NO_SEARCH_PIN_STATE, key, dummyString, state); + } + } + } + } else if (command == F("monitor")) { + if (parseString(string, 2) == F("mcp")) + { + success = true; + const uint32_t key = createKey(PLUGIN_ID_009, event->Par2); // WARNING: 'monitor' uses Par2 instead of Par1 + + addMonitorToPort(key); + + // giig1967g: Comment next line to receive an EVENT just after calling the monitor command + globalMapPortStatus[key].state = Plugin_009_Read(event->Par2); // set initial value to avoid an event just after calling the + // command + + log = String(F("MCP : GPIO ")) + String(event->Par2) + String(F(" added to monitor list.")); + addLog(LOG_LEVEL_INFO, log); + SendStatusOnlyIfNeeded(event->Source, SEARCH_PIN_STATE, key, dummyString, 0); + } + } else if (command == F("unmonitor")) { + if (parseString(string, 2) == F("mcp")) + { + success = true; + const uint32_t key = createKey(PLUGIN_ID_009, event->Par2); // WARNING: 'monitor' uses Par2 instead of Par1 + SendStatusOnlyIfNeeded(event->Source, SEARCH_PIN_STATE, key, dummyString, 0); + + removeMonitorFromPort(key); + log = String(F("MCP : GPIO ")) + String(event->Par2) + String(F(" removed from monitor list.")); + addLog(LOG_LEVEL_INFO, log); + } + } + break; + } + + case PLUGIN_TIMER_IN: + { + Plugin_009_Write(event->Par1, event->Par2); + + // setPinState(PLUGIN_ID_009, event->Par1, PIN_MODE_OUTPUT, event->Par2); + portStatusStruct tempStatus; + + // WARNING: operator [] creates an entry in the map if key does not exist + const uint32_t key = createKey(PLUGIN_ID_009, event->Par1); + tempStatus = globalMapPortStatus[key]; + + tempStatus.state = event->Par2; + tempStatus.mode = PIN_MODE_OUTPUT; + (tempStatus.monitor) ? tempStatus.forceMonitor = 1 : tempStatus.forceMonitor = 0; // added to send event for longpulse command + savePortStatus(key, tempStatus); + break; + } + + case PLUGIN_ONLY_TIMER_IN: + { + Plugin_009_Write(event->Par1, event->Par2); + + // setPinState(PLUGIN_ID_009, event->Par1, PIN_MODE_OUTPUT, event->Par2); + portStatusStruct tempStatus; + + // WARNING: operator [] creates an entry in the map if key does not exist + const uint32_t key = createKey(PLUGIN_ID_009, event->Par1); + tempStatus = globalMapPortStatus[key]; + + tempStatus.state = event->Par2; + tempStatus.mode = PIN_MODE_OUTPUT; + (tempStatus.monitor) ? tempStatus.forceMonitor = 1 : tempStatus.forceMonitor = 0; // added to send event for longpulse command + savePortStatus(key, tempStatus); + break; + } } return success; } - -//******************************************************************************** +// ******************************************************************************** // MCP23017 read -//******************************************************************************** +// ******************************************************************************** int8_t Plugin_009_Read(byte Par1) { - int8_t state = -1; - byte unit = (Par1 - 1) / 16; - byte port = Par1 - (unit * 16); - uint8_t address = 0x20 + unit; + int8_t state = -1; + byte unit = (Par1 - 1) / 16; + byte port = Par1 - (unit * 16); + uint8_t address = 0x20 + unit; byte IOBankValueReg = 0x12; + if (port > 8) { port = port - 8; IOBankValueReg++; } + // get the current pin status Wire.beginTransmission(address); Wire.write(IOBankValueReg); // IO data register Wire.endTransmission(); Wire.requestFrom(address, (uint8_t)0x1); + if (Wire.available()) { state = ((Wire.read() & _BV(port - 1)) >> (port - 1)); @@ -753,33 +823,35 @@ int8_t Plugin_009_Read(byte Par1) return state; } - -//******************************************************************************** +// ******************************************************************************** // MCP23017 write -//******************************************************************************** +// ******************************************************************************** boolean Plugin_009_Write(byte Par1, byte Par2) { - boolean success = false; - byte portvalue = 0; - byte unit = (Par1 - 1) / 16; - byte port = Par1 - (unit * 16); - uint8_t address = 0x20 + unit; + boolean success = false; + byte portvalue = 0; + byte unit = (Par1 - 1) / 16; + byte port = Par1 - (unit * 16); + uint8_t address = 0x20 + unit; byte IOBankConfigReg = 0; - byte IOBankValueReg = 0x12; + byte IOBankValueReg = 0x12; + if (port > 8) { port = port - 8; IOBankConfigReg++; IOBankValueReg++; } + // turn this port into output, first read current config Wire.beginTransmission(address); Wire.write(IOBankConfigReg); // IO config register Wire.endTransmission(); Wire.requestFrom(address, (uint8_t)0x1); + if (Wire.available()) { - portvalue = Wire.read(); + portvalue = Wire.read(); portvalue &= ~(1 << (port - 1)); // change pin from (default) input to output // write new IO config @@ -788,18 +860,23 @@ boolean Plugin_009_Write(byte Par1, byte Par2) Wire.write(portvalue); Wire.endTransmission(); } + // get the current pin status Wire.beginTransmission(address); Wire.write(IOBankValueReg); // IO data register Wire.endTransmission(); Wire.requestFrom(address, (uint8_t)0x1); + if (Wire.available()) { portvalue = Wire.read(); - if (Par2 == 1) + + if (Par2 == 1) { portvalue |= (1 << (port - 1)); - else + } + else { portvalue &= ~(1 << (port - 1)); + } // write back new data Wire.beginTransmission(address); @@ -808,38 +885,43 @@ boolean Plugin_009_Write(byte Par1, byte Par2) Wire.endTransmission(); success = true; } - return(success); + return success; } - -//******************************************************************************** +// ******************************************************************************** // MCP23017 config -//******************************************************************************** +// ******************************************************************************** void Plugin_009_Config(byte Par1, byte Par2) { // boolean success = false; - byte portvalue = 0; - byte unit = (Par1 - 1) / 16; - byte port = Par1 - (unit * 16); - uint8_t address = 0x20 + unit; + byte portvalue = 0; + byte unit = (Par1 - 1) / 16; + byte port = Par1 - (unit * 16); + uint8_t address = 0x20 + unit; byte IOBankConfigReg = 0xC; + if (port > 8) { port = port - 8; IOBankConfigReg++; } + // turn this port pullup on Wire.beginTransmission(address); Wire.write(IOBankConfigReg); Wire.endTransmission(); Wire.requestFrom(address, (uint8_t)0x1); + if (Wire.available()) { portvalue = Wire.read(); - if (Par2 == 1) + + if (Par2 == 1) { portvalue |= (1 << (port - 1)); - else + } + else { portvalue &= ~(1 << (port - 1)); + } // write new IO config Wire.beginTransmission(address); @@ -848,4 +930,5 @@ void Plugin_009_Config(byte Par1, byte Par2) Wire.endTransmission(); } } + #endif // USES_P009 diff --git a/src/_P010_BH1750.ino b/src/_P010_BH1750.ino index d68e22891..f0d0c2153 100644 --- a/src/_P010_BH1750.ino +++ b/src/_P010_BH1750.ino @@ -1,64 +1,66 @@ #include "_Plugin_Helper.h" #ifdef USES_P010 -//####################################################################################################### -//#################################### Plugin-010: LuxRead ############################################ -//####################################################################################################### + +// ####################################################################################################### +// #################################### Plugin-010: LuxRead ############################################ +// ####################################################################################################### -//#ifdef ESP8266 // Needed for precompile issues. -#include "AS_BH1750.h" -//#endif +// #ifdef ESP8266 // Needed for precompile issues. +# include "AS_BH1750.h" -#define PLUGIN_010 -#define PLUGIN_ID_010 10 -#define PLUGIN_NAME_010 "Light/Lux - BH1750" -#define PLUGIN_VALUENAME1_010 "Lux" +// #endif + +# define PLUGIN_010 +# define PLUGIN_ID_010 10 +# define PLUGIN_NAME_010 "Light/Lux - BH1750" +# define PLUGIN_VALUENAME1_010 "Lux" boolean Plugin_010(byte function, struct EventStruct *event, String& string) - { - boolean success=false; +{ + boolean success = false; - switch(function) + switch (function) { - case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_010; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].VType = SENSOR_TYPE_SINGLE; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = true; - Device[deviceCount].ValueCount = 1; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].GlobalSyncOption = true; - break; - } + { + Device[++deviceCount].Number = PLUGIN_ID_010; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = SENSOR_TYPE_SINGLE; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 1; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].GlobalSyncOption = true; + break; + } case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_010); - break; - } + { + string = F(PLUGIN_NAME_010); + break; + } case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_010)); - break; - } + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_010)); + break; + } case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: { byte choice = PCONFIG(0); + /* - String options[2]; - options[0] = F("0x23 - default settings (ADDR Low)"); - options[1] = F("0x5c - alternate settings (ADDR High)"); - */ + String options[2]; + options[0] = F("0x23 - default settings (ADDR Low)"); + options[1] = F("0x5c - alternate settings (ADDR High)"); + */ int optionValues[2]; optionValues[0] = BH1750_DEFAULT_I2CADDR; optionValues[1] = BH1750_SECOND_I2CADDR; @@ -69,71 +71,74 @@ boolean Plugin_010(byte function, struct EventStruct *event, String& string) case PLUGIN_WEBFORM_LOAD: - { - byte choiceMode = PCONFIG(1); - String optionsMode[4]; - optionsMode[0] = F("RESOLUTION_LOW"); - optionsMode[1] = F("RESOLUTION_NORMAL"); - optionsMode[2] = F("RESOLUTION_HIGH"); - optionsMode[3] = F("RESOLUTION_AUTO_HIGH"); - int optionValuesMode[4]; - optionValuesMode[0] = RESOLUTION_LOW; - optionValuesMode[1] = RESOLUTION_NORMAL; - optionValuesMode[2] = RESOLUTION_HIGH; - optionValuesMode[3] = RESOLUTION_AUTO_HIGH; - addFormSelector(F("Measurement mode"), F("p010_mode"), 4, optionsMode, optionValuesMode, choiceMode); + { + byte choiceMode = PCONFIG(1); + String optionsMode[4]; + optionsMode[0] = F("RESOLUTION_LOW"); + optionsMode[1] = F("RESOLUTION_NORMAL"); + optionsMode[2] = F("RESOLUTION_HIGH"); + optionsMode[3] = F("RESOLUTION_AUTO_HIGH"); + int optionValuesMode[4]; + optionValuesMode[0] = RESOLUTION_LOW; + optionValuesMode[1] = RESOLUTION_NORMAL; + optionValuesMode[2] = RESOLUTION_HIGH; + optionValuesMode[3] = RESOLUTION_AUTO_HIGH; + addFormSelector(F("Measurement mode"), F("p010_mode"), 4, optionsMode, optionValuesMode, choiceMode); - addFormCheckBox(F("Send sensor to sleep"), F("p010_sleep"), PCONFIG(2)); + addFormCheckBox(F("Send sensor to sleep"), F("p010_sleep"), PCONFIG(2)); - success = true; - break; - } + success = true; + break; + } case PLUGIN_WEBFORM_SAVE: - { - PCONFIG(0) = getFormItemInt(F("p010")); - PCONFIG(1) = getFormItemInt(F("p010_mode")); - PCONFIG(2) = isFormItemChecked(F("p010_sleep")); - success = true; - break; - } - - case PLUGIN_READ: { - uint8_t address = PCONFIG(0); + PCONFIG(0) = getFormItemInt(F("p010")); + PCONFIG(1) = getFormItemInt(F("p010_mode")); + PCONFIG(2) = isFormItemChecked(F("p010_sleep")); + success = true; + break; + } + + case PLUGIN_READ: + { + uint8_t address = PCONFIG(0); AS_BH1750 sensor = AS_BH1750(address); // replaced the 8 lines below to optimize code sensors_resolution_t mode = static_cast(PCONFIG(1)); + // if (PCONFIG(1)==RESOLUTION_LOW) - // mode = RESOLUTION_LOW; + // mode = RESOLUTION_LOW; // if (PCONFIG(1)==RESOLUTION_NORMAL) - // mode = RESOLUTION_NORMAL; + // mode = RESOLUTION_NORMAL; // if (PCONFIG(1)==RESOLUTION_HIGH) - // mode = RESOLUTION_HIGH; + // mode = RESOLUTION_HIGH; // if (PCONFIG(1)==RESOLUTION_AUTO_HIGH) - // mode = RESOLUTION_AUTO_HIGH; + // mode = RESOLUTION_AUTO_HIGH; bool autoPowerDown = PCONFIG(2); sensor.begin(mode, autoPowerDown); float lux = sensor.readLightLevel(); + if (lux != -1) { - UserVar[event->BaseVarIndex] = lux; - String log = F("BH1750 Address: 0x"); - log += String(address,HEX); - log += F(" Mode: 0x"); - log += String(mode); - log += F(" : Light intensity: "); - log += UserVar[event->BaseVarIndex]; - addLog(LOG_LEVEL_INFO,log); - success=true; + UserVar[event->BaseVarIndex] = lux; + String log = F("BH1750 Address: 0x"); + log += String(address, HEX); + log += F(" Mode: 0x"); + log += String(mode); + log += F(" : Light intensity: "); + log += UserVar[event->BaseVarIndex]; + addLog(LOG_LEVEL_INFO, log); + success = true; } break; } } return success; } + #endif // USES_P010 diff --git a/src/_P011_PME.ino b/src/_P011_PME.ino index b66082fed..30cc76afd 100644 --- a/src/_P011_PME.ino +++ b/src/_P011_PME.ino @@ -1,7 +1,8 @@ #ifdef USES_P011 -//####################################################################################################### -//#################################### Plugin 011: Pro Mini Extender #################################### -//####################################################################################################### + +// ####################################################################################################### +// #################################### Plugin 011: Pro Mini Extender #################################### +// ####################################################################################################### #include "_Plugin_Helper.h" @@ -18,250 +19,272 @@ boolean Plugin_011(byte function, struct EventStruct *event, String& string) switch (function) { - case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_011; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].VType = SENSOR_TYPE_SINGLE; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = true; - Device[deviceCount].Ports = 14; - Device[deviceCount].ValueCount = 1; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - break; - } + { + Device[++deviceCount].Number = PLUGIN_ID_011; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = SENSOR_TYPE_SINGLE; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].Ports = 14; + Device[deviceCount].ValueCount = 1; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + break; + } case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_011); - break; - } + { + string = F(PLUGIN_NAME_011); + break; + } case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_011)); - break; - } + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_011)); + break; + } case PLUGIN_WEBFORM_LOAD: - { - byte choice = PCONFIG(0); - String options[2] = { F("Digital"), F("Analog") }; - addFormSelector(F("Port Type"), F("p011"), 2, options, NULL, choice); + { + byte choice = PCONFIG(0); + String options[2] = { F("Digital"), F("Analog") }; + addFormSelector(F("Port Type"), F("p011"), 2, options, NULL, choice); - success = true; - break; - } + success = true; + break; + } case PLUGIN_WEBFORM_SAVE: - { - PCONFIG(0) = getFormItemInt(F("p011")); - success = true; - break; - } + { + PCONFIG(0) = getFormItemInt(F("p011")); + success = true; + break; + } case PLUGIN_READ: - { - UserVar[event->BaseVarIndex] = Plugin_011_Read(PCONFIG(0), CONFIG_PORT); - String log = F("PME : PortValue: "); - log += UserVar[event->BaseVarIndex]; - addLog(LOG_LEVEL_INFO, log); - success = true; - break; - } + { + UserVar[event->BaseVarIndex] = Plugin_011_Read(PCONFIG(0), CONFIG_PORT); + String log = F("PME : PortValue: "); + log += UserVar[event->BaseVarIndex]; + addLog(LOG_LEVEL_INFO, log); + success = true; + break; + } case PLUGIN_WRITE: + { + String log = ""; + String command = parseString(string, 1); + + if (command == F("extgpio")) { - String log = ""; - String command = parseString(string, 1); - - if (command == F("extgpio")) - { - success = true; - portStatusStruct tempStatus; - const uint32_t key = createKey(PLUGIN_ID_011,event->Par1); - // WARNING: operator [] creates an entry in the map if key does not exist - // So the next command should be part of each command: - tempStatus = globalMapPortStatus[key]; - - tempStatus.mode=PIN_MODE_OUTPUT; - tempStatus.state=event->Par2; - tempStatus.command=1; //set to 1 in order to display the status in the PinStatus page - savePortStatus(key,tempStatus); - - Plugin_011_Write(event->Par1, event->Par2); - //setPinState(PLUGIN_ID_011, event->Par1, PIN_MODE_OUTPUT, event->Par2); - log = String(F("PME : GPIO ")) + String(event->Par1) + String(F(" Set to ")) + String(event->Par2); - addLog(LOG_LEVEL_INFO, log); - //SendStatus(event->Source, getPinStateJSON(SEARCH_PIN_STATE, PLUGIN_ID_011, event->Par1, log, 0)); - SendStatusOnlyIfNeeded(event->Source, SEARCH_PIN_STATE, key, log, 0); - } - - if (command == F("extpwm")) - { - success = true; - uint8_t address = PLUGIN_011_I2C_ADDRESS; - Wire.beginTransmission(address); - Wire.write(3); - Wire.write(event->Par1); - Wire.write(event->Par2 & 0xff); - Wire.write((event->Par2 >> 8)); - Wire.endTransmission(); - - portStatusStruct tempStatus; - const uint32_t key = createKey(PLUGIN_ID_011,event->Par1); - // WARNING: operator [] creates an entry in the map if key does not exist - // So the next command should be part of each command: - tempStatus = globalMapPortStatus[key]; - tempStatus.mode=PIN_MODE_PWM; - tempStatus.state=event->Par2; - tempStatus.command=1; //set to 1 in order to display the status in the PinStatus page - savePortStatus(key,tempStatus); - - //setPinState(PLUGIN_ID_011, event->Par1, PIN_MODE_PWM, event->Par2); - log = String(F("PME : GPIO ")) + String(event->Par1) + String(F(" Set PWM to ")) + String(event->Par2); - addLog(LOG_LEVEL_INFO, log); - //SendStatus(event->Source, getPinStateJSON(SEARCH_PIN_STATE, PLUGIN_ID_011, event->Par1, log, 0)); - SendStatusOnlyIfNeeded(event->Source, SEARCH_PIN_STATE, key, log, 0); - } - - if (command == F("extpulse")) - { - success = true; - if (event->Par1 >= 0 && event->Par1 <= 13) - { - Plugin_011_Write(event->Par1, event->Par2); - delay(event->Par3); - Plugin_011_Write(event->Par1, !event->Par2); - - portStatusStruct tempStatus; - const uint32_t key = createKey(PLUGIN_ID_011,event->Par1); - // WARNING: operator [] creates an entry in the map if key does not exist - // So the next command should be part of each command: - tempStatus = globalMapPortStatus[key]; - tempStatus.mode=PIN_MODE_OUTPUT; - tempStatus.state=event->Par2; - tempStatus.command=1; //set to 1 in order to display the status in the PinStatus page - savePortStatus(key,tempStatus); - - //setPinState(PLUGIN_ID_011, event->Par1, PIN_MODE_OUTPUT, event->Par2); - log = String(F("PME : GPIO ")) + String(event->Par1) + String(F(" Pulsed for ")) + String(event->Par3) + String(F(" mS")); - addLog(LOG_LEVEL_INFO, log); - //SendStatus(event->Source, getPinStateJSON(SEARCH_PIN_STATE, PLUGIN_ID_011, event->Par1, log, 0)); - SendStatusOnlyIfNeeded(event->Source, SEARCH_PIN_STATE, key, log, 0); - } - } - - if (command == F("extlongpulse")) - { - success = true; - if (event->Par1 >= 0 && event->Par1 <= 13) - { - Plugin_011_Write(event->Par1, event->Par2); - Scheduler.setPluginTaskTimer(event->Par3 * 1000, event->TaskIndex, event->Par1, !event->Par2); - - portStatusStruct tempStatus; - const uint32_t key = createKey(PLUGIN_ID_011,event->Par1); - // WARNING: operator [] creates an entry in the map if key does not exist - // So the next command should be part of each command: - tempStatus = globalMapPortStatus[key]; - tempStatus.mode=PIN_MODE_OUTPUT; - tempStatus.state=event->Par2; - tempStatus.command=1; //set to 1 in order to display the status in the PinStatus page - savePortStatus(key,tempStatus); - - //setPinState(PLUGIN_ID_011, event->Par1, PIN_MODE_OUTPUT, event->Par2); - log = String(F("PME : GPIO ")) + String(event->Par1) + String(F(" Pulse set for ")) + String(event->Par3) + String(F(" S")); - addLog(LOG_LEVEL_INFO, log); - //SendStatus(event->Source, getPinStateJSON(SEARCH_PIN_STATE, PLUGIN_ID_011, event->Par1, log, 0)); - SendStatusOnlyIfNeeded(event->Source, SEARCH_PIN_STATE, key, log, 0); - } - } - - if (command == F("status")) { - if (parseString(string, 2) == F("ext")) - { - success = true; - const uint32_t key = createKey(PLUGIN_ID_011,event->Par2); //WARNING: 'status' uses Par2 instead of Par1 - - if (!existPortStatus(key)) //tempStatus.mode == PIN_MODE_OUTPUT) // has been set as output - SendStatusOnlyIfNeeded(event->Source, SEARCH_PIN_STATE, key, dummyString, 0); - else - { - byte port = event->Par2; // port 0-13 is digital, ports 20-27 are mapped to A0-A7 - byte type = 0; // digital - if (port > 13) - { - type = 1; - port -= 20; - } - int state = Plugin_011_Read(type, port); // report as input (todo: analog reading) - if (state != -1) - SendStatusOnlyIfNeeded(event->Source, NO_SEARCH_PIN_STATE, key, dummyString, state); - //status = getPinStateJSON(NO_SEARCH_PIN_STATE, PLUGIN_ID_011, event->Par2, dummyString, state); - } - } - } - break; - } - - case PLUGIN_TIMER_IN: - { - Plugin_011_Write(event->Par1, event->Par2); + success = true; portStatusStruct tempStatus; + const uint32_t key = createKey(PLUGIN_ID_011, event->Par1); + // WARNING: operator [] creates an entry in the map if key does not exist - const uint32_t key = createKey(PLUGIN_ID_011,event->Par1); + // So the next command should be part of each command: tempStatus = globalMapPortStatus[key]; - tempStatus.state = event->Par2; - tempStatus.mode = PIN_MODE_OUTPUT; - savePortStatus(key,tempStatus); - //setPinState(PLUGIN_ID_011, event->Par1, PIN_MODE_OUTPUT, event->Par2); - break; + tempStatus.mode = PIN_MODE_OUTPUT; + tempStatus.state = event->Par2; + tempStatus.command = 1; // set to 1 in order to display the status in the PinStatus page + savePortStatus(key, tempStatus); + + Plugin_011_Write(event->Par1, event->Par2); + + // setPinState(PLUGIN_ID_011, event->Par1, PIN_MODE_OUTPUT, event->Par2); + log = String(F("PME : GPIO ")) + String(event->Par1) + String(F(" Set to ")) + String(event->Par2); + addLog(LOG_LEVEL_INFO, log); + + // SendStatus(event->Source, getPinStateJSON(SEARCH_PIN_STATE, PLUGIN_ID_011, event->Par1, log, 0)); + SendStatusOnlyIfNeeded(event->Source, SEARCH_PIN_STATE, key, log, 0); } + + if (command == F("extpwm")) + { + success = true; + uint8_t address = PLUGIN_011_I2C_ADDRESS; + Wire.beginTransmission(address); + Wire.write(3); + Wire.write(event->Par1); + Wire.write(event->Par2 & 0xff); + Wire.write((event->Par2 >> 8)); + Wire.endTransmission(); + + portStatusStruct tempStatus; + const uint32_t key = createKey(PLUGIN_ID_011, event->Par1); + + // WARNING: operator [] creates an entry in the map if key does not exist + // So the next command should be part of each command: + tempStatus = globalMapPortStatus[key]; + tempStatus.mode = PIN_MODE_PWM; + tempStatus.state = event->Par2; + tempStatus.command = 1; // set to 1 in order to display the status in the PinStatus page + savePortStatus(key, tempStatus); + + // setPinState(PLUGIN_ID_011, event->Par1, PIN_MODE_PWM, event->Par2); + log = String(F("PME : GPIO ")) + String(event->Par1) + String(F(" Set PWM to ")) + String(event->Par2); + addLog(LOG_LEVEL_INFO, log); + + // SendStatus(event->Source, getPinStateJSON(SEARCH_PIN_STATE, PLUGIN_ID_011, event->Par1, log, 0)); + SendStatusOnlyIfNeeded(event->Source, SEARCH_PIN_STATE, key, log, 0); + } + + if (command == F("extpulse")) + { + success = true; + + if ((event->Par1 >= 0) && (event->Par1 <= 13)) + { + Plugin_011_Write(event->Par1, event->Par2); + delay(event->Par3); + Plugin_011_Write(event->Par1, !event->Par2); + + portStatusStruct tempStatus; + const uint32_t key = createKey(PLUGIN_ID_011, event->Par1); + + // WARNING: operator [] creates an entry in the map if key does not exist + // So the next command should be part of each command: + tempStatus = globalMapPortStatus[key]; + tempStatus.mode = PIN_MODE_OUTPUT; + tempStatus.state = event->Par2; + tempStatus.command = 1; // set to 1 in order to display the status in the PinStatus page + savePortStatus(key, tempStatus); + + // setPinState(PLUGIN_ID_011, event->Par1, PIN_MODE_OUTPUT, event->Par2); + log = String(F("PME : GPIO ")) + String(event->Par1) + String(F(" Pulsed for ")) + String(event->Par3) + String(F(" mS")); + addLog(LOG_LEVEL_INFO, log); + + // SendStatus(event->Source, getPinStateJSON(SEARCH_PIN_STATE, PLUGIN_ID_011, event->Par1, log, 0)); + SendStatusOnlyIfNeeded(event->Source, SEARCH_PIN_STATE, key, log, 0); + } + } + + if (command == F("extlongpulse")) + { + success = true; + + if ((event->Par1 >= 0) && (event->Par1 <= 13)) + { + Plugin_011_Write(event->Par1, event->Par2); + Scheduler.setPluginTaskTimer(event->Par3 * 1000, event->TaskIndex, event->Par1, !event->Par2); + + portStatusStruct tempStatus; + const uint32_t key = createKey(PLUGIN_ID_011, event->Par1); + + // WARNING: operator [] creates an entry in the map if key does not exist + // So the next command should be part of each command: + tempStatus = globalMapPortStatus[key]; + tempStatus.mode = PIN_MODE_OUTPUT; + tempStatus.state = event->Par2; + tempStatus.command = 1; // set to 1 in order to display the status in the PinStatus page + savePortStatus(key, tempStatus); + + // setPinState(PLUGIN_ID_011, event->Par1, PIN_MODE_OUTPUT, event->Par2); + log = String(F("PME : GPIO ")) + String(event->Par1) + String(F(" Pulse set for ")) + String(event->Par3) + String(F(" S")); + addLog(LOG_LEVEL_INFO, log); + + // SendStatus(event->Source, getPinStateJSON(SEARCH_PIN_STATE, PLUGIN_ID_011, event->Par1, log, 0)); + SendStatusOnlyIfNeeded(event->Source, SEARCH_PIN_STATE, key, log, 0); + } + } + + if (command == F("status")) { + if (parseString(string, 2) == F("ext")) + { + success = true; + const uint32_t key = createKey(PLUGIN_ID_011, event->Par2); // WARNING: 'status' uses Par2 instead of Par1 + + if (!existPortStatus(key)) { // tempStatus.mode == PIN_MODE_OUTPUT) // has been set as output + SendStatusOnlyIfNeeded(event->Source, SEARCH_PIN_STATE, key, dummyString, 0); + } + else + { + byte port = event->Par2; // port 0-13 is digital, ports 20-27 are mapped to A0-A7 + byte type = 0; // digital + + if (port > 13) + { + type = 1; + port -= 20; + } + int state = Plugin_011_Read(type, port); // report as input (todo: analog reading) + + if (state != -1) { + SendStatusOnlyIfNeeded(event->Source, NO_SEARCH_PIN_STATE, key, dummyString, state); + } + + // status = getPinStateJSON(NO_SEARCH_PIN_STATE, PLUGIN_ID_011, event->Par2, dummyString, state); + } + } + } + break; + } + + case PLUGIN_TIMER_IN: + { + Plugin_011_Write(event->Par1, event->Par2); + portStatusStruct tempStatus; + + // WARNING: operator [] creates an entry in the map if key does not exist + const uint32_t key = createKey(PLUGIN_ID_011, event->Par1); + tempStatus = globalMapPortStatus[key]; + + tempStatus.state = event->Par2; + tempStatus.mode = PIN_MODE_OUTPUT; + savePortStatus(key, tempStatus); + + // setPinState(PLUGIN_ID_011, event->Par1, PIN_MODE_OUTPUT, event->Par2); + break; + } } return success; } - -//******************************************************************************** +// ******************************************************************************** // PME read -//******************************************************************************** +// ******************************************************************************** int Plugin_011_Read(byte Par1, byte Par2) { - int value = -1; + int value = -1; uint8_t address = PLUGIN_011_I2C_ADDRESS; + Wire.beginTransmission(address); - if (Par1 == 0) + + if (Par1 == 0) { Wire.write(2); // Digital Read - else + } + else { Wire.write(4); // Analog Read + } Wire.write(Par2); Wire.write(0); Wire.write(0); Wire.endTransmission(); - delay(1); // remote unit needs some time for conversion... + delay(1); // remote unit needs some time for conversion... Wire.requestFrom(address, (uint8_t)0x4); byte buffer[4]; + if (Wire.available() == 4) { - for (byte x = 0; x < 4; x++) + for (byte x = 0; x < 4; x++) { buffer[x] = Wire.read(); + } value = buffer[0] + 256 * buffer[1]; } return value; } - -//******************************************************************************** +// ******************************************************************************** // PME write -//******************************************************************************** +// ******************************************************************************** void Plugin_011_Write(byte Par1, byte Par2) { uint8_t address = 0x7f; + Wire.beginTransmission(address); Wire.write(1); Wire.write(Par1); @@ -269,4 +292,5 @@ void Plugin_011_Write(byte Par1, byte Par2) Wire.write((Par2 >> 8)); Wire.endTransmission(); } + #endif // USES_P011 diff --git a/src/_P012_LCD.ino b/src/_P012_LCD.ino index f7b1d17b7..412cb2224 100644 --- a/src/_P012_LCD.ino +++ b/src/_P012_LCD.ino @@ -1,9 +1,10 @@ #include "_Plugin_Helper.h" #ifdef USES_P012 -//####################################################################################################### -//#################################### Plugin 012: LCD ################################################## -//####################################################################################################### + +// ####################################################################################################### +// #################################### Plugin 012: LCD ################################################## +// ####################################################################################################### // Sample templates @@ -12,321 +13,342 @@ // Lux:[Lux#Lux#R] // Baro:[Baro#Pressure#R] // Pump:[Pump#on#O] -> ON/OFF -#include +# include -LiquidCrystal_I2C *lcd=NULL; -int Plugin_012_cols = 16; -int Plugin_012_rows = 2; -int Plugin_012_mode = 1; +LiquidCrystal_I2C *lcd = NULL; +int Plugin_012_cols = 16; +int Plugin_012_rows = 2; +int Plugin_012_mode = 1; -#define PLUGIN_012 -#define PLUGIN_ID_012 12 -#define PLUGIN_NAME_012 "Display - LCD2004" -#define PLUGIN_VALUENAME1_012 "LCD" +# define PLUGIN_012 +# define PLUGIN_ID_012 12 +# define PLUGIN_NAME_012 "Display - LCD2004" +# define PLUGIN_VALUENAME1_012 "LCD" -#define P12_Nlines 4 // The number of different lines which can be displayed -#define P12_Nchars 80 +# define P12_Nlines 4 // The number of different lines which can be displayed +# define P12_Nchars 80 boolean Plugin_012(byte function, struct EventStruct *event, String& string) { - boolean success = false; + boolean success = false; static byte displayTimer = 0; switch (function) { - case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_012; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].VType = SENSOR_TYPE_NONE; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = false; - Device[deviceCount].ValueCount = 0; - Device[deviceCount].SendDataOption = false; - Device[deviceCount].TimerOption = true; - break; - } + { + Device[++deviceCount].Number = PLUGIN_ID_012; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = SENSOR_TYPE_NONE; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = false; + Device[deviceCount].ValueCount = 0; + Device[deviceCount].SendDataOption = false; + Device[deviceCount].TimerOption = true; + break; + } case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_012); - break; - } + { + string = F(PLUGIN_NAME_012); + break; + } case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_012)); - break; - } + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_012)); + break; + } case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: - { - byte choice = PCONFIG(0); - //String options[16]; - int optionValues[16]; - for (byte x = 0; x < 16; x++) - { - if (x < 8) - optionValues[x] = 0x20 + x; - else - optionValues[x] = 0x30 + x; - //options[x] = F("0x"); - //options[x] += String(optionValues[x], HEX); + { + byte choice = PCONFIG(0); + + // String options[16]; + int optionValues[16]; + + for (byte x = 0; x < 16; x++) + { + if (x < 8) { + optionValues[x] = 0x20 + x; } - addFormSelectorI2C(F("p012_adr"), 16, optionValues, choice); - break; + else { + optionValues[x] = 0x30 + x; + } + + // options[x] = F("0x"); + // options[x] += String(optionValues[x], HEX); } + addFormSelectorI2C(F("p012_adr"), 16, optionValues, choice); + break; + } case PLUGIN_WEBFORM_LOAD: + { + byte choice2 = PCONFIG(1); + String options2[2]; + options2[0] = F("2 x 16"); + options2[1] = F("4 x 20"); + int optionValues2[2] = { 1, 2 }; + addFormSelector(F("Display Size"), F("p012_size"), 2, options2, optionValues2, choice2); + { - byte choice2 = PCONFIG(1); - String options2[2]; - options2[0] = F("2 x 16"); - options2[1] = F("4 x 20"); - int optionValues2[2] = { 1, 2 }; - addFormSelector(F("Display Size"), F("p012_size"), 2, options2, optionValues2, choice2); + String strings[P12_Nlines]; + LoadCustomTaskSettings(event->TaskIndex, strings, P12_Nlines, P12_Nchars); - { - String strings[P12_Nlines]; - LoadCustomTaskSettings(event->TaskIndex, strings, P12_Nlines, P12_Nchars); - for (byte varNr = 0; varNr < P12_Nlines; varNr++) - { - addFormTextBox(String(F("Line ")) + (varNr + 1), getPluginCustomArgName(varNr), strings[varNr], P12_Nchars); - } - } - - addRowLabel(F("Display button")); - addPinSelect(false, F("taskdevicepin3"), CONFIG_PIN3); - - addFormNumericBox(F("Display Timeout"), F("p012_timer"), PCONFIG(2)); - - String options3[3]; - options3[0] = F("Continue to next line (as in v1.4)"); - options3[1] = F("Truncate exceeding message"); - options3[2] = F("Clear then truncate exceeding message"); - int optionValues3[3] = { 0,1,2 }; - addFormSelector(F("LCD command Mode"), F("p012_mode"), 3, options3, optionValues3, PCONFIG(3)); - - success = true; - break; - } - - case PLUGIN_WEBFORM_SAVE: - { - PCONFIG(0) = getFormItemInt(F("p012_adr")); - PCONFIG(1) = getFormItemInt(F("p012_size")); - PCONFIG(2) = getFormItemInt(F("p012_timer")); - PCONFIG(3) = getFormItemInt(F("p012_mode")); - - // FIXME TD-er: This is a huge stack allocated object. - char deviceTemplate[P12_Nlines][P12_Nchars]; - String error; for (byte varNr = 0; varNr < P12_Nlines; varNr++) { - if (!safe_strncpy(deviceTemplate[varNr], web_server.arg(getPluginCustomArgName(varNr)), P12_Nchars)) { - error += getCustomTaskSettingsError(varNr); - } + addFormTextBox(String(F("Line ")) + (varNr + 1), getPluginCustomArgName(varNr), strings[varNr], P12_Nchars); } - if (error.length() > 0) { - addHtmlError(error); - } - SaveCustomTaskSettings(event->TaskIndex, (byte*)&deviceTemplate, sizeof(deviceTemplate)); - success = true; - break; } + addRowLabel(F("Display button")); + addPinSelect(false, F("taskdevicepin3"), CONFIG_PIN3); + + addFormNumericBox(F("Display Timeout"), F("p012_timer"), PCONFIG(2)); + + String options3[3]; + options3[0] = F("Continue to next line (as in v1.4)"); + options3[1] = F("Truncate exceeding message"); + options3[2] = F("Clear then truncate exceeding message"); + int optionValues3[3] = { 0, 1, 2 }; + addFormSelector(F("LCD command Mode"), F("p012_mode"), 3, options3, optionValues3, PCONFIG(3)); + + success = true; + break; + } + + case PLUGIN_WEBFORM_SAVE: + { + PCONFIG(0) = getFormItemInt(F("p012_adr")); + PCONFIG(1) = getFormItemInt(F("p012_size")); + PCONFIG(2) = getFormItemInt(F("p012_timer")); + PCONFIG(3) = getFormItemInt(F("p012_mode")); + + // FIXME TD-er: This is a huge stack allocated object. + char deviceTemplate[P12_Nlines][P12_Nchars]; + String error; + + for (byte varNr = 0; varNr < P12_Nlines; varNr++) + { + if (!safe_strncpy(deviceTemplate[varNr], web_server.arg(getPluginCustomArgName(varNr)), P12_Nchars)) { + error += getCustomTaskSettingsError(varNr); + } + } + + if (error.length() > 0) { + addHtmlError(error); + } + SaveCustomTaskSettings(event->TaskIndex, (byte *)&deviceTemplate, sizeof(deviceTemplate)); + success = true; + break; + } + case PLUGIN_INIT: - { - if (PCONFIG(1) == 2) { - Plugin_012_rows = 4; - Plugin_012_cols = 20; - } else if (PCONFIG(1) == 1) { - Plugin_012_rows = 2; - Plugin_012_cols = 16; - } - - Plugin_012_mode = PCONFIG(3); - - //TODO:LiquidCrystal_I2C class doesn't have destructor. So if LCD type (size) is changed better reboot for changes to take effect. - // workaround is to fix the cols and rows at its maximum (20 and 4) - if (!lcd) - lcd = new LiquidCrystal_I2C(PCONFIG(0), 20, 4); //Plugin_012_cols, Plugin_012_rows); - - // Setup LCD display - lcd->init(); // initialize the lcd - lcd->backlight(); - lcd->print(F("ESP Easy")); - displayTimer = PCONFIG(2); - if (CONFIG_PIN3 != -1) - pinMode(CONFIG_PIN3, INPUT_PULLUP); - success = true; - break; + { + if (PCONFIG(1) == 2) { + Plugin_012_rows = 4; + Plugin_012_cols = 20; + } else if (PCONFIG(1) == 1) { + Plugin_012_rows = 2; + Plugin_012_cols = 16; } + Plugin_012_mode = PCONFIG(3); + + // TODO:LiquidCrystal_I2C class doesn't have destructor. So if LCD type (size) is changed better reboot for changes to take effect. + // workaround is to fix the cols and rows at its maximum (20 and 4) + if (!lcd) { + lcd = new LiquidCrystal_I2C(PCONFIG(0), 20, 4); // Plugin_012_cols, Plugin_012_rows); + } + + // Setup LCD display + lcd->init(); // initialize the lcd + lcd->backlight(); + lcd->print(F("ESP Easy")); + displayTimer = PCONFIG(2); + + if (CONFIG_PIN3 != -1) { + pinMode(CONFIG_PIN3, INPUT_PULLUP); + } + success = true; + break; + } + case PLUGIN_TEN_PER_SECOND: + { + if (CONFIG_PIN3 != -1) { - if (CONFIG_PIN3 != -1) + if (!digitalRead(CONFIG_PIN3)) { - if (!digitalRead(CONFIG_PIN3)) - { - if (lcd) { - lcd->backlight(); - } - displayTimer = PCONFIG(2); + if (lcd) { + lcd->backlight(); } + displayTimer = PCONFIG(2); } - break; } + break; + } case PLUGIN_ONCE_A_SECOND: + { + if (displayTimer > 0) { - if ( displayTimer > 0) - { - displayTimer--; - if (lcd && displayTimer == 0) - lcd->noBacklight(); + displayTimer--; + + if (lcd && (displayTimer == 0)) { + lcd->noBacklight(); } - break; } + break; + } case PLUGIN_READ: - { - // FIXME TD-er: This is a huge stack allocated object. - char deviceTemplate[P12_Nlines][P12_Nchars]; - LoadCustomTaskSettings(event->TaskIndex, (byte*)&deviceTemplate, sizeof(deviceTemplate)); + { + // FIXME TD-er: This is a huge stack allocated object. + char deviceTemplate[P12_Nlines][P12_Nchars]; + LoadCustomTaskSettings(event->TaskIndex, (byte *)&deviceTemplate, sizeof(deviceTemplate)); - for (byte x = 0; x < Plugin_012_rows; x++) + for (byte x = 0; x < Plugin_012_rows; x++) + { + String tmpString = deviceTemplate[x]; + + if (lcd && tmpString.length()) { - String tmpString = deviceTemplate[x]; - if (lcd && tmpString.length()) - { - String newString = P012_parseTemplate(tmpString, Plugin_012_cols); - lcd->setCursor(0, x); - lcd->print(newString); - } + String newString = P012_parseTemplate(tmpString, Plugin_012_cols); + lcd->setCursor(0, x); + lcd->print(newString); } - success = false; - break; } + success = false; + break; + } case PLUGIN_WRITE: + { + String cmd = parseString(string, 1); + + if (lcd && cmd.equalsIgnoreCase(F("LCDCMD"))) { - String cmd = parseString(string, 1); - if (lcd && cmd.equalsIgnoreCase(F("LCDCMD"))) - { - success = true; - String arg1 = parseString(string, 2); - if (arg1.equalsIgnoreCase(F("Off"))){ - lcd->noBacklight(); - } - else if (arg1.equalsIgnoreCase(F("On"))){ - lcd->backlight(); - } - else if (arg1.equalsIgnoreCase(F("Clear"))){ - lcd->clear(); - } + success = true; + String arg1 = parseString(string, 2); + + if (arg1.equalsIgnoreCase(F("Off"))) { + lcd->noBacklight(); } - else if (lcd && cmd.equalsIgnoreCase(F("LCD"))) - { - success = true; - int colPos = event->Par2 - 1; - int rowPos = event->Par1 - 1; - String text = parseStringKeepCase(string, 4); - text = P012_parseTemplate(text, Plugin_012_cols); - - //clear line before writing new string - if (Plugin_012_mode == 2){ - lcd->setCursor(colPos, rowPos); - for (byte i = colPos; i < Plugin_012_cols; i++) { - lcd->print(" "); - } - } - - // truncate message exceeding cols - lcd->setCursor(colPos, rowPos); - if(Plugin_012_mode == 1 || Plugin_012_mode == 2){ - lcd->setCursor(colPos, rowPos); - for (byte i = 0; i < Plugin_012_cols - colPos; i++) { - if(text[i]){ - lcd->print(text[i]); - } - } - } - - // message exceeding cols will continue to next line - else{ - // Fix Weird (native) lcd display behaviour that split long string into row 1,3,2,4, instead of 1,2,3,4 - boolean stillProcessing = 1; - byte charCount = 1; - while(stillProcessing) { - if (++colPos > Plugin_012_cols) { // have we printed 20 characters yet (+1 for the logic) - rowPos += 1; - lcd->setCursor(0,rowPos); // move cursor down - colPos = 1; - } - - //dont print if "lower" than the lcd - if(rowPos < Plugin_012_rows ){ - lcd->print(text[charCount - 1]); - } - - if (!text[charCount]) { // no more chars to process? - stillProcessing = 0; - } - charCount += 1; - } - //lcd->print(text.c_str()); - // end fix - } - + else if (arg1.equalsIgnoreCase(F("On"))) { + lcd->backlight(); + } + else if (arg1.equalsIgnoreCase(F("Clear"))) { + lcd->clear(); } - break; } + else if (lcd && cmd.equalsIgnoreCase(F("LCD"))) + { + success = true; + int colPos = event->Par2 - 1; + int rowPos = event->Par1 - 1; + String text = parseStringKeepCase(string, 4); + text = P012_parseTemplate(text, Plugin_012_cols); + // clear line before writing new string + if (Plugin_012_mode == 2) { + lcd->setCursor(colPos, rowPos); + + for (byte i = colPos; i < Plugin_012_cols; i++) { + lcd->print(" "); + } + } + + // truncate message exceeding cols + lcd->setCursor(colPos, rowPos); + + if ((Plugin_012_mode == 1) || (Plugin_012_mode == 2)) { + lcd->setCursor(colPos, rowPos); + + for (byte i = 0; i < Plugin_012_cols - colPos; i++) { + if (text[i]) { + lcd->print(text[i]); + } + } + } + + // message exceeding cols will continue to next line + else { + // Fix Weird (native) lcd display behaviour that split long string into row 1,3,2,4, instead of 1,2,3,4 + boolean stillProcessing = 1; + byte charCount = 1; + + while (stillProcessing) { + if (++colPos > Plugin_012_cols) { // have we printed 20 characters yet (+1 for the logic) + rowPos += 1; + lcd->setCursor(0, rowPos); // move cursor down + colPos = 1; + } + + // dont print if "lower" than the lcd + if (rowPos < Plugin_012_rows) { + lcd->print(text[charCount - 1]); + } + + if (!text[charCount]) { // no more chars to process? + stillProcessing = 0; + } + charCount += 1; + } + + // lcd->print(text.c_str()); + // end fix + } + } + break; + } } return success; } // Perform some specific changes for LCD display // https://www.letscontrolit.com/forum/viewtopic.php?t=2368 -String P012_parseTemplate(String &tmpString, byte lineSize) { - String result = parseTemplate_padded(tmpString, lineSize); - const char degree[3] = {0xc2, 0xb0, 0}; // Unicode degree symbol - const char degree_lcd[2] = {0xdf, 0}; // P012_LCD degree symbol +String P012_parseTemplate(String& tmpString, byte lineSize) { + String result = parseTemplate_padded(tmpString, lineSize); + const char degree[3] = { 0xc2, 0xb0, 0 }; // Unicode degree symbol + const char degree_lcd[2] = { 0xdf, 0 }; // P012_LCD degree symbol + result.replace(degree, degree_lcd); - + char unicodePrefix = 0xc3; + if (result.indexOf(unicodePrefix) != -1) { // See: https://github.com/letscontrolit/ESPEasy/issues/2081 - const char umlautAE_uni[3] = {0xc3, 0x84, 0}; // Unicode Umlaute AE - const char umlautAE_lcd[2] = {0xe1, 0}; // P012_LCD Umlaute - result.replace(umlautAE_uni, umlautAE_lcd); + const char umlautAE_uni[3] = { 0xc3, 0x84, 0 }; // Unicode Umlaute AE + const char umlautAE_lcd[2] = { 0xe1, 0 }; // P012_LCD Umlaute + result.replace(umlautAE_uni, umlautAE_lcd); - const char umlaut_ae_uni[3] = {0xc3, 0xa4, 0}; // Unicode Umlaute ae + const char umlaut_ae_uni[3] = { 0xc3, 0xa4, 0 }; // Unicode Umlaute ae result.replace(umlaut_ae_uni, umlautAE_lcd); - const char umlautOE_uni[3] = {0xc3, 0x96, 0}; // Unicode Umlaute OE - const char umlautOE_lcd[2] = {0xef, 0}; // P012_LCD Umlaute - result.replace(umlautOE_uni, umlautOE_lcd); + const char umlautOE_uni[3] = { 0xc3, 0x96, 0 }; // Unicode Umlaute OE + const char umlautOE_lcd[2] = { 0xef, 0 }; // P012_LCD Umlaute + result.replace(umlautOE_uni, umlautOE_lcd); - const char umlaut_oe_uni[3] = {0xc3, 0xb6, 0}; // Unicode Umlaute oe + const char umlaut_oe_uni[3] = { 0xc3, 0xb6, 0 }; // Unicode Umlaute oe result.replace(umlaut_oe_uni, umlautOE_lcd); - const char umlautUE_uni[3] = {0xc3, 0x9c, 0}; // Unicode Umlaute UE - const char umlautUE_lcd[2] = {0xf5, 0}; // P012_LCD Umlaute - result.replace(umlautUE_uni, umlautUE_lcd); + const char umlautUE_uni[3] = { 0xc3, 0x9c, 0 }; // Unicode Umlaute UE + const char umlautUE_lcd[2] = { 0xf5, 0 }; // P012_LCD Umlaute + result.replace(umlautUE_uni, umlautUE_lcd); - const char umlaut_ue_uni[3] = {0xc3, 0xbc, 0}; // Unicode Umlaute ue + const char umlaut_ue_uni[3] = { 0xc3, 0xbc, 0 }; // Unicode Umlaute ue result.replace(umlaut_ue_uni, umlautUE_lcd); - const char umlaut_sz_uni[3] = {0xc3, 0x9f, 0}; // Unicode Umlaute sz - const char umlaut_sz_lcd[2] = {0xe2, 0}; // P012_LCD Umlaute + const char umlaut_sz_uni[3] = { 0xc3, 0x9f, 0 }; // Unicode Umlaute sz + const char umlaut_sz_lcd[2] = { 0xe2, 0 }; // P012_LCD Umlaute result.replace(umlaut_sz_uni, umlaut_sz_lcd); } return result; } + #endif // USES_P012 diff --git a/src/_P015_TSL2561.ino b/src/_P015_TSL2561.ino index b592687b2..f1b0742d6 100644 --- a/src/_P015_TSL2561.ino +++ b/src/_P015_TSL2561.ino @@ -357,7 +357,7 @@ boolean Plugin_015(byte function, struct EventStruct *event, String& string) options1[0] = F("0x39 - (default)"); options1[1] = F("0x49"); options1[2] = F("0x29"); - */ + */ int optionValues[3]; optionValues[0] = TSL2561_ADDR; optionValues[1] = TSL2561_ADDR_1; diff --git a/src/_P017_PN532.ino b/src/_P017_PN532.ino index bfeab5570..420857665 100644 --- a/src/_P017_PN532.ino +++ b/src/_P017_PN532.ino @@ -1,7 +1,8 @@ #ifdef USES_P017 -//####################################################################################################### -//#################################### Plugin-017: PN532 RFID reader #################################### -//####################################################################################################### + +// ####################################################################################################### +// #################################### Plugin-017: PN532 RFID reader #################################### +// ####################################################################################################### #include "_Plugin_Helper.h" @@ -40,148 +41,153 @@ boolean Plugin_017(byte function, struct EventStruct *event, String& string) switch (function) { - case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_017; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].VType = SENSOR_TYPE_LONG; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].ValueCount = 1; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = false; - Device[deviceCount].GlobalSyncOption = true; - break; - } + { + Device[++deviceCount].Number = PLUGIN_ID_017; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = SENSOR_TYPE_LONG; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].ValueCount = 1; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = false; + Device[deviceCount].GlobalSyncOption = true; + break; + } case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_017); - break; - } + { + string = F(PLUGIN_NAME_017); + break; + } case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_017)); - break; - } + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_017)); + break; + } case PLUGIN_WEBFORM_LOAD: - { - // FIXME TD-er: Why is this using pin3 and not pin1? And why isn't this using the normal pin selection functions? - addFormPinSelect(F("Reset Pin"), F("taskdevicepin3"), CONFIG_PIN3); - success = true; - break; - } + { + // FIXME TD-er: Why is this using pin3 and not pin1? And why isn't this using the normal pin selection functions? + addFormPinSelect(F("Reset Pin"), F("taskdevicepin3"), CONFIG_PIN3); + success = true; + break; + } case PLUGIN_INIT: - { - //set clock stretch to 2000, if its not set via advanced settings yet - //something that Martinus figured out and added: https://github.com/esp8266/Arduino/issues/1541 - // if (!Settings.WireClockStretchLimit) - // Wire.setClockStretchLimit(2000); + { + // set clock stretch to 2000, if its not set via advanced settings yet + // something that Martinus figured out and added: https://github.com/esp8266/Arduino/issues/1541 + // if (!Settings.WireClockStretchLimit) + // Wire.setClockStretchLimit(2000); - for(byte x=0; x < 3; x++) - { - if(Plugin_017_Init(CONFIG_PIN3)) - break; - delay(1000); + for (byte x = 0; x < 3; x++) + { + if (Plugin_017_Init(CONFIG_PIN3)) { + break; } - break; + delay(1000); } + break; + } case PLUGIN_TIMER_IN: - { - // Reset card id on timeout - UserVar[event->BaseVarIndex] = 0; - UserVar[event->BaseVarIndex + 1] = 0; - addLog(LOG_LEVEL_INFO, F("RFID : Removed Tag")); - sendData(event); - success = true; - break; - } + { + // Reset card id on timeout + UserVar[event->BaseVarIndex] = 0; + UserVar[event->BaseVarIndex + 1] = 0; + addLog(LOG_LEVEL_INFO, F("RFID : Removed Tag")); + sendData(event); + success = true; + break; + } case PLUGIN_TEN_PER_SECOND: + { + static unsigned long tempcounter = 0; + static byte counter; + static byte errorCount = 0; + + counter++; + + if (counter == 3) { - static unsigned long tempcounter = 0; - static byte counter; - static byte errorCount=0; - - counter++; - if (counter == 3 ) + // TODO: Clock stretching issue https://github.com/esp8266/Arduino/issues/1541 + if ((Settings.Pin_i2c_sda >= 0) && (Settings.Pin_i2c_scl >= 0) + && ((digitalRead(Settings.Pin_i2c_sda) == 0) || (digitalRead(Settings.Pin_i2c_scl) == 0))) { - // TODO: Clock stretching issue https://github.com/esp8266/Arduino/issues/1541 - if (Settings.Pin_i2c_sda >= 0 && Settings.Pin_i2c_scl>= 0 - && (digitalRead(Settings.Pin_i2c_sda)==0 || digitalRead(Settings.Pin_i2c_scl)==0)) - { - addLog(LOG_LEVEL_ERROR, F("PN532: BUS error")); - Plugin_017_Init(CONFIG_PIN3); - // delay(1000); - } - counter = 0; - uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 }; - uint8_t uidLength; - byte error = Plugin_017_readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength); + addLog(LOG_LEVEL_ERROR, F("PN532: BUS error")); + Plugin_017_Init(CONFIG_PIN3); - if (error == 1) - { - errorCount++; - String log = F("PN532: Read error: "); - log += errorCount; - addLog(LOG_LEVEL_ERROR, log); + // delay(1000); + } + counter = 0; + uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 }; + uint8_t uidLength; + byte error = Plugin_017_readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength); + + if (error == 1) + { + errorCount++; + String log = F("PN532: Read error: "); + log += errorCount; + addLog(LOG_LEVEL_ERROR, log); + } + else { + errorCount = 0; + } + + if (errorCount > 2) // if three consecutive I2C errors, reset PN532 + { + Plugin_017_Init(CONFIG_PIN3); + } + + + if (error == 0) { + unsigned long key = uid[0]; + + for (uint8_t i = 1; i < 4; i++) { + key <<= 8; + key += uid[i]; } - else - errorCount=0; + unsigned long old_key = ((uint32_t)UserVar[event->BaseVarIndex]) | ((uint32_t)UserVar[event->BaseVarIndex + 1]) << 16; + bool new_key = false; - if (errorCount > 2) // if three consecutive I2C errors, reset PN532 - { - Plugin_017_Init(CONFIG_PIN3); + if (old_key != key) { + UserVar[event->BaseVarIndex] = (key & 0xFFFF); + UserVar[event->BaseVarIndex + 1] = ((key >> 16) & 0xFFFF); + new_key = true; } + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = F("PN532: "); - if (error == 0) { - unsigned long key = uid[0]; - for (uint8_t i = 1; i < 4; i++) { - key <<= 8; - key += uid[i]; + if (new_key) { + log += F("New Tag: "); + } else { + log += F("Old Tag: "); } - unsigned long old_key = ((uint32_t) UserVar[event->BaseVarIndex]) | ((uint32_t) UserVar[event->BaseVarIndex + 1])<<16; - bool new_key = false; - if (old_key != key) { - UserVar[event->BaseVarIndex] = (key & 0xFFFF); - UserVar[event->BaseVarIndex + 1] = ((key >> 16) & 0xFFFF); - new_key = true; - } + log += key; + tempcounter++; + log += ' '; + log += tempcounter; + addLog(LOG_LEVEL_INFO, log); + } - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("PN532: "); - if (new_key) { - log += F("New Tag: "); - } else { - log += F("Old Tag: "); - } - log += key; - tempcounter++; - log += ' '; - log += tempcounter; - addLog(LOG_LEVEL_INFO, log); - } - - if (new_key) sendData(event); - Scheduler.setPluginTaskTimer(500, event->TaskIndex, event->Par1); - } + if (new_key) { sendData(event); } + Scheduler.setPluginTaskTimer(500, event->TaskIndex, event->Par1); } - break; } + break; + } } return success; } - /*********************************************************************************************\ - * PN532 init +* PN532 init \*********************************************************************************************/ boolean Plugin_017_Init(int8_t resetPin) { @@ -203,6 +209,7 @@ boolean Plugin_017_Init(int8_t resetPin) delay(5); uint32_t versiondata = getFirmwareVersion(); + if (versiondata) { String log = F("PN532: Found chip PN5"); log += String((versiondata >> 24) & 0xFF, HEX); @@ -212,16 +219,18 @@ boolean Plugin_017_Init(int8_t resetPin) log += String((versiondata >> 8) & 0xFF, HEX); addLog(LOG_LEVEL_INFO, log); } - else + else { return false; + } Plugin_017_pn532_packetbuffer[0] = PN532_COMMAND_SAMCONFIGURATION; Plugin_017_pn532_packetbuffer[1] = 0x01; // normal mode; - Plugin_017_pn532_packetbuffer[2] = 0x2; // timeout 50ms * 2 = 100 mS + Plugin_017_pn532_packetbuffer[2] = 0x2; // timeout 50ms * 2 = 100 mS Plugin_017_pn532_packetbuffer[3] = 0x01; // use IRQ pin! - if (Plugin_017_writeCommand(Plugin_017_pn532_packetbuffer, 4)) + if (Plugin_017_writeCommand(Plugin_017_pn532_packetbuffer, 4)) { return false; + } // to prevent nack on next read Wire.beginTransmission(PN532_I2C_ADDRESS); @@ -231,9 +240,8 @@ boolean Plugin_017_Init(int8_t resetPin) return true; } - /*********************************************************************************************\ - * PN532 get firmware version +* PN532 get firmware version \*********************************************************************************************/ uint32_t getFirmwareVersion(void) { @@ -249,27 +257,26 @@ uint32_t getFirmwareVersion(void) // read data packet int16_t status = Plugin_017_readResponse(Plugin_017_pn532_packetbuffer, sizeof(Plugin_017_pn532_packetbuffer)); + if (0 > status) { return 0; } - response = Plugin_017_pn532_packetbuffer[0]; + response = Plugin_017_pn532_packetbuffer[0]; response <<= 8; - response |= Plugin_017_pn532_packetbuffer[1]; + response |= Plugin_017_pn532_packetbuffer[1]; response <<= 8; - response |= Plugin_017_pn532_packetbuffer[2]; + response |= Plugin_017_pn532_packetbuffer[2]; response <<= 8; - response |= Plugin_017_pn532_packetbuffer[3]; + response |= Plugin_017_pn532_packetbuffer[3]; return response; } - void Plugin_017_powerDown(void) { - Plugin_017_pn532_packetbuffer[0] = PN532_COMMAND_POWERDOWN; - Plugin_017_pn532_packetbuffer[1] = 1 << 7; //allowed wakeup source is i2c + Plugin_017_pn532_packetbuffer[1] = 1 << 7; // allowed wakeup source is i2c if (Plugin_017_writeCommand(Plugin_017_pn532_packetbuffer, 2)) { return; @@ -281,25 +288,23 @@ void Plugin_017_powerDown(void) Plugin_017_readResponse(Plugin_017_pn532_packetbuffer, sizeof(Plugin_017_pn532_packetbuffer)); } - /*********************************************************************************************\ - * PN532 read tag +* PN532 read tag \*********************************************************************************************/ byte Plugin_017_readPassiveTargetID(uint8_t cardbaudrate, uint8_t *uid, uint8_t *uidLength) { Plugin_017_pn532_packetbuffer[0] = PN532_COMMAND_INLISTPASSIVETARGET; - Plugin_017_pn532_packetbuffer[1] = 1; // max 1 cards at once + Plugin_017_pn532_packetbuffer[1] = 1; // max 1 cards at once Plugin_017_pn532_packetbuffer[2] = cardbaudrate; if (Plugin_017_writeCommand(Plugin_017_pn532_packetbuffer, 3)) { - return 0x1; // command failed + return 0x1; // command failed } - delay(50); // we can try to read faster, but it will only put extra load on the I2C bus... + delay(50); // we can try to read faster, but it will only put extra load on the I2C bus... // read data packet if (Plugin_017_readResponse(Plugin_017_pn532_packetbuffer, sizeof(Plugin_017_pn532_packetbuffer)) < 0) { - // if no tag read, need to clear something ? // it seems that without this code, the next read fails, taking another read to work again... @@ -309,12 +314,14 @@ byte Plugin_017_readPassiveTargetID(uint8_t cardbaudrate, uint8_t *uid, uint8_t return 0x2; } - if (Plugin_017_pn532_packetbuffer[0] != 1) + if (Plugin_017_pn532_packetbuffer[0] != 1) { return 0x3; + } uint16_t sens_res = Plugin_017_pn532_packetbuffer[2]; + sens_res <<= 8; - sens_res |= Plugin_017_pn532_packetbuffer[3]; + sens_res |= Plugin_017_pn532_packetbuffer[3]; /* Card appears to be Mifare Classic */ *uidLength = Plugin_017_pn532_packetbuffer[5]; @@ -330,9 +337,8 @@ byte Plugin_017_readPassiveTargetID(uint8_t cardbaudrate, uint8_t *uid, uint8_t return 0; } - /*********************************************************************************************\ - * PN532 write command +* PN532 write command \*********************************************************************************************/ int8_t Plugin_017_writeCommand(const uint8_t *header, uint8_t hlen) { @@ -343,104 +349,111 @@ int8_t Plugin_017_writeCommand(const uint8_t *header, uint8_t hlen) Wire.write(PN532_STARTCODE1); Wire.write(PN532_STARTCODE2); - uint8_t length = hlen + 1; // length of data field: TFI + DATA + uint8_t length = hlen + 1; // length of data field: TFI + DATA + Wire.write(length); - Wire.write(~length + 1); // checksum of length + Wire.write(~length + 1); // checksum of length Wire.write(PN532_HOSTTOPN532); - uint8_t sum = PN532_HOSTTOPN532; // sum of TFI + DATA + uint8_t sum = PN532_HOSTTOPN532; // sum of TFI + DATA for (uint8_t i = 0; i < hlen; i++) { if (Wire.write(header[i])) { sum += header[i]; - } else { return PN532_INVALID_FRAME; } } - uint8_t checksum = ~sum + 1; // checksum of TFI + DATA + uint8_t checksum = ~sum + 1; // checksum of TFI + DATA + Wire.write(checksum); Wire.write(PN532_POSTAMBLE); byte status = Wire.endTransmission(); - if (status != 0) + if (status != 0) { return PN532_INVALID_FRAME; + } return Plugin_017_readAckFrame(); } - /*********************************************************************************************\ - * PN532 read response +* PN532 read response \*********************************************************************************************/ int16_t Plugin_017_readResponse(uint8_t buf[], uint8_t len) { - if (!Wire.requestFrom(PN532_I2C_ADDRESS, len + 2)) + if (!Wire.requestFrom(PN532_I2C_ADDRESS, len + 2)) { return -1; + } - if (!(Wire.read() & 1)) + if (!(Wire.read() & 1)) { return -1; + } - if (0x00 != Wire.read() || // PREAMBLE - 0x00 != Wire.read() || // STARTCODE1 - 0xFF != Wire.read() // STARTCODE2 - ) { - + if ((0x00 != Wire.read()) || // PREAMBLE + (0x00 != Wire.read()) || // STARTCODE1 + (0xFF != Wire.read()) // STARTCODE2 + ) { return PN532_INVALID_FRAME; } uint8_t length = Wire.read(); - if (0 != (uint8_t)(length + Wire.read())) { // checksum of length + + if (0 != (uint8_t)(length + Wire.read())) { // checksum of length return PN532_INVALID_FRAME; } - uint8_t cmd = Plugin_017_command + 1; // response command - if (PN532_PN532TOHOST != Wire.read() || (cmd) != Wire.read()) { + uint8_t cmd = Plugin_017_command + 1; // response command + + if ((PN532_PN532TOHOST != Wire.read()) || ((cmd) != Wire.read())) { return PN532_INVALID_FRAME; } length -= 2; + if (length > len) { - return PN532_NO_SPACE; // not enough space + return PN532_NO_SPACE; // not enough space } uint8_t sum = PN532_PN532TOHOST + cmd; + for (uint8_t i = 0; i < length; i++) { buf[i] = Wire.read(); - sum += buf[i]; - + sum += buf[i]; } uint8_t checksum = Wire.read(); + if (0 != (uint8_t)(sum + checksum)) { return PN532_INVALID_FRAME; } - Wire.read(); // POSTAMBLE + Wire.read(); // POSTAMBLE return length; } - /*********************************************************************************************\ - * PN532 read ack +* PN532 read ack \*********************************************************************************************/ int8_t Plugin_017_readAckFrame() { - const uint8_t PN532_ACK[] = {0, 0, 0xFF, 0, 0xFF, 0}; + const uint8_t PN532_ACK[] = { 0, 0, 0xFF, 0, 0xFF, 0 }; uint8_t ackBuf[sizeof(PN532_ACK)]; uint16_t time = 0; + do { if (Wire.requestFrom(PN532_I2C_ADDRESS, sizeof(PN532_ACK) + 1)) { - if (Wire.read() & 1) { // check first byte --- status - break; // PN532 is ready + if (Wire.read() & 1) { // check first byte --- status + break; // PN532 is ready } } delay(5); time++; + if (time > PN532_ACK_WAIT_TIME) { return PN532_TIMEOUT; } @@ -457,4 +470,5 @@ int8_t Plugin_017_readAckFrame() return 0; } + #endif // USES_P017 diff --git a/src/_P019_PCF8574.ino b/src/_P019_PCF8574.ino index 051edebec..a58f7ee6b 100644 --- a/src/_P019_PCF8574.ino +++ b/src/_P019_PCF8574.ino @@ -3,33 +3,33 @@ #include "_Plugin_Helper.h" #include "src/DataStructs/PinMode.h" -//####################################################################################################### -//#################################### Plugin 019: PCF8574 ############################################## -//####################################################################################################### +// ####################################################################################################### +// #################################### Plugin 019: PCF8574 ############################################## +// ####################################################################################################### /**************************************************\ -CONFIG -TaskDevicePluginConfig settings: -0: send boot state (true,false) -1: -2: -3: -4: use doubleclick (0,1,2,3) -5: use longpress (0,1,2,3) -6: LP fired (true,false) -7: doubleclick counter (=0,1,2,3) + CONFIG + TaskDevicePluginConfig settings: + 0: send boot state (true,false) + 1: + 2: + 3: + 4: use doubleclick (0,1,2,3) + 5: use longpress (0,1,2,3) + 6: LP fired (true,false) + 7: doubleclick counter (=0,1,2,3) -TaskDevicePluginConfigFloat settings: -0: debounce interval ms -1: doubleclick interval ms -2: longpress interval ms -3: use safebutton (=0,1) + TaskDevicePluginConfigFloat settings: + 0: debounce interval ms + 1: doubleclick interval ms + 2: longpress interval ms + 3: use safebutton (=0,1) -TaskDevicePluginConfigLong settings: -0: clickTime debounce ms -1: clickTime doubleclick ms -2: clickTime longpress ms -3: safebutton counter (=0,1) + TaskDevicePluginConfigLong settings: + 0: clickTime debounce ms + 1: clickTime doubleclick ms + 2: clickTime longpress ms + 3: safebutton counter (=0,1) \**************************************************/ #define PLUGIN_019 @@ -52,704 +52,785 @@ TaskDevicePluginConfigLong settings: boolean Plugin_019(byte function, struct EventStruct *event, String& string) { boolean success = false; - //static int8_t switchstate[TASKS_MAX]; + + // static int8_t switchstate[TASKS_MAX]; switch (function) { - case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_019; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].VType = SENSOR_TYPE_SWITCH; - Device[deviceCount].Ports = 8; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = true; - Device[deviceCount].FormulaOption = false; - Device[deviceCount].ValueCount = 1; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].TimerOptional = true; - Device[deviceCount].GlobalSyncOption = true; - break; - } + { + Device[++deviceCount].Number = PLUGIN_ID_019; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = SENSOR_TYPE_SWITCH; + Device[deviceCount].Ports = 8; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = true; + Device[deviceCount].FormulaOption = false; + Device[deviceCount].ValueCount = 1; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].TimerOptional = true; + Device[deviceCount].GlobalSyncOption = true; + break; + } case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_019); - break; - } + { + string = F(PLUGIN_NAME_019); + break; + } case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_019)); - break; - } + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_019)); + break; + } case PLUGIN_WEBFORM_LOAD: - { - //@giig1967g: set current task value for taking actions after changes - const uint32_t key = createKey(PLUGIN_ID_019,CONFIG_PORT); - if (existPortStatus(key)) { - globalMapPortStatus[key].previousTask = event->TaskIndex; - } + { + // @giig1967g: set current task value for taking actions after changes + const uint32_t key = createKey(PLUGIN_ID_019, CONFIG_PORT); - addFormCheckBox(F("Send Boot state"), F("p019_boot"), PCONFIG(0)); - - //@giig1967-20181022 - addFormSubHeader(F("Advanced event management")); - - addFormNumericBox(F("De-bounce (ms)"), F("p019_debounce"), round(PCONFIG_FLOAT(0)), 0, 250); - - //set minimum value for doubleclick MIN max speed - if (PCONFIG_FLOAT(1) < PLUGIN_019_DOUBLECLICK_MIN_INTERVAL) - PCONFIG_FLOAT(1) = PLUGIN_019_DOUBLECLICK_MIN_INTERVAL; - - byte choiceDC = PCONFIG(4); - String buttonDC[4]; - buttonDC[0] = F("Disabled"); - buttonDC[1] = F("Active only on LOW (EVENT=3)"); - buttonDC[2] = F("Active only on HIGH (EVENT=3)"); - buttonDC[3] = F("Active on LOW & HIGH (EVENT=3)"); - int buttonDCValues[4] = {PLUGIN_019_DC_DISABLED, PLUGIN_019_DC_LOW, PLUGIN_019_DC_HIGH,PLUGIN_019_DC_BOTH}; - addFormSelector(F("Doubleclick event"), F("p019_dc"), 4, buttonDC, buttonDCValues, choiceDC); - - addFormNumericBox(F("Doubleclick max. interval (ms)"), F("p019_dcmaxinterval"), round(PCONFIG_FLOAT(1)), PLUGIN_019_DOUBLECLICK_MIN_INTERVAL, PLUGIN_019_DOUBLECLICK_MAX_INTERVAL); - - //set minimum value for longpress MIN max speed - if (PCONFIG_FLOAT(2) < PLUGIN_019_LONGPRESS_MIN_INTERVAL) - PCONFIG_FLOAT(2) = PLUGIN_019_LONGPRESS_MIN_INTERVAL; - - byte choiceLP = PCONFIG(5); - String buttonLP[4]; - buttonLP[0] = F("Disabled"); - buttonLP[1] = F("Active only on LOW (EVENT= 10 [NORMAL] or 11 [INVERSED])"); - buttonLP[2] = F("Active only on HIGH (EVENT= 11 [NORMAL] or 10 [INVERSED])"); - buttonLP[3] = F("Active on LOW & HIGH (EVENT= 10 or 11)"); - int buttonLPValues[4] = {PLUGIN_019_LONGPRESS_DISABLED, PLUGIN_019_LONGPRESS_LOW, PLUGIN_019_LONGPRESS_HIGH,PLUGIN_019_LONGPRESS_BOTH}; - addFormSelector(F("Longpress event"), F("p019_lp"), 4, buttonLP, buttonLPValues, choiceLP); - - addFormNumericBox(F("Longpress min. interval (ms)"), F("p019_lpmininterval"), round(PCONFIG_FLOAT(2)), PLUGIN_019_LONGPRESS_MIN_INTERVAL, PLUGIN_019_LONGPRESS_MAX_INTERVAL); - - addFormCheckBox(F("Use Safe Button (slower)"), F("p019_sb"), round(PCONFIG_FLOAT(3))); - - success = true; - break; + if (existPortStatus(key)) { + globalMapPortStatus[key].previousTask = event->TaskIndex; } + addFormCheckBox(F("Send Boot state"), F("p019_boot"), PCONFIG(0)); + + // @giig1967-20181022 + addFormSubHeader(F("Advanced event management")); + + addFormNumericBox(F("De-bounce (ms)"), F("p019_debounce"), round(PCONFIG_FLOAT(0)), 0, 250); + + // set minimum value for doubleclick MIN max speed + if (PCONFIG_FLOAT(1) < PLUGIN_019_DOUBLECLICK_MIN_INTERVAL) { + PCONFIG_FLOAT(1) = PLUGIN_019_DOUBLECLICK_MIN_INTERVAL; + } + + byte choiceDC = PCONFIG(4); + String buttonDC[4]; + buttonDC[0] = F("Disabled"); + buttonDC[1] = F("Active only on LOW (EVENT=3)"); + buttonDC[2] = F("Active only on HIGH (EVENT=3)"); + buttonDC[3] = F("Active on LOW & HIGH (EVENT=3)"); + int buttonDCValues[4] = { PLUGIN_019_DC_DISABLED, PLUGIN_019_DC_LOW, PLUGIN_019_DC_HIGH, PLUGIN_019_DC_BOTH }; + addFormSelector(F("Doubleclick event"), F("p019_dc"), 4, buttonDC, buttonDCValues, choiceDC); + + addFormNumericBox(F("Doubleclick max. interval (ms)"), + F("p019_dcmaxinterval"), + round(PCONFIG_FLOAT(1)), + PLUGIN_019_DOUBLECLICK_MIN_INTERVAL, + PLUGIN_019_DOUBLECLICK_MAX_INTERVAL); + + // set minimum value for longpress MIN max speed + if (PCONFIG_FLOAT(2) < PLUGIN_019_LONGPRESS_MIN_INTERVAL) { + PCONFIG_FLOAT(2) = PLUGIN_019_LONGPRESS_MIN_INTERVAL; + } + + byte choiceLP = PCONFIG(5); + String buttonLP[4]; + buttonLP[0] = F("Disabled"); + buttonLP[1] = F("Active only on LOW (EVENT= 10 [NORMAL] or 11 [INVERSED])"); + buttonLP[2] = F("Active only on HIGH (EVENT= 11 [NORMAL] or 10 [INVERSED])"); + buttonLP[3] = F("Active on LOW & HIGH (EVENT= 10 or 11)"); + int buttonLPValues[4] = + { PLUGIN_019_LONGPRESS_DISABLED, PLUGIN_019_LONGPRESS_LOW, PLUGIN_019_LONGPRESS_HIGH, PLUGIN_019_LONGPRESS_BOTH }; + addFormSelector(F("Longpress event"), F("p019_lp"), 4, buttonLP, buttonLPValues, choiceLP); + + addFormNumericBox(F("Longpress min. interval (ms)"), + F("p019_lpmininterval"), + round(PCONFIG_FLOAT(2)), + PLUGIN_019_LONGPRESS_MIN_INTERVAL, + PLUGIN_019_LONGPRESS_MAX_INTERVAL); + + addFormCheckBox(F("Use Safe Button (slower)"), F("p019_sb"), round(PCONFIG_FLOAT(3))); + + success = true; + break; + } + case PLUGIN_WEBFORM_SAVE: - { - PCONFIG(0) = isFormItemChecked(F("p019_boot")); + { + PCONFIG(0) = isFormItemChecked(F("p019_boot")); - //@giig1967-20181022 - PCONFIG_FLOAT(0) = getFormItemInt(F("p019_debounce")); + // @giig1967-20181022 + PCONFIG_FLOAT(0) = getFormItemInt(F("p019_debounce")); - PCONFIG(4) = getFormItemInt(F("p019_dc")); - PCONFIG_FLOAT(1) = getFormItemInt(F("p019_dcmaxinterval")); + PCONFIG(4) = getFormItemInt(F("p019_dc")); + PCONFIG_FLOAT(1) = getFormItemInt(F("p019_dcmaxinterval")); - PCONFIG(5) = getFormItemInt(F("p019_lp")); - PCONFIG_FLOAT(2) = getFormItemInt(F("p019_lpmininterval")); + PCONFIG(5) = getFormItemInt(F("p019_lp")); + PCONFIG_FLOAT(2) = getFormItemInt(F("p019_lpmininterval")); - PCONFIG_FLOAT(3) = isFormItemChecked(F("p019_sb")); + PCONFIG_FLOAT(3) = isFormItemChecked(F("p019_sb")); - //check if a task has been edited and remove task flag from the previous pin - for (std::map::iterator it=globalMapPortStatus.begin(); it!=globalMapPortStatus.end(); ++it) { - if (it->second.previousTask == event->TaskIndex && getPluginFromKey(it->first)==PLUGIN_ID_019) { - globalMapPortStatus[it->first].previousTask = -1; - removeTaskFromPort(it->first); - break; - } + // check if a task has been edited and remove task flag from the previous pin + for (std::map::iterator it = globalMapPortStatus.begin(); it != globalMapPortStatus.end(); ++it) { + if ((it->second.previousTask == event->TaskIndex) && (getPluginFromKey(it->first) == PLUGIN_ID_019)) { + globalMapPortStatus[it->first].previousTask = -1; + removeTaskFromPort(it->first); + break; } - success = true; - break; } + success = true; + break; + } case PLUGIN_INIT: + { + // apply INIT only if PIN is in range. Do not start INIT if pin not set in the device page. + if (CONFIG_PORT >= 0) { - //apply INIT only if PIN is in range. Do not start INIT if pin not set in the device page. - if (CONFIG_PORT >= 0) - { - portStatusStruct newStatus; - const uint32_t key = createKey(PLUGIN_ID_019,CONFIG_PORT); - //Read current status or create empty if it does not exist - newStatus = globalMapPortStatus[key]; + portStatusStruct newStatus; + const uint32_t key = createKey(PLUGIN_ID_019, CONFIG_PORT); - // read and store current state to prevent switching at boot time - // "state" could be -1, 0 or 1 - newStatus.state = Plugin_019_Read(CONFIG_PORT); - newStatus.output = newStatus.state; - (newStatus.state == -1) ? newStatus.mode = PIN_MODE_OFFLINE : newStatus.mode = PIN_MODE_INPUT; // @giig1967g: if it is in the device list we assume it's an input pin - newStatus.task++; // add this GPIO/port as a task + // Read current status or create empty if it does not exist + newStatus = globalMapPortStatus[key]; - // @giig1967g-20181022: set initial UserVar of the switch - if (newStatus.state != -1 && Settings.TaskDevicePin1Inversed[event->TaskIndex]) { - UserVar[event->BaseVarIndex] = !newStatus.state; - } else { - UserVar[event->BaseVarIndex] = newStatus.state; - } - - // if boot state must be send, inverse default state - // this is done to force the trigger in PLUGIN_TEN_PER_SECOND - if (PCONFIG(0)) - newStatus.state = !newStatus.state; - - // @giig1967g-20181022: counter = 0 - PCONFIG(7)=0; //doubleclick counter - PCONFIG_LONG(3)=0; //safebutton counter - - // @giig1967g-20181022: used to track if LP has fired - PCONFIG(6)=false; - - // @giig1967g-20181022: store millis for debounce, doubleclick and long press - PCONFIG_LONG(0)=millis(); //debounce timer - PCONFIG_LONG(1)=millis(); //doubleclick timer - PCONFIG_LONG(2)=millis(); //longpress timer - - // @giig1967g-20181022: set minimum value for doubleclick MIN max speed - if (PCONFIG_FLOAT(1) < PLUGIN_019_DOUBLECLICK_MIN_INTERVAL) - PCONFIG_FLOAT(1) = PLUGIN_019_DOUBLECLICK_MIN_INTERVAL; - - // @giig1967g-20181022: set minimum value for longpress MIN max speed - if (PCONFIG_FLOAT(2) < PLUGIN_019_LONGPRESS_MIN_INTERVAL) - PCONFIG_FLOAT(2) = PLUGIN_019_LONGPRESS_MIN_INTERVAL; - - //setPinState(PLUGIN_ID_019, CONFIG_PORT, PIN_MODE_INPUT, switchstate[event->TaskIndex]); - savePortStatus(key,newStatus); + // read and store current state to prevent switching at boot time + // "state" could be -1, 0 or 1 + newStatus.state = Plugin_019_Read(CONFIG_PORT); + newStatus.output = newStatus.state; + (newStatus.state == -1) ? newStatus.mode = PIN_MODE_OFFLINE : newStatus.mode = PIN_MODE_INPUT; // @giig1967g: if it is in the device + // list we assume it's an input pin + newStatus.task++; // add this GPIO/port as a task + // @giig1967g-20181022: set initial UserVar of the switch + if ((newStatus.state != -1) && Settings.TaskDevicePin1Inversed[event->TaskIndex]) { + UserVar[event->BaseVarIndex] = !newStatus.state; + } else { + UserVar[event->BaseVarIndex] = newStatus.state; } - success = true; - break; + + // if boot state must be send, inverse default state + // this is done to force the trigger in PLUGIN_TEN_PER_SECOND + if (PCONFIG(0)) { + newStatus.state = !newStatus.state; + } + + // @giig1967g-20181022: counter = 0 + PCONFIG(7) = 0; // doubleclick counter + PCONFIG_LONG(3) = 0; // safebutton counter + + // @giig1967g-20181022: used to track if LP has fired + PCONFIG(6) = false; + + // @giig1967g-20181022: store millis for debounce, doubleclick and long press + PCONFIG_LONG(0) = millis(); // debounce timer + PCONFIG_LONG(1) = millis(); // doubleclick timer + PCONFIG_LONG(2) = millis(); // longpress timer + + // @giig1967g-20181022: set minimum value for doubleclick MIN max speed + if (PCONFIG_FLOAT(1) < PLUGIN_019_DOUBLECLICK_MIN_INTERVAL) { + PCONFIG_FLOAT(1) = PLUGIN_019_DOUBLECLICK_MIN_INTERVAL; + } + + // @giig1967g-20181022: set minimum value for longpress MIN max speed + if (PCONFIG_FLOAT(2) < PLUGIN_019_LONGPRESS_MIN_INTERVAL) { + PCONFIG_FLOAT(2) = PLUGIN_019_LONGPRESS_MIN_INTERVAL; + } + + // setPinState(PLUGIN_ID_019, CONFIG_PORT, PIN_MODE_INPUT, switchstate[event->TaskIndex]); + savePortStatus(key, newStatus); } -/* - case PLUGIN_UNCONDITIONAL_POLL: - { - // port monitoring, generates an event by rule command 'monitor,pcf,port#' - for (std::map::iterator it=globalMapPortStatus.begin(); it!=globalMapPortStatus.end(); ++it) { - if (getPluginFromKey(it->first)==PLUGIN_ID_019 && (it->second.monitor || it->second.command || it->second.init)) { - const uint16_t port = getPortFromKey(it->first); - int8_t state = Plugin_019_Read(port); - if (it->second.state != state || it->second.forceMonitor) { - if (it->second.mode == PIN_MODE_OFFLINE) it->second.mode=PIN_MODE_UNDEFINED; //changed from offline to online - if (state == -1) it->second.mode=PIN_MODE_OFFLINE; //changed from online to offline - if (!it->second.task) it->second.state = state; //do not update state if task flag=1 otherwise it will not be picked up by 10xSEC function - if (it->second.monitor) { - it->second.forceMonitor=0; //reset flag - String eventString = F("PCF#"); - eventString += port; - eventString += '='; - eventString += state; - rulesProcessing(eventString); + success = true; + break; + } + + /* + case PLUGIN_UNCONDITIONAL_POLL: + { + // port monitoring, generates an event by rule command 'monitor,pcf,port#' + for (std::map::iterator it=globalMapPortStatus.begin(); it!=globalMapPortStatus.end(); ++it) { + if (getPluginFromKey(it->first)==PLUGIN_ID_019 && (it->second.monitor || it->second.command || it->second.init)) { + const uint16_t port = getPortFromKey(it->first); + int8_t state = Plugin_019_Read(port); + if (it->second.state != state || it->second.forceMonitor) { + if (it->second.mode == PIN_MODE_OFFLINE) it->second.mode=PIN_MODE_UNDEFINED; //changed from offline to online + if (state == -1) it->second.mode=PIN_MODE_OFFLINE; //changed from online to offline + if (!it->second.task) it->second.state = state; //do not update state if task flag=1 otherwise it will not be picked up + by 10xSEC function + if (it->second.monitor) { + it->second.forceMonitor=0; //reset flag + String eventString = F("PCF#"); + eventString += port; + eventString += '='; + eventString += state; + rulesProcessing(eventString); + } + } } } + break; } - } - break; - } -*/ - case PLUGIN_MONITOR: - { - // port monitoring, generates an event by rule command 'monitor,gpio,port#' - const uint32_t key = createKey(PLUGIN_ID_019,event->Par1); - const portStatusStruct currentStatus = globalMapPortStatus[key]; + */ + case PLUGIN_MONITOR: + { + // port monitoring, generates an event by rule command 'monitor,gpio,port#' + const uint32_t key = createKey(PLUGIN_ID_019, event->Par1); + const portStatusStruct currentStatus = globalMapPortStatus[key]; - // if (currentStatus.monitor || currentStatus.command || currentStatus.init) { - const int8_t state = Plugin_019_Read(event->Par1); - if (currentStatus.state != state || currentStatus.forceMonitor) { - if (!currentStatus.task) globalMapPortStatus[key].state = state; //do not update state if task flag=1 otherwise it will not be picked up by 10xSEC function - if (currentStatus.monitor) { - globalMapPortStatus[key].forceMonitor=0; //reset flag - String eventString = F("PCF#"); - eventString += event->Par1; - eventString += '='; - eventString += state; - rulesProcessing(eventString); - } - } - //} + // if (currentStatus.monitor || currentStatus.command || currentStatus.init) { + const int8_t state = Plugin_019_Read(event->Par1); - break; + if ((currentStatus.state != state) || currentStatus.forceMonitor) { + if (!currentStatus.task) { globalMapPortStatus[key].state = state; // do not update state if task flag=1 otherwise it will not be + // picked up by 10xSEC function } + if (currentStatus.monitor) { + globalMapPortStatus[key].forceMonitor = 0; // reset flag + String eventString = F("PCF#"); + eventString += event->Par1; + eventString += '='; + eventString += state; + rulesProcessing(eventString); + } + } + + // } + + break; + } + case PLUGIN_TEN_PER_SECOND: - { - const int8_t state = Plugin_019_Read(CONFIG_PORT); - /**************************************************************************\ - 20181022 - @giig1967g: new doubleclick logic is: - if there is a 'state' change, check debounce period. - Then if doubleclick interval exceeded, reset PCONFIG(7) to 0 - PCONFIG(7) contains the current status for doubleclick: - 0: start counting - 1: 1st click - 2: 2nd click - 3: 3rd click = doubleclick event if inside interval (calculated as: '3rd click time' minus '1st click time') + { + const int8_t state = Plugin_019_Read(CONFIG_PORT); - Returned EVENT value is = 3 always for doubleclick - In rules this can be checked: - on Button#State=3 do //will fire if doubleclick - \**************************************************************************/ - portStatusStruct currentStatus; - const uint32_t key = createKey(PLUGIN_ID_019,CONFIG_PORT); - //WARNING operator [],creates an entry in map if key doesn't exist: - currentStatus = globalMapPortStatus[key]; + /**************************************************************************\ + 20181022 - @giig1967g: new doubleclick logic is: + if there is a 'state' change, check debounce period. + Then if doubleclick interval exceeded, reset PCONFIG(7) to 0 + PCONFIG(7) contains the current status for doubleclick: + 0: start counting + 1: 1st click + 2: 2nd click + 3: 3rd click = doubleclick event if inside interval (calculated as: '3rd click time' minus '1st click time') - //Bug fixed: avoid 10xSEC in case of a non-fully configured device (no port defined yet) - if (state != -1 && CONFIG_PORT>=0) { + Returned EVENT value is = 3 always for doubleclick + In rules this can be checked: + on Button#State=3 do //will fire if doubleclick + \**************************************************************************/ + portStatusStruct currentStatus; + const uint32_t key = createKey(PLUGIN_ID_019, CONFIG_PORT); - //CASE 1: using SafeButton, so wait 1 more 100ms cycle to acknowledge the status change - //QUESTION: MAYBE IT'S BETTER TO WAIT 2 CYCLES?? - if (round(PCONFIG_FLOAT(3)) && state != currentStatus.state && PCONFIG_LONG(3)==0) + // WARNING operator [],creates an entry in map if key doesn't exist: + currentStatus = globalMapPortStatus[key]; + + // Bug fixed: avoid 10xSEC in case of a non-fully configured device (no port defined yet) + if ((state != -1) && (CONFIG_PORT >= 0)) { + // CASE 1: using SafeButton, so wait 1 more 100ms cycle to acknowledge the status change + // QUESTION: MAYBE IT'S BETTER TO WAIT 2 CYCLES?? + if (round(PCONFIG_FLOAT(3)) && (state != currentStatus.state) && (PCONFIG_LONG(3) == 0)) + { + addLog(LOG_LEVEL_DEBUG, F("PCF :SafeButton 1st click.")) + PCONFIG_LONG(3) = 1; + } + + // CASE 2: not using SafeButton, or already waited 1 more 100ms cycle, so proceed. + else if ((state != currentStatus.state) || currentStatus.forceEvent) + { + // Reset forceEvent + currentStatus.forceEvent = 0; + + // Reset SafeButton counter + PCONFIG_LONG(3) = 0; + + // @giig1967g20181022: reset timer for long press + PCONFIG_LONG(2) = millis(); + PCONFIG(6) = false; + + const unsigned long debounceTime = timePassedSince(PCONFIG_LONG(0)); + + if (debounceTime >= (unsigned long)lround(PCONFIG_FLOAT(0))) // de-bounce check { - addLog(LOG_LEVEL_DEBUG,F("PCF :SafeButton 1st click.")) - PCONFIG_LONG(3) = 1; - } - //CASE 2: not using SafeButton, or already waited 1 more 100ms cycle, so proceed. - else if (state != currentStatus.state || currentStatus.forceEvent) - { - //Reset forceEvent - currentStatus.forceEvent = 0; + const unsigned long deltaDC = timePassedSince(PCONFIG_LONG(1)); - // Reset SafeButton counter - PCONFIG_LONG(3) = 0; - - //@giig1967g20181022: reset timer for long press - PCONFIG_LONG(2)=millis(); - PCONFIG(6) = false; - - const unsigned long debounceTime = timePassedSince(PCONFIG_LONG(0)); - if (debounceTime >= (unsigned long)lround(PCONFIG_FLOAT(0))) //de-bounce check + if ((deltaDC >= (unsigned long)lround(PCONFIG_FLOAT(1))) || + (PCONFIG(7) == 3)) { - const unsigned long deltaDC = timePassedSince(PCONFIG_LONG(1)); - if ((deltaDC >= (unsigned long)lround(PCONFIG_FLOAT(1))) || - PCONFIG(7)==3) - { - //reset timer for doubleclick - PCONFIG(7)=0; - PCONFIG_LONG(1)=millis(); - } + // reset timer for doubleclick + PCONFIG(7) = 0; + PCONFIG_LONG(1) = millis(); + } -//just to simplify the reading of the code + // just to simplify the reading of the code #define COUNTER PCONFIG(7) #define DC PCONFIG(4) - //check settings for doubleclick according to the settings - if ( COUNTER!=0 || ( COUNTER==0 && (DC==3 || (DC==1 && state==0) || (DC==2 && state==1))) ) - PCONFIG(7)++; + // check settings for doubleclick according to the settings + if ((COUNTER != 0) || ((COUNTER == 0) && ((DC == 3) || ((DC == 1) && (state == 0)) || ((DC == 2) && (state == 1))))) { + PCONFIG(7)++; + } #undef DC #undef COUNTER - //switchstate[event->TaskIndex] = state; - if (currentStatus.mode == PIN_MODE_OFFLINE || currentStatus.mode == PIN_MODE_UNDEFINED) currentStatus.mode = PIN_MODE_INPUT; //changed from offline to online - currentStatus.state = state; - - byte output_value; - //boolean sendState = switchstate[event->TaskIndex]; - boolean sendState = currentStatus.state; - - if (Settings.TaskDevicePin1Inversed[event->TaskIndex]) - sendState = !sendState; - - if (PCONFIG(7)==3 && PCONFIG(4)>0) - { - output_value = 3; //double click - } else { - output_value = sendState ? 1 : 0; //single click - } - - UserVar[event->BaseVarIndex] = output_value; - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("PCF : Port="); - log += CONFIG_PORT; - log += F(" State="); - log += state; - log += output_value==3 ? F(" Doubleclick=") : F(" Output value="); - log += output_value; - addLog(LOG_LEVEL_INFO, log); - } - event->sensorType = SENSOR_TYPE_SWITCH; - sendData(event); - - //reset Userdata so it displays the correct state value in the web page - UserVar[event->BaseVarIndex] = sendState ? 1 : 0; - - PCONFIG_LONG(0) = millis(); + // switchstate[event->TaskIndex] = state; + if ((currentStatus.mode == PIN_MODE_OFFLINE) || (currentStatus.mode == PIN_MODE_UNDEFINED)) { currentStatus.mode = PIN_MODE_INPUT; // + // changed + // from + // offline + // to + // online } - savePortStatus(key,currentStatus); - } + currentStatus.state = state; -//just to simplify the reading of the code + byte output_value; + + // boolean sendState = switchstate[event->TaskIndex]; + boolean sendState = currentStatus.state; + + if (Settings.TaskDevicePin1Inversed[event->TaskIndex]) { + sendState = !sendState; + } + + if ((PCONFIG(7) == 3) && (PCONFIG(4) > 0)) + { + output_value = 3; // double click + } else { + output_value = sendState ? 1 : 0; // single click + } + + UserVar[event->BaseVarIndex] = output_value; + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = F("PCF : Port="); + log += CONFIG_PORT; + log += F(" State="); + log += state; + log += output_value == 3 ? F(" Doubleclick=") : F(" Output value="); + log += output_value; + addLog(LOG_LEVEL_INFO, log); + } + event->sensorType = SENSOR_TYPE_SWITCH; + sendData(event); + + // reset Userdata so it displays the correct state value in the web page + UserVar[event->BaseVarIndex] = sendState ? 1 : 0; + + PCONFIG_LONG(0) = millis(); + } + savePortStatus(key, currentStatus); + } + + // just to simplify the reading of the code #define LP PCONFIG(5) #define FIRED PCONFIG(6) - //check if LP is enabled and if LP has not fired yet - else if (!FIRED && (LP==3 ||(LP==1 && state==0)||(LP==2 && state==1) ) ) { - + // check if LP is enabled and if LP has not fired yet + else if (!FIRED && ((LP == 3) || ((LP == 1) && (state == 0)) || ((LP == 2) && (state == 1)))) { #undef LP #undef FIRED - /**************************************************************************\ - 20181022 - @giig1967g: new longpress logic is: - if there is no 'state' change, check if longpress interval reached - When reached send longpress event. - Returned Event value = state + 10 - So if state = 0 => EVENT longpress = 10 - if state = 1 => EVENT longpress = 11 - So we can trigger longpress for high or low contact - In rules this can be checked: - on Button#State=10 do //will fire if longpress when state = 0 - on Button#State=11 do //will fire if longpress when state = 1 - \**************************************************************************/ + /**************************************************************************\ + 20181022 - @giig1967g: new longpress logic is: + if there is no 'state' change, check if longpress interval reached + When reached send longpress event. + Returned Event value = state + 10 + So if state = 0 => EVENT longpress = 10 + if state = 1 => EVENT longpress = 11 + So we can trigger longpress for high or low contact + + In rules this can be checked: + on Button#State=10 do //will fire if longpress when state = 0 + on Button#State=11 do //will fire if longpress when state = 1 + \**************************************************************************/ + + // Reset SafeButton counter + PCONFIG_LONG(3) = 0; + + const unsigned long deltaLP = timePassedSince(PCONFIG_LONG(2)); + + if (deltaLP >= (unsigned long)lround(PCONFIG_FLOAT(2))) + { + byte output_value; + PCONFIG(6) = true; // fired = true + + boolean sendState = state; + + if (Settings.TaskDevicePin1Inversed[event->TaskIndex]) { + sendState = !sendState; + } + + output_value = sendState ? 1 : 0; + output_value = output_value + 10; + + UserVar[event->BaseVarIndex] = output_value; + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = F("PCF : LongPress: Port= "); + log += CONFIG_PORT; + log += F(" State="); + log += state ? '1' : '0'; + log += F(" Output value="); + log += output_value; + addLog(LOG_LEVEL_INFO, log); + } + sendData(event); + + // reset Userdata so it displays the correct state value in the web page + UserVar[event->BaseVarIndex] = sendState ? 1 : 0; + } + } else { + if (PCONFIG_LONG(3) == 1) { // Safe Button detected. Send EVENT value = 4 // Reset SafeButton counter PCONFIG_LONG(3) = 0; - const unsigned long deltaLP = timePassedSince(PCONFIG_LONG(2)); - if (deltaLP >= (unsigned long)lround(PCONFIG_FLOAT(2))) - { - byte output_value; - PCONFIG(6) = true; //fired = true + // Create EVENT with value = 4 for SafeButton false positive detection + const int tempUserVar = round(UserVar[event->BaseVarIndex]); + UserVar[event->BaseVarIndex] = 4; - boolean sendState = state; - if (Settings.TaskDevicePin1Inversed[event->TaskIndex]) - sendState = !sendState; - - output_value = sendState ? 1 : 0; - output_value = output_value + 10; - - UserVar[event->BaseVarIndex] = output_value; - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("PCF : LongPress: Port= "); - log += CONFIG_PORT; - log += F(" State="); - log += state ? '1' : '0'; - log += F(" Output value="); - log += output_value; - addLog(LOG_LEVEL_INFO, log); - } - sendData(event); - - //reset Userdata so it displays the correct state value in the web page - UserVar[event->BaseVarIndex] = sendState ? 1 : 0; + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = F("PCF : SafeButton: false positive detected. GPIO= "); + log += CONFIG_PIN1; + log += F(" State="); + log += tempUserVar; + addLog(LOG_LEVEL_INFO, log); } - } else { - if (PCONFIG_LONG(3)==1) { //Safe Button detected. Send EVENT value = 4 - // Reset SafeButton counter - PCONFIG_LONG(3) = 0; + sendData(event); - //Create EVENT with value = 4 for SafeButton false positive detection - const int tempUserVar = round(UserVar[event->BaseVarIndex]); - UserVar[event->BaseVarIndex] = 4; - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("PCF : SafeButton: false positive detected. GPIO= "); - log += CONFIG_PIN1; - log += F(" State="); - log += tempUserVar; - addLog(LOG_LEVEL_INFO, log); - } - sendData(event); - - //reset Userdata so it displays the correct state value in the web page - UserVar[event->BaseVarIndex] = tempUserVar; - } + // reset Userdata so it displays the correct state value in the web page + UserVar[event->BaseVarIndex] = tempUserVar; } - } else if (state != currentStatus.state && state == -1) { - //set UserVar and switchState = -1 and send EVENT to notify user - UserVar[event->BaseVarIndex] = state; - //switchstate[event->TaskIndex] = state; - currentStatus.state = state; - currentStatus.mode = PIN_MODE_OFFLINE; - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("PCF : Port="); - log += CONFIG_PORT; - log += F(" is offline (EVENT= -1)"); - addLog(LOG_LEVEL_INFO, log); - } - sendData(event); - savePortStatus(key,currentStatus); } - success = true; - break; - } + } else if ((state != currentStatus.state) && (state == -1)) { + // set UserVar and switchState = -1 and send EVENT to notify user + UserVar[event->BaseVarIndex] = state; - //giig1967g: Added EXIT function - case PLUGIN_EXIT: - { - removeTaskFromPort(createKey(PLUGIN_ID_019,CONFIG_PORT)); - break; - } + // switchstate[event->TaskIndex] = state; + currentStatus.state = state; + currentStatus.mode = PIN_MODE_OFFLINE; - case PLUGIN_READ: - { - // We do not actually read the pin state as this is already done 10x/second - // Instead we just send the last known state stored in Uservar if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("PCF : Port= "); + String log = F("PCF : Port="); log += CONFIG_PORT; - log += F(" State="); - log += UserVar[event->BaseVarIndex]; + log += F(" is offline (EVENT= -1)"); addLog(LOG_LEVEL_INFO, log); } - success = true; - break; + sendData(event); + savePortStatus(key, currentStatus); } + success = true; + break; + } + + // giig1967g: Added EXIT function + case PLUGIN_EXIT: + { + removeTaskFromPort(createKey(PLUGIN_ID_019, CONFIG_PORT)); + break; + } + + case PLUGIN_READ: + { + // We do not actually read the pin state as this is already done 10x/second + // Instead we just send the last known state stored in Uservar + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = F("PCF : Port= "); + log += CONFIG_PORT; + log += F(" State="); + log += UserVar[event->BaseVarIndex]; + addLog(LOG_LEVEL_INFO, log); + } + success = true; + break; + } case PLUGIN_REQUEST: - { - //parseString(string, 1) = device - //parseString(string, 2) = command - //parseString(string, 3) = gpio number + { + // parseString(string, 1) = device + // parseString(string, 2) = command + // parseString(string, 3) = gpio number - // returns pin value using syntax: [plugin#pcfgpio#pinstate#xx] - if (string.length()>=16 && string.substring(0,16).equalsIgnoreCase(F("pcfgpio,pinstate"))) - { - int par1; - if (validIntFromString(parseString(string, 3), par1)) { - string = Plugin_019_Read(par1); - } - success = true; + // returns pin value using syntax: [plugin#pcfgpio#pinstate#xx] + if ((string.length() >= 16) && string.substring(0, 16).equalsIgnoreCase(F("pcfgpio,pinstate"))) + { + int par1; + + if (validIntFromString(parseString(string, 3), par1)) { + string = Plugin_019_Read(par1); } - break; + success = true; } + break; + } case PLUGIN_WRITE: + { + String log = ""; + String command = parseString(string, 1); + + if (command == F("pcfgpio")) { - String log = ""; - String command = parseString(string, 1); + success = true; - if (command == F("pcfgpio")) + if ((event->Par1 > 0) && (event->Par1 <= 128)) { - success = true; - if (event->Par1 > 0 && event->Par1 <= 128) - { - portStatusStruct tempStatus; - const uint32_t key = createKey(PLUGIN_ID_019,event->Par1); - // WARNING: operator [] creates an entry in the map if key does not exist - // So the next command should be part of each command: - tempStatus = globalMapPortStatus[key]; - - const int8_t currentState = Plugin_019_Read(event->Par1); - - if (currentState == -1) { - tempStatus.mode=PIN_MODE_OFFLINE; - tempStatus.state=-1; - tempStatus.command=1; //set to 1 in order to display the status in the PinStatus page - tempStatus.forceEvent=1; - if (tempStatus.monitor) tempStatus.forceMonitor=1; //set to 1 in order to force an EVENT in case monitor is requested - savePortStatus(key,tempStatus); - log = String(F("PCF : GPIO ")) + String(event->Par1) + String(F(" is offline (-1). Cannot set value.")); - } else if (event->Par2 == 2) { //INPUT - // PCF8574 specific: only can read 0/low state, so we must send 1 - //setPinState(PLUGIN_ID_019, event->Par1, PIN_MODE_INPUT, 1); - tempStatus.mode=PIN_MODE_INPUT; - tempStatus.state = currentState; - tempStatus.command=1; //set to 1 in order to display the status in the PinStatus page - tempStatus.forceEvent=1; - if (tempStatus.monitor) tempStatus.forceMonitor=1; //set to 1 in order to force an EVENT in case monitor is requested - savePortStatus(key,tempStatus); - Plugin_019_Write(event->Par1,1); - log = String(F("PCF : GPIO INPUT ")) + String(event->Par1) + String(F(" Set to 1")); - } else { // OUTPUT - //setPinState(PLUGIN_ID_019, event->Par1, PIN_MODE_OUTPUT, event->Par2); - tempStatus.mode=PIN_MODE_OUTPUT; - tempStatus.state=event->Par2; - tempStatus.command=1; //set to 1 in order to display the status in the PinStatus page - tempStatus.forceEvent=1; - if (tempStatus.monitor) tempStatus.forceMonitor=1; //set to 1 in order to force an EVENT in case monitor is requested - savePortStatus(key,tempStatus); - Plugin_019_Write(event->Par1, event->Par2); - log = String(F("PCF : GPIO OUTPUT ")) + String(event->Par1) + String(F(" Set to ")) + String(event->Par2); - } - addLog(LOG_LEVEL_INFO, log); - SendStatusOnlyIfNeeded(event->Source, SEARCH_PIN_STATE, key, log, 0); - //SendStatus(event->Source, getPinStateJSON(SEARCH_PIN_STATE, PLUGIN_ID_019, event->Par1, log, 0)); - } - } else if (command == F("pcfgpiotoggle")) { - success = true; - if (event->Par1 > 0 && event->Par1 <= 128) - { - portStatusStruct tempStatus; - const uint32_t key = createKey(PLUGIN_ID_019,event->Par1); - // WARNING: operator [] creates an entry in the map if key does not exist - // So the next command should be part of each command: - tempStatus = globalMapPortStatus[key]; - const int8_t currentState = Plugin_019_Read(event->Par1); - bool needToSave = false; - - if (currentState == -1) { - tempStatus.mode=PIN_MODE_OFFLINE; - tempStatus.state=-1; - tempStatus.command=1; //set to 1 in order to display the status in the PinStatus page - tempStatus.forceEvent=1; - if (tempStatus.monitor) tempStatus.forceMonitor=1; //set to 1 in order to force an EVENT in case monitor is requested - savePortStatus(key,tempStatus); - log = String(F("PCF : GPIO ")) + String(event->Par1) + String(F(" is offline (-1). Cannot set value.")); - needToSave = true; - } else if (tempStatus.mode == PIN_MODE_OUTPUT || tempStatus.mode == PIN_MODE_UNDEFINED) { //toggle only output pins - tempStatus.state = !currentState; //toggle current state value - tempStatus.mode = PIN_MODE_OUTPUT; - tempStatus.command=1; //set to 1 in order to display the status in the PinStatus page - tempStatus.forceEvent=1; - if (tempStatus.monitor) tempStatus.forceMonitor=1; //set to 1 in order to force an EVENT in case monitor is requested - savePortStatus(key,tempStatus); - Plugin_019_Write(event->Par1, tempStatus.state); - log = String(F("PCF : Toggle GPIO ")) + String(event->Par1) + String(F(" Set to ")) + String(tempStatus.state); - needToSave = true; - } - if (needToSave) { - //setPinState(PLUGIN_ID_019, event->Par1, PIN_MODE_OUTPUT, !currentState); - addLog(LOG_LEVEL_INFO, log); - //SendStatus(event->Source, getPinStateJSON(SEARCH_PIN_STATE, PLUGIN_ID_019, event->Par1, log, 0)); - SendStatusOnlyIfNeeded(event->Source, SEARCH_PIN_STATE, key, log, 0); - } - } - } else if (command == F("pcfpulse")) { - success = true; - if (event->Par1 > 0 && event->Par1 <= 128) - { - portStatusStruct tempStatus; - const uint32_t key = createKey(PLUGIN_ID_019,event->Par1); - // WARNING: operator [] creates an entry in the map if key does not exist - // So the next command should be part of each command: - tempStatus = globalMapPortStatus[key]; - - //setPinState(PLUGIN_ID_019, event->Par1, PIN_MODE_OUTPUT, event->Par2); - tempStatus.mode = PIN_MODE_OUTPUT; - tempStatus.state = event->Par2; - savePortStatus(key,tempStatus); - Plugin_019_Write(event->Par1, event->Par2); - delay(event->Par3); - - tempStatus.mode = PIN_MODE_OUTPUT; - tempStatus.state = !event->Par2; - tempStatus.command=1; //set to 1 in order to display the status in the PinStatus page - savePortStatus(key,tempStatus); - Plugin_019_Write(event->Par1, !event->Par2); - //setPinState(PLUGIN_ID_019, event->Par1, PIN_MODE_OUTPUT, !event->Par2); - - log = String(F("PCF : GPIO ")) + String(event->Par1) + String(F(" Pulsed for ")) + String(event->Par3) + String(F(" mS")); - addLog(LOG_LEVEL_INFO, log); - //SendStatus(event->Source, getPinStateJSON(SEARCH_PIN_STATE, PLUGIN_ID_019, event->Par1, log, 0)); - SendStatusOnlyIfNeeded(event->Source, SEARCH_PIN_STATE, key, log, 0); - } - } else if (command == F("pcflongpulse")) { - success = true; - if (event->Par1 > 0 && event->Par1 <= 128) - { - portStatusStruct tempStatus; - const uint32_t key = createKey(PLUGIN_ID_019,event->Par1); - // WARNING: operator [] creates an entry in the map if key does not exist - // So the next command should be part of each command: - tempStatus = globalMapPortStatus[key]; - - //setPinState(PLUGIN_ID_019, event->Par1, PIN_MODE_OUTPUT, event->Par2); - tempStatus.mode = PIN_MODE_OUTPUT; - tempStatus.state = event->Par2; - tempStatus.command=1; //set to 1 in order to display the status in the PinStatus page - (tempStatus.monitor) ? tempStatus.forceMonitor = 1 : tempStatus.forceMonitor = 0; - savePortStatus(key,tempStatus); - Plugin_019_Write(event->Par1, event->Par2); - //Scheduler.setPluginTaskTimer(event->Par3 * 1000, event->TaskIndex, event->Par1, !event->Par2); //Calls PLUGIN_TIMER_IN - Scheduler.setPluginTimer(event->Par3 * 1000, PLUGIN_ID_019, event->Par1, !event->Par2); //Calls PLUGIN_TIMER_IN - log = String(F("PCF : GPIO ")) + String(event->Par1) + String(F(" Pulse set for ")) + String(event->Par3) + String(F(" S")); - addLog(LOG_LEVEL_INFO, log); - //SendStatus(event->Source, getPinStateJSON(SEARCH_PIN_STATE, PLUGIN_ID_019, event->Par1, log, 0)); - SendStatusOnlyIfNeeded(event->Source, SEARCH_PIN_STATE, key, log, 0); - } - } else if (command == F("status")) { - if (parseString(string, 2) == F("pcf")) - { - success = true; - const uint32_t key = createKey(PLUGIN_ID_019,event->Par2); //WARNING: 'status' uses Par2 instead of Par1 - - if (existPortStatus(key)) // has been set as output - SendStatusOnlyIfNeeded(event->Source, SEARCH_PIN_STATE, key, dummyString, 0); - else - { - const int8_t state = Plugin_019_Read(event->Par2); // report as input - if (state != -1) - SendStatusOnlyIfNeeded(event->Source, NO_SEARCH_PIN_STATE, key, dummyString, state); - } - } - } else if (command == F("monitor")) { - if (parseString(string, 2) == F("pcf")) - { - success = true; - const uint32_t key = createKey(PLUGIN_ID_019,event->Par2); //WARNING: 'monitor' uses Par2 instead of Par1 - - addMonitorToPort(key); - //giig1967g: Comment next line to receive an EVENT just after calling the monitor command - globalMapPortStatus[key].state = Plugin_019_Read(event->Par2); //set initial value to avoid an event just after calling the command - - log = String(F("PCF : PORT ")) + String(event->Par2) + String(F(" added to monitor list.")); - addLog(LOG_LEVEL_INFO, log); - SendStatusOnlyIfNeeded(event->Source, SEARCH_PIN_STATE, key, dummyString, 0); - } - } else if (command == F("unmonitor")) { - if (parseString(string, 2) == F("pcf")) - { - success = true; - const uint32_t key = createKey(PLUGIN_ID_019,event->Par2); //WARNING: 'monitor' uses Par2 instead of Par1 - SendStatusOnlyIfNeeded(event->Source, SEARCH_PIN_STATE, key, dummyString, 0); - - removeMonitorFromPort(key); - log = String(F("PCF : PORT ")) + String(event->Par2) + String(F(" removed from monitor list.")); - addLog(LOG_LEVEL_INFO, log); - } - } - break; - } - - case PLUGIN_TIMER_IN: - { - //setPinState(PLUGIN_ID_019, event->Par1, PIN_MODE_OUTPUT, event->Par2); - portStatusStruct tempStatus; - // WARNING: operator [] creates an entry in the map if key does not exist - const uint32_t key = createKey(PLUGIN_ID_019,event->Par1); - tempStatus = globalMapPortStatus[key]; - - tempStatus.state = event->Par2; - tempStatus.mode = PIN_MODE_OUTPUT; - (tempStatus.monitor) ? tempStatus.forceMonitor = 1 : tempStatus.forceMonitor = 0; //added to send event for longpulse command - savePortStatus(key,tempStatus); - Plugin_019_Write(event->Par1, event->Par2); - - break; - } - - case PLUGIN_ONLY_TIMER_IN: - { - //setPinState(PLUGIN_ID_019, event->Par1, PIN_MODE_OUTPUT, event->Par2); portStatusStruct tempStatus; + const uint32_t key = createKey(PLUGIN_ID_019, event->Par1); + // WARNING: operator [] creates an entry in the map if key does not exist - const uint32_t key = createKey(PLUGIN_ID_019,event->Par1); + // So the next command should be part of each command: tempStatus = globalMapPortStatus[key]; + const int8_t currentState = Plugin_019_Read(event->Par1); + + if (currentState == -1) { + tempStatus.mode = PIN_MODE_OFFLINE; + tempStatus.state = -1; + tempStatus.command = 1; // set to 1 in order to display the status in the PinStatus page + tempStatus.forceEvent = 1; + + if (tempStatus.monitor) { tempStatus.forceMonitor = 1; // set to 1 in order to force an EVENT in case monitor is requested + } + savePortStatus(key, tempStatus); + log = String(F("PCF : GPIO ")) + String(event->Par1) + String(F(" is offline (-1). Cannot set value.")); + } else if (event->Par2 == 2) { // INPUT + // PCF8574 specific: only can read 0/low state, so we must send 1 + // setPinState(PLUGIN_ID_019, event->Par1, PIN_MODE_INPUT, 1); + tempStatus.mode = PIN_MODE_INPUT; + tempStatus.state = currentState; + tempStatus.command = 1; // set to 1 in order to display the status in the PinStatus page + tempStatus.forceEvent = 1; + + if (tempStatus.monitor) { tempStatus.forceMonitor = 1; // set to 1 in order to force an EVENT in case monitor is requested + } + savePortStatus(key, tempStatus); + Plugin_019_Write(event->Par1, 1); + log = String(F("PCF : GPIO INPUT ")) + String(event->Par1) + String(F(" Set to 1")); + } else { // OUTPUT + // setPinState(PLUGIN_ID_019, event->Par1, PIN_MODE_OUTPUT, event->Par2); + tempStatus.mode = PIN_MODE_OUTPUT; + tempStatus.state = event->Par2; + tempStatus.command = 1; // set to 1 in order to display the status in the PinStatus page + tempStatus.forceEvent = 1; + + if (tempStatus.monitor) { tempStatus.forceMonitor = 1; // set to 1 in order to force an EVENT in case monitor is requested + } + savePortStatus(key, tempStatus); + Plugin_019_Write(event->Par1, event->Par2); + log = String(F("PCF : GPIO OUTPUT ")) + String(event->Par1) + String(F(" Set to ")) + String(event->Par2); + } + addLog(LOG_LEVEL_INFO, log); + SendStatusOnlyIfNeeded(event->Source, SEARCH_PIN_STATE, key, log, 0); + + // SendStatus(event->Source, getPinStateJSON(SEARCH_PIN_STATE, PLUGIN_ID_019, event->Par1, log, 0)); + } + } else if (command == F("pcfgpiotoggle")) { + success = true; + + if ((event->Par1 > 0) && (event->Par1 <= 128)) + { + portStatusStruct tempStatus; + const uint32_t key = createKey(PLUGIN_ID_019, event->Par1); + + // WARNING: operator [] creates an entry in the map if key does not exist + // So the next command should be part of each command: + tempStatus = globalMapPortStatus[key]; + const int8_t currentState = Plugin_019_Read(event->Par1); + bool needToSave = false; + + if (currentState == -1) { + tempStatus.mode = PIN_MODE_OFFLINE; + tempStatus.state = -1; + tempStatus.command = 1; // set to 1 in order to display the status in the PinStatus page + tempStatus.forceEvent = 1; + + if (tempStatus.monitor) { tempStatus.forceMonitor = 1; // set to 1 in order to force an EVENT in case monitor is requested + } + savePortStatus(key, tempStatus); + log = String(F("PCF : GPIO ")) + String(event->Par1) + String(F(" is offline (-1). Cannot set value.")); + needToSave = true; + } else if ((tempStatus.mode == PIN_MODE_OUTPUT) || (tempStatus.mode == PIN_MODE_UNDEFINED)) { // toggle only output pins + tempStatus.state = !currentState; // toggle current state value + tempStatus.mode = PIN_MODE_OUTPUT; + tempStatus.command = 1; // set to 1 in order to display the + // status in the PinStatus page + tempStatus.forceEvent = 1; + + if (tempStatus.monitor) { tempStatus.forceMonitor = 1; // set to 1 in order to force an + // EVENT in case monitor is + // requested + } + savePortStatus(key, tempStatus); + Plugin_019_Write(event->Par1, tempStatus.state); + log = String(F("PCF : Toggle GPIO ")) + String(event->Par1) + String(F(" Set to ")) + String(tempStatus.state); + needToSave = true; + } + + if (needToSave) { + // setPinState(PLUGIN_ID_019, event->Par1, PIN_MODE_OUTPUT, !currentState); + addLog(LOG_LEVEL_INFO, log); + + // SendStatus(event->Source, getPinStateJSON(SEARCH_PIN_STATE, PLUGIN_ID_019, event->Par1, log, 0)); + SendStatusOnlyIfNeeded(event->Source, SEARCH_PIN_STATE, key, log, 0); + } + } + } else if (command == F("pcfpulse")) { + success = true; + + if ((event->Par1 > 0) && (event->Par1 <= 128)) + { + portStatusStruct tempStatus; + const uint32_t key = createKey(PLUGIN_ID_019, event->Par1); + + // WARNING: operator [] creates an entry in the map if key does not exist + // So the next command should be part of each command: + tempStatus = globalMapPortStatus[key]; + + // setPinState(PLUGIN_ID_019, event->Par1, PIN_MODE_OUTPUT, event->Par2); + tempStatus.mode = PIN_MODE_OUTPUT; tempStatus.state = event->Par2; - tempStatus.mode = PIN_MODE_OUTPUT; - (tempStatus.monitor) ? tempStatus.forceMonitor = 1 : tempStatus.forceMonitor = 0; //added to send event for longpulse command - savePortStatus(key,tempStatus); + savePortStatus(key, tempStatus); + Plugin_019_Write(event->Par1, event->Par2); + delay(event->Par3); + + tempStatus.mode = PIN_MODE_OUTPUT; + tempStatus.state = !event->Par2; + tempStatus.command = 1; // set to 1 in order to display the status in the PinStatus page + savePortStatus(key, tempStatus); + Plugin_019_Write(event->Par1, !event->Par2); + + // setPinState(PLUGIN_ID_019, event->Par1, PIN_MODE_OUTPUT, !event->Par2); + + log = String(F("PCF : GPIO ")) + String(event->Par1) + String(F(" Pulsed for ")) + String(event->Par3) + String(F(" mS")); + addLog(LOG_LEVEL_INFO, log); + + // SendStatus(event->Source, getPinStateJSON(SEARCH_PIN_STATE, PLUGIN_ID_019, event->Par1, log, 0)); + SendStatusOnlyIfNeeded(event->Source, SEARCH_PIN_STATE, key, log, 0); + } + } else if (command == F("pcflongpulse")) { + success = true; + + if ((event->Par1 > 0) && (event->Par1 <= 128)) + { + portStatusStruct tempStatus; + const uint32_t key = createKey(PLUGIN_ID_019, event->Par1); + + // WARNING: operator [] creates an entry in the map if key does not exist + // So the next command should be part of each command: + tempStatus = globalMapPortStatus[key]; + + // setPinState(PLUGIN_ID_019, event->Par1, PIN_MODE_OUTPUT, event->Par2); + tempStatus.mode = PIN_MODE_OUTPUT; + tempStatus.state = event->Par2; + tempStatus.command = 1; // set to 1 in order to display the status in the PinStatus page + (tempStatus.monitor) ? tempStatus.forceMonitor = 1 : tempStatus.forceMonitor = 0; + savePortStatus(key, tempStatus); Plugin_019_Write(event->Par1, event->Par2); - break; + // Scheduler.setPluginTaskTimer(event->Par3 * 1000, event->TaskIndex, event->Par1, !event->Par2); //Calls PLUGIN_TIMER_IN + Scheduler.setPluginTimer(event->Par3 * 1000, PLUGIN_ID_019, event->Par1, !event->Par2); // Calls PLUGIN_TIMER_IN + log = String(F("PCF : GPIO ")) + String(event->Par1) + String(F(" Pulse set for ")) + String(event->Par3) + String(F(" S")); + addLog(LOG_LEVEL_INFO, log); + + // SendStatus(event->Source, getPinStateJSON(SEARCH_PIN_STATE, PLUGIN_ID_019, event->Par1, log, 0)); + SendStatusOnlyIfNeeded(event->Source, SEARCH_PIN_STATE, key, log, 0); } + } else if (command == F("status")) { + if (parseString(string, 2) == F("pcf")) + { + success = true; + const uint32_t key = createKey(PLUGIN_ID_019, event->Par2); // WARNING: 'status' uses Par2 instead of Par1 + + if (existPortStatus(key)) { // has been set as output + SendStatusOnlyIfNeeded(event->Source, SEARCH_PIN_STATE, key, dummyString, 0); + } + else + { + const int8_t state = Plugin_019_Read(event->Par2); // report as input + + if (state != -1) { + SendStatusOnlyIfNeeded(event->Source, NO_SEARCH_PIN_STATE, key, dummyString, state); + } + } + } + } else if (command == F("monitor")) { + if (parseString(string, 2) == F("pcf")) + { + success = true; + const uint32_t key = createKey(PLUGIN_ID_019, event->Par2); // WARNING: 'monitor' uses Par2 instead of Par1 + + addMonitorToPort(key); + + // giig1967g: Comment next line to receive an EVENT just after calling the monitor command + globalMapPortStatus[key].state = Plugin_019_Read(event->Par2); // set initial value to avoid an event just after calling the + // command + + log = String(F("PCF : PORT ")) + String(event->Par2) + String(F(" added to monitor list.")); + addLog(LOG_LEVEL_INFO, log); + SendStatusOnlyIfNeeded(event->Source, SEARCH_PIN_STATE, key, dummyString, 0); + } + } else if (command == F("unmonitor")) { + if (parseString(string, 2) == F("pcf")) + { + success = true; + const uint32_t key = createKey(PLUGIN_ID_019, event->Par2); // WARNING: 'monitor' uses Par2 instead of Par1 + SendStatusOnlyIfNeeded(event->Source, SEARCH_PIN_STATE, key, dummyString, 0); + + removeMonitorFromPort(key); + log = String(F("PCF : PORT ")) + String(event->Par2) + String(F(" removed from monitor list.")); + addLog(LOG_LEVEL_INFO, log); + } + } + break; + } + + case PLUGIN_TIMER_IN: + { + // setPinState(PLUGIN_ID_019, event->Par1, PIN_MODE_OUTPUT, event->Par2); + portStatusStruct tempStatus; + + // WARNING: operator [] creates an entry in the map if key does not exist + const uint32_t key = createKey(PLUGIN_ID_019, event->Par1); + tempStatus = globalMapPortStatus[key]; + + tempStatus.state = event->Par2; + tempStatus.mode = PIN_MODE_OUTPUT; + (tempStatus.monitor) ? tempStatus.forceMonitor = 1 : tempStatus.forceMonitor = 0; // added to send event for longpulse command + savePortStatus(key, tempStatus); + Plugin_019_Write(event->Par1, event->Par2); + + break; + } + + case PLUGIN_ONLY_TIMER_IN: + { + // setPinState(PLUGIN_ID_019, event->Par1, PIN_MODE_OUTPUT, event->Par2); + portStatusStruct tempStatus; + + // WARNING: operator [] creates an entry in the map if key does not exist + const uint32_t key = createKey(PLUGIN_ID_019, event->Par1); + tempStatus = globalMapPortStatus[key]; + + tempStatus.state = event->Par2; + tempStatus.mode = PIN_MODE_OUTPUT; + (tempStatus.monitor) ? tempStatus.forceMonitor = 1 : tempStatus.forceMonitor = 0; // added to send event for longpulse command + savePortStatus(key, tempStatus); + Plugin_019_Write(event->Par1, event->Par2); + + break; + } } return success; } - -//******************************************************************************** +// ******************************************************************************** // PCF8574 read -//******************************************************************************** -//@giig1967g-20181023: changed to int8_t +// ******************************************************************************** +// @giig1967g-20181023: changed to int8_t int8_t Plugin_019_Read(byte Par1) { - int8_t state = -1; - byte unit = (Par1 - 1) / 8; - byte port = Par1 - (unit * 8); + int8_t state = -1; + byte unit = (Par1 - 1) / 8; + byte port = Par1 - (unit * 8); uint8_t address = 0x20 + unit; - if (unit > 7) address += 0x10; + + if (unit > 7) { address += 0x10; } // get the current pin status Wire.requestFrom(address, (uint8_t)0x1); + if (Wire.available()) { state = ((Wire.read() & _BV(port - 1)) >> (port - 1)); @@ -762,43 +843,49 @@ uint8_t Plugin_019_ReadAllPins(uint8_t address) uint8_t rawState = 0; Wire.requestFrom(address, (uint8_t)0x1); + if (Wire.available()) { - rawState =Wire.read(); + rawState = Wire.read(); } return rawState; } -//******************************************************************************** +// ******************************************************************************** // PCF8574 write -//******************************************************************************** +// ******************************************************************************** boolean Plugin_019_Write(byte Par1, byte Par2) { - uint8_t unit = (Par1 - 1) / 8; - uint8_t port = Par1 - (unit * 8); + uint8_t unit = (Par1 - 1) / 8; + uint8_t port = Par1 - (unit * 8); uint8_t address = 0x20 + unit; - if (unit > 7) address += 0x10; - //generate bitmask - int i = 0; + if (unit > 7) { address += 0x10; } + + // generate bitmask + int i = 0; uint8_t portmask = 255; + unit = unit * 8 + 1; // calculate first pin uint32_t key; - for(i=0; i<8; i++){ - key = createKey(PLUGIN_ID_019,unit+i); + for (i = 0; i < 8; i++) { + key = createKey(PLUGIN_ID_019, unit + i); - if (existPortStatus(key) && globalMapPortStatus[key].mode == PIN_MODE_OUTPUT && globalMapPortStatus[key].state == 0) - portmask &= ~(1 << i); //set port i = 0 + if (existPortStatus(key) && (globalMapPortStatus[key].mode == PIN_MODE_OUTPUT) && (globalMapPortStatus[key].state == 0)) { + portmask &= ~(1 << i); // set port i = 0 + } } - key = createKey(PLUGIN_ID_019,Par1); + key = createKey(PLUGIN_ID_019, Par1); - if (Par2 == 1) - portmask |= (1 << (port-1)); - else - portmask &= ~(1 << (port-1)); + if (Par2 == 1) { + portmask |= (1 << (port - 1)); + } + else { + portmask &= ~(1 << (port - 1)); + } Wire.beginTransmission(address); Wire.write(portmask); @@ -806,4 +893,5 @@ boolean Plugin_019_Write(byte Par1, byte Par2) return true; } + #endif // USES_P019 diff --git a/src/_P022_PCA9685.ino b/src/_P022_PCA9685.ino index 50ffa7cdf..c364549e8 100644 --- a/src/_P022_PCA9685.ino +++ b/src/_P022_PCA9685.ino @@ -66,7 +66,6 @@ bool p022_clear_init(uint8_t address) { return true; } - boolean Plugin_022(byte function, struct EventStruct *event, String& string) { boolean success = false; @@ -189,6 +188,7 @@ boolean Plugin_022(byte function, struct EventStruct *event, String& string) if (!p022_is_init(CONFIG_PORT)) { Plugin_022_initialize(address); + if (PCONFIG(0) != mode2) { Plugin_022_writeRegister(address, PCA9685_MODE2, PCONFIG(0)); } @@ -449,11 +449,11 @@ boolean Plugin_022(byte function, struct EventStruct *event, String& string) } } Scheduler.setPluginTaskTimer(event->Par3 - , event->TaskIndex - , event->Par1 - , !event->Par2 - , event->Par3 - , autoreset); + , event->TaskIndex + , event->Par1 + , !event->Par2 + , event->Par3 + , autoreset); // setPinState(PLUGIN_ID_022, event->Par1, PIN_MODE_OUTPUT, event->Par2); portStatusStruct newStatus; @@ -503,11 +503,11 @@ boolean Plugin_022(byte function, struct EventStruct *event, String& string) autoreset--; } Scheduler.setPluginTaskTimer(event->Par3 - , event->TaskIndex - , event->Par1 - , !event->Par2 - , event->Par3 - , autoreset); + , event->TaskIndex + , event->Par1 + , !event->Par2 + , event->Par3 + , autoreset); } // setPinState(PLUGIN_ID_022, event->Par1, PIN_MODE_OUTPUT, event->Par2); @@ -592,10 +592,12 @@ void Plugin_022_Frequency(int address, uint16_t freq) // prescale = 25000000 / 4096; uint16_t prescale = 6103; + prescale /= freq; prescale -= 1; uint8_t oldmode = Plugin_022_readRegister(i2cAddress, 0); uint8_t newmode = (oldmode & 0x7f) | 0x10; + Plugin_022_writeRegister(i2cAddress, PLUGIN_022_PCA9685_MODE1, (byte)newmode); Plugin_022_writeRegister(i2cAddress, 0xfe, (byte)prescale); // prescale register Plugin_022_writeRegister(i2cAddress, PLUGIN_022_PCA9685_MODE1, (byte)oldmode); diff --git a/src/_P023_OLED.ino b/src/_P023_OLED.ino index 7b7278103..0ce553845 100644 --- a/src/_P023_OLED.ino +++ b/src/_P023_OLED.ino @@ -1,7 +1,8 @@ #ifdef USES_P023 -//####################################################################################################### -//#################################### Plugin 023: OLED SSD1306 display ################################# -//####################################################################################################### + +// ####################################################################################################### +// #################################### Plugin 023: OLED SSD1306 display ################################# +// ####################################################################################################### #include "_Plugin_Helper.h" @@ -17,13 +18,14 @@ #define PLUGIN_VALUENAME1_023 "OLED" #define PLUGIN_023_MAX_DYSPALY 2 -#define P23_Nlines 8 // The number of different lines which can be displayed +#define P23_Nlines 8 // The number of different lines which can be displayed #define P23_Nchars 64 struct Plugin_023_OLED_SettingStruct { - Plugin_023_OLED_SettingStruct(): address(0) - , type(0),font_width(0),displayTimer(0){} + Plugin_023_OLED_SettingStruct() : address(0) + , type(0), font_width(0), displayTimer(0) {} + byte address; byte type; byte font_width; @@ -49,468 +51,485 @@ boolean Plugin_023(byte function, struct EventStruct *event, String& string) switch (function) { - case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_023; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].VType = SENSOR_TYPE_NONE; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = false; - Device[deviceCount].ValueCount = 0; - Device[deviceCount].SendDataOption = false; - Device[deviceCount].TimerOption = true; - break; - } + { + Device[++deviceCount].Number = PLUGIN_ID_023; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = SENSOR_TYPE_NONE; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = false; + Device[deviceCount].ValueCount = 0; + Device[deviceCount].SendDataOption = false; + Device[deviceCount].TimerOption = true; + break; + } case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_023); - break; - } + { + string = F(PLUGIN_NAME_023); + break; + } case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_023)); - break; - } + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_023)); + break; + } case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: - { - byte choice = PCONFIG(0); - /*String options[2] = { F("3C"), F("3D") };*/ - int optionValues[2] = { 0x3C, 0x3D }; - addFormSelectorI2C(F("p023_adr"), 2, optionValues, choice); - break; - } + { + byte choice = PCONFIG(0); + + /*String options[2] = { F("3C"), F("3D") };*/ + int optionValues[2] = { 0x3C, 0x3D }; + addFormSelectorI2C(F("p023_adr"), 2, optionValues, choice); + break; + } case PLUGIN_WEBFORM_LOAD: + { { - { - byte choice2 = PCONFIG(1); - String options2[2] = { F("Normal"), F("Rotated") }; - int optionValues2[2] = { 1, 2 }; - addFormSelector(F("Rotation"), F("p023_rotate"), 2, options2, optionValues2, choice2); - } - { - byte choice3 = PCONFIG(3); - String options3[3] = { F("128x64"), F("128x32"), F("64x48") }; - int optionValues3[3] = { 1, 3, 2 }; - addFormSelector(F("Display Size"), F("p023_size"), 3, options3, optionValues3, choice3); - } - { - byte choice4 = PCONFIG(4); - String options4[2] = { F("Normal"), F("Optimized") }; - int optionValues4[2] = { 1, 2 }; - addFormSelector(F("Font Width"), F("p023_font_width"), 2, options4, optionValues4, choice4); - } - { - String strings[P23_Nlines]; - LoadCustomTaskSettings(event->TaskIndex, strings, P23_Nlines, P23_Nchars); - for (byte varNr = 0; varNr < 8; varNr++) - { - addFormTextBox(String(F("Line ")) + (varNr + 1), getPluginCustomArgName(varNr), strings[varNr], 64); - } - } - - // FIXME TD-er: Why is this using pin3 and not pin1? And why isn't this using the normal pin selection functions? - addFormPinSelect(F("Display button"), F("taskdevicepin3"), CONFIG_PIN3); - - addFormNumericBox(F("Display Timeout"), F("plugin_23_timer"), PCONFIG(2)); - - success = true; - break; + byte choice2 = PCONFIG(1); + String options2[2] = { F("Normal"), F("Rotated") }; + int optionValues2[2] = { 1, 2 }; + addFormSelector(F("Rotation"), F("p023_rotate"), 2, options2, optionValues2, choice2); } - - case PLUGIN_WEBFORM_SAVE: { - PCONFIG(0) = getFormItemInt(F("p023_adr")); - PCONFIG(1) = getFormItemInt(F("p023_rotate")); - PCONFIG(2) = getFormItemInt(F("plugin_23_timer")); - PCONFIG(3) = getFormItemInt(F("p023_size")); - PCONFIG(4) = getFormItemInt(F("p023_font_width")); - - // FIXME TD-er: This is a huge stack allocated object. - char deviceTemplate[P23_Nlines][P23_Nchars]; - String error; - for (byte varNr = 0; varNr < P23_Nlines; varNr++) - { - if (!safe_strncpy(deviceTemplate[varNr], web_server.arg(getPluginCustomArgName(varNr)), P23_Nchars)) { - error += getCustomTaskSettingsError(varNr); - } - } - if (error.length() > 0) { - addHtmlError(error); - } - SaveCustomTaskSettings(event->TaskIndex, (byte*)&deviceTemplate, sizeof(deviceTemplate)); - success = true; - break; + byte choice3 = PCONFIG(3); + String options3[3] = { F("128x64"), F("128x32"), F("64x48") }; + int optionValues3[3] = { 1, 3, 2 }; + addFormSelector(F("Display Size"), F("p023_size"), 3, options3, optionValues3, choice3); } - - case PLUGIN_INIT: { - int index = PCONFIG(0) == 0x3C - ? 0 - : 1; - OLED_Settings[index].address = PCONFIG(0); - OLED_Settings[index].type = 0; - if (PCONFIG(3) == 3) - { - OLED_Settings[index].type = OLED_128x32; - } - OLED_Settings[index].font_width = Size_normal; - if (PCONFIG(4) == 2) - { - OLED_Settings[index].font_width = Size_optimized; - } - - Plugin_023_StartUp_OLED(OLED_Settings[index]); - Plugin_023_clear_display(OLED_Settings[index]); - if (PCONFIG(1) == 2) - { - OLED_Settings[index].type |= OLED_rotated; - Plugin_023_sendcommand(OLED_Settings[index].address, 0xA0 | 0x1); //SEGREMAP //Rotate screen 180 deg - Plugin_023_sendcommand(OLED_Settings[index].address, 0xC8); //COMSCANDEC Rotate screen 180 Deg - } - if (PCONFIG(3) == 2) - { - OLED_Settings[index].type |= OLED_64x48; - } - - Plugin_023_sendStrXY(OLED_Settings[index], "ESP Easy ", 0, 0); - OLED_Settings[index].displayTimer = PCONFIG(2); - if (CONFIG_PIN3 != -1) - pinMode(CONFIG_PIN3, INPUT_PULLUP); - success = true; - break; + byte choice4 = PCONFIG(4); + String options4[2] = { F("Normal"), F("Optimized") }; + int optionValues4[2] = { 1, 2 }; + addFormSelector(F("Font Width"), F("p023_font_width"), 2, options4, optionValues4, choice4); } - - case PLUGIN_TEN_PER_SECOND: - { - if (CONFIG_PIN3 != -1) - { - int index = PCONFIG(0) == 0x3C - ? 0 - : 1; - if (!digitalRead(CONFIG_PIN3)) - { - Plugin_023_displayOn(OLED_Settings[index]); - OLED_Settings[index].displayTimer = PCONFIG(2); - } - } - break; - } - - case PLUGIN_ONCE_A_SECOND: - { - int index = PCONFIG(0) == 0x3C - ? 0 - : 1; - - if (OLED_Settings[index].displayTimer > 0) - { - OLED_Settings[index].displayTimer--; - if (OLED_Settings[index].displayTimer == 0) - Plugin_023_displayOff(OLED_Settings[index]); - } - break; - } - - case PLUGIN_READ: { String strings[P23_Nlines]; LoadCustomTaskSettings(event->TaskIndex, strings, P23_Nlines, P23_Nchars); - int index = PCONFIG(0) == 0x3C - ? 0 - : 1; - for (byte x = 0; x < 8; x++) + for (byte varNr = 0; varNr < 8; varNr++) { - if (strings[x].length()) - { - String newString = P023_parseTemplate(strings[x], 16); - Plugin_023_sendStrXY(OLED_Settings[index],newString.c_str(), x, 0); - } + addFormTextBox(String(F("Line ")) + (varNr + 1), getPluginCustomArgName(varNr), strings[varNr], 64); } - success = false; - break; } - case PLUGIN_WRITE: + // FIXME TD-er: Why is this using pin3 and not pin1? And why isn't this using the normal pin selection functions? + addFormPinSelect(F("Display button"), F("taskdevicepin3"), CONFIG_PIN3); + + addFormNumericBox(F("Display Timeout"), F("plugin_23_timer"), PCONFIG(2)); + + success = true; + break; + } + + case PLUGIN_WEBFORM_SAVE: + { + PCONFIG(0) = getFormItemInt(F("p023_adr")); + PCONFIG(1) = getFormItemInt(F("p023_rotate")); + PCONFIG(2) = getFormItemInt(F("plugin_23_timer")); + PCONFIG(3) = getFormItemInt(F("p023_size")); + PCONFIG(4) = getFormItemInt(F("p023_font_width")); + + // FIXME TD-er: This is a huge stack allocated object. + char deviceTemplate[P23_Nlines][P23_Nchars]; + String error; + + for (byte varNr = 0; varNr < P23_Nlines; varNr++) + { + if (!safe_strncpy(deviceTemplate[varNr], web_server.arg(getPluginCustomArgName(varNr)), P23_Nchars)) { + error += getCustomTaskSettingsError(varNr); + } + } + + if (error.length() > 0) { + addHtmlError(error); + } + SaveCustomTaskSettings(event->TaskIndex, (byte *)&deviceTemplate, sizeof(deviceTemplate)); + success = true; + break; + } + + case PLUGIN_INIT: + { + int index = PCONFIG(0) == 0x3C + ? 0 + : 1; + OLED_Settings[index].address = PCONFIG(0); + OLED_Settings[index].type = 0; + + if (PCONFIG(3) == 3) + { + OLED_Settings[index].type = OLED_128x32; + } + OLED_Settings[index].font_width = Size_normal; + + if (PCONFIG(4) == 2) + { + OLED_Settings[index].font_width = Size_optimized; + } + + Plugin_023_StartUp_OLED(OLED_Settings[index]); + Plugin_023_clear_display(OLED_Settings[index]); + + if (PCONFIG(1) == 2) + { + OLED_Settings[index].type |= OLED_rotated; + Plugin_023_sendcommand(OLED_Settings[index].address, 0xA0 | 0x1); // SEGREMAP //Rotate screen 180 deg + Plugin_023_sendcommand(OLED_Settings[index].address, 0xC8); // COMSCANDEC Rotate screen 180 Deg + } + + if (PCONFIG(3) == 2) + { + OLED_Settings[index].type |= OLED_64x48; + } + + Plugin_023_sendStrXY(OLED_Settings[index], "ESP Easy ", 0, 0); + OLED_Settings[index].displayTimer = PCONFIG(2); + + if (CONFIG_PIN3 != -1) { + pinMode(CONFIG_PIN3, INPUT_PULLUP); + } + success = true; + break; + } + + case PLUGIN_TEN_PER_SECOND: + { + if (CONFIG_PIN3 != -1) { int index = PCONFIG(0) == 0x3C + ? 0 + : 1; + + if (!digitalRead(CONFIG_PIN3)) + { + Plugin_023_displayOn(OLED_Settings[index]); + OLED_Settings[index].displayTimer = PCONFIG(2); + } + } + break; + } + + case PLUGIN_ONCE_A_SECOND: + { + int index = PCONFIG(0) == 0x3C ? 0 : 1; - String arguments = String(string); - //Fixed bug #1864 - // this was to manage multiple instances of the plug-in. - // You can also call it this way: - // [TaskName].OLED, 1,1, Temp. is 19.9 - int dotPos = arguments.indexOf('.'); - if(dotPos > -1 && arguments.substring(dotPos,dotPos+4).equalsIgnoreCase(F("oled"))) - { - LoadTaskSettings(event->TaskIndex); - String name = arguments.substring(0,dotPos); - name.replace("[",""); - name.replace("]",""); - if(name.equalsIgnoreCase(getTaskDeviceName(event->TaskIndex)) == true) - { - arguments = arguments.substring(dotPos+1); - } - else - { - return false; - } - } + if (OLED_Settings[index].displayTimer > 0) + { + OLED_Settings[index].displayTimer--; - // We now continue using 'arguments' and not 'string' as full command line. - // If there was any prefix to address a specific task, it is now removed from 'arguments' - String cmd = parseString(arguments, 1); - if (cmd.equalsIgnoreCase(F("OLEDCMD"))) - { - success = true; - String param = parseString(arguments, 2); - if (param.equalsIgnoreCase(F("Off"))) - Plugin_023_displayOff(OLED_Settings[index]); - else if (param.equalsIgnoreCase(F("On"))) - Plugin_023_displayOn(OLED_Settings[index]); - else if (param.equalsIgnoreCase(F("Clear"))) - Plugin_023_clear_display(OLED_Settings[index]); + if (OLED_Settings[index].displayTimer == 0) { + Plugin_023_displayOff(OLED_Settings[index]); } - else if (cmd.equalsIgnoreCase(F("OLED"))) - { - success = true; - String text = parseStringToEndKeepCase(arguments, 4); - text = P023_parseTemplate(text, 16); - Plugin_023_sendStrXY(OLED_Settings[index], text.c_str(), event->Par1 - 1, event->Par2 - 1); - } - break; } + break; + } + + case PLUGIN_READ: + { + String strings[P23_Nlines]; + LoadCustomTaskSettings(event->TaskIndex, strings, P23_Nlines, P23_Nchars); + int index = PCONFIG(0) == 0x3C + ? 0 + : 1; + + for (byte x = 0; x < 8; x++) + { + if (strings[x].length()) + { + String newString = P023_parseTemplate(strings[x], 16); + Plugin_023_sendStrXY(OLED_Settings[index], newString.c_str(), x, 0); + } + } + success = false; + break; + } + + case PLUGIN_WRITE: + { + int index = PCONFIG(0) == 0x3C + ? 0 + : 1; + String arguments = String(string); + + // Fixed bug #1864 + // this was to manage multiple instances of the plug-in. + // You can also call it this way: + // [TaskName].OLED, 1,1, Temp. is 19.9 + int dotPos = arguments.indexOf('.'); + + if ((dotPos > -1) && arguments.substring(dotPos, dotPos + 4).equalsIgnoreCase(F("oled"))) + { + LoadTaskSettings(event->TaskIndex); + String name = arguments.substring(0, dotPos); + name.replace("[", ""); + name.replace("]", ""); + + if (name.equalsIgnoreCase(getTaskDeviceName(event->TaskIndex)) == true) + { + arguments = arguments.substring(dotPos + 1); + } + else + { + return false; + } + } + + // We now continue using 'arguments' and not 'string' as full command line. + // If there was any prefix to address a specific task, it is now removed from 'arguments' + String cmd = parseString(arguments, 1); + + if (cmd.equalsIgnoreCase(F("OLEDCMD"))) + { + success = true; + String param = parseString(arguments, 2); + + if (param.equalsIgnoreCase(F("Off"))) { + Plugin_023_displayOff(OLED_Settings[index]); + } + else if (param.equalsIgnoreCase(F("On"))) { + Plugin_023_displayOn(OLED_Settings[index]); + } + else if (param.equalsIgnoreCase(F("Clear"))) { + Plugin_023_clear_display(OLED_Settings[index]); + } + } + else if (cmd.equalsIgnoreCase(F("OLED"))) + { + success = true; + String text = parseStringToEndKeepCase(arguments, 4); + text = P023_parseTemplate(text, 16); + Plugin_023_sendStrXY(OLED_Settings[index], text.c_str(), event->Par1 - 1, event->Par2 - 1); + } + break; + } } return success; } const char Plugin_023_myFont_Size[] PROGMEM = { - 0x05, // SPACE - 0x05, // ! - 0x07, // " - 0x08, // # - 0x08, // $ - 0x08, // % - 0x08, // & - 0x06, // ' - 0x06, // ( - 0x06, // ) - 0x08, // * - 0x08, // + - 0x05, // , - 0x08, // - - 0x05, // . - 0x08, // / - 0x08, // 0 - 0x07, // 1 - 0x08, // 2 - 0x08, // 3 - 0x08, // 4 - 0x08, // 5 - 0x08, // 6 - 0x08, // 7 - 0x08, // 8 - 0x08, // 9 - 0x06, // : - 0x06, // ; - 0x07, // < - 0x08, // = - 0x07, // > - 0x08, // ? - 0x08, // @ - 0x08, // A - 0x08, // B - 0x08, // C - 0x08, // D - 0x08, // E - 0x08, // F - 0x08, // G - 0x08, // H - 0x06, // I - 0x08, // J - 0x08, // K - 0x08, // L - 0x08, // M - 0x08, // N - 0x08, // O - 0x08, // P - 0x08, // Q - 0x08, // R - 0x08, // S - 0x08, // T - 0x08, // U - 0x08, // V - 0x08, // W - 0x08, // X - 0x08, // Y - 0x08, // Z - 0x06, // [ - 0x08, // BACKSLASH - 0x06, // ] - 0x08, // ^ - 0x08, // _ - 0x06, // ` - 0x08, // a - 0x08, // b - 0x07, // c - 0x08, // d - 0x08, // e - 0x07, // f - 0x08, // g - 0x08, // h - 0x05, // i - 0x06, // j - 0x07, // k - 0x06, // l - 0x08, // m - 0x07, // n - 0x07, // o - 0x07, // p - 0x07, // q - 0x07, // r - 0x07, // s - 0x06, // t - 0x07, // u - 0x08, // v - 0x08, // w - 0x08, // x - 0x07, // y - 0x08, // z - 0x06, // { - 0x05, // | - 0x06, // } - 0x08, // ~ - 0x08 // DEL + 0x05, // SPACE + 0x05, // ! + 0x07, // " + 0x08, // # + 0x08, // $ + 0x08, // % + 0x08, // & + 0x06, // ' + 0x06, // ( + 0x06, // ) + 0x08, // * + 0x08, // + + 0x05, // , + 0x08, // - + 0x05, // . + 0x08, // / + 0x08, // 0 + 0x07, // 1 + 0x08, // 2 + 0x08, // 3 + 0x08, // 4 + 0x08, // 5 + 0x08, // 6 + 0x08, // 7 + 0x08, // 8 + 0x08, // 9 + 0x06, // : + 0x06, // ; + 0x07, // < + 0x08, // = + 0x07, // > + 0x08, // ? + 0x08, // @ + 0x08, // A + 0x08, // B + 0x08, // C + 0x08, // D + 0x08, // E + 0x08, // F + 0x08, // G + 0x08, // H + 0x06, // I + 0x08, // J + 0x08, // K + 0x08, // L + 0x08, // M + 0x08, // N + 0x08, // O + 0x08, // P + 0x08, // Q + 0x08, // R + 0x08, // S + 0x08, // T + 0x08, // U + 0x08, // V + 0x08, // W + 0x08, // X + 0x08, // Y + 0x08, // Z + 0x06, // [ + 0x08, // BACKSLASH + 0x06, // ] + 0x08, // ^ + 0x08, // _ + 0x06, // ` + 0x08, // a + 0x08, // b + 0x07, // c + 0x08, // d + 0x08, // e + 0x07, // f + 0x08, // g + 0x08, // h + 0x05, // i + 0x06, // j + 0x07, // k + 0x06, // l + 0x08, // m + 0x07, // n + 0x07, // o + 0x07, // p + 0x07, // q + 0x07, // r + 0x07, // s + 0x06, // t + 0x07, // u + 0x08, // v + 0x08, // w + 0x08, // x + 0x07, // y + 0x08, // z + 0x06, // { + 0x05, // | + 0x06, // } + 0x08, // ~ + 0x08 // DEL }; // Perform some specific changes for OLED display -String P023_parseTemplate(String &tmpString, byte lineSize) { - String result = parseTemplate_padded(tmpString, lineSize); - const char degree[3] = {0xc2, 0xb0, 0}; // Unicode degree symbol - const char degree_oled[2] = {0x7F, 0}; // P023_OLED degree symbol +String P023_parseTemplate(String& tmpString, byte lineSize) { + String result = parseTemplate_padded(tmpString, lineSize); + const char degree[3] = { 0xc2, 0xb0, 0 }; // Unicode degree symbol + const char degree_oled[2] = { 0x7F, 0 }; // P023_OLED degree symbol + result.replace(degree, degree_oled); return result; } - - const char Plugin_023_myFont[][8] PROGMEM = { - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // SPACE - {0x00, 0x00, 0x5F, 0x00, 0x00, 0x00, 0x00, 0x00}, // ! - {0x00, 0x00, 0x07, 0x00, 0x07, 0x00, 0x00, 0x00}, // " - {0x00, 0x14, 0x7F, 0x14, 0x7F, 0x14, 0x00, 0x00}, // # - {0x00, 0x24, 0x2A, 0x7F, 0x2A, 0x12, 0x00, 0x00}, // $ - {0x00, 0x23, 0x13, 0x08, 0x64, 0x62, 0x00, 0x00}, // % - {0x00, 0x36, 0x49, 0x55, 0x22, 0x50, 0x00, 0x00}, // & - {0x00, 0x00, 0x05, 0x03, 0x00, 0x00, 0x00, 0x00}, // ' - {0x00, 0x1C, 0x22, 0x41, 0x00, 0x00, 0x00, 0x00}, // ( - {0x00, 0x41, 0x22, 0x1C, 0x00, 0x00, 0x00, 0x00}, // ) - {0x00, 0x08, 0x2A, 0x1C, 0x2A, 0x08, 0x00, 0x00}, // * - {0x00, 0x08, 0x08, 0x3E, 0x08, 0x08, 0x00, 0x00}, // + - {0x00, 0xA0, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00}, // , - {0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00}, // - - {0x00, 0x60, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00}, // . - {0x00, 0x20, 0x10, 0x08, 0x04, 0x02, 0x00, 0x00}, // / - {0x00, 0x3E, 0x51, 0x49, 0x45, 0x3E, 0x00, 0x00}, // 0 - {0x00, 0x00, 0x42, 0x7F, 0x40, 0x00, 0x00, 0x00}, // 1 - {0x00, 0x62, 0x51, 0x49, 0x49, 0x46, 0x00, 0x00}, // 2 - {0x00, 0x22, 0x41, 0x49, 0x49, 0x36, 0x00, 0x00}, // 3 - {0x00, 0x18, 0x14, 0x12, 0x7F, 0x10, 0x00, 0x00}, // 4 - {0x00, 0x27, 0x45, 0x45, 0x45, 0x39, 0x00, 0x00}, // 5 - {0x00, 0x3C, 0x4A, 0x49, 0x49, 0x30, 0x00, 0x00}, // 6 - {0x00, 0x01, 0x71, 0x09, 0x05, 0x03, 0x00, 0x00}, // 7 - {0x00, 0x36, 0x49, 0x49, 0x49, 0x36, 0x00, 0x00}, // 8 - {0x00, 0x06, 0x49, 0x49, 0x29, 0x1E, 0x00, 0x00}, // 9 - {0x00, 0x00, 0x36, 0x36, 0x00, 0x00, 0x00, 0x00}, // : - {0x00, 0x00, 0xAC, 0x6C, 0x00, 0x00, 0x00, 0x00}, // ; - {0x00, 0x08, 0x14, 0x22, 0x41, 0x00, 0x00, 0x00}, // < - {0x00, 0x14, 0x14, 0x14, 0x14, 0x14, 0x00, 0x00}, // = - {0x00, 0x41, 0x22, 0x14, 0x08, 0x00, 0x00, 0x00}, // > - {0x00, 0x02, 0x01, 0x51, 0x09, 0x06, 0x00, 0x00}, // ? - {0x00, 0x32, 0x49, 0x79, 0x41, 0x3E, 0x00, 0x00}, // @ - {0x00, 0x7E, 0x09, 0x09, 0x09, 0x7E, 0x00, 0x00}, // A - {0x00, 0x7F, 0x49, 0x49, 0x49, 0x36, 0x00, 0x00}, // B - {0x00, 0x3E, 0x41, 0x41, 0x41, 0x22, 0x00, 0x00}, // C - {0x00, 0x7F, 0x41, 0x41, 0x22, 0x1C, 0x00, 0x00}, // D - {0x00, 0x7F, 0x49, 0x49, 0x49, 0x41, 0x00, 0x00}, // E - {0x00, 0x7F, 0x09, 0x09, 0x09, 0x01, 0x00, 0x00}, // F - {0x00, 0x3E, 0x41, 0x41, 0x51, 0x72, 0x00, 0x00}, // G - {0x00, 0x7F, 0x08, 0x08, 0x08, 0x7F, 0x00, 0x00}, // H - {0x00, 0x41, 0x7F, 0x41, 0x00, 0x00, 0x00, 0x00}, // I - {0x00, 0x20, 0x40, 0x41, 0x3F, 0x01, 0x00, 0x00}, // J - {0x00, 0x7F, 0x08, 0x14, 0x22, 0x41, 0x00, 0x00}, // K - {0x00, 0x7F, 0x40, 0x40, 0x40, 0x40, 0x00, 0x00}, // L - {0x00, 0x7F, 0x02, 0x0C, 0x02, 0x7F, 0x00, 0x00}, // M - {0x00, 0x7F, 0x04, 0x08, 0x10, 0x7F, 0x00, 0x00}, // N - {0x00, 0x3E, 0x41, 0x41, 0x41, 0x3E, 0x00, 0x00}, // O - {0x00, 0x7F, 0x09, 0x09, 0x09, 0x06, 0x00, 0x00}, // P - {0x00, 0x3E, 0x41, 0x51, 0x21, 0x5E, 0x00, 0x00}, // Q - {0x00, 0x7F, 0x09, 0x19, 0x29, 0x46, 0x00, 0x00}, // R - {0x00, 0x26, 0x49, 0x49, 0x49, 0x32, 0x00, 0x00}, // S - {0x00, 0x01, 0x01, 0x7F, 0x01, 0x01, 0x00, 0x00}, // T - {0x00, 0x3F, 0x40, 0x40, 0x40, 0x3F, 0x00, 0x00}, // U - {0x00, 0x1F, 0x20, 0x40, 0x20, 0x1F, 0x00, 0x00}, // V - {0x00, 0x3F, 0x40, 0x38, 0x40, 0x3F, 0x00, 0x00}, // W - {0x00, 0x63, 0x14, 0x08, 0x14, 0x63, 0x00, 0x00}, // X - {0x00, 0x03, 0x04, 0x78, 0x04, 0x03, 0x00, 0x00}, // Y - {0x00, 0x61, 0x51, 0x49, 0x45, 0x43, 0x00, 0x00}, // Z - {0x00, 0x7F, 0x41, 0x41, 0x00, 0x00, 0x00, 0x00}, // [ - {0x00, 0x02, 0x04, 0x08, 0x10, 0x20, 0x00, 0x00}, // BACKSLASH - {0x00, 0x41, 0x41, 0x7F, 0x00, 0x00, 0x00, 0x00}, // ] - {0x00, 0x04, 0x02, 0x01, 0x02, 0x04, 0x00, 0x00}, // ^ - {0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00}, // _ - {0x00, 0x01, 0x02, 0x04, 0x00, 0x00, 0x00, 0x00}, // ` - {0x00, 0x20, 0x54, 0x54, 0x54, 0x78, 0x00, 0x00}, // a - {0x00, 0x7F, 0x48, 0x44, 0x44, 0x38, 0x00, 0x00}, // b - {0x00, 0x38, 0x44, 0x44, 0x28, 0x00, 0x00, 0x00}, // c - {0x00, 0x38, 0x44, 0x44, 0x48, 0x7F, 0x00, 0x00}, // d - {0x00, 0x38, 0x54, 0x54, 0x54, 0x18, 0x00, 0x00}, // e - {0x00, 0x08, 0x7E, 0x09, 0x02, 0x00, 0x00, 0x00}, // f - {0x00, 0x18, 0xA4, 0xA4, 0xA4, 0x7C, 0x00, 0x00}, // g - {0x00, 0x7F, 0x08, 0x04, 0x04, 0x78, 0x00, 0x00}, // h - {0x00, 0x00, 0x7D, 0x00, 0x00, 0x00, 0x00, 0x00}, // i - {0x00, 0x80, 0x84, 0x7D, 0x00, 0x00, 0x00, 0x00}, // j - {0x00, 0x7F, 0x10, 0x28, 0x44, 0x00, 0x00, 0x00}, // k - {0x00, 0x41, 0x7F, 0x40, 0x00, 0x00, 0x00, 0x00}, // l - {0x00, 0x7C, 0x04, 0x18, 0x04, 0x78, 0x00, 0x00}, // m - {0x00, 0x7C, 0x08, 0x04, 0x7C, 0x00, 0x00, 0x00}, // n - {0x00, 0x38, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00}, // o - {0x00, 0xFC, 0x24, 0x24, 0x18, 0x00, 0x00, 0x00}, // p - {0x00, 0x18, 0x24, 0x24, 0xFC, 0x00, 0x00, 0x00}, // q - {0x00, 0x00, 0x7C, 0x08, 0x04, 0x00, 0x00, 0x00}, // r - {0x00, 0x48, 0x54, 0x54, 0x24, 0x00, 0x00, 0x00}, // s - {0x00, 0x04, 0x7F, 0x44, 0x00, 0x00, 0x00, 0x00}, // t - {0x00, 0x3C, 0x40, 0x40, 0x7C, 0x00, 0x00, 0x00}, // u - {0x00, 0x1C, 0x20, 0x40, 0x20, 0x1C, 0x00, 0x00}, // v - {0x00, 0x3C, 0x40, 0x30, 0x40, 0x3C, 0x00, 0x00}, // w - {0x00, 0x44, 0x28, 0x10, 0x28, 0x44, 0x00, 0x00}, // x - {0x00, 0x1C, 0xA0, 0xA0, 0x7C, 0x00, 0x00, 0x00}, // y - {0x00, 0x44, 0x64, 0x54, 0x4C, 0x44, 0x00, 0x00}, // z - {0x00, 0x08, 0x36, 0x41, 0x00, 0x00, 0x00, 0x00}, // { - {0x00, 0x00, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00}, // | - {0x00, 0x41, 0x36, 0x08, 0x00, 0x00, 0x00, 0x00}, // } - {0x00, 0x02, 0x01, 0x01, 0x02, 0x01, 0x00, 0x00}, // ~ - {0x00, 0x02, 0x05, 0x05, 0x02, 0x00, 0x00, 0x00} // DEL + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // SPACE + { 0x00, 0x00, 0x5F, 0x00, 0x00, 0x00, 0x00, 0x00 }, // ! + { 0x00, 0x00, 0x07, 0x00, 0x07, 0x00, 0x00, 0x00 }, // " + { 0x00, 0x14, 0x7F, 0x14, 0x7F, 0x14, 0x00, 0x00 }, // # + { 0x00, 0x24, 0x2A, 0x7F, 0x2A, 0x12, 0x00, 0x00 }, // $ + { 0x00, 0x23, 0x13, 0x08, 0x64, 0x62, 0x00, 0x00 }, // % + { 0x00, 0x36, 0x49, 0x55, 0x22, 0x50, 0x00, 0x00 }, // & + { 0x00, 0x00, 0x05, 0x03, 0x00, 0x00, 0x00, 0x00 }, // ' + { 0x00, 0x1C, 0x22, 0x41, 0x00, 0x00, 0x00, 0x00 }, // ( + { 0x00, 0x41, 0x22, 0x1C, 0x00, 0x00, 0x00, 0x00 }, // ) + { 0x00, 0x08, 0x2A, 0x1C, 0x2A, 0x08, 0x00, 0x00 }, // * + { 0x00, 0x08, 0x08, 0x3E, 0x08, 0x08, 0x00, 0x00 }, // + + { 0x00, 0xA0, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00 }, // , + { 0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00 }, // - + { 0x00, 0x60, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00 }, // . + { 0x00, 0x20, 0x10, 0x08, 0x04, 0x02, 0x00, 0x00 }, // / + { 0x00, 0x3E, 0x51, 0x49, 0x45, 0x3E, 0x00, 0x00 }, // 0 + { 0x00, 0x00, 0x42, 0x7F, 0x40, 0x00, 0x00, 0x00 }, // 1 + { 0x00, 0x62, 0x51, 0x49, 0x49, 0x46, 0x00, 0x00 }, // 2 + { 0x00, 0x22, 0x41, 0x49, 0x49, 0x36, 0x00, 0x00 }, // 3 + { 0x00, 0x18, 0x14, 0x12, 0x7F, 0x10, 0x00, 0x00 }, // 4 + { 0x00, 0x27, 0x45, 0x45, 0x45, 0x39, 0x00, 0x00 }, // 5 + { 0x00, 0x3C, 0x4A, 0x49, 0x49, 0x30, 0x00, 0x00 }, // 6 + { 0x00, 0x01, 0x71, 0x09, 0x05, 0x03, 0x00, 0x00 }, // 7 + { 0x00, 0x36, 0x49, 0x49, 0x49, 0x36, 0x00, 0x00 }, // 8 + { 0x00, 0x06, 0x49, 0x49, 0x29, 0x1E, 0x00, 0x00 }, // 9 + { 0x00, 0x00, 0x36, 0x36, 0x00, 0x00, 0x00, 0x00 }, // : + { 0x00, 0x00, 0xAC, 0x6C, 0x00, 0x00, 0x00, 0x00 }, // ; + { 0x00, 0x08, 0x14, 0x22, 0x41, 0x00, 0x00, 0x00 }, // < + { 0x00, 0x14, 0x14, 0x14, 0x14, 0x14, 0x00, 0x00 }, // = + { 0x00, 0x41, 0x22, 0x14, 0x08, 0x00, 0x00, 0x00 }, // > + { 0x00, 0x02, 0x01, 0x51, 0x09, 0x06, 0x00, 0x00 }, // ? + { 0x00, 0x32, 0x49, 0x79, 0x41, 0x3E, 0x00, 0x00 }, // @ + { 0x00, 0x7E, 0x09, 0x09, 0x09, 0x7E, 0x00, 0x00 }, // A + { 0x00, 0x7F, 0x49, 0x49, 0x49, 0x36, 0x00, 0x00 }, // B + { 0x00, 0x3E, 0x41, 0x41, 0x41, 0x22, 0x00, 0x00 }, // C + { 0x00, 0x7F, 0x41, 0x41, 0x22, 0x1C, 0x00, 0x00 }, // D + { 0x00, 0x7F, 0x49, 0x49, 0x49, 0x41, 0x00, 0x00 }, // E + { 0x00, 0x7F, 0x09, 0x09, 0x09, 0x01, 0x00, 0x00 }, // F + { 0x00, 0x3E, 0x41, 0x41, 0x51, 0x72, 0x00, 0x00 }, // G + { 0x00, 0x7F, 0x08, 0x08, 0x08, 0x7F, 0x00, 0x00 }, // H + { 0x00, 0x41, 0x7F, 0x41, 0x00, 0x00, 0x00, 0x00 }, // I + { 0x00, 0x20, 0x40, 0x41, 0x3F, 0x01, 0x00, 0x00 }, // J + { 0x00, 0x7F, 0x08, 0x14, 0x22, 0x41, 0x00, 0x00 }, // K + { 0x00, 0x7F, 0x40, 0x40, 0x40, 0x40, 0x00, 0x00 }, // L + { 0x00, 0x7F, 0x02, 0x0C, 0x02, 0x7F, 0x00, 0x00 }, // M + { 0x00, 0x7F, 0x04, 0x08, 0x10, 0x7F, 0x00, 0x00 }, // N + { 0x00, 0x3E, 0x41, 0x41, 0x41, 0x3E, 0x00, 0x00 }, // O + { 0x00, 0x7F, 0x09, 0x09, 0x09, 0x06, 0x00, 0x00 }, // P + { 0x00, 0x3E, 0x41, 0x51, 0x21, 0x5E, 0x00, 0x00 }, // Q + { 0x00, 0x7F, 0x09, 0x19, 0x29, 0x46, 0x00, 0x00 }, // R + { 0x00, 0x26, 0x49, 0x49, 0x49, 0x32, 0x00, 0x00 }, // S + { 0x00, 0x01, 0x01, 0x7F, 0x01, 0x01, 0x00, 0x00 }, // T + { 0x00, 0x3F, 0x40, 0x40, 0x40, 0x3F, 0x00, 0x00 }, // U + { 0x00, 0x1F, 0x20, 0x40, 0x20, 0x1F, 0x00, 0x00 }, // V + { 0x00, 0x3F, 0x40, 0x38, 0x40, 0x3F, 0x00, 0x00 }, // W + { 0x00, 0x63, 0x14, 0x08, 0x14, 0x63, 0x00, 0x00 }, // X + { 0x00, 0x03, 0x04, 0x78, 0x04, 0x03, 0x00, 0x00 }, // Y + { 0x00, 0x61, 0x51, 0x49, 0x45, 0x43, 0x00, 0x00 }, // Z + { 0x00, 0x7F, 0x41, 0x41, 0x00, 0x00, 0x00, 0x00 }, // [ + { 0x00, 0x02, 0x04, 0x08, 0x10, 0x20, 0x00, 0x00 }, // BACKSLASH + { 0x00, 0x41, 0x41, 0x7F, 0x00, 0x00, 0x00, 0x00 }, // ] + { 0x00, 0x04, 0x02, 0x01, 0x02, 0x04, 0x00, 0x00 }, // ^ + { 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00 }, // _ + { 0x00, 0x01, 0x02, 0x04, 0x00, 0x00, 0x00, 0x00 }, // ` + { 0x00, 0x20, 0x54, 0x54, 0x54, 0x78, 0x00, 0x00 }, // a + { 0x00, 0x7F, 0x48, 0x44, 0x44, 0x38, 0x00, 0x00 }, // b + { 0x00, 0x38, 0x44, 0x44, 0x28, 0x00, 0x00, 0x00 }, // c + { 0x00, 0x38, 0x44, 0x44, 0x48, 0x7F, 0x00, 0x00 }, // d + { 0x00, 0x38, 0x54, 0x54, 0x54, 0x18, 0x00, 0x00 }, // e + { 0x00, 0x08, 0x7E, 0x09, 0x02, 0x00, 0x00, 0x00 }, // f + { 0x00, 0x18, 0xA4, 0xA4, 0xA4, 0x7C, 0x00, 0x00 }, // g + { 0x00, 0x7F, 0x08, 0x04, 0x04, 0x78, 0x00, 0x00 }, // h + { 0x00, 0x00, 0x7D, 0x00, 0x00, 0x00, 0x00, 0x00 }, // i + { 0x00, 0x80, 0x84, 0x7D, 0x00, 0x00, 0x00, 0x00 }, // j + { 0x00, 0x7F, 0x10, 0x28, 0x44, 0x00, 0x00, 0x00 }, // k + { 0x00, 0x41, 0x7F, 0x40, 0x00, 0x00, 0x00, 0x00 }, // l + { 0x00, 0x7C, 0x04, 0x18, 0x04, 0x78, 0x00, 0x00 }, // m + { 0x00, 0x7C, 0x08, 0x04, 0x7C, 0x00, 0x00, 0x00 }, // n + { 0x00, 0x38, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00 }, // o + { 0x00, 0xFC, 0x24, 0x24, 0x18, 0x00, 0x00, 0x00 }, // p + { 0x00, 0x18, 0x24, 0x24, 0xFC, 0x00, 0x00, 0x00 }, // q + { 0x00, 0x00, 0x7C, 0x08, 0x04, 0x00, 0x00, 0x00 }, // r + { 0x00, 0x48, 0x54, 0x54, 0x24, 0x00, 0x00, 0x00 }, // s + { 0x00, 0x04, 0x7F, 0x44, 0x00, 0x00, 0x00, 0x00 }, // t + { 0x00, 0x3C, 0x40, 0x40, 0x7C, 0x00, 0x00, 0x00 }, // u + { 0x00, 0x1C, 0x20, 0x40, 0x20, 0x1C, 0x00, 0x00 }, // v + { 0x00, 0x3C, 0x40, 0x30, 0x40, 0x3C, 0x00, 0x00 }, // w + { 0x00, 0x44, 0x28, 0x10, 0x28, 0x44, 0x00, 0x00 }, // x + { 0x00, 0x1C, 0xA0, 0xA0, 0x7C, 0x00, 0x00, 0x00 }, // y + { 0x00, 0x44, 0x64, 0x54, 0x4C, 0x44, 0x00, 0x00 }, // z + { 0x00, 0x08, 0x36, 0x41, 0x00, 0x00, 0x00, 0x00 }, // { + { 0x00, 0x00, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00 }, // | + { 0x00, 0x41, 0x36, 0x08, 0x00, 0x00, 0x00, 0x00 }, // } + { 0x00, 0x02, 0x01, 0x01, 0x02, 0x01, 0x00, 0x00 }, // ~ + { 0x00, 0x02, 0x05, 0x05, 0x02, 0x00, 0x00, 0x00 } // DEL }; -void Plugin_023_reset_display(struct Plugin_023_OLED_SettingStruct &oled) +void Plugin_023_reset_display(struct Plugin_023_OLED_SettingStruct& oled) { Plugin_023_displayOff(oled); Plugin_023_clear_display(oled); Plugin_023_displayOn(oled); } - -void Plugin_023_StartUp_OLED(struct Plugin_023_OLED_SettingStruct &oled) +void Plugin_023_StartUp_OLED(struct Plugin_023_OLED_SettingStruct& oled) { Plugin_023_init_OLED(oled); Plugin_023_reset_display(oled); @@ -520,47 +539,43 @@ void Plugin_023_StartUp_OLED(struct Plugin_023_OLED_SettingStruct &oled) Plugin_023_displayOn(oled); } - -void Plugin_023_displayOn(struct Plugin_023_OLED_SettingStruct &oled) +void Plugin_023_displayOn(struct Plugin_023_OLED_SettingStruct& oled) { - Plugin_023_sendcommand(oled.address, 0xaf); //display on + Plugin_023_sendcommand(oled.address, 0xaf); // display on } - -void Plugin_023_displayOff(struct Plugin_023_OLED_SettingStruct &oled) +void Plugin_023_displayOff(struct Plugin_023_OLED_SettingStruct& oled) { - Plugin_023_sendcommand(oled.address, 0xae); //display off + Plugin_023_sendcommand(oled.address, 0xae); // display off } - -void Plugin_023_clear_display(struct Plugin_023_OLED_SettingStruct &oled) +void Plugin_023_clear_display(struct Plugin_023_OLED_SettingStruct& oled) { unsigned char i, k; + for (k = 0; k < 8; k++) { Plugin_023_setXY(oled, k, 0); { - for (i = 0; i < 128; i++) //clear all COL + for (i = 0; i < 128; i++) // clear all COL { - Plugin_023_SendChar(oled, 0); //clear all COL + Plugin_023_SendChar(oled, 0); // clear all COL } } } } - // Actually this sends a byte, not a char to draw in the display. -void Plugin_023_SendChar(struct Plugin_023_OLED_SettingStruct &oled, unsigned char data) +void Plugin_023_SendChar(struct Plugin_023_OLED_SettingStruct& oled, unsigned char data) { - Wire.beginTransmission(oled.address); // begin transmitting - Wire.write(0x40); //data mode + Wire.beginTransmission(oled.address); // begin transmitting + Wire.write(0x40); // data mode Wire.write(data); - Wire.endTransmission(); // stop transmitting + Wire.endTransmission(); // stop transmitting } - // Prints a display char (not just a byte) in coordinates X Y, -//currently unused: +// currently unused: // void Plugin_023_sendCharXY(unsigned char data, int X, int Y) // { // //if (interrupt && !doing_menu) return; // Stop printing only if interrupt is call but not in button functions @@ -577,16 +592,15 @@ void Plugin_023_SendChar(struct Plugin_023_OLED_SettingStruct &oled, unsigned ch void Plugin_023_sendcommand(byte address, unsigned char com) { - Wire.beginTransmission(address); //begin transmitting - Wire.write(0x80); //command mode + Wire.beginTransmission(address); // begin transmitting + Wire.write(0x80); // command mode Wire.write(com); - Wire.endTransmission(); // stop transmitting + Wire.endTransmission(); // stop transmitting } - // Set the cursor position in a 16 COL * 8 ROW map (128x64 pixels) // or 8 COL * 5 ROW map (64x48 pixels) -void Plugin_023_setXY(struct Plugin_023_OLED_SettingStruct &oled, unsigned char row, unsigned char col) +void Plugin_023_setXY(struct Plugin_023_OLED_SettingStruct& oled, unsigned char row, unsigned char col) { switch (oled.type) { @@ -598,12 +612,11 @@ void Plugin_023_setXY(struct Plugin_023_OLED_SettingStruct &oled, unsigned char row += 2; } - Plugin_023_sendcommand(oled.address, 0xb0 + row); //set page address - Plugin_023_sendcommand(oled.address, 0x00 + (8 * col & 0x0f)); //set low col address - Plugin_023_sendcommand(oled.address, 0x10 + ((8 * col >> 4) & 0x0f)); //set high col address + Plugin_023_sendcommand(oled.address, 0xb0 + row); // set page address + Plugin_023_sendcommand(oled.address, 0x00 + (8 * col & 0x0f)); // set low col address + Plugin_023_sendcommand(oled.address, 0x10 + ((8 * col >> 4) & 0x0f)); // set high col address } - // Prints a string regardless the cursor position. // unused: // void Plugin_023_sendStr(unsigned char *string) @@ -622,15 +635,15 @@ void Plugin_023_setXY(struct Plugin_023_OLED_SettingStruct &oled, unsigned char // Prints a string in coordinates X Y, being multiples of 8. // This means we have 16 COLS (0-15) and 8 ROWS (0-7). -void Plugin_023_sendStrXY(struct Plugin_023_OLED_SettingStruct &oled, const char *string, int X, int Y) +void Plugin_023_sendStrXY(struct Plugin_023_OLED_SettingStruct& oled, const char *string, int X, int Y) { Plugin_023_setXY(oled, X, Y); - unsigned char i = 0; - unsigned char font_width = 0; + unsigned char i = 0; + unsigned char font_width = 0; unsigned char currentPixels = Y * 8; // setXY always uses font_width = 8, Y = 0-based - unsigned char maxPixels = 128; // Assumed default display width + unsigned char maxPixels = 128; // Assumed default display width - switch (oled.type) { // Cater for that 1 smaller size display + switch (oled.type) { // Cater for that 1 smaller size display case OLED_64x48: case OLED_64x48 | OLED_rotated: maxPixels = 64; @@ -657,52 +670,52 @@ void Plugin_023_sendStrXY(struct Plugin_023_OLED_SettingStruct &oled, const cha } } - -void Plugin_023_init_OLED(struct Plugin_023_OLED_SettingStruct &oled) +void Plugin_023_init_OLED(struct Plugin_023_OLED_SettingStruct& oled) { unsigned char multiplex; unsigned char compins; byte address = oled.address; + switch (oled.type) { case OLED_128x32: multiplex = 0x1F; - compins = 0x02; + compins = 0x02; break; default: multiplex = 0x3F; - compins = 0x12; + compins = 0x12; } - Plugin_023_sendcommand(address, 0xAE); //display off - Plugin_023_sendcommand(address, 0xD5); //SETDISPLAYCLOCKDIV - Plugin_023_sendcommand(address, 0x80); // the suggested ratio 0x80 - Plugin_023_sendcommand(address, 0xA8); //SSD1306_SETMULTIPLEX - Plugin_023_sendcommand(address, multiplex); //0x1F if 128x32, 0x3F if others (e.g. 128x64) - Plugin_023_sendcommand(address, 0xD3); //SETDISPLAYOFFSET - Plugin_023_sendcommand(address, 0x00); //no offset - Plugin_023_sendcommand(address, 0x40 | 0x0); //SETSTARTLINE - Plugin_023_sendcommand(address, 0x8D); //CHARGEPUMP + Plugin_023_sendcommand(address, 0xAE); // display off + Plugin_023_sendcommand(address, 0xD5); // SETDISPLAYCLOCKDIV + Plugin_023_sendcommand(address, 0x80); // the suggested ratio 0x80 + Plugin_023_sendcommand(address, 0xA8); // SSD1306_SETMULTIPLEX + Plugin_023_sendcommand(address, multiplex); // 0x1F if 128x32, 0x3F if others (e.g. 128x64) + Plugin_023_sendcommand(address, 0xD3); // SETDISPLAYOFFSET + Plugin_023_sendcommand(address, 0x00); // no offset + Plugin_023_sendcommand(address, 0x40 | 0x0); // SETSTARTLINE + Plugin_023_sendcommand(address, 0x8D); // CHARGEPUMP Plugin_023_sendcommand(address, 0x14); - Plugin_023_sendcommand(address, 0x20); //MEMORYMODE - Plugin_023_sendcommand(address, 0x00); //0x0 act like ks0108 - Plugin_023_sendcommand(address, 0xA0); //128x32 ??? - Plugin_023_sendcommand(address, 0xC0); //128x32 ??? - Plugin_023_sendcommand(address, 0xDA); //COMPINS - Plugin_023_sendcommand(address, compins); //0x02 if 128x32, 0x12 if others (e.g. 128x64) - Plugin_023_sendcommand(address, 0x81); //SETCONTRAS + Plugin_023_sendcommand(address, 0x20); // MEMORYMODE + Plugin_023_sendcommand(address, 0x00); // 0x0 act like ks0108 + Plugin_023_sendcommand(address, 0xA0); // 128x32 ??? + Plugin_023_sendcommand(address, 0xC0); // 128x32 ??? + Plugin_023_sendcommand(address, 0xDA); // COMPINS + Plugin_023_sendcommand(address, compins); // 0x02 if 128x32, 0x12 if others (e.g. 128x64) + Plugin_023_sendcommand(address, 0x81); // SETCONTRAS Plugin_023_sendcommand(address, 0xCF); - Plugin_023_sendcommand(address, 0xD9); //SETPRECHARGE + Plugin_023_sendcommand(address, 0xD9); // SETPRECHARGE Plugin_023_sendcommand(address, 0xF1); - Plugin_023_sendcommand(address, 0xDB); //SETVCOMDETECT + Plugin_023_sendcommand(address, 0xDB); // SETVCOMDETECT Plugin_023_sendcommand(address, 0x40); - Plugin_023_sendcommand(address, 0xA4); //DISPLAYALLON_RESUME - Plugin_023_sendcommand(address, 0xA6); //NORMALDISPLAY + Plugin_023_sendcommand(address, 0xA4); // DISPLAYALLON_RESUME + Plugin_023_sendcommand(address, 0xA6); // NORMALDISPLAY Plugin_023_clear_display(oled); - Plugin_023_sendcommand(address, 0x2E); // stop scroll - Plugin_023_sendcommand(address, 0x20); //Set Memory Addressing Mode - Plugin_023_sendcommand(address, 0x00); //Set Memory Addressing Mode ab Horizontal addressing mode - + Plugin_023_sendcommand(address, 0x2E); // stop scroll + Plugin_023_sendcommand(address, 0x20); // Set Memory Addressing Mode + Plugin_023_sendcommand(address, 0x00); // Set Memory Addressing Mode ab Horizontal addressing mode } + #endif // USES_P023 diff --git a/src/_P024_MLX90614.ino b/src/_P024_MLX90614.ino index 1c808d0b3..c181eb85f 100644 --- a/src/_P024_MLX90614.ino +++ b/src/_P024_MLX90614.ino @@ -1,7 +1,8 @@ #ifdef USES_P024 -//####################################################################################################### -//#################################### Plugin 024: MLX90614 IR temperature I2C 0x5A) ############################################### -//####################################################################################################### + +// ####################################################################################################### +// #################################### Plugin 024: MLX90614 IR temperature I2C 0x5A) ############################################### +// ####################################################################################################### #include "_Plugin_Helper.h" @@ -16,11 +17,12 @@ boolean Plugin_024_init = false; uint16_t readRegister024(uint8_t i2cAddress, uint8_t reg) { uint16_t ret; + Wire.beginTransmission(i2cAddress); Wire.write(reg); Wire.endTransmission(false); Wire.requestFrom(i2cAddress, (uint8_t)3); - ret = Wire.read(); // receive DATA + ret = Wire.read(); // receive DATA ret |= Wire.read() << 8; // receive DATA Wire.read(); return ret; @@ -29,7 +31,8 @@ uint16_t readRegister024(uint8_t i2cAddress, uint8_t reg) { float readTemp024(uint8_t i2c_addr, uint8_t i2c_reg) { float temp; - temp = readRegister024(i2c_addr, i2c_reg); + + temp = readRegister024(i2c_addr, i2c_reg); temp *= .02; temp -= 273.15; return temp; @@ -38,91 +41,96 @@ float readTemp024(uint8_t i2c_addr, uint8_t i2c_reg) boolean Plugin_024(byte function, struct EventStruct *event, String& string) { boolean success = false; + // static byte portValue = 0; switch (function) { case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_024; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].VType = SENSOR_TYPE_SINGLE; - Device[deviceCount].Ports = 16; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = true; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].ValueCount = 1; - Device[deviceCount].TimerOption = true; - Device[deviceCount].GlobalSyncOption = true; - break; - } + { + Device[++deviceCount].Number = PLUGIN_ID_024; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = SENSOR_TYPE_SINGLE; + Device[deviceCount].Ports = 16; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].ValueCount = 1; + Device[deviceCount].TimerOption = true; + Device[deviceCount].GlobalSyncOption = true; + break; + } case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_024); - break; - } + { + string = F(PLUGIN_NAME_024); + break; + } case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_024)); - break; - } + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_024)); + break; + } case PLUGIN_WEBFORM_LOAD: - { + { #define MLX90614_OPTION 2 - byte choice = PCONFIG(0); - String options[MLX90614_OPTION]; - int optionValues[MLX90614_OPTION]; - optionValues[0] = (0x07); - options[0] = F("IR object temperature"); - optionValues[1] = (0x06); - options[1] = F("Ambient temperature"); - addFormSelector(F("Option"), F("p024_option"), MLX90614_OPTION, options, optionValues, choice); + byte choice = PCONFIG(0); + String options[MLX90614_OPTION]; + int optionValues[MLX90614_OPTION]; + optionValues[0] = (0x07); + options[0] = F("IR object temperature"); + optionValues[1] = (0x06); + options[1] = F("Ambient temperature"); + addFormSelector(F("Option"), F("p024_option"), MLX90614_OPTION, options, optionValues, choice); - success = true; - break; - } + success = true; + break; + } case PLUGIN_WEBFORM_SAVE: - { - PCONFIG(0) = getFormItemInt(F("p024_option")); - Plugin_024_init = false; // Force device setup next time - success = true; - break; - } + { + PCONFIG(0) = getFormItemInt(F("p024_option")); + Plugin_024_init = false; // Force device setup next time + success = true; + break; + } case PLUGIN_INIT: - { - Plugin_024_init = true; -// if (!msgTemp024) // Mysensors -// msgTemp024 = new MyMessage(event->BaseVarIndex, V_TEMP); //Mysensors -// present(event->BaseVarIndex, S_TEMP); //Mysensors -// serialPrint("Present MLX90614: "); //Mysensors -// serialPrintln(event->BaseVarIndex); //Mysensors - success = true; - break; - } + { + Plugin_024_init = true; + + // if (!msgTemp024) // Mysensors + // msgTemp024 = new MyMessage(event->BaseVarIndex, V_TEMP); //Mysensors + // present(event->BaseVarIndex, S_TEMP); //Mysensors + // serialPrint("Present MLX90614: "); //Mysensors + // serialPrintln(event->BaseVarIndex); //Mysensors + success = true; + break; + } case PLUGIN_READ: - { - // noInterrupts(); - // int value; - // value = 0; - byte unit = CONFIG_PORT; - uint8_t address = 0x5A + unit; - UserVar[event->BaseVarIndex] = (float) readTemp024(address, PCONFIG(0)); - String log = F("MLX90614 : Temperature: "); - log += UserVar[event->BaseVarIndex]; -// send(msgObjTemp024->set(UserVar[event->BaseVarIndex], 1)); // Mysensors - addLog(LOG_LEVEL_INFO,log); - success = true; - // interrupts(); - break; - } + { + // noInterrupts(); + // int value; + // value = 0; + byte unit = CONFIG_PORT; + uint8_t address = 0x5A + unit; + UserVar[event->BaseVarIndex] = (float)readTemp024(address, PCONFIG(0)); + String log = F("MLX90614 : Temperature: "); + log += UserVar[event->BaseVarIndex]; + + // send(msgObjTemp024->set(UserVar[event->BaseVarIndex], 1)); // Mysensors + addLog(LOG_LEVEL_INFO, log); + success = true; + + // interrupts(); + break; + } } return success; } + #endif // USES_P024 diff --git a/src/_P025_ADS1115.ino b/src/_P025_ADS1115.ino index aa6807920..4f566c6b1 100644 --- a/src/_P025_ADS1115.ino +++ b/src/_P025_ADS1115.ino @@ -1,7 +1,8 @@ #ifdef USES_P025 -//####################################################################################################### -//#################################### Plugin 025: ADS1115 I2C 0x48) ############################################### -//####################################################################################################### + +// ####################################################################################################### +// #################################### Plugin 025: ADS1115 I2C 0x48) ############################################### +// ####################################################################################################### #include "_Plugin_Helper.h" @@ -16,201 +17,207 @@ uint16_t readRegister025(uint8_t i2cAddress, uint8_t reg) { Wire.beginTransmission(i2cAddress); Wire.write((0x00)); Wire.endTransmission(); - if (Wire.requestFrom(i2cAddress, (uint8_t)2) != 2) + + if (Wire.requestFrom(i2cAddress, (uint8_t)2) != 2) { return 0x8000; - return ((Wire.read() << 8) | Wire.read()); + } + return (Wire.read() << 8) | Wire.read(); } boolean Plugin_025(byte function, struct EventStruct *event, String& string) { boolean success = false; + // static byte portValue = 0; switch (function) { case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_025; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].VType = SENSOR_TYPE_SINGLE; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = true; - Device[deviceCount].ValueCount = 1; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].GlobalSyncOption = true; - break; - } + { + Device[++deviceCount].Number = PLUGIN_ID_025; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = SENSOR_TYPE_SINGLE; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 1; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].GlobalSyncOption = true; + break; + } case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_025); - break; - } + { + string = F(PLUGIN_NAME_025); + break; + } case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_025)); - break; - } + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_025)); + break; + } case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: - { + { #define ADS1115_I2C_OPTION 4 - byte addr = PCONFIG(0); - int optionValues[ADS1115_I2C_OPTION] = { 0x48, 0x49, 0x4A, 0x4B }; - addFormSelectorI2C(F("p025_i2c"), ADS1115_I2C_OPTION, optionValues, addr); - break; - } + byte addr = PCONFIG(0); + int optionValues[ADS1115_I2C_OPTION] = { 0x48, 0x49, 0x4A, 0x4B }; + addFormSelectorI2C(F("p025_i2c"), ADS1115_I2C_OPTION, optionValues, addr); + break; + } case PLUGIN_WEBFORM_LOAD: + { + byte port = CONFIG_PORT; + + if (port > 0) // map old port logic to new gain and mode settings { - byte port = CONFIG_PORT; - if (port > 0) //map old port logic to new gain and mode settings - { - PCONFIG(1) = PCONFIG(0) / 2; - PCONFIG(0) = 0x48 + ((port-1)/4); - PCONFIG(2) = ((port-1) & 3) | 4; - CONFIG_PORT = 0; - } - - addFormSubHeader(F("Input")); - - { - #define ADS1115_PGA_OPTION 6 - byte pga = PCONFIG(1); - String pgaOptions[ADS1115_PGA_OPTION] = { - F("2/3x gain (FS=6.144V)"), - F("1x gain (FS=4.096V)"), - F("2x gain (FS=2.048V)"), - F("4x gain (FS=1.024V)"), - F("8x gain (FS=0.512V)"), - F("16x gain (FS=0.256V)") - }; - addFormSelector(F("Gain"), F("p025_gain"), ADS1115_PGA_OPTION, pgaOptions, NULL, pga); - } - - { - #define ADS1115_MUX_OPTION 8 - byte mux = PCONFIG(2); - String muxOptions[ADS1115_MUX_OPTION] = { - F("AIN0 - AIN1 (Differential)"), - F("AIN0 - AIN3 (Differential)"), - F("AIN1 - AIN3 (Differential)"), - F("AIN2 - AIN3 (Differential)"), - F("AIN0 - GND (Single-Ended)"), - F("AIN1 - GND (Single-Ended)"), - F("AIN2 - GND (Single-Ended)"), - F("AIN3 - GND (Single-Ended)"), - }; - addFormSelector(F("Input Multiplexer"), F("p025_mode"), ADS1115_MUX_OPTION, muxOptions, NULL, mux); - } - - addFormSubHeader(F("Two Point Calibration")); - - addFormCheckBox(F("Calibration Enabled"), F("p025_cal"), PCONFIG(3)); - - addFormNumericBox(F("Point 1"), F("p025_adc1"), PCONFIG_LONG(0), -32768, 32767); - html_add_estimate_symbol(); - addTextBox(F("p025_out1"), String(PCONFIG_FLOAT(0), 3), 10); - - addFormNumericBox(F("Point 2"), F("p025_adc2"), PCONFIG_LONG(1), -32768, 32767); - html_add_estimate_symbol(); - addTextBox(F("p025_out2"), String(PCONFIG_FLOAT(1), 3), 10); - - success = true; - break; + PCONFIG(1) = PCONFIG(0) / 2; + PCONFIG(0) = 0x48 + ((port - 1) / 4); + PCONFIG(2) = ((port - 1) & 3) | 4; + CONFIG_PORT = 0; } + addFormSubHeader(F("Input")); + + { + #define ADS1115_PGA_OPTION 6 + byte pga = PCONFIG(1); + String pgaOptions[ADS1115_PGA_OPTION] = { + F("2/3x gain (FS=6.144V)"), + F("1x gain (FS=4.096V)"), + F("2x gain (FS=2.048V)"), + F("4x gain (FS=1.024V)"), + F("8x gain (FS=0.512V)"), + F("16x gain (FS=0.256V)") + }; + addFormSelector(F("Gain"), F("p025_gain"), ADS1115_PGA_OPTION, pgaOptions, NULL, pga); + } + + { + #define ADS1115_MUX_OPTION 8 + byte mux = PCONFIG(2); + String muxOptions[ADS1115_MUX_OPTION] = { + F("AIN0 - AIN1 (Differential)"), + F("AIN0 - AIN3 (Differential)"), + F("AIN1 - AIN3 (Differential)"), + F("AIN2 - AIN3 (Differential)"), + F("AIN0 - GND (Single-Ended)"), + F("AIN1 - GND (Single-Ended)"), + F("AIN2 - GND (Single-Ended)"), + F("AIN3 - GND (Single-Ended)"), + }; + addFormSelector(F("Input Multiplexer"), F("p025_mode"), ADS1115_MUX_OPTION, muxOptions, NULL, mux); + } + + addFormSubHeader(F("Two Point Calibration")); + + addFormCheckBox(F("Calibration Enabled"), F("p025_cal"), PCONFIG(3)); + + addFormNumericBox(F("Point 1"), F("p025_adc1"), PCONFIG_LONG(0), -32768, 32767); + html_add_estimate_symbol(); + addTextBox(F("p025_out1"), String(PCONFIG_FLOAT(0), 3), 10); + + addFormNumericBox(F("Point 2"), F("p025_adc2"), PCONFIG_LONG(1), -32768, 32767); + html_add_estimate_symbol(); + addTextBox(F("p025_out2"), String(PCONFIG_FLOAT(1), 3), 10); + + success = true; + break; + } + case PLUGIN_WEBFORM_SAVE: - { - PCONFIG(0) = getFormItemInt(F("p025_i2c")); + { + PCONFIG(0) = getFormItemInt(F("p025_i2c")); - PCONFIG(1) = getFormItemInt(F("p025_gain")); + PCONFIG(1) = getFormItemInt(F("p025_gain")); - PCONFIG(2) = getFormItemInt(F("p025_mode")); + PCONFIG(2) = getFormItemInt(F("p025_mode")); - PCONFIG(3) = isFormItemChecked(F("p025_cal")); + PCONFIG(3) = isFormItemChecked(F("p025_cal")); - PCONFIG_LONG(0) = getFormItemInt(F("p025_adc1")); - PCONFIG_FLOAT(0) = getFormItemFloat(F("p025_out1")); + PCONFIG_LONG(0) = getFormItemInt(F("p025_adc1")); + PCONFIG_FLOAT(0) = getFormItemFloat(F("p025_out1")); - PCONFIG_LONG(1) = getFormItemInt(F("p025_adc2")); - PCONFIG_FLOAT(1) = getFormItemFloat(F("p025_out2")); + PCONFIG_LONG(1) = getFormItemInt(F("p025_adc2")); + PCONFIG_FLOAT(1) = getFormItemFloat(F("p025_out2")); - Plugin_025_init = false; // Force device setup next time - success = true; - break; - } + Plugin_025_init = false; // Force device setup next time + success = true; + break; + } case PLUGIN_INIT: - { - Plugin_025_init = true; - success = true; - break; - } + { + Plugin_025_init = true; + success = true; + break; + } case PLUGIN_READ: + { + // int value = 0; + // byte unit = (CONFIG_PORT - 1) / 4; + // byte port = CONFIG_PORT - (unit * 4); + // uint8_t address = 0x48 + unit; + + uint8_t address = PCONFIG(0); + + uint16_t config = (0x0003) | // Disable the comparator (default val) + (0x0000) | // Non-latching (default val) + (0x0000) | // Alert/Rdy active low (default val) + (0x0000) | // Traditional comparator (default val) + (0x0080) | // 128 samples per second (default) + (0x0100); // Single-shot mode (default) + + uint16_t pga = PCONFIG(1); + config |= pga << 9; + + uint16_t mux = PCONFIG(2); + config |= mux << 12; + + config |= (0x8000); // Start a single conversion + + Wire.beginTransmission(address); + Wire.write((uint8_t)(0x01)); + Wire.write((uint8_t)(config >> 8)); + Wire.write((uint8_t)(config & 0xFF)); + Wire.endTransmission(); + + String log = F("ADS1115 : Analog value: "); + + delay(9); // See https://github.com/letscontrolit/ESPEasy/issues/3159#issuecomment-660546091 + int16_t value = readRegister025((address), (0x00)); + UserVar[event->BaseVarIndex] = (float)value; + log += value; + + if (PCONFIG(3)) // Calibration? { - //int value = 0; - //byte unit = (CONFIG_PORT - 1) / 4; - //byte port = CONFIG_PORT - (unit * 4); - //uint8_t address = 0x48 + unit; + int adc1 = PCONFIG_LONG(0); + int adc2 = PCONFIG_LONG(1); + float out1 = PCONFIG_FLOAT(0); + float out2 = PCONFIG_FLOAT(1); - uint8_t address = PCONFIG(0); - - uint16_t config = (0x0003) | // Disable the comparator (default val) - (0x0000) | // Non-latching (default val) - (0x0000) | // Alert/Rdy active low (default val) - (0x0000) | // Traditional comparator (default val) - (0x0080) | // 128 samples per second (default) - (0x0100); // Single-shot mode (default) - - uint16_t pga = PCONFIG(1); - config |= pga << 9; - - uint16_t mux = PCONFIG(2); - config |= mux << 12; - - config |= (0x8000); // Start a single conversion - - Wire.beginTransmission(address); - Wire.write((uint8_t)(0x01)); - Wire.write((uint8_t)(config >> 8)); - Wire.write((uint8_t)(config & 0xFF)); - Wire.endTransmission(); - - String log = F("ADS1115 : Analog value: "); - - delay(9); // See https://github.com/letscontrolit/ESPEasy/issues/3159#issuecomment-660546091 - int16_t value = readRegister025((address), (0x00)); - UserVar[event->BaseVarIndex] = (float)value; - log += value; - - if (PCONFIG(3)) //Calibration? + if (adc1 != adc2) { - int adc1 = PCONFIG_LONG(0); - int adc2 = PCONFIG_LONG(1); - float out1 = PCONFIG_FLOAT(0); - float out2 = PCONFIG_FLOAT(1); - if (adc1 != adc2) - { - float normalized = (float)(value - adc1) / (float)(adc2 - adc1); - UserVar[event->BaseVarIndex] = normalized * (out2 - out1) + out1; + float normalized = (float)(value - adc1) / (float)(adc2 - adc1); + UserVar[event->BaseVarIndex] = normalized * (out2 - out1) + out1; - log += ' '; - log += UserVar[event->BaseVarIndex]; - } + log += ' '; + log += UserVar[event->BaseVarIndex]; } - - //TEST log += F(" @0x"); - //TEST log += String(config, 16); - addLog(LOG_LEVEL_DEBUG,log); - success = true; - break; } + + // TEST log += F(" @0x"); + // TEST log += String(config, 16); + addLog(LOG_LEVEL_DEBUG, log); + success = true; + break; + } } return success; } + #endif // USES_P025 diff --git a/src/_P027_INA219.ino b/src/_P027_INA219.ino index 3a9eb7005..1d3eaa6f5 100644 --- a/src/_P027_INA219.ino +++ b/src/_P027_INA219.ino @@ -1,7 +1,8 @@ #ifdef USES_P027 -//####################################################################################################### -//######################### Plugin 027: INA219 DC Voltage/Current sensor ################################ -//####################################################################################################### + +// ####################################################################################################### +// ######################### Plugin 027: INA219 DC Voltage/Current sensor ################################ +// ####################################################################################################### #include "_Plugin_Helper.h" @@ -15,44 +16,44 @@ // Many boards, like Adafruit INA219: https://learn.adafruit.com/adafruit-ina219-current-sensor-breakout/assembly // A0 and A1 are default connected to GND with 10k pull-down resistor. // To select another address, bridge either A0 and/or A1 to set to VS+ level. -#define INA219_ADDRESS (0x40) // 1000000 (A0+A1=GND) -#define INA219_ADDRESS2 (0x41) // 1000001 (A0=VS+, A1=GND) -#define INA219_ADDRESS3 (0x44) // 1000100 (A0=GND, A1=VS+) -#define INA219_ADDRESS4 (0x45) // 1000101 (A0=VS+, A1=VS+) +#define INA219_ADDRESS (0x40) // 1000000 (A0+A1=GND) +#define INA219_ADDRESS2 (0x41) // 1000001 (A0=VS+, A1=GND) +#define INA219_ADDRESS3 (0x44) // 1000100 (A0=GND, A1=VS+) +#define INA219_ADDRESS4 (0x45) // 1000101 (A0=VS+, A1=VS+) #define INA219_READ (0x01) #define INA219_REG_CONFIG (0x00) -#define INA219_CONFIG_RESET (0x8000) // Reset Bit +#define INA219_CONFIG_RESET (0x8000) // Reset Bit -#define INA219_CONFIG_BVOLTAGERANGE_MASK (0x2000) // Bus Voltage Range Mask -#define INA219_CONFIG_BVOLTAGERANGE_16V (0x0000) // 0-16V Range -#define INA219_CONFIG_BVOLTAGERANGE_32V (0x2000) // 0-32V Range +#define INA219_CONFIG_BVOLTAGERANGE_MASK (0x2000) // Bus Voltage Range Mask +#define INA219_CONFIG_BVOLTAGERANGE_16V (0x0000) // 0-16V Range +#define INA219_CONFIG_BVOLTAGERANGE_32V (0x2000) // 0-32V Range -#define INA219_CONFIG_GAIN_MASK (0x1800) // Gain Mask -#define INA219_CONFIG_GAIN_1_40MV (0x0000) // Gain 1, 40mV Range -#define INA219_CONFIG_GAIN_2_80MV (0x0800) // Gain 2, 80mV Range -#define INA219_CONFIG_GAIN_4_160MV (0x1000) // Gain 4, 160mV Range -#define INA219_CONFIG_GAIN_8_320MV (0x1800) // Gain 8, 320mV Range +#define INA219_CONFIG_GAIN_MASK (0x1800) // Gain Mask +#define INA219_CONFIG_GAIN_1_40MV (0x0000) // Gain 1, 40mV Range +#define INA219_CONFIG_GAIN_2_80MV (0x0800) // Gain 2, 80mV Range +#define INA219_CONFIG_GAIN_4_160MV (0x1000) // Gain 4, 160mV Range +#define INA219_CONFIG_GAIN_8_320MV (0x1800) // Gain 8, 320mV Range -#define INA219_CONFIG_BADCRES_MASK (0x0780) // Bus ADC Resolution Mask -#define INA219_CONFIG_BADCRES_9BIT (0x0080) // 9-bit bus res = 0..511 -#define INA219_CONFIG_BADCRES_10BIT (0x0100) // 10-bit bus res = 0..1023 -#define INA219_CONFIG_BADCRES_11BIT (0x0200) // 11-bit bus res = 0..2047 -#define INA219_CONFIG_BADCRES_12BIT (0x0400) // 12-bit bus res = 0..4097 +#define INA219_CONFIG_BADCRES_MASK (0x0780) // Bus ADC Resolution Mask +#define INA219_CONFIG_BADCRES_9BIT (0x0080) // 9-bit bus res = 0..511 +#define INA219_CONFIG_BADCRES_10BIT (0x0100) // 10-bit bus res = 0..1023 +#define INA219_CONFIG_BADCRES_11BIT (0x0200) // 11-bit bus res = 0..2047 +#define INA219_CONFIG_BADCRES_12BIT (0x0400) // 12-bit bus res = 0..4097 -#define INA219_CONFIG_SADCRES_MASK (0x0078) // Shunt ADC Resolution and Averaging Mask -#define INA219_CONFIG_SADCRES_9BIT_1S_84US (0x0000) // 1 x 9-bit shunt sample -#define INA219_CONFIG_SADCRES_10BIT_1S_148US (0x0008) // 1 x 10-bit shunt sample -#define INA219_CONFIG_SADCRES_11BIT_1S_276US (0x0010) // 1 x 11-bit shunt sample -#define INA219_CONFIG_SADCRES_12BIT_1S_532US (0x0018) // 1 x 12-bit shunt sample -#define INA219_CONFIG_SADCRES_12BIT_2S_1060US (0x0048) // 2 x 12-bit shunt samples averaged together -#define INA219_CONFIG_SADCRES_12BIT_4S_2130US (0x0050) // 4 x 12-bit shunt samples averaged together -#define INA219_CONFIG_SADCRES_12BIT_8S_4260US (0x0058) // 8 x 12-bit shunt samples averaged together -#define INA219_CONFIG_SADCRES_12BIT_16S_8510US (0x0060) // 16 x 12-bit shunt samples averaged together -#define INA219_CONFIG_SADCRES_12BIT_32S_17MS (0x0068) // 32 x 12-bit shunt samples averaged together -#define INA219_CONFIG_SADCRES_12BIT_64S_34MS (0x0070) // 64 x 12-bit shunt samples averaged together -#define INA219_CONFIG_SADCRES_12BIT_128S_69MS (0x0078) // 128 x 12-bit shunt samples averaged together +#define INA219_CONFIG_SADCRES_MASK (0x0078) // Shunt ADC Resolution and Averaging Mask +#define INA219_CONFIG_SADCRES_9BIT_1S_84US (0x0000) // 1 x 9-bit shunt sample +#define INA219_CONFIG_SADCRES_10BIT_1S_148US (0x0008) // 1 x 10-bit shunt sample +#define INA219_CONFIG_SADCRES_11BIT_1S_276US (0x0010) // 1 x 11-bit shunt sample +#define INA219_CONFIG_SADCRES_12BIT_1S_532US (0x0018) // 1 x 12-bit shunt sample +#define INA219_CONFIG_SADCRES_12BIT_2S_1060US (0x0048) // 2 x 12-bit shunt samples averaged together +#define INA219_CONFIG_SADCRES_12BIT_4S_2130US (0x0050) // 4 x 12-bit shunt samples averaged together +#define INA219_CONFIG_SADCRES_12BIT_8S_4260US (0x0058) // 8 x 12-bit shunt samples averaged together +#define INA219_CONFIG_SADCRES_12BIT_16S_8510US (0x0060) // 16 x 12-bit shunt samples averaged together +#define INA219_CONFIG_SADCRES_12BIT_32S_17MS (0x0068) // 32 x 12-bit shunt samples averaged together +#define INA219_CONFIG_SADCRES_12BIT_64S_34MS (0x0070) // 64 x 12-bit shunt samples averaged together +#define INA219_CONFIG_SADCRES_12BIT_128S_69MS (0x0078) // 128 x 12-bit shunt samples averaged together -#define INA219_CONFIG_MODE_MASK (0x0007) // Operating Mode Mask +#define INA219_CONFIG_MODE_MASK (0x0007) // Operating Mode Mask #define INA219_CONFIG_MODE_POWERDOWN (0x0000) #define INA219_CONFIG_MODE_SVOLT_TRIGGERED (0x0001) #define INA219_CONFIG_MODE_BVOLT_TRIGGERED (0x0002) @@ -70,6 +71,7 @@ typedef struct { uint32_t calValue; + // The following multipliers are used to convert raw current and power // values to mA and mW, taking into account the current config settings uint32_t currentDivider_mA; @@ -78,11 +80,11 @@ typedef struct { ina219_data _ina219_data[4]; uint8_t Plugin_027_i2c_addr(struct EventStruct *event) { - return (uint8_t)PCONFIG(1); + return (uint8_t)PCONFIG(1); } uint8_t Plugin_027_device_index(const uint8_t i2caddr) { - switch(i2caddr) { + switch (i2caddr) { case INA219_ADDRESS: return 0u; case INA219_ADDRESS2: return 1u; case INA219_ADDRESS3: return 2u; @@ -97,220 +99,222 @@ boolean Plugin_027(byte function, struct EventStruct *event, String& string) switch (function) { - case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_027; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].VType = SENSOR_TYPE_TRIPLE; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = true; - Device[deviceCount].ValueCount = 3; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].GlobalSyncOption = true; - break; - } + { + Device[++deviceCount].Number = PLUGIN_ID_027; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = SENSOR_TYPE_TRIPLE; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 3; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].GlobalSyncOption = true; + break; + } case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_027); - break; - } + { + string = F(PLUGIN_NAME_027); + break; + } case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_027)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_027)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_VALUENAME3_027)); - break; - } + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_027)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_027)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_VALUENAME3_027)); + break; + } case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: - { - int Plugin_27_i2c_addresses[4] = { INA219_ADDRESS, INA219_ADDRESS2, INA219_ADDRESS3, INA219_ADDRESS4 }; - addFormSelectorI2C(F("p027_i2c"), 4, Plugin_27_i2c_addresses, Plugin_027_i2c_addr(event)); - break; - } + { + int Plugin_27_i2c_addresses[4] = { INA219_ADDRESS, INA219_ADDRESS2, INA219_ADDRESS3, INA219_ADDRESS4 }; + addFormSelectorI2C(F("p027_i2c"), 4, Plugin_27_i2c_addresses, Plugin_027_i2c_addr(event)); + break; + } case PLUGIN_WEBFORM_LOAD: + { { - { - byte choiceMode = PCONFIG(0); - String optionsMode[3]; - optionsMode[0] = F("32V, 2A"); - optionsMode[1] = F("32V, 1A"); - optionsMode[2] = F("16V, 0.4A"); - int optionValuesMode[3]; - optionValuesMode[0] = 0; - optionValuesMode[1] = 1; - optionValuesMode[2] = 2; - addFormSelector(F("Measure range"), F("p027_range"), 3, optionsMode, optionValuesMode, choiceMode); - } - { - byte choiceMeasureType = PCONFIG(2); - String options[4] = { F("Voltage"), F("Current"), F("Power"), F("Voltage/Current/Power") }; - addFormSelector(F("Measurement Type"), F("p027_measuretype"), 4, options, NULL, choiceMeasureType ); - } - - success = true; - break; + byte choiceMode = PCONFIG(0); + String optionsMode[3]; + optionsMode[0] = F("32V, 2A"); + optionsMode[1] = F("32V, 1A"); + optionsMode[2] = F("16V, 0.4A"); + int optionValuesMode[3]; + optionValuesMode[0] = 0; + optionValuesMode[1] = 1; + optionValuesMode[2] = 2; + addFormSelector(F("Measure range"), F("p027_range"), 3, optionsMode, optionValuesMode, choiceMode); } + { + byte choiceMeasureType = PCONFIG(2); + String options[4] = { F("Voltage"), F("Current"), F("Power"), F("Voltage/Current/Power") }; + addFormSelector(F("Measurement Type"), F("p027_measuretype"), 4, options, NULL, choiceMeasureType); + } + + success = true; + break; + } case PLUGIN_WEBFORM_SAVE: - { - PCONFIG(0) = getFormItemInt(F("p027_range")); - PCONFIG(1) = getFormItemInt(F("p027_i2c")); - PCONFIG(2) = getFormItemInt(F("p027_measuretype")); - success = true; - break; - } + { + PCONFIG(0) = getFormItemInt(F("p027_range")); + PCONFIG(1) = getFormItemInt(F("p027_i2c")); + PCONFIG(2) = getFormItemInt(F("p027_measuretype")); + success = true; + break; + } case PLUGIN_INIT: + { + const uint8_t i2caddr = Plugin_027_i2c_addr(event); + const uint8_t idx = Plugin_027_device_index(i2caddr); + _ina219_data[idx].currentDivider_mA = 0; + String log = F("INA219 0x"); + log += String(i2caddr, HEX); + log += F(" setting Range to: "); + + switch (PCONFIG(0)) { - const uint8_t i2caddr = Plugin_027_i2c_addr(event); - const uint8_t idx = Plugin_027_device_index(i2caddr); - _ina219_data[idx].currentDivider_mA = 0; - String log = F("INA219 0x"); - log += String(i2caddr,HEX); - log += F(" setting Range to: "); - switch (PCONFIG(0)) + case 0: { - case 0: - { - log += F("32V, 2A"); - Plugin_027_setCalibration_32V_2A(i2caddr); - break; - } - case 1: - { - log += F("32V, 1A"); - Plugin_027_setCalibration_32V_1A(i2caddr); - break; - } - case 2: - { - log += F("16V, 400mA"); - Plugin_027_setCalibration_16V_400mA(i2caddr); - break; - } + log += F("32V, 2A"); + Plugin_027_setCalibration_32V_2A(i2caddr); + break; + } + case 1: + { + log += F("32V, 1A"); + Plugin_027_setCalibration_32V_1A(i2caddr); + break; + } + case 2: + { + log += F("16V, 400mA"); + Plugin_027_setCalibration_16V_400mA(i2caddr); + break; } - addLog(LOG_LEVEL_INFO, log); - success = true; - break; } + addLog(LOG_LEVEL_INFO, log); + success = true; + break; + } case PLUGIN_READ: + { + // shuntvoltage = Plugin_027_getShuntVoltage_mV(); + // busvoltage = Plugin_027_getBusVoltage_V(); + // current_mA = Plugin_027_getCurrent_mA(); + // loadvoltage = Plugin_027_getBusVoltage_V() + (Plugin_027_getShuntVoltage_mV() / 1000); + const uint8_t i2caddr = Plugin_027_i2c_addr(event); + + float voltage = Plugin_027_getBusVoltage_V(i2caddr) + (Plugin_027_getShuntVoltage_mV(i2caddr) / 1000); + float current = Plugin_027_getCurrent_mA(i2caddr) / 1000; + float power = voltage * current; + + UserVar[event->BaseVarIndex] = voltage; + UserVar[event->BaseVarIndex + 1] = current; + UserVar[event->BaseVarIndex + 2] = power; + + String log = F("INA219 0x"); + log += String(i2caddr, HEX); + + // for backward compability we allow the user to select if only one measurement should be returned + // or all 3 measurement at once + switch (PCONFIG(2)) { - // shuntvoltage = Plugin_027_getShuntVoltage_mV(); - // busvoltage = Plugin_027_getBusVoltage_V(); - // current_mA = Plugin_027_getCurrent_mA(); - // loadvoltage = Plugin_027_getBusVoltage_V() + (Plugin_027_getShuntVoltage_mV() / 1000); - const uint8_t i2caddr = Plugin_027_i2c_addr(event); - - float voltage = Plugin_027_getBusVoltage_V(i2caddr) + (Plugin_027_getShuntVoltage_mV(i2caddr) / 1000); - float current = Plugin_027_getCurrent_mA(i2caddr)/1000; - float power = voltage * current; - - UserVar[event->BaseVarIndex] = voltage; - UserVar[event->BaseVarIndex + 1] = current; - UserVar[event->BaseVarIndex + 2] = power; - - String log = F("INA219 0x"); - log += String(i2caddr,HEX); - - // for backward compability we allow the user to select if only one measurement should be returned - // or all 3 measurement at once - switch (PCONFIG(2)) + case 0: { - case 0: - { - event->sensorType = SENSOR_TYPE_SINGLE; - UserVar[event->BaseVarIndex] = voltage; - log += F(": Voltage: "); - log += voltage; - break; - } - case 1: - { - event->sensorType = SENSOR_TYPE_SINGLE; - UserVar[event->BaseVarIndex] = current; - log += F(" Current: "); - log += current; - break; - } - case 2: - { - event->sensorType = SENSOR_TYPE_SINGLE; - UserVar[event->BaseVarIndex] = power; - log += F(" Power: "); - log += power; - break; - } - case 3: - { - event->sensorType = SENSOR_TYPE_TRIPLE; - UserVar[event->BaseVarIndex] = voltage; - UserVar[event->BaseVarIndex+1] = current; - UserVar[event->BaseVarIndex+2] = power; - log += F(": Voltage: "); - log += voltage; - log += F(" Current: "); - log += current; - log += F(" Power: "); - log += power; - break; - } + event->sensorType = SENSOR_TYPE_SINGLE; + UserVar[event->BaseVarIndex] = voltage; + log += F(": Voltage: "); + log += voltage; + break; + } + case 1: + { + event->sensorType = SENSOR_TYPE_SINGLE; + UserVar[event->BaseVarIndex] = current; + log += F(" Current: "); + log += current; + break; + } + case 2: + { + event->sensorType = SENSOR_TYPE_SINGLE; + UserVar[event->BaseVarIndex] = power; + log += F(" Power: "); + log += power; + break; + } + case 3: + { + event->sensorType = SENSOR_TYPE_TRIPLE; + UserVar[event->BaseVarIndex] = voltage; + UserVar[event->BaseVarIndex + 1] = current; + UserVar[event->BaseVarIndex + 2] = power; + log += F(": Voltage: "); + log += voltage; + log += F(" Current: "); + log += current; + log += F(" Power: "); + log += power; + break; } - - addLog(LOG_LEVEL_INFO, log); - success = true; - break; } + + addLog(LOG_LEVEL_INFO, log); + success = true; + break; + } } return success; } -//**************************************************************************/ +// **************************************************************************/ // Sends a single command byte over I2C -//**************************************************************************/ -void Plugin_027_wireWriteRegister (uint8_t i2caddr, uint8_t reg, uint16_t value) +// **************************************************************************/ +void Plugin_027_wireWriteRegister(uint8_t i2caddr, uint8_t reg, uint16_t value) { Wire.beginTransmission(i2caddr); - Wire.write(reg); // Register - Wire.write((value >> 8) & 0xFF); // Upper 8-bits - Wire.write(value & 0xFF); // Lower 8-bits + Wire.write(reg); // Register + Wire.write((value >> 8) & 0xFF); // Upper 8-bits + Wire.write(value & 0xFF); // Lower 8-bits Wire.endTransmission(); } -//**************************************************************************/ +// **************************************************************************/ // Reads a 16 bit values over I2C -//**************************************************************************/ +// **************************************************************************/ void Plugin_027_wireReadRegister(uint8_t i2caddr, uint8_t reg, uint16_t *value) { - Wire.beginTransmission(i2caddr); - Wire.write(reg); // Register + Wire.write(reg); // Register Wire.endTransmission(); - delay(1); // Max 12-bit conversion time is 586us per sample + delay(1); // Max 12-bit conversion time is 586us per sample Wire.requestFrom(i2caddr, (uint8_t)2); + // Shift values to create properly formed integer *value = ((Wire.read() << 8) | Wire.read()); } -//**************************************************************************/ +// **************************************************************************/ // Configures to INA219 to be able to measure up to 32V and 2A + /**************************************************************************/ void Plugin_027_setCalibration_32V_2A(uint8_t i2caddr) { const uint8_t idx = Plugin_027_device_index(i2caddr); + _ina219_data[idx].calValue = 4027; // Set multipliers to convert raw current/power values - _ina219_data[idx].currentDivider_mA = 10; // Current LSB = 100uA per bit (1000/100 = 10) + _ina219_data[idx].currentDivider_mA = 10; // Current LSB = 100uA per bit (1000/100 = 10) // Set Calibration register to 'Cal' calculated above Plugin_027_wireWriteRegister(i2caddr, INA219_REG_CALIBRATION, _ina219_data[idx].calValue); @@ -321,18 +325,20 @@ void Plugin_027_setCalibration_32V_2A(uint8_t i2caddr) { INA219_CONFIG_BADCRES_12BIT | INA219_CONFIG_SADCRES_12BIT_1S_532US | INA219_CONFIG_MODE_SANDBVOLT_CONTINUOUS; + Plugin_027_wireWriteRegister(i2caddr, INA219_REG_CONFIG, config); } -//**************************************************************************/ +// **************************************************************************/ // Configures to INA219 to be able to measure up to 32V and 1A -//**************************************************************************/ +// **************************************************************************/ void Plugin_027_setCalibration_32V_1A(uint8_t i2caddr) { const uint8_t idx = Plugin_027_device_index(i2caddr); + _ina219_data[idx].calValue = 10240; // Set multipliers to convert raw current/power values - _ina219_data[idx].currentDivider_mA = 25; // Current LSB = 40uA per bit (1000/40 = 25) + _ina219_data[idx].currentDivider_mA = 25; // Current LSB = 40uA per bit (1000/40 = 25) // Set Calibration register to 'Cal' calculated above Plugin_027_wireWriteRegister(i2caddr, INA219_REG_CALIBRATION, _ina219_data[idx].calValue); @@ -343,19 +349,20 @@ void Plugin_027_setCalibration_32V_1A(uint8_t i2caddr) { INA219_CONFIG_BADCRES_12BIT | INA219_CONFIG_SADCRES_12BIT_1S_532US | INA219_CONFIG_MODE_SANDBVOLT_CONTINUOUS; + Plugin_027_wireWriteRegister(i2caddr, INA219_REG_CONFIG, config); } -//**************************************************************************/ +// **************************************************************************/ // Configures to INA219 to be able to measure up to 16V and 400mA -//**************************************************************************/ +// **************************************************************************/ void Plugin_027_setCalibration_16V_400mA(uint8_t i2caddr) { const uint8_t idx = Plugin_027_device_index(i2caddr); _ina219_data[idx].calValue = 8192; // Set multipliers to convert raw current/power values - _ina219_data[idx].currentDivider_mA = 20; // Current LSB = 50uA per bit (1000/50 = 20) + _ina219_data[idx].currentDivider_mA = 20; // Current LSB = 50uA per bit (1000/50 = 20) // Set Calibration register to 'Cal' calculated above Plugin_027_wireWriteRegister(i2caddr, INA219_REG_CALIBRATION, _ina219_data[idx].calValue); @@ -366,33 +373,35 @@ void Plugin_027_setCalibration_16V_400mA(uint8_t i2caddr) { INA219_CONFIG_BADCRES_12BIT | INA219_CONFIG_SADCRES_12BIT_1S_532US | INA219_CONFIG_MODE_SANDBVOLT_CONTINUOUS; + Plugin_027_wireWriteRegister(i2caddr, INA219_REG_CONFIG, config); } - -//**************************************************************************/ +// **************************************************************************/ // Gets the raw bus voltage (16-bit signed integer, so +-32767) -//**************************************************************************/ +// **************************************************************************/ int16_t Plugin_027_getBusVoltage_raw(uint8_t i2caddr) { uint16_t value; + Plugin_027_wireReadRegister(i2caddr, INA219_REG_BUSVOLTAGE, &value); // Shift to the right 3 to drop CNVR and OVF and multiply by LSB return (int16_t)((value >> 3) * 4); } -//**************************************************************************/ +// **************************************************************************/ // Gets the raw shunt voltage (16-bit signed integer, so +-32767) -//**************************************************************************/ +// **************************************************************************/ int16_t Plugin_027_getShuntVoltage_raw(uint8_t i2caddr) { uint16_t value; + Plugin_027_wireReadRegister(i2caddr, INA219_REG_SHUNTVOLTAGE, &value); return (int16_t)value; } -//**************************************************************************/ +// **************************************************************************/ // Gets the raw current value (16-bit signed integer, so +-32767) -//**************************************************************************/ +// **************************************************************************/ int16_t Plugin_027_getCurrent_raw(uint8_t i2caddr) { const uint8_t idx = Plugin_027_device_index(i2caddr); uint16_t value; @@ -409,31 +418,35 @@ int16_t Plugin_027_getCurrent_raw(uint8_t i2caddr) { return (int16_t)value; } -//**************************************************************************/ +// **************************************************************************/ // Gets the shunt voltage in mV (so +-327mV) -//**************************************************************************/ +// **************************************************************************/ float Plugin_027_getShuntVoltage_mV(uint8_t i2caddr) { int16_t value; + value = Plugin_027_getShuntVoltage_raw(i2caddr); return value * 0.01; } -//**************************************************************************/ +// **************************************************************************/ // Gets the shunt voltage in volts -//**************************************************************************/ +// **************************************************************************/ float Plugin_027_getBusVoltage_V(uint8_t i2caddr) { int16_t value = Plugin_027_getBusVoltage_raw(i2caddr); + return value * 0.001; } -//**************************************************************************/ +// **************************************************************************/ // Gets the current value in mA, taking into account the // config settings and current LSB -//**************************************************************************/ +// **************************************************************************/ float Plugin_027_getCurrent_mA(uint8_t i2caddr) { const uint8_t idx = Plugin_027_device_index(i2caddr); - float valueDec = Plugin_027_getCurrent_raw(i2caddr); + float valueDec = Plugin_027_getCurrent_raw(i2caddr); + valueDec /= _ina219_data[idx].currentDivider_mA; return valueDec; } + #endif // USES_P027 diff --git a/src/_P028_BME280.ino b/src/_P028_BME280.ino index 2ef0dca53..b9a9974a5 100644 --- a/src/_P028_BME280.ino +++ b/src/_P028_BME280.ino @@ -18,7 +18,6 @@ #define PLUGIN_VALUENAME3_028 "Pressure" - boolean Plugin_028(byte function, struct EventStruct *event, String& string) { boolean success = false; diff --git a/src/_P030_BMP280.ino b/src/_P030_BMP280.ino index 422053025..f32dd6e8c 100644 --- a/src/_P030_BMP280.ino +++ b/src/_P030_BMP280.ino @@ -1,7 +1,8 @@ #ifdef USES_P030 -//####################################################################################################### -//#################### Plugin 030 BMP280 I2C Temp/Barometric Pressure Sensor ####################### -//####################################################################################################### + +// ####################################################################################################### +// #################### Plugin 030 BMP280 I2C Temp/Barometric Pressure Sensor ####################### +// ####################################################################################################### #include "_Plugin_Helper.h" @@ -13,33 +14,33 @@ enum { - BMP280_REGISTER_DIG_T1 = 0x88, - BMP280_REGISTER_DIG_T2 = 0x8A, - BMP280_REGISTER_DIG_T3 = 0x8C, + BMP280_REGISTER_DIG_T1 = 0x88, + BMP280_REGISTER_DIG_T2 = 0x8A, + BMP280_REGISTER_DIG_T3 = 0x8C, - BMP280_REGISTER_DIG_P1 = 0x8E, - BMP280_REGISTER_DIG_P2 = 0x90, - BMP280_REGISTER_DIG_P3 = 0x92, - BMP280_REGISTER_DIG_P4 = 0x94, - BMP280_REGISTER_DIG_P5 = 0x96, - BMP280_REGISTER_DIG_P6 = 0x98, - BMP280_REGISTER_DIG_P7 = 0x9A, - BMP280_REGISTER_DIG_P8 = 0x9C, - BMP280_REGISTER_DIG_P9 = 0x9E, + BMP280_REGISTER_DIG_P1 = 0x8E, + BMP280_REGISTER_DIG_P2 = 0x90, + BMP280_REGISTER_DIG_P3 = 0x92, + BMP280_REGISTER_DIG_P4 = 0x94, + BMP280_REGISTER_DIG_P5 = 0x96, + BMP280_REGISTER_DIG_P6 = 0x98, + BMP280_REGISTER_DIG_P7 = 0x9A, + BMP280_REGISTER_DIG_P8 = 0x9C, + BMP280_REGISTER_DIG_P9 = 0x9E, - BMP280_REGISTER_CHIPID = 0xD0, - BMP280_REGISTER_VERSION = 0xD1, - BMP280_REGISTER_SOFTRESET = 0xE0, + BMP280_REGISTER_CHIPID = 0xD0, + BMP280_REGISTER_VERSION = 0xD1, + BMP280_REGISTER_SOFTRESET = 0xE0, - BMP280_REGISTER_CAL26 = 0xE1, // R calibration stored in 0xE1-0xF0 + BMP280_REGISTER_CAL26 = 0xE1, // R calibration stored in 0xE1-0xF0 - BMP280_REGISTER_CONTROL = 0xF4, - BMP280_REGISTER_CONFIG = 0xF5, - BMP280_REGISTER_PRESSUREDATA = 0xF7, - BMP280_REGISTER_TEMPDATA = 0xFA, + BMP280_REGISTER_CONTROL = 0xF4, + BMP280_REGISTER_CONFIG = 0xF5, + BMP280_REGISTER_PRESSUREDATA = 0xF7, + BMP280_REGISTER_TEMPDATA = 0xFA, - BMP280_CONTROL_SETTING = 0x57, // Oversampling: 16x P, 2x T, normal mode - BMP280_CONFIG_SETTING = 0xE0, // Tstandby 1000ms, filter 16, 3-wire SPI Disable + BMP280_CONTROL_SETTING = 0x57, // Oversampling: 16x P, 2x T, normal mode + BMP280_CONFIG_SETTING = 0xE0, // Tstandby 1000ms, filter 16, 3-wire SPI Disable }; typedef struct @@ -65,7 +66,7 @@ uint8_t bmp280_i2caddr; int32_t bmp280_sensorID; int32_t bmp280_t_fine; -boolean Plugin_030_init[2] = {false, false}; +boolean Plugin_030_init[2] = { false, false }; boolean Plugin_030(byte function, struct EventStruct *event, String& string) { @@ -74,159 +75,168 @@ boolean Plugin_030(byte function, struct EventStruct *event, String& string) switch (function) { case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_030; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].VType = SENSOR_TYPE_TEMP_BARO; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = true; - Device[deviceCount].ValueCount = 2; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].GlobalSyncOption = true; - break; - } + { + Device[++deviceCount].Number = PLUGIN_ID_030; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = SENSOR_TYPE_TEMP_BARO; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 2; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].GlobalSyncOption = true; + break; + } case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_030); - break; - } + { + string = F(PLUGIN_NAME_030); + break; + } case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_030)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_030)); - break; - } + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_030)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_030)); + break; + } case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: - { - byte choice = PCONFIG(0); - /*String options[2] = { F("0x76 - default settings (SDO Low)"), F("0x77 - alternate settings (SDO HIGH)") };*/ - int optionValues[2] = { 0x76, 0x77 }; - addFormSelectorI2C(F("p030_bmp280_i2c"), 2, optionValues, choice); - addFormNote(F("SDO Low=0x76, High=0x77")); - break; - } + { + byte choice = PCONFIG(0); + + /*String options[2] = { F("0x76 - default settings (SDO Low)"), F("0x77 - alternate settings (SDO HIGH)") };*/ + int optionValues[2] = { 0x76, 0x77 }; + addFormSelectorI2C(F("p030_bmp280_i2c"), 2, optionValues, choice); + addFormNote(F("SDO Low=0x76, High=0x77")); + break; + } case PLUGIN_WEBFORM_LOAD: - { - addFormNumericBox(F("Altitude"), F("p030_bmp280_elev"), PCONFIG(1)); - addUnit(F("m")); + { + addFormNumericBox(F("Altitude"), F("p030_bmp280_elev"), PCONFIG(1)); + addUnit(F("m")); - success = true; - break; - } + success = true; + break; + } case PLUGIN_WEBFORM_SAVE: - { - PCONFIG(0) = getFormItemInt(F("p030_bmp280_i2c")); - PCONFIG(1) = getFormItemInt(F("p030_bmp280_elev")); - success = true; - break; - } + { + PCONFIG(0) = getFormItemInt(F("p030_bmp280_i2c")); + PCONFIG(1) = getFormItemInt(F("p030_bmp280_elev")); + success = true; + break; + } case PLUGIN_READ: + { + uint8_t idx = PCONFIG(0) & 0x1; // Addresses are 0x76 and + // 0x77 so we may use it + // this way + Plugin_030_init[idx] &= Plugin_030_check(PCONFIG(0)); // Check id device is + // present + Plugin_030_init[idx] &= (I2C_read8_reg(bmp280_i2caddr, BMP280_REGISTER_CONTROL) == BMP280_CONTROL_SETTING); // Check if the + // coefficients are still + // valid + + if (!Plugin_030_init[idx]) { - uint8_t idx = PCONFIG(0) & 0x1; //Addresses are 0x76 and 0x77 so we may use it this way - Plugin_030_init[idx] &= Plugin_030_check(PCONFIG(0)); // Check id device is present - Plugin_030_init[idx] &= (I2C_read8_reg(bmp280_i2caddr, BMP280_REGISTER_CONTROL) == BMP280_CONTROL_SETTING); // Check if the coefficients are still valid - - if (!Plugin_030_init[idx]) - { - Plugin_030_init[idx] = Plugin_030_begin(PCONFIG(0)); - delay(65); // Ultra high resolution for BMP280 is 43.2 ms, add some extra time - } - - if (Plugin_030_init[idx]) - { - UserVar[event->BaseVarIndex] = Plugin_030_readTemperature(idx); - int elev = PCONFIG(1); - if (elev) - { - UserVar[event->BaseVarIndex + 1] = Plugin_030_pressureElevation((float)Plugin_030_readPressure(idx) / 100, elev); - } else { - UserVar[event->BaseVarIndex + 1] = ((float)Plugin_030_readPressure(idx)) / 100; - } - - String log = F("BMP280 : Address: 0x"); - log += String(bmp280_i2caddr,HEX); - addLog(LOG_LEVEL_INFO, log); - log = F("BMP280 : Temperature: "); - log += UserVar[event->BaseVarIndex]; - addLog(LOG_LEVEL_INFO, log); - log = F("BMP280 : Barometric Pressure: "); - log += UserVar[event->BaseVarIndex + 1]; - addLog(LOG_LEVEL_INFO, log); -/* - log = F("BMP280 : Coefficients [T]: "); - log += _bmp280_calib[idx].dig_T1; - log += ", "; - log += _bmp280_calib[idx].dig_T2; - log += ", "; - log += _bmp280_calib[idx].dig_T3; - addLog(LOG_LEVEL_INFO, log); - log = F("BMP280 : Coefficients [P]: "); - log += _bmp280_calib[idx].dig_P1; - log += ", "; - log += _bmp280_calib[idx].dig_P2; - log += ", "; - log += _bmp280_calib[idx].dig_P3; - log += ", "; - log += _bmp280_calib[idx].dig_P4; - log += ", "; - log += _bmp280_calib[idx].dig_P5; - log += ", "; - log += _bmp280_calib[idx].dig_P6; - log += ", "; - log += _bmp280_calib[idx].dig_P7; - log += ", "; - log += _bmp280_calib[idx].dig_P8; - log += ", "; - log += _bmp280_calib[idx].dig_P9; - addLog(LOG_LEVEL_INFO, log); -*/ - success = true; - } - break; + Plugin_030_init[idx] = Plugin_030_begin(PCONFIG(0)); + delay(65); // Ultra high resolution for BMP280 is 43.2 ms, add some extra time } + if (Plugin_030_init[idx]) + { + UserVar[event->BaseVarIndex] = Plugin_030_readTemperature(idx); + int elev = PCONFIG(1); + + if (elev) + { + UserVar[event->BaseVarIndex + 1] = Plugin_030_pressureElevation((float)Plugin_030_readPressure(idx) / 100, elev); + } else { + UserVar[event->BaseVarIndex + 1] = ((float)Plugin_030_readPressure(idx)) / 100; + } + + String log = F("BMP280 : Address: 0x"); + log += String(bmp280_i2caddr, HEX); + addLog(LOG_LEVEL_INFO, log); + log = F("BMP280 : Temperature: "); + log += UserVar[event->BaseVarIndex]; + addLog(LOG_LEVEL_INFO, log); + log = F("BMP280 : Barometric Pressure: "); + log += UserVar[event->BaseVarIndex + 1]; + addLog(LOG_LEVEL_INFO, log); + + /* + log = F("BMP280 : Coefficients [T]: "); + log += _bmp280_calib[idx].dig_T1; + log += ", "; + log += _bmp280_calib[idx].dig_T2; + log += ", "; + log += _bmp280_calib[idx].dig_T3; + addLog(LOG_LEVEL_INFO, log); + log = F("BMP280 : Coefficients [P]: "); + log += _bmp280_calib[idx].dig_P1; + log += ", "; + log += _bmp280_calib[idx].dig_P2; + log += ", "; + log += _bmp280_calib[idx].dig_P3; + log += ", "; + log += _bmp280_calib[idx].dig_P4; + log += ", "; + log += _bmp280_calib[idx].dig_P5; + log += ", "; + log += _bmp280_calib[idx].dig_P6; + log += ", "; + log += _bmp280_calib[idx].dig_P7; + log += ", "; + log += _bmp280_calib[idx].dig_P8; + log += ", "; + log += _bmp280_calib[idx].dig_P9; + addLog(LOG_LEVEL_INFO, log); + */ + success = true; + } + break; + } } return success; } -//**************************************************************************/ +// **************************************************************************/ // Check BMP280 presence -//**************************************************************************/ +// **************************************************************************/ bool Plugin_030_check(uint8_t a) { - bmp280_i2caddr = a?a:0x76; + bmp280_i2caddr = a ? a : 0x76; bool wire_status = false; + if (I2C_read8_reg(bmp280_i2caddr, BMP280_REGISTER_CHIPID, &wire_status) != 0x58) { - return false; + return false; } else { - return wire_status; + return wire_status; } } -//**************************************************************************/ +// **************************************************************************/ // Initialize BMP280 -//**************************************************************************/ +// **************************************************************************/ bool Plugin_030_begin(uint8_t a) { - if (! Plugin_030_check(a)) + if (!Plugin_030_check(a)) { return false; + } Plugin_030_readCoefficients(a & 0x1); I2C_write8_reg(bmp280_i2caddr, BMP280_REGISTER_CONTROL, BMP280_CONTROL_SETTING); - I2C_write8_reg(bmp280_i2caddr, BMP280_REGISTER_CONFIG, BMP280_CONFIG_SETTING); + I2C_write8_reg(bmp280_i2caddr, BMP280_REGISTER_CONFIG, BMP280_CONFIG_SETTING); return true; } -//**************************************************************************/ +// **************************************************************************/ // Reads the factory-set coefficients -//**************************************************************************/ +// **************************************************************************/ void Plugin_030_readCoefficients(uint8_t idx) { _bmp280_calib[idx].dig_T1 = I2C_read16_LE_reg(bmp280_i2caddr, BMP280_REGISTER_DIG_T1); @@ -244,36 +254,39 @@ void Plugin_030_readCoefficients(uint8_t idx) _bmp280_calib[idx].dig_P9 = I2C_readS16_LE_reg(bmp280_i2caddr, BMP280_REGISTER_DIG_P9); } -//**************************************************************************/ +// **************************************************************************/ // Read temperature -//**************************************************************************/ +// **************************************************************************/ float Plugin_030_readTemperature(uint8_t idx) { int32_t var1, var2; int32_t adc_T = I2C_read24_reg(bmp280_i2caddr, BMP280_REGISTER_TEMPDATA); + adc_T >>= 4; - var1 = ((((adc_T >> 3) - ((int32_t)_bmp280_calib[idx].dig_T1 << 1))) * - ((int32_t)_bmp280_calib[idx].dig_T2)) >> 11; + var1 = ((((adc_T >> 3) - ((int32_t)_bmp280_calib[idx].dig_T1 << 1))) * + ((int32_t)_bmp280_calib[idx].dig_T2)) >> 11; - var2 = (((((adc_T >> 4) - ((int32_t)_bmp280_calib[idx].dig_T1)) * - ((adc_T >> 4) - ((int32_t)_bmp280_calib[idx].dig_T1))) >> 12) * - ((int32_t)_bmp280_calib[idx].dig_T3)) >> 14; + var2 = (((((adc_T >> 4) - ((int32_t)_bmp280_calib[idx].dig_T1)) * + ((adc_T >> 4) - ((int32_t)_bmp280_calib[idx].dig_T1))) >> 12) * + ((int32_t)_bmp280_calib[idx].dig_T3)) >> 14; bmp280_t_fine = var1 + var2; - float T = (bmp280_t_fine * 5 + 128) >> 8; + float T = (bmp280_t_fine * 5 + 128) >> 8; + return T / 100; } -//**************************************************************************/ +// **************************************************************************/ // Read pressure -//**************************************************************************/ +// **************************************************************************/ float Plugin_030_readPressure(uint8_t idx) { int64_t var1, var2, p; int32_t adc_P = I2C_read24_reg(bmp280_i2caddr, BMP280_REGISTER_PRESSUREDATA); + adc_P >>= 4; var1 = ((int64_t)bmp280_t_fine) - 128000; @@ -285,10 +298,10 @@ float Plugin_030_readPressure(uint8_t idx) { var1 = (((((int64_t)1) << 47) + var1)) * ((int64_t)_bmp280_calib[idx].dig_P1) >> 33; if (var1 == 0) { - return 0; // avoid exception caused by division by zero + return 0; // avoid exception caused by division by zero } - p = 1048576 - adc_P; - p = (((p << 31) - var2) * 3125) / var1; + p = 1048576 - adc_P; + p = (((p << 31) - var2) * 3125) / var1; var1 = (((int64_t)_bmp280_calib[idx].dig_P9) * (p >> 13) * (p >> 13)) >> 25; var2 = (((int64_t)_bmp280_calib[idx].dig_P8) * p) >> 19; @@ -296,22 +309,24 @@ float Plugin_030_readPressure(uint8_t idx) { return (float)p / 256; } -//**************************************************************************/ +// **************************************************************************/ // Calculates the altitude (in meters) from the specified atmospheric // pressure (in hPa), and sea-level pressure (in hPa). // @param seaLevel Sea-level pressure in hPa // @param atmospheric Atmospheric pressure in hPa -//**************************************************************************/ +// **************************************************************************/ float Plugin_030_readAltitude(float seaLevel) { float atmospheric = Plugin_030_readPressure(bmp280_i2caddr & 0x01) / 100.0F; + return 44330.0 * (1.0 - pow(atmospheric / seaLevel, 0.1903)); } -//**************************************************************************/ +// **************************************************************************/ // MSL pressure formula -//**************************************************************************/ +// **************************************************************************/ float Plugin_030_pressureElevation(float atmospheric, int altitude) { - return atmospheric / pow(1.0 - (altitude/44330.0), 5.255); + return atmospheric / pow(1.0 - (altitude / 44330.0), 5.255); } + #endif // USES_P030 diff --git a/src/_P032_MS5611.ino b/src/_P032_MS5611.ino index 3f5dc0608..8fa28226d 100644 --- a/src/_P032_MS5611.ino +++ b/src/_P032_MS5611.ino @@ -1,7 +1,8 @@ #ifdef USES_P032 -//####################################################################################################### -//################ Plugin 032 MS5611 (GY-63) I2C Temp/Barometric Pressure Sensor ####################### -//####################################################################################################### + +// ####################################################################################################### +// ################ Plugin 032 MS5611 (GY-63) I2C Temp/Barometric Pressure Sensor ####################### +// ####################################################################################################### // This sketch is based on https://github.com/Schm1tz1/arduino-ms5xxx #include "_Plugin_Helper.h" @@ -14,25 +15,24 @@ enum { - MS5xxx_CMD_RESET = 0x1E, // perform reset - MS5xxx_CMD_ADC_READ = 0x00, // initiate read sequence - MS5xxx_CMD_ADC_CONV = 0x40, // start conversion - MS5xxx_CMD_ADC_D1 = 0x00, // read ADC 1 - MS5xxx_CMD_ADC_D2 = 0x10, // read ADC 2 - MS5xxx_CMD_ADC_256 = 0x00, // set ADC oversampling ratio to 256 - MS5xxx_CMD_ADC_512 = 0x02, // set ADC oversampling ratio to 512 - MS5xxx_CMD_ADC_1024 = 0x04, // set ADC oversampling ratio to 1024 - MS5xxx_CMD_ADC_2048 = 0x06, // set ADC oversampling ratio to 2048 - MS5xxx_CMD_ADC_4096 = 0x08, // set ADC oversampling ratio to 4096 - MS5xxx_CMD_PROM_RD = 0xA0 // initiate readout of PROM registers + MS5xxx_CMD_RESET = 0x1E, // perform reset + MS5xxx_CMD_ADC_READ = 0x00, // initiate read sequence + MS5xxx_CMD_ADC_CONV = 0x40, // start conversion + MS5xxx_CMD_ADC_D1 = 0x00, // read ADC 1 + MS5xxx_CMD_ADC_D2 = 0x10, // read ADC 2 + MS5xxx_CMD_ADC_256 = 0x00, // set ADC oversampling ratio to 256 + MS5xxx_CMD_ADC_512 = 0x02, // set ADC oversampling ratio to 512 + MS5xxx_CMD_ADC_1024 = 0x04, // set ADC oversampling ratio to 1024 + MS5xxx_CMD_ADC_2048 = 0x06, // set ADC oversampling ratio to 2048 + MS5xxx_CMD_ADC_4096 = 0x08, // set ADC oversampling ratio to 4096 + MS5xxx_CMD_PROM_RD = 0xA0 // initiate readout of PROM registers }; - uint8_t ms5611_i2caddr; unsigned int ms5611_prom[8]; -double ms5611_pressure; -double ms5611_temperature; +double ms5611_pressure; +double ms5611_temperature; boolean Plugin_032_init = false; @@ -43,190 +43,195 @@ boolean Plugin_032(byte function, struct EventStruct *event, String& string) switch (function) { case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_032; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].VType = SENSOR_TYPE_TEMP_BARO; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = true; - Device[deviceCount].ValueCount = 2; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].GlobalSyncOption = true; - break; - } + { + Device[++deviceCount].Number = PLUGIN_ID_032; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = SENSOR_TYPE_TEMP_BARO; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 2; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].GlobalSyncOption = true; + break; + } case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_032); - break; - } + { + string = F(PLUGIN_NAME_032); + break; + } case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_032)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_032)); - break; - } + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_032)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_032)); + break; + } case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: - { - byte choice = PCONFIG(0); - /*String options[2] = { F("0x77 - default I2C address"), F("0x76 - alternate I2C address") };*/ - int optionValues[2] = { 0x77, 0x76 }; - addFormSelectorI2C(F("p032_ms5611_i2c"), 2, optionValues, choice); - break; - } + { + byte choice = PCONFIG(0); + + /*String options[2] = { F("0x77 - default I2C address"), F("0x76 - alternate I2C address") };*/ + int optionValues[2] = { 0x77, 0x76 }; + addFormSelectorI2C(F("p032_ms5611_i2c"), 2, optionValues, choice); + break; + } case PLUGIN_WEBFORM_LOAD: - { - addFormNumericBox(F("Altitude [m]"), F("p032_ms5611_elev"), PCONFIG(1)); + { + addFormNumericBox(F("Altitude [m]"), F("p032_ms5611_elev"), PCONFIG(1)); - success = true; - break; - } + success = true; + break; + } case PLUGIN_WEBFORM_SAVE: - { - PCONFIG(0) = getFormItemInt(F("p032_ms5611_i2c")); - PCONFIG(1) = getFormItemInt(F("p032_ms5611_elev")); - success = true; - break; - } + { + PCONFIG(0) = getFormItemInt(F("p032_ms5611_i2c")); + PCONFIG(1) = getFormItemInt(F("p032_ms5611_elev")); + success = true; + break; + } case PLUGIN_READ: + { + if (!Plugin_032_init) { - if (!Plugin_032_init) - { - Plugin_032_init = Plugin_032_begin(PCONFIG(0)); - } - - if (Plugin_032_init) { - Plugin_032_read_prom(); - Plugin_032_readout(); - - UserVar[event->BaseVarIndex] = ms5611_temperature / 100; - int elev = PCONFIG(1); - if (elev) - { - UserVar[event->BaseVarIndex + 1] = Plugin_032_pressureElevation(ms5611_pressure, elev); - } else { - UserVar[event->BaseVarIndex + 1] = ms5611_pressure; - } - - String log = F("MS5611 : Temperature: "); - log += UserVar[event->BaseVarIndex]; - addLog(LOG_LEVEL_INFO, log); - log = F("MS5611 : Barometric Pressure: "); - log += UserVar[event->BaseVarIndex + 1]; - addLog(LOG_LEVEL_INFO, log); - success = true; - } - break; + Plugin_032_init = Plugin_032_begin(PCONFIG(0)); } + if (Plugin_032_init) { + Plugin_032_read_prom(); + Plugin_032_readout(); + + UserVar[event->BaseVarIndex] = ms5611_temperature / 100; + int elev = PCONFIG(1); + + if (elev) + { + UserVar[event->BaseVarIndex + 1] = Plugin_032_pressureElevation(ms5611_pressure, elev); + } else { + UserVar[event->BaseVarIndex + 1] = ms5611_pressure; + } + + String log = F("MS5611 : Temperature: "); + log += UserVar[event->BaseVarIndex]; + addLog(LOG_LEVEL_INFO, log); + log = F("MS5611 : Barometric Pressure: "); + log += UserVar[event->BaseVarIndex + 1]; + addLog(LOG_LEVEL_INFO, log); + success = true; + } + break; + } } return success; } -//**************************************************************************/ +// **************************************************************************/ // Initialize MS5611 -//**************************************************************************/ +// **************************************************************************/ bool Plugin_032_begin(uint8_t a) { ms5611_i2caddr = a; Wire.beginTransmission((uint8_t)ms5611_i2caddr); - uint8_t ret=Wire.endTransmission(true); - return ret==0; + uint8_t ret = Wire.endTransmission(true); + + return ret == 0; } -//**************************************************************************/ +// **************************************************************************/ // Reads the PROM of MS5611 // There are in total 8 addresses resulting in a total memory of 128 bit. // Address 0 contains factory data and the setup, addresses 1-6 calibration // coefficients and address 7 contains the serial code and CRC. // The command sequence is 8 bits long with a 16 bit result which is // clocked with the MSB first. -//**************************************************************************/ +// **************************************************************************/ void Plugin_032_read_prom() { I2C_write8(ms5611_i2caddr, MS5xxx_CMD_RESET); delay(3); - for(uint8_t i=0;i<8;i++) + for (uint8_t i = 0; i < 8; i++) { - ms5611_prom[i] = I2C_read16_reg(ms5611_i2caddr, MS5xxx_CMD_PROM_RD+2*i); + ms5611_prom[i] = I2C_read16_reg(ms5611_i2caddr, MS5xxx_CMD_PROM_RD + 2 * i); } } -//**************************************************************************/ +// **************************************************************************/ // Read analog/digital converter -//**************************************************************************/ +// **************************************************************************/ unsigned long Plugin_032_read_adc(unsigned char aCMD) { - I2C_write8(ms5611_i2caddr, MS5xxx_CMD_ADC_CONV+aCMD); // start DAQ and conversion of ADC data + I2C_write8(ms5611_i2caddr, MS5xxx_CMD_ADC_CONV + aCMD); // start DAQ and conversion of ADC data + switch (aCMD & 0x0f) { - case MS5xxx_CMD_ADC_256 : delayMicroseconds(900); - break; - case MS5xxx_CMD_ADC_512 : delay(3); - break; + case MS5xxx_CMD_ADC_256: delayMicroseconds(900); + break; + case MS5xxx_CMD_ADC_512: delay(3); + break; case MS5xxx_CMD_ADC_1024: delay(4); - break; + break; case MS5xxx_CMD_ADC_2048: delay(6); - break; + break; case MS5xxx_CMD_ADC_4096: delay(10); - break; + break; } + // read out values return I2C_read24_reg(ms5611_i2caddr, MS5xxx_CMD_ADC_READ); } - -//**************************************************************************/ +// **************************************************************************/ // Readout -//**************************************************************************/ +// **************************************************************************/ void Plugin_032_readout() { - - unsigned long D1=0, D2=0; + unsigned long D1 = 0, D2 = 0; double dT; double OFF; double SENS; - D2=Plugin_032_read_adc(MS5xxx_CMD_ADC_D2+MS5xxx_CMD_ADC_4096); - D1=Plugin_032_read_adc(MS5xxx_CMD_ADC_D1+MS5xxx_CMD_ADC_4096); + D2 = Plugin_032_read_adc(MS5xxx_CMD_ADC_D2 + MS5xxx_CMD_ADC_4096); + D1 = Plugin_032_read_adc(MS5xxx_CMD_ADC_D1 + MS5xxx_CMD_ADC_4096); // calculate 1st order pressure and temperature (MS5611 1st order algorithm) - dT=D2-ms5611_prom[5]*pow(2,8); - OFF=ms5611_prom[2]*pow(2,16)+dT*ms5611_prom[4]/pow(2,7); - SENS=ms5611_prom[1]*pow(2,15)+dT*ms5611_prom[3]/pow(2,8); - ms5611_temperature=(2000+(dT*ms5611_prom[6])/pow(2,23)); - ms5611_pressure=(((D1*SENS)/pow(2,21)-OFF)/pow(2,15)); + dT = D2 - ms5611_prom[5] * pow(2, 8); + OFF = ms5611_prom[2] * pow(2, 16) + dT * ms5611_prom[4] / pow(2, 7); + SENS = ms5611_prom[1] * pow(2, 15) + dT * ms5611_prom[3] / pow(2, 8); + ms5611_temperature = (2000 + (dT * ms5611_prom[6]) / pow(2, 23)); + ms5611_pressure = (((D1 * SENS) / pow(2, 21) - OFF) / pow(2, 15)); // perform higher order corrections - double T2=0., OFF2=0., SENS2=0.; - if(ms5611_temperature<2000) { - T2=dT*dT/pow(2,31); - OFF2=5*(ms5611_temperature-2000)*(ms5611_temperature-2000)/pow(2,1); - SENS2=5*(ms5611_temperature-2000)*(ms5611_temperature-2000)/pow(2,2); - if(ms5611_temperature<-1500) { - OFF2+=7*(ms5611_temperature+1500)*(ms5611_temperature+1500); - SENS2+=11*(ms5611_temperature+1500)*(ms5611_temperature+1500)/pow(2,1); + double T2 = 0., OFF2 = 0., SENS2 = 0.; + + if (ms5611_temperature < 2000) { + T2 = dT * dT / pow(2, 31); + OFF2 = 5 * (ms5611_temperature - 2000) * (ms5611_temperature - 2000) / pow(2, 1); + SENS2 = 5 * (ms5611_temperature - 2000) * (ms5611_temperature - 2000) / pow(2, 2); + + if (ms5611_temperature < -1500) { + OFF2 += 7 * (ms5611_temperature + 1500) * (ms5611_temperature + 1500); + SENS2 += 11 * (ms5611_temperature + 1500) * (ms5611_temperature + 1500) / pow(2, 1); } } - ms5611_temperature-=T2; - OFF-=OFF2; - SENS-=SENS2; - ms5611_pressure=(((D1*SENS)/pow(2,21)-OFF)/pow(2,15)); + ms5611_temperature -= T2; + OFF -= OFF2; + SENS -= SENS2; + ms5611_pressure = (((D1 * SENS) / pow(2, 21) - OFF) / pow(2, 15)); } -//**************************************************************************/ +// **************************************************************************/ // MSL pressure formula -//**************************************************************************/ +// **************************************************************************/ double Plugin_032_pressureElevation(double atmospheric, int altitude) { - return atmospheric / pow(1.0 - (altitude/44330.0), 5.255); + return atmospheric / pow(1.0 - (altitude / 44330.0), 5.255); } + #endif // USES_P032 diff --git a/src/_P034_DHT12.ino b/src/_P034_DHT12.ino index b86d59a09..28bea79ef 100644 --- a/src/_P034_DHT12.ino +++ b/src/_P034_DHT12.ino @@ -1,7 +1,8 @@ #ifdef USES_P034 -//####################################################################################################### -//######################## Plugin 034: Temperature and Humidity sensor DHT 12 (I2C) ##################### -//####################################################################################################### + +// ####################################################################################################### +// ######################## Plugin 034: Temperature and Humidity sensor DHT 12 (I2C) ##################### +// ####################################################################################################### #include "_Plugin_Helper.h" @@ -22,94 +23,101 @@ boolean Plugin_034(byte function, struct EventStruct *event, String& string) switch (function) { case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_034; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].VType = SENSOR_TYPE_TEMP_HUM; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = true; - Device[deviceCount].ValueCount = 2; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].GlobalSyncOption = true; - break; - } + { + Device[++deviceCount].Number = PLUGIN_ID_034; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = SENSOR_TYPE_TEMP_HUM; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 2; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].GlobalSyncOption = true; + break; + } case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_034); - break; - } + { + string = F(PLUGIN_NAME_034); + break; + } case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_034)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_034)); - break; - } + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_034)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_034)); + break; + } case PLUGIN_READ: - { - byte dht_dat[5]; - // byte dht_in; - byte i; - // byte Retry = 0; - boolean error = false; + { + byte dht_dat[5]; - Wire.beginTransmission(DHT12_I2C_ADDRESS); // start transmission to device - Wire.write(0); // sends register address to read from - Wire.endTransmission(); // end transmission + // byte dht_in; + byte i; - if (Wire.requestFrom(DHT12_I2C_ADDRESS, 5) == 5) { // send data n-bytes read - for (i = 0; i < 5; i++) - { - dht_dat[i] = Wire.read(); // receive DATA - } - } else { - error = true; - } - if (!error) + // byte Retry = 0; + boolean error = false; + + Wire.beginTransmission(DHT12_I2C_ADDRESS); // start transmission to device + Wire.write(0); // sends register address to read from + Wire.endTransmission(); // end transmission + + if (Wire.requestFrom(DHT12_I2C_ADDRESS, 5) == 5) { // send data n-bytes read + for (i = 0; i < 5; i++) { - // Checksum calculation is a Rollover Checksum by design! - byte dht_check_sum = dht_dat[0] + dht_dat[1] + dht_dat[2] + dht_dat[3]; // check check_sum - - if (dht_dat[4] == dht_check_sum) - { - float temperature = float(dht_dat[2]*10 + (dht_dat[3] & 0x7f)) / 10.0; // Temperature - if (dht_dat[3] & 0x80) { temperature = -temperature; } - float humidity = float(dht_dat[0]*10+dht_dat[1]) / 10.0; // Humidity - - UserVar[event->BaseVarIndex] = temperature; - UserVar[event->BaseVarIndex + 1] = humidity; - String log = F("DHT12: Temperature: "); - log += UserVar[event->BaseVarIndex]; - addLog(LOG_LEVEL_INFO, log); - log = F("DHT12: Humidity: "); - log += UserVar[event->BaseVarIndex + 1]; - addLog(LOG_LEVEL_INFO, log); -/* - log = F("DHT12: Data: "); - for (int i=0; i < 5; i++) - { - log += dht_dat[i]; - log += ", "; - } - addLog(LOG_LEVEL_INFO, log); -*/ - success = true; - } // checksum - } // error - if(!success) - { - addLog(LOG_LEVEL_INFO, F("DHT12: No reading!")); - UserVar[event->BaseVarIndex] = NAN; - UserVar[event->BaseVarIndex + 1] = NAN; + dht_dat[i] = Wire.read(); // receive DATA } - break; + } else { + error = true; } + + if (!error) + { + // Checksum calculation is a Rollover Checksum by design! + byte dht_check_sum = dht_dat[0] + dht_dat[1] + dht_dat[2] + dht_dat[3]; // check check_sum + + if (dht_dat[4] == dht_check_sum) + { + float temperature = float(dht_dat[2] * 10 + (dht_dat[3] & 0x7f)) / 10.0; // Temperature + + if (dht_dat[3] & 0x80) { temperature = -temperature; } + float humidity = float(dht_dat[0] * 10 + dht_dat[1]) / 10.0; // Humidity + + UserVar[event->BaseVarIndex] = temperature; + UserVar[event->BaseVarIndex + 1] = humidity; + String log = F("DHT12: Temperature: "); + log += UserVar[event->BaseVarIndex]; + addLog(LOG_LEVEL_INFO, log); + log = F("DHT12: Humidity: "); + log += UserVar[event->BaseVarIndex + 1]; + addLog(LOG_LEVEL_INFO, log); + + /* + log = F("DHT12: Data: "); + for (int i=0; i < 5; i++) + { + log += dht_dat[i]; + log += ", "; + } + addLog(LOG_LEVEL_INFO, log); + */ + success = true; + } // checksum + } // error + + if (!success) + { + addLog(LOG_LEVEL_INFO, F("DHT12: No reading!")); + UserVar[event->BaseVarIndex] = NAN; + UserVar[event->BaseVarIndex + 1] = NAN; + } + break; + } } return success; } + #endif // USES_P034 diff --git a/src/_P036_FrameOLED.ino b/src/_P036_FrameOLED.ino index 4d8fd5f0f..a77fff825 100644 --- a/src/_P036_FrameOLED.ino +++ b/src/_P036_FrameOLED.ino @@ -93,8 +93,6 @@ #define P036_RESOLUTION PCONFIG(7) - - boolean Plugin_036(uint8_t function, struct EventStruct *event, String& string) { bool success = false; @@ -172,7 +170,11 @@ boolean Plugin_036(uint8_t function, struct EventStruct *event, String& string) { p036_resolution tOLEDIndex = static_cast(P036_RESOLUTION); - addFormNumericBox(F("Lines per Frame"), F("p036_nlines"), P036_NLINES, 1, P036_data_struct::getDisplaySizeSettings(tOLEDIndex).MaxLines); + addFormNumericBox(F("Lines per Frame"), + F("p036_nlines"), + P036_NLINES, + 1, + P036_data_struct::getDisplaySizeSettings(tOLEDIndex).MaxLines); } { @@ -189,10 +191,10 @@ boolean Plugin_036(uint8_t function, struct EventStruct *event, String& string) // FIXME TD-er: Why is this using pin3 and not pin1? And why isn't this using the normal pin selection functions? addFormPinSelect(F("Display button"), F("taskdevicepin3"), CONFIG_PIN3); - bool tbPin3Invers = bitRead(PCONFIG_LONG(0), 16); // Bit 16 - addFormCheckBox(F("Inversed Logic"), F("p036_pin3invers"), tbPin3Invers); + bool tbPin3Invers = bitRead(PCONFIG_LONG(0), 16); // Bit 16 + addFormCheckBox(F("Inversed Logic"), F("p036_pin3invers"), tbPin3Invers); bool bStepThroughPages = bitRead(PCONFIG_LONG(0), 19); // Bit 19 - addFormCheckBox(F("Step through frames with Display button"), F("p036_StepPages"), bStepThroughPages); + addFormCheckBox(F("Step through frames with Display button"), F("p036_StepPages"), bStepThroughPages); addFormNumericBox(F("Display Timeout"), F("p036_timer"), P036_TIMER); @@ -221,8 +223,8 @@ boolean Plugin_036(uint8_t function, struct EventStruct *event, String& string) uint8_t choice9 = get8BitFromUL(PCONFIG_LONG(0), 8); // Bit15-8 HeaderContent uint8_t choice10 = get8BitFromUL(PCONFIG_LONG(0), 0); // Bit7-0 HeaderContentAlternative String options9[14] = - { F("SSID"), F("SysName"), F("IP"), F("MAC"), F("RSSI"), F("BSSID"), - F("WiFi channel"), F("Unit"), F("SysLoad"), F("SysHeap"), F("SysStack"), F("Date"), + { F("SSID"), F("SysName"), F("IP"), F("MAC"), F("RSSI"), F("BSSID"), + F("WiFi channel"), F("Unit"), F("SysLoad"), F("SysHeap"), F("SysStack"), F("Date"), F("Time"), F("PageNumbers") }; int optionValues9[14] = { eSSID, eSysName, eIP, eMAC, eRSSI, eBSSID, eWiFiCh, eUnit, eSysLoad, eSysHeap, eSysStack, eDate, eTime, ePageNo }; @@ -234,7 +236,7 @@ boolean Plugin_036(uint8_t function, struct EventStruct *event, String& string) addFormCheckBox(F("Scroll long lines"), F("p036_ScrollLines"), tbScrollLines); bool tbNoDisplayOnReceivedText = bitRead(PCONFIG_LONG(0), 18); // Bit 18 - addFormCheckBox(F("Wake display on receiving text"), F("p036_NoDisplay"), !tbNoDisplayOnReceivedText); + addFormCheckBox(F("Wake display on receiving text"), F("p036_NoDisplay"), !tbNoDisplayOnReceivedText); addFormNote(F("When checked, the display wakes up at receiving remote updates.")); { @@ -242,6 +244,7 @@ boolean Plugin_036(uint8_t function, struct EventStruct *event, String& string) // This data in memory can be altered through write commands. // Therefore we must read the lines from flash in a temporary object. P036_data_struct *P036_data = new P036_data_struct(); + if (nullptr != P036_data) { uint8_t version = get4BitFromUL(PCONFIG_LONG(0), 20); // Bit23-20 Version CustomTaskSettings P036_data->loadDisplayLines(event->TaskIndex, version); @@ -253,6 +256,7 @@ boolean Plugin_036(uint8_t function, struct EventStruct *event, String& string) String(P036_data->DisplayLinesV1[varNr].Content), P36_NcharsV1 - 1); } + // Need to delete the allocated object here delete P036_data; } @@ -283,12 +287,13 @@ boolean Plugin_036(uint8_t function, struct EventStruct *event, String& string) uint32_t lSettings = 0; set8BitToUL(lSettings, 8, uint8_t(getFormItemInt(F("p036_header")) & 0xff)); // Bit15-8 HeaderContent set8BitToUL(lSettings, 0, uint8_t(getFormItemInt(F("p036_headerAlternate")) & 0xff)); // Bit 7-0 HeaderContentAlternative - bitWrite(lSettings, 16, isFormItemChecked(F("p036_pin3invers"))); // Bit 16 Pin3Invers - bitWrite(lSettings, 17, isFormItemChecked(F("p036_ScrollLines"))); // Bit 17 ScrollLines + bitWrite(lSettings, 16, isFormItemChecked(F("p036_pin3invers"))); // Bit 16 Pin3Invers + bitWrite(lSettings, 17, isFormItemChecked(F("p036_ScrollLines"))); // Bit 17 ScrollLines bitWrite(lSettings, 18, !isFormItemChecked(F("p036_NoDisplay"))); // Bit 18 NoDisplayOnReceivingText - bitWrite(lSettings, 19, isFormItemChecked(F("p036_StepPages"))); // Bit 19 StepThroughPagesWithButton + bitWrite(lSettings, 19, isFormItemChecked(F("p036_StepPages"))); // Bit 19 StepThroughPagesWithButton // save CustomTaskSettings always in version V1 - set4BitToUL(lSettings, 20, 0x01); // Bit23-20 Version CustomTaskSettings -> version V1 + set4BitToUL(lSettings, 20, 0x01); // Bit23-20 Version CustomTaskSettings -> + // version V1 bitWrite(lSettings, 24, !isFormItemChecked(F("p036_ScrollWithoutWifi"))); // Bit 24 ScrollWithoutWifi PCONFIG_LONG(0) = lSettings; @@ -298,6 +303,7 @@ boolean Plugin_036(uint8_t function, struct EventStruct *event, String& string) // This data in memory can be altered through write commands. // Therefore we must use a temporary version to store the settings. P036_data_struct *P036_data = new P036_data_struct(); + if (nullptr != P036_data) { String error; @@ -352,17 +358,17 @@ boolean Plugin_036(uint8_t function, struct EventStruct *event, String& string) } // Load the custom settings from flash - uint8_t version = get4BitFromUL(PCONFIG_LONG(0), 20);// Bit23-20 Version CustomTaskSettings + uint8_t version = get4BitFromUL(PCONFIG_LONG(0), 20); // Bit23-20 Version CustomTaskSettings // Init the display and turn it on if (!(P036_data->init(event->TaskIndex, version, - P036_CONTROLLER,// Type - P036_ADR, // I2C address + P036_CONTROLLER, // Type + P036_ADR, // I2C address Settings.Pin_i2c_sda, Settings.Pin_i2c_scl, - static_cast(P036_RESOLUTION), // OLED index - (P036_ROTATE == 2), // 1 = Normal, 2 = Rotated + static_cast(P036_RESOLUTION), // OLED index + (P036_ROTATE == 2), // 1 = Normal, 2 = Rotated P036_CONTRAST, P036_TIMER, P036_NLINES @@ -420,7 +426,7 @@ boolean Plugin_036(uint8_t function, struct EventStruct *event, String& string) if (CONFIG_PIN3 != -1) { uint8_t newButtonState = digitalRead(CONFIG_PIN3); - bool bPin3Invers = bitRead(PCONFIG_LONG(0), 16); // Bit 16 + bool bPin3Invers = bitRead(PCONFIG_LONG(0), 16); // Bit 16 P036_data->registerButtonState(newButtonState, bPin3Invers); } success = true; @@ -553,8 +559,8 @@ boolean Plugin_036(uint8_t function, struct EventStruct *event, String& string) return success; } - if ((UserVar[event->BaseVarIndex] == 1) && P036_data->display_scroll_timer()) { // page scrolling only when the display is on - Scheduler.setPluginTaskTimer(P36_PageScrollTimer, event->TaskIndex, event->Par1); // calls next page scrollng tick + if ((UserVar[event->BaseVarIndex] == 1) && P036_data->display_scroll_timer()) { // page scrolling only when the display is on + Scheduler.setPluginTaskTimer(P36_PageScrollTimer, event->TaskIndex, event->Par1); // calls next page scrollng tick } return success; } @@ -704,7 +710,7 @@ boolean Plugin_036(uint8_t function, struct EventStruct *event, String& string) // shorten string because OLED controller can not handle such long strings P036_data->DisplayLinesV1[LineNo - 1].Content[strlen - iCharToRemove] = 0; } - P036_data->MaxFramesToDisplay = 0xff; // update frame count + P036_data->MaxFramesToDisplay = 0xff; // update frame count bool bNoDisplayOnReceivedText = bitRead(PCONFIG_LONG(0), 18); // Bit 18 NoDisplayOnReceivedText diff --git a/src/_P045_MPU6050.ino b/src/_P045_MPU6050.ino index 4620f6532..a68b52a61 100644 --- a/src/_P045_MPU6050.ino +++ b/src/_P045_MPU6050.ino @@ -1,7 +1,8 @@ #ifdef USES_P045 -//####################################################################################################### -//#################################### Plugin 045: MPU6050 [Testing] #################################### -//####################################################################################################### + +// ####################################################################################################### +// #################################### Plugin 045: MPU6050 [Testing] #################################### +// ####################################################################################################### // Based on the works of Nolan Gilley @ https://home-assistant.io/blog/2016/08/03/laundry-automation-update/ // falling under the following license CC-BY-SA, https://creativecommons.org/licenses/by-sa/2.0/ @@ -84,7 +85,7 @@ #define PLUGIN_NAME_045 "Gyro - MPU 6050 [TESTING]" #define PLUGIN_VALUENAME1_045 "" -int16_t _P045_axis[3][5][2]; // [xyz], [min/max/range,a,g], [0x68/0x69] +int16_t _P045_axis[3][5][2]; // [xyz], [min/max/range,a,g], [0x68/0x69] unsigned long _P045_time[2]; boolean Plugin_045(byte function, struct EventStruct *event, String& string) @@ -93,250 +94,269 @@ boolean Plugin_045(byte function, struct EventStruct *event, String& string) switch (function) { - case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_045; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].VType = SENSOR_TYPE_SINGLE; - Device[deviceCount].ValueCount = 1; // Unfortunatly domoticz has no custom multivalue sensors. - Device[deviceCount].SendDataOption = true; // and I use Domoticz ... so there. - Device[deviceCount].TimerOption = true; - Device[deviceCount].FormulaOption = false; - break; - } + { + Device[++deviceCount].Number = PLUGIN_ID_045; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = SENSOR_TYPE_SINGLE; + Device[deviceCount].ValueCount = 1; // Unfortunatly domoticz has no custom multivalue sensors. + Device[deviceCount].SendDataOption = true; // and I use Domoticz ... so there. + Device[deviceCount].TimerOption = true; + Device[deviceCount].FormulaOption = false; + break; + } case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_045); - break; - } + { + string = F(PLUGIN_NAME_045); + break; + } case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_045)); - break; - } + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_045)); + break; + } case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: - { - byte choice = PCONFIG(0); - // Setup webform for address selection - /* - String options[10]; - options[0] = F("0x68 - default settings (ADDR Low)"); - options[1] = F("0x69 - alternate settings (ADDR High)"); - */ - int optionValues[2]; - optionValues[0] = 0x68; - optionValues[1] = 0x69; - addFormSelectorI2C(F("p045_address"), 2, optionValues, choice); - addFormNote(F("ADDR Low=0x68, High=0x69")); - break; - } + { + byte choice = PCONFIG(0); + + // Setup webform for address selection + + /* + String options[10]; + options[0] = F("0x68 - default settings (ADDR Low)"); + options[1] = F("0x69 - alternate settings (ADDR High)"); + */ + int optionValues[2]; + optionValues[0] = 0x68; + optionValues[1] = 0x69; + addFormSelectorI2C(F("p045_address"), 2, optionValues, choice); + addFormNote(F("ADDR Low=0x68, High=0x69")); + break; + } case PLUGIN_WEBFORM_LOAD: + { + byte choice = PCONFIG(1); { - byte choice = PCONFIG(1); - { - String options[10]; - options[0] = F("Movement detection"); - options[1] = F("Range acceleration X"); - options[2] = F("Range acceleration Y"); - options[3] = F("Range acceleration Z"); - options[4] = F("Acceleration X"); - options[5] = F("Acceleration Y"); - options[6] = F("Acceleration Z"); - options[7] = F("G-force X"); - options[8] = F("G-force Y"); - options[9] = F("G-force Z"); - addFormSelector(F("Function"), F("p045_function"), 10, options, NULL, choice); - } - - if (choice == 0) { - // If this is instance function 0, setup webform for additional vars - // Show some user information about the webform and what the vars mean. - addHtml(F("The thresholdvalues (0-65535) can be used to set a threshold for one or more
")); - addHtml(F("axis. The axis will trigger when the range for that axis exceeds the threshold
")); - addHtml(F("value. A value of 0 disables movement detection for that axis.")); - - addFormNumericBox(F("Detection threshold X"), F("p045_threshold_x"), PCONFIG(2), 0, 65535); - addFormNumericBox(F("Detection threshold Y"), F("p045_threshold_y"), PCONFIG(3), 0, 65535); - addFormNumericBox(F("Detection threshold Z"), F("p045_threshold_z"), PCONFIG(4), 0, 65535); - - addHtml(F("Each 30 seconds a counter for the detection window is increased plus all axis
")); - addHtml(F("are checked and if they *all* exceeded the threshold values, a counter is increased.
")); - addHtml(F("Each period, defined by the [detection window], the counter is checked against
")); - addHtml(F("the [min. detection count] and if found equal or larger, movement is detected.
")); - addHtml(F("If in the next window the [min. detection count] value is not met, movement has stopped.")); - addHtml(F("The [detection window] cannot be smaller than the [min. detection count].")); - - addFormNumericBox(F("Min. detection count"), F("p045_threshold_counter"), PCONFIG(5), 0, 999999); - addFormNumericBox(F("Detection window"), F("p045_threshold_window"), PCONFIG(6), 0, 999999); - - } - success = true; - break; + String options[10]; + options[0] = F("Movement detection"); + options[1] = F("Range acceleration X"); + options[2] = F("Range acceleration Y"); + options[3] = F("Range acceleration Z"); + options[4] = F("Acceleration X"); + options[5] = F("Acceleration Y"); + options[6] = F("Acceleration Z"); + options[7] = F("G-force X"); + options[8] = F("G-force Y"); + options[9] = F("G-force Z"); + addFormSelector(F("Function"), F("p045_function"), 10, options, NULL, choice); } + if (choice == 0) { + // If this is instance function 0, setup webform for additional vars + // Show some user information about the webform and what the vars mean. + addHtml(F("The thresholdvalues (0-65535) can be used to set a threshold for one or more
")); + addHtml(F("axis. The axis will trigger when the range for that axis exceeds the threshold
")); + addHtml(F("value. A value of 0 disables movement detection for that axis.")); + + addFormNumericBox(F("Detection threshold X"), F("p045_threshold_x"), PCONFIG(2), 0, 65535); + addFormNumericBox(F("Detection threshold Y"), F("p045_threshold_y"), PCONFIG(3), 0, 65535); + addFormNumericBox(F("Detection threshold Z"), F("p045_threshold_z"), PCONFIG(4), 0, 65535); + + addHtml(F("Each 30 seconds a counter for the detection window is increased plus all axis
")); + addHtml(F("are checked and if they *all* exceeded the threshold values, a counter is increased.
")); + addHtml(F("Each period, defined by the [detection window], the counter is checked against
")); + addHtml(F("the [min. detection count] and if found equal or larger, movement is detected.
")); + addHtml(F("If in the next window the [min. detection count] value is not met, movement has stopped.")); + addHtml(F("The [detection window] cannot be smaller than the [min. detection count].")); + + addFormNumericBox(F("Min. detection count"), F("p045_threshold_counter"), PCONFIG(5), 0, 999999); + addFormNumericBox(F("Detection window"), F("p045_threshold_window"), PCONFIG(6), 0, 999999); + } + success = true; + break; + } + case PLUGIN_WEBFORM_SAVE: - { - // Save the vars - PCONFIG(0) = getFormItemInt(F("p045_address")); - PCONFIG(1) = getFormItemInt(F("p045_function")); - PCONFIG(2) = getFormItemInt(F("p045_threshold_x")); - PCONFIG(3) = getFormItemInt(F("p045_threshold_y")); - PCONFIG(4) = getFormItemInt(F("p045_threshold_z")); - PCONFIG(5) = getFormItemInt(F("p045_threshold_counter")); - PCONFIG(6) = getFormItemInt(F("p045_threshold_window")); - if (PCONFIG(6) < PCONFIG(5)) { - PCONFIG(6) = PCONFIG(5); - } - success = true; - break; + { + // Save the vars + PCONFIG(0) = getFormItemInt(F("p045_address")); + PCONFIG(1) = getFormItemInt(F("p045_function")); + PCONFIG(2) = getFormItemInt(F("p045_threshold_x")); + PCONFIG(3) = getFormItemInt(F("p045_threshold_y")); + PCONFIG(4) = getFormItemInt(F("p045_threshold_z")); + PCONFIG(5) = getFormItemInt(F("p045_threshold_counter")); + PCONFIG(6) = getFormItemInt(F("p045_threshold_window")); + + if (PCONFIG(6) < PCONFIG(5)) { + PCONFIG(6) = PCONFIG(5); } + success = true; + break; + } case PLUGIN_INIT: - { - // Initialize the MPU6050. This *can* be done multiple times per instance and device address. - // We could make sure that this is only done once per device address, but why bother? - uint8_t devAddr = PCONFIG(0); - if ((devAddr < 0x68) || (devAddr > 0x69)) { // Just in case the address is not initialized, set it anyway. - devAddr = 0x68; - PCONFIG(0) = devAddr; - } - // Initialize the MPU6050, for details look at the MPU6050 library: MPU6050::Initialize - _P045_writeBits(devAddr, MPU6050_RA_PWR_MGMT_1, MPU6050_PWR1_CLKSEL_BIT, MPU6050_PWR1_CLKSEL_LENGTH, MPU6050_CLOCK_PLL_XGYRO); - _P045_writeBits(devAddr, MPU6050_RA_GYRO_CONFIG, MPU6050_GCONFIG_FS_SEL_BIT, MPU6050_GCONFIG_FS_SEL_LENGTH, MPU6050_GYRO_FS_250); - _P045_writeBits(devAddr, MPU6050_RA_ACCEL_CONFIG, MPU6050_ACONFIG_AFS_SEL_BIT, MPU6050_ACONFIG_AFS_SEL_LENGTH, MPU6050_ACCEL_FS_2); - _P045_writeBits(devAddr, MPU6050_RA_PWR_MGMT_1, MPU6050_PWR1_SLEEP_BIT, 1, 0); + { + // Initialize the MPU6050. This *can* be done multiple times per instance and device address. + // We could make sure that this is only done once per device address, but why bother? + uint8_t devAddr = PCONFIG(0); - // Read the MPU6050 once to clear out zeros (1st time reading MPU6050 returns all 0s) - int16_t ax, ay, az, gx, gy, gz; - _P045_getMotion6(devAddr, &ax, &ay, &az, &gx, &gy, &gz); - - // Reset vars - PCONFIG(7) = 0; // Last known value of "switch" is off - UserVar[event->BaseVarIndex] = 0; // Switch is off - PCONFIG_LONG(0) = 0; // Minimal detection counter is zero - PCONFIG_LONG(1) = 0; // Detection window counter is zero - success = true; - break; + if ((devAddr < 0x68) || (devAddr > 0x69)) { // Just in case the address is not initialized, set it anyway. + devAddr = 0x68; + PCONFIG(0) = devAddr; } + // Initialize the MPU6050, for details look at the MPU6050 library: MPU6050::Initialize + _P045_writeBits(devAddr, MPU6050_RA_PWR_MGMT_1, MPU6050_PWR1_CLKSEL_BIT, MPU6050_PWR1_CLKSEL_LENGTH, MPU6050_CLOCK_PLL_XGYRO); + _P045_writeBits(devAddr, MPU6050_RA_GYRO_CONFIG, MPU6050_GCONFIG_FS_SEL_BIT, MPU6050_GCONFIG_FS_SEL_LENGTH, MPU6050_GYRO_FS_250); + _P045_writeBits(devAddr, MPU6050_RA_ACCEL_CONFIG, MPU6050_ACONFIG_AFS_SEL_BIT, MPU6050_ACONFIG_AFS_SEL_LENGTH, MPU6050_ACCEL_FS_2); + _P045_writeBits(devAddr, MPU6050_RA_PWR_MGMT_1, MPU6050_PWR1_SLEEP_BIT, 1, 0); + + // Read the MPU6050 once to clear out zeros (1st time reading MPU6050 returns all 0s) + int16_t ax, ay, az, gx, gy, gz; + _P045_getMotion6(devAddr, &ax, &ay, &az, &gx, &gy, &gz); + + // Reset vars + PCONFIG(7) = 0; // Last known value of "switch" is off + UserVar[event->BaseVarIndex] = 0; // Switch is off + PCONFIG_LONG(0) = 0; // Minimal detection counter is zero + PCONFIG_LONG(1) = 0; // Detection window counter is zero + success = true; + break; + } + case PLUGIN_ONCE_A_SECOND: + { + uint8_t devAddr = PCONFIG(0); + byte dev = devAddr & 1; + + // Read the sensorvalues, we run this bit every 1/10th of a second + _P045_getMotion6(devAddr, + &_P045_axis[0][3][dev], + &_P045_axis[1][3][dev], + &_P045_axis[2][3][dev], + &_P045_axis[0][4][dev], + &_P045_axis[1][4][dev], + &_P045_axis[2][4][dev]); + + // Set the minimum and maximum value for each axis a-value, overwrite previous values if smaller/larger + _P045_trackMinMax(_P045_axis[0][3][dev], &_P045_axis[0][0][dev], &_P045_axis[0][1][dev]); + _P045_trackMinMax(_P045_axis[1][3][dev], &_P045_axis[1][0][dev], &_P045_axis[1][1][dev]); + _P045_trackMinMax(_P045_axis[2][3][dev], &_P045_axis[2][0][dev], &_P045_axis[2][1][dev]); + + // ^ current value @ 3 ^ min val @ 0 ^ max val @ 1 + + /* // Uncomment this block if you want to debug your MPU6050, but be prepared for a log overload + String log = F("MPU6050 : axis values: "); + log += _P045_axis[0][3][dev]; + log += F(", "); + log += _P045_axis[1][3][dev]; + log += F(", "); + log += _P045_axis[2][3][dev]; + log += F(", g values: "); + log += _P045_axis[0][4][dev]; + log += F(", "); + log += _P045_axis[1][4][dev]; + log += F(", "); + log += _P045_axis[2][4][dev]; + addLog(LOG_LEVEL_INFO,log); + */ + + // Run this bit every 5 seconds per deviceaddress (not per instance) + if (timeOutReached(_P045_time[dev] + 5000)) { - uint8_t devAddr = PCONFIG(0); - byte dev = devAddr & 1; + _P045_time[dev] = millis(); - // Read the sensorvalues, we run this bit every 1/10th of a second - _P045_getMotion6(devAddr, &_P045_axis[0][3][dev], &_P045_axis[1][3][dev], &_P045_axis[2][3][dev], &_P045_axis[0][4][dev], &_P045_axis[1][4][dev], &_P045_axis[2][4][dev]); - // Set the minimum and maximum value for each axis a-value, overwrite previous values if smaller/larger - _P045_trackMinMax(_P045_axis[0][3][dev], &_P045_axis[0][0][dev], &_P045_axis[0][1][dev]); - _P045_trackMinMax(_P045_axis[1][3][dev], &_P045_axis[1][0][dev], &_P045_axis[1][1][dev]); - _P045_trackMinMax(_P045_axis[2][3][dev], &_P045_axis[2][0][dev], &_P045_axis[2][1][dev]); - // ^ current value @ 3 ^ min val @ 0 ^ max val @ 1 - -/* // Uncomment this block if you want to debug your MPU6050, but be prepared for a log overload - String log = F("MPU6050 : axis values: "); - log += _P045_axis[0][3][dev]; - log += F(", "); - log += _P045_axis[1][3][dev]; - log += F(", "); - log += _P045_axis[2][3][dev]; - log += F(", g values: "); - log += _P045_axis[0][4][dev]; - log += F(", "); - log += _P045_axis[1][4][dev]; - log += F(", "); - log += _P045_axis[2][4][dev]; - addLog(LOG_LEVEL_INFO,log); -*/ - // Run this bit every 5 seconds per deviceaddress (not per instance) - if (timeOutReached(_P045_time[dev] + 5000)) - { - _P045_time[dev] = millis(); - - // Determine the maximum measured range of each axis - for (uint8_t i=0; i<3; i++) { - _P045_axis[i][2][dev] = abs(_P045_axis[i][1][dev] - _P045_axis[i][0][dev]); - _P045_axis[i][0][dev] = _P045_axis[i][3][dev]; - _P045_axis[i][1][dev] = _P045_axis[i][3][dev]; - } + // Determine the maximum measured range of each axis + for (uint8_t i = 0; i < 3; i++) { + _P045_axis[i][2][dev] = abs(_P045_axis[i][1][dev] - _P045_axis[i][0][dev]); + _P045_axis[i][0][dev] = _P045_axis[i][3][dev]; + _P045_axis[i][1][dev] = _P045_axis[i][3][dev]; } - success = true; - break; } + success = true; + break; + } case PLUGIN_READ: + { + int devAddr = PCONFIG(0); + byte dev = devAddr & 1; + int _P045_Function = PCONFIG(1); + + switch (_P045_Function) { - int devAddr = PCONFIG(0); - byte dev = devAddr & 1; - int _P045_Function = PCONFIG(1); - switch (_P045_Function) + // Function 0 is for movement detection + case 0: { - // Function 0 is for movement detection - case 0: + // Check if all (enabled, so !=0) thresholds are exceeded, if one fails then thresexceed (thesholds exceeded) is reset to false; + boolean thresexceed = true; + byte count = 0; // Counter to check if not all thresholdvalues are set to 0 or disabled + + for (byte i = 0; i < 3; i++) { - // Check if all (enabled, so !=0) thresholds are exceeded, if one fails then thresexceed (thesholds exceeded) is reset to false; - boolean thresexceed = true; - byte count = 0; // Counter to check if not all thresholdvalues are set to 0 or disabled - for (byte i=0; i<3; i++) - { - // for each axis: - if (PCONFIG(i + 2) != 0) { // not disabled, check threshold - if (_P045_axis[i][2][dev] < PCONFIG(i + 2)) { thresexceed = false; } - } else { count++; } // If disabled count + 1 - } - if (count == 3) { thresexceed = false; } // If we counted to three, all three axis are disabled. - - // If all enabled thresholds are exceeded the increase the counter - if (thresexceed) { PCONFIG_LONG(0)++; } - // And increase the window counter - PCONFIG_LONG(1)++; - - if (PCONFIG_LONG(1) >= PCONFIG(6)) { - // Detection window has passed. - PCONFIG_LONG(1) = 0; // reset window counter - - // Did we count more times exceeded then the minimum detection value? - if (PCONFIG_LONG(0) >= PCONFIG(5)) { - UserVar[event->BaseVarIndex] = 1; // x times threshold exceeded within window. - } else { - UserVar[event->BaseVarIndex] = 0; // reset because x times threshold within window not met. - } - - // Check if UserVar changed so we do not overload homecontroller with the same readings - if (PCONFIG(7) != UserVar[event->BaseVarIndex]) { - PCONFIG(7) = UserVar[event->BaseVarIndex]; - success = true; - } else { - success = false; - } - PCONFIG_LONG(0) = 0; // reset threshold exceeded counter - } - // The default sensorType of the device is a single sensor value. But for detection movement we want it to be - // a switch so we change the sensortype here. Looks like a legal thing to do because _P001_Switch does it as well. - event->sensorType = SENSOR_TYPE_SWITCH; - break; + // for each axis: + if (PCONFIG(i + 2) != 0) { // not disabled, check threshold + if (_P045_axis[i][2][dev] < PCONFIG(i + 2)) { thresexceed = false; } + } else { count++; } // If disabled count + 1 } - // All other functions are reading values. So extract xyz value and wanted type from function number: - default: // [1-3]: range-values, [4-6]: a-values, [7-9]: g-values - { - uint8_t reqaxis = (_P045_Function - 1) % 3; // xyz -> eg: function 5(ay) (5-1) % 3 = 1 (y) - uint8_t reqvar = ((_P045_Function - 1) / 3) + 2; // range, a, g -> eg: function 9(gz) ((9-1) / 3 = 2) + 2 = 4 (g) - UserVar[event->BaseVarIndex] = float(_P045_axis[reqaxis][reqvar][dev]); - success = true; - break; + + if (count == 3) { thresexceed = false; } // If we counted to three, all three axis are disabled. + + // If all enabled thresholds are exceeded the increase the counter + if (thresexceed) { PCONFIG_LONG(0)++; } + + // And increase the window counter + PCONFIG_LONG(1)++; + + if (PCONFIG_LONG(1) >= PCONFIG(6)) { + // Detection window has passed. + PCONFIG_LONG(1) = 0; // reset window counter + + // Did we count more times exceeded then the minimum detection value? + if (PCONFIG_LONG(0) >= PCONFIG(5)) { + UserVar[event->BaseVarIndex] = 1; // x times threshold exceeded within window. + } else { + UserVar[event->BaseVarIndex] = 0; // reset because x times threshold within window not met. + } + + // Check if UserVar changed so we do not overload homecontroller with the same readings + if (PCONFIG(7) != UserVar[event->BaseVarIndex]) { + PCONFIG(7) = UserVar[event->BaseVarIndex]; + success = true; + } else { + success = false; + } + PCONFIG_LONG(0) = 0; // reset threshold exceeded counter } + + // The default sensorType of the device is a single sensor value. But for detection movement we want it to be + // a switch so we change the sensortype here. Looks like a legal thing to do because _P001_Switch does it as well. + event->sensorType = SENSOR_TYPE_SWITCH; + break; + } + + // All other functions are reading values. So extract xyz value and wanted type from function number: + default: // [1-3]: range-values, [4-6]: a-values, [7-9]: g-values + { + uint8_t reqaxis = (_P045_Function - 1) % 3; // xyz -> eg: function 5(ay) (5-1) % 3 = 1 (y) + uint8_t reqvar = ((_P045_Function - 1) / 3) + 2; // range, a, g -> eg: function 9(gz) ((9-1) / 3 = 2) + 2 = 4 (g) + UserVar[event->BaseVarIndex] = float(_P045_axis[reqaxis][reqvar][dev]); + success = true; + break; } - break; } + break; + } } return success; } void _P045_trackMinMax(int16_t current, int16_t *min, int16_t *max) + // From nodemcu-laundry.ino by Nolan Gilley { if (current > *max) @@ -349,7 +369,6 @@ void _P045_trackMinMax(int16_t current, int16_t *min, int16_t *max) } } - /** Get raw 6-axis motion sensor readings (accel/gyro). * Retrieves all currently available motion sensor values. * @param devAddr I2C slave device address @@ -360,21 +379,23 @@ void _P045_trackMinMax(int16_t current, int16_t *min, int16_t *max) * @param gy 16-bit signed integer container for gyroscope Y-axis value * @param gz 16-bit signed integer container for gyroscope Z-axis value */ -void _P045_getMotion6(uint8_t devAddr, int16_t* ax, int16_t* ay, int16_t* az, int16_t* gx, int16_t* gy, int16_t* gz) { - // From I2Cdev::readBytes and MPU6050::getMotion6, both by Jeff Rowberg - uint8_t buffer[14]; - uint8_t count = 0; - I2C_write8(devAddr, MPU6050_RA_ACCEL_XOUT_H); - Wire.requestFrom(devAddr, (uint8_t)14); - for (; Wire.available(); count++) { - buffer[count] = Wire.read(); - } - *ax = (((int16_t)buffer[0]) << 8) | buffer[1]; - *ay = (((int16_t)buffer[2]) << 8) | buffer[3]; - *az = (((int16_t)buffer[4]) << 8) | buffer[5]; - *gx = (((int16_t)buffer[8]) << 8) | buffer[9]; - *gy = (((int16_t)buffer[10]) << 8) | buffer[11]; - *gz = (((int16_t)buffer[12]) << 8) | buffer[13]; +void _P045_getMotion6(uint8_t devAddr, int16_t *ax, int16_t *ay, int16_t *az, int16_t *gx, int16_t *gy, int16_t *gz) { + // From I2Cdev::readBytes and MPU6050::getMotion6, both by Jeff Rowberg + uint8_t buffer[14]; + uint8_t count = 0; + + I2C_write8(devAddr, MPU6050_RA_ACCEL_XOUT_H); + Wire.requestFrom(devAddr, (uint8_t)14); + + for (; Wire.available(); count++) { + buffer[count] = Wire.read(); + } + *ax = (((int16_t)buffer[0]) << 8) | buffer[1]; + *ay = (((int16_t)buffer[2]) << 8) | buffer[3]; + *az = (((int16_t)buffer[4]) << 8) | buffer[5]; + *gx = (((int16_t)buffer[8]) << 8) | buffer[9]; + *gy = (((int16_t)buffer[10]) << 8) | buffer[11]; + *gz = (((int16_t)buffer[12]) << 8) | buffer[13]; } /** Write multiple bits in an 8-bit device register. @@ -385,23 +406,25 @@ void _P045_getMotion6(uint8_t devAddr, int16_t* ax, int16_t* ay, int16_t* az, in * @param data Right-aligned value to write */ void _P045_writeBits(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uint8_t length, uint8_t data) { - // From I2Cdev::writeBits by Jeff Rowberg - // 010 value to write - // 76543210 bit numbers - // xxx args: bitStart=4, length=3 - // 00011100 mask byte - // 10101111 original value (sample) - // 10100011 original & ~mask - // 10101011 masked | value - bool is_ok = true; - uint8_t b = I2C_read8_reg(devAddr, regAddr, &is_ok); - if (is_ok) { - uint8_t mask = ((1 << length) - 1) << (bitStart - length + 1); - data <<= (bitStart - length + 1); // shift data into correct position - data &= mask; // zero all non-important bits in data - b &= ~(mask); // zero all important bits in existing byte - b |= data; // combine data with existing byte - I2C_write8_reg(devAddr, regAddr, b); - } + // From I2Cdev::writeBits by Jeff Rowberg + // 010 value to write + // 76543210 bit numbers + // xxx args: bitStart=4, length=3 + // 00011100 mask byte + // 10101111 original value (sample) + // 10100011 original & ~mask + // 10101011 masked | value + bool is_ok = true; + uint8_t b = I2C_read8_reg(devAddr, regAddr, &is_ok); + + if (is_ok) { + uint8_t mask = ((1 << length) - 1) << (bitStart - length + 1); + data <<= (bitStart - length + 1); // shift data into correct position + data &= mask; // zero all non-important bits in data + b &= ~(mask); // zero all important bits in existing byte + b |= data; // combine data with existing byte + I2C_write8_reg(devAddr, regAddr, b); + } } + #endif // USES_P045 diff --git a/src/_P047_i2c-soil-moisture-sensor.ino b/src/_P047_i2c-soil-moisture-sensor.ino index 6801d200f..cde28d7dc 100644 --- a/src/_P047_i2c-soil-moisture-sensor.ino +++ b/src/_P047_i2c-soil-moisture-sensor.ino @@ -1,7 +1,8 @@ #ifdef USES_P047 -//####################################################################################################### -//#################### Plugin 047 Moisture & Temperature & Light I2C Soil moisture sensor ############## -//####################################################################################################### + +// ####################################################################################################### +// #################### Plugin 047 Moisture & Temperature & Light I2C Soil moisture sensor ############## +// ####################################################################################################### // // Capacitive soil moisture sensor // like this one: https://www.tindie.com/products/miceuz/i2c-soil-moisture-sensor/ @@ -19,21 +20,20 @@ #define PLUGIN_VALUENAME3_047 "Light" -//Default I2C Address of the sensor +// Default I2C Address of the sensor #define SOILMOISTURESENSOR_DEFAULT_ADDR 0x20 -//Soil Moisture Sensor Register Addresses -#define SOILMOISTURESENSOR_GET_CAPACITANCE 0x00 // (r) 2 bytes -#define SOILMOISTURESENSOR_SET_ADDRESS 0x01 // (w) 1 byte -#define SOILMOISTURESENSOR_GET_ADDRESS 0x02 // (r) 1 byte -#define SOILMOISTURESENSOR_MEASURE_LIGHT 0x03 // (w) n/a -#define SOILMOISTURESENSOR_GET_LIGHT 0x04 // (r) 2 bytes -#define SOILMOISTURESENSOR_GET_TEMPERATURE 0x05 // (r) 2 bytes -#define SOILMOISTURESENSOR_RESET 0x06 // (w) n/a -#define SOILMOISTURESENSOR_GET_VERSION 0x07 // (r) 1 bytes -#define SOILMOISTURESENSOR_SLEEP 0x08 // (w) n/a -#define SOILMOISTURESENSOR_GET_BUSY 0x09 // (r) 1 bytes - +// Soil Moisture Sensor Register Addresses +#define SOILMOISTURESENSOR_GET_CAPACITANCE 0x00 // (r) 2 bytes +#define SOILMOISTURESENSOR_SET_ADDRESS 0x01 // (w) 1 byte +#define SOILMOISTURESENSOR_GET_ADDRESS 0x02 // (r) 1 byte +#define SOILMOISTURESENSOR_MEASURE_LIGHT 0x03 // (w) n/a +#define SOILMOISTURESENSOR_GET_LIGHT 0x04 // (r) 2 bytes +#define SOILMOISTURESENSOR_GET_TEMPERATURE 0x05 // (r) 2 bytes +#define SOILMOISTURESENSOR_RESET 0x06 // (w) n/a +#define SOILMOISTURESENSOR_GET_VERSION 0x07 // (r) 1 bytes +#define SOILMOISTURESENSOR_SLEEP 0x08 // (w) n/a +#define SOILMOISTURESENSOR_GET_BUSY 0x09 // (r) 1 bytes uint8_t _i2caddrP47; @@ -45,186 +45,188 @@ boolean Plugin_047(byte function, struct EventStruct *event, String& string) switch (function) { case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_047; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].VType = SENSOR_TYPE_TRIPLE; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = true; - Device[deviceCount].ValueCount = 3; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].GlobalSyncOption = true; - break; - } + { + Device[++deviceCount].Number = PLUGIN_ID_047; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = SENSOR_TYPE_TRIPLE; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 3; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].GlobalSyncOption = true; + break; + } case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_047); - break; - } + { + string = F(PLUGIN_NAME_047); + break; + } case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_047)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_047)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_VALUENAME3_047)); - break; - } + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_047)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_047)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_VALUENAME3_047)); + break; + } case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: - { - addFormTextBox(F("I2C Address (Hex)"), F("p047_i2cSoilMoisture_i2cAddress"), - formatToHex_decimal(PCONFIG(0)), 4); - // FIXME TD-er: Why not using addFormSelectorI2C here? - break; - } + { + addFormTextBox(F("I2C Address (Hex)"), F("p047_i2cSoilMoisture_i2cAddress"), + formatToHex_decimal(PCONFIG(0)), 4); + + // FIXME TD-er: Why not using addFormSelectorI2C here? + break; + } case PLUGIN_WEBFORM_LOAD: - { - addFormCheckBox(F("Send sensor to sleep"), F("p047_sleep"), PCONFIG(1)); + { + addFormCheckBox(F("Send sensor to sleep"), F("p047_sleep"), PCONFIG(1)); - addFormCheckBox(F("Check sensor version") ,F("p047_version"), PCONFIG(2)); + addFormCheckBox(F("Check sensor version"), F("p047_version"), PCONFIG(2)); - addFormSeparator(2); + addFormSeparator(2); - addFormCheckBox(F("Change Sensor address"),F("p047_changeAddr"), false); - addFormTextBox(F("Change I2C Addr. to (Hex)"), F("p047_i2cSoilMoisture_changeAddr"), - formatToHex_decimal(PCONFIG(0)), 4); + addFormCheckBox(F("Change Sensor address"), F("p047_changeAddr"), false); + addFormTextBox(F("Change I2C Addr. to (Hex)"), F("p047_i2cSoilMoisture_changeAddr"), + formatToHex_decimal(PCONFIG(0)), 4); - addFormSeparator(2); + addFormSeparator(2); - success = true; - break; - } + success = true; + break; + } case PLUGIN_WEBFORM_SAVE: - { - String plugin1 = web_server.arg(F("p047_i2cSoilMoisture_i2cAddress")); - PCONFIG(0) = (int) strtol(plugin1.c_str(), 0, 16); + { + String plugin1 = web_server.arg(F("p047_i2cSoilMoisture_i2cAddress")); + PCONFIG(0) = (int)strtol(plugin1.c_str(), 0, 16); - PCONFIG(1) = isFormItemChecked(F("p047_sleep")); + PCONFIG(1) = isFormItemChecked(F("p047_sleep")); - PCONFIG(2) = isFormItemChecked(F("p047_version")); + PCONFIG(2) = isFormItemChecked(F("p047_version")); - String plugin4 = web_server.arg(F("p047_i2cSoilMoisture_changeAddr")); - PCONFIG(3) = (int) strtol(plugin4.c_str(), 0, 16); + String plugin4 = web_server.arg(F("p047_i2cSoilMoisture_changeAddr")); + PCONFIG(3) = (int)strtol(plugin4.c_str(), 0, 16); - PCONFIG(4) = isFormItemChecked(F("p047_changeAddr")); + PCONFIG(4) = isFormItemChecked(F("p047_changeAddr")); + success = true; + break; + } + + case PLUGIN_READ: + { + _i2caddrP47 = PCONFIG(0); + + if (PCONFIG(1)) { + // wake sensor + Plugin_047_getVersion(); + delayBackground(20); + addLog(LOG_LEVEL_DEBUG, F("SoilMoisture->wake")); + } + + uint8_t sensorVersion = 0; + + if (PCONFIG(2)) { + // get sensor version to check if sensor is present + sensorVersion = Plugin_047_getVersion(); + + if ((sensorVersion == 0x22) || (sensorVersion == 0x23) || (sensorVersion == 0x24) || (sensorVersion == 0x25) || + (sensorVersion == 0x26)) { + // valid sensor + } + else { + addLog(LOG_LEVEL_INFO, F("SoilMoisture: Bad Version, no Sensor?")); + I2C_write8(_i2caddrP47, SOILMOISTURESENSOR_RESET); + break; + } + } + + // check if we want to change the sensor address + if (PCONFIG(4)) { + addLog(LOG_LEVEL_INFO, String(F("SoilMoisture: Change Address: 0x")) + String(_i2caddrP47, HEX) + String(F("->0x")) + + String(PCONFIG(3), HEX)); + + if (Plugin_047_setAddress(PCONFIG(3))) { + PCONFIG(0) = PCONFIG(3); + } + PCONFIG(4) = false; + } + + // start light measurement + I2C_write8(_i2caddrP47, SOILMOISTURESENSOR_MEASURE_LIGHT); + + // 2 s delay ...we need this delay, otherwise we get only the last reading... + delayBackground(2000); + + float temperature = ((float)Plugin_047_readTemperature()) / 10; + float moisture = ((float)Plugin_047_readMoisture()); + float light = ((float)Plugin_047_readLight()); + + if ((temperature > 100) || (temperature < -40) || (moisture > 800) || (moisture < 1) || (light > 65535) || (light < 0)) { + addLog(LOG_LEVEL_INFO, F("SoilMoisture: Bad Reading, resetting Sensor...")); + I2C_write8(_i2caddrP47, SOILMOISTURESENSOR_RESET); + success = false; + break; + } + else { + UserVar[event->BaseVarIndex] = temperature; + UserVar[event->BaseVarIndex + 1] = moisture; + UserVar[event->BaseVarIndex + 2] = light; + + String log = F("SoilMoisture: Address: 0x"); + log += String(_i2caddrP47, HEX); + + if (PCONFIG(2)) { + log += F(" Version: 0x"); + log += String(sensorVersion, HEX); + } + addLog(LOG_LEVEL_INFO, log); + log = F("SoilMoisture: Temperature: "); + log += temperature; + addLog(LOG_LEVEL_INFO, log); + log = F("SoilMoisture: Moisture: "); + log += moisture; + addLog(LOG_LEVEL_INFO, log); + log = F("SoilMoisture: Light: "); + log += light; + addLog(LOG_LEVEL_INFO, log); + + if (PCONFIG(1)) { + // send sensor to sleep + I2C_write8(_i2caddrP47, SOILMOISTURESENSOR_SLEEP); + addLog(LOG_LEVEL_DEBUG, F("SoilMoisture->sleep")); + } success = true; break; } - - case PLUGIN_READ: - { - _i2caddrP47 = PCONFIG(0); - - if (PCONFIG(1)) { - // wake sensor - Plugin_047_getVersion(); - delayBackground(20); - addLog(LOG_LEVEL_DEBUG, F("SoilMoisture->wake")); - } - - uint8_t sensorVersion = 0; - if (PCONFIG(2)) { - // get sensor version to check if sensor is present - sensorVersion = Plugin_047_getVersion(); - if (sensorVersion==0x22 || sensorVersion==0x23 || sensorVersion==0x24 || sensorVersion==0x25 || sensorVersion==0x26) { - //valid sensor - } - else { - addLog(LOG_LEVEL_INFO, F("SoilMoisture: Bad Version, no Sensor?")); - I2C_write8(_i2caddrP47, SOILMOISTURESENSOR_RESET); - break; - } - } - - // check if we want to change the sensor address - if (PCONFIG(4)) { - addLog(LOG_LEVEL_INFO, String(F("SoilMoisture: Change Address: 0x")) + String(_i2caddrP47,HEX) + String(F("->0x")) + - String(PCONFIG(3),HEX)); - if (Plugin_047_setAddress(PCONFIG(3))) { - PCONFIG(0) = PCONFIG(3); - } - PCONFIG(4) = false; - } - - // start light measurement - I2C_write8(_i2caddrP47, SOILMOISTURESENSOR_MEASURE_LIGHT); - - // 2 s delay ...we need this delay, otherwise we get only the last reading... - delayBackground(2000); - - float temperature = ((float)Plugin_047_readTemperature()) / 10; - float moisture = ((float)Plugin_047_readMoisture()); - float light = ((float)Plugin_047_readLight()); - - if (temperature>100 || temperature < -40 || moisture > 800 || moisture < 1 || light > 65535 || light < 0) { - addLog(LOG_LEVEL_INFO, F("SoilMoisture: Bad Reading, resetting Sensor...")); - I2C_write8(_i2caddrP47, SOILMOISTURESENSOR_RESET); - success = false; - break; - } - else { - UserVar[event->BaseVarIndex] = temperature; - UserVar[event->BaseVarIndex + 1] = moisture; - UserVar[event->BaseVarIndex + 2] = light; - - String log = F("SoilMoisture: Address: 0x"); - log += String(_i2caddrP47,HEX); - if (PCONFIG(2)) { - log += F(" Version: 0x"); - log += String(sensorVersion,HEX); - } - addLog(LOG_LEVEL_INFO, log); - log = F("SoilMoisture: Temperature: "); - log += temperature; - addLog(LOG_LEVEL_INFO, log); - log = F("SoilMoisture: Moisture: "); - log += moisture; - addLog(LOG_LEVEL_INFO, log); - log = F("SoilMoisture: Light: "); - log += light; - addLog(LOG_LEVEL_INFO, log); - - if (PCONFIG(1)) { - // send sensor to sleep - I2C_write8(_i2caddrP47, SOILMOISTURESENSOR_SLEEP); - addLog(LOG_LEVEL_DEBUG, F("SoilMoisture->sleep")); - } - success = true; - break; - } - } + } } return success; } - - - - -//**************************************************************************/ +// **************************************************************************/ // Read temperature -//**************************************************************************/ +// **************************************************************************/ float Plugin_047_readTemperature() { return I2C_readS16_reg(_i2caddrP47, SOILMOISTURESENSOR_GET_TEMPERATURE); } -//**************************************************************************/ +// **************************************************************************/ // Read light -//**************************************************************************/ +// **************************************************************************/ float Plugin_047_readLight() { return I2C_read16_reg(_i2caddrP47, SOILMOISTURESENSOR_GET_LIGHT); } -//**************************************************************************/ +// **************************************************************************/ // Read moisture -//**************************************************************************/ +// **************************************************************************/ unsigned int Plugin_047_readMoisture() { return I2C_read16_reg(_i2caddrP47, SOILMOISTURESENSOR_GET_CAPACITANCE); } @@ -234,21 +236,19 @@ uint8_t Plugin_047_getVersion() { return I2C_read8_reg(_i2caddrP47, SOILMOISTURESENSOR_GET_VERSION); } - /*----------------------------------------------------------------------* - * Change I2C address of the sensor to the provided address (1..127) * - * and do a reset after it in order for the new address to become * - * effective if second parameter is true. * - * Method returns true if the new address is set successfully on sensor.* - *----------------------------------------------------------------------*/ +* Change I2C address of the sensor to the provided address (1..127) * +* and do a reset after it in order for the new address to become * +* effective if second parameter is true. * +* Method returns true if the new address is set successfully on sensor.* +*----------------------------------------------------------------------*/ bool Plugin_047_setAddress(int addr) { - I2C_write8_reg(_i2caddrP47, SOILMOISTURESENSOR_SET_ADDRESS, addr); - I2C_write8_reg(_i2caddrP47, SOILMOISTURESENSOR_SET_ADDRESS, addr); - I2C_write8(_i2caddrP47, SOILMOISTURESENSOR_RESET); - delayBackground(1000); - _i2caddrP47=addr; - return (I2C_read8_reg(_i2caddrP47, SOILMOISTURESENSOR_GET_ADDRESS) == addr); + I2C_write8_reg(_i2caddrP47, SOILMOISTURESENSOR_SET_ADDRESS, addr); + I2C_write8_reg(_i2caddrP47, SOILMOISTURESENSOR_SET_ADDRESS, addr); + I2C_write8(_i2caddrP47, SOILMOISTURESENSOR_RESET); + delayBackground(1000); + _i2caddrP47 = addr; + return I2C_read8_reg(_i2caddrP47, SOILMOISTURESENSOR_GET_ADDRESS) == addr; } - #endif // USES_P047 diff --git a/src/_P048_Motorshield_v2.ino b/src/_P048_Motorshield_v2.ino index 6fb0cbcd8..5dee0664f 100644 --- a/src/_P048_Motorshield_v2.ino +++ b/src/_P048_Motorshield_v2.ino @@ -1,7 +1,8 @@ #ifdef USES_P048 -//####################################################################################################### -//#################################### Plugin 048: Adafruit Motorshield v2 ############################## -//####################################################################################################### + +// ####################################################################################################### +// #################################### Plugin 048: Adafruit Motorshield v2 ############################## +// ####################################################################################################### // Adafruit Motorshield v2 // like this one: https://www.adafruit.com/products/1438 @@ -21,268 +22,277 @@ uint8_t Plugin_048_MotorShield_address = 0x60; int Plugin_048_MotorStepsPerRevolution = 200; -int Plugin_048_StepperSpeed = 10; +int Plugin_048_StepperSpeed = 10; boolean Plugin_048(byte function, struct EventStruct *event, String& string) { - boolean success = false; + boolean success = false; - Adafruit_MotorShield AFMS; + Adafruit_MotorShield AFMS; + switch (function) { + case PLUGIN_DEVICE_ADD: { + Device[++deviceCount].Number = PLUGIN_ID_048; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = SENSOR_TYPE_NONE; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = false; + Device[deviceCount].ValueCount = 0; + Device[deviceCount].SendDataOption = false; + Device[deviceCount].TimerOption = false; + break; + } - switch (function) { + case PLUGIN_GET_DEVICENAME: { + string = F(PLUGIN_NAME_048); + break; + } - case PLUGIN_DEVICE_ADD: { - Device[++deviceCount].Number = PLUGIN_ID_048; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].VType = SENSOR_TYPE_NONE; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = false; - Device[deviceCount].ValueCount = 0; - Device[deviceCount].SendDataOption = false; - Device[deviceCount].TimerOption = false; - break; - } + case PLUGIN_GET_DEVICEVALUENAMES: { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], + PSTR(PLUGIN_VALUENAME1_048)); + break; + } - case PLUGIN_GET_DEVICENAME: { - string = F(PLUGIN_NAME_048); - break; - } + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: + { + addFormTextBox(F("I2C Address (Hex)"), F("p048_adr"), + formatToHex_decimal(PCONFIG(0)), 4); - case PLUGIN_GET_DEVICEVALUENAMES: { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], - PSTR(PLUGIN_VALUENAME1_048)); - break; - } - - case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: - { - addFormTextBox(F("I2C Address (Hex)"), F("p048_adr"), - formatToHex_decimal(PCONFIG(0)), 4); - // FIXME TD-er: Why not using addFormSelectorI2C here? - break; - } + // FIXME TD-er: Why not using addFormSelectorI2C here? + break; + } - case PLUGIN_WEBFORM_LOAD: { - addFormNumericBox(F("Stepper: steps per revolution"), F("p048_MotorStepsPerRevolution") - , PCONFIG(1)); + case PLUGIN_WEBFORM_LOAD: { + addFormNumericBox(F("Stepper: steps per revolution"), F("p048_MotorStepsPerRevolution") + , PCONFIG(1)); - addFormNumericBox(F("Stepper speed (rpm)"), F("p048_StepperSpeed") - , PCONFIG(2)); + addFormNumericBox(F("Stepper speed (rpm)"), F("p048_StepperSpeed") + , PCONFIG(2)); - success = true; - break; - } + success = true; + break; + } - case PLUGIN_WEBFORM_SAVE: { - String plugin1 = web_server.arg(F("p048_adr")); - PCONFIG(0) = (int) strtol(plugin1.c_str(), 0, 16); + case PLUGIN_WEBFORM_SAVE: { + String plugin1 = web_server.arg(F("p048_adr")); + PCONFIG(0) = (int)strtol(plugin1.c_str(), 0, 16); - PCONFIG(1) = getFormItemInt(F("p048_MotorStepsPerRevolution")); + PCONFIG(1) = getFormItemInt(F("p048_MotorStepsPerRevolution")); - PCONFIG(2) = getFormItemInt(F("p048_StepperSpeed")); - success = true; - break; - } + PCONFIG(2) = getFormItemInt(F("p048_StepperSpeed")); + success = true; + break; + } - case PLUGIN_INIT: { - Plugin_048_MotorShield_address = PCONFIG(0); - Plugin_048_MotorStepsPerRevolution = PCONFIG(1); - Plugin_048_StepperSpeed = PCONFIG(2); + case PLUGIN_INIT: { + Plugin_048_MotorShield_address = PCONFIG(0); + Plugin_048_MotorStepsPerRevolution = PCONFIG(1); + Plugin_048_StepperSpeed = PCONFIG(2); - success = true; - break; - } + success = true; + break; + } - case PLUGIN_READ: { + case PLUGIN_READ: { + success = false; + break; + } - success = false; - break; - } + case PLUGIN_WRITE: { + String cmd = parseString(string, 1); - case PLUGIN_WRITE: { - String cmd = parseString(string, 1); + // Commands: + // MotorShieldCMD,,,, - // Commands: - // MotorShieldCMD,,,, - - if (cmd.equalsIgnoreCase(F("MotorShieldCMD"))) - { + if (cmd.equalsIgnoreCase(F("MotorShieldCMD"))) + { String param1 = parseString(string, 2); String param2 = parseString(string, 3); String param3 = parseString(string, 4); String param4 = parseString(string, 5); String param5 = parseString(string, 6); - int p2_int; - int p4_int; - const bool param2_is_int = validIntFromString(param2, p2_int); - const bool param4_is_int = validIntFromString(param4, p4_int); + int p2_int; + int p4_int; + const bool param2_is_int = validIntFromString(param2, p2_int); + const bool param4_is_int = validIntFromString(param4, p4_int); - // Create the motor shield object with the default I2C address - AFMS = Adafruit_MotorShield(Plugin_048_MotorShield_address); - String log = F("MotorShield: Address: 0x"); - log += String(Plugin_048_MotorShield_address,HEX); - addLog(LOG_LEVEL_DEBUG, log); + // Create the motor shield object with the default I2C address + AFMS = Adafruit_MotorShield(Plugin_048_MotorShield_address); + String log = F("MotorShield: Address: 0x"); + log += String(Plugin_048_MotorShield_address, HEX); + addLog(LOG_LEVEL_DEBUG, log); - if (param1.equalsIgnoreCase(F("DCMotor"))) { - if (param2_is_int && p2_int > 0 && p2_int < 5) - { - Adafruit_DCMotor *myMotor; - myMotor = AFMS.getMotor(p2_int); - if (param3.equalsIgnoreCase(F("Forward"))) - { - byte speed = 255; - if (param4_is_int && p4_int >= 0 && p4_int <= 255) - speed = p4_int; - AFMS.begin(); - addLog(LOG_LEVEL_INFO, String(F("DCMotor")) + param2 + String(F("->Forward Speed: ")) + String(speed)); - myMotor->setSpeed(speed); - myMotor->run(FORWARD); - success = true; - } - if (param3.equalsIgnoreCase(F("Backward"))) - { - byte speed = 255; - if (param4_is_int && p4_int >= 0 && p4_int <= 255) - speed = p4_int; - AFMS.begin(); - addLog(LOG_LEVEL_INFO, String(F("DCMotor")) + param2 + String(F("->Backward Speed: ")) + String(speed)); - myMotor->setSpeed(speed); - myMotor->run(BACKWARD); - success = true; - } - if (param3.equalsIgnoreCase(F("Release"))) - { - AFMS.begin(); - addLog(LOG_LEVEL_INFO, String(F("DCMotor")) + param2 + String(F("->Release"))); - myMotor->run(RELEASE); - success = true; - } - } - } + if (param1.equalsIgnoreCase(F("DCMotor"))) { + if (param2_is_int && (p2_int > 0) && (p2_int < 5)) + { + Adafruit_DCMotor *myMotor; + myMotor = AFMS.getMotor(p2_int); - // MotorShieldCMD,,,,, - if (param1.equalsIgnoreCase(F("Stepper"))) - { - // Stepper# is which port it is connected to. If you're using M1 and M2, its port 1. - // If you're using M3 and M4 indicate port 2 - if (param2_is_int && p2_int > 0 && p2_int < 3) - { - Adafruit_StepperMotor *myStepper; - myStepper = AFMS.getStepper(Plugin_048_MotorStepsPerRevolution, p2_int); - myStepper->setSpeed(Plugin_048_StepperSpeed); - if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) { - String log = F("MotorShield: StepsPerRevolution: "); - log += String(Plugin_048_MotorStepsPerRevolution); - log += F(" Stepperspeed: "); - log += String(Plugin_048_StepperSpeed); - addLog(LOG_LEVEL_DEBUG_MORE, log); - } + if (param3.equalsIgnoreCase(F("Forward"))) + { + byte speed = 255; - if (param3.equalsIgnoreCase(F("Forward"))) - { - if (param4_is_int && p4_int != 0) - { - int steps = p4_int; - if (param5.equalsIgnoreCase(F("SINGLE"))) - { - AFMS.begin(); - addLog(LOG_LEVEL_INFO, String(F("Stepper")) + param2 + String(F("->Forward Steps: ")) + - steps + String(F(" SINGLE"))); - myStepper->step(steps, FORWARD, SINGLE); - success = true; - } - if (param5.equalsIgnoreCase(F("DOUBLE"))) - { - AFMS.begin(); - addLog(LOG_LEVEL_INFO, String(F("Stepper")) + param2 + String(F("->Forward Steps: ")) + - steps + String(F(" DOUBLE"))); - myStepper->step(steps, FORWARD, DOUBLE); - success = true; - } - if (param5.equalsIgnoreCase(F("INTERLEAVE"))) - { - AFMS.begin(); - addLog(LOG_LEVEL_INFO, String(F("Stepper")) + param2 + String(F("->Forward Steps: ")) + - steps + String(F(" INTERLEAVE"))); - myStepper->step(steps, FORWARD, INTERLEAVE); - success = true; - } - if (param5.equalsIgnoreCase(F("MICROSTEP"))) - { - AFMS.begin(); - addLog(LOG_LEVEL_INFO, String(F("Stepper")) + param2 + String(F("->Forward Steps: ")) + - steps + String(F(" MICROSTEP"))); - myStepper->step(steps, FORWARD, MICROSTEP); - success = true; - } - } - } + if (param4_is_int && (p4_int >= 0) && (p4_int <= 255)) { + speed = p4_int; + } + AFMS.begin(); + addLog(LOG_LEVEL_INFO, String(F("DCMotor")) + param2 + String(F("->Forward Speed: ")) + String(speed)); + myMotor->setSpeed(speed); + myMotor->run(FORWARD); + success = true; + } - if (param3.equalsIgnoreCase(F("Backward"))) - { - if (param4_is_int && p4_int != 0) - { - int steps = p4_int; - if (param5.equalsIgnoreCase(F("SINGLE"))) - { - AFMS.begin(); - addLog(LOG_LEVEL_INFO, String(F("Stepper")) + param2 + String(F("->Backward Steps: ")) + - steps + String(F(" SINGLE"))); - myStepper->step(steps, BACKWARD, SINGLE); - success = true; - } - if (param5.equalsIgnoreCase(F("DOUBLE"))) - { - AFMS.begin(); - addLog(LOG_LEVEL_INFO, String(F("Stepper")) + param2 + String(F("->Backward Steps: ")) + - steps + String(F(" DOUBLE"))); - myStepper->step(steps, BACKWARD, DOUBLE); - success = true; - } - if (param5.equalsIgnoreCase(F("INTERLEAVE"))) - { - AFMS.begin(); - addLog(LOG_LEVEL_INFO, String(F("Stepper")) + param2 + String(F("->Backward Steps: ")) + - steps + String(F(" INTERLEAVE"))); - myStepper->step(steps, BACKWARD, INTERLEAVE); - success = true; - } - if (param5.equalsIgnoreCase(F("MICROSTEP"))) - { - AFMS.begin(); - addLog(LOG_LEVEL_INFO, String(F("Stepper")) + param2 + String(F("->Backward Steps: ")) + - steps + String(F(" MICROSTEP"))); - myStepper->step(steps, BACKWARD, MICROSTEP); - success = true; - } + if (param3.equalsIgnoreCase(F("Backward"))) + { + byte speed = 255; - } - } + if (param4_is_int && (p4_int >= 0) && (p4_int <= 255)) { + speed = p4_int; + } + AFMS.begin(); + addLog(LOG_LEVEL_INFO, String(F("DCMotor")) + param2 + String(F("->Backward Speed: ")) + String(speed)); + myMotor->setSpeed(speed); + myMotor->run(BACKWARD); + success = true; + } - if (param3.equalsIgnoreCase(F("Release"))) - { - AFMS.begin(); - addLog(LOG_LEVEL_INFO, String(F("Stepper")) + param2 + String(F("->Release."))); - myStepper->release(); - success = true; - } + if (param3.equalsIgnoreCase(F("Release"))) + { + AFMS.begin(); + addLog(LOG_LEVEL_INFO, String(F("DCMotor")) + param2 + String(F("->Release"))); + myMotor->run(RELEASE); + success = true; + } + } + } - } - } + // MotorShieldCMD,,,,, + if (param1.equalsIgnoreCase(F("Stepper"))) + { + // Stepper# is which port it is connected to. If you're using M1 and M2, its port 1. + // If you're using M3 and M4 indicate port 2 + if (param2_is_int && (p2_int > 0) && (p2_int < 3)) + { + Adafruit_StepperMotor *myStepper; + myStepper = AFMS.getStepper(Plugin_048_MotorStepsPerRevolution, p2_int); + myStepper->setSpeed(Plugin_048_StepperSpeed); - } + if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) { + String log = F("MotorShield: StepsPerRevolution: "); + log += String(Plugin_048_MotorStepsPerRevolution); + log += F(" Stepperspeed: "); + log += String(Plugin_048_StepperSpeed); + addLog(LOG_LEVEL_DEBUG_MORE, log); + } - break; - } + if (param3.equalsIgnoreCase(F("Forward"))) + { + if (param4_is_int && (p4_int != 0)) + { + int steps = p4_int; - } - return success; + if (param5.equalsIgnoreCase(F("SINGLE"))) + { + AFMS.begin(); + addLog(LOG_LEVEL_INFO, String(F("Stepper")) + param2 + String(F("->Forward Steps: ")) + + steps + String(F(" SINGLE"))); + myStepper->step(steps, FORWARD, SINGLE); + success = true; + } + + if (param5.equalsIgnoreCase(F("DOUBLE"))) + { + AFMS.begin(); + addLog(LOG_LEVEL_INFO, String(F("Stepper")) + param2 + String(F("->Forward Steps: ")) + + steps + String(F(" DOUBLE"))); + myStepper->step(steps, FORWARD, DOUBLE); + success = true; + } + + if (param5.equalsIgnoreCase(F("INTERLEAVE"))) + { + AFMS.begin(); + addLog(LOG_LEVEL_INFO, String(F("Stepper")) + param2 + String(F("->Forward Steps: ")) + + steps + String(F(" INTERLEAVE"))); + myStepper->step(steps, FORWARD, INTERLEAVE); + success = true; + } + + if (param5.equalsIgnoreCase(F("MICROSTEP"))) + { + AFMS.begin(); + addLog(LOG_LEVEL_INFO, String(F("Stepper")) + param2 + String(F("->Forward Steps: ")) + + steps + String(F(" MICROSTEP"))); + myStepper->step(steps, FORWARD, MICROSTEP); + success = true; + } + } + } + + if (param3.equalsIgnoreCase(F("Backward"))) + { + if (param4_is_int && (p4_int != 0)) + { + int steps = p4_int; + + if (param5.equalsIgnoreCase(F("SINGLE"))) + { + AFMS.begin(); + addLog(LOG_LEVEL_INFO, String(F("Stepper")) + param2 + String(F("->Backward Steps: ")) + + steps + String(F(" SINGLE"))); + myStepper->step(steps, BACKWARD, SINGLE); + success = true; + } + + if (param5.equalsIgnoreCase(F("DOUBLE"))) + { + AFMS.begin(); + addLog(LOG_LEVEL_INFO, String(F("Stepper")) + param2 + String(F("->Backward Steps: ")) + + steps + String(F(" DOUBLE"))); + myStepper->step(steps, BACKWARD, DOUBLE); + success = true; + } + + if (param5.equalsIgnoreCase(F("INTERLEAVE"))) + { + AFMS.begin(); + addLog(LOG_LEVEL_INFO, String(F("Stepper")) + param2 + String(F("->Backward Steps: ")) + + steps + String(F(" INTERLEAVE"))); + myStepper->step(steps, BACKWARD, INTERLEAVE); + success = true; + } + + if (param5.equalsIgnoreCase(F("MICROSTEP"))) + { + AFMS.begin(); + addLog(LOG_LEVEL_INFO, String(F("Stepper")) + param2 + String(F("->Backward Steps: ")) + + steps + String(F(" MICROSTEP"))); + myStepper->step(steps, BACKWARD, MICROSTEP); + success = true; + } + } + } + + if (param3.equalsIgnoreCase(F("Release"))) + { + AFMS.begin(); + addLog(LOG_LEVEL_INFO, String(F("Stepper")) + param2 + String(F("->Release."))); + myStepper->release(); + success = true; + } + } + } + } + + break; + } + } + return success; } - #endif // USES_P048 diff --git a/src/_P050_TCS34725.ino b/src/_P050_TCS34725.ino index 59ac851a1..2e0973c86 100644 --- a/src/_P050_TCS34725.ino +++ b/src/_P050_TCS34725.ino @@ -1,7 +1,8 @@ #ifdef USES_P050 -//####################################################################################################### -//#################### Plugin 050 I2C TCS34725 RGB Color Sensor with IR filter and White LED ############ -//####################################################################################################### + +// ####################################################################################################### +// #################### Plugin 050 I2C TCS34725 RGB Color Sensor with IR filter and White LED ############ +// ####################################################################################################### // // RGB Color Sensor with IR filter and White LED // like this one: https://www.adafruit.com/products/1334 @@ -22,8 +23,6 @@ #define PLUGIN_VALUENAME4_050 "Color Temperature" - - boolean Plugin_050(byte function, struct EventStruct *event, String& string) { boolean success = false; @@ -31,149 +30,166 @@ boolean Plugin_050(byte function, struct EventStruct *event, String& string) switch (function) { case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_050; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].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; - } + { + Device[++deviceCount].Number = PLUGIN_ID_050; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].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_050); - break; - } + { + string = F(PLUGIN_NAME_050); + break; + } case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_050)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_050)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_VALUENAME3_050)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[3], PSTR(PLUGIN_VALUENAME4_050)); - break; - } + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_050)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_050)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_VALUENAME3_050)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[3], PSTR(PLUGIN_VALUENAME4_050)); + break; + } case PLUGIN_WEBFORM_LOAD: - { - byte choiceMode = PCONFIG(0); - String optionsMode[6]; - optionsMode[0] = F("TCS34725_INTEGRATIONTIME_2_4MS"); - optionsMode[1] = F("TCS34725_INTEGRATIONTIME_24MS"); - optionsMode[2] = F("TCS34725_INTEGRATIONTIME_50MS"); - optionsMode[3] = F("TCS34725_INTEGRATIONTIME_101MS"); - optionsMode[4] = F("TCS34725_INTEGRATIONTIME_154MS"); - optionsMode[5] = F("TCS34725_INTEGRATIONTIME_700MS"); - int optionValuesMode[6]; - optionValuesMode[0] = TCS34725_INTEGRATIONTIME_2_4MS; - optionValuesMode[1] = TCS34725_INTEGRATIONTIME_24MS; - optionValuesMode[2] = TCS34725_INTEGRATIONTIME_50MS; - optionValuesMode[3] = TCS34725_INTEGRATIONTIME_101MS; - optionValuesMode[4] = TCS34725_INTEGRATIONTIME_154MS; - optionValuesMode[5] = TCS34725_INTEGRATIONTIME_700MS; - addFormSelector(F("Integration Time"), F("p050_integrationTime"), 6, optionsMode, optionValuesMode, choiceMode); + { + byte choiceMode = PCONFIG(0); + String optionsMode[6]; + optionsMode[0] = F("TCS34725_INTEGRATIONTIME_2_4MS"); + optionsMode[1] = F("TCS34725_INTEGRATIONTIME_24MS"); + optionsMode[2] = F("TCS34725_INTEGRATIONTIME_50MS"); + optionsMode[3] = F("TCS34725_INTEGRATIONTIME_101MS"); + optionsMode[4] = F("TCS34725_INTEGRATIONTIME_154MS"); + optionsMode[5] = F("TCS34725_INTEGRATIONTIME_700MS"); + int optionValuesMode[6]; + optionValuesMode[0] = TCS34725_INTEGRATIONTIME_2_4MS; + optionValuesMode[1] = TCS34725_INTEGRATIONTIME_24MS; + optionValuesMode[2] = TCS34725_INTEGRATIONTIME_50MS; + optionValuesMode[3] = TCS34725_INTEGRATIONTIME_101MS; + optionValuesMode[4] = TCS34725_INTEGRATIONTIME_154MS; + optionValuesMode[5] = TCS34725_INTEGRATIONTIME_700MS; + addFormSelector(F("Integration Time"), F("p050_integrationTime"), 6, optionsMode, optionValuesMode, choiceMode); - byte choiceMode2 = PCONFIG(1); - String optionsMode2[4]; - optionsMode2[0] = F("TCS34725_GAIN_1X"); - optionsMode2[1] = F("TCS34725_GAIN_4X"); - optionsMode2[2] = F("TCS34725_GAIN_16X"); - optionsMode2[3] = F("TCS34725_GAIN_60X"); - int optionValuesMode2[4]; - optionValuesMode2[0] = TCS34725_GAIN_1X; - optionValuesMode2[1] = TCS34725_GAIN_4X; - optionValuesMode2[2] = TCS34725_GAIN_16X; - optionValuesMode2[3] = TCS34725_GAIN_60X; - addFormSelector(F("Gain"), F("p050_gain"), 4, optionsMode2, optionValuesMode2, choiceMode2); + byte choiceMode2 = PCONFIG(1); + String optionsMode2[4]; + optionsMode2[0] = F("TCS34725_GAIN_1X"); + optionsMode2[1] = F("TCS34725_GAIN_4X"); + optionsMode2[2] = F("TCS34725_GAIN_16X"); + optionsMode2[3] = F("TCS34725_GAIN_60X"); + int optionValuesMode2[4]; + optionValuesMode2[0] = TCS34725_GAIN_1X; + optionValuesMode2[1] = TCS34725_GAIN_4X; + optionValuesMode2[2] = TCS34725_GAIN_16X; + optionValuesMode2[3] = TCS34725_GAIN_60X; + addFormSelector(F("Gain"), F("p050_gain"), 4, optionsMode2, optionValuesMode2, choiceMode2); - success = true; - break; - } + success = true; + break; + } case PLUGIN_WEBFORM_SAVE: - { - String plugin1 = web_server.arg(F("p050_integrationTime")); - PCONFIG(0) = plugin1.toInt(); - String plugin2 = web_server.arg(F("p050_gain")); - PCONFIG(1) = plugin2.toInt(); + { + String plugin1 = web_server.arg(F("p050_integrationTime")); + PCONFIG(0) = plugin1.toInt(); + String plugin2 = web_server.arg(F("p050_gain")); + PCONFIG(1) = plugin2.toInt(); - success = true; - break; - } + success = true; + break; + } case PLUGIN_READ: - { - tcs34725IntegrationTime_t integrationTime; - if (PCONFIG(0)==TCS34725_INTEGRATIONTIME_2_4MS) - integrationTime = TCS34725_INTEGRATIONTIME_2_4MS; - if (PCONFIG(0)==TCS34725_INTEGRATIONTIME_24MS) - integrationTime = TCS34725_INTEGRATIONTIME_24MS; - if (PCONFIG(0)==TCS34725_INTEGRATIONTIME_50MS) - integrationTime = TCS34725_INTEGRATIONTIME_50MS; - if (PCONFIG(0)==TCS34725_INTEGRATIONTIME_101MS) - integrationTime = TCS34725_INTEGRATIONTIME_101MS; - if (PCONFIG(0)==TCS34725_INTEGRATIONTIME_154MS) - integrationTime = TCS34725_INTEGRATIONTIME_154MS; - if (PCONFIG(0)==TCS34725_INTEGRATIONTIME_700MS) - integrationTime = TCS34725_INTEGRATIONTIME_700MS; + { + tcs34725IntegrationTime_t integrationTime; - tcs34725Gain_t gain; - if (PCONFIG(1)==TCS34725_GAIN_1X) - gain = TCS34725_GAIN_1X; - if (PCONFIG(1)==TCS34725_GAIN_4X) - gain = TCS34725_GAIN_4X; - if (PCONFIG(1)==TCS34725_GAIN_16X) - gain = TCS34725_GAIN_16X; - if (PCONFIG(1)==TCS34725_GAIN_60X) - gain = TCS34725_GAIN_60X; - - /* Initialise with specific int time and gain values */ - Adafruit_TCS34725 tcs = Adafruit_TCS34725(integrationTime, gain); - if (tcs.begin()) { - - addLog(LOG_LEVEL_DEBUG, F("Found TCS34725 sensor")); - - uint16_t r, g, b, c; - - tcs.getRawData(&r, &g, &b, &c); - tcs.calculateColorTemperature(r, g, b); - tcs.calculateLux(r, g, b); - - UserVar[event->BaseVarIndex] = r; - UserVar[event->BaseVarIndex + 1] = g; - UserVar[event->BaseVarIndex + 2] = b; - UserVar[event->BaseVarIndex + 3] = tcs.calculateColorTemperature(r, g, b); - - String log = F("TCS34725: Color Temp (K): "); - log += String(UserVar[event->BaseVarIndex + 3], DEC); - log += F(" R: "); - log += String(UserVar[event->BaseVarIndex], DEC); - log += F(" G: "); - log += String(UserVar[event->BaseVarIndex + 1], DEC); - log += F(" B: "); - log += String(UserVar[event->BaseVarIndex + 2], DEC); - addLog(LOG_LEVEL_INFO, log); - success = true; - - } else { - addLog(LOG_LEVEL_DEBUG, F("No TCS34725 found")); - success = false; - } - - break; + if (PCONFIG(0) == TCS34725_INTEGRATIONTIME_2_4MS) { + integrationTime = TCS34725_INTEGRATIONTIME_2_4MS; } + if (PCONFIG(0) == TCS34725_INTEGRATIONTIME_24MS) { + integrationTime = TCS34725_INTEGRATIONTIME_24MS; + } + + if (PCONFIG(0) == TCS34725_INTEGRATIONTIME_50MS) { + integrationTime = TCS34725_INTEGRATIONTIME_50MS; + } + + if (PCONFIG(0) == TCS34725_INTEGRATIONTIME_101MS) { + integrationTime = TCS34725_INTEGRATIONTIME_101MS; + } + + if (PCONFIG(0) == TCS34725_INTEGRATIONTIME_154MS) { + integrationTime = TCS34725_INTEGRATIONTIME_154MS; + } + + if (PCONFIG(0) == TCS34725_INTEGRATIONTIME_700MS) { + integrationTime = TCS34725_INTEGRATIONTIME_700MS; + } + + tcs34725Gain_t gain; + + if (PCONFIG(1) == TCS34725_GAIN_1X) { + gain = TCS34725_GAIN_1X; + } + + if (PCONFIG(1) == TCS34725_GAIN_4X) { + gain = TCS34725_GAIN_4X; + } + + if (PCONFIG(1) == TCS34725_GAIN_16X) { + gain = TCS34725_GAIN_16X; + } + + if (PCONFIG(1) == TCS34725_GAIN_60X) { + gain = TCS34725_GAIN_60X; + } + + /* Initialise with specific int time and gain values */ + Adafruit_TCS34725 tcs = Adafruit_TCS34725(integrationTime, gain); + + if (tcs.begin()) { + addLog(LOG_LEVEL_DEBUG, F("Found TCS34725 sensor")); + + uint16_t r, g, b, c; + + tcs.getRawData(&r, &g, &b, &c); + tcs.calculateColorTemperature(r, g, b); + tcs.calculateLux(r, g, b); + + UserVar[event->BaseVarIndex] = r; + UserVar[event->BaseVarIndex + 1] = g; + UserVar[event->BaseVarIndex + 2] = b; + UserVar[event->BaseVarIndex + 3] = tcs.calculateColorTemperature(r, g, b); + + String log = F("TCS34725: Color Temp (K): "); + log += String(UserVar[event->BaseVarIndex + 3], DEC); + log += F(" R: "); + log += String(UserVar[event->BaseVarIndex], DEC); + log += F(" G: "); + log += String(UserVar[event->BaseVarIndex + 1], DEC); + log += F(" B: "); + log += String(UserVar[event->BaseVarIndex + 2], DEC); + addLog(LOG_LEVEL_INFO, log); + success = true; + } else { + addLog(LOG_LEVEL_DEBUG, F("No TCS34725 found")); + success = false; + } + + break; + } } return success; } - #endif // USES_P050 diff --git a/src/_P051_AM2320.ino b/src/_P051_AM2320.ino index 99f6812dc..a304de4fc 100644 --- a/src/_P051_AM2320.ino +++ b/src/_P051_AM2320.ino @@ -1,7 +1,8 @@ #ifdef USES_P051 -//####################################################################################################### -//#################### Plugin 051 Temperature and Humidity Sensor AM2320 ############## -//####################################################################################################### + +// ####################################################################################################### +// #################### Plugin 051 Temperature and Humidity Sensor AM2320 ############## +// ####################################################################################################### // // Temperature and Humidity Sensor AM2320 // written by https://github.com/krikk @@ -21,8 +22,6 @@ #define PLUGIN_VALUENAME2_051 "Humidity" - - boolean Plugin_051(byte function, struct EventStruct *event, String& string) { boolean success = false; @@ -30,78 +29,75 @@ boolean Plugin_051(byte function, struct EventStruct *event, String& string) switch (function) { case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_051; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].VType = SENSOR_TYPE_TEMP_HUM; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = true; - Device[deviceCount].ValueCount = 2; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].GlobalSyncOption = true; - break; - } + { + Device[++deviceCount].Number = PLUGIN_ID_051; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = SENSOR_TYPE_TEMP_HUM; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 2; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].GlobalSyncOption = true; + break; + } case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_051); - break; - } + { + string = F(PLUGIN_NAME_051); + break; + } case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_051)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_051)); - break; - } + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_051)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_051)); + break; + } case PLUGIN_WEBFORM_LOAD: - { - success = true; - break; - } + { + success = true; + break; + } case PLUGIN_WEBFORM_SAVE: - { - success = true; - break; - } + { + success = true; + break; + } case PLUGIN_READ: - { - AM2320 th; + { + AM2320 th; - switch(th.Read()) { - case 2: - addLog(LOG_LEVEL_ERROR, F("AM2320: CRC failed")); - break; - case 1: - addLog(LOG_LEVEL_ERROR, F("AM2320: Sensor offline")); - break; - case 0: - { - UserVar[event->BaseVarIndex] = th.t; - UserVar[event->BaseVarIndex + 1] = th.h; + switch (th.Read()) { + case 2: + addLog(LOG_LEVEL_ERROR, F("AM2320: CRC failed")); + break; + case 1: + addLog(LOG_LEVEL_ERROR, F("AM2320: Sensor offline")); + break; + case 0: + { + UserVar[event->BaseVarIndex] = th.t; + UserVar[event->BaseVarIndex + 1] = th.h; - String log = F("AM2320: Temperature: "); - log += UserVar[event->BaseVarIndex]; - addLog(LOG_LEVEL_INFO, log); - log = F("AM2320: Humidity: "); - log += UserVar[event->BaseVarIndex + 1]; - addLog(LOG_LEVEL_INFO, log); - success = true; - break; - } + String log = F("AM2320: Temperature: "); + log += UserVar[event->BaseVarIndex]; + addLog(LOG_LEVEL_INFO, log); + log = F("AM2320: Humidity: "); + log += UserVar[event->BaseVarIndex + 1]; + addLog(LOG_LEVEL_INFO, log); + success = true; + break; } - - } + } } return success; } - #endif // USES_P051 diff --git a/src/_P057_HT16K33_LED.ino b/src/_P057_HT16K33_LED.ino index c98c16611..1cabebca6 100644 --- a/src/_P057_HT16K33_LED.ino +++ b/src/_P057_HT16K33_LED.ino @@ -1,7 +1,8 @@ #ifdef USES_P057 -//####################################################################################################### -//#################################### Plugin 057: HT16K33 LED ########################################## -//####################################################################################################### + +// ####################################################################################################### +// #################################### Plugin 057: HT16K33 LED ########################################## +// ####################################################################################################### // ESPEasy Plugin to control a 16x8 LED matrix or 8 7-segment displays with chip HT16K33 // written by Jochen Krapf (jk@nerd2nerd.org) @@ -70,7 +71,7 @@ #include #include "_Plugin_Helper.h" -CHT16K33* Plugin_057_M = NULL; +CHT16K33 *Plugin_057_M = NULL; boolean Plugin_057(byte function, struct EventStruct *event, String& string) @@ -80,290 +81,307 @@ boolean Plugin_057(byte function, struct EventStruct *event, String& string) switch (function) { case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_057; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].Ports = 0; - Device[deviceCount].VType = SENSOR_TYPE_NONE; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = false; - Device[deviceCount].ValueCount = 0; - Device[deviceCount].SendDataOption = false; - Device[deviceCount].TimerOption = false; - Device[deviceCount].TimerOptional = false; - Device[deviceCount].GlobalSyncOption = true; - break; - } + { + Device[++deviceCount].Number = PLUGIN_ID_057; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].Ports = 0; + Device[deviceCount].VType = SENSOR_TYPE_NONE; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = false; + Device[deviceCount].ValueCount = 0; + Device[deviceCount].SendDataOption = false; + Device[deviceCount].TimerOption = false; + Device[deviceCount].TimerOptional = false; + Device[deviceCount].GlobalSyncOption = true; + break; + } case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_057); - break; - } + { + string = F(PLUGIN_NAME_057); + break; + } case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: - { - byte addr = PCONFIG(0); + { + byte addr = PCONFIG(0); - int optionValues[8] = { 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77 }; - addFormSelectorI2C(F("i2c_addr"), 8, optionValues, addr); - break; - } + int optionValues[8] = { 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77 }; + addFormSelectorI2C(F("i2c_addr"), 8, optionValues, addr); + break; + } case PLUGIN_WEBFORM_LOAD: - { - addFormSubHeader(F("7-Seg. Clock")); + { + addFormSubHeader(F("7-Seg. Clock")); - int16_t choice = PCONFIG(1); - String options[3] = {F("none"), F("7-Seg. HH:MM (24 hour)"), F("7-Seg. HH:MM (12 hour)")}; - addFormSelector(F("Clock Type"), F("clocktype"), 3, options, NULL, choice); + int16_t choice = PCONFIG(1); + String options[3] = { F("none"), F("7-Seg. HH:MM (24 hour)"), F("7-Seg. HH:MM (12 hour)") }; + addFormSelector(F("Clock Type"), F("clocktype"), 3, options, NULL, choice); - addFormNumericBox(F("Seg. for Xx:xx"), F("clocksegh10"), PCONFIG(2), 0, 7); - addFormNumericBox(F("Seg. for xX:xx"), F("clocksegh1"), PCONFIG(3), 0, 7); - addFormNumericBox(F("Seg. for xx:Xx"), F("clocksegm10"), PCONFIG(4), 0, 7); - addFormNumericBox(F("Seg. for xx:xX"), F("clocksegm1"), PCONFIG(5), 0, 7); + addFormNumericBox(F("Seg. for Xx:xx"), F("clocksegh10"), PCONFIG(2), 0, 7); + addFormNumericBox(F("Seg. for xX:xx"), F("clocksegh1"), PCONFIG(3), 0, 7); + addFormNumericBox(F("Seg. for xx:Xx"), F("clocksegm10"), PCONFIG(4), 0, 7); + addFormNumericBox(F("Seg. for xx:xX"), F("clocksegm1"), PCONFIG(5), 0, 7); - addFormNumericBox(F("Seg. for Colon"), F("clocksegcol"), PCONFIG(6), -1, 7); - addHtml(F(" Value ")); - addNumericBox(F("clocksegcolval"), PCONFIG(7), 0, 255); + addFormNumericBox(F("Seg. for Colon"), F("clocksegcol"), PCONFIG(6), -1, 7); + addHtml(F(" Value ")); + addNumericBox(F("clocksegcolval"), PCONFIG(7), 0, 255); - success = true; - break; - } + success = true; + break; + } case PLUGIN_WEBFORM_SAVE: - { - PCONFIG(0) = getFormItemInt(F("i2c_addr")); + { + PCONFIG(0) = getFormItemInt(F("i2c_addr")); - PCONFIG(1) = getFormItemInt(F("clocktype")); + PCONFIG(1) = getFormItemInt(F("clocktype")); - PCONFIG(2) = getFormItemInt(F("clocksegh10")); - PCONFIG(3) = getFormItemInt(F("clocksegh1")); - PCONFIG(4) = getFormItemInt(F("clocksegm10")); - PCONFIG(5) = getFormItemInt(F("clocksegm1")); - PCONFIG(6) = getFormItemInt(F("clocksegcol")); - PCONFIG(7) = getFormItemInt(F("clocksegcolval")); + PCONFIG(2) = getFormItemInt(F("clocksegh10")); + PCONFIG(3) = getFormItemInt(F("clocksegh1")); + PCONFIG(4) = getFormItemInt(F("clocksegm10")); + PCONFIG(5) = getFormItemInt(F("clocksegm1")); + PCONFIG(6) = getFormItemInt(F("clocksegcol")); + PCONFIG(7) = getFormItemInt(F("clocksegcolval")); - success = true; - break; - } + success = true; + break; + } case PLUGIN_INIT: - { - byte addr = PCONFIG(0); + { + byte addr = PCONFIG(0); - if (!Plugin_057_M) - Plugin_057_M = new CHT16K33; - - Plugin_057_M->Init(addr); - - success = true; - break; + if (!Plugin_057_M) { + Plugin_057_M = new CHT16K33; } + Plugin_057_M->Init(addr); + + success = true; + break; + } + case PLUGIN_WRITE: + { + if (!Plugin_057_M) { + return false; + } + + String command = parseString(string, 1); + + if (command == F("mprint")) { - if (!Plugin_057_M) - return false; + String text = parseStringToEnd(string, 2); - String command = parseString(string, 1); + if (text.length() > 0) { + byte seg = 0; - if (command == F("mprint")) - { - String text = parseStringToEnd(string, 2); - if (text.length() > 0) { - byte seg = 0; + Plugin_057_M->ClearRowBuffer(); - Plugin_057_M->ClearRowBuffer(); - while (text[seg] && seg < 8) - { - // uint16_t value = 0; - char c = text[seg]; - Plugin_057_M->SetDigit(seg, c); - seg++; - } - Plugin_057_M->TransmitRowBuffer(); - success = true; - } - } - else if (command == F("mbr")) { - String param = parseString(string, 2); - int brightness; - if (validIntFromString(param, brightness)) { - if (brightness >= 0 && brightness <= 255) - Plugin_057_M->SetBrightness(brightness); + while (text[seg] && seg < 8) + { + // uint16_t value = 0; + char c = text[seg]; + Plugin_057_M->SetDigit(seg, c); + seg++; } + Plugin_057_M->TransmitRowBuffer(); success = true; } - else if (command == F("m") || command == F("mx") || command == F("mnum")) + } + else if (command == F("mbr")) { + String param = parseString(string, 2); + int brightness; + + if (validIntFromString(param, brightness)) { + if ((brightness >= 0) && (brightness <= 255)) { + Plugin_057_M->SetBrightness(brightness); + } + } + success = true; + } + else if ((command == F("m")) || (command == F("mx")) || (command == F("mnum"))) + { + String param; + String paramKey; + String paramVal; + byte paramIdx = 2; + uint8_t seg = 0; + uint16_t value = 0; + + String lowerString = string; + lowerString.toLowerCase(); + lowerString.replace(F(" "), " "); + lowerString.replace(F(" ="), "="); + lowerString.replace(F("= "), "="); + + param = parseString(lowerString, paramIdx++); + + if (param.length()) { - String param; - String paramKey; - String paramVal; - byte paramIdx = 2; - uint8_t seg = 0; - uint16_t value = 0; - - String lowerString=string; - lowerString.toLowerCase(); - lowerString.replace(F(" "), " "); - lowerString.replace(F(" ="), "="); - lowerString.replace(F("= "), "="); - - param = parseString(lowerString, paramIdx++); - if (param.length()) + while (param.length()) { - while (param.length()) + addLog(LOG_LEVEL_DEBUG_MORE, param); + + if (param == F("log")) { - addLog(LOG_LEVEL_DEBUG_MORE, param); + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = F("MX : "); - if (param == F("log")) - { - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("MX : "); - for (byte i = 0; i < 8; i++) - { - log += String(Plugin_057_M->GetRow(i), 16); - log += F("h, "); - } - addLog(LOG_LEVEL_INFO, log); - } - success = true; - } - - else if (param == F("test")) - { for (byte i = 0; i < 8; i++) - Plugin_057_M->SetRow(i, 1 << i); - success = true; + { + log += String(Plugin_057_M->GetRow(i), 16); + log += F("h, "); + } + addLog(LOG_LEVEL_INFO, log); } + success = true; + } - else if (param == F("clear")) + else if (param == F("test")) + { + for (byte i = 0; i < 8; i++) { + Plugin_057_M->SetRow(i, 1 << i); + } + success = true; + } + + else if (param == F("clear")) + { + Plugin_057_M->ClearRowBuffer(); + success = true; + } + + else + { + int index = param.indexOf('='); + + if (index > 0) // syntax: "=" { - Plugin_057_M->ClearRowBuffer(); - success = true; + paramKey = param.substring(0, index); + paramVal = param.substring(index + 1); + seg = paramKey.toInt(); + } + else // syntax: "" + { + paramVal = param; } + if (command == F("mnum")) + { + value = paramVal.toInt(); + + if (value < 16) { + Plugin_057_M->SetDigit(seg, value); + } + else { + Plugin_057_M->SetRow(seg, value); + } + } + else if (command == F("mx")) + { + char *ep; + value = strtol(paramVal.c_str(), &ep, 16); + Plugin_057_M->SetRow(seg, value); + } else { - int index = param.indexOf('='); - if (index > 0) //syntax: "=" - { - paramKey = param.substring(0, index); - paramVal = param.substring(index+1); - seg = paramKey.toInt(); - } - else //syntax: "" - { - paramVal = param; - } - - if (command == F("mnum")) - { - value = paramVal.toInt(); - if (value < 16) - Plugin_057_M->SetDigit(seg, value); - else - Plugin_057_M->SetRow(seg, value); - } - else if (command == F("mx")) - { - char* ep; - value = strtol(paramVal.c_str(), &ep, 16); - Plugin_057_M->SetRow(seg, value); - } - else - { - value = paramVal.toInt(); - Plugin_057_M->SetRow(seg, value); - } - - success = true; - seg++; + value = paramVal.toInt(); + Plugin_057_M->SetRow(seg, value); } - param = parseString(lowerString, paramIdx++); + success = true; + seg++; } - } - else - { - //??? no params - } - if (success) - Plugin_057_M->TransmitRowBuffer(); - success = true; + param = parseString(lowerString, paramIdx++); + } + } + else + { + // ??? no params } - break; + if (success) { + Plugin_057_M->TransmitRowBuffer(); + } + success = true; } + break; + } + case PLUGIN_CLOCK_IN: - { - if (!Plugin_057_M || PCONFIG(1) == 0) - break; - - byte hours = node_time.hour(); - byte minutes = node_time.minute(); - - // Plugin_057_M->ClearRowBuffer(); - Plugin_057_M->SetDigit(PCONFIG(5), minutes % 10); - Plugin_057_M->SetDigit(PCONFIG(4), minutes / 10); - - if (PCONFIG(1) == 1) { // 24-hour clock - // 24-hour clock shows leading zero - Plugin_057_M->SetDigit(PCONFIG(2), hours / 10); - Plugin_057_M->SetDigit(PCONFIG(3), hours % 10); - } else if (PCONFIG(1) == 2) { // 12-hour clock - if (hours < 12) { - // to set AM marker, get buffer and add decimal to it. - Plugin_057_M->SetRow(PCONFIG(5), (Plugin_057_M->GetRow(PCONFIG(5)) | 0x80)); - } - - hours = hours % 12; - if (hours == 0) { - hours = 12; - } - - Plugin_057_M->SetDigit(PCONFIG(3), hours % 10); - - if (hours < 10) { - // 12-hour clock will show empty segment when hours < 10 - Plugin_057_M->SetRow(PCONFIG(2), 0); - } else { - Plugin_057_M->SetDigit(PCONFIG(2), hours / 10); - } - } - - //if (PCONFIG(6) >= 0) - // Plugin_057_M->SetRow(PCONFIG(6), PCONFIG(7)); - Plugin_057_M->TransmitRowBuffer(); - - success = true; - + { + if (!Plugin_057_M || (PCONFIG(1) == 0)) { break; } - case PLUGIN_TEN_PER_SECOND: - { - if (!Plugin_057_M || PCONFIG(1) == 0) //clock enabled? - break; + byte hours = node_time.hour(); + byte minutes = node_time.minute(); - if (PCONFIG(6) >= 0) //colon used? - { - uint8_t act = ((uint16_t)millis() >> 9) & 1; //blink with about 2 Hz - static uint8_t last = 0; - if (act != last) - { - last = act; - Plugin_057_M->SetRow(PCONFIG(6), (act) ? PCONFIG(7) : 0); - Plugin_057_M->TransmitRowBuffer(); - } + // Plugin_057_M->ClearRowBuffer(); + Plugin_057_M->SetDigit(PCONFIG(5), minutes % 10); + Plugin_057_M->SetDigit(PCONFIG(4), minutes / 10); + + if (PCONFIG(1) == 1) { // 24-hour clock + // 24-hour clock shows leading zero + Plugin_057_M->SetDigit(PCONFIG(2), hours / 10); + Plugin_057_M->SetDigit(PCONFIG(3), hours % 10); + } else if (PCONFIG(1) == 2) { // 12-hour clock + if (hours < 12) { + // to set AM marker, get buffer and add decimal to it. + Plugin_057_M->SetRow(PCONFIG(5), (Plugin_057_M->GetRow(PCONFIG(5)) | 0x80)); + } + + hours = hours % 12; + + if (hours == 0) { + hours = 12; + } + + Plugin_057_M->SetDigit(PCONFIG(3), hours % 10); + + if (hours < 10) { + // 12-hour clock will show empty segment when hours < 10 + Plugin_057_M->SetRow(PCONFIG(2), 0); + } else { + Plugin_057_M->SetDigit(PCONFIG(2), hours / 10); } } + // if (PCONFIG(6) >= 0) + // Plugin_057_M->SetRow(PCONFIG(6), PCONFIG(7)); + Plugin_057_M->TransmitRowBuffer(); + + success = true; + + break; + } + + case PLUGIN_TEN_PER_SECOND: + { + if (!Plugin_057_M || (PCONFIG(1) == 0)) { // clock enabled? + break; + } + + if (PCONFIG(6) >= 0) // colon used? + { + uint8_t act = ((uint16_t)millis() >> 9) & 1; // blink with about 2 Hz + static uint8_t last = 0; + + if (act != last) + { + last = act; + Plugin_057_M->SetRow(PCONFIG(6), (act) ? PCONFIG(7) : 0); + Plugin_057_M->TransmitRowBuffer(); + } + } + } } return success; } diff --git a/src/_P058_HT16K33_KeyPad.ino b/src/_P058_HT16K33_KeyPad.ino index 047603ffc..ff4006b4b 100644 --- a/src/_P058_HT16K33_KeyPad.ino +++ b/src/_P058_HT16K33_KeyPad.ino @@ -1,7 +1,8 @@ #ifdef USES_P058 -//####################################################################################################### -//#################################### Plugin 058: HT16K33 KeyPad ####################################### -//####################################################################################################### + +// ####################################################################################################### +// #################################### Plugin 058: HT16K33 KeyPad ####################################### +// ####################################################################################################### // ESPEasy Plugin to scan a 13x3 key pad matrix chip HT16K33 // written by Jochen Krapf (jk@nerd2nerd.org) @@ -27,7 +28,6 @@ // Note: The HT16K33-LED-plugin and the HT16K33-key-plugin can be used at the same time with the same I2C address - #define PLUGIN_058 #define PLUGIN_ID_058 58 #define PLUGIN_NAME_058 "Keypad - HT16K33 [TESTING]" @@ -36,7 +36,7 @@ #include #include "_Plugin_Helper.h" -CHT16K33* Plugin_058_K = NULL; +CHT16K33 *Plugin_058_K = NULL; boolean Plugin_058(byte function, struct EventStruct *event, String& string) @@ -46,105 +46,103 @@ boolean Plugin_058(byte function, struct EventStruct *event, String& string) switch (function) { case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_058; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].Ports = 0; - Device[deviceCount].VType = SENSOR_TYPE_SWITCH; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = false; - Device[deviceCount].ValueCount = 1; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].TimerOptional = true; - Device[deviceCount].GlobalSyncOption = true; - break; - } + { + Device[++deviceCount].Number = PLUGIN_ID_058; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].Ports = 0; + Device[deviceCount].VType = SENSOR_TYPE_SWITCH; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = false; + Device[deviceCount].ValueCount = 1; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].TimerOptional = true; + Device[deviceCount].GlobalSyncOption = true; + break; + } case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_058); - break; - } + { + string = F(PLUGIN_NAME_058); + break; + } case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_058)); - break; - } + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_058)); + break; + } case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: - { - byte addr = PCONFIG(0); + { + byte addr = PCONFIG(0); - int optionValues[8] = { 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77 }; - addFormSelectorI2C(F("i2c_addr"), 8, optionValues, addr); - break; - } + int optionValues[8] = { 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77 }; + addFormSelectorI2C(F("i2c_addr"), 8, optionValues, addr); + break; + } case PLUGIN_WEBFORM_LOAD: - { - success = true; - break; - } + { + success = true; + break; + } case PLUGIN_WEBFORM_SAVE: - { - PCONFIG(0) = getFormItemInt(F("i2c_addr")); + { + PCONFIG(0) = getFormItemInt(F("i2c_addr")); - success = true; - break; - } + success = true; + break; + } case PLUGIN_INIT: - { - byte addr = PCONFIG(0); + { + byte addr = PCONFIG(0); - if (!Plugin_058_K) - Plugin_058_K = new CHT16K33; - - Plugin_058_K->Init(addr); - - success = true; - break; + if (!Plugin_058_K) { + Plugin_058_K = new CHT16K33; } + Plugin_058_K->Init(addr); + + success = true; + break; + } + case PLUGIN_TEN_PER_SECOND: + { + if (Plugin_058_K) { - if (Plugin_058_K) + static uint8_t keyLast = 0; + + uint8_t key = Plugin_058_K->ReadKeys(); + + if (keyLast != key) { - static uint8_t keyLast = 0; + keyLast = key; + UserVar[event->BaseVarIndex] = (float)key; + event->sensorType = SENSOR_TYPE_SWITCH; - uint8_t key = Plugin_058_K->ReadKeys(); - - if (keyLast != key) - { - keyLast = key; - UserVar[event->BaseVarIndex] = (float)key; - event->sensorType = SENSOR_TYPE_SWITCH; - - String log = F("Mkey : key=0x"); - log += String(key, 16); - addLog(LOG_LEVEL_INFO, log); - - sendData(event); - } + String log = F("Mkey : key=0x"); + log += String(key, 16); + addLog(LOG_LEVEL_INFO, log); + sendData(event); } - success = true; - break; } + success = true; + break; + } case PLUGIN_READ: - { - if (Plugin_058_K) - { - } - success = true; - break; - } - + { + if (Plugin_058_K) + {} + success = true; + break; + } } return success; } diff --git a/src/_P060_MCP3221.ino b/src/_P060_MCP3221.ino index fd5e9ec05..1a0340eb8 100644 --- a/src/_P060_MCP3221.ino +++ b/src/_P060_MCP3221.ino @@ -1,7 +1,8 @@ #ifdef USES_P060 -//####################################################################################################### -//#################################### Plugin 060: MCP3221 ############################################## -//####################################################################################################### + +// ####################################################################################################### +// #################################### Plugin 060: MCP3221 ############################################## +// ####################################################################################################### // Plugin to read 12-bit-values from ADC chip MCP3221. It is used e.g. in MinipH pH interface to sample a pH probe in an aquarium // written by Jochen Krapf (jk@nerd2nerd.org) @@ -20,13 +21,16 @@ uint16_t Plugin_060_OversamplingCount = 0; uint16_t readMCP3221(byte addr) { uint16_t value; + Wire.requestFrom(addr, (uint8_t)2); + if (Wire.available() == 2) { value = (Wire.read() << 8) | Wire.read(); } - else + else { value = 9999; + } return value; } @@ -38,131 +42,132 @@ boolean Plugin_060(byte function, struct EventStruct *event, String& string) switch (function) { case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_060; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].VType = SENSOR_TYPE_SINGLE; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = true; - Device[deviceCount].ValueCount = 1; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].GlobalSyncOption = true; - break; - } + { + Device[++deviceCount].Number = PLUGIN_ID_060; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = SENSOR_TYPE_SINGLE; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 1; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].GlobalSyncOption = true; + break; + } case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_060); - break; - } + { + string = F(PLUGIN_NAME_060); + break; + } case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_060)); - break; - } + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_060)); + break; + } case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: - { - byte addr = PCONFIG(0); + { + byte addr = PCONFIG(0); - int optionValues[8] = { 0x4D, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4E, 0x4F }; - addFormSelectorI2C(F("i2c_addr"), 8, optionValues, addr); - break; - } + int optionValues[8] = { 0x4D, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4E, 0x4F }; + addFormSelectorI2C(F("i2c_addr"), 8, optionValues, addr); + break; + } case PLUGIN_WEBFORM_LOAD: - { - addFormCheckBox(F("Oversampling"), F("p060_oversampling"), PCONFIG(1)); + { + addFormCheckBox(F("Oversampling"), F("p060_oversampling"), PCONFIG(1)); - addFormSubHeader(F("Two Point Calibration")); + addFormSubHeader(F("Two Point Calibration")); - addFormCheckBox(F("Calibration Enabled"), F("p060_cal"), PCONFIG(3)); + addFormCheckBox(F("Calibration Enabled"), F("p060_cal"), PCONFIG(3)); - addFormNumericBox(F("Point 1"), F("p060_adc1"), PCONFIG_LONG(0), 0, 4095); - html_add_estimate_symbol(); - addTextBox(F("p060_out1"), String(PCONFIG_FLOAT(0), 3), 10); + addFormNumericBox(F("Point 1"), F("p060_adc1"), PCONFIG_LONG(0), 0, 4095); + html_add_estimate_symbol(); + addTextBox(F("p060_out1"), String(PCONFIG_FLOAT(0), 3), 10); - addFormNumericBox(F("Point 2"), F("p060_adc2"), PCONFIG_LONG(1), 0, 4095); - html_add_estimate_symbol(); - addTextBox(F("p060_out2"), String(PCONFIG_FLOAT(1), 3), 10); + addFormNumericBox(F("Point 2"), F("p060_adc2"), PCONFIG_LONG(1), 0, 4095); + html_add_estimate_symbol(); + addTextBox(F("p060_out2"), String(PCONFIG_FLOAT(1), 3), 10); - success = true; - break; - } + success = true; + break; + } case PLUGIN_WEBFORM_SAVE: - { - PCONFIG(0) = getFormItemInt(F("i2c_addr")); + { + PCONFIG(0) = getFormItemInt(F("i2c_addr")); - PCONFIG(1) = isFormItemChecked(F("p060_oversampling")); + PCONFIG(1) = isFormItemChecked(F("p060_oversampling")); - PCONFIG(3) = isFormItemChecked(F("p060_cal")); + PCONFIG(3) = isFormItemChecked(F("p060_cal")); - PCONFIG_LONG(0) = getFormItemInt(F("p060_adc1")); - PCONFIG_FLOAT(0) = getFormItemFloat(F("p060_out1")); + PCONFIG_LONG(0) = getFormItemInt(F("p060_adc1")); + PCONFIG_FLOAT(0) = getFormItemFloat(F("p060_out1")); - PCONFIG_LONG(1) = getFormItemInt(F("p060_adc2")); - PCONFIG_FLOAT(1) = getFormItemFloat(F("p060_out2")); + PCONFIG_LONG(1) = getFormItemInt(F("p060_adc2")); + PCONFIG_FLOAT(1) = getFormItemFloat(F("p060_out2")); - success = true; - break; - } + success = true; + break; + } case PLUGIN_TEN_PER_SECOND: + { + if (PCONFIG(1)) // Oversampling? { - if (PCONFIG(1)) //Oversampling? - { - Plugin_060_OversamplingValue += readMCP3221(PCONFIG(0)); - Plugin_060_OversamplingCount ++; - } - success = true; - break; + Plugin_060_OversamplingValue += readMCP3221(PCONFIG(0)); + Plugin_060_OversamplingCount++; } + success = true; + break; + } case PLUGIN_READ: + { + String log = F("ADMCP: Analog value: "); + + if (Plugin_060_OversamplingCount > 0) { - String log = F("ADMCP: Analog value: "); + UserVar[event->BaseVarIndex] = (float)Plugin_060_OversamplingValue / Plugin_060_OversamplingCount; + Plugin_060_OversamplingValue = 0; + Plugin_060_OversamplingCount = 0; - if (Plugin_060_OversamplingCount > 0) + log += String(UserVar[event->BaseVarIndex], 3); + } + else + { + int16_t value = readMCP3221(PCONFIG(0)); + UserVar[event->BaseVarIndex] = (float)value; + + log += value; + } + + if (PCONFIG(3)) // Calibration? + { + int adc1 = PCONFIG_LONG(0); + int adc2 = PCONFIG_LONG(1); + float out1 = PCONFIG_FLOAT(0); + float out2 = PCONFIG_FLOAT(1); + + if (adc1 != adc2) { - UserVar[event->BaseVarIndex] = (float)Plugin_060_OversamplingValue / Plugin_060_OversamplingCount; - Plugin_060_OversamplingValue = 0; - Plugin_060_OversamplingCount = 0; + float normalized = (float)(UserVar[event->BaseVarIndex] - adc1) / (float)(adc2 - adc1); + UserVar[event->BaseVarIndex] = normalized * (out2 - out1) + out1; + log += F(" = "); log += String(UserVar[event->BaseVarIndex], 3); } - else - { - int16_t value = readMCP3221(PCONFIG(0)); - UserVar[event->BaseVarIndex] = (float)value; - - log += value; - } - - if (PCONFIG(3)) //Calibration? - { - int adc1 = PCONFIG_LONG(0); - int adc2 = PCONFIG_LONG(1); - float out1 = PCONFIG_FLOAT(0); - float out2 = PCONFIG_FLOAT(1); - if (adc1 != adc2) - { - float normalized = (float)(UserVar[event->BaseVarIndex] - adc1) / (float)(adc2 - adc1); - UserVar[event->BaseVarIndex] = normalized * (out2 - out1) + out1; - - log += F(" = "); - log += String(UserVar[event->BaseVarIndex], 3); - } - } - - addLog(LOG_LEVEL_INFO,log); - success = true; - break; } + + addLog(LOG_LEVEL_INFO, log); + success = true; + break; + } } return success; } diff --git a/src/_P061_KeyPad.ino b/src/_P061_KeyPad.ino index 9e48cedc8..cc01bc770 100644 --- a/src/_P061_KeyPad.ino +++ b/src/_P061_KeyPad.ino @@ -1,7 +1,8 @@ #ifdef USES_P061 -//####################################################################################################### -//#################################### Plugin 061: PCF8574/MCP23017 KeyPad ############################## -//####################################################################################################### + +// ####################################################################################################### +// #################################### Plugin 061: PCF8574/MCP23017 KeyPad ############################## +// ####################################################################################################### // ESPEasy Plugin to scan a (up to) 9x8 key pad matrix chip MCP23017 // or a (up to) 5x4 key pad matrix chip PCF8574 @@ -19,7 +20,7 @@ // ... // column 8 = GPB7 -//Typical Key Pad: +// Typical Key Pad: // C1 C2 C3 // R1 [1] [2] [3] // R2 [4] [5] [6] @@ -68,309 +69,325 @@ boolean Plugin_061(byte function, struct EventStruct *event, String& string) switch (function) { case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_061; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].Ports = 0; - Device[deviceCount].VType = SENSOR_TYPE_SWITCH; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = false; - Device[deviceCount].ValueCount = 1; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].TimerOptional = true; - Device[deviceCount].GlobalSyncOption = true; - break; - } + { + Device[++deviceCount].Number = PLUGIN_ID_061; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].Ports = 0; + Device[deviceCount].VType = SENSOR_TYPE_SWITCH; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = false; + Device[deviceCount].ValueCount = 1; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].TimerOptional = true; + Device[deviceCount].GlobalSyncOption = true; + break; + } case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_061); - break; - } + { + string = F(PLUGIN_NAME_061); + break; + } case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_061)); - break; - } + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_061)); + break; + } case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: - { - byte addr = PCONFIG(0); + { + byte addr = PCONFIG(0); - int optionValues[16] = { 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F }; - addFormSelectorI2C(F("i2c_addr"), (PCONFIG(1) == 0) ? 8 : 16, optionValues, addr); - if (PCONFIG(1) != 0) - addFormNote(F("PCF8574 uses address 0x20+; PCF8574A uses address 0x38+")); - break; + int optionValues[16] = { 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F }; + addFormSelectorI2C(F("i2c_addr"), (PCONFIG(1) == 0) ? 8 : 16, optionValues, addr); + + if (PCONFIG(1) != 0) { + addFormNote(F("PCF8574 uses address 0x20+; PCF8574A uses address 0x38+")); } + break; + } case PLUGIN_WEBFORM_LOAD: - { - String options[3] = { F("MCP23017 (Matrix 9x8)"), F("PCF8574 (Matrix 5x4)"), F("PCF8574 (Direct 8)") }; - addFormSelector(F("Chip (Mode)"), F("chip"), 3, options, NULL, PCONFIG(1)); + { + String options[3] = { F("MCP23017 (Matrix 9x8)"), F("PCF8574 (Matrix 5x4)"), F("PCF8574 (Direct 8)") }; + addFormSelector(F("Chip (Mode)"), F("chip"), 3, options, NULL, PCONFIG(1)); - success = true; - break; - } + success = true; + break; + } case PLUGIN_WEBFORM_SAVE: - { - PCONFIG(0) = getFormItemInt(F("i2c_addr")); + { + PCONFIG(0) = getFormItemInt(F("i2c_addr")); - PCONFIG(1) = getFormItemInt(F("chip")); + PCONFIG(1) = getFormItemInt(F("chip")); - success = true; - break; - } + success = true; + break; + } case PLUGIN_INIT: + { + switch (PCONFIG(1)) { - switch (PCONFIG(1)) - { - case 0: MCP23017_KeyPadMatrixInit(PCONFIG(0)); break; - case 1: PCF8574_KeyPadMatrixInit(PCONFIG(0)); break; - case 2: PCF8574_KeyPadDirectInit(PCONFIG(0)); break; - } - - success = true; - break; + case 0: MCP23017_KeyPadMatrixInit(PCONFIG(0)); break; + case 1: PCF8574_KeyPadMatrixInit(PCONFIG(0)); break; + case 2: PCF8574_KeyPadDirectInit(PCONFIG(0)); break; } + success = true; + break; + } + case PLUGIN_FIFTY_PER_SECOND: + { + static byte lastScanCode = 0xFF; + static byte sentScanCode = 0xFF; + byte actScanCode = 0; + + switch (PCONFIG(1)) { - static byte lastScanCode = 0xFF; - static byte sentScanCode = 0xFF; - byte actScanCode = 0; - - switch (PCONFIG(1)) - { - case 0: actScanCode = MCP23017_KeyPadMatrixScan(PCONFIG(0)); break; - case 1: actScanCode = PCF8574_KeyPadMatrixScan(PCONFIG(0)); break; - case 2: actScanCode = PCF8574_KeyPadDirectScan(PCONFIG(0)); break; - } - - if (lastScanCode == actScanCode) // debounced? - two times the same value? - { - if (sentScanCode != actScanCode) // any change to last sent data? - { - UserVar[event->BaseVarIndex] = (float)actScanCode; - event->sensorType = SENSOR_TYPE_SWITCH; - - String log = F("KPad : ScanCode=0x"); - log += String(actScanCode, 16); - addLog(LOG_LEVEL_INFO, log); - - sendData(event); - - sentScanCode = actScanCode; - } - } - else - lastScanCode = actScanCode; - - success = true; - break; + case 0: actScanCode = MCP23017_KeyPadMatrixScan(PCONFIG(0)); break; + case 1: actScanCode = PCF8574_KeyPadMatrixScan(PCONFIG(0)); break; + case 2: actScanCode = PCF8574_KeyPadDirectScan(PCONFIG(0)); break; } + if (lastScanCode == actScanCode) // debounced? - two times the same value? + { + if (sentScanCode != actScanCode) // any change to last sent data? + { + UserVar[event->BaseVarIndex] = (float)actScanCode; + event->sensorType = SENSOR_TYPE_SWITCH; + + String log = F("KPad : ScanCode=0x"); + log += String(actScanCode, 16); + addLog(LOG_LEVEL_INFO, log); + + sendData(event); + + sentScanCode = actScanCode; + } + } + else { + lastScanCode = actScanCode; + } + + success = true; + break; + } + case PLUGIN_READ: - { - // work is done in PLUGIN_FIFTY_PER_SECOND - success = true; - break; - } - + { + // work is done in PLUGIN_FIFTY_PER_SECOND + success = true; + break; + } } return success; } - // MCP23017 Matrix ///////////////////////////////////////////////////////////// -#define MCP23017_IODIRA 0x00 // I/O DIRECTION REGISTER IO7 IO6 IO5 IO4 IO3 IO2 IO1 IO0 1111 1111 -#define MCP23017_IODIRB 0x01 // I/O DIRECTION REGISTER IO7 IO6 IO5 IO4 IO3 IO2 IO1 IO0 1111 1111 -#define MCP23017_IPOLA 0x02 // INPUT POLARITY PORT REGISTER IP7 IP6 IP5 IP4 IP3 IP2 IP1 IP0 0000 0000 -#define MCP23017_IPOLB 0x03 // INPUT POLARITY PORT REGISTER IP7 IP6 IP5 IP4 IP3 IP2 IP1 IP0 0000 0000 -#define MCP23017_GPINTENA 0x04 // INTERRUPT-ON-CHANGE PINS GPINT7 GPINT6 GPINT5 GPINT4 GPINT3 GPINT2 GPINT1 GPINT0 0000 0000 -#define MCP23017_GPINTENB 0x05 // INTERRUPT-ON-CHANGE PINS GPINT7 GPINT6 GPINT5 GPINT4 GPINT3 GPINT2 GPINT1 GPINT0 0000 0000 -#define MCP23017_DEFVALA 0x06 // DEFAULT VALUE REGISTER DEF7 DEF6 DEF5 DEF4 DEF3 DEF2 DEF1 DEF0 0000 0000 -#define MCP23017_DEFVALB 0x07 // DEFAULT VALUE REGISTER DEF7 DEF6 DEF5 DEF4 DEF3 DEF2 DEF1 DEF0 0000 0000 -#define MCP23017_INTCONA 0x08 // INTERRUPT-ON-CHANGE CONTROL REGISTER IOC7 IOC6 IOC5 IOC4 IOC3 IOC2 IOC1 IOC0 0000 0000 -#define MCP23017_INTCONB 0x09 // INTERRUPT-ON-CHANGE CONTROL REGISTER IOC7 IOC6 IOC5 IOC4 IOC3 IOC2 IOC1 IOC0 0000 0000 -#define MCP23017_IOCON 0x0A // I/O EXPANDER CONFIGURATION REGISTER BANK MIRROR SEQOP DISSLW HAEN ODR INTPOL — 0000 0000 - also on addr 0x0B -#define MCP23017_GPPUA 0x0C // GPIO PULL-UP RESISTOR REGISTER PU7 PU6 PU5 PU4 PU3 PU2 PU1 PU0 0000 0000 -#define MCP23017_GPPUB 0x0D // GPIO PULL-UP RESISTOR REGISTER PU7 PU6 PU5 PU4 PU3 PU2 PU1 PU0 0000 0000 -#define MCP23017_INTFA 0x0E // INTERRUPT FLAG REGISTER INT7 INT6 INT5 INT4 INT3 INT2 INT1 INTO 0000 0000 -#define MCP23017_INTFB 0x0F // INTERRUPT FLAG REGISTER INT7 INT6 INT5 INT4 INT3 INT2 INT1 INTO 0000 0000 -#define MCP23017_INTCAPA 0x10 // INTERRUPT CAPTURED VALUE FOR PORT REGISTER ICP7 ICP6 ICP5 ICP4 ICP3 ICP2 ICP1 ICP0 0000 0000 -#define MCP23017_INTCAPB 0x11 // INTERRUPT CAPTURED VALUE FOR PORT REGISTER ICP7 ICP6 ICP5 ICP4 ICP3 ICP2 ICP1 ICP0 0000 0000 -#define MCP23017_GPIOA 0x12 // GENERAL PURPOSE I/O PORT REGISTER GP7 GP6 GP5 GP4 GP3 GP2 GP1 GP0 0000 0000 -#define MCP23017_GPIOB 0x13 // GENERAL PURPOSE I/O PORT REGISTER GP7 GP6 GP5 GP4 GP3 GP2 GP1 GP0 0000 0000 -#define MCP23017_OLATA 0x14 // OUTPUT LATCH REGISTER OL7 OL6 OL5 OL4 OL3 OL2 OL1 OL0 0000 0000 -#define MCP23017_OLATB 0x15 // OUTPUT LATCH REGISTER OL7 OL6 OL5 OL4 OL3 OL2 OL1 OL0 0000 0000 +#define MCP23017_IODIRA 0x00 // I/O DIRECTION REGISTER IO7 IO6 IO5 IO4 IO3 IO2 IO1 IO0 1111 1111 +#define MCP23017_IODIRB 0x01 // I/O DIRECTION REGISTER IO7 IO6 IO5 IO4 IO3 IO2 IO1 IO0 1111 1111 +#define MCP23017_IPOLA 0x02 // INPUT POLARITY PORT REGISTER IP7 IP6 IP5 IP4 IP3 IP2 IP1 IP0 0000 0000 +#define MCP23017_IPOLB 0x03 // INPUT POLARITY PORT REGISTER IP7 IP6 IP5 IP4 IP3 IP2 IP1 IP0 0000 0000 +#define MCP23017_GPINTENA 0x04 // INTERRUPT-ON-CHANGE PINS GPINT7 GPINT6 GPINT5 GPINT4 GPINT3 GPINT2 GPINT1 GPINT0 0000 0000 +#define MCP23017_GPINTENB 0x05 // INTERRUPT-ON-CHANGE PINS GPINT7 GPINT6 GPINT5 GPINT4 GPINT3 GPINT2 GPINT1 GPINT0 0000 0000 +#define MCP23017_DEFVALA 0x06 // DEFAULT VALUE REGISTER DEF7 DEF6 DEF5 DEF4 DEF3 DEF2 DEF1 DEF0 0000 0000 +#define MCP23017_DEFVALB 0x07 // DEFAULT VALUE REGISTER DEF7 DEF6 DEF5 DEF4 DEF3 DEF2 DEF1 DEF0 0000 0000 +#define MCP23017_INTCONA 0x08 // INTERRUPT-ON-CHANGE CONTROL REGISTER IOC7 IOC6 IOC5 IOC4 IOC3 IOC2 IOC1 IOC0 0000 0000 +#define MCP23017_INTCONB 0x09 // INTERRUPT-ON-CHANGE CONTROL REGISTER IOC7 IOC6 IOC5 IOC4 IOC3 IOC2 IOC1 IOC0 0000 0000 +#define MCP23017_IOCON 0x0A // I/O EXPANDER CONFIGURATION REGISTER BANK MIRROR SEQOP DISSLW HAEN ODR INTPOL — 0000 0000 - also on + // addr 0x0B +#define MCP23017_GPPUA 0x0C // GPIO PULL-UP RESISTOR REGISTER PU7 PU6 PU5 PU4 PU3 PU2 PU1 PU0 0000 0000 +#define MCP23017_GPPUB 0x0D // GPIO PULL-UP RESISTOR REGISTER PU7 PU6 PU5 PU4 PU3 PU2 PU1 PU0 0000 0000 +#define MCP23017_INTFA 0x0E // INTERRUPT FLAG REGISTER INT7 INT6 INT5 INT4 INT3 INT2 INT1 INTO 0000 0000 +#define MCP23017_INTFB 0x0F // INTERRUPT FLAG REGISTER INT7 INT6 INT5 INT4 INT3 INT2 INT1 INTO 0000 0000 +#define MCP23017_INTCAPA 0x10 // INTERRUPT CAPTURED VALUE FOR PORT REGISTER ICP7 ICP6 ICP5 ICP4 ICP3 ICP2 ICP1 ICP0 0000 0000 +#define MCP23017_INTCAPB 0x11 // INTERRUPT CAPTURED VALUE FOR PORT REGISTER ICP7 ICP6 ICP5 ICP4 ICP3 ICP2 ICP1 ICP0 0000 0000 +#define MCP23017_GPIOA 0x12 // GENERAL PURPOSE I/O PORT REGISTER GP7 GP6 GP5 GP4 GP3 GP2 GP1 GP0 0000 0000 +#define MCP23017_GPIOB 0x13 // GENERAL PURPOSE I/O PORT REGISTER GP7 GP6 GP5 GP4 GP3 GP2 GP1 GP0 0000 0000 +#define MCP23017_OLATA 0x14 // OUTPUT LATCH REGISTER OL7 OL6 OL5 OL4 OL3 OL2 OL1 OL0 0000 0000 +#define MCP23017_OLATB 0x15 // OUTPUT LATCH REGISTER OL7 OL6 OL5 OL4 OL3 OL2 OL1 OL0 0000 0000 void MCP23017_setReg(byte addr, byte reg, byte data) { - Wire.beginTransmission(addr); - Wire.write(reg); - Wire.write(data); - Wire.endTransmission(); + Wire.beginTransmission(addr); + Wire.write(reg); + Wire.write(data); + Wire.endTransmission(); } byte MCP23017_getReg(byte addr, byte reg) { - Wire.beginTransmission(addr); - Wire.write(reg); - Wire.endTransmission(); - Wire.requestFrom(addr, (uint8_t)0x1); - if (Wire.available()) - { - return Wire.read(); - } - return 0xFF; + Wire.beginTransmission(addr); + Wire.write(reg); + Wire.endTransmission(); + Wire.requestFrom(addr, (uint8_t)0x1); + + if (Wire.available()) + { + return Wire.read(); + } + return 0xFF; } void MCP23017_KeyPadMatrixInit(byte addr) { - MCP23017_setReg(addr, MCP23017_IODIRA, 0x00); // port A to output - MCP23017_setReg(addr, MCP23017_GPIOA, 0x00); // port A to low - MCP23017_setReg(addr, MCP23017_IODIRB, 0xFF); // port B to input - MCP23017_setReg(addr, MCP23017_GPPUA, 0xFF); // port A pullup on - MCP23017_setReg(addr, MCP23017_GPPUB, 0xFF); // port B pullup on + MCP23017_setReg(addr, MCP23017_IODIRA, 0x00); // port A to output + MCP23017_setReg(addr, MCP23017_GPIOA, 0x00); // port A to low + MCP23017_setReg(addr, MCP23017_IODIRB, 0xFF); // port B to input + MCP23017_setReg(addr, MCP23017_GPPUA, 0xFF); // port A pullup on + MCP23017_setReg(addr, MCP23017_GPPUB, 0xFF); // port B pullup on } byte MCP23017_KeyPadMatrixScan(byte addr) { - byte rowMask = 1; - byte colData; + byte rowMask = 1; + byte colData; - colData = MCP23017_getReg(addr, MCP23017_GPIOB); - if (colData == 0xFF) // no key pressed? - return 0; // no key pressed! + colData = MCP23017_getReg(addr, MCP23017_GPIOB); - for (byte row = 0; row <= 8; row++) - { - if (row == 0) - MCP23017_setReg(addr, MCP23017_IODIRA, 0xFF); // no bit of port A to output + if (colData == 0xFF) { // no key pressed? + return 0; // no key pressed! + } + + for (byte row = 0; row <= 8; row++) + { + if (row == 0) { + MCP23017_setReg(addr, MCP23017_IODIRA, 0xFF); // no bit of port A to output + } else { - MCP23017_setReg(addr, MCP23017_IODIRA, ~rowMask); // one bit of port A to output 0 + MCP23017_setReg(addr, MCP23017_IODIRA, ~rowMask); // one bit of port A to output 0 rowMask <<= 1; } - colData = MCP23017_getReg(addr, MCP23017_GPIOB); - if (colData != 0xFF) // any key pressed? - { - byte colMask = 1; - for (byte col = 1; col <= 8; col++) - { - if ((colData & colMask) == 0) // this key pressed? - { - MCP23017_setReg(addr, MCP23017_IODIRA, 0x00); // port A to output 0 - return ((row << 4) | col); - } - colMask <<= 1; - } - } - } + colData = MCP23017_getReg(addr, MCP23017_GPIOB); - MCP23017_setReg(addr, MCP23017_IODIRA, 0x00); // port A to output 0 - return 0; // no key pressed! + if (colData != 0xFF) // any key pressed? + { + byte colMask = 1; + + for (byte col = 1; col <= 8; col++) + { + if ((colData & colMask) == 0) // this key pressed? + { + MCP23017_setReg(addr, MCP23017_IODIRA, 0x00); // port A to output 0 + return (row << 4) | col; + } + colMask <<= 1; + } + } + } + + MCP23017_setReg(addr, MCP23017_IODIRA, 0x00); // port A to output 0 + return 0; // no key pressed! } // PCF8574 Matrix ////////////////////////////////////////////////////////////// void PCF8574_setReg(byte addr, byte data) { - Wire.beginTransmission(addr); - Wire.write(data); - Wire.endTransmission(); + Wire.beginTransmission(addr); + Wire.write(data); + Wire.endTransmission(); } byte PCF8574_getReg(byte addr) { - Wire.requestFrom(addr, (uint8_t)0x1); - if (Wire.available()) - { - return Wire.read(); - } - return 0xFF; + Wire.requestFrom(addr, (uint8_t)0x1); + + if (Wire.available()) + { + return Wire.read(); + } + return 0xFF; } void PCF8574_KeyPadMatrixInit(byte addr) { - PCF8574_setReg(addr, 0xF0); // low nibble to output 0 + PCF8574_setReg(addr, 0xF0); // low nibble to output 0 } byte PCF8574_KeyPadMatrixScan(byte addr) { - byte rowMask = 1; - byte colData; + byte rowMask = 1; + byte colData; - colData = PCF8574_getReg(addr) & 0xF0; - if (colData == 0xF0) // no key pressed? - return 0; // no key pressed! + colData = PCF8574_getReg(addr) & 0xF0; - for (byte row = 0; row <= 4; row++) - { - if (row == 0) - PCF8574_setReg(addr, 0xFF); // no bit of port A to output + if (colData == 0xF0) { // no key pressed? + return 0; // no key pressed! + } + + for (byte row = 0; row <= 4; row++) + { + if (row == 0) { + PCF8574_setReg(addr, 0xFF); // no bit of port A to output + } else { - PCF8574_setReg(addr, ~rowMask); // one bit of port A to output 0 + PCF8574_setReg(addr, ~rowMask); // one bit of port A to output 0 rowMask <<= 1; } colData = PCF8574_getReg(addr) & 0xF0; - if (colData != 0xF0) // any key pressed? - { - byte colMask = 0x10; - for (byte col = 1; col <= 4; col++) - { - if ((colData & colMask) == 0) // this key pressed? - { - PCF8574_setReg(addr, 0xF0); // low nibble to output 0 - return ((row << 4) | col); - } - colMask <<= 1; - } - } - } - PCF8574_setReg(addr, 0xF0); // low nibble to output 0 - return 0; // no key pressed! + if (colData != 0xF0) // any key pressed? + { + byte colMask = 0x10; + + for (byte col = 1; col <= 4; col++) + { + if ((colData & colMask) == 0) // this key pressed? + { + PCF8574_setReg(addr, 0xF0); // low nibble to output 0 + return (row << 4) | col; + } + colMask <<= 1; + } + } + } + + PCF8574_setReg(addr, 0xF0); // low nibble to output 0 + return 0; // no key pressed! } // PCF8574 Direct ////////////////////////////////////////////////////////////// void PCF8574_KeyPadDirectInit(byte addr) { - PCF8574_setReg(addr, 0xFF); // all to input + PCF8574_setReg(addr, 0xFF); // all to input } byte PCF8574_KeyPadDirectScan(byte addr) { - byte colData; + byte colData; - colData = PCF8574_getReg(addr); - if (colData == 0xFF) // no key pressed? - return 0; // no key pressed! + colData = PCF8574_getReg(addr); - byte colMask = 0x01; - for (byte col = 1; col <= 8; col++) - { - if ((colData & colMask) == 0) // this key pressed? - { - return (col); - } - colMask <<= 1; - } + if (colData == 0xFF) { // no key pressed? + return 0; // no key pressed! + } + byte colMask = 0x01; - return 0; // no key pressed! + for (byte col = 1; col <= 8; col++) + { + if ((colData & colMask) == 0) // this key pressed? + { + return col; + } + colMask <<= 1; + } + + return 0; // no key pressed! } #endif // USES_P061 diff --git a/src/_P062_MPR121_KeyPad.ino b/src/_P062_MPR121_KeyPad.ino index 28408e1a0..236ff6e2e 100644 --- a/src/_P062_MPR121_KeyPad.ino +++ b/src/_P062_MPR121_KeyPad.ino @@ -1,7 +1,8 @@ #ifdef USES_P062 -//####################################################################################################### -//#################################### Plugin 062: MPR121 KeyPad ######################################## -//####################################################################################################### + +// ####################################################################################################### +// #################################### Plugin 062: MPR121 KeyPad ######################################## +// ####################################################################################################### // ESPEasy Plugin to scan a 12 key touch pad chip MPR121 // written by Jochen Krapf (jk@nerd2nerd.org) @@ -15,7 +16,6 @@ // If more than one key is pressed, the value is sum of all KeyMap-values - #define PLUGIN_062 #define PLUGIN_ID_062 62 #define PLUGIN_NAME_062 "Keypad - MPR121 Touch [TESTING]" @@ -24,7 +24,7 @@ #include #include "_Plugin_Helper.h" -Adafruit_MPR121* Plugin_062_K = NULL; +Adafruit_MPR121 *Plugin_062_K = NULL; boolean Plugin_062(byte function, struct EventStruct *event, String& string) @@ -34,127 +34,129 @@ boolean Plugin_062(byte function, struct EventStruct *event, String& string) switch (function) { case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_062; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].Ports = 0; - Device[deviceCount].VType = SENSOR_TYPE_SWITCH; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = false; - Device[deviceCount].ValueCount = 1; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].TimerOptional = true; - Device[deviceCount].GlobalSyncOption = true; - break; - } + { + Device[++deviceCount].Number = PLUGIN_ID_062; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].Ports = 0; + Device[deviceCount].VType = SENSOR_TYPE_SWITCH; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = false; + Device[deviceCount].ValueCount = 1; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].TimerOptional = true; + Device[deviceCount].GlobalSyncOption = true; + break; + } case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_062); - break; - } + { + string = F(PLUGIN_NAME_062); + break; + } case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_062)); - break; - } + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_062)); + break; + } case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: - { - byte addr = PCONFIG(0); + { + byte addr = PCONFIG(0); - int optionValues[4] = { 0x5A, 0x5B, 0x5C, 0x5D }; - addFormSelectorI2C(F("i2c_addr"), 4, optionValues, addr); - break; - } + int optionValues[4] = { 0x5A, 0x5B, 0x5C, 0x5D }; + addFormSelectorI2C(F("i2c_addr"), 4, optionValues, addr); + break; + } case PLUGIN_WEBFORM_LOAD: - { - addFormCheckBox(F("ScanCode"), F("scancode"), PCONFIG(1)); + { + addFormCheckBox(F("ScanCode"), F("scancode"), PCONFIG(1)); - success = true; - break; - } + success = true; + break; + } case PLUGIN_WEBFORM_SAVE: - { - PCONFIG(0) = getFormItemInt(F("i2c_addr")); + { + PCONFIG(0) = getFormItemInt(F("i2c_addr")); - PCONFIG(1) = isFormItemChecked(F("scancode")); + PCONFIG(1) = isFormItemChecked(F("scancode")); - success = true; - break; - } + success = true; + break; + } case PLUGIN_INIT: - { - byte addr = PCONFIG(0); + { + byte addr = PCONFIG(0); - if (!Plugin_062_K) - Plugin_062_K = new Adafruit_MPR121; - - Plugin_062_K->begin(addr); - - success = true; - break; + if (!Plugin_062_K) { + Plugin_062_K = new Adafruit_MPR121; } + Plugin_062_K->begin(addr); + + success = true; + break; + } + case PLUGIN_TEN_PER_SECOND: + { + if (Plugin_062_K) { - if (Plugin_062_K) + static uint16_t keyLast = 0; + + uint16_t key = Plugin_062_K->touched(); + + if (key && PCONFIG(1)) { - static uint16_t keyLast = 0; + uint16_t colMask = 0x01; - uint16_t key = Plugin_062_K->touched(); - - if (key && PCONFIG(1)) + for (byte col = 1; col <= 12; col++) { - uint16_t colMask = 0x01; - for (byte col = 1; col <= 12; col++) + if (key & colMask) // this key pressed? { - if (key & colMask) // this key pressed? - { - key = col; - break; - } - colMask <<= 1; + key = col; + break; } + colMask <<= 1; } - - if (keyLast != key) - { - keyLast = key; - UserVar[event->BaseVarIndex] = (float)key; - event->sensorType = SENSOR_TYPE_SWITCH; - - String log = F("Tkey : "); - if (PCONFIG(1)) - log = F("ScanCode=0x"); - else - log = F("KeyMap=0x"); - log += String(key, 16); - addLog(LOG_LEVEL_INFO, log); - - sendData(event); - } - } - success = true; - break; + + if (keyLast != key) + { + keyLast = key; + UserVar[event->BaseVarIndex] = (float)key; + event->sensorType = SENSOR_TYPE_SWITCH; + + String log = F("Tkey : "); + + if (PCONFIG(1)) { + log = F("ScanCode=0x"); + } + else { + log = F("KeyMap=0x"); + } + log += String(key, 16); + addLog(LOG_LEVEL_INFO, log); + + sendData(event); + } } + success = true; + break; + } case PLUGIN_READ: - { - if (Plugin_062_K) - { - } - success = true; - break; - } - + { + if (Plugin_062_K) + {} + success = true; + break; + } } return success; } diff --git a/src/_P064_APDS9960.ino b/src/_P064_APDS9960.ino index 198513543..439367ab5 100644 --- a/src/_P064_APDS9960.ino +++ b/src/_P064_APDS9960.ino @@ -1,7 +1,8 @@ #ifdef USES_P064 -//####################################################################################################### -//#################################### Plugin 064: APDS9960 Gesture ############################## -//####################################################################################################### + +// ####################################################################################################### +// #################################### Plugin 064: APDS9960 Gesture ############################## +// ####################################################################################################### // ESPEasy Plugin to scan a gesture, proximity and light chip APDS9960 // written by Jochen Krapf (jk@nerd2nerd.org) @@ -17,8 +18,8 @@ // 2020-04-25 tonhuisman: Added Plugin Mode setting to switch between Proximity/Ambient Light Sensor or R/G/B Colors. // Added settings for Gain (Gesture, Proximity, Ambient Light Sensor), Led Power (Gesture and Proximity/ALS) and Led Boost (Gesture) // to allow better tuning for use of the sensor. Also adapted the SparkFun_APDS9960 driver for enabling this. -// R/G/B Colors mode has it's settings shared with the Gesture/Proximity/ALS as they are the exact same parameters, but with different labels only. - +// R/G/B Colors mode has it's settings shared with the Gesture/Proximity/ALS as they are the exact same parameters, but with different +// labels only. #define PLUGIN_064 @@ -32,8 +33,8 @@ #define PLUGIN_RGB_VALUENAME2_064 "G" #define PLUGIN_RGB_VALUENAME3_064 "B" -#define PLUGIN_MODE_GPL_064 0 // GPL = Gesture/Proximity/(Ambient) Light Sensor mode -#define PLUGIN_MODE_RGB_064 1 // RGB = R/G/B Colors mode +#define PLUGIN_MODE_GPL_064 0 // GPL = Gesture/Proximity/(Ambient) Light Sensor mode +#define PLUGIN_MODE_RGB_064 1 // RGB = R/G/B Colors mode #define P064_ADDR PCONFIG(0) #define P064_MODE PCONFIG(1) @@ -47,10 +48,10 @@ #define P064_IS_GPL_SENSOR (P064_MODE == PLUGIN_MODE_GPL_064) #define P064_IS_RGB_SENSOR (P064_MODE == PLUGIN_MODE_RGB_064) -#include //Lib is modified to work with ESP +#include // Lib is modified to work with ESP #include "_Plugin_Helper.h" -SparkFun_APDS9960* PLUGIN_064_pds = NULL; +SparkFun_APDS9960 *PLUGIN_064_pds = NULL; boolean Plugin_064(byte function, struct EventStruct *event, String& string) { @@ -59,278 +60,291 @@ boolean Plugin_064(byte function, struct EventStruct *event, String& string) switch (function) { case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_064; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].Ports = 0; - Device[deviceCount].VType = SENSOR_TYPE_SWITCH; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = false; - Device[deviceCount].ValueCount = 3; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].TimerOptional = true; - Device[deviceCount].GlobalSyncOption = true; - break; - } + { + Device[++deviceCount].Number = PLUGIN_ID_064; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].Ports = 0; + Device[deviceCount].VType = SENSOR_TYPE_SWITCH; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = false; + Device[deviceCount].ValueCount = 3; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].TimerOptional = true; + Device[deviceCount].GlobalSyncOption = true; + break; + } case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_064); - break; - } + { + string = F(PLUGIN_NAME_064); + break; + } case PLUGIN_GET_DEVICEVALUENAMES: - { - if (P064_IS_GPL_SENSOR) { // Gesture/Proximity/ALS mode - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_GPL_VALUENAME1_064)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_GPL_VALUENAME2_064)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_GPL_VALUENAME3_064)); - } else { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_RGB_VALUENAME1_064)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_RGB_VALUENAME2_064)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_RGB_VALUENAME3_064)); - } - break; + { + if (P064_IS_GPL_SENSOR) { // Gesture/Proximity/ALS mode + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_GPL_VALUENAME1_064)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_GPL_VALUENAME2_064)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_GPL_VALUENAME3_064)); + } else { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_RGB_VALUENAME1_064)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_RGB_VALUENAME2_064)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_RGB_VALUENAME3_064)); } + break; + } case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: - { - byte addr = 0x39; // P064_ADDR; chip has only 1 address + { + byte addr = 0x39; // P064_ADDR; chip has only 1 address - int optionValues[1] = { 0x39 }; - addFormSelectorI2C(F("i2c_addr"), 1, optionValues, addr); //Only for display I2C address - break; - } + int optionValues[1] = { 0x39 }; + addFormSelectorI2C(F("i2c_addr"), 1, optionValues, addr); // Only for display I2C address + break; + } case PLUGIN_WEBFORM_LOAD: - { - String optionsPluginMode[2]; - optionsPluginMode[0] = F("Gesture/Proximity/Ambient Light Sensor"); - optionsPluginMode[1] = F("R/G/B Colors"); - int optionsPluginModeValues[2] = {PLUGIN_MODE_GPL_064, PLUGIN_MODE_RGB_064}; - addFormSelector(F("Plugin Mode"), F("p064_mode"), 2, optionsPluginMode, optionsPluginModeValues, P064_MODE, true); - addFormNote(F("After changing Plugin Mode you may want to change the Values names, below.")); + { + String optionsPluginMode[2]; + optionsPluginMode[0] = F("Gesture/Proximity/Ambient Light Sensor"); + optionsPluginMode[1] = F("R/G/B Colors"); + int optionsPluginModeValues[2] = { PLUGIN_MODE_GPL_064, PLUGIN_MODE_RGB_064 }; + addFormSelector(F("Plugin Mode"), F("p064_mode"), 2, optionsPluginMode, optionsPluginModeValues, P064_MODE, true); + addFormNote(F("After changing Plugin Mode you may want to change the Values names, below.")); - if (P064_IS_RGB_SENSOR // R/G/B Colors mode and default Gesture/Proximity/ALS values: Set new default names - && strcmp_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_GPL_VALUENAME1_064)) == 0 - && strcmp_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_GPL_VALUENAME2_064)) == 0 - && strcmp_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_GPL_VALUENAME3_064)) == 0) { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_RGB_VALUENAME1_064)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_RGB_VALUENAME2_064)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_RGB_VALUENAME3_064)); - // Reset values - UserVar[event->BaseVarIndex + 0] = 0.0; - UserVar[event->BaseVarIndex + 1] = 0.0; - UserVar[event->BaseVarIndex + 2] = 0.0; - } - if (P064_IS_GPL_SENSOR // Gesture/Proximity/ALS mode and default R/G/B values: Set new default names - && strcmp_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_RGB_VALUENAME1_064)) == 0 - && strcmp_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_RGB_VALUENAME2_064)) == 0 - && strcmp_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_RGB_VALUENAME3_064)) == 0) { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_GPL_VALUENAME1_064)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_GPL_VALUENAME2_064)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_GPL_VALUENAME3_064)); - // Reset values - UserVar[event->BaseVarIndex + 0] = 0.0; - UserVar[event->BaseVarIndex + 1] = 0.0; - UserVar[event->BaseVarIndex + 2] = 0.0; - } + if (P064_IS_RGB_SENSOR // R/G/B Colors mode and default Gesture/Proximity/ALS values: Set new default names + && (strcmp_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_GPL_VALUENAME1_064)) == 0) + && (strcmp_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_GPL_VALUENAME2_064)) == 0) + && (strcmp_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_GPL_VALUENAME3_064)) == 0)) { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_RGB_VALUENAME1_064)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_RGB_VALUENAME2_064)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_RGB_VALUENAME3_064)); - // Gain options, multiple gain optionsets in SparkFun_APDS9960.h have the same valueset, so only defined once here - String optionsGain[4]; - optionsGain[0] = F("1x"); - optionsGain[1] = F("2x"); - optionsGain[2] = F("4x (default)"); - optionsGain[3] = F("8x"); - int optionsGainValues[4] = {PGAIN_1X, PGAIN_2X, PGAIN_4X, PGAIN_8X}; // Also used for optionsALSGain - // Ambient Light Sensor Gain options, values are equal to PGAIN values, so again avoid duplication - String optionsALSGain[4]; - optionsALSGain[0] = F("1x"); - optionsALSGain[1] = F("4x (default)"); - optionsALSGain[2] = F("16x"); - optionsALSGain[3] = F("64x"); - // Led_Drive options, all Led_Drive optionsets in SparkFun_APDS9960.h have the same valueset, so only defined once here - String optionsLedDrive[4]; - optionsLedDrive[0] = F("100 mA (default)"); - optionsLedDrive[1] = F("50 mA"); - optionsLedDrive[2] = F("25 mA"); - optionsLedDrive[3] = F("12.5 mA"); - int optionsLedDriveValues[4] = {LED_DRIVE_100MA, LED_DRIVE_50MA, LED_DRIVE_25MA, LED_DRIVE_12_5MA}; - // Gesture Led-boost values - String optionsLedBoost[4]; - optionsLedBoost[0] = F("100 %"); - optionsLedBoost[1] = F("150 %"); - optionsLedBoost[2] = F("200 %"); - optionsLedBoost[3] = F("300 % (default)"); - int optionsLedBoostValues[4] = {LED_BOOST_100, LED_BOOST_150, LED_BOOST_200, LED_BOOST_300}; - - String lightSensorGainLabel; - String lightSensorDriveLabel; - - if (P064_IS_GPL_SENSOR) { // Gesture/Proximity/ALS mode - addFormSubHeader(F("Gesture parameters")); - - addFormSelector(F("Gesture Gain"), F("p064_ggain"), 4, optionsGain, optionsGainValues, P064_GGAIN); - - addFormSelector(F("Gesture LED Drive"), F("p064_gldrive"), 4, optionsLedDrive, optionsLedDriveValues, P064_GLDRIVE); - - addFormSelector(F("Gesture LED Boost"), F("p064_lboost"), 4, optionsLedBoost, optionsLedBoostValues, P064_LED_BOOST); - - addFormSubHeader(F("Proximity & Ambient Light Sensor parameters")); - - addFormSelector(F("Proximity Gain"), F("p064_pgain"), 4, optionsGain, optionsGainValues, P064_PGAIN); - - lightSensorGainLabel = F("Ambient Light Sensor Gain"); - lightSensorDriveLabel = F("Proximity & ALS LED Drive"); - } else { - addFormSubHeader(F("R/G/B Colors parameters")); - - lightSensorGainLabel = F("Light Sensor Gain"); - lightSensorDriveLabel = F("Light Sensor LED Drive"); - } - addFormSelector(lightSensorGainLabel, F("p064_again"), 4, optionsALSGain, optionsGainValues, P064_AGAIN); - - addFormSelector(lightSensorDriveLabel, F("p064_ldrive"), 4, optionsLedDrive, optionsLedDriveValues, P064_LDRIVE); - - success = true; - break; + // Reset values + UserVar[event->BaseVarIndex + 0] = 0.0; + UserVar[event->BaseVarIndex + 1] = 0.0; + UserVar[event->BaseVarIndex + 2] = 0.0; } + if (P064_IS_GPL_SENSOR // Gesture/Proximity/ALS mode and default R/G/B values: Set new default names + && (strcmp_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_RGB_VALUENAME1_064)) == 0) + && (strcmp_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_RGB_VALUENAME2_064)) == 0) + && (strcmp_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_RGB_VALUENAME3_064)) == 0)) { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_GPL_VALUENAME1_064)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_GPL_VALUENAME2_064)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_GPL_VALUENAME3_064)); + + // Reset values + UserVar[event->BaseVarIndex + 0] = 0.0; + UserVar[event->BaseVarIndex + 1] = 0.0; + UserVar[event->BaseVarIndex + 2] = 0.0; + } + + // Gain options, multiple gain optionsets in SparkFun_APDS9960.h have the same valueset, so only defined once here + String optionsGain[4]; + optionsGain[0] = F("1x"); + optionsGain[1] = F("2x"); + optionsGain[2] = F("4x (default)"); + optionsGain[3] = F("8x"); + int optionsGainValues[4] = { PGAIN_1X, PGAIN_2X, PGAIN_4X, PGAIN_8X }; // Also used for optionsALSGain + // Ambient Light Sensor Gain options, values are equal to PGAIN values, so again avoid duplication + String optionsALSGain[4]; + optionsALSGain[0] = F("1x"); + optionsALSGain[1] = F("4x (default)"); + optionsALSGain[2] = F("16x"); + optionsALSGain[3] = F("64x"); + + // Led_Drive options, all Led_Drive optionsets in SparkFun_APDS9960.h have the same valueset, so only defined once here + String optionsLedDrive[4]; + optionsLedDrive[0] = F("100 mA (default)"); + optionsLedDrive[1] = F("50 mA"); + optionsLedDrive[2] = F("25 mA"); + optionsLedDrive[3] = F("12.5 mA"); + int optionsLedDriveValues[4] = { LED_DRIVE_100MA, LED_DRIVE_50MA, LED_DRIVE_25MA, LED_DRIVE_12_5MA }; + + // Gesture Led-boost values + String optionsLedBoost[4]; + optionsLedBoost[0] = F("100 %"); + optionsLedBoost[1] = F("150 %"); + optionsLedBoost[2] = F("200 %"); + optionsLedBoost[3] = F("300 % (default)"); + int optionsLedBoostValues[4] = { LED_BOOST_100, LED_BOOST_150, LED_BOOST_200, LED_BOOST_300 }; + + String lightSensorGainLabel; + String lightSensorDriveLabel; + + if (P064_IS_GPL_SENSOR) { // Gesture/Proximity/ALS mode + addFormSubHeader(F("Gesture parameters")); + + addFormSelector(F("Gesture Gain"), F("p064_ggain"), 4, optionsGain, optionsGainValues, P064_GGAIN); + + addFormSelector(F("Gesture LED Drive"), F("p064_gldrive"), 4, optionsLedDrive, optionsLedDriveValues, P064_GLDRIVE); + + addFormSelector(F("Gesture LED Boost"), F("p064_lboost"), 4, optionsLedBoost, optionsLedBoostValues, P064_LED_BOOST); + + addFormSubHeader(F("Proximity & Ambient Light Sensor parameters")); + + addFormSelector(F("Proximity Gain"), F("p064_pgain"), 4, optionsGain, optionsGainValues, P064_PGAIN); + + lightSensorGainLabel = F("Ambient Light Sensor Gain"); + lightSensorDriveLabel = F("Proximity & ALS LED Drive"); + } else { + addFormSubHeader(F("R/G/B Colors parameters")); + + lightSensorGainLabel = F("Light Sensor Gain"); + lightSensorDriveLabel = F("Light Sensor LED Drive"); + } + addFormSelector(lightSensorGainLabel, F("p064_again"), 4, optionsALSGain, optionsGainValues, P064_AGAIN); + + addFormSelector(lightSensorDriveLabel, F("p064_ldrive"), 4, optionsLedDrive, optionsLedDriveValues, P064_LDRIVE); + + success = true; + break; + } + case PLUGIN_WEBFORM_SAVE: - { - // P064_ADDR = getFormItemInt(F("i2c_addr")); + { + // P064_ADDR = getFormItemInt(F("i2c_addr")); - P064_MODE = getFormItemInt(F("p064_mode")); - if (P064_IS_GPL_SENSOR) { - P064_GGAIN = getFormItemInt(F("p064_ggain")); - P064_GLDRIVE = getFormItemInt(F("p064_gldrive")); - P064_LED_BOOST = getFormItemInt(F("p064_lboost")); - P064_PGAIN = getFormItemInt(F("p064_pgain")); - } - P064_AGAIN = getFormItemInt(F("p064_again")); - P064_LDRIVE = getFormItemInt(F("p064_ldrive")); + P064_MODE = getFormItemInt(F("p064_mode")); - success = true; - break; + if (P064_IS_GPL_SENSOR) { + P064_GGAIN = getFormItemInt(F("p064_ggain")); + P064_GLDRIVE = getFormItemInt(F("p064_gldrive")); + P064_LED_BOOST = getFormItemInt(F("p064_lboost")); + P064_PGAIN = getFormItemInt(F("p064_pgain")); } + P064_AGAIN = getFormItemInt(F("p064_again")); + P064_LDRIVE = getFormItemInt(F("p064_ldrive")); + + success = true; + break; + } case PLUGIN_INIT: + { + if (PLUGIN_064_pds) { + delete PLUGIN_064_pds; + } + PLUGIN_064_pds = new SparkFun_APDS9960(); + + String log = F("APDS : "); + + if (PLUGIN_064_pds->init(P064_GGAIN, P064_GLDRIVE, P064_PGAIN, P064_AGAIN, P064_LDRIVE)) { - if (PLUGIN_064_pds) - delete PLUGIN_064_pds; - PLUGIN_064_pds = new SparkFun_APDS9960(); + log += F("Init"); - String log = F("APDS : "); - if (PLUGIN_064_pds->init(P064_GGAIN, P064_GLDRIVE, P064_PGAIN, P064_AGAIN, P064_LDRIVE) ) - { - log += F("Init"); + PLUGIN_064_pds->enablePower(); - PLUGIN_064_pds->enablePower(); + if (!PLUGIN_064_pds->enableLightSensor(false)) { + log += F(" - Error during light sensor init!"); + } - if (! PLUGIN_064_pds->enableLightSensor(false)) - log += F(" - Error during light sensor init!"); - if (P064_IS_GPL_SENSOR) { // Gesture/Proximity/ALS mode - if (! PLUGIN_064_pds->enableProximitySensor(false)) - log += F(" - Error during proximity sensor init!"); + if (P064_IS_GPL_SENSOR) { // Gesture/Proximity/ALS mode + if (!PLUGIN_064_pds->enableProximitySensor(false)) { + log += F(" - Error during proximity sensor init!"); + } - if (! PLUGIN_064_pds->enableGestureSensor(false, P064_LED_BOOST)) - log += F(" - Error during gesture sensor init!"); + if (!PLUGIN_064_pds->enableGestureSensor(false, P064_LED_BOOST)) { + log += F(" - Error during gesture sensor init!"); } } - else - { - log += F("Error during APDS-9960 init!"); - } - - addLog(LOG_LEVEL_INFO, log); - success = true; - break; } + else + { + log += F("Error during APDS-9960 init!"); + } + + addLog(LOG_LEVEL_INFO, log); + success = true; + break; + } case PLUGIN_FIFTY_PER_SECOND: - { - if (!PLUGIN_064_pds) - break; - - if (P064_MODE != PLUGIN_MODE_GPL_064 || !PLUGIN_064_pds->isGestureAvailable() ) - break; - - int gesture = PLUGIN_064_pds->readGesture(); - - //int gesture = PLUGIN_064_pds->readGestureNonBlocking(); - - //if (gesture == -1) serialPrint("."); - //if (gesture == -2) serialPrint(":"); - //if (gesture == -3) serialPrint("|"); - - //if ( 0 && PLUGIN_064_pds->isGestureAvailable() ) - if (gesture >= 0) - { - String log = F("APDS : Gesture="); - - switch ( gesture ) - { - case DIR_UP: log += F("UP"); break; - case DIR_DOWN: log += F("DOWN"); break; - case DIR_LEFT: log += F("LEFT"); break; - case DIR_RIGHT: log += F("RIGHT"); break; - case DIR_NEAR: log += F("NEAR"); break; - case DIR_FAR: log += F("FAR"); break; - default: log += F("NONE"); break; - } - log += " ("; - log += gesture; - log += ')'; - - UserVar[event->BaseVarIndex] = static_cast(gesture); - event->sensorType = SENSOR_TYPE_SWITCH; - - sendData(event); - - addLog(LOG_LEVEL_INFO, log); - } - - success = true; + { + if (!PLUGIN_064_pds) { break; } + if ((P064_MODE != PLUGIN_MODE_GPL_064) || !PLUGIN_064_pds->isGestureAvailable()) { + break; + } + + int gesture = PLUGIN_064_pds->readGesture(); + + // int gesture = PLUGIN_064_pds->readGestureNonBlocking(); + + // if (gesture == -1) serialPrint("."); + // if (gesture == -2) serialPrint(":"); + // if (gesture == -3) serialPrint("|"); + + // if ( 0 && PLUGIN_064_pds->isGestureAvailable() ) + if (gesture >= 0) + { + String log = F("APDS : Gesture="); + + switch (gesture) + { + case DIR_UP: log += F("UP"); break; + case DIR_DOWN: log += F("DOWN"); break; + case DIR_LEFT: log += F("LEFT"); break; + case DIR_RIGHT: log += F("RIGHT"); break; + case DIR_NEAR: log += F("NEAR"); break; + case DIR_FAR: log += F("FAR"); break; + default: log += F("NONE"); break; + } + log += " ("; + log += gesture; + log += ')'; + + UserVar[event->BaseVarIndex] = static_cast(gesture); + event->sensorType = SENSOR_TYPE_SWITCH; + + sendData(event); + + addLog(LOG_LEVEL_INFO, log); + } + + success = true; + break; + } + case PLUGIN_READ: - { - if (!PLUGIN_064_pds) - break; - - // Gesture - work is done in PLUGIN_FIFTY_PER_SECOND - - if (1) - { - if (P064_IS_GPL_SENSOR) { // Gesture/Proximity/ALS mode - uint8_t proximity_data = 0; - PLUGIN_064_pds->readProximity(proximity_data); - UserVar[event->BaseVarIndex + 1] = static_cast(proximity_data); - - uint16_t ambient_light = 0; - PLUGIN_064_pds->readAmbientLight(ambient_light); - UserVar[event->BaseVarIndex + 2] = static_cast(ambient_light); - - } else { - uint16_t red_light = 0; - uint16_t green_light = 0; - uint16_t blue_light = 0; - PLUGIN_064_pds->readRedLight(red_light); - PLUGIN_064_pds->readGreenLight(green_light); - PLUGIN_064_pds->readBlueLight(blue_light); - UserVar[event->BaseVarIndex + 0] = static_cast(red_light); - UserVar[event->BaseVarIndex + 1] = static_cast(green_light); - UserVar[event->BaseVarIndex + 2] = static_cast(blue_light); - } - } - - success = true; + { + if (!PLUGIN_064_pds) { break; } + // Gesture - work is done in PLUGIN_FIFTY_PER_SECOND + + if (1) + { + if (P064_IS_GPL_SENSOR) { // Gesture/Proximity/ALS mode + uint8_t proximity_data = 0; + PLUGIN_064_pds->readProximity(proximity_data); + UserVar[event->BaseVarIndex + 1] = static_cast(proximity_data); + + uint16_t ambient_light = 0; + PLUGIN_064_pds->readAmbientLight(ambient_light); + UserVar[event->BaseVarIndex + 2] = static_cast(ambient_light); + } else { + uint16_t red_light = 0; + uint16_t green_light = 0; + uint16_t blue_light = 0; + PLUGIN_064_pds->readRedLight(red_light); + PLUGIN_064_pds->readGreenLight(green_light); + PLUGIN_064_pds->readBlueLight(blue_light); + UserVar[event->BaseVarIndex + 0] = static_cast(red_light); + UserVar[event->BaseVarIndex + 1] = static_cast(green_light); + UserVar[event->BaseVarIndex + 2] = static_cast(blue_light); + } + } + + success = true; + break; + } } return success; } diff --git a/src/_P066_VEML6040.ino b/src/_P066_VEML6040.ino index 26b8d3df9..7ec5e4b44 100644 --- a/src/_P066_VEML6040.ino +++ b/src/_P066_VEML6040.ino @@ -1,7 +1,8 @@ #ifdef USES_P066 -//####################################################################################################### -//#################################### Plugin 066: VEML6040 RGBW ############################## -//####################################################################################################### + +// ####################################################################################################### +// #################################### Plugin 066: VEML6040 RGBW ############################## +// ####################################################################################################### // ESPEasy Plugin to scan color values (RGBW) with chip VEML6040 // written by Jochen Krapf (jk@nerd2nerd.org) @@ -10,7 +11,6 @@ // Application Note: www.vishay.com/doc?84331 - #define PLUGIN_066 #define PLUGIN_ID_066 66 #define PLUGIN_NAME_066 "Color - VEML6040 [TESTING]" @@ -22,7 +22,7 @@ #define VEML6040_ADDR 0x10 // #include <*.h> // no include needed -#include // no include needed +#include // no include needed #include "_Plugin_Helper.h" boolean Plugin_066(byte function, struct EventStruct *event, String& string) @@ -32,193 +32,194 @@ boolean Plugin_066(byte function, struct EventStruct *event, String& string) switch (function) { case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_066; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].Ports = 0; - Device[deviceCount].VType = SENSOR_TYPE_QUAD; - 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].TimerOptional = false; - Device[deviceCount].GlobalSyncOption = true; - break; - } + { + Device[++deviceCount].Number = PLUGIN_ID_066; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].Ports = 0; + Device[deviceCount].VType = SENSOR_TYPE_QUAD; + 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].TimerOptional = false; + Device[deviceCount].GlobalSyncOption = true; + break; + } case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_066); - break; - } + { + string = F(PLUGIN_NAME_066); + break; + } case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_066)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_066)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_VALUENAME3_066)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[3], PSTR(PLUGIN_VALUENAME4_066)); - break; - } + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_066)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_066)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_VALUENAME3_066)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[3], PSTR(PLUGIN_VALUENAME4_066)); + break; + } case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: - { - int optionValues[1] = { VEML6040_ADDR }; - addFormSelectorI2C(F("i2c_addr"), 1, optionValues, VEML6040_ADDR); //Only for display I2C address - break; - } + { + int optionValues[1] = { VEML6040_ADDR }; + addFormSelectorI2C(F("i2c_addr"), 1, optionValues, VEML6040_ADDR); // Only for display I2C address + break; + } case PLUGIN_WEBFORM_LOAD: + { { - { - String optionsMode[6] = { F("40ms (16496)"), F("80ms (8248)"), F("160ms (4124)"), F("320ms (2062)"), F("640ms (1031)"), F("1280ms (515)") }; - addFormSelector(F("Integration Time (Max Lux)"), F("itime"), 6, optionsMode, NULL, PCONFIG(1)); - } - - { - String optionsVarMap[6] = { - F("R, G, B, W"), - F("r, g, b, W - relative rgb [%]"), - F("r, g, b, W - relative rgb^Gamma [%]"), - F("R, G, B, Color Temperature [K]"), - F("R, G, B, Ambient Light [Lux]"), - F("Color Temperature [K], Ambient Light [Lux], Y, W") }; - addFormSelector(F("Value Mapping"), F("map"), 6, optionsVarMap, NULL, PCONFIG(2)); - } - - success = true; - break; + String optionsMode[6] = { F("40ms (16496)"), F("80ms (8248)"), F("160ms (4124)"), F("320ms (2062)"), F("640ms (1031)"), F( + "1280ms (515)") }; + addFormSelector(F("Integration Time (Max Lux)"), F("itime"), 6, optionsMode, NULL, PCONFIG(1)); } + { + String optionsVarMap[6] = { + F("R, G, B, W"), + F("r, g, b, W - relative rgb [%]"), + F("r, g, b, W - relative rgb^Gamma [%]"), + F("R, G, B, Color Temperature [K]"), + F("R, G, B, Ambient Light [Lux]"), + F("Color Temperature [K], Ambient Light [Lux], Y, W") }; + addFormSelector(F("Value Mapping"), F("map"), 6, optionsVarMap, NULL, PCONFIG(2)); + } + + success = true; + break; + } + case PLUGIN_WEBFORM_SAVE: - { - //PCONFIG(0) = getFormItemInt(F("i2c_addr")); - PCONFIG(1) = getFormItemInt(F("itime")); - PCONFIG(2) = getFormItemInt(F("map")); + { + // PCONFIG(0) = getFormItemInt(F("i2c_addr")); + PCONFIG(1) = getFormItemInt(F("itime")); + PCONFIG(2) = getFormItemInt(F("map")); - success = true; - break; - } + success = true; + break; + } case PLUGIN_INIT: - { - VEML6040_Init(PCONFIG(1)); + { + VEML6040_Init(PCONFIG(1)); - success = true; - break; - } + success = true; + break; + } case PLUGIN_READ: + { + float R, G, B, W; + + R = VEML6040_GetValue(0x08); + G = VEML6040_GetValue(0x09); + B = VEML6040_GetValue(0x0A); + W = VEML6040_GetValue(0x0B); + + switch (PCONFIG(2)) { - float R, G, B, W; - - R = VEML6040_GetValue(0x08); - G = VEML6040_GetValue(0x09); - B = VEML6040_GetValue(0x0A); - W = VEML6040_GetValue(0x0B); - - switch (PCONFIG(2)) + default: + case 0: { - default: - case 0: - { - UserVar[event->BaseVarIndex + 0] = R; - UserVar[event->BaseVarIndex + 1] = G; - UserVar[event->BaseVarIndex + 2] = B; - UserVar[event->BaseVarIndex + 3] = W; - break; - } - case 1: - { - UserVar[event->BaseVarIndex + 0] = Plugin_066_CalcRelW(R, W) * 100.0; - UserVar[event->BaseVarIndex + 1] = Plugin_066_CalcRelW(G, W) * 100.0; - UserVar[event->BaseVarIndex + 2] = Plugin_066_CalcRelW(B, W) * 100.0; - UserVar[event->BaseVarIndex + 3] = W; - break; - } - case 2: - { - UserVar[event->BaseVarIndex + 0] = pow(Plugin_066_CalcRelW(R, W), 0.4545) * 100.0; - UserVar[event->BaseVarIndex + 1] = pow(Plugin_066_CalcRelW(G, W), 0.4545) * 100.0; - UserVar[event->BaseVarIndex + 2] = pow(Plugin_066_CalcRelW(B, W), 0.4545) * 100.0; - UserVar[event->BaseVarIndex + 3] = W; - break; - } - case 3: - { - UserVar[event->BaseVarIndex + 0] = R; - UserVar[event->BaseVarIndex + 1] = G; - UserVar[event->BaseVarIndex + 2] = B; - UserVar[event->BaseVarIndex + 3] = Plugin_066_CalcCCT(R, G, B); - break; - } - case 4: - { - UserVar[event->BaseVarIndex + 0] = R; - UserVar[event->BaseVarIndex + 1] = G; - UserVar[event->BaseVarIndex + 2] = B; - UserVar[event->BaseVarIndex + 3] = Plugin_066_CalcAmbientLight(G, PCONFIG(1)); - break; - } - case 5: - { - UserVar[event->BaseVarIndex + 0] = Plugin_066_CalcCCT(R, G, B); - UserVar[event->BaseVarIndex + 1] = Plugin_066_CalcAmbientLight(G, PCONFIG(1)); - UserVar[event->BaseVarIndex + 2] = (R + G + B) / 3.0; //0.299*R + 0.587*G + 0.114*B; - UserVar[event->BaseVarIndex + 3] = W; - break; - } + UserVar[event->BaseVarIndex + 0] = R; + UserVar[event->BaseVarIndex + 1] = G; + UserVar[event->BaseVarIndex + 2] = B; + UserVar[event->BaseVarIndex + 3] = W; + break; + } + case 1: + { + UserVar[event->BaseVarIndex + 0] = Plugin_066_CalcRelW(R, W) * 100.0; + UserVar[event->BaseVarIndex + 1] = Plugin_066_CalcRelW(G, W) * 100.0; + UserVar[event->BaseVarIndex + 2] = Plugin_066_CalcRelW(B, W) * 100.0; + UserVar[event->BaseVarIndex + 3] = W; + break; + } + case 2: + { + UserVar[event->BaseVarIndex + 0] = pow(Plugin_066_CalcRelW(R, W), 0.4545) * 100.0; + UserVar[event->BaseVarIndex + 1] = pow(Plugin_066_CalcRelW(G, W), 0.4545) * 100.0; + UserVar[event->BaseVarIndex + 2] = pow(Plugin_066_CalcRelW(B, W), 0.4545) * 100.0; + UserVar[event->BaseVarIndex + 3] = W; + break; + } + case 3: + { + UserVar[event->BaseVarIndex + 0] = R; + UserVar[event->BaseVarIndex + 1] = G; + UserVar[event->BaseVarIndex + 2] = B; + UserVar[event->BaseVarIndex + 3] = Plugin_066_CalcCCT(R, G, B); + break; + } + case 4: + { + UserVar[event->BaseVarIndex + 0] = R; + UserVar[event->BaseVarIndex + 1] = G; + UserVar[event->BaseVarIndex + 2] = B; + UserVar[event->BaseVarIndex + 3] = Plugin_066_CalcAmbientLight(G, PCONFIG(1)); + break; + } + case 5: + { + UserVar[event->BaseVarIndex + 0] = Plugin_066_CalcCCT(R, G, B); + UserVar[event->BaseVarIndex + 1] = Plugin_066_CalcAmbientLight(G, PCONFIG(1)); + UserVar[event->BaseVarIndex + 2] = (R + G + B) / 3.0; // 0.299*R + 0.587*G + 0.114*B; + UserVar[event->BaseVarIndex + 3] = W; + break; } - success = true; - break; } - + success = true; + break; + } } return success; } - // VEML6040 ///////////////////////////////////////////////////////////// void VEML6040_setControlReg(byte data) { - Wire.beginTransmission(VEML6040_ADDR); - Wire.write(0); //command 0=control register - Wire.write(data); //lsb - Wire.write(0); //msb - Wire.endTransmission(); + Wire.beginTransmission(VEML6040_ADDR); + Wire.write(0); // command 0=control register + Wire.write(data); // lsb + Wire.write(0); // msb + Wire.endTransmission(); } float VEML6040_GetValue(byte reg) { - Wire.beginTransmission(VEML6040_ADDR); - Wire.write(reg); - Wire.endTransmission(false); - Wire.requestFrom((uint8_t)VEML6040_ADDR, (uint8_t)0x2); - if (Wire.available() == 2) - { + Wire.beginTransmission(VEML6040_ADDR); + Wire.write(reg); + Wire.endTransmission(false); + Wire.requestFrom((uint8_t)VEML6040_ADDR, (uint8_t)0x2); + + if (Wire.available() == 2) + { uint16_t lsb = Wire.read(); uint16_t msb = Wire.read(); - return (float)((msb << 8) | lsb); - } - return -1.0; + return (float)((msb << 8) | lsb); + } + return -1.0; } void VEML6040_Init(byte it) { - VEML6040_setControlReg(it << 4); // IT=it, TRIG=0, AF=0, SD=0 + VEML6040_setControlReg(it << 4); // IT=it, TRIG=0, AF=0, SD=0 } - float Plugin_066_CalcCCT(float R, float G, float B) { - if (G == 0) + if (G == 0) { return 0; + } float CCTi = (R - B) / G + 0.5; - float CCT = 4278.6 * pow(CCTi, -1.2455); + float CCT = 4278.6 * pow(CCTi, -1.2455); + return CCT; } @@ -231,8 +232,9 @@ float Plugin_066_CalcAmbientLight(float G, byte it) float Plugin_066_CalcRelW(float X, float W) { - if (W == 0) + if (W == 0) { return 0; + } return X / W; } diff --git a/src/_P068_SHT3x.ino b/src/_P068_SHT3x.ino index f9a0ffc0b..c2c714ce0 100644 --- a/src/_P068_SHT3x.ino +++ b/src/_P068_SHT3x.ino @@ -1,14 +1,15 @@ #ifdef USES_P068 -//####################################################################################################### -//################ Plugin 68: SHT30/SHT31/SHT35 Temperature and Humidity Sensor (I2C) ################### -//####################################################################################################### -//######################## Library source code for Arduino by WeMos, 2016 ############################### -//####################################################################################################### -//###################### Plugin for ESP Easy by B.E.I.C. ELECTRONICS, 2017 ############################## -//############################### http://www.beicelectronics.com ######################################## -//####################################################################################################### -//########################## Adapted to ESPEasy 2.0 by Jochen Krapf ##################################### -//####################################################################################################### + +// ####################################################################################################### +// ################ Plugin 68: SHT30/SHT31/SHT35 Temperature and Humidity Sensor (I2C) ################### +// ####################################################################################################### +// ######################## Library source code for Arduino by WeMos, 2016 ############################### +// ####################################################################################################### +// ###################### Plugin for ESP Easy by B.E.I.C. ELECTRONICS, 2017 ############################## +// ############################### http://www.beicelectronics.com ######################################## +// ####################################################################################################### +// ########################## Adapted to ESPEasy 2.0 by Jochen Krapf ##################################### +// ####################################################################################################### #define PLUGIN_068 @@ -17,201 +18,208 @@ #define PLUGIN_VALUENAME1_068 "Temperature" #define PLUGIN_VALUENAME2_068 "Humidity" -//============================================== +// ============================================== // SHT3X LIBRARY - SHT3X.h // ============================================= -# ifndef SHT3X_H +#ifndef SHT3X_H # define SHT3X_H -#include "_Plugin_Helper.h" +# include "_Plugin_Helper.h" -class SHT3X: public PluginTaskData_base -{ +class SHT3X : public PluginTaskData_base { public: - SHT3X(uint8_t addr); - void readFromSensor(void); - bool CRC8(uint8_t MSB, uint8_t LSB, uint8_t CRC); + SHT3X(uint8_t addr); - float tmp=0; - float hum=0; + void readFromSensor(void); + bool CRC8(uint8_t MSB, + uint8_t LSB, + uint8_t CRC); + + float tmp = 0; + float hum = 0; private: - uint8_t _i2c_device_address; + + uint8_t _i2c_device_address; }; -#endif +#endif // ifndef SHT3X_H -//============================================== +// ============================================== // SHT3X LIBRARY - SHT3X.cpp // ============================================= SHT3X::SHT3X(uint8_t addr) { - _i2c_device_address = addr; - //Wire.begin(); called in ESPEasy framework + _i2c_device_address = addr; - // Set to periodic mode - I2C_write8_reg( - _i2c_device_address, - 0x20, // periodic 0.5mps - 0x32 // repeatability high - ); + // Wire.begin(); called in ESPEasy framework + + // Set to periodic mode + I2C_write8_reg( + _i2c_device_address, + 0x20, // periodic 0.5mps + 0x32 // repeatability high + ); } void SHT3X::readFromSensor() { - uint16_t data[6]; + uint16_t data[6]; I2C_write8_reg( - _i2c_device_address, - 0xE0, // fetch data command - 0x00 - ); + _i2c_device_address, + 0xE0, // fetch data command + 0x00 + ); - // FIXME TD-er: Currently the I2Cdev::readBytes does not support writing 2 bytes before reading. - Wire.requestFrom(_i2c_device_address, (uint8_t)6); - if (Wire.available() == 6) - { - data[0] = Wire.read(); - data[1] = Wire.read(); - data[2] = Wire.read(); - data[3] = Wire.read(); - data[4] = Wire.read(); - data[5] = Wire.read(); + // FIXME TD-er: Currently the I2Cdev::readBytes does not support writing 2 bytes before reading. + Wire.requestFrom(_i2c_device_address, (uint8_t)6); - //TODO: check CRC (data[2] and data[5]) - if (CRC8(data[0], data[1], data[2]) && - CRC8(data[3], data[4], data[5])) - { - tmp = ((((data[0] << 8) | data[1]) * 175.0) / 65535.0) - 45.0; - hum = ((((data[3] << 8) | data[4]) * 100.0) / 65535.0); - } - } - else - { - tmp = NAN; - hum = NAN; + if (Wire.available() == 6) + { + data[0] = Wire.read(); + data[1] = Wire.read(); + data[2] = Wire.read(); + data[3] = Wire.read(); + data[4] = Wire.read(); + data[5] = Wire.read(); - // Set to periodic mode - Wire.beginTransmission(_i2c_device_address); - Wire.write(0x20); // periodic 0.5mps - Wire.write(0x32); // repeatability high - Wire.endTransmission(); - } + // TODO: check CRC (data[2] and data[5]) + if (CRC8(data[0], data[1], data[2]) && + CRC8(data[3], data[4], data[5])) + { + tmp = ((((data[0] << 8) | data[1]) * 175.0) / 65535.0) - 45.0; + hum = ((((data[3] << 8) | data[4]) * 100.0) / 65535.0); + } + } + else + { + tmp = NAN; + hum = NAN; + + // Set to periodic mode + Wire.beginTransmission(_i2c_device_address); + Wire.write(0x20); // periodic 0.5mps + Wire.write(0x32); // repeatability high + Wire.endTransmission(); + } } - // FIXME TD-er: Try to make some collection of used CRC algorithms // See http://reveng.sourceforge.net/crc-catalogue/1-15.htm#crc.cat.crc-8-dvb-s2 bool SHT3X::CRC8(uint8_t MSB, uint8_t LSB, uint8_t CRC) { - /* - * Name : CRC-8 - * Polynomial : 0x31 (x8 + x5 + x4 + 1) - * Initialization : 0xFF - * Reflect input : False - * Reflect output : False - * Final : XOR 0x00 - * Example : CRC8( 0xBE, 0xEF, 0x92) should be true - */ - uint8_t crc = 0xFF; - for (uint8_t bytenr = 0; bytenr < 2; ++bytenr) { - crc ^= (bytenr == 0) ? MSB : LSB; - for (uint8_t i = 0; i < 8; ++i) { - crc = crc & 0x80 ? (crc << 1) ^ 0x31 : crc << 1; - } - } - return (crc == CRC); + /* + * Name : CRC-8 + * Polynomial : 0x31 (x8 + x5 + x4 + 1) + * Initialization : 0xFF + * Reflect input : False + * Reflect output : False + * Final : XOR 0x00 + * Example : CRC8( 0xBE, 0xEF, 0x92) should be true + */ + uint8_t crc = 0xFF; + + for (uint8_t bytenr = 0; bytenr < 2; ++bytenr) { + crc ^= (bytenr == 0) ? MSB : LSB; + + for (uint8_t i = 0; i < 8; ++i) { + crc = crc & 0x80 ? (crc << 1) ^ 0x31 : crc << 1; + } + } + return crc == CRC; } -//============================================== +// ============================================== // PLUGIN // ============================================= boolean Plugin_068(byte function, struct EventStruct *event, String& string) { - boolean success = false; + boolean success = false; - switch (function) - { - case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_068; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].VType = SENSOR_TYPE_TEMP_HUM; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = true; - Device[deviceCount].ValueCount = 2; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].GlobalSyncOption = true; - break; - } + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + Device[++deviceCount].Number = PLUGIN_ID_068; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = SENSOR_TYPE_TEMP_HUM; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 2; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].GlobalSyncOption = true; + break; + } - case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_068); - break; - } + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_068); + break; + } - case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_068)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_068)); - break; - } + case PLUGIN_GET_DEVICEVALUENAMES: + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_068)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_068)); + break; + } - case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: - { - int optionValues[2] = { 0x44, 0x45 }; - addFormSelectorI2C(F("i2c_addr"), 2, optionValues, PCONFIG(0)); - break; - } + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: + { + int optionValues[2] = { 0x44, 0x45 }; + addFormSelectorI2C(F("i2c_addr"), 2, optionValues, PCONFIG(0)); + break; + } - case PLUGIN_WEBFORM_LOAD: - { - success = true; - break; - } + case PLUGIN_WEBFORM_LOAD: + { + success = true; + break; + } - case PLUGIN_WEBFORM_SAVE: - { - PCONFIG(0) = getFormItemInt(F("i2c_addr")); + case PLUGIN_WEBFORM_SAVE: + { + PCONFIG(0) = getFormItemInt(F("i2c_addr")); - success = true; - break; - } + success = true; + break; + } - case PLUGIN_INIT: - { - initPluginTaskData(event->TaskIndex, new SHT3X(PCONFIG(0))); - success = true; - break; - } + case PLUGIN_INIT: + { + initPluginTaskData(event->TaskIndex, new SHT3X(PCONFIG(0))); + success = true; + break; + } - case PLUGIN_READ: - { - SHT3X* sht3x = static_cast(getPluginTaskData(event->TaskIndex)); - if (nullptr == sht3x) { - addLog(LOG_LEVEL_ERROR, F("SHT3x: not initialised!")); - return success; - } + case PLUGIN_READ: + { + SHT3X *sht3x = static_cast(getPluginTaskData(event->TaskIndex)); - sht3x->readFromSensor(); - UserVar[event->BaseVarIndex + 0] = sht3x->tmp; - UserVar[event->BaseVarIndex + 1] = sht3x->hum; - String log = F("SHT3x: Temperature: "); - log += UserVar[event->BaseVarIndex + 0]; - addLog(LOG_LEVEL_INFO, log); - log = F("SHT3x: Humidity: "); - log += UserVar[event->BaseVarIndex + 1]; - addLog(LOG_LEVEL_INFO, log); - success = true; - break; - } - } - return success; + if (nullptr == sht3x) { + addLog(LOG_LEVEL_ERROR, F("SHT3x: not initialised!")); + return success; + } + + sht3x->readFromSensor(); + UserVar[event->BaseVarIndex + 0] = sht3x->tmp; + UserVar[event->BaseVarIndex + 1] = sht3x->hum; + String log = F("SHT3x: Temperature: "); + log += UserVar[event->BaseVarIndex + 0]; + addLog(LOG_LEVEL_INFO, log); + log = F("SHT3x: Humidity: "); + log += UserVar[event->BaseVarIndex + 1]; + addLog(LOG_LEVEL_INFO, log); + success = true; + break; + } + } + return success; } #endif // USES_P068 diff --git a/src/_P069_LM75A.ino b/src/_P069_LM75A.ino index 1f379195f..4785c9714 100644 --- a/src/_P069_LM75A.ino +++ b/src/_P069_LM75A.ino @@ -1,14 +1,15 @@ #ifdef USES_P069 -//####################################################################################################### -//########################### Plugin 69: LM75A Temperature Sensor (I2C) ################################# -//####################################################################################################### -//###################### Library source code for Arduino by QuentinCG, 2016 ############################# -//####################################################################################################### -//##################### Plugin for ESP Easy by B.E.I.C. ELECTRONICS, 2017 ############################### -//############################## http://www.beicelectronics.com ######################################### -//####################################################################################################### -//########################## Adapted to ESPEasy 2.0 by Jochen Krapf ##################################### -//####################################################################################################### + +// ####################################################################################################### +// ########################### Plugin 69: LM75A Temperature Sensor (I2C) ################################# +// ####################################################################################################### +// ###################### Library source code for Arduino by QuentinCG, 2016 ############################# +// ####################################################################################################### +// ##################### Plugin for ESP Easy by B.E.I.C. ELECTRONICS, 2017 ############################### +// ############################## http://www.beicelectronics.com ######################################### +// ####################################################################################################### +// ########################## Adapted to ESPEasy 2.0 by Jochen Krapf ##################################### +// ####################################################################################################### #define PLUGIN_069 @@ -18,23 +19,22 @@ #ifndef LM75A_h -#define LM75A_h +# define LM75A_h -#define INVALID_LM75A_TEMPERATURE 1000 +# define INVALID_LM75A_TEMPERATURE 1000 -#include "_Plugin_Helper.h" -namespace LM75AConstValues -{ - const int LM75A_BASE_ADDRESS = 0x48; - const float LM75A_DEGREES_RESOLUTION = 0.125; - const int LM75A_REG_ADDR_TEMP = 0; +# include "_Plugin_Helper.h" +namespace LM75AConstValues { +const int LM75A_BASE_ADDRESS = 0x48; +const float LM75A_DEGREES_RESOLUTION = 0.125; +const int LM75A_REG_ADDR_TEMP = 0; } using namespace LM75AConstValues; -class LM75A -{ +class LM75A { public: + LM75A(bool A0_value = false, bool A1_value = false, bool A2_value = false) { _i2c_device_address = LM75A_BASE_ADDRESS; @@ -51,13 +51,14 @@ public: _i2c_device_address += 4; } - //Wire.begin(); called in ESPEasy framework + // Wire.begin(); called in ESPEasy framework } LM75A(uint8_t addr) { _i2c_device_address = addr; - //Wire.begin(); called in ESPEasy framework + + // Wire.begin(); called in ESPEasy framework } void setAddress(uint8_t addr) @@ -67,12 +68,13 @@ public: float getTemperatureInDegrees() const { - float real_result = INVALID_LM75A_TEMPERATURE; - int16_t value = 0; + float real_result = INVALID_LM75A_TEMPERATURE; + int16_t value = 0; // Go to temperature data register Wire.beginTransmission(_i2c_device_address); Wire.write(LM75A_REG_ADDR_TEMP); + if (Wire.endTransmission()) { // Transmission error @@ -81,6 +83,7 @@ public: // Get content Wire.requestFrom(_i2c_device_address, (uint8_t)2); + if (Wire.available() == 2) { value = (Wire.read() << 8) | Wire.read(); @@ -95,9 +98,9 @@ public: value >>= 5; // Relocate negative bit (11th bit to 16th bit) - if (value & 0x0400) // negative? + if (value & 0x0400) // negative? { - value |= 0xFC00; // expand to 16 bit + value |= 0xFC00; // expand to 16 bit } // Real value can be calculated with sensor resolution @@ -107,13 +110,14 @@ public: } private: + uint8_t _i2c_device_address; }; -#endif +#endif // ifndef LM75A_h -LM75A* PLUGIN_069_LM75A = NULL; +LM75A *PLUGIN_069_LM75A = NULL; boolean Plugin_069(byte function, struct EventStruct *event, String& string) @@ -124,17 +128,17 @@ boolean Plugin_069(byte function, struct EventStruct *event, String& string) { case PLUGIN_DEVICE_ADD: { - Device[++deviceCount].Number = PLUGIN_ID_069; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].VType = SENSOR_TYPE_SINGLE; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; + Device[++deviceCount].Number = PLUGIN_ID_069; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = SENSOR_TYPE_SINGLE; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = true; - Device[deviceCount].ValueCount = 1; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].GlobalSyncOption = true; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 1; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].GlobalSyncOption = true; break; } @@ -173,8 +177,9 @@ boolean Plugin_069(byte function, struct EventStruct *event, String& string) case PLUGIN_INIT: { - if (PLUGIN_069_LM75A) + if (PLUGIN_069_LM75A) { delete PLUGIN_069_LM75A; + } PLUGIN_069_LM75A = new LM75A((uint8_t)PCONFIG(0)); success = true; @@ -183,8 +188,9 @@ boolean Plugin_069(byte function, struct EventStruct *event, String& string) case PLUGIN_READ: { - if (!PLUGIN_069_LM75A) + if (!PLUGIN_069_LM75A) { return success; + } PLUGIN_069_LM75A->setAddress((uint8_t)PCONFIG(0)); diff --git a/src/_P072_HDC1080.ino b/src/_P072_HDC1080.ino index fcddcf9e3..c6d5a71e1 100644 --- a/src/_P072_HDC1080.ino +++ b/src/_P072_HDC1080.ino @@ -1,7 +1,8 @@ #ifdef USES_P072 -//###################################################################################################### -//####################### Plugin 072: Temperature and Humidity sensor HDC1080 (I2C) #################### -//###################################################################################################### + +// ###################################################################################################### +// ####################### Plugin 072: Temperature and Humidity sensor HDC1080 (I2C) #################### +// ###################################################################################################### #define PLUGIN_072 @@ -23,79 +24,78 @@ boolean Plugin_072(byte function, struct EventStruct *event, String& string) switch (function) { case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_072; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].VType = SENSOR_TYPE_TEMP_HUM; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = true; - Device[deviceCount].ValueCount = 2; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].GlobalSyncOption = true; - break; - } + { + Device[++deviceCount].Number = PLUGIN_ID_072; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = SENSOR_TYPE_TEMP_HUM; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 2; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].GlobalSyncOption = true; + break; + } case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_072); - break; - } + { + string = F(PLUGIN_NAME_072); + break; + } case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_072)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_072)); - break; - } + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_072)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_072)); + break; + } case PLUGIN_READ: - { - byte hdc1080_msb, hdc1080_lsb; - uint16_t hdc1080_rawtemp, hdc1080_rawhum; - float hdc1080_temp, hdc1080_hum; + { + byte hdc1080_msb, hdc1080_lsb; + uint16_t hdc1080_rawtemp, hdc1080_rawhum; + float hdc1080_temp, hdc1080_hum; - Wire.beginTransmission(HDC1080_I2C_ADDRESS); // start transmission to device - Wire.write(0x02); // sends HDC1080_CONFIGURATION - Wire.write(0b00000000); // set resolution to 14bits both for T and H - Wire.write(0x00); // **reserved** - Wire.endTransmission(); // end transmission - delay(10); + Wire.beginTransmission(HDC1080_I2C_ADDRESS); // start transmission to device + Wire.write(0x02); // sends HDC1080_CONFIGURATION + Wire.write(0b00000000); // set resolution to 14bits both for T and H + Wire.write(0x00); // **reserved** + Wire.endTransmission(); // end transmission + delay(10); - Wire.beginTransmission(HDC1080_I2C_ADDRESS); // start transmission to device - Wire.write(0x00); // sends HDC1080_TEMPERATURE - Wire.endTransmission(); // end transmission - delay(9); - Wire.requestFrom(HDC1080_I2C_ADDRESS, 2); // read 2 bytes for temperature - hdc1080_msb = Wire.read(); - hdc1080_lsb = Wire.read(); - hdc1080_rawtemp = hdc1080_msb << 8 | hdc1080_lsb; - hdc1080_temp = (hdc1080_rawtemp / pow(2,16)) * 165 -40; + Wire.beginTransmission(HDC1080_I2C_ADDRESS); // start transmission to device + Wire.write(0x00); // sends HDC1080_TEMPERATURE + Wire.endTransmission(); // end transmission + delay(9); + Wire.requestFrom(HDC1080_I2C_ADDRESS, 2); // read 2 bytes for temperature + hdc1080_msb = Wire.read(); + hdc1080_lsb = Wire.read(); + hdc1080_rawtemp = hdc1080_msb << 8 | hdc1080_lsb; + hdc1080_temp = (hdc1080_rawtemp / pow(2, 16)) * 165 - 40; - Wire.beginTransmission(HDC1080_I2C_ADDRESS); // start transmission to device - Wire.write(0x01); // sends HDC1080_HUMIDITY - Wire.endTransmission(); // end transmission - delay(9); - Wire.requestFrom(HDC1080_I2C_ADDRESS, 2); // read 2 bytes for humidity - hdc1080_msb = Wire.read(); - hdc1080_lsb = Wire.read(); - hdc1080_rawhum = hdc1080_msb << 8 | hdc1080_lsb; - hdc1080_hum = (hdc1080_rawhum / pow(2,16)) * 100; + Wire.beginTransmission(HDC1080_I2C_ADDRESS); // start transmission to device + Wire.write(0x01); // sends HDC1080_HUMIDITY + Wire.endTransmission(); // end transmission + delay(9); + Wire.requestFrom(HDC1080_I2C_ADDRESS, 2); // read 2 bytes for humidity + hdc1080_msb = Wire.read(); + hdc1080_lsb = Wire.read(); + hdc1080_rawhum = hdc1080_msb << 8 | hdc1080_lsb; + hdc1080_hum = (hdc1080_rawhum / pow(2, 16)) * 100; - UserVar[event->BaseVarIndex] = hdc1080_temp; - UserVar[event->BaseVarIndex + 1] = hdc1080_hum; - String log = F("HDC1080: Temperature: "); - log += UserVar[event->BaseVarIndex]; - addLog(LOG_LEVEL_INFO, log); - log = F("HDC1080: Humidity: "); - log += UserVar[event->BaseVarIndex + 1]; - addLog(LOG_LEVEL_INFO, log); - success = true; - break; - - } + UserVar[event->BaseVarIndex] = hdc1080_temp; + UserVar[event->BaseVarIndex + 1] = hdc1080_hum; + String log = F("HDC1080: Temperature: "); + log += UserVar[event->BaseVarIndex]; + addLog(LOG_LEVEL_INFO, log); + log = F("HDC1080: Humidity: "); + log += UserVar[event->BaseVarIndex + 1]; + addLog(LOG_LEVEL_INFO, log); + success = true; + break; + } } return success; } diff --git a/src/_P074_TSL2591.ino b/src/_P074_TSL2591.ino index 43d973c8b..0fe01a1a4 100644 --- a/src/_P074_TSL2591.ino +++ b/src/_P074_TSL2591.ino @@ -1,8 +1,9 @@ #ifdef USES_P074 -//####################################################################################################### -//######################## Plugin 074 TSL2591 I2C Lux/IR Sensor -//######################################### -//####################################################################################################### + +// ####################################################################################################### +// ######################## Plugin 074 TSL2591 I2C Lux/IR Sensor +// ######################################### +// ####################################################################################################### // // by: https://github.com/krikk // this plugin is based on the adafruit library @@ -27,7 +28,6 @@ #include "_Plugin_Helper.h" struct P074_data_struct : public PluginTaskData_base { - P074_data_struct() { tsl = Adafruit_TSL2591(2591); // pass in a number for the sensor identifier // (for your use later) @@ -38,13 +38,13 @@ struct P074_data_struct : public PluginTaskData_base { // longer timelines are slower, but are good in very low light situtations! void setIntegrationTime(int time) { switch (time) { - default: - case 0: tsl.setTiming(TSL2591_INTEGRATIONTIME_100MS); break; - case 1: tsl.setTiming(TSL2591_INTEGRATIONTIME_200MS); break; - case 2: tsl.setTiming(TSL2591_INTEGRATIONTIME_300MS); break; - case 3: tsl.setTiming(TSL2591_INTEGRATIONTIME_400MS); break; - case 4: tsl.setTiming(TSL2591_INTEGRATIONTIME_500MS); break; - case 5: tsl.setTiming(TSL2591_INTEGRATIONTIME_600MS); break; + default: + case 0: tsl.setTiming(TSL2591_INTEGRATIONTIME_100MS); break; + case 1: tsl.setTiming(TSL2591_INTEGRATIONTIME_200MS); break; + case 2: tsl.setTiming(TSL2591_INTEGRATIONTIME_300MS); break; + case 3: tsl.setTiming(TSL2591_INTEGRATIONTIME_400MS); break; + case 4: tsl.setTiming(TSL2591_INTEGRATIONTIME_500MS); break; + case 5: tsl.setTiming(TSL2591_INTEGRATIONTIME_600MS); break; } } @@ -52,34 +52,34 @@ struct P074_data_struct : public PluginTaskData_base { // situations void setGain(int gain) { switch (gain) { - default: - case 0: tsl.setGain(TSL2591_GAIN_LOW); break; // 1x gain (bright light) - case 1: tsl.setGain(TSL2591_GAIN_MED); break; // 25x (Medium) - case 2: tsl.setGain(TSL2591_GAIN_HIGH); break; // 428x (High) - case 3: tsl.setGain(TSL2591_GAIN_MAX); break; // 9876x (Max) + default: + case 0: tsl.setGain(TSL2591_GAIN_LOW); break; // 1x gain (bright light) + case 1: tsl.setGain(TSL2591_GAIN_MED); break; // 25x (Medium) + case 2: tsl.setGain(TSL2591_GAIN_HIGH); break; // 428x (High) + case 3: tsl.setGain(TSL2591_GAIN_MAX); break; // 9876x (Max) } } Adafruit_TSL2591 tsl; }; -boolean Plugin_074(byte function, struct EventStruct *event, String &string) { +boolean Plugin_074(byte function, struct EventStruct *event, String& string) { boolean success = false; switch (function) { case PLUGIN_DEVICE_ADD: { - Device[++deviceCount].Number = PLUGIN_ID_074; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].Ports = 0; - Device[deviceCount].VType = SENSOR_TYPE_QUAD; - Device[deviceCount].PullUpOption = false; + Device[++deviceCount].Number = PLUGIN_ID_074; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].Ports = 0; + Device[deviceCount].VType = SENSOR_TYPE_QUAD; + 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].TimerOptional = false; - Device[deviceCount].GlobalSyncOption = true; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 4; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].TimerOptional = false; + Device[deviceCount].GlobalSyncOption = true; break; } @@ -98,9 +98,9 @@ boolean Plugin_074(byte function, struct EventStruct *event, String &string) { case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: { - int optionValues[1] = {TSL2591_ADDR}; + int optionValues[1] = { TSL2591_ADDR }; addFormSelectorI2C(F("p074_i2c_addr"), 1, optionValues, - TSL2591_ADDR); // Only for display I2C address + TSL2591_ADDR); // Only for display I2C address break; } @@ -119,8 +119,8 @@ boolean Plugin_074(byte function, struct EventStruct *event, String &string) { // integration time (dim light) // } { - String optionsMode[6] = {F("100ms"), F("200ms"), F("300ms"), - F("400ms"), F("500ms"), F("600ms")}; + String optionsMode[6] = { F("100ms"), F("200ms"), F("300ms"), + F("400ms"), F("500ms"), F("600ms") }; addFormSelector(F("Integration Time"), F("p074_itime"), 6, optionsMode, NULL, PCONFIG(1)); } @@ -130,8 +130,8 @@ boolean Plugin_074(byte function, struct EventStruct *event, String &string) { // TSL2591_GAIN_HIGH = 0x20, // medium gain (428x) // TSL2591_GAIN_MAX = 0x30, // max gain (9876x) { - String optionsGain[4] = {F("low gain (1x)"), F("medium gain (25x)"), - F("medium gain (428x)"), F("max gain (9876x)")}; + String optionsGain[4] = { F("low gain (1x)"), F("medium gain (25x)"), + F("medium gain (428x)"), F("max gain (9876x)") }; addFormSelector(F("Value Mapping"), F("p074_gain"), 4, optionsGain, NULL, PCONFIG(2)); } @@ -152,13 +152,16 @@ boolean Plugin_074(byte function, struct EventStruct *event, String &string) { case PLUGIN_INIT: { initPluginTaskData(event->TaskIndex, new P074_data_struct()); P074_data_struct *P074_data = - static_cast(getPluginTaskData(event->TaskIndex)); + static_cast(getPluginTaskData(event->TaskIndex)); + if (nullptr == P074_data) { return success; } + if (P074_data->tsl.begin()) { P074_data->setIntegrationTime(PCONFIG(1)); P074_data->setGain(PCONFIG(2)); + if (loglevelActiveFor(LOG_LEVEL_INFO)) { String log = F("TSL2591: Address: 0x"); log += String(TSL2591_ADDR, HEX); @@ -168,6 +171,7 @@ boolean Plugin_074(byte function, struct EventStruct *event, String &string) { /* Display the gain and integration time for reference sake */ log += (F(" Gain: ")); + switch (P074_data->tsl.getGain()) { default: case TSL2591_GAIN_LOW: log += F("1x (Low)"); break; // 1x gain (bright light) @@ -189,7 +193,8 @@ boolean Plugin_074(byte function, struct EventStruct *event, String &string) { case PLUGIN_READ: { P074_data_struct *P074_data = - static_cast(getPluginTaskData(event->TaskIndex)); + static_cast(getPluginTaskData(event->TaskIndex)); + if (nullptr != P074_data) { // Simple data read example. Just read the infrared, fullspecrtrum diode // or 'visible' (difference between the two) channels. @@ -226,4 +231,5 @@ boolean Plugin_074(byte function, struct EventStruct *event, String &string) { } return success; } + #endif // USES_P074 diff --git a/src/_P079_Wemos_Motorshield.ino b/src/_P079_Wemos_Motorshield.ino index 4cd013ee0..59f8a725f 100644 --- a/src/_P079_Wemos_Motorshield.ino +++ b/src/_P079_Wemos_Motorshield.ino @@ -1,7 +1,8 @@ #ifdef USES_P079 -//####################################################################################################### -//#################################### Plugin 079: Wemos Motorshield ############################## -//####################################################################################################### + +// ####################################################################################################### +// #################################### Plugin 079: Wemos Motorshield ############################## +// ####################################################################################################### // Wemos Motorshield // like this one: https://wiki.wemos.cc/products:d1_mini_shields:motor_shield @@ -9,7 +10,7 @@ // based on this library: https://github.com/wemos/WEMOS_Motor_Shield_Arduino_Library // Plugin part written by Susanne Jaeckel + TungstenE2 -//Note: see wiki for setup. motor_shield.bin needs to be flashed first!!! +// Note: see wiki for setup. motor_shield.bin needs to be flashed first!!! // see wiki: https://www.letscontrolit.com/wiki/index.php?title=WemosMotorshield @@ -20,42 +21,51 @@ // copied from #ifndef __WEMOS_MOTOR_H -#define __WEMOS_MOTOR_H +# define __WEMOS_MOTOR_H -#if ARDUINO >= 100 - #include "Arduino.h" -#else - #include "WProgram.h" -#endif +# if ARDUINO >= 100 + # include "Arduino.h" +# else // if ARDUINO >= 100 + # include "WProgram.h" +# endif // if ARDUINO >= 100 -#include "Wire.h" -#include "_Plugin_Helper.h" +# include "Wire.h" +# include "_Plugin_Helper.h" -#define _MOTOR_A 0 -#define _MOTOR_B 1 +# define _MOTOR_A 0 +# define _MOTOR_B 1 -#define _SHORT_BRAKE 0 -#define _CCW 1 -#define _CW 2 -#define _STOP 3 -#define _STANDBY 4 +# define _SHORT_BRAKE 0 +# define _CCW 1 +# define _CW 2 +# define _STOP 3 +# define _STANDBY 4 class WemosMotor { public: -WemosMotor(uint8_t address, uint8_t motor, uint32_t freq); -WemosMotor(uint8_t address, uint8_t motor, uint32_t freq, uint8_t STBY_IO); -void setfreq(uint32_t freq); -void setmotor(uint8_t dir, float pwm_val); -void setmotor(uint8_t dir); + + WemosMotor(uint8_t address, + uint8_t motor, + uint32_t freq); + WemosMotor(uint8_t address, + uint8_t motor, + uint32_t freq, + uint8_t STBY_IO); + void setfreq(uint32_t freq); + void setmotor(uint8_t dir, + float pwm_val); + void setmotor(uint8_t dir); private: -uint8_t _address; -uint8_t _motor; -bool _use_STBY_IO = false; -uint8_t _STBY_IO = 0; + + uint8_t _address; + uint8_t _motor; + bool _use_STBY_IO = false; + uint8_t _STBY_IO = 0; }; -#endif +#endif // ifndef __WEMOS_MOTOR_H + // end copied from uint8_t Plugin_079_MotorShield_address = 0x30; @@ -63,168 +73,179 @@ uint8_t Plugin_079_MotorShield_address = 0x30; boolean Plugin_079(byte function, struct EventStruct *event, String& string) { - boolean success = false; + boolean success = false; - //WemosMotor WMS; + // WemosMotor WMS; - switch (function) { - case PLUGIN_DEVICE_ADD: { - Device[++deviceCount].Number = PLUGIN_ID_079; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].VType = SENSOR_TYPE_NONE; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = false; - Device[deviceCount].ValueCount = 0; - Device[deviceCount].SendDataOption = false; - Device[deviceCount].TimerOption = false; - break; - } + switch (function) { + case PLUGIN_DEVICE_ADD: { + Device[++deviceCount].Number = PLUGIN_ID_079; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = SENSOR_TYPE_NONE; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = false; + Device[deviceCount].ValueCount = 0; + Device[deviceCount].SendDataOption = false; + Device[deviceCount].TimerOption = false; + break; + } - case PLUGIN_GET_DEVICENAME: { - string = F(PLUGIN_NAME_079); - break; - } + case PLUGIN_GET_DEVICENAME: { + string = F(PLUGIN_NAME_079); + break; + } - case PLUGIN_GET_DEVICEVALUENAMES: { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], - PSTR(PLUGIN_VALUENAME1_079)); - break; - } + case PLUGIN_GET_DEVICEVALUENAMES: { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], + PSTR(PLUGIN_VALUENAME1_079)); + break; + } case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: { - String i2c_addres_string = formatToHex(PCONFIG(0)); - addFormTextBox(F("I2C Address (Hex)"), F("p079_adr"), i2c_addres_string, 4); - addFormNote(F("Make sure to update the Wemos Motorshield firmware, see wiki")); - break; + String i2c_addres_string = formatToHex(PCONFIG(0)); + addFormTextBox(F("I2C Address (Hex)"), F("p079_adr"), i2c_addres_string, 4); + addFormNote(F( + "Make sure to update the Wemos Motorshield firmware, see wiki")); + break; } - case PLUGIN_WEBFORM_LOAD: { - success = true; - break; - } + case PLUGIN_WEBFORM_LOAD: { + success = true; + break; + } - case PLUGIN_WEBFORM_SAVE: { - String i2c_address = web_server.arg(F("p079_adr")); - PCONFIG(0) = (int)strtol(i2c_address.c_str(), 0, 16); + case PLUGIN_WEBFORM_SAVE: { + String i2c_address = web_server.arg(F("p079_adr")); + PCONFIG(0) = (int)strtol(i2c_address.c_str(), 0, 16); - success = true; - break; - } + success = true; + break; + } - case PLUGIN_INIT: { - Plugin_079_MotorShield_address = PCONFIG(0); + case PLUGIN_INIT: { + Plugin_079_MotorShield_address = PCONFIG(0); - success = true; - break; - } + success = true; + break; + } - case PLUGIN_READ: { - success = false; - break; - } + case PLUGIN_READ: { + success = false; + break; + } - case PLUGIN_WRITE: { - String tmpString = string; + case PLUGIN_WRITE: { + String tmpString = string; - String cmd = parseString(tmpString, 1); + String cmd = parseString(tmpString, 1); - // Commands: - // MotorShieldCMD,,,, + // Commands: + // MotorShieldCMD,,,, - if (cmd.equalsIgnoreCase(F("WemosMotorShieldCMD"))) { - String paramMotor = parseString(tmpString, 2); // Motor 0 or 1 - String paramDirection = parseString(tmpString, 3); // Direction - String paramSpeed = parseString(tmpString, 4); // Speed + if (cmd.equalsIgnoreCase(F("WemosMotorShieldCMD"))) { + String paramMotor = parseString(tmpString, 2); // Motor 0 or 1 + String paramDirection = parseString(tmpString, 3); // Direction + String paramSpeed = parseString(tmpString, 4); // Speed - WemosMotor WMS(Plugin_079_MotorShield_address, paramMotor.toInt(), 1000); - addLog(LOG_LEVEL_DEBUG, String(F("WemosMotorShield: Address = ")) + Plugin_079_MotorShield_address + String(F(" Motor = ")) + paramMotor); + WemosMotor WMS(Plugin_079_MotorShield_address, paramMotor.toInt(), 1000); + addLog(LOG_LEVEL_DEBUG, + String(F("WemosMotorShield: Address = ")) + Plugin_079_MotorShield_address + String(F(" Motor = ")) + paramMotor); - if (paramDirection.equalsIgnoreCase(F("Forward"))) { - WMS.setmotor(_CW, paramSpeed.toInt()); - addLog(LOG_LEVEL_INFO, String(F("WemosMotor: Motor = ")) + paramMotor + String(F(" Direction = ")) + paramDirection + String(F(" Speed = ")) + paramSpeed); - } - if (paramDirection.equalsIgnoreCase(F("Backward"))) { - WMS.setmotor(_CCW, paramSpeed.toInt()); - addLog(LOG_LEVEL_INFO, String(F("WemosMotor: Motor = ")) + paramMotor + String(F(" Direction = ")) + paramDirection + String(F(" Speed = ")) + paramSpeed); - } - if (paramDirection.equalsIgnoreCase(F("Stop"))) { - WMS.setmotor(_STOP); - addLog(LOG_LEVEL_INFO, String(F("WemosMotor: Motor = ")) + paramMotor + String(F(" Direction = ")) + paramDirection); - } + if (paramDirection.equalsIgnoreCase(F("Forward"))) { + WMS.setmotor(_CW, paramSpeed.toInt()); + addLog(LOG_LEVEL_INFO, + String(F("WemosMotor: Motor = ")) + paramMotor + String(F(" Direction = ")) + paramDirection + String(F( + " Speed = ")) + + paramSpeed); + } - success = true; - } + if (paramDirection.equalsIgnoreCase(F("Backward"))) { + WMS.setmotor(_CCW, paramSpeed.toInt()); + addLog(LOG_LEVEL_INFO, + String(F("WemosMotor: Motor = ")) + paramMotor + String(F(" Direction = ")) + paramDirection + String(F( + " Speed = ")) + + paramSpeed); + } - break; - } - } - return success; + if (paramDirection.equalsIgnoreCase(F("Stop"))) { + WMS.setmotor(_STOP); + addLog(LOG_LEVEL_INFO, String(F("WemosMotor: Motor = ")) + paramMotor + String(F(" Direction = ")) + paramDirection); + } + + success = true; + } + + break; + } + } + return success; } - // copied from WemosMotor::WemosMotor(uint8_t address, uint8_t motor, uint32_t freq) { - _use_STBY_IO = false; + _use_STBY_IO = false; - if (motor == _MOTOR_A) - _motor = _MOTOR_A; - else - _motor = _MOTOR_B; + if (motor == _MOTOR_A) { + _motor = _MOTOR_A; + } + else { + _motor = _MOTOR_B; + } - //Wire.begin(); called in ESPEasy framework + // Wire.begin(); called in ESPEasy framework - _address = address; + _address = address; - setfreq(freq); + setfreq(freq); } - WemosMotor::WemosMotor(uint8_t address, uint8_t motor, uint32_t freq, uint8_t STBY_IO) { - _use_STBY_IO = true; - _STBY_IO = STBY_IO; + _use_STBY_IO = true; + _STBY_IO = STBY_IO; - if (motor == _MOTOR_A) - _motor = _MOTOR_A; - else - _motor = _MOTOR_B; + if (motor == _MOTOR_A) { + _motor = _MOTOR_A; + } + else { + _motor = _MOTOR_B; + } - //Wire.begin(); called in ESPEasy framework + // Wire.begin(); called in ESPEasy framework - _address = address; + _address = address; - setfreq(freq); + setfreq(freq); - pinMode(_STBY_IO, OUTPUT); - digitalWrite(_STBY_IO, LOW); + pinMode(_STBY_IO, OUTPUT); + digitalWrite(_STBY_IO, LOW); } - /* setfreq() -- set PWM's frequency freq: PWM's frequency total 4bytes - |0.5byte CMD | 3.5byte Parm| - |CMD | parm | - |0x0X set freq | uint32 freq| + |0.5byte CMD | 3.5byte Parm| + |CMD | parm | + |0x0X set freq | uint32 freq| */ void WemosMotor::setfreq(uint32_t freq) { - Wire.beginTransmission(_address); - Wire.write(((byte)(freq >> 24)) & (byte)0x0f); - Wire.write((byte)(freq >> 16)); - Wire.write((byte)(freq >> 8)); - Wire.write((byte)freq); - Wire.endTransmission(); // stop transmitting - delay(0); + Wire.beginTransmission(_address); + Wire.write(((byte)(freq >> 24)) & (byte)0x0f); + Wire.write((byte)(freq >> 16)); + Wire.write((byte)(freq >> 8)); + Wire.write((byte)freq); + Wire.endTransmission(); // stop transmitting + delay(0); } /* setmotor() -- set motor @@ -244,44 +265,46 @@ void WemosMotor::setfreq(uint32_t freq) 0.00 - 100.00 (%) total 4bytes - |0.5byte CMD | 3.5byte Parm | - |CMD | parm | - |0x10 set motorA | uint8 dir uint16 pwm| - |0x11 set motorB | uint8 dir uint16 pwm| + |0.5byte CMD | 3.5byte Parm | + |CMD | parm | + |0x10 set motorA | uint8 dir uint16 pwm| + |0x11 set motorB | uint8 dir uint16 pwm| */ void WemosMotor::setmotor(uint8_t dir, float pwm_val) { - uint16_t _pwm_val; + uint16_t _pwm_val; - if (_use_STBY_IO == true) { - if (dir == _STANDBY) { - digitalWrite(_STBY_IO, LOW); - return; - }else - digitalWrite(_STBY_IO, HIGH); - } + if (_use_STBY_IO == true) { + if (dir == _STANDBY) { + digitalWrite(_STBY_IO, LOW); + return; + } else { + digitalWrite(_STBY_IO, HIGH); + } + } - Wire.beginTransmission(_address); - Wire.write(_motor | (byte)0x10); // CMD either 0x10 or 0x11 - Wire.write(dir); + Wire.beginTransmission(_address); + Wire.write(_motor | (byte)0x10); // CMD either 0x10 or 0x11 + Wire.write(dir); // PWM in % - _pwm_val = uint16_t(pwm_val * 100); + _pwm_val = uint16_t(pwm_val * 100); - if (_pwm_val > 10000) // _pwm_val > 100.00 - _pwm_val = 10000; + if (_pwm_val > 10000) { // _pwm_val > 100.00 + _pwm_val = 10000; + } - Wire.write((byte)(_pwm_val >> 8)); - Wire.write((byte)_pwm_val); - Wire.endTransmission(); // stop transmitting + Wire.write((byte)(_pwm_val >> 8)); + Wire.write((byte)_pwm_val); + Wire.endTransmission(); // stop transmitting - delay(0); + delay(0); } void WemosMotor::setmotor(uint8_t dir) { - setmotor(dir, 100); + setmotor(dir, 100); } // end copied from diff --git a/src/_P083_SGP30.ino b/src/_P083_SGP30.ino index db2121498..633ecb30a 100644 --- a/src/_P083_SGP30.ino +++ b/src/_P083_SGP30.ino @@ -24,9 +24,9 @@ Adafruit_SGP30 sgp; -unsigned long Plugin_083_init_time = 0; -bool Plugin_083_init = false; -bool Plugin_083_newValues = false; +unsigned long Plugin_083_init_time = 0; +bool Plugin_083_init = false; +bool Plugin_083_newValues = false; boolean Plugin_083(byte function, struct EventStruct *event, String& string) { @@ -80,7 +80,7 @@ boolean Plugin_083(byte function, struct EventStruct *event, String& string) case PLUGIN_INIT: { - Plugin_083_init = sgp.begin(); + Plugin_083_init = sgp.begin(); Plugin_083_init_time = millis(); if (!Plugin_083_init) { @@ -113,7 +113,7 @@ boolean Plugin_083(byte function, struct EventStruct *event, String& string) // For the first 15s after the sgp30_iaq_init command the sensor is // in an initialization phase during which a sgp30_measure_iaq command // returns fixed values of 400 ppm CO2eq and 0 ppb TVOC. - if (timePassedSince(Plugin_083_init_time) > 15000 || ((sgp.TVOC != 0) && (sgp.eCO2 != 400))) { + if ((timePassedSince(Plugin_083_init_time) > 15000) || ((sgp.TVOC != 0) && (sgp.eCO2 != 400))) { Plugin_083_newValues = true; } } diff --git a/src/_P084_VEML6070.ino b/src/_P084_VEML6070.ino index af7f6acf0..132770cfe 100644 --- a/src/_P084_VEML6070.ino +++ b/src/_P084_VEML6070.ino @@ -1,7 +1,8 @@ #ifdef USES_P084 -//####################################################################################################### -//#################################### Plugin 084: VEML6070 UV ########################################## -//####################################################################################################### + +// ####################################################################################################### +// #################################### Plugin 084: VEML6070 UV ########################################## +// ####################################################################################################### // ESPEasy Plugin to for UV with chip VEML6070 // written by Remco van Essen (https://github.com/RemCom) @@ -17,17 +18,19 @@ #define PLUGIN_VALUENAME2_084 "UV-Risk" #define PLUGIN_VALUENAME3_084 "UV-Power" -#define VEML6070_ADDR_H 0x39 -#define VEML6070_ADDR_L 0x38 -#define VEML6070_RSET_DEFAULT 270000 // 270K default resistor value 270000 ohm, range from 220K..1Meg -#define VEML6070_UV_MAX_INDEX 15 // normal 11, internal on weather laboratories and NASA it's 15 so far the sensor is linear -#define VEML6070_UV_MAX_DEFAULT 11 // 11 = public default table values -#define VEML6070_POWER_COEFFCIENT 0.025 // based on calculations from Karel Vanicek and reorder by hand -#define VEML6070_TABLE_COEFFCIENT 32.86270591 // calculated by hand with help from a friend of mine, a professor which works in aero space things - // (resistor, differences, power coefficients and official UV index calculations (LAT & LONG will be added later) +#define VEML6070_ADDR_H 0x39 +#define VEML6070_ADDR_L 0x38 +#define VEML6070_RSET_DEFAULT 270000 // 270K default resistor value 270000 ohm, range from 220K..1Meg +#define VEML6070_UV_MAX_INDEX 15 // normal 11, internal on weather laboratories and NASA it's 15 so far the sensor is linear +#define VEML6070_UV_MAX_DEFAULT 11 // 11 = public default table values +#define VEML6070_POWER_COEFFCIENT 0.025 // based on calculations from Karel Vanicek and reorder by hand +#define VEML6070_TABLE_COEFFCIENT 32.86270591 // calculated by hand with help from a friend of mine, a professor which works in aero space + // things + // (resistor, differences, power coefficients and official UV index calculations (LAT & LONG + // will be added later) -#define VEML6070_base_value ( (VEML6070_RSET_DEFAULT / VEML6070_TABLE_COEFFCIENT) / VEML6070_UV_MAX_DEFAULT ) * (1) -#define VEML6070_max_value ( (VEML6070_RSET_DEFAULT / VEML6070_TABLE_COEFFCIENT) / VEML6070_UV_MAX_DEFAULT ) * (VEML6070_UV_MAX_INDEX) +#define VEML6070_base_value ((VEML6070_RSET_DEFAULT / VEML6070_TABLE_COEFFCIENT) / VEML6070_UV_MAX_DEFAULT) * (1) +#define VEML6070_max_value ((VEML6070_RSET_DEFAULT / VEML6070_TABLE_COEFFCIENT) / VEML6070_UV_MAX_DEFAULT) * (VEML6070_UV_MAX_INDEX) boolean Plugin_084(byte function, struct EventStruct *event, String& string) { @@ -36,98 +39,97 @@ boolean Plugin_084(byte function, struct EventStruct *event, String& string) switch (function) { case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_084; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].Ports = 0; - Device[deviceCount].VType = SENSOR_TYPE_SINGLE; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = true; - Device[deviceCount].ValueCount = 3; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].TimerOptional = false; - Device[deviceCount].GlobalSyncOption = true; - break; - } + { + Device[++deviceCount].Number = PLUGIN_ID_084; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].Ports = 0; + Device[deviceCount].VType = SENSOR_TYPE_SINGLE; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 3; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].TimerOptional = false; + Device[deviceCount].GlobalSyncOption = true; + break; + } case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_084); - break; - } + { + string = F(PLUGIN_NAME_084); + break; + } case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_084)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_084)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_VALUENAME3_084)); - break; - } + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_084)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_084)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_VALUENAME3_084)); + break; + } case PLUGIN_WEBFORM_LOAD: - { + { + String optionsMode[4] = { F("1/2T"), F("1T"), F("2T"), F("4T (Default)") }; + addFormSelector(F("Refresh Time Determination"), F("itime"), 4, optionsMode, NULL, PCONFIG(0)); - String optionsMode[4] = { F("1/2T"), F("1T"), F("2T"), F("4T (Default)") }; - addFormSelector(F("Refresh Time Determination"), F("itime"), 4, optionsMode, NULL, PCONFIG(0)); - - success = true; - break; - } + success = true; + break; + } case PLUGIN_WEBFORM_SAVE: - { + { + PCONFIG(0) = getFormItemInt(F("itime")); - PCONFIG(0) = getFormItemInt(F("itime")); - - success = true; - break; - } + success = true; + break; + } case PLUGIN_INIT: - { - bool status = VEML6070_Init(PCONFIG(0)); - if(!status){ - addLog(LOG_LEVEL_INFO, F("VEML6070: Not available!")); - } + { + bool status = VEML6070_Init(PCONFIG(0)); - success = status; - break; + if (!status) { + addLog(LOG_LEVEL_INFO, F("VEML6070: Not available!")); } + success = status; + break; + } + case PLUGIN_READ: - { - uint16_t uv_raw; - double uv_risk, uv_power; - bool read_status; + { + uint16_t uv_raw; + double uv_risk, uv_power; + bool read_status; - uv_raw = VEML6070_ReadUv(&read_status); // get UV raw values - uv_risk = VEML6070_UvRiskLevel(uv_raw); // get UV risk level - uv_power = VEML6070_UvPower(uv_risk); // get UV power in W/m2 + uv_raw = VEML6070_ReadUv(&read_status); // get UV raw values + uv_risk = VEML6070_UvRiskLevel(uv_raw); // get UV risk level + uv_power = VEML6070_UvPower(uv_risk); // get UV power in W/m2 - if (isnan(uv_raw) || uv_raw == 65535 || !read_status) { - addLog(LOG_LEVEL_INFO, F("VEML6070: no data read!")); - UserVar[event->BaseVarIndex + 0] = NAN; - UserVar[event->BaseVarIndex + 1] = NAN; - UserVar[event->BaseVarIndex + 2] = NAN; - success = false; - } else { - UserVar[event->BaseVarIndex + 0] = uv_raw; - UserVar[event->BaseVarIndex + 1] = uv_risk; - UserVar[event->BaseVarIndex + 2] = uv_power; - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("VEML6070: UV: "); - log += UserVar[event->BaseVarIndex]; - addLog(LOG_LEVEL_INFO, log); - } - - success = true; + if (isnan(uv_raw) || (uv_raw == 65535) || !read_status) { + addLog(LOG_LEVEL_INFO, F("VEML6070: no data read!")); + UserVar[event->BaseVarIndex + 0] = NAN; + UserVar[event->BaseVarIndex + 1] = NAN; + UserVar[event->BaseVarIndex + 2] = NAN; + success = false; + } else { + UserVar[event->BaseVarIndex + 0] = uv_raw; + UserVar[event->BaseVarIndex + 1] = uv_risk; + UserVar[event->BaseVarIndex + 2] = uv_power; + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = F("VEML6070: UV: "); + log += UserVar[event->BaseVarIndex]; + addLog(LOG_LEVEL_INFO, log); } - break; + success = true; } + break; + } } return success; } @@ -136,14 +138,14 @@ boolean Plugin_084(byte function, struct EventStruct *event, String& string) // VEML6070 // ////////////// -//get UV raw values -uint16_t VEML6070_ReadUv(bool * status) +// get UV raw values +uint16_t VEML6070_ReadUv(bool *status) { - uint16_t uv_raw = 0; - bool wire_status = false; + uint16_t uv_raw = 0; + bool wire_status = false; uv_raw = I2C_read8(VEML6070_ADDR_H, &wire_status); - *status = wire_status; + *status = wire_status; uv_raw <<= 8; uv_raw |= I2C_read8(VEML6070_ADDR_L, &wire_status); *status &= wire_status; @@ -158,8 +160,7 @@ bool VEML6070_Init(byte it) return succes; } - -//Definition of risk numbers +// Definition of risk numbers // 0.0 - 2.9 "Low" = sun->fun // 3.0 - 5.9 "Mid" = sun->glases advised // 6.0 - 7.9 "High" = sun->glases a must @@ -191,4 +192,4 @@ double VEML6070_UvPower(double uvrisk) return VEML6070_POWER_COEFFCIENT * uvrisk; } -#endif // USES_P084 \ No newline at end of file +#endif // USES_P084 diff --git a/src/_P090_CCS811.ino b/src/_P090_CCS811.ino index 019ad1842..4a4f3c680 100644 --- a/src/_P090_CCS811.ino +++ b/src/_P090_CCS811.ino @@ -56,7 +56,7 @@ # define __CCS811_H__ # include "stdint.h" -#include "_Plugin_Helper.h" +# include "_Plugin_Helper.h" // Register addresses # define CSS811_STATUS 0x00 @@ -108,7 +108,7 @@ public: virtual ~CCS811Core() = default; status beginCore(void); - void setAddress(uint8_t); + void setAddress(uint8_t); // ***Reading functions***// @@ -850,6 +850,7 @@ CCS811Core::status CCS811::setEnvironmentalData(float relativeHumidity, float te } CCS811Core::status returnError = multiWriteRegister(CSS811_ENV_DATA, envData, 4); + return returnError; } // CCS811::setEnvironmentalData diff --git a/src/_P095_ILI9341.ino b/src/_P095_ILI9341.ino index e91a9a376..7e5aeeb4d 100644 --- a/src/_P095_ILI9341.ino +++ b/src/_P095_ILI9341.ino @@ -1,7 +1,8 @@ #ifdef USES_P095 -//####################################################################################################### -//#################################### Plugin 095: ILI9341 TFT 2.4inches display ################################# -//####################################################################################################### + +// ####################################################################################################### +// #################################### Plugin 095: ILI9341 TFT 2.4inches display ################################# +// ####################################################################################################### #define PLUGIN_095 #define PLUGIN_ID_095 95 @@ -12,138 +13,146 @@ #include "_Plugin_Helper.h" #if !defined(LIMIT_BUILD_SIZE) && !defined(PLUGIN_095_FONT_INCLUDED) - #define PLUGIN_095_FONT_INCLUDED // enable to use fonts in this plugin -#endif + # define PLUGIN_095_FONT_INCLUDED // enable to use fonts in this plugin +#endif // if !defined(LIMIT_BUILD_SIZE) && !defined(PLUGIN_095_FONT_INCLUDED) /* README.MD -## INTRO + ## INTRO -This plugin allow to control a TFT screen (ILI9341) through HTTP API + This plugin allow to control a TFT screen (ILI9341) through HTTP API -## Environment -Tested with WEMOS D1 Mini Pro and Wemos TDFT 2.4 -Tested with ESPEasy 2.4.2 -tag mega-201902225) + ## Environment + Tested with WEMOS D1 Mini Pro and Wemos TDFT 2.4 + Tested with ESPEasy 2.4.2 -tag mega-201902225) -TFT Shield : https://docs.wemos.cc/en/latest/d1_mini_shiled/tft_2_4.html -Price : ~ 5.40€/$ (https://fr.aliexpress.com/item/32919729730.html) + TFT Shield : https://docs.wemos.cc/en/latest/d1_mini_shiled/tft_2_4.html + Price : ~ 5.40€/$ (https://fr.aliexpress.com/item/32919729730.html) -## Dependencies -Plugin lib_deps = Adafruit GFX, Adafruit ILI9341 + ## Dependencies + Plugin lib_deps = Adafruit GFX, Adafruit ILI9341 -## API Documentation + ## API Documentation -This plugin is controlled by HTTP API, ie : http:///control?cmd=tft,tx,HelloWorld + This plugin is controlled by HTTP API, ie : http:///control?cmd=tft,tx,HelloWorld -| command | details | description | -|-----|-----|-----| -| tft | `TFT,,....` | Draw line, rect, circle, triangle and text | -|tftcmd | `TFTCMD,` | Control the screen (on, off, clear,..) | + | command | details | description | + |-----|-----|-----| + | tft | `TFT,,....` | Draw line, rect, circle, triangle and text | + |tftcmd | `TFTCMD,` | Control the screen (on, off, clear,..) | -TFT Subcommands: + TFT Subcommands: -| TFT Subcommands | details | description | -|-----|-----|-----| -| txt | txt, | Write simple text (use last position, color and size) | -| txp | txp,, | Set text position (move the cursor) | -| txc | txc,, | Set text color (background is transparent if not provided | -| txs | txs, | Set text size | -| txtfull | txtfull,,,,,, | Write text with all options | -| l | l,,,<2>,, | Draw a simple line | -| lh | lh,,, | Draw an horizontal line (width = Line width in pixels (positive = right of first point, negative = point of first corner). | -| lv | lv,,, | Draw a vertical line (height= Line height in pixels (positive = below first point, negative = above first point).| -| r | r,,,,, | Draw a rectangle | -| rf | rf,,,,,, | Draw a filled rectangle | -| c | c,,,, | Draw a circle | -| cf | cf,,,,, | Draw a filled circle | -| t | t,,,,,,,| Draw a triangle | -| tf | tf,,,,,,,, | Draw a filled triangle | -| rr | rr,,,,,, | Draw a round rectangle | -| rrf | rrf,,,,,,, | Draw a filled round rectangle | -| px | px,,, | Print a single pixel | -| font| font, | Switch to font - SEVENSEG24, SEVENSEG18, FREESANS, DEFAULT | + | TFT Subcommands | details | description | + |-----|-----|-----| + | txt | txt, | Write simple text (use last position, color and size) | + | txp | txp,, | Set text position (move the cursor) | + | txc | txc,, | Set text color (background is transparent if not provided | + | txs | txs, | Set text size | + | txtfull | txtfull,,,,,, | Write text with all options | + | l | l,,,<2>,, | Draw a simple line | + | lh | lh,,, | Draw an horizontal line (width = Line width in pixels (positive = right of first point, negative = point of + |first corner). | + | lv | lv,,, | Draw a vertical line (height= Line height in pixels (positive = below first point, negative = above first + |point).| + | r | r,,,,, | Draw a rectangle | + | rf | rf,,,,,, | Draw a filled rectangle | + | c | c,,,, | Draw a circle | + | cf | cf,,,,, | Draw a filled circle | + | t | t,,,,,,,| Draw a triangle | + | tf | tf,,,,,,,, | Draw a filled triangle | + | rr | rr,,,,,, | Draw a round rectangle | + | rrf | rrf,,,,,,, | Draw a filled round rectangle | + | px | px,,, | Print a single pixel | + | font| font, | Switch to font - SEVENSEG24, SEVENSEG18, FREESANS, DEFAULT | -TFTCMD Subcommands: + TFTCMD Subcommands: -| TFT Subcommands | details | description | -|-----|-----|-----| -| on | on | Display ON | -| off | off | Display OFF | -| clear | clear, | Clear display | -| inv | inv, | Invert the dispaly (value:0 normal display, 1 inverted display) | -| rot | rot, | Rotate display (value from 0 to 3 inclusive) | + | TFT Subcommands | details | description | + |-----|-----|-----| + | on | on | Display ON | + | off | off | Display OFF | + | clear | clear, | Clear display | + | inv | inv, | Invert the dispaly (value:0 normal display, 1 inverted display) | + | rot | rot, | Rotate display (value from 0 to 3 inclusive) | -Examples: + Examples: - Write Text : - http:///control?cmd=tft,txtfull,0,0,HelloWorld + Write Text : + http:///control?cmd=tft,txtfull,0,0,HelloWorld - Write Text another place: - http:///control?cmd=tft,txtfull,100,40,HelloWorld + Write Text another place: + http:///control?cmd=tft,txtfull,100,40,HelloWorld - Write bigger Text : - http:///control?cmd=tft,txtfull,0,0,3,HelloWorld + Write bigger Text : + http:///control?cmd=tft,txtfull,0,0,3,HelloWorld - Write RED Text : - http:///control?cmd=tft,txtfull,0,0,3,HelloWorld + Write RED Text : + http:///control?cmd=tft,txtfull,0,0,3,HelloWorld - Write RED Text (size is 1): - http:///control?cmd=tft,txtfull,0,0,1,RED,HelloWorld + Write RED Text (size is 1): + http:///control?cmd=tft,txtfull,0,0,1,RED,HelloWorld - Write RED Text on YELLOW background (size is 1): - http:///control?cmd=tft,txtfull,0,0,1,RED,YELLOW,HelloWorld + Write RED Text on YELLOW background (size is 1): + http:///control?cmd=tft,txtfull,0,0,1,RED,YELLOW,HelloWorld - Switch display ON - http:///control?cmd=tftcmd,on + Switch display ON + http:///control?cmd=tftcmd,on - Switch display OFF - http:///control?cmd=tftcmd,off + Switch display OFF + http:///control?cmd=tftcmd,off - Clear whole display - http:///control?cmd=tftcmd,clear + Clear whole display + http:///control?cmd=tftcmd,clear - Clear GREEN whole display - http:///control?cmd=tftcmd,clear,green -*/ + Clear GREEN whole display + http:///control?cmd=tftcmd,clear,green + */ -//plugin dependency +// plugin dependency #include #ifdef PLUGIN_095_FONT_INCLUDED - #include "src/Static/Fonts/Seven_Segment24pt7b.h" - #include "src/Static/Fonts/Seven_Segment18pt7b.h" - #include "Fonts/FreeSans9pt7b.h" -#endif + # include "src/Static/Fonts/Seven_Segment24pt7b.h" + # include "src/Static/Fonts/Seven_Segment18pt7b.h" + # include "Fonts/FreeSans9pt7b.h" +#endif // ifdef PLUGIN_095_FONT_INCLUDED -//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); +// 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 +// Define the default values for both ESP32/lolin32 and D1 Mini #ifdef ESP32 -//for D32 Pro with TFT connector - #define TFT_CS 14 - #define TFT_DC 27 - #define TFT_RST 33 - #define TS_CS 12 -#else - //for D1 Mini with shield connection - #define TFT_CS 16 // D0 - #define TFT_DC 15 // D8 - #define TFT_RST -1 - #define TS_CS 0 // D3 -#endif -//The setting structure +// for D32 Pro with TFT connector + # define TFT_CS 14 + # define TFT_DC 27 + # define TFT_RST 33 + # define TS_CS 12 +#else // ifdef ESP32 + +// for D1 Mini with shield connection + # define TFT_CS 16 // D0 + # define TFT_DC 15 // D8 + # define TFT_RST -1 + # define TS_CS 0 // D3 +#endif // ifdef ESP32 + +// The setting structure struct Plugin_095_TFT_SettingStruct { Plugin_095_TFT_SettingStruct() - : address_tft_cs(TFT_CS), address_tft_dc(TFT_DC), address_tft_rst(TFT_RST), address_ts_cs(TS_CS), rotation(0) - { + : address_tft_cs(TFT_CS), address_tft_dc(TFT_DC), address_tft_rst(TFT_RST), address_ts_cs(TS_CS), rotation(0) + {} - } byte address_tft_cs; byte address_tft_dc; byte address_tft_rst; @@ -151,7 +160,7 @@ struct Plugin_095_TFT_SettingStruct byte rotation; } TFT_Settings; -//The display pointer +// The display pointer Adafruit_ILI9341 *tft = NULL; boolean Plugin_095(byte function, struct EventStruct *event, String& string) @@ -160,340 +169,382 @@ boolean Plugin_095(byte function, struct EventStruct *event, String& string) switch (function) { - case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_095; - Device[deviceCount].Type = DEVICE_TYPE_I2C; // FIXME TD-er: This is no I2C, but looks like SPI. - Device[deviceCount].VType = SENSOR_TYPE_NONE; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = false; - Device[deviceCount].ValueCount = 0; - Device[deviceCount].SendDataOption = false; - Device[deviceCount].TimerOption = false; - success = true; - break; - } + { + Device[++deviceCount].Number = PLUGIN_ID_095; + Device[deviceCount].Type = DEVICE_TYPE_I2C; // FIXME TD-er: This is no I2C, but looks like SPI. + Device[deviceCount].VType = SENSOR_TYPE_NONE; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = false; + Device[deviceCount].ValueCount = 0; + Device[deviceCount].SendDataOption = false; + Device[deviceCount].TimerOption = false; + success = true; + break; + } case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_095); - success = true; - break; - } + { + string = F(PLUGIN_NAME_095); + success = true; + break; + } case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_095)); - success = true; - break; - } + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_095)); + success = true; + break; + } case PLUGIN_WEBFORM_LOAD: + { + byte init = PCONFIG(0); + + // if already configured take it from settings, else use default values (only for pin values) + if (init == 1) { - - byte init = PCONFIG(0); - - //if already configured take it from settings, else use default values (only for pin values) - if(init == 1) - { - TFT_Settings.address_tft_cs = PIN(0); - TFT_Settings.address_tft_dc = PIN(1); - TFT_Settings.address_tft_rst = PIN(2); - TFT_Settings.address_ts_cs = PIN(3); - } - - addFormPinSelect(F("TFT CS"), F("p095_tft_cs"), TFT_Settings.address_tft_cs); - addFormPinSelect(F("TFT DC"), F("p095_tft_dc"), TFT_Settings.address_tft_dc); - addFormPinSelect(F("TFT RST"), F("p095_tft_rst"), TFT_Settings.address_tft_rst); - addFormPinSelect(F("TS CS"), F("p095_ts_cs"), TFT_Settings.address_ts_cs); - - byte choice2 = PCONFIG(1); - String options2[4] = { F("Normal"), F("+90°"), F("+180°"), F("+270°") }; - int optionValues2[4] = { 0, 1, 2, 3 }; - addFormSelector(F("Rotation"), F("p095_rotate"), 4, options2, optionValues2, choice2); - - success = true; - break; + TFT_Settings.address_tft_cs = PIN(0); + TFT_Settings.address_tft_dc = PIN(1); + TFT_Settings.address_tft_rst = PIN(2); + TFT_Settings.address_ts_cs = PIN(3); } + addFormPinSelect(F("TFT CS"), F("p095_tft_cs"), TFT_Settings.address_tft_cs); + addFormPinSelect(F("TFT DC"), F("p095_tft_dc"), TFT_Settings.address_tft_dc); + addFormPinSelect(F("TFT RST"), F("p095_tft_rst"), TFT_Settings.address_tft_rst); + addFormPinSelect(F("TS CS"), F("p095_ts_cs"), TFT_Settings.address_ts_cs); + + byte choice2 = PCONFIG(1); + String options2[4] = { F("Normal"), F("+90°"), F("+180°"), F("+270°") }; + int optionValues2[4] = { 0, 1, 2, 3 }; + addFormSelector(F("Rotation"), F("p095_rotate"), 4, options2, optionValues2, choice2); + + success = true; + break; + } + case PLUGIN_WEBFORM_SAVE: - { - PCONFIG(0) = 1; //mark config as already saved (next time, will not use default values) - PIN(0) = getFormItemInt(F("p095_tft_cs")); - PIN(1) = getFormItemInt(F("p095_tft_dc")); - PIN(2) = getFormItemInt(F("p095_tft_rst")); - PIN(3) = getFormItemInt(F("p095_ts_cs")); - PCONFIG(1) = getFormItemInt(F("p095_rotate")); - success = true; - break; - } + { + PCONFIG(0) = 1; // mark config as already saved (next time, will not use default values) + PIN(0) = getFormItemInt(F("p095_tft_cs")); + PIN(1) = getFormItemInt(F("p095_tft_dc")); + PIN(2) = getFormItemInt(F("p095_tft_rst")); + PIN(3) = getFormItemInt(F("p095_ts_cs")); + PCONFIG(1) = getFormItemInt(F("p095_rotate")); + success = true; + break; + } case PLUGIN_INIT: - { + { + TFT_Settings.address_tft_cs = PIN(0); + TFT_Settings.address_tft_dc = PIN(1); + TFT_Settings.address_tft_rst = PIN(2); + TFT_Settings.address_ts_cs = PIN(3); + TFT_Settings.rotation = PCONFIG(1); - TFT_Settings.address_tft_cs = PIN(0); - TFT_Settings.address_tft_dc = PIN(1); - TFT_Settings.address_tft_rst = PIN(2); - TFT_Settings.address_ts_cs = PIN(3); - 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("--cdt--", 1, 1); - success = true; - break; - } + 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("--cdt--", 1, 1); + success = true; + break; + } case PLUGIN_WRITE: - { - String tmpString = String(string); - String arguments = String(string); + { + String tmpString = String(string); + String arguments = String(string); - int dotPos = arguments.indexOf('.'); - if(dotPos > -1 && arguments.substring(dotPos,dotPos+3).equalsIgnoreCase(F("tft"))) + int dotPos = arguments.indexOf('.'); + + if ((dotPos > -1) && arguments.substring(dotPos, dotPos + 3).equalsIgnoreCase(F("tft"))) + { + LoadTaskSettings(event->TaskIndex); + String name = arguments.substring(0, dotPos); + name.replace(F("["), F("")); + name.replace(F("]"), F("")); + + if (name.equalsIgnoreCase(getTaskDeviceName(event->TaskIndex)) == true) { - LoadTaskSettings(event->TaskIndex); - String name = arguments.substring(0,dotPos); - name.replace(F("["),F("")); - name.replace(F("]"),F("")); - if(name.equalsIgnoreCase(getTaskDeviceName(event->TaskIndex)) == true) + arguments = arguments.substring(dotPos + 1); + } + else + { + return false; + } + } + + String command = F(""); + String subcommand = F(""); + + 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; + + tmpString += "
command= " + command; + tmpString += "
arguments= " + arguments; + tmpString += "
argIndex= " + String(argIndex); + tmpString += "
subcommand= " + subcommand; + + + if (command.equalsIgnoreCase(F("TFTCMD"))) + { + if (subcommand.equalsIgnoreCase(F("ON"))) { - arguments = arguments.substring(dotPos+1); + 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() % 3); } else { - return false; + success = false; } } - - String command = F(""); - String subcommand = F(""); - - int argIndex = arguments.indexOf(','); - if (argIndex) + else if (command.equalsIgnoreCase(F("TFT"))) { - command = arguments.substring(0, argIndex); - arguments = arguments.substring(argIndex+1); - argIndex = arguments.indexOf(','); - subcommand = arguments.substring(0, argIndex); - success = true; + tmpString += "
TFT "; - tmpString += "
command= " + command; - tmpString += "
arguments= " + arguments; - tmpString += "
argIndex= " + String(argIndex); - tmpString += "
subcommand= " + subcommand; + arguments = arguments.substring(argIndex + 1); + String sParams[8]; + int argCount = Plugin_095_StringSplit(arguments, ',', sParams, 8); - - if (command.equalsIgnoreCase(F("TFTCMD"))) + for (int a = 0; a < argCount && a < 8; a++) { - if(subcommand.equalsIgnoreCase(F("ON"))) - { - tft->sendCommand(ILI9341_DISPON); + tmpString += "
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 if(subcommand.equalsIgnoreCase(F("OFF"))) - { - tft->sendCommand(ILI9341_DISPOFF); + else { // argCount=2 + tft->setTextColor(Plugin_095_ParseColor(sParams[0]), Plugin_095_ParseColor(sParams[1])); } - 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() %3); - } - else - { + } + 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; } } - else if (command.equalsIgnoreCase(F("TFT"))) + #endif // ifdef PLUGIN_095_FONT_INCLUDED + else if (subcommand.equalsIgnoreCase(F("txtfull")) && (argCount >= 3) && (argCount <= 6)) { - tmpString += "
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++) + switch (argCount) { - tmpString += "
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) - { - case 3: //single text - Plugin_095_printText(sParams[2].c_str(), sParams[0].toInt() - 1,sParams[1].toInt() - 1); + case 3: // single text + Plugin_095_printText(sParams[2].c_str(), sParams[0].toInt() - 1, sParams[1].toInt() - 1); break; - case 4: //text + size + 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])); + 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])); + 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) - { - tft->drawLine(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), sParams[3].toInt(), Plugin_095_ParseColor(sParams[4])); - } - 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 - { - success = false; } } - else { + else if (subcommand.equalsIgnoreCase(F("l")) && (argCount == 5)) + { + tft->drawLine(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), sParams[3].toInt(), Plugin_095_ParseColor(sParams[4])); + } + 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 + { success = false; - } + } } - else - { - //invalid arguments + else { success = false; } - - if(!success) - { - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLog(LOG_LEVEL_INFO, F("Fail to parse command correctly; please check API documentation")); - String log2 = F("Parsed command = \""); - log2 += string; - log2 += "\""; - addLog(LOG_LEVEL_INFO, log2); - } - } - else - { - String log; - log.reserve(110); // Prevent re-allocation - log = F("P095-ILI9341 : WRITE = "); - log += tmpString; - SendStatus(event->Source, log); // Reply (echo) to sender. This will print message on browser. - } - break; } - } + else + { + // invalid arguments + success = false; + } + if (!success) + { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, F("Fail to parse command correctly; please check API documentation")); + String log2 = F("Parsed command = \""); + log2 += string; + log2 += "\""; + addLog(LOG_LEVEL_INFO, log2); + } + } + else + { + String log; + log.reserve(110); // Prevent re-allocation + log = F("P095-ILI9341 : WRITE = "); + log += tmpString; + SendStatus(event->Source, log); // Reply (echo) to sender. This will print message on browser. + } + break; + } + } 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) +// 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); @@ -502,83 +553,122 @@ void Plugin_095_printText(const char *string, int X, int Y, unsigned int textSiz 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) +// 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"))) + 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] == '#') + 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) + 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 + 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) +// 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; + int count = 0; + char *pch; String d = String(c); - pch = strtok ((char*)(s.c_str()),d.c_str()); + + pch = strtok((char *)(s.c_str()), d.c_str()); + while (pch != NULL && count < limit) { op[count] = String(pch); count++; - pch = strtok (NULL, ","); + pch = strtok(NULL, ","); } return count; } - #endif // USES_P095 From 3cf3723ad551aa3ac9d477d3ff943d3f6ee92628 Mon Sep 17 00:00:00 2001 From: Gijs Noorlander Date: Thu, 20 Aug 2020 12:36:55 +0200 Subject: [PATCH 26/28] Revert changes in P095 as they are already already covered by #3205 --- src/_P095_ILI9341.ino | 910 +++++++++++++++++++----------------------- 1 file changed, 410 insertions(+), 500 deletions(-) diff --git a/src/_P095_ILI9341.ino b/src/_P095_ILI9341.ino index 7e5aeeb4d..1b605afe7 100644 --- a/src/_P095_ILI9341.ino +++ b/src/_P095_ILI9341.ino @@ -1,8 +1,7 @@ #ifdef USES_P095 - -// ####################################################################################################### -// #################################### Plugin 095: ILI9341 TFT 2.4inches display ################################# -// ####################################################################################################### +//####################################################################################################### +//#################################### Plugin 095: ILI9341 TFT 2.4inches display ################################# +//####################################################################################################### #define PLUGIN_095 #define PLUGIN_ID_095 95 @@ -13,146 +12,138 @@ #include "_Plugin_Helper.h" #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) + #define PLUGIN_095_FONT_INCLUDED // enable to use fonts in this plugin +#endif /* README.MD - ## INTRO +## INTRO - This plugin allow to control a TFT screen (ILI9341) through HTTP API +This plugin allow to control a TFT screen (ILI9341) through HTTP API - ## Environment - Tested with WEMOS D1 Mini Pro and Wemos TDFT 2.4 - Tested with ESPEasy 2.4.2 -tag mega-201902225) +## Environment +Tested with WEMOS D1 Mini Pro and Wemos TDFT 2.4 +Tested with ESPEasy 2.4.2 -tag mega-201902225) - TFT Shield : https://docs.wemos.cc/en/latest/d1_mini_shiled/tft_2_4.html - Price : ~ 5.40€/$ (https://fr.aliexpress.com/item/32919729730.html) +TFT Shield : https://docs.wemos.cc/en/latest/d1_mini_shiled/tft_2_4.html +Price : ~ 5.40€/$ (https://fr.aliexpress.com/item/32919729730.html) - ## Dependencies - Plugin lib_deps = Adafruit GFX, Adafruit ILI9341 +## Dependencies +Plugin lib_deps = Adafruit GFX, Adafruit ILI9341 - ## API Documentation +## API Documentation - This plugin is controlled by HTTP API, ie : http:///control?cmd=tft,tx,HelloWorld +This plugin is controlled by HTTP API, ie : http:///control?cmd=tft,tx,HelloWorld - | command | details | description | - |-----|-----|-----| - | tft | `TFT,,....` | Draw line, rect, circle, triangle and text | - |tftcmd | `TFTCMD,` | Control the screen (on, off, clear,..) | +| command | details | description | +|-----|-----|-----| +| tft | `TFT,,....` | Draw line, rect, circle, triangle and text | +|tftcmd | `TFTCMD,` | Control the screen (on, off, clear,..) | - TFT Subcommands: +TFT Subcommands: - | TFT Subcommands | details | description | - |-----|-----|-----| - | txt | txt, | Write simple text (use last position, color and size) | - | txp | txp,, | Set text position (move the cursor) | - | txc | txc,, | Set text color (background is transparent if not provided | - | txs | txs, | Set text size | - | txtfull | txtfull,,,,,, | Write text with all options | - | l | l,,,<2>,, | Draw a simple line | - | lh | lh,,, | Draw an horizontal line (width = Line width in pixels (positive = right of first point, negative = point of - |first corner). | - | lv | lv,,, | Draw a vertical line (height= Line height in pixels (positive = below first point, negative = above first - |point).| - | r | r,,,,, | Draw a rectangle | - | rf | rf,,,,,, | Draw a filled rectangle | - | c | c,,,, | Draw a circle | - | cf | cf,,,,, | Draw a filled circle | - | t | t,,,,,,,| Draw a triangle | - | tf | tf,,,,,,,, | Draw a filled triangle | - | rr | rr,,,,,, | Draw a round rectangle | - | rrf | rrf,,,,,,, | Draw a filled round rectangle | - | px | px,,, | Print a single pixel | - | font| font, | Switch to font - SEVENSEG24, SEVENSEG18, FREESANS, DEFAULT | +| TFT Subcommands | details | description | +|-----|-----|-----| +| txt | txt, | Write simple text (use last position, color and size) | +| txp | txp,, | Set text position (move the cursor) | +| txc | txc,, | Set text color (background is transparent if not provided | +| txs | txs, | Set text size | +| txtfull | txtfull,,,,,, | Write text with all options | +| l | l,,,<2>,, | Draw a simple line | +| lh | lh,,, | Draw an horizontal line (width = Line width in pixels (positive = right of first point, negative = point of first corner). | +| lv | lv,,, | Draw a vertical line (height= Line height in pixels (positive = below first point, negative = above first point).| +| r | r,,,,, | Draw a rectangle | +| rf | rf,,,,,, | Draw a filled rectangle | +| c | c,,,, | Draw a circle | +| cf | cf,,,,, | Draw a filled circle | +| t | t,,,,,,,| Draw a triangle | +| tf | tf,,,,,,,, | Draw a filled triangle | +| rr | rr,,,,,, | Draw a round rectangle | +| rrf | rrf,,,,,,, | Draw a filled round rectangle | +| px | px,,, | Print a single pixel | +| font| font, | Switch to font - SEVENSEG24, SEVENSEG18, FREESANS, DEFAULT | - TFTCMD Subcommands: +TFTCMD Subcommands: - | TFT Subcommands | details | description | - |-----|-----|-----| - | on | on | Display ON | - | off | off | Display OFF | - | clear | clear, | Clear display | - | inv | inv, | Invert the dispaly (value:0 normal display, 1 inverted display) | - | rot | rot, | Rotate display (value from 0 to 3 inclusive) | +| TFT Subcommands | details | description | +|-----|-----|-----| +| on | on | Display ON | +| off | off | Display OFF | +| clear | clear, | Clear display | +| inv | inv, | Invert the dispaly (value:0 normal display, 1 inverted display) | +| rot | rot, | Rotate display (value from 0 to 3 inclusive) | - Examples: +Examples: - Write Text : - http:///control?cmd=tft,txtfull,0,0,HelloWorld + Write Text : + http:///control?cmd=tft,txtfull,0,0,HelloWorld - Write Text another place: - http:///control?cmd=tft,txtfull,100,40,HelloWorld + Write Text another place: + http:///control?cmd=tft,txtfull,100,40,HelloWorld - Write bigger Text : - http:///control?cmd=tft,txtfull,0,0,3,HelloWorld + Write bigger Text : + http:///control?cmd=tft,txtfull,0,0,3,HelloWorld - Write RED Text : - http:///control?cmd=tft,txtfull,0,0,3,HelloWorld + Write RED Text : + http:///control?cmd=tft,txtfull,0,0,3,HelloWorld - Write RED Text (size is 1): - http:///control?cmd=tft,txtfull,0,0,1,RED,HelloWorld + Write RED Text (size is 1): + http:///control?cmd=tft,txtfull,0,0,1,RED,HelloWorld - Write RED Text on YELLOW background (size is 1): - http:///control?cmd=tft,txtfull,0,0,1,RED,YELLOW,HelloWorld + Write RED Text on YELLOW background (size is 1): + http:///control?cmd=tft,txtfull,0,0,1,RED,YELLOW,HelloWorld - Switch display ON - http:///control?cmd=tftcmd,on + Switch display ON + http:///control?cmd=tftcmd,on - Switch display OFF - http:///control?cmd=tftcmd,off + Switch display OFF + http:///control?cmd=tftcmd,off - Clear whole display - http:///control?cmd=tftcmd,clear + Clear whole display + http:///control?cmd=tftcmd,clear - Clear GREEN whole display - http:///control?cmd=tftcmd,clear,green - */ + Clear GREEN whole display + http:///control?cmd=tftcmd,clear,green +*/ -// plugin dependency +//plugin dependency #include #ifdef PLUGIN_095_FONT_INCLUDED - # include "src/Static/Fonts/Seven_Segment24pt7b.h" - # include "src/Static/Fonts/Seven_Segment18pt7b.h" - # include "Fonts/FreeSans9pt7b.h" -#endif // 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); +//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 +//Define the default values for both ESP32/lolin32 and D1 Mini #ifdef ESP32 +//for D32 Pro with TFT connector + #define TFT_CS 14 + #define TFT_DC 27 + #define TFT_RST 33 + #define TS_CS 12 +#else + //for D1 Mini with shield connection + #define TFT_CS 16 // D0 + #define TFT_DC 15 // D8 + #define TFT_RST -1 + #define TS_CS 0 // D3 +#endif -// for D32 Pro with TFT connector - # define TFT_CS 14 - # define TFT_DC 27 - # define TFT_RST 33 - # define TS_CS 12 -#else // ifdef ESP32 - -// for D1 Mini with shield connection - # define TFT_CS 16 // D0 - # define TFT_DC 15 // D8 - # define TFT_RST -1 - # define TS_CS 0 // D3 -#endif // ifdef ESP32 - -// The setting structure +//The setting structure struct Plugin_095_TFT_SettingStruct { Plugin_095_TFT_SettingStruct() - : address_tft_cs(TFT_CS), address_tft_dc(TFT_DC), address_tft_rst(TFT_RST), address_ts_cs(TS_CS), rotation(0) - {} + : address_tft_cs(TFT_CS), address_tft_dc(TFT_DC), address_tft_rst(TFT_RST), address_ts_cs(TS_CS), rotation(0) + { + } byte address_tft_cs; byte address_tft_dc; byte address_tft_rst; @@ -160,7 +151,7 @@ struct Plugin_095_TFT_SettingStruct byte rotation; } TFT_Settings; -// The display pointer +//The display pointer Adafruit_ILI9341 *tft = NULL; boolean Plugin_095(byte function, struct EventStruct *event, String& string) @@ -169,382 +160,340 @@ boolean Plugin_095(byte function, struct EventStruct *event, String& string) switch (function) { + case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_095; - Device[deviceCount].Type = DEVICE_TYPE_I2C; // FIXME TD-er: This is no I2C, but looks like SPI. - Device[deviceCount].VType = SENSOR_TYPE_NONE; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = false; - Device[deviceCount].ValueCount = 0; - Device[deviceCount].SendDataOption = false; - Device[deviceCount].TimerOption = false; - success = true; - break; - } + { + Device[++deviceCount].Number = PLUGIN_ID_095; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = SENSOR_TYPE_NONE; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = false; + Device[deviceCount].ValueCount = 0; + Device[deviceCount].SendDataOption = false; + Device[deviceCount].TimerOption = false; + success = true; + break; + } case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_095); - success = true; - break; - } + { + string = F(PLUGIN_NAME_095); + success = true; + break; + } case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_095)); - success = true; - break; - } + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_095)); + success = true; + break; + } case PLUGIN_WEBFORM_LOAD: - { - byte init = PCONFIG(0); - - // if already configured take it from settings, else use default values (only for pin values) - if (init == 1) { - TFT_Settings.address_tft_cs = PIN(0); - TFT_Settings.address_tft_dc = PIN(1); - TFT_Settings.address_tft_rst = PIN(2); - TFT_Settings.address_ts_cs = PIN(3); + + byte init = PCONFIG(0); + + //if already configured take it from settings, else use default values (only for pin values) + if(init == 1) + { + TFT_Settings.address_tft_cs = PIN(0); + TFT_Settings.address_tft_dc = PIN(1); + TFT_Settings.address_tft_rst = PIN(2); + TFT_Settings.address_ts_cs = PIN(3); + } + + addFormPinSelect(F("TFT CS"), F("p095_tft_cs"), TFT_Settings.address_tft_cs); + addFormPinSelect(F("TFT DC"), F("p095_tft_dc"), TFT_Settings.address_tft_dc); + addFormPinSelect(F("TFT RST"), F("p095_tft_rst"), TFT_Settings.address_tft_rst); + addFormPinSelect(F("TS CS"), F("p095_ts_cs"), TFT_Settings.address_ts_cs); + + byte choice2 = PCONFIG(1); + String options2[4] = { F("Normal"), F("+90°"), F("+180°"), F("+270°") }; + int optionValues2[4] = { 0, 1, 2, 3 }; + addFormSelector(F("Rotation"), F("p095_rotate"), 4, options2, optionValues2, choice2); + + success = true; + break; } - addFormPinSelect(F("TFT CS"), F("p095_tft_cs"), TFT_Settings.address_tft_cs); - addFormPinSelect(F("TFT DC"), F("p095_tft_dc"), TFT_Settings.address_tft_dc); - addFormPinSelect(F("TFT RST"), F("p095_tft_rst"), TFT_Settings.address_tft_rst); - addFormPinSelect(F("TS CS"), F("p095_ts_cs"), TFT_Settings.address_ts_cs); - - byte choice2 = PCONFIG(1); - String options2[4] = { F("Normal"), F("+90°"), F("+180°"), F("+270°") }; - int optionValues2[4] = { 0, 1, 2, 3 }; - addFormSelector(F("Rotation"), F("p095_rotate"), 4, options2, optionValues2, choice2); - - success = true; - break; - } - case PLUGIN_WEBFORM_SAVE: - { - PCONFIG(0) = 1; // mark config as already saved (next time, will not use default values) - PIN(0) = getFormItemInt(F("p095_tft_cs")); - PIN(1) = getFormItemInt(F("p095_tft_dc")); - PIN(2) = getFormItemInt(F("p095_tft_rst")); - PIN(3) = getFormItemInt(F("p095_ts_cs")); - PCONFIG(1) = getFormItemInt(F("p095_rotate")); - success = true; - break; - } - - case PLUGIN_INIT: - { - TFT_Settings.address_tft_cs = PIN(0); - TFT_Settings.address_tft_dc = PIN(1); - TFT_Settings.address_tft_rst = PIN(2); - TFT_Settings.address_ts_cs = PIN(3); - 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("--cdt--", 1, 1); - success = true; - break; - } - - case PLUGIN_WRITE: - { - String tmpString = String(string); - String arguments = String(string); - - int dotPos = arguments.indexOf('.'); - - if ((dotPos > -1) && arguments.substring(dotPos, dotPos + 3).equalsIgnoreCase(F("tft"))) { - LoadTaskSettings(event->TaskIndex); - String name = arguments.substring(0, dotPos); - name.replace(F("["), F("")); - name.replace(F("]"), F("")); - - if (name.equalsIgnoreCase(getTaskDeviceName(event->TaskIndex)) == true) - { - arguments = arguments.substring(dotPos + 1); - } - else - { - return false; - } + PCONFIG(0) = 1; //mark config as already saved (next time, will not use default values) + PIN(0) = getFormItemInt(F("p095_tft_cs")); + PIN(1) = getFormItemInt(F("p095_tft_dc")); + PIN(2) = getFormItemInt(F("p095_tft_rst")); + PIN(3) = getFormItemInt(F("p095_ts_cs")); + PCONFIG(1) = getFormItemInt(F("p095_rotate")); + success = true; + break; } - String command = F(""); - String subcommand = F(""); - - int argIndex = arguments.indexOf(','); - - if (argIndex) + case PLUGIN_INIT: { - command = arguments.substring(0, argIndex); - arguments = arguments.substring(argIndex + 1); - argIndex = arguments.indexOf(','); - subcommand = arguments.substring(0, argIndex); - success = true; - tmpString += "
command= " + command; - tmpString += "
arguments= " + arguments; - tmpString += "
argIndex= " + String(argIndex); - tmpString += "
subcommand= " + subcommand; + TFT_Settings.address_tft_cs = PIN(0); + TFT_Settings.address_tft_dc = PIN(1); + TFT_Settings.address_tft_rst = PIN(2); + TFT_Settings.address_ts_cs = PIN(3); + 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("--cdt--", 1, 1); + success = true; + break; + } - if (command.equalsIgnoreCase(F("TFTCMD"))) + case PLUGIN_WRITE: + { + String tmpString = String(string); + String arguments = String(string); + + int dotPos = arguments.indexOf('.'); + if(dotPos > -1 && arguments.substring(dotPos,dotPos+3).equalsIgnoreCase(F("tft"))) { - if (subcommand.equalsIgnoreCase(F("ON"))) + LoadTaskSettings(event->TaskIndex); + String name = arguments.substring(0,dotPos); + name.replace(F("["),F("")); + name.replace(F("]"),F("")); + if(name.equalsIgnoreCase(getTaskDeviceName(event->TaskIndex)) == true) { - 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() % 3); + arguments = arguments.substring(dotPos+1); } else { - success = false; + return false; } } - else if (command.equalsIgnoreCase(F("TFT"))) + + String command = F(""); + String subcommand = F(""); + + int argIndex = arguments.indexOf(','); + if (argIndex) { - tmpString += "
TFT "; + command = arguments.substring(0, argIndex); + arguments = arguments.substring(argIndex+1); + argIndex = arguments.indexOf(','); + subcommand = arguments.substring(0, argIndex); + success = true; - arguments = arguments.substring(argIndex + 1); - String sParams[8]; - int argCount = Plugin_095_StringSplit(arguments, ',', sParams, 8); + tmpString += "
command= " + command; + tmpString += "
arguments= " + arguments; + tmpString += "
argIndex= " + String(argIndex); + tmpString += "
subcommand= " + subcommand; - for (int a = 0; a < argCount && a < 8; a++) - { - tmpString += "
ARGS[" + String(a) + "]=" + sParams[a]; - } - if (subcommand.equalsIgnoreCase(F("txt"))) + if (command.equalsIgnoreCase(F("TFTCMD"))) { - 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])); + if(subcommand.equalsIgnoreCase(F("ON"))) + { + tft->sendCommand(ILI9341_DISPON); } - else { // argCount=2 - tft->setTextColor(Plugin_095_ParseColor(sParams[0]), Plugin_095_ParseColor(sParams[1])); + else if(subcommand.equalsIgnoreCase(F("OFF"))) + { + tft->sendCommand(ILI9341_DISPOFF); } - } - 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 { + 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() %3); + } + else + { success = false; } } - #endif // ifdef PLUGIN_095_FONT_INCLUDED - else if (subcommand.equalsIgnoreCase(F("txtfull")) && (argCount >= 3) && (argCount <= 6)) + else if (command.equalsIgnoreCase(F("TFT"))) { - switch (argCount) + tmpString += "
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++) { - case 3: // single text - Plugin_095_printText(sParams[2].c_str(), sParams[0].toInt() - 1, sParams[1].toInt() - 1); + tmpString += "
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) + { + case 3: //single text + Plugin_095_printText(sParams[2].c_str(), sParams[0].toInt() - 1,sParams[1].toInt() - 1); break; - case 4: // text + size + 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])); + 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])); + 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) + { + tft->drawLine(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), sParams[3].toInt(), Plugin_095_ParseColor(sParams[4])); + } + 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 + { + success = false; } } - else if (subcommand.equalsIgnoreCase(F("l")) && (argCount == 5)) - { - tft->drawLine(sParams[0].toInt(), sParams[1].toInt(), sParams[2].toInt(), sParams[3].toInt(), Plugin_095_ParseColor(sParams[4])); - } - 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 { + else + { + //invalid arguments success = false; } - } - else - { - // invalid arguments - success = false; - } - if (!success) - { - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLog(LOG_LEVEL_INFO, F("Fail to parse command correctly; please check API documentation")); - String log2 = F("Parsed command = \""); - log2 += string; - log2 += "\""; - addLog(LOG_LEVEL_INFO, log2); + if(!success) + { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, F("Fail to parse command correctly; please check API documentation")); + String log2 = F("Parsed command = \""); + log2 += string; + log2 += "\""; + addLog(LOG_LEVEL_INFO, log2); + } + } + else + { + String log; + log.reserve(110); // Prevent re-allocation + log = F("P095-ILI9341 : WRITE = "); + log += tmpString; + SendStatus(event->Source, log); // Reply (echo) to sender. This will print message on browser. } + break; } - else - { - String log; - log.reserve(110); // Prevent re-allocation - log = F("P095-ILI9341 : WRITE = "); - log += tmpString; - SendStatus(event->Source, log); // Reply (echo) to sender. This will print message on browser. - } - break; - } } + 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) + +//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); @@ -553,122 +502,83 @@ void Plugin_095_printText(const char *string, int X, int Y, unsigned int textSiz 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) +//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"))) { + if (s.equalsIgnoreCase(F("BLACK"))) return ILI9341_BLACK; - } - - if (s.equalsIgnoreCase(F("NAVY"))) { + if (s.equalsIgnoreCase(F("NAVY"))) return ILI9341_NAVY; - } - - if (s.equalsIgnoreCase(F("DARKGREEN"))) { + if (s.equalsIgnoreCase(F("DARKGREEN"))) return ILI9341_DARKGREEN; - } - - if (s.equalsIgnoreCase(F("DARKCYAN"))) { + if (s.equalsIgnoreCase(F("DARKCYAN"))) return ILI9341_DARKCYAN; - } - - if (s.equalsIgnoreCase(F("MAROON"))) { + if (s.equalsIgnoreCase(F("MAROON"))) return ILI9341_MAROON; - } - - if (s.equalsIgnoreCase(F("PURPLE"))) { + if (s.equalsIgnoreCase(F("PURPLE"))) return ILI9341_PURPLE; - } - - if (s.equalsIgnoreCase(F("OLIVE"))) { + if (s.equalsIgnoreCase(F("OLIVE"))) return ILI9341_OLIVE; - } - - if (s.equalsIgnoreCase(F("LIGHTGREY"))) { + if (s.equalsIgnoreCase(F("LIGHTGREY"))) return ILI9341_LIGHTGREY; - } - - if (s.equalsIgnoreCase(F("DARKGREY"))) { + if (s.equalsIgnoreCase(F("DARKGREY"))) return ILI9341_DARKGREY; - } - - if (s.equalsIgnoreCase(F("BLUE"))) { + if (s.equalsIgnoreCase(F("BLUE"))) return ILI9341_BLUE; - } - - if (s.equalsIgnoreCase(F("GREEN"))) { + if (s.equalsIgnoreCase(F("GREEN"))) return ILI9341_GREEN; - } - - if (s.equalsIgnoreCase(F("CYAN"))) { + if (s.equalsIgnoreCase(F("CYAN"))) return ILI9341_CYAN; - } - - if (s.equalsIgnoreCase(F("RED"))) { + if (s.equalsIgnoreCase(F("RED"))) return ILI9341_RED; - } - - if (s.equalsIgnoreCase(F("MAGENTA"))) { + if (s.equalsIgnoreCase(F("MAGENTA"))) return ILI9341_MAGENTA; - } - - if (s.equalsIgnoreCase(F("YELLOW"))) { + if (s.equalsIgnoreCase(F("YELLOW"))) return ILI9341_YELLOW; - } - - if (s.equalsIgnoreCase(F("WHITE"))) { + if (s.equalsIgnoreCase(F("WHITE"))) return ILI9341_WHITE; - } - - if (s.equalsIgnoreCase(F("ORANGE"))) { + if (s.equalsIgnoreCase(F("ORANGE"))) return ILI9341_ORANGE; - } - - if (s.equalsIgnoreCase(F("GREENYELLOW"))) { + if (s.equalsIgnoreCase(F("GREENYELLOW"))) return ILI9341_GREENYELLOW; - } - - if (s.equalsIgnoreCase(F("PINK"))) { + if (s.equalsIgnoreCase(F("PINK"))) return ILI9341_PINK; - } - if ((s.length() == 7) && (s[0] == '#')) + 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) + 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 + 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) +//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; + int count = 0; + char * pch; String d = String(c); - - pch = strtok((char *)(s.c_str()), d.c_str()); - + pch = strtok ((char*)(s.c_str()),d.c_str()); while (pch != NULL && count < limit) { op[count] = String(pch); count++; - pch = strtok(NULL, ","); + pch = strtok (NULL, ","); } return count; } -#endif // USES_P095 + +#endif // USES_P095 \ No newline at end of file From 1e5f464bccd95195949474ca4ec4b7355b1f9dba Mon Sep 17 00:00:00 2001 From: Gijs Noorlander Date: Thu, 20 Aug 2020 13:01:01 +0200 Subject: [PATCH 27/28] [I2C multiplexer] Fix build issue on units not using I2C multiplexer --- src/src/DataStructs/DeviceStruct.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/src/DataStructs/DeviceStruct.h b/src/src/DataStructs/DeviceStruct.h index fb41ed8d2..757ed9819 100644 --- a/src/src/DataStructs/DeviceStruct.h +++ b/src/src/DataStructs/DeviceStruct.h @@ -15,13 +15,11 @@ #define DEVICE_TYPE_SPI 23 // connected through SPI #define DEVICE_TYPE_DUMMY 99 // Dummy device, has no physical connection -#ifdef FEATURE_I2CMULTIPLEXER #define I2C_MULTIPLEXER_NONE -1 // None selected #define I2C_MULTIPLEXER_TCA9548A 0 // TCA9548a 8 channel I2C switch, with reset, addresses 0x70-0x77 #define I2C_MULTIPLEXER_TCA9546A 1 // TCA9546a or TCA9545a 4 channel I2C switch, with reset, addresses 0x70-0x77 (no interrupt support on TCA9545a) #define I2C_MULTIPLEXER_TCA9543A 2 // TCA9543a 2 channel I2C switch, with reset, addresses 0x70-0x73 #define I2C_MULTIPLEXER_PCA9540 3 // PCA9540 2 channel I2C switch, no reset, address 0x70, different channel addressing -#endif #define I2C_FLAGS_SLOW_SPEED 0 // Force slow speed when this flag is set #define I2C_FLAGS_MUX_MULTICHANNEL 1 // Allow multiple multiplexer channels when set From 18fcbac93a5f3bc5bf78c5353790f94e44d1bb9e Mon Sep 17 00:00:00 2001 From: Gijs Noorlander Date: Thu, 20 Aug 2020 14:42:28 +0200 Subject: [PATCH 28/28] [Build] Fix build size exceeded for testing builds --- src/define_plugin_sets.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/define_plugin_sets.h b/src/define_plugin_sets.h index d5eccd816..39ca096ce 100644 --- a/src/define_plugin_sets.h +++ b/src/define_plugin_sets.h @@ -342,9 +342,6 @@ To create/register a plugin, you have to : #undef USES_SSDP #endif - #ifdef FEATURE_I2CMULTIPLEXER - #undef FEATURE_I2CMULTIPLEXER - #endif #endif @@ -1096,6 +1093,9 @@ To create/register a plugin, you have to : #ifndef BUILD_NO_DEBUG #define BUILD_NO_DEBUG #endif + #ifdef FEATURE_I2CMULTIPLEXER + #undef FEATURE_I2CMULTIPLEXER + #endif #endif // Timing stats page needs timing stats