diff --git a/BUILDS.md b/BUILDS.md
index 254c84be8..686798083 100644
--- a/BUILDS.md
+++ b/BUILDS.md
@@ -186,6 +186,10 @@
| USE_DISPLAY_ILI9488 | - | - | - | - | - | - | - |
| USE_DISPLAY_SSD1351 | - | - | - | - | - | - | - |
| USE_DISPLAY_RA8876 | - | - | - | - | - | - | - |
+| | | | | | | | |
+| Feature or Sensor | minimal | lite | tasmota | knx | sensors | ir | display | Remarks
+| USE_EZOPH | - | - | - | - | - | - | - |
+| USE_EZOORP | - | - | - | - | - | - | - |
## Additional Features and Sensors on ESP32
diff --git a/I2CDEVICES.md b/I2CDEVICES.md
index 71ceba8ac..282381489 100644
--- a/I2CDEVICES.md
+++ b/I2CDEVICES.md
@@ -77,3 +77,5 @@ Index | Define | Driver | Device | Address(es) | Description
52 | USE_HP303B | xsns_73 | HP303B | 0x76 - 0x77 | Pressure and temperature sensor
53 | USE_MLX90640 | xdrv_84 | MLX90640 | 0x33 | IR array temperature sensor
54 | USE_VL53L1X | xsns_77 | VL53L1X | 0x29 | Time-of-flight (ToF) distance sensor
+ 55 | USE_EZO_PH | xsns_78 | EZOPH | 0x61 - 0x70 | pH Sensor
+ 55 | USE_EZO_ORP | xsns_79 | EZOORP | 0x61 - 0x70 | ORP Sensor
diff --git a/tasmota/ezo.ino b/tasmota/ezo.ino
new file mode 100644
index 000000000..926fc334b
--- /dev/null
+++ b/tasmota/ezo.ino
@@ -0,0 +1,120 @@
+/*
+ ezo.ino - EZO modules base class
+
+ Copyright (C) 2020 Christopher Tremblay
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+#ifdef USE_I2C
+#if defined(USE_EZOPH) || defined(USE_EZOORP)
+
+#define D_EZO_DELAY 300 // Minimum delay for any instruction
+#define D_EZO_MAX_BUF 40 // Maximum response
+
+
+
+// This baseclass provides common functionality for all EZO devices
+struct EZOStruct {
+ void MeasureRequest(void)
+ {
+ const uint8_t EZOMeasureCmd[2] = {'R', 0};
+
+ if (valid) {
+ valid--;
+ }
+
+ Wire.beginTransmission(addr);
+ Wire.write(EZOMeasureCmd, sizeof(EZOMeasureCmd));
+ Wire.endTransmission();
+
+ lastRead = millis();
+ }
+
+ void ProcessMeasurement(char *const data, const uint32_t len, const uint32_t latency)
+ {
+ // Wait for the data to arrive first
+ const int32_t dur = lastRead + latency - millis();
+
+ // Wait for the last readout to complete (only when commands are issued)
+ if (dur > 0) {
+ delay(dur);
+ }
+
+ Wire.requestFrom(addr, len);
+ const char code = Wire.read();
+
+ if (code == 1) {
+ for (uint32_t i = 0; (Wire.available() > 0) && (i < len); i++) {
+ data[i] = Wire.read();
+ }
+
+ valid = SENSOR_MAX_MISS;
+ }
+ }
+
+ void HandleCommand(uint32_t index) const
+ {
+ char *at = XdrvMailbox.data;
+ uint32_t len = XdrvMailbox.data_len;
+
+ // Figure out if we're trying to address a specific device
+ // PS: This should ideally be done through the Tasmota mailbox
+ if (at[0] == '-') {
+ uint32_t idx = atoi(&at[1]) - 1;
+ at = strchr(at, ' ');
+
+ if (!at++) {
+ return;
+ }
+
+ len -= (at - XdrvMailbox.data);
+
+ if (idx != index) {
+ return;
+ }
+ }
+
+ // Transmit our command verbatim
+ Wire.beginTransmission(addr);
+ Wire.write(at, len);
+ if (Wire.endTransmission() != 0) {
+ return;
+ }
+
+ // Attempt to read the results
+ char data[D_EZO_MAX_BUF];
+ for (uint32_t code = 254; code == 254; code = Wire.read()) {
+ delay(D_EZO_DELAY);
+ Wire.requestFrom(addr, sizeof(data));
+ }
+
+ for (uint32_t i = 0; Wire.available() > 0; i++) {
+ data[i] = Wire.read();
+ }
+
+ ResponseCmndChar((char *)data);
+ }
+
+public:
+ uint8_t valid = 0;
+ uint8_t addr;
+
+private:
+ uint32_t lastRead = -2000;
+};
+
+
+
+#endif // USE_EZO*
+#endif // USE_I2C
diff --git a/tasmota/ezoManager.ino b/tasmota/ezoManager.ino
new file mode 100644
index 000000000..5e2bf4485
--- /dev/null
+++ b/tasmota/ezoManager.ino
@@ -0,0 +1,261 @@
+/*
+ ezoManager.ino - EZO device manager
+
+ Copyright (C) 2020 Christopher Tremblay
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+#ifdef USE_I2C
+#if defined(USE_EZOPH) || defined(USE_EZOORP)
+
+#define XI2C_55 55 // See I2CDEVICES.md
+
+#define EZO_ADDR_0 0x61 // First EZO address
+#define EZO_ADDR_n 16 // Number of ports for use with EZO devices
+
+
+// List of known EZO devices and their default address
+enum EZOType {
+ EZO_DO = 0x61, // D.O.
+ EZO_ORP = 0x62, // ORP
+ EZO_PH = 0x63, // pH
+ EZO_EC = 0x64, // EC
+
+ EZO_RTD = 0x66, // RTD
+ EZO_PMP = 0x67, // PMP
+ EZO_FLO = 0x68, // FLO
+ EZO_CO2 = 0x69, // CO2
+ EZO_PRS = 0x6A, // PRS
+
+ EZO_O2 = 0x6C, // O2
+
+
+ EZO_HUM = 0x6F, // HUM
+ EZO_RGB = 0x70, // RGB
+};
+
+const char EZO_EMPTY[] PROGMEM = "";
+//const char EZO_DO_NAME[] PROGMEM = "DO";
+#ifdef USE_EZOORP
+const char EZO_ORP_NAME[] PROGMEM = "ORP";
+#endif
+#ifdef USE_EZOPH
+const char EZO_PH_NAME[] PROGMEM = "pH";
+#endif
+//const char EZO_EC_NAME[] PROGMEM = "EC";
+//const char EZO_RTD_NAME[] PROGMEM = "RTD";
+//const char EZO_PMP_NAME[] PROGMEM = "PMP";
+//const char EZO_FLO_NAME[] PROGMEM = "FLO";
+//const char EZO_CO2_NAME[] PROGMEM = "CO2";
+//const char EZO_PRS_NAME[] PROGMEM = "PRS";
+//const char EZO_O2_NAME[] PROGMEM = "O2";
+//const char EZO_HUM_NAME[] PROGMEM = "HUM";
+//const char EZO_RGB_NAME[] PROGMEM = "RGB";
+
+const char *const EZOSupport[EZO_ADDR_n] PROGMEM = {
+ EZO_EMPTY,
+
+#ifdef USE_EZOORP
+ EZO_ORP_NAME,
+#else
+ EZO_EMPTY,
+#endif
+
+#ifdef USE_EZOPH
+ EZO_PH_NAME,
+#else
+ EZO_EMPTY,
+#endif
+
+ EZO_EMPTY,
+ EZO_EMPTY,
+ EZO_EMPTY,
+ EZO_EMPTY,
+ EZO_EMPTY,
+ EZO_EMPTY,
+ EZO_EMPTY,
+ EZO_EMPTY,
+ EZO_EMPTY,
+ EZO_EMPTY,
+ EZO_EMPTY,
+ EZO_EMPTY,
+ EZO_EMPTY,
+};
+
+
+
+struct EZOManager {
+ // Returns the count of devices of the specified type or -1 if the driver isn't ready yet
+ // list must be a client-allocated array of atleast 16 elements
+ int getDevice(const EZOType type, uint32_t *list)
+ {
+ // EZO devices take 2s to boot
+ if (uptime >= next) {
+ if (stage == 0) {
+ DetectRequest();
+ next = uptime + 1;
+ } else if (stage == 1) {
+ ProcessDetection();
+ }
+
+ stage++;
+ }
+
+ if (stage >= 2) {
+ int count = 0;
+
+ for (uint32_t i = 0; i < EZO_ADDR_n; i++) {
+ if ((alive & (1 << i)) && (((devices[i >> 3] >> ((i & 7) << 2)) & 0xF) == (type - EZO_ADDR_0))) {
+ list[count++] = i + EZO_ADDR_0;
+ }
+ }
+
+ return count;
+ }
+
+ return -1;
+ }
+
+private:
+ void DetectRequest(void)
+ {
+ const uint8_t EZOInfoCmd[2] = {'i', 0};
+ alive = 0;
+
+ // Scan the address range
+ uint16_t shift = 1;
+ for (uint8_t i = EZO_ADDR_0; shift; i++) {
+ if (!I2cActive(i)) {
+ // Request the device to identify itself
+ Wire.beginTransmission(i);
+ Wire.write(EZOInfoCmd, sizeof(EZOInfoCmd));
+
+ int c = Wire.endTransmission();
+
+ if (c == 0) {
+ alive |= shift;
+ }
+ }
+ shift <<= 1;
+ }
+ }
+
+ void ProcessDetection(void)
+ {
+ uint32_t mask = alive;
+
+ devices[0] = devices[1] = 0;
+
+ // Check every address that we sent a request to
+ for (uint8_t addr = 0; addr < EZO_ADDR_n; addr++) {
+ if (mask & 1) {
+ char data[D_EZO_MAX_BUF];
+ Wire.requestFrom(addr + EZO_ADDR_0, sizeof(data));
+ char code = Wire.read();
+
+ if (code == 1) {
+ uint32_t i;
+
+ for (i = 0; Wire.available() > 0; i++) {
+ char c = Wire.read();
+
+ // Helps us strcmp
+ data[i] = (c == ',') ? 0 : c;
+ }
+
+ // Technically the response starts with "?I," but we'll skip testing it to save space
+ if (i >= 3) {
+ for (uint32_t j = 0; j < EZO_ADDR_n; j++) {
+ if (strcasecmp_P(&data[3], EZOSupport[j]) == 0) {
+ data[0] = 'E';
+ data[1] = 'Z';
+ data[2] = 'O';
+ I2cSetActiveFound(addr, data);
+ devices[addr >> 3] |= j << ((addr & 7) * 4);
+ }
+ }
+ }
+ }
+ }
+
+ mask >>= 1;
+ }
+ }
+
+ uint32_t next = 2;
+ uint8_t stage = 0;
+
+// Following 2 members are harcoded to allow a maximum of 16 entries
+ uint16_t alive;
+ uint32_t devices[2];
+} EZOManager;
+
+
+
+// The main driver is the same for all devices.
+// What changes is the implementation of the class itself
+template bool XsnsEZO(uint8_t function)
+{
+ if (!I2cEnabled(XI2C_55)) {
+ return false;
+ }
+
+ // Initialization: Gather the list of devices for this class
+ if ((T::count < 0) && (function == FUNC_EVERY_SECOND)) {
+ uint32_t addr[EZO_ADDR_n];
+ T::count = EZOManager.getDevice(type, &addr[0]);
+
+ if (T::count > 0) {
+ T::list = new T[T::count];
+
+ for (uint32_t i = 0; i < T::count; i++) {
+ T::list[i].addr = addr[i];
+ }
+ }
+ }
+
+ // Process the function on each of them
+ T *cur = &T::list[0];
+ for (int32_t i = 0; i < T::count; i++) {
+ switch (function) {
+ case FUNC_COMMAND_SENSOR:
+ cur->ProcessMeasurement();
+ cur->HandleCommand(i);
+ break;
+
+ case FUNC_EVERY_SECOND:
+ if (uptime & 1) {
+ cur->ProcessMeasurement();
+ cur->MeasureRequest();
+ }
+ break;
+
+ case FUNC_JSON_APPEND:
+ cur->Show(1, i);
+ break;
+
+#ifdef USE_WEBSERVER
+ case FUNC_WEB_SENSOR:
+ cur->Show(0, i);
+ break;
+ }
+#endif // USE_WEBSERVER
+ cur++;
+ }
+
+ return false;
+}
+
+#endif // USE_EZO*
+#endif // USE_I2C
diff --git a/tasmota/i18n.h b/tasmota/i18n.h
index d6ee87038..b41a2505d 100644
--- a/tasmota/i18n.h
+++ b/tasmota/i18n.h
@@ -111,7 +111,9 @@
#define D_JSON_NOISE "Noise"
#define D_JSON_NONE "None"
#define D_JSON_OR "or"
+#define D_JSON_ORP "ORP"
#define D_JSON_PERIOD "Period"
+#define D_JSON_PH "pH"
#define D_JSON_PHASE_ANGLE "PhaseAngle"
#define D_JSON_POWERFACTOR "Factor"
#define D_JSON_POWERUSAGE "Power"
@@ -768,6 +770,8 @@ const char HTTP_SNS_VOLTAGE[] PROGMEM = "{s}" D_VOLTAGE "{
const char HTTP_SNS_CURRENT[] PROGMEM = "{s}" D_CURRENT "{m}%s " D_UNIT_AMPERE "{e}";
const char HTTP_SNS_POWER[] PROGMEM = "{s}" D_POWERUSAGE "{m}%s " D_UNIT_WATT "{e}";
const char HTTP_SNS_ENERGY_TOTAL[] PROGMEM = "{s}" D_ENERGY_TOTAL "{m}%s " D_UNIT_KILOWATTHOUR "{e}";
+const char HTTP_SNS_PH[] PROGMEM = "{s}%s " D_PH "{m}%s " "{e}";
+const char HTTP_SNS_ORP[] PROGMEM = "{s}%s " D_ORP "{m}%s " D_UNIT_MILLIVOLT "{e}";
const char S_MAIN_MENU[] PROGMEM = D_MAIN_MENU;
const char S_CONFIGURATION[] PROGMEM = D_CONFIGURATION;
diff --git a/tasmota/language/bg_BG.h b/tasmota/language/bg_BG.h
index 9cc01de9a..f0a869cf9 100644
--- a/tasmota/language/bg_BG.h
+++ b/tasmota/language/bg_BG.h
@@ -129,7 +129,9 @@
#define D_OK "Ок"
#define D_ON "Вкл."
#define D_ONLINE "Онлайн"
+#define D_ORP "ORP"
#define D_PASSWORD "Парола"
+#define D_PH "pH"
#define D_PORT "Порт"
#define D_POWER_FACTOR "Фактор на мощността"
#define D_POWERUSAGE "Мощност"
@@ -749,6 +751,7 @@
#define D_UNIT_MILLIMETER "mm"
#define D_UNIT_MILLIMETER_MERCURY "mmHg"
#define D_UNIT_MILLISECOND "ms"
+#define D_UNIT_MILLIVOLT "mV"
#define D_UNIT_MINUTE "min"
#define D_UNIT_PARTS_PER_BILLION "ppb"
#define D_UNIT_PARTS_PER_DECILITER "ppd"
diff --git a/tasmota/language/cs_CZ.h b/tasmota/language/cs_CZ.h
index bf8f67a37..ec09983a7 100644
--- a/tasmota/language/cs_CZ.h
+++ b/tasmota/language/cs_CZ.h
@@ -129,7 +129,9 @@
#define D_OK "OK"
#define D_ON "Zap."
#define D_ONLINE "Online" // Don't translate, LWT message! Nepředkládat, LWT zpráva!
+#define D_ORP "ORP"
#define D_PASSWORD "Heslo"
+#define D_PH "pH"
#define D_PORT "Port"
#define D_POWER_FACTOR "Účiník"
#define D_POWERUSAGE "Příkon"
@@ -749,6 +751,7 @@
#define D_UNIT_MILLIMETER "mm"
#define D_UNIT_MILLIMETER_MERCURY "mmHg"
#define D_UNIT_MILLISECOND "ms"
+#define D_UNIT_MILLIVOLT "mV"
#define D_UNIT_MINUTE "min"
#define D_UNIT_PARTS_PER_BILLION "ppb"
#define D_UNIT_PARTS_PER_DECILITER "ppd"
diff --git a/tasmota/language/de_DE.h b/tasmota/language/de_DE.h
index 23db7da4f..4d3d9eecd 100644
--- a/tasmota/language/de_DE.h
+++ b/tasmota/language/de_DE.h
@@ -129,7 +129,9 @@
#define D_OK "OK"
#define D_ON "an"
#define D_ONLINE "Online"
+#define D_ORP "ORP"
#define D_PASSWORD "Passwort"
+#define D_PH "pH"
#define D_PORT "Port"
#define D_POWER_FACTOR "Leistungsfaktor"
#define D_POWERUSAGE "Leistung"
@@ -749,6 +751,7 @@
#define D_UNIT_MILLIMETER "mm"
#define D_UNIT_MILLIMETER_MERCURY "mmHg"
#define D_UNIT_MILLISECOND "ms"
+#define D_UNIT_MILLIVOLT "mV"
#define D_UNIT_MINUTE "min"
#define D_UNIT_PARTS_PER_BILLION "ppb"
#define D_UNIT_PARTS_PER_DECILITER "ppd"
diff --git a/tasmota/language/el_GR.h b/tasmota/language/el_GR.h
index 95b640da8..1f20dd270 100644
--- a/tasmota/language/el_GR.h
+++ b/tasmota/language/el_GR.h
@@ -129,7 +129,9 @@
#define D_OK "Ok"
#define D_ON "On"
#define D_ONLINE "Online"
+#define D_ORP "ORP"
#define D_PASSWORD "Κωδικός"
+#define D_PH "pH"
#define D_PORT "Θύρα"
#define D_POWER_FACTOR "Συντελεστής Ισχύος"
#define D_POWERUSAGE "Ισχύς"
@@ -749,6 +751,7 @@
#define D_UNIT_MILLIMETER "mm"
#define D_UNIT_MILLIMETER_MERCURY "mmHg"
#define D_UNIT_MILLISECOND "ms"
+#define D_UNIT_MILLIVOLT "mV"
#define D_UNIT_MINUTE "Min"
#define D_UNIT_PARTS_PER_BILLION "ppb"
#define D_UNIT_PARTS_PER_DECILITER "ppd"
diff --git a/tasmota/language/en_GB.h b/tasmota/language/en_GB.h
index 2ac306f8e..7e84ff75b 100644
--- a/tasmota/language/en_GB.h
+++ b/tasmota/language/en_GB.h
@@ -129,7 +129,9 @@
#define D_OK "Ok"
#define D_ON "On"
#define D_ONLINE "Online"
+#define D_ORP "ORP"
#define D_PASSWORD "Password"
+#define D_PH "pH"
#define D_PORT "Port"
#define D_POWER_FACTOR "Power Factor"
#define D_POWERUSAGE "Power"
@@ -749,6 +751,7 @@
#define D_UNIT_MILLIMETER "mm"
#define D_UNIT_MILLIMETER_MERCURY "mmHg"
#define D_UNIT_MILLISECOND "ms"
+#define D_UNIT_MILLIVOLT "mV"
#define D_UNIT_MINUTE "Min"
#define D_UNIT_PARTS_PER_BILLION "ppb"
#define D_UNIT_PARTS_PER_DECILITER "ppd"
diff --git a/tasmota/language/es_ES.h b/tasmota/language/es_ES.h
index 1401c4fce..35f0ac6f5 100644
--- a/tasmota/language/es_ES.h
+++ b/tasmota/language/es_ES.h
@@ -129,7 +129,9 @@
#define D_OK "Ok"
#define D_ON "Encendido"
#define D_ONLINE "Online"
+#define D_ORP "ORP"
#define D_PASSWORD "Clave"
+#define D_PH "pH"
#define D_PORT "Puerto"
#define D_POWER_FACTOR "Factor de Potencia"
#define D_POWERUSAGE "Potencia"
@@ -749,6 +751,7 @@
#define D_UNIT_MILLIMETER "mm"
#define D_UNIT_MILLIMETER_MERCURY "mmHg"
#define D_UNIT_MILLISECOND "ms"
+#define D_UNIT_MILLIVOLT "mV"
#define D_UNIT_MINUTE "Min"
#define D_UNIT_PARTS_PER_BILLION "ppb"
#define D_UNIT_PARTS_PER_DECILITER "ppd"
diff --git a/tasmota/language/fr_FR.h b/tasmota/language/fr_FR.h
index 15dd090dc..4c7775e49 100644
--- a/tasmota/language/fr_FR.h
+++ b/tasmota/language/fr_FR.h
@@ -125,7 +125,9 @@
#define D_OK "Ok"
#define D_ON "Marche"
#define D_ONLINE "Connecté"
+#define D_ORP "ORP"
#define D_PASSWORD "Mot de passe"
+#define D_PH "pH"
#define D_PORT "Port"
#define D_POWER_FACTOR "Fact de puiss"
#define D_POWERUSAGE "Puissance"
@@ -745,6 +747,7 @@
#define D_UNIT_MILLIMETER "mm"
#define D_UNIT_MILLIMETER_MERCURY "mmHg"
#define D_UNIT_MILLISECOND "ms"
+#define D_UNIT_MILLIVOLT "mV"
#define D_UNIT_MINUTE "min" // https://fr.wikipedia.org/wiki/Minute_(temps)#Symbole%20et%20d%C3%A9finition
#define D_UNIT_PARTS_PER_BILLION "ppb"
#define D_UNIT_PARTS_PER_DECILITER "ppd"
diff --git a/tasmota/language/he_HE.h b/tasmota/language/he_HE.h
index ae2e439be..4fb8cf5b6 100644
--- a/tasmota/language/he_HE.h
+++ b/tasmota/language/he_HE.h
@@ -129,7 +129,9 @@
#define D_OK "אוקיי"
#define D_ON "פועל"
#define D_ONLINE "מחובר"
+#define D_ORP "ORP"
#define D_PASSWORD "סיסמא"
+#define D_PH "pH"
#define D_PORT "פורט"
#define D_POWER_FACTOR "גורם כוח"
#define D_POWERUSAGE "כוח"
@@ -749,6 +751,7 @@
#define D_UNIT_MILLIMETER "mm"
#define D_UNIT_MILLIMETER_MERCURY "mmHg"
#define D_UNIT_MILLISECOND "ms"
+#define D_UNIT_MILLIVOLT "mV"
#define D_UNIT_MINUTE "Min"
#define D_UNIT_PARTS_PER_BILLION "ppb"
#define D_UNIT_PARTS_PER_DECILITER "ppd"
diff --git a/tasmota/language/hu_HU.h b/tasmota/language/hu_HU.h
index 8a800383d..d0cddc994 100644
--- a/tasmota/language/hu_HU.h
+++ b/tasmota/language/hu_HU.h
@@ -129,7 +129,9 @@
#define D_OK "OK"
#define D_ON "Be"
#define D_ONLINE "Online"
+#define D_ORP "ORP"
#define D_PASSWORD "Jelszó"
+#define D_PH "pH"
#define D_PORT "Port"
#define D_POWER_FACTOR "Teljesítménytényező"
#define D_POWERUSAGE "Energiafelhasználás"
@@ -749,6 +751,7 @@
#define D_UNIT_MILLIMETER "mm"
#define D_UNIT_MILLIMETER_MERCURY "mmHg"
#define D_UNIT_MILLISECOND "ms"
+#define D_UNIT_MILLIVOLT "mV"
#define D_UNIT_MINUTE "min"
#define D_UNIT_PARTS_PER_BILLION "ppb"
#define D_UNIT_PARTS_PER_DECILITER "ppd"
diff --git a/tasmota/language/it_IT.h b/tasmota/language/it_IT.h
index d82c47aa3..44c671fdb 100644
--- a/tasmota/language/it_IT.h
+++ b/tasmota/language/it_IT.h
@@ -129,7 +129,9 @@
#define D_OK "OK"
#define D_ON "ON"
#define D_ONLINE "Online"
+#define D_ORP "ORP"
#define D_PASSWORD "Password"
+#define D_PH "pH"
#define D_PORT "Porta"
#define D_POWER_FACTOR "Fattore di potenza"
#define D_POWERUSAGE "Potenza"
@@ -749,6 +751,7 @@
#define D_UNIT_MILLIMETER "mm"
#define D_UNIT_MILLIMETER_MERCURY "mmHg"
#define D_UNIT_MILLISECOND "ms"
+#define D_UNIT_MILLIVOLT "mV"
#define D_UNIT_MINUTE "Min"
#define D_UNIT_PARTS_PER_BILLION "ppb"
#define D_UNIT_PARTS_PER_DECILITER "ppd"
diff --git a/tasmota/language/ko_KO.h b/tasmota/language/ko_KO.h
index 5a8522b72..a0c162c17 100644
--- a/tasmota/language/ko_KO.h
+++ b/tasmota/language/ko_KO.h
@@ -129,7 +129,9 @@
#define D_OK "Ok"
#define D_ON "켜짐"
#define D_ONLINE "온라인"
+#define D_ORP "ORP"
#define D_PASSWORD "비밀번호"
+#define D_PH "pH"
#define D_PORT "포트"
#define D_POWER_FACTOR "Power Factor"
#define D_POWERUSAGE "전원"
@@ -749,6 +751,7 @@
#define D_UNIT_MILLIMETER "mm"
#define D_UNIT_MILLIMETER_MERCURY "mmHg"
#define D_UNIT_MILLISECOND "밀리초"
+#define D_UNIT_MILLIVOLT "mV"
#define D_UNIT_MINUTE "분"
#define D_UNIT_PARTS_PER_BILLION "ppb"
#define D_UNIT_PARTS_PER_DECILITER "ppd"
diff --git a/tasmota/language/nl_NL.h b/tasmota/language/nl_NL.h
index ec8116deb..5abc5852a 100644
--- a/tasmota/language/nl_NL.h
+++ b/tasmota/language/nl_NL.h
@@ -129,7 +129,9 @@
#define D_OK "Ok"
#define D_ON "Aan"
#define D_ONLINE "Online"
+#define D_ORP "ORP"
#define D_PASSWORD "Wachtwoord"
+#define D_PH "pH"
#define D_PORT "Poort"
#define D_POWER_FACTOR "Arbeidsfactor"
#define D_POWERUSAGE "Vermogen"
@@ -749,6 +751,7 @@
#define D_UNIT_MILLIMETER "mm"
#define D_UNIT_MILLIMETER_MERCURY "mmHg"
#define D_UNIT_MILLISECOND "ms"
+#define D_UNIT_MILLIVOLT "mV"
#define D_UNIT_MINUTE "Min"
#define D_UNIT_PARTS_PER_BILLION "ppb"
#define D_UNIT_PARTS_PER_DECILITER "ppd"
diff --git a/tasmota/language/pl_PL.h b/tasmota/language/pl_PL.h
index 8ebba663e..4d274f6c8 100644
--- a/tasmota/language/pl_PL.h
+++ b/tasmota/language/pl_PL.h
@@ -129,7 +129,9 @@
#define D_OK "Ok"
#define D_ON "Załączony"
#define D_ONLINE "Aktywny"
+#define D_ORP "ORP"
#define D_PASSWORD "Hasło"
+#define D_PH "pH"
#define D_PORT "Port"
#define D_POWER_FACTOR "Cosinus fi"
#define D_POWERUSAGE "Moc"
@@ -749,6 +751,7 @@
#define D_UNIT_MILLIMETER "mm"
#define D_UNIT_MILLIMETER_MERCURY "mmHg"
#define D_UNIT_MILLISECOND "ms"
+#define D_UNIT_MILLIVOLT "mV"
#define D_UNIT_MINUTE "Min"
#define D_UNIT_PARTS_PER_BILLION "ppb"
#define D_UNIT_PARTS_PER_DECILITER "ppd"
diff --git a/tasmota/language/pt_BR.h b/tasmota/language/pt_BR.h
index 115704748..b81212785 100644
--- a/tasmota/language/pt_BR.h
+++ b/tasmota/language/pt_BR.h
@@ -129,7 +129,9 @@
#define D_OK "Ok"
#define D_ON "Ligado"
#define D_ONLINE "Conectado"
+#define D_ORP "ORP"
#define D_PASSWORD "Senha"
+#define D_PH "pH"
#define D_PORT "Porta"
#define D_POWER_FACTOR "Fator de potência"
#define D_POWERUSAGE "Potência"
@@ -749,6 +751,7 @@
#define D_UNIT_MILLIMETER "mm"
#define D_UNIT_MILLIMETER_MERCURY "mmHg"
#define D_UNIT_MILLISECOND "ms"
+#define D_UNIT_MILLIVOLT "mV"
#define D_UNIT_MINUTE "M"
#define D_UNIT_PARTS_PER_BILLION "ppb"
#define D_UNIT_PARTS_PER_DECILITER "ppd"
diff --git a/tasmota/language/pt_PT.h b/tasmota/language/pt_PT.h
index 202564422..684f87424 100644
--- a/tasmota/language/pt_PT.h
+++ b/tasmota/language/pt_PT.h
@@ -129,7 +129,9 @@
#define D_OK "Ok"
#define D_ON "On"
#define D_ONLINE "Conetado"
+#define D_ORP "ORP"
#define D_PASSWORD "Palavra Chave"
+#define D_PH "pH"
#define D_PORT "Porta"
#define D_POWER_FACTOR "Factor de Potência"
#define D_POWERUSAGE "Potência"
@@ -749,6 +751,7 @@
#define D_UNIT_MILLIMETER "mm"
#define D_UNIT_MILLIMETER_MERCURY "mmHg"
#define D_UNIT_MILLISECOND "ms"
+#define D_UNIT_MILLIVOLT "mV"
#define D_UNIT_MINUTE "Min"
#define D_UNIT_PARTS_PER_BILLION "ppb"
#define D_UNIT_PARTS_PER_DECILITER "ppd"
diff --git a/tasmota/language/ro_RO.h b/tasmota/language/ro_RO.h
index 13c91f8e8..f61504c4e 100644
--- a/tasmota/language/ro_RO.h
+++ b/tasmota/language/ro_RO.h
@@ -129,7 +129,9 @@
#define D_OK "Ok"
#define D_ON "Aprins"
#define D_ONLINE "Online"
+#define D_ORP "ORP"
#define D_PASSWORD "Parolă"
+#define D_PH "pH"
#define D_PORT "Port"
#define D_POWER_FACTOR "Factor de Putere"
#define D_POWERUSAGE "Putere"
@@ -749,6 +751,7 @@
#define D_UNIT_MILLIMETER "mm"
#define D_UNIT_MILLIMETER_MERCURY "mmHg"
#define D_UNIT_MILLISECOND "ms"
+#define D_UNIT_MILLIVOLT "mV"
#define D_UNIT_MINUTE "Min"
#define D_UNIT_PARTS_PER_BILLION "ppb"
#define D_UNIT_PARTS_PER_DECILITER "ppd"
diff --git a/tasmota/language/ru_RU.h b/tasmota/language/ru_RU.h
index fe4de2043..1bef3939e 100644
--- a/tasmota/language/ru_RU.h
+++ b/tasmota/language/ru_RU.h
@@ -129,7 +129,9 @@
#define D_OK "Ок"
#define D_ON "Вкл"
#define D_ONLINE "Он-лайн"
+#define D_ORP "ORP"
#define D_PASSWORD "Пароль"
+#define D_PH "pH"
#define D_PORT "Порт"
#define D_POWER_FACTOR "Коэффициент Мощности"
#define D_POWERUSAGE "Мощность"
@@ -749,6 +751,7 @@
#define D_UNIT_MILLIMETER "mm"
#define D_UNIT_MILLIMETER_MERCURY "мм рт.ст."
#define D_UNIT_MILLISECOND "мс"
+#define D_UNIT_MILLIVOLT "mV"
#define D_UNIT_MINUTE "мин"
#define D_UNIT_PARTS_PER_BILLION "ppb"
#define D_UNIT_PARTS_PER_DECILITER "ppd"
diff --git a/tasmota/language/sk_SK.h b/tasmota/language/sk_SK.h
index 986bc95cd..d090767a4 100644
--- a/tasmota/language/sk_SK.h
+++ b/tasmota/language/sk_SK.h
@@ -129,7 +129,9 @@
#define D_OK "OK"
#define D_ON "Zap."
#define D_ONLINE "Aktívny"
+#define D_ORP "ORP"
#define D_PASSWORD "Heslo"
+#define D_PH "pH"
#define D_PORT "Port"
#define D_POWER_FACTOR "Účinník"
#define D_POWERUSAGE "Príkon"
@@ -749,6 +751,7 @@
#define D_UNIT_MILLIMETER "mm"
#define D_UNIT_MILLIMETER_MERCURY "mmHg"
#define D_UNIT_MILLISECOND "ms"
+#define D_UNIT_MILLIVOLT "mV"
#define D_UNIT_MINUTE "min"
#define D_UNIT_PARTS_PER_BILLION "ppb"
#define D_UNIT_PARTS_PER_DECILITER "ppd"
diff --git a/tasmota/language/sv_SE.h b/tasmota/language/sv_SE.h
index 3178cbd30..d186cf43b 100644
--- a/tasmota/language/sv_SE.h
+++ b/tasmota/language/sv_SE.h
@@ -129,7 +129,9 @@
#define D_OK "Ok"
#define D_ON "På"
#define D_ONLINE "Ansluten"
+#define D_ORP "ORP"
#define D_PASSWORD "Lösenord"
+#define D_PH "pH"
#define D_PORT "Port"
#define D_POWER_FACTOR "Spänningsfaktor"
#define D_POWERUSAGE "Spänning"
@@ -749,6 +751,7 @@
#define D_UNIT_MILLIMETER "mm"
#define D_UNIT_MILLIMETER_MERCURY "mmHg"
#define D_UNIT_MILLISECOND "ms"
+#define D_UNIT_MILLIVOLT "mV"
#define D_UNIT_MINUTE "Min"
#define D_UNIT_PARTS_PER_BILLION "ppb"
#define D_UNIT_PARTS_PER_DECILITER "ppd"
diff --git a/tasmota/language/tr_TR.h b/tasmota/language/tr_TR.h
index 5c6254f3b..244dad325 100644
--- a/tasmota/language/tr_TR.h
+++ b/tasmota/language/tr_TR.h
@@ -129,7 +129,9 @@
#define D_OK "Tamam"
#define D_ON "On"
#define D_ONLINE "Çevirimiçi"
+#define D_ORP "ORP"
#define D_PASSWORD "Şifre"
+#define D_PH "pH"
#define D_PORT "Port"
#define D_POWER_FACTOR "Güç Faktörü"
#define D_POWERUSAGE "Güç"
@@ -749,6 +751,7 @@
#define D_UNIT_MILLIMETER "mm"
#define D_UNIT_MILLIMETER_MERCURY "mmHg"
#define D_UNIT_MILLISECOND "ms"
+#define D_UNIT_MILLIVOLT "mV"
#define D_UNIT_MINUTE "Min"
#define D_UNIT_PARTS_PER_BILLION "ppb"
#define D_UNIT_PARTS_PER_DECILITER "ppd"
diff --git a/tasmota/language/uk_UA.h b/tasmota/language/uk_UA.h
index 8e3b01691..1f3448303 100644
--- a/tasmota/language/uk_UA.h
+++ b/tasmota/language/uk_UA.h
@@ -129,7 +129,9 @@
#define D_OK "Ок"
#define D_ON "Увімкнено"
#define D_ONLINE "Активний"
+#define D_ORP "ORP"
#define D_PASSWORD "Гасло"
+#define D_PH "pH"
#define D_PORT "Порт"
#define D_POWER_FACTOR "Коефіцієнт потужності"
#define D_POWERUSAGE "Потужність"
@@ -749,6 +751,7 @@
#define D_UNIT_MILLIMETER "мм"
#define D_UNIT_MILLIMETER_MERCURY "ммHg"
#define D_UNIT_MILLISECOND "мС"
+#define D_UNIT_MILLIVOLT "mV"
#define D_UNIT_MINUTE "хв"
#define D_UNIT_PARTS_PER_BILLION "млрд⁻¹"
#define D_UNIT_PARTS_PER_DECILITER "децилітр⁻¹"
diff --git a/tasmota/language/vi_VN.h b/tasmota/language/vi_VN.h
index 48a7b503c..e0700ba32 100644
--- a/tasmota/language/vi_VN.h
+++ b/tasmota/language/vi_VN.h
@@ -129,7 +129,9 @@
#define D_OK "Ok"
#define D_ON "Bật"
#define D_ONLINE "Trực tuyến"
+#define D_ORP "ORP"
#define D_PASSWORD "Mật khẩu"
+#define D_PH "pH"
#define D_PORT "Cổng"
#define D_POWER_FACTOR "Hệ số công suất"
#define D_POWERUSAGE "Công suất"
@@ -749,6 +751,7 @@
#define D_UNIT_MILLIMETER "mm"
#define D_UNIT_MILLIMETER_MERCURY "mmHg"
#define D_UNIT_MILLISECOND "ms"
+#define D_UNIT_MILLIVOLT "mV"
#define D_UNIT_MINUTE "Min"
#define D_UNIT_PARTS_PER_BILLION "ppb"
#define D_UNIT_PARTS_PER_DECILITER "ppd"
diff --git a/tasmota/language/zh_CN.h b/tasmota/language/zh_CN.h
index a50acdb0e..a7d930ae4 100644
--- a/tasmota/language/zh_CN.h
+++ b/tasmota/language/zh_CN.h
@@ -129,7 +129,9 @@
#define D_OK "好"
#define D_ON "开"
#define D_ONLINE "在线"
+#define D_ORP "ORP"
#define D_PASSWORD "密码"
+#define D_PH "pH"
#define D_PORT "端口"
#define D_POWER_FACTOR "功率因数"
#define D_POWERUSAGE "功率"
@@ -749,6 +751,7 @@
#define D_UNIT_MILLIMETER "毫米"
#define D_UNIT_MILLIMETER_MERCURY "毫米汞柱"
#define D_UNIT_MILLISECOND "毫秒"
+#define D_UNIT_MILLIVOLT "mV"
#define D_UNIT_MINUTE "分"
#define D_UNIT_PARTS_PER_BILLION "ppb"
#define D_UNIT_PARTS_PER_DECILITER "每分升"
diff --git a/tasmota/language/zh_TW.h b/tasmota/language/zh_TW.h
index 65c865491..4243f0b55 100644
--- a/tasmota/language/zh_TW.h
+++ b/tasmota/language/zh_TW.h
@@ -129,7 +129,9 @@
#define D_OK "好"
#define D_ON "開啟"
#define D_ONLINE "線上"
+#define D_ORP "ORP"
#define D_PASSWORD "密碼"
+#define D_PH "pH"
#define D_PORT "通訊埠"
#define D_POWER_FACTOR "功率因數"
#define D_POWERUSAGE "用電量"
@@ -749,6 +751,7 @@
#define D_UNIT_MILLIMETER "mm"
#define D_UNIT_MILLIMETER_MERCURY "mmHg"
#define D_UNIT_MILLISECOND "毫秒"
+#define D_UNIT_MILLIVOLT "mV"
#define D_UNIT_MINUTE "分"
#define D_UNIT_PARTS_PER_BILLION "ppb"
#define D_UNIT_PARTS_PER_DECILITER "每分升"
diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h
index 883d3ca46..0c0cd6cda 100644
--- a/tasmota/my_user_config.h
+++ b/tasmota/my_user_config.h
@@ -796,6 +796,9 @@
//#define USE_WEBCAM // Add support for webcam
#endif
+ // Shared EZO code required for any EZO device (+1k0 code)
+// #define USE_EZOPH // Add support for EZO's pH sensor (+0k6 code)
+// #define USE_EZOORP // Add support for EZO's ORP sensor (+0k6 code)
/*********************************************************************************************\
* Debug features
diff --git a/tasmota/support_features.ino b/tasmota/support_features.ino
index 0a71d86ed..d3e66eb35 100644
--- a/tasmota/support_features.ino
+++ b/tasmota/support_features.ino
@@ -613,9 +613,9 @@ void GetFeatures(void)
#if defined(USE_ENERGY_SENSOR) && defined(USE_WE517)
feature6 |= 0x08000000; // xnrg_17_ornowe517.ino
#endif
-
-// feature6 |= 0x10000000;
-
+#if defined(USE_I2C) && defined(USE_EZOPH)
+ feature6 |= 0x10000000; // xsns_78_ezoph.ino
+#endif
#if defined(ESP32) && defined(USE_TTGO_WATCH)
feature6 |= 0x20000000; // xdrv_83_esp32watch.ino
#endif
@@ -630,7 +630,9 @@ void GetFeatures(void)
feature7 = 0x00000000;
-// feature7 |= 0x00000001;
+#if defined(USE_I2C) && defined(USE_EZOORP)
+ feature7 |= 0x00000001; // xsns_79_ezoorp.ino
+#endif
// feature7 |= 0x00000002;
// feature7 |= 0x00000004;
// feature7 |= 0x00000008;
diff --git a/tasmota/xsns_78_ezoph.ino b/tasmota/xsns_78_ezoph.ino
new file mode 100644
index 000000000..fd5f0c667
--- /dev/null
+++ b/tasmota/xsns_78_ezoph.ino
@@ -0,0 +1,79 @@
+/*
+ xsns_78_ezoph.ino - EZO pH I2C pH sensor support for Tasmota
+
+ Copyright (C) 2020 Christopher Tremblay
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#ifdef USE_I2C
+#ifdef USE_EZOPH
+
+#define XSNS_78 78
+
+#define EZO_PH_READ_LATENCY 900
+
+struct EZOpH : public EZOStruct {
+ void ProcessMeasurement(void)
+ {
+ char data[D_EZO_MAX_BUF];
+
+ EZOStruct::ProcessMeasurement(data, sizeof(data), EZO_PH_READ_LATENCY);
+ pH = CharToFloat(data);
+ }
+
+ void Show(bool json, uint32_t index)
+ {
+ if (valid) {
+ char str[6];
+ dtostrfd(pH, 2, str);
+
+ char name[10];
+ snprintf_P(name, sizeof(name), PSTR("%s%c%X"), EZOpH::name, IndexSeparator(), index + 1);
+
+ if (count == 1) {
+ name[sizeof("EZOpH") - 1] = 0;
+ }
+
+ if (json) {
+ ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_PH "\":%s}" ), name, str);
+ }
+#ifdef USE_WEBSERVER
+ else {
+ WSContentSend_PD(HTTP_SNS_PH, name, str);
+#endif // USE_WEBSERVER
+ }
+ }
+ }
+
+ static int8_t count;
+ static EZOpH *list;
+ static const char name[];
+
+private:
+ float pH = NAN;
+};
+
+int8_t EZOpH::count = -1;
+EZOpH *EZOpH::list = NULL;
+const char EZOpH::name[] PROGMEM = "EZOpH";
+
+
+/*********************************************************************************************\
+ * Interface
+\*********************************************************************************************/
+#define Xsns78 XsnsEZO
+
+#endif // USE_EZOPH
+#endif // USE_I2C
diff --git a/tasmota/xsns_79_ezoorp.ino b/tasmota/xsns_79_ezoorp.ino
new file mode 100644
index 000000000..4599bfecd
--- /dev/null
+++ b/tasmota/xsns_79_ezoorp.ino
@@ -0,0 +1,79 @@
+/*
+ xsns_79_ezoorp.ino - EZO ORP I2C ORP sensor support for Tasmota
+
+ Copyright (C) 2020 Christopher Tremblay
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#ifdef USE_I2C
+#ifdef USE_EZOORP
+
+#define XSNS_79 79
+
+#define EZO_ORP_READ_LATENCY 900
+
+struct EZOORP : public EZOStruct {
+ void ProcessMeasurement(void)
+ {
+ char data[D_EZO_MAX_BUF];
+
+ EZOStruct::ProcessMeasurement(data, sizeof(data), EZO_ORP_READ_LATENCY);
+ ORP = CharToFloat(data);
+ }
+
+ void Show(bool json, uint32_t index)
+ {
+ if (valid) {
+ char str[6];
+ dtostrfd(ORP, 2, str);
+
+ char name[10];
+ snprintf_P(name, sizeof(name), PSTR("%s%c%X"), EZOORP::name, IndexSeparator(), index + 1);
+
+ if (count == 1) {
+ name[sizeof("EZOORP") - 1] = 0;
+ }
+
+ if (json) {
+ ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_ORP "\":%s}" ), name, str);
+ }
+#ifdef USE_WEBSERVER
+ else {
+ WSContentSend_PD(HTTP_SNS_ORP, name, str);
+#endif // USE_WEBSERVER
+ }
+ }
+ }
+
+ static int8_t count;
+ static EZOORP *list;
+ static const char name[];
+
+private:
+ float ORP = NAN;
+};
+
+int8_t EZOORP::count = -1;
+EZOORP *EZOORP::list = NULL;
+const char EZOORP::name[] PROGMEM = "EZOORP";
+
+
+/*********************************************************************************************\
+ * Interface
+\*********************************************************************************************/
+#define Xsns79 XsnsEZO
+
+#endif // USE_EZOORP
+#endif // USE_I2C