I have just completed a water meter project using Bluetooth with a Nano 33 IoT. It's battery operated, where the batteries are charged from a solar panel. I'm in the UK where solar charging isn't reliable so to conserve power I want to put the device to sleep during the night and for it to wake up in the morning.
During daylight hours I might connect my central (Lightblue app) at anytime to check on water consumption, hence the need for it to sleep only at night when I won't be doing any checking.
I am already using an interrupt coming from the water flow sensor to count pulses and I can't see how to have that interrupt in place and also use it to wake the Nano from sleep while also having an internal one coming from the inbuilt real time clock to wake it from sleep in the morning.
Checking in various places I read that I can't have more than one interrupt "active" at the same time so I'm wondering if what I want is possible.
Any help would be appreciated.
Here's my sketch (now updated with the solution to my original question for anyone who stumbles into this in the future):
/*
Battery-powered Water Meter with battery monitor
Quick review of Bluetooth Low Energy terminology that's relevant to this sketch:
o A device / gadget is known as a "peripheral".
o It's made up of one or more "services", each of which are logical
groupings of one or more features / facilities - known as
"characteristics" in BLE terms.
o A computer / mobile phone / tablet is known as a "central" and can
connect to a peripheral to give instructions / receive data via
characteristics
//=============================================================================
This sketch creates a Bluetooth® Low Energy peripheral with three services:
o A service for metering the water flow through a garden hose
(that I'm using to drive a humane cat scarer water sprayer to stop the
local cats from using my back yard as a toilet).
o A battery service for tracking the voltage of the battery powering
the device.
o A date / time service so that counts and volumes can be by reported
just for today or for the whole month.
The real time clock on Nano 33 IoTs is known for it's poor timekeeping so
I've included a characteristic that allows the time to be adjusted by the
central by sending the number of minutes that are to be added / subtracted.
Initially the date and time are set when the Nano 33 IoT has the sketch
uploaded and it uses the constants set below for it (myHours, myMinutes
etc). This is also done if it's powered on when there's no flash values
to restore.
For water flow, characteristics are defined to report:
o Volume of water that flowed in last flow event (litres per minute)
o The frequency of the pulses in the flow event occuring now, averaged over
the last 30 (set by a constant) pulses
o Number of pulses counted for last flow event
o Total volume of water flowed today so far (litres)
o Total number of pulses today so far
o Number of flow events today so far
o Total volume of water that's flowed so far this month (litres)
o Number of flow events so far this month
The circuit components:
- Arduino Nano 33 IoT
- Clear Turbine Water Flow Sensor from the Pi Hut
https://thepihut.com/products/clear-turbine-water-flow-sensor-with-3-pin-jst
The sensor description on that page says:
// each pulse is approximately 2.25 millilitres
An alternative for describing how to calculate volume flow etc:
https://wiki.seeedstudio.com/Water-Flow-Sensor/#the-formula-for-the-calculation-of-water-flow-sensor
Sensor details
Working Voltage: DC 5V to 15V
Logic output/voltage: 5V
Current Draw: 15mA (at DC 5V)
Flow Rate Range: 1~30L/min
Flow Pulse: Frequency (Hz) = (5.0*Q) ±3% where Q=L/Min
Max Operating Temperature: <80 degree C
Max Liquid Temperature: <120 degree C
Operating Humidity: 35%~90%RH
Max Water Pressure: <1.75MPa
Storage Temperature: -25~+80 degree C
Storage Humidity: 25%~95%RH
- For Powering the Nano and water sensor:
o Two 18650 Lithium Ion batteries in parallel (fully charged voltage is 4.2V).
To allow the voltage to be monitored, a wire goes from positive to a voltage
divider so that the battery voltage is brought down to the 0-3.3V range that
the Nano 33 IoT can handle.
--battery +ve (max of 4.2v)
|
2kΩ
|
-----------> (max of 3.24v) to pin D14
|
6.8kΩ
--battery negative
o A step-up voltage booster to take the battery voltage up to 5v. This connects
into the Nano's micro USB and to the sensor to power them both.
o A nominal 6V solar panel and a charging module to keep the batteries charged.
o A voltage divider so the water flow sensor voltage is brought down to the
0-3.3V range that the Nano 33 IoT can handle.
--sensor +ve (max of 5v)
|
4.7kΩ
|
-----------> (max of 3.3v) to pin D7
|
9.1kΩ
|
--sensor negative
o An LED and a 680Ω resistor in series, connected to an output pin. It's used as
an indicator if there's ever a need to swap batteries.
In case the batteries ever get too discharged (I'm in the UK and we can't rely
on the yellow thing in the sky to shine reliably) there's a button that allows
for swapping batteries (or powering down for other reasons). When pressed it
triggers a function to store relevant data, date and time to flash storage.
The LED then flashes continually until power is removed.
When power is re-applied, a flag that was set instructs the processing to
reinstate the values from flash memory and the LED fast-blinks 15 times to
indicate it's happened.
I'm using the generic Bluetooth® Low Energy central app, LightBlue (iOS and Android)
to interact with the services and characteristics created in this sketch.
This code is in the public domain.
*/
#include <ArduinoBLE.h>
#include <RTCZero.h>
#include "ArduinoLowPower.h"
#include <FlashStorage.h>
// Note that uploading a new (or recompiled) sketch zero-ises any used flash memory.
// The following are all stored in flash memory:
// o batterySwapOrLowBatteryPowerDownFlag - 1 means you're swapping batteries and 2 means
// a save happened automatically because the battery voltage got too low.
// The flag is set to 1 when you press the button and the relevant stuff gets squirelled
// away in flash, ready to be restored when you've swapped the batteries. When you do,
// setup() is run as normal when power is applied again. In setup() the flag is read
// and, because it indicates so, the data gets restored.
// The characteristic variables stored are:
// o flowEventMeteredVolume
// o currentAveragedPulseFrequency
// o meteredVolumeToday
// o numberOfFlowEventsToday
// o meteredVolumeThisMonth
// o numberOfFlowEventsThisMonth
// o numberOfPulsesInFlowEvent
// o numberOfPulsesToday
#define batteryPin 14
#define waterFlowSensorPulseIn 2
#define ledPin 5
#define buttonPin 6
const auto PRESSED = LOW; // for push button
const auto connectedToComputerForTesting = false;
RTCZero rtc;
uint8_t rtcToday, rtcThisMonth;
const uint8_t mySeconds = 5;
const uint8_t myMinutes = 06;
const uint8_t myHours = 21;
const uint8_t myDay = 27;
const uint8_t myMonth = 5;
const uint8_t myYear = 26;
// Note that once deployed, the minutes can be adjusted using the
// respective characterstic. Just make sure to use a UTF-8 String when
// writing the value.
// In the LightBlue app, look at the "Arduino minutes adjustment"
// characterstic and then tap where it says "Hex" at top right. Choose
// "UTF-8 String" and tap "Save". When you tap the "Write new value"
// button, you'll be able to type in the minutes adjustment and it'll
// work as expected. You can even use negative values to set the time
// backwards. Examples: 23 adds 23 minutes to the current time. 180
// adds 3 hours and -74 puts the clock back by 1 hour and 14 minutes.
char myBuff[12];
// used when converting floats to strings prior to updating characteristics
char myDateAndTimeBuffer[20];
//used for preparing to set the value for the equivalent bluetooth characteristic
uint32_t now, bedTimeEpochTime, wakeupEpochTime;
const uint8_t bedTimeHour = 0, wakeupTimeHour = 8;
const float batteryFullyChargedVoltage = 4.2f;
const float flashSaveVoltage = 2.9f;
const float recoveryVoltage = 3.5f;
const float volumePerPulse = 0.00225f; // 2.25mL per pulse
const float measuredToActualFlowVolumeMultFactor = 1.44f;
// The volume of water sprayed by the cat scarer is 500mL. I captured
// the water from one spray in a bucket and then used a measuring jug.
// When measured by the sensor I got 154 pulses for one spray event. At
// a value of 2.25mL per pulse, this gives a volume of water of 347mL.
// This is out by a factor of 1.44 (500/347) so this sketch multiplies
// whatever it measures by this fiddle factor to come up with an actual
// value for the volume of water.
// Instead of routing the water via the cat scarer, for a further test I
// just turned on the tap, varying the flow rate up and down slowly. The
// end result of that test was that a fiddle factor of 1.6 was needed. I
// was surprised as I expected the results to be pretty much the same.
// Anyway, seeeing this project is all about measuring how much water my
// cat scarer is using, I'm running with the fiddle factor of 1.44.
volatile int pulseCount = 0; // 'volatile' is required for interrupt variables
const int pulsesStoppedTimeout = 50; // 50 milliseconds
const int ignorePulsesThreshold = 50;
// equiv to 50 * 0.00225mL * 1.44 which is just over 150 mL
// When attaching hosepipe or switching on tap, a small amount of water
// will move, so this makes sure it's ignored.
int batteryRead;
float batteryVoltage, oldBatteryVoltage;
long previousMillis = 0; // last time the battery voltage was checked, in ms
// Declare vars for characteristics and flow details. Set as global because of the need to
// store them in flash during a battery swap and then restore them from flash in setup()
// once the batteries have been replaced making the power come back on.
int numberOfFlowEventsToday = 0, numberOfFlowEventsThisMonth = 0,
numberOfPulsesInFlowEvent = 0, numberOfPulsesToday = 0;
float flowEventMeteredVolume = 0.00f, meteredVolumeToday = 0.00f,
meteredVolumeThisMonth = 0.00f, currentAveragedPulseFrequency = 0.00f;
FlashStorage(flashBatterySwapOrLowBatteryPowerDownFlag, byte);
FlashStorage(flashFlowEventMeteredVolume, float);
FlashStorage(flashCurrentAveragedPulseFrequency, float);
FlashStorage(flashMeteredVolumeToday, float);
FlashStorage(flashMeteredVolumeThisMonth, float);
FlashStorage(flashNumberOfFlowEventsToday, int);
FlashStorage(flashNumberOfFlowEventsThisMonth, int);
FlashStorage(flashNumberOfPulsesInFlowEvent, int);
FlashStorage(flashNumberOfPulsesToday, int);
FlashStorage(flashHours, uint8_t);
FlashStorage(flashMinutes, uint8_t);
FlashStorage(flashSeconds, uint8_t);
FlashStorage(flashDay, uint8_t);
FlashStorage(flashMonth, uint8_t);
FlashStorage(flashYear, uint8_t);
//===================================================
// Bluetooth® Low Energy Battery Service declarations
// 839860000-9fa2-49b6-a1eb-9bc4eaca3dee generated randomly and 0000 substituted in digit positions 5,6,7,8
BLEService myBatteryService("39860000-9fa2-49b6-a1eb-9bc4eaca3dee");
// The voltage of the batteries powering the water meter. They are charged with a nominal 6v solar panel
BLEStringCharacteristic batteryVoltageChar("39860001-9fa2-49b6-a1eb-9bc4eaca3dee", BLERead | BLENotify, 10);
BLEDescriptor batteryVoltageDescriptor("2901", "Battery voltage");
// Bluetooth® Low Energy Date and Time Service
// 9e620a5a-5fde-4d08-96e6-2d5a731c3263 generated randomly and 0000 substituted in digit positions 5,6,7,8
BLEService myDateAndTimeService("9e620000-5fde-4d08-96e6-2d5a731c3263");
//The current Date and Time stored on the Arduino Nano 33 IoT in dd/mm/yyy hh:mm:ss format
BLEStringCharacteristic dateAndTimeChar("9e620001-5fde-4d08-96e6-2d5a731c3263", BLERead | BLENotify, 20);
BLEDescriptor dateAndTimeDescriptor("2901", "Arduino date/time");
//The adjustment for minutes. When written to , it updates the Arduino clock's minutes
BLEStringCharacteristic minutesAdjustChar("9e620002-5fde-4d08-96e6-2d5a731c3263", BLERead | BLEWrite, 4);
BLEDescriptor minutesAdjustDescriptor("2901", "Arduino minutes adjustment");
// Bluetooth® Low Energy Metered Water Flow Service;
// 89090000-7d04-4d5a-a2cd-913481c1cdd7 generated randomly and 0000 substituted in digit positions 5,6,7,8
BLEService myWaterMeterService("89090000-7d04-4d5a-a2cd-913481c1cdd7");
// Bluetooth® Low Energy Metered Flow characteristic definitions follow
// volume that flowed in a single flow event - remote clients will be able to get notifications if this characteristic changes
BLEStringCharacteristic flowEventMeteredVolumeChar("89090001-7d04-4d5a-a2cd-913481c1cdd7", BLERead | BLENotify, 10);
BLEDescriptor flowEventMeteredVolumeDescriptor("2901", "Flow event metered volume");
// pulse frequency of current flow event - remote clients will be able to get notifications if this characteristic changes
BLEStringCharacteristic pulseFrequencyChar("89090002-7d04-4d5a-a2cd-913481c1cdd7", BLERead | BLENotify, 10);
BLEDescriptor pulseFrequencyDescriptor("2901", "Current ave pulse frequency");
// Total volume flow today so far - remote clients will be able to get notifications if this characteristic changes
BLEStringCharacteristic meteredFlowTodayChar("89090003-7d04-4d5a-a2cd-913481c1cdd7", BLERead | BLENotify, 10);
BLEDescriptor meteredFlowTodayDescriptor("2901", "Metered flow today");
// Number of individual flow events today so far - remote clients will be able to get notifications if this characteristic changes
BLEStringCharacteristic numberOfFlowEventsTodayChar("89090004-7d04-4d5a-a2cd-913481c1cdd7", BLERead | BLENotify, 10);
BLEDescriptor numberOfFlowEventsTodayDescriptor("2901", "Number of flow events today");
// Total volume flow this month so far - remote clients will be able to get notifications if this characteristic changes
BLEStringCharacteristic meteredFlowThisMonthChar("89090005-7d04-4d5a-a2cd-913481c1cdd7", BLERead | BLENotify, 10);
BLEDescriptor meteredFlowThisMonthDescriptor("2901", "Metered flow this month");
// Number of individual flow events this month so far - remote clients will be able to get notifications if this characteristic changes
BLEStringCharacteristic numberOfFlowEventsThisMonthChar("89090006-7d04-4d5a-a2cd-913481c1cdd7", BLERead | BLENotify, 10);
BLEDescriptor numberOfFlowEventsThisMonthDescriptor("2901", "Number of flow events this month");
// Number of pulses in this flow event - remote clients will be able to get notifications if this characteristic changes
BLEStringCharacteristic numberOfPulsesInFlowEventChar("89090007-7d04-4d5a-a2cd-913481c1cdd7", BLERead | BLENotify, 10);
BLEDescriptor numberOfPulsesInFlowEventDescriptor("2901", "Number of pulses in flow event");
// Number of pulses in flow events today so far - remote clients will be able to get notifications if this characteristic changes
BLEStringCharacteristic numberOfPulsesTodayChar("89090008-7d04-4d5a-a2cd-913481c1cdd7", BLERead | BLENotify, 10);
BLEDescriptor numberOfPulsesTodayDescriptor("2901", "Number of pulses today");
void setup() {
// IMPORTANT Depending on how you've built the device you may need to
// make sure of the following:
// When connecting / reconnecting the device, connect in this sequence:
// 1) Battery voltage sense wire to its voltage divider resistor chain.
// This is because when the Nano is powered and the startup delay has
// finished, it checks the battery voltage. By then if the wire isn't
// connected it'll get a random reading from pin D14 (as it'll be
// floating). By chance, that reading may represent a battery voltage
// that's too low and so it will incorrectly do a battery swap power down.
// 2) Water flow sensor wire from its voltage divider resistor chain to the
// interrupt pin (pulse_input: 2)
// 3) Water sensor power -ve to Arduino board's ground
// 4) USB connector from battery pack to Arduino board's USB socket
// 5) Water sensor +5v power in
//
rtc.begin(); // initialize RTC
if (connectedToComputerForTesting) {
// Note: When connecting to a computer you need to connect charged batteries
// to the battery voltage divider. If you don't, the sketch will conclude that
// the batteries are below the save voltage and get everyone confused!!!
Serial.begin(9600); // initialize serial communication for when connected to computer
while (!Serial);
Serial.println("starting");
}
delay(5000);
// Give time for batteries to be properly inserted if that's what's
// happening.
// It's needed because there may be a few moments where the insertion of the
// first battery doesn't initially make a perfect connection and instead the
// connection 'bounces'. The delay gives time for that connection of power to
// settle down.
//
pinMode(LED_BUILTIN, OUTPUT);
// initialize the built-in LED pin to indicate when a central is connected
pinMode(waterFlowSensorPulseIn, INPUT_PULLUP);
pinMode(buttonPin, INPUT_PULLUP);
pinMode(ledPin, OUTPUT);
//attachInterrupt(digitalPinToInterrupt(waterFlowSensorPulseIn), countPulse, RISING);
LowPower.attachInterruptWakeup(waterFlowSensorPulseIn, countPulse, RISING);
// begin initialization
if (!BLE.begin()) {
if (connectedToComputerForTesting) {
Serial.println("starting Bluetooth® Low Energy module failed!");
}
while (1);
}
if (flashBatterySwapOrLowBatteryPowerDownFlag.read() == 1) {
// 1 is for a user-triggered battery swap
restoreFromFlash();
// Reset the flag to show all vals read
flashBatterySwapOrLowBatteryPowerDownFlag.write(0);
// fast blink the LED 15 times to show data from flash has been restored
blinker(100, 15);
if (connectedToComputerForTesting) Serial.println("flash read");
} else if (flashBatterySwapOrLowBatteryPowerDownFlag.read() == 2) {
// 2 is for low voltage save. Either you got to it in time
// and replaced the batteries or you didn't and the solar
// panel has charged them enough so that the Arduino is
// now powered. Whichever happened, as long as the battery
// voltage is above the recovery voltage things can
// continue automatically. This can really only happen if
// you got to it in time and changed the batteries because
// if you didn't, there won't have been enough time for the
// batteries to get charged up to the recovery voltage.
if ( analogRead(batteryPin) * (batteryFullyChargedVoltage / 1023.0f) >= recoveryVoltage ) {
restoreFromFlash();
// Reset the flag to show all vals read
flashBatterySwapOrLowBatteryPowerDownFlag.write(0);
// fast blink the LED 15 times to show data from flash has been restored
blinker(100, 15);
if (connectedToComputerForTesting) Serial.println("flash read");
} else { // the recovery voltage HASN'T been reached
// It's a user decision as to what to do because if too much time
// has passed, too many flow events may have been missed.
//
if (connectedToComputerForTesting) Serial.println("Recovery voltage not reached - replace batteries");
while (1) {
// Will loop forever - this blink pattern is unique to this status
// and the only option really is to swap the batteries for charged ones.
blinker(80, 3);
blinker(300, 2);
}
}
} else {
// flashBatterySwapOrLowBatteryPowerDownFlag not set so use constants defined
// above for setting time. This happens on the first time it runs or whenever
// your sketch is uploaded to the Nano, so make sure you set the constants first.
rtc.setTime(myHours, myMinutes, mySeconds);
rtc.setDate(myDay, myMonth, myYear);
rtcToday = rtc.getDay();
rtcThisMonth = rtc.getMonth();
if (connectedToComputerForTesting) Serial.println("just set time using constants");
}
setUpBlueTooth();
}
void setUpBlueTooth() {
/* Set a local name for the Bluetooth® Low Energy device
This name will appear in advertising packets and can be used by remote devices to
identify this Bluetooth® Low Energy device. The name can be changed but maybe be
truncated based on space left in advertisement packet
*/
BLE.setLocalName("Arduino Water Meter");
// -------------------------------------- Battery Service
BLE.setAdvertisedService(myBatteryService); // add the service UUID
myBatteryService.addCharacteristic(batteryVoltageChar); // add the battery voltage characteristic
batteryVoltageChar.addDescriptor(batteryVoltageDescriptor);
BLE.addService(myBatteryService); // Add the battery service
batteryRead = analogRead(batteryPin); // battery voltage is available immediately
oldBatteryVoltage = batteryRead * (batteryFullyChargedVoltage / 1023.0f); // Calculate for later
batteryVoltageChar.writeValue("waiting..."); // set initial value for this characteristic
// ------------------------------------- Date and Time Service
BLE.setAdvertisedService(myDateAndTimeService); // add the service UUID
// Date and time
myDateAndTimeService.addCharacteristic(dateAndTimeChar); // add the dat and time characteristic
dateAndTimeChar.addDescriptor(dateAndTimeDescriptor);
myDateAndTimeService.addCharacteristic(minutesAdjustChar); // add the minutes adjustment characteristic
minutesAdjustChar.addDescriptor(minutesAdjustDescriptor);
BLE.addService(myDateAndTimeService); // Add the date and time service
// date and time is available immediately so update characteristic with it dd/mm/yy hh:mm:ss
sprintf(myDateAndTimeBuffer, "%02u/%02u/%u %02u:%02u:%02u", rtc.getDay(), rtc.getMonth(), rtc.getYear(), rtc.getHours(), rtc.getMinutes(), rtc.getSeconds());
dateAndTimeChar.setValue(myDateAndTimeBuffer);
// ------------------------------------- Water Meter Service
BLE.setAdvertisedService(myWaterMeterService); // add the service UUID
// Flow event
myWaterMeterService.addCharacteristic(flowEventMeteredVolumeChar); // add the water volume flow characteristic
flowEventMeteredVolumeChar.addDescriptor(flowEventMeteredVolumeDescriptor);
// pulse frequency of current flow event
myWaterMeterService.addCharacteristic(pulseFrequencyChar); // add the pulse frequency characteristic
pulseFrequencyChar.addDescriptor(pulseFrequencyDescriptor);
// Total volume flow today so far
myWaterMeterService.addCharacteristic(meteredFlowTodayChar); // add the water volume flow characteristic
meteredFlowTodayChar.addDescriptor(meteredFlowTodayDescriptor);
// Number of flow events today so far
myWaterMeterService.addCharacteristic(numberOfFlowEventsTodayChar); // add the water flow daily triggers characteristic
numberOfFlowEventsTodayChar.addDescriptor(numberOfFlowEventsTodayDescriptor);
// Total volume flow this month so far
myWaterMeterService.addCharacteristic(meteredFlowThisMonthChar); // add the water volume flow characteristic
meteredFlowThisMonthChar.addDescriptor(meteredFlowThisMonthDescriptor);
// Number of flow events this month so far
myWaterMeterService.addCharacteristic(numberOfFlowEventsThisMonthChar); // add the water flow daily triggers characteristic
numberOfFlowEventsThisMonthChar.addDescriptor(numberOfFlowEventsThisMonthDescriptor);
// Number of pulses in flow event
myWaterMeterService.addCharacteristic(numberOfPulsesInFlowEventChar); // add the number of pulses in flow event characteristic
numberOfPulsesInFlowEventChar.addDescriptor(numberOfPulsesInFlowEventDescriptor);
// Number of pulses today
myWaterMeterService.addCharacteristic(numberOfPulsesTodayChar); // add the number of pulses today characteristic
numberOfPulsesTodayChar.addDescriptor(numberOfPulsesTodayDescriptor);
BLE.addService(myWaterMeterService); // Add the water flow service
minutesAdjustChar.setValue("waiting..."); // set initial value for this characteristic
flowEventMeteredVolumeChar.setValue("waiting..."); // set initial value for this characteristic
pulseFrequencyChar.setValue("waiting..."); // set initial value for this characteristic
meteredFlowTodayChar.setValue("waiting..."); // set initial value for this characteristic
numberOfFlowEventsTodayChar.setValue("waiting...");
meteredFlowThisMonthChar.setValue("waiting..."); // set initial value for this characteristic
numberOfFlowEventsThisMonthChar.setValue("waiting...");
numberOfPulsesInFlowEventChar.setValue("waiting...");
numberOfPulsesTodayChar.setValue("waiting...");
/*
Start advertising - it'll start continuously transmitting Bluetooth® Low Energy
advertising packets and will be visible to remote Bluetooth® Low Energy central
devices until it receives a new connection.
*/
BLE.advertise();
if (connectedToComputerForTesting) Serial.println("Bluetooth® device active, waiting for connection...");
}
void restoreFromFlash() {
// Executed if a battery swap (1) or a low battery voltage powerdown (2) happened.
// Use time stored in flash - and add 10 mins (great for a battery swap, useless
// for a low battery powerdown!)
// If more than a few moments have passed, you'll need to correctly set the clock
// using the minutes adjustment characteristic
rtc.setTime(flashHours.read(), flashMinutes.read(), flashSeconds.read());
rtc.setDate(flashDay.read(), flashMonth.read(), flashYear.read());
rtc.setEpoch(rtc.getEpoch() + 600);
rtcToday = rtc.getDay();
rtcThisMonth = rtc.getMonth();
if (connectedToComputerForTesting) {
Serial.println("just set time using flash and added 10 minutes");
Serial.println(rtc.getHours());
Serial.println(rtc.getMinutes());
Serial.println(rtc.getDay());
Serial.println(rtc.getMonth());
Serial.println(rtc.getYear());
}
// Read relevant characteristic and flow values from flash
flowEventMeteredVolume = flashFlowEventMeteredVolume.read();
currentAveragedPulseFrequency = flashCurrentAveragedPulseFrequency.read();
meteredVolumeToday = flashMeteredVolumeToday.read();
meteredVolumeThisMonth = flashMeteredVolumeThisMonth.read();
numberOfFlowEventsToday = flashNumberOfFlowEventsToday.read();
numberOfFlowEventsThisMonth = flashNumberOfFlowEventsThisMonth.read();
numberOfPulsesInFlowEvent = flashNumberOfPulsesInFlowEvent.read();
numberOfPulsesToday = flashNumberOfPulsesToday.read();
// write values to characteristics so they start off correctly
// Note that battery voltage isn't stored in flash as there's no need
dtostrf(flowEventMeteredVolume, 9, 2, myBuff);
flowEventMeteredVolumeChar.setValue(myBuff);
dtostrf(meteredVolumeToday, 9, 2, myBuff);
meteredFlowTodayChar.setValue(myBuff);
sprintf(myBuff, "%u", numberOfFlowEventsToday);
numberOfFlowEventsTodayChar.setValue(myBuff);
dtostrf(currentAveragedPulseFrequency, 9, 2, myBuff);
pulseFrequencyChar.setValue(myBuff);
dtostrf(meteredVolumeThisMonth, 9, 2, myBuff);
meteredFlowThisMonthChar.setValue(myBuff);
sprintf(myBuff, "%u", numberOfFlowEventsThisMonth);
numberOfFlowEventsThisMonthChar.setValue(myBuff);
sprintf(myBuff, "%u", numberOfPulsesInFlowEvent);
numberOfPulsesInFlowEventChar.setValue(myBuff);
sprintf(myBuff, "%u", numberOfPulsesToday);
numberOfPulsesTodayChar.setValue(myBuff);
}
char *dtostrf (double val, signed char width, unsigned char prec, char *sout) {
/*
dtostrf - Emulation for dtostrf function from avr-libc
Copyright (c) 2013 Arduino. All rights reserved.
Written by Cristian Maglie <c.maglie@bug.st>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
char fmt[20];
sprintf(fmt, "%%%d.%df", width, prec);
sprintf(sout, fmt, val);
return sout;
}
void countPulse() {
pulseCount++;
// delayMicroseconds(11000);
}
void updateBatteryVoltage() {
// Read the current voltage on the batteryPin input pin.
// and update the characteristic.
batteryRead = analogRead(batteryPin);
batteryVoltage = batteryRead * (batteryFullyChargedVoltage / 1023.0f);
// Debug: Serial.print("Battery voltage is now: ");
// Debug: Serial.println(batteryVoltage);
if (batteryVoltage != oldBatteryVoltage) { // if the battery voltage has changed
dtostrf(batteryVoltage, 9, 2, myBuff);
batteryVoltageChar.setValue(myBuff);
oldBatteryVoltage = batteryVoltage; // save the voltage for next comparison
}
if (batteryVoltage < flashSaveVoltage) {
// make sure all saved to flash and blink the LED forever (until power switched off)
if (connectedToComputerForTesting) {
Serial.print("Battery voltage is less than save voltage. It is: "); // print it
Serial.println(batteryVoltage);
Serial.print("save voltage is: "); // print it
Serial.println(flashSaveVoltage);
}
// Set the flag to show values are to be restored
flashBatterySwapOrLowBatteryPowerDownFlag.write(2); // 2 means low battery voltage triggered save to flash
batterySwapOrLowBatteryPowerDown();
}
}
void batterySwapOrLowBatteryPowerDown() {
// Executed if pushbutton pressed or battery voltage falls below batterysave voltage
// Write real time clock values to flash
flashHours.write(rtc.getHours());
flashMinutes.write(rtc.getMinutes());
flashSeconds.write(rtc.getSeconds());
flashDay.write(rtc.getDay());
flashMonth.write(rtc.getMonth());
flashYear.write(rtc.getYear());
// Write relevant characteristic and flow values to flash
flashFlowEventMeteredVolume.write(flowEventMeteredVolume);
flashMeteredVolumeToday.write(meteredVolumeToday);
flashMeteredVolumeThisMonth.write(meteredVolumeThisMonth);
flashNumberOfFlowEventsToday.write(numberOfFlowEventsToday);
flashNumberOfFlowEventsThisMonth.write(numberOfFlowEventsThisMonth);
flashNumberOfPulsesInFlowEvent.write(numberOfPulsesInFlowEvent);
flashNumberOfPulsesToday.write(numberOfPulsesToday);
if (connectedToComputerForTesting) {
Serial.println("flash written");
Serial.print("Disconnected from central");
}
digitalWrite(LED_BUILTIN, LOW);
while (1) {
// Slow blink the LED to indicate vals all stored in flash.
blinker(1000, 1);
// Leave it blinking forever as a reminder to swap the
// batteries (assuming you notice).
// When the old batteries are removed, it'll switch off and
// when the new ones are put in, processing will start at
// setup() as normal.
// If battery voltage had fallen to the level where things
// were automatically saved to flash, two things might happen
// from there:
// 1) You get to it (while the Arduino is still being powered)
// and replace the batteries, meaning the Arduino will get
// powered down as part of that. When you put the new ones
// in, processing will start at setup() as normal.
// 2) You don't get to it, the batteries eventually deplete
// so the Arduino becomes powered down.
// As the solar panels charge the batteries, eventually
// they will be charged enough to power the Arduino again.
// When they are, processing will start at setup() as
// normal. You'll have to decide how to cope with this.
}
}
void dealWithPossibleDayOrMonthAdvance(){
if ( rtcThisMonth != rtc.getMonth() ) { // must be next month!
rtcThisMonth = rtc.getMonth();
meteredVolumeThisMonth = 0.0f;
dtostrf(meteredVolumeThisMonth, 9, 2, myBuff);
meteredFlowThisMonthChar.setValue(myBuff);
numberOfFlowEventsThisMonth = 0;
sprintf(myBuff, "%u", numberOfFlowEventsThisMonth);
numberOfFlowEventsThisMonthChar.setValue(myBuff);
}
if ( rtcToday != rtc.getDay() ) { // must be tomorrow!
rtcToday = rtc.getDay();
flowEventMeteredVolume = 0;
dtostrf(flowEventMeteredVolume, 9, 2, myBuff);
flowEventMeteredVolumeChar.setValue(myBuff);
meteredVolumeToday = 0;
dtostrf(meteredVolumeToday, 9, 2, myBuff);
meteredFlowTodayChar.setValue(myBuff);
numberOfFlowEventsToday = 0;
sprintf(myBuff, "%u", numberOfFlowEventsToday);
numberOfFlowEventsTodayChar.setValue(myBuff);
numberOfPulsesToday = 0;
sprintf(myBuff, "%u", numberOfPulsesToday);
numberOfPulsesTodayChar.setValue(myBuff);
}
}
void blinker(long mySpeed, int numBlinks) {
/* These values work well
blinker(80, 3);
blinker(300, 2);
*/
long myCount = 0, previousMillis = 0, currentMillis = millis();
bool blinkingIsToContinue = true, ledState = LOW;
while (blinkingIsToContinue) {
currentMillis = millis();
if (digitalRead(buttonPin) == PRESSED) blinkingIsToContinue = false;
// allow early exit from blinker
if (blinkingIsToContinue){
if (currentMillis - previousMillis > mySpeed){
previousMillis = currentMillis;
myCount++;
digitalWrite(ledPin, ledState);
ledState = !ledState;
if (myCount == numBlinks * 3) return;
}
}
}
}
void loop() {
static unsigned long timePulseReceived = 0;
static int previousPulseCount = 0;
static long elapsedTime = 0;
const byte NumberOfPulsesToAverageFrequencyOver = 30;
static int numberOfPulsesReceivedForAveraging = 0, timeTakenForPulsesBeingAveraged = 0;
static bool flowEventInProgress = false;
// First, allow for a battery swap / power down
if (digitalRead(buttonPin) == PRESSED) {
// Debug: Serial.println("button press detected");
// write relevant characteristic vals to flash, set the flag and light the LED
// Set the flag to show values are to be restored
// First, blink with this pattern to acknowledge button press
blinker(100,2);
blinker(300,1);
blinker(100,2);
delay(1000);
flashBatterySwapOrLowBatteryPowerDownFlag.write(1); // 1 means battery swap requested by user
batterySwapOrLowBatteryPowerDown();
}
// allow for a Bluetooth® Low Energy central to connect
BLEDevice central = BLE.central();
if (central) {
if (minutesAdjustChar.written()) {
rtc.setEpoch(rtc.getEpoch()+60UL*((long)minutesAdjustChar.value().toInt()));
}
}
// check battery level every 200 ms
long currentMillis = millis();
// if 600 ms have passed, check battery level and record clock:
if (currentMillis - previousMillis >= 600) {
previousMillis = currentMillis;
updateBatteryVoltage();
sprintf(myDateAndTimeBuffer, "%02u/%02u/%u %02u:%02u:%02u", rtc.getDay(), rtc.getMonth(), rtc.getYear(), rtc.getHours(), rtc.getMinutes(), rtc.getSeconds());
dateAndTimeChar.setValue(myDateAndTimeBuffer);
}
// now deal with water flow. The interrupt service routine, countPulse, is fired
// whenever a pulse comes in from the sensor and increments pulseCount
if (pulseCount > 0){ // water is flowing, wait until it stops before calculating volumes etc.
flowEventInProgress = true;
if (connectedToComputerForTesting) Serial.println("Flow event in progress");
if (pulseCount == previousPulseCount) {
// no additional pulse received yet, keep waiting / allowing for another
// pulse to arrive within pulsesStoppedTimeout
elapsedTime = millis() - timePulseReceived;
if (elapsedTime > pulsesStoppedTimeout) { // the flow event is finished so calculate volumes etc
if (connectedToComputerForTesting) Serial.println("Flow event just stopped");
if (pulseCount > ignorePulsesThreshold) { // connecting a hosepipe can cause a few pulses so ignore them
numberOfPulsesInFlowEvent = pulseCount;
numberOfFlowEventsToday++;
numberOfFlowEventsThisMonth++;
numberOfPulsesToday = numberOfPulsesToday + numberOfPulsesInFlowEvent;
// can now calculate volume of water in this flow period
flowEventMeteredVolume = (float)numberOfPulsesInFlowEvent * volumePerPulse * measuredToActualFlowVolumeMultFactor;
meteredVolumeToday += flowEventMeteredVolume;
meteredVolumeThisMonth += flowEventMeteredVolume;
dtostrf(flowEventMeteredVolume, 9, 3, myBuff);
flowEventMeteredVolumeChar.setValue(myBuff);
dtostrf(meteredVolumeToday, 9, 2, myBuff);
meteredFlowTodayChar.setValue(myBuff);
sprintf(myBuff, "%u", numberOfFlowEventsToday);
numberOfFlowEventsTodayChar.setValue(myBuff);
dtostrf(meteredVolumeThisMonth, 9, 2, myBuff);
meteredFlowThisMonthChar.setValue(myBuff);
sprintf(myBuff, "%u", numberOfFlowEventsThisMonth);
numberOfFlowEventsThisMonthChar.setValue(myBuff);
sprintf(myBuff, "%u", numberOfPulsesInFlowEvent);
numberOfPulsesInFlowEventChar.setValue(myBuff);
sprintf(myBuff, "%u", numberOfPulsesToday);
numberOfPulsesTodayChar.setValue(myBuff);
}
// prepare for next flow event
flowEventInProgress = false;
pulseCount = 0;
previousPulseCount = 0;
elapsedTime = 0;
}
} else { // another pulse has arrived
numberOfPulsesReceivedForAveraging++;
timeTakenForPulsesBeingAveraged = timeTakenForPulsesBeingAveraged + millis() - timePulseReceived;
if(numberOfPulsesReceivedForAveraging == NumberOfPulsesToAverageFrequencyOver) {
currentAveragedPulseFrequency = 1000.0f * (float)numberOfPulsesReceivedForAveraging/(float)timeTakenForPulsesBeingAveraged;
dtostrf(currentAveragedPulseFrequency, 9, 0, myBuff);
pulseFrequencyChar.setValue(myBuff);
numberOfPulsesReceivedForAveraging = 0;
timeTakenForPulsesBeingAveraged = 0;
}
timePulseReceived = millis();
previousPulseCount = pulseCount;
}
}
if (!flowEventInProgress) {
dealWithPossibleDayOrMonthAdvance();
if (rtc.getHours() < wakeupTimeHour) {
// Check to see if it's worth doing the full check - no point if it's later than
// the wakeup hour
// temporarily store time in "now" while working out the epoch values needed
now = rtc.getEpoch();
// represent bedTime and Wakeup as epoch numbers so they can easily be compared
// Get epoch time of bedtimeHour
rtc.setTime(bedTimeHour, 0, 0);
bedTimeEpochTime = rtc.getEpoch();
// Get epoch time of wakeupTimeHour
rtc.setTime(wakeupTimeHour, 0, 0);
wakeupEpochTime = rtc.getEpoch();
// put the time back to now
rtc.setEpoch(now);
if ( (now > bedTimeEpochTime) && (now < wakeupEpochTime) ) {
// It should be asleep so send it to bed!
if (connectedToComputerForTesting) {
Serial.print("going to sleep, rtc mins is: ");
Serial.println(rtc.getMinutes());
Serial.flush();
Serial.end();
delay(500); // give it a chance to fully end before sleep
}
LowPower.sleep((wakeupEpochTime - now) * 1000); // sleep until wakeup time
// NB the first pulse of a new flow event would also wake up the Nano. When
// the flow event is finished (and as long as it's still bedtime) the Nano
// will be put back to sleep.
if (connectedToComputerForTesting) {
Serial.begin(9600);
while(!Serial);
Serial.println("awake");
}
}
}
}
}

