diff --git a/Examples/IOShield/IOShield.ino b/Examples/IOShield/IOShield.ino new file mode 100644 index 0000000..4580752 --- /dev/null +++ b/Examples/IOShield/IOShield.ino @@ -0,0 +1,345 @@ +// ********************************************************************************** +// IOShield sample sketch works with Moteinos equipped with RFM69W/RFM69HW +// Can be adapted to use Moteinos/Arduinos using other RFM69 variants (RFM69CW, RFM69HCW) or even RFM12b +// http://moteino.com +// http://www.LowPowerLab.com/IOShield +// 2015-03-13 (C) Felix Rusu of Low Power Lab LLC +// ********************************************************************************** +// It works with IOShield(s) that have 2 shift registers (74HC595) +// You can daisy chain up to 16 IOShields for a total of 256 outputs/stations/zones +// If you chain more than 2 IOShields be sure to adjust the REGISTERCOUNT setting below in the define section +// Also you must adjust the radio settings: frequency/ HW setting, encryption key etc below in the define section +// It listens for tokens like: +// - 'ON:N' where N={1..16} - turns ON one output dictated by position N +// - 'ALL' - turns ON all outputs +// - 'OFF' turns off all outputs +// - 'PRG A:n ... Z:m' - runs a program in sequence, first token is station/zone/output number, second is the number of seconds to turn it ON for +// WARNING: there is no delay between switching zones, so beware of the frequency you switch the valves on/off +// ********************************************************************************** +// 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 . +// +// 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 //get it here: http://github.com/lowpowerlab/rfm69 +#include //get it here: http://github.com/lowpowerlab/spiflash +#include //get it here: https://github.com/LowPowerLab/WirelessProgramming +#include //comes with Arduino IDE (www.arduino.cc) +//***************************************************************************************************************************** +// ADJUST THE SETTINGS BELOW DEPENDING ON YOUR HARDWARE/SITUATION! +//***************************************************************************************************************************** +#define GATEWAYID 1 +#define NODEID 121 +#define NETWORKID 250 +#define FREQUENCY RF69_915MHZ //Match this with the version of your Moteino! (others: RF69_433MHZ, RF69_868MHZ) +#define ENCRYPTKEY "sampleEncryptKey" //has to be same 16 characters/bytes on all nodes, not more not less! +//#define IS_RFM69HW //uncomment only for RFM69HW! Leave out if you have RFM69W! + +#define LATCHPIN 5 +#define CLOCKPIN 6 +#define DATAPIN 7 +#define REGISTERCOUNT 2 //Moteino IOShield has 2 daisy chained registers, if you have more adjust this number +//***************************************************************************************************************************** +#define LED 9 //pin connected to onboard LED +#define SERIAL_BAUD 115200 +#define SERIAL_EN //comment out if you don't want any serial output + +#ifdef SERIAL_EN + #define DEBUG(input) Serial.print(input) + #define DEBUGln(input) Serial.println(input) +#else + #define DEBUG(input) + #define DEBUGln(input) +#endif + +RFM69 radio; +///////////////////////////////////////////////////////////////////////////// +// flash(SPI_CS, MANUFACTURER_ID) +// SPI_CS - CS pin attached to SPI flash chip (8 in case of Moteino) +// MANUFACTURER_ID - OPTIONAL, 0xEF30 for windbond 4mbit flash (Moteino OEM) +///////////////////////////////////////////////////////////////////////////// +SPIFlash flash(8, 0xEF30); //regular Moteinos have FLASH MEM on D8, MEGA has it on D4 +char buff[20]; //max radio DATA length = 61 +String str; +String substr; + +void setup(void) +{ + Serial.begin(SERIAL_BAUD); + pinMode(LATCHPIN, OUTPUT); + pinMode(DATAPIN, OUTPUT); + pinMode(CLOCKPIN, OUTPUT); + pinMode(LED, OUTPUT); + + radio.initialize(FREQUENCY,NODEID,NETWORKID); +#ifdef IS_RFM69HW + radio.setHighPower(); //uncomment only for RFM69HW! +#endif + radio.encrypt(ENCRYPTKEY); + + //sprintf(buff, "IOShield : %d Mhz...", FREQUENCY==RF69_433MHZ ? 433 : FREQUENCY==RF69_868MHZ ? 868 : 915); + //DEBUGln(buff); + DEBUGln(F("START")); + + if (flash.initialize()) + DEBUGln(F("SPI Flash Init OK")); + else + DEBUGln(F("SPI Flash Init FAIL! (is chip present?)")); + + radio.sendWithRetry(GATEWAYID, "START", 5); + zonesOFF(); + Blink(LED, 100); + Blink(LED, 100); + Blink(LED, 100); + DEBUG("Free RAM:");DEBUGln(checkFreeRAM()); +} + +byte LEDSTATE=LOW; +uint32_t LEDCYCLETIMER=0; +byte programZone[32]; //used to store program data +unsigned int programSeconds[32]; //used to store program data +byte programLength=0; //how many zones in this program +byte programPointer=0; //keeps track of active zone (when >=0) +unsigned int seconds=0; +byte whichZone; +int index; + +void loop() +{ + if (radio.receiveDone()) + { + DEBUG(F("["));DEBUG(radio.SENDERID);DEBUG(F("] ")); + DEBUG((char *)radio.DATA); + DEBUG(F(" [RX_RSSI:"));DEBUG(radio.RSSI);DEBUGln("]"); + + // wireless programming token check + // DO NOT REMOVE, or this node will not be wirelessly programmable any more! + CheckForWirelessHEX(radio, flash, true); + + whichZone=0; + str = String((char *)radio.DATA); + str.trim(); + if (str.equals(F("OFF"))) zonesOFF(); + else if (str.equals(F("ALL"))) zonesAllON(); //for testing registers only, you should never turn ON all zones in a sprinkler system + else if (str.length() >= 4 && str.startsWith("ON:")) //ON:zoneNumber + { + str = str.substring(3); //trim "ON:" + whichZone = str.toInt(); //extract zone id + if (whichZone > 0) { + stopAndResetProgram();//this command forces any programs to stop and only set the requested zone/output ON + zoneON(whichZone); + } + else DEBUG("Invalid ON"); + } + else if (str.length() >= 7 && str.startsWith("PRG ")) //PRG 1:5 2:5 3:10 4:5 .... zoneN:seconds + { + str = str.substring(4); //trim "PRG " + while (str.length()>=3 && programPointer < 255) + { + whichZone = str.toInt(); //extract zone id + index = str.indexOf(':'); + if (index > 0 && str.length() > index) //make sure there's something after the colon + { + str = str.substring(index+1); //trim "zoneId:" + seconds = str.toInt(); //extract seconds + if (seconds > 0) + { + programZone[programLength] = whichZone; + programSeconds[programLength] = seconds; + programLength++; + + DEBUG("Extracted ZONE"); + DEBUG(whichZone); + DEBUG(":"); + DEBUG(seconds); + DEBUGln("s"); + } + else + { + programLength = 0; + DEBUG("INVALID PRG"); + break; + } + } + else + { + programLength = 0; + DEBUG("INVALID PRG"); + break; + } + + //trim current token and move to next one + index = str.indexOf(' '); + if (index > 0 && str.length() > index+3) //X X:X + { + str = str.substring(index+1); + } + else //EOS? + { + DEBUG( programLength>0 ? "DONE" : "INVALID PRG"); + break; + } + } + DEBUG(F("Found programs:"));DEBUGln(programLength); + } + + //respond to any other ACKs + if (radio.ACKRequested()) radio.sendACK(); + DEBUGln(); + DEBUG("Free RAM:");DEBUGln(checkFreeRAM()); + } + + handleProgram(); + + if (millis() - LEDCYCLETIMER > 2000) //flip onboard LED state every so often to indicate activity + { + LEDCYCLETIMER = millis(); + LEDSTATE = !LEDSTATE; + digitalWrite(LED, LEDSTATE); + } +} + +unsigned long programZoneStart=0; +void handleProgram() +{ + if (programLength > 0) + { + if (programZoneStart == 0) + { + programZoneStart = millis(); //mark start time of a zone + zoneON(programZone[programPointer]); + DEBUG("Running zone "); + DEBUG(programZone[programPointer]); + DEBUG(" for "); + DEBUG((((unsigned long)programSeconds[programPointer])*1000)); + DEBUGln("ms..."); + } + else if (millis() - programZoneStart > (((unsigned long)programSeconds[programPointer])*1000)) //check if zone time expired, jump to next zone + { + programZoneStart=0; + programLength--; + if (programLength == 0) //finished + zonesOFF(); + else programPointer++; //skip to next zone in program + } + } +} + +void stopAndResetProgram() +{ + programLength=0; + programPointer=0; + programZoneStart=0; +} + +//turns ON one zone only, all others off +void zoneON(byte which) +{ + //stopAndResetProgram(); //stop any running programs + if (radio.ACKRequested()) radio.sendACK(); + registersWriteBit(which-1); + sprintf(buff, "ZONE:%d", which); + if (radio.sendWithRetry(GATEWAYID, buff, strlen(buff))) + {DEBUGln(F("..OK"));} + else {DEBUGln(F("..NOK"));} +} + +//all zones OFF +void zonesOFF() +{ + stopAndResetProgram(); //stop any running programs + if (radio.ACKRequested()) radio.sendACK(); + registersClear(); + if (radio.sendWithRetry(GATEWAYID, "ZONES:OFF", 9)) + {DEBUGln(F("..OK"));} + else {DEBUGln(F("..NOK"));} +} + +//all zones ON - for testing purposes only +void zonesAllON() +{ + stopAndResetProgram(); //stop any running programs + registersAllOn(); +} + +void registersClear() { + digitalWrite(LATCHPIN, LOW); + for(byte i=0;i