diff --git a/docs/source/Plugin/P009.rst b/docs/source/Plugin/P009.rst index add34c20f..db4766bd8 100644 --- a/docs/source/Plugin/P009.rst +++ b/docs/source/Plugin/P009.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p00x.repl +.. include:: ../Plugin/_plugin_substitutions_p00x.repl .. _P009_page: |P009_typename| @@ -21,15 +21,109 @@ Maintainer: |P009_maintainer| Used libraries: |P009_usedlibraries| +Introduction +------------ + +The number of GPIO pins on the ESP module can be expanded with a IO Expander. This plugin supports the MCP23017 that provides 16 more pins that can be used as input or output. This way it becomes possible to control a 16 channel relay board. Multiple of these boards can be connected, as there are 8 I2C addresses available via jumper pins. + +Each individual pin can be used as either input or output. + +As this plugin shares many attributes with the regular :ref:`P001_Switch_page` plugin, the description has many similarities. + Supported hardware ------------------ +.. image:: P009_MCP23017Module.png + +The chip can be used on specifically designed hardware, or a generic module can be used. These are available from several sources. + |P009_usedby| -.. Commands available -.. ^^^^^^^^^^^^^^^^^^ +Configuration +------------- -.. .. include:: P009_commands.repl +.. image:: P009_DeviceConfiguration.png + +* **Name** A unique name should be entered here. + +* **Enabled** The device can be disabled or enabled. When not enabled the device should not use any resources. + +Sensor +^^^^^^^ + +* **Inversed Logic** When enabled, inverts the input signal, so if the pin is logic high (3.3V), the Value will be 0, and if it is logic low (gnd), the Value will be 1. + +I2C Options +^^^^^^^^^^^^ + +The available settings here depend on the build used. At least the **Force Slow I2C speed** option is available, but selections for the I2C Multiplexer can also be shown. For details see the :ref:`Hardware_page` + +* **I2C Address**: The address the device is using. As there are 8 possible I2C addresses, when the jumpers are configured, the selected value should match with that. + +Available options: + +.. image:: P009_I2CAddressOptions.png + +* **Port** As there are multiple Ports available on each board, the desired Port can be selected here. + +Available options: + +.. image:: P009_PortOptions.png + +Device Settings +^^^^^^^^^^^^^^^^ + +* **Send boot state**: If checked the unit will publish the switch state when booting. If not checked you may find yourself + with a latching switch caught in limbo. This means that the unit is registering a low/high value but the physical state of + the switch might be the opposite. If you use a mechanical switch that may be physically set to a state you should check this + option. + +Advanced event management +^^^^^^^^^^^^^^^^^^^^^^^^^ + +* **De-bounce (ms)**: How long should the pulse (the time you press the button) be, if set to high you need to have it published + for a longer time before the unit will register it as an state change. You could experiment with this setting to find a good + behavior of the button if you feel that it's not responding according to your preferences. + +* **Double click event**: If enabled the unit will detect double clicks which are within the set interval (see below). The double + click event is identified as :code:`MCP23017#State=3`. There's three options for the double click: + * Active only on low: the double clicks will be counted by how many low signals that is triggered within the set time. + * Active only on high: the double clicks will be counted by how many high signals that is triggered within the set time. + + * Active on high & low: the double clicks will be counted by how many high and low signals that is triggered within the set time. + This means that a double click could be registered as a press and release of a button. So not actually double click. + +* **Double click max. interval (ms)**: This is the interval that you need to perform the double click within. + +* **Long press event**: If enabled the unit will detect a long press of a button. There's three different behaviors of the long press: + + * Active only on low: this means that the unit will only be triggering the long press event if the signal is low. Two different event + values are used, :code:`10` if the state goes from 0 to 1 (:code:`MCP23017#State=10`), and :code:`11` if the state goes + from 1 to 0 (:code:`MCP23017#State=11`). + + * Active only on high: same as above but only triggered on high signal. + * Active on high & low: the long press will be triggered both on high and low signals. + +* **Long press min interval (ms)**: This is the interval that you need to press the button before the long press event is triggered. + +* **Use safe button (slower)**: This effectively adds an extra De-bounce delay and sends event value ``4`` when reached. + +Data Acquisition +^^^^^^^^^^^^^^^^ + +This group of settings, **Single event with all values**, **Send to Controller** and **Interval** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured. + +* **Interval** By default, Interval will be set to 60 sec. It is the frequency used to read sensor values and send these to any Controllers configured for this device. + +Values +^^^^^^ + +The name for the value is initially set to a default name, but can be changed if desired. + +Commands available +^^^^^^^^^^^^^^^^^^ + +.. include:: P009_commands.repl .. Events .. ~~~~~~ @@ -42,6 +136,9 @@ Change log .. versionchanged:: 2.0 ... + |added| + 2021-08-04 Replaced single Port inputfield with separate I2CAddress and Port selections. + |added| Major overhaul for 2.0 release. diff --git a/docs/source/Plugin/P009_DeviceConfiguration.png b/docs/source/Plugin/P009_DeviceConfiguration.png new file mode 100644 index 000000000..ff88da042 Binary files /dev/null and b/docs/source/Plugin/P009_DeviceConfiguration.png differ diff --git a/docs/source/Plugin/P009_I2CAddressOptions.png b/docs/source/Plugin/P009_I2CAddressOptions.png new file mode 100644 index 000000000..20c3c3fd6 Binary files /dev/null and b/docs/source/Plugin/P009_I2CAddressOptions.png differ diff --git a/docs/source/Plugin/P009_MCP23017Module.png b/docs/source/Plugin/P009_MCP23017Module.png new file mode 100644 index 000000000..159b9c51c Binary files /dev/null and b/docs/source/Plugin/P009_MCP23017Module.png differ diff --git a/docs/source/Plugin/P009_PortOptions.png b/docs/source/Plugin/P009_PortOptions.png new file mode 100644 index 000000000..3472f3c65 Binary files /dev/null and b/docs/source/Plugin/P009_PortOptions.png differ diff --git a/docs/source/Plugin/P019.rst b/docs/source/Plugin/P019.rst index c14538dd0..9f21ae919 100644 --- a/docs/source/Plugin/P019.rst +++ b/docs/source/Plugin/P019.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p01x.repl +.. include:: ../Plugin/_plugin_substitutions_p01x.repl .. _P019_page: |P019_typename| @@ -21,15 +21,113 @@ Maintainer: |P019_maintainer| Used libraries: |P019_usedlibraries| +Introduction +------------ + +The number of GPIO pins on the ESP module can be expanded with a IO Expander. This plugin supports the PCF8574 that provides 8 more pins that can be used as input or output. This way it becomes possible to control an 8 channel relay board. Multiple of these boards can be connected, as there are 16 I2C addresses available via jumper pins. + +Each individual pin can be used as either input or output. It can also be helpful in improving system stability when using ESPEasy for actuators. The PCF8574 keeps the GPIO states might the ESP reboot (as long as power is not disconnected). + +As this plugin shares many attributes with the regular :ref:`P001_Switch_page` plugin, the description has many similarities. + Supported hardware ------------------ +.. image:: P019_PCF8574Module.jpg + +.. image:: P019_PCF8574.jpg + +The chip can be used on specifically designed hardware, or a generic module can be used. These are available from several sources. + |P019_usedby| -.. Commands available -.. ^^^^^^^^^^^^^^^^^^ +Configuration +------------- -.. .. include:: P019_commands.repl +.. image:: P019_DeviceConfiguration.png + +* **Name** A unique name should be entered here. + +* **Enabled** The device can be disabled or enabled. When not enabled the device should not use any resources. + +Sensor +^^^^^^^ + +* **Inversed Logic** When enabled, inverts the input signal, so if the pin is logic high (3.3V), the Value will be 0, and if it is logic low (gnd), the Value will be 1. + +I2C Options +^^^^^^^^^^^^ + +The available settings here depend on the build used. At least the **Force Slow I2C speed** option is available, but selections for the I2C Multiplexer can also be shown. For details see the :ref:`Hardware_page` + +* **I2C Address**: The address the device is using. As there are 16 possible I2C addresses, when the jumpers are configured, the selected value should match with that. + +There are 2 ranges of addresses used, determined by the chip design, ``0x20..0x27`` and ``0x38..0x3F``. + +Available options: + +.. image:: P019_I2CAddressOptions.png + +* **Port** As there are multiple Ports available on each board, the desired Port can be selected here. + +Available options: + +.. image:: P019_PortOptions.png + +Device Settings +^^^^^^^^^^^^^^^^ + +* **Send boot state**: If checked the unit will publish the switch state when booting. If not checked you may find yourself + with a latching switch caught in limbo. This means that the unit is registering a low/high value but the physical state of + the switch might be the opposite. If you use a mechanical switch that may be physically set to a state you should check this + option. + +Advanced event management +^^^^^^^^^^^^^^^^^^^^^^^^^ + +* **De-bounce (ms)**: How long should the pulse (the time you press the button) be, if set to high you need to have it published + for a longer time before the unit will register it as an state change. You could experiment with this setting to find a good + behavior of the button if you feel that it's not responding according to your preferences. + +* **Double click event**: If enabled the unit will detect double clicks which are within the set interval (see below). The double + click event is identified as :code:`PCF8574#State=3`. There's three options for the double click: + * Active only on low: the double clicks will be counted by how many low signals that is triggered within the set time. + * Active only on high: the double clicks will be counted by how many high signals that is triggered within the set time. + + * Active on high & low: the double clicks will be counted by how many high and low signals that is triggered within the set time. + This means that a double click could be registered as a press and release of a button. So not actually double click. + +* **Double click max. interval (ms)**: This is the interval that you need to perform the double click within. + +* **Long press event**: If enabled the unit will detect a long press of a button. There's three different behaviors of the long press: + + * Active only on low: this means that the unit will only be triggering the long press event if the signal is low. Two different event + values are used, :code:`10` if the state goes from 0 to 1 (:code:`PCF8574#State=10`), and :code:`11` if the state goes + from 1 to 0 (:code:`PCF8574#State=11`). + + * Active only on high: same as above but only triggered on high signal. + * Active on high & low: the long press will be triggered both on high and low signals. + +* **Long press min interval (ms)**: This is the interval that you need to press the button before the long press event is triggered. + +* **Use safe button (slower)**: This effectively adds an extra De-bounce delay and sends event value ``4`` when reached. + +Data Acquisition +^^^^^^^^^^^^^^^^ + +This group of settings, **Single event with all values**, **Send to Controller** and **Interval** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured. + +* **Interval** By default, Interval will be set to 60 sec. It is the frequency used to read sensor values and send these to any Controllers configured for this device. + +Values +^^^^^^ + +The name for the value is initially set to a default name, but can be changed if desired. + +Commands available +^^^^^^^^^^^^^^^^^^ + +.. include:: P019_commands.repl .. Events .. ~~~~~~ @@ -42,6 +140,9 @@ Change log .. versionchanged:: 2.0 ... + |added| + 2021-08-03 Replaced single Port inputfield with separate I2CAddress and Port selections. + |added| Major overhaul for 2.0 release. @@ -51,7 +152,3 @@ Change log |added| Initial release version. - - - - diff --git a/docs/source/Plugin/P019_DeviceConfiguration.png b/docs/source/Plugin/P019_DeviceConfiguration.png new file mode 100644 index 000000000..7bd11023b Binary files /dev/null and b/docs/source/Plugin/P019_DeviceConfiguration.png differ diff --git a/docs/source/Plugin/P019_I2CAddressOptions.png b/docs/source/Plugin/P019_I2CAddressOptions.png new file mode 100644 index 000000000..bee2b6596 Binary files /dev/null and b/docs/source/Plugin/P019_I2CAddressOptions.png differ diff --git a/docs/source/Plugin/P019_PCF8574.jpg b/docs/source/Plugin/P019_PCF8574.jpg new file mode 100644 index 000000000..e94c0b0df Binary files /dev/null and b/docs/source/Plugin/P019_PCF8574.jpg differ diff --git a/docs/source/Plugin/P019_PCF8574Module.jpg b/docs/source/Plugin/P019_PCF8574Module.jpg new file mode 100644 index 000000000..a5ccb4185 Binary files /dev/null and b/docs/source/Plugin/P019_PCF8574Module.jpg differ diff --git a/docs/source/Plugin/P019_PortOptions.png b/docs/source/Plugin/P019_PortOptions.png new file mode 100644 index 000000000..a87df6309 Binary files /dev/null and b/docs/source/Plugin/P019_PortOptions.png differ diff --git a/docs/source/Tools/Tools.rst b/docs/source/Tools/Tools.rst index 9112b5bbc..5c76f4161 100644 --- a/docs/source/Tools/Tools.rst +++ b/docs/source/Tools/Tools.rst @@ -352,6 +352,29 @@ To use it on other then a 'dummy' task, this option must be checked. Default: unchecked +Try clear I2C bus when stuck +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Added: 2021-09-26 + +Occasionally the I2C bus can become "stuck". +This is quite hard to reproduce, as it is very likely this is caused by external noise. +Another possible cause can be that the ESP rebooted in the middle of an I2C transaction. + +When this happens, the ESP cannot communicate with any I2C device on the bus, until a power cycle. +In every occasion when a user reported this, it appeared the SDA line was held low by an I2C device. + +With this option checked to clear the I2C bus, the ESP will detect if the I2C bus is "stuck" and does make several attempts to clear the I2C bus without the need for a power cycle. + +As a last resort, the user may try to perform an I2C scan via the web interface, which will then perform an even more rigorous attempt to clear the bus. +This will swap the SDA/SCL pins and tries to perform a scan and then restores the correct SDA/SCL assignment. + +If this is the fix, where ESPEasy is not able to resolve the lockec I2C bus on itself, please open an issue for this on GitHub. + +Default: unchecked + + + Deep Sleep Alternative ^^^^^^^^^^^^^^^^^^^^^^ diff --git a/lib/Adafruit_RTClib/src/RTClib.cpp b/lib/Adafruit_RTClib/src/RTClib.cpp index ef8678d1f..22a83ced9 100644 --- a/lib/Adafruit_RTClib/src/RTClib.cpp +++ b/lib/Adafruit_RTClib/src/RTClib.cpp @@ -809,7 +809,7 @@ static uint8_t bin2bcd(uint8_t val) { return val + 6 * (val / 10); } boolean RTC_DS1307::begin(TwoWire *wireInstance) { RTCWireBus = wireInstance; - RTCWireBus->begin(); + //RTCWireBus->begin(); RTCWireBus->beginTransmission(DS1307_ADDRESS); if (RTCWireBus->endTransmission() == 0) return true; @@ -1054,7 +1054,7 @@ DateTime RTC_Micros::now() { boolean RTC_PCF8523::begin(TwoWire *wireInstance) { RTCWireBus = wireInstance; - RTCWireBus->begin(); + //RTCWireBus->begin(); RTCWireBus->beginTransmission(PCF8523_ADDRESS); if (RTCWireBus->endTransmission() == 0) return true; @@ -1403,7 +1403,7 @@ void RTC_PCF8523::calibrate(Pcf8523OffsetMode mode, int8_t offset) { boolean RTC_PCF8563::begin(TwoWire *wireInstance) { RTCWireBus = wireInstance; - RTCWireBus->begin(); + //RTCWireBus->begin(); RTCWireBus->beginTransmission(PCF8563_ADDRESS); if (RTCWireBus->endTransmission() == 0) return true; @@ -1565,7 +1565,7 @@ static uint8_t dowToDS3231(uint8_t d) { return d == 0 ? 7 : d; } boolean RTC_DS3231::begin(TwoWire *wireInstance) { RTCWireBus = wireInstance; - RTCWireBus->begin(); + //RTCWireBus->begin(); RTCWireBus->beginTransmission(DS3231_ADDRESS); if (RTCWireBus->endTransmission() == 0) return true; diff --git a/lib/TinyGPSPlus-1.0.2/src/TinyGPS++.cpp b/lib/TinyGPSPlus-1.0.2/src/TinyGPS++.cpp index a8b1687c5..71b62a9ac 100644 --- a/lib/TinyGPSPlus-1.0.2/src/TinyGPS++.cpp +++ b/lib/TinyGPSPlus-1.0.2/src/TinyGPS++.cpp @@ -28,6 +28,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #include #include +#include + TinyGPSPlus::TinyGPSPlus() : parity(0) , isChecksumTerm(false) diff --git a/platformio_core_defs.ini b/platformio_core_defs.ini index 7477113b2..6641f5722 100644 --- a/platformio_core_defs.ini +++ b/platformio_core_defs.ini @@ -147,11 +147,13 @@ platform = espressif32@1.12.4 platform = espressif32@2.1.0 build_flags = + [core_esp32_3_3_2] -platform = https://github.com/platformio/platform-espressif32.git#feature/arduino-idf-master -platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/424/framework-arduinoespressif32-master-cdcf92440.tar.gz +platform = espressif32 @ 3.3.2 +platform_packages = framework-arduinoespressif32 build_flags = -DESP32_STAGE + [core_esp32_3_3_2_esp32s2] platform = https://github.com/platformio/platform-espressif32.git#feature/arduino-idf-master platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/arduino-esp32/releases/download/v.2.0-post/framework-arduinoespressif32_i2c.zip @@ -163,7 +165,6 @@ platform = espressif32@3.0.0 platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/arduino-esp32/releases/download/1.0.5-rc6/esp32-1.0.5-rc6.zip - [core_esp32_stage] platform = espressif32 platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/arduino-esp32/releases/download/v.2.0-post/framework-arduinoespressif32_i2c.zip diff --git a/platformio_esp32_envs.ini b/platformio_esp32_envs.ini index de66b1c9e..cacabb8e8 100644 --- a/platformio_esp32_envs.ini +++ b/platformio_esp32_envs.ini @@ -50,6 +50,7 @@ build_flags = ${esp32_common.build_flags} board = esp32-s2-saola-1 extra_scripts = ${esp32_common.extra_scripts} pre:tools/pio/pre_custom_esp32.py +platform = ${core_esp32_3_3_2_esp32s2.platform} platform_packages = ${core_esp32_3_3_2_esp32s2.platform_packages} diff --git a/src/ESPEasy-Globals.cpp b/src/ESPEasy-Globals.cpp index 61d955b33..dd03cf830 100644 --- a/src/ESPEasy-Globals.cpp +++ b/src/ESPEasy-Globals.cpp @@ -26,10 +26,6 @@ float vcc = -1.0f; #endif -String dummyString; // FIXME @TD-er This may take a lot of memory over time, since long-lived Strings only tend to grow. - - - diff --git a/src/ESPEasy-Globals.h b/src/ESPEasy-Globals.h index 37908d050..e728cdc21 100644 --- a/src/ESPEasy-Globals.h +++ b/src/ESPEasy-Globals.h @@ -93,10 +93,6 @@ extern float vcc; #endif -extern String dummyString; // FIXME @TD-er This may take a lot of memory over time, since long-lived Strings only tend to grow. - - - extern bool shouldReboot; extern bool firstLoop; diff --git a/src/_P009_MCP.ino b/src/_P009_MCP.ino index 7b1149359..b2c4385a9 100644 --- a/src/_P009_MCP.ino +++ b/src/_P009_MCP.ino @@ -2,9 +2,9 @@ #ifdef USES_P009 -#include "src/DataStructs/PinMode.h" -#include "src/Commands/GPIO.h" -#include "src/ESPEasyCore/ESPEasyGPIO.h" +# include "src/DataStructs/PinMode.h" +# include "src/Commands/GPIO.h" +# include "src/ESPEasyCore/ESPEasyGPIO.h" // ####################################################################################################### // #################################### Plugin 009: MCP23017 input ####################################### @@ -35,22 +35,22 @@ 3: safebutton counter (=0,1) \**************************************************/ -#define PLUGIN_009 -#define PLUGIN_ID_009 9 -#define PLUGIN_NAME_009 "Switch input - MCP23017" -#define PLUGIN_VALUENAME1_009 "State" -#define PLUGIN_009_DOUBLECLICK_MIN_INTERVAL 1000 -#define PLUGIN_009_DOUBLECLICK_MAX_INTERVAL 3000 -#define PLUGIN_009_LONGPRESS_MIN_INTERVAL 1000 -#define PLUGIN_009_LONGPRESS_MAX_INTERVAL 5000 -#define PLUGIN_009_DC_DISABLED 0 -#define PLUGIN_009_DC_LOW 1 -#define PLUGIN_009_DC_HIGH 2 -#define PLUGIN_009_DC_BOTH 3 -#define PLUGIN_009_LONGPRESS_DISABLED 0 -#define PLUGIN_009_LONGPRESS_LOW 1 -#define PLUGIN_009_LONGPRESS_HIGH 2 -#define PLUGIN_009_LONGPRESS_BOTH 3 +# define PLUGIN_009 +# define PLUGIN_ID_009 9 +# define PLUGIN_NAME_009 "Switch input - MCP23017" +# define PLUGIN_VALUENAME1_009 "State" +# define PLUGIN_009_DOUBLECLICK_MIN_INTERVAL 1000 +# define PLUGIN_009_DOUBLECLICK_MAX_INTERVAL 3000 +# define PLUGIN_009_LONGPRESS_MIN_INTERVAL 1000 +# define PLUGIN_009_LONGPRESS_MAX_INTERVAL 5000 +# define PLUGIN_009_DC_DISABLED 0 +# define PLUGIN_009_DC_LOW 1 +# define PLUGIN_009_DC_HIGH 2 +# define PLUGIN_009_DC_BOTH 3 +# define PLUGIN_009_LONGPRESS_DISABLED 0 +# define PLUGIN_009_LONGPRESS_LOW 1 +# define PLUGIN_009_LONGPRESS_HIGH 2 +# define PLUGIN_009_LONGPRESS_BOTH 3 boolean Plugin_009(uint8_t function, struct EventStruct *event, String& string) { @@ -65,7 +65,7 @@ boolean Plugin_009(uint8_t function, struct EventStruct *event, String& string) Device[++deviceCount].Number = PLUGIN_ID_009; Device[deviceCount].Type = DEVICE_TYPE_I2C; Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_SWITCH; - Device[deviceCount].Ports = 16; + Device[deviceCount].Ports = 0; Device[deviceCount].PullUpOption = false; Device[deviceCount].InverseLogicOption = true; Device[deviceCount].FormulaOption = false; @@ -90,9 +90,28 @@ boolean Plugin_009(uint8_t function, struct EventStruct *event, String& string) } case PLUGIN_I2C_HAS_ADDRESS: + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: { - const int i2cAddressValues[] = { 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27 }; - success = intArrayContains(8, i2cAddressValues, event->Par1); + const uint8_t i2cAddressValues[] = { 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27 }; + + if (function == PLUGIN_WEBFORM_SHOW_I2C_PARAMS) { + String portNames[16]; + int portValues[16]; + uint8_t unit = (CONFIG_PORT - 1) / 16; + uint8_t port = CONFIG_PORT - (unit * 16); + uint8_t address = 0x20 + unit; + + for (uint8_t x = 0; x < 16; x++) { + portValues[x] = x + 1; + portNames[x] = 'P'; + portNames[x] += (x < 8 ? 'A' : 'B'); + portNames[x] += (x < 8 ? x : x - 8); + } + addFormSelectorI2C(F("p009_i2c"), 8, i2cAddressValues, address); + addFormSelector(F("Port"), F("p009_port"), 16, portNames, portValues, port); + } else { + success = intArrayContains(8, i2cAddressValues, event->Par1); + } break; } @@ -102,6 +121,7 @@ boolean Plugin_009(uint8_t function, struct EventStruct *event, String& string) const uint32_t key = createKey(PLUGIN_ID_009, CONFIG_PORT); auto it = globalMapPortStatus.find(key); + if (it != globalMapPortStatus.end()) { it->second.previousTask = event->TaskIndex; } @@ -118,15 +138,14 @@ boolean Plugin_009(uint8_t function, struct EventStruct *event, String& string) PCONFIG_FLOAT(1) = PLUGIN_009_DOUBLECLICK_MIN_INTERVAL; } - uint8_t choiceDC = PCONFIG(4); { - const __FlashStringHelper * buttonDC[4]; + const __FlashStringHelper *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); + addFormSelector(F("Doubleclick event"), F("p009_dc"), 4, buttonDC, buttonDCValues, PCONFIG(4)); } addFormNumericBox(F("Doubleclick max. interval (ms)"), @@ -140,10 +159,9 @@ boolean Plugin_009(uint8_t function, struct EventStruct *event, String& string) PCONFIG_FLOAT(2) = PLUGIN_009_LONGPRESS_MIN_INTERVAL; } - + { - uint8_t choiceLP = PCONFIG(5); - const __FlashStringHelper * buttonLP[4]; + const __FlashStringHelper *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])"); @@ -151,7 +169,7 @@ boolean Plugin_009(uint8_t function, struct EventStruct *event, String& string) 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); + addFormSelector(F("Longpress event"), F("p009_lp"), 4, buttonLP, buttonLPValues, PCONFIG(5)); } addFormNumericBox(F("Longpress min. interval (ms)"), @@ -168,6 +186,10 @@ boolean Plugin_009(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_WEBFORM_SAVE: { + uint8_t i2c = getFormItemInt(F("p009_i2c")); + uint8_t port = getFormItemInt(F("p009_port")); + CONFIG_PORT = (((i2c - 0x20) << 4) + port); + PCONFIG(0) = isFormItemChecked(F("p009_boot")); // @giig1967-20181022 @@ -209,11 +231,12 @@ boolean Plugin_009(uint8_t function, struct EventStruct *event, String& string) // read and store current state to prevent switching at boot time // "state" could be -1, 0 or 1 - newStatus.state = GPIO_MCP_Read(CONFIG_PORT); + newStatus.state = GPIO_MCP_Read(CONFIG_PORT); + if (loglevelActiveFor(LOG_LEVEL_INFO)) { String log = F("MCP INIT="); log += newStatus.state; - addLog(LOG_LEVEL_INFO,log); + addLog(LOG_LEVEL_INFO, log); } newStatus.output = newStatus.state; (newStatus.state == -1) ? newStatus.mode = PIN_MODE_OFFLINE : newStatus.mode = PIN_MODE_INPUT_PULLUP; // @giig1967g: if it is in the @@ -265,12 +288,13 @@ boolean Plugin_009(uint8_t function, struct EventStruct *event, String& string) } case PLUGIN_TEN_PER_SECOND: - { - const int8_t state = GPIO_MCP_Read(CONFIG_PORT); - const String monitorEventString = F("MCP"); - /**************************************************************************\ - 20181022 - @giig1967g: new doubleclick logic is: - if there is a 'state' change, check debounce period. + { + const int8_t state = GPIO_MCP_Read(CONFIG_PORT); + const String monitorEventString = F("MCP"); + + /**************************************************************************\ + 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 @@ -321,15 +345,15 @@ boolean Plugin_009(uint8_t function, struct EventStruct *event, String& string) } // just to simplify the reading of the code -#define COUNTER PCONFIG(7) -#define DC PCONFIG(4) +# 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)++; } -#undef DC -#undef COUNTER +# undef DC +# undef COUNTER // switchstate[event->TaskIndex] = state; if ((currentStatus.mode == PIN_MODE_OFFLINE) || @@ -364,10 +388,12 @@ boolean Plugin_009(uint8_t function, struct EventStruct *event, String& string) log += output_value; addLog(LOG_LEVEL_INFO, log); } + // send task event sendData(event); + // send monitor event - if (currentStatus.monitor) sendMonitorEvent(monitorEventString.c_str(), CONFIG_PORT, output_value); + if (currentStatus.monitor) { sendMonitorEvent(monitorEventString.c_str(), CONFIG_PORT, output_value); } // Reset forceEvent currentStatus.forceEvent = 0; @@ -378,13 +404,13 @@ boolean Plugin_009(uint8_t function, struct EventStruct *event, String& string) } // just to simplify the reading of the code -#define LP PCONFIG(5) -#define FIRED PCONFIG(6) +# 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)))) { -#undef LP -#undef FIRED +# undef LP +# undef FIRED /**************************************************************************\ 20181022 - @giig1967g: new longpress logic is: @@ -429,10 +455,12 @@ boolean Plugin_009(uint8_t function, struct EventStruct *event, String& string) log += output_value; addLog(LOG_LEVEL_INFO, log); } + // send task event sendData(event); + // send monitor event - if (currentStatus.monitor) sendMonitorEvent(monitorEventString.c_str(), CONFIG_PORT, output_value); + if (currentStatus.monitor) { sendMonitorEvent(monitorEventString.c_str(), CONFIG_PORT, output_value); } // reset Userdata so it displays the correct state value in the web page UserVar[event->BaseVarIndex] = sendState ? 1 : 0; @@ -456,7 +484,7 @@ boolean Plugin_009(uint8_t function, struct EventStruct *event, String& string) // send task event: DO NOT SEND TASK EVENT //sendData(event); // send monitor event - if (currentStatus.monitor) sendMonitorEvent(monitorEventString.c_str(), CONFIG_PORT, 4); + if (currentStatus.monitor) { sendMonitorEvent(monitorEventString.c_str(), CONFIG_PORT, 4); } // reset Userdata so it displays the correct state value in the web page UserVar[event->BaseVarIndex] = tempUserVar; @@ -474,10 +502,12 @@ boolean Plugin_009(uint8_t function, struct EventStruct *event, String& string) log += F(" is offline (EVENT= -1)"); addLog(LOG_LEVEL_INFO, log); } + // send task event sendData(event); + // send monitor event - if (currentStatus.monitor) sendMonitorEvent(monitorEventString.c_str(), CONFIG_PORT, -1); + if (currentStatus.monitor) { sendMonitorEvent(monitorEventString.c_str(), CONFIG_PORT, -1); } savePortStatus(key, currentStatus); } @@ -528,8 +558,8 @@ boolean Plugin_009(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_WRITE: { - //String log; - //String command = parseString(string, 1); + // String log; + // String command = parseString(string, 1); break; } @@ -576,64 +606,66 @@ boolean Plugin_009(uint8_t function, struct EventStruct *event, String& string) // ******************************************************************************** // MCP23017 read // ******************************************************************************** -/* -int8_t Plugin_009_Read(uint8_t Par1) -{ - int8_t state = -1; - uint8_t unit = (Par1 - 1) / 16; - uint8_t port = Par1 - (unit * 16); - uint8_t address = 0x20 + unit; - uint8_t IOBankValueReg = 0x12; - if (port > 8) - { +/* + int8_t Plugin_009_Read(uint8_t Par1) + { + int8_t state = -1; + uint8_t unit = (Par1 - 1) / 16; + uint8_t port = Par1 - (unit * 16); + uint8_t address = 0x20 + unit; + uint8_t 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); + // 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()) - { + if (Wire.available()) + { state = ((Wire.read() & _BV(port - 1)) >> (port - 1)); - } - return state; -} -*/ + } + return state; + } + */ // ******************************************************************************** // MCP23017 write // ******************************************************************************** -/* -boolean Plugin_009_Write(uint8_t Par1, uint8_t Par2) -{ - boolean success = false; - uint8_t portvalue = 0; - uint8_t unit = (Par1 - 1) / 16; - uint8_t port = Par1 - (unit * 16); - uint8_t address = 0x20 + unit; - uint8_t IOBankConfigReg = 0; - uint8_t IOBankValueReg = 0x12; - if (port > 8) - { +/* + boolean Plugin_009_Write(uint8_t Par1, uint8_t Par2) + { + boolean success = false; + uint8_t portvalue = 0; + uint8_t unit = (Par1 - 1) / 16; + uint8_t port = Par1 - (unit * 16); + uint8_t address = 0x20 + unit; + uint8_t IOBankConfigReg = 0; + uint8_t 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); + // 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()) - { + if (Wire.available()) + { portvalue = Wire.read(); portvalue &= ~(1 << (port - 1)); // change pin from (default) input to output @@ -642,16 +674,16 @@ boolean Plugin_009_Write(uint8_t Par1, uint8_t Par2) Wire.write(IOBankConfigReg); // IO config register 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); + // 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()) - { + if (Wire.available()) + { portvalue = Wire.read(); if (Par2 == 1) { @@ -667,37 +699,39 @@ boolean Plugin_009_Write(uint8_t Par1, uint8_t Par2) Wire.write(portvalue); Wire.endTransmission(); success = true; - } - return success; -} -*/ + } + return success; + } + */ + // ******************************************************************************** // MCP23017 config // ******************************************************************************** -/* -void Plugin_009_Config(uint8_t Par1, uint8_t Par2) -{ - // boolean success = false; - uint8_t portvalue = 0; - uint8_t unit = (Par1 - 1) / 16; - uint8_t port = Par1 - (unit * 16); - uint8_t address = 0x20 + unit; - uint8_t IOBankConfigReg = 0xC; - if (port > 8) - { +/* + void Plugin_009_Config(uint8_t Par1, uint8_t Par2) + { + // boolean success = false; + uint8_t portvalue = 0; + uint8_t unit = (Par1 - 1) / 16; + uint8_t port = Par1 - (unit * 16); + uint8_t address = 0x20 + unit; + uint8_t 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); + // turn this port pullup on + Wire.beginTransmission(address); + Wire.write(IOBankConfigReg); + Wire.endTransmission(); + Wire.requestFrom(address, (uint8_t)0x1); - if (Wire.available()) - { + if (Wire.available()) + { portvalue = Wire.read(); if (Par2 == 1) { @@ -712,7 +746,7 @@ void Plugin_009_Config(uint8_t Par1, uint8_t Par2) Wire.write(IOBankConfigReg); // IO config register Wire.write(portvalue); Wire.endTransmission(); - } -} -*/ + } + } + */ #endif // USES_P009 diff --git a/src/_P011_PME.ino b/src/_P011_PME.ino index 3df9a1ee0..21f4c3616 100644 --- a/src/_P011_PME.ino +++ b/src/_P011_PME.ino @@ -1,8 +1,6 @@ #include "_Plugin_Helper.h" #ifdef USES_P011 -#include "ESPEasy-Globals.h" // For dummyString - // ####################################################################################################### // #################################### Plugin 011: Pro Mini Extender #################################### @@ -228,6 +226,7 @@ boolean Plugin_011(uint8_t function, struct EventStruct *event, String& string) { success = true; const uint32_t key = createKey(PLUGIN_ID_011, event->Par2); // WARNING: 'status' uses Par2 instead of Par1 + String dummyString; if (!existPortStatus(key)) { // tempStatus.mode == PIN_MODE_OUTPUT) // has been set as output SendStatusOnlyIfNeeded(event, SEARCH_PIN_STATE, key, dummyString, 0); diff --git a/src/_P013_HCSR04.ino b/src/_P013_HCSR04.ino index 366072f17..eb795376c 100644 --- a/src/_P013_HCSR04.ino +++ b/src/_P013_HCSR04.ino @@ -1,40 +1,41 @@ #include "_Plugin_Helper.h" #ifdef USES_P013 -//####################################################################################################### -//############################### Plugin 013: HC-SR04, RCW-0001, etc. ################################### -//####################################################################################################### + +// ####################################################################################################### +// ############################### Plugin 013: HC-SR04, RCW-0001, etc. ################################### +// ####################################################################################################### -#define PLUGIN_013 -#define PLUGIN_ID_013 13 -#define PLUGIN_NAME_013 "Position - HC-SR04, RCW-0001, etc." -#define PLUGIN_VALUENAME1_013 "Distance" +# define PLUGIN_013 +# define PLUGIN_ID_013 13 +# define PLUGIN_NAME_013 "Position - HC-SR04, RCW-0001, etc." +# define PLUGIN_VALUENAME1_013 "Distance" -#include -#include -#include +# include +# include +# include // PlugIn specific defines // operatingMode -#define OPMODE_VALUE (0) -#define OPMODE_STATE (1) +# define OPMODE_VALUE (0) +# define OPMODE_STATE (1) // measuringUnit -#define UNIT_CM (0) -#define UNIT_INCH (1) +# define UNIT_CM (0) +# define UNIT_INCH (1) // filterType -#define FILTER_NONE (0) -#define FILTER_MEDIAN (1) +# define FILTER_NONE (0) +# define FILTER_MEDIAN (1) // map of sensors std::map > P_013_sensordefs; // Forward declaration -const __FlashStringHelper * Plugin_013_getErrorStatusString(taskIndex_t taskIndex); +const __FlashStringHelper* Plugin_013_getErrorStatusString(taskIndex_t taskIndex); -boolean Plugin_013(uint8_t function, struct EventStruct *event, String& string) +boolean Plugin_013(uint8_t function, struct EventStruct *event, String& string) { static uint8_t switchstate[TASKS_MAX]; boolean success = false; @@ -42,273 +43,278 @@ boolean Plugin_013(uint8_t function, struct EventStruct *event, String& string) switch (function) { case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_013; - Device[deviceCount].Type = DEVICE_TYPE_DUAL; - Device[deviceCount].VType = Sensor_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].Number = PLUGIN_ID_013; + Device[deviceCount].Type = DEVICE_TYPE_DUAL; + Device[deviceCount].VType = Sensor_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; - } + break; + } case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_013); - break; - } + { + string = F(PLUGIN_NAME_013); + break; + } case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_013)); - break; - } + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_013)); + break; + } case PLUGIN_GET_DEVICEGPIONAMES: - { - event->String1 = formatGpioName_output(F("Trigger")); - event->String2 = formatGpioName_input(F("Echo, 5V")); - break; - } + { + event->String1 = formatGpioName_output(F("Trigger")); + event->String2 = formatGpioName_input(F("Echo, 5V")); + break; + } case PLUGIN_WEBFORM_LOAD: - { - int16_t operatingMode = PCONFIG(0); - int16_t threshold = PCONFIG(1); - int16_t max_distance = PCONFIG(2); - int16_t measuringUnit = PCONFIG(3); - int16_t filterType = PCONFIG(4); - int16_t filterSize = PCONFIG(5); + { + int16_t operatingMode = PCONFIG(0); + int16_t threshold = PCONFIG(1); + int16_t max_distance = PCONFIG(2); + int16_t measuringUnit = PCONFIG(3); + int16_t filterType = PCONFIG(4); + int16_t filterSize = PCONFIG(5); - // default filtersize = 5 - if (filterSize == 0) { - filterSize = 5; - PCONFIG(5) = filterSize; - } - - - String strUnit = (measuringUnit == UNIT_CM) ? F("cm") : F("inch"); - - { - const __FlashStringHelper * optionsOpMode[2]; - int optionValuesOpMode[2] = { 0, 1 }; - optionsOpMode[0] = F("Value"); - optionsOpMode[1] = F("State"); - addFormSelector(F("Mode"), F("p013_mode"), 2, optionsOpMode, optionValuesOpMode, operatingMode); - } - - if (operatingMode == OPMODE_STATE) - { - addFormNumericBox(F("Threshold"), F("p013_threshold"), threshold); - addUnit(strUnit); - } - addFormNumericBox(F("Max Distance"), F("p013_max_distance"), max_distance, 0, 500); - addUnit(strUnit); - - { - const __FlashStringHelper * optionsUnit[2]; - int optionValuesUnit[2] = { 0, 1 }; - optionsUnit[0] = F("Metric"); - optionsUnit[1] = F("Imperial"); - addFormSelector(F("Unit"), F("p013_Unit"), 2, optionsUnit, optionValuesUnit, measuringUnit); - } - - { - const __FlashStringHelper * optionsFilter[2]; - int optionValuesFilter[2] = { 0, 1 }; - optionsFilter[0] = F("None"); - optionsFilter[1] = F("Median"); - addFormSelector(F("Filter"), F("p013_FilterType"), 2, optionsFilter, optionValuesFilter, filterType); - } - - // enable filtersize option if filter is used, - if (filterType != FILTER_NONE) - addFormNumericBox(F("Number of Pings"), F("p013_FilterSize"), filterSize, 2, 20); - - success = true; - break; + // default filtersize = 5 + if (filterSize == 0) { + filterSize = 5; + PCONFIG(5) = filterSize; } + + String strUnit = (measuringUnit == UNIT_CM) ? F("cm") : F("inch"); + + { + const __FlashStringHelper *optionsOpMode[2]; + int optionValuesOpMode[2] = { 0, 1 }; + optionsOpMode[0] = F("Value"); + optionsOpMode[1] = F("State"); + addFormSelector(F("Mode"), F("p013_mode"), 2, optionsOpMode, optionValuesOpMode, operatingMode); + } + + if (operatingMode == OPMODE_STATE) + { + addFormNumericBox(F("Threshold"), F("p013_threshold"), threshold); + addUnit(strUnit); + } + addFormNumericBox(F("Max Distance"), F("p013_max_distance"), max_distance, 0, 500); + addUnit(strUnit); + + { + const __FlashStringHelper *optionsUnit[2]; + int optionValuesUnit[2] = { 0, 1 }; + optionsUnit[0] = F("Metric"); + optionsUnit[1] = F("Imperial"); + addFormSelector(F("Unit"), F("p013_Unit"), 2, optionsUnit, optionValuesUnit, measuringUnit); + } + + { + const __FlashStringHelper *optionsFilter[2]; + int optionValuesFilter[2] = { 0, 1 }; + optionsFilter[0] = F("None"); + optionsFilter[1] = F("Median"); + addFormSelector(F("Filter"), F("p013_FilterType"), 2, optionsFilter, optionValuesFilter, filterType); + } + + // enable filtersize option if filter is used, + if (filterType != FILTER_NONE) { + addFormNumericBox(F("Number of Pings"), F("p013_FilterSize"), filterSize, 2, 20); + } + + success = true; + break; + } + case PLUGIN_WEBFORM_SAVE: - { - int16_t operatingMode = PCONFIG(0); - int16_t filterType = PCONFIG(4); + { + int16_t operatingMode = PCONFIG(0); + int16_t filterType = PCONFIG(4); - PCONFIG(0) = getFormItemInt(F("p013_mode")); - if (operatingMode == OPMODE_STATE) - PCONFIG(1) = getFormItemInt(F("p013_threshold")); - PCONFIG(2) = getFormItemInt(F("p013_max_distance")); + PCONFIG(0) = getFormItemInt(F("p013_mode")); - PCONFIG(3) = getFormItemInt(F("p013_Unit")); - PCONFIG(4) = getFormItemInt(F("p013_FilterType")); - if (filterType != FILTER_NONE) - PCONFIG(5) = getFormItemInt(F("p013_FilterSize")); + if (operatingMode == OPMODE_STATE) { + PCONFIG(1) = getFormItemInt(F("p013_threshold")); + } + PCONFIG(2) = getFormItemInt(F("p013_max_distance")); - success = true; - break; + PCONFIG(3) = getFormItemInt(F("p013_Unit")); + PCONFIG(4) = getFormItemInt(F("p013_FilterType")); + + if (filterType != FILTER_NONE) { + PCONFIG(5) = getFormItemInt(F("p013_FilterSize")); } + success = true; + break; + } + case PLUGIN_INIT: + { + int16_t max_distance = PCONFIG(2); + int16_t measuringUnit = PCONFIG(3); + int16_t filterType = PCONFIG(4); + int16_t filterSize = PCONFIG(5); + + int8_t Plugin_013_TRIG_Pin = CONFIG_PIN1; + int8_t Plugin_013_IRQ_Pin = CONFIG_PIN2; + int16_t max_distance_cm = (measuringUnit == UNIT_CM) ? max_distance : static_cast(max_distance) * 2.54f; + + // create sensor instance and add to std::map + P_013_sensordefs.erase(event->TaskIndex); + P_013_sensordefs[event->TaskIndex] = + std::shared_ptr(new NewPingESP8266(Plugin_013_TRIG_Pin, Plugin_013_IRQ_Pin, max_distance_cm)); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = F("ULTRASONIC : TaskNr: "); + log += event->TaskIndex + 1; + log += F(" TrigPin: "); + log += Plugin_013_TRIG_Pin; + log += F(" IRQ_Pin: "); + log += Plugin_013_IRQ_Pin; + log += F(" max dist "); + log += (measuringUnit == UNIT_CM) ? F("[cm]: ") : F("[inch]: "); + log += max_distance; + log += F(" max echo: "); + log += P_013_sensordefs[event->TaskIndex]->getMaxEchoTime(); + log += F(" Filter: "); + + if (filterType == FILTER_NONE) { + log += F("none"); + } + else + if (filterType == FILTER_MEDIAN) { + log += F("Median size: "); + log += filterSize; + } + else { + log += F("invalid!"); + } + log += F(" nr_tasks: "); + log += P_013_sensordefs.size(); + addLog(LOG_LEVEL_INFO, log); + } + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + // FIXME TD-er: What kind of nonsense code is this? + unsigned long tmpmillis = millis(); + unsigned long tmpmicros = micros(); + delay(100); + long millispassed = timePassedSince(tmpmillis); + long microspassed = usecPassedSince(tmpmicros); + + String log = F("ULTRASONIC : micros() test: "); + log += millispassed; + log += F(" msec, "); + log += microspassed; + log += F(" usec, "); + addLog(LOG_LEVEL_INFO, log); + } + + success = true; + break; + } + + case PLUGIN_EXIT: + { + P_013_sensordefs.erase(event->TaskIndex); + break; + } + + case PLUGIN_READ: // If we select value mode, read and send the value based on global timer + { + int16_t operatingMode = PCONFIG(0); + int16_t measuringUnit = PCONFIG(3); + + if (operatingMode == OPMODE_VALUE) { - int16_t max_distance = PCONFIG(2); - int16_t measuringUnit = PCONFIG(3); - int16_t filterType = PCONFIG(4); - int16_t filterSize = PCONFIG(5); - - int8_t Plugin_013_TRIG_Pin = CONFIG_PIN1; - int8_t Plugin_013_IRQ_Pin = CONFIG_PIN2; - int16_t max_distance_cm = (measuringUnit == UNIT_CM) ? max_distance : static_cast(max_distance) * 2.54f; - - // create sensor instance and add to std::map - P_013_sensordefs.erase(event->TaskIndex); - P_013_sensordefs[event->TaskIndex] = - std::shared_ptr (new NewPingESP8266(Plugin_013_TRIG_Pin, Plugin_013_IRQ_Pin, max_distance_cm)); + const float value = Plugin_013_read(event->TaskIndex); + UserVar[event->BaseVarIndex] = value; if (loglevelActiveFor(LOG_LEVEL_INFO)) { String log = F("ULTRASONIC : TaskNr: "); - log += event->TaskIndex +1; - log += F(" TrigPin: "); - log += Plugin_013_TRIG_Pin; - log += F(" IRQ_Pin: "); - log += Plugin_013_IRQ_Pin; - log += F(" max dist "); - log += (measuringUnit == UNIT_CM) ? F("[cm]: ") : F("[inch]: "); - log += max_distance; - log += F(" max echo: "); - log += P_013_sensordefs[event->TaskIndex]->getMaxEchoTime(); - log += F(" Filter: "); - if (filterType == FILTER_NONE) - log += F("none"); - else - if (filterType == FILTER_MEDIAN) { - log += F("Median size: "); - log += filterSize; - } - else - log += F("invalid!"); - log += F(" nr_tasks: "); - log += P_013_sensordefs.size(); - addLog(LOG_LEVEL_INFO, log); - } + log += event->TaskIndex + 1; + log += F(" Distance: "); + log += formatUserVarNoCheck(event->TaskIndex, 0); + log += (measuringUnit == UNIT_CM) ? F(" cm ") : F(" inch "); - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - // FIXME TD-er: What kind of nonsense code is this? - unsigned long tmpmillis = millis(); - unsigned long tmpmicros = micros(); - delay(100); - long millispassed = timePassedSince(tmpmillis); - long microspassed = usecPassedSince(tmpmicros); - - String log = F("ULTRASONIC : micros() test: "); - log += millispassed; - log += F(" msec, "); - log += microspassed; - log += F(" usec, "); - addLog(LOG_LEVEL_INFO, log); - } - - success = true; - break; - } - - case PLUGIN_EXIT: - { - P_013_sensordefs.erase(event->TaskIndex); - break; - } - - case PLUGIN_READ: // If we select value mode, read and send the value based on global timer - { - int16_t operatingMode = PCONFIG(0); - int16_t measuringUnit = PCONFIG(3); - - if (operatingMode == OPMODE_VALUE) - { - const float value = Plugin_013_read(event->TaskIndex); - UserVar[event->BaseVarIndex] = value; - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("ULTRASONIC : TaskNr: "); - log += event->TaskIndex +1; - log += F(" Distance: "); - log += formatUserVarNoCheck(event->TaskIndex, 0); - log += (measuringUnit == UNIT_CM) ? F(" cm ") : F(" inch "); - if (value == NO_ECHO) - { - log += F(" Error: "); - log += Plugin_013_getErrorStatusString(event->TaskIndex); - } - - addLog(LOG_LEVEL_INFO,log); + if (value == NO_ECHO) + { + log += F(" Error: "); + log += Plugin_013_getErrorStatusString(event->TaskIndex); } + + addLog(LOG_LEVEL_INFO, log); } - success = true; - break; } + success = true; + break; + } case PLUGIN_TEN_PER_SECOND: // If we select state mode, do more frequent checks and send only state changes - { - int16_t operatingMode = PCONFIG(0); - int16_t threshold = PCONFIG(1); + { + int16_t operatingMode = PCONFIG(0); + int16_t threshold = PCONFIG(1); - if (operatingMode == OPMODE_STATE) - { - uint8_t state = 0; - float value = Plugin_013_read(event->TaskIndex); - if (value != NO_ECHO) - { - if (value < threshold) - state = 1; - if (state != switchstate[event->TaskIndex]) - { - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("ULTRASONIC : TaskNr: "); - log += event->TaskIndex +1; - log += F(" state: "); - log += state; - addLog(LOG_LEVEL_INFO,log); - } - switchstate[event->TaskIndex] = state; - UserVar[event->BaseVarIndex] = state; - event->sensorType = Sensor_VType::SENSOR_TYPE_SWITCH; - sendData(event); - } - } - else { - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("ULTRASONIC : TaskNr: "); - log += event->TaskIndex +1; + if (operatingMode == OPMODE_STATE) { + uint8_t state = 0; + float value = Plugin_013_read(event->TaskIndex); + + if ((value != NO_ECHO) && (value < threshold)) { + state = 1; + } + + if (state != switchstate[event->TaskIndex]) { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = F("ULTRASONIC : TaskNr: "); + log += event->TaskIndex + 1; + + if (value != NO_ECHO) { + log += F(" state: "); + log += state; + } else { log += F(" Error: "); log += Plugin_013_getErrorStatusString(event->TaskIndex); - addLog(LOG_LEVEL_INFO,log); } + addLog(LOG_LEVEL_INFO, log); } - + switchstate[event->TaskIndex] = state; + UserVar[event->BaseVarIndex] = state; + event->sensorType = Sensor_VType::SENSOR_TYPE_SWITCH; + sendData(event); } - success = true; - break; } + success = true; + break; + } } return success; } /*********************************************************************/ float Plugin_013_read(taskIndex_t taskIndex) + /*********************************************************************/ { - if (P_013_sensordefs.count(taskIndex) == 0) + if (P_013_sensordefs.count(taskIndex) == 0) { return 0; + } - int16_t max_distance = Settings.TaskDevicePluginConfig[taskIndex][2]; - int16_t measuringUnit = Settings.TaskDevicePluginConfig[taskIndex][3]; - int16_t filterType = Settings.TaskDevicePluginConfig[taskIndex][4]; - int16_t filterSize = Settings.TaskDevicePluginConfig[taskIndex][5]; + int16_t max_distance = Settings.TaskDevicePluginConfig[taskIndex][2]; + int16_t measuringUnit = Settings.TaskDevicePluginConfig[taskIndex][3]; + int16_t filterType = Settings.TaskDevicePluginConfig[taskIndex][4]; + int16_t filterSize = Settings.TaskDevicePluginConfig[taskIndex][5]; int16_t max_distance_cm = (measuringUnit == UNIT_CM) ? max_distance : static_cast(max_distance) * 2.54f; unsigned int echoTime = 0; @@ -324,18 +330,22 @@ float Plugin_013_read(taskIndex_t taskIndex) addLog(LOG_LEVEL_ERROR, F("invalid Filter Type setting!")); } - if (measuringUnit == UNIT_CM) + if (measuringUnit == UNIT_CM) { return NewPingESP8266::convert_cm_F(echoTime); - else + } + else { return NewPingESP8266::convert_in_F(echoTime); + } } /*********************************************************************/ -const __FlashStringHelper * Plugin_013_getErrorStatusString(taskIndex_t taskIndex) +const __FlashStringHelper* Plugin_013_getErrorStatusString(taskIndex_t taskIndex) + /*********************************************************************/ { - if (P_013_sensordefs.count(taskIndex) == 0) + if (P_013_sensordefs.count(taskIndex) == 0) { return F("invalid taskindex"); + } switch ((P_013_sensordefs[taskIndex])->getErrorState()) { case NewPingESP8266::STATUS_SENSOR_READY: { @@ -369,7 +379,7 @@ const __FlashStringHelper * Plugin_013_getErrorStatusString(taskIndex_t taskInde default: { return F("unknown error"); } - } } + #endif // USES_P013 diff --git a/src/_P019_PCF8574.ino b/src/_P019_PCF8574.ino index 3a0935f8f..7badf7847 100644 --- a/src/_P019_PCF8574.ino +++ b/src/_P019_PCF8574.ino @@ -62,7 +62,7 @@ boolean Plugin_019(uint8_t function, struct EventStruct *event, String& string) Device[++deviceCount].Number = PLUGIN_ID_019; Device[deviceCount].Type = DEVICE_TYPE_I2C; Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_SWITCH; - Device[deviceCount].Ports = 8; + Device[deviceCount].Ports = 0; Device[deviceCount].PullUpOption = false; Device[deviceCount].InverseLogicOption = true; Device[deviceCount].FormulaOption = false; @@ -87,9 +87,29 @@ boolean Plugin_019(uint8_t function, struct EventStruct *event, String& string) } case PLUGIN_I2C_HAS_ADDRESS: + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: { - const int i2cAddressValues[] = { 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27 }; - success = intArrayContains(8, i2cAddressValues, event->Par1); + const uint8_t i2cAddressValues[] = { 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f }; + if (function == PLUGIN_WEBFORM_SHOW_I2C_PARAMS) { + String portNames[8]; + int portValues[8]; + uint8_t unit = (CONFIG_PORT - 1) / 8; + uint8_t port = CONFIG_PORT - (unit * 8); + uint8_t address = 0x20 + unit; + + if (unit > 7) { address += 0x10; } + + for (uint8_t x = 0; x < 8; x++) { + portValues[x] = x + 1; + portNames[x] = 'P'; + portNames[x] += x; + } + addFormSelectorI2C(F("plugin_019_i2c"), 16, i2cAddressValues, address); + addFormSelector(F("Port"), F("plugin_019_port"), 8, portNames, portValues, port); + addFormNote(F("PCF8574 uses addresses 0x20..0x27, PCF8574A uses addresses 0x38..0x3F.")); + } else { + success = intArrayContains(16, i2cAddressValues, event->Par1); + } break; } @@ -163,6 +183,13 @@ boolean Plugin_019(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_WEBFORM_SAVE: { + uint8_t i2c = getFormItemInt(F("plugin_019_i2c")); + + if (i2c > 0x27) { i2c -= 0x10; } + + uint8_t port = getFormItemInt(F("plugin_019_port")); + CONFIG_PORT = (((i2c - 0x20) << 3) + port); + PCONFIG(0) = isFormItemChecked(F("p019_boot")); // @giig1967-20181022 diff --git a/src/_P022_PCA9685.ino b/src/_P022_PCA9685.ino index f3fcced6b..267a5507e 100644 --- a/src/_P022_PCA9685.ino +++ b/src/_P022_PCA9685.ino @@ -334,6 +334,7 @@ boolean Plugin_022(uint8_t function, struct EventStruct *event, String& string) P022_data->Plugin_022_Frequency(address, freq); } success = true; + String dummyString; // SendStatus(event, getPinStateJSON(SEARCH_PIN_STATE, PLUGIN_ID_022, event->Par2, dummyString, 0)); SendStatusOnlyIfNeeded(event, SEARCH_PIN_STATE, createKey(PLUGIN_ID_022, event->Par2), dummyString, 0); diff --git a/src/src/DataStructs/I2CTypes.cpp b/src/src/DataStructs/I2CTypes.cpp new file mode 100644 index 000000000..b4d354972 --- /dev/null +++ b/src/src/DataStructs/I2CTypes.cpp @@ -0,0 +1,14 @@ +#include "../DataStructs/I2CTypes.h" + +const __FlashStringHelper* toString(I2C_bus_state state) { + switch (state) { + case I2C_bus_state::BusCleared: return F("Bus Cleared"); + case I2C_bus_state::OK: return F("OK"); + case I2C_bus_state::NotConfigured: return F("Not Configured"); + case I2C_bus_state::ClearingProcessActive: return F("Clearing Process Active"); + case I2C_bus_state::SCL_Low: return F("SCL Low"); + case I2C_bus_state::SDA_Low_over_2_sec: return F("SCL Low by I2C device clock stretch > 2 sec"); + case I2C_bus_state::SDA_Low_20_clocks: return F("SDA Low"); + } + return F(""); +} diff --git a/src/src/DataStructs/I2CTypes.h b/src/src/DataStructs/I2CTypes.h index 8faac7dfb..1fa8c24ff 100644 --- a/src/src/DataStructs/I2CTypes.h +++ b/src/src/DataStructs/I2CTypes.h @@ -56,4 +56,18 @@ private: typedef I2Cdata I2Cdata_bytes; typedef I2CdataI2Cdata_words; +enum class I2C_bus_state { + NotConfigured, + OK, + BusCleared, + ClearingProcessActive, + SCL_Low, // I2C bus error. Could not clear. SCL clock line held low + SDA_Low_over_2_sec, // I2C bus error. Could not clear. SCL clock line held low by slave clock stretch for >2sec + SDA_Low_20_clocks // I2C bus error. Could not clear. SDA data line held low +}; + +const __FlashStringHelper * toString(I2C_bus_state state); + + + #endif // I2C_TYPES_H diff --git a/src/src/DataStructs/SettingsStruct.cpp b/src/src/DataStructs/SettingsStruct.cpp index 873cd65ff..ec26149df 100644 --- a/src/src/DataStructs/SettingsStruct.cpp +++ b/src/src/DataStructs/SettingsStruct.cpp @@ -247,6 +247,18 @@ void SettingsStruct_tmpl::AllowTaskValueSetAllPlugins(bool value) { bitWrite(VariousBits1, 21, value); } +template +bool SettingsStruct_tmpl::EnableClearHangingI2Cbus() const { + return bitRead(VariousBits1, 22); +} + +template +void SettingsStruct_tmpl::EnableClearHangingI2Cbus(bool value) { + bitWrite(VariousBits1, 22, value); +} + + + template ExtTimeSource_e SettingsStruct_tmpl::ExtTimeSource() const { return static_cast(ExternalTimeSource >> 1); diff --git a/src/src/DataStructs/SettingsStruct.h b/src/src/DataStructs/SettingsStruct.h index 85986daef..0e4f8f0b1 100644 --- a/src/src/DataStructs/SettingsStruct.h +++ b/src/src/DataStructs/SettingsStruct.h @@ -117,6 +117,10 @@ class SettingsStruct_tmpl bool EnableTimingStats() const; void EnableTimingStats(bool value); + // Allow to actively reset I2C bus if it appears to be hanging. + bool EnableClearHangingI2Cbus() const; + void EnableClearHangingI2Cbus(bool value); + // Flag indicating whether all task values should be sent in a single event or one event per task value (default behavior) bool CombineTaskValues_SingleEvent(taskIndex_t taskIndex) const; diff --git a/src/src/ESPEasyCore/ESPEasyEth.cpp b/src/src/ESPEasyCore/ESPEasyEth.cpp index 98b67d274..d1bafe73c 100644 --- a/src/src/ESPEasyCore/ESPEasyEth.cpp +++ b/src/src/ESPEasyCore/ESPEasyEth.cpp @@ -151,6 +151,10 @@ bool ETHConnected() { } return false; } else { + if (EthEventData.last_eth_connect_attempt_moment.isSet() && + EthEventData.last_eth_connect_attempt_moment.millisPassedSince() < 5000) { + return false; + } setNetworkMedium(NetworkMedium_t::WIFI); } } diff --git a/src/src/ESPEasyCore/ESPEasyWifi.cpp b/src/src/ESPEasyCore/ESPEasyWifi.cpp index 717573a31..f58c5cffd 100644 --- a/src/src/ESPEasyCore/ESPEasyWifi.cpp +++ b/src/src/ESPEasyCore/ESPEasyWifi.cpp @@ -383,7 +383,7 @@ void AttemptWiFiConnect() { WiFiEventData.markWiFiTurnOn(); if (WiFi_AP_Candidates.getNext(WiFiScanAllowed())) { - const WiFi_AP_Candidate& candidate = WiFi_AP_Candidates.getCurrent(); + const WiFi_AP_Candidate candidate = WiFi_AP_Candidates.getCurrent(); if (loglevelActiveFor(LOG_LEVEL_INFO)) { String log = F("WIFI : Connecting "); @@ -403,7 +403,7 @@ void AttemptWiFiConnect() { SetWiFiTXpower(tx_pwr, candidate.rssi); // Start connect attempt now, so no longer needed to attempt new connection. WiFiEventData.wifiConnectAttemptNeeded = false; - if (candidate.allowQuickConnect()) { + if (candidate.allowQuickConnect() && !candidate.isHidden) { WiFi.begin(candidate.ssid.c_str(), candidate.key.c_str(), candidate.channel, candidate.bssid.mac); } else { WiFi.begin(candidate.ssid.c_str(), candidate.key.c_str()); @@ -470,7 +470,7 @@ bool checkAndResetWiFi() { switch(status) { case STATION_GOT_IP: - if (WiFi.RSSI() < 0 && NetworkLocalIP().isSet()) { + if (WiFi.RSSI() < 0 && WiFi.localIP().isSet()) { //if (WiFi.channel() == WiFiEventData.usedChannel || WiFiEventData.usedChannel == 0) { // This is a valid status, no need to reset return false; @@ -484,7 +484,7 @@ bool checkAndResetWiFi() { break; case STATION_IDLE: case STATION_CONNECTING: - if (!WiFiEventData.last_wifi_connect_attempt_moment.timeoutReached(15000)) { + if (WiFiEventData.last_wifi_connect_attempt_moment.isSet() && !WiFiEventData.last_wifi_connect_attempt_moment.timeoutReached(15000)) { return false; } break; @@ -496,7 +496,7 @@ bool checkAndResetWiFi() { return false; //} } - if (!WiFiEventData.last_wifi_connect_attempt_moment.timeoutReached(15000)) { + if (WiFiEventData.last_wifi_connect_attempt_moment.isSet() && !WiFiEventData.last_wifi_connect_attempt_moment.timeoutReached(15000)) { return false; } #endif diff --git a/src/src/ESPEasyCore/ESPEasyWifi_ProcessEvent.cpp b/src/src/ESPEasyCore/ESPEasyWifi_ProcessEvent.cpp index 5caf9ab02..1a541e1e6 100644 --- a/src/src/ESPEasyCore/ESPEasyWifi_ProcessEvent.cpp +++ b/src/src/ESPEasyCore/ESPEasyWifi_ProcessEvent.cpp @@ -246,13 +246,13 @@ void processDisconnect() { } - bool mustRestartWiFi = Settings.WiFiRestart_connection_lost(); + bool mustRestartWiFi = true; //Settings.WiFiRestart_connection_lost(); if (WiFiEventData.lastConnectedDuration_us > 0 && (WiFiEventData.lastConnectedDuration_us / 1000) < 5000) { mustRestartWiFi = true; } + WifiDisconnect(); // Needed or else node may not reconnect reliably. if (mustRestartWiFi) { - WifiDisconnect(); // Needed or else node may not reconnect reliably. delay(100); setWifiMode(WIFI_OFF); initWiFi(); diff --git a/src/src/ESPEasyCore/ESPEasy_loop.cpp b/src/src/ESPEasyCore/ESPEasy_loop.cpp index 5c362fdfb..8ea2873d7 100644 --- a/src/src/ESPEasyCore/ESPEasy_loop.cpp +++ b/src/src/ESPEasyCore/ESPEasy_loop.cpp @@ -15,6 +15,8 @@ #include "../Helpers/DeepSleep.h" #include "../Helpers/ESPEasyRTC.h" #include "../Helpers/ESPEasy_time_calc.h" +#include "../Helpers/I2C_access.h" +#include "../Helpers/Hardware.h" #include "../Helpers/Misc.h" #include "../Helpers/Networking.h" #include "../Helpers/PeriodicalActions.h" @@ -60,7 +62,6 @@ void ESPEasy_loop() if(MainLoopCall_ptr) MainLoopCall_ptr(); */ - dummyString = String(); // Fixme TD-er Make sure this global variable doesn't keep memory allocated. updateLoopStats(); @@ -87,6 +88,41 @@ void ESPEasy_loop() sendSysInfoUDP(1); } + if (Settings.EnableClearHangingI2Cbus()) + { + // Check I2C bus to see if it needs to be cleared. + // See: http://www.forward.com.au/pfod/ArduinoProgramming/I2C_ClearBus/index.html + const I2C_bus_state I2C_state_prev = I2C_state; + I2C_state = I2C_check_bus(Settings.Pin_i2c_scl, Settings.Pin_i2c_sda); + switch (I2C_state) { + case I2C_bus_state::BusCleared: + // Log I2C bus cleared, update stats + ++I2C_bus_cleared_count; + addLog(LOG_LEVEL_ERROR, F("I2C : Cleared I2C bus error state")); + I2C_state = I2C_bus_state::OK; + initI2C(); + break; + case I2C_bus_state::SCL_Low: + addLog(LOG_LEVEL_ERROR, F("I2C : I2C bus error, SCL clock line held low")); + break; + case I2C_bus_state::SDA_Low_over_2_sec: + addLog(LOG_LEVEL_ERROR, F("I2C : I2C bus error, SCL clock line held low by slave clock stretch for >2 sec")); + break; + case I2C_bus_state::SDA_Low_20_clocks: + addLog(LOG_LEVEL_ERROR, F("I2C : I2C bus error, SDA data line held low")); + break; + case I2C_bus_state::ClearingProcessActive: + if (I2C_state_prev != I2C_state) { + addLog(LOG_LEVEL_ERROR, F("I2C : I2C bus error, start clearing process")); + } + break; + case I2C_bus_state::NotConfigured: + case I2C_bus_state::OK: + break; + } + } + + // Work around for nodes that do not have WiFi connection for a long time and may reboot after N unsuccessful connect attempts if (getUptimeMinutes() > 2) { // Apparently the uptime is already a few minutes. Let's consider it a successful boot. diff --git a/src/src/ESPEasyCore/ESPEasy_setup.cpp b/src/src/ESPEasyCore/ESPEasy_setup.cpp index 2533893d0..55dcc033f 100644 --- a/src/src/ESPEasyCore/ESPEasy_setup.cpp +++ b/src/src/ESPEasyCore/ESPEasy_setup.cpp @@ -224,6 +224,14 @@ void ESPEasy_setup() logMemUsageAfter(F("LoadSettings()")); #endif + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("hardwareInit")); + #endif // ifndef BUILD_NO_RAM_TRACKER + hardwareInit(); + #ifndef BUILD_NO_RAM_TRACKER + logMemUsageAfter(F("hardwareInit()")); + #endif + node_time.restoreFromRTC(); Settings.UseRTOSMultitasking = false; // For now, disable it, we experience heap corruption. @@ -244,7 +252,8 @@ void ESPEasy_setup() // This ensures, that changing WIFI OR ETHERNET MODE happens properly only after reboot. Changing without reboot would not be a good idea. // This only works after LoadSettings(); - setNetworkMedium(Settings.NetworkMedium); + // Do not call setNetworkMedium here as that may try to clean up settings. + active_network_medium = Settings.NetworkMedium; #endif // ifdef HAS_ETHERNET if (active_network_medium == NetworkMedium_t::WIFI) { @@ -309,14 +318,6 @@ void ESPEasy_setup() Serial.setDebugOutput(true); } - #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("hardwareInit")); - #endif // ifndef BUILD_NO_RAM_TRACKER - hardwareInit(); - #ifndef BUILD_NO_RAM_TRACKER - logMemUsageAfter(F("hardwareInit()")); - #endif - timermqtt_interval = 250; // Interval for checking MQTT timerAwakeFromDeepSleep = millis(); diff --git a/src/src/Globals/Plugins.cpp b/src/src/Globals/Plugins.cpp index 659f4139f..76e89cecc 100644 --- a/src/src/Globals/Plugins.cpp +++ b/src/src/Globals/Plugins.cpp @@ -19,6 +19,7 @@ #include "../Globals/EventQueue.h" #include "../Globals/GlobalMapPortStatus.h" #include "../Globals/Settings.h" +#include "../Globals/Statistics.h" #include "../Helpers/ESPEasyRTC.h" #include "../Helpers/ESPEasy_Storage.h" @@ -225,12 +226,15 @@ void sortDeviceIndexArray() { // when addressing a task // ******************************************************************************** -void prepare_I2C_by_taskIndex(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) { +bool prepare_I2C_by_taskIndex(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) { if (!validTaskIndex(taskIndex) || !validDeviceIndex(DeviceIndex)) { - return; + return false; } if (Device[DeviceIndex].Type != DEVICE_TYPE_I2C) { - return; + return true; // No I2C task, so consider all-OK + } + if (I2C_state != I2C_bus_state::OK) { + return false; // Bus state is not OK, so do not consider task runnable } #ifdef FEATURE_I2CMULTIPLEXER I2CMultiplexerSelectByTaskIndex(taskIndex); @@ -239,8 +243,9 @@ void prepare_I2C_by_taskIndex(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) #endif if (bitRead(Settings.I2C_Flags[taskIndex], I2C_FLAGS_SLOW_SPEED)) { - I2CSelectClockSpeed(true); // Set to slow + I2CSelectLowClockSpeed(); // Set to slow } + return true; } @@ -256,7 +261,7 @@ void post_I2C_by_taskIndex(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) { #endif if (bitRead(Settings.I2C_Flags[taskIndex], I2C_FLAGS_SLOW_SPEED)) { - I2CSelectClockSpeed(false); // Reset + I2CSelectHighClockSpeed(); // Reset } } @@ -297,7 +302,9 @@ bool PluginCallForTask(taskIndex_t taskIndex, uint8_t Function, EventStruct *Tem TempEvent->OriginTaskIndex = event->TaskIndex; } - prepare_I2C_by_taskIndex(taskIndex, DeviceIndex); + if (!prepare_I2C_by_taskIndex(taskIndex, DeviceIndex)) { + return false; + } switch (Function) { case PLUGIN_WRITE: // First set case PLUGIN_REQUEST: @@ -369,24 +376,9 @@ bool PluginCall(uint8_t Function, struct EventStruct *event, String& str) // FIXME TD-er: Also resize DeviceIndex_to_Plugin_id ? } } - // FIXME TD-er: This is not correct as we don't have a taskIndex here when addressing a plugin - /* - taskIndex_t taskIndex = INVALID_TASK_INDEX; - if (Function != PLUGIN_DEVICE_ADD && Device[x].Type == DEVICE_TYPE_I2C) { - unsigned int varNr; - validTaskVars(event, taskIndex, varNr); - prepare_I2C_by_taskIndex(taskIndex, x); - } - */ START_TIMER; Plugin_ptr[x](Function, event, str); STOP_TIMER_TASK(x, Function); - /* - // FIXME TD-er: This is not correct as we don't have a taskIndex here when addressing a plugin - if (Function != PLUGIN_DEVICE_ADD) { - post_I2C_by_taskIndex(taskIndex, x); - } - */ delay(0); // SMY: call delay(0) unconditionally } } @@ -404,19 +396,9 @@ bool PluginCall(uint8_t Function, struct EventStruct *event, String& str) const deviceIndex_t DeviceIndex = it->second.x; if (validDeviceIndex(DeviceIndex)) { - // FIXME TD-er: This is not correct, as the event is NULL for calls to PLUGIN_MONITOR - /* - taskIndex_t taskIndex = INVALID_TASK_INDEX; - if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C) { - unsigned int varNr; - validTaskVars(event, taskIndex, varNr); - prepare_I2C_by_taskIndex(taskIndex, DeviceIndex); - } - */ START_TIMER; Plugin_ptr[DeviceIndex](Function, &TempEvent, str); STOP_TIMER_TASK(DeviceIndex, Function); - // post_I2C_by_taskIndex(taskIndex, DeviceIndex); } } } @@ -600,7 +582,9 @@ bool PluginCall(uint8_t Function, struct EventStruct *event, String& str) checkRAM(descr, String(Function)); #endif } - prepare_I2C_by_taskIndex(event->TaskIndex, DeviceIndex); + if (!prepare_I2C_by_taskIndex(event->TaskIndex, DeviceIndex)) { + return false; + } START_TIMER; bool retval = Plugin_ptr[DeviceIndex](Function, event, str); diff --git a/src/src/Globals/Plugins.h b/src/src/Globals/Plugins.h index 8c81a9fcd..4bbd0dccb 100644 --- a/src/src/Globals/Plugins.h +++ b/src/src/Globals/Plugins.h @@ -95,7 +95,9 @@ String getPluginNameFromPluginID(pluginID_t pluginID); void sortDeviceIndexArray(); -void prepare_I2C_by_taskIndex(taskIndex_t taskIndex, deviceIndex_t DeviceIndex); +// Prepare I2C bus for next call to task +// Return false if task is I2C, but I2C bus is not ready +bool prepare_I2C_by_taskIndex(taskIndex_t taskIndex, deviceIndex_t DeviceIndex); void post_I2C_by_taskIndex(taskIndex_t taskIndex, deviceIndex_t DeviceIndex); /*********************************************************************************************\ diff --git a/src/src/Globals/Statistics.cpp b/src/src/Globals/Statistics.cpp index 9bb5e3c8e..33a3872d3 100644 --- a/src/src/Globals/Statistics.cpp +++ b/src/src/Globals/Statistics.cpp @@ -28,3 +28,7 @@ float loop_usec_duration_total = 0.0f; unsigned long dailyResetCounter = 0; volatile unsigned long sw_watchdog_callback_count = 0; + + +I2C_bus_state I2C_state = I2C_bus_state::OK; +unsigned long I2C_bus_cleared_count = 0; diff --git a/src/src/Globals/Statistics.h b/src/src/Globals/Statistics.h index dfbf30a6e..9e91f45a6 100644 --- a/src/src/Globals/Statistics.h +++ b/src/src/Globals/Statistics.h @@ -5,6 +5,8 @@ #include "../../ESPEasy_common.h" +#include "../DataStructs/I2CTypes.h" + class String; @@ -40,4 +42,8 @@ extern unsigned long dailyResetCounter; extern volatile unsigned long sw_watchdog_callback_count; +extern I2C_bus_state I2C_state; +extern unsigned long I2C_bus_cleared_count; + + #endif // GLOBALS_STATISTICS_H diff --git a/src/src/Helpers/ESPEasy_time.cpp b/src/src/Helpers/ESPEasy_time.cpp index e3fdf5fa7..e0aa8d966 100644 --- a/src/src/Helpers/ESPEasy_time.cpp +++ b/src/src/Helpers/ESPEasy_time.cpp @@ -11,6 +11,7 @@ #include "../Globals/Settings.h" #include "../Globals/TimeZone.h" +#include "../Helpers/Hardware.h" #include "../Helpers/Misc.h" #include "../Helpers/Networking.h" #include "../Helpers/Numerical.h" @@ -723,6 +724,7 @@ bool ESPEasy_time::ExtRTC_get(uint32_t &unixtime) case ExtTimeSource_e::DS1307: { #ifdef USE_EXT_RTC + I2CSelect_Max100kHz_ClockSpeed(); // Only supports upto 100 kHz RTC_DS1307 rtc; if (!rtc.begin()) { // Not found @@ -815,6 +817,7 @@ bool ESPEasy_time::ExtRTC_set(uint32_t unixtime) case ExtTimeSource_e::DS1307: { #ifdef USE_EXT_RTC + I2CSelect_Max100kHz_ClockSpeed(); // Only supports upto 100 kHz RTC_DS1307 rtc; if (rtc.begin()) { rtc.adjust(DateTime(unixtime)); diff --git a/src/src/Helpers/Hardware.cpp b/src/src/Helpers/Hardware.cpp index 4f5786f42..fc540301d 100644 --- a/src/src/Helpers/Hardware.cpp +++ b/src/src/Helpers/Hardware.cpp @@ -183,12 +183,12 @@ void hardwareInit() void initI2C() { // configure hardware pins according to eeprom settings. - if (Settings.isI2CEnabled()) + if (!Settings.isI2CEnabled()) { return; } addLog(LOG_LEVEL_INFO, F("INIT : I2C")); - I2CSelectClockSpeed(false); // Set normal clock speed + I2CSelectHighClockSpeed(); // Set normal clock speed if (Settings.WireClockStretchLimit) { @@ -237,19 +237,56 @@ void initI2C() { } } -void I2CSelectClockSpeed(bool setLowSpeed) { +void I2CSelectHighClockSpeed() { + I2CSelectClockSpeed(Settings.I2C_clockSpeed); +} + +void I2CSelectLowClockSpeed() { + I2CSelectClockSpeed(Settings.I2C_clockSpeed_Slow); +} + +void I2CSelect_Max100kHz_ClockSpeed() { + if (Settings.I2C_clockSpeed <= 100000) { + I2CSelectHighClockSpeed(); + } else if (Settings.I2C_clockSpeed_Slow <= 100000) { + I2CSelectLowClockSpeed(); + } else { + I2CSelectClockSpeed(100000); + } +} + +void I2CSelectClockSpeed(uint32_t clockFreq) { + I2CBegin(Settings.Pin_i2c_sda, Settings.Pin_i2c_scl, clockFreq); +} + +void I2CForceResetBus_swap_pins(uint8_t address) { + if (!Settings.EnableClearHangingI2Cbus()) return; + // As a final work-around, we temporary swap SDA and SCL, perform a scan and return pin order. + I2CBegin(Settings.Pin_i2c_scl, Settings.Pin_i2c_sda, 100000); + Wire.beginTransmission(address); + Wire.endTransmission(); + delay(1); + // Now we switch back to the correct pins + I2CSelectClockSpeed(100000); +} + +void I2CBegin(int8_t sda, int8_t scl, uint32_t clockFreq) { static uint32_t lastI2CClockSpeed = 0; - const uint32_t newI2CClockSpeed = setLowSpeed ? Settings.I2C_clockSpeed_Slow : Settings.I2C_clockSpeed; - if (newI2CClockSpeed == lastI2CClockSpeed) { + static int8_t last_sda = -1; + static int8_t last_scl = -1; + if (clockFreq == lastI2CClockSpeed && sda == last_sda && scl == last_scl) { // No need to change the clock speed. return; } - lastI2CClockSpeed = newI2CClockSpeed; + lastI2CClockSpeed = clockFreq; + last_scl = scl; + last_sda = sda; + #ifdef ESP32 - Wire.begin(Settings.Pin_i2c_sda, Settings.Pin_i2c_scl, newI2CClockSpeed); + Wire.begin(sda, scl, clockFreq); #else - Wire.begin(Settings.Pin_i2c_sda, Settings.Pin_i2c_scl); - Wire.setClock(newI2CClockSpeed); + Wire.begin(sda, scl); + Wire.setClock(clockFreq); #endif } diff --git a/src/src/Helpers/Hardware.h b/src/src/Helpers/Hardware.h index 703a5d00c..e63f82324 100644 --- a/src/src/Helpers/Hardware.h +++ b/src/src/Helpers/Hardware.h @@ -24,7 +24,12 @@ void hardwareInit(); void initI2C(); -void I2CSelectClockSpeed(bool setLowSpeed); +void I2CSelectHighClockSpeed(); +void I2CSelectLowClockSpeed(); +void I2CSelect_Max100kHz_ClockSpeed(); +void I2CSelectClockSpeed(uint32_t clockFreq); +void I2CForceResetBus_swap_pins(uint8_t address); +void I2CBegin(int8_t sda, int8_t scl, uint32_t clockFreq); #ifdef FEATURE_I2CMULTIPLEXER bool isI2CMultiplexerEnabled(); diff --git a/src/src/Helpers/I2C_access.cpp b/src/src/Helpers/I2C_access.cpp index a6f2d128d..710d1b255 100644 --- a/src/src/Helpers/I2C_access.cpp +++ b/src/src/Helpers/I2C_access.cpp @@ -1,6 +1,138 @@ -#include "I2C_access.h" +#include "../Helpers/I2C_access.h" #include "../Globals/I2Cdev.h" +#include "../Helpers/ESPEasy_time_calc.h" + +enum class I2C_clear_bus_state { + Start, + Wait_SCL_become_high, // Wait for 2.5 seconds for SCL to become high after enabling pull-up resistors + Wait_SDA_become_high, + Wait_SCL_SDA_become_high, // SDA is low, try to toggle SCL and wait for it to be freed. +}; + + +// Code to clear I2C bus as described here: +// http://www.forward.com.au/pfod/ArduinoProgramming/I2C_ClearBus/index.html +// Changed into a state machine for use in a non blocking way to be used in ESPEasy. +I2C_bus_state I2C_check_bus(int8_t scl, int8_t sda) { + static I2C_clear_bus_state clearing_state = I2C_clear_bus_state::Start; + static unsigned long last_state_change = 0; + static int clockCount = 20; // > 2x9 clock + + + switch (clearing_state) { + case I2C_clear_bus_state::Start: + { + // FIXME TD-er: Check for proper I2C pins + if ((sda < 0) || (scl < 0)) { + last_state_change = 0; + return I2C_bus_state::NotConfigured; + } + + if ((digitalRead(scl) == HIGH) && (digitalRead(sda) == HIGH)) { + last_state_change = 0; + return I2C_bus_state::OK; + } + + pinMode(sda, INPUT_PULLUP); // Make SDA (data) and SCL (clock) pins Inputs with pullup. + pinMode(scl, INPUT_PULLUP); + + clockCount = 20; + clearing_state = I2C_clear_bus_state::Wait_SCL_become_high; + last_state_change = millis(); + break; + } + + case I2C_clear_bus_state::Wait_SCL_become_high: + { + // Wait 2.5 secs. This is strictly only necessary on the first power + // up of the DS3231 module to allow it to initialize properly, + // but is also assists in reliable programming of FioV3 boards as it gives the + // IDE a chance to start uploaded the program + // before existing sketch confuses the IDE by sending Serial data. + if (digitalRead(scl) == LOW) { + if (timePassedSince(last_state_change) > 2500) { + clearing_state = I2C_clear_bus_state::Start; + return I2C_bus_state::SCL_Low; // I2C bus error. Could not clear SCL clock line held low + } + return I2C_bus_state::ClearingProcessActive; + } + clearing_state = I2C_clear_bus_state::Wait_SDA_become_high; + last_state_change = millis(); + break; + } + + case I2C_clear_bus_state::Wait_SDA_become_high: + { + boolean SDA_LOW = (digitalRead(sda) == LOW); // vi. Check SDA input. + + while (SDA_LOW && (clockCount > 0)) { // vii. If SDA is Low, + clockCount--; + + // Note: I2C bus is open collector so do NOT drive SCL or SDA high. + pinMode(scl, INPUT); // release SCL pullup so that when made output it will be LOW + pinMode(scl, OUTPUT); // then clock SCL Low + delayMicroseconds(10); // for >5uS + pinMode(scl, INPUT); // release SCL LOW + pinMode(scl, INPUT_PULLUP); // turn on pullup resistors again + // do not force high as slave may be holding it low for clock stretching. + delayMicroseconds(10); // for >5uS + + // The >5uS is so that even the slowest I2C devices are handled. + if (digitalRead(scl) == LOW) { + // loop waiting for SCL to become High only wait 2sec. + clearing_state = I2C_clear_bus_state::Wait_SCL_SDA_become_high; + last_state_change = millis(); + return I2C_bus_state::ClearingProcessActive; + } + SDA_LOW = (digitalRead(sda) == LOW); // and check SDA input again and loop + } + + if (SDA_LOW) { // still low + clearing_state = I2C_clear_bus_state::Start; + return I2C_bus_state::SDA_Low_20_clocks; // I2C bus error. Could not clear. SDA data line held low + } + + // else pull SDA line low for Start or Repeated Start + pinMode(sda, INPUT); // remove pullup. + pinMode(sda, OUTPUT); // and then make it LOW i.e. send an I2C Start or Repeated start control. + // When there is only one I2C master a Start or Repeat Start has the same function as a Stop and clears the bus. + // A Repeat Start is a Start occurring after a Start with no intervening Stop. + delayMicroseconds(10); // wait >5uS + pinMode(sda, INPUT); // remove output low + pinMode(sda, INPUT_PULLUP); // and make SDA high i.e. send I2C STOP control. + delayMicroseconds(10); // x. wait >5uS + pinMode(sda, INPUT); // and reset pins as tri-state inputs which is the default state on reset + pinMode(scl, INPUT); + clearing_state = I2C_clear_bus_state::Start; + return I2C_bus_state::BusCleared; // all ok + } + + case I2C_clear_bus_state::Wait_SCL_SDA_become_high: + { + if (digitalRead(scl) == LOW) { + if (timePassedSince(last_state_change) > 2000) { + // I2C bus error. Could not clear. + // SCL clock line held low by slave clock stretch for >2sec + clearing_state = I2C_clear_bus_state::Start; + return I2C_bus_state::SDA_Low_over_2_sec; + } + return I2C_bus_state::ClearingProcessActive; + } + clearing_state = I2C_clear_bus_state::Wait_SDA_become_high; + last_state_change = millis(); + break; + } + } + + if (timePassedSince(last_state_change) > 5000) { + // Just to prevent infinite loop + // Should not be needed. + clearing_state = I2C_clear_bus_state::Start; + last_state_change = millis(); + } + return I2C_bus_state::ClearingProcessActive; +} // **************************************************************************/ // Central functions for I2C data transfers @@ -19,19 +151,20 @@ bool I2C_read_words(uint8_t i2caddr, I2Cdata_words& data) { // See https://github.com/platformio/platform-espressif32/issues/126 #ifdef ESP32 - // ESP32: uint8_t TwoWire::endTransmission(bool sendStop) + +// ESP32: uint8_t TwoWire::endTransmission(bool sendStop) # define END_TRANSMISSION_FLAG true #else // ifdef ESP32 - // ESP8266: uint8_t TwoWire::endTransmission(uint8_t sendStop) +// ESP8266: uint8_t TwoWire::endTransmission(uint8_t sendStop) # define END_TRANSMISSION_FLAG 0 #endif // ifdef ESP32 // **************************************************************************/ // Wake up I2C device // **************************************************************************/ -void I2C_wakeup(uint8_t i2caddr) { +unsigned char I2C_wakeup(uint8_t i2caddr) { Wire.beginTransmission(i2caddr); - Wire.endTransmission(); + return Wire.endTransmission(); } // **************************************************************************/ diff --git a/src/src/Helpers/I2C_access.h b/src/src/Helpers/I2C_access.h index e1cf3b030..58e99f74f 100644 --- a/src/src/Helpers/I2C_access.h +++ b/src/src/Helpers/I2C_access.h @@ -3,6 +3,8 @@ #include "../DataStructs/I2CTypes.h" +I2C_bus_state I2C_check_bus(int8_t scl, int8_t sda); + // **************************************************************************/ // Central functions for I2C data transfers // **************************************************************************/ @@ -15,7 +17,7 @@ bool I2C_read_words(uint8_t i2caddr, // **************************************************************************/ // Wake up I2C device // **************************************************************************/ -void I2C_wakeup(uint8_t i2caddr); +unsigned char I2C_wakeup(uint8_t i2caddr); // **************************************************************************/ // Writes an 8 bit value over I2C diff --git a/src/src/Helpers/StringProvider.cpp b/src/src/Helpers/StringProvider.cpp index a08bba2ce..479bb5369 100644 --- a/src/src/Helpers/StringProvider.cpp +++ b/src/src/Helpers/StringProvider.cpp @@ -84,6 +84,7 @@ const __FlashStringHelper * getLabel(LabelType::Enum label) { case LabelType::JSON_BOOL_QUOTES: return F("JSON bool output without quotes"); case LabelType::ENABLE_TIMING_STATISTICS: return F("Collect Timing Statistics"); case LabelType::TASKVALUESET_ALL_PLUGINS: return F("Allow TaskValueSet on all plugins"); + case LabelType::ENABLE_CLEAR_HUNG_I2C_BUS: return F("Try clear I2C bus when stuck"); case LabelType::BOOT_TYPE: return F("Last Boot Cause"); case LabelType::BOOT_COUNT: return F("Boot Count"); @@ -141,6 +142,9 @@ const __FlashStringHelper * getLabel(LabelType::Enum label) { case LabelType::BUILD_PLATFORM: return F("Build Platform"); case LabelType::GIT_HEAD: return F("Git HEAD"); + case LabelType::I2C_BUS_STATE: return F("I2C Bus State"); + case LabelType::I2C_BUS_CLEARED_COUNT: return F("I2C bus cleared count"); + case LabelType::SYSLOG_LOG_LEVEL: return F("Syslog Log Level"); case LabelType::SERIAL_LOG_LEVEL: return F("Serial Log Level"); case LabelType::WEB_LOG_LEVEL: return F("Web Log Level"); @@ -262,6 +266,7 @@ String getValue(LabelType::Enum label) { case LabelType::JSON_BOOL_QUOTES: return jsonBool(Settings.JSONBoolWithoutQuotes()); case LabelType::ENABLE_TIMING_STATISTICS: return jsonBool(Settings.EnableTimingStats()); case LabelType::TASKVALUESET_ALL_PLUGINS: return jsonBool(Settings.AllowTaskValueSetAllPlugins()); + case LabelType::ENABLE_CLEAR_HUNG_I2C_BUS: return jsonBool(Settings.EnableClearHangingI2Cbus()); case LabelType::BOOT_TYPE: return getLastBootCauseString(); case LabelType::BOOT_COUNT: break; @@ -327,6 +332,8 @@ String getValue(LabelType::Enum label) { case LabelType::BINARY_FILENAME: return get_binary_filename(); case LabelType::BUILD_PLATFORM: return get_build_platform(); case LabelType::GIT_HEAD: return get_git_head(); + case LabelType::I2C_BUS_STATE: return toString(I2C_state); + case LabelType::I2C_BUS_CLEARED_COUNT: return String(I2C_bus_cleared_count); case LabelType::SYSLOG_LOG_LEVEL: return getLogLevelDisplayString(Settings.SyslogLevel); case LabelType::SERIAL_LOG_LEVEL: return getLogLevelDisplayString(getSerialLogLevel()); case LabelType::WEB_LOG_LEVEL: return getLogLevelDisplayString(getWebLogLevel()); diff --git a/src/src/Helpers/StringProvider.h b/src/src/Helpers/StringProvider.h index 37280a694..aeac7b114 100644 --- a/src/src/Helpers/StringProvider.h +++ b/src/src/Helpers/StringProvider.h @@ -58,6 +58,7 @@ struct LabelType { JSON_BOOL_QUOTES, ENABLE_TIMING_STATISTICS, TASKVALUESET_ALL_PLUGINS, + ENABLE_CLEAR_HUNG_I2C_BUS, BOOT_TYPE, // Cold boot BOOT_COUNT, // 0 @@ -114,6 +115,9 @@ struct LabelType { GIT_HEAD, + I2C_BUS_STATE, + I2C_BUS_CLEARED_COUNT, + SYSLOG_LOG_LEVEL, SERIAL_LOG_LEVEL, WEB_LOG_LEVEL, diff --git a/src/src/Helpers/WiFi_AP_CandidatesList.cpp b/src/src/Helpers/WiFi_AP_CandidatesList.cpp index 209f876d2..d5b547383 100644 --- a/src/src/Helpers/WiFi_AP_CandidatesList.cpp +++ b/src/src/Helpers/WiFi_AP_CandidatesList.cpp @@ -36,11 +36,12 @@ void WiFi_AP_CandidatesList::load_knownCredentials() { while (!done) { if (get_SSID_key(index, ssid, key)) { known.emplace_back(index, ssid, key); - if (index == WIFI_CUSTOM_DEPLOYMENT_KEY_INDEX || - index == WIFI_CUSTOM_SUPPORT_KEY_INDEX) { - known.back().lowPriority = true; - } else if (index == WIFI_CREDENTIALS_FALLBACK_SSID_INDEX) { - known.back().isEmergencyFallback = true; + if (SettingsIndexMatchCustomCredentials(index)) { + if (SettingsIndexMatchEmergencyFallback(index)) { + known.back().isEmergencyFallback = true; + } else { + known.back().lowPriority = true; + } } ++index; } else { @@ -189,10 +190,18 @@ bool WiFi_AP_CandidatesList::getNext(bool scanAllowed) { } if (mustPop) { - known_it = known.begin(); - if (!candidates.empty()) { - candidates.pop_front(); + if (currentCandidate.isHidden) { + // We tried to connect to hidden SSIDs in 1 run, so pop all hidden candidates. + for (auto cand_it = candidates.begin(); cand_it != candidates.end() && cand_it->isHidden; ) { + cand_it = candidates.erase(cand_it); + } + } else { + if (!candidates.empty()) { + candidates.pop_front(); + } } + + known_it = known.begin(); } return currentCandidate.usable(); } @@ -258,9 +267,15 @@ bool WiFi_AP_CandidatesList::SettingsIndexMatchCustomCredentials(uint8_t index) { return (WIFI_CUSTOM_DEPLOYMENT_KEY_INDEX == index || WIFI_CUSTOM_SUPPORT_KEY_INDEX == index || - WIFI_CREDENTIALS_FALLBACK_SSID_INDEX == index); + SettingsIndexMatchEmergencyFallback(index)); } +bool WiFi_AP_CandidatesList::SettingsIndexMatchEmergencyFallback(uint8_t index) +{ + return (WIFI_CREDENTIALS_FALLBACK_SSID_INDEX == index); +} + + void WiFi_AP_CandidatesList::loadCandidatesFromScanned() { if (candidates.size() > 1) { // Do not mess with the current candidates order if > 1 present diff --git a/src/src/Helpers/WiFi_AP_CandidatesList.h b/src/src/Helpers/WiFi_AP_CandidatesList.h index 93e2c1b02..1c271dc60 100644 --- a/src/src/Helpers/WiFi_AP_CandidatesList.h +++ b/src/src/Helpers/WiFi_AP_CandidatesList.h @@ -58,6 +58,8 @@ struct WiFi_AP_CandidatesList { static bool SettingsIndexMatchCustomCredentials(uint8_t index); + static bool SettingsIndexMatchEmergencyFallback(uint8_t index); + private: // Pick the possible diff --git a/src/src/WebServer/AdvancedConfigPage.cpp b/src/src/WebServer/AdvancedConfigPage.cpp index e74c3ec51..5898b89a6 100644 --- a/src/src/WebServer/AdvancedConfigPage.cpp +++ b/src/src/WebServer/AdvancedConfigPage.cpp @@ -104,6 +104,7 @@ void handle_advanced() { Settings.JSONBoolWithoutQuotes(isFormItemChecked(LabelType::JSON_BOOL_QUOTES)); Settings.EnableTimingStats(isFormItemChecked(LabelType::ENABLE_TIMING_STATISTICS)); Settings.AllowTaskValueSetAllPlugins(isFormItemChecked(LabelType::TASKVALUESET_ALL_PLUGINS)); + Settings.EnableClearHangingI2Cbus(isFormItemChecked(LabelType::ENABLE_CLEAR_HUNG_I2C_BUS)); #ifdef ESP8266 Settings.UseAlternativeDeepSleep(isFormItemChecked(LabelType::DEEP_SLEEP_ALTERNATIVE_CALL)); #endif @@ -219,6 +220,7 @@ void handle_advanced() { addFormCheckBox(LabelType::ENABLE_TIMING_STATISTICS, Settings.EnableTimingStats()); #endif addFormCheckBox(LabelType::TASKVALUESET_ALL_PLUGINS, Settings.AllowTaskValueSetAllPlugins()); + addFormCheckBox(LabelType::ENABLE_CLEAR_HUNG_I2C_BUS, Settings.EnableClearHangingI2Cbus()); #ifdef ESP8266 addFormCheckBox(LabelType::DEEP_SLEEP_ALTERNATIVE_CALL, Settings.UseAlternativeDeepSleep()); diff --git a/src/src/WebServer/I2C_Scanner.cpp b/src/src/WebServer/I2C_Scanner.cpp index 12df06bc9..ca397da3b 100644 --- a/src/src/WebServer/I2C_Scanner.cpp +++ b/src/src/WebServer/I2C_Scanner.cpp @@ -113,7 +113,7 @@ void handle_i2cscanner_json() { int nDevices = 0; - I2CSelectClockSpeed(true); // Always scan in low speed to also find old/slow devices + I2CSelect_Max100kHz_ClockSpeed(); // Always scan in low speed to also find old/slow devices #ifdef FEATURE_I2CMULTIPLEXER i2c_addresses_t mainBusDevices; mainBusDevices.resize(128); @@ -135,7 +135,7 @@ void handle_i2cscanner_json() { I2CMultiplexerOff(); } #endif - I2CSelectClockSpeed(false); // Reset bus to standard speed + I2CSelectHighClockSpeed(); // Reset bus to standard speed json_close(true); TXBuffer.endStream(); @@ -325,7 +325,8 @@ int scanI2CbusForDevices( // Utility function for scanning the I2C bus for valid error = Wire.endTransmission(); delay(1); - if (error == 0) + switch (error) { + case 0: { html_TR_TD(); #ifdef FEATURE_I2CMULTIPLEXER @@ -350,12 +351,26 @@ int scanI2CbusForDevices( // Utility function for scanning the I2C bus for valid addHtml(description); } nDevices++; + break; } - else if (error == 4) + case 2: // NACK on transmit address, thus not found + break; + case 3: { html_TR_TD(); - addHtml(F("Unknown error at address ")); + addHtml(F("NACK on transmit data to address ")); addHtml(formatToHex(address)); + break; + } + case 4: + { + html_TR_TD(); + addHtml(F("SDA low at address ")); + addHtml(formatToHex(address)); + I2CForceResetBus_swap_pins(address); + addHtml(F(" Reset bus attempted")); + break; + } } #ifdef FEATURE_I2CMULTIPLEXER } @@ -386,7 +401,7 @@ void handle_i2cscanner() { if (Settings.isI2CEnabled()) { int nDevices = 0; - I2CSelectClockSpeed(true); // Scan bus using low speed + I2CSelect_Max100kHz_ClockSpeed(); // Scan bus using low speed #ifdef FEATURE_I2CMULTIPLEXER i2c_addresses_t mainBusDevices; mainBusDevices.resize(128); @@ -408,7 +423,7 @@ void handle_i2cscanner() { I2CMultiplexerOff(); } #endif - I2CSelectClockSpeed(false); // By default the bus is in standard speed + I2CSelectHighClockSpeed(); // By default the bus is in standard speed if (nDevices == 0) { addHtml(F("No I2C devices found")); diff --git a/src/src/WebServer/SysInfoPage.cpp b/src/src/WebServer/SysInfoPage.cpp index 6cb442999..23b97e6df 100644 --- a/src/src/WebServer/SysInfoPage.cpp +++ b/src/src/WebServer/SysInfoPage.cpp @@ -20,6 +20,7 @@ #include "../Globals/ESPEasyWiFiEvent.h" #include "../Globals/NetworkState.h" #include "../Globals/RTC.h" +#include "../Globals/Settings.h" #include "../Helpers/CompiletimeDefines.h" #include "../Helpers/ESPEasyStatistics.h" @@ -524,6 +525,11 @@ void handle_sysinfo_SystemStatus() { # ifdef FEATURE_SD addRowLabelValue(LabelType::SD_LOG_LEVEL); # endif // ifdef FEATURE_SD + + if (Settings.EnableClearHangingI2Cbus()) { + addRowLabelValue(LabelType::I2C_BUS_STATE); + addRowLabelValue(LabelType::I2C_BUS_CLEARED_COUNT); + } } void handle_sysinfo_NetworkServices() {