├── 3DPrint
├── lid.jpg
├── case-lid.stl
├── insert.jpg
└── case-insert.stl
├── Images
├── lid.jpg
├── insert.jpg
├── homepage.jpg
├── installed.jpg
├── mqttsetup.jpg
├── setupchart.jpg
├── watermeter2.jpg
└── watermeter3.JPG
├── Circuit
├── tcrt5000.pdf
├── watermeter-cct.jpg
├── sch_d1_mini_v3.0.0.pdf
├── water-meter-reader.fzz
└── watermeter-wiring.jpg
├── WaterMeter
├── UserConfig.h
└── WaterMeter.ino
└── README.md
/3DPrint/lid.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CraigHoffmann/water-meter-reader/HEAD/3DPrint/lid.jpg
--------------------------------------------------------------------------------
/Images/lid.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CraigHoffmann/water-meter-reader/HEAD/Images/lid.jpg
--------------------------------------------------------------------------------
/Images/insert.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CraigHoffmann/water-meter-reader/HEAD/Images/insert.jpg
--------------------------------------------------------------------------------
/3DPrint/case-lid.stl:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CraigHoffmann/water-meter-reader/HEAD/3DPrint/case-lid.stl
--------------------------------------------------------------------------------
/3DPrint/insert.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CraigHoffmann/water-meter-reader/HEAD/3DPrint/insert.jpg
--------------------------------------------------------------------------------
/Circuit/tcrt5000.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CraigHoffmann/water-meter-reader/HEAD/Circuit/tcrt5000.pdf
--------------------------------------------------------------------------------
/Images/homepage.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CraigHoffmann/water-meter-reader/HEAD/Images/homepage.jpg
--------------------------------------------------------------------------------
/Images/installed.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CraigHoffmann/water-meter-reader/HEAD/Images/installed.jpg
--------------------------------------------------------------------------------
/Images/mqttsetup.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CraigHoffmann/water-meter-reader/HEAD/Images/mqttsetup.jpg
--------------------------------------------------------------------------------
/Images/setupchart.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CraigHoffmann/water-meter-reader/HEAD/Images/setupchart.jpg
--------------------------------------------------------------------------------
/Images/watermeter2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CraigHoffmann/water-meter-reader/HEAD/Images/watermeter2.jpg
--------------------------------------------------------------------------------
/Images/watermeter3.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CraigHoffmann/water-meter-reader/HEAD/Images/watermeter3.JPG
--------------------------------------------------------------------------------
/3DPrint/case-insert.stl:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CraigHoffmann/water-meter-reader/HEAD/3DPrint/case-insert.stl
--------------------------------------------------------------------------------
/Circuit/watermeter-cct.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CraigHoffmann/water-meter-reader/HEAD/Circuit/watermeter-cct.jpg
--------------------------------------------------------------------------------
/Circuit/sch_d1_mini_v3.0.0.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CraigHoffmann/water-meter-reader/HEAD/Circuit/sch_d1_mini_v3.0.0.pdf
--------------------------------------------------------------------------------
/Circuit/water-meter-reader.fzz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CraigHoffmann/water-meter-reader/HEAD/Circuit/water-meter-reader.fzz
--------------------------------------------------------------------------------
/Circuit/watermeter-wiring.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CraigHoffmann/water-meter-reader/HEAD/Circuit/watermeter-wiring.jpg
--------------------------------------------------------------------------------
/WaterMeter/UserConfig.h:
--------------------------------------------------------------------------------
1 | // ****************************************************************
2 | // WATER METER READER by Craig Hoffmann
3 | //
4 | // User configuration - UserConfig.h
5 | //
6 | // ****************************************************************
7 |
8 |
9 | // Access Point SSID name and password
10 | // hostname is also used for mDNS name ie http://watermeter.local/
11 | // and also used as the MQTT client name
12 | #define MY_HOSTNAME "watermeter"
13 | #define AP_PASSWORD "wifisetup" // Access Point Password
14 |
15 | // Pull this pin to GND on power up to erase wifi settings & reconfigure
16 | #define RESET_WIFI_PIN D7
17 |
18 | // MQTT Settings - max string lengths for server ip, username, password
19 | #define MAX_SERVER_STR_LEN 32
20 | #define MAX_USER_STR_LEN 32
21 | #define MAX_PASSWORD_STR_LEN 32
22 |
23 | // MQTT topic strings
24 | #define meter_kLiters_topic "sensor/water-meter/kLiters"
25 | #define meter_flowrate_topic "sensor/water-meter/FlowRate"
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Water Meter Reader
2 | ## Remote reading of a South Australian mains water meter
3 |
4 | I made this water meter reader specifically for use with my home assistant monitoring system. (If you haven't heard of [home assistant](https://www.home-assistant.io/) I suggest you check it out!). I have created a discussion thread on the home ssistant [forum here](https://community.home-assistant.io/t/whole-of-house-water-meter-reader-south-australian-meter/240961). After already designing a successful electricity meter reader this was my next requirement for home monitoring and automation.
5 | I have read that some water meters have a rotating magnet inside the meter that can be detected by a hall effect sensor but couldn't find any specific information about the meter at my house (a typical South Australian mains water meter) so I decided to sense the rotating needle on the main dial.
6 |
7 |  
8 |
9 | This is what it looks like with the sensor fitted...
10 |
11 | 
12 |
13 | ## Electronics
14 |
15 | The meter reader uses a [WeMOS D1 Mini (R3)](https://docs.wemos.cc/en/latest/d1/d1_mini.html) but would work with any ESP8266 with a few conditions.
16 |
17 | * It uses the ADC Input for the sensor reading so a variant that has access to that pin is required. If the module doesn't have a resistor voltage divider on the ADC input you will need to add it/adjust the resistors to the sensor.
18 | * The WeMOS has a built in 3.3v regulator for the ESP8266 and the sensor runs at 5V so suitable supply electronics are required.
19 |
20 | An optical reflectance sensor (TCRT5000) is used to detect the IR reflected light and is monitored using the analog input. As the dial turns the reflectance changes as the needle passes by. Trigger levels are set on the analog signal to count the revolutions of the dial. Its simple but works reliably for my meter.
21 |
22 | I have the Wemos D1 in the garage and run a 3wire cable out to the meter with for the sensor. Alternatively all electrics could be placed at the meter and run power to the meter (or maybe use solar and batteries?)
23 |
24 | | |
25 | :-------------------------:
26 |
27 | | |
28 | :-------------------------:
29 |
30 | ## Firmware
31 |
32 | The Arduino IDE was used to develop and program the code. If you have used this before with the ESP8266 devices you should have no problems. If you're new to arduino or ESP8266's then there are hundreds of pages on the internet detailing how to setup the environment and program devices - I suggest you start by looking for one that suits your level of background knowledge to get up to speed.
33 |
34 | Simply compile and upload the **WaterMeter.ino** code using the Arduino IDE.
35 |
36 | ## Case
37 |
38 | My water meter cover had been broken off for years so I decided to make a new 3D printed cover that doubled as the case for the sensor. The lid is "keyed" with the old lid hinge mounting so that the sensor is always aligned to the rotating dial. Files are available in the github repository to download and print your own. The electronics are just hot glued to the inside plate.
39 |
40 |  
41 |
42 |
43 | If you don't have access to a 3D printer you could probably use the bottom of a plastic container or tin - anything that fits snug over the meter to keep the sensor aligned to the dial. Use your imagination and maker skills.
44 |
45 | ## Setup
46 |
47 | First step is to **configure the wifi** to connect to your network. If the device wifi has not been setup previously WiFiManager will automatically setup an access point called **watermeter**. Connect to the access point with password **wifisetup** and then load the default page 192.168.4.1 and enter your network wifi details. **If you want to force the erasure of the wifi settings for some reason power up the device with pin D7 (GPIO13) pulled to ground.** This will erase the wifi settings and go stright to the WiFiManager access point config.
48 |
49 | After entering your wifi details in wifimanager restart the Wemos D1 mini and wifimanager should connect to your network.
50 |
51 | If you have a serial monitor plugged in when powering up the device it will display the assigned IP address on startup. If you don't have an easy way of finding device IP addresses on your network then I suggest allocating an IP address in your router.
52 |
53 | * The main water meter setup page can be found at http://\/
54 | * Alternatively if your system supports multicast DNS then you can use http://watermeter.local/
55 |
56 | The default landing page is shows the cumulative count of kLiters used as well as the usage in the past 1 minute. Both the MQTT setup and Sensor setup can be accessed from the home page.
57 |
58 | |  |
59 | :-------------------------:
60 |
61 | ### Setup the MQTT connection.
62 |
63 | This is done by either clicking on the **MQTT Setup** button from the home page or entering the url directly http://watermeter.local/mqttsetup - replace watermeter.local with the ip address if you don't have mDNS available.
64 |
65 | |  |
66 | :-------------------------:
67 |
68 | ### Setup the sensor trigger thresholds
69 |
70 | To access the sensor setup page in a browser click on the **Sensor Setup** button on the water meter home page or alternatively enter http://watermeter.local/sensorsetup in the browser address bar
71 |
72 | It is necessary to setup the **trigger thresholds** for the sensor for the counter to work properly. The best way to do this is to turn on a water tap full so you get a resonableflow rate then refresh the sensor setup page. You should see the sensor signal rise and fall as the meter dial rotates as shown below. Set the thresholds a bit below the maximum and a bit above the minimum as can be seen below.
73 |
74 | |  |
75 | :-------------------------:
76 |
77 | Next set the **Counts per Liter**. This is the number of times the needle rotates for 1 Liter of water use. In my case the dial rotates once per liter so the value is set to 1.
78 |
79 | Set the **Meter Reading (kL)** to match the current water meter reading and **click the update button**. All settings will be saved to non-volatile memory on the Wemos.
80 |
81 | **Note:** the counter will not start "counting" until the kLiters reading is set to a value greater than zero. The kLiteres is set as "reatined" in MQTT so this is how the value is maintained through reset or power fail.
82 |
83 |
84 |
85 | ## Home Assistant
86 |
87 | Home assistant configuration is pretty straight forward, just add an MQTT sensor to the configuration.yaml file as follows: (this assumes you already have MQTT setup and working in home assistant)
88 |
89 | ```YAML
90 | sensor:
91 | # Wemos Water Meter kLiters Reading
92 | - platform: mqtt
93 | state_topic: "sensor/water-meter/kLiters"
94 | name: "Meter Reading"
95 | unit_of_measurement: "kLiters"
96 |
97 | # Wemos Water Meter Flowrate Reading
98 | - platform: mqtt
99 | state_topic: "sensor/water-meter/FlowRate"
100 | name: "Flowrate"
101 | unit_of_measurement: "L/min"
102 | ```
103 |
104 |
--------------------------------------------------------------------------------
/WaterMeter/WaterMeter.ino:
--------------------------------------------------------------------------------
1 | // ****************************************************************
2 | // WATER METER READER by Craig Hoffmann
3 | //
4 | // Written for WeMOS D1 mini R3
5 | //
6 | // Do not remove this comment block
7 | // ****************************************************************
8 |
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include
15 | #include
16 | #include //https://github.com/tzapu/WiFiManager WiFi Configuration Magic
17 |
18 | #include "UserConfig.h"
19 |
20 |
21 | // ****************************************************************
22 | // Some defines that are used
23 | // ****************************************************************
24 |
25 | #define ONE_WIRE_BUS 0 // Data wire (1 wire bus) is plugged into pin GPIO 0
26 | #define PROCESS_PERIOD_mS (30*1000) // how often to do some work
27 | #define SAMPLE_PERIOD_mS 25 // how often to sample the ADC
28 | #define MQTT_CONN_RETRY_ms (10*1000) // Only try to reconnect every 10 seconds
29 | #define MQTT_PERIOD_mS (60*1000) // Send MQTT updates every minute
30 | #define LED_ON_TIME_mS 30 // How long to flash the LED on
31 | #define MAX_CHART_POINTS 400 // Number of readings to remember for plotting 288 * 5min
32 | #define LED_PIN 2 // GPIO 2 is the LED on WeMOS D1 Mini
33 | #define CHART_LOGIC_LOW_VALUE 0
34 | #define CHART_LOGIC_LOW_TO_HIGH_DELTA 15
35 | #define VALID_DATA_CODE_ADDR 0 // eeprom address used to store int value code
36 | #define VALID_DATA_CODE ((int)12256) // just a value used to flag if eeprom has been written before
37 | #define SETUP_DATA_ADDR 4 // Setup Data Structure starting address in flash/eeprom
38 | #define ADC_BUFFER_SIZE 100
39 |
40 |
41 | // ****************************************************************
42 | // EEPROM data structure
43 | // ****************************************************************
44 |
45 | struct EEPROMDataStruct
46 | {
47 | int CountsPerLiter; // How many sensor toggles for 1 liter
48 | int MinThreshold; // Sensor threshold for a high
49 | int MaxThreshold; // Sensor threshold for a low
50 | char MQTTHost[MAX_SERVER_STR_LEN+2]; // mqtt host ip address
51 | char MQTTUser[MAX_USER_STR_LEN+2]; // mqtt user name
52 | char MQTTPassword[MAX_PASSWORD_STR_LEN+2]; // mqtt password
53 | int MQTTPort; // mqtt port number
54 | } SetupData = { 1, 45, 65, "0.0.0.0", "user", "secret", 1883 };
55 |
56 |
57 | // ****************************************************************
58 | // Setup the global variables
59 | // ****************************************************************
60 |
61 | const int ANALOG_PIN = A0; // ESP8266 Analog Pin ADC0 = A0
62 | char TempStr[20];
63 | float SensorLevels[MAX_CHART_POINTS + 1];
64 | float TriggerLow[MAX_CHART_POINTS + 1];
65 | float TriggerHigh[MAX_CHART_POINTS + 1];
66 | char TriggerLevels[MAX_CHART_POINTS + 1];
67 | uint16_t HistoryBufferIn;
68 | uint16_t PreviousHistoryBufferIn;
69 | float MeterReading_kL = -1.0;
70 | float LastMeterReading_kL = 0;
71 | float FlowRate = 0;
72 |
73 | volatile uint16_t ADCBuffer[ADC_BUFFER_SIZE];
74 | volatile uint16_t ADCBufferIn;
75 | uint16_t ADCBufferOut;
76 | uint16_t NextADCBufferOut;
77 |
78 | unsigned long PreviousMillis = 0;
79 | unsigned long PreviousMQTTMillis = 0;
80 | unsigned long LastMQTTReconnectMillis = 0;
81 | unsigned long LED_OnMillis = 0;
82 |
83 |
84 | // ****************************************************************
85 | // Start some things up
86 | // ****************************************************************
87 |
88 | //multicast DNS responder
89 | MDNSResponder MDNS;
90 |
91 | //Web server object. Will be listening in port 80 (default for HTTP)
92 | ESP8266WebServer server(80);
93 |
94 | // Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs)
95 | OneWire oneWire(ONE_WIRE_BUS);
96 |
97 | // Pass our oneWire reference to Dallas Temperature.
98 | DallasTemperature DS18B20(&oneWire);
99 |
100 | // MQTT client
101 | WiFiClient espClient;
102 | PubSubClient client(espClient);
103 |
104 |
105 | // ****************************************************************
106 | // Power on/reset setup
107 | // ****************************************************************
108 |
109 | void setup()
110 | {
111 | int i;
112 |
113 | // Start setup
114 | Serial.begin(115200);
115 | Serial.println("\n");
116 | delay(10);
117 | pinMode(LED_PIN, OUTPUT);
118 | digitalWrite(LED_PIN, LOW); // Low LED ON
119 |
120 | // Load the water meter settings from EEPROM/Flash
121 | EEPROM.begin(512);
122 | EEPROM.get(VALID_DATA_CODE_ADDR,i);
123 | if (i==VALID_DATA_CODE) // Does it look like data has been written to the eeprom before??
124 | {
125 | EEPROM.get(SETUP_DATA_ADDR,SetupData);
126 | if ((SetupData.CountsPerLiter < 1) || (SetupData.CountsPerLiter > 10))
127 | {
128 | SetupData.CountsPerLiter = 1; // if out of range then reset to default
129 | }
130 | if ((SetupData.MinThreshold < 0) || (SetupData.MinThreshold > 100))
131 | {
132 | SetupData.MinThreshold = 45; // if out of range then reset to default
133 | }
134 | if ((SetupData.MaxThreshold < 0) || (SetupData.MaxThreshold > 100))
135 | {
136 | SetupData.MaxThreshold = 65; // if out of range then reset to default
137 | }
138 | }
139 | else
140 | {
141 | EEPROM.put(VALID_DATA_CODE_ADDR,VALID_DATA_CODE);
142 | EEPROM.put(SETUP_DATA_ADDR,SetupData);
143 |
144 | noInterrupts();
145 | EEPROM.commit();
146 | interrupts();
147 | }
148 |
149 | // Make sure AP mode still not active from previous session seems to mess with mDNS
150 | WiFi.softAPdisconnect();
151 | //WiFi.disconnect();
152 | //WiFi.mode(WIFI_STA);
153 | delay(10);
154 |
155 | // Force wifimanager to reset config by pulling this pin to GND during startup
156 | pinMode(RESET_WIFI_PIN, INPUT_PULLUP);
157 |
158 | // WiFiManager for configuring/managing the wifi settings
159 | WiFiManager wifiManager;
160 | wifiManager.setTimeout(180); // 3min (180second) timeout for Access Point configuration
161 | wifiManager.setDebugOutput(false); // Note WiFiManager prints password on serial if debug enabled!!
162 |
163 | // Check if wifi config needs to be reset - forcing Access Point Config portal
164 | if (digitalRead(RESET_WIFI_PIN) == LOW)
165 | {
166 | Serial.println("Pin LOW - Resetting Wifi setings");
167 | wifiManager.resetSettings();
168 | }
169 |
170 | // Connect using wifimanager
171 | if(!wifiManager.autoConnect(MY_HOSTNAME,AP_PASSWORD))
172 | {
173 | Serial.println("Failed to connect - resetting");
174 | ESP.reset(); //reset and try again
175 | }
176 |
177 | // Made it past wifimanager so must be connected
178 | Serial.println("WiFi connected");
179 | WiFi.softAPdisconnect(); // ensure softAP is stopped so mDNS works reliably
180 | delay(10);
181 |
182 | // Setup mDNS for easy address resolution
183 | if (!MDNS.begin(MY_HOSTNAME)) // NOTE: In windows need bonjour installed for mDNS to work, linux install Avahi
184 | {
185 | Serial.println("mDNS setup failed!");
186 | }
187 | else
188 | {
189 | Serial.print("mDNS setup http://");
190 | Serial.print(MY_HOSTNAME);
191 | Serial.println(".local/");
192 | MDNS.addService("http","tcp",80);
193 | delay(100); // not sure why but mDNS was unreliable without this
194 | }
195 |
196 | // Start the server
197 | server.on("/", HandleRootPath); //Associate the handler function to the path
198 | server.on("/sensorsetup", HandleSetupPath); //Associate the handler function to the path
199 | server.on("/ConfirmSave", HandleSaveConfirmation); //Associate the handler function to the path
200 | server.on("/mqttsetup", HandleMQTTSetupPath); //Associate the handler function to the path
201 | server.on("/mqttConfirmSave", HTTP_POST, HandleMQTTSaveConfirmation);
202 | server.begin(); //Start the server
203 |
204 | // Print the IP address
205 | Serial.print("Use this URL to connect: ");
206 | Serial.print("http://");
207 | Serial.print(WiFi.localIP());
208 | Serial.println("/");
209 |
210 | // Start the MQTT
211 | client.setServer(SetupData.MQTTHost, SetupData.MQTTPort);
212 | client.setCallback(MQTTcallback);
213 |
214 | DS18B20.begin(); // IC Default 9 bit.
215 |
216 | // Initialise some data
217 | for (i=0;i<=MAX_CHART_POINTS;i++)
218 | {
219 | SensorLevels[i]=0;
220 | TriggerLow[i]=SetupData.MinThreshold;
221 | TriggerHigh[i]=SetupData.MaxThreshold;
222 | TriggerLevels[i] = 0;
223 | }
224 |
225 | ADCBuffer[0] = 0;
226 | ADCBuffer[1] = 0;
227 | ADCBufferIn = 1; // We will always let input stay one ahead of output so no risk of reading value as get changed by ISR
228 | ADCBufferOut = 0;
229 | SetupInterrupts();
230 |
231 | PreviousMillis = millis();
232 | PreviousMQTTMillis = millis();
233 | digitalWrite(LED_PIN, HIGH); // Low LED OFF
234 | }
235 |
236 |
237 | // ****************************************************************
238 | // Use Timer Interrupt to read the analog input
239 | // Initialize timer interrupt to trigger every 25ms (SAMPLE_PERIOD_mS)
240 | // ****************************************************************
241 |
242 | void ICACHE_RAM_ATTR MyTimerInterrupt();
243 | void SetupInterrupts()
244 | {
245 | timer1_attachInterrupt(MyTimerInterrupt); // Add ISR Function
246 | timer1_enable(TIM_DIV16, TIM_EDGE, TIM_LOOP);
247 | // Dividers:
248 | // TIM_DIV1 = 0, //80MHz (80 ticks/us - 104857.588 us max)
249 | // TIM_DIV16 = 1, //5MHz (5 ticks/us - 1677721.4 us max)
250 | // TIM_DIV256 = 3 //312.5Khz (1 tick = 3.2us - 26843542.4 us max)
251 | // Reloads:
252 | // TIM_SINGLE 0 //on interrupt routine you need to write a new value to start the timer again
253 | // TIM_LOOP 1 //on interrupt the counter will start with the same value again
254 | //
255 | // Arm the Timer for our SAMPLE_PERIOD_mS Interval
256 | timer1_write(SAMPLE_PERIOD_mS * 5 * 1000); // 5 Ticks for TIM_DIV16 so use 5 * MicroSeconds
257 | }
258 |
259 |
260 | // ****************************************************************
261 | // The Timer Interrupt handler
262 | // ****************************************************************
263 |
264 | void ICACHE_RAM_ATTR MyTimerInterrupt()
265 | {
266 | ADCBufferIn++;
267 | if (ADCBufferIn >= ADC_BUFFER_SIZE) // Buffer is way bigger than it needs to be so no need to check for overruns
268 | {
269 | ADCBufferIn = 0;
270 | }
271 | ADCBuffer[ADCBufferIn] = analogRead(ANALOG_PIN);
272 | }
273 |
274 |
275 | // ****************************************************************
276 | // Reconnect MQTT if necessary
277 | // ****************************************************************
278 |
279 | void ReconnectMQTT()
280 | {
281 | // Only attempt to reconnect every MQTT_CONN_RETRY_ms
282 | if ((millis() - LastMQTTReconnectMillis) >= MQTT_CONN_RETRY_ms)
283 | {
284 | Serial.println("X");
285 | //Serial.println(SetupData.MQTTHost);
286 | //Serial.println(SetupData.MQTTUser);
287 | //Serial.println(SetupData.MQTTPassword);
288 | // Attempt to connect
289 | if (client.connect(MY_HOSTNAME, SetupData.MQTTUser, SetupData.MQTTPassword))
290 | {
291 | Serial.println("Y");
292 | client.subscribe(meter_kLiters_topic); // Success connecting so subscribe to topic
293 | }
294 | else
295 | {
296 | // Failed - Reset reconnect timeout so wait a while before retrying
297 | LastMQTTReconnectMillis = millis();
298 | }
299 | }
300 | }
301 |
302 |
303 | // ****************************************************************
304 | // Process incoming MQTT
305 | // ****************************************************************
306 |
307 | void MQTTcallback(char* topic, byte* payload, unsigned int length)
308 | {
309 | char SetkLiters[20];
310 |
311 | if (length < 19) // if payload is too long ignore it
312 | {
313 | for (int i = 0; i < length; i++)
314 | {
315 | SetkLiters[i]=(char)payload[i];
316 | }
317 | SetkLiters[length] = NULL;
318 | MeterReading_kL = atof(SetkLiters);
319 | LastMeterReading_kL = MeterReading_kL;
320 | }
321 | }
322 |
323 |
324 | // ****************************************************************
325 | // MAIN LOOP
326 | // ****************************************************************
327 |
328 | void loop()
329 | {
330 | int i;
331 | int analog_sample;
332 |
333 | // Check if MQTT is connected
334 | if (!client.connected())
335 | {
336 | ReconnectMQTT();
337 | }
338 | else
339 | {
340 | client.loop();
341 | LastMQTTReconnectMillis = millis(); // This is to ensure the last reconnect doesnt roll over
342 |
343 | // The following block only gets processed approx every MQTT_PERIOD_mS
344 | if ((millis() - PreviousMQTTMillis) >= MQTT_PERIOD_mS)
345 | {
346 | //LastMQTTReconnectMillis = PreviousMQTTMillis; // This is to ensure the last reconnect doesnt roll over
347 | PreviousMQTTMillis += MQTT_PERIOD_mS; // Prepare for the next MQTT update time
348 | // Do MQTT Updates here
349 |
350 | if (MeterReading_kL > 0.0) // only calculate kWhrs if we already have a starting value
351 | {
352 | dtostrf(MeterReading_kL, -12, 3, TempStr); // The negative means left justify
353 | client.publish(meter_kLiters_topic, TempStr, true); // we want this retained so we update after reset/power out
354 | FlowRate = ((MeterReading_kL - LastMeterReading_kL) * 1000 / (MQTT_PERIOD_mS / 60000)); // Calculation gives Liters per minute
355 | dtostrf(FlowRate, -12, 0, TempStr); // The negative means left justify
356 | client.publish(meter_flowrate_topic, TempStr, false); // we dont need this one retained
357 | LastMeterReading_kL = MeterReading_kL;
358 | }
359 | }
360 | }
361 |
362 | // The following block only gets processed approx every PROCESS_PERIOD_mS
363 | if ((millis() - PreviousMillis) >= PROCESS_PERIOD_mS)
364 | {
365 | PreviousMillis += PROCESS_PERIOD_mS;
366 | // Do stuff here - currently not required
367 | }
368 |
369 | // Check if the LED has been on long enough and turn off
370 | if ((millis() - LED_OnMillis) >= LED_ON_TIME_mS)
371 | {
372 | digitalWrite(LED_PIN, HIGH); // High for LED OFF
373 | }
374 |
375 | // The following block only gets processed when ADC samples are ready - usually every 25ms unless que'd up
376 | NextADCBufferOut = ADCBufferOut + 1;
377 | if (NextADCBufferOut >= ADC_BUFFER_SIZE)
378 | {
379 | NextADCBufferOut = 0;
380 | }
381 | if (NextADCBufferOut != ADCBufferIn) // Process the next sample - this will always leave one sample in buffer
382 | {
383 | ADCBufferOut = NextADCBufferOut;
384 | analog_sample = ADCBuffer[ADCBufferOut];
385 |
386 | // Save the history buffer index for later comparison/ calculation
387 | PreviousHistoryBufferIn = HistoryBufferIn;
388 |
389 | // Find the next index in the history values ring buffer
390 | HistoryBufferIn++;
391 | if (HistoryBufferIn >= MAX_CHART_POINTS)
392 | {
393 | HistoryBufferIn = 0;
394 | }
395 |
396 | SensorLevels[HistoryBufferIn] = (float)analog_sample / 10.23; // Scale 0 to 100
397 |
398 | // Check if triggered count
399 | if (SensorLevels[HistoryBufferIn] > SetupData.MaxThreshold)
400 | {
401 | TriggerLevels[HistoryBufferIn] = 1; // Use this for the high / low schmitt trigger level
402 | if (TriggerLevels[PreviousHistoryBufferIn] == 0)
403 | {
404 | digitalWrite(LED_PIN, LOW); // Low LED ON
405 | LED_OnMillis = millis();
406 | if (MeterReading_kL > 0.0) // Only calculate kLiters if we already have a starting value
407 | {
408 | MeterReading_kL = MeterReading_kL + (0.001 / SetupData.CountsPerLiter);
409 | }
410 | }
411 | }
412 | else if (SensorLevels[HistoryBufferIn] < SetupData.MinThreshold)
413 | {
414 | TriggerLevels[HistoryBufferIn] = 0; // Use this for the high / low schmitt trigger level
415 | }
416 | else
417 | {
418 | TriggerLevels[HistoryBufferIn] = TriggerLevels[PreviousHistoryBufferIn]; // This is in the deadzone - no change
419 | }
420 |
421 | TriggerLow[HistoryBufferIn] = SetupData.MinThreshold;
422 | TriggerHigh[HistoryBufferIn] = SetupData.MaxThreshold;
423 | }
424 | MDNS.update();
425 | server.handleClient(); // Handling of incoming web requests
426 | }
427 |
428 |
429 | // ****************************************************************
430 | // Read temperature from Dallas DS18B20
431 | // ****************************************************************
432 |
433 | float getTemperature(int Sensor)
434 | {
435 | float tempC;
436 | do {
437 | DS18B20.requestTemperatures();
438 | tempC = DS18B20.getTempCByIndex(Sensor);
439 | delay(100);
440 | } while (tempC == 85.0 || tempC == (-127.0));
441 | return tempC;
442 | }
443 |
444 |
445 | // ****************************************************************
446 | // Handle default web page request (Home page)
447 | // ****************************************************************
448 |
449 | void HandleRootPath()
450 | {
451 | int i;
452 |
453 | server.sendContent("HTTP/1.1 200 OK\r\n"); // start the page header
454 | server.sendContent("Content-Type: text/html\r\n");
455 | server.sendContent("Connection: close\r\n"); // the connection will be closed after completion of the response
456 | server.sendContent("Refresh: 60\r\n"); // tell browser to refresh the page automatically every 60sec
457 | server.sendContent("\r\n"); // this separates header from content that follows
458 | server.sendContent("");
459 | server.sendContent("");
460 | server.sendContent("Water Meter");
461 | server.sendContent("
Water Usage");
466 | dtostrf(MeterReading_kL, -12, 3, TempStr); // negative width means left justify
467 | server.sendContent(String("
") + TempStr + " kL
");
468 |
469 | server.sendContent("
Average Flowrate last 1min");
470 | dtostrf(FlowRate, -12, 0, TempStr); // The negative means left justify, Calculation gives Liters per minute
471 | server.sendContent(String("