Merge branch 'development' into gateway-refactoring

Conflicts:
	libraries/MySensors/MyConfig.h
	libraries/MySensors/examples/EthernetGateway/EthernetGateway.ino
This commit is contained in:
Henrik Ekblad
2015-08-11 23:39:34 +02:00
118 changed files with 17297 additions and 951 deletions
+10 -4
View File
@@ -27,7 +27,7 @@
#define DEBUG
// Serial output baud rate (for debug prints and serial gateway)
#define BAUD_RATE 115200
#define MY_BAUD_RATE 115200
/**********************************
@@ -53,7 +53,14 @@
// but now can be used in any sensor node. Also the LEDs can now be
// disabled in the gateway.
// #define MY_LEDS_BLINKING_FEATURE
//#define MY_WITH_LEDS_BLINKING
// The following setting allows you to inverse the blinking feature WITH_LEDS_BLINKING
// When WITH_LEDS_BLINKING_INVERSE is enabled LEDSs are normally turned on and switches
// off when blinking
//#define WITH_LEDS_BLINKING_INVERSE
// default LEDs blinking period in milliseconds
#define MY_DEFAULT_LED_BLINK_PERIOD 300
@@ -113,8 +120,7 @@
#define MY_RANDOMSEED_PIN 7 // A7 - Pin used for random generation (do not connect anything to this)
// Key to use for HMAC calculation in MySigningAtsha204Soft (32 bytes)
#define MY_HMAC_KEY 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,\
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
#define MY_HMAC_KEY 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
/**********************************
+30 -2
View File
@@ -37,13 +37,41 @@
#include <SPI.h>
#endif
#if defined __AVR_ATmega328P__
#ifndef sleep_bod_disable
#define sleep_bod_disable() \
do { \
unsigned char tempreg; \
__asm__ __volatile__("in %[tempreg], %[mcucr]" "\n\t" \
"ori %[tempreg], %[bods_bodse]" "\n\t" \
"out %[mcucr], %[tempreg]" "\n\t" \
"andi %[tempreg], %[not_bodse]" "\n\t" \
"out %[mcucr], %[tempreg]" \
: [tempreg] "=&d" (tempreg) \
: [mcucr] "I" _SFR_IO_ADDR(MCUCR), \
[bods_bodse] "i" (_BV(BODS) | _BV(BODSE)), \
[not_bodse] "i" (~_BV(BODSE))); \
} while (0)
#endif
#endif
// Define these as macros to save valuable space
#define hw_init() Serial.begin(BAUD_RATE)
#define hw_digitalWrite(__pin, __value) (digitalWrite(__pin, __value))
#define hw_init() Serial.begin(MY_BAUD_RATE)
#define hw_watchdogReset() wdt_reset()
#define hw_reboot() wdt_enable(WDTO_15MS); while (1)
#define hw_millis() millis()
#define hw_readConfig(__pos) (eeprom_read_byte((uint8_t*)(__pos)))
#define hw_writeConfig(__pos, __value) (eeprom_update_byte((uint8_t*)(__pos), (__value)))
#ifndef eeprom_update_byte
#define hw_writeConfig(loc, val) if((uint8_t)(val) != eeprom_read_byte((uint8_t*)(loc))) { eeprom_write_byte((uint8_t*)(loc), (val)); }
#else
#define hw_writeConfig(__pos, __value) (eeprom_update_byte((uint8_t*)(__pos), (__value)))
#endif
//
#define hw_readConfigBlock(__buf, __pos, __length) (eeprom_read_block((__buf), (void*)(__pos), (__length)))
#define hw_writeConfigBlock(__pos, __buf, __length) (eeprom_write_block((void*)(__pos), (void*)__buf, (__length)))
+5 -6
View File
@@ -81,12 +81,11 @@ char* MyMessage::getStream(char *buffer) const {
char* MyMessage::getString(char *buffer) const {
uint8_t payloadType = miGetPayloadType();
if (payloadType == P_STRING) {
strncpy(buffer, data, miGetLength());
buffer[miGetLength()] = 0;
return buffer;
} else if (buffer != NULL) {
if (payloadType == P_BYTE) {
if (buffer != NULL) {
if (payloadType == P_STRING) {
strncpy(buffer, data, miGetLength());
buffer[miGetLength()] = 0;
} else if (payloadType == P_BYTE) {
itoa(bValue, buffer, 10);
} else if (payloadType == P_INT16) {
itoa(iValue, buffer, 10);
+66 -58
View File
@@ -41,12 +41,55 @@ typedef enum {
C_STREAM = 4 // For Firmware and other larger chunks of data that need to be divided into pieces.
} mysensor_command;
// Type of sensor (used when presenting sensors)
typedef enum {
S_DOOR, // Door sensor, V_TRIPPED, V_ARMED
S_MOTION, // Motion sensor, V_TRIPPED, V_ARMED
S_SMOKE, // Smoke sensor, V_TRIPPED, V_ARMED
S_LIGHT, // Binary light or relay, V_STATUS (or V_LIGHT), V_WATT
S_BINARY=3, // Binary light or relay, V_STATUS (or V_LIGHT), V_WATT (same as S_LIGHT)
S_DIMMER, // Dimmable light or fan device, V_STATUS (on/off), V_DIMMER (dimmer level 0-100), V_WATT
S_COVER, // Blinds or window cover, V_UP, V_DOWN, V_STOP, V_DIMMER (open/close to a percentage)
S_TEMP, // Temperature sensor, V_TEMP
S_HUM, // Humidity sensor, V_HUM
S_BARO, // Barometer sensor, V_PRESSURE, V_FORECAST
S_WIND, // Wind sensor, V_WIND, V_GUST
S_RAIN, // Rain sensor, V_RAIN, V_RAINRATE
S_UV, // Uv sensor, V_UV
S_WEIGHT, // Personal scale sensor, V_WEIGHT, V_IMPEDANCE
S_POWER, // Power meter, V_WATT, V_KWH
S_HEATER, // Header device, V_HVAC_SETPOINT_HEAT, V_HVAC_FLOW_STATE
S_DISTANCE, // Distance sensor, V_DISTANCE
S_LIGHT_LEVEL, // Light level sensor, V_LIGHT_LEVEL (uncalibrated in percentage), V_LEVEL (light level in lux)
S_ARDUINO_NODE, // Used (internally) for presenting a non-repeating Arduino node
S_ARDUINO_REPEATER_NODE, // Used (internally) for presenting a repeating Arduino node
S_LOCK, // Lock device, V_LOCK_STATUS
S_IR, // Ir device, V_IR_SEND, V_IR_RECEIVE
S_WATER, // Water meter, V_FLOW, V_VOLUME
S_AIR_QUALITY, // Air quality sensor, V_LEVEL
S_CUSTOM, // Custom sensor
S_DUST, // Dust sensor, V_LEVEL
S_SCENE_CONTROLLER, // Scene controller device, V_SCENE_ON, V_SCENE_OFF.
S_RGB_LIGHT, // RGB light. Send color component data using V_RGB. Also supports V_WATT
S_RGBW_LIGHT, // RGB light with an additional White component. Send data using V_RGBW. Also supports V_WATT
S_COLOR_SENSOR, // Color sensor, send color information using V_RGB
S_HVAC, // Thermostat/HVAC device. V_HVAC_SETPOINT_HEAT, V_HVAC_SETPOINT_COLD, V_HVAC_FLOW_STATE, V_HVAC_FLOW_MODE
S_MULTIMETER, // Multimeter device, V_VOLTAGE, V_CURRENT, V_IMPEDANCE
S_SPRINKLER, // Sprinkler, V_STATUS (turn on/off), V_TRIPPED (if fire detecting device)
S_WATER_LEAK, // Water leak sensor, V_TRIPPED, V_ARMED
S_SOUND, // Sound sensor, V_TRIPPED, V_ARMED, V_LEVEL (sound level in dB)
S_VIBRATION, // Vibration sensor, V_TRIPPED, V_ARMED, V_LEVEL (vibration in Hz)
S_MOISTURE, // Moisture sensor, V_TRIPPED, V_ARMED, V_LEVEL (water content or moisture in percentage?)
} mysensor_sensor;
// Type of sensor data (for set/req/ack messages)
typedef enum {
V_TEMP, // S_TEMP
V_HUM, // S_HUM
V_LIGHT, // S_LIGHT (Light level in uncalibrated percentage)
V_DIMMER, // S_DIMMER
V_STATUS, // S_LIGHT, S_DIMMER, S_SPRINKLER, S_HVAC, S_HEATER. Used for setting binary (on/off) status. 1=on, 0=off
V_LIGHT=2, // Same as V_STATUS
V_PERCENTAGE, // S_DIMMER. Used for sending a percentage value (0-100).
V_DIMMER=3, // S_DIMMER. Same as V_PERCENTAGE.
V_PRESSURE, // S_BARO
V_FORECAST, // S_BARO
V_RAIN, // S_RAIN
@@ -59,14 +102,15 @@ typedef enum {
V_DISTANCE, // S_DISTANCE
V_IMPEDANCE, // S_MULTIMETER, S_WEIGHT
V_ARMED, // S_DOOR, S_MOTION, S_SMOKE, S_SPRINKLER
V_TRIPPED, // S_DOOR, S_MOTION, S_SMOKE, S_SPRINKLER
V_WATT, // S_POWER
V_TRIPPED, // S_DOOR, S_MOTION, S_SMOKE, S_SPRINKLER (for sprinklers with fire detection)
V_WATT, // S_POWER, S_LIGHT, S_DIMMER
V_KWH, // S_POWER
V_SCENE_ON, // S_SCENE_CONTROLLER
V_SCENE_OFF, // S_SCENE_CONTROLLER
V_HEATER, // S_HEATER
V_HEATER_SW, // S_HEATER
V_LIGHT_LEVEL, // S_LIGHT_LEVEL
V_HEATER, // Deprecated. Use V_HVAC_FLOW_STATE instead.
V_HVAC_FLOW_STATE=21, // HVAC flow state ("Off", "HeatOn", "CoolOn", or "AutoChangeOver"). S_HEATER, S_HVAC
V_HVAC_SPEED, // HVAC/Heater fan speed ("Min", "Normal", "Max", "Auto")
V_LIGHT_LEVEL, // Used for sending light level in uncalibrated percentage. See also V_LEVEL (light level in lux). S_LIGHT_LEVEL
V_VAR1, V_VAR2, V_VAR3, V_VAR4, V_VAR5,
V_UP, // S_COVER
V_DOWN, // S_COVER
@@ -76,72 +120,36 @@ typedef enum {
V_FLOW, // S_WATER
V_VOLUME, // S_WATER
V_LOCK_STATUS, // S_LOCK
V_DUST_LEVEL, // S_DUST
V_LEVEL, // S_DUST, S_AIR_QUALITY, S_SOUND (dB), S_VIBRATION (hz), S_LIGHT_LEVEL (lux)
V_VOLTAGE, // S_MULTIMETER
V_CURRENT, // S_MULTIMETER
V_RGB, // S_RGB_LIGHT, S_COLOR_SENSOR.
// Used for sending color information for led lighting or color sensors.
// Sent as ascii hex. RRGGBB (RR=red, GG=green, BB=blue component)
V_RGBW, // S_RGB_LIGHT
// Used for sending color information to led lighting.
// Sent as ascii hex. RRGGBBWW (WW=while component)
// Used for sending color information for multi color LED lighting or color sensors.
// Sent as ASCII hex: RRGGBB (RR=red, GG=green, BB=blue component)
V_RGBW, // S_RGBW_LIGHT
// Used for sending color information to multi color LED lighting.
// Sent as ASCII hex: RRGGBBWW (WW=while component)
V_ID, // S_TEMP
// Used for reporting the sensor internal ids (E.g. DS1820b).
V_LIGHT_LEVEL_LUX, // S_LIGHT, Light level in lux
// Used for sending in sensors hardware ids (i.e. OneWire DS1820b).
V_UNIT_PREFIX, // Allows sensors to send in a string representing the
// unit prefix to be displayed in GUI, not parsed! E.g. cm, m, km, inch.
// Can be used for S_DISTANCE
V_SOUND_DB, // S_SOUND sound level in db
V_VIBRATION_HZ, // S_VIBRATION vibration i Hz
V_ENCODER_VALUE, // S_ROTARY_ENCODER. Rotary encoder value.
// unit prefix to be displayed in GUI, not parsed by controller! E.g. cm, m, km, inch.
// Can be used for S_DISTANCE or gas concentration (S_DUST, S_AIR_QUALITY)
V_HVAC_SETPOINT_COOL, // HVAC cool setpoint (Integer between 0-100). S_HVAC
V_HVAC_SETPOINT_HEAT, // HVAC/Heater setpoint (Integer between 0-100). S_HEATER, S_HVAC
V_HVAC_FLOW_MODE, // Flow mode for HVAC ("Auto", "ContinuousOn", "PeriodicOn"). S_HVAC
} mysensor_data;
// Type of internal messages (for internal messages)
typedef enum {
I_BATTERY_LEVEL, I_TIME, I_VERSION, I_ID_REQUEST, I_ID_RESPONSE,
I_INCLUSION_MODE, I_CONFIG, I_FIND_PARENT, I_FIND_PARENT_RESPONSE,
I_LOG_MESSAGE, I_CHILDREN, I_SKETCH_NAME, I_SKETCH_VERSION,
I_REBOOT, I_GATEWAY_READY, I_REQUEST_SIGNING, I_GET_NONCE, I_GET_NONCE_RESPONSE
I_REBOOT, I_GATEWAY_READY, I_REQUEST_SIGNING, I_GET_NONCE, I_GET_NONCE_RESPONSE,
I_HEARTBEAT
} mysensor_internal;
// Type of sensor (for presentation message)
typedef enum {
S_DOOR, // V_TRIPPED, V_ARMED
S_MOTION, // V_TRIPPED, V_ARMED
S_SMOKE, // V_TRIPPED, V_ARMED
S_LIGHT, // V_LIGHT, V_WATT
S_DIMMER, // V_DIMMER, V_WATT
S_COVER, // V_UP, V_DOWN, V_STOP
S_TEMP, // V_TEMP
S_HUM, // V_HUM
S_BARO, // V_PRESSURE, V_FORECAST
S_WIND, // V_WIND, V_GUST
S_RAIN, // V_RAIN, V_RAINRATE
S_UV, // V_UV
S_WEIGHT, // V_WEIGHT, V_IMPEDANCE
S_POWER, // V_WATT, V_KWH
S_HEATER, // V_HEATER, V_HEATER_SW
S_DISTANCE, // V_DISTANCE
S_LIGHT_LEVEL, // V_LIGHT
S_ARDUINO_NODE,
S_ARDUINO_REPEATER_NODE,
S_LOCK, // V_LOCK_STATUS
S_IR, // V_IR_SEND, V_IR_RECEIVE
S_WATER, // V_FLOW, V_VOLUME
S_AIR_QUALITY, // V_VAR1
S_CUSTOM,
S_DUST, // V_DUST_LEVEL
S_SCENE_CONTROLLER, // V_SCENE_ON, V_SCENE_OFF.
S_RGB_LIGHT, // Send data using V_RGB or V_RGBW
S_COLOR_SENSOR, // Send data using V_RGB
S_MULTIMETER, // V_VOLTAGE, V_CURRENT, V_IMPEDANCE
S_SPRINKLER, // V_TRIPPED, V_ARMED
S_WATER_LEAK, // V_TRIPPED, V_ARMED
S_SOUND, // V_TRIPPED, V_ARMED, V_SOUND_DB
S_VIBRATION, // V_TRIPPED, V_ARMED, V_VIBRATION_HZ
S_ROTARY_ENCODER, // V_ENCODER_VALUE
} mysensor_sensor;
// Type of data stream (for streamed message)
typedef enum {
+90 -63
View File
@@ -74,24 +74,21 @@ MySensor::MySensor(MyTransport &_radio, MyHw &_hw
#ifdef MY_OTA_FIRMWARE_FEATURE
// do a crc16 on the whole received firmware
bool MySensor::isValidFirmware() {
void* ptr = 0;
uint16_t crc = ~0;
int j;
// do a crc16 on the whole received firmware
bool MySensor::isValidFirmware() {
// init crc
uint16_t crc = ~0;
for (uint16_t i = 0; i < fc.blocks * FIRMWARE_BLOCK_SIZE; ++i) {
uint8_t a = flash.readByte((uint16_t) ptr + i + FIRMWARE_START_OFFSET);
crc ^= a;
for (j = 0; j < 8; ++j)
{
crc ^= flash.readByte(i + FIRMWARE_START_OFFSET);
for (int8_t j = 0; j < 8; ++j) {
if (crc & 1)
crc = (crc >> 1) ^ 0xA001;
else
crc = (crc >> 1);
}
}
return crc == fc.crc;
}
return crc == fc.crc;
}
#endif
@@ -110,33 +107,33 @@ void MySensor::handleLedsBlinking() {
// do the actual blinking
if(countRx && countRx != 255) {
// switch led on
digitalWrite(pinRx, LOW);
hw_digitalWrite(pinRx, LED_ON);
}
else if(!countRx) {
// switching off
digitalWrite(pinRx, HIGH);
hw_digitalWrite(pinRx, LED_OFF);
}
if(countRx != 255)
--countRx;
if(countTx && countTx != 255) {
// switch led on
digitalWrite(pinTx, LOW);
hw_digitalWrite(pinTx, LED_ON);
}
else if(!countTx) {
// switching off
digitalWrite(pinTx, HIGH);
hw_digitalWrite(pinTx, LED_OFF);
}
if(countTx != 255)
--countTx;
if(countErr && countErr != 255) {
// switch led on
digitalWrite(pinEr, LOW);
hw_digitalWrite(pinEr, LED_ON);
}
else if(!countErr) {
// switching off
digitalWrite(pinEr, HIGH);
hw_digitalWrite(pinEr, LED_OFF);
}
if(countErr != 255)
--countErr;
@@ -182,9 +179,9 @@ void MySensor::begin(void (*_msgCallback)(const MyMessage &), uint8_t _nodeId, b
pinMode(pinEr, OUTPUT);
// Set initial state of leds
digitalWrite(pinRx, HIGH);
digitalWrite(pinTx, HIGH);
digitalWrite(pinEr, HIGH);
hw_digitalWrite(pinRx, LED_OFF);
hw_digitalWrite(pinTx, LED_OFF);
hw_digitalWrite(pinEr, LED_OFF);
// initialize counters
countRx = 0;
@@ -302,6 +299,12 @@ void MySensor::setupNode() {
}
void MySensor::findParentNode() {
static boolean findingParentNode = false;
if (findingParentNode)
return;
findingParentNode = true;
failedTransmissions = 0;
// Set distance to max
@@ -316,6 +319,7 @@ void MySensor::findParentNode() {
// Wait for ping response.
wait(2000);
findingParentNode = false;
}
boolean MySensor::sendRoute(MyMessage &message) {
@@ -346,7 +350,7 @@ boolean MySensor::sendRoute(MyMessage &message) {
#ifdef MY_SIGNING_FEATURE
// If destination is known to require signed messages and we are the sender, sign this message unless it is an ACK or a handshake message
if (DO_SIGN(message.destination) && message.sender == nc.nodeId && !mGetAck(message) && mGetLength(msg) &&
if (DO_SIGN(message.destination) && message.sender == nc.nodeId && !mGetAck(message) && mGetLength(message) &&
(mGetCommand(message) != C_INTERNAL ||
(message.type != I_GET_NONCE && message.type != I_GET_NONCE_RESPONSE && message.type != I_REQUEST_SIGNING &&
message.type != I_ID_REQUEST && message.type != I_ID_RESPONSE &&
@@ -483,8 +487,13 @@ void MySensor::sendBatteryLevel(uint8_t value, bool enableAck) {
sendRoute(build(msg, nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_BATTERY_LEVEL, enableAck).set(value));
}
void MySensor::present(uint8_t childSensorId, uint8_t sensorType, bool enableAck) {
sendRoute(build(msg, nc.nodeId, GATEWAY_ADDRESS, childSensorId, C_PRESENTATION, sensorType, enableAck).set(LIBRARY_VERSION));
void MySensor::sendHeartbeat(void) {
sendRoute(build(msg, nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_HEARTBEAT, false).set(heartbeat++));
}
void MySensor::present(uint8_t childSensorId, uint8_t sensorType, const char *description, bool enableAck) {
sendRoute(build(msg, nc.nodeId, GATEWAY_ADDRESS, childSensorId, C_PRESENTATION, sensorType, enableAck).set(childSensorId==NODE_SENSOR_ID?LIBRARY_VERSION:description));
}
void MySensor::sendSketchInfo(const char *name, const char *version, bool enableAck) {
@@ -518,7 +527,7 @@ boolean MySensor::process() {
#ifdef MY_OTA_FIRMWARE_FEATURE
unsigned long enter = hw_millis();
if (fwUpdateOngoing && (enter - fwLastRequestTime > MY_OTA_RETRY_DELAY)) {
if (fwRetry == 0) {
if (!fwRetry) {
debug(PSTR("fw upd fail\n"));
// Give up. We have requested MY_OTA_RETRY times without any packet in return.
fwUpdateOngoing = false;
@@ -691,46 +700,61 @@ boolean MySensor::process() {
// compare with current node configuration, if they differ, start fw fetch process
if (memcmp(&fc,firmwareConfigResponse,sizeof(NodeFirmwareConfig))) {
debug(PSTR("fw update\n"));
fwUpdateOngoing = true;
fwBlock = fc.blocks;
// copy new FW config
memcpy(&fc,firmwareConfigResponse,sizeof(NodeFirmwareConfig));
// Init flash
if (!flash.initialize()) {
debug(PSTR("flash init fail\n"));
}
}
} else if (type == ST_FIRMWARE_RESPONSE) {
// Save block to eeprom
debug(PSTR("fw block %d\n"), fwBlock);
ReplyFWBlock *firmwareResponse = (ReplyFWBlock *)msg.data;
// write to flash
flash.writeBytes(((fwBlock-1)*FIRMWARE_BLOCK_SIZE) + FIRMWARE_START_OFFSET, firmwareResponse->data, FIRMWARE_BLOCK_SIZE);
fwBlock--;
if (fwBlock == 0) {
// We're finished! Do a checksum and reboot.
if (isValidFirmware()) {
debug(PSTR("fw checksum ok\n"));
// All seems ok, write size and signature to flash (DualOptiboot will pick this up and flash it)
uint16_t fwsize = FIRMWARE_BLOCK_SIZE*fc.blocks;
flash.writeBytes(0, "FLXIMG:", 7);
flash.writeByte(7, fwsize >> 8);
flash.writeByte(8, fwsize);
flash.writeByte(9, ':');
// Write the new firmware config to eeprom
hw_writeConfigBlock((void*)&fc, (void*)EEPROM_FIRMWARE_TYPE_ADDRESS, sizeof(NodeFirmwareConfig));
hw_reboot();
fwUpdateOngoing = false;
} else {
debug(PSTR("fw checksum fail\n"));
// erase lower 32K -> max flash size for ATMEGA328
flash.blockErase32K(0);
// wait until flash erased
while ( flash.busy() );
fwBlock = fc.blocks;
fwUpdateOngoing = true;
// reset flags
fwRetry = MY_OTA_RETRY+1;
fwLastRequestTime = 0;
}
return false;
} else debug(PSTR("fw update skipped\n"));
} else if (type == ST_FIRMWARE_RESPONSE) {
if (fwUpdateOngoing) {
// Save block to flash
debug(PSTR("fw block %d\n"), fwBlock);
// extract FW block
ReplyFWBlock *firmwareResponse = (ReplyFWBlock *)msg.data;
// write to flash
flash.writeBytes( ((fwBlock - 1) * FIRMWARE_BLOCK_SIZE) + FIRMWARE_START_OFFSET, firmwareResponse->data, FIRMWARE_BLOCK_SIZE);
// wait until flash written
while ( flash.busy() );
fwBlock--;
if (!fwBlock) {
// We're finished! Do a checksum and reboot.
fwUpdateOngoing = false;
if (isValidFirmware()) {
debug(PSTR("fw checksum ok\n"));
// All seems ok, write size and signature to flash (DualOptiboot will pick this up and flash it)
uint16_t fwsize = FIRMWARE_BLOCK_SIZE * fc.blocks;
uint8_t OTAbuffer[10] = {'F','L','X','I','M','G',':',(fwsize >> 8),fwsize,':'};
flash.writeBytes(0, OTAbuffer, 10);
// Write the new firmware config to eeprom
hw_writeConfigBlock((void*)&fc, (void*)EEPROM_FIRMWARE_TYPE_ADDRESS, sizeof(NodeFirmwareConfig));
hw_reboot();
} else {
debug(PSTR("fw checksum fail\n"));
}
}
// reset flags
fwRetry = MY_OTA_RETRY+1;
fwLastRequestTime = 0;
} else {
debug(PSTR("No fw update ongoing\n"));
}
return false;
}
// Make sure packet request occurs next time process() is called by timing out requestTime.
fwRetry = MY_OTA_RETRY+1;
fwLastRequestTime = 0;
return false;
}
#endif
// Call incoming message callback if available
@@ -743,14 +767,17 @@ boolean MySensor::process() {
// If this node have an id, relay the message
if (command == C_INTERNAL && type == I_FIND_PARENT) {
if (nc.distance == DISTANCE_INVALID) {
findParentNode();
} else if (sender != nc.parentNodeId) {
// Relaying nodes should always answer ping messages
// Wait a random delay of 0-2 seconds to minimize collision
// between ping ack messages from other relaying nodes
wait(hw_millis() & 0x3ff);
sendWrite(sender, build(msg, nc.nodeId, sender, NODE_SENSOR_ID, C_INTERNAL, I_FIND_PARENT_RESPONSE, false).set(nc.distance));
if (sender != nc.parentNodeId) {
if (nc.distance == DISTANCE_INVALID)
findParentNode();
if (nc.distance != DISTANCE_INVALID) {
// Relaying nodes should always answer ping messages
// Wait a random delay of 0-2 seconds to minimize collision
// between ping ack messages from other relaying nodes
wait(hw_millis() & 0x3ff);
sendWrite(sender, build(msg, nc.nodeId, sender, NODE_SENSOR_ID, C_INTERNAL, I_FIND_PARENT_RESPONSE, false).set(nc.distance));
}
}
} else if (to == nc.nodeId) {
// We should try to relay this message to another node
+18 -1
View File
@@ -51,6 +51,14 @@ typedef MyHwATMega328 MyHwDriver;
#define debug(x,...)
#endif
#ifdef WITH_LEDS_BLINKING_INVERSE
#define LED_ON 0x1
#define LED_OFF 0x0
#else
#define LED_ON 0x0
#define LED_OFF 0x1
#endif
// EEPROM start address for mysensors library data
#define EEPROM_START 0
@@ -176,9 +184,11 @@ class MySensor
*
* @param sensorId Select a unique sensor id for this sensor. Choose a number between 0-254.
* @param sensorType The sensor type. See sensor typedef in MyMessage.h.
* @param description A textual description of the sensor.
* @param ack Set this to true if you want destination node to send ack back to this node. Default is not to request any ack.
* @param description A textual description of the sensor.
*/
void present(uint8_t sensorId, uint8_t sensorType, bool ack=false);
void present(uint8_t sensorId, uint8_t sensorType, const char *description="", bool ack=false);
/**
* Sends sketch meta information to the gateway. Not mandatory but a nice thing to do.
@@ -207,6 +217,12 @@ class MySensor
*/
void sendBatteryLevel(uint8_t level, bool ack=false);
/**
* Send a heartbeat message (I'm alive!) to the gateway/controller.
* The payload will be an incremental 16 bit integer value starting at 1 when sensor is powered on.
*/
void sendHeartbeat(void);
/**
* Requests a value from gateway or some other sensor in the radio network.
* Make sure to add callback-method in begin-method to handle request responses.
@@ -362,6 +378,7 @@ class MySensor
char convBuf[MAX_PAYLOAD*2+1];
#endif
uint8_t failedTransmissions;
uint16_t heartbeat;
void (*timeCallback)(unsigned long); // Callback for requested time messages
void (*msgCallback)(const MyMessage &); // Callback for incoming messages from other nodes and gateway.
+1 -1
View File
@@ -59,7 +59,7 @@ public:
bool signMsg(MyMessage &msg);
bool verifyMsg(MyMessage &msg);
private:
atsha204Class atsha204;
ATSHA204Class atsha204;
unsigned long timestamp;
bool verification_ongoing;
uint8_t current_nonce[NONCE_NUMIN_SIZE_PASSTHROUGH+SHA204_SERIAL_SZ+1];
@@ -61,6 +61,9 @@ static void DEBUG_SIGNING_PRINTBUF(const __FlashStringHelper* str, uint8_t* buf,
#define DEBUG_SIGNING_PRINTBUF(str, buf, sz)
#endif
// Initialize hmacKey from MyConfig.h (codebender didn't like static initialization in constructor)
uint8_t MySigningAtsha204Soft::hmacKey[32] = { MY_HMAC_KEY };
MySigningAtsha204Soft::MySigningAtsha204Soft(bool requestSignatures,
#ifdef MY_SECURE_NODE_WHITELISTING
uint8_t nof_whitelist_entries, const whitelist_entry_t* the_whitelist,
@@ -70,7 +73,6 @@ MySigningAtsha204Soft::MySigningAtsha204Soft(bool requestSignatures,
:
MySigning(requestSignatures),
rndPin(randomseedPin),
hmacKey({MY_HMAC_KEY}),
#ifdef MY_SECURE_NODE_WHITELISTING
whitlist_sz(nof_whitelist_entries),
whitelist(the_whitelist),
+1 -1
View File
@@ -73,7 +73,7 @@ private:
bool verification_ongoing;
uint8_t current_nonce[NONCE_NUMIN_SIZE_PASSTHROUGH];
uint8_t temp_message[32];
uint8_t hmacKey[32];
static uint8_t hmacKey[32];
uint8_t rndPin;
uint8_t hmac[32];
void calculateSignature(MyMessage &msg);
+1 -1
View File
@@ -6,6 +6,6 @@
#ifndef Version_h
#define Version_h
#define LIBRARY_VERSION "1.5 b1"
#define LIBRARY_VERSION "1.5"
#endif
@@ -77,7 +77,7 @@ float SmokeCurve[3] ={2.3,0.53,-0.44}; //two points are taken from
MySensor gw;
MyMessage msg(CHILD_ID_MQ, V_VAR1);
MyMessage msg(CHILD_ID_MQ, V_LEVEL);
void setup()
@@ -30,6 +30,8 @@
#include <SPI.h>
#include <MySensor.h>
#define DEBUG 1 // Debug enables Serial.print 1 = Yes 0 = No
int BATTERY_SENSE_PIN = A0; // select the input pin for the battery sense point
MySensor gw;
@@ -50,7 +52,9 @@ void loop()
{
// get the battery Voltage
int sensorValue = analogRead(BATTERY_SENSE_PIN);
#if DEBUG > 0
Serial.println(sensorValue);
#endif
// 1M, 470K divider across battery and using internal ADC ref of 1.1V
// Sense point is bypassed with 0.1 uF cap to reduce noise at that point
@@ -59,6 +63,7 @@ void loop()
float batteryV = sensorValue * 0.003363075;
int batteryPcnt = sensorValue / 10;
#if DEBUG > 0
Serial.print("Battery Voltage: ");
Serial.print(batteryV);
Serial.println(" V");
@@ -66,6 +71,7 @@ void loop()
Serial.print("Battery percent: ");
Serial.print(batteryPcnt);
Serial.println(" %");
#endif
if (oldBatteryPcnt != batteryPcnt) {
// Power up radio after sleep
@@ -29,11 +29,13 @@
#include <DallasTemperature.h>
#include <OneWire.h>
#define COMPARE_TEMP 1 // Send temperature only if changed? 1 = Yes 0 = No
#define ONE_WIRE_BUS 3 // Pin where dallase sensor is connected
#define MAX_ATTACHED_DS18B20 16
unsigned long SLEEP_TIME = 30000; // Sleep time between reads (in milliseconds)
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
OneWire oneWire(ONE_WIRE_BUS); // Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs)
DallasTemperature sensors(&oneWire); // Pass the oneWire reference to Dallas Temperature.
MySensor gw;
float lastTemperature[MAX_ATTACHED_DS18B20];
int numSensors=0;
@@ -44,14 +46,16 @@ MyMessage msg(0,V_TEMP);
void setup()
{
// Startup OneWire
// Startup up the OneWire library
sensors.begin();
// requestTemperatures() will not block current thread
sensors.setWaitForConversion(false);
// Startup and initialize MySensors library. Set callback for incoming messages.
gw.begin();
gw.begin();
// Send the sketch version information to the gateway and Controller
gw.sendSketchInfo("Temperature Sensor", "1.0");
gw.sendSketchInfo("Temperature Sensor", "1.1");
// Fetch the number of attached temperature sensors
numSensors = sensors.getDeviceCount();
@@ -69,7 +73,12 @@ void loop()
gw.process();
// Fetch temperatures from Dallas sensors
sensors.requestTemperatures();
sensors.requestTemperatures();
// query conversion time and sleep until conversion completed
int16_t conversionTime = sensors.millisToWaitForConversion(sensors.getResolution());
// sleep() call can be replaced by wait() call if node need to process incoming messages (or if node is repeater)
gw.sleep(conversionTime);
// Read temperatures and send them to controller
for (int i=0; i<numSensors && i<MAX_ATTACHED_DS18B20; i++) {
@@ -78,15 +87,17 @@ void loop()
float temperature = static_cast<float>(static_cast<int>((gw.getConfig().isMetric?sensors.getTempCByIndex(i):sensors.getTempFByIndex(i)) * 10.)) / 10.;
// Only send data if temperature has changed and no error
if (lastTemperature[i] != temperature && temperature != -127.00) {
#if COMPARE_TEMP == 1
if (lastTemperature[i] != temperature && temperature != -127.00 && temperature != 85.00) {
#else
if (temperature != -127.00 && temperature != 85.00) {
#endif
// Send in the new temperature
gw.send(msg.setSensor(i).set(temperature,1));
// Save new temperatures for next compare
lastTemperature[i]=temperature;
}
}
gw.sleep(SLEEP_TIME);
}
@@ -0,0 +1,238 @@
/**
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
*******************************
*
* REVISION HISTORY
* Version 1.0 - Developed by Bruce Lacey and GizMoCuz (Domoticz)
* Version 1.1 - Modified by hek to incorporate a rotary encode to adjust
* light level locally at node
*
* DESCRIPTION
* This sketch provides an example how to implement a dimmable led light node with a rotary
* encoder connected for adjusting light level.
* The encoder has a click button which turns on/off the light (and remembers last dim-level)
* The sketch fades the light (non-blocking) to the desired level.
*
* Default MOSFET pin is 3
*
* Arduino Encoder module
* ---------------------------
* 5V 5V (+)
* GND GND (-)
* 4 CLK (or putput 1)
* 5 DT (or output 1)
* 6 SW (Switch/Click)
*/
#include <SPI.h>
#include <Encoder.h>
#include <MySensor.h>
#include <Bounce2.h>
#define LED_PIN 3 // Arduino pin attached to MOSFET Gate pin
#define KNOB_ENC_PIN_1 4 // Rotary encoder input pin 1
#define KNOB_ENC_PIN_2 5 // Rotary encoder input pin 2
#define KNOB_BUTTON_PIN 6 // Rotary encoder button pin
#define FADE_DELAY 10 // Delay in ms for each percentage fade up/down (10ms = 1s full-range dim)
#define SEND_THROTTLE_DELAY 400 // Number of milliseconds before sending after user stops turning knob
#define SN "DimmableLED /w button"
#define SV "1.2"
#define CHILD_ID_LIGHT 1
#define EEPROM_DIM_LEVEL_LAST 1
#define EEPROM_DIM_LEVEL_SAVE 2
#define LIGHT_OFF 0
#define LIGHT_ON 1
int dimValue;
int fadeTo;
int fadeDelta;
byte oldButtonVal;
bool changedByKnob=false;
bool sendDimValue=false;
unsigned long lastFadeStep;
unsigned long sendDimTimeout;
char convBuffer[10];
MySensor gw;
MyMessage dimmerMsg(CHILD_ID_LIGHT, V_DIMMER);
Encoder knob(KNOB_ENC_PIN_1, KNOB_ENC_PIN_2);
Bounce debouncer = Bounce();
void setup()
{
// Set knob button pin as input (with debounce)
pinMode(KNOB_BUTTON_PIN, INPUT);
digitalWrite(KNOB_BUTTON_PIN, HIGH);
debouncer.attach(KNOB_BUTTON_PIN);
debouncer.interval(5);
oldButtonVal = debouncer.read();
// Set analog led pin to off
analogWrite( LED_PIN, 0);
// Init mysensors library
gw.begin(incomingMessage, AUTO, false);
// Send the Sketch Version Information to the Gateway
gw.present(CHILD_ID_LIGHT, S_DIMMER);
gw.sendSketchInfo(SN, SV);
// Retreive our last dim levels from the eprom
fadeTo = dimValue = 0;
byte oldLevel = loadLevelState(EEPROM_DIM_LEVEL_LAST);
Serial.print("Sending in last known light level to controller: ");
Serial.println(oldLevel);
gw.send(dimmerMsg.set(oldLevel), true);
Serial.println("Ready to receive messages...");
}
void loop()
{
// Process incoming messages (like config and light state from controller)
gw.process();
// Check if someone turned the rotary encode
checkRotaryEncoder();
// Check if someone has pressed the knob button
checkButtonClick();
// Fade light to new dim value
fadeStep();
}
void incomingMessage(const MyMessage &message)
{
if (message.type == V_LIGHT) {
// Incoming on/off command sent from controller ("1" or "0")
int lightState = message.getString()[0] == '1';
int newLevel = 0;
if (lightState==LIGHT_ON) {
// Pick up last saved dimmer level from the eeprom
newLevel = loadLevelState(EEPROM_DIM_LEVEL_SAVE);
}
// Send dimmer level back to controller with ack enabled
gw.send(dimmerMsg.set(0), true);
// We do not change any levels here until ack comes back from gateway
return;
} else if (message.type == V_DIMMER) {
// Incoming dim-level command sent from controller (or ack message)
fadeTo = atoi(message.getString(convBuffer));
// Save received dim value to eeprom (unless turned off). Will be
// retreived when a on command comes in
if (fadeTo != 0)
saveLevelState(EEPROM_DIM_LEVEL_SAVE, fadeTo);
}
saveLevelState(EEPROM_DIM_LEVEL_LAST, fadeTo);
Serial.print("New light level received: ");
Serial.println(fadeTo);
if (!changedByKnob)
knob.write(fadeTo);
// Cancel send if user turns knob while message comes in
changedByKnob = false;
sendDimValue = false;
// Stard fading to new light level
startFade();
}
void checkRotaryEncoder() {
long encoderValue = knob.read();
if (encoderValue > 100) {
encoderValue = 100;
knob.write(100);
} else if (encoderValue < 0) {
encoderValue = 0;
knob.write(0);
}
if (encoderValue != fadeTo) {
fadeTo = encoderValue;
changedByKnob = true;
startFade();
}
}
void checkButtonClick() {
debouncer.update();
byte buttonVal = debouncer.read();
byte newLevel = 0;
if (buttonVal != oldButtonVal && buttonVal == LOW) {
if (dimValue==0) {
// Turn on light. Set the level to last saved dim value
int saved = loadLevelState(EEPROM_DIM_LEVEL_SAVE);
newLevel = saved > 0 ? saved : 100;
}
gw.send(dimmerMsg.set(newLevel),true);
}
oldButtonVal = buttonVal;
}
void startFade() {
fadeDelta = ( fadeTo - dimValue ) < 0 ? -1 : 1;
lastFadeStep = millis();
}
// This method provides a graceful none-blocking fade up/down effect
void fadeStep() {
unsigned long currentTime = millis();
if ( dimValue != fadeTo && currentTime > lastFadeStep + FADE_DELAY) {
dimValue += fadeDelta;
analogWrite( LED_PIN, (int)(dimValue / 100. * 255) );
lastFadeStep = currentTime;
Serial.print("Fading level: ");
Serial.println(dimValue);
if (fadeTo == dimValue && changedByKnob) {
sendDimValue = true;
sendDimTimeout = currentTime;
}
}
// Wait a few millisecs before sending in new value (if user still turns the knob)
if (sendDimValue && currentTime > sendDimTimeout + SEND_THROTTLE_DELAY) {
// We're done fading.. send in new dim-value to controller.
// Send in new dim value with ack (will be picked up in incomingMessage)
gw.send(dimmerMsg.set(dimValue), true); // Send new dimmer value and request ack back
sendDimValue = false;
}
}
// Make sure only to store/fetch values in the range 0-100 from eeprom
int loadLevelState(byte pos) {
return min(max(gw.loadState(pos),0),100);
}
void saveLevelState(byte pos, byte data) {
gw.saveState(pos,min(max(data,0),100));
}
@@ -57,7 +57,7 @@ float calcVoltage = 0;
float dustDensity = 0;
MySensor gw;
MyMessage dustMsg(CHILD_ID_DUST, V_DUST_LEVEL);
MyMessage dustMsg(CHILD_ID_DUST, V_LEVEL);
void setup()
{
@@ -90,7 +90,7 @@ IPAddress myIp (192, 168, 178, 66); // Configure your static ip-address here
#endif /* IP_ADDRESS_DHCP */
// NRFRF24L01 radio driver (set low transmit power by default)
MyTransportNRF24 transport(RF24_CE_PIN, RF24_CS_PIN, RF24_PA_LEVEL_GW);
MyTransportNRF24 transport(RADIO_CE_PIN, RADIO_SPI_SS_PIN, RF24_PA_LEVEL_GW);
//MyTransportRFM69 transport;
// Message signing driver (signer needed if MY_SIGNING_FEATURE is turned on in MyConfig.h)
@@ -101,7 +101,7 @@ MyTransportNRF24 transport(RF24_CE_PIN, RF24_CS_PIN, RF24_PA_LEVEL_GW);
// Hardware profile
MyHwATMega328 hw;
// Controller protocol
// Use default controller protocol
MyProtocolDefault protocol;
// The MAC address can be anything you want but should be unique on your network.
@@ -0,0 +1,864 @@
/*
MySprinkler for MySensors
Arduino Multi-Zone Sprinkler Control
May 31, 2015
*** Version 2.0
*** Upgraded to http://MySensors.org version 1.4.1
*** Expanded for up to 16 Valves
*** Setup for active low relay board or comment out #define ACTIVE_LOW to switch to active high
*** Switch to bitshift method vs byte arrays
*** Changed RUN_ALL_ZONES Vera device to 0 (was highest valve)
*** Added optional LCD display featuring remaining time, date last ran & current time
*** Features 'raindrop' and 'clock' icons which indicate sensor is updating valve data and clock respectively
*** Added single pushbutton menu to manually select which program to run (All Zones or a Single Zone)
*** Added option of naming your Zones programmatically or with Vera (V_VAR3 used to store names)
Utilizing your Vera home automation controller and the MySensors.org gateway you can
control up to a sixteen zone irrigation system with only three digital pins. This sketch
will create NUMBER_OF_VALVES + 1 devices on your Vera controller
This sketch features the following:
* Allows you to cycle through All zones (RUN_ALL_ZONES) or individual zone (RUN_SINGLE_ZONE) control.
* Use the 0th controller to activate RUN_ALL_ZONES (each zone in numeric sequence 1 to n)
using Variable1 as the "ON" time in minutes in each of the vera devices created.
* Use the individual zone controller to activate a single zone. This feature uses
Variable2 as the "ON" time for each individual device/zone.
* Connect according to pinout below and uses Shift Registers as to allow the MySensors
standard radio configuration and still leave available digital pins
* Turning on any zone will stop the current process and begin that particular process.
* Turning off any zone will stop the current process and turn off all zones.
* To push your new time intervals for your zones, simply change the variable on your Vera and
your arduino will call to Vera once a minute and update accordingly. Variables will also be
requested when the device is first powered on.
* Pushbutton activation to RUN_ALL_ZONES, RUN_SINGLE_ZONE or halt the current program
* LED status indicator
PARTS LIST:
Available from the MySensors store - http://www.mysensors.org/store/
* Relays (8 channel)
* Female Pin Header Connector Strip
* Prototype Universal Printed Circuit Boards (PCB)
* NRF24L01 Radio
* Arduino (I used a Pro Mini)
* FTDI USB to TTL Serial Adapter
* Capacitors (10uf and .1uf)
* 3.3v voltage regulator
* Resistors (270 & 10K)
* Female Dupont Cables
* 1602 LCD (with I2C Interface)
* LED
* Push button
* Shift Register (SN74HC595)
* 2 Pole 5mm Pitch PCB Mount Screw Terminal Block
* 3 Pole 5mm Pitch PCB Mount Screw Terminal Block
* 22-24 gauge wire or similar (I used Cat5/Cat6 cable)
* 18 gauge wire (for relay)
* Irrigation Power Supply (24-Volt/750 mA Transformer)
INSTRUCTIONS:
* A step-by-step setup video is available here: http://youtu.be/l4GPRTsuHkI
* After assembling your arduino, radio, decoupling capacitors, shift register(s), status LED, pushbutton LCD (I2C connected to
A4 and A5) and relays, and load the sketch.
* Following the instructions at https://MySensors.org include the device to your MySensors Gateway.
* Verify that each new device has a Variable1, Variable2 and Variable3. Populate data accordingly with whole minutes for
the RUN_ALL_ZONES routine (Variable1) and the RUN_SINGLE_ZONE routines (Variable 2). The values entered for times may be zero and
you may use the defaulet zone names by leaving Variable3 blank.
* Once you have entered values for each zone and each variable, save the settings by pressing the red save button on your Vera.
* Restart your arduino; verify the settings are loaded into your arduino with the serial monitor; the array will be printed
on the serial monitor.
* Your arduino should slow-flash, indicating that it is in ready mode.
* There are multiple debug serial prints that can be monitored to assure that it is operating properly.
* ***THIS SHOULD NO LONGER BE NEEDED*** The standard MySensors library now works. https://bitbucket.org/fmalpartida/new-liquidcrystal/downloads for the I2C library, or use yours
Contributed by Jim (BulldogLowell@gmail.com) with much contribution from Pete (pete.will@mysensors.org) and is released to the public domain
*/
//
#include <Wire.h>
#include <Time.h>
#include <MySensor.h>
#include <SPI.h>
#include <LiquidCrystal.h>
#include <LiquidCrystal_I2C.h>
//
#define NUMBER_OF_VALVES 8 // Change this to set your valve count up to 16.
#define VALVE_RESET_TIME 7500UL // Change this (in milliseconds) for the time you need your valves to hydraulically reset and change state
#define RADIO_ID AUTO // Change this to fix your Radio ID or use Auto
#define SKETCH_NAME "MySprinkler"
#define SKETCH_VERSION "2.0"
//
#define CHILD_ID_SPRINKLER 0
//
#define ACTIVE_LOW // comment out this line if your relays are active high
//
#define DEBUG_ON // comment out to supress serial monitor output
//
#ifdef ACTIVE_LOW
#define BITSHIFT_VALVE_NUMBER ~(1U << (valveNumber-1))
#define ALL_VALVES_OFF 0xFFFF
#else
#define BITSHIFT_VALVE_NUMBER (1U << (valveNumber-1))
#define ALL_VALVES_OFF 0U
#endif
//
#ifdef DEBUG_ON
#define DEBUG_PRINT(x) Serial.print(x)
#define DEBUG_PRINTLN(x) Serial.println(x)
#define SERIAL_START(x) Serial.begin(x)
#else
#define DEBUG_PRINT(x)
#define DEBUG_PRINTLN(x)
#define SERIAL_START(x)
#endif
//
typedef enum {
STAND_BY_ALL_OFF, RUN_SINGLE_ZONE, RUN_ALL_ZONES, CYCLE_COMPLETE, ZONE_SELECT_MENU
}
SprinklerStates;
//
SprinklerStates state = STAND_BY_ALL_OFF;
SprinklerStates lastState;
byte menuState = 0;
unsigned long menuTimer;
byte countDownTime = 10;
//
int allZoneTime [NUMBER_OF_VALVES + 1];
int valveSoloTime [NUMBER_OF_VALVES + 1];
int valveNumber;
int lastValve;
unsigned long startMillis;
const int ledPin = 5;
const int waterButtonPin = 3;
boolean buttonPushed = false;
boolean showTime = true;
boolean clockUpdating = false;
boolean recentUpdate = true;
const char *dayOfWeek[] = {
"Null", "Sunday ", "Monday ", "Tuesday ", "Wednesday ", "Thursday ", "Friday ", "Saturday "
};
// Name your Zones here or use Vera to edit them by adding a name in Variable3...
String valveNickName[17] = {
"All Zones", "Zone 1", "Zone 2", "Zone 3", "Zone 4", "Zone 5", "Zone 6", "Zone 7", "Zone 8", "Zone 9", "Zone 10", "Zone 11", "Zone 12", "Zone 13", "Zone 14", "Zone 15", "Zone 16"
};
//
time_t lastTimeRun = 0;
//Setup Shift Register...
const int latchPin = 8;
const int clockPin = 4;
const int dataPin = 7;
//
byte clock[8] = {0x0, 0xe, 0x15, 0x17, 0x11, 0xe, 0x0}; // fetching time indicator
byte raindrop[8] = {0x4, 0x4, 0xA, 0xA, 0x11, 0xE, 0x0,}; // fetching Valve Data indicator
// Set the pins on the I2C chip used for LCD connections:
// addr, en,rw,rs,d4,d5,d6,d7,bl,blpol
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE); // Set the LCD I2C address to 0x27
MySensor gw;
//
MyMessage msg1valve(CHILD_ID_SPRINKLER, V_LIGHT);
MyMessage var1valve(CHILD_ID_SPRINKLER, V_VAR1);
MyMessage var2valve(CHILD_ID_SPRINKLER, V_VAR2);
//
void setup()
{
SERIAL_START(115200);
DEBUG_PRINTLN(F("Initialising..."));
pinMode(latchPin, OUTPUT);
pinMode(clockPin, OUTPUT);
pinMode(dataPin, OUTPUT);
pinMode(ledPin, OUTPUT);
pinMode(waterButtonPin, INPUT_PULLUP);
//pinMode(waterButtonPin, INPUT);
attachInterrupt(1, PushButton, RISING); //May need to change for your Arduino model
digitalWrite (ledPin, HIGH);
DEBUG_PRINTLN(F("Turning All Valves Off..."));
updateRelays(ALL_VALVES_OFF);
//delay(5000);
lcd.begin(16, 2); //(16 characters and 2 line display)
lcd.clear();
lcd.backlight();
lcd.createChar(0, clock);
lcd.createChar(1, raindrop);
//
//check for saved date in EEPROM
DEBUG_PRINTLN(F("Checking EEPROM for stored date:"));
delay(500);
if (gw.loadState(0) == 0xFF); // EEPROM flag
{
DEBUG_PRINTLN(F("Retreiving last run time from EEPROM..."));
for (int i = 0; i < 4 ; i++)
{
lastTimeRun = lastTimeRun << 8;
lastTimeRun = lastTimeRun | gw.loadState(i + 1); // assemble 4 bytes into an ussigned long epoch timestamp
}
}
gw.begin(getVariables, RADIO_ID, false); // Change 'false' to 'true' to create a Radio repeating node
gw.sendSketchInfo(SKETCH_NAME, SKETCH_VERSION);
for (byte i = 0; i <= NUMBER_OF_VALVES; i++)
{
gw.present(i, S_LIGHT);
}
DEBUG_PRINTLN(F("Sensor Presentation Complete"));
//
digitalWrite (ledPin, LOW);
DEBUG_PRINTLN(F("Ready..."));
//
lcd.setCursor(0, 0);
lcd.print(F(" Syncing Time "));
lcd.setCursor(15, 0);
lcd.write(byte(0));
lcd.setCursor(0, 1);
int clockCounter = 0;
while (timeStatus() == timeNotSet && clockCounter < 21)
{
gw.process();
gw.requestTime(receiveTime);
DEBUG_PRINTLN(F("Requesting time from Gateway:"));
delay(1000);
lcd.print(".");
clockCounter++;
if (clockCounter > 16)
{
DEBUG_PRINTLN(F("Failed initial clock synchronization!"));
lcd.clear();
lcd.print(F(" Failed Clock "));
lcd.setCursor(0, 1);
lcd.print(F(" Syncronization "));
delay(2000);
break;
}
}
//
lcd.clear();
//Update valve data when first powered on
for (byte i = 0; i <= NUMBER_OF_VALVES; i++)
{
lcd.print(F(" Updating "));
lcd.setCursor(0, 1);
lcd.print(F(" Valve Data: "));
lcd.print(i);
boolean flashIcon = false;
DEBUG_PRINT(F("Calling for Valve "));
DEBUG_PRINT(i);
DEBUG_PRINTLN(F(" Data..."));
while (gw.process() == false)
{
lcd.setCursor(15, 0);
flashIcon = !flashIcon;
flashIcon ? lcd.write(byte(1)) : lcd.print(F(" "));
gw.request(i, V_VAR1);
delay(100);
}
while (gw.process() == false)
{
lcd.setCursor(15, 0);
flashIcon = !flashIcon;
flashIcon ? lcd.write(byte(1)) : lcd.print(F(" "));
gw.request(i, V_VAR2);
delay(100);
}
while (gw.process() == false)
{
lcd.setCursor(15, 0);
flashIcon = !flashIcon;
flashIcon ? lcd.write(byte(1)) : lcd.print(F(" "));
gw.request(i, V_VAR3);
delay(100);
}
}
lcd.clear();
}
//
void loop()
{
gw.process();
updateClock();
updateDisplay();
goGetValveTimes();
//
if (buttonPushed)
{
menuTimer = millis();
DEBUG_PRINTLN(F("Button Pressed"));
if (state == STAND_BY_ALL_OFF)
{
state = ZONE_SELECT_MENU;
menuState = 0;
}
else if (state == ZONE_SELECT_MENU)
{
menuState++;
if (menuState > NUMBER_OF_VALVES)
{
menuState = 0;
}
}
else
{
state = STAND_BY_ALL_OFF;
}
buttonPushed = false;
}
if (state == STAND_BY_ALL_OFF)
{
slowToggleLED ();
if (state != lastState)
{
updateRelays(ALL_VALVES_OFF);
DEBUG_PRINTLN(F("State Changed... all Zones off"));
for (byte i = 0; i <= NUMBER_OF_VALVES; i++)
{
delay(50);
gw.send(msg1valve.setSensor(i).set(false), false);
}
lcd.clear();
lcd.setCursor(0,0);
lcd.print(F("** Irrigation **"));
lcd.setCursor(0,1);
lcd.print(F("** Halted **"));
delay(2000);
lastValve = -1;
}
}
//
else if (state == RUN_ALL_ZONES)
{
if (lastValve != valveNumber)
{
for (byte i = 0; i <= NUMBER_OF_VALVES; i++)
{
if (i == 0 || i == valveNumber)
{
gw.send(msg1valve.setSensor(i).set(true), false);
}
else
{
gw.send(msg1valve.setSensor(i).set(false), false);
}
}
}
lastValve = valveNumber;
fastToggleLed();
if (state != lastState)
{
valveNumber = 1;
updateRelays(ALL_VALVES_OFF);
DEBUG_PRINTLN(F("State Changed, Running All Zones..."));
}
unsigned long nowMillis = millis();
if (nowMillis - startMillis < VALVE_RESET_TIME)
{
updateRelays(ALL_VALVES_OFF);
}
else if (nowMillis - startMillis < (allZoneTime[valveNumber] * 60000UL))
{
updateRelays(BITSHIFT_VALVE_NUMBER);
}
else
{
DEBUG_PRINTLN(F("Changing Valves..."));
updateRelays(ALL_VALVES_OFF);
startMillis = millis();
valveNumber++;
if (valveNumber > NUMBER_OF_VALVES)
{
state = CYCLE_COMPLETE;
startMillis = millis();
lastValve = -1;
lastTimeRun = now();
saveDateToEEPROM(lastTimeRun);
for (byte i = 0; i <= NUMBER_OF_VALVES; i++)
{
gw.send(msg1valve.setSensor(i).set(false), false);
}
DEBUG_PRINT(F("State = "));
DEBUG_PRINTLN(state);
}
}
}
//
else if (state == RUN_SINGLE_ZONE)
{
fastToggleLed();
if (state != lastState)
{
for (byte i = 0; i <= NUMBER_OF_VALVES; i++)
{
if (i == 0 || i == valveNumber)
{
gw.send(msg1valve.setSensor(i).set(true), false);
}
else
{
gw.send(msg1valve.setSensor(i).set(false), false);
}
}
DEBUG_PRINTLN(F("State Changed, Single Zone Running..."));
DEBUG_PRINT(F("Zone: "));
DEBUG_PRINTLN(valveNumber);
}
unsigned long nowMillis = millis();
if (nowMillis - startMillis < VALVE_RESET_TIME)
{
updateRelays(ALL_VALVES_OFF);
}
else if (nowMillis - startMillis < (valveSoloTime [valveNumber] * 60000UL))
{
updateRelays(BITSHIFT_VALVE_NUMBER);
}
else
{
updateRelays(ALL_VALVES_OFF);
for (byte i = 0; i <= NUMBER_OF_VALVES; i++)
{
gw.send(msg1valve.setSensor(i).set(false), false);
}
state = CYCLE_COMPLETE;
startMillis = millis();
DEBUG_PRINT(F("State = "));
DEBUG_PRINTLN(state);
}
lastTimeRun = now();
}
else if (state == CYCLE_COMPLETE)
{
if (millis() - startMillis < 30000UL)
{
fastToggleLed();
}
else
{
state = STAND_BY_ALL_OFF;
}
}
else if (state = ZONE_SELECT_MENU)
{
displayMenu();
}
lastState = state;
}
//
void displayMenu(void)
{
static byte lastMenuState = -1;
static int lastSecond;
if (menuState != lastMenuState)
{
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(valveNickName[menuState]);
lcd.setCursor(0, 1);
lcd.print(F("Starting"));
DEBUG_PRINT(valveNickName[menuState]);
Serial.print(F(" Starting Shortly"));
}
int thisSecond = (millis() - menuTimer) / 1000UL;
if (thisSecond != lastSecond && thisSecond < 8)
{
lcd.print(F("."));
Serial.print(".");
}
lastSecond = thisSecond;
if (millis() - menuTimer > 10000UL)
{
startMillis = millis();
if (menuState == 0)
{
valveNumber = 1;
state = RUN_ALL_ZONES;
}
else
{
valveNumber = menuState;
state = RUN_SINGLE_ZONE;
}
}
else
{
}
lastMenuState = menuState;
}
//
void updateRelays(int value)
{
digitalWrite(latchPin, LOW);
shiftOut(dataPin, clockPin, MSBFIRST, highByte(value));
shiftOut(dataPin, clockPin, MSBFIRST, lowByte(value));
digitalWrite(latchPin, HIGH);
}
//
void PushButton() //interrupt with debounce
{
static unsigned long last_interrupt_time = 0;
unsigned long interrupt_time = millis();
if (interrupt_time - last_interrupt_time > 200)
{
buttonPushed = true;
}
last_interrupt_time = interrupt_time;
}
//
void fastToggleLed()
{
static unsigned long fastLedTimer;
if (millis() - fastLedTimer >= 100UL)
{
digitalWrite(ledPin, !digitalRead(ledPin));
fastLedTimer = millis ();
}
}
//
void slowToggleLED ()
{
static unsigned long slowLedTimer;
if (millis() - slowLedTimer >= 1250UL)
{
digitalWrite(ledPin, !digitalRead(ledPin));
slowLedTimer = millis ();
}
}
//
void getVariables(const MyMessage &message)
{
boolean zoneTimeUpdate = false;
if (message.isAck())
{
DEBUG_PRINTLN(F("This is an ack from gateway"));
}
for (byte i = 0; i <= NUMBER_OF_VALVES; i++)
{
if (message.sensor == i)
{
if (message.type == V_LIGHT)
{
int switchState = atoi(message.data);
if (switchState == 0)
{
state = STAND_BY_ALL_OFF;
DEBUG_PRINTLN(F("Recieved Instruction to Cancel..."));
}
else
{
if (i == 0)
{
state = RUN_ALL_ZONES;
valveNumber = 1;
DEBUG_PRINTLN(F("Recieved Instruction to Run All Zones..."));
}
else
{
state = RUN_SINGLE_ZONE;
valveNumber = i;
DEBUG_PRINT(F("Recieved Instruction to Activate Zone: "));
DEBUG_PRINTLN(i);
}
}
startMillis = millis();
}
else if (message.type == V_VAR1)
{
int variable1 = atoi(message.data);// RUN_ALL_ZONES time
DEBUG_PRINT(F("Recieved variable1 valve:"));
DEBUG_PRINT(i);
DEBUG_PRINT(F(" = "));
DEBUG_PRINTLN(variable1);
if (variable1 != allZoneTime[i])
{
allZoneTime[i] = variable1;
zoneTimeUpdate = true;
}
}
else if (message.type == V_VAR2)
{
int variable2 = atoi(message.data);// RUN_SINGLE_ZONE time
DEBUG_PRINT(F("Recieved variable2 valve:"));
DEBUG_PRINT(i);
DEBUG_PRINT(F(" = "));
DEBUG_PRINTLN(variable2);
if (variable2 != valveSoloTime[i])
{
valveSoloTime[i] = variable2;
zoneTimeUpdate = true;
}
}
else if (message.type == V_VAR3)
{
String newMessage = String(message.data);
if (newMessage.length() == 0)
{
DEBUG_PRINT(F("No Name Recieved for zone "));
DEBUG_PRINTLN(i);
break;
}
if (newMessage.length() > 16)
{
newMessage.substring(0, 16);
}
valveNickName[i] = "";
valveNickName[i] += newMessage;
DEBUG_PRINT(F("Recieved new name for zone "));
DEBUG_PRINT(i);
DEBUG_PRINT(F(" and it is now called: "));
DEBUG_PRINTLN(valveNickName[i]);
}
}
}
if (zoneTimeUpdate)
{
//
DEBUG_PRINTLN(F("New Zone Times Recieved..."));
for (byte i = 0; i <= NUMBER_OF_VALVES; i++)
{
if (i != 0)
{
DEBUG_PRINT(F("Zone "));
DEBUG_PRINT(i);
DEBUG_PRINT(F(" individual time: "));
DEBUG_PRINT(valveSoloTime[i]);
DEBUG_PRINT(F(" group time: "));
DEBUG_PRINTLN(allZoneTime[i]);
recentUpdate = true;
}
}
}
else
{
recentUpdate = false;
}
}
//
void updateDisplay()
{
static unsigned long lastUpdateTime;
static boolean displayToggle = false;
//static byte toggleCounter = 0;
static SprinklerStates lastDisplayState;
if (state != lastDisplayState || millis() - lastUpdateTime >= 3000UL)
{
displayToggle = !displayToggle;
switch (state) {
case STAND_BY_ALL_OFF:
//
fastClear();
lcd.setCursor(0, 0);
if (displayToggle)
{
lcd.print(F(" System Ready "));
if (clockUpdating)
{
lcd.setCursor(15, 0);
lcd.write(byte(0));
}
lcd.setCursor(0, 1);
lcd.print(hourFormat12() < 10 ? F(" ") : F(""));
lcd.print(hourFormat12());
lcd.print(minute() < 10 ? F(":0") : F(":"));
lcd.print(minute());
lcd.print(isAM() ? F("am") : F("pm"));
lcd.print(month() < 10 ? F(" 0") : F(" "));
lcd.print(month());
lcd.print(day() < 10 ? F("/0") : F("/"));
lcd.print(day());
lcd.print(F("/"));
lcd.print(year() % 100);
}
else
{
lcd.print(F(" Last Watered "));
if (clockUpdating)
{
lcd.setCursor(15, 0);
lcd.write(byte(0));
}
lcd.setCursor(0, 1);
lcd.print(dayOfWeek[weekday(lastTimeRun)]);
lcd.setCursor(11, 1);
lcd.print(month(lastTimeRun) < 10 ? F(" ") : F(""));
lcd.print(month(lastTimeRun));
lcd.print(day(lastTimeRun) < 10 ? F("/0") : F("/"));
lcd.print(day(lastTimeRun));
}
break;
case RUN_SINGLE_ZONE:
//
fastClear();
lcd.setCursor(0, 0);
if (displayToggle)
{
lcd.print(F("Single Zone Mode"));
lcd.setCursor(0, 1);
lcd.print(F(" Zone:"));
if (valveNumber < 10) lcd.print(F("0"));
lcd.print(valveNumber);
lcd.print(F(" Active"));
}
else
{
lcd.print(F(" Time Remaining "));
lcd.setCursor(0, 1);
if (valveSoloTime[valveNumber] == 0)
{
lcd.print(F(" No Valve Time "));
}
else
{
unsigned long timeRemaining = (valveSoloTime[valveNumber] * 60) - ((millis() - startMillis) / 1000);
lcd.print(timeRemaining / 60 < 10 ? " 0" : " ");
lcd.print(timeRemaining / 60);
lcd.print("min");
lcd.print(timeRemaining % 60 < 10 ? " 0" : " ");
lcd.print(timeRemaining % 60);
lcd.print("sec ");
}
}
break;
case RUN_ALL_ZONES:
//
fastClear();
lcd.setCursor(0, 0);
if (displayToggle)
{
lcd.print(F(" All-Zone Mode "));
lcd.setCursor(0, 1);
lcd.print(F(" Zone:"));
if (valveNumber < 10) lcd.print(F("0"));
lcd.print(valveNumber);
lcd.print(F(" Active "));
}
else
{
lcd.print(F(" Time Remaining "));
lcd.setCursor(0, 1);
int timeRemaining = (allZoneTime[valveNumber] * 60) - ((millis() - startMillis) / 1000);
lcd.print((timeRemaining / 60) < 10 ? " 0" : " ");
lcd.print(timeRemaining / 60);
lcd.print("min");
lcd.print(timeRemaining % 60 < 10 ? " 0" : " ");
lcd.print(timeRemaining % 60);
lcd.print("sec ");
}
break;
case CYCLE_COMPLETE:
//
if (displayToggle)
{
lcd.setCursor(0, 0);
lcd.print(F(" Watering Cycle "));
lcd.setCursor(0, 1);
lcd.print(F(" Complete "));
}
else
{
int totalTimeRan = 0;
for (int i = 1; i < NUMBER_OF_VALVES + 1; i++)
{
totalTimeRan += allZoneTime[i];
}
lcd.setCursor(0, 0);
lcd.print(F(" Total Time Run "));
lcd.setCursor(0, 1);
lcd.print(totalTimeRan < 10 ? " 0" : " ");
lcd.print(totalTimeRan);
lcd.print(" Minutes ");
}
}
lastUpdateTime = millis();
}
lastDisplayState = state;
}
void receiveTime(time_t newTime)
{
DEBUG_PRINTLN(F("Time value received and updated..."));
int lastSecond = second();
int lastMinute = minute();
int lastHour = hour();
setTime(newTime);
if (((second() != lastSecond) || (minute() != lastMinute) || (hour() != lastHour)) || showTime)
{
DEBUG_PRINTLN(F("Clock updated...."));
DEBUG_PRINT(F("Sensor's time currently set to:"));
DEBUG_PRINT(hourFormat12() < 10 ? F(" 0") : F(" "));
DEBUG_PRINT(hourFormat12());
DEBUG_PRINT(minute() < 10 ? F(":0") : F(":"));
DEBUG_PRINT(minute());
DEBUG_PRINTLN(isAM() ? F("am") : F("pm"));
DEBUG_PRINT(month());
DEBUG_PRINT(F("/"));
DEBUG_PRINT(day());
DEBUG_PRINT(F("/"));
DEBUG_PRINTLN(year());
DEBUG_PRINTLN(dayOfWeek[weekday()]);
showTime = false;
}
else
{
DEBUG_PRINTLN(F("Sensor's time did NOT need adjustment greater than 1 second."));
}
clockUpdating = false;
}
void fastClear()
{
lcd.setCursor(0, 0);
lcd.print(F(" "));
lcd.setCursor(0, 1);
lcd.print(F(" "));
}
//
void updateClock()
{
static unsigned long lastVeraGetTime;
if (millis() - lastVeraGetTime >= 3600000UL) // updates clock time and gets zone times from vera once every hour
{
DEBUG_PRINTLN(F("Requesting time and valve data from Gateway..."));
lcd.setCursor(15, 0);
lcd.write(byte(0));
clockUpdating = true;
gw.requestTime(receiveTime);
lastVeraGetTime = millis();
}
}
//
void saveDateToEEPROM(unsigned long theDate)
{
DEBUG_PRINTLN(F("Saving Last Run date"));
if (gw.loadState(0) != 0xFF)
{
gw.saveState(0, 0xFF); // EEPROM flag for last date saved stored in EEPROM (location zero)
}
//
for (int i = 1; i < 5; i++)
{
gw.saveState(5 - i, byte(theDate >> 8 * (i - 1))); // store epoch datestamp in 4 bytes of EEPROM starting in location one
}
}
//
void goGetValveTimes()
{
static unsigned long valveUpdateTime;
static byte valveIndex = 1;
if (millis() - valveUpdateTime >= 300000UL / NUMBER_OF_VALVES)// update each valve once every 5 mins (distributes the traffic)
{
DEBUG_PRINTLN(F("Calling for Valve Data..."));
lcd.setCursor(15, 0);
lcd.write(byte(1)); //lcd.write(1);
gw.request(valveIndex, V_VAR1);
gw.request(valveIndex, V_VAR2);
gw.request(valveIndex, V_VAR3);
valveUpdateTime = millis();
valveIndex++;
if (valveIndex > NUMBER_OF_VALVES + 1)
{
valveIndex = 1;
}
}
}
@@ -43,12 +43,16 @@
#include <Wire.h>
#define CHILD_ID_LIGHT 0
#define LIGHT_SENSOR_ANALOG_PIN 0
unsigned long SLEEP_TIME = 30000; // Sleep time between reads (in milliseconds)
BH1750 lightSensor;
MySensor gw;
MyMessage msg(CHILD_ID_LIGHT, V_LIGHT_LEVEL_LUX);
// V_LIGHT_LEVEL should only be used for uncalibrated light level 0-100%.
// If your controller supports the new V_LEVEL variable, use this instead for
// transmitting LUX light level.
MyMessage msg(CHILD_ID_LIGHT, V_LIGHT_LEVEL);
// MyMessage msg(CHILD_ID_LIGHT, V_LEVEL);
uint16_t lastlux;
void setup()
@@ -0,0 +1,121 @@
// This sketch is to integrate the Playing With Fusion AXS3935 Lightning Sensor Breakout Board
// with the MySensors environment and is based on a sketch provided by Playing With Fusion
// http://playingwithfusion.com/productview.php?pdid=22&catid=1001
//
#include "MySensor.h"
// the lightning sensor can communicate via SPI or I2C. This sketch uses the SPI interface
#include "SPI.h"
// include Playing With Fusion AXS3935 libraries
#include "PWFusion_AS3935.h"
// setup CS pins used for the connection with the lightning sensor
// other connections are controlled by the SPI library)
int8_t CS_PIN = 8;
int8_t SI_PIN = 7;
int8_t IRQ_PIN = 3;
volatile int8_t AS3935_ISR_Trig = 0;
// #defines
#define AS3935_INDOORS 1
#define AS3935_OUTDOORS 0
#define AS3935_DIST_DIS 0
#define AS3935_DIST_EN 1
#define AS3935_CAPACITANCE 96 // <-- SET THIS VALUE TO THE NUMBER LISTED ON YOUR BOARD
// prototypes
void AS3935_ISR();
PWF_AS3935 lightning0(CS_PIN, IRQ_PIN, SI_PIN);
#define CHILD_ID_DISTANCE 1
#define CHILD_ID_INTENSITY 2
MySensor gw;
MyMessage msgDist(CHILD_ID_DISTANCE, V_DISTANCE);
MyMessage msgInt(CHILD_ID_INTENSITY, V_VAR1);
void setup()
{
gw.begin();
// Send the sketch version information to the gateway and Controller
gw.sendSketchInfo("Lightning Sensor", "1.1");
// Register all sensors to gw (they will be created as child devices)
gw.present(CHILD_ID_DISTANCE, S_DISTANCE);
gw.present(CHILD_ID_INTENSITY, S_CUSTOM);
boolean metric = gw.getConfig().isMetric;
Serial.begin(115200);
Serial.println("Playing With Fusion: AS3935 Lightning Sensor, SEN-39001");
Serial.println("beginning boot procedure....");
// setup for the the SPI library:
SPI.begin(); // begin SPI
SPI.setClockDivider(SPI_CLOCK_DIV4); // SPI speed to SPI_CLOCK_DIV16/1MHz (max 2MHz, NEVER 500kHz!)
SPI.setDataMode(SPI_MODE1); // MAX31855 is a Mode 1 device
// --> clock starts low, read on rising edge
SPI.setBitOrder(MSBFIRST); // data sent to chip MSb first
lightning0.AS3935_DefInit(); // set registers to default
// now update sensor cal for your application and power up chip
lightning0.AS3935_ManualCal(AS3935_CAPACITANCE, AS3935_OUTDOORS, AS3935_DIST_EN);
// AS3935_ManualCal Parameters:
// --> capacitance, in pF (marked on package)
// --> indoors/outdoors (AS3935_INDOORS:0 / AS3935_OUTDOORS:1)
// --> disturbers (AS3935_DIST_EN:1 / AS3935_DIST_DIS:2)
// function also powers up the chip
// enable interrupt (hook IRQ pin to Arduino Uno/Mega interrupt input: 1 -> pin 3 )
attachInterrupt(1, AS3935_ISR, RISING);
// dump the registry data to the serial port for troubleshooting purposes
lightning0.AS3935_PrintAllRegs();
// delay execution to allow chip to stabilize.
delay(1000);
}
void loop()
{
// This program only handles an AS3935 lightning sensor. It does nothing until
// an interrupt is detected on the IRQ pin.
while(0 == AS3935_ISR_Trig){}
// reset interrupt flag
AS3935_ISR_Trig = 0;
// now get interrupt source
uint8_t int_src = lightning0.AS3935_GetInterruptSrc();
if(0 == int_src)
{
Serial.println("Unknown interrupt source");
}
else if(1 == int_src)
{
uint8_t lightning_dist_km = lightning0.AS3935_GetLightningDistKm();
uint32_t lightning_intensity = lightning0.AS3935_GetStrikeEnergyRaw();
Serial.print("Lightning detected! Distance to strike: ");
Serial.print(lightning_dist_km);
Serial.println(" kilometers");
Serial.print("Lightning detected! Lightning Intensity: ");
Serial.println(lightning_intensity);
gw.send(msgDist.set(lightning_dist_km));
gw.send(msgInt.set(lightning_intensity));
}
else if(2 == int_src)
{
Serial.println("Disturber detected");
}
else if(3 == int_src)
{
Serial.println("Noise level too high");
}
}
// this is irq handler for AS3935 interrupts, has to return void and take no arguments
// always make code in interrupt handlers fast and short
void AS3935_ISR()
{
AS3935_ISR_Trig = 1;
}␍
@@ -118,7 +118,7 @@ byte TCP_MAC[] = { 0x02, 0xDE, 0xAD, 0x00, 0x00, 0x42 }; // Mac-address - You sh
//////////////////////////////////////////////////////////////////
// NRFRF24L01 radio driver (set low transmit power by default)
MyTransportNRF24 transport(RF24_CE_PIN, RF24_CS_PIN, RF24_PA_LEVEL_GW);
MyTransportNRF24 transport(RADIO_CE_PIN, RADIO_SPI_SS_PIN, RF24_PA_LEVEL_GW);
//MyTransportRFM69 transport;
// Message signing driver (signer needed if MY_SIGNING_FEATURE is turned on in MyConfig.h)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,15 @@
# MockMySensors
Arduino sketch to fake sensor for the MySensors Library
This skecth is intended to crate fake sensors which register and respond to the controller
Barduino 2015
-----------------
Comment/Uncomment the sensors you would like to test
Arduino Uno R3 can support about 12 sensors before it runs out of memory.
You probably need to turn of the MySensor Lib debug to save some memory
Nest step will be to remove the read/write to the eprom
and extend to support the v1.5 sensors
@@ -0,0 +1,164 @@
/**
* The MySensors Arduino library handles the wireless radio link and protocol
* between your home built sensors/actuators and HA controller of choice.
* The sensors forms a self healing radio network with optional repeaters. Each
* repeater and gateway builds a routing tables in EEPROM which keeps track of the
* network topology allowing messages to be routed to nodes.
*
* Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
* Copyright (C) 2013-2015 Sensnology AB
* Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
*
* Documentation: http://www.mysensors.org
* Support Forum: http://forum.mysensors.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
*******************************
*
* REVISION HISTORY
* Version 1.0 - Created by Henrik Ekblad
*
* DESCRIPTION
* Parking sensor using a neopixel led ring and distance sensor (HC-SR04).
* Configure the digital pins used for distance sensor and neopixels below.
* NOTE! Remeber to feed leds and distance sensor serparatly from your Arduino.
* It will probably not survive feeding more than a couple of LEDs. You
* can also adjust intesity below to reduce the power requirements.
*
* Sends parking status to the controller as a DOOR sensor if SEND_STATUS_TO_CONTROLLER
* is defined below. You can also use this _standalone_ without any radio by
* removing the SEND_STATUS_TO_CONTROLLER define.
*/
#define SEND_STATUS_TO_CONTROLLER // Put a comment on this line for standalone mode
#include <Adafruit_NeoPixel.h>
#include <NewPing.h>
#ifdef SEND_STATUS_TO_CONTROLLER
#include <SPI.h>
#include <MySensor.h>
#endif
#define NEO_PIN 4 // NeoPixels input pin
#define TRIGGER_PIN 6 // Arduino pin tied to trigger pin on the ultrasonic sensor.
#define ECHO_PIN 5 // Arduino pin tied to echo pin on the ultrasonic sensor.
#define NUMPIXELS 24 // Number of nexpixels in ring/strip
#define MAX_INTESITY 20 // Intesity of leds (in percentage). Remeber more intesity requires more power.
// The maximum rated measuring range for the HC-SR04 is about 400-500cm.
#define MAX_DISTANCE 100 // Max distance we want to start indicating green (in cm)
#define PANIC_DISTANCE 5 // Mix distance we red warning indication should be active (in cm)
#define PARKED_DISTANCE 20 // Distance when "parked signal" should be sent to controller (in cm)
// Note that for older NeoPixel strips you might need to change the third parameter--see the strandtest
// example for more information on possible values.
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, NEO_PIN, NEO_GRB + NEO_KHZ400);
NewPing sonar(TRIGGER_PIN, ECHO_PIN, MAX_DISTANCE); // NewPing setup of pins and maximum distance.
#ifdef SEND_STATUS_TO_CONTROLLER
#define CHILD_ID 1
MySensor gw;
MyMessage msg(CHILD_ID,V_TRIPPED);
int oldParkedStatus=-1;
int sendInterval = 5000; // Send park status at maximum every 5 second.
unsigned long lastSend;
#endif
int blinkInterval = 100; // blink interval (milliseconds)
unsigned long lastBlinkPeriod;
bool blinkColor = true;
// To make a fading motion on the led ring/tape we only move one pixel/distDebounce time
int distDebounce = 30;
unsigned long lastDebouncePeriod;
int numLightPixels=0;
int skipZero=0;
void setup() {
Serial.begin(115200);
Serial.println("Starting distance sensor");
pixels.begin(); // This initializes the NeoPixel library.
Serial.println("Neopixels initialized");
#ifdef SEND_STATUS_TO_CONTROLLER
gw.begin();
gw.sendSketchInfo("Parking Sensor", "1.0");
gw.present(CHILD_ID, S_DOOR, "Parking Status");
#endif
}
void loop() {
unsigned long now = millis();
int fullDist = sonar.ping_cm();
// Serial.println(fullDist);
int displayDist = min(fullDist, MAX_DISTANCE);
if (displayDist == 0 && skipZero<10) {
// Try to filter zero readings
skipZero++;
return;
}
// Check if it is time to alter the leds
if (now-lastDebouncePeriod > distDebounce) {
lastDebouncePeriod = now;
#ifdef SEND_STATUS_TO_CONTROLLER
// Update parked status
int parked = displayDist != 0 && displayDist<PARKED_DISTANCE;
if (parked != oldParkedStatus && now-lastSend > sendInterval) {
if (parked)
Serial.println("Car Parked");
else
Serial.println("Car Gone");
gw.send(msg.set(parked));
oldParkedStatus = parked;
lastSend = now;
}
#endif
if (displayDist == 0) {
// No reading from sensor, assume no object found
numLightPixels--;
} else {
skipZero = 0;
int newLightPixels = NUMPIXELS - (NUMPIXELS*(displayDist-PANIC_DISTANCE)/MAX_DISTANCE);
if (newLightPixels>numLightPixels) {
// Fast raise
numLightPixels += max((newLightPixels - numLightPixels) / 2, 1);
} else if (newLightPixels<numLightPixels) {
// Slow decent
numLightPixels--;
}
}
if (numLightPixels>=NUMPIXELS) {
// Do some intense red blinking
if (now-lastBlinkPeriod > blinkInterval) {
blinkColor = !blinkColor;
lastBlinkPeriod = now;
}
for(int i=0;i<numLightPixels;i++){
pixels.setPixelColor(i, pixels.Color(blinkColor?255*MAX_INTESITY/100:0,0,0));
}
} else {
for(int i=0;i<numLightPixels;i++){
int r = 255 * i/NUMPIXELS;
int g = 255 - r;
// pixels.Color takes RGB values, from 0,0,0 up to 255,255,255
pixels.setPixelColor(i, pixels.Color(r*MAX_INTESITY/100,g*MAX_INTESITY/100,0));
}
// Turn off the rest
for(int i=numLightPixels;i<NUMPIXELS;i++){
pixels.setPixelColor(i, pixels.Color(0,0,0));
}
}
pixels.show(); // This sends the updated pixel color to the hardware.
}
}
@@ -0,0 +1,44 @@
/****
* Formatted logging to the Serial console.
* Compiled in by setting LOGDEBUG
*
* 2015-05-25 Bruce Lacey V1.0
*
* Based upon Arduino Playground prior art and should be moved to
* the MySensors library at some point as a common debug logging facility
*/
#ifndef MYSLog_h
#define MYSLog_h
#define LOGDEBUG 1
#if defined ( LOGDEBUG )
#define LOG(fmt, args... ) log( fmt, ## args );
#else
#define log(fmt, args... )
#endif
void log(const char *fmt, ... ) {
char buff[128];
va_list args;
va_start (args, fmt);
vsnprintf(buff, sizeof(buff), fmt, args);
va_end (args);
buff[sizeof(buff)/sizeof(buff[0])-1]='\0';
Serial.print(buff);
}
void log(const __FlashStringHelper *fmt, ... ){
char buf[128]; // resulting string limited to 128 chars
va_list args;
va_start (args, fmt);
#ifdef __AVR__
vsnprintf_P(buf, sizeof(buf), (const char *)fmt, args); // progmem for AVR
#else
vsnprintf(buf, sizeof(buf), (const char *)fmt, args); // for the rest of the world
#endif
va_end(args);
Serial.print(buf);
}
#endif
@@ -0,0 +1,99 @@
/***
* This is a simple sketch used to demenstrate and test node-to-node MySensor's communication.
* To use this sketch, assemble MySensors nodes - they need nothing more than a radio
* 1. Flash each node with the same sketch, open the console and type either 0 or 1 to the respective nodes to set thei ID
* 2. You only need to set the node id once, and restart the nodes
* 3. To being a ping-pong test, simply type T in the console for one of the nodes.
*
* 2015-05-25 Bruce Lacey v1.0
*/
#include <MySensor.h>
#include <SPI.h>
#include "MYSLog.h"
#define VSN "v1.0"
// Define two generic nodes with a single child
#define YING 200
#define YANG 201
#define CHILD 1
// Sensor objects
MySensor gw;
MyMessage mPing(CHILD, V_VAR1); //Ping message
MyMessage mPong(CHILD, V_VAR2); //Pong message
void setup() {
Serial.begin(115200);
gw.begin(incomingMessage); // Node ids are written to EEPROM during sensor node configuration by entering a 0 or 1
gw.present(CHILD, S_CUSTOM); //
gw.sendSketchInfo( nodeTypeAsCharRepresentation( gw.getNodeId() ), VSN );
LOG(F("\n%sReady.\n"), nodeTypeAsCharRepresentation(gw.getNodeId()));
}
void loop() {
// Interactive command and control
// Entering a number from 0 or 1 will write the node 200 (YING) or 201 (YANG) to EEPROM
// Entering T on either node will initiatve a ping-pong test.
if (Serial.available()) {
byte inChar = Serial.read();
uint8_t node = gw.getNodeId();
// Manual Test Mode
if (inChar == 'T' || inChar == 't') {
LOG(F("T received - starting test...\n"));
MyMessage msg = mPong;
msg.sender = (node == YING ? YANG : YING);
sendPingOrPongResponse( msg );
}
else if (inChar == '0' or inChar == '1') {
byte nodeID = 200 + (inChar - '0');
setNodeId(nodeID);
}
else {
LOG("Invalid input\n");
}
}
// Process sensor messages
gw.process();
}
void incomingMessage(const MyMessage &message) {
LOG(F("Received %s from %s\n"), msgTypeAsCharRepresentation((mysensor_data)message.type), nodeTypeAsCharRepresentation(message.sender));
delay(250);
sendPingOrPongResponse( message );
}
void sendPingOrPongResponse( MyMessage msg ) {
MyMessage response = (msg.type == V_VAR1 ? mPong : mPing);
LOG(F("Sending %s to %s\n"), msgTypeAsCharRepresentation( (mysensor_data)response.type ), nodeTypeAsCharRepresentation(msg.sender));
// Set payload to current time in millis to ensure each message is unique
response.set( millis() );
response.setDestination(msg.sender);
gw.send(response);
}
void setNodeId(byte nodeID) {
LOG(F("Setting node id to: %i.\n***Please restart the node for changes to take effect.\n"), nodeID);
eeprom_write_byte((uint8_t*)EEPROM_NODE_ID_ADDRESS, (byte)nodeID);
}
const char * msgTypeAsCharRepresentation( mysensor_data mType ) {
return mType == V_VAR1 ? "Ping" : "Pong";
}
const char * nodeTypeAsCharRepresentation( uint8_t node ) {
return node == YING ? "Ying Node" : "Yang Node";
}
@@ -35,7 +35,21 @@
#define BARO_CHILD 0
#define TEMP_CHILD 1
unsigned long SLEEP_TIME = 60000; // Sleep time between reads (in seconds)
const float ALTITUDE = 688; // <-- adapt this value to your own location's altitude.
// Sleep time between reads (in seconds). Do not change this value as the forecast algorithm needs a sample every minute.
const unsigned long SLEEP_TIME = 60000;
const char *weather[] = { "stable", "sunny", "cloudy", "unstable", "thunderstorm", "unknown" };
enum FORECAST
{
STABLE = 0, // "Stable Weather Pattern"
SUNNY = 1, // "Slowly rising Good Weather", "Clear/Sunny "
CLOUDY = 2, // "Slowly falling L-Pressure ", "Cloudy/Rain "
UNSTABLE = 3, // "Quickly rising H-Press", "Not Stable"
THUNDERSTORM = 4, // "Quickly falling L-Press", "Thunderstorm"
UNKNOWN = 5 // "Unknown (More Time needed)
};
Adafruit_BMP085 bmp = Adafruit_BMP085(); // Digital Pressure Sensor
MySensor gw;
@@ -43,206 +57,243 @@ MySensor gw;
float lastPressure = -1;
float lastTemp = -1;
int lastForecast = -1;
const char *weather[] = {"stable","sunny","cloudy","unstable","thunderstorm","unknown"};
int minutes;
float pressureSamples[7][6];
const int LAST_SAMPLES_COUNT = 5;
float lastPressureSamples[LAST_SAMPLES_COUNT];
// this CONVERSION_FACTOR is used to convert from Pa to kPa in forecast algorithm
// get kPa/h be dividing hPa by 10
#define CONVERSION_FACTOR (1.0/10.0)
int minuteCount = 0;
bool firstRound = true;
float pressureAvg[7];
// average value is used in forecast algorithm.
float pressureAvg;
// average after 2 hours is used as reference value for the next iteration.
float pressureAvg2;
float dP_dt;
boolean metric;
boolean metric;
MyMessage tempMsg(TEMP_CHILD, V_TEMP);
MyMessage pressureMsg(BARO_CHILD, V_PRESSURE);
MyMessage forecastMsg(BARO_CHILD, V_FORECAST);
void setup() {
gw.begin();
// Send the sketch version information to the gateway and Controller
gw.sendSketchInfo("Pressure Sensor", "1.0");
void setup()
{
gw.begin();
if (!bmp.begin()) {
Serial.println("Could not find a valid BMP085 sensor, check wiring!");
while (1) { }
}
// Send the sketch version information to the gateway and Controller
gw.sendSketchInfo("Pressure Sensor", "1.1");
// Register sensors to gw (they will be created as child devices)
gw.present(BARO_CHILD, S_BARO);
gw.present(TEMP_CHILD, S_TEMP);
metric = gw.getConfig().isMetric;
if (!bmp.begin())
{
Serial.println("Could not find a valid BMP085 sensor, check wiring!");
while (1) {}
}
// Register sensors to gw (they will be created as child devices)
gw.present(BARO_CHILD, S_BARO);
gw.present(TEMP_CHILD, S_TEMP);
metric = gw.getConfig().isMetric;
}
void loop() {
float pressure = bmp.readSealevelPressure(205)/100; // 205 meters above sealevel
float temperature = bmp.readTemperature();
if (!metric) {
// Convert to fahrenheit
temperature = temperature * 9.0 / 5.0 + 32.0;
}
int forecast = sample(pressure);
void loop()
{
float pressure = bmp.readSealevelPressure(ALTITUDE) / 100.0;
float temperature = bmp.readTemperature();
Serial.print("Temperature = ");
Serial.print(temperature);
Serial.println(metric?" *C":" *F");
Serial.print("Pressure = ");
Serial.print(pressure);
Serial.println(" Pa");
Serial.println(weather[forecast]);
if (!metric)
{
// Convert to fahrenheit
temperature = temperature * 9.0 / 5.0 + 32.0;
}
int forecast = sample(pressure);
Serial.print("Temperature = ");
Serial.print(temperature);
Serial.println(metric ? " *C" : " *F");
Serial.print("Pressure = ");
Serial.print(pressure);
Serial.println(" hPa");
Serial.print("Forecast = ");
Serial.println(weather[forecast]);
if (temperature != lastTemp) {
gw.send(tempMsg.set(temperature,1));
lastTemp = temperature;
}
if (temperature != lastTemp)
{
gw.send(tempMsg.set(temperature, 1));
lastTemp = temperature;
}
if (pressure != lastPressure) {
gw.send(pressureMsg.set(pressure, 0));
lastPressure = pressure;
}
if (pressure != lastPressure)
{
gw.send(pressureMsg.set(pressure, 0));
lastPressure = pressure;
}
if (forecast != lastForecast) {
gw.send(forecastMsg.set(weather[forecast]));
lastForecast = forecast;
}
/*
DP/Dt explanation
if (forecast != lastForecast)
{
gw.send(forecastMsg.set(weather[forecast]));
lastForecast = forecast;
}
0 = "Stable Weather Pattern"
1 = "Slowly rising Good Weather", "Clear/Sunny "
2 = "Slowly falling L-Pressure ", "Cloudy/Rain "
3 = "Quickly rising H-Press", "Not Stable"
4 = "Quickly falling L-Press", "Thunderstorm"
5 = "Unknown (More Time needed)
*/
gw.sleep(SLEEP_TIME);
gw.sleep(SLEEP_TIME);
}
int sample(float pressure) {
// Algorithm found here
// http://www.freescale.com/files/sensors/doc/app_note/AN3914.pdf
if (minuteCount == 180)
minuteCount = 5;
//From 0 to 5 min.
if (minuteCount <= 5){
pressureSamples[0][minuteCount] = pressure;
}
//From 30 to 35 min.
if ((minuteCount <= 30) && (minuteCount >= 35)){
pressureSamples[1][minuteCount - 30] = pressure;
}
//From 55 to 60 min.
if ((minuteCount <= 55) && (minuteCount >= 60)){
pressureSamples[2][minuteCount - 55] = pressure;
}
//From 90 to 95 min.
if ((minuteCount <= 90) && (minuteCount >= 95)){
pressureSamples[3][minuteCount - 90] = pressure;
}
//From 115 to 119 min.
if ((minuteCount <= 115) && (minuteCount >= 120)){
pressureSamples[4][minuteCount - 115] = pressure;
}
//From 150 to 155 min.
if ((minuteCount <= 150) && (minuteCount >= 155)){
pressureSamples[5][minuteCount - 150] = pressure;
}
//From 175 to 180 min.
if ((minuteCount <= 175) && (minuteCount >= 180)){
pressureSamples[6][minuteCount - 175] = pressure;
}
float getLastPressureSamplesAverage()
{
float lastPressureSamplesAverage = 0;
for (int i = 0; i < LAST_SAMPLES_COUNT; i++)
{
lastPressureSamplesAverage += lastPressureSamples[i];
}
lastPressureSamplesAverage /= LAST_SAMPLES_COUNT;
return lastPressureSamplesAverage;
}
// Algorithm found here
// http://www.freescale.com/files/sensors/doc/app_note/AN3914.pdf
// Pressure in hPa --> forecast done by calculating kPa/h
int sample(float pressure)
{
// Calculate the average of the last n minutes.
int index = minuteCount % LAST_SAMPLES_COUNT;
lastPressureSamples[index] = pressure;
minuteCount++;
if (minuteCount > 185)
{
minuteCount = 6;
}
if (minuteCount == 5) {
// Avg pressure in first 5 min, value averaged from 0 to 5 min.
pressureAvg[0] = ((pressureSamples[0][0] + pressureSamples[0][1]
+ pressureSamples[0][2] + pressureSamples[0][3]
+ pressureSamples[0][4] + pressureSamples[0][5]) / 6);
} else if (minuteCount == 35) {
// Avg pressure in 30 min, value averaged from 0 to 5 min.
pressureAvg[1] = ((pressureSamples[1][0] + pressureSamples[1][1]
+ pressureSamples[1][2] + pressureSamples[1][3]
+ pressureSamples[1][4] + pressureSamples[1][5]) / 6);
float change = (pressureAvg[1] - pressureAvg[0]);
if (minuteCount == 5)
{
pressureAvg = getLastPressureSamplesAverage();
}
else if (minuteCount == 35)
{
float lastPressureAvg = getLastPressureSamplesAverage();
float change = (lastPressureAvg - pressureAvg) * CONVERSION_FACTOR;
if (firstRound) // first time initial 3 hour
dP_dt = ((65.0 / 1023.0) * 2 * change); // note this is for t = 0.5hour
{
dP_dt = change * 2; // note this is for t = 0.5hour
}
else
dP_dt = (((65.0 / 1023.0) * change) / 1.5); // divide by 1.5 as this is the difference in time from 0 value.
} else if (minuteCount == 60) {
// Avg pressure at end of the hour, value averaged from 0 to 5 min.
pressureAvg[2] = ((pressureSamples[2][0] + pressureSamples[2][1]
+ pressureSamples[2][2] + pressureSamples[2][3]
+ pressureSamples[2][4] + pressureSamples[2][5]) / 6);
float change = (pressureAvg[2] - pressureAvg[0]);
{
dP_dt = change / 1.5; // divide by 1.5 as this is the difference in time from 0 value.
}
}
else if (minuteCount == 65)
{
float lastPressureAvg = getLastPressureSamplesAverage();
float change = (lastPressureAvg - pressureAvg) * CONVERSION_FACTOR;
if (firstRound) //first time initial 3 hour
dP_dt = ((65.0 / 1023.0) * change); //note this is for t = 1 hour
{
dP_dt = change; //note this is for t = 1 hour
}
else
dP_dt = (((65.0 / 1023.0) * change) / 2); //divide by 2 as this is the difference in time from 0 value
} else if (minuteCount == 95) {
// Avg pressure at end of the hour, value averaged from 0 to 5 min.
pressureAvg[3] = ((pressureSamples[3][0] + pressureSamples[3][1]
+ pressureSamples[3][2] + pressureSamples[3][3]
+ pressureSamples[3][4] + pressureSamples[3][5]) / 6);
float change = (pressureAvg[3] - pressureAvg[0]);
{
dP_dt = change / 2; //divide by 2 as this is the difference in time from 0 value
}
}
else if (minuteCount == 95)
{
float lastPressureAvg = getLastPressureSamplesAverage();
float change = (lastPressureAvg - pressureAvg) * CONVERSION_FACTOR;
if (firstRound) // first time initial 3 hour
dP_dt = (((65.0 / 1023.0) * change) / 1.5); // note this is for t = 1.5 hour
{
dP_dt = change / 1.5; // note this is for t = 1.5 hour
}
else
dP_dt = (((65.0 / 1023.0) * change) / 2.5); // divide by 2.5 as this is the difference in time from 0 value
} else if (minuteCount == 120) {
// Avg pressure at end of the hour, value averaged from 0 to 5 min.
pressureAvg[4] = ((pressureSamples[4][0] + pressureSamples[4][1]
+ pressureSamples[4][2] + pressureSamples[4][3]
+ pressureSamples[4][4] + pressureSamples[4][5]) / 6);
float change = (pressureAvg[4] - pressureAvg[0]);
{
dP_dt = change / 2.5; // divide by 2.5 as this is the difference in time from 0 value
}
}
else if (minuteCount == 125)
{
float lastPressureAvg = getLastPressureSamplesAverage();
pressureAvg2 = lastPressureAvg; // store for later use.
float change = (lastPressureAvg - pressureAvg) * CONVERSION_FACTOR;
if (firstRound) // first time initial 3 hour
dP_dt = (((65.0 / 1023.0) * change) / 2); // note this is for t = 2 hour
{
dP_dt = change / 2; // note this is for t = 2 hour
}
else
dP_dt = (((65.0 / 1023.0) * change) / 3); // divide by 3 as this is the difference in time from 0 value
} else if (minuteCount == 155) {
// Avg pressure at end of the hour, value averaged from 0 to 5 min.
pressureAvg[5] = ((pressureSamples[5][0] + pressureSamples[5][1]
+ pressureSamples[5][2] + pressureSamples[5][3]
+ pressureSamples[5][4] + pressureSamples[5][5]) / 6);
float change = (pressureAvg[5] - pressureAvg[0]);
{
dP_dt = change / 3; // divide by 3 as this is the difference in time from 0 value
}
}
else if (minuteCount == 155)
{
float lastPressureAvg = getLastPressureSamplesAverage();
float change = (lastPressureAvg - pressureAvg) * CONVERSION_FACTOR;
if (firstRound) // first time initial 3 hour
dP_dt = (((65.0 / 1023.0) * change) / 2.5); // note this is for t = 2.5 hour
{
dP_dt = change / 2.5; // note this is for t = 2.5 hour
}
else
dP_dt = (((65.0 / 1023.0) * change) / 3.5); // divide by 3.5 as this is the difference in time from 0 value
} else if (minuteCount == 180) {
// Avg pressure at end of the hour, value averaged from 0 to 5 min.
pressureAvg[6] = ((pressureSamples[6][0] + pressureSamples[6][1]
+ pressureSamples[6][2] + pressureSamples[6][3]
+ pressureSamples[6][4] + pressureSamples[6][5]) / 6);
float change = (pressureAvg[6] - pressureAvg[0]);
{
dP_dt = change / 3.5; // divide by 3.5 as this is the difference in time from 0 value
}
}
else if (minuteCount == 185)
{
float lastPressureAvg = getLastPressureSamplesAverage();
float change = (lastPressureAvg - pressureAvg) * CONVERSION_FACTOR;
if (firstRound) // first time initial 3 hour
dP_dt = (((65.0 / 1023.0) * change) / 3); // note this is for t = 3 hour
{
dP_dt = change / 3; // note this is for t = 3 hour
}
else
dP_dt = (((65.0 / 1023.0) * change) / 4); // divide by 4 as this is the difference in time from 0 value
pressureAvg[0] = pressureAvg[5]; // Equating the pressure at 0 to the pressure at 2 hour after 3 hours have past.
{
dP_dt = change / 4; // divide by 4 as this is the difference in time from 0 value
}
pressureAvg = pressureAvg2; // Equating the pressure at 0 to the pressure at 2 hour after 3 hours have past.
firstRound = false; // flag to let you know that this is on the past 3 hour mark. Initialized to 0 outside main loop.
}
int forecast = UNKNOWN;
if (minuteCount < 35 && firstRound) //if time is less than 35 min on the first 3 hour interval.
return 5; // Unknown, more time needed
{
forecast = UNKNOWN;
}
else if (dP_dt < (-0.25))
return 4; // Quickly falling LP, Thunderstorm, not stable
{
forecast = THUNDERSTORM;
}
else if (dP_dt > 0.25)
return 3; // Quickly rising HP, not stable weather
{
forecast = UNSTABLE;
}
else if ((dP_dt > (-0.25)) && (dP_dt < (-0.05)))
return 2; // Slowly falling Low Pressure System, stable rainy weather
{
forecast = CLOUDY;
}
else if ((dP_dt > 0.05) && (dP_dt < 0.25))
return 1; // Slowly rising HP stable good weather
else if ((dP_dt > (-0.05)) && (dP_dt < 0.05))
return 0; // Stable weather
{
forecast = SUNNY;
}
else if ((dP_dt >(-0.05)) && (dP_dt < 0.05))
{
forecast = STABLE;
}
else
return 5; // Unknown
{
forecast = UNKNOWN;
}
// uncomment when debugging
//Serial.print(F("Forecast at minute "));
//Serial.print(minuteCount);
//Serial.print(F(" dP/dt = "));
//Serial.print(dP_dt);
//Serial.print(F("kPa/h --> "));
//Serial.println(weather[forecast]);
return forecast;
}
@@ -0,0 +1,594 @@
/*
Arduino Tipping Bucket Rain Gauge
April 26, 2015
Version 1.4.1 alpha
Arduino Tipping Bucket Rain Gauge
Utilizing a tipping bucket sensor, your Vera home automation controller and the MySensors.org
gateway you can measure and sense local rain. This sketch will create two devices on your
Vera controller. One will display your total precipitation for the last 5 days. The other,
a sensor that changes state if there is recent rain (up to last 120 hours) above a threshold.
Both these settings are user definable.
There is a build overview video here: https://youtu.be/1eMfKQaLROo
This sketch features the following:
* Allows you to set the rain threshold in mm
* Allows you to determine the tripped indicator window up to 120 hours.
* Displays the last 5 days of rain in Variable1 through Variable5
of the Rain Sensor device
* Configuration changes to Sensor device updated every hour
* Should run on any Arduino
* Will retain Tripped/Not Tripped status and data in a power interruption, saving small amount
of data to EEPROM (Circular Buffer to maximize life of EEPROM)
* LED status indicator
* Optional Temp/Humidity (DHT-22 or DHT-11) and Light LUX (BH1750) sensors. To use, uncomment
#define DHT_ON and/or #define LUX_ON
* Optionally send total accumulation of each day's rainfall or send only individual days rainfall totals.
Comment out #define USE_DAILY to display individual daily rainfall.
by @BulldogLowell and @PeteWill for free public use
*/
#include <SPI.h>
#include <MySensor.h>
#include <math.h>
#include <Time.h>
#define NODE_ID 24 //or AUTO to let controller assign
#define SKETCH_NAME "Rain Gauge"
#define SKETCH_VERSION "1.4.1a"
#define DWELL_TIME 125 // this allows for radio to come back to power after a transmission, ideally 0
//#define DEBUG_ON // comment out this line to disable serial debug
#define DHT_ON // uncomment out this line to enable DHT sensor
#define LUX_ON // uncomment out this line to enable BH1750 sensor
//#define USE_DAILY // displays each time segment as an accumulation of prior periods inclusive. Comment out to display individual daily rainfall totals in the variables sent to your controller.
#define CALIBRATE_FACTOR 60 // amount of rain per rain bucket tip e.g. 5 is .05mm
#define DHT_LUX_DELAY 300000 //Delay in milliseconds that the DHT and LUX sensors will wait before sending data
#define CHILD_ID_RAIN_LOG 3 // Keeps track of accumulated rainfall
#define CHILD_ID_TRIPPED_INDICATOR 4 // Indicates Tripped when rain detected
#define EEPROM_BUFFER_LOCATION 0 // location of the EEPROM circular buffer
#define E_BUFFER_LENGTH 240
#define RAIN_BUCKET_SIZE 120
#ifdef DEBUG_ON
#define DEBUG_PRINT(x) Serial.print(x)
#define DEBUG_PRINTLN(x) Serial.println(x)
#define SERIAL_START(x) Serial.begin(x)
#else
#define DEBUG_PRINT(x)
#define DEBUG_PRINTLN(x)
#define SERIAL_START(x)
#endif
//
MySensor gw;
//
MyMessage msgRainRate(CHILD_ID_RAIN_LOG, V_RAINRATE);
MyMessage msgRain(CHILD_ID_RAIN_LOG, V_RAIN);
//
MyMessage msgRainVAR1(CHILD_ID_RAIN_LOG, V_VAR1);
MyMessage msgRainVAR2(CHILD_ID_RAIN_LOG, V_VAR2);
MyMessage msgRainVAR3(CHILD_ID_RAIN_LOG, V_VAR3);
MyMessage msgRainVAR4(CHILD_ID_RAIN_LOG, V_VAR4);
MyMessage msgRainVAR5(CHILD_ID_RAIN_LOG, V_VAR5);
//
MyMessage msgTripped(CHILD_ID_TRIPPED_INDICATOR, V_TRIPPED);
MyMessage msgTrippedVar1(CHILD_ID_TRIPPED_INDICATOR, V_VAR1);
MyMessage msgTrippedVar2(CHILD_ID_TRIPPED_INDICATOR, V_VAR2);
//
#ifdef DHT_ON
#include <DHT.h>
#define CHILD_ID_HUM 0
#define CHILD_ID_TEMP 1
#define HUMIDITY_SENSOR_DIGITAL_PIN 8
DHT dht;
float lastTemp;
float lastHum;
boolean metric = true;
MyMessage msgHum(CHILD_ID_HUM, V_HUM);
MyMessage msgTemp(CHILD_ID_TEMP, V_TEMP);
#endif
//
#ifdef LUX_ON
//BH1750 is connected to SCL (analog input A5) and SDA (analog input A4)
#include <BH1750.h>
#include <Wire.h>
#define CHILD_ID_LIGHT 2
BH1750 lightSensor;
MyMessage msg(CHILD_ID_LIGHT, V_LIGHT_LEVEL);
unsigned int lastlux;
byte heartbeat = 10; //Used to send the light lux to gateway as soon as the device is restarted and after the DHT_LUX_DELAY has happened 10 times
#endif
unsigned long sensorPreviousMillis;
int eepromIndex;
int tipSensorPin = 3; // Pin the tipping bucket is connected to. Must be interrupt capable pin
int ledPin = 5; // Pin the LED is connected to. PWM capable pin required
unsigned long dataMillis;
unsigned long serialInterval = 600000UL;
const unsigned long oneHour = 3600000UL;
unsigned long lastTipTime;
unsigned long lastRainTime; //Used for rainRate calculation
unsigned int rainBucket [RAIN_BUCKET_SIZE] ; /* 24 hours x 5 Days = 120 hours */
unsigned int rainRate = 0;
byte rainWindow = 72; //default rain window in hours. Will be overwritten with msgTrippedVar1.
volatile int wasTippedBuffer = 0;
int rainSensorThreshold = 50; //default rain sensor sensitivity in hundredths. Will be overwritten with msgTrippedVar2.
byte state = 0;
byte oldState = -1;
int lastRainRate = 0;
int lastMeasure = 0;
boolean gotTime = false;
byte lastHour;
byte currentHour;
//
void setup()
{
SERIAL_START(115200);
//
// Set up the IO
pinMode(tipSensorPin, INPUT_PULLUP);
attachInterrupt (1, sensorTipped, FALLING); // depending on location of the hall effect sensor may need CHANGE
pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, HIGH);
//
//Let's get the controller talking to the Arduino
gw.begin(getVariables, NODE_ID);
gw.sendSketchInfo(SKETCH_NAME, SKETCH_VERSION);
gw.wait(DWELL_TIME);
gw.present(CHILD_ID_RAIN_LOG, S_RAIN);
gw.wait(DWELL_TIME);
gw.present(CHILD_ID_TRIPPED_INDICATOR, S_MOTION);
gw.wait(DWELL_TIME);
DEBUG_PRINTLN(F("Sensor Presentation Complete"));
//
//Sync time with the server, this will be called hourly in order to keep time from creeping with the crystal
//
unsigned long functionTimeout = millis();
while (timeStatus() == timeNotSet && millis() - functionTimeout < 30000UL)
{
gw.process();
gw.requestTime(receiveTime);
DEBUG_PRINTLN(F("Getting Time"));
gw.wait(1000); // call once per second
DEBUG_PRINTLN(F("."));
}
currentHour = hour();
lastHour = hour();
//
//retrieve from EEPROM stored values on a power cycle.
//
boolean isDataOnEeprom = false;
for (int i = 0; i < E_BUFFER_LENGTH; i++)
{
byte locator = gw.loadState(EEPROM_BUFFER_LOCATION + i);
if (locator == 0xFE) // found the EEPROM circular buffer index
{
eepromIndex = EEPROM_BUFFER_LOCATION + i;
DEBUG_PRINT(F("EEPROM Index "));
DEBUG_PRINTLN(eepromIndex);
//Now that we have the buffer index let's populate the rainBucket[] with data from eeprom
loadRainArray(eepromIndex);
isDataOnEeprom = true;
break;
}
}
//
if (!isDataOnEeprom) // Added for the first time it is run on a new Arduino
{
DEBUG_PRINTLN(F("I didn't find valid EEPROM Index, so I'm writing one to location 0"));
eepromIndex = EEPROM_BUFFER_LOCATION;
gw.saveState(eepromIndex, 0xFE);
gw.saveState(eepromIndex + 1, 0xFE);
//then I will clear out any bad data
for (int i = 2; i <= E_BUFFER_LENGTH; i++)
{
gw.saveState(i, 0x00);
}
}
dataMillis = millis();
lastTipTime = millis() - oneHour; //why is this -oneHour?? Doesn't millis() start at 0 when first powered on?
//
gw.request(CHILD_ID_TRIPPED_INDICATOR, V_VAR1);
gw.wait(DWELL_TIME);
gw.request(CHILD_ID_TRIPPED_INDICATOR, V_VAR2);
gw.wait(DWELL_TIME);
//
#ifdef DHT_ON
dht.setup(HUMIDITY_SENSOR_DIGITAL_PIN);
// Register all sensors to gw (they will be created as child devices)
gw.present(CHILD_ID_HUM, S_HUM);
gw.wait(DWELL_TIME);
gw.present(CHILD_ID_TEMP, S_TEMP);
gw.wait(DWELL_TIME);
metric = gw.getConfig().isMetric;
gw.wait(DWELL_TIME);
#endif
//
#ifdef LUX_ON
gw.present(CHILD_ID_LIGHT, S_LIGHT_LEVEL);
gw.wait(DWELL_TIME);
lightSensor.begin();
#endif
//
DEBUG_PRINTLN(F("Radio Setup Complete!"));
transmitRainData();
}
void loop()
{
gw.process();
if (state)
{
prettyFade(); // breathe if tripped
}
else
{
slowFlash(); // blink if not tripped
}
#ifdef DEBUG_ON // Serial Debug Block
if ( (millis() - dataMillis) >= serialInterval)
{
for (int i = 24; i <= 120; i = i + 24)
{
updateSerialData(i);
}
dataMillis = millis();
}
#endif
//
// let's constantly check to see if the rain in the past rainWindow hours is greater than rainSensorThreshold
//
int measure = 0; // Check to see if we need to show sensor tripped in this block
for (int i = 0; i < rainWindow; i++)
{
measure += rainBucket [i];
if (measure != lastMeasure)
{
// DEBUG_PRINT(F("measure value (total rainBucket within rainWindow): "));
// DEBUG_PRINTLN(measure);
lastMeasure = measure;
}
}
//
state = (measure >= (rainSensorThreshold * 100));
if (state != oldState)
{
gw.send(msgTripped.set(state));
gw.wait(DWELL_TIME);
DEBUG_PRINT(F("New Sensor State... Sensor: "));
DEBUG_PRINTLN(state ? "Tripped" : "Not Tripped");
oldState = state;
}
//
unsigned long tipDelay = millis() - lastRainTime;
if (wasTippedBuffer) // if was tipped, then update the 24hour total and transmit to Vera
{
DEBUG_PRINTLN(F("Sensor Tipped"));
DEBUG_PRINT(F("rainBucket [0] value: "));
DEBUG_PRINTLN(rainBucket [0]);
gw.send(msgRain.set((float)rainTotal(currentHour) / 100, 1)); //Calculate the total rain for the day
gw.wait(DWELL_TIME);
wasTippedBuffer--;
rainRate = ((oneHour) / tipDelay);
if (rainRate != lastRainRate)
{
gw.send(msgRainRate.set(rainRate, 1));
gw.wait(DWELL_TIME);
DEBUG_PRINT(F("RainRate= "));
DEBUG_PRINTLN(rainRate);
lastRainRate = rainRate;
}
lastRainTime = lastTipTime;
}
//
currentHour = hour();
if (currentHour != lastHour)
{
DEBUG_PRINTLN(F("One hour elapsed."));
gw.send(msgRain.set((float)rainTotal(currentHour) / 100, 1)); // send today's rainfall
gw.wait(DWELL_TIME);
gw.saveState(eepromIndex, highByte(rainBucket[0]));
gw.saveState(eepromIndex + 1, lowByte(rainBucket[0]));
DEBUG_PRINT(F("Saving rainBucket[0] to eeprom. rainBucket[0] = "));
DEBUG_PRINTLN(rainBucket[0]);
for (int i = RAIN_BUCKET_SIZE - 1; i >= 0; i--)//cascade an hour of values back into the array
{
rainBucket [i + 1] = rainBucket [i];
}
gw.request(CHILD_ID_TRIPPED_INDICATOR, V_VAR1);
gw.wait(DWELL_TIME);
gw.request(CHILD_ID_TRIPPED_INDICATOR, V_VAR2);
gw.wait(DWELL_TIME);
rainBucket[0] = 0;
eepromIndex = eepromIndex + 2;
if (eepromIndex > EEPROM_BUFFER_LOCATION + E_BUFFER_LENGTH)
{
eepromIndex = EEPROM_BUFFER_LOCATION;
}
DEBUG_PRINT(F("Writing to EEPROM. Index: "));
DEBUG_PRINTLN(eepromIndex);
gw.saveState(eepromIndex, 0xFE);
gw.saveState(eepromIndex + 1, 0xFE);
gw.requestTime(receiveTime); // sync the time every hour
gw.wait(DWELL_TIME);
transmitRainData();
rainRate = 0;
gw.send(msgRainRate.set(rainRate, 1));
gw.wait(DWELL_TIME);
DEBUG_PRINTLN(F("Sending rainRate is 0 to controller"));
lastHour = currentHour;
}
if (millis() - sensorPreviousMillis > DHT_LUX_DELAY)
{
#ifdef DHT_ON //DHT Code
doDHT();
#endif
#ifdef LUX_ON
doLUX();
#endif
sensorPreviousMillis = millis();
}
}
//
#ifdef DHT_ON
void doDHT(void)
{
float temperature = dht.getTemperature();
if (isnan(temperature))
{
DEBUG_PRINTLN(F("Failed reading temperature from DHT"));
} else if (temperature != lastTemp)
{
lastTemp = temperature;
if (!metric)
{
temperature = dht.toFahrenheit(temperature);
}
gw.send(msgTemp.set(temperature, 1));
gw.wait(DWELL_TIME);
DEBUG_PRINT(F("Temperature is: "));
DEBUG_PRINTLN(temperature);
}
float humidity = dht.getHumidity();;
if (isnan(humidity))
{
DEBUG_PRINTLN(F("Failed reading humidity from DHT"));
} else if (humidity != lastHum)
{
lastHum = humidity;
gw.send(msgHum.set(humidity, 1));
gw.wait(DWELL_TIME);
DEBUG_PRINT(F("Humidity is: "));
DEBUG_PRINTLN(humidity);
}
}
#endif
//
#ifdef LUX_ON
void doLUX(void)
{
unsigned int lux = lightSensor.readLightLevel();// Get Lux value
DEBUG_PRINT(F("Current LUX Level: "));
DEBUG_PRINTLN(lux);
heartbeat++;
if (lux != lastlux || heartbeat > 10)
{
gw.send(msg.set(lux));
lastlux = lux;
}
if (heartbeat > 10)
{
heartbeat = 0;
}
}
#endif
//
void sensorTipped()
{
unsigned long thisTipTime = millis();
if (thisTipTime - lastTipTime > 100UL)
{
rainBucket[0] += CALIBRATE_FACTOR; // adds CALIBRATE_FACTOR hundredths of unit each tip
wasTippedBuffer++;
}
lastTipTime = thisTipTime;
}
//
int rainTotal(int hours)
{
int total = 0;
for ( int i = 0; i <= hours; i++)
{
total += rainBucket [i];
}
return total;
}
void updateSerialData(int x)
{
DEBUG_PRINT(F("Rain last "));
DEBUG_PRINT(x);
DEBUG_PRINTLN(F(" hours: "));
float tipCount = 0;
for (int i = 0; i < x; i++)
{
tipCount = tipCount + rainBucket [i];
}
tipCount = tipCount / 100;
DEBUG_PRINTLN(tipCount);
}
void loadRainArray(int value) // retrieve stored rain array from EEPROM on powerup
{
for (int i = 0; i < RAIN_BUCKET_SIZE; i++)
{
value = value - 2;
if (value < EEPROM_BUFFER_LOCATION)
{
value = EEPROM_BUFFER_LOCATION + E_BUFFER_LENGTH;
}
DEBUG_PRINT(F("EEPROM location: "));
DEBUG_PRINTLN(value);
byte rainValueHigh = gw.loadState(value);
byte rainValueLow = gw.loadState(value + 1);
unsigned int rainValue = rainValueHigh << 8;
rainValue |= rainValueLow;
rainBucket[i + 1] = rainValue;
//
DEBUG_PRINT(F("rainBucket[ value: "));
DEBUG_PRINT(i + 1);
DEBUG_PRINT(F("] value: "));
DEBUG_PRINTLN(rainBucket[i + 1]);
}
}
void transmitRainData(void)
{
DEBUG_PRINT(F("In transmitRainData. currentHour = "));
DEBUG_PRINTLN(currentHour);
int rainUpdateTotal = 0;
for (int i = currentHour; i >= 0; i--)
{
rainUpdateTotal += rainBucket[i];
DEBUG_PRINT(F("Adding rainBucket["));
DEBUG_PRINT(i);
DEBUG_PRINTLN(F("] to rainUpdateTotal."));
}
DEBUG_PRINT(F("TX Day 1: rainUpdateTotal = "));
DEBUG_PRINTLN((float)rainUpdateTotal / 100.0);
gw.send(msgRainVAR1.set((float)rainUpdateTotal / 100.0, 1)); //Send current day rain totals (resets at midnight)
gw.wait(DWELL_TIME);
#ifdef USE_DAILY
rainUpdateTotal = 0;
#endif
for (int i = currentHour + 24; i > currentHour; i--)
{
rainUpdateTotal += rainBucket[i];
DEBUG_PRINT(F("Adding rainBucket["));
DEBUG_PRINT(i);
DEBUG_PRINTLN(F("] to rainUpdateTotal."));
}
DEBUG_PRINT(F("TX Day 2: rainUpdateTotal = "));
DEBUG_PRINTLN((float)rainUpdateTotal / 100.0);
gw.send(msgRainVAR2.set((float)rainUpdateTotal / 100.0, 1));
gw.wait(DWELL_TIME);
#ifdef USE_DAILY
rainUpdateTotal = 0;
#endif
for (int i = currentHour + 48; i > currentHour + 24; i--)
{
rainUpdateTotal += rainBucket[i];
DEBUG_PRINT(F("Adding rainBucket["));
DEBUG_PRINT(i);
DEBUG_PRINTLN(F("] to rainUpdateTotal."));
}
DEBUG_PRINT(F("TX Day 3: rainUpdateTotal = "));
DEBUG_PRINTLN((float)rainUpdateTotal / 100.0);
gw.send(msgRainVAR3.set((float)rainUpdateTotal / 100.0, 1));
gw.wait(DWELL_TIME);
#ifdef USE_DAILY
rainUpdateTotal = 0;
#endif
for (int i = currentHour + 72; i > currentHour + 48; i--)
{
rainUpdateTotal += rainBucket[i];
DEBUG_PRINT(F("Adding rainBucket["));
DEBUG_PRINT(i);
DEBUG_PRINTLN(F("] to rainUpdateTotal."));
}
DEBUG_PRINT(F("TX Day 4: rainUpdateTotal = "));
DEBUG_PRINTLN((float)rainUpdateTotal / 100.0);
gw.send(msgRainVAR4.set((float)rainUpdateTotal / 100.0, 1));
gw.wait(DWELL_TIME);
#ifdef USE_DAILY
rainUpdateTotal = 0;
#endif
for (int i = currentHour + 96; i > currentHour + 72; i--)
{
rainUpdateTotal += rainBucket[i];
DEBUG_PRINT(F("Adding rainBucket["));
DEBUG_PRINT(i);
DEBUG_PRINTLN(F("] to rainUpdateTotal."));
}
DEBUG_PRINT(F("TX Day 5: rainUpdateTotal = "));
DEBUG_PRINTLN((float)rainUpdateTotal / 100.0);
gw.send(msgRainVAR5.set((float)rainUpdateTotal / 100.0, 1));
gw.wait(DWELL_TIME);
}
void getVariables(const MyMessage &message)
{
if (message.sensor == CHILD_ID_RAIN_LOG)
{
// nothing to do here
}
else if (message.sensor == CHILD_ID_TRIPPED_INDICATOR)
{
if (message.type == V_VAR1)
{
rainWindow = atoi(message.data);
if (rainWindow > 120)
{
rainWindow = 120;
}
else if (rainWindow < 1)
{
rainWindow = 1;
}
if (rainWindow != atoi(message.data)) // if I changed the value back inside the boundries, push that number back to Vera
{
gw.send(msgTrippedVar1.set(rainWindow));
}
}
else if (message.type == V_VAR2)
{
rainSensorThreshold = atoi(message.data);
if (rainSensorThreshold > 10000)
{
rainSensorThreshold = 10000;
}
else if (rainSensorThreshold < 1)
{
rainSensorThreshold = 1;
}
if (rainSensorThreshold != atoi(message.data)) // if I changed the value back inside the boundries, push that number back to Vera
{
gw.send(msgTrippedVar2.set(rainSensorThreshold));
}
}
}
}
void prettyFade(void)
{
float val = (exp(sin(millis() / 2000.0 * PI)) - 0.36787944) * 108.0;
analogWrite(ledPin, val);
}
void slowFlash(void)
{
static boolean ledState = true;
static unsigned long pulseStart = millis();
if (millis() - pulseStart < 100UL)
{
digitalWrite(ledPin, !ledState);
pulseStart = millis();
}
}
void receiveTime(unsigned long time)
{
DEBUG_PRINTLN(F("Time received..."));
setTime(time);
char theTime[6];
sprintf(theTime, "%d:%2d", hour(), minute());
DEBUG_PRINTLN(theTime);
}
@@ -22,45 +22,67 @@
* Version 1.0 - Thomas Bowman Mørch
*
* DESCRIPTION
* Default sensor sketch for MySensor Sensebender Micro module
* Default sensor sketch for Sensebender Micro module
* Act as a temperature / humidity sensor by default.
*
* If A0 is held low while powering on, it will enter testmode, which verifies all on-board peripherals
*
* Battery voltage is repported as child sensorId 199, as well as battery percentage
*
* Battery voltage is as battery percentage (Internal message), and optionally as a sensor value (See defines below)
*
*
* Version 1.3 - Thomas Bowman Mørch
* Improved transmission logic, eliminating spurious transmissions (when temperatuere / humidity fluctuates 1 up and down between measurements)
*
* Added OTA boot mode, need to hold A1 low while applying power. (uses slightly more power as it's waiting for bootloader messages)
*
*/
#include <MySensor.h>
#include <Wire.h>
#include <SI7021.h>
#include <SPI.h>
#include <SPIFlash.h>
#include "utility/SPIFlash.h"
#include <EEPROM.h>
#include <sha204_lib_return_codes.h>
#include <sha204_library.h>
#include <RunningAverage.h>
//#include <avr/power.h>
// Define a static node address, remove if you want auto address assignment
//#define NODE_ADDRESS 3
// Uncomment the line below, to transmit battery voltage as a normal sensor value
//#define BATT_SENSOR 199
#define RELEASE "1.3"
#define AVERAGES 2
// Child sensor ID's
#define CHILD_ID_TEMP 1
#define CHILD_ID_HUM 2
#define CHILD_ID_BATT 199
// How many milli seconds between each measurement
#define MEASURE_INTERVAL 60000
// How many milli seconds should we wait for OTA?
#define OTA_WAIT_PERIOD 300
// FORCE_TRANSMIT_INTERVAL, this number of times of wakeup, the sensor is forced to report all values to the controller
#define FORCE_TRANSMIT_INTERVAL 30
// When MEASURE_INTERVAL is 60000 and FORCE_TRANSMIT_INTERVAL is 30, we force a transmission every 30 minutes.
// Between the forced transmissions a tranmission will only occur if the measured value differs from the previous measurement
//Pin definitions
// HUMI_TRANSMIT_THRESHOLD tells how much the humidity should have changed since last time it was transmitted. Likewise with
// TEMP_TRANSMIT_THRESHOLD for temperature threshold.
#define HUMI_TRANSMIT_THRESHOLD 0.5
#define TEMP_TRANSMIT_THRESHOLD 0.5
// Pin definitions
#define TEST_PIN A0
#define OTA_ENABLE A1
#define LED_PIN A2
#define ATSHA204_PIN 17 // A3
@@ -75,120 +97,209 @@ MySensor gw;
// Sensor messages
MyMessage msgHum(CHILD_ID_HUM, V_HUM);
MyMessage msgTemp(CHILD_ID_TEMP, V_TEMP);
MyMessage msgBattery(CHILD_ID_BATT, V_VOLTAGE);
#ifdef BATT_SENSOR
MyMessage msgBatt(BATT_SENSOR, V_VOLTAGE);
#endif
// Global settings
int measureCount = 0;
int sendBattery = 0;
boolean isMetric = true;
boolean highfreq = true;
boolean ota_enabled = false;
boolean transmission_occured = false;
// Storage of old measurements
float lastTemperature = -100;
int lastHumidity = -100;
long lastBattery = -100;
RunningAverage raHum(AVERAGES);
/****************************************************
*
* Setup code
*
****************************************************/
void setup() {
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
Serial.begin(115200);
Serial.print(F("Sensebender Micro FW "));
Serial.print(RELEASE);
Serial.flush();
// First check if we should boot into test mode
pinMode(TEST_PIN,INPUT);
digitalWrite(TEST_PIN, HIGH); // Enable pullup
if (!digitalRead(TEST_PIN)) testMode();
pinMode(OTA_ENABLE, INPUT);
digitalWrite(OTA_ENABLE, HIGH);
if (!digitalRead(OTA_ENABLE)) {
ota_enabled = true;
}
// Make sure that ATSHA204 is not floating
pinMode(ATSHA204_PIN, INPUT);
digitalWrite(ATSHA204_PIN, HIGH);
digitalWrite(TEST_PIN,LOW);
digitalWrite(LED_PIN, HIGH);
digitalWrite(OTA_ENABLE, LOW); // remove pullup, save some power.
digitalWrite(LED_PIN, HIGH);
#ifdef NODE_ADDRESS
gw.begin(NULL, NODE_ADDRESS, false);
#else
gw.begin(NULL,AUTO,false);
#endif
humiditySensor.begin();
digitalWrite(LED_PIN, LOW);
humiditySensor.begin();
gw.sendSketchInfo("MysensorMicro", "1.0");
Serial.flush();
Serial.println(F(" - Online!"));
gw.sendSketchInfo("Sensebender Micro", RELEASE);
gw.present(CHILD_ID_TEMP,S_TEMP);
gw.present(CHILD_ID_HUM,S_HUM);
gw.present(CHILD_ID_BATT, S_POWER);
switchClock(1<<CLKPS2); // Switch to 1Mhz for the reminder of the sketch, save power.
#ifdef BATT_SENSOR
gw.present(BATT_SENSOR, S_POWER);
#endif
isMetric = gw.getConfig().isMetric;
Serial.print(F("isMetric: ")); Serial.println(isMetric);
raHum.clear();
sendTempHumidityMeasurements(false);
sendBattLevel(false);
if (ota_enabled) Serial.println("OTA FW update enabled");
}
// Main loop function
/***********************************************
*
* Main loop function
*
***********************************************/
void loop() {
measureCount ++;
bool forceTransmit = false;
if (measureCount > FORCE_TRANSMIT_INTERVAL
) { // force a transmission
measureCount ++;
sendBattery ++;
bool forceTransmit = false;
transmission_occured = false;
if ((measureCount == 5) && highfreq)
{
if (!ota_enabled) clock_prescale_set(clock_div_8); // Switch to 1Mhz for the reminder of the sketch, save power.
highfreq = false;
}
if (measureCount > FORCE_TRANSMIT_INTERVAL) { // force a transmission
forceTransmit = true;
measureCount = 0;
}
gw.process();
sendBattLevel(forceTransmit);
sendTempHumidityMeasurements(forceTransmit);
if (sendBattery > 60)
{
sendBattLevel(forceTransmit); // Not needed to send battery info that often
sendBattery = 0;
}
if (ota_enabled & transmission_occured) {
gw.wait(OTA_WAIT_PERIOD);
}
gw.sleep(MEASURE_INTERVAL);
}
/*
/*********************************************
*
* Sends temperature and humidity from Si7021 sensor
*
* Parameters
* - force : Forces transmission of a value (even if it's the same as previous measurement)
*/
*
*********************************************/
void sendTempHumidityMeasurements(bool force)
{
if (force) {
lastHumidity = -100;
lastTemperature = -100;
}
bool tx = force;
si7021_env data = humiditySensor.getHumidityAndTemperature();
float temperature = data.celsiusHundredths/100;
int humidity = data.humidityPercent;
raHum.addValue(data.humidityPercent);
float diffTemp = abs(lastTemperature - (isMetric ? data.celsiusHundredths : data.fahrenheitHundredths)/100);
float diffHum = abs(lastHumidity - raHum.getAverage());
if (lastTemperature != temperature) {
Serial.print(F("TempDiff :"));Serial.println(diffTemp);
Serial.print(F("HumDiff :"));Serial.println(diffHum);
if (isnan(diffHum)) tx = true;
if (diffTemp > TEMP_TRANSMIT_THRESHOLD) tx = true;
if (diffHum > HUMI_TRANSMIT_THRESHOLD) tx = true;
if (tx) {
measureCount = 0;
float temperature = (isMetric ? data.celsiusHundredths : data.fahrenheitHundredths) / 100.0;
int humidity = data.humidityPercent;
Serial.print("T: ");Serial.println(temperature);
Serial.print("H: ");Serial.println(humidity);
gw.send(msgTemp.set(temperature,1));
lastTemperature = temperature;
}
if (lastHumidity != humidity) {
gw.send(msgHum.set(humidity));
lastTemperature = temperature;
lastHumidity = humidity;
transmission_occured = true;
}
}
/*
* Sends battery information (both voltage, and battery percentage)
/********************************************
*
* Sends battery information (battery percentage)
*
* Parameters
* - force : Forces transmission of a value
*/
*
*******************************************/
void sendBattLevel(bool force)
{
if (force) lastBattery = -1;
long vcc = readVcc();
if (vcc != lastBattery) {
lastBattery = vcc;
#ifdef BATT_SENSOR
gw.send(msgBatt.set(vcc));
#endif
// Calculate percentage
gw.send(msgBattery.set(vcc));
vcc = vcc - 1900; // subtract 1.9V from vcc, as this is the lowest voltage we will operate at
long percent = vcc / 14;
long percent = vcc / 14.0;
gw.sendBatteryLevel(percent);
transmission_occured = true;
}
}
/*******************************************
*
* Internal battery ADC measuring
*
*******************************************/
long readVcc() {
// Read 1.1V reference against AVcc
// set the reference to Vcc and the measurement to the internal 1.1V reference
@@ -213,19 +324,14 @@ long readVcc() {
result = 1125300L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000
return result; // Vcc in millivolts
}
void switchClock(unsigned char clk)
{
cli();
CLKPR = 1<<CLKPCE; // Set CLKPCE to enable clk switching
CLKPR = clk;
sei();
}
// Verify all peripherals, and signal via the LED if any problems.
/****************************************************
*
* Verify all peripherals, and signal via the LED if any problems.
*
****************************************************/
void testMode()
{
uint8_t rx_buffer[SHA204_RSP_SIZE_MAX];
@@ -233,7 +339,7 @@ void testMode()
byte tests = 0;
digitalWrite(LED_PIN, HIGH); // Turn on LED.
Serial.println(F(" - TestMode"));
Serial.println(F("Testing peripherals!"));
Serial.flush();
Serial.print(F("-> SI7021 : "));
@@ -305,7 +411,7 @@ void testMode()
while (1) // Blink OK pattern!
{
digitalWrite(LED_PIN, HIGH);
delay(800);
delay(200);
digitalWrite(LED_PIN, LOW);
delay(200);
}
@@ -315,10 +421,7 @@ void testMode()
Serial.println(F("----> Selftest failed!"));
while (1) // Blink FAILED pattern! Rappidly blinking..
{
digitalWrite(LED_PIN, HIGH);
delay(100);
digitalWrite(LED_PIN, LOW);
delay(100);
}
}
}
+16 -16
View File
@@ -5,7 +5,7 @@
// Feed this function the Arduino-ized pin number you want to assign to the ATSHA204's SDA pin
// This will find the DDRX, PORTX, and PINX registrs it'll need to point to to control that pin
// As well as the bit value for each of those registers
atsha204Class::atsha204Class(uint8_t pin)
ATSHA204Class::ATSHA204Class(uint8_t pin)
{
device_pin = digitalPinToBitMask(pin); // Find the bit value of the pin
uint8_t port = digitalPinToPort(pin); // temoporarily used to get the next three registers
@@ -18,7 +18,7 @@ atsha204Class::atsha204Class(uint8_t pin)
device_port_IN = portInputRegister(port);
}
void atsha204Class::getSerialNumber(uint8_t * response)
void ATSHA204Class::getSerialNumber(uint8_t * response)
{
uint8_t readCommand[READ_COUNT];
uint8_t readResponse[READ_4_RSP_SIZE];
@@ -48,7 +48,7 @@ void atsha204Class::getSerialNumber(uint8_t * response)
/* SWI bit bang functions */
void atsha204Class::swi_set_signal_pin(uint8_t is_high)
void ATSHA204Class::swi_set_signal_pin(uint8_t is_high)
{
*device_port_DDR |= device_pin;
@@ -58,7 +58,7 @@ void atsha204Class::swi_set_signal_pin(uint8_t is_high)
*device_port_OUT &= ~device_pin;
}
uint8_t atsha204Class::swi_send_bytes(uint8_t count, uint8_t *buffer)
uint8_t ATSHA204Class::swi_send_bytes(uint8_t count, uint8_t *buffer)
{
uint8_t i, bit_mask;
@@ -101,12 +101,12 @@ uint8_t atsha204Class::swi_send_bytes(uint8_t count, uint8_t *buffer)
return SWI_FUNCTION_RETCODE_SUCCESS;
}
uint8_t atsha204Class::swi_send_byte(uint8_t value)
uint8_t ATSHA204Class::swi_send_byte(uint8_t value)
{
return swi_send_bytes(1, &value);
}
uint8_t atsha204Class::swi_receive_bytes(uint8_t count, uint8_t *buffer)
uint8_t ATSHA204Class::swi_receive_bytes(uint8_t count, uint8_t *buffer)
{
uint8_t status = SWI_FUNCTION_RETCODE_SUCCESS;
uint8_t i;
@@ -212,12 +212,12 @@ uint8_t atsha204Class::swi_receive_bytes(uint8_t count, uint8_t *buffer)
/* Physical functions */
void atsha204Class::sha204c_sleep()
void ATSHA204Class::sha204c_sleep()
{
swi_send_byte(SHA204_SWI_FLAG_SLEEP);
}
uint8_t atsha204Class::sha204p_receive_response(uint8_t size, uint8_t *response)
uint8_t ATSHA204Class::sha204p_receive_response(uint8_t size, uint8_t *response)
{
uint8_t count_byte;
uint8_t i;
@@ -249,7 +249,7 @@ uint8_t atsha204Class::sha204p_receive_response(uint8_t size, uint8_t *response)
/* Communication functions */
uint8_t atsha204Class::sha204c_wakeup(uint8_t *response)
uint8_t ATSHA204Class::sha204c_wakeup(uint8_t *response)
{
swi_set_signal_pin(0);
delayMicroseconds(10*SHA204_WAKEUP_PULSE_WIDTH);
@@ -277,7 +277,7 @@ uint8_t atsha204Class::sha204c_wakeup(uint8_t *response)
return ret_code;
}
uint8_t atsha204Class::sha204c_resync(uint8_t size, uint8_t *response)
uint8_t ATSHA204Class::sha204c_resync(uint8_t size, uint8_t *response)
{
// Try to re-synchronize without sending a Wake token
// (step 1 of the re-synchronization process).
@@ -298,7 +298,7 @@ uint8_t atsha204Class::sha204c_resync(uint8_t size, uint8_t *response)
return (ret_code == SHA204_SUCCESS ? SHA204_RESYNC_WITH_WAKEUP : ret_code);
}
uint8_t atsha204Class::sha204c_send_and_receive(uint8_t *tx_buffer, uint8_t rx_size, uint8_t *rx_buffer, uint8_t execution_delay, uint8_t execution_timeout)
uint8_t ATSHA204Class::sha204c_send_and_receive(uint8_t *tx_buffer, uint8_t rx_size, uint8_t *rx_buffer, uint8_t execution_delay, uint8_t execution_timeout)
{
uint8_t ret_code = SHA204_FUNC_FAIL;
uint8_t ret_code_resync;
@@ -440,7 +440,7 @@ uint8_t atsha204Class::sha204c_send_and_receive(uint8_t *tx_buffer, uint8_t rx_s
/* Marshaling functions */
uint8_t atsha204Class::sha204m_read(uint8_t *tx_buffer, uint8_t *rx_buffer, uint8_t zone, uint16_t address)
uint8_t ATSHA204Class::sha204m_read(uint8_t *tx_buffer, uint8_t *rx_buffer, uint8_t zone, uint16_t address)
{
uint8_t rx_size;
@@ -457,7 +457,7 @@ uint8_t atsha204Class::sha204m_read(uint8_t *tx_buffer, uint8_t *rx_buffer, uint
return sha204c_send_and_receive(&tx_buffer[0], rx_size, &rx_buffer[0], READ_DELAY, READ_EXEC_MAX - READ_DELAY);
}
uint8_t atsha204Class::sha204m_execute(uint8_t op_code, uint8_t param1, uint16_t param2,
uint8_t ATSHA204Class::sha204m_execute(uint8_t op_code, uint8_t param1, uint16_t param2,
uint8_t datalen1, uint8_t *data1, uint8_t tx_size, uint8_t *tx_buffer, uint8_t rx_size, uint8_t *rx_buffer)
{
uint8_t poll_delay, poll_timeout, response_size;
@@ -534,7 +534,7 @@ uint8_t atsha204Class::sha204m_execute(uint8_t op_code, uint8_t param1, uint16_t
/* CRC Calculator and Checker */
void atsha204Class::sha204c_calculate_crc(uint8_t length, uint8_t *data, uint8_t *crc)
void ATSHA204Class::sha204c_calculate_crc(uint8_t length, uint8_t *data, uint8_t *crc)
{
uint8_t counter;
uint16_t crc_register = 0;
@@ -560,7 +560,7 @@ void atsha204Class::sha204c_calculate_crc(uint8_t length, uint8_t *data, uint8_t
crc[1] = (uint8_t) (crc_register >> 8);
}
uint8_t atsha204Class::sha204c_check_crc(uint8_t *response)
uint8_t ATSHA204Class::sha204c_check_crc(uint8_t *response)
{
uint8_t crc[SHA204_CRC_SIZE];
uint8_t count = response[SHA204_BUFFER_POS_COUNT];
@@ -570,4 +570,4 @@ uint8_t atsha204Class::sha204c_check_crc(uint8_t *response)
return (crc[0] == response[count] && crc[1] == response[count + 1])
? SHA204_SUCCESS : SHA204_BAD_CRC;
}
}
+2 -2
View File
@@ -220,7 +220,7 @@
#define SHA204_SERIAL_SZ 9 // The number of bytes the serial number consists of
class atsha204Class
class ATSHA204Class
{
private:
uint8_t device_pin;
@@ -237,7 +237,7 @@ private:
uint8_t sha204c_resync(uint8_t size, uint8_t *response);
uint8_t sha204c_send_and_receive(uint8_t *tx_buffer, uint8_t rx_size, uint8_t *rx_buffer, uint8_t execution_delay, uint8_t execution_timeout);
public:
atsha204Class(uint8_t pin); // Constructor
ATSHA204Class(uint8_t pin); // Constructor
void sha204c_sleep();
uint8_t sha204m_execute(uint8_t op_code, uint8_t param1, uint16_t param2,
uint8_t datalen1, uint8_t *data1, uint8_t tx_size, uint8_t *tx_buffer, uint8_t rx_size, uint8_t *rx_buffer);
+8 -2
View File
@@ -36,13 +36,19 @@
#define RF69_MAX_DATA_LEN 61 // to take advantage of the built in AES/CRC we want to limit the frame size to the internal FIFO size (66 bytes - 3 bytes overhead)
#define RF69_SPI_CS SS // SS is the SPI slave select pin, for instance D10 on atmega328
// INT0 on AVRs should be connected to RFM69's DIO0 (ex on Atmega328 it's D2, on Atmega644/1284 it's D2)
#if defined(__AVR_ATmega168__) || defined(__AVR_ATmega328P__) || defined(__AVR_ATmega88) || defined(__AVR_ATmega8__) || defined(__AVR_ATmega88__) || defined(__AVR_ATmega32U4__)
// INT0 on AVRs should be connected to RFM69's DIO0 (ex on ATmega328 it's D2, on ATmega644/1284 it's D2)
#if defined(__AVR_ATmega168__) || defined(__AVR_ATmega328P__) || defined(__AVR_ATmega88) || defined(__AVR_ATmega8__) || defined(__AVR_ATmega88__)
#define RF69_IRQ_PIN 2
#define RF69_IRQ_NUM 0
#elif defined(__AVR_ATmega644P__) || defined(__AVR_ATmega1284P__)
#define RF69_IRQ_PIN 2
#define RF69_IRQ_NUM 2
#elif defined(__AVR_ATmega32U4__)
#define RF69_IRQ_PIN 3
#define RF69_IRQ_NUM 0
#else
#define RF69_IRQ_PIN 2
#define RF69_IRQ_NUM 0
#endif
#define CSMA_LIMIT -90 // upper RX signal sensitivity threshold in dBm for carrier sense access
+94 -47
View File
@@ -1,20 +1,40 @@
/*
* Copyright (c) 2013 by Felix Rusu <felix@lowpowerlab.com>
* SPI Flash memory library for arduino/moteino.
* This works with 256byte/page SPI flash memory
* For instance a 4MBit (512Kbyte) flash chip will have 2048 pages: 256*2048 = 524288 bytes (512Kbytes)
* Minimal modifications should allow chips that have different page size but modifications
* DEPENDS ON: Arduino SPI library
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of either the GNU General Public License version 2
* or the GNU Lesser General Public License version 2.1, both as
* published by the Free Software Foundation.
*/
// Copyright (c) 2013-2015 by Felix Rusu, LowPowerLab.com
// SPI Flash memory library for arduino/moteino.
// This works with 256byte/page SPI flash memory
// For instance a 4MBit (512Kbyte) flash chip will have 2048 pages: 256*2048 = 524288 bytes (512Kbytes)
// Minimal modifications should allow chips that have different page size but modifications
// DEPENDS ON: Arduino SPI library
// > Updated Jan. 5, 2015, TomWS1, modified writeBytes to allow blocks > 256 bytes and handle page misalignment.
// > Updated Feb. 26, 2015 TomWS1, added support for SPI Transactions (Arduino 1.5.8 and above)
// > Selective merge by Felix after testing in IDE 1.0.6, 1.6.4
// **********************************************************************************
// License
// **********************************************************************************
// 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 <http://www.gnu.org/licenses/>.
//
// Licence can be viewed at
// http://www.gnu.org/licenses/gpl-3.0.txt
//
// Please maintain this license information along with authorship
// and copyright notices in any redistribution of this code
#include <SPIFlash.h>
byte SPIFlash::UNIQUEID[8];
uint8_t SPIFlash::UNIQUEID[8];
/// IMPORTANT: NAND FLASH memory requires erase before write, because
/// it can only transition from 1s to 0s and only the erase command can reset all 0s to 1s
@@ -32,15 +52,22 @@ SPIFlash::SPIFlash(uint8_t slaveSelectPin, uint16_t jedecID) {
/// Select the flash chip
void SPIFlash::select() {
noInterrupts();
//save current SPI settings
#ifndef SPI_HAS_TRANSACTION
noInterrupts();
#endif
_SPCR = SPCR;
_SPSR = SPSR;
//set FLASH chip SPI settings
#ifdef SPI_HAS_TRANSACTION
SPI.beginTransaction(_settings);
#else
// set FLASH SPI settings
SPI.setDataMode(SPI_MODE0);
SPI.setBitOrder(MSBFIRST);
SPI.setClockDivider(SPI_CLOCK_DIV4); //decided to slow down from DIV2 after SPI stalling in some instances, especially visible on mega1284p when RFM69 and FLASH chip both present
SPI.begin();
#endif
digitalWrite(_slaveSelectPin, LOW);
}
@@ -48,9 +75,13 @@ void SPIFlash::select() {
void SPIFlash::unselect() {
digitalWrite(_slaveSelectPin, HIGH);
//restore SPI settings to what they were before talking to the FLASH chip
#ifdef SPI_HAS_TRANSACTION
SPI.endTransaction();
#else
interrupts();
#endif
SPCR = _SPCR;
SPSR = _SPSR;
interrupts();
}
/// setup SPI, read device ID etc...
@@ -59,6 +90,10 @@ boolean SPIFlash::initialize()
_SPCR = SPCR;
_SPSR = SPSR;
pinMode(_slaveSelectPin, OUTPUT);
#ifdef SPI_HAS_TRANSACTION
_settings = SPISettings(4000000, MSBFIRST, SPI_MODE0);
#endif
unselect();
wakeup();
@@ -72,7 +107,7 @@ boolean SPIFlash::initialize()
}
/// Get the manufacturer and device ID bytes (as a short word)
word SPIFlash::readDeviceId()
uint16_t SPIFlash::readDeviceId()
{
#if defined(__AVR_ATmega32U4__) // Arduino Leonardo, MoteinoLeo
command(SPIFLASH_IDREAD); // Read JEDEC ID
@@ -80,7 +115,7 @@ word SPIFlash::readDeviceId()
select();
SPI.transfer(SPIFLASH_IDREAD);
#endif
word jedecid = SPI.transfer(0) << 8;
uint16_t jedecid = SPI.transfer(0) << 8;
jedecid |= SPI.transfer(0);
unselect();
return jedecid;
@@ -89,47 +124,47 @@ word SPIFlash::readDeviceId()
/// Get the 64 bit unique identifier, stores it in UNIQUEID[8]. Only needs to be called once, ie after initialize
/// Returns the byte pointer to the UNIQUEID byte array
/// Read UNIQUEID like this:
/// flash.readUniqueId(); for (byte i=0;i<8;i++) { Serial.print(flash.UNIQUEID[i], HEX); Serial.print(' '); }
/// flash.readUniqueId(); for (uint8_t i=0;i<8;i++) { Serial.print(flash.UNIQUEID[i], HEX); Serial.print(' '); }
/// or like this:
/// flash.readUniqueId(); byte* MAC = flash.readUniqueId(); for (byte i=0;i<8;i++) { Serial.print(MAC[i], HEX); Serial.print(' '); }
byte* SPIFlash::readUniqueId()
/// flash.readUniqueId(); uint8_t* MAC = flash.readUniqueId(); for (uint8_t i=0;i<8;i++) { Serial.print(MAC[i], HEX); Serial.print(' '); }
uint8_t* SPIFlash::readUniqueId()
{
command(SPIFLASH_MACREAD);
SPI.transfer(0);
SPI.transfer(0);
SPI.transfer(0);
SPI.transfer(0);
for (byte i=0;i<8;i++)
for (uint8_t i=0;i<8;i++)
UNIQUEID[i] = SPI.transfer(0);
unselect();
return UNIQUEID;
}
/// read 1 byte from flash memory
byte SPIFlash::readByte(long addr) {
uint8_t SPIFlash::readByte(uint32_t addr) {
command(SPIFLASH_ARRAYREADLOWFREQ);
SPI.transfer(addr >> 16);
SPI.transfer(addr >> 8);
SPI.transfer(addr);
byte result = SPI.transfer(0);
uint8_t result = SPI.transfer(0);
unselect();
return result;
}
/// read unlimited # of bytes
void SPIFlash::readBytes(long addr, void* buf, word len) {
void SPIFlash::readBytes(uint32_t addr, void* buf, uint16_t len) {
command(SPIFLASH_ARRAYREAD);
SPI.transfer(addr >> 16);
SPI.transfer(addr >> 8);
SPI.transfer(addr);
SPI.transfer(0); //"dont care"
for (word i = 0; i < len; ++i)
((byte*) buf)[i] = SPI.transfer(0);
for (uint16_t i = 0; i < len; ++i)
((uint8_t*) buf)[i] = SPI.transfer(0);
unselect();
}
/// Send a command to the flash chip, pass TRUE for isWrite when its a write command
void SPIFlash::command(byte cmd, boolean isWrite){
void SPIFlash::command(uint8_t cmd, boolean isWrite){
#if defined(__AVR_ATmega32U4__) // Arduino Leonardo, MoteinoLeo
DDRB |= B00000001; // Make sure the SS pin (PB0 - used by RFM12B on MoteinoLeo R1) is set as output HIGH!
PORTB |= B00000001;
@@ -155,7 +190,7 @@ boolean SPIFlash::busy()
/*
select();
SPI.transfer(SPIFLASH_STATUSREAD);
byte status = SPI.transfer(0);
uint8_t status = SPI.transfer(0);
unselect();
return status & 1;
*/
@@ -163,11 +198,11 @@ boolean SPIFlash::busy()
}
/// return the STATUS register
byte SPIFlash::readStatus()
uint8_t SPIFlash::readStatus()
{
select();
SPI.transfer(SPIFLASH_STATUSREAD);
byte status = SPI.transfer(0);
uint8_t status = SPI.transfer(0);
unselect();
return status;
}
@@ -176,7 +211,7 @@ byte SPIFlash::readStatus()
/// Write 1 byte to flash memory
/// WARNING: you can only write to previously erased memory locations (see datasheet)
/// use the block erase commands to first clear memory (write 0xFFs)
void SPIFlash::writeByte(long addr, uint8_t byt) {
void SPIFlash::writeByte(uint32_t addr, uint8_t byt) {
command(SPIFLASH_BYTEPAGEPROGRAM, true); // Byte/Page Program
SPI.transfer(addr >> 16);
SPI.transfer(addr >> 8);
@@ -185,20 +220,32 @@ void SPIFlash::writeByte(long addr, uint8_t byt) {
unselect();
}
/// write 1-256 bytes to flash memory
/// write multiple bytes to flash memory (up to 64K)
/// WARNING: you can only write to previously erased memory locations (see datasheet)
/// use the block erase commands to first clear memory (write 0xFFs)
/// WARNING: if you write beyond a page boundary (or more than 256bytes),
/// the bytes will wrap around and start overwriting at the beginning of that same page
/// see datasheet for more details
void SPIFlash::writeBytes(long addr, const void* buf, uint16_t len) {
command(SPIFLASH_BYTEPAGEPROGRAM, true); // Byte/Page Program
SPI.transfer(addr >> 16);
SPI.transfer(addr >> 8);
SPI.transfer(addr);
for (uint16_t i = 0; i < len; i++)
SPI.transfer(((byte*) buf)[i]);
unselect();
/// This version handles both page alignment and data blocks larger than 256 bytes.
///
void SPIFlash::writeBytes(uint32_t addr, const void* buf, uint16_t len) {
uint16_t n;
uint16_t maxBytes = 256-(addr%256); // force the first set of bytes to stay within the first page
uint16_t offset = 0;
while (len>0)
{
n = (len<=maxBytes) ? len : maxBytes;
command(SPIFLASH_BYTEPAGEPROGRAM, true); // Byte/Page Program
SPI.transfer(addr >> 16);
SPI.transfer(addr >> 8);
SPI.transfer(addr);
for (uint16_t i = 0; i < n; i++)
SPI.transfer(((uint8_t*) buf)[offset + i]);
unselect();
addr+=n; // adjust the addresses and remaining bytes by what we've just transferred.
offset +=n;
len -= n;
maxBytes = 256; // now we can do up to 256 bytes per loop
}
}
/// erase entire flash memory array
@@ -213,7 +260,7 @@ void SPIFlash::chipErase() {
}
/// erase a 4Kbyte block
void SPIFlash::blockErase4K(long addr) {
void SPIFlash::blockErase4K(uint32_t addr) {
command(SPIFLASH_BLOCKERASE_4K, true); // Block Erase
SPI.transfer(addr >> 16);
SPI.transfer(addr >> 8);
@@ -222,7 +269,7 @@ void SPIFlash::blockErase4K(long addr) {
}
/// erase a 32Kbyte block
void SPIFlash::blockErase32K(long addr) {
void SPIFlash::blockErase32K(uint32_t addr) {
command(SPIFLASH_BLOCKERASE_32K, true); // Block Erase
SPI.transfer(addr >> 16);
SPI.transfer(addr >> 8);
+64 -41
View File
@@ -1,16 +1,36 @@
/*
* Copyright (c) 2013 by Felix Rusu <felix@lowpowerlab.com>
* SPI Flash memory library for arduino/moteino.
* This works with 256byte/page SPI flash memory
* For instance a 4MBit (512Kbyte) flash chip will have 2048 pages: 256*2048 = 524288 bytes (512Kbytes)
* Minimal modifications should allow chips that have different page size but modifications
* DEPENDS ON: Arduino SPI library
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of either the GNU General Public License version 2
* or the GNU Lesser General Public License version 2.1, both as
* published by the Free Software Foundation.
*/
// Copyright (c) 2013-2015 by Felix Rusu, LowPowerLab.com
// SPI Flash memory library for arduino/moteino.
// This works with 256byte/page SPI flash memory
// For instance a 4MBit (512Kbyte) flash chip will have 2048 pages: 256*2048 = 524288 bytes (512Kbytes)
// Minimal modifications should allow chips that have different page size but modifications
// DEPENDS ON: Arduino SPI library
// > Updated Jan. 5, 2015, TomWS1, modified writeBytes to allow blocks > 256 bytes and handle page misalignment.
// > Updated Feb. 26, 2015 TomWS1, added support for SPI Transactions (Arduino 1.5.8 and above)
// > Selective merge by Felix after testing in IDE 1.0.6, 1.6.4
// **********************************************************************************
// License
// **********************************************************************************
// 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 <http://www.gnu.org/licenses/>.
//
// Licence can be viewed at
// http://www.gnu.org/licenses/gpl-3.0.txt
//
// Please maintain this license information along with authorship
// and copyright notices in any redistribution of this code
#ifndef _SPIFLASH_H_
#define _SPIFLASH_H_
@@ -33,19 +53,19 @@
/// Assuming the WP pin is pulled up (to disable hardware write protection)
/// To use any write commands the WEL bit in the status register must be set to 1.
/// This is accomplished by sending a 0x06 command before any such write/erase command.
/// The WEL bit in the status register resets to the logical “0” state after a
/// device power-up or reset. In addition, the WEL bit will be reset to the logical “0” state automatically under the following conditions:
/// • Write Disable operation completes successfully
/// • Write Status Register operation completes successfully or aborts
/// • Protect Sector operation completes successfully or aborts
/// • Unprotect Sector operation completes successfully or aborts
/// • Byte/Page Program operation completes successfully or aborts
/// • Sequential Program Mode reaches highest unprotected memory location
/// • Sequential Program Mode reaches the end of the memory array
/// • Sequential Program Mode aborts
/// • Block Erase operation completes successfully or aborts
/// • Chip Erase operation completes successfully or aborts
/// • Hold condition aborts
/// The WEL bit in the status register resets to the logical ?0? state after a
/// device power-up or reset. In addition, the WEL bit will be reset to the logical ?0? state automatically under the following conditions:
/// ? Write Disable operation completes successfully
/// ? Write Status Register operation completes successfully or aborts
/// ? Protect Sector operation completes successfully or aborts
/// ? Unprotect Sector operation completes successfully or aborts
/// ? Byte/Page Program operation completes successfully or aborts
/// ? Sequential Program Mode reaches highest unprotected memory location
/// ? Sequential Program Mode reaches the end of the memory array
/// ? Sequential Program Mode aborts
/// ? Block Erase operation completes successfully or aborts
/// ? Chip Erase operation completes successfully or aborts
/// ? Hold condition aborts
#define SPIFLASH_WRITEENABLE 0x06 // write enable
#define SPIFLASH_WRITEDISABLE 0x04 // write disable
@@ -69,21 +89,21 @@
class SPIFlash {
public:
static byte UNIQUEID[8];
SPIFlash(byte slaveSelectPin, uint16_t jedecID=0);
static uint8_t UNIQUEID[8];
SPIFlash(uint8_t slaveSelectPin, uint16_t jedecID=0);
boolean initialize();
void command(byte cmd, boolean isWrite=false);
byte readStatus();
byte readByte(long addr);
void readBytes(long addr, void* buf, word len);
void writeByte(long addr, byte byt);
void writeBytes(long addr, const void* buf, uint16_t len);
void command(uint8_t cmd, boolean isWrite=false);
uint8_t readStatus();
uint8_t readByte(uint32_t addr);
void readBytes(uint32_t addr, void* buf, uint16_t len);
void writeByte(uint32_t addr, uint8_t byt);
void writeBytes(uint32_t addr, const void* buf, uint16_t len);
boolean busy();
void chipErase();
void blockErase4K(long address);
void blockErase32K(long address);
word readDeviceId();
byte* readUniqueId();
void blockErase4K(uint32_t address);
void blockErase32K(uint32_t address);
uint16_t readDeviceId();
uint8_t* readUniqueId();
void sleep();
void wakeup();
@@ -91,10 +111,13 @@ public:
protected:
void select();
void unselect();
byte _slaveSelectPin;
uint8_t _slaveSelectPin;
uint16_t _jedecID;
byte _SPCR;
byte _SPSR;
uint8_t _SPCR;
uint8_t _SPSR;
#ifdef SPI_HAS_TRANSACTION
SPISettings _settings;
#endif
};
#endif