|
// |
|
// Basic Z-Uno Power Metering Sensor |
|
// |
|
// Counts light pulses on a power meter and |
|
// delivers it over z-wave as a sensor metered in kWh. |
|
// |
|
// Anders Evenrud <[email protected]> |
|
// |
|
// https://gist.github.com/andersevenrud/bfba208f0901887f3bf2b7b9ee8a6f97 |
|
// |
|
// License: MIT |
|
// |
|
|
|
#define WITH_CC_METER |
|
|
|
#include <EEPROM.h> |
|
#include <ZUNO_Buttons.h> |
|
|
|
/* Enable Serial debugging */ |
|
#define DEBUG 0 |
|
|
|
/* Debugger Serial baud rate */ |
|
#define SERIAL_BAUD 115200 |
|
|
|
/* Debugger Serial device */ |
|
#define SERIAL_DEVICE Serial |
|
|
|
/* Pin of the photoresistor */ |
|
#define ZUNO_SENSOR_PIN A0 |
|
|
|
/* Pin of the service button */ |
|
#define ZUNO_BUTTON_PIN 23 |
|
|
|
/* Location in EEPROM where history is stored */ |
|
#define ZUNO_EEPROM_ADDR 0x0 |
|
|
|
/* Enable hack to get publish to work... |
|
There seems to be a bug in the SDK related to Meter type controllers |
|
which corrupts CC reports. Z-Wave JS will drop these! The "solution" |
|
is to occupy the first channel with some basic sensor. This seems to |
|
create a Meter without a version (undefined) that does not get the same |
|
checks as v4+ (v6 is standard in Z-Uno) and the value comes through. |
|
An unfortunate side effect of this is that the reset feature disappears. */ |
|
#define ZUNO_SDK_BUG_WORKAROUND 0 |
|
|
|
/* The channel used for emitting updates */ |
|
#define ZUNO_CHANNEL_NUMBER_POWER ZUNO_SDK_BUG_WORKAROUND + 1 |
|
|
|
/* How often we announce and store value */ |
|
#define CONFIG_UPDATE_INTERVAL 1000 * 60 |
|
|
|
/* The light threshold where we tick the meter (0-100) */ |
|
#define CONFIG_LIGHT_THRESHOLD 20 |
|
|
|
/* Store data in EEPROM to keep it between power cycles */ |
|
#define CONFIG_ENABLE_STORAGE 1 |
|
|
|
/* Disable publishing of values if you're doing polling exclusively. |
|
This changes behavior of the getter for consistency. */ |
|
#define CONFIG_DISABLE_PUBLISH 0 |
|
|
|
/* We have 4 bytes here and a precision of two. |
|
My meter counts 1Wh per pulse (1000imp/kWh), so if we ex |
|
have 100 counts, it needs to be divided by 10 to get 0.10 kWh. */ |
|
#define PULSE_VALUE_CONVERTER(VALUE) VALUE / 10 |
|
|
|
/* Current meter increment value */ |
|
DWORD meterValue = 0; |
|
|
|
// Internal States |
|
bool high = false; |
|
unsigned long lastScheduleMs = 0; |
|
unsigned int lastLightLevel = 0; |
|
#if CONFIG_DISABLE_PUBLISH |
|
DWORD announceValue = 0; |
|
#endif |
|
|
|
// Set up Z-Wave features |
|
ZUNO_SETUP_SLEEPING_MODE(ZUNO_SLEEPING_MODE_ALWAYS_AWAKE); |
|
|
|
#if ZUNO_SDK_BUG_WORKAROUND |
|
bool meterBugfixVariable = false; |
|
|
|
ZUNO_SETUP_CHANNELS( |
|
ZUNO_SENSOR_BINARY(ZUNO_SENSOR_BINARY_TYPE_GENERAL_PURPOSE, meterBugfixVariable), |
|
ZUNO_METER(ZUNO_METER_TYPE_ELECTRIC, METER_RESET_ENABLE, ZUNO_METER_ELECTRIC_SCALE_KWH, METER_SIZE_FOUR_BYTES, METER_PRECISION_TWO_DECIMALS, meterGetter, meterReseter)); |
|
#else |
|
ZUNO_SETUP_CHANNELS( |
|
ZUNO_METER(ZUNO_METER_TYPE_ELECTRIC, METER_RESET_ENABLE, ZUNO_METER_ELECTRIC_SCALE_KWH, METER_SIZE_FOUR_BYTES, METER_PRECISION_TWO_DECIMALS, meterGetter, meterReseter)); |
|
#endif |
|
|
|
/* Z-Wave reset method */ |
|
void meterReseter() { |
|
eepromReset(); |
|
} |
|
|
|
/* Z-Wave getter method */ |
|
DWORD meterGetter(void) { |
|
#if CONFIG_DISABLE_PUBLISH |
|
return PULSE_VALUE_CONVERTER(announceValue); |
|
#else |
|
return PULSE_VALUE_CONVERTER(meterValue); |
|
#endif |
|
} |
|
|
|
/* Save current state to EEPROM */ |
|
void eepromSave() { |
|
#if CONFIG_ENABLE_STORAGE |
|
SERIAL_DEVICE.println("Save EEPROM..."); |
|
EEPROM.put(ZUNO_EEPROM_ADDR, &meterValue, sizeof(meterValue)); |
|
#endif |
|
} |
|
|
|
/* Reset EEPROM values */ |
|
void eepromReset() { |
|
meterValue = 0; |
|
#if CONFIG_DISABLE_PUBLISH |
|
announceValue = 0; |
|
#endif |
|
eepromSave(); |
|
} |
|
|
|
/* Restore previous value from EEPROM */ |
|
void eepromRestore() { |
|
#if CONFIG_ENABLE_STORAGE |
|
SERIAL_DEVICE.println("Restpre EEPROM..."); |
|
|
|
DWORD tmpValue; |
|
EEPROM.get(ZUNO_EEPROM_ADDR, &tmpValue, sizeof(meterValue)); |
|
|
|
if (tmpValue > meterValue) { |
|
SERIAL_DEVICE.println("Got value: " + String(tmpValue)); |
|
|
|
meterValue = tmpValue; |
|
} |
|
#endif |
|
|
|
#if CONFIG_DISABLE_PUBLISH |
|
announceValue = meterValue; |
|
#endif |
|
} |
|
|
|
/* Reads photoresistor value and clamps the value to a perctage range */ |
|
unsigned int sensorRead() { |
|
unsigned long raw = analogRead(ZUNO_SENSOR_PIN); |
|
unsigned int value = map(raw, 0, 800, 0, 100); |
|
return value; |
|
} |
|
|
|
/* Emit our current value onto the network */ |
|
void announce() { |
|
SERIAL_DEVICE.println("Announce"); |
|
|
|
#if CONFIG_DISABLE_PUBLISH |
|
announceValue = meterValue; |
|
#else |
|
zunoSendReport(ZUNO_CHANNEL_NUMBER_POWER); |
|
#endif |
|
} |
|
|
|
/* Increment metering value */ |
|
void increment() { |
|
meterValue++; |
|
SERIAL_DEVICE.println("Tick (" + String(lastLightLevel) + "L) > " + String(meterValue) + "Wh"); |
|
} |
|
|
|
/* Arduino bootstrapping */ |
|
void setup() { |
|
#if DEBUG |
|
SERIAL_DEVICE.begin(SERIAL_BAUD); |
|
#endif |
|
|
|
SERIAL_DEVICE.println("Starting Meter..."); |
|
|
|
// Activate internal light |
|
pinMode(LED_BUILTIN, OUTPUT); |
|
digitalWrite(LED_BUILTIN, LOW); |
|
|
|
// Activate internal button |
|
Btn.addButton(ZUNO_BUTTON_PIN); |
|
|
|
// Then make sure our state is up to date |
|
eepromRestore(); |
|
announce(); |
|
} |
|
|
|
/* Arduino main loop */ |
|
void loop() { |
|
unsigned long now = millis(); |
|
unsigned int lightLevel = sensorRead(); |
|
|
|
if (lightLevel != lastLightLevel) { |
|
SERIAL_DEVICE.println(lightLevel); |
|
} |
|
|
|
lastLightLevel = lightLevel; |
|
|
|
if (Btn.isLongClick(ZUNO_BUTTON_PIN)) { |
|
SERIAL_DEVICE.println("Reset"); |
|
eepromReset(); |
|
} |
|
|
|
if (lightLevel > CONFIG_LIGHT_THRESHOLD) { |
|
if (!high) { |
|
digitalWrite(LED_BUILTIN, HIGH); |
|
increment(); |
|
} |
|
|
|
high = true; |
|
} else { |
|
if (high) { |
|
digitalWrite(LED_BUILTIN, LOW); |
|
} |
|
high = false; |
|
} |
|
|
|
if ((now - lastScheduleMs) > CONFIG_UPDATE_INTERVAL) { |
|
lastScheduleMs = now; |
|
// Store before announce to prevent any cases of value going down |
|
eepromSave(); |
|
announce(); |
|
} |
|
} |