This commit is contained in:
Ton Huisman
2021-10-04 19:19:50 +02:00
40 changed files with 948 additions and 386 deletions
+11 -12
View File
@@ -24,23 +24,25 @@ Used libraries: |P008_usedlibraries|
Description
-----------
The Wiegand plugin reads the Wiegand protocol, either the 26 or 34 bit variant, transmitted over a 2-wire serial connection. The RFID reader or keypad has to be connected to two GPIO pins on the ESP module. The plugin uses an interrupt routine to decode the bitstream. A RFID reader will send a message containing the unique RFID Tag ID. A keypad will send the input after pressing the #, or another configured, confirmation key.
The Wiegand plugin reads the Wiegand protocol, from 26 to 64 bits (mostly 26, 34 or 37 is used), transmitted over a 2-wire serial connection. The RFID reader or keypad has to be connected to two GPIO pins on the ESP module. The plugin uses an interrupt routine to decode the bitstream. A RFID reader will send a message containing the unique RFID Tag ID. A keypad will send the input after pressing the #, or another configured, confirmation key.
Data is transmitted in hexadecimal format. (See below for an option to transform to decimal when using a decimal numeric keypad.)
The value is placed in the Tag variable (this name can be changed).
.. image:: P008_Settings.png
.. image:: P008_DeviceConfiguration.png
**Present hex as decimal value** (Disabled by default) For decimal numeric keypads this option is provided to transform the hex value into a decimal representation, so that when entering value 1234# (# is the confirmation key here), not the result 4660 (0x1234) is made available, but actually 1234. This for easier processing/validating of the entered value. Any input of A-F is replaced by 0 when this option is enabled! It should not be enabled when using a RFID reader, as the Tag ID won't be correct!
* **Wiegand Type (bits)** Select the number of bits to expect from the device. Often used values are 26 and 34, resulting in 24 and 32 bit received data. Selection allows up to 64 bits to be received, giving a 62 bit value, as 2 bits are used for parity information during transmission, and discarded from the received value. When set too high, no data will be recognized, and automatic resets will appear in the log after 5 seconds (``Info : RFID : reset bits: nn``).
**Automatic Tag removal** (Enabled by default) after scanning a tag, it can be automatically removed (reset).
* **Present hex as decimal value** (Disabled by default) For decimal numeric keypads this option is provided to transform the hex value into a decimal representation, so that when entering value 1234# (# is the confirmation key here), not the result 4660 (0x1234) is made available, but actually 1234. This for easier processing/validating of the entered value. Any input of A-F is replaced by 0 when this option is enabled! It should not be enabled when using a RFID reader, as the Tag ID won't be correct!
**Automatic Tag removal after** (Default 500 mSec) The timeout in milli seconds (range 250 - 60000) after which the last Tag will be automatically removed, if that option is enabled.
* **Automatic Tag removal** (Enabled by default) after scanning a tag, it can be automatically removed (reset).
**Value to set on Tag removal** (Default 0) Set a value to the Tag when the previous tag is removed. (Range 0 to 2147483647 as larger values are difficult to store in settings.)
* **Automatic Tag removal after** (Default 500 mSec) The timeout in milli seconds (range 250 - 60000) after which the last Tag will be automatically removed, if that option is enabled.
**Event on Tag removal** (Disabled by default) When enabled sends the removed Tag value as an event and to all enabled controllers.
* **Value to set on Tag removal** (Default 0) Set a value to the Tag when the previous tag is removed. (Range 0 to 2147483647 as larger values are difficult to store in settings.)
* **Event on Tag removal** (Disabled by default) When enabled sends the removed Tag value as an event and to all enabled controllers.
Supported hardware
------------------
@@ -63,6 +65,8 @@ Change log
.. versionchanged:: 2.0
...
|added| 2021-08-23 Extended selection of received bits to 26..64 bit range.
|added|
Major overhaul for 2.0 release.
@@ -71,8 +75,3 @@ Change log
|added|
Initial release version.
Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

+101 -4
View File
@@ -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.
Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

+105 -8
View File
@@ -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.
Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

