// ********************************************************************************** // 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