diff --git a/sonoff/i18n.h b/sonoff/i18n.h
index 60b5880e0..766a2cd7e 100644
--- a/sonoff/i18n.h
+++ b/sonoff/i18n.h
@@ -69,6 +69,7 @@
#define D_JSON_FLASHCHIPID "FlashChipId"
#define D_JSON_FLASHMODE "FlashMode"
#define D_JSON_FLASHSIZE "FlashSize"
+#define D_JSON_FLOWRATE "FlowRate"
#define D_JSON_FREEMEMORY "Free"
#define D_JSON_FREQUENCY "Frequency"
#define D_JSON_FROM "from"
@@ -143,6 +144,7 @@
#define D_JSON_TIME "Time"
#define D_JSON_TODAY "Today"
#define D_JSON_TOTAL "Total"
+#define D_JSON_TOTAL_USAGE "TotalUsage"
#define D_JSON_TOTAL_REACTIVE "TotalReactivePower"
#define D_JSON_TOTAL_START_TIME "TotalStartTime"
#define D_JSON_TVOC "TVOC"
@@ -515,6 +517,8 @@ const char S_JSON_DRIVER_INDEX_SVALUE[] PROGMEM = "{\"" D_CMND_DRIVE
const char JSON_SNS_TEMP[] PROGMEM = ",\"%s\":{\"" D_JSON_TEMPERATURE "\":%s}";
const char JSON_SNS_TEMPHUM[] PROGMEM = ",\"%s\":{\"" D_JSON_TEMPERATURE "\":%s,\"" D_JSON_HUMIDITY "\":%s}";
+const char JSON_SNS_GNGPM[] PROGMEM = "%s,\"%s\":{\"" D_JSON_TOTAL_USAGE "\":%s,\"" D_JSON_FLOWRATE "\":%s}";
+
const char S_LOG_I2C_FOUND_AT[] PROGMEM = D_LOG_I2C "%s " D_FOUND_AT " 0x%x";
const char S_LOG_HTTP[] PROGMEM = D_LOG_HTTP;
@@ -569,6 +573,8 @@ const char HTTP_SNS_ANALOG[] PROGMEM = "{s}%s " D_ANALOG_INPUT "%d{m}%d{e}";
const char HTTP_SNS_ILLUMINANCE[] PROGMEM = "{s}%s " D_ILLUMINANCE "{m}%d " D_UNIT_LUX "{e}"; // {s} =
, {m} = | , {e} = |
const char HTTP_SNS_CO2[] PROGMEM = "{s}%s " D_CO2 "{m}%d " D_UNIT_PARTS_PER_MILLION "{e}"; // {s} = , {m} = | , {e} = |
const char HTTP_SNS_CO2EAVG[] PROGMEM = "{s}%s " D_ECO2 "{m}%d " D_UNIT_PARTS_PER_MILLION "{e}"; // {s} = , {m} = | , {e} = |
+const char HTTP_SNS_GALLONS[] PROGMEM = "{s}%s " D_TOTAL_USAGE "{m}%s " D_UNIT_GALLONS " {e}"; // {s} = , {m} = | , {e} = |
+const char HTTP_SNS_GPM[] PROGMEM = "{s}%s " D_FLOW_RATE "{m}%s " D_UNIT_GALLONS_PER_MIN" {e}"; // {s} = , {m} = | , {e} = |
const char S_MAIN_MENU[] PROGMEM = D_MAIN_MENU;
const char S_CONFIGURATION[] PROGMEM = D_CONFIGURATION;
diff --git a/sonoff/language/en-GB.h b/sonoff/language/en-GB.h
index d5e792462..e8ba8f3fb 100644
--- a/sonoff/language/en-GB.h
+++ b/sonoff/language/en-GB.h
@@ -93,6 +93,7 @@
#define D_FALLBACK_TOPIC "Fallback Topic"
#define D_FALSE "False"
#define D_FILE "File"
+#define D_FLOW_RATE "Flow rate"
#define D_FREE_MEMORY "Free Memory"
#define D_FREQUENCY "Frequency"
#define D_GAS "Gas"
@@ -156,6 +157,7 @@
#define D_TO "to"
#define D_TOGGLE "Toggle"
#define D_TOPIC "Topic"
+#define D_TOTAL_USAGE "Total Usage"
#define D_TRANSMIT "Transmit"
#define D_TRUE "True"
#define D_TVOC "TVOC"
@@ -576,12 +578,15 @@
#define D_SENSOR_TXD "Serial Tx"
#define D_SENSOR_RXD "Serial Rx"
#define D_SENSOR_ROTARY "Rotary" // Suffix "1A"
-
+#define D_SENSOR_HRE_CLOCK "HRE Clock"
+#define D_SENSOR_HRE_DATA "HRE Data"
// Units
#define D_UNIT_AMPERE "A"
#define D_UNIT_CENTIMETER "cm"
#define D_UNIT_HERTZ "Hz"
#define D_UNIT_HOUR "Hr"
+#define D_UNIT_GALLONS "gal"
+#define D_UNIT_GALLONS_PER_MIN "g/m"
#define D_UNIT_INCREMENTS "inc"
#define D_UNIT_KILOGRAM "kg"
#define D_UNIT_KILOMETER_PER_HOUR "km/h" // or "km/h"
diff --git a/sonoff/sonoff_template.h b/sonoff/sonoff_template.h
index cf5a1af78..b50c2184b 100644
--- a/sonoff/sonoff_template.h
+++ b/sonoff/sonoff_template.h
@@ -178,6 +178,8 @@ enum UserSelectablePins {
GPIO_ROT1B, // Rotary switch1 B Pin
GPIO_ROT2A, // Rotary switch2 A Pin
GPIO_ROT2B, // Rotary switch2 B Pin
+ GPIO_HRE_CLOCK, // Clock/Power line for HR-E Water Meter
+ GPIO_HRE_DATA, // Data line for HR-E Water Meter
GPIO_SENSOR_END };
// Programmer selectable GPIO functionality
@@ -241,6 +243,7 @@ const char kSensorNames[] PROGMEM =
D_SENSOR_CSE7766_TX "|" D_SENSOR_CSE7766_RX "|"
D_SENSOR_ARIRFRCV "|" D_SENSOR_TXD "|" D_SENSOR_RXD "|"
D_SENSOR_ROTARY "1a|" D_SENSOR_ROTARY "1b|" D_SENSOR_ROTARY "2a|" D_SENSOR_ROTARY "2b|"
+ D_SENSOR_HRE_CLOCK "|" D_SENSOR_HRE_DATA "|"
;
/********************************************************************************************/
@@ -584,7 +587,11 @@ const uint8_t kGpioNiceList[] PROGMEM = {
GPIO_ROT1B, // Rotary switch1 B Pin
GPIO_ROT2A, // Rotary switch2 A Pin
GPIO_ROT2B, // Rotary switch2 B Pin
- GPIO_ARIRFRCV // AliLux RF Receive input
+ GPIO_ARIRFRCV, // AliLux RF Receive input
+#ifdef USE_HRE
+ GPIO_HRE_CLOCK,
+ GPIO_HRE_DATA
+#endif
};
const uint8_t kModuleNiceList[MAXMODULE] PROGMEM = {
diff --git a/sonoff/xsns_91_hre.ino b/sonoff/xsns_91_hre.ino
new file mode 100644
index 000000000..ac15a937a
--- /dev/null
+++ b/sonoff/xsns_91_hre.ino
@@ -0,0 +1,299 @@
+/*
+ xsns_07_sht1x.ino - SHT1x temperature and sensor support for Sonoff-Tasmota
+
+ Copyright (C) 2019 Theo Arends
+
+ 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 .
+*/
+
+/*********************************************************************************************\
+ * HR-E LCD Water meter register interface
+ *
+ * https://www.badgermeter.com/business-lines/utility/high-resolution-lcd-encoders-hr-e-lcd/
+ * Source: Jon Little, https://github.com/burundiocibu/particle/blob/master/water_meter/src/HRE_Reader.cpp
+ *
+ * This code marches the bits out the data line as ASCII characters with the form
+ * KG44?Q45484=0444444V;RB000000022;IB018435683
+ * where the RB...; is the miligalons used
+ *
+ * Note that this sensor takes a _long_ time to read. 62 bits * 4 ms/bit for the
+ * sync sequence plus 46 bytes * 40 ms/byte = 2088 ms minimum. If we aren't alligned
+ * to the sync sequence, it could be almost twice that.
+ * To keep from bogging the kernel down, we read 8 bits at a time on the 50 ms callback.
+ * It will take seconds to discover if the device is there.
+ *
+ * In lieu of an actual schematic to describe the electrical interface, here is a description:
+ *
+ * hre_clock_pin: drives the power/clock for the water meter through a 1k resister to
+ * the base of a pnp transistor
+ * hre_data_pin: is the data and has a 1 k pulldown
+ *
+ * The pnp transitor has the collector connected to the power/clock and is pulled up
+ * to +5 via a 1 k resistor.
+ * The emitter is connected to ground
+ *
+\*********************************************************************************************/
+
+#ifdef USE_HRE
+
+#define XSNS_91 91
+
+enum hre_states {
+ hre_idle, // Initial state,
+ hre_sync, // Start search for sync sequence
+ hre_syncing, // Searching for sync sequence
+ hre_read, // Start reading data block
+ hre_reading, // Reading data
+ hre_sleep, // Start sleeping
+ hre_sleeping // pausing before reading again
+};
+
+hre_states hre_state = hre_idle;
+
+float hre_usage = 0; // total water usage, in gal
+float hre_rate = 0; // flow rate, in gal/min
+uint32_t hre_usage_time = 0; // uptime associated with hre_usage and hre_rate
+
+int hre_read_errors = 0; // total number of read errors since boot
+bool hre_good = false;
+
+
+// The settling times here were determined using a single unit hooked to a scope
+int hreReadBit()
+{
+ digitalWrite(pin[GPIO_HRE_CLOCK], HIGH);
+ delay(1);
+ int bit = digitalRead(pin[GPIO_HRE_DATA]);
+ digitalWrite(pin[GPIO_HRE_CLOCK], LOW);
+ delay(1);
+ return bit;
+}
+
+// With the times in the HreReadBit routine, a characer will take
+// 20 ms plus io time.
+char hreReadChar(int &parity_errors)
+{
+ // start bit
+ hreReadBit();
+
+ unsigned ch=0;
+ int sum=0;
+ for (int i=0; i<7; i++)
+ {
+ int b = hreReadBit();
+ ch |= b << i;
+ sum += b;
+ }
+
+ // parity
+ if ( (sum & 0x1) != hreReadBit())
+ parity_errors++;
+
+ // stop bit
+ hreReadBit();
+
+ return ch;
+}
+
+void hreInit(void)
+{
+ hre_read_errors = 0;
+ hre_good = false;
+
+ pinMode(pin[GPIO_HRE_CLOCK], OUTPUT);
+ pinMode(pin[GPIO_HRE_DATA], INPUT);
+
+ // Note that the level shifter inverts this line and we want to leave it
+ // high when not being read.
+ digitalWrite(pin[GPIO_HRE_CLOCK], LOW);
+
+ hre_state = hre_sync;
+}
+
+
+void hreEvery50ms(void)
+{
+ static int sync_counter = 0; // Number of sync bit reads
+ static int sync_run = 0; // Number of consecutive '1's read
+
+ static uint32_t curr_start = 0; // uptime when entered hre_reading for current read
+ static int read_counter = 0; // number of bytes in the current read
+ static int parity_errors = 0; // Number of parity errors in current read
+ static char buff[46]; // 8 char and a term
+ static char aux[46]; // 8 char and a term
+
+ static char ch;
+ static size_t i;
+
+ switch (hre_state)
+ {
+ case hre_sync:
+ if (uptime < 15)
+ break;
+ sync_run = 0;
+ sync_counter = 0;
+ hre_state = hre_syncing;
+ snprintf_P(log_data, sizeof(log_data), PSTR("HRE: state:syncing"));
+ AddLog(LOG_LEVEL_DEBUG);
+ break;
+
+ case hre_syncing:
+ // Find the header, a string of 62 '1's
+ // Note that on startup, this could take a a whole block (46 bytes)
+ // before we start seeing the header
+ for (int i=0; i<8; i++)
+ {
+ if (hreReadBit())
+ sync_run++;
+ else
+ sync_run = 0;
+ if (sync_run == 62)
+ {
+ hre_state = hre_read;
+ break;
+ }
+ sync_counter++;
+ }
+ // If the meter doesn't get in sync within 1000 bits, give up for now
+ if (sync_counter > 1000)
+ {
+ hre_state = hre_sleep;
+ snprintf_P(log_data, sizeof(log_data), PSTR("HRE: sync error"));
+ AddLog(LOG_LEVEL_DEBUG);
+ }
+ break;
+
+ // Start reading the data block
+ case hre_read:
+ snprintf_P(log_data, sizeof(log_data), PSTR("HRE: sync_run:%d, sync_counter:%d"), sync_run, sync_counter);
+ AddLog(LOG_LEVEL_DEBUG);
+ read_counter = 0;
+ parity_errors = 0;
+ curr_start = uptime;
+ memset(buff, 0, sizeof(buff));
+ hre_state = hre_reading;
+ snprintf_P(log_data, sizeof(log_data), PSTR("HRE: state:reading"));
+ AddLog(LOG_LEVEL_DEBUG);
+ // So this is intended to fall through to the hre_reading section.
+ // it seems that if there is much of a delay between getting the sync
+ // bits and starting the read, the HRE won't output the message we
+ // are looking for...
+
+ case hre_reading:
+ //ch = hreReadChar(parity_errors);
+ //i = read_counter - 24; // The water usage reading starts 24 bytes into the block
+ //if (i>=0 && i 27)
+ hre_state = hre_sync;
+ }
+}
+
+void hreShow(boolean json)
+{
+ if (!hre_good)
+ return;
+
+ const char hre_types[] = "HRE";
+
+ char usage[33];
+ char rate[33];
+ dtostrfd(hre_usage, 2, usage);
+ dtostrfd(hre_rate, 3, rate);
+
+ if (json)
+ {
+ snprintf_P(mqtt_data, sizeof(mqtt_data), JSON_SNS_GNGPM, mqtt_data, hre_types, usage, rate);
+#ifdef USE_WEBSERVER
+ }
+ else
+ {
+ snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_SNS_GALLONS, mqtt_data, hre_types, usage);
+ snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_SNS_GPM, mqtt_data, hre_types, rate);
+#endif // USE_WEBSERVER
+ }
+}
+
+
+/*********************************************************************************************\
+ * Interface
+\*********************************************************************************************/
+bool Xsns91(byte function)
+{
+ // If we don't have pins assigned give up quickly.
+ if (pin[GPIO_HRE_CLOCK] >= 99 || pin[GPIO_HRE_DATA] >= 99)
+ return false;
+
+ switch (function)
+ {
+ case FUNC_INIT:
+ hreInit();
+ break;
+ case FUNC_EVERY_50_MSECOND:
+ hreEvery50ms();
+ break;
+ case FUNC_EVERY_SECOND:
+ break;
+ case FUNC_JSON_APPEND:
+ hreShow(1);
+ break;
+#ifdef USE_WEBSERVER
+ case FUNC_WEB_SENSOR:
+ hreShow(0);
+ break;
+#endif // USE_WEBSERVER
+ }
+ return false;
+}
+
+#endif // USE_HRE