+23
View File
@@ -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
^^^^^^^^^^^^^^^^^^^^^^
-4
View File
@@ -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.
-4
View File
@@ -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;
+204 -178
View File
@@ -1,52 +1,64 @@
#include "_Plugin_Helper.h"
#ifdef USES_P008
//#######################################################################################################
//################################# Plugin 008: Wiegand RFID Tag Reader #################################
//#######################################################################################################
// #######################################################################################################
// ################################# Plugin 008: Wiegand RFID Tag Reader #################################
// #######################################################################################################
/*
History:
2020-07-04 tonhuisman: Add checkbox for 'Present hex as decimal value' option (with note) so hexadecimal
History:
2021-08-02 tonhuisman: Add checkbos for 'Alternative decoding', swapping the receving of the bits, resulting
in little-endian versus big-endian output. This is supposed to give the same output as the
official Wiegand RFID scanner.
Reformatted using Uncrustify
2020-07-04 tonhuisman: Add checkbox for 'Present hex as decimal value' option (with note) so hexadecimal
value of f.e. a numeric keypad using the Wiegand protocol (hexadecimal data) will be cast to decimal.
When enabled entering 1234# will result in Tag = 1234 instead of 4660 (= 0x1234), any A-F
entered will result in a 0 in the output value.
-------------
No initial history available.
*/
-------------
No initial history available.
*/
#define PLUGIN_008
#define PLUGIN_ID_008 8
#define PLUGIN_NAME_008 "RFID - Wiegand"
#define PLUGIN_VALUENAME1_008 "Tag"
# define PLUGIN_008
# define PLUGIN_ID_008 8
# define PLUGIN_NAME_008 "RFID - Wiegand"
# define PLUGIN_VALUENAME1_008 "Tag"
void Plugin_008_interrupt1() ICACHE_RAM_ATTR;
void Plugin_008_interrupt2() ICACHE_RAM_ATTR;
volatile uint8_t Plugin_008_bitCount = 0; // Count the number of bits received.
uint64_t Plugin_008_keyBuffer = 0; // A 64-bit-long keyBuffer into which the number is stored.
uint8_t Plugin_008_timeoutCount = 0;
uint8_t Plugin_008_WiegandSize = 26; // size of a tag via wiegand (26-bits or 36-bits)
volatile uint8_t Plugin_008_bitCount = 0; // Count the number of bits received.
uint64_t Plugin_008_keyBuffer = 0; // A 64-bit-long keyBuffer into which the number is stored.
uint8_t Plugin_008_timeoutCount = 0;
uint8_t Plugin_008_WiegandSize = 26; // size of a tag via wiegand (26-bits or 36-bits)
boolean Plugin_008_init = false;
/**
* Convert/cast a hexadecimal input to a decimal representation, so 0x1234 (= 4660) comes out as 1234.
*
*
* //FIXME Move to a more global place to also be used elsewhere?
*/
uint64_t castHexAsDec(uint64_t hexValue) {
uint64_t result = 0;
uint8_t digit;
uint8_t digit;
for (int i = 0; i < 8; i++) {
digit = (hexValue & 0x0000000F);
if (digit > 10) digit = 0; // Cast by dropping any non-decimal input
if (digit > 10) {
digit = 0; // Cast by dropping any non-decimal input
}
if (digit > 0) { // Avoid 'expensive' pow operation if not used
result += (digit * static_cast<uint64_t>(pow(10, i)));
}
hexValue >>= 4;
if (hexValue == 0) break; // Stop when no more to process
if (hexValue == 0) {
break; // Stop when no more to process
}
}
return result;
}
@@ -58,138 +70,148 @@ boolean Plugin_008(uint8_t function, struct EventStruct *event, String& string)
switch (function)
{
case PLUGIN_DEVICE_ADD:
{
Device[++deviceCount].Number = PLUGIN_ID_008;
Device[deviceCount].Type = DEVICE_TYPE_DUAL;
Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_LONG;
Device[deviceCount].Ports = 0;
Device[deviceCount].PullUpOption = false;
Device[deviceCount].InverseLogicOption = false;
Device[deviceCount].FormulaOption = false;
Device[deviceCount].ValueCount = 1;
Device[deviceCount].SendDataOption = true;
Device[deviceCount].TimerOption = false;
Device[deviceCount].GlobalSyncOption = true;
break;
}
{
Device[++deviceCount].Number = PLUGIN_ID_008;
Device[deviceCount].Type = DEVICE_TYPE_DUAL;
Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_LONG;
Device[deviceCount].Ports = 0;
Device[deviceCount].PullUpOption = false;
Device[deviceCount].InverseLogicOption = false;
Device[deviceCount].FormulaOption = false;
Device[deviceCount].ValueCount = 1;
Device[deviceCount].SendDataOption = true;
Device[deviceCount].TimerOption = false;
Device[deviceCount].GlobalSyncOption = true;
break;
}
case PLUGIN_GET_DEVICENAME:
{
string = F(PLUGIN_NAME_008);
break;
}
{
string = F(PLUGIN_NAME_008);
break;
}
case PLUGIN_GET_DEVICEVALUENAMES:
{
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_008));
break;
}
{
strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_008));
break;
}
case PLUGIN_GET_DEVICEGPIONAMES:
{
event->String1 = formatGpioName_input(F("D0 (Green, 5V)"));
event->String2 = formatGpioName_input(F("D1 (White, 5V)"));
break;
}
{
event->String1 = formatGpioName_input(F("D0 (Green, 5V)"));
event->String2 = formatGpioName_input(F("D1 (White, 5V)"));
break;
}
case PLUGIN_INIT:
{
Plugin_008_init = true;
Plugin_008_WiegandSize = PCONFIG(0);
pinMode(CONFIG_PIN1, INPUT_PULLUP);
pinMode(CONFIG_PIN2, INPUT_PULLUP);
attachInterrupt(CONFIG_PIN1, Plugin_008_interrupt1, FALLING);
attachInterrupt(CONFIG_PIN2, Plugin_008_interrupt2, FALLING);
success = true;
break;
}
{
Plugin_008_init = true;
Plugin_008_WiegandSize = PCONFIG(0);
pinMode(CONFIG_PIN1, INPUT_PULLUP);
pinMode(CONFIG_PIN2, INPUT_PULLUP);
attachInterrupt(CONFIG_PIN1, Plugin_008_interrupt1, FALLING);
attachInterrupt(CONFIG_PIN2, Plugin_008_interrupt2, FALLING);
success = true;
break;
}
case PLUGIN_TIMER_IN:
{
if (Plugin_008_init && PCONFIG(2) == 0) { // PCONFIG(2) check uses inversed logic!
// Reset card id on timeout
UserVar.setSensorTypeLong(event->TaskIndex, PCONFIG_LONG(0));
addLog(LOG_LEVEL_INFO, F("RFID : Removed Tag"));
if (PCONFIG(3) == 1) {
sendData(event);
}
success = true;
{
if (Plugin_008_init && (PCONFIG(2) == 0)) { // PCONFIG(2) check uses inversed logic!
// Reset card id on timeout
UserVar.setSensorTypeLong(event->TaskIndex, PCONFIG_LONG(0));
addLog(LOG_LEVEL_INFO, F("RFID : Removed Tag"));
if (PCONFIG(3) == 1) {
sendData(event);
}
break;
success = true;
}
break;
}
case PLUGIN_ONCE_A_SECOND:
{
if (Plugin_008_init)
{
if (Plugin_008_init)
if (Plugin_008_bitCount > 0)
{
if (Plugin_008_bitCount > 0)
if ((Plugin_008_bitCount % 4 == 0) && ((Plugin_008_keyBuffer & 0xF) == 11))
{
if (Plugin_008_bitCount % 4 == 0 && ((Plugin_008_keyBuffer & 0xF) == 11))
{
// a number of keys were pressed and finished by #
Plugin_008_keyBuffer = Plugin_008_keyBuffer >> 4; // Strip #
}
else if (Plugin_008_bitCount == Plugin_008_WiegandSize)
{
// read a tag
Plugin_008_keyBuffer = Plugin_008_keyBuffer >> 1; // Strip leading and trailing parity bits from the keyBuffer
if (Plugin_008_WiegandSize == 26)
Plugin_008_keyBuffer &= 0xFFFFFF;
else
Plugin_008_keyBuffer &= 0xFFFFFFFF;
}
else
{
// not enough bits, maybe next time
Plugin_008_timeoutCount++;
if (Plugin_008_timeoutCount > 5)
{
if (loglevelActiveFor(LOG_LEVEL_INFO)) {
String log = F("RFID : reset bits: ");
log += Plugin_008_bitCount;
addLog(LOG_LEVEL_INFO, log );
}
// reset after ~5 sec
Plugin_008_keyBuffer = 0;
Plugin_008_bitCount = 0;
Plugin_008_timeoutCount = 0;
}
break;
}
// a number of keys were pressed and finished by #
Plugin_008_keyBuffer = Plugin_008_keyBuffer >> 4; // Strip #
}
else if (Plugin_008_bitCount == Plugin_008_WiegandSize)
{
// read a tag
Plugin_008_keyBuffer = Plugin_008_keyBuffer >> 1; // Strip leading and trailing parity bits from the keyBuffer
unsigned long old_key = UserVar.getSensorTypeLong(event->TaskIndex);
bool new_key = false;
if (PCONFIG(1) == 1) {
Plugin_008_keyBuffer = castHexAsDec(Plugin_008_keyBuffer);
}
if (old_key != Plugin_008_keyBuffer) {
UserVar.setSensorTypeLong(event->TaskIndex, Plugin_008_keyBuffer);
new_key = true;
}
if (loglevelActiveFor(LOG_LEVEL_INFO)) {
// write log
String log = F("RFID : ");
if (new_key) {
log += F("New Tag: ");
} else {
log += F("Old Tag: ");
}
log += (unsigned long) Plugin_008_keyBuffer;
log += F(" Bits: ");
log += Plugin_008_bitCount;
addLog(LOG_LEVEL_INFO, log);
}
// reset everything
Plugin_008_keyBuffer = 0;
Plugin_008_bitCount = 0;
Plugin_008_timeoutCount = 0;
uint64_t keyMask = 0LL;
keyMask = (0x1 << (Plugin_008_WiegandSize - 2)); // Shift in 1 just past the number of remaining bits
keyMask--; // Decrement by 1 to get 0xFFFFFFFFFFFF...
Plugin_008_keyBuffer &= keyMask;
}
else
{
// not enough bits, maybe next time
Plugin_008_timeoutCount++;
if (new_key) sendData(event);
uint32_t resetTimer = PCONFIG_LONG(1);
if (resetTimer < 250) resetTimer = 250;
Scheduler.setPluginTaskTimer(resetTimer, event->TaskIndex, event->Par1);
if (Plugin_008_timeoutCount > 5)
{
if (loglevelActiveFor(LOG_LEVEL_INFO)) {
String log = F("RFID : reset bits: ");
log += Plugin_008_bitCount;
addLog(LOG_LEVEL_INFO, log);
}
// reset after ~5 sec
Plugin_008_keyBuffer = 0;
Plugin_008_bitCount = 0;
Plugin_008_timeoutCount = 0;
}
break;
}
unsigned long old_key = UserVar.getSensorTypeLong(event->TaskIndex);
bool new_key = false;
if (PCONFIG(1) == 1) {
Plugin_008_keyBuffer = castHexAsDec(Plugin_008_keyBuffer);
}
if (old_key != Plugin_008_keyBuffer) {
UserVar.setSensorTypeLong(event->TaskIndex, Plugin_008_keyBuffer);
new_key = true;
}
if (loglevelActiveFor(LOG_LEVEL_INFO)) {
// write log
String log = F("RFID : ");
if (new_key) {
log += F("New Tag: ");
} else {
log += F("Old Tag: ");
}
log += (unsigned long)Plugin_008_keyBuffer;
log += F(" Bits: ");
log += Plugin_008_bitCount;
addLog(LOG_LEVEL_INFO, log);
}
// reset everything
Plugin_008_keyBuffer = 0;
Plugin_008_bitCount = 0;
Plugin_008_timeoutCount = 0;
if (new_key) { sendData(event); }
uint32_t resetTimer = PCONFIG_LONG(1);
if (resetTimer < 250) { resetTimer = 250; }
Scheduler.setPluginTaskTimer(resetTimer, event->TaskIndex, event->Par1);
// String info = "";
// uint64_t invalue = 0x1234;
@@ -200,74 +222,78 @@ boolean Plugin_008(uint8_t function, struct EventStruct *event, String& string)
// info += F(") => ");
// info += (double)outvalue;
// addLog(LOG_LEVEL_INFO, info);
}
}
break;
}
case PLUGIN_WEBFORM_LOAD:
{
uint8_t choice = PCONFIG(0);
{
const __FlashStringHelper * options[2];
options[0] = F("26 Bits");
options[1] = F("34 Bits");
int optionValues[2];
optionValues[0] = 26;
optionValues[1] = 34;
addFormSelector(F("Wiegand Type"), F("p008_type"), 2, options, optionValues, choice);
}
bool presentHexToDec = PCONFIG(1) == 1;
addFormCheckBox(F("Present hex as decimal value"), F("p008_hexasdec"), presentHexToDec);
addFormNote(F("Useful only for numeric keypad input!"));
break;
}
case PLUGIN_WEBFORM_LOAD:
{
addFormNumericBox(F("Wiegand Type (bits)"), F("p008_type"), PCONFIG(0), 26, 64);
addUnit(F("26..64 bits"));
addFormNote(F("Select the number of bits to be received, f.e. 26, 34, 37."));
bool autoTagRemoval = PCONFIG(2) == 0; // Inverted state!
addFormCheckBox(F("Automatic Tag removal"), F("p008_autotagremoval"), autoTagRemoval);
addFormCheckBox(F("Present hex as decimal value"), F("p008_hexasdec"), PCONFIG(1) == 1);
addFormNote(F("Useful only for numeric keypad input!"));
if (PCONFIG_LONG(1) == 0) PCONFIG_LONG(1) = 500; // Defaulty 500 mSec (was hardcoded value)
addFormNumericBox(F("Automatic Tag removal after"),F("p008_removaltimeout"), PCONFIG_LONG(1), 250, 60000); // 0.25 to 60 seconds
addUnit(F("mSec."));
addFormCheckBox(F("Automatic Tag removal"), F("p008_autotagremoval"), PCONFIG(2) == 0); // Inverted state!
addFormNumericBox(F("Value to set on Tag removal"),F("p008_removalvalue"), PCONFIG_LONG(0), 0, 2147483647); // Max allowed is int = 0x7FFFFFFF ...
if (PCONFIG_LONG(1) == 0) { PCONFIG_LONG(1) = 500; } // Defaulty 500 mSec (was hardcoded value)
addFormNumericBox(F("Automatic Tag removal after"), F("p008_removaltimeout"), PCONFIG_LONG(1), 250, 60000); // 0.25 to 60 seconds
addUnit(F("mSec."));
bool eventOnRemoval = PCONFIG(3) == 1; // Normal state!
addFormCheckBox(F("Event on Tag removal"), F("p008_sendreset"), eventOnRemoval);
addFormNumericBox(F("Value to set on Tag removal"), F("p008_removalvalue"), PCONFIG_LONG(0), 0, 2147483647); // Max allowed is int =
// 0x7FFFFFFF ...
success = true;
break;
}
addFormCheckBox(F("Event on Tag removal"), F("p008_sendreset"), PCONFIG(3) == 1); // Normal state!
case PLUGIN_WEBFORM_SAVE:
{
PCONFIG(0) = getFormItemInt(F("p008_type"));
PCONFIG(1) = isFormItemChecked(F("p008_hexasdec")) ? 1 : 0;
PCONFIG(2) = isFormItemChecked(F("p008_autotagremoval")) ? 0 : 1; // Inverted logic!
PCONFIG(3) = isFormItemChecked(F("p008_sendreset")) ? 1 : 0;
PCONFIG_LONG(0) = getFormItemInt(F("p008_removalvalue"));
PCONFIG_LONG(1) = getFormItemInt(F("p008_removaltimeout"));
success = true;
break;
}
success = true;
break;
}
case PLUGIN_WEBFORM_SAVE:
{
PCONFIG(0) = getFormItemInt(F("p008_type"));
PCONFIG(1) = isFormItemChecked(F("p008_hexasdec")) ? 1 : 0;
PCONFIG(2) = isFormItemChecked(F("p008_autotagremoval")) ? 0 : 1; // Inverted logic!
PCONFIG(3) = isFormItemChecked(F("p008_sendreset")) ? 1 : 0;
PCONFIG_LONG(0) = getFormItemInt(F("p008_removalvalue"));
PCONFIG_LONG(1) = getFormItemInt(F("p008_removaltimeout"));
// uint64_t keyMask = 0LL;
// keyMask = (0x1 << (PCONFIG(0) - 2));
// keyMask--;
// String log = F("P008: testing keyMask = 0x");
// log += ull2String(keyMask, HEX);
// log += F(" bits: ");
// log += PCONFIG(0);
// addLog(LOG_LEVEL_INFO, log);
success = true;
break;
}
}
return success;
}
/*********************************************************************/
void Plugin_008_interrupt1()
/*********************************************************************/
{
// We've received a 1 bit. (bit 0 = high, bit 1 = low)
Plugin_008_keyBuffer = Plugin_008_keyBuffer << 1; // Left shift the number (effectively multiplying by 2)
Plugin_008_keyBuffer += 1; // Add the 1 (not necessary for the zeroes)
Plugin_008_bitCount++; // Increment the bit count
Plugin_008_keyBuffer = Plugin_008_keyBuffer << 1; // Left shift the number (effectively multiplying by 2)
Plugin_008_keyBuffer += 1; // Add the 1 (not necessary for the zeroes)
Plugin_008_bitCount++; // Increment the bit count
}
/*********************************************************************/
void Plugin_008_interrupt2()
/*********************************************************************/
{
// We've received a 0 bit. (bit 0 = low, bit 1 = high)
Plugin_008_keyBuffer = Plugin_008_keyBuffer << 1; // Left shift the number (effectively multiplying by 2)
Plugin_008_bitCount++; // Increment the bit count
Plugin_008_keyBuffer = Plugin_008_keyBuffer << 1; // Left shift the number (effectively multiplying by 2)
Plugin_008_bitCount++; // Increment the bit count
}
#endif // USES_P008
+161 -127
View File
@@ -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
+1 -2
View File
@@ -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);
+30 -3
View File
@@ -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, PCF8574<b>A</b> 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
+1
View File
@@ -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);
+14
View File
@@ -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("");
}
+14
View File
@@ -56,4 +56,18 @@ private:
typedef I2Cdata<uint8_t> I2Cdata_bytes;
typedef I2Cdata<uint16_t>I2Cdata_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
+12
View File
@@ -247,6 +247,18 @@ void SettingsStruct_tmpl<N_TASKS>::AllowTaskValueSetAllPlugins(bool value) {
bitWrite(VariousBits1, 21, value);
}
template<unsigned int N_TASKS>
bool SettingsStruct_tmpl<N_TASKS>::EnableClearHangingI2Cbus() const {
return bitRead(VariousBits1, 22);
}
template<unsigned int N_TASKS>
void SettingsStruct_tmpl<N_TASKS>::EnableClearHangingI2Cbus(bool value) {
bitWrite(VariousBits1, 22, value);
}
template<unsigned int N_TASKS>
ExtTimeSource_e SettingsStruct_tmpl<N_TASKS>::ExtTimeSource() const {
return static_cast<ExtTimeSource_e>(ExternalTimeSource >> 1);
+4
View File
@@ -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;
+37 -1
View File
@@ -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.
+14 -30
View File
@@ -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);
@@ -241,6 +245,7 @@ void prepare_I2C_by_taskIndex(taskIndex_t taskIndex, deviceIndex_t DeviceIndex)
if (bitRead(Settings.I2C_Flags[taskIndex], I2C_FLAGS_SLOW_SPEED)) {
I2CSelectLowClockSpeed(); // Set to slow
}
return true;
}
@@ -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);
+3 -1
View File
@@ -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);
/*********************************************************************************************\
+4
View File
@@ -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;
+6
View File
@@ -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
+23 -3
View File
@@ -256,16 +256,36 @@ void I2CSelect_Max100kHz_ClockSpeed() {
}
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;
if (clockFreq == 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 = clockFreq;
last_scl = scl;
last_sda = sda;
#ifdef ESP32
Wire.begin(Settings.Pin_i2c_sda, Settings.Pin_i2c_scl, clockFreq);
Wire.begin(sda, scl, clockFreq);
#else
Wire.begin(Settings.Pin_i2c_sda, Settings.Pin_i2c_scl);
Wire.begin(sda, scl);
Wire.setClock(clockFreq);
#endif
}
+2
View File
@@ -28,6 +28,8 @@ 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();
+138 -5
View File
@@ -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();
}
// **************************************************************************/
+3 -1
View File
@@ -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
+7
View File
@@ -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());
+4
View File
@@ -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,
+2
View File
@@ -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());
+18 -3
View File
@@ -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
}
+6
View File
@@ -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() {