publish libraries to avoid users to have to download them one by one
This commit is contained in:
1 parent
93c075e880
commit
f0546fab8c
396 files changed
+536928
No files matched your search
@@ -0,0 +1,106 @@
|
||||
//***********************************************************************************************************
|
||||
// Sample sketch that achieves the lowest power on a Moteino of ~6.5uA
|
||||
// Everything is put to sleep including the MCU, the radio (if any) and the FlashMem chip
|
||||
//**** SETTINGS *********************************************************************************************
|
||||
#define WITH_RFM69 //comment this line out if you don't have a RFM69 on your Moteino
|
||||
#define WITH_SPIFLASH //comment this line out if you don't have the FLASH-MEM chip on your Moteino
|
||||
//***********************************************************************************************************
|
||||
|
||||
#include <Arduino.h> // assumes Arduino IDE v1.0 or greater
|
||||
#include <avr/sleep.h>
|
||||
#include <avr/wdt.h>
|
||||
#include <avr/power.h>
|
||||
|
||||
#ifdef __AVR_ATmega1284P__
|
||||
#define LED 15 // Moteino MEGAs have LEDs on D15
|
||||
#define FLASH_SS 23 // and FLASH SS on D23
|
||||
#else
|
||||
#define LED 9 // Moteinos have LEDs on D9
|
||||
#define FLASH_SS 8 // and FLASH SS on D8
|
||||
#endif
|
||||
|
||||
#if defined(WITH_RFM69) || defined(WITH_SPIFLASH)
|
||||
#include <SPI.h> //comes with Arduino IDE (www.arduino.cc)
|
||||
#if defined(WITH_RFM69)
|
||||
#include <RFM69.h> //get it here: https://www.github.com/lowpowerlab/rfm69
|
||||
RFM69 radio;
|
||||
#define NETWORKID 100
|
||||
#define NODEID 123
|
||||
#define FREQUENCY RF69_915MHZ
|
||||
#endif
|
||||
#if defined(WITH_SPIFLASH)
|
||||
#include <SPIFlash.h> //get it here: https://www.github.com/lowpowerlab/spiflash
|
||||
SPIFlash flash(FLASH_SS, 0xEF30); //EF30 for 4mbit Windbond chip (W25X40CL)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
//watchdog interrupt
|
||||
ISR (WDT_vect) {
|
||||
wdt_disable();
|
||||
}
|
||||
|
||||
void setup () {
|
||||
#ifdef WITH_RFM69
|
||||
radio.initialize(FREQUENCY,NODEID,NETWORKID);
|
||||
radio.sleep();
|
||||
#endif
|
||||
|
||||
#ifdef WITH_SPIFLASH
|
||||
if (flash.initialize())
|
||||
flash.sleep();
|
||||
#endif
|
||||
|
||||
// //optional blink to know radio/flash sleeping went OK
|
||||
// pinMode(LED, OUTPUT);
|
||||
// digitalWrite(LED, HIGH);
|
||||
// delay(30);
|
||||
// digitalWrite(LED, LOW);
|
||||
// delay(50);
|
||||
// digitalWrite(LED, HIGH);
|
||||
// delay(50);
|
||||
// digitalWrite(LED, LOW);
|
||||
|
||||
for (uint8_t i=0; i<=A5; i++)
|
||||
{
|
||||
#ifdef WITH_RFM69
|
||||
if (i == RF69_SPI_CS) continue;
|
||||
#endif
|
||||
#ifdef WITH_SPIFLASH
|
||||
if (i == FLASH_SS) continue;
|
||||
#endif
|
||||
pinMode(i, OUTPUT);
|
||||
digitalWrite(i, LOW);
|
||||
}
|
||||
|
||||
power_timer1_disable();
|
||||
power_timer2_disable();
|
||||
power_twi_disable();
|
||||
}
|
||||
|
||||
void loop ()
|
||||
{
|
||||
// disable ADC
|
||||
ADCSRA = 0;
|
||||
// clear various "reset" flags
|
||||
MCUSR = 0;
|
||||
// allow changes, disable reset
|
||||
WDTCSR = bit (WDCE) | bit (WDE);
|
||||
//set interrupt mode and an interval
|
||||
WDTCSR = bit (WDIE) | bit (WDP3) | bit (WDP0); //set WDIE, and 8 seconds delay
|
||||
wdt_reset(); //pat the dog...
|
||||
|
||||
set_sleep_mode (SLEEP_MODE_PWR_DOWN);
|
||||
noInterrupts(); // timed sequence follows
|
||||
sleep_enable();
|
||||
|
||||
// turn off brown-out enable in software
|
||||
// BODS must be set to one and BODSE must be set to zero within four clock cycles
|
||||
MCUCR = bit (BODS) | bit (BODSE);
|
||||
// The BODS bit is automatically cleared after three clock cycles
|
||||
MCUCR = bit (BODS);
|
||||
interrupts();
|
||||
sleep_cpu();
|
||||
|
||||
//cancel sleep as a precaution
|
||||
sleep_disable();
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
//***********************************************************************************************************
|
||||
// Sample sketch that achieves the lowest power on a Moteino of ~6.5uA
|
||||
// Everything is put to sleep including the MCU, the radio (if any) and the FlashMem chip
|
||||
//**** SETTINGS *********************************************************************************************
|
||||
#define WITH_RFM69 //comment this line out if you don't have a RFM69 on your Moteino
|
||||
#define WITH_SPIFLASH //comment this line out if you don't have the FLASH-MEM chip on your Moteino
|
||||
//***********************************************************************************************************
|
||||
|
||||
#include <LowPower.h> //get library from: https://github.com/lowpowerlab/lowpower
|
||||
//writeup here: http://www.rocketscream.com/blog/2011/07/04/lightweight-low-power-arduino-library/
|
||||
|
||||
#ifdef __AVR_ATmega1284P__
|
||||
#define LED 15 // Moteino MEGAs have LEDs on D15
|
||||
#define FLASH_SS 23 // and FLASH SS on D23
|
||||
#else
|
||||
#define LED 9 // Moteinos have LEDs on D9
|
||||
#define FLASH_SS 8 // and FLASH SS on D8
|
||||
#endif
|
||||
|
||||
#if defined(WITH_RFM69) || defined(WITH_SPIFLASH)
|
||||
#include <SPI.h> //comes with Arduino IDE (www.arduino.cc)
|
||||
#if defined(WITH_RFM69)
|
||||
#include <RFM69.h> //get it here: https://www.github.com/lowpowerlab/rfm69
|
||||
RFM69 radio;
|
||||
#endif
|
||||
#if defined(WITH_SPIFLASH)
|
||||
#include <SPIFlash.h> //get it here: https://www.github.com/lowpowerlab/spiflash
|
||||
SPIFlash flash(FLASH_SS, 0xEF30); //EF30 for 4mbit Windbond chip (W25X40CL)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
void setup () {
|
||||
#ifdef WITH_RFM69
|
||||
radio.sleep();
|
||||
#endif
|
||||
|
||||
#ifdef WITH_SPIFLASH
|
||||
if (flash.initialize())
|
||||
flash.sleep();
|
||||
#endif
|
||||
|
||||
for (uint8_t i=0; i<=A5; i++)
|
||||
{
|
||||
#ifdef WITH_RFM69
|
||||
if (i == RF69_SPI_CS) continue;
|
||||
#endif
|
||||
#ifdef WITH_SPIFLASH
|
||||
if (i == FLASH_SS) continue;
|
||||
#endif
|
||||
pinMode(i, OUTPUT);
|
||||
digitalWrite(i, LOW);
|
||||
}
|
||||
}
|
||||
|
||||
void loop ()
|
||||
{
|
||||
//optional blink to know radio/flash sleeping went OK
|
||||
pinMode(LED, OUTPUT);
|
||||
digitalWrite(LED, HIGH);
|
||||
delay(30);
|
||||
digitalWrite(LED, LOW);
|
||||
|
||||
//sleep MCU for 8seconds
|
||||
LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
// **********************************************************************************
|
||||
// DoorBellMote sketch works with Moteinos equipped with RFM69W/RFM69HW/RFM69CW/RFM69HCW
|
||||
// It detects current flow at the doorbell transformer and send a message each time to the gateway
|
||||
// It can trigger doorbell through a relay powered from pins D6+D7
|
||||
// Deploy and forget: wirelessly programmable via Moteino + RFM69_OTA library
|
||||
// **********************************************************************************
|
||||
// Copyright Felix Rusu 2016, http://www.LowPowerLab.com/contact
|
||||
// **********************************************************************************
|
||||
// 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.
|
||||
//
|
||||
// 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 <RFM69.h> //get it here: https://github.com/lowpowerlab/rfm69
|
||||
#include <RFM69_ATC.h> //get it here: https://github.com/lowpowerlab/RFM69
|
||||
#include <RFM69_OTA.h> //get it here: https://github.com/lowpowerlab/RFM69
|
||||
#include <SPIFlash.h> //get it here: https://github.com/lowpowerlab/spiflash
|
||||
#include <SPI.h> //included with Arduino IDE (www.arduino.cc)
|
||||
|
||||
//****************************************************************************************************************
|
||||
//**** IMPORTANT RADIO SETTINGS - YOU MUST CHANGE/CONFIGURE TO MATCH YOUR HARDWARE TRANSCEIVER CONFIGURATION! ****
|
||||
//****************************************************************************************************************
|
||||
#define GATEWAYID 1
|
||||
#define NODEID 133
|
||||
#define NETWORKID 100
|
||||
//#define FREQUENCY RF69_433MHZ
|
||||
//#define FREQUENCY RF69_868MHZ
|
||||
#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_HCW //uncomment only for RFM69HW/HCW! Leave out if you have RFM69W/CW!
|
||||
//*****************************************************************************************************************************
|
||||
#define ENABLE_ATC //comment out this line to disable AUTO TRANSMISSION CONTROL
|
||||
#define ATC_RSSI -75
|
||||
//*****************************************************************************************************************************
|
||||
#define CHIMEPIN 4 // active HIGH chime signal from detector H11AA1 circuit
|
||||
#define RELAYPIN1 6 //for the bell ring relay we just need 2 digital pins together to activate the relay for a short pulse
|
||||
#define RELAYPIN2 7 //for the bell ring relay we just need 2 digital pins together to activate the relay for a short pulse
|
||||
#define DISABLE_RELAY 5 //for the bell disable relay we use a single digital pin through a transistor
|
||||
#define RELAY_PULSE_MS 250 //just enough that the doorbell chime will trigger
|
||||
#define RINGDELAY 3000 //time between rings (avoid fast repeated rings)
|
||||
//*****************************************************************************************************************************
|
||||
#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
|
||||
|
||||
#ifdef ENABLE_ATC
|
||||
RFM69_ATC radio;
|
||||
#else
|
||||
RFM69 radio;
|
||||
#endif
|
||||
//*****************************************************************************************************************************
|
||||
// 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[50];
|
||||
|
||||
void setup(void)
|
||||
{
|
||||
Serial.begin(SERIAL_BAUD);
|
||||
pinMode(CHIMEPIN, INPUT);
|
||||
pinMode(RELAYPIN1, OUTPUT);
|
||||
pinMode(RELAYPIN2, OUTPUT);
|
||||
pinMode(DISABLE_RELAY, OUTPUT);
|
||||
pinMode(LED, OUTPUT);
|
||||
|
||||
radio.initialize(FREQUENCY,NODEID,NETWORKID);
|
||||
#ifdef IS_RFM69HW_HCW
|
||||
radio.setHighPower(); //must include this only for RFM69HW/HCW!
|
||||
#endif
|
||||
radio.encrypt(ENCRYPTKEY);
|
||||
|
||||
#ifdef ENABLE_ATC
|
||||
radio.enableAutoPower(ATC_RSSI);
|
||||
#endif
|
||||
|
||||
sprintf(buff, "DoorBellMote : %d Mhz...", FREQUENCY==RF69_433MHZ ? 433 : FREQUENCY==RF69_868MHZ ? 868 : 915);
|
||||
DEBUGln(buff);
|
||||
|
||||
if (flash.initialize())
|
||||
DEBUGln("SPI Flash Init OK");
|
||||
else
|
||||
DEBUGln("SPI Flash Init FAIL! (is chip present?)");
|
||||
|
||||
radio.sendWithRetry(GATEWAYID, "START", 6);
|
||||
Blink(LED, 100);
|
||||
Blink(LED, 100);
|
||||
Blink(LED, 100);
|
||||
}
|
||||
|
||||
uint32_t doorPulseCount = 0;
|
||||
uint32_t lastStatusTimestamp=0;
|
||||
uint32_t LEDCYCLETIMER=0;
|
||||
byte LEDSTATE=LOW;
|
||||
char input;
|
||||
boolean ring=false;
|
||||
boolean disable=false;
|
||||
byte disableStatus=0;
|
||||
|
||||
void loop()
|
||||
{
|
||||
if (Serial.available())
|
||||
input = Serial.read();
|
||||
|
||||
if (input=='r')
|
||||
{
|
||||
DEBUGln("Relay test...");
|
||||
pulseRelay();
|
||||
input = 0;
|
||||
}
|
||||
|
||||
if (millis()-(lastStatusTimestamp)>RINGDELAY)
|
||||
{
|
||||
if (digitalRead(CHIMEPIN) == HIGH)
|
||||
{
|
||||
lastStatusTimestamp = millis();
|
||||
radio.sendWithRetry(GATEWAYID, "RING", 4);
|
||||
Blink(LED,20);
|
||||
Blink(LED,20);
|
||||
Blink(LED,20);
|
||||
}
|
||||
}
|
||||
|
||||
if (radio.receiveDone())
|
||||
{
|
||||
DEBUG('[');DEBUG(radio.SENDERID);DEBUG("] ");
|
||||
for (byte i = 0; i < radio.DATALEN; i++)
|
||||
DEBUG((char)radio.DATA[i]);
|
||||
|
||||
if (radio.DATALEN==4)
|
||||
if (radio.DATA[0]=='R' && radio.DATA[1]=='I' && radio.DATA[2]=='N' && radio.DATA[3]=='G')
|
||||
ring = true;
|
||||
|
||||
if (radio.DATALEN==6)
|
||||
if (radio.DATA[0]=='B' && radio.DATA[1]=='E' && radio.DATA[2]=='L' && radio.DATA[3]=='L' && radio.DATA[4]==':')
|
||||
if (radio.DATA[5]=='0')
|
||||
{
|
||||
disableStatus = 1;
|
||||
disable = true;
|
||||
}
|
||||
else if (radio.DATA[5]=='1')
|
||||
{
|
||||
disableStatus = 0;
|
||||
disable = true;
|
||||
}
|
||||
|
||||
// wireless programming token check
|
||||
// DO NOT REMOVE, or this Moteino will not be wirelessly programmable any more!
|
||||
CheckForWirelessHEX(radio, flash, true);
|
||||
|
||||
//first send any ACK to request
|
||||
DEBUG(" [RX_RSSI:");DEBUG(radio.RSSI);DEBUG("]");
|
||||
if (radio.ACKRequested())
|
||||
{
|
||||
radio.sendACK();
|
||||
DEBUG(" - ACK sent.");
|
||||
}
|
||||
|
||||
if (ring)
|
||||
{
|
||||
//if other relay is ON we must temporarily turn it off while we pulse the RING relay, to avoid any rail collapse and reset
|
||||
if (disableStatus) digitalWrite(DISABLE_RELAY, 0);
|
||||
pulseRelay();
|
||||
if (disableStatus) digitalWrite(DISABLE_RELAY, 1);
|
||||
radio.sendWithRetry(GATEWAYID, "RING OK", 4);
|
||||
ring = false;
|
||||
}
|
||||
|
||||
if (disable)
|
||||
{
|
||||
digitalWrite(DISABLE_RELAY, disableStatus); //disable it
|
||||
sprintf(buff, "BELL:%d", disableStatus ? 0 : 1);
|
||||
radio.sendWithRetry(GATEWAYID, buff, 6);
|
||||
disable=false;
|
||||
}
|
||||
|
||||
DEBUGln();
|
||||
}
|
||||
|
||||
if (millis() - LEDCYCLETIMER > 2000) //flip onboard LED state every so often to indicate activity
|
||||
{
|
||||
LEDCYCLETIMER = millis();
|
||||
LEDSTATE = !LEDSTATE;
|
||||
digitalWrite(LED, LEDSTATE);
|
||||
}
|
||||
}
|
||||
|
||||
void pulseRelay()
|
||||
{
|
||||
digitalWrite(RELAYPIN1, HIGH);
|
||||
digitalWrite(RELAYPIN2, HIGH);
|
||||
delay(RELAY_PULSE_MS);
|
||||
digitalWrite(RELAYPIN1, LOW);
|
||||
digitalWrite(RELAYPIN2, LOW);
|
||||
}
|
||||
|
||||
void Blink(byte PIN, byte DELAY_MS)
|
||||
{
|
||||
pinMode(PIN, OUTPUT);
|
||||
digitalWrite(PIN,HIGH);
|
||||
delay(DELAY_MS/2);
|
||||
digitalWrite(PIN,LOW);
|
||||
delay(DELAY_MS/2);
|
||||
}
|
||||
@@ -0,0 +1,422 @@
|
||||
// **********************************************************************************************************
|
||||
// GarageMote garage door controller sketch that works with Moteinos equipped with RFM69W/RFM69HW/RFM69CW/RFM69HCW
|
||||
// Monitors door position (open, closed, closing, unknown)
|
||||
// Can trigger door open/close
|
||||
// http://www.LowPowerLab.com/GarageMote
|
||||
// **********************************************************************************
|
||||
// Copyright Felix Rusu 2016, http://www.LowPowerLab.com/contact
|
||||
// **********************************************************************************
|
||||
// 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.
|
||||
//
|
||||
// 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
|
||||
// **********************************************************************************
|
||||
//#define WEATHERSHIELD //uncomment if WeatherShield is present to report temp/humidity/pressure periodically
|
||||
//#define WEATHERSENDDELAY 300000 // send WeatherShield data every so often (ms)
|
||||
// ***************************************************************************************************************************
|
||||
#include <RFM69.h> //get it here: https://github.com/lowpowerlab/rfm69
|
||||
#include <RFM69_ATC.h> //get it here: https://github.com/lowpowerlab/RFM69
|
||||
#include <RFM69_OTA.h> //get it here: https://github.com/lowpowerlab/RFM69
|
||||
#include <SPIFlash.h> //get it here: https://github.com/lowpowerlab/spiflash
|
||||
#include <SPI.h> //included with Arduino IDE (www.arduino.cc)
|
||||
|
||||
//For WeatherShield with BME280 see the WeatherNode example
|
||||
#ifdef WEATHERSHIELD
|
||||
#include <SFE_BMP180.h> //get it here: https://github.com/LowPowerLab/SFE_BMP180
|
||||
#include <SI7021.h> //get it here: https://github.com/LowPowerLab/SI7021
|
||||
#include <Wire.h>
|
||||
#endif
|
||||
//****************************************************************************************************************
|
||||
//**** IMPORTANT RADIO SETTINGS - YOU MUST CHANGE/CONFIGURE TO MATCH YOUR HARDWARE TRANSCEIVER CONFIGURATION! ****
|
||||
//****************************************************************************************************************
|
||||
#define GATEWAYID 1
|
||||
#define NODEID 11
|
||||
#define NETWORKID 250
|
||||
//#define FREQUENCY RF69_433MHZ
|
||||
//#define FREQUENCY RF69_868MHZ
|
||||
#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_HCW //uncomment only for RFM69HW/HCW! Leave out if you have RFM69W/CW!
|
||||
//*****************************************************************************************************************************
|
||||
#define ENABLE_ATC //comment out this line to disable AUTO TRANSMISSION CONTROL
|
||||
#define ATC_RSSI -75
|
||||
//*****************************************************************************************************************************
|
||||
#define HALLSENSOR1 A0
|
||||
#define HALLSENSOR1_EN 4
|
||||
#define HALLSENSOR2 A1
|
||||
#define HALLSENSOR2_EN 5
|
||||
|
||||
#define RELAYPIN1 6
|
||||
#define RELAYPIN2 7
|
||||
#define RELAY_PULSE_MS 250 //just enough that the opener will pick it up
|
||||
|
||||
#define DOOR_MOVEMENT_TIME 14000 // this has to be at least as long as the max between [door opening time, door closing time]
|
||||
// my door opens and closes in about 12s
|
||||
#define STATUS_CHANGE_MIN 1500 // this has to be at least as long as the delay
|
||||
// between a opener button press and door movement start
|
||||
// most garage doors will start moving immediately (within half a second)
|
||||
//*****************************************************************************************************************************
|
||||
#define HALLSENSOR_OPENSIDE 0
|
||||
#define HALLSENSOR_CLOSEDSIDE 1
|
||||
|
||||
#define STATUS_CLOSED 0
|
||||
#define STATUS_CLOSING 1
|
||||
#define STATUS_OPENING 2
|
||||
#define STATUS_OPEN 3
|
||||
#define STATUS_UNKNOWN 4
|
||||
|
||||
#define LED 9 //pin connected to onboard LED
|
||||
#define LED_PULSE_PERIOD 5000 //5s seems good value for pulsing/blinking (not too fast/slow)
|
||||
#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); delay(1);}
|
||||
#define DEBUGln(input) {Serial.println(input); delay(1);}
|
||||
#else
|
||||
#define DEBUG(input);
|
||||
#define DEBUGln(input);
|
||||
#endif
|
||||
|
||||
#ifdef WEATHERSHIELD
|
||||
SI7021 weatherShield_SI7021;
|
||||
SFE_BMP180 weatherShield_BMP180;
|
||||
#endif
|
||||
|
||||
//function prototypes
|
||||
void setStatus(byte newSTATUS, boolean reportStatus=true);
|
||||
void reportStatus();
|
||||
boolean hallSensorRead(byte which);
|
||||
void pulseRelay();
|
||||
|
||||
//global program variables
|
||||
byte STATUS;
|
||||
unsigned long lastStatusTimestamp=0;
|
||||
unsigned long ledPulseTimestamp=0;
|
||||
unsigned long lastWeatherSent=0;
|
||||
int ledPulseValue=0;
|
||||
boolean ledPulseDirection=false; //false=down, true=up
|
||||
char Pstr[10];
|
||||
char sendBuf[30];
|
||||
SPIFlash flash(8, 0xEF30); //WINDBOND 4MBIT flash chip on CS pin D8 (default for Moteino)
|
||||
|
||||
#ifdef ENABLE_ATC
|
||||
RFM69_ATC radio;
|
||||
#else
|
||||
RFM69 radio;
|
||||
#endif
|
||||
|
||||
void setup(void)
|
||||
{
|
||||
#ifdef SERIAL_EN
|
||||
Serial.begin(SERIAL_BAUD);
|
||||
#endif
|
||||
pinMode(HALLSENSOR1, INPUT);
|
||||
pinMode(HALLSENSOR2, INPUT);
|
||||
pinMode(HALLSENSOR1_EN, OUTPUT);
|
||||
pinMode(HALLSENSOR2_EN, OUTPUT);
|
||||
pinMode(RELAYPIN1, OUTPUT);
|
||||
pinMode(RELAYPIN2, OUTPUT);
|
||||
pinMode(LED, OUTPUT);
|
||||
|
||||
radio.initialize(FREQUENCY,NODEID,NETWORKID);
|
||||
#ifdef IS_RFM69HW_HCW
|
||||
radio.setHighPower(); //must include this only for RFM69HW/HCW!
|
||||
#endif
|
||||
radio.encrypt(ENCRYPTKEY);
|
||||
|
||||
#ifdef ENABLE_ATC
|
||||
radio.enableAutoPower(ATC_RSSI);
|
||||
#endif
|
||||
|
||||
char buff[50];
|
||||
sprintf(buff, "GarageMote : %d Mhz...", FREQUENCY==RF69_433MHZ ? 433 : FREQUENCY==RF69_868MHZ ? 868 : 915);
|
||||
DEBUGln(buff);
|
||||
|
||||
if (hallSensorRead(HALLSENSOR_OPENSIDE)==true)
|
||||
setStatus(STATUS_OPEN);
|
||||
if (hallSensorRead(HALLSENSOR_CLOSEDSIDE)==true)
|
||||
setStatus(STATUS_CLOSED);
|
||||
else setStatus(STATUS_UNKNOWN);
|
||||
|
||||
#ifdef WEATHERSHIELD
|
||||
//initialize weather shield sensors
|
||||
weatherShield_SI7021.begin();
|
||||
if (weatherShield_BMP180.begin())
|
||||
{ DEBUGln("BMP180 init success"); }
|
||||
else { DEBUGln("BMP180 init fail\n"); }
|
||||
#endif
|
||||
}
|
||||
|
||||
unsigned long doorPulseCount = 0;
|
||||
char input=0;
|
||||
double P;
|
||||
|
||||
void loop()
|
||||
{
|
||||
#ifdef SERIAL_EN
|
||||
if (Serial.available())
|
||||
input = Serial.read();
|
||||
#endif
|
||||
|
||||
if (input=='r')
|
||||
{
|
||||
DEBUGln("Relay test...");
|
||||
pulseRelay();
|
||||
input = 0;
|
||||
}
|
||||
|
||||
// UNKNOWN => OPEN/CLOSED
|
||||
if (STATUS == STATUS_UNKNOWN && millis()-(lastStatusTimestamp)>STATUS_CHANGE_MIN)
|
||||
{
|
||||
if (hallSensorRead(HALLSENSOR_OPENSIDE)==true)
|
||||
setStatus(STATUS_OPEN);
|
||||
if (hallSensorRead(HALLSENSOR_CLOSEDSIDE)==true)
|
||||
setStatus(STATUS_CLOSED);
|
||||
}
|
||||
|
||||
// OPEN => CLOSING
|
||||
if (STATUS == STATUS_OPEN && millis()-(lastStatusTimestamp)>STATUS_CHANGE_MIN)
|
||||
{
|
||||
if (hallSensorRead(HALLSENSOR_OPENSIDE)==false)
|
||||
setStatus(STATUS_CLOSING);
|
||||
}
|
||||
|
||||
// CLOSED => OPENING
|
||||
if (STATUS == STATUS_CLOSED && millis()-(lastStatusTimestamp)>STATUS_CHANGE_MIN)
|
||||
{
|
||||
if (hallSensorRead(HALLSENSOR_CLOSEDSIDE)==false)
|
||||
setStatus(STATUS_OPENING);
|
||||
}
|
||||
|
||||
// OPENING/CLOSING => OPEN (when door returns to open due to obstacle or toggle action)
|
||||
// => CLOSED (when door closes normally from OPEN)
|
||||
// => UNKNOWN (when more time passes than normally would for a door up/down movement)
|
||||
if ((STATUS == STATUS_OPENING || STATUS == STATUS_CLOSING) && millis()-(lastStatusTimestamp)>STATUS_CHANGE_MIN)
|
||||
{
|
||||
if (hallSensorRead(HALLSENSOR_OPENSIDE)==true)
|
||||
setStatus(STATUS_OPEN);
|
||||
else if (hallSensorRead(HALLSENSOR_CLOSEDSIDE)==true)
|
||||
setStatus(STATUS_CLOSED);
|
||||
else if (millis()-(lastStatusTimestamp)>DOOR_MOVEMENT_TIME)
|
||||
setStatus(STATUS_UNKNOWN);
|
||||
}
|
||||
|
||||
if (radio.receiveDone())
|
||||
{
|
||||
byte newStatus=STATUS;
|
||||
boolean reportStatusRequest=false;
|
||||
DEBUG('[');DEBUG(radio.SENDERID);DEBUG("] ");
|
||||
for (byte i = 0; i < radio.DATALEN; i++)
|
||||
DEBUG((char)radio.DATA[i]);
|
||||
|
||||
if (radio.DATALEN==3)
|
||||
{
|
||||
//check for an OPEN/CLOSE/STATUS request
|
||||
if (radio.DATA[0]=='O' && radio.DATA[1]=='P' && radio.DATA[2]=='N')
|
||||
{
|
||||
if (millis()-(lastStatusTimestamp) > STATUS_CHANGE_MIN && (STATUS == STATUS_CLOSED || STATUS == STATUS_CLOSING || STATUS == STATUS_UNKNOWN))
|
||||
newStatus = STATUS_OPENING;
|
||||
//else radio.Send(requester, "INVALID", 7);
|
||||
}
|
||||
if (radio.DATA[0]=='C' && radio.DATA[1]=='L' && radio.DATA[2]=='S')
|
||||
{
|
||||
if (millis()-(lastStatusTimestamp) > STATUS_CHANGE_MIN && (STATUS == STATUS_OPEN || STATUS == STATUS_OPENING || STATUS == STATUS_UNKNOWN))
|
||||
newStatus = STATUS_CLOSING;
|
||||
//else radio.Send(requester, "INVALID", 7);
|
||||
}
|
||||
if (radio.DATA[0]=='S' && radio.DATA[1]=='T' && radio.DATA[2]=='S')
|
||||
{
|
||||
reportStatusRequest = true;
|
||||
}
|
||||
}
|
||||
|
||||
// wireless programming token check
|
||||
// DO NOT REMOVE, or GarageMote will not be wirelessly programmable any more!
|
||||
CheckForWirelessHEX(radio, flash, true);
|
||||
|
||||
//first send any ACK to request
|
||||
DEBUG(" [RX_RSSI:");DEBUG(radio.RSSI);DEBUG("]");
|
||||
if (radio.ACKRequested())
|
||||
{
|
||||
radio.sendACK();
|
||||
DEBUG(" - ACK sent.");
|
||||
}
|
||||
|
||||
//now take care of the request, if not invalid
|
||||
if (STATUS != newStatus)
|
||||
{
|
||||
pulseRelay();
|
||||
setStatus(newStatus);
|
||||
}
|
||||
if (reportStatusRequest)
|
||||
{
|
||||
reportStatus();
|
||||
}
|
||||
|
||||
DEBUGln();
|
||||
}
|
||||
|
||||
//use LED to visually indicate STATUS
|
||||
if (STATUS == STATUS_OPEN || STATUS == STATUS_CLOSED) //solid ON/OFF
|
||||
{
|
||||
digitalWrite(LED, STATUS == STATUS_OPEN ? LOW : HIGH);
|
||||
}
|
||||
if (STATUS == STATUS_OPENING || STATUS == STATUS_CLOSING) //pulse
|
||||
{
|
||||
if (millis()-(ledPulseTimestamp) > LED_PULSE_PERIOD/256)
|
||||
{
|
||||
ledPulseValue = ledPulseDirection ? ledPulseValue + LED_PULSE_PERIOD/256 : ledPulseValue - LED_PULSE_PERIOD/256;
|
||||
|
||||
if (ledPulseDirection && ledPulseValue > 255)
|
||||
{
|
||||
ledPulseDirection=false;
|
||||
ledPulseValue = 255;
|
||||
}
|
||||
else if (!ledPulseDirection && ledPulseValue < 0)
|
||||
{
|
||||
ledPulseDirection=true;
|
||||
ledPulseValue = 0;
|
||||
}
|
||||
|
||||
analogWrite(LED, ledPulseValue);
|
||||
ledPulseTimestamp = millis();
|
||||
}
|
||||
}
|
||||
if (STATUS == STATUS_UNKNOWN) //blink
|
||||
{
|
||||
if (millis()-(ledPulseTimestamp) > LED_PULSE_PERIOD/20)
|
||||
{
|
||||
ledPulseDirection = !ledPulseDirection;
|
||||
digitalWrite(LED, ledPulseDirection ? HIGH : LOW);
|
||||
ledPulseTimestamp = millis();
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef WEATHERSHIELD
|
||||
if (millis()-lastWeatherSent > WEATHERSENDDELAY)
|
||||
{
|
||||
lastWeatherSent = millis();
|
||||
P = getPressure();
|
||||
P*=0.0295333727; //transform to inHg
|
||||
dtostrf(P, 3,2, Pstr);
|
||||
sprintf(sendBuf, "F:%d H:%d P:%s", weatherShield_SI7021.getFahrenheitHundredths(), weatherShield_SI7021.getHumidityPercent(), Pstr);
|
||||
byte sendLen = strlen(sendBuf);
|
||||
radio.send(GATEWAYID, sendBuf, sendLen);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
//returns TRUE if magnet is next to sensor, FALSE if magnet is away
|
||||
boolean hallSensorRead(byte which)
|
||||
{
|
||||
//while(millis()-lastStatusTimestamp<STATUS_CHANGE_MIN);
|
||||
digitalWrite(which ? HALLSENSOR2_EN : HALLSENSOR1_EN, HIGH); //turn sensor ON
|
||||
delay(1); //wait a little
|
||||
byte reading = digitalRead(which ? HALLSENSOR2 : HALLSENSOR1);
|
||||
digitalWrite(which ? HALLSENSOR2_EN : HALLSENSOR1_EN, LOW); //turn sensor OFF
|
||||
return reading==0;
|
||||
}
|
||||
|
||||
void setStatus(byte newSTATUS, boolean reportIt)
|
||||
{
|
||||
if (STATUS != newSTATUS) lastStatusTimestamp = millis();
|
||||
STATUS = newSTATUS;
|
||||
DEBUGln(STATUS==STATUS_CLOSED ? "CLOSED" : STATUS==STATUS_CLOSING ? "CLOSING" : STATUS==STATUS_OPENING ? "OPENING" : STATUS==STATUS_OPEN ? "OPEN" : "UNKNOWN");
|
||||
if (reportIt)
|
||||
reportStatus();
|
||||
}
|
||||
|
||||
void reportStatus(void)
|
||||
{
|
||||
char buff[10];
|
||||
sprintf(buff, STATUS==STATUS_CLOSED ? "CLOSED" : STATUS==STATUS_CLOSING ? "CLOSING" : STATUS==STATUS_OPENING ? "OPENING" : STATUS==STATUS_OPEN ? "OPEN" : "UNKNOWN");
|
||||
byte len = strlen(buff);
|
||||
radio.sendWithRetry(GATEWAYID, buff, len);
|
||||
}
|
||||
|
||||
void pulseRelay()
|
||||
{
|
||||
digitalWrite(RELAYPIN1, HIGH);
|
||||
digitalWrite(RELAYPIN2, HIGH);
|
||||
delay(RELAY_PULSE_MS);
|
||||
digitalWrite(RELAYPIN1, LOW);
|
||||
digitalWrite(RELAYPIN2, LOW);
|
||||
}
|
||||
|
||||
void Blink(byte PIN, byte DELAY_MS)
|
||||
{
|
||||
pinMode(PIN, OUTPUT);
|
||||
digitalWrite(PIN,HIGH);
|
||||
delay(DELAY_MS);
|
||||
digitalWrite(PIN,LOW);
|
||||
}
|
||||
|
||||
#ifdef WEATHERSHIELD
|
||||
double getPressure()
|
||||
{
|
||||
char status;
|
||||
double T,P,p0,a;
|
||||
// If you want sea-level-compensated pressure, as used in weather reports,
|
||||
// you will need to know the altitude at which your measurements are taken.
|
||||
// We're using a constant called ALTITUDE in this sketch:
|
||||
|
||||
// If you want to measure altitude, and not pressure, you will instead need
|
||||
// to provide a known baseline pressure. This is shown at the end of the sketch.
|
||||
// You must first get a temperature measurement to perform a pressure reading.
|
||||
// Start a temperature measurement:
|
||||
// If request is successful, the number of ms to wait is returned.
|
||||
// If request is unsuccessful, 0 is returned.
|
||||
status = weatherShield_BMP180.startTemperature();
|
||||
if (status != 0)
|
||||
{
|
||||
// Wait for the measurement to complete:
|
||||
delay(status);
|
||||
|
||||
// Retrieve the completed temperature measurement:
|
||||
// Note that the measurement is stored in the variable T.
|
||||
// Function returns 1 if successful, 0 if failure.
|
||||
status = weatherShield_BMP180.getTemperature(T);
|
||||
if (status != 0)
|
||||
{
|
||||
// Start a pressure measurement:
|
||||
// The parameter is the oversampling setting, from 0 to 3 (highest res, longest wait).
|
||||
// If request is successful, the number of ms to wait is returned.
|
||||
// If request is unsuccessful, 0 is returned.
|
||||
status = weatherShield_BMP180.startPressure(3);
|
||||
if (status != 0)
|
||||
{
|
||||
// Wait for the measurement to complete:
|
||||
delay(status);
|
||||
|
||||
// Retrieve the completed pressure measurement:
|
||||
// Note that the measurement is stored in the variable P.
|
||||
// Note also that the function requires the previous temperature measurement (T).
|
||||
// (If temperature is stable, you can do one temperature measurement for a number of pressure measurements.)
|
||||
// Function returns 1 if successful, 0 if failure.
|
||||
status = weatherShield_BMP180.getPressure(P,T);
|
||||
if (status != 0)
|
||||
{
|
||||
return P;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,212 @@
|
||||
// Sample RFM69 receiver/gateway sketch, with ACK and optional encryption, and Automatic Transmission Control
|
||||
// Passes through any wireless received messages to the serial port & responds to ACKs
|
||||
// It also looks for an onboard FLASH chip, if present
|
||||
// **********************************************************************************
|
||||
// Copyright Felix Rusu 2016, http://www.LowPowerLab.com/contact
|
||||
// **********************************************************************************
|
||||
// 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.
|
||||
//
|
||||
// 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 <RFM69.h> //get it here: https://www.github.com/lowpowerlab/rfm69
|
||||
#include <RFM69_ATC.h> //get it here: https://www.github.com/lowpowerlab/rfm69
|
||||
#include <SPIFlash.h> //get it here: https://www.github.com/lowpowerlab/spiflash
|
||||
#include <SPI.h> //included with Arduino IDE install (www.arduino.cc)
|
||||
|
||||
//*********************************************************************************************
|
||||
//************ IMPORTANT SETTINGS - YOU MUST CHANGE/CONFIGURE TO FIT YOUR HARDWARE *************
|
||||
//*********************************************************************************************
|
||||
#define NODEID 1 //unique for each node on same network
|
||||
#define NETWORKID 100 //the same on all nodes that talk to each other
|
||||
//Match frequency to the hardware version of the radio on your Moteino (uncomment one):
|
||||
#define FREQUENCY RF69_433MHZ
|
||||
//#define FREQUENCY RF69_868MHZ
|
||||
//#define FREQUENCY RF69_915MHZ
|
||||
#define ENCRYPTKEY "sampleEncryptKey" //exactly the same 16 characters/bytes on all nodes!
|
||||
#define IS_RFM69HW_HCW //uncomment only for RFM69HW/HCW! Leave out if you have RFM69W/CW!
|
||||
//*********************************************************************************************
|
||||
//Auto Transmission Control - dials down transmit power to save battery
|
||||
//Usually you do not need to always transmit at max output power
|
||||
//By reducing TX power even a little you save a significant amount of battery power
|
||||
//This setting enables this gateway to work with remote nodes that have ATC enabled to
|
||||
//dial their power down to only the required level
|
||||
#define ENABLE_ATC //comment out this line to disable AUTO TRANSMISSION CONTROL
|
||||
//*********************************************************************************************
|
||||
#define SERIAL_BAUD 115200
|
||||
|
||||
#ifdef __AVR_ATmega1284P__
|
||||
#define LED 15 // Moteino MEGAs have LEDs on D15
|
||||
#define FLASH_SS 23 // and FLASH SS on D23
|
||||
#else
|
||||
#define LED 9 // Moteinos have LEDs on D9
|
||||
#define FLASH_SS 8 // and FLASH SS on D8
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_ATC
|
||||
RFM69_ATC radio;
|
||||
#else
|
||||
RFM69 radio;
|
||||
#endif
|
||||
|
||||
SPIFlash flash(FLASH_SS, 0xEF30); //EF30 for 4mbit Windbond chip (W25X40CL)
|
||||
bool promiscuousMode = false; //set to 'true' to sniff all packets on the same network
|
||||
|
||||
void setup() {
|
||||
Serial.begin(SERIAL_BAUD);
|
||||
delay(10);
|
||||
radio.initialize(FREQUENCY,NODEID,NETWORKID);
|
||||
#ifdef IS_RFM69HW_HCW
|
||||
radio.setHighPower(); //must include this only for RFM69HW/HCW!
|
||||
#endif
|
||||
radio.encrypt(ENCRYPTKEY);
|
||||
radio.promiscuous(promiscuousMode);
|
||||
//radio.setFrequency(919000000); //set frequency to some custom frequency
|
||||
char buff[50];
|
||||
sprintf(buff, "\nListening at %d Mhz...", FREQUENCY==RF69_433MHZ ? 433 : FREQUENCY==RF69_868MHZ ? 868 : 915);
|
||||
Serial.println(buff);
|
||||
if (flash.initialize())
|
||||
{
|
||||
Serial.print("SPI Flash Init OK. Unique MAC = [");
|
||||
flash.readUniqueId();
|
||||
for (byte i=0;i<8;i++)
|
||||
{
|
||||
Serial.print(flash.UNIQUEID[i], HEX);
|
||||
if (i!=8) Serial.print(':');
|
||||
}
|
||||
Serial.println(']');
|
||||
|
||||
//alternative way to read it:
|
||||
//byte* MAC = flash.readUniqueId();
|
||||
//for (byte i=0;i<8;i++)
|
||||
//{
|
||||
// Serial.print(MAC[i], HEX);
|
||||
// Serial.print(' ');
|
||||
//}
|
||||
}
|
||||
else
|
||||
Serial.println("SPI Flash MEM not found (is chip soldered?)...");
|
||||
|
||||
#ifdef ENABLE_ATC
|
||||
Serial.println("RFM69_ATC Enabled (Auto Transmission Control)");
|
||||
#endif
|
||||
}
|
||||
|
||||
byte ackCount=0;
|
||||
uint32_t packetCount = 0;
|
||||
void loop() {
|
||||
//process any serial input
|
||||
if (Serial.available() > 0)
|
||||
{
|
||||
char input = Serial.read();
|
||||
if (input == 'r') //d=dump all register values
|
||||
radio.readAllRegs();
|
||||
if (input == 'E') //E=enable encryption
|
||||
radio.encrypt(ENCRYPTKEY);
|
||||
if (input == 'e') //e=disable encryption
|
||||
radio.encrypt(null);
|
||||
if (input == 'p')
|
||||
{
|
||||
promiscuousMode = !promiscuousMode;
|
||||
radio.promiscuous(promiscuousMode);
|
||||
Serial.print("Promiscuous mode ");Serial.println(promiscuousMode ? "on" : "off");
|
||||
}
|
||||
|
||||
if (input == 'd') //d=dump flash area
|
||||
{
|
||||
Serial.println("Flash content:");
|
||||
int counter = 0;
|
||||
|
||||
while(counter<=256){
|
||||
Serial.print(flash.readByte(counter++), HEX);
|
||||
Serial.print('.');
|
||||
}
|
||||
while(flash.busy());
|
||||
Serial.println();
|
||||
}
|
||||
if (input == 'D')
|
||||
{
|
||||
Serial.print("Deleting Flash chip ... ");
|
||||
flash.chipErase();
|
||||
while(flash.busy());
|
||||
Serial.println("DONE");
|
||||
}
|
||||
if (input == 'i')
|
||||
{
|
||||
Serial.print("DeviceID: ");
|
||||
word jedecid = flash.readDeviceId();
|
||||
Serial.println(jedecid, HEX);
|
||||
}
|
||||
if (input == 't')
|
||||
{
|
||||
byte temperature = radio.readTemperature(-1); // -1 = user cal factor, adjust for correct ambient
|
||||
byte fTemp = 1.8 * temperature + 32; // 9/5=1.8
|
||||
Serial.print( "Radio Temp is ");
|
||||
Serial.print(temperature);
|
||||
Serial.print("C, ");
|
||||
Serial.print(fTemp); //converting to F loses some resolution, obvious when C is on edge between 2 values (ie 26C=78F, 27C=80F)
|
||||
Serial.println('F');
|
||||
}
|
||||
}
|
||||
|
||||
if (radio.receiveDone())
|
||||
{
|
||||
Serial.print("#[");
|
||||
Serial.print(++packetCount);
|
||||
Serial.print(']');
|
||||
Serial.print('[');Serial.print(radio.SENDERID, DEC);Serial.print("] ");
|
||||
if (promiscuousMode)
|
||||
{
|
||||
Serial.print("to [");Serial.print(radio.TARGETID, DEC);Serial.print("] ");
|
||||
}
|
||||
for (byte i = 0; i < radio.DATALEN; i++)
|
||||
Serial.print((char)radio.DATA[i]);
|
||||
Serial.print(" [RX_RSSI:");Serial.print(radio.RSSI);Serial.print("]");
|
||||
|
||||
if (radio.ACKRequested())
|
||||
{
|
||||
byte theNodeID = radio.SENDERID;
|
||||
radio.sendACK();
|
||||
Serial.print(" - ACK sent.");
|
||||
|
||||
// When a node requests an ACK, respond to the ACK
|
||||
// and also send a packet requesting an ACK (every 3rd one only)
|
||||
// This way both TX/RX NODE functions are tested on 1 end at the GATEWAY
|
||||
if (ackCount++%3==0)
|
||||
{
|
||||
Serial.print(" Pinging node ");
|
||||
Serial.print(theNodeID);
|
||||
Serial.print(" - ACK...");
|
||||
delay(3); //need this when sending right after reception .. ?
|
||||
if (radio.sendWithRetry(theNodeID, "ACK TEST", 8, 0)) // 0 = only 1 attempt, no retries
|
||||
Serial.print("ok!");
|
||||
else Serial.print("nothing");
|
||||
}
|
||||
}
|
||||
Serial.println();
|
||||
Blink(LED,3);
|
||||
}
|
||||
}
|
||||
|
||||
void Blink(byte PIN, int DELAY_MS)
|
||||
{
|
||||
pinMode(PIN, OUTPUT);
|
||||
digitalWrite(PIN,HIGH);
|
||||
delay(DELAY_MS);
|
||||
digitalWrite(PIN,LOW);
|
||||
}
|
||||
@@ -0,0 +1,356 @@
|
||||
// **********************************************************************************
|
||||
// IOShield sample sketch works with Moteinos equipped with RFM69W/RFM69HW/RFM69CW/RFM69HCW
|
||||
// http://www.LowPowerLab.com/IOShield
|
||||
// **********************************************************************************
|
||||
// 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
|
||||
// Example use: control your sprinkler controller wirelessly, more at lowpowerlab.com/gateway
|
||||
// Deploy and forget: wirelessly programmable via Moteino + RFM69_OTA library
|
||||
// **********************************************************************************
|
||||
// Copyright Felix Rusu 2016, http://www.LowPowerLab.com/contact
|
||||
// **********************************************************************************
|
||||
// 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.
|
||||
//
|
||||
// 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 <RFM69.h> //get it here: https://github.com/lowpowerlab/rfm69
|
||||
#include <RFM69_ATC.h> //get it here: https://github.com/lowpowerlab/RFM69
|
||||
#include <RFM69_OTA.h> //get it here: https://github.com/lowpowerlab/RFM69
|
||||
#include <SPIFlash.h> //get it here: https://github.com/lowpowerlab/spiflash
|
||||
#include <SPI.h> //included with Arduino IDE (www.arduino.cc)
|
||||
|
||||
//****************************************************************************************************************
|
||||
//**** IMPORTANT RADIO SETTINGS - YOU MUST CHANGE/CONFIGURE TO MATCH YOUR HARDWARE TRANSCEIVER CONFIGURATION! ****
|
||||
//****************************************************************************************************************
|
||||
#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_HCW //uncomment only for RFM69HW/HCW! Leave out if you have RFM69W/CW!
|
||||
//*****************************************************************************************************************************
|
||||
#define ENABLE_ATC //comment out this line to disable AUTO TRANSMISSION CONTROL
|
||||
#define ATC_RSSI -75
|
||||
//*****************************************************************************************************************************
|
||||
#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
|
||||
|
||||
#ifdef ENABLE_ATC
|
||||
RFM69_ATC radio;
|
||||
#else
|
||||
RFM69 radio;
|
||||
#endif
|
||||
|
||||
//*****************************************************************************************************************************
|
||||
// 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[30]; //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_HCW
|
||||
radio.setHighPower(); //must include this only for RFM69HW/HCW!
|
||||
#endif
|
||||
radio.encrypt(ENCRYPTKEY);
|
||||
|
||||
#ifdef ENABLE_ATC
|
||||
radio.enableAutoPower(ATC_RSSI);
|
||||
#endif
|
||||
|
||||
//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<REGISTERCOUNT;i++)
|
||||
shiftOut(DATAPIN, CLOCKPIN, MSBFIRST, 0x0);
|
||||
digitalWrite(LATCHPIN, HIGH);
|
||||
}
|
||||
|
||||
void registersAllOn() {
|
||||
digitalWrite(LATCHPIN, LOW);
|
||||
for(byte i=0;i<REGISTERCOUNT;i++)
|
||||
shiftOut(DATAPIN, CLOCKPIN, MSBFIRST, 0xFF);
|
||||
digitalWrite(LATCHPIN, HIGH);
|
||||
}
|
||||
|
||||
//writes a single bit to a daisy chain of up to 32 shift registers (max 16 IOShields) chained via LATCHPIN, CLOCKPIN, DATAPIN
|
||||
void registersWriteBit(byte whichPin) {
|
||||
byte bitPosition = whichPin % 8;
|
||||
int zeroFills = (REGISTERCOUNT - 1) - (whichPin/8);
|
||||
|
||||
if (zeroFills<0) { //whichPin was "out of bounds"
|
||||
DEBUGln("requested bit out of bounds (ie learger than available register bits to set)");
|
||||
registersClear();
|
||||
return;
|
||||
}
|
||||
|
||||
digitalWrite(LATCHPIN, LOW);
|
||||
for (byte i=0;i<zeroFills;i++)
|
||||
shiftOut(DATAPIN, CLOCKPIN, MSBFIRST, 0x0);
|
||||
|
||||
byte byteToWrite = 0;
|
||||
bitSet(byteToWrite, bitPosition);
|
||||
shiftOut(DATAPIN, CLOCKPIN, MSBFIRST, byteToWrite);
|
||||
|
||||
for (byte i=0;i<REGISTERCOUNT-zeroFills-1;i++)
|
||||
shiftOut(DATAPIN, CLOCKPIN, MSBFIRST, 0x0);
|
||||
digitalWrite(LATCHPIN, HIGH);
|
||||
}
|
||||
|
||||
//writes a byte stream to the shift register daisy chain (up to 256 daisy chained shift registers)
|
||||
void registerWriteBytes(const void* buffer, byte byteCount) {
|
||||
digitalWrite(LATCHPIN, LOW);
|
||||
for (byte i = 0; i < byteCount; i++)
|
||||
shiftOut(DATAPIN, CLOCKPIN, MSBFIRST, ((byte*)buffer)[i]);
|
||||
digitalWrite(LATCHPIN, HIGH);
|
||||
}
|
||||
|
||||
void Blink(byte PIN, byte DELAY_MS)
|
||||
{
|
||||
pinMode(PIN, OUTPUT);
|
||||
digitalWrite(PIN,HIGH);
|
||||
delay(DELAY_MS/2);
|
||||
digitalWrite(PIN,LOW);
|
||||
delay(DELAY_MS/2);
|
||||
}
|
||||
|
||||
|
||||
int checkFreeRAM()
|
||||
{
|
||||
extern int __heap_start, *__brkval;
|
||||
int v;
|
||||
return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
// Sample RFM69 sender/node sketch for mailbox motion sensor
|
||||
// http://www.lowpowerlab.com/mailbox
|
||||
// PIR motion sensor connected to D3 (INT1)
|
||||
// When RISE happens on D3, the sketch transmits a "MOTION" msg to receiver Moteino and goes back to sleep
|
||||
// It then wakes up every 32 seconds and sends a message indicating when the last
|
||||
// motion event happened (days, hours, minutes, seconds ago) and the battery level
|
||||
// In sleep mode, Moteino + PIR motion sensor use about ~78uA
|
||||
// Make sure you adjust the settings in the configuration section below !!!
|
||||
// **********************************************************************************
|
||||
// Copyright Felix Rusu 2016, http://www.LowPowerLab.com/contact
|
||||
// **********************************************************************************
|
||||
// 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.
|
||||
//
|
||||
// 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 <RFM69.h> //get it here: https://github.com/LowPowerLab/RFM69
|
||||
#include <RFM69_ATC.h>//get it here: https://github.com/lowpowerlab/RFM69
|
||||
#include <SPIFlash.h> //get it here: https://github.com/lowpowerlab/spiflash
|
||||
#include <SPI.h> //included with Arduino IDE (www.arduino.cc)
|
||||
#include <LowPower.h> //get library from: https://github.com/LowPowerLab/LowPower
|
||||
//writeup here: http://www.rocketscream.com/blog/2011/07/04/lightweight-low-power-arduino-library/
|
||||
|
||||
//****************************************************************************************************************
|
||||
//**** IMPORTANT RADIO SETTINGS - YOU MUST CHANGE/CONFIGURE TO MATCH YOUR HARDWARE TRANSCEIVER CONFIGURATION! ****
|
||||
//****************************************************************************************************************
|
||||
#define NODEID 55 //unique for each node on same network
|
||||
#define NETWORKID 100 //the same on all nodes that talk to each other
|
||||
#define GATEWAYID 1
|
||||
//Match frequency to the hardware version of the radio on your Moteino (uncomment one):
|
||||
//#define FREQUENCY RF69_433MHZ
|
||||
//#define FREQUENCY RF69_868MHZ
|
||||
#define FREQUENCY RF69_915MHZ
|
||||
//#define FREQUENCY_EXACT 917000000
|
||||
#define IS_RFM69HW_HCW //uncomment only for RFM69HW/HCW! Leave out if you have RFM69W/CW!
|
||||
#define ENCRYPTKEY "sampleEncryptKey" //exactly the same 16 characters/bytes on all nodes!
|
||||
#define SENDEVERYXLOOPS 8 //each loop sleeps 8 seconds, so send status message every this many sleep cycles (default "4" = 32 seconds)
|
||||
//*********************************************************************************************
|
||||
#define ENABLE_ATC //comment out this line to disable AUTO TRANSMISSION CONTROL
|
||||
#define ATC_RSSI -75
|
||||
//*********************************************************************************************
|
||||
|
||||
#define MOTION_PIN 3 // D3
|
||||
#define MOTION_IRQ 1 // hardware interrupt 1 (D3) - where motion sensors OUTput is connected, this will generate an interrupt every time there is MOTION
|
||||
#define BATT_MONITOR A7 //through 1Meg+470Kohm and 0.1uF cap from battery VCC - this ratio divides the voltage to bring it below 3.3V where it is scaled to a readable range
|
||||
#define BATT_CYCLES 50 // read and report battery voltage every this many sleep cycles (ex 30cycles * 8sec sleep = 240sec/4min). For 450 cyclesyou would get ~1 hour intervals
|
||||
#define BATT_FORMULA(reading) reading * 0.00322 * 1.47
|
||||
#define LED 5 // Moteinos have LEDs on D9
|
||||
#define DUPLICATE_INTERVAL 55000 //avoid duplicates in 55second intervals (ie mailman sometimes spends 30+ seconds at mailbox)
|
||||
//#define BLINK_EN //uncomment to make LED flash when messages are sent, leave out if you want low power
|
||||
|
||||
//#define SERIAL_EN //uncomment this line to enable serial IO debug messages, leave out if you want low power
|
||||
#ifdef SERIAL_EN
|
||||
#define SERIAL_BAUD 115200
|
||||
#define DEBUG(input) {Serial.print(input); delay(1);}
|
||||
#define DEBUGln(input) {Serial.println(input); delay(1);}
|
||||
#else
|
||||
#define DEBUG(input);
|
||||
#define DEBUGln(input);
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_ATC
|
||||
RFM69_ATC radio;
|
||||
#else
|
||||
RFM69 radio;
|
||||
#endif
|
||||
|
||||
volatile boolean motionDetected=false;
|
||||
char sendBuf[61];
|
||||
byte sendLen;
|
||||
byte sendLoops=0;
|
||||
unsigned long MLO=0; //MailLastOpen (ago, in ms)
|
||||
unsigned long now = 0, time=0, lastSend = 0, temp = 0;
|
||||
float batteryVolts = 5;
|
||||
char BATstr[20];
|
||||
char MLOstr[20];
|
||||
|
||||
void setup() {
|
||||
#ifdef SERIAL_EN
|
||||
Serial.begin(SERIAL_BAUD);
|
||||
#endif
|
||||
radio.initialize(FREQUENCY,NODEID,NETWORKID);
|
||||
#ifdef IS_RFM69HW_HCW
|
||||
radio.setHighPower(); //must include this only for RFM69HW/HCW!
|
||||
#endif
|
||||
radio.encrypt(ENCRYPTKEY);
|
||||
|
||||
#ifdef FREQUENCY_EXACT
|
||||
radio.setFrequency(FREQUENCY_EXACT); //set frequency to some custom frequency
|
||||
#endif
|
||||
|
||||
radio.setPowerLevel(29);
|
||||
#ifdef ENABLE_ATC
|
||||
radio.enableAutoPower(ATC_RSSI);
|
||||
#endif
|
||||
|
||||
radio.sendWithRetry(GATEWAYID, "START", 6);
|
||||
|
||||
radio.sleep();
|
||||
pinMode(MOTION_PIN, INPUT);
|
||||
pinMode(BATT_MONITOR, INPUT);
|
||||
attachInterrupt(MOTION_IRQ, motionIRQ, RISING);
|
||||
char buff[50];
|
||||
sprintf(buff, "\nTransmitting at %d Mhz...", FREQUENCY==RF69_433MHZ ? 433 : FREQUENCY==RF69_868MHZ ? 868 : 915);
|
||||
DEBUGln(buff);
|
||||
checkBattery();
|
||||
}
|
||||
|
||||
void motionIRQ()
|
||||
{
|
||||
motionDetected=true;
|
||||
DEBUGln("IRQ");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
now = millis();
|
||||
checkBattery();
|
||||
|
||||
if (motionDetected && (time-MLO > DUPLICATE_INTERVAL))
|
||||
{
|
||||
DEBUG("MOTION");
|
||||
MLO = time; //save timestamp of event
|
||||
sprintf(sendBuf, "MOTION LO:0s BAT:%sv", BATstr);
|
||||
sendLen = strlen(sendBuf);
|
||||
if (radio.sendWithRetry(GATEWAYID, sendBuf, sendLen))
|
||||
{
|
||||
DEBUGln("..OK");
|
||||
#ifdef BLINK_EN
|
||||
Blink(LED,3);
|
||||
#endif
|
||||
}
|
||||
else DEBUGln("..NOK");
|
||||
radio.sleep();
|
||||
}
|
||||
else sendLoops++;
|
||||
|
||||
//send readings every SENDEVERYXLOOPS
|
||||
if (sendLoops>=SENDEVERYXLOOPS)
|
||||
{
|
||||
sendLoops=0;
|
||||
char periodO='X', periodC='X';
|
||||
unsigned long lastOpened = (time - MLO) / 1000; //get seconds
|
||||
unsigned long LO = lastOpened;
|
||||
|
||||
if (lastOpened <= 59) periodO = 's'; //1-59 seconds
|
||||
else if (lastOpened <= 3599) { periodO = 'm'; lastOpened/=60; } //1-59 minutes
|
||||
else if (lastOpened <= 259199) { periodO = 'h'; lastOpened/=3600; } // 1-71 hours
|
||||
else if (lastOpened >= 259200) { periodO = 'd'; lastOpened/=86400; } // >=3 days
|
||||
|
||||
if (periodO == 'd')
|
||||
sprintf(MLOstr, "LO:%ldd%ldh", lastOpened, (LO%86400)/3600);
|
||||
else if (periodO == 'h')
|
||||
sprintf(MLOstr, "LO:%ldh%ldm", lastOpened, (LO%3600)/60);
|
||||
else sprintf(MLOstr, "LO:%ld%c", lastOpened, periodO);
|
||||
|
||||
sprintf(sendBuf, "%s BAT:%sv", MLOstr, BATstr);
|
||||
sendLen = strlen(sendBuf);
|
||||
radio.send(GATEWAYID, sendBuf, sendLen);
|
||||
radio.sleep();
|
||||
DEBUG(sendBuf); DEBUG(" ("); DEBUG(sendLen); DEBUGln(")");
|
||||
lastSend = time;
|
||||
#ifdef BLINK_EN
|
||||
Blink(LED, 5);
|
||||
#endif
|
||||
}
|
||||
|
||||
motionDetected=false; //do NOT move this after the SLEEP line below or motion will never be detected
|
||||
time = time + 8000 + millis()-now + 480; //correct millis() resonator drift, may need to be tweaked to be accurate
|
||||
LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
|
||||
DEBUGln("WAKEUP");
|
||||
}
|
||||
|
||||
void Blink(byte PIN, int DELAY_MS)
|
||||
{
|
||||
pinMode(PIN, OUTPUT);
|
||||
digitalWrite(PIN,HIGH);
|
||||
delay(DELAY_MS);
|
||||
digitalWrite(PIN,LOW);
|
||||
}
|
||||
|
||||
byte cycleCount=BATT_CYCLES;
|
||||
void checkBattery()
|
||||
{
|
||||
if (cycleCount++ == BATT_CYCLES) //only read battery every BATT_CYCLES sleep cycles
|
||||
{
|
||||
unsigned int reading=0;
|
||||
//enable battery monitor
|
||||
pinMode(A3, OUTPUT);
|
||||
digitalWrite(A3, LOW);
|
||||
|
||||
for (byte i=0; i<10; i++)
|
||||
reading += analogRead(BATT_MONITOR);
|
||||
|
||||
//disable battery monitor
|
||||
pinMode(A3, INPUT); //highZ mode will allow p-mosfet to be pulled high and disconnect the voltage divider on the weather shield
|
||||
batteryVolts = BATT_FORMULA(reading/10);
|
||||
DEBUG("reading:"); DEBUGln(reading);
|
||||
dtostrf(batteryVolts, 3,2, BATstr);
|
||||
cycleCount = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
// *************************************************************************************************************
|
||||
// MightyBoost control sample sketch
|
||||
// *************************************************************************************************************
|
||||
// Copyright (2015) Felix Rusu of http://lowpowerlab.com
|
||||
// http://lowpowerlab.com/mightyboost
|
||||
// MightyBoost is a smart backup PSU controllable by Moteino, and this sketch is a sample control sketch to run
|
||||
// MightyBoost in this mode.
|
||||
// Be sure to check back for code updates and patches
|
||||
// *************************************************************************************************************
|
||||
// This sketch will provide control over the essential features of MightyBoost:
|
||||
// - provide switched 5V power to a sensitive load like RaspberryPi which should not lose power instantly
|
||||
// - Control the "5V*" output via Moteino+PowerButton (momentary tactile)
|
||||
// - Monitor input supply and switch to battery backup when external power is lost
|
||||
// - Monitor battery voltage and issue a shutdown/reboot signal when battery runs low
|
||||
// This sketch may be extended to include integration with other LowPowerLab automation products
|
||||
// *************************************************************************************************************
|
||||
// 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 2 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, write
|
||||
// to the Free Software Foundation, Inc.,
|
||||
// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//
|
||||
// Licence can be viewed at
|
||||
// http://www.fsf.org/licenses/gpl.txt
|
||||
//
|
||||
// Please maintain this license information along with authorship
|
||||
// and copyright notices in any redistribution of this code
|
||||
// *************************************************************************************************************
|
||||
#define LED 5 // LED pin, should be analog for fading effect (PWM)
|
||||
#define BUTTON 3 // Power button pin
|
||||
#define SIG_SHUTOFF 6 // Signal to Pi to ask for a shutdown
|
||||
#define SIG_BOOTOK A0 // Signal from Pi that it's OK to cutoff power
|
||||
// !!NOTE!! Originally this was D7 but it was moved to A0 at least temporarily.
|
||||
// On MightyBoost R1 you need to connect D7 and A0 with a jumper wire.
|
||||
// The explanation for this is given here: http://lowpowerlab.com/mightyboost/#source
|
||||
#define OUTPUT_5V 4 // HIGH on this pin will switch the "5V*" output ON
|
||||
#define BATTERYSENSE A7 // Sense VBAT_COND signal (when powered externally should read ~3.25v/3.3v (1000-1023), when external power is cutoff it should start reading around 2.85v/3.3v * 1023 ~= 880 (ratio given by 10k+4.7K divider from VBAT_COND = 1.47 multiplier)
|
||||
// hence the actual input voltage = analogRead(A7) * 0.00322 (3.3v/1024) * 1.47 (10k+4.7k voltage divider ratio)
|
||||
// when plugged in this should be 4.80v, nothing to worry about
|
||||
// when on battery power this should decrease from 4.15v (fully charged Lipoly) to 3.3v (discharged Lipoly)
|
||||
// trigger a shutdown to the target device once voltage is around 3.4v to allow 30sec safe shutdown
|
||||
#define LOWBATTERYTHRESHOLD 3.5 // a shutdown will be triggered to the target device when battery voltage drops below this (Volts)
|
||||
|
||||
#define RESETHOLDTIME 500 // Button must be hold this many mseconds before a reset is issued (should be much less than SHUTDOWNHOLDTIME)
|
||||
#define SHUTDOWNHOLDTIME 2000 // Button must be hold this many mseconds before a shutdown sequence is started (should be much less than ForcedShutoffDelay)
|
||||
#define ShutoffTriggerDelay 6000 // will start checking the SIG_BOOTOK line after this long
|
||||
#define RecycleTime 50000 // window of time in which SIG_BOOTOK is expected to go HIGH
|
||||
// should be at least 3000 more than Min
|
||||
// if nothing happens after this window, if button is
|
||||
// still pressed, force cutoff power, otherwise switch back to normal ON state
|
||||
#define RESETPULSETIME 500 // When reset is issued, the SHUTOFF signal is held HIGH this many ms
|
||||
#define ForcedShutoffDelay 7500 // when SIG_BOOTOK==0 (PI in unknown state): if button is held
|
||||
// for this long, force shutdown (this should be less than RecycleTime)
|
||||
#define ShutdownFinalDelay 4500 // after shutdown signal is received, delay for this long
|
||||
// to allow all PI LEDs to stop activity (pulse LED faster)
|
||||
|
||||
#define PRINTPERIOD 10000
|
||||
|
||||
int lastValidReading = 1;
|
||||
unsigned long lastValidReadingTime = 0;
|
||||
unsigned long NOW=0;
|
||||
int PowerState = 0;
|
||||
long lastPeriod = -1;
|
||||
float systemVoltage = 5;
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
pinMode(BUTTON, INPUT_PULLUP);
|
||||
pinMode(SIG_BOOTOK, INPUT);
|
||||
pinMode(SIG_SHUTOFF, OUTPUT);
|
||||
pinMode(LED, OUTPUT);
|
||||
pinMode(OUTPUT_5V, OUTPUT);
|
||||
pinMode(A7, INPUT);
|
||||
digitalWrite(SIG_SHUTOFF, LOW);//added after sudden shutdown quirks, DO NOT REMOVE!
|
||||
digitalWrite(OUTPUT_5V, LOW);//added after sudden shutdown quirks, DO NOT REMOVE!
|
||||
}
|
||||
|
||||
void loop() {
|
||||
int reading = digitalRead(BUTTON);
|
||||
NOW = millis();
|
||||
digitalWrite(SIG_SHUTOFF, LOW);//added after sudden shutdown quirks, DO NOT REMOVE!
|
||||
boolean batteryLow = systemVoltage < LOWBATTERYTHRESHOLD;
|
||||
|
||||
if (batteryLow || reading != lastValidReading && NOW - lastValidReadingTime > 200)
|
||||
{
|
||||
lastValidReading = reading;
|
||||
lastValidReadingTime = NOW;
|
||||
|
||||
if (batteryLow || reading == 0)
|
||||
{
|
||||
//make sure the button is held down for at least 'RESETHOLDTIME' before taking action (this is to avoid accidental button presses and consequently Pi shutdowns)
|
||||
NOW = millis();
|
||||
while (!batteryLow && (PowerState == 1 && millis()-NOW < RESETHOLDTIME)) { delay(10); if (digitalRead(BUTTON) != 0) return; }
|
||||
|
||||
//RESETHOLDTIME is satisfied, now check if button still held until SHUTDOWNHOLDTIME is satisfied
|
||||
analogWrite(LED, 128); //dim the LED to show something's going on
|
||||
while (!batteryLow && (PowerState == 1 && millis()-NOW < SHUTDOWNHOLDTIME))
|
||||
{
|
||||
if (digitalRead(BUTTON) != 0)
|
||||
{
|
||||
if (BOOTOK()) //SIG_BOOTOK is HIGH so Pi is running the shutdowncheck.sh script, ready to intercept the RESET PULSE
|
||||
{
|
||||
digitalWrite(SIG_SHUTOFF, HIGH);
|
||||
delay(RESETPULSETIME);
|
||||
digitalWrite(SIG_SHUTOFF, LOW);
|
||||
|
||||
NOW = millis();
|
||||
boolean recycleDetected=false;
|
||||
while (millis()-NOW < RecycleTime) //blink LED while waiting for BOOTOK to go high
|
||||
{
|
||||
//blink 3 times and pause
|
||||
digitalWrite(LED, LOW);
|
||||
delay(100);
|
||||
digitalWrite(LED, HIGH);
|
||||
delay(100);
|
||||
digitalWrite(LED, LOW);
|
||||
delay(100);
|
||||
digitalWrite(LED, HIGH);
|
||||
delay(100);
|
||||
digitalWrite(LED, LOW);
|
||||
delay(100);
|
||||
digitalWrite(LED, HIGH);
|
||||
delay(500);
|
||||
|
||||
if (!BOOTOK()) recycleDetected = true;
|
||||
else if (BOOTOK() && recycleDetected)
|
||||
return;
|
||||
}
|
||||
return; //reboot pulse sent but it appears a reboot failed; exit all checks
|
||||
}
|
||||
else return; //ignore everything else (button was held for RESETHOLDTIME, but SIG_BOOTOK was LOW)
|
||||
}
|
||||
}
|
||||
|
||||
//SIG_BOOTOK must be HIGH when Pi is ON. During boot, this will take a while to happen (till it executes the "shutdowncheck" script)
|
||||
//so I dont want to cutoff power before it had a chance to fully boot up
|
||||
if (batteryLow || (PowerState == 1 && BOOTOK()))
|
||||
{
|
||||
// signal Pi to shutdown
|
||||
digitalWrite(SIG_SHUTOFF, HIGH);
|
||||
|
||||
//now wait for the Pi to signal back
|
||||
NOW = millis();
|
||||
float in, out;
|
||||
boolean forceShutdown = true;
|
||||
|
||||
while (millis()-NOW < RecycleTime)
|
||||
{
|
||||
if (in > 6.283) in = 0;
|
||||
in += .00628;
|
||||
|
||||
out = sin(in) * 127.5 + 127.5;
|
||||
analogWrite(LED,out);
|
||||
delayMicroseconds(1500);
|
||||
|
||||
//account for force-shutdown action (if button held for ForcedShutoffDelay, then force shutdown regardless)
|
||||
if (millis()-NOW <= (ForcedShutoffDelay-SHUTDOWNHOLDTIME) && digitalRead(BUTTON) != 0)
|
||||
forceShutdown = false;
|
||||
if (millis()-NOW >= (ForcedShutoffDelay-SHUTDOWNHOLDTIME) && forceShutdown)
|
||||
{
|
||||
PowerState = 0;
|
||||
digitalWrite(LED, PowerState); //turn off LED to indicate power is being cutoff
|
||||
digitalWrite(OUTPUT_5V, PowerState);
|
||||
break;
|
||||
}
|
||||
|
||||
if (millis() - NOW > ShutoffTriggerDelay)
|
||||
{
|
||||
// Pi signaling OK to turn off
|
||||
if (!BOOTOK())
|
||||
{
|
||||
PowerState = 0;
|
||||
digitalWrite(LED, PowerState); //turn off LED to indicate power is being cutoff
|
||||
NOW = millis();
|
||||
while (millis()-NOW < ShutdownFinalDelay)
|
||||
{
|
||||
if (in > 6.283) in = 0;
|
||||
in += .00628;
|
||||
|
||||
out = sin(in) * 127.5 + 127.5;
|
||||
analogWrite(LED,out);
|
||||
delayMicroseconds(300);
|
||||
}
|
||||
|
||||
digitalWrite(OUTPUT_5V, PowerState);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// last chance: if power still on but button still pressed, force cutoff power
|
||||
if (PowerState == 1 && digitalRead(BUTTON) == 0)
|
||||
{
|
||||
PowerState = 0;
|
||||
digitalWrite(OUTPUT_5V, PowerState);
|
||||
}
|
||||
|
||||
digitalWrite(SIG_SHUTOFF, LOW);
|
||||
}
|
||||
else if (PowerState == 1 && !BOOTOK())
|
||||
{
|
||||
NOW = millis();
|
||||
unsigned long NOW2 = millis();
|
||||
int analogstep = 255 / ((ForcedShutoffDelay-SHUTDOWNHOLDTIME)/100); //every 500ms decrease LED intensity
|
||||
while (digitalRead(BUTTON) == 0)
|
||||
{
|
||||
if (millis()-NOW2 > 100)
|
||||
{
|
||||
analogWrite(LED, 255 - ((millis()-NOW)/100)*analogstep);
|
||||
NOW2 = millis();
|
||||
}
|
||||
if (millis()-NOW > ForcedShutoffDelay-SHUTDOWNHOLDTIME)
|
||||
{
|
||||
//TODO: add blinking here to signal final shutdown delay
|
||||
PowerState = 0;
|
||||
digitalWrite(OUTPUT_5V, PowerState);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (PowerState == 0)
|
||||
{
|
||||
PowerState = 1;
|
||||
digitalWrite(OUTPUT_5V, PowerState); //digitalWrite(LED, PowerState);
|
||||
}
|
||||
}
|
||||
|
||||
digitalWrite(LED, PowerState);
|
||||
}
|
||||
|
||||
int currPeriod = millis()/PRINTPERIOD;
|
||||
if (currPeriod != lastPeriod)
|
||||
{
|
||||
lastPeriod=currPeriod;
|
||||
Serial.print("VIN: ");
|
||||
systemVoltage = analogRead(BATTERYSENSE) * 0.00322 * 1.47;
|
||||
Serial.print(systemVoltage);
|
||||
if (systemVoltage > 4.3)
|
||||
Serial.println(" (plugged in)");
|
||||
else Serial.println(" (running from battery!)");
|
||||
}
|
||||
}
|
||||
|
||||
boolean BOOTOK() {
|
||||
return analogRead(SIG_BOOTOK) > 800;
|
||||
}
|
||||
@@ -0,0 +1,756 @@
|
||||
// **********************************************************************************************************
|
||||
// MightyHat gateway base unit sketch that works with MightyHat equipped with RFM69W/RFM69HW/RFM69CW/RFM69HCW
|
||||
// This will relay all RF data over serial to the host computer (RaspberryPi, PC etc) and vice versa
|
||||
// http://LowPowerLab.com/MightyHat
|
||||
// Also see http://LowPowerLab.com/gateway
|
||||
// **********************************************************************************
|
||||
// Copyright Felix Rusu 2016, http://www.LowPowerLab.com/contact
|
||||
// **********************************************************************************
|
||||
// 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.
|
||||
//
|
||||
// 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
|
||||
// **********************************************************************************
|
||||
#define MHAT_VERSION 3 //latest version is R3, change to "2" if you have a MightyHat R2
|
||||
// ****************************************************************************************
|
||||
#include <RFM69.h> //get it here: https://github.com/lowpowerlab/rfm69
|
||||
#include <RFM69_ATC.h> //get it here: https://github.com/lowpowerlab/RFM69
|
||||
#include <RFM69_OTA.h> //get it here: https://github.com/lowpowerlab/RFM69
|
||||
#include <SPIFlash.h> //get it here: https://github.com/lowpowerlab/spiflash
|
||||
#include <SPI.h> //included with Arduino IDE (www.arduino.cc)
|
||||
#include "U8glib.h" //https://bintray.com/olikraus/u8glib/Arduino
|
||||
//u8g compared to adafruit lib: https://www.youtube.com/watch?v=lkWZuAnHa2Y
|
||||
//draing bitmaps: https://www.coconauts.net/blog/2015/01/19/easy-draw-bitmaps-arduino/
|
||||
//****************************************************************************************************************
|
||||
//**** IMPORTANT RADIO SETTINGS - YOU MUST CHANGE/CONFIGURE TO MATCH YOUR HARDWARE TRANSCEIVER CONFIGURATION! ****
|
||||
//****************************************************************************************************************
|
||||
#define NODEID 1 //the gateway has ID=1
|
||||
#define NETWORKID 100 //all nodes on the same network can talk to each other
|
||||
//#define FREQUENCY RF69_433MHZ //Match this with the version of your Moteino! (others: RF69_433MHZ, RF69_868MHZ)
|
||||
#define FREQUENCY RF69_915MHZ //Match this with the version of your Moteino! (others: RF69_433MHZ, RF69_868MHZ)
|
||||
//#define FREQUENCY_EXACT 917000000 //uncomment and set to a specific frequency in Hz, if commented the center frequency is used
|
||||
#define ENCRYPTKEY "sampleEncryptKey" //has to be same 16 characters/bytes on all nodes, not more not less!
|
||||
#define IS_RFM69HW_HCW //uncomment only for RFM69HW/HCW! Leave out if you have RFM69W/CW!
|
||||
#define ENABLE_ATC //comment out this line to disable AUTO TRANSMISSION CONTROL //more here: http://lowpowerlab.com/blog/2015/11/11/rfm69_atc-automatic-transmission-control/
|
||||
//#define ENABLE_WIRELESS_PROGRAMMING //comment out this line to disable Wireless Programming of this gateway node
|
||||
#define ENABLE_LCD //comment this out if you don't have or don't want to use the LCD
|
||||
//*****************************************************************************************************************************
|
||||
#define ACK_TIME 30 // # of ms to wait for an ack
|
||||
#define SERIAL_BAUD 19200
|
||||
#define DEBUG_EN //comment out if you don't want any serial verbose output (keep out in real use)
|
||||
|
||||
#define BTN_LED_RED 9
|
||||
#define BTN_LED_GRN 6 // This will indicate when Pi has power
|
||||
#define POWER_LED_RED() { digitalWrite(BTN_LED_RED, HIGH); digitalWrite(BTN_LED_GRN, LOW); }
|
||||
#define POWER_LED_GRN() { digitalWrite(BTN_LED_RED, LOW); digitalWrite(BTN_LED_GRN, HIGH); }
|
||||
#define POWER_LED_ORANGE() { digitalWrite(BTN_LED_RED, HIGH); digitalWrite(BTN_LED_GRN, HIGH); }
|
||||
#define POWER_LED_OFF() { digitalWrite(BTN_LED_RED, LOW); digitalWrite(BTN_LED_GRN, LOW); }
|
||||
#define ON 1
|
||||
#define OFF 0
|
||||
|
||||
#define FLASH_CS 8
|
||||
|
||||
#define BUZZER 5 // Buzzer attached to D5 (PWM pin required for tones)
|
||||
#define BUTTON A2 // Power button pin
|
||||
#define BUTTON1 A4 // Backlight control button
|
||||
#define BUTTON2 A5 // Backlight control button
|
||||
#define LATCH_EN 4
|
||||
#define LATCH_VAL 7
|
||||
#define SIG_SHUTOFF A3 // Signal to Pi to ask for a shutdown
|
||||
#define SIG_BOOTOK A6 // Signal from Pi that it's OK to cutoff power
|
||||
// !!NOTE!! Originally this was D7 but it was moved to A0 at least temporarily.
|
||||
// On MightyBoost R1 you need to connect D7 and A0 with a jumper wire.
|
||||
// The explanation for this is given here: http://lowpowerlab.com/mightyboost/#source
|
||||
#define BATTERYSENSE A7 // Sense VBAT_COND signal (when powered externally should read ~3.25v/3.3v (1000-1023), when external power is cutoff it should start reading around 2.85v/3.3v * 1023 ~= 880 (ratio given by 10k+4.7K divider from VBAT_COND = 1.47 multiplier)
|
||||
// hence the actual input voltage = analogRead(A7) * 0.00322 (3.3v/1024) * 1.47 (10k+4.7k voltage divider ratio)
|
||||
// when plugged in this should be 4.80v, nothing to worry about
|
||||
// when on battery power this should decrease from 4.15v (fully charged Lipoly) to 3.3v (discharged Lipoly)
|
||||
// trigger a shutdown to the target device once voltage is around 3.4v to allow 30sec safe shutdown
|
||||
|
||||
#define BATTERY_VOLTS(analog_reading) analog_reading * 0.00322 * 1.51 // 100/66 is the inverse ratio of the voltage divider ( Batt > 1MEG > A7 > 2MEG > GND )
|
||||
#define LOWBATTERYTHRESHOLD 3.5 // a shutdown will be triggered to the target device when battery voltage drops below this (Volts)
|
||||
#define CHARGINGTHRESHOLD 4.3
|
||||
#define RESETHOLDTIME 500 // Button must be hold this many mseconds before a reset is issued (should be much less than SHUTDOWNHOLDTIME)
|
||||
#define SHUTDOWNHOLDTIME 2000 // Button must be hold this many mseconds before a shutdown sequence is started (should be much less than ForcedShutoffDelay)
|
||||
#define ShutoffTriggerDelay 6000 // will start checking the SIG_BOOTOK line after this long
|
||||
#define RESETPULSETIME 500 // When reset is issued, the SHUTOFF signal is held HIGH this many ms
|
||||
#define ForcedShutoffDelay 7500 // when SIG_BOOTOK==0 (PI in unknown state): if button is held
|
||||
// for this long, force shutdown (this should be less than RecycleTime)
|
||||
#define ShutdownFinalDelay 4500 // after shutdown signal is received, delay for this long
|
||||
// to allow all PI LEDs to stop activity (pulse LED faster)
|
||||
#define RecycleTime 60000 // window of time in which SIG_BOOTOK is expected to go HIGH
|
||||
// should be at least 3000 more than Min
|
||||
// if nothing happens after this window, if button is
|
||||
// still pressed, force cutoff power, otherwise switch back to normal ON state
|
||||
#define BATTERYREADINTERVAL 2000
|
||||
|
||||
#ifdef DEBUG_EN
|
||||
#define DEBUG(input) Serial.print(input)
|
||||
#define DEBUGln(input) Serial.println(input)
|
||||
#else
|
||||
#define DEBUG(input)
|
||||
#define DEBUGln(input)
|
||||
#endif
|
||||
|
||||
//general variables
|
||||
byte ackCount=0;
|
||||
String inputstr;
|
||||
byte inputLen=0;
|
||||
char RSSIstr[] = "-100dBm";
|
||||
char temp[64];
|
||||
|
||||
byte lastValidReading = 1;
|
||||
unsigned long lastValidReadingTime = 0;
|
||||
unsigned long NOW=0;
|
||||
byte PowerState = OFF;
|
||||
long lastPeriod = -1;
|
||||
int rssi=0;
|
||||
float systemVoltage = 5;
|
||||
float systemVoltagePrevious = 5;
|
||||
boolean batteryLow=false;
|
||||
boolean batteryLowShutdown=false;
|
||||
|
||||
SPIFlash flash(FLASH_CS, 0xEF30); //EF30 for 4mbit Windbond FLASH MEM
|
||||
#ifdef ENABLE_ATC
|
||||
RFM69_ATC radio;
|
||||
#else
|
||||
RFM69 radio;
|
||||
#endif
|
||||
|
||||
//******************************************** BEGIN LCD STUFF ********************************************************************************
|
||||
char lcdbuff[80];
|
||||
#ifdef ENABLE_LCD
|
||||
|
||||
#if defined(MHAT_VERSION) && (MHAT_VERSION == 3)
|
||||
#define PIN_LCD_CS A1 //Pin 2 on LCD, lcd CS is shared with Latch value pin since they are both outputs and never HIGH at the same time
|
||||
#define PIN_LCD_RST U8G_PIN_NONE //this is tied directly to the atmega RST
|
||||
#else
|
||||
#define PIN_LCD_CS LATCH_VAL //Pin 2 on LCD, lcd CS is shared with Latch value pin since they are both outputs and never HIGH at the same time
|
||||
#define PIN_LCD_RST A1 //Pin 1 on LCD
|
||||
#endif
|
||||
|
||||
#define PIN_LCD_DC A0 //Pin 3 on LCD
|
||||
#define PIN_LCD_LIGHT 3 //Backlight pin
|
||||
#define xbmp_logo_width 30
|
||||
#define xbmp_logo_height 27
|
||||
#define BACKLIGHTLEVELS 5 //5 levels gives a nice round number that allows full brightness
|
||||
void LCD_BACKLIGHT(byte level) { if (level>BACKLIGHTLEVELS) level=BACKLIGHTLEVELS; analogWrite(PIN_LCD_LIGHT, 255-level*255/BACKLIGHTLEVELS); }
|
||||
byte backlightLevel=BACKLIGHTLEVELS; //max at startup
|
||||
|
||||
const uint8_t xbmp_logo[] PROGMEM = {
|
||||
0xe0, 0xff, 0xff, 0x01, 0xf0, 0xff, 0xff, 0x03, 0x08, 0x00, 0x00, 0x04,
|
||||
0x06, 0x00, 0x00, 0x18, 0xc3, 0x03, 0xf0, 0x30, 0x23, 0x04, 0x08, 0x31,
|
||||
0x23, 0x04, 0x08, 0x31, 0x23, 0x0c, 0x0c, 0x31, 0xc3, 0x13, 0xf2, 0x30,
|
||||
0x03, 0xe0, 0x01, 0x30, 0x03, 0xe0, 0x01, 0x30, 0xc3, 0xe3, 0xf1, 0x30,
|
||||
0x23, 0xe4, 0x09, 0x31, 0x23, 0xfc, 0x0f, 0x31, 0x23, 0xe4, 0x09, 0x31,
|
||||
0xc3, 0xe3, 0xf1, 0x30, 0x03, 0xe0, 0x01, 0x30, 0x03, 0xe0, 0x01, 0x30,
|
||||
0xc3, 0x13, 0xf2, 0x30, 0x23, 0x0c, 0x0c, 0x31, 0x23, 0x04, 0x08, 0x31,
|
||||
0x23, 0x04, 0x08, 0x31, 0xc3, 0x03, 0xf0, 0x30, 0x06, 0x00, 0x00, 0x18,
|
||||
0x08, 0x00, 0x00, 0x04, 0xf0, 0xff, 0xff, 0x03, 0xe0, 0xff, 0xff, 0x01 };
|
||||
|
||||
#define xbmp_batt_width 9
|
||||
#define xbmp_batt_height 6
|
||||
const uint8_t xbmp_batt_c[] PROGMEM = { 0xff, 0x00, 0xbf, 0x00, 0x9f, 0x01, 0x8f, 0x01, 0x87, 0x00, 0xff, 0x00 };
|
||||
const uint8_t xbmp_batt_x[] PROGMEM = { 0xff, 0x00, 0xa5, 0x00, 0x81, 0x01, 0x99, 0x01, 0xa5, 0x00, 0xff, 0x00 };
|
||||
|
||||
const uint8_t xbmp_batt_0[] PROGMEM = { };
|
||||
const uint8_t xbmp_batt_1[] PROGMEM = { 0xff, 0x00, 0x83, 0x00, 0x83, 0x01, 0x83, 0x01, 0x83, 0x00, 0xff, 0x00 };
|
||||
const uint8_t xbmp_batt_2[] PROGMEM = { 0xff, 0x00, 0x87, 0x00, 0x87, 0x01, 0x87, 0x01, 0x87, 0x00, 0xff, 0x00 };
|
||||
const uint8_t xbmp_batt_3[] PROGMEM = { 0xff, 0x00, 0x8f, 0x00, 0x8f, 0x01, 0x8f, 0x01, 0x8f, 0x00, 0xff, 0x00 };
|
||||
const uint8_t xbmp_batt_4[] PROGMEM = { 0xff, 0x00, 0x9f, 0x00, 0x9f, 0x01, 0x9f, 0x01, 0x9f, 0x00, 0xff, 0x00 };
|
||||
const uint8_t xbmp_batt_5[] PROGMEM = { 0xff, 0x00, 0xbf, 0x00, 0xbf, 0x01, 0xbf, 0x01, 0xbf, 0x00, 0xff, 0x00 };
|
||||
const uint8_t xbmp_batt_6[] PROGMEM = { 0xff, 0x00, 0xff, 0x00, 0xff, 0x01, 0xff, 0x01, 0xff, 0x00, 0xff, 0x00 };
|
||||
|
||||
#define xbmp_rssi_width 7
|
||||
#define xbmp_rssi_height 6
|
||||
const uint8_t xbmp_rssi_1[] PROGMEM = { 0x40, 0x10, 0x00, 0x04, 0x04, 0x05 };
|
||||
const uint8_t xbmp_rssi_2[] PROGMEM = { 0x40, 0x10, 0x10, 0x14, 0x14, 0x15 };
|
||||
const uint8_t xbmp_rssi_3[] PROGMEM = { 0x40, 0x50, 0x50, 0x54, 0x54, 0x55 };
|
||||
const uint8_t xbmp_rssi_0[] PROGMEM = { 0x40, 0x10, 0x00, 0x04, 0x00, 0x01 };
|
||||
|
||||
U8GLIB_PCD8544 lcd(PIN_LCD_CS, PIN_LCD_DC, PIN_LCD_RST); //hardware SPI
|
||||
//U8GLIB_PCD8544 lcd(SCK, MOSI, PIN_LCD_CS, PIN_LCD_DC , PIN_LCD_RST); //software SPI
|
||||
|
||||
//******************************************** LCD FUNCTIONS ********************************************************************************
|
||||
void drawLogo() {
|
||||
lcd.firstPage();
|
||||
do {
|
||||
lcd.drawXBMP((84-xbmp_logo_width)/2, (48-xbmp_logo_height)/2, xbmp_logo_width, xbmp_logo_height, xbmp_logo); //tutorial: https://www.coconauts.net/blog/2015/01/19/easy-draw-bitmaps-arduino/
|
||||
} while(lcd.nextPage());
|
||||
}
|
||||
|
||||
void clearDisplay() { lcd.firstPage(); do{}while(lcd.nextPage()); }
|
||||
|
||||
void refreshLCD() {
|
||||
noInterrupts(); //while messing with LCD need to pause interrups from radio to avoid SPI conflicts!
|
||||
byte lcdwidth = lcd.getWidth();
|
||||
byte lcdheight = lcd.getHeight();
|
||||
char c;
|
||||
byte i,pos,swidth;
|
||||
byte * bmpPtr;
|
||||
|
||||
//u8glib picture loop
|
||||
lcd.firstPage();
|
||||
do {
|
||||
lcd.setFont(u8g_font_profont10);
|
||||
lcd.setFontRefHeightText();
|
||||
lcd.setFontPosTop();
|
||||
byte fontheight = lcd.getFontAscent()-lcd.getFontDescent();
|
||||
char * textp = lcdbuff;
|
||||
byte textLength = strlen(textp);
|
||||
byte line=0;
|
||||
byte done = false;
|
||||
|
||||
//this section splits the textp string into chunks that fit on the screen width and prints each to a new line
|
||||
while(textLength && !done)
|
||||
{
|
||||
for (i=1;i<=textLength;i++)
|
||||
{
|
||||
c = textp[i];
|
||||
textp[i]=0;
|
||||
swidth = lcd.getStrWidth(textp);
|
||||
textp[i] = c;
|
||||
if (c=='\n') { pos = i; break; } //newline char found, skip it and go to next line
|
||||
if (swidth > lcdwidth) { pos = i-1; break; } //line is full, go to next line
|
||||
else if (i==textLength) { done = true; }
|
||||
}
|
||||
if (!done)
|
||||
{
|
||||
c = textp[pos];
|
||||
textp[pos]=0;
|
||||
}
|
||||
lcd.drawStr(0, line * fontheight, textp);
|
||||
if (done) break;
|
||||
textp[pos] = c;
|
||||
textp += pos;
|
||||
textLength -= pos;
|
||||
line++;
|
||||
}
|
||||
|
||||
lcd.setFontPosBaseline();
|
||||
|
||||
//print battery voltage and icon
|
||||
if (systemVoltage >= 4.3) bmpPtr = (byte*)xbmp_batt_c;
|
||||
else if (systemVoltage >= 4) bmpPtr = (byte*)xbmp_batt_6;
|
||||
else if (systemVoltage >= 3.9) bmpPtr = (byte*)xbmp_batt_5;
|
||||
else if (systemVoltage >= 3.8) bmpPtr = (byte*)xbmp_batt_4;
|
||||
else if (systemVoltage >= 3.7) bmpPtr = (byte*)xbmp_batt_3;
|
||||
else if (systemVoltage >= 3.6) bmpPtr = (byte*)xbmp_batt_2;
|
||||
else if (systemVoltage >= 3.5) bmpPtr = (byte*)xbmp_batt_1;
|
||||
else bmpPtr = (byte*)xbmp_batt_x;
|
||||
lcd.drawXBMP(lcdwidth-xbmp_batt_width, lcdheight-xbmp_batt_height, xbmp_batt_width, xbmp_batt_height, bmpPtr);
|
||||
|
||||
lcd.setPrintPos(54, 48);
|
||||
if (systemVoltage >= CHARGINGTHRESHOLD)
|
||||
lcd.print("CHRG");
|
||||
else
|
||||
lcd.print(systemVoltage); //sprintf(BATvstr, "%sv", BATstr);
|
||||
|
||||
lcd.setPrintPos(0, 40);
|
||||
uint16_t uptimeSeconds = millis()/1000;
|
||||
if (uptimeSeconds<60)
|
||||
sprintf(temp, "up:%us", uptimeSeconds);
|
||||
else
|
||||
sprintf(temp, "up:%um", uptimeSeconds/60);
|
||||
lcd.print(temp);
|
||||
|
||||
lcd.setPrintPos(45, 40);
|
||||
sprintf(temp, "RAM:%u", checkFreeRAM());
|
||||
lcd.print(temp);
|
||||
|
||||
//print rssi and icon
|
||||
if (rssi > -70) bmpPtr = (byte*)xbmp_rssi_3;
|
||||
else if (rssi > -80) bmpPtr = (byte*)xbmp_rssi_2;
|
||||
else if (rssi > -90) bmpPtr = (byte*)xbmp_rssi_1;
|
||||
else if (rssi > -95) bmpPtr = (byte*)xbmp_rssi_0;
|
||||
lcd.drawXBMP(0, lcdheight-xbmp_rssi_height, xbmp_rssi_width, xbmp_rssi_height, bmpPtr);
|
||||
lcd.drawStr(xbmp_rssi_width+1, 48, RSSIstr);
|
||||
} while(lcd.nextPage());
|
||||
digitalWrite(PIN_LCD_CS, HIGH);
|
||||
interrupts(); //re-enable interrupts
|
||||
}
|
||||
#endif
|
||||
//******************************************** END LCD STUFF ********************************************************************************
|
||||
|
||||
//******************************************** MESSAGE HISTORY ******************************************************************************
|
||||
#define MSG_MAX_LEN 32 //truncate message at 32 chars since most are shorter than that anyway
|
||||
#define HISTORY_LEN 10 //hold this many past messages (IMPORTANT: 10 records needs about 330 bytes of RAM so be careful about making this too large)
|
||||
typedef struct {
|
||||
char data[MSG_MAX_LEN];
|
||||
int rssi;
|
||||
} Message;
|
||||
Message * messageHistory = new Message[HISTORY_LEN];
|
||||
byte lastMessageIndex = HISTORY_LEN;
|
||||
byte currMessageIndex = HISTORY_LEN;
|
||||
byte historyLength = 0;
|
||||
|
||||
void saveToHistory(char * msg, int rssi)
|
||||
{
|
||||
byte length = strlen(msg);
|
||||
byte i = 0;
|
||||
if (lastMessageIndex >= HISTORY_LEN-1) lastMessageIndex = 0;
|
||||
else lastMessageIndex++;
|
||||
if (historyLength < HISTORY_LEN) historyLength++;
|
||||
currMessageIndex = historyLength-1; //reset history pointer back to latest message
|
||||
|
||||
for (; i<(MSG_MAX_LEN-1) && (i < length); i++)
|
||||
messageHistory[lastMessageIndex].data[i] = msg[i];
|
||||
|
||||
messageHistory[lastMessageIndex].data[i] = '\0'; //terminate string
|
||||
messageHistory[lastMessageIndex].rssi = rssi;
|
||||
}
|
||||
//******************************************** END MESSAGE HISTORY **************************************************************************
|
||||
|
||||
//parse through any serial commands from the host (Pi)
|
||||
void handleSerialInput() {
|
||||
inputLen = readSerialLine(temp, 10, 64, 10); //readSerialLine(char* input, char endOfLineChar=10, byte maxLength=64, uint16_t timeout=10);
|
||||
|
||||
if (inputLen > 0)
|
||||
{
|
||||
DEBUG("GTWCMD:");DEBUGln(temp);
|
||||
inputstr = String(temp);
|
||||
inputstr.toUpperCase();
|
||||
|
||||
if (inputstr.equals("BEEP")) Beep(5, false);
|
||||
if (inputstr.equals("BEEP2")) Beep(10, true);
|
||||
if (inputstr.equals("RAM")) { DEBUG(F("Free RAM bytes: "));DEBUGln(checkFreeRAM()); }
|
||||
if (inputstr.equals("KEY?")) { Serial.print(F("ENCRYPTKEY:"));Serial.println(ENCRYPTKEY); }
|
||||
|
||||
byte targetId = inputstr.toInt(); //extract ID if any
|
||||
byte colonIndex = inputstr.indexOf(":"); //find position of first colon
|
||||
if (targetId > 0) inputstr = inputstr.substring(colonIndex+1); //trim "ID:" if any
|
||||
if (targetId > 0 && targetId != NODEID && targetId != RF69_BROADCAST_ADDR && colonIndex>0 && colonIndex<4 && inputstr.length()>0)
|
||||
{
|
||||
inputstr.getBytes((byte*)temp, 61);
|
||||
if (radio.sendWithRetry(targetId, (byte*)temp, inputstr.length()))
|
||||
DEBUGln(F("ACK:OK"));
|
||||
else
|
||||
DEBUGln(F("ACK:NOK"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Blink(byte PIN, int DELAY_MS)
|
||||
{
|
||||
pinMode(PIN, OUTPUT);
|
||||
digitalWrite(PIN,HIGH);
|
||||
delay(DELAY_MS);
|
||||
digitalWrite(PIN,LOW);
|
||||
}
|
||||
|
||||
void setupPowerControl(){
|
||||
pinMode(BUTTON, INPUT_PULLUP);
|
||||
pinMode(SIG_BOOTOK, INPUT);
|
||||
pinMode(SIG_SHUTOFF, OUTPUT);
|
||||
pinMode(BTN_LED_RED, OUTPUT);
|
||||
pinMode(BTN_LED_GRN, OUTPUT);
|
||||
pinMode(LATCH_EN, OUTPUT);
|
||||
digitalWrite(LATCH_EN, LOW);
|
||||
pinMode(PIN_LCD_CS, OUTPUT);
|
||||
digitalWrite(PIN_LCD_CS, HIGH);
|
||||
pinMode(LATCH_VAL, OUTPUT);
|
||||
pinMode(BUTTON1, INPUT_PULLUP);
|
||||
pinMode(BUTTON2, INPUT_PULLUP);
|
||||
pinMode(BATTERYSENSE, INPUT);
|
||||
digitalWrite(SIG_SHUTOFF, LOW);//added after sudden shutdown quirks, DO NOT REMOVE!
|
||||
}
|
||||
|
||||
void handlePowerControl() {
|
||||
byte reading = digitalRead(BUTTON);
|
||||
NOW = millis();
|
||||
digitalWrite(SIG_SHUTOFF, LOW);//added after sudden shutdown quirks, DO NOT REMOVE!
|
||||
|
||||
//artificial power ON after a low battery shutdown
|
||||
if (PowerState == OFF && batteryLowShutdown && systemVoltage >= CHARGINGTHRESHOLD)
|
||||
reading = HIGH;
|
||||
|
||||
if ((PowerState == ON && batteryLow) || (reading != lastValidReading && NOW - lastValidReadingTime > 200))
|
||||
{
|
||||
lastValidReading = reading;
|
||||
lastValidReadingTime = NOW;
|
||||
|
||||
if ((PowerState == ON && batteryLow) || reading == LOW)
|
||||
{
|
||||
radio.sleep();
|
||||
//make sure the button is held down for at least 'RESETHOLDTIME' before taking action (this is to avoid accidental button presses and consequently Pi shutdowns)
|
||||
NOW = millis();
|
||||
while (!batteryLow && (PowerState == ON && millis()-NOW < RESETHOLDTIME)) { delay(10); if (digitalRead(BUTTON) != 0) return; }
|
||||
|
||||
//RESETHOLDTIME is satisfied, now check if button still held until SHUTDOWNHOLDTIME is satisfied
|
||||
POWER_LED_ORANGE(); //make the button LED orange to show something's going on
|
||||
while (!batteryLow && (PowerState == ON && millis()-NOW < SHUTDOWNHOLDTIME))
|
||||
{
|
||||
if (digitalRead(BUTTON) != 0)
|
||||
{
|
||||
if (BOOTOK()) //SIG_BOOTOK is HIGH so Pi is running the shutdowncheck.sh script, ready to intercept the RESET PULSE
|
||||
{
|
||||
#ifdef ENABLE_LCD
|
||||
sprintf(lcdbuff, "Rebooting Pi..");
|
||||
refreshLCD();
|
||||
#endif
|
||||
digitalWrite(SIG_SHUTOFF, HIGH);
|
||||
delay(RESETPULSETIME);
|
||||
digitalWrite(SIG_SHUTOFF, LOW);
|
||||
|
||||
NOW = millis();
|
||||
boolean recycleDetected=false;
|
||||
while (millis()-NOW < RecycleTime) //blink LED while waiting for BOOTOK to go high
|
||||
{
|
||||
//blink 3 times and pause
|
||||
POWER_LED_OFF(); //digitalWrite(POWER_LED, LOW);
|
||||
delay(100);
|
||||
POWER_LED_ORANGE(); //digitalWrite(POWER_LED, HIGH);
|
||||
delay(100);
|
||||
POWER_LED_OFF(); //digitalWrite(POWER_LED, LOW);
|
||||
delay(100);
|
||||
POWER_LED_ORANGE(); //digitalWrite(POWER_LED, HIGH);
|
||||
delay(100);
|
||||
POWER_LED_OFF(); //digitalWrite(POWER_LED, LOW);
|
||||
delay(100);
|
||||
POWER_LED_ORANGE(); //digitalWrite(POWER_LED, HIGH);
|
||||
delay(500);
|
||||
|
||||
if (!BOOTOK()) recycleDetected = true;
|
||||
else if (BOOTOK() && recycleDetected)
|
||||
{
|
||||
#ifdef ENABLE_LCD
|
||||
sprintf(lcdbuff, "Reboot OK!");
|
||||
refreshLCD();
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
}
|
||||
return; //reboot pulse sent but it appears a reboot failed; exit all checks
|
||||
}
|
||||
else return; //ignore everything else (button was held for RESETHOLDTIME, but SIG_BOOTOK was LOW)
|
||||
}
|
||||
}
|
||||
|
||||
//SIG_BOOTOK must be HIGH when Pi is ON. During boot, this will take a while to happen (till it executes the "shutdowncheck" script)
|
||||
//so I dont want to cutoff power before it had a chance to fully boot up
|
||||
if ((batteryLow || PowerState == ON) && BOOTOK())
|
||||
{
|
||||
if (batteryLow) {
|
||||
#ifdef ENABLE_LCD
|
||||
sprintf(lcdbuff, "Battery low! Shutting down Pi..");
|
||||
#endif
|
||||
batteryLowShutdown = true;
|
||||
}
|
||||
#ifdef ENABLE_LCD
|
||||
else
|
||||
sprintf(lcdbuff, "Shutting down Pi..");
|
||||
refreshLCD();
|
||||
#endif
|
||||
|
||||
// signal Pi to shutdown
|
||||
digitalWrite(SIG_SHUTOFF, HIGH);
|
||||
//DEBUGln("SIG_SHUTOFF - HIGH - if(batteryLow || (PowerState == 1 && BOOTOK())");
|
||||
|
||||
//now wait for the Pi to signal back
|
||||
NOW = millis();
|
||||
float in, out;
|
||||
boolean forceShutdown = true;
|
||||
|
||||
POWER_LED_OFF();
|
||||
while (millis()-NOW < RecycleTime)
|
||||
{
|
||||
if (in > 6.283) in = 0;
|
||||
in += .00628;
|
||||
|
||||
out = sin(in) * 127.5 + 127.5;
|
||||
analogWrite(BTN_LED_RED, out);
|
||||
delayMicroseconds(1500);
|
||||
|
||||
//account for force-shutdown action (if button held for ForcedShutoffDelay, then force shutdown regardless)
|
||||
if (millis()-NOW <= (ForcedShutoffDelay-SHUTDOWNHOLDTIME) && digitalRead(BUTTON) != 0)
|
||||
forceShutdown = false;
|
||||
if (millis()-NOW >= (ForcedShutoffDelay-SHUTDOWNHOLDTIME) && forceShutdown)
|
||||
{
|
||||
PowerState = OFF;
|
||||
POWER_LED_OFF(); //digitalWrite(POWER_LED, PowerState); //turn off LED to indicate power is being cutoff
|
||||
POWER(PowerState);
|
||||
break;
|
||||
}
|
||||
|
||||
if (millis() - NOW > ShutoffTriggerDelay)
|
||||
{
|
||||
// Pi signaling OK to turn off
|
||||
if (!BOOTOK())
|
||||
{
|
||||
PowerState = OFF;
|
||||
POWER_LED_OFF(); //digitalWrite(POWER_LED, PowerState); //turn off LED to indicate power is being cutoff
|
||||
NOW = millis();
|
||||
while (millis()-NOW < ShutdownFinalDelay)
|
||||
{
|
||||
if (in > 6.283) in = 0;
|
||||
in += .00628;
|
||||
out = sin(in) * 127.5 + 127.5;
|
||||
analogWrite(BTN_LED_RED,out);
|
||||
delayMicroseconds(300);
|
||||
}
|
||||
|
||||
POWER(PowerState);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// last chance: if power still on but button still pressed, force cutoff power
|
||||
if (PowerState == ON && digitalRead(BUTTON) == 0)
|
||||
{
|
||||
PowerState = OFF;
|
||||
POWER(PowerState);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_LCD
|
||||
if (PowerState == OFF)
|
||||
{
|
||||
sprintf(lcdbuff, "Pi is now OFF");
|
||||
refreshLCD();
|
||||
}
|
||||
#endif
|
||||
|
||||
digitalWrite(SIG_SHUTOFF, LOW);
|
||||
//DEBUGln("SIG_SHUTOFF - LOW");
|
||||
}
|
||||
else if (PowerState == ON && !BOOTOK())
|
||||
{
|
||||
#ifdef ENABLE_LCD
|
||||
sprintf(lcdbuff, "Forced shutdown..");
|
||||
refreshLCD();
|
||||
#endif
|
||||
|
||||
NOW = millis();
|
||||
unsigned long NOW2 = millis();
|
||||
int analogstep = 255 / ((ForcedShutoffDelay-SHUTDOWNHOLDTIME)/100); //every 500ms decrease LED intensity
|
||||
while (digitalRead(BUTTON) == 0)
|
||||
{
|
||||
if (millis()-NOW2 > 100)
|
||||
{
|
||||
analogWrite(BTN_LED_RED, 255 - ((millis()-NOW)/100)*analogstep);
|
||||
NOW2 = millis();
|
||||
}
|
||||
if (millis()-NOW > ForcedShutoffDelay-SHUTDOWNHOLDTIME)
|
||||
{
|
||||
//TODO: add blinking here to signal final shutdown delay
|
||||
PowerState = OFF;
|
||||
POWER(PowerState);
|
||||
#ifdef ENABLE_LCD
|
||||
sprintf(lcdbuff, "Pi is now OFF");
|
||||
refreshLCD();
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (PowerState == OFF)
|
||||
{
|
||||
PowerState = ON;
|
||||
batteryLowShutdown=false;
|
||||
POWER(PowerState);
|
||||
#ifdef ENABLE_LCD
|
||||
sprintf(lcdbuff, "Pi is now ON");
|
||||
refreshLCD();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
if (PowerState == ON) POWER_LED_GRN() else POWER_LED_OFF(); //digitalWrite(POWER_LED, PowerState);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t buttonsLastChanged;
|
||||
void handle2Buttons()
|
||||
{
|
||||
if (millis() - buttonsLastChanged < 200) return; //basic button debouncing & prevent changing level too fast
|
||||
|
||||
//button 1 - backlight
|
||||
if (digitalRead(BUTTON1)==LOW)
|
||||
{
|
||||
buttonsLastChanged=millis();
|
||||
Beep(3, false);
|
||||
#ifdef ENABLE_LCD
|
||||
if (backlightLevel==BACKLIGHTLEVELS) backlightLevel=0;
|
||||
else backlightLevel++;
|
||||
LCD_BACKLIGHT(backlightLevel);
|
||||
sprintf(lcdbuff, "LCDlight:%d/100", 100*backlightLevel/BACKLIGHTLEVELS);
|
||||
refreshLCD();
|
||||
#endif
|
||||
}
|
||||
|
||||
//button 2 - message history
|
||||
if (digitalRead(BUTTON2)==LOW)
|
||||
{
|
||||
buttonsLastChanged=millis();
|
||||
Beep(3, false);
|
||||
|
||||
if (historyLength > 0) //if at least 1 data packet was received and saved to history...
|
||||
{
|
||||
sprintf(RSSIstr, "%ddBm", messageHistory[currMessageIndex].rssi); //paint the history rssi string for the LCDRefresh
|
||||
rssi = messageHistory[currMessageIndex].rssi; //save the history rssi for the LCDRefresh signal icon
|
||||
#ifdef ENABLE_LCD
|
||||
sprintf(lcdbuff, "<HISTORY[%d/%d]>\n%s", currMessageIndex+1, historyLength, messageHistory[currMessageIndex].data); //fill the LCD string buffer with the history data string
|
||||
refreshLCD(); //paint the screen
|
||||
#endif
|
||||
if (currMessageIndex==0) currMessageIndex=historyLength-1; else currMessageIndex--; //this makes it cycle from the latest message towards oldest as you press BTN2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean BOOTOK() {
|
||||
return analogRead(SIG_BOOTOK) > 800; //the BOOTOK signal is on an analog pin because a digital may not always pick it up (its less than 3.3v)
|
||||
}
|
||||
|
||||
void POWER(uint8_t ON_OFF) {
|
||||
digitalWrite(LATCH_EN, HIGH);
|
||||
digitalWrite(LATCH_VAL, ON_OFF);
|
||||
delay(5);
|
||||
digitalWrite(LATCH_EN, LOW);
|
||||
delay(5);
|
||||
#ifdef ENABLE_LCD
|
||||
digitalWrite(PIN_LCD_CS, HIGH); //if shared with LATCH_VAL, should be HIGH when not used by latch
|
||||
#endif
|
||||
}
|
||||
|
||||
void Beep(byte theDelay, boolean twoSounds)
|
||||
{
|
||||
if (theDelay > 20) theDelay = 20;
|
||||
tone(BUZZER, 4200); //4200
|
||||
delay(theDelay);
|
||||
noTone(BUZZER);
|
||||
delay(10);
|
||||
if (twoSounds)
|
||||
{
|
||||
tone(BUZZER, 4500); //4500
|
||||
delay(theDelay);
|
||||
noTone(BUZZER);
|
||||
}
|
||||
}
|
||||
|
||||
int checkFreeRAM()
|
||||
{
|
||||
extern int __heap_start, *__brkval;
|
||||
int v;
|
||||
return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
|
||||
}
|
||||
|
||||
boolean readBattery() {
|
||||
//periodically read the battery voltage
|
||||
int currPeriod = millis()/BATTERYREADINTERVAL;
|
||||
if (currPeriod != lastPeriod)
|
||||
{
|
||||
lastPeriod=currPeriod;
|
||||
systemVoltage = BATTERY_VOLTS(analogRead(BATTERYSENSE));
|
||||
//dtostrf(systemVoltage, 3,2, BATstr);
|
||||
batteryLow = systemVoltage < LOWBATTERYTHRESHOLD;
|
||||
return true; //signal that batt has been read
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void setup() {
|
||||
Beep(20, false);delay(50);Beep(20, false);delay(50);Beep(20, false);
|
||||
setupPowerControl();
|
||||
Serial.begin(SERIAL_BAUD);
|
||||
|
||||
radio.initialize(FREQUENCY,NODEID,NETWORKID);
|
||||
radio.encrypt(ENCRYPTKEY);
|
||||
|
||||
#ifdef IS_RFM69HW_HCW
|
||||
radio.setHighPower(); //must include this only for RFM69HW/HCW!
|
||||
#endif
|
||||
|
||||
sprintf(lcdbuff, "Listening @ %dmhz...", FREQUENCY==RF69_433MHZ ? 433 : FREQUENCY==RF69_868MHZ ? 868 : 915);
|
||||
DEBUGln(lcdbuff);
|
||||
if (flash.initialize()) DEBUGln("SPI Flash Init OK!");
|
||||
else DEBUGln(F("SPI Flash MEM FAIL!"));
|
||||
|
||||
#ifdef FREQUENCY_EXACT
|
||||
radio.setFrequency(FREQUENCY_EXACT); //set frequency to some custom frequency
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_ATC
|
||||
DEBUGln(F("RFM69_ATC Enabled (Auto Transmission Control)"));
|
||||
#endif
|
||||
|
||||
readBattery();
|
||||
DEBUG(F("Free RAM bytes: "));DEBUG(checkFreeRAM());
|
||||
|
||||
#ifdef ENABLE_LCD
|
||||
pinMode(PIN_LCD_LIGHT, OUTPUT); //LCD backlight, LOW = backlight ON
|
||||
lcd.setRot180(); //rotate screen 180 degrees
|
||||
lcd.setContrast(140); //120-160 seems to be usable range
|
||||
drawLogo();
|
||||
LCD_BACKLIGHT(backlightLevel);
|
||||
delay(2000);
|
||||
refreshLCD();
|
||||
delay(1000);
|
||||
#endif
|
||||
}
|
||||
|
||||
boolean newPacketReceived;
|
||||
void loop() {
|
||||
handlePowerControl(); //checks any button presses and takes action
|
||||
handle2Buttons(); //checks the general purpose buttons next to the LCD (R2+)
|
||||
handleSerialInput(); //checks for any serial input from the Pi computer
|
||||
|
||||
//process any received radio packets
|
||||
if (radio.receiveDone())
|
||||
{
|
||||
rssi = radio.RSSI;
|
||||
if (radio.DATALEN > 0) //data packets have a payload
|
||||
{
|
||||
sprintf(lcdbuff, "[%d] %s", radio.SENDERID, radio.DATA);
|
||||
sprintf(RSSIstr, "%ddBm", rssi);
|
||||
Serial.print(lcdbuff); //this passes data to MightyHat / RaspberryPi
|
||||
Serial.print(F(" [RSSI:"));Serial.print(rssi);Serial.print(']');
|
||||
saveToHistory(lcdbuff, rssi);
|
||||
}
|
||||
|
||||
//check if the packet is a wireless programming request
|
||||
//removing this line will save 3kb+ of flash space
|
||||
#ifdef ENABLE_WIRELESS_PROGRAMMING
|
||||
CheckForWirelessHEX(radio, flash, false); //non verbose DEBUG
|
||||
#endif
|
||||
|
||||
//respond to any ACK if requested
|
||||
if (radio.ACKRequested())
|
||||
{
|
||||
radio.sendACK();
|
||||
DEBUG(F("[ACK-sent]"));
|
||||
}
|
||||
|
||||
//DEBUG(F("Free RAM bytes: "));DEBUG(checkFreeRAM());
|
||||
|
||||
Serial.println();
|
||||
Blink(LED,2);
|
||||
newPacketReceived = true;
|
||||
}
|
||||
|
||||
readBattery();
|
||||
|
||||
#ifdef ENABLE_LCD
|
||||
if (newPacketReceived || systemVoltagePrevious-systemVoltage > 0.01 || systemVoltagePrevious-systemVoltage < -0.1)
|
||||
{
|
||||
systemVoltagePrevious = systemVoltage;
|
||||
newPacketReceived = false;
|
||||
refreshLCD();
|
||||
}
|
||||
LCD_BACKLIGHT(batteryLow ? 0 : backlightLevel);
|
||||
#endif
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,249 @@
|
||||
// Sample RFM69 sender/node sketch for the MotionMote
|
||||
// http://lowpowerlab.com/motionmote
|
||||
// PIR motion sensor connected to D3 (INT1)
|
||||
// When RISE happens on D3, the sketch transmits a "MOTION" msg to receiver Moteino and goes back to sleep
|
||||
// In sleep mode, Moteino + PIR motion sensor use about ~60uA
|
||||
// IMPORTANT: adjust the settings in the configuration section below !!!
|
||||
|
||||
// **********************************************************************************
|
||||
// Copyright Felix Rusu of LowPowerLab.com, 2016
|
||||
// RFM69 library and sample code by Felix Rusu - lowpowerlab.com/contact
|
||||
// **********************************************************************************
|
||||
// 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.
|
||||
//
|
||||
// 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 <RFM69.h> //get it here: https://www.github.com/lowpowerlab/rfm69
|
||||
#include <RFM69_ATC.h>//get it here: https://www.github.com/lowpowerlab/rfm69
|
||||
#include <SPI.h> //comes with Arduino IDE (www.arduino.cc)
|
||||
#include <LowPower.h> //get library from: https://github.com/lowpowerlab/lowpower
|
||||
//writeup here: http://www.rocketscream.com/blog/2011/07/04/lightweight-low-power-arduino-library/
|
||||
#include <SPIFlash.h> //get it here: https://www.github.com/lowpowerlab/spiflash
|
||||
#include <SparkFunBME280.h> //get it here: https://github.com/sparkfun/SparkFun_BME280_Breakout_Board/tree/master/Libraries/Arduino/src
|
||||
#include <Wire.h> //comes with Arduino
|
||||
|
||||
//****************************************************************************************************************
|
||||
//**** IMPORTANT RADIO SETTINGS - YOU MUST CHANGE/CONFIGURE TO MATCH YOUR HARDWARE TRANSCEIVER CONFIGURATION! ****
|
||||
//****************************************************************************************************************
|
||||
#define NODEID 88 //unique for each node on same network
|
||||
#define NETWORKID 100 //the same on all nodes that talk to each other
|
||||
#define GATEWAYID 1
|
||||
//Match frequency to the hardware version of the radio on your Moteino (uncomment one):
|
||||
//#define FREQUENCY RF69_433MHZ
|
||||
//#define FREQUENCY RF69_868MHZ
|
||||
#define FREQUENCY RF69_915MHZ
|
||||
#define IS_RFM69HW_HCW //uncomment only for RFM69HW/HCW! Leave out if you have RFM69W/CW!
|
||||
#define ENCRYPTKEY "sampleEncryptKey" //exactly the same 16 characters/bytes on all nodes!
|
||||
#define ENABLE_ATC //comment out this line to disable AUTO TRANSMISSION CONTROL
|
||||
#define ATC_RSSI -75
|
||||
//#define ENABLE_BME280 //uncomment to allow reading the BME280 (if present)
|
||||
//*********************************************************************************************
|
||||
#define ACK_TIME 30 // max # of ms to wait for an ack
|
||||
#define ONBOARDLED 9 // Moteinos have LEDs on D9
|
||||
#define LED 5 // MotionOLEDMote has an external LED on D5
|
||||
#define MOTION_PIN 3 // D3
|
||||
#define MOTION_IRQ 1 // hardware interrupt 1 (D3) - where motion sensors OUTput is connected, this will generate an interrupt every time there is MOTION
|
||||
#define BATT_MONITOR A7 // Sense VBAT_COND signal (when powered externally should read ~3.25v/3.3v (1000-1023), when external power is cutoff it should start reading around 2.85v/3.3v * 1023 ~= 883 (ratio given by 10k+4.7K divider from VBAT_COND = 1.47 multiplier)
|
||||
#define BATT_FORMULA(reading) reading * 0.00322 * 1.49 // >>> fine tune this parameter to match your voltage when fully charged
|
||||
// details on how this works: https://lowpowerlab.com/forum/index.php/topic,1206.0.html
|
||||
#define DUPLICATE_INTERVAL 20000 //avoid duplicates in 55second intervals (ie mailman sometimes spends 30+ seconds at mailbox)
|
||||
#define BATT_INTERVAL 300000 // read and report battery voltage every this many ms (approx)
|
||||
|
||||
//#define SERIAL_EN //comment this out when deploying to an installed Mote to save a few KB of sketch size
|
||||
#define SERIAL_BAUD 115200
|
||||
#ifdef SERIAL_EN
|
||||
#define DEBUG(input) {Serial.print(input); delay(1);}
|
||||
#define DEBUGln(input) {Serial.println(input); delay(1);}
|
||||
#define DEBUGFlush() { Serial.flush(); }
|
||||
#else
|
||||
#define DEBUG(input);
|
||||
#define DEBUGln(input);
|
||||
#define DEBUGFlush();
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_ATC
|
||||
RFM69_ATC radio;
|
||||
#else
|
||||
RFM69 radio;
|
||||
#endif
|
||||
|
||||
#define FLASH_SS 8 // and FLASH SS on D8 on regular Moteinos (D23 on MoteinoMEGA)
|
||||
SPIFlash flash(FLASH_SS, 0xEF30); //EF30 for 4mbit Windbond chip (W25X40CL)
|
||||
|
||||
#ifdef ENABLE_BME280
|
||||
BME280 bme280;
|
||||
#endif
|
||||
|
||||
volatile boolean motionDetected=false;
|
||||
float batteryVolts = 5;
|
||||
char BATstr[10]; //longest battery voltage reading message = 9chars
|
||||
char sendBuf[32];
|
||||
byte sendLen;
|
||||
#ifdef ENABLE_BME280
|
||||
float temperature=0;
|
||||
char Fstr[10];
|
||||
#endif
|
||||
|
||||
void motionIRQ(void);
|
||||
void checkBattery(void);
|
||||
|
||||
void setup() {
|
||||
Serial.begin(SERIAL_BAUD);
|
||||
radio.initialize(FREQUENCY,NODEID,NETWORKID);
|
||||
#ifdef IS_RFM69HW_HCW
|
||||
radio.setHighPower(); //must include this only for RFM69HW/HCW!
|
||||
#endif
|
||||
radio.encrypt(ENCRYPTKEY);
|
||||
|
||||
//Auto Transmission Control - dials down transmit power to save battery (-100 is the noise floor, -90 is still pretty good)
|
||||
//For indoor nodes that are pretty static and at pretty stable temperatures (like a MotionMote) -90dBm is quite safe
|
||||
//For more variable nodes that can expect to move or experience larger temp drifts a lower margin like -70 to -80 would probably be better
|
||||
//Always test your ATC mote in the edge cases in your own environment to ensure ATC will perform as you expect
|
||||
#ifdef ENABLE_ATC
|
||||
radio.enableAutoPower(ATC_RSSI);
|
||||
#endif
|
||||
|
||||
pinMode(MOTION_PIN, INPUT);
|
||||
attachInterrupt(MOTION_IRQ, motionIRQ, RISING);
|
||||
char buff[50];
|
||||
sprintf(buff, "\nTransmitting at %d Mhz...", FREQUENCY==RF69_433MHZ ? 433 : FREQUENCY==RF69_868MHZ ? 868 : 915);
|
||||
DEBUGln(buff);
|
||||
pinMode(ONBOARDLED, OUTPUT);
|
||||
pinMode(LED, OUTPUT);
|
||||
radio.sendWithRetry(GATEWAYID, "START", 5);
|
||||
|
||||
#ifdef ENABLE_ATC
|
||||
DEBUGln("RFM69_ATC Enabled (Auto Transmission Control)\n");
|
||||
#endif
|
||||
|
||||
if (flash.initialize()) flash.sleep(); //if Moteino has FLASH-MEM, make sure it sleeps
|
||||
|
||||
#ifdef ENABLE_BME280
|
||||
bme280.settings.commInterface = I2C_MODE;
|
||||
bme280.settings.I2CAddress = 0x77;
|
||||
bme280.settings.runMode = 3; //Normal mode
|
||||
bme280.settings.tStandby = 0;
|
||||
bme280.settings.filter = 0;
|
||||
bme280.settings.tempOverSample = 1;
|
||||
bme280.settings.pressOverSample = 1;
|
||||
bme280.settings.humidOverSample = 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
void motionIRQ()
|
||||
{
|
||||
motionDetected=true;
|
||||
DEBUGln("IRQ");
|
||||
}
|
||||
|
||||
uint16_t batteryReportCycles=0;
|
||||
uint32_t time=0, now=0, MLO=0, BLO=0;
|
||||
byte motionRecentlyCycles=0;
|
||||
void loop() {
|
||||
now = millis();
|
||||
checkBattery();
|
||||
//DEBUG("Slept: ");DEBUG(now-lastSleepTime);DEBUGln("ms");
|
||||
|
||||
if (motionDetected && (time-MLO > DUPLICATE_INTERVAL))
|
||||
{
|
||||
digitalWrite(LED, HIGH);
|
||||
MLO = time; //save timestamp of event
|
||||
|
||||
#ifdef ENABLE_BME280
|
||||
//read BME sensor
|
||||
bme280.begin();
|
||||
dtostrf(bme280.readTempF(), 3,2, Fstr);
|
||||
bme280.writeRegister(BME280_CTRL_MEAS_REG, 0x00); //sleep the BME280
|
||||
sprintf(sendBuf, "MOTION BAT:%sv F:%s", BATstr, Fstr);
|
||||
#else
|
||||
sprintf(sendBuf, "MOTION BAT:%sv", BATstr);
|
||||
#endif
|
||||
|
||||
sendLen = strlen(sendBuf);
|
||||
|
||||
if (radio.sendWithRetry(GATEWAYID, sendBuf, sendLen))
|
||||
{
|
||||
DEBUG("MOTION ACK:OK! RSSI:");
|
||||
DEBUG(radio.RSSI);
|
||||
batteryReportCycles = 0;
|
||||
}
|
||||
else DEBUG("MOTION ACK:NOK...");
|
||||
|
||||
DEBUG(" VIN: ");
|
||||
DEBUGln(BATstr);
|
||||
|
||||
radio.sleep();
|
||||
digitalWrite(LED, LOW);
|
||||
}
|
||||
else if (time-BLO > BATT_INTERVAL)
|
||||
{
|
||||
#ifdef ENABLE_BME280
|
||||
//read BME sensor
|
||||
bme280.begin();
|
||||
dtostrf(bme280.readTempF(), 3,2, Fstr);
|
||||
bme280.writeRegister(BME280_CTRL_MEAS_REG, 0x00); //sleep the BME280
|
||||
sprintf(sendBuf, "BAT:%sv F:%s", BATstr, Fstr);
|
||||
#else
|
||||
sprintf(sendBuf, "BAT:%sv", BATstr);
|
||||
#endif
|
||||
|
||||
sendLen = strlen(sendBuf);
|
||||
BLO = time;
|
||||
radio.sendWithRetry(GATEWAYID, sendBuf, sendLen);
|
||||
radio.sleep();
|
||||
batteryReportCycles=0;
|
||||
}
|
||||
|
||||
DEBUGFlush();
|
||||
|
||||
//while motion recently happened sleep for small slots of time to better approximate last motion event
|
||||
//this helps with debouncing a "MOTION" event more accurately for sensors that fire the IRQ very rapidly (ie panasonic sensors)
|
||||
if (motionDetected ||motionRecentlyCycles>0)
|
||||
{
|
||||
if (motionDetected) motionRecentlyCycles=8;
|
||||
else motionRecentlyCycles--;
|
||||
motionDetected=false; //do NOT move this after the SLEEP line below or motion will never be detected
|
||||
time = time + 250 + millis()-now;
|
||||
radio.sleep();
|
||||
LowPower.powerDown(SLEEP_250MS, ADC_OFF, BOD_OFF);
|
||||
DEBUGln("WAKEUP250ms");
|
||||
}
|
||||
else
|
||||
{
|
||||
time = time + 8000 + millis()-now;
|
||||
radio.sleep();
|
||||
LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
|
||||
DEBUGln("WAKEUP8s");
|
||||
}
|
||||
batteryReportCycles++;
|
||||
}
|
||||
|
||||
uint32_t BLR=0;
|
||||
void checkBattery()
|
||||
{
|
||||
if (time-BLR > 30000) //only read battery every 30s or so
|
||||
{
|
||||
unsigned int readings=0;
|
||||
BLR = time;
|
||||
for (byte i=0; i<10; i++) //take 10 samples, and average
|
||||
readings+=analogRead(BATT_MONITOR);
|
||||
batteryVolts = BATT_FORMULA(readings / 10.0);
|
||||
dtostrf(batteryVolts, 3,2, BATstr); //update the BATStr which gets sent every BATT_CYCLES or along with the MOTION message
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
// Sample RFM69 sender/node sketch for motion sensor
|
||||
// PIR motion sensor connected to D3 (INT1)
|
||||
// When RISE happens on D3, the sketch transmits a "MOTION" msg to receiver Moteino and goes back to sleep
|
||||
// In sleep mode, Moteino + PIR motion sensor use about ~78uA
|
||||
// Library and code by Felix Rusu - felix@lowpowerlab.com
|
||||
// Get the RFM69 and SPIFlash library at: https://github.com/LowPowerLab/
|
||||
|
||||
#include <RFM69.h> //get it here: https://www.github.com/lowpowerlab/rfm69
|
||||
#include <SPI.h> //get it here: https://github.com/lowpowerlab/spiflash
|
||||
#include <LowPower.h> //get library from: https://github.com/LowPowerLab/LowPower
|
||||
|
||||
#define NODEID 18 //unique for each node on same network
|
||||
#define NETWORKID 100 //the same on all nodes that talk to each other
|
||||
#define GATEWAYID 1
|
||||
//Match frequency to the hardware version of the radio on your Moteino (uncomment one):
|
||||
//#define FREQUENCY RF69_433MHZ
|
||||
//#define FREQUENCY RF69_868MHZ
|
||||
#define FREQUENCY RF69_915MHZ
|
||||
#define ENCRYPTKEY "sampleEncryptKey" //exactly the same 16 characters/bytes on all nodes!
|
||||
//#define IS_RFM69HW //uncomment only for RFM69HW! Leave out if you have RFM69W!
|
||||
#define ACK_TIME 30 // max # of ms to wait for an ack
|
||||
#define LED 9 // Moteinos have LEDs on D9
|
||||
#define SERIAL_BAUD 115200
|
||||
#define MOTIONPIN 1 //hardware interrupt 1 (D3)
|
||||
|
||||
RFM69 radio;
|
||||
volatile boolean motionDetected=false;
|
||||
|
||||
void setup() {
|
||||
Serial.begin(SERIAL_BAUD);
|
||||
radio.initialize(FREQUENCY,NODEID,NETWORKID);
|
||||
#ifdef IS_RFM69HW
|
||||
radio.setHighPower(); //uncomment only for RFM69HW!
|
||||
#endif
|
||||
radio.encrypt(ENCRYPTKEY);
|
||||
attachInterrupt(MOTIONPIN, motionIRQ, RISING);
|
||||
char buff[50];
|
||||
sprintf(buff, "\nTransmitting at %d Mhz...", FREQUENCY==RF69_433MHZ ? 433 : FREQUENCY==RF69_868MHZ ? 868 : 915);
|
||||
Serial.println(buff);
|
||||
}
|
||||
|
||||
void motionIRQ()
|
||||
{
|
||||
motionDetected=true;
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (motionDetected)
|
||||
{
|
||||
if (radio.sendWithRetry(GATEWAYID, "MOTION", 6))
|
||||
{
|
||||
Serial.println(" ok!");
|
||||
Blink(LED,3);
|
||||
}
|
||||
else Serial.println(" nothing...");
|
||||
motionDetected=false;
|
||||
}
|
||||
radio.sleep();
|
||||
LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
|
||||
}
|
||||
|
||||
void Blink(byte PIN, int DELAY_MS)
|
||||
{
|
||||
pinMode(PIN, OUTPUT);
|
||||
digitalWrite(PIN,HIGH);
|
||||
delay(DELAY_MS);
|
||||
digitalWrite(PIN,LOW);
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
@@ -0,0 +1,26 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="141.7mm" height="110.7mm" viewBox = " 0 0 502.087027 392.244417" >
|
||||
<!-- Front-->
|
||||
<g transform="translate(22.5000185, 22.5000185) " >
|
||||
<polygon style="stroke:#000000; fill:none; stroke-width:0.90000074" points="0, 0 24.80317, 0 24.80317, 10.62993 49.60634, 10.62993 49.60634, 0 74.40951000000001, 0 74.40951000000001, 10.62993 99.21268, 10.62993 99.21268, 0 124.01585, 0 124.01585, 29.2323075 113.38592, 29.2323075 113.38592, 58.464615 124.01585, 58.464615 124.01585, 79.724475 113.38592, 79.724475 113.38592, 108.9567825 124.01585, 108.9567825 124.01585, 138.18909 99.21268, 138.18909 99.21268, 127.55915999999999 74.40951000000001, 127.55915999999999 74.40951000000001, 138.18909 49.60634, 138.18909 49.60634, 127.55915999999999 24.80317, 127.55915999999999 24.80317, 138.18909 0, 138.18909 0, 108.9567825 10.62993, 108.9567825 10.62993, 79.724475 0, 79.724475 0, 58.464615 10.62993, 58.464615 10.62993, 29.2323075 0, 29.2323075 "/>
|
||||
</g>
|
||||
<!-- Back-->
|
||||
<g transform="translate(146.5158685, 22.5000185) " >
|
||||
<polygon style="stroke:#000000; fill:none; stroke-width:0.90000074" points="0, 0 24.80317, 0 24.80317, 10.62993 49.60634, 10.62993 49.60634, 0 74.40951000000001, 0 74.40951000000001, 10.62993 99.21268, 10.62993 99.21268, 0 124.01585, 0 124.01585, 29.2323075 113.38592, 29.2323075 113.38592, 58.464615 124.01585, 58.464615 124.01585, 79.724475 113.38592, 79.724475 113.38592, 108.9567825 124.01585, 108.9567825 124.01585, 138.18909 99.21268, 138.18909 99.21268, 127.55915999999999 74.40951000000001, 127.55915999999999 74.40951000000001, 138.18909 49.60634, 138.18909 49.60634, 127.55915999999999 24.80317, 127.55915999999999 24.80317, 138.18909 0, 138.18909 0, 108.9567825 10.62993, 108.9567825 10.62993, 79.724475 0, 79.724475 0, 58.464615 10.62993, 58.464615 10.62993, 29.2323075 0, 29.2323075 "/>
|
||||
</g>
|
||||
<!-- Left-->
|
||||
<g transform="translate(270.53171849999995, 22.5000185) " >
|
||||
<polygon style="stroke:#000000; fill:none; stroke-width:0.90000074" points="10.62993, 0 36.3189275, 0 36.3189275, 10.62993 72.637855, 10.62993 72.637855, 0 93.897715, 0 93.897715, 10.62993 115.15757500000001, 10.62993 115.15757500000001, 0 136.417435, 0 136.417435, 10.62993 172.73636249999998, 10.62993 172.73636249999998, 0 198.42535999999998, 0 198.42535999999998, 29.2323075 209.05528999999999, 29.2323075 209.05528999999999, 58.464615 198.42535999999998, 58.464615 198.42535999999998, 79.724475 209.05528999999999, 79.724475 209.05528999999999, 108.9567825 198.42535999999998, 108.9567825 198.42536, 138.18909 172.73636249999998, 138.18909 172.73636249999998, 127.55915999999999 136.417435, 127.55915999999999 136.417435, 138.18909 115.15757500000001, 138.18909 115.15757500000001, 127.55915999999999 93.897715, 127.55915999999999 93.897715, 138.18909 72.637855, 138.18909 72.637855, 127.55915999999999 36.3189275, 127.55915999999999 36.3189275, 138.18909 10.62993, 138.18909 10.62993, 108.9567825 0, 108.9567825 0, 79.724475 10.62993, 79.724475 10.62993, 58.464615 0, 58.464615 0, 29.2323075 10.62993, 29.2323075 "/>
|
||||
</g>
|
||||
<!-- Right-->
|
||||
<g transform="translate(270.53171849999995, 160.6891085) " >
|
||||
<polygon style="stroke:#000000; fill:none; stroke-width:0.90000074" points="10.62993, 0 36.3189275, 0 36.3189275, 10.62993 72.637855, 10.62993 72.637855, 0 93.897715, 0 93.897715, 10.62993 115.15757500000001, 10.62993 115.15757500000001, 0 136.417435, 0 136.417435, 10.62993 172.73636249999998, 10.62993 172.73636249999998, 0 198.42535999999998, 0 198.42535999999998, 29.2323075 209.05528999999999, 29.2323075 209.05528999999999, 58.464615 198.42535999999998, 58.464615 198.42535999999998, 79.724475 209.05528999999999, 79.724475 209.05528999999999, 108.9567825 198.42535999999998, 108.9567825 198.42536, 138.18909 172.73636249999998, 138.18909 172.73636249999998, 127.55915999999999 136.417435, 127.55915999999999 136.417435, 138.18909 115.15757500000001, 138.18909 115.15757500000001, 127.55915999999999 93.897715, 127.55915999999999 93.897715, 138.18909 72.637855, 138.18909 72.637855, 127.55915999999999 36.3189275, 127.55915999999999 36.3189275, 138.18909 10.62993, 138.18909 10.62993, 108.9567825 0, 108.9567825 0, 79.724475 10.62993, 79.724475 10.62993, 58.464615 0, 58.464615 0, 29.2323075 10.62993, 29.2323075 "/>
|
||||
</g>
|
||||
<!-- Top-->
|
||||
<g transform="translate(22.5000185, 160.6891085) " >
|
||||
<polygon style="stroke:#000000; fill:none; stroke-width:0.90000074" points="10.62993, 10.62993 24.80317, 10.62993 24.80317, 0 49.60634, 0 49.60634, 10.62993 74.40951000000001, 10.62993 74.40951000000001, 0 99.21268, 0 99.21268, 10.62993 113.38592, 10.62993 113.38592, 36.3189275 124.01585, 36.3189275 124.01585, 72.637855 113.38592, 72.637855 113.38592, 93.897715 124.01585, 93.897715 124.01585, 115.15757500000001 113.38592, 115.15757500000001 113.38592, 136.417435 124.01585, 136.417435 124.01585, 172.73636249999998 113.38592, 172.73636249999998 113.38592, 198.42535999999998 99.21268, 198.42535999999998 99.21268, 209.05528999999999 74.40951000000001, 209.05528999999999 74.40951000000001, 198.42535999999998 49.60634, 198.42535999999998 49.60634, 209.05528999999999 24.80317, 209.05528999999999 24.80317, 198.42535999999998 10.62993, 198.42536 10.62993, 172.73636249999998 0, 172.73636249999998 0, 136.417435 10.62993, 136.417435 10.62993, 115.15757500000001 0, 115.15757500000001 0, 93.897715 10.62993, 93.897715 10.62993, 72.637855 0, 72.637855 0, 36.3189275 10.62993, 36.3189275 "/>
|
||||
<circle cx="62.007925" cy="77.95282" r="39.8622375" style="stroke:#000000;stroke-width:0.90000074;fill:none" />
|
||||
</g>
|
||||
<!-- Bottom-->
|
||||
<g transform="translate(146.5158685, 160.6891085) " >
|
||||
<polygon style="stroke:#000000; fill:none; stroke-width:0.90000074" points="10.62993, 10.62993 24.80317, 10.62993 24.80317, 0 49.60634, 0 49.60634, 10.62993 74.40951000000001, 10.62993 74.40951000000001, 0 99.21268, 0 99.21268, 10.62993 113.38592, 10.62993 113.38592, 36.3189275 124.01585, 36.3189275 124.01585, 72.637855 113.38592, 72.637855 113.38592, 93.897715 124.01585, 93.897715 124.01585, 115.15757500000001 113.38592, 115.15757500000001 113.38592, 136.417435 124.01585, 136.417435 124.01585, 172.73636249999998 113.38592, 172.73636249999998 113.38592, 198.42535999999998 99.21268, 198.42535999999998 99.21268, 209.05528999999999 74.40951000000001, 209.05528999999999 74.40951000000001, 198.42535999999998 49.60634, 198.42535999999998 49.60634, 209.05528999999999 24.80317, 209.05528999999999 24.80317, 198.42535999999998 10.62993, 198.42536 10.62993, 172.73636249999998 0, 172.73636249999998 0, 136.417435 10.62993, 136.417435 10.62993, 115.15757500000001 0, 115.15757500000001 0, 93.897715 10.62993, 93.897715 10.62993, 72.637855 0, 72.637855 0, 36.3189275 10.62993, 36.3189275 "/>
|
||||
</g></svg>
|
||||
|
After Width: | Height: | Size: 6.6 KiB |
@@ -0,0 +1,223 @@
|
||||
// Sample RFM69 sender/node sketch, with ACK and optional encryption, and Automatic Transmission Control
|
||||
// Sends periodic messages of increasing length to gateway (id=1)
|
||||
// It also looks for an onboard FLASH chip, if present
|
||||
// **********************************************************************************
|
||||
// Copyright Felix Rusu 2016, http://www.LowPowerLab.com/contact
|
||||
// **********************************************************************************
|
||||
// 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.
|
||||
//
|
||||
// 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 <RFM69.h> //get it here: https://www.github.com/lowpowerlab/rfm69
|
||||
#include <RFM69_ATC.h> //get it here: https://www.github.com/lowpowerlab/rfm69
|
||||
#include <SPIFlash.h> //get it here: https://www.github.com/lowpowerlab/spiflash
|
||||
#include <SPI.h> //included with Arduino IDE install (www.arduino.cc)
|
||||
|
||||
//*********************************************************************************************
|
||||
//************ IMPORTANT SETTINGS - YOU MUST CHANGE/CONFIGURE TO FIT YOUR HARDWARE ************
|
||||
//*********************************************************************************************
|
||||
#define NODEID 2 //must be unique for each node on same network (range up to 254, 255 is used for broadcast)
|
||||
#define NETWORKID 100 //the same on all nodes that talk to each other (range up to 255)
|
||||
#define GATEWAYID 1
|
||||
//Match frequency to the hardware version of the radio on your Moteino (uncomment one):
|
||||
#define FREQUENCY RF69_433MHZ
|
||||
//#define FREQUENCY RF69_868MHZ
|
||||
//#define FREQUENCY RF69_915MHZ
|
||||
#define ENCRYPTKEY "sampleEncryptKey" //exactly the same 16 characters/bytes on all nodes!
|
||||
#define IS_RFM69HW_HCW //uncomment only for RFM69HW/HCW! Leave out if you have RFM69W/CW!
|
||||
//*********************************************************************************************
|
||||
//Auto Transmission Control - dials down transmit power to save battery
|
||||
//Usually you do not need to always transmit at max output power
|
||||
//By reducing TX power even a little you save a significant amount of battery power
|
||||
//This setting enables this gateway to work with remote nodes that have ATC enabled to
|
||||
//dial their power down to only the required level (ATC_RSSI)
|
||||
#define ENABLE_ATC //comment out this line to disable AUTO TRANSMISSION CONTROL
|
||||
#define ATC_RSSI -80
|
||||
//*********************************************************************************************
|
||||
|
||||
#ifdef __AVR_ATmega1284P__
|
||||
#define LED 15 // Moteino MEGAs have LEDs on D15
|
||||
#define FLASH_SS 23 // and FLASH SS on D23
|
||||
#else
|
||||
#define LED 9 // Moteinos have LEDs on D9
|
||||
#define FLASH_SS 8 // and FLASH SS on D8
|
||||
#endif
|
||||
|
||||
#define SERIAL_BAUD 115200
|
||||
|
||||
int TRANSMITPERIOD = 200; //transmit a packet to gateway so often (in ms)
|
||||
char payload[] = "123 ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
char buff[20];
|
||||
byte sendSize=0;
|
||||
boolean requestACK = false;
|
||||
SPIFlash flash(FLASH_SS, 0xEF30); //EF30 for 4mbit Windbond chip (W25X40CL)
|
||||
|
||||
#ifdef ENABLE_ATC
|
||||
RFM69_ATC radio;
|
||||
#else
|
||||
RFM69 radio;
|
||||
#endif
|
||||
|
||||
void setup() {
|
||||
Serial.begin(SERIAL_BAUD);
|
||||
radio.initialize(FREQUENCY,NODEID,NETWORKID);
|
||||
#ifdef IS_RFM69HW_HCW
|
||||
radio.setHighPower(); //must include this only for RFM69HW/HCW!
|
||||
#endif
|
||||
radio.encrypt(ENCRYPTKEY);
|
||||
//radio.setFrequency(919000000); //set frequency to some custom frequency
|
||||
|
||||
//Auto Transmission Control - dials down transmit power to save battery (-100 is the noise floor, -90 is still pretty good)
|
||||
//For indoor nodes that are pretty static and at pretty stable temperatures (like a MotionMote) -90dBm is quite safe
|
||||
//For more variable nodes that can expect to move or experience larger temp drifts a lower margin like -70 to -80 would probably be better
|
||||
//Always test your ATC mote in the edge cases in your own environment to ensure ATC will perform as you expect
|
||||
#ifdef ENABLE_ATC
|
||||
radio.enableAutoPower(ATC_RSSI);
|
||||
#endif
|
||||
|
||||
char buff[50];
|
||||
sprintf(buff, "\nTransmitting at %d Mhz...", FREQUENCY==RF69_433MHZ ? 433 : FREQUENCY==RF69_868MHZ ? 868 : 915);
|
||||
Serial.println(buff);
|
||||
|
||||
if (flash.initialize())
|
||||
{
|
||||
Serial.print("SPI Flash Init OK ... UniqueID (MAC): ");
|
||||
flash.readUniqueId();
|
||||
for (byte i=0;i<8;i++)
|
||||
{
|
||||
Serial.print(flash.UNIQUEID[i], HEX);
|
||||
Serial.print(' ');
|
||||
}
|
||||
Serial.println();
|
||||
}
|
||||
else
|
||||
Serial.println("SPI Flash MEM not found (is chip soldered?)...");
|
||||
|
||||
#ifdef ENABLE_ATC
|
||||
Serial.println("RFM69_ATC Enabled (Auto Transmission Control)\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
void Blink(byte PIN, int DELAY_MS)
|
||||
{
|
||||
pinMode(PIN, OUTPUT);
|
||||
digitalWrite(PIN,HIGH);
|
||||
delay(DELAY_MS);
|
||||
digitalWrite(PIN,LOW);
|
||||
}
|
||||
|
||||
long lastPeriod = 0;
|
||||
void loop() {
|
||||
//process any serial input
|
||||
if (Serial.available() > 0)
|
||||
{
|
||||
char input = Serial.read();
|
||||
if (input >= 48 && input <= 57) //[0,9]
|
||||
{
|
||||
TRANSMITPERIOD = 100 * (input-48);
|
||||
if (TRANSMITPERIOD == 0) TRANSMITPERIOD = 1000;
|
||||
Serial.print("\nChanging delay to ");
|
||||
Serial.print(TRANSMITPERIOD);
|
||||
Serial.println("ms\n");
|
||||
}
|
||||
|
||||
if (input == 'r') //d=dump register values
|
||||
radio.readAllRegs();
|
||||
//if (input == 'E') //E=enable encryption
|
||||
// radio.encrypt(KEY);
|
||||
//if (input == 'e') //e=disable encryption
|
||||
// radio.encrypt(null);
|
||||
|
||||
if (input == 'd') //d=dump flash area
|
||||
{
|
||||
Serial.println("Flash content:");
|
||||
uint16_t counter = 0;
|
||||
|
||||
Serial.print("0-256: ");
|
||||
while(counter<=256){
|
||||
Serial.print(flash.readByte(counter++), HEX);
|
||||
Serial.print('.');
|
||||
}
|
||||
while(flash.busy());
|
||||
Serial.println();
|
||||
}
|
||||
if (input == 'e')
|
||||
{
|
||||
Serial.print("Erasing Flash chip ... ");
|
||||
flash.chipErase();
|
||||
while(flash.busy());
|
||||
Serial.println("DONE");
|
||||
}
|
||||
if (input == 'i')
|
||||
{
|
||||
Serial.print("DeviceID: ");
|
||||
word jedecid = flash.readDeviceId();
|
||||
Serial.println(jedecid, HEX);
|
||||
}
|
||||
}
|
||||
|
||||
//check for any received packets
|
||||
if (radio.receiveDone())
|
||||
{
|
||||
Serial.print('[');Serial.print(radio.SENDERID, DEC);Serial.print("] ");
|
||||
for (byte i = 0; i < radio.DATALEN; i++)
|
||||
Serial.print((char)radio.DATA[i]);
|
||||
Serial.print(" [RX_RSSI:");Serial.print(radio.RSSI);Serial.print("]");
|
||||
|
||||
if (radio.ACKRequested())
|
||||
{
|
||||
radio.sendACK();
|
||||
Serial.print(" - ACK sent");
|
||||
}
|
||||
Blink(LED,3);
|
||||
Serial.println();
|
||||
}
|
||||
|
||||
int currPeriod = millis()/TRANSMITPERIOD;
|
||||
if (currPeriod != lastPeriod)
|
||||
{
|
||||
lastPeriod=currPeriod;
|
||||
|
||||
//send FLASH id
|
||||
if(sendSize==0)
|
||||
{
|
||||
sprintf(buff, "FLASH_MEM_ID:0x%X", flash.readDeviceId());
|
||||
byte buffLen=strlen(buff);
|
||||
if (radio.sendWithRetry(GATEWAYID, buff, buffLen))
|
||||
Serial.print(" ok!");
|
||||
else Serial.print(" nothing...");
|
||||
//sendSize = (sendSize + 1) % 31;
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.print("Sending[");
|
||||
Serial.print(sendSize);
|
||||
Serial.print("]: ");
|
||||
for(byte i = 0; i < sendSize; i++)
|
||||
Serial.print((char)payload[i]);
|
||||
|
||||
if (radio.sendWithRetry(GATEWAYID, payload, sendSize))
|
||||
Serial.print(" ok!");
|
||||
else Serial.print(" nothing...");
|
||||
}
|
||||
sendSize = (sendSize + 1) % 31;
|
||||
Serial.println();
|
||||
Blink(LED,3);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
// Sample RFM69 sketch for the MotionOLED mote containing the OLED
|
||||
// Displays any messages on the network on the OLED display and beeps the buzzer every time a message is received
|
||||
// The side button will step through 10 past received messages
|
||||
// Library and code by Felix Rusu - felix@lowpowerlab.com
|
||||
// Get libraries at: https://github.com/LowPowerLab/
|
||||
// Make sure you adjust the settings in the configuration section below !!!
|
||||
|
||||
// **********************************************************************************
|
||||
// Copyright Felix Rusu 2016, http://www.LowPowerLab.com/contact
|
||||
// **********************************************************************************
|
||||
// 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.
|
||||
//
|
||||
// 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 <RFM69.h> //get it here: https://www.github.com/lowpowerlab/rfm69
|
||||
#include <SPIFlash.h> //get library from: https://www.github.com/lowpowerlab/spiflash
|
||||
#include <LowPower.h> //get library from: https://github.com/lowpowerlab/lowpower
|
||||
#include "U8glib.h" //get library from: https://code.google.com/p/u8glib/
|
||||
#include <SPI.h> //included with Arduino IDE (www.arduino.cc)
|
||||
|
||||
//****************************************************************************************************************
|
||||
//**** IMPORTANT RADIO SETTINGS - YOU MUST CHANGE/CONFIGURE TO MATCH YOUR HARDWARE TRANSCEIVER CONFIGURATION! ****
|
||||
//****************************************************************************************************************
|
||||
#define NODEID 122 //unique for each node on same network
|
||||
#define NETWORKID 100 //the same on all nodes that talk to each other
|
||||
//Match frequency to the hardware version of the radio on your Moteino (uncomment one):
|
||||
//#define FREQUENCY RF69_433MHZ
|
||||
//#define FREQUENCY RF69_868MHZ
|
||||
#define FREQUENCY RF69_915MHZ
|
||||
#define ENCRYPTKEY "sampleEncryptKey" //exactly the same 16 characters/bytes on all nodes!
|
||||
#define IS_RFM69HW_HCW //uncomment only for RFM69HW/HCW! Leave out if you have RFM69W/CW!
|
||||
//*********************************************************************************************
|
||||
|
||||
#define SERIAL_BAUD 115200
|
||||
#define LED 5 // Moteinos have LEDs on D9, but for MotionMote we are using the external led on D5
|
||||
#define BUZZER 6
|
||||
#define BUTTON_INT 1 //user button on interrupt 1
|
||||
#define BUTTON_PIN 3 //user button on interrupt 1
|
||||
|
||||
RFM69 radio;
|
||||
U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_NONE); // I2C / TWI SSD1306 OLED 128x64
|
||||
bool promiscuousMode = true; //set to 'true' to sniff all packets on the same network
|
||||
|
||||
void setup() {
|
||||
Serial.begin(SERIAL_BAUD);
|
||||
radio.initialize(FREQUENCY,NODEID,NETWORKID);
|
||||
#ifdef IS_RFM69HW_HCW
|
||||
radio.setHighPower(); //must include this only for RFM69HW/HCW!
|
||||
#endif
|
||||
radio.encrypt(ENCRYPTKEY);
|
||||
radio.promiscuous(promiscuousMode);
|
||||
char buff[50];
|
||||
sprintf(buff, "\nListening at %d Mhz...", FREQUENCY==RF69_433MHZ ? 433 : FREQUENCY==RF69_868MHZ ? 868 : 915);
|
||||
Serial.println(buff);
|
||||
pinMode(BUZZER, OUTPUT);
|
||||
|
||||
//configure OLED
|
||||
u8g.setRot180(); //flip screen
|
||||
// assign default color value
|
||||
if ( u8g.getMode() == U8G_MODE_R3G3B2 )
|
||||
u8g.setColorIndex(255); // white
|
||||
else if ( u8g.getMode() == U8G_MODE_GRAY2BIT )
|
||||
u8g.setColorIndex(3); // max intensity
|
||||
else if ( u8g.getMode() == U8G_MODE_BW )
|
||||
u8g.setColorIndex(1); // pixel on
|
||||
else if ( u8g.getMode() == U8G_MODE_HICOLOR )
|
||||
u8g.setHiColorByRGB(255,255,255);
|
||||
u8g.begin();
|
||||
Serial.flush();
|
||||
pinMode(BUTTON_PIN, INPUT_PULLUP);
|
||||
attachInterrupt(BUTTON_INT, handleButton, FALLING);
|
||||
}
|
||||
|
||||
#define FLAG_INTERRUPT 0x01
|
||||
volatile int mainEventFlags = 0;
|
||||
boolean buttonPressed = false;
|
||||
void handleButton()
|
||||
{
|
||||
mainEventFlags |= FLAG_INTERRUPT;
|
||||
}
|
||||
|
||||
byte ackCount=0;
|
||||
|
||||
#define MSG_MAX_LEN 17 //OLED 1 line max # of chars (16 + EOL)
|
||||
#define HISTORY_LEN 10 //hold this many past messages
|
||||
typedef struct {
|
||||
char data[MSG_MAX_LEN];
|
||||
int rssi;
|
||||
byte from;
|
||||
} Message;
|
||||
Message * messageHistory = new Message[HISTORY_LEN];
|
||||
|
||||
byte lastMessageIndex = HISTORY_LEN;
|
||||
byte currMessageIndex = HISTORY_LEN;
|
||||
byte historyLength = 0;
|
||||
void loop() {
|
||||
if (mainEventFlags & FLAG_INTERRUPT)
|
||||
{
|
||||
LowPower.powerDown(SLEEP_30MS, ADC_OFF, BOD_ON);
|
||||
mainEventFlags &= ~FLAG_INTERRUPT;
|
||||
if (!digitalRead(BUTTON_PIN)) {
|
||||
buttonPressed=true;
|
||||
}
|
||||
}
|
||||
|
||||
if (buttonPressed)
|
||||
{
|
||||
buttonPressed = false;
|
||||
Beep(10, false);
|
||||
|
||||
//save non-ACK messages in a circular buffer
|
||||
if (!radio.ACK_RECEIVED && historyLength > 1) //only care if at least 2 messages saved. if only 1 message it should be displayed already
|
||||
{
|
||||
if (currMessageIndex==0)
|
||||
currMessageIndex=historyLength-1;
|
||||
else currMessageIndex--;
|
||||
|
||||
//Serial.print("HIST currIndex/histLen=");Serial.print(currMessageIndex+1);Serial.print("/");Serial.print(historyLength);
|
||||
//Serial.print(" - ");
|
||||
//Serial.println(messageHistory[currMessageIndex].data);
|
||||
|
||||
u8g.firstPage();
|
||||
do {
|
||||
draw(messageHistory[currMessageIndex].data, messageHistory[currMessageIndex].rssi, messageHistory[currMessageIndex].from, true);
|
||||
} while(u8g.nextPage());
|
||||
//delay(10); //give OLED time to draw?
|
||||
}
|
||||
}
|
||||
|
||||
if (radio.receiveDone())
|
||||
{
|
||||
Serial.print('[');Serial.print(radio.SENDERID);Serial.print("] ");
|
||||
if (promiscuousMode)
|
||||
Serial.print("to [");Serial.print(radio.TARGETID);Serial.print("] ");
|
||||
|
||||
Serial.print((char*)radio.DATA);
|
||||
Serial.print(" [RX_RSSI:");Serial.print(radio.RSSI);Serial.print("]");
|
||||
Serial.println();
|
||||
|
||||
saveToHistory((char *)radio.DATA, radio.RSSI, radio.SENDERID);
|
||||
Blink(LED,3);
|
||||
Beep(20, true);
|
||||
u8g.firstPage();
|
||||
do {
|
||||
draw((char*)radio.DATA, radio.RSSI, radio.SENDERID, false);
|
||||
} while(u8g.nextPage());
|
||||
//delay(10); //give OLED time to draw?
|
||||
}
|
||||
radio.receiveDone();
|
||||
Serial.flush();
|
||||
LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_ON);
|
||||
}
|
||||
|
||||
float batteryVolts = 5;
|
||||
char* BATstr="BAT:5.00v";
|
||||
void draw(char * data, int rssi, byte from, boolean isHist) {
|
||||
char buff[20];
|
||||
// graphic commands to redraw the complete screen should be placed here
|
||||
u8g.setFont(u8g_font_unifont);
|
||||
u8g.drawStr( 0, 10, data);
|
||||
sprintf(buff, "ID:%d", from);
|
||||
u8g.drawStr( 0, 25, buff);
|
||||
sprintf(buff, "RSSI:%d", rssi);
|
||||
u8g.drawStr( 60, 25, buff);
|
||||
|
||||
if (!isHist)
|
||||
{
|
||||
batteryVolts = analogRead(A7) * 0.00322 * 1.42;
|
||||
dtostrf(batteryVolts, 3,2, BATstr);
|
||||
sprintf(buff, "BAT:%sv", BATstr);
|
||||
u8g.drawStr( 0, 55, buff);
|
||||
}
|
||||
}
|
||||
|
||||
void Beep(byte theDelay, boolean both)
|
||||
{
|
||||
if (theDelay > 20) theDelay = 20;
|
||||
tone(BUZZER, 4200); //4200
|
||||
delay(theDelay);
|
||||
noTone(BUZZER);
|
||||
LowPower.powerDown(SLEEP_15MS, ADC_OFF, BOD_ON);
|
||||
if (both)
|
||||
{
|
||||
tone(BUZZER, 4500); //4500
|
||||
delay(theDelay);
|
||||
noTone(BUZZER);
|
||||
}
|
||||
}
|
||||
|
||||
void Blink(byte PIN, int DELAY_MS)
|
||||
{
|
||||
pinMode(PIN, OUTPUT);
|
||||
digitalWrite(PIN,HIGH);
|
||||
delay(DELAY_MS);
|
||||
digitalWrite(PIN,LOW);
|
||||
}
|
||||
|
||||
void saveToHistory(char * msg, int rssi, byte from)
|
||||
{
|
||||
byte length = strlen(msg);
|
||||
byte i = 0;
|
||||
if (lastMessageIndex >=9) lastMessageIndex = 0;
|
||||
else lastMessageIndex++;
|
||||
currMessageIndex = lastMessageIndex;
|
||||
if (historyLength < HISTORY_LEN) historyLength++;
|
||||
|
||||
//Serial.print("HIST SAVE lastIndex=");Serial.print(lastMessageIndex);Serial.print(" strlen=");Serial.print(length);
|
||||
//Serial.print(" msg=[");
|
||||
|
||||
for (; i<(MSG_MAX_LEN-1) && (i < length); i++)
|
||||
{
|
||||
messageHistory[lastMessageIndex].data[i] = msg[i];
|
||||
Serial.print(msg[i]);
|
||||
}
|
||||
//Serial.print("] copied:");
|
||||
messageHistory[lastMessageIndex].data[i] = '\0'; //terminate string
|
||||
//Serial.println((char*)messageHistory[lastMessageIndex].data);
|
||||
|
||||
messageHistory[lastMessageIndex].rssi = rssi;
|
||||
messageHistory[lastMessageIndex].from = from;
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
// **********************************************************************************************************
|
||||
// Moteino gateway/base sketch that works with Moteinos equipped with RFM69W/RFM69HW/RFM69CW/RFM69HCW
|
||||
// This is a basic gateway sketch that receives packets from end node Moteinos, formats them as ASCII strings
|
||||
// with the end node [ID] and passes them to Pi/host computer via serial port
|
||||
// (ex: "messageFromNode" from node 123 gets passed to serial as "[123] messageFromNode")
|
||||
// It also listens to serial messages that should be sent to listening end nodes
|
||||
// (ex: "123:messageToNode" sends "messageToNode" to node 123)
|
||||
// Make sure to adjust the settings to match your transceiver settings (frequency, HW etc).
|
||||
// **********************************************************************************
|
||||
// Copyright Felix Rusu 2016, http://www.LowPowerLab.com/contact
|
||||
// **********************************************************************************
|
||||
// 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.
|
||||
//
|
||||
// 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 <RFM69.h> //get it here: https://github.com/lowpowerlab/rfm69
|
||||
#include <RFM69_ATC.h> //get it here: https://github.com/lowpowerlab/RFM69
|
||||
#include <RFM69_OTA.h> //get it here: https://github.com/lowpowerlab/RFM69
|
||||
#include <SPIFlash.h> //get it here: https://github.com/lowpowerlab/spiflash
|
||||
#include <SPI.h> //included with Arduino IDE (www.arduino.cc)
|
||||
|
||||
//****************************************************************************************************************
|
||||
//**** IMPORTANT RADIO SETTINGS - YOU MUST CHANGE/CONFIGURE TO MATCH YOUR HARDWARE TRANSCEIVER CONFIGURATION! ****
|
||||
//****************************************************************************************************************
|
||||
#define NODEID 1 //the ID of this node
|
||||
#define NETWORKID 200 //the network ID of all nodes this node listens/talks to
|
||||
#define FREQUENCY RF69_915MHZ //Match this with the version of your Moteino! (others: RF69_433MHZ, RF69_868MHZ)
|
||||
#define ENCRYPTKEY "sampleEncryptKey" //identical 16 characters/bytes on all nodes, not more not less!
|
||||
#define IS_RFM69HW_HCW //uncomment only for RFM69HW/HCW! Leave out if you have RFM69W/CW!
|
||||
#define ACK_TIME 30 // # of ms to wait for an ack packet
|
||||
//*****************************************************************************************************************************
|
||||
#define ENABLE_ATC //comment out this line to disable AUTO TRANSMISSION CONTROL
|
||||
#define ATC_RSSI -75 //target RSSI for RFM69_ATC (recommended > -80)
|
||||
//*****************************************************************************************************************************
|
||||
// Serial baud rate must match your Pi/host computer serial port baud rate!
|
||||
#define SERIAL_EN //comment out if you don't want any serial verbose output
|
||||
#define SERIAL_BAUD 19200
|
||||
//*****************************************************************************************************************************
|
||||
|
||||
#ifdef __AVR_ATmega1284P__
|
||||
#define LED 15 // Moteino MEGAs have LEDs on D15
|
||||
#define FLASH_SS 23 // and FLASH SS on D23
|
||||
#else
|
||||
#define LED 9 // Moteinos have LEDs on D9
|
||||
#define FLASH_SS 8 // and FLASH SS on D8
|
||||
#endif
|
||||
|
||||
#ifdef SERIAL_EN
|
||||
#define DEBUG(input) {Serial.print(input); delay(1);}
|
||||
#define DEBUGln(input) {Serial.println(input); delay(1);}
|
||||
#else
|
||||
#define DEBUG(input);
|
||||
#define DEBUGln(input);
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_ATC
|
||||
RFM69_ATC radio;
|
||||
#else
|
||||
RFM69 radio;
|
||||
#endif
|
||||
|
||||
SPIFlash flash(FLASH_SS, 0xEF30); //EF30 for 4mbit Windbond FlashMEM chip
|
||||
|
||||
void setup() {
|
||||
Serial.begin(SERIAL_BAUD);
|
||||
delay(10);
|
||||
radio.initialize(FREQUENCY,NODEID,NETWORKID);
|
||||
#ifdef IS_RFM69HW_HCW
|
||||
radio.setHighPower(); //must include this only for RFM69HW/HCW!
|
||||
#endif
|
||||
radio.encrypt(ENCRYPTKEY);
|
||||
|
||||
#ifdef ENABLE_ATC
|
||||
radio.enableAutoPower(ATC_RSSI);
|
||||
#endif
|
||||
|
||||
char buff[50];
|
||||
sprintf(buff, "\nTransmitting at %d Mhz...", radio.getFrequency()/1000000);
|
||||
DEBUGln(buff);
|
||||
|
||||
if (flash.initialize())
|
||||
{
|
||||
DEBUGln("SPI Flash Init OK!");
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUGln("SPI FlashMEM not found (is chip onboard?)");
|
||||
}
|
||||
}
|
||||
|
||||
byte ackCount=0;
|
||||
byte inputLen=0;
|
||||
char input[64];
|
||||
byte buff[61];
|
||||
String inputstr;
|
||||
|
||||
void loop() {
|
||||
inputLen = readSerialLine(input);
|
||||
inputstr = String(input);
|
||||
inputstr.toUpperCase();
|
||||
|
||||
if (inputLen > 0)
|
||||
{
|
||||
if (inputstr.equals("KEY?"))
|
||||
{
|
||||
DEBUG("ENCRYPTKEY:");
|
||||
DEBUG(ENCRYPTKEY);
|
||||
}
|
||||
|
||||
byte targetId = inputstr.toInt(); //extract ID if any
|
||||
byte colonIndex = inputstr.indexOf(":"); //find position of first colon
|
||||
|
||||
if (targetId > 0)
|
||||
{
|
||||
inputstr = inputstr.substring(colonIndex+1); //trim "ID:" if any
|
||||
}
|
||||
|
||||
if (targetId > 0 && targetId != NODEID && targetId != RF69_BROADCAST_ADDR && colonIndex>0 && colonIndex<4 && inputstr.length()>0)
|
||||
{
|
||||
inputstr.getBytes(buff, 61);
|
||||
//DEBUGln((char*)buff);
|
||||
//DEBUGln(targetId);
|
||||
//DEBUGln(colonIndex);
|
||||
if (radio.sendWithRetry(targetId, buff, inputstr.length()))
|
||||
{
|
||||
DEBUGln("ACK:OK");
|
||||
}
|
||||
else
|
||||
DEBUGln("ACK:NOK");
|
||||
}
|
||||
}
|
||||
|
||||
if (radio.receiveDone())
|
||||
{
|
||||
int rssi = radio.RSSI;
|
||||
DEBUG('[');DEBUG(radio.SENDERID);DEBUG("] ");
|
||||
if (radio.DATALEN > 0)
|
||||
{
|
||||
for (byte i = 0; i < radio.DATALEN; i++)
|
||||
DEBUG((char)radio.DATA[i]);
|
||||
DEBUG(" [RSSI:");DEBUG(rssi);DEBUG("]");
|
||||
}
|
||||
|
||||
CheckForWirelessHEX(radio, flash, false); //non verbose DEBUG
|
||||
|
||||
if (radio.ACKRequested())
|
||||
{
|
||||
byte theNodeID = radio.SENDERID;
|
||||
radio.sendACK();
|
||||
DEBUG("[ACK-sent]");
|
||||
}
|
||||
DEBUGln();
|
||||
Blink(LED,3);
|
||||
}
|
||||
}
|
||||
|
||||
void Blink(byte PIN, int DELAY_MS)
|
||||
{
|
||||
pinMode(PIN, OUTPUT);
|
||||
digitalWrite(PIN,HIGH);
|
||||
delay(DELAY_MS);
|
||||
digitalWrite(PIN,LOW);
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
// **********************************************************************************************************
|
||||
// Moteino gateway/base sketch that works with Moteinos equipped with RFM69W/RFM69HW/RFM69CW/RFM69HCW
|
||||
// This sketch uses a 2x20 Newhaven RGB backlight LCD to display incoming messages from end nodes
|
||||
// http://www.digikey.com/product-search/en?keywords=NHD-C0220BIZ-FS(RGB)-FBW-3VM
|
||||
// This is a basic gateway sketch that receives packets from end node Moteinos, formats them as ASCII strings
|
||||
// with the end node [ID] and passes them to Pi/host computer via serial port
|
||||
// (ex: "messageFromNode" from node 123 gets passed to serial as "[123] messageFromNode")
|
||||
// It also listens to serial messages that should be sent to listening end nodes
|
||||
// (ex: "123:messageToNode" sends "messageToNode" to node 123)
|
||||
// Make sure to adjust the settings to match your transceiver settings (frequency, HW etc).
|
||||
// **********************************************************************************
|
||||
// Copyright Felix Rusu 2016, http://www.LowPowerLab.com/contact
|
||||
// **********************************************************************************
|
||||
// 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.
|
||||
|
||||
// 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 <RFM69.h> //get it here: https://github.com/lowpowerlab/rfm69
|
||||
#include <RFM69_ATC.h> //get it here: https://github.com/lowpowerlab/RFM69
|
||||
#include <RFM69_OTA.h> //get it here: https://github.com/lowpowerlab/RFM69
|
||||
#include <SPIFlash.h> //get it here: https://github.com/lowpowerlab/spiflash
|
||||
#include <SPI.h> //included with Arduino IDE (www.arduino.cc)
|
||||
#include <Wire.h> //included with Arduino IDE (www.arduino.cc)
|
||||
#include "ST7036.h" //get it from here: https://bitbucket.org/fmalpartida/st7036-display-driver/src/
|
||||
#include "LCD_C0220BiZ.h" //get it from here: https://bitbucket.org/fmalpartida/st7036-display-driver/src/
|
||||
|
||||
//****************************************************************************************************************
|
||||
//**** IMPORTANT RADIO SETTINGS - YOU MUST CHANGE/CONFIGURE TO MATCH YOUR HARDWARE TRANSCEIVER CONFIGURATION! ****
|
||||
//****************************************************************************************************************
|
||||
#define NODEID 1 //the ID of this node
|
||||
#define NETWORKID 200 //the network ID of all nodes this node listens/talks to
|
||||
#define FREQUENCY RF69_915MHZ //Match this with the version of your Moteino! (others: RF69_433MHZ, RF69_868MHZ)
|
||||
#define ENCRYPTKEY "sampleEncryptKey" //identical 16 characters/bytes on all nodes, not more not less!
|
||||
#define IS_RFM69HW_HCW //uncomment only for RFM69HW/HCW! Leave out if you have RFM69W/CW!
|
||||
#define ACK_TIME 30 // # of ms to wait for an ack
|
||||
//*****************************************************************************************************************************
|
||||
#define ENABLE_ATC //comment out this line to disable AUTO TRANSMISSION CONTROL
|
||||
#define ATC_RSSI -75 //target RSSI for RFM69_ATC (recommended > -80)
|
||||
//*****************************************************************************************************************************
|
||||
// Serial baud rate must match your Pi/host computer serial port baud rate!
|
||||
#define SERIAL_EN //comment out if you don't want any serial verbose output
|
||||
#define SERIAL_BAUD 19200
|
||||
//*****************************************************************************************************************************
|
||||
|
||||
#define BACKLIGHTPIN 5 //3=R,5=G,6=B
|
||||
|
||||
#ifdef __AVR_ATmega1284P__
|
||||
#define LED 15 // Moteino MEGAs have LEDs on D15
|
||||
#define FLASH_SS 23 // and FLASH SS on D23
|
||||
#else
|
||||
#define LED 9 // Moteinos have LEDs on D9
|
||||
#define FLASH_SS 8 // and FLASH SS on D8
|
||||
#endif
|
||||
|
||||
#ifdef SERIAL_EN
|
||||
#define DEBUG(input) {Serial.print(input); delay(1);}
|
||||
#define DEBUGln(input) {Serial.println(input); delay(1);}
|
||||
#else
|
||||
#define DEBUG(input);
|
||||
#define DEBUGln(input);
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_ATC
|
||||
RFM69_ATC radio;
|
||||
#else
|
||||
RFM69 radio;
|
||||
#endif
|
||||
|
||||
SPIFlash flash(FLASH_SS, 0xEF30); //EF40 for 16mbit windbond chip
|
||||
|
||||
//initialize LCD
|
||||
ST7036 lcd = ST7036(2, 20, 0x78, BACKLIGHTPIN); //row count, column count, I2C addr, pin for backlight PWM
|
||||
byte battChar[8] = {0b00000,0b01110,0b11111,0b11111,0b11111,0b11111,0b11111,0};
|
||||
byte rssiChar[8] = {0b00000,0b00100,0b10101,0b01110,0b00100,0b00100,0b00100,0};
|
||||
|
||||
void setup() {
|
||||
Serial.begin(SERIAL_BAUD);
|
||||
delay(10);
|
||||
radio.initialize(FREQUENCY,NODEID,NETWORKID);
|
||||
#ifdef IS_RFM69HW_HCW
|
||||
radio.setHighPower(); //must include this only for RFM69HW/HCW!
|
||||
#endif
|
||||
radio.encrypt(ENCRYPTKEY);
|
||||
|
||||
#ifdef ENABLE_ATC
|
||||
radio.enableAutoPower(ATC_RSSI);
|
||||
#endif
|
||||
|
||||
char buff[50];
|
||||
sprintf(buff, "\nTransmitting at %d Mhz...", radio.getFrequency()/1000000);
|
||||
|
||||
DEBUGln(buff);
|
||||
|
||||
if (flash.initialize())
|
||||
{
|
||||
DEBUGln("SPI Flash Init OK!");
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUGln("SPI FlashMEM not found (is chip onboard?)");
|
||||
}
|
||||
|
||||
lcd.init();
|
||||
lcd.setContrast(10);
|
||||
lcd.clear();
|
||||
lcd.load_custom_character(0, battChar);
|
||||
lcd.load_custom_character(1, rssiChar);
|
||||
lcd.setCursor(0,0);
|
||||
lcd.print(buff);
|
||||
}
|
||||
|
||||
byte ackCount=0;
|
||||
byte inputLen=0;
|
||||
char input[64];
|
||||
byte buff[61];
|
||||
char LO[20];
|
||||
char BAT[20];
|
||||
char temp[25];
|
||||
String inputstr;
|
||||
|
||||
void loop() {
|
||||
inputLen = readSerialLine(input, 10, 64, 10); //readSerialLine(char* input, char endOfLineChar=10, byte maxLength=64, uint16_t timeout=10);
|
||||
inputstr = String(input);
|
||||
inputstr.toUpperCase();
|
||||
|
||||
if (inputLen > 0)
|
||||
{
|
||||
if (inputstr.equals("KEY?"))
|
||||
{
|
||||
DEBUG("ENCRYPTKEY:");
|
||||
DEBUG(ENCRYPTKEY);
|
||||
}
|
||||
|
||||
byte targetId = inputstr.toInt(); //extract ID if any
|
||||
byte colonIndex = inputstr.indexOf(":"); //find position of first colon
|
||||
if (targetId > 0) inputstr = inputstr.substring(colonIndex+1); //trim "ID:" if any
|
||||
if (targetId > 0 && targetId != NODEID && targetId != RF69_BROADCAST_ADDR && colonIndex>0 && colonIndex<4 && inputstr.length()>0)
|
||||
{
|
||||
|
||||
inputstr.getBytes(buff, 61);
|
||||
//DEBUGln((char*)buff);
|
||||
//DEBUGln(targetId);
|
||||
//DEBUGln(colonIndex);
|
||||
if (radio.sendWithRetry(targetId, buff, inputstr.length()))
|
||||
{
|
||||
DEBUGln("ACK:OK");
|
||||
}
|
||||
else
|
||||
DEBUGln("ACK:NOK");
|
||||
}
|
||||
}
|
||||
|
||||
if (radio.receiveDone())
|
||||
{
|
||||
int rssi = radio.RSSI;
|
||||
DEBUG('[');DEBUG(radio.SENDERID);DEBUG("] ");
|
||||
if (radio.DATALEN > 0)
|
||||
{
|
||||
for (byte i = 0; i < radio.DATALEN; i++)
|
||||
DEBUG((char)radio.DATA[i]);
|
||||
DEBUG(" [RSSI:");DEBUG(rssi);DEBUG("]");
|
||||
}
|
||||
|
||||
CheckForWirelessHEX(radio, flash, false); //non verbose DEBUG
|
||||
|
||||
if (radio.ACKRequested())
|
||||
{
|
||||
byte theNodeID = radio.SENDERID;
|
||||
radio.sendACK();
|
||||
DEBUG("[ACK-sent]");
|
||||
}
|
||||
DEBUGln();
|
||||
Blink(LED,3);
|
||||
|
||||
lcd.clear();
|
||||
lcd.setCursor(0,0);
|
||||
|
||||
//if (radio.DATALEN < RF69_MAX_DATA_LEN) radio.DATA[radio.DATALEN]=0;
|
||||
byte matches = sscanf((const char*)radio.DATA, "%s BAT:%s", LO, BAT);
|
||||
if (matches==2)
|
||||
{
|
||||
lcd.print(LO);
|
||||
lcd.setCursor(0,14);
|
||||
lcd.print(char(0));
|
||||
lcd.setCursor(0,15);
|
||||
lcd.print(BAT);
|
||||
}
|
||||
else lcd.print((const char*)radio.DATA);
|
||||
|
||||
lcd.setCursor(1,14);
|
||||
lcd.print(char(1));
|
||||
lcd.setCursor(1,16);
|
||||
lcd.print(rssi);
|
||||
}
|
||||
}
|
||||
|
||||
void Blink(byte PIN, int DELAY_MS)
|
||||
{
|
||||
pinMode(PIN, OUTPUT);
|
||||
digitalWrite(PIN,HIGH);
|
||||
delay(DELAY_MS);
|
||||
digitalWrite(PIN,LOW);
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
// Sample RFM69 sketch for PulseMote sketch works with Moteinos equipped with RFM69W/RFM69HW/RFM69CW/RFM69HCW
|
||||
// Reads an EE-SY310 based water/pulse meter and reports readings like:
|
||||
// - GPM - gallons per minute (when actively flowing)
|
||||
// - GLM - gallons last minute (gallons used in the last minute)
|
||||
// - GAL - total gallons used
|
||||
// Example: https://lowpowerlab.com/blog/2013/02/02/meet-the-watermote-moteino-based-water-meter-reader-ee-sy310/
|
||||
// **********************************************************************************
|
||||
// Copyright Felix Rusu 2016, http://www.LowPowerLab.com/contact
|
||||
// **********************************************************************************
|
||||
// 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.
|
||||
//
|
||||
// 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 <RFM69.h> //get it here: https://github.com/lowpowerlab/rfm69
|
||||
#include <RFM69_ATC.h> //get it here: https://github.com/lowpowerlab/RFM69
|
||||
#include <RFM69_OTA.h> //get it here: https://github.com/lowpowerlab/RFM69
|
||||
#include <SPIFlash.h> //get it here: https://github.com/lowpowerlab/spiflash
|
||||
#include <SPI.h> //included with Arduino IDE (www.arduino.cc)
|
||||
#include <EEPROM.h> //included with Arduino IDE (www.arduino.cc)
|
||||
#include <TimerOne.h>
|
||||
|
||||
//****************************************************************************************************************
|
||||
//**** IMPORTANT RADIO SETTINGS - YOU MUST CHANGE/CONFIGURE TO MATCH YOUR HARDWARE TRANSCEIVER CONFIGURATION! ****
|
||||
//****************************************************************************************************************
|
||||
#define NODEID 5
|
||||
#define GATEWAYID 1
|
||||
#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_HCW //uncomment only for RFM69HW/HCW! Leave out if you have RFM69W/CW!
|
||||
//*****************************************************************************************************************************
|
||||
#define ENABLE_ATC //comment out this line to disable AUTO TRANSMISSION CONTROL
|
||||
#define ATC_RSSI -75
|
||||
//*****************************************************************************************************************************
|
||||
#define PULSESPERGALLON 45 //how many pulses from sensor equal 1 gallon
|
||||
#define GPMTHRESHOLD 8000 // GPM will reset after this many MS if no pulses are registered
|
||||
#define XMITPERIOD 5000 // GPMthreshold should be less than 2*XMITPERIOD
|
||||
//*********************************************************************************************
|
||||
#ifdef __AVR_ATmega1284P__
|
||||
#define LED 15 // Moteino MEGAs have LEDs on D15
|
||||
#define INTERRUPTPIN 1 //INT1 = digital pin 11 (must be a hardware interrupt pin!)
|
||||
#else
|
||||
#define LED 9 // Moteinos have LEDs on D9
|
||||
#define INTERRUPTPIN 1 //INT1 = digital pin 3 (must be a hardware interrupt pin!)
|
||||
#endif
|
||||
//*********************************************************************************************
|
||||
#define SERIAL_EN //uncomment this line to enable serial IO (when you debug Moteino and need serial output)
|
||||
#define SERIAL_BAUD 115200
|
||||
#ifdef SERIAL_EN
|
||||
#define DEBUG(input) {Serial.print(input);}
|
||||
#define DEBUGln(input) {Serial.println(input);}
|
||||
#else
|
||||
#define DEBUG(input);
|
||||
#define DEBUGln(input);
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_ATC
|
||||
RFM69_ATC radio;
|
||||
#else
|
||||
RFM69 radio;
|
||||
#endif
|
||||
//*****************************************************************************************************************************
|
||||
// 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); //WINDBOND 4MBIT flash chip on CS pin D8 (default for Moteino)
|
||||
|
||||
volatile byte ledState = LOW;
|
||||
volatile unsigned long PulseCounterVolatile = 0; // use volatile for shared variables
|
||||
unsigned long NOW = 0;
|
||||
unsigned long PulseCounter = 0;
|
||||
unsigned long LASTMINUTEMARK = 0;
|
||||
unsigned long PULSECOUNTLASTMINUTEMARK = 0; //keeps pulse count at the last minute mark
|
||||
|
||||
byte COUNTEREEPROMSLOTS = 10;
|
||||
unsigned long COUNTERADDRBASE = 8; //address in EEPROM that points to the first possible slot for a counter
|
||||
unsigned long COUNTERADDR = 0; //address in EEPROM that points to the latest Counter in EEPROM
|
||||
byte secondCounter = 0;
|
||||
|
||||
unsigned long TIMESTAMP_pulse_prev = 0;
|
||||
unsigned long TIMESTAMP_pulse_curr = 0;
|
||||
int pulseAVGInterval = 0;
|
||||
int pulsesPerXMITperiod = 0;
|
||||
float GPM=0, GLM=0, GAL=0, GALlast=0, GPMlast=0, GLMlast=0;
|
||||
byte sendLen;
|
||||
char buff[80];
|
||||
char* GALstr="99999999999999.99"; //longest expected GAL message
|
||||
char* GPMstr="99999.99"; //longest expected GPM message
|
||||
char* GLMstr="9999999.99"; //longest expected GLM message
|
||||
boolean WPReady = false;
|
||||
|
||||
void setup() {
|
||||
#ifdef SERIAL_EN
|
||||
Serial.begin(SERIAL_BAUD);
|
||||
#endif
|
||||
|
||||
radio.initialize(FREQUENCY,NODEID,NETWORKID);
|
||||
#ifdef IS_RFM69HW_HCW
|
||||
radio.setHighPower(); //must include this only for RFM69HW/HCW!
|
||||
#endif
|
||||
radio.encrypt(ENCRYPTKEY);
|
||||
pinMode(LED, OUTPUT);
|
||||
|
||||
#ifdef ENABLE_ATC
|
||||
radio.enableAutoPower(ATC_RSSI);
|
||||
#endif
|
||||
|
||||
//initialize counter from EEPROM
|
||||
unsigned long savedCounter = EEPROM_Read_Counter();
|
||||
if (savedCounter <=0) savedCounter = 1; //avoid division by 0
|
||||
PulseCounterVolatile = PulseCounter = PULSECOUNTLASTMINUTEMARK = savedCounter;
|
||||
attachInterrupt(INTERRUPTPIN, pulseCounterInterrupt, RISING);
|
||||
Timer1.initialize(XMITPERIOD * 1000L);
|
||||
Timer1.attachInterrupt(XMIT);
|
||||
|
||||
sprintf(buff, "\nTransmitting at %d Mhz, id:%d nid:%d gid:%d", FREQUENCY==RF69_433MHZ ? 433 : FREQUENCY==RF69_868MHZ ? 868 : 915, NODEID, NETWORKID, GATEWAYID);
|
||||
DEBUG(buff);
|
||||
for (byte i=0;i<strlen(ENCRYPTKEY);i++) DEBUG(ENCRYPTKEY[i]);
|
||||
DEBUGln();
|
||||
|
||||
if (flash.initialize())
|
||||
{
|
||||
DEBUGln("FLASH MEM present, ready for wireless programming.");
|
||||
WPReady = true;
|
||||
}
|
||||
else
|
||||
DEBUGln("FLASH MEM not found, skipping wireless programming checks.");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (WPReady && radio.receiveDone())
|
||||
{
|
||||
DEBUG('[');DEBUG(radio.SENDERID);DEBUG("] ");
|
||||
for (byte i = 0; i < radio.DATALEN; i++)
|
||||
DEBUG((char)radio.DATA[i]);
|
||||
|
||||
// wireless programming token check
|
||||
// DO NOT REMOVE, or PulseMote will not be wirelessly programmable any more!
|
||||
CheckForWirelessHEX(radio, flash, true);
|
||||
}
|
||||
}
|
||||
|
||||
void pulseCounterInterrupt()
|
||||
{
|
||||
noInterrupts();
|
||||
ledState = !ledState;
|
||||
PulseCounterVolatile++; // increase when LED turns on
|
||||
digitalWrite(LED, ledState);
|
||||
NOW = millis();
|
||||
|
||||
//remember how long between pulses (sliding window)
|
||||
TIMESTAMP_pulse_prev = TIMESTAMP_pulse_curr;
|
||||
TIMESTAMP_pulse_curr = NOW;
|
||||
|
||||
if (TIMESTAMP_pulse_curr - TIMESTAMP_pulse_prev > GPMTHRESHOLD)
|
||||
//more than 'GPMthreshold' seconds passed since last pulse... resetting GPM
|
||||
pulsesPerXMITperiod=pulseAVGInterval=0;
|
||||
else
|
||||
{
|
||||
pulsesPerXMITperiod++;
|
||||
pulseAVGInterval += TIMESTAMP_pulse_curr - TIMESTAMP_pulse_prev;
|
||||
}
|
||||
interrupts();
|
||||
}
|
||||
|
||||
void XMIT()
|
||||
{
|
||||
noInterrupts();
|
||||
PulseCounter = PulseCounterVolatile;
|
||||
interrupts();
|
||||
|
||||
if (millis() - TIMESTAMP_pulse_curr >= 5000)
|
||||
{
|
||||
ledState = !ledState;
|
||||
digitalWrite(LED, ledState);
|
||||
}
|
||||
|
||||
//calculate Gallons counter
|
||||
GAL = ((float)PulseCounter)/PULSESPERGALLON;
|
||||
DEBUG("PulseCounter:");DEBUG(PulseCounter);DEBUG(", GAL: "); DEBUGln(GAL);
|
||||
|
||||
//calculate & output GPM
|
||||
GPM = pulseAVGInterval > 0 ? 60.0 * 1000 * (1.0/PULSESPERGALLON)/(pulseAVGInterval/pulsesPerXMITperiod)
|
||||
: 0;
|
||||
dtostrf(GAL,3,2, GALstr);
|
||||
dtostrf(GPM,3,2, GPMstr);
|
||||
|
||||
pulsesPerXMITperiod = 0;
|
||||
pulseAVGInterval = 0;
|
||||
secondCounter += XMITPERIOD/1000;
|
||||
|
||||
//once per minute, output a GallonsLastMinute count
|
||||
if (secondCounter>=60)
|
||||
{
|
||||
//DEBUG("60sec mark ... ");
|
||||
secondCounter=0;
|
||||
GLM = ((float)(PulseCounter - PULSECOUNTLASTMINUTEMARK))/PULSESPERGALLON;
|
||||
PULSECOUNTLASTMINUTEMARK = PulseCounter;
|
||||
EEPROM_Write_Counter(PulseCounter);
|
||||
dtostrf(GLM,3,2, GLMstr);
|
||||
sprintf(buff, "GAL:%s GPM:%s GLM:%s", GALstr, GPMstr, GLMstr);
|
||||
//DEBUGln("done");
|
||||
}
|
||||
else
|
||||
{
|
||||
sprintf(buff, "GAL:%s GPM:%s", GALstr, GPMstr);
|
||||
}
|
||||
|
||||
if (GPM!=GPMlast || GAL!=GALlast || GLM!=GLMlast)
|
||||
{
|
||||
sendLen = strlen(buff);
|
||||
radio.sendWithRetry(GATEWAYID, buff, sendLen);
|
||||
GALlast = GAL;
|
||||
GPMlast = GPM;
|
||||
GLMlast = GLM;
|
||||
}
|
||||
|
||||
DEBUGln(buff);
|
||||
}
|
||||
|
||||
unsigned long EEPROM_Read_Counter()
|
||||
{
|
||||
return EEPROM_Read_ULong(EEPROM_Read_ULong(COUNTERADDR));
|
||||
}
|
||||
|
||||
void EEPROM_Write_Counter(unsigned long counterNow)
|
||||
{
|
||||
if (counterNow == EEPROM_Read_Counter())
|
||||
{
|
||||
DEBUG("{EEPROM-SKIP(no changes)}");
|
||||
return; //skip if nothing changed
|
||||
}
|
||||
|
||||
DEBUG("{EEPROM-SAVE(");
|
||||
DEBUG(EEPROM_Read_ULong(COUNTERADDR));
|
||||
DEBUG(")=");
|
||||
DEBUG(PulseCounter);
|
||||
DEBUG("}");
|
||||
|
||||
unsigned long CounterAddr = EEPROM_Read_ULong(COUNTERADDR);
|
||||
if (CounterAddr == COUNTERADDRBASE+8*(COUNTEREEPROMSLOTS-1))
|
||||
CounterAddr = COUNTERADDRBASE;
|
||||
else CounterAddr += 8;
|
||||
|
||||
EEPROM_Write_ULong(CounterAddr, counterNow);
|
||||
EEPROM_Write_ULong(COUNTERADDR, CounterAddr);
|
||||
}
|
||||
|
||||
unsigned long EEPROM_Read_ULong(int address)
|
||||
{
|
||||
unsigned long temp;
|
||||
for (byte i=0; i<8; i++)
|
||||
temp = (temp << 8) + EEPROM.read(address++);
|
||||
return temp;
|
||||
}
|
||||
|
||||
void EEPROM_Write_ULong(int address, unsigned long data)
|
||||
{
|
||||
for (byte i=0; i<8; i++)
|
||||
{
|
||||
EEPROM.write(address+7-i, data);
|
||||
data = data >> 8;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
// Sample RFM69 random temperature sender at fixed intervals (Fahrenheit)
|
||||
// **********************************************************************************
|
||||
// Copyright Felix Rusu 2016, http://www.LowPowerLab.com/contact
|
||||
// **********************************************************************************
|
||||
// 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.
|
||||
//
|
||||
// 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 <RFM69.h> //get it here: https://github.com/lowpowerlab/rfm69
|
||||
#include <RFM69_ATC.h> //get it here: https://github.com/lowpowerlab/RFM69
|
||||
#include <RFM69_OTA.h> //get it here: https://github.com/lowpowerlab/RFM69
|
||||
#include <SPIFlash.h> //get it here: https://github.com/lowpowerlab/spiflash
|
||||
#include <SPI.h> //included with Arduino IDE (www.arduino.cc)
|
||||
|
||||
//****************************************************************************************************************
|
||||
//**** IMPORTANT RADIO SETTINGS - YOU MUST CHANGE/CONFIGURE TO MATCH YOUR HARDWARE TRANSCEIVER CONFIGURATION! ****
|
||||
//****************************************************************************************************************
|
||||
#define NODEID 98 //unique for each node on same network
|
||||
#define NETWORKID 100 //the same on all nodes that talk to each other
|
||||
#define GATEWAYID 1
|
||||
//Match frequency to the hardware version of the radio on your Moteino (uncomment one):
|
||||
//#define FREQUENCY RF69_433MHZ
|
||||
#define FREQUENCY RF69_915MHZ
|
||||
#define IS_RFM69HW_HCW //uncomment only for RFM69HW/HCW! Leave out if you have RFM69W/CW!
|
||||
#define ENCRYPTKEY "sampleEncryptKey" //exactly the same 16 characters/bytes on all nodes!
|
||||
//*****************************************************************************************************************************
|
||||
#define ENABLE_ATC //comment out this line to disable AUTO TRANSMISSION CONTROL
|
||||
#define ATC_RSSI -75
|
||||
//*****************************************************************************************************************************
|
||||
#define ACK_TIME 30 // max # of ms to wait for an ack
|
||||
|
||||
#ifdef __AVR_ATmega1284P__
|
||||
#define LED 15 // Moteino MEGAs have LEDs on D15
|
||||
#define FLASH_SS 23 // and FLASH SS on D23
|
||||
#else
|
||||
#define LED 9 // Moteinos have LEDs on D9
|
||||
#define FLASH_SS 8 // and FLASH SS on D8
|
||||
#endif
|
||||
|
||||
#define SERIAL_BAUD 115200
|
||||
|
||||
int TRANSMITPERIOD = 2000; //transmit a packet to gateway so often (in ms)
|
||||
char buff[50];
|
||||
byte sendSize=0;
|
||||
|
||||
#ifdef ENABLE_ATC
|
||||
RFM69_ATC radio;
|
||||
#else
|
||||
RFM69 radio;
|
||||
#endif
|
||||
|
||||
void setup() {
|
||||
Serial.begin(SERIAL_BAUD);
|
||||
radio.initialize(FREQUENCY,NODEID,NETWORKID);
|
||||
#ifdef IS_RFM69HW_HCW
|
||||
radio.setHighPower(); //must include this only for RFM69HW/HCW!
|
||||
#endif
|
||||
radio.encrypt(ENCRYPTKEY);
|
||||
|
||||
#ifdef ENABLE_ATC
|
||||
radio.enableAutoPower(ATC_RSSI);
|
||||
#endif
|
||||
|
||||
sprintf(buff, "\nTransmitting at %d Mhz...", FREQUENCY==RF69_433MHZ ? 433 : FREQUENCY==RF69_868MHZ ? 868 : 915);
|
||||
Serial.println(buff);
|
||||
}
|
||||
|
||||
long lastPeriod = 0;
|
||||
long randNumber1 = 0;
|
||||
long randNumber2 = 0;
|
||||
void loop() {
|
||||
if (radio.receiveDone())
|
||||
{
|
||||
Serial.print('[');Serial.print(radio.SENDERID, DEC);Serial.print("] ");
|
||||
for (byte i = 0; i < radio.DATALEN; i++)
|
||||
Serial.print((char)radio.DATA[i]);
|
||||
Serial.print(" [RX_RSSI:");Serial.print(radio.RSSI);Serial.print("]");
|
||||
|
||||
if (radio.ACKRequested())
|
||||
{
|
||||
radio.sendACK();
|
||||
Serial.print(" - ACK sent");
|
||||
}
|
||||
Blink(LED,3);
|
||||
Serial.println();
|
||||
}
|
||||
|
||||
int currPeriod = millis()/TRANSMITPERIOD;
|
||||
if (currPeriod != lastPeriod)
|
||||
{
|
||||
lastPeriod=currPeriod;
|
||||
randNumber1 = random(99);
|
||||
randNumber2 = random(99);
|
||||
|
||||
if (randNumber2 > 70)
|
||||
sprintf(buff, "F:%d.5", randNumber1);
|
||||
else
|
||||
sprintf(buff, "F:-%d.5", randNumber1);
|
||||
sendSize = strlen(buff);
|
||||
|
||||
if (radio.sendWithRetry(GATEWAYID, buff, sendSize))
|
||||
Serial.print(" ok!");
|
||||
else Serial.print(" nothing...");
|
||||
|
||||
Serial.println();
|
||||
Blink(LED,3);
|
||||
}
|
||||
}
|
||||
|
||||
void Blink(byte PIN, int DELAY_MS)
|
||||
{
|
||||
pinMode(PIN, OUTPUT);
|
||||
digitalWrite(PIN,HIGH);
|
||||
delay(DELAY_MS);
|
||||
digitalWrite(PIN,LOW);
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
// Sample sketch for the SonarMote - Simple distance reading
|
||||
// http://lowpowerlab.com/sonar
|
||||
// Ultrasonic sensor (HC-SR04) connected to D6 (Trig), D7 (Echo), and power enabled through D5
|
||||
// Make sure you adjust the settings in the configuration section below !!!
|
||||
// **********************************************************************************
|
||||
// Copyright Felix Rusu, LowPowerLab.com
|
||||
// Library and code by Felix Rusu - felix@lowpowerlab.com
|
||||
// **********************************************************************************
|
||||
// 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
|
||||
// **********************************************************************************
|
||||
#define TRIG 6
|
||||
#define ECHO 7
|
||||
#define EN 5
|
||||
|
||||
void setup() {
|
||||
pinMode (TRIG,OUTPUT);//attach pin 2 to vcc
|
||||
pinMode (ECHO,OUTPUT);//attach pin 2 to vcc
|
||||
pinMode (EN,OUTPUT);//attach pin 5 to GND
|
||||
pinMode (ECHO, INPUT);//attach pin 4 to Echo
|
||||
// initialize serial communication:
|
||||
Serial.begin(115200);
|
||||
digitalWrite(EN, LOW);
|
||||
}
|
||||
|
||||
long cm;
|
||||
void loop()
|
||||
{
|
||||
cm = readDistanceCM();
|
||||
Serial.print(cm); Serial.println("cm");
|
||||
delay(500);
|
||||
}
|
||||
|
||||
long readDistanceCM()
|
||||
{
|
||||
digitalWrite(EN, HIGH);
|
||||
delay(75);
|
||||
// The PING))) is triggered by a HIGH pulse of 2 or more microseconds.
|
||||
// Give a short LOW pulse beforehand to ensure a clean HIGH pulse:
|
||||
digitalWrite(TRIG, LOW);
|
||||
delayMicroseconds(2);
|
||||
digitalWrite(TRIG, HIGH);
|
||||
delayMicroseconds(5);
|
||||
digitalWrite(TRIG, LOW);
|
||||
pulseIn(ECHO, HIGH);
|
||||
delay(16);
|
||||
digitalWrite(TRIG, LOW);
|
||||
delayMicroseconds(2);
|
||||
digitalWrite(TRIG, HIGH);
|
||||
delayMicroseconds(5);
|
||||
digitalWrite(TRIG, LOW);
|
||||
// The same pin is used to read the signal from the PING))): a HIGH
|
||||
// pulse whose duration is the time (in microseconds) from the sending
|
||||
// of the ping to the reception of its echo off of an object.
|
||||
long duration = pulseIn(ECHO, HIGH);
|
||||
digitalWrite(EN, LOW);
|
||||
return microsecondsToCentimeters(duration);
|
||||
}
|
||||
|
||||
long microsecondsToInches(long microseconds)
|
||||
{
|
||||
// According to Parallax's datasheet for the PING))), there are
|
||||
// 73.746 microseconds per inch (i.e. sound travels at 1130 feet per
|
||||
// second). This gives the distance travelled by the ping, outbound
|
||||
// and return, so we divide by 2 to get the distance of the obstacle.
|
||||
// See: http://www.parallax.com/dl/docs/prod/acc/28015-PING-v1.3.pdf
|
||||
return microseconds / 74 / 2;
|
||||
}
|
||||
|
||||
long microsecondsToCentimeters(long microseconds)
|
||||
{
|
||||
// The speed of sound is 340 m/s or 29 microseconds per centimeter.
|
||||
// The ping travels out and back, so to find the distance of the
|
||||
// object we take half of the distance travelled.
|
||||
return microseconds / 29 / 2;
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
// Sample RFM69 sender/node sketch for the SonarMote - Distance tracker
|
||||
// Can be used for inventory control - ex to measure distance in a multi lane cigarette pack rack
|
||||
// http://lowpowerlab.com/sonarmote
|
||||
// Ultrasonic sensor (HC-SR04) connected to D6 (Trig), D7 (Echo), and power enabled through D5
|
||||
// This sketch sleeps the Moteino and sensor most of the time. It wakes up every few seconds to take
|
||||
// a distance reading. If it detects an approaching object (car) it increases the sampling rate
|
||||
// and starts lighting up the LED (from green to yellow to red to blinking red). Once there is no more
|
||||
// motion the LED is turned off and the cycle is back to a few seconds in between sensor reads.
|
||||
// Button is connected on D3. Holding the button for a few seconds enters the "red zone adjust" mode (RZA).
|
||||
// By default the red zone limit is at 25cm (LED turns RED below this and starts blinking faster and faster).
|
||||
// In RZA, readings are taken for 5 seconds. In this time you have the chance to set a new red zone limit.
|
||||
// Valid new red zone readings are between the RED__LIMIT_UPPER (default 25cm) and MAX_ADJUST_DISTANCE (cm).
|
||||
// In RZA mode the BLU Led blinks fast to indicate new red limit distance. It blinks slow if the readings are invalid
|
||||
// If desired this value could be saved to EEPROM to persist if unit is turned off
|
||||
// Get the RFM69 at: https://github.com/LowPowerLab/
|
||||
// Make sure you adjust the settings in the configuration section below !!!
|
||||
|
||||
// **********************************************************************************
|
||||
// Copyright Felix Rusu 2016, http://www.LowPowerLab.com/contact
|
||||
// **********************************************************************************
|
||||
// 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.
|
||||
//
|
||||
// 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 <RFM69.h> //get it here: https://www.github.com/lowpowerlab/rfm69
|
||||
#include <SPI.h> //included with Arduino IDE (www.arduino.cc)
|
||||
#include <LowPower.h> //get library from: https://github.com/lowpowerlab/lowpower
|
||||
//writeup here: http://www.rocketscream.com/blog/2011/07/04/lightweight-low-power-arduino-library/
|
||||
|
||||
//*********************************************************************************************
|
||||
//************ IMPORTANT SETTINGS - YOU MUST CHANGE/CONFIGURE TO FIT YOUR HARDWARE *************
|
||||
//*********************************************************************************************
|
||||
#define NODEID 22 //unique for each node on same network
|
||||
#define GATEWAYID 1 //node Id of the receiver we are sending data to
|
||||
#define NETWORKID 100 //the same on all nodes that talk to each other including this node and the gateway
|
||||
#define FREQUENCY RF69_915MHZ //others: RF69_433MHZ, RF69_868MHZ (this must match the RFM69 freq you have on your Moteino)
|
||||
#define IS_RFM69HW_HCW //uncomment only for RFM69HW/HCW! Leave out if you have RFM69W/CW!
|
||||
#define ENCRYPTKEY "sampleEncryptKey" //exactly the same 16 characters/bytes on all nodes!
|
||||
#define SENDLOOPS 80 //default:80 //if no message was sent for this many sleep loops/cycles, then force a send
|
||||
#define READ_SAMPLES 3
|
||||
#define HISTERESIS 1.3 //(cm) only send a message when new reading is this many centimeters different
|
||||
#define DIST_READ_LOOPS 2 //read distance every this many sleeping loops (ie if sleep time is 8s then 2 loops => a read occurs every 16s)
|
||||
//*********************************************************************************************
|
||||
#define BUZZER_ENABLE //uncomment this line if you have the BUZZER soldered and want the sketch to make sounds
|
||||
//#define SERIAL_EN //uncomment if you want serial debugging output
|
||||
//*********************************************************************************************
|
||||
#define SLEEP_FASTEST SLEEP_15MS
|
||||
#define SLEEP_FAST SLEEP_250MS
|
||||
#define SLEEP_SEC SLEEP_1S
|
||||
#define SLEEP_LONG SLEEP_2S
|
||||
#define SLEEP_LONGER SLEEP_4S
|
||||
#define SLEEP_LONGEST SLEEP_8S
|
||||
period_t sleepTime = SLEEP_LONGEST; //period_t is an enum type defined in the LowPower library (LowPower.h)
|
||||
//*********************************************************************************************
|
||||
#ifdef __AVR_ATmega1284P__
|
||||
#define LED 15 // Moteino MEGAs have LEDs on D15
|
||||
#define FLASH_SS 23
|
||||
#else
|
||||
#define LED 9 // Moteinos have LEDs on D9
|
||||
#define FLASH_SS 8
|
||||
#endif
|
||||
#define TRIG 6 // digital pin wired to TRIG pin of ultrasonic sensor
|
||||
#define ECHO 7 // digital pin wired to ECHO pin of ultrasonic sensor
|
||||
#define SENSOR_EN 5 // digital pin that enables power to ultrasonic sensor
|
||||
#define BUZZER 4 // digital pin that is connected to onboard buzzer
|
||||
#define MAX_DISTANCE 150 // maximum valid distance
|
||||
#define MIN_DISTANCE 2 // minimum valid distance
|
||||
#define MAX_ADJUST_DISTANCE (MAX_DISTANCE-GRN_LIMIT_UPPER) //this is the amount by which the RED_LIMIT_UPPER can by increased
|
||||
|
||||
#ifdef SERIAL_EN
|
||||
#define SERIAL_BAUD 115200
|
||||
#define DEBUG(input) {Serial.print(input);}
|
||||
#define DEBUGln(input) {Serial.println(input);}
|
||||
#define SERIALFLUSH() {Serial.flush();}
|
||||
#else
|
||||
#define DEBUG(input);
|
||||
#define DEBUGln(input);
|
||||
#define SERIALFLUSH();
|
||||
#endif
|
||||
|
||||
#define BATT_MONITOR A7 // Sense VBAT_COND signal (when powered externally should read ~3.25v/3.3v (1000-1023), when external power is cutoff it should start reading around 2.85v/3.3v * 1023 ~= 883 (ratio given by 10k+4.7K divider from VBAT_COND = 1.47 multiplier)
|
||||
#define BATT_READ_LOOPS SENDLOOPS // read and report battery voltage every this many sleep cycles (ex 30cycles * 8sec sleep = 240sec/4min). For 450 cycles you would get ~1 hour intervals between readings
|
||||
#define BATT_FORMULA(reading) reading * 0.00322 * 1.475 // >>> fine tune this parameter to match your voltage when fully charged
|
||||
#define BATT_LOW 3.55
|
||||
|
||||
byte sendLen;
|
||||
byte sendLoops=0;
|
||||
byte distReadLoops=0;
|
||||
byte battReadLoops=0;
|
||||
float distance=0;
|
||||
float prevDistance=0;
|
||||
float batteryVolts = 5;
|
||||
char buff[50]; //this is just an empty string used as a buffer to place the payload for the radio
|
||||
char* BATstr="BAT:5.00v"; //longest battery voltage reading message = 9chars
|
||||
char* DISTstr="99999.99cm"; //longest distance reading message = 5chars
|
||||
float readDistance(byte samples=1); //take 1 samples by default
|
||||
RFM69 radio;
|
||||
|
||||
void setup() {
|
||||
#ifdef SERIAL_EN
|
||||
Serial.begin(SERIAL_BAUD); // Open serial monitor at 115200 baud to see ping results.
|
||||
#endif
|
||||
|
||||
radio.initialize(FREQUENCY,NODEID,NETWORKID);
|
||||
#ifdef IS_RFM69HW_HCW
|
||||
radio.setHighPower(); //must include this only for RFM69HW/HCW!
|
||||
#endif
|
||||
radio.encrypt(ENCRYPTKEY);
|
||||
//sprintf(buff, "\nTransmitting at %d Mhz...", FREQUENCY==RF69_433MHZ ? 433 : FREQUENCY==RF69_868MHZ ? 868 : 915);
|
||||
sprintf(buff, "\nTransmitting at %d Mhz, id:%d nid:%d gid:%d", FREQUENCY==RF69_433MHZ ? 433 : FREQUENCY==RF69_868MHZ ? 868 : 915, NODEID, NETWORKID, GATEWAYID);
|
||||
DEBUG(buff);
|
||||
for (byte i=0;i<strlen(ENCRYPTKEY);i++) DEBUG(ENCRYPTKEY[i]);
|
||||
DEBUGln();
|
||||
radio.sleep();
|
||||
|
||||
pinMode(TRIG, OUTPUT);
|
||||
pinMode(ECHO, INPUT);
|
||||
pinMode(SENSOR_EN, OUTPUT);
|
||||
digitalWrite(SENSOR_EN, LOW);
|
||||
#ifdef BUZZER_ENABLE
|
||||
pinMode(BUZZER, OUTPUT);
|
||||
buzzer(50,2,100);
|
||||
#endif
|
||||
|
||||
radio.sendWithRetry(GATEWAYID, "START", 5);
|
||||
|
||||
SERIALFLUSH();
|
||||
readDistance(); //first reading seems to always be low
|
||||
readBattery();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (battReadLoops--<=0) //only read battery every BATT_READ_LOOPS cycles
|
||||
{
|
||||
readBattery();
|
||||
battReadLoops = BATT_READ_LOOPS-1;
|
||||
}
|
||||
|
||||
if (distReadLoops--<=0)
|
||||
{
|
||||
distance = readDistance(READ_SAMPLES);
|
||||
distReadLoops = DIST_READ_LOOPS-1;
|
||||
}
|
||||
|
||||
float diff = distance - prevDistance;
|
||||
if ((diff > HISTERESIS || diff < -HISTERESIS) || (sendLoops--<=0)) //only send a new message if the distance has changed more than the HISTERESIS or if sendloops has expired
|
||||
{
|
||||
if (distance > MAX_DISTANCE || distance < MIN_DISTANCE)
|
||||
DISTstr = "0"; // zero, out of range
|
||||
else dtostrf(distance,3,2, DISTstr);
|
||||
|
||||
if (diff > HISTERESIS || diff < -HISTERESIS)
|
||||
sprintf(buff, "%scm BAT:%s", DISTstr, BATstr); //send both distance and battery readings
|
||||
else
|
||||
sprintf(buff, "BAT:%s", BATstr); //distance has not changed significantly so only send last battery reading
|
||||
sendLen = strlen(buff);
|
||||
|
||||
digitalWrite(LED, HIGH);
|
||||
DEBUG(buff);
|
||||
if (radio.sendWithRetry(GATEWAYID, buff, sendLen))
|
||||
{
|
||||
prevDistance = distance;
|
||||
DEBUG(" - ACK:OK! RSSI:");
|
||||
DEBUGln(radio.RSSI);
|
||||
}
|
||||
else DEBUGln(" - ACK:NOK...");
|
||||
digitalWrite(LED, LOW);
|
||||
sendLoops = SENDLOOPS-1; //reset send loop counter
|
||||
}
|
||||
radio.sleep();
|
||||
SERIALFLUSH();
|
||||
|
||||
LowPower.powerDown(sleepTime, ADC_OFF, BOD_OFF); //put microcontroller to sleep to save battery life
|
||||
}
|
||||
|
||||
float readDistance(byte samples)
|
||||
{
|
||||
if (samples == 0) samples = 1;
|
||||
if (samples > 10) samples = 10;
|
||||
digitalWrite(SENSOR_EN, HIGH);
|
||||
//need about 60-75ms after power up before HC-SR04 will be usable, so just sleep in the meantime
|
||||
LowPower.powerDown(SLEEP_60MS, ADC_OFF, BOD_OFF);
|
||||
LowPower.powerDown(SLEEP_15MS, ADC_OFF, BOD_OFF);
|
||||
PING();
|
||||
LowPower.powerDown(SLEEP_15MS, ADC_OFF, BOD_OFF);
|
||||
|
||||
unsigned long duration = 0;
|
||||
for (byte i=0; i<samples; i++)
|
||||
{
|
||||
duration += PING();
|
||||
if (samples > 1) LowPower.powerDown(SLEEP_15MS, ADC_OFF, BOD_OFF);
|
||||
}
|
||||
digitalWrite(SENSOR_EN, LOW);
|
||||
return microsecondsToCentimeters(duration / samples);
|
||||
}
|
||||
|
||||
long PING()
|
||||
{
|
||||
digitalWrite(TRIG, LOW);
|
||||
delayMicroseconds(2);
|
||||
digitalWrite(TRIG, HIGH);
|
||||
delayMicroseconds(5);
|
||||
digitalWrite(TRIG, LOW);
|
||||
return pulseIn(ECHO, HIGH);
|
||||
}
|
||||
|
||||
void readBattery()
|
||||
{
|
||||
unsigned int readings=0;
|
||||
for (byte i=0; i<5; i++) //take several samples, and average
|
||||
readings+=analogRead(BATT_MONITOR);
|
||||
batteryVolts = BATT_FORMULA(readings / 5.0);
|
||||
dtostrf(batteryVolts,3,2, BATstr); //update the BATStr which gets sent every BATT_CYCLES or along with the MOTION message
|
||||
if (batteryVolts <= BATT_LOW) BATstr = "LOW";
|
||||
}
|
||||
|
||||
float microsecondsToInches(long microseconds)
|
||||
{
|
||||
// According to Parallax's datasheet for the PING))), there are
|
||||
// 73.746 microseconds per inch (i.e. sound travels at 1130 feet per
|
||||
// second). This gives the distance travelled by the ping, outbound
|
||||
// and return, so we divide by 2 to get the distance of the obstacle.
|
||||
// See: http://www.parallax.com/dl/docs/prod/acc/28015-PING-v1.3.pdf
|
||||
return microseconds / 74.0 / 2.0f;
|
||||
}
|
||||
|
||||
float microsecondsToCentimeters(long microseconds)
|
||||
{
|
||||
// The speed of sound is 340 m/s or 29 microseconds per centimeter.
|
||||
// The ping travels out and back, so to find the distance of the
|
||||
// object we take half of the distance travelled.
|
||||
return (float)microseconds / 29.0f / 2.0f;
|
||||
}
|
||||
|
||||
#ifdef BUZZER_ENABLE
|
||||
void buzzer(byte soundTime, byte repeats, byte repeatsDelay)
|
||||
{
|
||||
for (byte i=0;i<=repeats;i++)
|
||||
{
|
||||
tone(BUZZER, 4500); //4500hz makes a nice audible sound from a 3.3v Moteino digital pin
|
||||
delay(soundTime);
|
||||
noTone(BUZZER);
|
||||
if (repeats>0) delay(repeatsDelay);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void Blink(byte pin)
|
||||
{
|
||||
pinMode(pin, OUTPUT);
|
||||
digitalWrite(pin, HIGH);
|
||||
delay(2);
|
||||
digitalWrite(pin, LOW);
|
||||
}
|
||||
@@ -0,0 +1,418 @@
|
||||
// Sample sketch for the SonarMote - Standalone parking assist with RGB LED indicator
|
||||
// This example uses the NewPing library from https://code.google.com/p/arduino-new-ping/
|
||||
// but that could be replaced by raw reading of the sonar sensor as seen in other SonarMote examples
|
||||
// More info/photos at: http://lowpowerlab.com/sonar
|
||||
// Ultrasonic sensor (HC-SR04) connected to D6 (Trig), D7 (Echo), and power enabled through D5
|
||||
// This sketch sleeps the Moteino and sensor most of the time. It wakes up every few seconds to take
|
||||
// a distance reading. If it detects an approaching object (car) it increases the sampling rate
|
||||
// and starts lighting up the LED (from green to yellow to red to blinking red). Once there is no more
|
||||
// motion the LED is turned off and the cycle is back to a few seconds in between sensor reads.
|
||||
// Button is connected on D3. Holding the button for a few seconds enters the "red zone adjust" mode (RZA).
|
||||
// By default the red zone limit is at 25cm (LED turns RED below this and starts blinking faster and faster).
|
||||
// In RZA, readings are taken for 5 seconds. In this time you have the chance to set a new red zone limit.
|
||||
// Valid new red zone readings are between the RED__LIMIT_UPPER (default 25cm) and MAX_ADJUST_DISTANCE (cm).
|
||||
// In RZA mode the BLU Led blinks fast to indicate new red limit distance. It blinks slow if the readings are invalid
|
||||
// If desired this value could be saved to EEPROM to persist if unit is turned off
|
||||
// Make sure you adjust the settings in the configuration section below !!!
|
||||
|
||||
// **********************************************************************************
|
||||
// Copyright Felix Rusu, LowPowerLab.com
|
||||
// Library and code by Felix Rusu - felix@lowpowerlab.com
|
||||
// **********************************************************************************
|
||||
// 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 <NewPing.h> // get this library at: https://code.google.com/p/arduino-new-ping/
|
||||
#include <LowPower.h> //get library from: https://github.com/lowpowerlab/lowpower
|
||||
//writeup here: http://www.rocketscream.com/blog/2011/07/04/lightweight-low-power-arduino-library/
|
||||
|
||||
#define TRIG 6 // digital pin wired to TRIG pin of ultrasonic sensor
|
||||
#define ECHO 7 // digital pin wired to ECHO pin of ultrasonic sensor
|
||||
#define SENSOR_EN 5 // digital pin that enables power to ultrasonic sensor
|
||||
#define RED A0 // pin connected to red LED
|
||||
#define GRN A1 // pin connected to green LED
|
||||
#define BLU A2 // pin connected to blue LED
|
||||
#define BUTTON_INT 1 // user button on interrupt 1 (D3)
|
||||
#define BUTTON_PIN 3 // user button on interrupt 1 (D3)
|
||||
#define BUTTON_HOLD_MS 3000 // hold button this many ms before entering red zone adjust
|
||||
#define MOTEINOLED 9 // moteino onboard LED
|
||||
#define MAX_DISTANCE 220 // maximum valid distance
|
||||
#define MIN_DISTANCE 2 // minimum valid distance
|
||||
|
||||
#define GRN_LIMIT_UPPER 180+redZoneAdjust // upper limit distance for GREEN
|
||||
#define YLW_LIMIT_UPPER 40+redZoneAdjust // upper limit distance for YELLOW
|
||||
#define RED_LIMIT_UPPER 25+redZoneAdjust // upper limit distance for RED
|
||||
|
||||
#define MAX_ADJUST_DISTANCE (MAX_DISTANCE-GRN_LIMIT_UPPER) //this is the amount by which the RED_LIMIT_UPPER can by increased
|
||||
|
||||
//possible states of LED status
|
||||
#define STATE_SOLID 0
|
||||
#define STATE_BLINK 1
|
||||
|
||||
//possible states for LED color
|
||||
#define STATE_OFF 0
|
||||
#define STATE_GRN 1
|
||||
#define STATE_YLW 2
|
||||
#define STATE_RED 3
|
||||
|
||||
byte state_LED = STATE_SOLID;
|
||||
byte state_LEDCOLOR = STATE_OFF;
|
||||
byte state_LEDONOFF = LOW;
|
||||
byte redZoneAdjust = 0; //this is adjustable via the button (press button for a few seconds, then take a reading)
|
||||
|
||||
#define LED_RED {digitalWrite(RED,HIGH);digitalWrite(GRN,LOW);}
|
||||
#define LED_GRN {digitalWrite(RED,LOW);digitalWrite(GRN,HIGH);}
|
||||
#define LED_YLW {digitalWrite(RED,HIGH);digitalWrite(GRN,HIGH);}
|
||||
#define LED_OFF {digitalWrite(RED,LOW);digitalWrite(GRN,LOW);}
|
||||
|
||||
#define SERIAL_EN //uncomment if you want serial debugging output
|
||||
#ifdef SERIAL_EN
|
||||
#define SERIAL_BAUD 115200
|
||||
#define DEBUG(input) {Serial.print(input);}
|
||||
#define DEBUGln(input) {Serial.println(input);}
|
||||
#define SERIALFLUSH() {Serial.flush();}
|
||||
#else
|
||||
#define DEBUG(input);
|
||||
#define DEBUGln(input);
|
||||
#define SERIALFLUSH();
|
||||
#endif
|
||||
|
||||
#define SLEEP_MINILOOP SLEEP_15MS
|
||||
#define SLEEP_LOOP SLEEP_250MS
|
||||
#define SLEEP_LONG SLEEP_2S
|
||||
#define SLEEP_LOBATT SLEEP_4S
|
||||
#define SLEEP_HIBERNATE SLEEP_8S
|
||||
|
||||
#define BATT_MONITOR A7 // Sense VBAT_COND signal (when powered externally should read ~3.25v/3.3v (1000-1023), when external power is cutoff it should start reading around 2.85v/3.3v * 1023 ~= 883 (ratio given by 10k+4.7K divider from VBAT_COND = 1.47 multiplier)
|
||||
#define BATT_CYCLES 120 // read and report battery voltage every this many sleep cycles (ex 30cycles * 8sec sleep = 240sec/4min). For 450 cyclesyou would get ~1 hour intervals
|
||||
#define BATT_FORMULA(reading) reading * 0.00322 * 1.475 // >>> fine tune this parameter to match your voltage when fully charged
|
||||
#define BATT_LOW 3.35
|
||||
|
||||
NewPing sensor(TRIG, ECHO, MAX_DISTANCE); // NewPing setup of pins and maximum distance.
|
||||
float readDistance(byte samples=3); //take 3 samples by default
|
||||
void checkBattery(byte samples=10); //take 10 samples by default
|
||||
float batteryVolts = 5;
|
||||
|
||||
void setup() {
|
||||
#ifdef SERIAL_EN
|
||||
Serial.begin(SERIAL_BAUD); // Open serial monitor at 115200 baud to see ping results.
|
||||
#endif
|
||||
pinMode(TRIG, OUTPUT);
|
||||
pinMode(ECHO, INPUT);
|
||||
pinMode(RED, OUTPUT);
|
||||
pinMode(GRN, OUTPUT);
|
||||
pinMode(BLU, OUTPUT);
|
||||
pinMode(SENSOR_EN, OUTPUT);
|
||||
digitalWrite(SENSOR_EN, LOW);
|
||||
pinMode(BUTTON_PIN, INPUT_PULLUP);
|
||||
attachInterrupt(BUTTON_INT, buttonInterrupt, FALLING);
|
||||
}
|
||||
|
||||
#define FLAG_INTERRUPT 0x01
|
||||
volatile int mainEventFlags = 0;
|
||||
boolean buttonPressed = false;
|
||||
void buttonInterrupt()
|
||||
{
|
||||
mainEventFlags |= FLAG_INTERRUPT;
|
||||
}
|
||||
|
||||
long distance=0;
|
||||
long lastDistance=0;
|
||||
long lastSigDistance=0;
|
||||
byte loops=0;
|
||||
byte miniLoops=0;
|
||||
byte skipBlinkingLoops=5;
|
||||
unsigned long now=0;
|
||||
period_t sleepTime = SLEEP_LONG; //period_t is an enum type defined in the LowPower library (LowPower.h)
|
||||
|
||||
void loop() {
|
||||
if (mainEventFlags & FLAG_INTERRUPT)
|
||||
{
|
||||
LowPower.powerDown(SLEEP_30MS, ADC_OFF, BOD_ON);
|
||||
mainEventFlags &= ~FLAG_INTERRUPT;
|
||||
if (!digitalRead(BUTTON_PIN)) {
|
||||
buttonPressed=true;
|
||||
}
|
||||
}
|
||||
|
||||
if (buttonPressed)
|
||||
{
|
||||
detachInterrupt(BUTTON_INT);
|
||||
DEBUGln("BUTTON PRESS!");
|
||||
unsigned long timestamp = millis();
|
||||
while (millis() - timestamp < BUTTON_HOLD_MS)
|
||||
{
|
||||
if (digitalRead(BUTTON_PIN))
|
||||
{
|
||||
buttonPressed = false;
|
||||
break;
|
||||
}
|
||||
DEBUG('.');SERIALFLUSH();
|
||||
LowPower.powerDown(SLEEP_30MS, ADC_OFF, BOD_ON);
|
||||
timestamp-=40;
|
||||
}
|
||||
|
||||
//if it's still pressed after BUTTON_HOLD_MS then enter red zone adjust mode
|
||||
if (buttonPressed)
|
||||
{
|
||||
DEBUG("STILL_PRESSED");SERIALFLUSH();
|
||||
handleRedZoneAdjust();
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG("ABORTED");SERIALFLUSH();
|
||||
}
|
||||
attachInterrupt(BUTTON_INT, buttonInterrupt, FALLING);
|
||||
}
|
||||
|
||||
|
||||
if (miniLoops>0)
|
||||
{
|
||||
miniLoops--;
|
||||
sleepTime = SLEEP_MINILOOP;
|
||||
//when looping fast we need to wake the sensor about 60-75ms before doing a reading (about 5 miniloops assuming 1 miniloop=15ms)
|
||||
//otherwise there will be a visible delay in the LED blinking
|
||||
if (miniLoops == 5) digitalWrite(SENSOR_EN, HIGH);
|
||||
}
|
||||
else if (loops > 0)
|
||||
{
|
||||
loops--;
|
||||
miniLoops=16; //16 mini loops translate
|
||||
sleepTime = SLEEP_MINILOOP;
|
||||
}
|
||||
else
|
||||
//sleep longer when no significant state changes happened
|
||||
//if battery is low, sleep even longer to try to squeeze more life
|
||||
sleepTime = (batteryVolts > BATT_LOW ? SLEEP_LONG : SLEEP_LOBATT);
|
||||
|
||||
if ((loops == 0 && miniLoops == 0) || ((miniLoops % skipBlinkingLoops) == 0))
|
||||
{
|
||||
DEBUG('*');
|
||||
handleLEDState();
|
||||
}
|
||||
|
||||
SERIALFLUSH(); //flush any characters in the serial buffer before sleeping otherwise they get lost or garbled
|
||||
LowPower.powerDown(sleepTime, ADC_OFF, BOD_OFF); //put microcontroller to sleep to save battery life
|
||||
if (miniLoops > 0) { DEBUG('.');return; } //as long as we still have miniloops we skip readings
|
||||
//only proceed to a reading every loop
|
||||
now = millis();
|
||||
distance = readDistance();
|
||||
|
||||
DEBUGln();
|
||||
DEBUG("Read: ");
|
||||
DEBUG(distance); // Convert ping time to distance in cm and print result (0 = outside set distance range)
|
||||
DEBUG("cm");
|
||||
DEBUG(" [");
|
||||
DEBUG(millis()-now);
|
||||
DEBUGln("]ms");
|
||||
|
||||
if (distance > MAX_DISTANCE || distance < MIN_DISTANCE)
|
||||
{
|
||||
DEBUGln("Out of range");
|
||||
loops=0;
|
||||
lastDistance = distance;
|
||||
return;
|
||||
}
|
||||
|
||||
if (distance < GRN_LIMIT_UPPER && distance > YLW_LIMIT_UPPER && abs(lastSigDistance-distance)>20)
|
||||
{
|
||||
if (distance < lastSigDistance)
|
||||
loops=12; //begin looping fast only when object is approaching
|
||||
lastSigDistance = distance;
|
||||
}
|
||||
else if (distance < YLW_LIMIT_UPPER && abs(lastSigDistance-distance)>5)
|
||||
{
|
||||
if (distance < lastSigDistance)
|
||||
loops=12; //begin looping fast only when object is approaching
|
||||
lastSigDistance = distance;
|
||||
}
|
||||
|
||||
//if the looping was started, determine the state we're in
|
||||
if (loops > 0)
|
||||
{
|
||||
if (distance >= YLW_LIMIT_UPPER) { state_LEDCOLOR = STATE_GRN; state_LED = STATE_SOLID; }
|
||||
else if (distance >= RED_LIMIT_UPPER) { state_LEDCOLOR=STATE_YLW; state_LED = STATE_SOLID; }
|
||||
else { state_LEDCOLOR=STATE_RED; state_LED = STATE_BLINK; }
|
||||
}
|
||||
else { state_LEDCOLOR=STATE_OFF; state_LED = STATE_SOLID; }
|
||||
|
||||
//adjust the blinking rate based on the distance to the object
|
||||
if (state_LEDCOLOR==STATE_RED)
|
||||
{
|
||||
if (distance > RED_LIMIT_UPPER-2)
|
||||
skipBlinkingLoops = 8;
|
||||
else if (distance > RED_LIMIT_UPPER-6)
|
||||
skipBlinkingLoops = 6;
|
||||
else if (lastDistance > RED_LIMIT_UPPER-10)
|
||||
skipBlinkingLoops = 4;
|
||||
else if (lastDistance > RED_LIMIT_UPPER-14)
|
||||
skipBlinkingLoops = 2;
|
||||
else
|
||||
{
|
||||
skipBlinkingLoops = 1;
|
||||
state_LED = STATE_SOLID;
|
||||
}
|
||||
}
|
||||
else skipBlinkingLoops = 1;
|
||||
lastDistance = distance; //remember the last reading
|
||||
checkBattery();
|
||||
if (batteryVolts < BATT_LOW)
|
||||
Blink(BLU);
|
||||
else Blink(MOTEINOLED);
|
||||
DEBUG("Batt: ");
|
||||
DEBUG(batteryVolts);
|
||||
DEBUGln("v");
|
||||
}
|
||||
|
||||
//reads the ultrasonic sensor, takes 3 samples by default
|
||||
float uS;
|
||||
float readDistance(byte samples)
|
||||
{
|
||||
uS = 0;
|
||||
if (loops == 0 && miniLoops == 0)
|
||||
{
|
||||
digitalWrite(SENSOR_EN, HIGH);
|
||||
//need about 60-75ms after power up before HC-SR04 will be usable, so just sleep in the meantime
|
||||
LowPower.powerDown(SLEEP_60MS, ADC_OFF, BOD_OFF);
|
||||
LowPower.powerDown(SLEEP_15MS, ADC_OFF, BOD_OFF);
|
||||
}
|
||||
|
||||
sensor.ping();
|
||||
for (byte i=0; i<samples; i++)
|
||||
{
|
||||
uS += sensor.ping(); // Send ping, get ping time in microseconds (uS).
|
||||
if (samples >1) delay(4); //need a short delay between samples
|
||||
}
|
||||
digitalWrite(SENSOR_EN, LOW);
|
||||
return (uS / samples) / US_ROUNDTRIP_CM;
|
||||
}
|
||||
|
||||
|
||||
////reads the ultrasonic sensor, takes 3 samples by default
|
||||
//float readDistance(byte samples)
|
||||
//{
|
||||
// long duration, distance;
|
||||
// digitalWrite(SENSOR_EN, HIGH);
|
||||
// delay(75);
|
||||
//
|
||||
// digitalWrite(TRIG, LOW); // Added this line
|
||||
// delayMicroseconds(2); // Added this line
|
||||
// digitalWrite(TRIG, HIGH);
|
||||
// delayMicroseconds(10); // Added this line
|
||||
// digitalWrite(TRIG, LOW);
|
||||
// pulseIn(ECHO, HIGH);
|
||||
//
|
||||
// digitalWrite(TRIG, LOW); // Added this line
|
||||
// delayMicroseconds(2); // Added this line
|
||||
// digitalWrite(TRIG, HIGH);
|
||||
// delayMicroseconds(10); // Added this line
|
||||
// digitalWrite(TRIG, LOW);
|
||||
// duration = pulseIn(ECHO, HIGH);
|
||||
// distance = (duration/2) / 29.1;
|
||||
// digitalWrite(SENSOR_EN, LOW);
|
||||
// return distance;
|
||||
//}
|
||||
|
||||
//handles the status and color of the LED depending what state we are in
|
||||
void handleLEDState()
|
||||
{
|
||||
switch(state_LEDCOLOR)
|
||||
{
|
||||
case STATE_OFF: LED_OFF; break;
|
||||
case STATE_GRN: LED_GRN; break;
|
||||
case STATE_YLW: LED_YLW; break;
|
||||
case STATE_RED:
|
||||
if (state_LED == STATE_BLINK)
|
||||
{
|
||||
if (state_LEDONOFF == HIGH)
|
||||
{
|
||||
LED_OFF;
|
||||
state_LEDONOFF = LOW;
|
||||
}
|
||||
else
|
||||
{
|
||||
LED_RED;
|
||||
state_LEDONOFF = HIGH;
|
||||
}
|
||||
}
|
||||
else LED_RED;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Blink(byte pin)
|
||||
{
|
||||
pinMode(pin, OUTPUT);
|
||||
digitalWrite(pin, HIGH);
|
||||
delay(2);
|
||||
digitalWrite(pin, LOW);
|
||||
}
|
||||
|
||||
byte cycleCount=BATT_CYCLES;
|
||||
void checkBattery(byte samples)
|
||||
{
|
||||
if (cycleCount++ == BATT_CYCLES) //only read battery every BATT_CYCLES sleep cycles
|
||||
{
|
||||
unsigned int readings=0;
|
||||
for (byte i=0; i<samples; i++) //take 10 samples, and average
|
||||
readings+=analogRead(BATT_MONITOR);
|
||||
batteryVolts = BATT_FORMULA((float)readings / samples);
|
||||
cycleCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
//enter red zone adjust mode
|
||||
void handleRedZoneAdjust()
|
||||
{
|
||||
DEBUGln("\nRED_ZONE_ADJUST");SERIALFLUSH();
|
||||
LED_OFF; //turn off all other LEDs
|
||||
|
||||
unsigned long startTimestamp=millis();
|
||||
float distance;
|
||||
int adjustTime = 5000;
|
||||
byte state = HIGH;
|
||||
|
||||
while (millis()-startTimestamp < adjustTime)
|
||||
{
|
||||
digitalWrite(BLU, state);
|
||||
state = state ? LOW : HIGH; //flip state for next loop
|
||||
distance = readDistance();
|
||||
if (distance > MAX_ADJUST_DISTANCE + RED_LIMIT_UPPER-redZoneAdjust)
|
||||
delay(300);
|
||||
else if (distance <= RED_LIMIT_UPPER-redZoneAdjust)
|
||||
state = HIGH; //keep LED on
|
||||
else
|
||||
delay (distance);
|
||||
|
||||
DEBUG(distance);DEBUGln("cm");SERIALFLUSH();
|
||||
Blink(MOTEINOLED);
|
||||
}
|
||||
|
||||
digitalWrite(BLU, LOW); //turn LED off
|
||||
if (distance > RED_LIMIT_UPPER-redZoneAdjust && distance <= MAX_ADJUST_DISTANCE + RED_LIMIT_UPPER-redZoneAdjust)
|
||||
{
|
||||
redZoneAdjust = distance - RED_LIMIT_UPPER - redZoneAdjust;
|
||||
DEBUG("New RED_ZONE_SHIFT = "); DEBUGln(redZoneAdjust);SERIALFLUSH();
|
||||
}
|
||||
}
|
||||
+512
@@ -0,0 +1,512 @@
|
||||
// Sample sketch for the SonarMote - Standalone parking assist with RGB LED indicator, piezo buzzer and OLED display
|
||||
// This example uses the NewPing library from https://code.google.com/p/arduino-new-ping/
|
||||
// but that could be replaced by raw reading of the sonar sensor as seen in other SonarMote examples
|
||||
// More info/photos at: http://lowpowerlab.com/sonar
|
||||
// Ultrasonic sensor (HC-SR04) connected to D6 (Trig), D7 (Echo), and power enabled through D5
|
||||
// This sketch sleeps the Moteino and sensor most of the time. It wakes up every few seconds to take
|
||||
// a distance reading. If it detects an approaching object (car) it increases the sampling rate
|
||||
// and starts lighting up the LED (from green to yellow to red to blinking red). Once there is no more
|
||||
// motion the LED is turned off and the cycle is back to a few seconds in between sensor reads.
|
||||
// Button is connected on D3. Holding the button for a few seconds enters the "red zone adjust" mode (RZA).
|
||||
// By default the red zone limit is at 25cm (LED turns RED below this and starts blinking faster and faster).
|
||||
// In RZA, readings are taken for 5 seconds. In this time you have the chance to set a new red zone limit.
|
||||
// Valid new red zone readings are between the RED__LIMIT_UPPER (default 25cm) and MAX_ADJUST_DISTANCE (cm).
|
||||
// In RZA mode the BLU Led blinks fast to indicate new red limit distance. It blinks slow if the readings are invalid
|
||||
// If desired this value could be saved to EEPROM to persist if unit is turned off
|
||||
// Make sure you adjust the settings in the configuration section below !!!
|
||||
|
||||
// **********************************************************************************
|
||||
// Copyright Felix Rusu, LowPowerLab.com
|
||||
// Library and code by Felix Rusu - felix@lowpowerlab.com
|
||||
// **********************************************************************************
|
||||
// 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 <LowPower.h> //get library from: https://github.com/lowpowerlab/lowpower
|
||||
//writeup here: http://www.rocketscream.com/blog/2011/07/04/lightweight-low-power-arduino-library/
|
||||
//#define BUZZER_ENABLE //uncomment this line if you have the BUZZER soldered and want the sketch to make sounds
|
||||
//#define OLED_ENABLE //uncomment this line if you have an OLED attached on the SonarMote and want to see the distance printed on it
|
||||
//the OLED drawing will cause a visible delay in the LED blinking, nothing much to do about it without complicating the sketch a lot more
|
||||
|
||||
#ifdef OLED_ENABLE
|
||||
#include "U8glib.h" //get library from: https://code.google.com/p/u8glib/
|
||||
#endif
|
||||
|
||||
#ifdef __AVR_ATmega1284P__
|
||||
#define LED 15 // Moteino MEGAs have LEDs on D15
|
||||
#else
|
||||
#define LED 9 // Moteinos have LEDs on D9
|
||||
#endif
|
||||
|
||||
#define TRIG 6 // digital pin wired to TRIG pin of ultrasonic sensor
|
||||
#define ECHO 7 // digital pin wired to ECHO pin of ultrasonic sensor
|
||||
#define SENSOR_EN 5 // digital pin that enables power to ultrasonic sensor
|
||||
#define RED A0 // pin connected to red LED
|
||||
#define GRN A1 // pin connected to green LED
|
||||
#define BLU A2 // pin connected to blue LED
|
||||
#define BUZZER 4
|
||||
#define BUTTON_INT 1 // user button on interrupt 1 (D3)
|
||||
#define BUTTON_PIN 3 // user button on interrupt 1 (D3)
|
||||
#define BUTTON_HOLD_MS 3000 // hold button this many ms before entering red zone adjust
|
||||
#define MAX_DISTANCE 220 // maximum valid distance
|
||||
#define MIN_DISTANCE 2 // minimum valid distance
|
||||
|
||||
#define GRN_LIMIT_UPPER 180+redZoneAdjust // upper limit distance for GREEN
|
||||
#define YLW_LIMIT_UPPER 40+redZoneAdjust // upper limit distance for YELLOW
|
||||
#define RED_LIMIT_UPPER 25+redZoneAdjust // upper limit distance for RED
|
||||
|
||||
#define MAX_ADJUST_DISTANCE (MAX_DISTANCE-GRN_LIMIT_UPPER) //this is the amount by which the RED_LIMIT_UPPER can by increased
|
||||
|
||||
//possible states of LED status
|
||||
#define STATE_SOLID 0
|
||||
#define STATE_BLINK 1
|
||||
|
||||
//possible states for LED color
|
||||
#define STATE_OFF 0
|
||||
#define STATE_GRN 1
|
||||
#define STATE_YLW 2
|
||||
#define STATE_RED 3
|
||||
|
||||
byte state_LED = STATE_SOLID;
|
||||
byte state_LEDCOLOR = STATE_OFF;
|
||||
byte state_LEDONOFF = LOW;
|
||||
byte redZoneAdjust = 0; //this is adjustable via the button (press button for a few seconds, then take a reading)
|
||||
|
||||
#define LED_RED {digitalWrite(RED,HIGH);digitalWrite(GRN,LOW);}
|
||||
#define LED_GRN {digitalWrite(RED,LOW);digitalWrite(GRN,HIGH);}
|
||||
#define LED_YLW {digitalWrite(RED,HIGH);digitalWrite(GRN,HIGH);}
|
||||
#define LED_OFF {digitalWrite(RED,LOW);digitalWrite(GRN,LOW);}
|
||||
|
||||
#define SERIAL_EN //uncomment if you want serial debugging output
|
||||
#ifdef SERIAL_EN
|
||||
#define SERIAL_BAUD 115200
|
||||
#define DEBUG(input) {Serial.print(input);}
|
||||
#define DEBUGln(input) {Serial.println(input);}
|
||||
#define SERIALFLUSH() {Serial.flush();}
|
||||
#else
|
||||
#define DEBUG(input);
|
||||
#define DEBUGln(input);
|
||||
#define SERIALFLUSH();
|
||||
#endif
|
||||
|
||||
#define SLEEP_MINILOOP SLEEP_15MS
|
||||
#define SLEEP_LOOP SLEEP_250MS
|
||||
#define SLEEP_LONG SLEEP_2S
|
||||
#define SLEEP_LOBATT SLEEP_4S
|
||||
#define SLEEP_HIBERNATE SLEEP_8S
|
||||
|
||||
#define BATT_MONITOR A7 // Sense VBAT_COND signal (when powered externally should read ~3.25v/3.3v (1000-1023), when external power is cutoff it should start reading around 2.85v/3.3v * 1023 ~= 883 (ratio given by 10k+4.7K divider from VBAT_COND = 1.47 multiplier)
|
||||
#define BATT_CYCLES 120 // read and report battery voltage every this many sleep cycles (ex 30cycles * 8sec sleep = 240sec/4min). For 450 cyclesyou would get ~1 hour intervals
|
||||
#define BATT_FORMULA(reading) reading * 0.00322 * 1.475 // >>> fine tune this parameter to match your voltage when fully charged
|
||||
#define BATT_LOW 3.35
|
||||
|
||||
#ifdef OLED_ENABLE
|
||||
U8GLIB_SSD1306_128X64 OLED(U8G_I2C_OPT_NONE); // I2C / TWI SSD1306 OLED 128x64
|
||||
#endif
|
||||
void checkBattery(byte samples=10); //take 10 samples by default
|
||||
float batteryVolts = 5;
|
||||
char buff[50];
|
||||
|
||||
void setup() {
|
||||
#ifdef SERIAL_EN
|
||||
Serial.begin(SERIAL_BAUD); // Open serial monitor at 115200 baud to see ping results.
|
||||
#endif
|
||||
pinMode(TRIG, OUTPUT);
|
||||
pinMode(ECHO, INPUT);
|
||||
pinMode(RED, OUTPUT);
|
||||
pinMode(GRN, OUTPUT);
|
||||
pinMode(BLU, OUTPUT);
|
||||
pinMode(SENSOR_EN, OUTPUT);
|
||||
pinMode(BUZZER, OUTPUT);
|
||||
digitalWrite(SENSOR_EN, LOW);
|
||||
pinMode(BUTTON_PIN, INPUT_PULLUP);
|
||||
attachInterrupt(BUTTON_INT, buttonInterrupt, FALLING);
|
||||
|
||||
#ifdef OLED_ENABLE
|
||||
OLED.setRot180(); //flip screen
|
||||
// assign default color value
|
||||
if (OLED.getMode() == U8G_MODE_R3G3B2 )
|
||||
OLED.setColorIndex(255); // white
|
||||
else if (OLED.getMode() == U8G_MODE_GRAY2BIT)
|
||||
OLED.setColorIndex(3); // max intensity
|
||||
else if (OLED.getMode() == U8G_MODE_BW)
|
||||
OLED.setColorIndex(1); // pixel on
|
||||
else if (OLED.getMode() == U8G_MODE_HICOLOR)
|
||||
OLED.setHiColorByRGB(255,255,255);
|
||||
OLED.begin();
|
||||
|
||||
OLED.firstPage();
|
||||
OLED.setFont(u8g_font_unifont);
|
||||
do {
|
||||
OLED.drawStr(0, 10, "SonarMote");
|
||||
} while(OLED.nextPage());
|
||||
#endif
|
||||
|
||||
#ifdef BUZZER_ENABLE
|
||||
buzzer(50,2,100);
|
||||
#endif
|
||||
|
||||
readDistance(); //first reading seems to always be low
|
||||
}
|
||||
|
||||
#define FLAG_INTERRUPT 0x01
|
||||
volatile int mainEventFlags = 0;
|
||||
boolean buttonPressed = false;
|
||||
void buttonInterrupt()
|
||||
{
|
||||
mainEventFlags |= FLAG_INTERRUPT;
|
||||
}
|
||||
|
||||
long distance=0;
|
||||
long lastDistance=0;
|
||||
long lastSigDistance=0;
|
||||
byte loops=0;
|
||||
byte miniLoops=0;
|
||||
byte skipBlinkingLoops=5;
|
||||
unsigned long now=0;
|
||||
period_t sleepTime = SLEEP_LONG; //period_t is an enum type defined in the LowPower library (LowPower.h)
|
||||
|
||||
void loop() {
|
||||
if (mainEventFlags & FLAG_INTERRUPT)
|
||||
{
|
||||
LowPower.powerDown(SLEEP_30MS, ADC_OFF, BOD_ON);
|
||||
mainEventFlags &= ~FLAG_INTERRUPT;
|
||||
if (!digitalRead(BUTTON_PIN)) {
|
||||
buttonPressed=true;
|
||||
}
|
||||
}
|
||||
|
||||
if (buttonPressed)
|
||||
{
|
||||
detachInterrupt(BUTTON_INT);
|
||||
DEBUGln("BUTTON PRESS!");
|
||||
unsigned long timestamp = millis();
|
||||
while (millis() - timestamp < BUTTON_HOLD_MS)
|
||||
{
|
||||
if (digitalRead(BUTTON_PIN))
|
||||
{
|
||||
buttonPressed = false;
|
||||
break;
|
||||
}
|
||||
DEBUG('.');SERIALFLUSH();
|
||||
LowPower.powerDown(SLEEP_30MS, ADC_OFF, BOD_ON);
|
||||
timestamp-=40;
|
||||
}
|
||||
|
||||
//if it's still pressed after BUTTON_HOLD_MS then enter red zone adjust mode
|
||||
if (buttonPressed)
|
||||
{
|
||||
DEBUG("STILL_PRESSED");SERIALFLUSH();
|
||||
handleRedZoneAdjust();
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG("ABORTED");SERIALFLUSH();
|
||||
}
|
||||
attachInterrupt(BUTTON_INT, buttonInterrupt, FALLING);
|
||||
}
|
||||
|
||||
if (miniLoops>0)
|
||||
{
|
||||
miniLoops--; //miniloops starts at
|
||||
sleepTime = SLEEP_MINILOOP;
|
||||
//EARLY SENSOR WAKEUP
|
||||
//When looping fast we need to wake the sensor about 60-75ms before doing a reading (about 5 miniloops assuming 1 miniloop=15ms)
|
||||
//otherwise there will be a visible delay in the LED blinking
|
||||
if (miniLoops == 6) digitalWrite(SENSOR_EN, HIGH);
|
||||
else if (miniLoops == 1) sacrificialPing(); //need 5 miniloops (75ms) between wakeup and dummy reading and another 15ms (1 miniloop) to real reading
|
||||
//reading will happen when miniLoops==0
|
||||
}
|
||||
else if (loops > 0)
|
||||
{
|
||||
loops--;
|
||||
miniLoops=16; //16 "miniloops" form 1 "loop" (~240ms)
|
||||
sleepTime = SLEEP_MINILOOP;
|
||||
}
|
||||
else
|
||||
//sleep longer when no significant state changes happened
|
||||
//if battery is low, sleep even longer to try to squeeze more life
|
||||
sleepTime = (batteryVolts > BATT_LOW ? SLEEP_LONG : SLEEP_LOBATT);
|
||||
|
||||
if ((loops == 0 && miniLoops == 0) || ((miniLoops % skipBlinkingLoops) == 0))
|
||||
{
|
||||
DEBUG('*');
|
||||
handleLEDState();
|
||||
}
|
||||
|
||||
SERIALFLUSH(); //flush any characters in the serial buffer before sleeping otherwise they get lost or garbled
|
||||
LowPower.powerDown(sleepTime, ADC_OFF, BOD_OFF); //put microcontroller to sleep to save battery life
|
||||
if (miniLoops > 0) { DEBUG('.');return; } //as long as we still have miniloops we skip readings
|
||||
//only proceed to a reading every loop
|
||||
now = millis();
|
||||
distance = readDistance();
|
||||
byte d = millis()-now;
|
||||
#ifdef OLED_ENABLE
|
||||
draw(distance);
|
||||
#endif
|
||||
|
||||
DEBUG("Read: ");
|
||||
DEBUG(distance); // Convert ping time to distance in cm and print result (0 = outside set distance range)
|
||||
DEBUG("cm");
|
||||
DEBUG(" [");
|
||||
DEBUG(d);
|
||||
DEBUGln("]ms");
|
||||
|
||||
if (distance > MAX_DISTANCE || distance < MIN_DISTANCE)
|
||||
{
|
||||
DEBUGln("Out of range");
|
||||
loops=0;
|
||||
lastDistance = distance;
|
||||
return;
|
||||
}
|
||||
|
||||
if (distance < GRN_LIMIT_UPPER && distance > YLW_LIMIT_UPPER && abs(lastSigDistance-distance)>20)
|
||||
{
|
||||
if (distance < lastSigDistance)
|
||||
loops=12; //begin looping fast only when object is approaching
|
||||
lastSigDistance = distance;
|
||||
}
|
||||
else if (distance < YLW_LIMIT_UPPER && abs(lastSigDistance-distance)>5)
|
||||
{
|
||||
if (distance < lastSigDistance)
|
||||
loops=12; //begin looping fast only when object is approaching
|
||||
lastSigDistance = distance;
|
||||
}
|
||||
|
||||
//if the looping was started, determine the state we're in
|
||||
if (loops > 0)
|
||||
{
|
||||
if (distance >= YLW_LIMIT_UPPER) { state_LEDCOLOR = STATE_GRN; state_LED = STATE_SOLID; }
|
||||
else if (distance >= RED_LIMIT_UPPER) { state_LEDCOLOR=STATE_YLW; state_LED = STATE_SOLID; }
|
||||
else { state_LEDCOLOR=STATE_RED; state_LED = STATE_BLINK; }
|
||||
}
|
||||
else { state_LEDCOLOR=STATE_OFF; state_LED = STATE_SOLID; }
|
||||
|
||||
//adjust the blinking rate based on the distance to the object
|
||||
if (state_LEDCOLOR==STATE_RED)
|
||||
{
|
||||
if (distance > RED_LIMIT_UPPER-2)
|
||||
skipBlinkingLoops = 8;
|
||||
else if (distance > RED_LIMIT_UPPER-6)
|
||||
skipBlinkingLoops = 6;
|
||||
else if (lastDistance > RED_LIMIT_UPPER-10)
|
||||
skipBlinkingLoops = 4;
|
||||
else if (lastDistance > RED_LIMIT_UPPER-14)
|
||||
skipBlinkingLoops = 2;
|
||||
else
|
||||
{
|
||||
skipBlinkingLoops = 1;
|
||||
state_LED = STATE_SOLID;
|
||||
}
|
||||
}
|
||||
else skipBlinkingLoops = 1;
|
||||
lastDistance = distance; //remember the last reading
|
||||
checkBattery();
|
||||
if (batteryVolts < BATT_LOW)
|
||||
Blink(BLU);
|
||||
else Blink(LED);
|
||||
DEBUG("Batt: ");
|
||||
DEBUG(batteryVolts);
|
||||
DEBUGln("v");
|
||||
}
|
||||
|
||||
float readDistance()
|
||||
{
|
||||
//To save battery we need to sleep the HC-SR04 sonar sensor in between readings.
|
||||
//However to get valid readings it requires special waking up and delay
|
||||
//It needs to be powered up for ~75ms, then a dummy reading has to be made which is typically bogus
|
||||
//Then another 15ms needs to pass before doing the real reading
|
||||
//Because the main loop sleeps between readings and we want to avoid visible delays in the LED blinking
|
||||
// we need to wake up the sensor in the main loop, look for "EARLY SENSOR WAKEUP".
|
||||
// When looping fast in the main loop (aka minilooping, to allow fast LED blinking)
|
||||
// each "miniloop" is about 15ms of sleep time. So we should wake up the sensor 5 miniloops before we do the reading
|
||||
// and do the sacrificial dummy reading 1 miniloop before the real reading. See the "EARLY SENSOR WAKEUP" code in the main loop.
|
||||
|
||||
//when not looping fast in the main loop, just do the special sensor wakeup here
|
||||
//first enable sensor power and sleep MCU while sensor settles (needs)75ms
|
||||
if (loops == 0 && miniLoops == 0)
|
||||
{
|
||||
digitalWrite(SENSOR_EN, HIGH);
|
||||
//need about 60-75ms after power up before HC-SR04 will be usable, so just sleep in the meantime
|
||||
LowPower.powerDown(SLEEP_60MS, ADC_OFF, BOD_OFF);
|
||||
LowPower.powerDown(SLEEP_15MS, ADC_OFF, BOD_OFF);
|
||||
// do a dummy reading first and wait another 15ms for the real reading
|
||||
sacrificialPing();
|
||||
LowPower.powerDown(SLEEP_15MS, ADC_OFF, BOD_OFF);
|
||||
}
|
||||
|
||||
// Now do the real reading
|
||||
// The PING))) is triggered by a HIGH pulse of 2 or more microseconds.
|
||||
// Give a short LOW pulse beforehand to ensure a clean HIGH pulse:
|
||||
digitalWrite(TRIG, LOW);
|
||||
delayMicroseconds(2);
|
||||
digitalWrite(TRIG, HIGH);
|
||||
delayMicroseconds(5);
|
||||
digitalWrite(TRIG, LOW);
|
||||
// The same pin is used to read the signal from the PING))): a HIGH
|
||||
// pulse whose duration is the time (in microseconds) from the sending
|
||||
// of the ping to the reception of its echo off of an object.
|
||||
long duration = pulseIn(ECHO, HIGH);
|
||||
digitalWrite(SENSOR_EN, LOW);
|
||||
return microsecondsToCentimeters(duration);
|
||||
}
|
||||
|
||||
void sacrificialPing()
|
||||
{
|
||||
digitalWrite(TRIG, LOW);
|
||||
delayMicroseconds(2);
|
||||
digitalWrite(TRIG, HIGH);
|
||||
delayMicroseconds(5);
|
||||
digitalWrite(TRIG, LOW);
|
||||
pulseIn(ECHO, HIGH);
|
||||
}
|
||||
|
||||
//handles the status and color of the LED depending what state we are in
|
||||
void handleLEDState()
|
||||
{
|
||||
switch(state_LEDCOLOR)
|
||||
{
|
||||
case STATE_OFF: LED_OFF; break;
|
||||
case STATE_GRN: LED_GRN; break;
|
||||
case STATE_YLW: LED_YLW; break;
|
||||
case STATE_RED:
|
||||
if (state_LED == STATE_BLINK)
|
||||
{
|
||||
if (state_LEDONOFF == HIGH)
|
||||
{
|
||||
LED_OFF;
|
||||
state_LEDONOFF = LOW;
|
||||
}
|
||||
else
|
||||
{
|
||||
LED_RED;
|
||||
state_LEDONOFF = HIGH;
|
||||
#ifdef BUZZER_ENABLE
|
||||
buzzer(skipBlinkingLoops,0,0);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else LED_RED;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Blink(byte pin)
|
||||
{
|
||||
pinMode(pin, OUTPUT);
|
||||
digitalWrite(pin, HIGH);
|
||||
delay(2);
|
||||
digitalWrite(pin, LOW);
|
||||
}
|
||||
|
||||
byte cycleCount=BATT_CYCLES;
|
||||
void checkBattery(byte samples)
|
||||
{
|
||||
if (cycleCount++ == BATT_CYCLES) //only read battery every BATT_CYCLES sleep cycles
|
||||
{
|
||||
unsigned int readings=0;
|
||||
for (byte i=0; i<samples; i++) //take 10 samples, and average
|
||||
readings+=analogRead(BATT_MONITOR);
|
||||
batteryVolts = BATT_FORMULA((float)readings / samples);
|
||||
cycleCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
//enter red zone adjust mode
|
||||
void handleRedZoneAdjust()
|
||||
{
|
||||
DEBUGln("\nRED_ZONE_ADJUST");SERIALFLUSH();
|
||||
LED_OFF; //turn off all other LEDs
|
||||
|
||||
unsigned long startTimestamp=millis();
|
||||
float distance;
|
||||
int adjustTime = 5000;
|
||||
byte state = HIGH;
|
||||
|
||||
while (millis()-startTimestamp < adjustTime)
|
||||
{
|
||||
digitalWrite(BLU, state);
|
||||
state = state ? LOW : HIGH; //flip state for next loop
|
||||
distance = readDistance();
|
||||
if (distance > MAX_ADJUST_DISTANCE + RED_LIMIT_UPPER-redZoneAdjust)
|
||||
delay(300);
|
||||
else if (distance <= RED_LIMIT_UPPER-redZoneAdjust)
|
||||
state = HIGH; //keep LED on
|
||||
else
|
||||
delay (distance);
|
||||
|
||||
DEBUG(distance);DEBUGln("cm");SERIALFLUSH();
|
||||
Blink(LED);
|
||||
}
|
||||
|
||||
digitalWrite(BLU, LOW); //turn LED off
|
||||
if (distance > RED_LIMIT_UPPER-redZoneAdjust && distance <= MAX_ADJUST_DISTANCE + RED_LIMIT_UPPER-redZoneAdjust)
|
||||
{
|
||||
redZoneAdjust = distance - RED_LIMIT_UPPER - redZoneAdjust;
|
||||
DEBUG("New RED_ZONE_SHIFT = "); DEBUGln(redZoneAdjust);SERIALFLUSH();
|
||||
}
|
||||
}
|
||||
|
||||
float microsecondsToInches(long microseconds)
|
||||
{
|
||||
// According to Parallax's datasheet for the PING))), there are
|
||||
// 73.746 microseconds per inch (i.e. sound travels at 1130 feet per
|
||||
// second). This gives the distance travelled by the ping, outbound
|
||||
// and return, so we divide by 2 to get the distance of the obstacle.
|
||||
// See: http://www.parallax.com/dl/docs/prod/acc/28015-PING-v1.3.pdf
|
||||
return microseconds / 74.0 / 2.0f;
|
||||
}
|
||||
|
||||
float microsecondsToCentimeters(long microseconds)
|
||||
{
|
||||
// The speed of sound is 340 m/s or 29 microseconds per centimeter.
|
||||
// The ping travels out and back, so to find the distance of the
|
||||
// object we take half of the distance travelled.
|
||||
return (float)microseconds / 29.0f / 2.0f;
|
||||
}
|
||||
|
||||
#ifdef OLED_ENABLE
|
||||
void draw(byte distance) {
|
||||
OLED.firstPage();
|
||||
OLED.setFont(u8g_font_unifont);
|
||||
sprintf(buff, "Reading: %dcm", distance);
|
||||
|
||||
do {
|
||||
OLED.drawStr(0, 10, buff);
|
||||
} while(OLED.nextPage());
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef BUZZER_ENABLE
|
||||
void buzzer(byte soundTime, byte repeats, byte repeatsDelay)
|
||||
{
|
||||
for (byte i=0;i<=repeats;i++)
|
||||
{
|
||||
tone(BUZZER, 4500); //4500hz makes a nice audible sound from a 3.3v Moteino digital pin
|
||||
delay(soundTime);
|
||||
noTone(BUZZER);
|
||||
if (repeats>0) delay(repeatsDelay);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,142 @@
|
||||
#include <RFM69.h>
|
||||
#include <SPI.h>
|
||||
#include <SPIFlash.h>
|
||||
|
||||
#define NODEID 1
|
||||
#define NETWORKID 100
|
||||
#define FREQUENCY RF69_433MHZ //Match this with the version of your Moteino! (others: RF69_433MHZ, RF69_868MHZ)
|
||||
#define KEY "sampleEncryptKey" //has to be same 16 characters/bytes on all nodes, not more not less!
|
||||
#define LED 9
|
||||
#define SERIAL_BAUD 115200
|
||||
#define ACK_TIME 30 // # of ms to wait for an ack
|
||||
#define IS_RFM69HW_HCW //uncomment only for RFM69HW/HCW! Leave out if you have RFM69W/CW!
|
||||
|
||||
RFM69 radio;
|
||||
SPIFlash flash(8, 0xEF30); //EF40 for 16mbit windbond chip
|
||||
bool promiscuousMode = false; //set to 'true' to sniff all packets on the same network
|
||||
|
||||
typedef struct {
|
||||
int nodeId; //store this nodeId
|
||||
unsigned long uptime; //uptime in ms
|
||||
float temp; //temperature maybe?
|
||||
} Payload;
|
||||
Payload theData;
|
||||
|
||||
void setup() {
|
||||
Serial.begin(SERIAL_BAUD);
|
||||
delay(10);
|
||||
radio.initialize(FREQUENCY,NODEID,NETWORKID);
|
||||
#ifdef IS_RFM69HW_HCW
|
||||
radio.setHighPower(); //must include this only for RFM69HW/HCW!
|
||||
#endif
|
||||
radio.encrypt(KEY);
|
||||
radio.promiscuous(promiscuousMode);
|
||||
char buff[50];
|
||||
sprintf(buff, "\nListening at %d Mhz...", FREQUENCY==RF69_433MHZ ? 433 : FREQUENCY==RF69_868MHZ ? 868 : 915);
|
||||
Serial.println(buff);
|
||||
if (flash.initialize())
|
||||
Serial.println("SPI Flash Init OK!");
|
||||
else
|
||||
Serial.println("SPI Flash Init FAIL! (is chip present?)");
|
||||
}
|
||||
|
||||
byte ackCount=0;
|
||||
void loop() {
|
||||
//process any serial input
|
||||
if (Serial.available() > 0)
|
||||
{
|
||||
char input = Serial.read();
|
||||
if (input == 'r') //d=dump all register values
|
||||
radio.readAllRegs();
|
||||
if (input == 'E') //E=enable encryption
|
||||
radio.encrypt(KEY);
|
||||
if (input == 'e') //e=disable encryption
|
||||
radio.encrypt(null);
|
||||
if (input == 'p')
|
||||
{
|
||||
promiscuousMode = !promiscuousMode;
|
||||
radio.promiscuous(promiscuousMode);
|
||||
Serial.print("Promiscuous mode ");Serial.println(promiscuousMode ? "on" : "off");
|
||||
}
|
||||
|
||||
if (input == 'd') //d=dump flash area
|
||||
{
|
||||
Serial.println("Flash content:");
|
||||
int counter = 0;
|
||||
|
||||
while(counter<=256){
|
||||
Serial.print(flash.readByte(counter++), HEX);
|
||||
Serial.print('.');
|
||||
}
|
||||
while(flash.busy());
|
||||
Serial.println();
|
||||
}
|
||||
if (input == 'D')
|
||||
{
|
||||
Serial.print("Deleting Flash chip content... ");
|
||||
flash.chipErase();
|
||||
while(flash.busy());
|
||||
Serial.println("DONE");
|
||||
}
|
||||
if (input == 'i')
|
||||
{
|
||||
Serial.print("DeviceID: ");
|
||||
word jedecid = flash.readDeviceId();
|
||||
Serial.println(jedecid, HEX);
|
||||
}
|
||||
}
|
||||
|
||||
if (radio.receiveDone())
|
||||
{
|
||||
Serial.print('[');Serial.print(radio.SENDERID, DEC);Serial.print("] ");
|
||||
Serial.print(" [RX_RSSI:");Serial.print(radio.readRSSI());Serial.print("]");
|
||||
if (promiscuousMode)
|
||||
{
|
||||
Serial.print("to [");Serial.print(radio.TARGETID, DEC);Serial.print("] ");
|
||||
}
|
||||
|
||||
if (radio.DATALEN != sizeof(Payload))
|
||||
Serial.print("Invalid payload received, not matching Payload struct!");
|
||||
else
|
||||
{
|
||||
theData = *(Payload*)radio.DATA; //assume radio.DATA actually contains our struct and not something else
|
||||
Serial.print(" nodeId=");
|
||||
Serial.print(theData.nodeId);
|
||||
Serial.print(" uptime=");
|
||||
Serial.print(theData.uptime);
|
||||
Serial.print(" temp=");
|
||||
Serial.print(theData.temp);
|
||||
}
|
||||
|
||||
if (radio.ACKRequested())
|
||||
{
|
||||
byte theNodeID = radio.SENDERID;
|
||||
radio.sendACK();
|
||||
Serial.print(" - ACK sent.");
|
||||
|
||||
// When a node requests an ACK, respond to the ACK
|
||||
// and also send a packet requesting an ACK (every 3rd one only)
|
||||
// This way both TX/RX NODE functions are tested on 1 end at the GATEWAY
|
||||
if (ackCount++%3==0)
|
||||
{
|
||||
Serial.print(" Pinging node ");
|
||||
Serial.print(theNodeID);
|
||||
Serial.print(" - ACK...");
|
||||
delay(3); //need this when sending right after reception .. ?
|
||||
if (radio.sendWithRetry(theNodeID, "ACK TEST", 8, 0)) // 0 = only 1 attempt, no retries
|
||||
Serial.print("ok!");
|
||||
else Serial.print("nothing");
|
||||
}
|
||||
}
|
||||
Serial.println();
|
||||
Blink(LED,3);
|
||||
}
|
||||
}
|
||||
|
||||
void Blink(byte PIN, int DELAY_MS)
|
||||
{
|
||||
pinMode(PIN, OUTPUT);
|
||||
digitalWrite(PIN,HIGH);
|
||||
delay(DELAY_MS);
|
||||
digitalWrite(PIN,LOW);
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
#include <RFM69.h>
|
||||
#include <SPI.h>
|
||||
#include <SPIFlash.h>
|
||||
|
||||
#define NODEID 99
|
||||
#define NETWORKID 100
|
||||
#define GATEWAYID 1
|
||||
#define FREQUENCY RF69_433MHZ //Match this with the version of your Moteino! (others: RF69_433MHZ, RF69_868MHZ)
|
||||
#define KEY "sampleEncryptKey" //has to be same 16 characters/bytes on all nodes, not more not less!
|
||||
#define LED 9
|
||||
#define SERIAL_BAUD 115200
|
||||
#define ACK_TIME 30 // # of ms to wait for an ack
|
||||
#define IS_RFM69HW_HCW //uncomment only for RFM69HW/HCW! Leave out if you have RFM69W/CW!
|
||||
|
||||
int TRANSMITPERIOD = 300; //transmit a packet to gateway so often (in ms)
|
||||
byte sendSize=0;
|
||||
boolean requestACK = false;
|
||||
SPIFlash flash(8, 0xEF30); //EF40 for 16mbit windbond chip
|
||||
RFM69 radio;
|
||||
|
||||
typedef struct {
|
||||
int nodeId; //store this nodeId
|
||||
unsigned long uptime; //uptime in ms
|
||||
float temp; //temperature maybe?
|
||||
} Payload;
|
||||
Payload theData;
|
||||
|
||||
void setup() {
|
||||
Serial.begin(SERIAL_BAUD);
|
||||
radio.initialize(FREQUENCY,NODEID,NETWORKID);
|
||||
#ifdef IS_RFM69HW_HCW
|
||||
radio.setHighPower(); //must include this only for RFM69HW/HCW!
|
||||
#endif
|
||||
radio.encrypt(KEY);
|
||||
char buff[50];
|
||||
sprintf(buff, "\nTransmitting at %d Mhz...", FREQUENCY==RF69_433MHZ ? 433 : FREQUENCY==RF69_868MHZ ? 868 : 915);
|
||||
Serial.println(buff);
|
||||
|
||||
if (flash.initialize())
|
||||
Serial.println("SPI Flash Init OK!");
|
||||
else
|
||||
Serial.println("SPI Flash Init FAIL! (is chip present?)");
|
||||
}
|
||||
|
||||
long lastPeriod = -1;
|
||||
void loop() {
|
||||
//process any serial input
|
||||
if (Serial.available() > 0)
|
||||
{
|
||||
char input = Serial.read();
|
||||
if (input >= 48 && input <= 57) //[0,9]
|
||||
{
|
||||
TRANSMITPERIOD = 100 * (input-48);
|
||||
if (TRANSMITPERIOD == 0) TRANSMITPERIOD = 1000;
|
||||
Serial.print("\nChanging delay to ");
|
||||
Serial.print(TRANSMITPERIOD);
|
||||
Serial.println("ms\n");
|
||||
}
|
||||
|
||||
if (input == 'r') //d=dump register values
|
||||
radio.readAllRegs();
|
||||
//if (input == 'E') //E=enable encryption
|
||||
// radio.encrypt(KEY);
|
||||
//if (input == 'e') //e=disable encryption
|
||||
// radio.encrypt(null);
|
||||
|
||||
if (input == 'd') //d=dump flash area
|
||||
{
|
||||
Serial.println("Flash content:");
|
||||
int counter = 0;
|
||||
|
||||
while(counter<=256){
|
||||
Serial.print(flash.readByte(counter++), HEX);
|
||||
Serial.print('.');
|
||||
}
|
||||
while(flash.busy());
|
||||
Serial.println();
|
||||
}
|
||||
if (input == 'e')
|
||||
{
|
||||
Serial.print("Erasing Flash chip ... ");
|
||||
flash.chipErase();
|
||||
while(flash.busy());
|
||||
Serial.println("DONE");
|
||||
}
|
||||
if (input == 'i')
|
||||
{
|
||||
Serial.print("DeviceID: ");
|
||||
word jedecid = flash.readDeviceId();
|
||||
Serial.println(jedecid, HEX);
|
||||
}
|
||||
}
|
||||
|
||||
//check for any received packets
|
||||
if (radio.receiveDone())
|
||||
{
|
||||
Serial.print('[');Serial.print(radio.SENDERID, DEC);Serial.print("] ");
|
||||
for (byte i = 0; i < radio.DATALEN; i++)
|
||||
Serial.print((char)radio.DATA[i]);
|
||||
Serial.print(" [RX_RSSI:");Serial.print(radio.readRSSI());Serial.print("]");
|
||||
|
||||
if (radio.ACKRequested())
|
||||
{
|
||||
radio.sendACK();
|
||||
Serial.print(" - ACK sent");
|
||||
delay(10);
|
||||
}
|
||||
Blink(LED,5);
|
||||
Serial.println();
|
||||
}
|
||||
|
||||
int currPeriod = millis()/TRANSMITPERIOD;
|
||||
if (currPeriod != lastPeriod)
|
||||
{
|
||||
//fill in the struct with new values
|
||||
theData.nodeId = NODEID;
|
||||
theData.uptime = millis();
|
||||
theData.temp = 91.23; //it's hot!
|
||||
|
||||
Serial.print("Sending struct (");
|
||||
Serial.print(sizeof(theData));
|
||||
Serial.print(" bytes) ... ");
|
||||
if (radio.sendWithRetry(GATEWAYID, (const void*)(&theData), sizeof(theData)))
|
||||
Serial.print(" ok!");
|
||||
else Serial.print(" nothing...");
|
||||
Serial.println();
|
||||
Blink(LED,3);
|
||||
lastPeriod=currPeriod;
|
||||
}
|
||||
}
|
||||
|
||||
void Blink(byte PIN, int DELAY_MS)
|
||||
{
|
||||
pinMode(PIN, OUTPUT);
|
||||
digitalWrite(PIN,HIGH);
|
||||
delay(DELAY_MS);
|
||||
digitalWrite(PIN,LOW);
|
||||
}
|
||||
@@ -0,0 +1,692 @@
|
||||
// *************************************************************************************************************
|
||||
// SwitchMote sample sketch
|
||||
// *************************************************************************************************************
|
||||
// Handles the single 5A relay SwitchMote, as well as the dual 10A relay SwitchMote2x10A
|
||||
// https://lowpowerlab.com/switchmote
|
||||
// SwitchMote is a highly integrated wireless AC switch controller based on Moteino, the wirelessly programmable Arduino
|
||||
// *************************************************************************************************************
|
||||
// This sketch will provide the essential features of SwitchMote:
|
||||
// - wireless programming
|
||||
// - accept ON/OFF commands from a Moteino gateway (format is BTNx:y commands where x={0,1,2}, y={0,1})
|
||||
// - control the relay(s) to turn the AC load ON/OFF
|
||||
// - control up to 6 LEDs and 3 buttons on front panel
|
||||
// - SYNC feature allows out of box synchronization with other SwitchMotes
|
||||
// ie - when a button is pressed on this SwitchMote it can trigger the press of a button on another SwitchMote
|
||||
// so you can use a SwitchMote button to control a light/set of lights from remote locations
|
||||
// this sketch allows up to 5 SYNCs but could be extended
|
||||
// This sketch may be extended to include integration with other LowPowerLab automation products, for instance to
|
||||
// control the GarageMote from a button on the SwitchMote, etc.
|
||||
// *************************************************************************************************************
|
||||
// Copyright Felix Rusu 2016, http://www.LowPowerLab.com/contact
|
||||
// **********************************************************************************
|
||||
// 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.
|
||||
//
|
||||
// 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 <EEPROMex.h> //get it here: http://playground.arduino.cc/Code/EEPROMex
|
||||
#include <RFM69.h> //get it here: http://github.com/lowpowerlab/rfm69
|
||||
#include <RFM69_ATC.h> //get it here: https://github.com/lowpowerlab/rfm69
|
||||
#include <RFM69_OTA.h> //get it here: https://github.com/LowPowerLab/rfm69
|
||||
#include <SPIFlash.h> //get it here: http://github.com/lowpowerlab/spiflash
|
||||
#include <SPI.h> //comes with Arduino
|
||||
// **********************************************************************************
|
||||
//Auto Transmission Control - dials down transmit power to save battery (-100 is the noise floor, -90 is still pretty good)
|
||||
//For indoor nodes that are pretty static and at pretty stable temperatures (like a MotionMote) -90dBm is quite safe
|
||||
//For more variable nodes that can expect to move or experience larger temp drifts a lower margin like -70 to -80 would probably be better
|
||||
//Always test your ATC mote in the edge cases in your own environment to ensure ATC will perform as you expect
|
||||
#define ENABLE_ATC //comment out this line to disable AUTO TRANSMISSION CONTROL
|
||||
#define ATC_RSSI -75
|
||||
// **********************************************************************************
|
||||
#define GATEWAYID 1 //assumed 1 in general
|
||||
#define LED_RM 15 //digital pin for MIDDLE RED LED
|
||||
#define LED_GM 18 //digital pin for MIDDLE GREEN LED
|
||||
#define LED_RT 16 //digital pin for TOP RED LED
|
||||
#define LED_GT 19 //digital pin for TOP GREEN LED
|
||||
#define LED_RB 14 //digital pin for BOTTOM RED LED
|
||||
#define LED_GB 17 //digital pin for BOTTOM GREEN LE
|
||||
|
||||
#define RELAY1 7 //digital pin connected to MAIN relay
|
||||
#define RELAY2 3 //digital pin connected to secondary relay (SwitchMote 2x10A only)
|
||||
#define RELAY1_INDEX 1 //index in btn[] array which is associated with the MAIN relay
|
||||
#define RELAY2_INDEX 0 //index in btn[] array which is associated with the secondary relay (SwitchMote 2x10A only)
|
||||
|
||||
#define BTNCOUNT 3 //1 or 3 (2 also possible)
|
||||
#define BTNM 5 //digital pin of middle button
|
||||
#define BTNT 6 //digital pin of top button
|
||||
#define BTNB 4 //digital pin of bottom button
|
||||
|
||||
#define BUTTON_BOUNCE_MS 200 //timespan before another button change can occur
|
||||
#define SYNC_ENTER 3000 //time required to hold a button before SwitchMote enters [SYNC mode]
|
||||
#define SYNC_TIME 20000 //max time spent in SYNC mode before returning to normal operation (you got this much time to SYNC 2 SMs, increase if need more time to walk)
|
||||
#define SYNC_MAX_COUNT 10 //max number of SYNC entries (increase for more interactions)
|
||||
#define SYNC_EEPROM_ADDR 64 //SYNC_TO and SYNC_INFO data starts at this EEPROM address
|
||||
#define ERASE_HOLD 6000 //time required to hold a button before SYNC data is erased
|
||||
#define MOTION_TIME_ON 60000 //time to hold a button/output HIGH after a motion triggered command
|
||||
|
||||
//in SYNC_INFO we're storing 4 pieces of information in each byte:
|
||||
#define SYNC_DIGIT_THISBTN 0 //first digit is the button pressed on this unit which will trigger an action on another unit
|
||||
#define SYNC_DIGIT_THISMODE 1 //second digit indicates the mode of this unit is in when triggering
|
||||
#define SYNC_DIGIT_SYNCBTN 2 //third digit indicates the button that should be requested to be "pressed" on the target
|
||||
#define SYNC_DIGIT_SYNCMODE 3 //fourth digit indicates the mode that should be requested on the target
|
||||
#define SYNC_MIN_TIME_LIMIT 2000 //minimum time limit since last SYNC before a new sync can be propagated (used to stop circular SYNC loops)
|
||||
|
||||
#define SERIAL_EN //comment this out when deploying to an installed SM to save a few KB of sketch size
|
||||
#define SERIAL_BAUD 115200
|
||||
#ifdef SERIAL_EN
|
||||
#define DEBUG(input) {Serial.print(input); delay(1);}
|
||||
#define DEBUGln(input) {Serial.println(input); delay(1);}
|
||||
#else
|
||||
#define DEBUG(input);
|
||||
#define DEBUGln(input);
|
||||
#endif
|
||||
|
||||
#define LED_PERIOD_ERROR 50
|
||||
#define LED_PERIOD_OK 200
|
||||
#define ON 1
|
||||
#define OFF 0
|
||||
#define PRESSED 0
|
||||
#define RELEASED 1
|
||||
|
||||
struct configuration {
|
||||
byte frequency;
|
||||
byte isHW;
|
||||
byte nodeID;
|
||||
byte networkID;
|
||||
char encryptionKey[16];
|
||||
byte separator1;
|
||||
char description[10];
|
||||
byte separator2;
|
||||
//byte buttons?
|
||||
//byte DEBUG?
|
||||
} CONFIG;
|
||||
|
||||
// *************************************************************************************************************
|
||||
//compiler wants these function prototype here because of the optional parameter(s)
|
||||
void action(byte whichButtonIndex, byte whatMode, boolean notifyGateway=true);
|
||||
boolean checkSYNC(byte nodeIDToSkip=0);
|
||||
// *************************************************************************************************************
|
||||
//SYNC data is stored in 2 arrays:
|
||||
byte SYNC_TO[SYNC_MAX_COUNT]; // stores the address of the remote SM(s) that this SM has to notify/send request to
|
||||
int SYNC_INFO[SYNC_MAX_COUNT]; // stores the buttons and modes of this and the remote SM as last 4 digits:
|
||||
// - this SM button # (0,1,2) = least significant digit (SYNC_DIGIT_BTN=0)
|
||||
// - this button mode (0,1) = second digit ((SYNC_DIGIT_THISMODE=1)
|
||||
// - remote SM button # (0,1,2) = 3rd digit from right (SYNC_DIGIT_SYNCBTN=2)
|
||||
// - remote SM mode (0,1) = most significant digit (SYNC_DIGIT_SYNCMODE=3)
|
||||
// the 4 pieces of information require an int (a byte only has up to 3 digits)
|
||||
#ifdef ENABLE_ATC
|
||||
RFM69_ATC radio;
|
||||
#else
|
||||
RFM69 radio;
|
||||
#endif
|
||||
|
||||
SPIFlash flash(8, 0xEF30); //FLASH MEM CS pin is wired to Moteino D8
|
||||
unsigned long syncStart=0;
|
||||
unsigned long now=0;
|
||||
byte btnIndex=0; // as the sketch loops this index will loop through the available physical buttons
|
||||
byte mode[] = {ON,ON,ON}; //could use single bytes for efficiency but keeping it separate for clarity
|
||||
byte btn[] = {BTNT, BTNM, BTNB};
|
||||
byte btnLastState[]={RELEASED,RELEASED,RELEASED};
|
||||
unsigned long btnLastPress[]={0,0,0};
|
||||
byte btnLEDRED[] = {LED_RT, LED_RM, LED_RB};
|
||||
byte btnLEDGRN[] = {LED_GT, LED_GM, LED_GB};
|
||||
uint32_t lastSYNC=0; //remember last status change - used to detect & stop loop conditions in circular SYNC scenarios
|
||||
char * buff="justAnEmptyString";
|
||||
|
||||
void setup(void)
|
||||
{
|
||||
#ifdef SERIAL_EN
|
||||
Serial.begin(SERIAL_BAUD);
|
||||
#endif
|
||||
EEPROM.readBlock(0, CONFIG);
|
||||
if (CONFIG.frequency!=RF69_433MHZ && CONFIG.frequency!=RF69_868MHZ && CONFIG.frequency!=RF69_915MHZ) // virgin CONFIG, expected [4,8,9]
|
||||
{
|
||||
DEBUGln(F("\r\nNo valid config found in EEPROM, setting defaults.."));
|
||||
CONFIG.separator1=CONFIG.separator2=0;
|
||||
CONFIG.frequency=RF69_915MHZ;
|
||||
CONFIG.description[0]=0;
|
||||
CONFIG.encryptionKey[0]=0;
|
||||
CONFIG.isHW=CONFIG.nodeID=CONFIG.networkID=0;
|
||||
}
|
||||
|
||||
//if SYNC_INFO[0] == 255 it means it's virgin EEPROM memory, needs initialization (one time ever)
|
||||
if (EEPROM.read(SYNC_EEPROM_ADDR+SYNC_MAX_COUNT)==255) eraseSYNC();
|
||||
EEPROM.readBlock<byte>(SYNC_EEPROM_ADDR, SYNC_TO, SYNC_MAX_COUNT);
|
||||
EEPROM.readBlock<byte>(SYNC_EEPROM_ADDR+SYNC_MAX_COUNT, (byte *)SYNC_INFO, SYNC_MAX_COUNT*2); //int=2bytes so need to cast to byte array
|
||||
|
||||
radio.initialize(CONFIG.frequency,CONFIG.nodeID,CONFIG.networkID);
|
||||
radio.sleep();
|
||||
|
||||
if (CONFIG.isHW) radio.setHighPower(); //must include this only for RFM69HW/HCW!
|
||||
if (CONFIG.encryptionKey[0]!=0) radio.encrypt(CONFIG.encryptionKey);
|
||||
|
||||
#ifdef ENABLE_ATC
|
||||
radio.enableAutoPower(ATC_RSSI);
|
||||
DEBUGln(F("\r\nRFM69_ATC Enabled (Auto Transmission Control)"));
|
||||
#endif
|
||||
|
||||
pinMode(LED_RM, OUTPUT);pinMode(LED_GM, OUTPUT);
|
||||
pinMode(LED_RT, OUTPUT);pinMode(LED_GT, OUTPUT);
|
||||
pinMode(LED_RB, OUTPUT);pinMode(LED_GB, OUTPUT);
|
||||
// by writing HIGH while in INPUT mode, the internal pullup is activated
|
||||
// the button will read 1 when RELEASED (because of the pullup)
|
||||
// the button will read 0 when PRESSED (because it's shorted to GND)
|
||||
pinMode(BTNM, INPUT);digitalWrite(BTNM, HIGH); //activate pullup
|
||||
pinMode(BTNT, INPUT);digitalWrite(BTNT, HIGH); //activate pullup
|
||||
pinMode(BTNB, INPUT);digitalWrite(BTNB, HIGH); //activate pullup
|
||||
pinMode(RELAY1, OUTPUT);
|
||||
pinMode(RELAY2, OUTPUT);
|
||||
blinkLED(LED_RM,LED_PERIOD_ERROR,LED_PERIOD_ERROR,3);
|
||||
delay(500);
|
||||
DEBUGln("Listening for wireless ON/OFF commands...");
|
||||
|
||||
DEBUG(F("|-----------------------------------------------------------"));
|
||||
//initialize LEDs according to default modes
|
||||
action(btnIndex, OFF, false);btnIndex++;
|
||||
action(btnIndex, OFF, false);btnIndex++;
|
||||
action(btnIndex, OFF, false);
|
||||
|
||||
displayMainMenu();
|
||||
}
|
||||
|
||||
byte btnState=RELEASED;
|
||||
boolean isSyncMode=0;
|
||||
boolean ignorePress=false;
|
||||
unsigned long int offTimer=0;
|
||||
byte offIndex=0;
|
||||
void loop()
|
||||
{
|
||||
if(Serial.available())
|
||||
handleMenuInput(Serial.read());
|
||||
|
||||
//on each loop pass check the next button
|
||||
if (isSyncMode==false)
|
||||
{
|
||||
btnIndex++;
|
||||
if (btnIndex>BTNCOUNT-1) btnIndex=0;
|
||||
}
|
||||
btnState = digitalRead(btn[btnIndex]);
|
||||
now = millis();
|
||||
|
||||
if (btnState != btnLastState[btnIndex] && now-btnLastPress[btnIndex] >= BUTTON_BOUNCE_MS) //button event happened
|
||||
{
|
||||
btnLastState[btnIndex] = btnState;
|
||||
if (btnState == PRESSED) btnLastPress[btnIndex] = now;
|
||||
|
||||
//if normal button press, do the RELAY/LED action and notify sync-ed SwitchMotes
|
||||
if (btnState == RELEASED && !isSyncMode)
|
||||
{
|
||||
ignorePress=false;
|
||||
action(btnIndex, mode[btnIndex]==ON ? OFF : ON);
|
||||
checkSYNC(0);
|
||||
}
|
||||
}
|
||||
|
||||
//enter SYNC mode when a button pressed for more than SYNC_ENTER ms
|
||||
if (isSyncMode==false && btnState == PRESSED && now-btnLastPress[btnIndex] >= SYNC_ENTER && !ignorePress)
|
||||
{
|
||||
// first broadcast SYNC token to sync with another SwitchMote that is in SYNC mode
|
||||
// "SYNC?" means "is there anyone wanting to Synchronize with me?"
|
||||
// response "SYNCx:0" (meaning "OK, SYNC with me and turn my button x OFF")
|
||||
// response "SYNCx:1" (meaning "OK, SYNC with me and turn my button x ON")
|
||||
// no response means no other SwMote in range is in SYNC mode, so enter SYNC and
|
||||
// listen for another SwMote to broadcast its SYNC token
|
||||
if (radio.sendWithRetry(RF69_BROADCAST_ADDR,"SYNC?",5))
|
||||
{
|
||||
DEBUG(F("GOT SYNC? REPLY FROM ["));
|
||||
DEBUG(radio.SENDERID);
|
||||
DEBUG(F(":"));DEBUG(radio.DATALEN);DEBUG(F("]:["));
|
||||
for (byte i = 0; i < radio.DATALEN; i++)
|
||||
DEBUG((char)radio.DATA[i]);
|
||||
DEBUGln(F("]"));
|
||||
|
||||
//ACK received, check payload
|
||||
if (radio.DATALEN==7 && radio.DATA[0]=='S' && radio.DATA[1]=='Y' && radio.DATA[2]=='N' && radio.DATA[3]=='C' && radio.DATA[5]==':'
|
||||
&& radio.DATA[4]>='0' && radio.DATA[4]<='2' && (radio.DATA[6]=='0' || radio.DATA[6]=='1'))
|
||||
{
|
||||
if (addSYNC(radio.SENDERID, radio.DATA[4]-'0', radio.DATA[6]-'0'))
|
||||
blinkLED(btnLEDGRN[btnIndex],LED_PERIOD_OK,LED_PERIOD_OK,3);
|
||||
else
|
||||
blinkLED(btnLEDRED[btnIndex],LED_PERIOD_ERROR,LED_PERIOD_ERROR,3);
|
||||
|
||||
action(btnIndex, mode[btnIndex]);
|
||||
return; //exit SYNC
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG(F("SYNC ACK mismatch: ["));
|
||||
for (byte i = 0; i < radio.DATALEN; i++)
|
||||
DEBUG((char)radio.DATA[i]);
|
||||
DEBUGln(']');
|
||||
}
|
||||
}
|
||||
else { DEBUGln(F("NO SYNC REPLY ..")); }
|
||||
|
||||
isSyncMode = true;
|
||||
DEBUGln(F("SYNC MODE ON"));
|
||||
displaySYNC();
|
||||
syncStart = now;
|
||||
}
|
||||
|
||||
//if button held for more than ERASE_TRIGGER, erase SYNC table
|
||||
if (isSyncMode==true && btnState == PRESSED && now-btnLastPress[btnIndex] >= ERASE_HOLD && !ignorePress)
|
||||
{
|
||||
DEBUG(F("ERASING SYNC TABLE ... "));
|
||||
eraseSYNC();
|
||||
isSyncMode = false;
|
||||
ignorePress = true;
|
||||
DEBUGln(F("... DONE"));
|
||||
blinkLED(btnLEDRED[btnIndex],LED_PERIOD_ERROR,LED_PERIOD_ERROR,3);
|
||||
action(btnIndex, mode[btnIndex], false);
|
||||
}
|
||||
|
||||
//SYNC exit condition
|
||||
if (isSyncMode)
|
||||
{
|
||||
syncBlink(btnLEDRED[btnIndex], btnLEDGRN[btnIndex]);
|
||||
if (now-syncStart >= SYNC_TIME)
|
||||
{
|
||||
isSyncMode = false;
|
||||
DEBUGln(F("SYNC MODE OFF"));
|
||||
action(btnIndex, mode[btnIndex], false);
|
||||
}
|
||||
}
|
||||
|
||||
//check if any packet received
|
||||
if (radio.receiveDone())
|
||||
{
|
||||
byte senderID = radio.SENDERID;
|
||||
DEBUGln();
|
||||
DEBUG("[");DEBUG(senderID);DEBUG("] ");
|
||||
for (byte i = 0; i < radio.DATALEN; i++)
|
||||
DEBUG((char)radio.DATA[i]);
|
||||
DEBUG(F(" [RX_RSSI:"));DEBUG(radio.RSSI);DEBUG(F("]"));
|
||||
|
||||
// wireless programming token check
|
||||
// DO NOT REMOVE, or SwitchMote will not be wirelessly programmable any more!
|
||||
CheckForWirelessHEX(radio, flash, true, LED_RM);
|
||||
|
||||
//respond to SYNC request
|
||||
if (isSyncMode && radio.DATALEN == 5
|
||||
&& radio.DATA[0]=='S' && radio.DATA[1]=='Y' && radio.DATA[2]=='N' && radio.DATA[3] == 'C' && radio.DATA[4]=='?')
|
||||
{
|
||||
sprintf(buff,"SYNC%d:%d",btnIndex, mode[btnIndex]); //respond to SYNC request with this SM's button and mode information
|
||||
radio.sendACK(buff, strlen(buff));
|
||||
DEBUG(F(" - SYNC ACK sent : "));
|
||||
DEBUGln(buff);
|
||||
isSyncMode = false;
|
||||
action(btnIndex, mode[btnIndex], false);
|
||||
return; //continue loop
|
||||
}
|
||||
|
||||
//listen for BTNx:y commands where x={0,1,2}, y={0,1}
|
||||
if (radio.DATALEN == 6
|
||||
&& radio.DATA[0]=='B' && radio.DATA[1]=='T' && radio.DATA[2]=='N' && radio.DATA[4] == ':'
|
||||
&& (radio.DATA[3]>='0' && radio.DATA[3]<='2') && (radio.DATA[5]=='0' || radio.DATA[5]=='1'))
|
||||
{
|
||||
if (radio.ACKRequested()) radio.sendACK(); //send ACK sooner when a ON/OFF + ACK is requested
|
||||
btnIndex = radio.DATA[3]-'0';
|
||||
action(btnIndex, (radio.DATA[5]=='1'?ON:OFF), true); //senderID!=GATEWAYID
|
||||
checkSYNC(senderID);
|
||||
}
|
||||
|
||||
//listen for MOT:x commands where x={0,1,2} - motion activated command to turn ON a button/relay (timed ON)
|
||||
if (radio.DATALEN == 5
|
||||
&& radio.DATA[0]=='M' && radio.DATA[1]=='O' && radio.DATA[2]=='T' && radio.DATA[3] == ':'
|
||||
&& (radio.DATA[4]>='0' && radio.DATA[4]<='2'))
|
||||
{
|
||||
if (radio.ACKRequested()) radio.sendACK(); //send ACK sooner when a ON/OFF + ACK is requested
|
||||
btnIndex = radio.DATA[4]-'0';
|
||||
//if(mode[btnIndex] != ON) //if a light is already ON, ignore MOTION triggered commands and do nothing, uncomment this line to
|
||||
offTimer = millis();
|
||||
offIndex = btnIndex;
|
||||
if(mode[btnIndex] != ON) //only take action when mode is not already ON
|
||||
{
|
||||
action(btnIndex, ON, true); //senderID!=GATEWAYID
|
||||
checkSYNC(senderID);
|
||||
}
|
||||
}
|
||||
|
||||
if (radio.ACKRequested()) //dont ACK broadcasted messages except in special circumstances (like SYNCing)
|
||||
{
|
||||
radio.sendACK();
|
||||
DEBUG(F(" - ACK sent"));
|
||||
//delay(5);
|
||||
}
|
||||
}
|
||||
|
||||
//check if a motion command timer expired and the corresponding light can turn off
|
||||
if ((offTimer > 0) && (millis() - offTimer > MOTION_TIME_ON))
|
||||
{
|
||||
offTimer = 0;
|
||||
if(mode[offIndex] != OFF)
|
||||
{
|
||||
DEBUGln(F("OffTimer expired, turning off"));
|
||||
action(offIndex, OFF, true);
|
||||
checkSYNC(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//sets the mode (ON/OFF) for the current button (btnIndex) and turns SSR ON if the btnIndex points to BTNSSR
|
||||
void action(byte whichButtonIndex, byte whatMode, boolean notifyGateway)
|
||||
{
|
||||
DEBUG(F("\r\nbtn["));
|
||||
DEBUG(whichButtonIndex);
|
||||
DEBUG(F("]:D"));
|
||||
DEBUG(btn[whichButtonIndex]);
|
||||
DEBUG(F(" - "));
|
||||
DEBUG(btn[whichButtonIndex]==BTNT?F("TOP: "):btn[whichButtonIndex]==BTNM?F("MAIN: "):btn[whichButtonIndex]==BTNB?F("BOTTOM: "):F("UNKNOWN"));
|
||||
DEBUG(whatMode==ON?F("ON "):F("OFF"));
|
||||
|
||||
mode[whichButtonIndex] = whatMode;
|
||||
|
||||
//change LEDs and relay states
|
||||
digitalWrite(btnLEDRED[whichButtonIndex], whatMode == ON ? LOW : HIGH);
|
||||
digitalWrite(btnLEDGRN[whichButtonIndex], whatMode == ON ? HIGH : LOW);
|
||||
if (whichButtonIndex==RELAY1_INDEX)
|
||||
digitalWrite(RELAY1, whatMode == ON ? HIGH : LOW);
|
||||
if (whichButtonIndex==RELAY2_INDEX)
|
||||
digitalWrite(RELAY2, whatMode == ON ? HIGH : LOW); //SwitchMote2x10A has 2 10A relays
|
||||
|
||||
//notify gateway
|
||||
if (notifyGateway)
|
||||
{
|
||||
sprintf(buff, "BTN%d:%d", whichButtonIndex,whatMode);
|
||||
if (radio.sendWithRetry(GATEWAYID, buff, strlen(buff), 5)) //up to 5 attempts
|
||||
{DEBUGln(F("..OK"));}
|
||||
else {DEBUGln(F("..NOK"));}
|
||||
}
|
||||
}
|
||||
|
||||
long blinkLastCycle=0;
|
||||
boolean blinkState=0;
|
||||
void syncBlink(byte LED1, byte LED2)
|
||||
{
|
||||
if (now-blinkLastCycle>=60)
|
||||
{
|
||||
blinkLastCycle=now;
|
||||
blinkState=!blinkState;
|
||||
digitalWrite(LED1,blinkState);
|
||||
digitalWrite(LED2,!blinkState);
|
||||
}
|
||||
}
|
||||
|
||||
//adds a new entry in the SYNC data
|
||||
boolean addSYNC(byte targetAddr, byte targetButton, byte targetMode)
|
||||
{
|
||||
byte emptySlot=255;
|
||||
if (targetAddr==0) return false;
|
||||
|
||||
//traverse all SYNC data and look for an empty slot, or matching slot that should be overwritten
|
||||
for (byte i=0; i < SYNC_MAX_COUNT; i++)
|
||||
{
|
||||
if (SYNC_TO[i]==0 && emptySlot==255) //save first empty slot
|
||||
emptySlot=i; //remember first empty slot anyway
|
||||
else if (SYNC_TO[i]==targetAddr && //save first slot that matches the same button with the same mode in this unit
|
||||
//but different target unit mode (cant have 2 opposing conditions so just override it)
|
||||
getDigit(SYNC_INFO[i],SYNC_DIGIT_SYNCBTN)==targetButton &&
|
||||
getDigit(SYNC_INFO[i],SYNC_DIGIT_THISMODE)==mode[btnIndex]) //getDigit(SYNC_INFO[i],SYNC_DIGIT_SYNCNODE)!=targetMode)
|
||||
{
|
||||
emptySlot=i; //remember matching non-empty slot
|
||||
break; //stop as soon as we found a match
|
||||
}
|
||||
}
|
||||
|
||||
if (emptySlot==255) //means SYNC data is full, do nothing and return
|
||||
{
|
||||
DEBUGln(F("SYNC data full, aborting..."));
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
SYNC_TO[emptySlot] = targetAddr;
|
||||
SYNC_INFO[emptySlot] = targetMode*1000 + targetButton*100 + mode[btnIndex]*10 + btnIndex;
|
||||
DEBUG(F("Saving SYNC_TO["));
|
||||
DEBUG(emptySlot);
|
||||
DEBUG(F("]="));
|
||||
DEBUG(SYNC_TO[emptySlot]);
|
||||
DEBUG(F(" SYNC_INFO = "));
|
||||
DEBUG(SYNC_INFO[emptySlot]);
|
||||
saveSYNC();
|
||||
DEBUGln(F(" .. Saved!"));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
//checks the SYNC table for any necessary requests to other SwitchMotes
|
||||
boolean checkSYNC(byte nodeIDToSkip)
|
||||
{
|
||||
for (byte i=0; i < SYNC_MAX_COUNT; i++)
|
||||
{
|
||||
if (SYNC_TO[i]!=0 && SYNC_TO[i]!=nodeIDToSkip && getDigit(SYNC_INFO[i],SYNC_DIGIT_THISBTN)==btnIndex && getDigit(SYNC_INFO[i],SYNC_DIGIT_THISMODE)==mode[btnIndex])
|
||||
{
|
||||
DEBUGln();
|
||||
DEBUG(F(" SYNC["));
|
||||
DEBUG(SYNC_TO[i]);
|
||||
DEBUG(F(":"));
|
||||
DEBUG(getDigit(SYNC_INFO[i],SYNC_DIGIT_SYNCMODE));
|
||||
DEBUG(F("]:"));
|
||||
sprintf(buff, "BTN%d:%d", getDigit(SYNC_INFO[i],SYNC_DIGIT_SYNCBTN), getDigit(SYNC_INFO[i],SYNC_DIGIT_SYNCMODE));
|
||||
if (radio.sendWithRetry(SYNC_TO[i], buff,6))
|
||||
{DEBUG(F("OK"));}
|
||||
else {DEBUG(F("NOK"));}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void eraseSYNC()
|
||||
{
|
||||
for(byte i = 0; i<SYNC_MAX_COUNT; i++)
|
||||
{
|
||||
SYNC_TO[i]=0;
|
||||
SYNC_INFO[i]=0;
|
||||
}
|
||||
saveSYNC();
|
||||
}
|
||||
|
||||
//saves SYNC_TO and SYNC_INFO arrays to EEPROM
|
||||
void saveSYNC()
|
||||
{
|
||||
EEPROM.writeBlock<byte>(SYNC_EEPROM_ADDR, SYNC_TO, SYNC_MAX_COUNT);
|
||||
EEPROM.writeBlock<byte>(SYNC_EEPROM_ADDR+SYNC_MAX_COUNT, (byte*)SYNC_INFO, SYNC_MAX_COUNT*2);
|
||||
}
|
||||
|
||||
void displaySYNC()
|
||||
{
|
||||
for (byte i=0; i < SYNC_MAX_COUNT; i++)
|
||||
{
|
||||
DEBUG(SYNC_INFO[i]);
|
||||
if (i!=SYNC_MAX_COUNT-1) DEBUG(',');
|
||||
}
|
||||
}
|
||||
|
||||
//returns the Nth digit in an integer
|
||||
byte getDigit(int n, byte pos){return (n/(pos==0?1:pos==1?10:pos==2?100:1000))%10;}
|
||||
|
||||
void blinkLED(byte LEDpin, byte periodON, byte periodOFF, byte repeats)
|
||||
{
|
||||
while(repeats-->0)
|
||||
{
|
||||
digitalWrite(LEDpin, HIGH);
|
||||
delay(periodON);
|
||||
digitalWrite(LEDpin, LOW);
|
||||
delay(periodOFF);
|
||||
}
|
||||
}
|
||||
|
||||
/*CONFIGURATION HELPERS*/
|
||||
void displayMainMenu()
|
||||
{
|
||||
Serial.println();
|
||||
Serial.println(F("|-----------------------------------------------------------"));
|
||||
Serial.println(F("| SwitchMote RFM69 configuration menu "));
|
||||
Serial.println(F("| Use Putty or a similar client to setup params"));
|
||||
Serial.println(F("| ArduinoIDE serial monitor doesn't work well"));
|
||||
Serial.println(F("| Don't forget to save 's' and reboot 'r' to apply changes"));
|
||||
Serial.println(F("|-----------------------------------------------------------"));
|
||||
Serial.print (F("| f - set frequency band (set to: "));Serial.print(CONFIG.frequency==RF69_433MHZ?F("433"):CONFIG.frequency==RF69_868MHZ?F("868"):F("915"));Serial.println(F("mhz)"));
|
||||
Serial.print (F("| i - set node ID (set to: "));Serial.print(CONFIG.nodeID);Serial.println(F(")"));
|
||||
Serial.print (F("| n - set network ID (set to: "));Serial.print(CONFIG.networkID);Serial.println(")");
|
||||
Serial.print (F("| w - set RFM69 type (set to: "));Serial.print(CONFIG.isHW?F("HW/HCW"):F("W/CW"));Serial.println(F(")"));
|
||||
Serial.print (F("| e - set encryption key (set to: '"));Serial.print(CONFIG.encryptionKey);Serial.println(F("')"));
|
||||
Serial.print (F("| d - set description (set to: '"));Serial.print(CONFIG.description);Serial.println(F("')"));
|
||||
Serial.println(F("| s - save CONFIG to EEPROM"));
|
||||
Serial.println(F("| E - erase whole EEPROM - [0..1023]"));
|
||||
Serial.print (F("| S - erase SYNC data ["));Serial.print(SYNC_EEPROM_ADDR);Serial.print(F(".."));Serial.print(SYNC_EEPROM_ADDR+3*SYNC_MAX_COUNT-1);Serial.print(F("]:["));
|
||||
displaySYNC();
|
||||
Serial.println(']');
|
||||
Serial.println(F("| r - reboot"));
|
||||
Serial.println(F("| ESC - re-display config menu"));
|
||||
Serial.println(F("|-----------------------------------------------------------"));
|
||||
Serial.println(F("| Usage ex.: press 'f' to change frequency"));
|
||||
Serial.println(F("|-----------------------------------------------------------"));
|
||||
}
|
||||
|
||||
char menu = 0;
|
||||
byte charsRead = 0;
|
||||
void handleMenuInput(char c)
|
||||
{
|
||||
switch(menu)
|
||||
{
|
||||
case 0:
|
||||
switch(c)
|
||||
{
|
||||
case 'f': Serial.print(F("\r\nEnter frequency ('4'= 433mhz, '8'=868mhz, '9'=915mhz): ")); menu=c; break;
|
||||
case 'i': Serial.print(F("\r\nEnter node ID (1-255 + <ENTER>): ")); CONFIG.nodeID=0;menu=c; break;
|
||||
case 'n': Serial.print(F("\r\nEnter network ID (0-255 + <ENTER>): ")); CONFIG.networkID=0; menu=c; break;
|
||||
case 'e': Serial.print(F("\r\nEnter encryption key (type 16 characters): ")); menu=c; break;
|
||||
case 'w': Serial.print(F("\r\nIs this RFM69W/CW/HW (0=W/CW, 1=HW/HCW): ")); menu=c; break;
|
||||
case 'd': Serial.print(F("\r\nEnter description (10 chars max + <ENTER>): ")); menu=c; break;
|
||||
case 's': Serial.print(F("\r\nCONFIG saved to EEPROM!")); EEPROM.writeBlock(0, CONFIG); break;
|
||||
case 'E': Serial.print(F("\r\nErasing EEPROM ... ")); menu=c; break;
|
||||
case 'S': Serial.print(F("\r\nErasing SYNC EEPROM ... ")); menu=c; break;
|
||||
case 'r': Serial.print(F("\r\nRebooting")); resetUsingWatchdog(1); break;
|
||||
case 27: displayMainMenu();menu=0;break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'f':
|
||||
switch(c)
|
||||
{
|
||||
case '4': Serial.println(F("Set to 433Mhz")); CONFIG.frequency = RF69_433MHZ; menu=0; break;
|
||||
case '8': Serial.println(F("Set to 868Mhz")); CONFIG.frequency = RF69_868MHZ; menu=0; break;
|
||||
case '9': Serial.println(F("Set to 915Mhz")); CONFIG.frequency = RF69_915MHZ; menu=0; break;
|
||||
case 27: displayMainMenu();menu=0;break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'i':
|
||||
if (c >= '0' && c <= '9')
|
||||
{
|
||||
if (CONFIG.nodeID * 10 + c - 48 <= 255)
|
||||
{
|
||||
CONFIG.nodeID = CONFIG.nodeID * 10 + c - 48;
|
||||
Serial.print(c);
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.print(" - Set to ");Serial.println(CONFIG.nodeID);
|
||||
menu=0;
|
||||
}
|
||||
}
|
||||
else if (c == 13 || c == 27)
|
||||
{
|
||||
Serial.print(F(" - Set to "));Serial.println(CONFIG.nodeID);
|
||||
displayMainMenu();
|
||||
menu=0;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'n':
|
||||
if (c >= '0' && c <= '9')
|
||||
{
|
||||
if (CONFIG.networkID * 10 + c - 48 <= 255)
|
||||
{
|
||||
CONFIG.networkID = CONFIG.networkID * 10 + c - 48;
|
||||
Serial.print(c);
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.print(" - Set to ");Serial.println(CONFIG.networkID);
|
||||
menu=0;
|
||||
}
|
||||
}
|
||||
if (c == 13 || c == 27)
|
||||
{
|
||||
Serial.print(" - Set to ");Serial.println(CONFIG.networkID);
|
||||
displayMainMenu();
|
||||
menu=0;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'e':
|
||||
if (c >= ' ' && c <= '~') //human readable chars (32 - 126)
|
||||
if (++charsRead<=16)
|
||||
{
|
||||
CONFIG.encryptionKey[charsRead-1] = c;
|
||||
CONFIG.encryptionKey[charsRead] = 0;
|
||||
Serial.print(c);
|
||||
}
|
||||
if (charsRead >= 16 || c == 27 || c == 13)
|
||||
{
|
||||
//Serial.print(" - Set to [");Serial.print(CONFIG.encryptionKey);Serial.println(']');
|
||||
Serial.println(F(" - DONE"));
|
||||
displayMainMenu();menu=0;charsRead=0;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'd':
|
||||
if (c >= ' ' && c <= '~') //human readable chars (32 - 126)
|
||||
{
|
||||
if (++charsRead<=10)
|
||||
{
|
||||
CONFIG.description[charsRead-1] = c;
|
||||
CONFIG.description[charsRead] = 0;
|
||||
Serial.print(c);
|
||||
}
|
||||
}
|
||||
if (charsRead>=10 || c == 13 || c == 27)
|
||||
{
|
||||
//Serial.print(" - Set to [");Serial.print(CONFIG.description);Serial.println(']');
|
||||
Serial.println(F(" - DONE"));
|
||||
displayMainMenu();menu=0;charsRead=0;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'w':
|
||||
switch(c)
|
||||
{
|
||||
case '0': Serial.println(F("Set to RFM69W\\CW")); CONFIG.isHW = 0; menu=0; break;
|
||||
case '1': Serial.println(F("Set to RFM69HW\\HCW")); CONFIG.isHW = 1; menu=0; break;
|
||||
case 27: displayMainMenu();menu=0;break;
|
||||
}
|
||||
break;
|
||||
case 'E':
|
||||
for (int i=0;i<1024;i++) EEPROM.write(i,255); //eeprom_write_byte((unsigned char *) i, 255);
|
||||
Serial.println(F("DONE"));
|
||||
//resetUsingWatchdog(1);
|
||||
menu=0;
|
||||
break;
|
||||
case 'S':
|
||||
eraseSYNC();
|
||||
Serial.println(F("DONE"));
|
||||
menu=0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,775 @@
|
||||
// *************************************************************************************************************
|
||||
// SwitchMote Moteino sample sketch with PIR motion sensor support
|
||||
// *************************************************************************************************************
|
||||
// Handles the single 5A relay SwitchMote, as well as the dual 10A relay SwitchMote2x10A
|
||||
// SwitchMote is a highly integrated wireless AC switch controller based on Moteino
|
||||
// https://lowpowerlab.com/switchmote
|
||||
// *************************************************************************************************************
|
||||
// This sketch will provide the essential features of SwitchMote:
|
||||
// - wireless programming
|
||||
// - accept ON/OFF commands from a Moteino gateway (format is BTNx:y commands where x={0,1,2}, y={0,1})
|
||||
// - control the relay(s) to turn the AC load ON/OFF
|
||||
// - control up to 6 LEDs and 3 buttons on front panel
|
||||
// - SYNC feature allows out of box synchronization with other SwitchMotes
|
||||
// ie - when a button is pressed on this SwitchMote it can trigger the press of a button on another SwitchMote
|
||||
// so you can use a SwitchMote button to control a light/set of lights from remote locations
|
||||
// this sketch allows up to 5 SYNCs but could be extended
|
||||
// - Panasonic PIR motion sensor support in place of middle button
|
||||
// This sketch may be extended to include integration with other LowPowerLab automation products, for instance to
|
||||
// control the GarageMote from a button on the SwitchMote, etc.
|
||||
// *************************************************************************************************************
|
||||
// Copyright Felix Rusu 2017, http://www.LowPowerLab.com/contact
|
||||
// **********************************************************************************
|
||||
// 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.
|
||||
//
|
||||
// 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 <EEPROMex.h> //get it here: http://playground.arduino.cc/Code/EEPROMex
|
||||
#include <RFM69.h> //get it here: http://github.com/lowpowerlab/rfm69
|
||||
#include <RFM69_ATC.h> //get it here: https://github.com/lowpowerlab/rfm69
|
||||
#include <RFM69_OTA.h> //get it here: https://github.com/LowPowerLab/rfm69
|
||||
#include <SPIFlash.h> //get it here: http://github.com/lowpowerlab/spiflash
|
||||
#include <SPI.h> //comes with Arduino
|
||||
// **********************************************************************************
|
||||
//Auto Transmission Control - dials down transmit power to save battery (-100 is the noise floor, -90 is still pretty good)
|
||||
//For indoor nodes that are pretty static and at pretty stable temperatures (like a MotionMote) -90dBm is quite safe
|
||||
//For more variable nodes that can expect to move or experience larger temp drifts a lower margin like -70 to -80 would probably be better
|
||||
//Always test your ATC mote in the edge cases in your own environment to ensure ATC will perform as you expect
|
||||
#define ENABLE_ATC //comment out this line to disable AUTO TRANSMISSION CONTROL
|
||||
#define ATC_RSSI -75
|
||||
// **********************************************************************************
|
||||
#define GATEWAYID 1 //always send messages to NodeID=1
|
||||
// **********************************************************************************
|
||||
#define PIRPRESENT //un-comment if you have a panasonic PIR instead of the MIDDLE button
|
||||
|
||||
#ifdef PIRPRESENT
|
||||
#define PIR_POWER 18 //PIR powered by D18
|
||||
#define PIR_OUTPUT 5 //PIR output signal to D5 - SHARED WITH middle button!
|
||||
#define PIR_DEBOUNCE 6000 //PIR signals valid at least this many ms apart
|
||||
#define PIR_LED_ON 3000
|
||||
#endif
|
||||
// **********************************************************************************
|
||||
#define BTNCOUNT 2 //1 or 3 (2 also possible)
|
||||
#define BTNT 6 //digital pin of top button
|
||||
#define BTNB 4 //digital pin of bottom button
|
||||
|
||||
#define LED_RT 16 //digital pin for TOP RED LED
|
||||
#define LED_GT 19 //digital pin for TOP GREEN LED
|
||||
#define LED_RM 15 //digital pin for MIDDLE RED LED
|
||||
//#define LED_GM 18 //digital pin for MIDDLE GREEN LED
|
||||
#define LED_RM 15 //digital pin for MIDDLE RED LED
|
||||
#define LED_RB 14 //digital pin for BOTTOM RED LED
|
||||
#define LED_GB 17 //digital pin for BOTTOM GREEN LE
|
||||
|
||||
#define RELAY1 7 //digital pin connected to MAIN relay
|
||||
#define RELAY2 3 //digital pin connected to secondary relay (SwitchMote 2x10A only)
|
||||
#define RELAY1_INDEX 0 //index in btn[] array which is associated with the MAIN relay
|
||||
#define RELAY2_INDEX 1 //index in btn[] array which is associated with the secondary relay (SwitchMote 2x10A only)
|
||||
|
||||
#define BUTTON_BOUNCE_MS 200 //timespan before another button change can occur
|
||||
#define SYNC_ENTER 3000 //time required to hold a button before SwitchMote enters [SYNC mode]
|
||||
#define SYNC_TIME 20000 //max time spent in SYNC mode before returning to normal operation (you got this much time to SYNC 2 SMs, increase if need more time to walk)
|
||||
#define SYNC_MAX_COUNT 10 //max number of SYNC entries (increase for more interactions)
|
||||
#define SYNC_EEPROM_ADDR 64 //SYNC_TO and SYNC_INFO data starts at this EEPROM address
|
||||
#define ERASE_HOLD 6000 //time required to hold a button before SYNC data is erased
|
||||
#define MOTION_TIME_ON 60000 //time to hold a button/output HIGH after a motion triggered command
|
||||
// **********************************************************************************
|
||||
//in SYNC_INFO we're storing 4 pieces of information in each byte:
|
||||
#define SYNC_DIGIT_THISBTN 0 //first digit is the button pressed on this unit which will trigger an action on another unit
|
||||
#define SYNC_DIGIT_THISMODE 1 //second digit indicates the mode of this unit is in when triggering
|
||||
#define SYNC_DIGIT_SYNCBTN 2 //third digit indicates the button that should be requested to be "pressed" on the target
|
||||
#define SYNC_DIGIT_SYNCMODE 3 //fourth digit indicates the mode that should be requested on the target
|
||||
#define SYNC_MIN_TIME_LIMIT 2000 //minimum time limit since last SYNC before a new sync can be propagated (used to stop circular SYNC loops)
|
||||
// **********************************************************************************
|
||||
#define SERIAL_EN //comment this out when deploying to an installed SM to save a few KB of sketch size
|
||||
#define SERIAL_BAUD 115200
|
||||
#ifdef SERIAL_EN
|
||||
#define DEBUG(input) {Serial.print(input); delay(1);}
|
||||
#define DEBUGln(input) {Serial.println(input); delay(1);}
|
||||
#else
|
||||
#define DEBUG(input);
|
||||
#define DEBUGln(input);
|
||||
#endif
|
||||
// **********************************************************************************
|
||||
#define LED_PERIOD_ERROR 50
|
||||
#define LED_PERIOD_OK 200
|
||||
#define ON 1
|
||||
#define OFF 0
|
||||
#define PRESSED 0
|
||||
#define RELEASED 1
|
||||
#define FLASHMEM_CS 8 //FLASH_MEM SPI CS on D8
|
||||
// **********************************************************************************
|
||||
//Structure used to store Moteino configuration in EEPROM
|
||||
struct configuration {
|
||||
byte frequency;
|
||||
byte isHW;
|
||||
byte nodeID;
|
||||
byte networkID;
|
||||
char encryptionKey[16];
|
||||
byte separator1;
|
||||
char description[10];
|
||||
byte separator2;
|
||||
//byte buttons?
|
||||
//byte DEBUG?
|
||||
} CONFIG;
|
||||
|
||||
// *************************************************************************************************************
|
||||
//compiler wants these function prototype here because of the optional parameter(s)
|
||||
void action(byte whichButtonIndex, byte whatMode, boolean notifyGateway=true);
|
||||
boolean checkSYNC(byte nodeIDToSkip=0);
|
||||
// *************************************************************************************************************
|
||||
//SYNC data is stored in 2 arrays:
|
||||
byte SYNC_TO[SYNC_MAX_COUNT]; // stores the address of the remote SM(s) that this SM has to notify/send request to
|
||||
int SYNC_INFO[SYNC_MAX_COUNT]; // stores the buttons and modes of this and the remote SM as last 4 digits:
|
||||
// - this SM button # (0,1,2) = least significant digit (SYNC_DIGIT_BTN=0)
|
||||
// - this button mode (0,1) = second digit ((SYNC_DIGIT_THISMODE=1)
|
||||
// - remote SM button # (0,1,2) = 3rd digit from right (SYNC_DIGIT_SYNCBTN=2)
|
||||
// - remote SM mode (0,1) = most significant digit (SYNC_DIGIT_SYNCMODE=3)
|
||||
// the 4 pieces of information require an int (a byte only has up to 3 digits)
|
||||
#ifdef ENABLE_ATC
|
||||
RFM69_ATC radio;
|
||||
#else
|
||||
RFM69 radio;
|
||||
#endif
|
||||
|
||||
SPIFlash flash(FLASHMEM_CS, 0xEF30); //FLASH MEM CS pin is wired to Moteino D8
|
||||
unsigned long syncStart=0;
|
||||
unsigned long now=0;
|
||||
byte btnIndex=0; // as the sketch loops this index will loop through the available physical buttons
|
||||
byte mode[] = {ON,ON,ON}; //could use single bytes for efficiency but keeping it separate for clarity
|
||||
byte btn[] = {BTNT, BTNB};
|
||||
byte btnLastState[]={RELEASED,RELEASED,RELEASED};
|
||||
unsigned long btnLastPress[]={0,0,0};
|
||||
byte btnLEDRED[] = {LED_RT, LED_RB};
|
||||
byte btnLEDGRN[] = {LED_GT, LED_GB};
|
||||
uint32_t lastSYNC=0; //remember last status change - used to detect & stop loop conditions in circular SYNC scenarios
|
||||
char * buff="justAnEmptyString";
|
||||
|
||||
#ifdef PIRPRESENT
|
||||
boolean PIR_MOTION_RELAY=false;
|
||||
#endif
|
||||
|
||||
void setup(void)
|
||||
{
|
||||
#ifdef SERIAL_EN
|
||||
Serial.begin(SERIAL_BAUD);
|
||||
#endif
|
||||
EEPROM.readBlock(0, CONFIG);
|
||||
if (CONFIG.frequency!=RF69_433MHZ && CONFIG.frequency!=RF69_868MHZ && CONFIG.frequency!=RF69_915MHZ) // virgin CONFIG, expected [4,8,9]
|
||||
{
|
||||
DEBUGln(F("\r\nNo valid config found in EEPROM, setting defaults.."));
|
||||
CONFIG.separator1=CONFIG.separator2=0;
|
||||
CONFIG.frequency=RF69_915MHZ;
|
||||
CONFIG.description[0]=0;
|
||||
CONFIG.encryptionKey[0]=0;
|
||||
CONFIG.isHW=CONFIG.nodeID=CONFIG.networkID=0;
|
||||
}
|
||||
|
||||
//if SYNC_INFO[0] == 255 it means it's virgin EEPROM memory, needs initialization (one time ever)
|
||||
if (EEPROM.read(SYNC_EEPROM_ADDR+SYNC_MAX_COUNT)==255) eraseSYNC();
|
||||
EEPROM.readBlock<byte>(SYNC_EEPROM_ADDR, SYNC_TO, SYNC_MAX_COUNT);
|
||||
EEPROM.readBlock<byte>(SYNC_EEPROM_ADDR+SYNC_MAX_COUNT, (byte *)SYNC_INFO, SYNC_MAX_COUNT*2); //int=2bytes so need to cast to byte array
|
||||
|
||||
radio.initialize(CONFIG.frequency,CONFIG.nodeID,CONFIG.networkID);
|
||||
radio.sleep();
|
||||
|
||||
if (CONFIG.isHW) radio.setHighPower(); //must include this only for RFM69HW/HCW!
|
||||
if (CONFIG.encryptionKey[0]!=0) radio.encrypt(CONFIG.encryptionKey);
|
||||
|
||||
#ifdef ENABLE_ATC
|
||||
radio.enableAutoPower(ATC_RSSI);
|
||||
DEBUGln(F("\r\nRFM69_ATC Enabled (Auto Transmission Control)"));
|
||||
#endif
|
||||
|
||||
pinMode(LED_RM, OUTPUT);//pinMode(LED_GM, OUTPUT);
|
||||
pinMode(LED_RT, OUTPUT);pinMode(LED_GT, OUTPUT);
|
||||
pinMode(LED_RB, OUTPUT);pinMode(LED_GB, OUTPUT);
|
||||
// by writing HIGH while in INPUT mode, the internal pullup is activated
|
||||
// the button will read 1 when RELEASED (because of the pullup)
|
||||
// the button will read 0 when PRESSED (because it's shorted to GND)
|
||||
//pinMode(BTNM, INPUT);digitalWrite(BTNM, HIGH); //activate pullup
|
||||
pinMode(BTNT, INPUT);digitalWrite(BTNT, HIGH); //activate pullup
|
||||
pinMode(BTNB, INPUT);digitalWrite(BTNB, HIGH); //activate pullup
|
||||
pinMode(RELAY1, OUTPUT);
|
||||
pinMode(RELAY2, OUTPUT);
|
||||
blinkLED(LED_RM,LED_PERIOD_ERROR,LED_PERIOD_ERROR,3);
|
||||
delay(500);
|
||||
DEBUGln("Listening for wireless ON/OFF commands...");
|
||||
|
||||
DEBUG(F("|-----------------------------------------------------------"));
|
||||
//initialize LEDs according to default modes
|
||||
action(btnIndex, OFF, false);btnIndex++;
|
||||
action(btnIndex, OFF, false);
|
||||
|
||||
displayMainMenu();
|
||||
|
||||
#ifdef PIRPRESENT
|
||||
pinMode(PIR_POWER, OUTPUT);
|
||||
PIR_ONOFF(HIGH); //give power to the PIR
|
||||
digitalWrite(PIR_OUTPUT, LOW); //cancel the pullup activation from BTNM above
|
||||
digitalWrite(LED_RM, LOW);
|
||||
#endif
|
||||
}
|
||||
|
||||
byte btnState=RELEASED;
|
||||
boolean isSyncMode=0;
|
||||
boolean ignorePress=false;
|
||||
unsigned long int offTimer=0;
|
||||
byte offIndex=0;
|
||||
|
||||
#ifdef PIRPRESENT
|
||||
byte motionDetected=false;
|
||||
unsigned long lastMotionTime=0;
|
||||
#endif
|
||||
|
||||
void PIR_ONOFF(byte state) {
|
||||
digitalWrite(PIR_POWER, state);
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
if(Serial.available())
|
||||
handleMenuInput(Serial.read());
|
||||
|
||||
#ifdef PIRPRESENT
|
||||
//check motion sensor
|
||||
if (digitalRead(PIR_OUTPUT) && motionDetected==false && millis()-lastMotionTime > PIR_DEBOUNCE)
|
||||
{
|
||||
motionDetected=true;
|
||||
digitalWrite(LED_RM, HIGH);
|
||||
lastMotionTime = millis();
|
||||
|
||||
if (radio.sendWithRetry(GATEWAYID, "MOTION", 6))
|
||||
{
|
||||
DEBUG("MOTION ACK:OK! RSSI:");
|
||||
DEBUGln(radio.RSSI);
|
||||
}
|
||||
else DEBUGln("MOTION ACK:NOK...");
|
||||
|
||||
if (PIR_MOTION_RELAY)
|
||||
{
|
||||
offTimer = millis();
|
||||
if(mode[offIndex] != ON) //only take action when mode is not already ON
|
||||
{
|
||||
action(offIndex, ON, true); //senderID!=GATEWAYID
|
||||
checkSYNC(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (motionDetected && millis()-lastMotionTime > PIR_LED_ON) //RED-LED-MIDDLE light up for 3 sec
|
||||
{
|
||||
motionDetected = false;
|
||||
digitalWrite(LED_RM, LOW);
|
||||
}
|
||||
#endif
|
||||
|
||||
//on each loop pass check the next button
|
||||
if (isSyncMode==false)
|
||||
{
|
||||
btnIndex++;
|
||||
if (btnIndex>BTNCOUNT-1) btnIndex=0;
|
||||
}
|
||||
btnState = digitalRead(btn[btnIndex]);
|
||||
now = millis();
|
||||
|
||||
if (btnState != btnLastState[btnIndex] && now-btnLastPress[btnIndex] >= BUTTON_BOUNCE_MS) //button event happened
|
||||
{
|
||||
btnLastState[btnIndex] = btnState;
|
||||
if (btnState == PRESSED) btnLastPress[btnIndex] = now;
|
||||
|
||||
//if normal button press, do the RELAY/LED action and notify sync-ed SwitchMotes
|
||||
if (btnState == RELEASED && !isSyncMode)
|
||||
{
|
||||
DEBUG("BTN PRESS: ");DEBUGln(btnIndex);
|
||||
|
||||
//when PIR is installed, both button pressed toggles PIR ON/OFF
|
||||
#ifdef PIRPRESENT
|
||||
if (BTNCOUNT == 2)
|
||||
{
|
||||
byte otherBtnIndex = (btnIndex == 0 ? 1:0);
|
||||
if (digitalRead(btn[otherBtnIndex]) == RELEASED && now-btnLastPress[otherBtnIndex] >= BUTTON_BOUNCE_MS && now-btnLastPress[otherBtnIndex] <= 2*BUTTON_BOUNCE_MS)
|
||||
{
|
||||
//other button was also pressed at same time, toggle PIR-motion->RELAY-ON function
|
||||
PIR_MOTION_RELAY = !PIR_MOTION_RELAY;
|
||||
DEBUG("PIR_MOTION_RELAY: ");DEBUGln(PIR_MOTION_RELAY);
|
||||
return; //don't do anything else
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
ignorePress=false;
|
||||
action(btnIndex, mode[btnIndex]==ON ? OFF : ON);
|
||||
checkSYNC(0);
|
||||
}
|
||||
}
|
||||
|
||||
//enter SYNC mode when a button pressed for more than SYNC_ENTER ms
|
||||
if (isSyncMode==false && btnState == PRESSED && now-btnLastPress[btnIndex] >= SYNC_ENTER && !ignorePress)
|
||||
{
|
||||
// first broadcast SYNC token to sync with another SwitchMote that is in SYNC mode
|
||||
// "SYNC?" means "is there anyone wanting to Synchronize with me?"
|
||||
// response "SYNCx:0" (meaning "OK, SYNC with me and turn my button x OFF")
|
||||
// response "SYNCx:1" (meaning "OK, SYNC with me and turn my button x ON")
|
||||
// no response means no other SwMote in range is in SYNC mode, so enter SYNC and
|
||||
// listen for another SwMote to broadcast its SYNC token
|
||||
if (radio.sendWithRetry(RF69_BROADCAST_ADDR,"SYNC?",5))
|
||||
{
|
||||
DEBUG(F("GOT SYNC? REPLY FROM ["));
|
||||
DEBUG(radio.SENDERID);
|
||||
DEBUG(F(":"));DEBUG(radio.DATALEN);DEBUG(F("]:["));
|
||||
for (byte i = 0; i < radio.DATALEN; i++)
|
||||
DEBUG((char)radio.DATA[i]);
|
||||
DEBUGln(F("]"));
|
||||
|
||||
//ACK received, check payload
|
||||
if (radio.DATALEN==7 && radio.DATA[0]=='S' && radio.DATA[1]=='Y' && radio.DATA[2]=='N' && radio.DATA[3]=='C' && radio.DATA[5]==':'
|
||||
&& radio.DATA[4]>='0' && radio.DATA[4]<='2' && (radio.DATA[6]=='0' || radio.DATA[6]=='1'))
|
||||
{
|
||||
if (addSYNC(radio.SENDERID, radio.DATA[4]-'0', radio.DATA[6]-'0'))
|
||||
blinkLED(btnLEDGRN[btnIndex],LED_PERIOD_OK,LED_PERIOD_OK,3);
|
||||
else
|
||||
blinkLED(btnLEDRED[btnIndex],LED_PERIOD_ERROR,LED_PERIOD_ERROR,3);
|
||||
|
||||
action(btnIndex, mode[btnIndex]);
|
||||
return; //exit SYNC
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG(F("SYNC ACK mismatch: ["));
|
||||
for (byte i = 0; i < radio.DATALEN; i++)
|
||||
DEBUG((char)radio.DATA[i]);
|
||||
DEBUGln(']');
|
||||
}
|
||||
}
|
||||
else { DEBUGln(F("NO SYNC REPLY ..")); }
|
||||
|
||||
isSyncMode = true;
|
||||
DEBUGln(F("SYNC MODE ON"));
|
||||
displaySYNC();
|
||||
syncStart = now;
|
||||
}
|
||||
|
||||
//if button held for more than ERASE_TRIGGER, erase SYNC table
|
||||
if (isSyncMode==true && btnState == PRESSED && now-btnLastPress[btnIndex] >= ERASE_HOLD && !ignorePress)
|
||||
{
|
||||
DEBUG(F("ERASING SYNC TABLE ... "));
|
||||
eraseSYNC();
|
||||
isSyncMode = false;
|
||||
ignorePress = true;
|
||||
DEBUGln(F("... DONE"));
|
||||
blinkLED(btnLEDRED[btnIndex],LED_PERIOD_ERROR,LED_PERIOD_ERROR,3);
|
||||
action(btnIndex, mode[btnIndex], false);
|
||||
}
|
||||
|
||||
//SYNC exit condition
|
||||
if (isSyncMode)
|
||||
{
|
||||
syncBlink(btnLEDRED[btnIndex], btnLEDGRN[btnIndex]);
|
||||
if (now-syncStart >= SYNC_TIME)
|
||||
{
|
||||
isSyncMode = false;
|
||||
DEBUGln(F("SYNC MODE OFF"));
|
||||
action(btnIndex, mode[btnIndex], false);
|
||||
}
|
||||
}
|
||||
|
||||
//check if any packet received
|
||||
if (radio.receiveDone())
|
||||
{
|
||||
byte senderID = radio.SENDERID;
|
||||
DEBUGln();
|
||||
DEBUG("[");DEBUG(senderID);DEBUG("] ");
|
||||
for (byte i = 0; i < radio.DATALEN; i++)
|
||||
DEBUG((char)radio.DATA[i]);
|
||||
DEBUG(F(" [RX_RSSI:"));DEBUG(radio.RSSI);DEBUG(F("]"));
|
||||
|
||||
// wireless programming token check
|
||||
// DO NOT REMOVE, or SwitchMote will not be wirelessly programmable any more!
|
||||
CheckForWirelessHEX(radio, flash, true, LED_RM);
|
||||
|
||||
//respond to SYNC request
|
||||
if (isSyncMode && radio.DATALEN == 5
|
||||
&& radio.DATA[0]=='S' && radio.DATA[1]=='Y' && radio.DATA[2]=='N' && radio.DATA[3] == 'C' && radio.DATA[4]=='?')
|
||||
{
|
||||
sprintf(buff,"SYNC%d:%d",btnIndex, mode[btnIndex]); //respond to SYNC request with this SM's button and mode information
|
||||
radio.sendACK(buff, strlen(buff));
|
||||
DEBUG(F(" - SYNC ACK sent : "));
|
||||
DEBUGln(buff);
|
||||
isSyncMode = false;
|
||||
action(btnIndex, mode[btnIndex], false);
|
||||
return; //continue loop
|
||||
}
|
||||
|
||||
//listen for BTNx:y commands where x={0,1,2}, y={0,1}
|
||||
if (radio.DATALEN == 6
|
||||
&& radio.DATA[0]=='B' && radio.DATA[1]=='T' && radio.DATA[2]=='N' && radio.DATA[4] == ':'
|
||||
&& (radio.DATA[3]>='0' && radio.DATA[3]<='2') && (radio.DATA[5]=='0' || radio.DATA[5]=='1'))
|
||||
{
|
||||
if (radio.ACKRequested()) radio.sendACK(); //send ACK sooner when a ON/OFF + ACK is requested
|
||||
btnIndex = radio.DATA[3]-'0';
|
||||
action(btnIndex, (radio.DATA[5]=='1'?ON:OFF), true); //senderID!=GATEWAYID
|
||||
checkSYNC(senderID);
|
||||
}
|
||||
|
||||
//listen for MOT:x commands where x={0,1,2} - motion activated command to turn ON a button/relay (timed ON)
|
||||
if (radio.DATALEN == 5
|
||||
&& radio.DATA[0]=='M' && radio.DATA[1]=='O' && radio.DATA[2]=='T' && radio.DATA[3] == ':'
|
||||
&& (radio.DATA[4]>='0' && radio.DATA[4]<='2'))
|
||||
{
|
||||
if (radio.ACKRequested()) radio.sendACK(); //send ACK sooner when a ON/OFF + ACK is requested
|
||||
btnIndex = radio.DATA[4]-'0';
|
||||
//if(mode[btnIndex] != ON) //if a light is already ON, ignore MOTION triggered commands and do nothing, uncomment this line to
|
||||
offTimer = millis();
|
||||
offIndex = btnIndex;
|
||||
if(mode[btnIndex] != ON) //only take action when mode is not already ON
|
||||
{
|
||||
action(btnIndex, ON, true); //senderID!=GATEWAYID
|
||||
checkSYNC(senderID);
|
||||
}
|
||||
}
|
||||
|
||||
if (radio.ACKRequested()) //dont ACK broadcasted messages except in special circumstances (like SYNCing)
|
||||
{
|
||||
radio.sendACK();
|
||||
DEBUG(F(" - ACK sent"));
|
||||
//delay(5);
|
||||
}
|
||||
}
|
||||
|
||||
//check if a motion command timer expired and the corresponding light can turn off
|
||||
if ((offTimer > 0) && (millis() - offTimer > MOTION_TIME_ON))
|
||||
{
|
||||
offTimer = 0;
|
||||
if(mode[offIndex] != OFF)
|
||||
{
|
||||
DEBUGln(F("OffTimer expired, turning off"));
|
||||
action(offIndex, OFF, true);
|
||||
checkSYNC(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//sets the mode (ON/OFF) for the current button (btnIndex) and turns SSR ON if the btnIndex points to BTNSSR
|
||||
void action(byte whichButtonIndex, byte whatMode, boolean notifyGateway)
|
||||
{
|
||||
DEBUG(F("\r\nbtn["));
|
||||
DEBUG(whichButtonIndex);
|
||||
DEBUG(F("]:D"));
|
||||
DEBUG(btn[whichButtonIndex]);
|
||||
DEBUG(F(" - "));
|
||||
//DEBUG(btn[whichButtonIndex]==BTNT?F("TOP: "):btn[whichButtonIndex]==BTNM?F("MAIN: "):btn[whichButtonIndex]==BTNB?F("BOTTOM: "):F("UNKNOWN"));
|
||||
DEBUG(btn[whichButtonIndex]==BTNT?F("TOP: "):btn[whichButtonIndex]==BTNB?F("BOTTOM: "):F("UNKNOWN"));
|
||||
DEBUG(whatMode==ON?F("ON "):F("OFF"));
|
||||
|
||||
mode[whichButtonIndex] = whatMode;
|
||||
|
||||
//change LEDs and relay states
|
||||
digitalWrite(btnLEDRED[whichButtonIndex], whatMode == ON ? LOW : HIGH);
|
||||
digitalWrite(btnLEDGRN[whichButtonIndex], whatMode == ON ? HIGH : LOW);
|
||||
if (whichButtonIndex==RELAY1_INDEX)
|
||||
digitalWrite(RELAY1, whatMode == ON ? HIGH : LOW);
|
||||
if (whichButtonIndex==RELAY2_INDEX)
|
||||
digitalWrite(RELAY2, whatMode == ON ? HIGH : LOW); //SwitchMote2x10A has 2 10A relays
|
||||
|
||||
//notify gateway
|
||||
if (notifyGateway)
|
||||
{
|
||||
sprintf(buff, "BTN%d:%d", whichButtonIndex,whatMode);
|
||||
if (radio.sendWithRetry(GATEWAYID, buff, strlen(buff), 5)) //up to 5 attempts
|
||||
{DEBUGln(F("..OK"));}
|
||||
else {DEBUGln(F("..NOK"));}
|
||||
}
|
||||
}
|
||||
|
||||
long blinkLastCycle=0;
|
||||
boolean blinkState=0;
|
||||
void syncBlink(byte LED1, byte LED2)
|
||||
{
|
||||
if (now-blinkLastCycle>=60)
|
||||
{
|
||||
blinkLastCycle=now;
|
||||
blinkState=!blinkState;
|
||||
digitalWrite(LED1,blinkState);
|
||||
digitalWrite(LED2,!blinkState);
|
||||
}
|
||||
}
|
||||
|
||||
//adds a new entry in the SYNC data
|
||||
boolean addSYNC(byte targetAddr, byte targetButton, byte targetMode)
|
||||
{
|
||||
byte emptySlot=255;
|
||||
if (targetAddr==0) return false;
|
||||
|
||||
//traverse all SYNC data and look for an empty slot, or matching slot that should be overwritten
|
||||
for (byte i=0; i < SYNC_MAX_COUNT; i++)
|
||||
{
|
||||
if (SYNC_TO[i]==0 && emptySlot==255) //save first empty slot
|
||||
emptySlot=i; //remember first empty slot anyway
|
||||
else if (SYNC_TO[i]==targetAddr && //save first slot that matches the same button with the same mode in this unit
|
||||
//but different target unit mode (cant have 2 opposing conditions so just override it)
|
||||
getDigit(SYNC_INFO[i],SYNC_DIGIT_SYNCBTN)==targetButton &&
|
||||
getDigit(SYNC_INFO[i],SYNC_DIGIT_THISMODE)==mode[btnIndex]) //getDigit(SYNC_INFO[i],SYNC_DIGIT_SYNCNODE)!=targetMode)
|
||||
{
|
||||
emptySlot=i; //remember matching non-empty slot
|
||||
break; //stop as soon as we found a match
|
||||
}
|
||||
}
|
||||
|
||||
if (emptySlot==255) //means SYNC data is full, do nothing and return
|
||||
{
|
||||
DEBUGln(F("SYNC data full, aborting..."));
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
SYNC_TO[emptySlot] = targetAddr;
|
||||
SYNC_INFO[emptySlot] = targetMode*1000 + targetButton*100 + mode[btnIndex]*10 + btnIndex;
|
||||
DEBUG(F("Saving SYNC_TO["));
|
||||
DEBUG(emptySlot);
|
||||
DEBUG(F("]="));
|
||||
DEBUG(SYNC_TO[emptySlot]);
|
||||
DEBUG(F(" SYNC_INFO = "));
|
||||
DEBUG(SYNC_INFO[emptySlot]);
|
||||
saveSYNC();
|
||||
DEBUGln(F(" .. Saved!"));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
//checks the SYNC table for any necessary requests to other SwitchMotes
|
||||
boolean checkSYNC(byte nodeIDToSkip)
|
||||
{
|
||||
for (byte i=0; i < SYNC_MAX_COUNT; i++)
|
||||
{
|
||||
if (SYNC_TO[i]!=0 && SYNC_TO[i]!=nodeIDToSkip && getDigit(SYNC_INFO[i],SYNC_DIGIT_THISBTN)==btnIndex && getDigit(SYNC_INFO[i],SYNC_DIGIT_THISMODE)==mode[btnIndex])
|
||||
{
|
||||
DEBUGln();
|
||||
DEBUG(F(" SYNC["));
|
||||
DEBUG(SYNC_TO[i]);
|
||||
DEBUG(F(":"));
|
||||
DEBUG(getDigit(SYNC_INFO[i],SYNC_DIGIT_SYNCMODE));
|
||||
DEBUG(F("]:"));
|
||||
sprintf(buff, "BTN%d:%d", getDigit(SYNC_INFO[i],SYNC_DIGIT_SYNCBTN), getDigit(SYNC_INFO[i],SYNC_DIGIT_SYNCMODE));
|
||||
if (radio.sendWithRetry(SYNC_TO[i], buff,6))
|
||||
{DEBUG(F("OK"));}
|
||||
else {DEBUG(F("NOK"));}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void eraseSYNC()
|
||||
{
|
||||
for(byte i = 0; i<SYNC_MAX_COUNT; i++)
|
||||
{
|
||||
SYNC_TO[i]=0;
|
||||
SYNC_INFO[i]=0;
|
||||
}
|
||||
saveSYNC();
|
||||
}
|
||||
|
||||
//saves SYNC_TO and SYNC_INFO arrays to EEPROM
|
||||
void saveSYNC()
|
||||
{
|
||||
EEPROM.writeBlock<byte>(SYNC_EEPROM_ADDR, SYNC_TO, SYNC_MAX_COUNT);
|
||||
EEPROM.writeBlock<byte>(SYNC_EEPROM_ADDR+SYNC_MAX_COUNT, (byte*)SYNC_INFO, SYNC_MAX_COUNT*2);
|
||||
}
|
||||
|
||||
void displaySYNC()
|
||||
{
|
||||
for (byte i=0; i < SYNC_MAX_COUNT; i++)
|
||||
{
|
||||
DEBUG(SYNC_INFO[i]);
|
||||
if (i!=SYNC_MAX_COUNT-1) DEBUG(',');
|
||||
}
|
||||
}
|
||||
|
||||
//returns the Nth digit in an integer
|
||||
byte getDigit(int n, byte pos){return (n/(pos==0?1:pos==1?10:pos==2?100:1000))%10;}
|
||||
|
||||
void blinkLED(byte LEDpin, byte periodON, byte periodOFF, byte repeats)
|
||||
{
|
||||
while(repeats-->0)
|
||||
{
|
||||
digitalWrite(LEDpin, HIGH);
|
||||
delay(periodON);
|
||||
digitalWrite(LEDpin, LOW);
|
||||
delay(periodOFF);
|
||||
}
|
||||
}
|
||||
|
||||
/*CONFIGURATION HELPERS*/
|
||||
void displayMainMenu()
|
||||
{
|
||||
Serial.println();
|
||||
Serial.println(F("|-----------------------------------------------------------"));
|
||||
Serial.println(F("| SwitchMote RFM69 configuration menu "));
|
||||
Serial.println(F("| Use Putty or a similar client to setup params"));
|
||||
Serial.println(F("| ArduinoIDE serial monitor doesn't work well"));
|
||||
Serial.println(F("| Don't forget to save 's' and reboot 'r' to apply changes"));
|
||||
Serial.println(F("|-----------------------------------------------------------"));
|
||||
Serial.print (F("| f - set frequency band (set to: "));Serial.print(CONFIG.frequency==RF69_433MHZ?F("433"):CONFIG.frequency==RF69_868MHZ?F("868"):F("915"));Serial.println(F("mhz)"));
|
||||
Serial.print (F("| i - set node ID (set to: "));Serial.print(CONFIG.nodeID);Serial.println(F(")"));
|
||||
Serial.print (F("| n - set network ID (set to: "));Serial.print(CONFIG.networkID);Serial.println(")");
|
||||
Serial.print (F("| w - set RFM69 type (set to: "));Serial.print(CONFIG.isHW?F("HW/HCW"):F("W/CW"));Serial.println(F(")"));
|
||||
Serial.print (F("| e - set encryption key (set to: '"));Serial.print(CONFIG.encryptionKey);Serial.println(F("')"));
|
||||
Serial.print (F("| d - set description (set to: '"));Serial.print(CONFIG.description);Serial.println(F("')"));
|
||||
Serial.println(F("| s - save CONFIG to EEPROM"));
|
||||
Serial.println(F("| E - erase whole EEPROM - [0..1023]"));
|
||||
Serial.print (F("| S - erase SYNC data ["));Serial.print(SYNC_EEPROM_ADDR);Serial.print(F(".."));Serial.print(SYNC_EEPROM_ADDR+3*SYNC_MAX_COUNT-1);Serial.print(F("]:["));
|
||||
displaySYNC();
|
||||
Serial.println(']');
|
||||
Serial.println(F("| r - reboot"));
|
||||
Serial.println(F("| ESC - re-display config menu"));
|
||||
Serial.println(F("|-----------------------------------------------------------"));
|
||||
Serial.println(F("| Usage ex.: press 'f' to change frequency"));
|
||||
Serial.println(F("|-----------------------------------------------------------"));
|
||||
}
|
||||
|
||||
char menu = 0;
|
||||
byte charsRead = 0;
|
||||
void handleMenuInput(char c)
|
||||
{
|
||||
switch(menu)
|
||||
{
|
||||
case 0:
|
||||
switch(c)
|
||||
{
|
||||
case 'f': Serial.print(F("\r\nEnter frequency ('4'= 433mhz, '8'=868mhz, '9'=915mhz): ")); menu=c; break;
|
||||
case 'i': Serial.print(F("\r\nEnter node ID (1-255 + <ENTER>): ")); CONFIG.nodeID=0;menu=c; break;
|
||||
case 'n': Serial.print(F("\r\nEnter network ID (0-255 + <ENTER>): ")); CONFIG.networkID=0; menu=c; break;
|
||||
case 'e': Serial.print(F("\r\nEnter encryption key (type 16 characters): ")); menu=c; break;
|
||||
case 'w': Serial.print(F("\r\nIs this RFM69W/CW/HW (0=W/CW, 1=HW/HCW): ")); menu=c; break;
|
||||
case 'd': Serial.print(F("\r\nEnter description (10 chars max + <ENTER>): ")); menu=c; break;
|
||||
case 's': Serial.print(F("\r\nCONFIG saved to EEPROM!")); EEPROM.writeBlock(0, CONFIG); break;
|
||||
case 'E': Serial.print(F("\r\nErasing EEPROM ... ")); menu=c; break;
|
||||
case 'S': Serial.print(F("\r\nErasing SYNC EEPROM ... ")); menu=c; break;
|
||||
case 'r': Serial.print(F("\r\nRebooting")); resetUsingWatchdog(1); break;
|
||||
case 27: displayMainMenu();menu=0;break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'f':
|
||||
switch(c)
|
||||
{
|
||||
case '4': Serial.println(F("Set to 433Mhz")); CONFIG.frequency = RF69_433MHZ; menu=0; break;
|
||||
case '8': Serial.println(F("Set to 868Mhz")); CONFIG.frequency = RF69_868MHZ; menu=0; break;
|
||||
case '9': Serial.println(F("Set to 915Mhz")); CONFIG.frequency = RF69_915MHZ; menu=0; break;
|
||||
case 27: displayMainMenu();menu=0;break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'i':
|
||||
if (c >= '0' && c <= '9')
|
||||
{
|
||||
if (CONFIG.nodeID * 10 + c - 48 <= 255)
|
||||
{
|
||||
CONFIG.nodeID = CONFIG.nodeID * 10 + c - 48;
|
||||
Serial.print(c);
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.print(" - Set to ");Serial.println(CONFIG.nodeID);
|
||||
menu=0;
|
||||
}
|
||||
}
|
||||
else if (c == 13 || c == 27)
|
||||
{
|
||||
Serial.print(F(" - Set to "));Serial.println(CONFIG.nodeID);
|
||||
displayMainMenu();
|
||||
menu=0;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'n':
|
||||
if (c >= '0' && c <= '9')
|
||||
{
|
||||
if (CONFIG.networkID * 10 + c - 48 <= 255)
|
||||
{
|
||||
CONFIG.networkID = CONFIG.networkID * 10 + c - 48;
|
||||
Serial.print(c);
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.print(" - Set to ");Serial.println(CONFIG.networkID);
|
||||
menu=0;
|
||||
}
|
||||
}
|
||||
if (c == 13 || c == 27)
|
||||
{
|
||||
Serial.print(" - Set to ");Serial.println(CONFIG.networkID);
|
||||
displayMainMenu();
|
||||
menu=0;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'e':
|
||||
if (c >= ' ' && c <= '~') //human readable chars (32 - 126)
|
||||
if (++charsRead<=16)
|
||||
{
|
||||
CONFIG.encryptionKey[charsRead-1] = c;
|
||||
CONFIG.encryptionKey[charsRead] = 0;
|
||||
Serial.print(c);
|
||||
}
|
||||
if (charsRead >= 16 || c == 27 || c == 13)
|
||||
{
|
||||
//Serial.print(" - Set to [");Serial.print(CONFIG.encryptionKey);Serial.println(']');
|
||||
Serial.println(F(" - DONE"));
|
||||
displayMainMenu();menu=0;charsRead=0;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'd':
|
||||
if (c >= ' ' && c <= '~') //human readable chars (32 - 126)
|
||||
{
|
||||
if (++charsRead<=10)
|
||||
{
|
||||
CONFIG.description[charsRead-1] = c;
|
||||
CONFIG.description[charsRead] = 0;
|
||||
Serial.print(c);
|
||||
}
|
||||
}
|
||||
if (charsRead>=10 || c == 13 || c == 27)
|
||||
{
|
||||
//Serial.print(" - Set to [");Serial.print(CONFIG.description);Serial.println(']');
|
||||
Serial.println(F(" - DONE"));
|
||||
displayMainMenu();menu=0;charsRead=0;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'w':
|
||||
switch(c)
|
||||
{
|
||||
case '0': Serial.println(F("Set to RFM69W\\CW")); CONFIG.isHW = 0; menu=0; break;
|
||||
case '1': Serial.println(F("Set to RFM69HW\\HCW")); CONFIG.isHW = 1; menu=0; break;
|
||||
case 27: displayMainMenu();menu=0;break;
|
||||
}
|
||||
break;
|
||||
case 'E':
|
||||
for (int i=0;i<1024;i++) EEPROM.write(i,255); //eeprom_write_byte((unsigned char *) i, 255);
|
||||
Serial.println(F("DONE"));
|
||||
//resetUsingWatchdog(1);
|
||||
menu=0;
|
||||
break;
|
||||
case 'S':
|
||||
eraseSYNC();
|
||||
Serial.println(F("DONE"));
|
||||
menu=0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
// ***************************************************************************************
|
||||
// Sample RFM69 sketch for Moteino to illustrate:
|
||||
// - sending
|
||||
// - receiving
|
||||
// - automatic transmission control
|
||||
// - button reading/interrupts
|
||||
// ***************************************************************************************
|
||||
// When you press the button on the SENDER Moteino, it will send a short message to the
|
||||
// RECEIVER Moteino and wait for an ACK (acknowledgement that message was received) from
|
||||
// the RECEIVER Moteino. If the ACK was received, the SENDER will blink the onboard LED
|
||||
// a few times. The RECEIVER listens to a specific token, and it alternates the onboard LED
|
||||
// state from HIGH to LOW or vice versa whenever this token is received.
|
||||
// ***************************************************************************************
|
||||
// Hardware setup:
|
||||
// ***************************************************************************************
|
||||
// On the sender, hook up a momentary tactile button to D3 like this:
|
||||
// __-__
|
||||
// __| |___
|
||||
// GND ----> BTN ----> D3 (D11 on MoteinoMEGA)
|
||||
// Load this sketch on the RECEIVER with NODEID=RECEIVER (adjust in config section below)
|
||||
// Load this sketch on the SENDER with NODEID=SENDER (adjust in config section below)
|
||||
// RFM69 library and code by Felix Rusu - felix@lowpowerlab.com
|
||||
// Get libraries at: https://github.com/LowPowerLab/
|
||||
// Make sure you adjust the settings in the configuration section below !!!
|
||||
// **********************************************************************************
|
||||
// Copyright Felix Rusu 2016, http://www.LowPowerLab.com/contact
|
||||
// **********************************************************************************
|
||||
// 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.
|
||||
//
|
||||
// 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 <RFM69.h> //get it here: https://www.github.com/lowpowerlab/rfm69
|
||||
#include <RFM69_ATC.h> //get it here: https://github.com/lowpowerlab/RFM69
|
||||
#include <SPI.h> //included with Arduino IDE (www.arduino.cc)
|
||||
#include <LowPower.h> //get library from: https://github.com/lowpowerlab/lowpower
|
||||
|
||||
//****************************************************************************************************************
|
||||
//**** IMPORTANT RADIO SETTINGS - YOU MUST CHANGE/CONFIGURE TO MATCH YOUR HARDWARE TRANSCEIVER CONFIGURATION! ****
|
||||
//****************************************************************************************************************
|
||||
#define NETWORKID 100 //the same on all nodes that talk to each other
|
||||
#define RECEIVER 1 //unique ID of the gateway/receiver
|
||||
#define SENDER 2
|
||||
#define NODEID RECEIVER //change to "SENDER" if this is the sender node (the one with the button)
|
||||
//Match frequency to the hardware version of the radio on your Moteino (uncomment one):
|
||||
//#define FREQUENCY RF69_433MHZ
|
||||
//#define FREQUENCY RF69_868MHZ
|
||||
#define FREQUENCY RF69_915MHZ
|
||||
#define ENCRYPTKEY "sampleEncryptKey" //exactly the same 16 characters/bytes on all nodes!
|
||||
#define IS_RFM69HW_HCW //uncomment only for RFM69HW/HCW! Leave out if you have RFM69W/CW!
|
||||
//*****************************************************************************************************************************
|
||||
#define ENABLE_ATC //comment out this line to disable AUTO TRANSMISSION CONTROL
|
||||
#define ATC_RSSI -75
|
||||
//*********************************************************************************************
|
||||
#define SERIAL_BAUD 115200
|
||||
#ifdef __AVR_ATmega1284P__
|
||||
#define LED 15 // Moteino MEGAs have LEDs on D15
|
||||
#define BUTTON_INT 1 //user button on interrupt 1 (D3)
|
||||
#define BUTTON_PIN 11 //user button on interrupt 1 (D3)
|
||||
#else
|
||||
#define LED 9 // Moteinos have LEDs on D9
|
||||
#define BUTTON_INT 1 //user button on interrupt 1 (D3)
|
||||
#define BUTTON_PIN 3 //user button on interrupt 1 (D3)
|
||||
#endif
|
||||
|
||||
#define LED_GREEN 4 //GREEN LED on the SENDER
|
||||
#define LED_RED 5 //RED LED on the SENDER
|
||||
#define RX_TOGGLE_PIN 7 //GPIO to toggle on the RECEIVER
|
||||
|
||||
#ifdef ENABLE_ATC
|
||||
RFM69_ATC radio;
|
||||
#else
|
||||
RFM69 radio;
|
||||
#endif
|
||||
|
||||
void setup() {
|
||||
Serial.begin(SERIAL_BAUD);
|
||||
radio.initialize(FREQUENCY,NODEID,NETWORKID);
|
||||
#ifdef IS_RFM69HW_HCW
|
||||
radio.setHighPower(); //must include this only for RFM69HW/HCW!
|
||||
#endif
|
||||
radio.encrypt(ENCRYPTKEY);
|
||||
|
||||
#ifdef ENABLE_ATC
|
||||
radio.enableAutoPower(ATC_RSSI);
|
||||
#endif
|
||||
|
||||
char buff[50];
|
||||
sprintf(buff, "\nListening at %d Mhz...", FREQUENCY==RF69_433MHZ ? 433 : FREQUENCY==RF69_868MHZ ? 868 : 915);
|
||||
Serial.println(buff);
|
||||
Serial.flush();
|
||||
pinMode(BUTTON_PIN, INPUT_PULLUP);
|
||||
pinMode(LED, OUTPUT);
|
||||
attachInterrupt(BUTTON_INT, handleButton, FALLING);
|
||||
|
||||
pinMode(LED_GREEN, OUTPUT);
|
||||
pinMode(LED_RED, OUTPUT);
|
||||
pinMode(RX_TOGGLE_PIN, OUTPUT);
|
||||
digitalWrite(LED_GREEN, LOW);
|
||||
digitalWrite(LED_RED, HIGH);
|
||||
}
|
||||
|
||||
//******** THIS IS INTERRUPT BASED DEBOUNCING FOR BUTTON ATTACHED TO D3 (INTERRUPT 1)
|
||||
#define FLAG_INTERRUPT 0x01
|
||||
volatile int mainEventFlags = 0;
|
||||
boolean buttonPressed = false;
|
||||
void handleButton()
|
||||
{
|
||||
mainEventFlags |= FLAG_INTERRUPT;
|
||||
}
|
||||
|
||||
byte LEDSTATE=LOW; //LOW=0
|
||||
void loop() {
|
||||
//******** THIS IS INTERRUPT BASED DEBOUNCING FOR BUTTON ATTACHED TO D3 (INTERRUPT 1)
|
||||
if (mainEventFlags & FLAG_INTERRUPT)
|
||||
{
|
||||
LowPower.powerDown(SLEEP_120MS, ADC_OFF, BOD_ON);
|
||||
mainEventFlags &= ~FLAG_INTERRUPT;
|
||||
if (!digitalRead(BUTTON_PIN)) {
|
||||
buttonPressed=true;
|
||||
}
|
||||
}
|
||||
|
||||
if (buttonPressed)
|
||||
{
|
||||
Serial.println("Button pressed!");
|
||||
buttonPressed = false;
|
||||
|
||||
if(LEDSTATE==LOW)
|
||||
{
|
||||
LEDSTATE=HIGH;
|
||||
digitalWrite(LED_GREEN, HIGH);
|
||||
digitalWrite(LED_RED, LOW);
|
||||
}
|
||||
else
|
||||
{
|
||||
LEDSTATE=LOW;
|
||||
digitalWrite(LED_GREEN, LOW);
|
||||
digitalWrite(LED_RED, HIGH);
|
||||
}
|
||||
|
||||
if (radio.sendWithRetry(RECEIVER, "Hi", 2)) //target node Id, message as string or byte array, message length
|
||||
Blink(LED, 40, 3); //blink LED 3 times, 40ms between blinks
|
||||
}
|
||||
|
||||
//check if something was received (could be an interrupt from the radio)
|
||||
if (radio.receiveDone())
|
||||
{
|
||||
//print message received to serial
|
||||
Serial.print('[');Serial.print(radio.SENDERID);Serial.print("] ");
|
||||
Serial.print((char*)radio.DATA);
|
||||
Serial.print(" [RX_RSSI:");Serial.print(radio.RSSI);Serial.print("]");
|
||||
Serial.println();
|
||||
|
||||
//check if received message is 2 bytes long, and check if the message is specifically "Hi"
|
||||
if (radio.DATALEN==2 && radio.DATA[0]=='H' && radio.DATA[1]=='i')
|
||||
{
|
||||
if(LEDSTATE==LOW)
|
||||
LEDSTATE=HIGH;
|
||||
else LEDSTATE=LOW;
|
||||
digitalWrite(LED, LEDSTATE);
|
||||
digitalWrite(RX_TOGGLE_PIN, LEDSTATE);
|
||||
}
|
||||
|
||||
//check if sender wanted an ACK
|
||||
if (radio.ACKRequested())
|
||||
{
|
||||
radio.sendACK();
|
||||
Serial.print(" - ACK sent");
|
||||
}
|
||||
}
|
||||
|
||||
radio.receiveDone(); //put radio in RX mode
|
||||
Serial.flush(); //make sure all serial data is clocked out before sleeping the MCU
|
||||
LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_ON); //sleep Moteino in low power mode (to save battery)
|
||||
}
|
||||
|
||||
void Blink(byte PIN, byte DELAY_MS, byte loops)
|
||||
{
|
||||
for (byte i=0; i<loops; i++)
|
||||
{
|
||||
digitalWrite(PIN,HIGH);
|
||||
delay(DELAY_MS);
|
||||
digitalWrite(PIN,LOW);
|
||||
delay(DELAY_MS);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
// **********************************************************************************************************
|
||||
// WeatherShield R2 (BME280 sensor) sameple sketch that works with Moteinos equipped with RFM69W/RFM69HW
|
||||
// It sends periodic weather readings (temp, hum, atm pressure) from WeatherShield to the base node Moteino
|
||||
// For use with MoteinoMEGA you will have to revisit the pin definitions defined below
|
||||
// http://www.LowPowerLab.com/WeatherShield
|
||||
// Example setup (with R1): http://lowpowerlab.com/blog/2015/07/24/attic-fan-cooling-tests/
|
||||
// **********************************************************************************
|
||||
// Copyright Felix Rusu 2016, http://www.LowPowerLab.com/contact
|
||||
// **********************************************************************************
|
||||
// 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.
|
||||
//
|
||||
// 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 <RFM69.h> //get it here: https://github.com/lowpowerlab/rfm69
|
||||
#include <RFM69_ATC.h> //get it here: https://github.com/lowpowerlab/rfm69
|
||||
#include <RFM69_OTA.h> //get it here: https://github.com/lowpowerlab/rfm69
|
||||
#include <SPIFlash.h> //get it here: https://github.com/lowpowerlab/spiflash
|
||||
#include <SPI.h> //included in Arduino IDE (www.arduino.cc)
|
||||
#include <Wire.h> //included in Arduino IDE (www.arduino.cc)
|
||||
#include <SparkFunBME280.h>//get it here: https://github.com/sparkfun/SparkFun_BME280_Breakout_Board/tree/master/Libraries/Arduino/src
|
||||
#include <LowPower.h> //get it here: https://github.com/lowpowerlab/lowpower
|
||||
//writeup here: http://www.rocketscream.com/blog/2011/07/04/lightweight-low-power-arduino-library/
|
||||
|
||||
//*********************************************************************************************
|
||||
//************ IMPORTANT SETTINGS - YOU MUST CHANGE/CONFIGURE TO FIT YOUR HARDWARE ************
|
||||
//*********************************************************************************************
|
||||
#define GATEWAYID 1
|
||||
#define NODEID 40
|
||||
#define NETWORKID 100
|
||||
//#define FREQUENCY RF69_433MHZ
|
||||
//#define FREQUENCY RF69_868MHZ
|
||||
#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_HCW //uncomment only for RFM69HW/HCW! Leave out if you have RFM69W/CW!
|
||||
//*********************************************************************************************
|
||||
#define ENABLE_ATC //comment out this line to disable AUTO TRANSMISSION CONTROL
|
||||
#define ATC_RSSI -75
|
||||
//*********************************************************************************************
|
||||
#define SEND_LOOPS 15 //send data this many sleep loops (15 loops of 8sec cycles = 120sec ~ 2 minutes)
|
||||
#define SLEEP_FASTEST SLEEP_15MS
|
||||
#define SLEEP_FAST SLEEP_250MS
|
||||
#define SLEEP_SEC SLEEP_1S
|
||||
#define SLEEP_LONG SLEEP_2S
|
||||
#define SLEEP_LONGER SLEEP_4S
|
||||
#define SLEEP_LONGEST SLEEP_8S
|
||||
period_t sleepTime = SLEEP_LONGEST; //period_t is an enum type defined in the LowPower library (LowPower.h)
|
||||
//*********************************************************************************************
|
||||
#define BATT_MONITOR_EN A3 //enables battery voltage divider to get a reading from a battery, disable it to save power
|
||||
#define BATT_MONITOR A7 //through 1Meg+470Kohm and 0.1uF cap from battery VCC - this ratio divides the voltage to bring it below 3.3V where it is scaled to a readable range
|
||||
#define BATT_CYCLES 2 //read and report battery voltage every this many sleep cycles (ex 30cycles * 8sec sleep = 240sec/4min). For 450 cyclesyou would get ~1 hour intervals
|
||||
#define BATT_FORMULA(reading) reading * 0.00322 * 1.475 // >>> fine tune this parameter to match your voltage when fully charged
|
||||
#define BATT_LOW 3.6 //(volts)
|
||||
#define BATT_READ_LOOPS SEND_LOOPS*10 // read and report battery voltage every this many sleep cycles (ex 30cycles * 8sec sleep = 240sec/4min). For 450 cycles you would get ~1 hour intervals between readings
|
||||
//*****************************************************************************************************************************
|
||||
|
||||
#ifdef __AVR_ATmega1284P__
|
||||
#define LED 15 // Moteino MEGAs have LEDs on D15
|
||||
#define FLASH_SS 23 // and FLASH SS on D23
|
||||
#else
|
||||
#define LED 9 // Moteinos have LEDs on D9
|
||||
#define FLASH_SS 8 // and FLASH SS on D8
|
||||
#endif
|
||||
|
||||
//#define BLINK_EN //uncomment to blink LED on every send
|
||||
//#define SERIAL_EN //comment out if you don't want any serial output
|
||||
|
||||
#ifdef SERIAL_EN
|
||||
#define SERIAL_BAUD 115200
|
||||
#define DEBUG(input) {Serial.print(input);}
|
||||
#define DEBUGln(input) {Serial.println(input);}
|
||||
#define SERIALFLUSH() {Serial.flush();}
|
||||
#else
|
||||
#define DEBUG(input);
|
||||
#define DEBUGln(input);
|
||||
#define SERIALFLUSH();
|
||||
#endif
|
||||
//*****************************************************************************************************************************
|
||||
|
||||
#ifdef ENABLE_ATC
|
||||
RFM69_ATC radio;
|
||||
#else
|
||||
RFM69 radio;
|
||||
#endif
|
||||
|
||||
SPIFlash flash(FLASH_SS, 0xEF30); //WINDBOND 4MBIT flash chip on CS pin D8 (default for Moteino)
|
||||
|
||||
BME280 bme280;
|
||||
char Pstr[10];
|
||||
char Fstr[10];
|
||||
char Hstr[10];
|
||||
char buffer[50];
|
||||
|
||||
void setup(void)
|
||||
{
|
||||
#ifdef SERIAL_EN
|
||||
Serial.begin(SERIAL_BAUD);
|
||||
#endif
|
||||
pinMode(LED, OUTPUT);
|
||||
|
||||
radio.initialize(FREQUENCY,NODEID,NETWORKID);
|
||||
#ifdef IS_RFM69HW_HCW
|
||||
radio.setHighPower(); //must include this only for RFM69HW/HCW!
|
||||
#endif
|
||||
radio.encrypt(ENCRYPTKEY);
|
||||
|
||||
//Auto Transmission Control - dials down transmit power to save battery (-100 is the noise floor, -90 is still pretty good)
|
||||
//For indoor nodes that are pretty static and at pretty stable temperatures (like a MotionMote) -90dBm is quite safe
|
||||
//For more variable nodes that can expect to move or experience larger temp drifts a lower margin like -70 to -80 would probably be better
|
||||
//Always test your ATC mote in the edge cases in your own environment to ensure ATC will perform as you expect
|
||||
#ifdef ENABLE_ATC
|
||||
radio.enableAutoPower(ATC_RSSI);
|
||||
#endif
|
||||
|
||||
sprintf(buffer, "WeatherMote - transmitting at: %d Mhz...", FREQUENCY==RF69_433MHZ ? 433 : FREQUENCY==RF69_868MHZ ? 868 : 915);
|
||||
DEBUGln(buffer);
|
||||
|
||||
//initialize weather shield BME280 sensor
|
||||
bme280.settings.commInterface = I2C_MODE;
|
||||
bme280.settings.I2CAddress = 0x77;
|
||||
bme280.settings.runMode = 3; //Normal mode
|
||||
bme280.settings.tStandby = 0;
|
||||
bme280.settings.filter = 0;
|
||||
bme280.settings.tempOverSample = 1;
|
||||
bme280.settings.pressOverSample = 1;
|
||||
bme280.settings.humidOverSample = 1;
|
||||
|
||||
radio.sendWithRetry(GATEWAYID, "START", 6);
|
||||
Blink(LED, 100);Blink(LED, 100);Blink(LED, 100);
|
||||
|
||||
if (flash.initialize()) flash.sleep();
|
||||
|
||||
for (uint8_t i=0; i<=A5; i++)
|
||||
{
|
||||
if (i == RF69_SPI_CS) continue;
|
||||
if (i == FLASH_SS) continue;
|
||||
pinMode(i, OUTPUT);
|
||||
digitalWrite(i, LOW);
|
||||
}
|
||||
|
||||
SERIALFLUSH();
|
||||
readBattery();
|
||||
}
|
||||
|
||||
unsigned long doorPulseCount = 0;
|
||||
char input=0;
|
||||
double F,P,H;
|
||||
byte sendLoops=0;
|
||||
byte battReadLoops=0;
|
||||
float batteryVolts = 5;
|
||||
char* BATstr="BAT:5.00v"; //longest battery voltage reading message = 9chars
|
||||
byte sendLen;
|
||||
|
||||
void loop()
|
||||
{
|
||||
if (battReadLoops--<=0) //only read battery every BATT_READ_LOOPS cycles
|
||||
{
|
||||
readBattery();
|
||||
battReadLoops = BATT_READ_LOOPS-1;
|
||||
}
|
||||
|
||||
if (sendLoops--<=0) //send readings every SEND_LOOPS
|
||||
{
|
||||
sendLoops = SEND_LOOPS-1;
|
||||
|
||||
//read BME sensor
|
||||
bme280.begin();
|
||||
P = bme280.readFloatPressure() * 0.0002953; //read Pa and convert to inHg
|
||||
F = bme280.readTempF();
|
||||
H = bme280.readFloatHumidity();
|
||||
bme280.writeRegister(BME280_CTRL_MEAS_REG, 0x00); //sleep the BME280
|
||||
|
||||
dtostrf(F, 3,2, Fstr);
|
||||
dtostrf(H, 3,2, Hstr);
|
||||
dtostrf(P, 3,2, Pstr);
|
||||
|
||||
sprintf(buffer, "BAT:%sv F:%s H:%s P:%s", BATstr, Fstr, Hstr, Pstr);
|
||||
|
||||
sendLen = strlen(buffer);
|
||||
radio.sendWithRetry(GATEWAYID, buffer, sendLen, 1); //retry one time
|
||||
DEBUG(buffer); DEBUG(" (packet length:"); DEBUG(sendLen); DEBUGln(")");
|
||||
|
||||
#ifdef BLINK_EN
|
||||
Blink(LED, 5);
|
||||
#endif
|
||||
}
|
||||
|
||||
//When this sketch is on a node where you can afford the power to keep the radio awake all the time
|
||||
// you can make it receive messages and also make it wirelessly programmable
|
||||
// otherwise this section can be removed
|
||||
if (radio.receiveDone())
|
||||
{
|
||||
boolean reportStatusRequest=false;
|
||||
DEBUG('[');DEBUG(radio.SENDERID);DEBUG("] ");
|
||||
for (byte i = 0; i < radio.DATALEN; i++)
|
||||
DEBUG((char)radio.DATA[i]);
|
||||
|
||||
flash.wakeup();
|
||||
// wireless programming token check - this only works when radio is kept awake to listen for WP tokens
|
||||
CheckForWirelessHEX(radio, flash, true);
|
||||
|
||||
//first send any ACK to request
|
||||
DEBUG(" [RX_RSSI:");DEBUG(radio.RSSI);DEBUG("]");
|
||||
if (radio.ACKRequested())
|
||||
{
|
||||
radio.sendACK();
|
||||
DEBUG(" - ACK sent.");
|
||||
}
|
||||
DEBUGln();
|
||||
}
|
||||
|
||||
SERIALFLUSH();
|
||||
flash.sleep();
|
||||
radio.sleep(); //you can comment out this line if you want this node to listen for wireless programming requests
|
||||
LowPower.powerDown(sleepTime, ADC_OFF, BOD_OFF);
|
||||
DEBUGln("WAKEUP");
|
||||
}
|
||||
|
||||
void readBattery()
|
||||
{
|
||||
unsigned int readings=0;
|
||||
|
||||
//enable battery monitor on WeatherShield (via mosfet controlled by A3)
|
||||
pinMode(BATT_MONITOR_EN, OUTPUT);
|
||||
digitalWrite(BATT_MONITOR_EN, LOW);
|
||||
|
||||
for (byte i=0; i<5; i++) //take several samples, and average
|
||||
readings+=analogRead(BATT_MONITOR);
|
||||
|
||||
//disable battery monitor
|
||||
pinMode(BATT_MONITOR_EN, INPUT); //highZ mode will allow p-mosfet to be pulled high and disconnect the voltage divider on the weather shield
|
||||
|
||||
batteryVolts = BATT_FORMULA(readings / 5.0);
|
||||
dtostrf(batteryVolts,3,2, BATstr); //update the BATStr which gets sent every BATT_CYCLES or along with the MOTION message
|
||||
if (batteryVolts <= BATT_LOW) BATstr = "LOW";
|
||||
}
|
||||
|
||||
void Blink(byte PIN, byte DELAY_MS)
|
||||
{
|
||||
pinMode(PIN, OUTPUT);
|
||||
digitalWrite(PIN,HIGH);
|
||||
delay(DELAY_MS/2);
|
||||
digitalWrite(PIN,LOW);
|
||||
delay(DELAY_MS/2);
|
||||
}
|
||||
@@ -0,0 +1,317 @@
|
||||
#**********************************************************************************
|
||||
# This script will handle the transmission of a compiled sketch in the
|
||||
# form of an INTEL HEX flash image to an attached gateway/master Moteino node,
|
||||
# for further wireless transmission to a target Moteino node that will receive it de-HEXified and
|
||||
# store it in external memory. Once received by the target (which is also loaded with a custom bootloader
|
||||
# capable of reading back that image) it will reset and reprogram itself with the new sketch
|
||||
#
|
||||
# EXAMPLE command line: python WirelessProgramming.py -f PathToFile.hex -s COM100 -t 123
|
||||
# where -t is the target ID of the Moteino you are programming
|
||||
# and -s is the serial port of the programmer Moteino (on linux/osx it is something like ttyAMA0)
|
||||
# To get the .hex file path go to Arduino>file>preferences and check the verbosity for compilation
|
||||
# then you will get the path in the debug status area once the sketch compiles
|
||||
#**********************************************************************************
|
||||
# Copyright Felix Rusu, LowPowerLab.com
|
||||
# Library and code by Felix Rusu - lowpowerlab.com/contact
|
||||
#**********************************************************************************
|
||||
# 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
|
||||
# **********************************************************************************
|
||||
import time, sys, serial
|
||||
import collections
|
||||
import re
|
||||
|
||||
### GENERAL SETTINGS ###
|
||||
SERIALPORT = "COM100" # the default com/serial port the receiver is connected to
|
||||
BAUDRATE = 115200 # default baud rate we talk to Moteino
|
||||
TARGET=0 # Node ID of the Target that is being OTA reflashed
|
||||
HEX = "flash.hex" # the HEX file containing the new program for the Target
|
||||
LINESPERPACKET = 3 # HEX lines to send per RF packet (1,2 or 3)
|
||||
retries = 2
|
||||
DEBUG = False
|
||||
|
||||
# Read command line arguments
|
||||
if (sys.argv and len(sys.argv) > 1):
|
||||
if len(sys.argv)==2 and (sys.argv[1] == "-h" or sys.argv[1] == "-help" or sys.argv[1] == "?"):
|
||||
#print " -d or -debug Turn debugging ON (verbose output)"
|
||||
print " -f or -file HEX file to upload (Default: ", HEX, ")"
|
||||
print " -t or -target {ID} Specify WirelessProgramming node target"
|
||||
print " -l or -lines {1,2,3} HEX lines per RF packet (Default: 3)"
|
||||
print " -s or -serial {port} Specify serial port of WirelessProgramming gateway (Default: ", SERIALPORT, ")"
|
||||
print " -b or -baud {baud} Specify serial port baud rate (Default: ", BAUDRATE, ")"
|
||||
print " -h or -help or ? Print this message"
|
||||
exit(0)
|
||||
|
||||
for i in range(len(sys.argv)):
|
||||
#{
|
||||
#if sys.argv[i] == "-d" or sys.argv[i] == "-debug":
|
||||
# DEBUG = True
|
||||
if (sys.argv[i] == "-s" or sys.argv[i] == "-serial") and len(sys.argv) >= i+2:
|
||||
SERIALPORT = sys.argv[i+1]
|
||||
if (sys.argv[i] == "-b" or sys.argv[i] == "-baud") and len(sys.argv) >= i+2:
|
||||
BAUD = sys.argv[i+1]
|
||||
if (sys.argv[i] == "-f" or sys.argv[i] == "-file") and len(sys.argv) >= i+2:
|
||||
HEX = sys.argv[i+1].strip()
|
||||
if (sys.argv[i] == "-t" or sys.argv[i] == "-target") and len(sys.argv) >= i+2:
|
||||
if sys.argv[i+1].isdigit() and int(sys.argv[i+1])>0 and int(sys.argv[i+1])<=255:
|
||||
TARGET = int(sys.argv[i+1])
|
||||
else:
|
||||
print "TARGET invalid (", sys.argv[i+1], "), must be 1-255."
|
||||
exit(1)
|
||||
if (sys.argv[i] == "-l" or sys.argv[i] == "-lines") and len(sys.argv) >= i+2:
|
||||
if sys.argv[i+1].isdigit() and int(sys.argv[i+1])>0 and int(sys.argv[i+1])<=3:
|
||||
LINESPERPACKET = int(sys.argv[i+1])
|
||||
else:
|
||||
print "LINESPERPACKET invalid (", sys.argv[i+1], "), must be 1-3."
|
||||
exit(1)
|
||||
#}
|
||||
|
||||
def millis():
|
||||
return int(round(time.time() * 1000))
|
||||
|
||||
def serWriteln(ser, msg):
|
||||
#ser.write(msg + '\n')
|
||||
ser.write(bytes((msg+'\n').encode('utf-8')))
|
||||
|
||||
HANDSHAKE_OK = 0
|
||||
HANDSHAKE_FAIL = 1
|
||||
HANDSHAKE_FAIL_TIMEOUT = 2
|
||||
HANDSHAKE_ERROR = 3
|
||||
|
||||
def waitForHandshake(isEOF=False):
|
||||
now = millis()
|
||||
while True:
|
||||
if millis()-now < 4000:
|
||||
#{
|
||||
if isEOF:
|
||||
serWriteln(ser, "FLX?EOF")
|
||||
else:
|
||||
serWriteln(ser, "FLX?")
|
||||
print "FLX?\n"
|
||||
ser.flush()
|
||||
rx = ser.readline().rstrip().upper()
|
||||
|
||||
if len(rx) > 0:
|
||||
#{
|
||||
print "Moteino: [" + rx + "]"
|
||||
if rx == "FLX?OK":
|
||||
print "HANDSHAKE OK!"
|
||||
return HANDSHAKE_OK
|
||||
elif rx == "FLX?NOK":
|
||||
print "HANDSHAKE FAIL [TIMEOUT]: " + rx
|
||||
return HANDSHAKE_FAIL
|
||||
elif (len(rx)>7 and rx.startswith("FLX?NOK") or rx.startswith("FLX?ERR")):
|
||||
print "HANDSHAKE FAIL [HEX IMG refused by target node], reason: " + rx
|
||||
return HANDSHAKE_FAIL_ERROR
|
||||
#}
|
||||
#}
|
||||
else: return HANDSHAKE_FAIL_TIMEOUT
|
||||
|
||||
def waitForTargetSet(targetNode):
|
||||
now = millis()
|
||||
to = "TO:" + str(TARGET)
|
||||
print to
|
||||
serWriteln(ser, to)
|
||||
ser.flush()
|
||||
while True:
|
||||
#{
|
||||
if millis()-now < 3000:
|
||||
#{
|
||||
rx = ser.readline().rstrip()
|
||||
if len(rx) > 0:
|
||||
#{
|
||||
print "Moteino: [" + rx + "]"
|
||||
if rx == to + ":OK":
|
||||
return True
|
||||
else: return False
|
||||
#}
|
||||
#}
|
||||
#}
|
||||
return False
|
||||
|
||||
# return 0:timeout, 1:OK!, 2:match but out of synch
|
||||
def waitForSEQ(seq):
|
||||
now = millis()
|
||||
while True:
|
||||
if millis()-now < 3000:
|
||||
rx = ser.readline().strip()
|
||||
|
||||
if (rx.upper().startswith("RFTX >") or rx.upper().startswith("RFACK >")):
|
||||
#{
|
||||
print "Moteino DEBUG: " + rx
|
||||
rx = ""
|
||||
continue
|
||||
#}
|
||||
|
||||
if len(rx) > 0:
|
||||
#{
|
||||
print "Moteino: " + rx
|
||||
result = re.match("FLX:([0-9]*):OK", rx)
|
||||
if result != None:
|
||||
if int(result.group(1)) == seq:
|
||||
return 1
|
||||
else: return 2
|
||||
#}
|
||||
else: return 0
|
||||
|
||||
# MAIN()
|
||||
#if __name__ == "__main__":
|
||||
try:
|
||||
start = millis();
|
||||
# open up the serial port to get data transmitted to Programmer Moteino
|
||||
ser = serial.Serial(SERIALPORT, BAUDRATE, timeout=1) #timeout=0 means nonblocking
|
||||
ser.setDTR(False)
|
||||
ser.setRTS(False)
|
||||
time.sleep(2) #wait for Programmer Moteino reset after port open and potential bootloader time (~1.6s)
|
||||
ser.flushInput();
|
||||
except IOError as e:
|
||||
print "COM Port [", SERIALPORT, "] not found, exiting..."
|
||||
exitNow(1)
|
||||
|
||||
try:
|
||||
if not 0<TARGET<= 255:
|
||||
print "TARGET not provided (use -h for help), now exiting..."
|
||||
exit(1)
|
||||
|
||||
#send target ID first
|
||||
if waitForTargetSet(TARGET):
|
||||
print "TARGET SET OK"
|
||||
else:
|
||||
print "TARGET SET FAIL, exiting..."
|
||||
exit(1)
|
||||
|
||||
with open(HEX) as f:
|
||||
print "File found, passing to Moteino..."
|
||||
|
||||
handshakeResponse = waitForHandshake()
|
||||
if (handshakeResponse == HANDSHAKE_OK):
|
||||
seq = 0
|
||||
packetCounter = 0
|
||||
content = f.readlines()
|
||||
|
||||
while seq < len(content):
|
||||
#{
|
||||
currentLine = content[seq].strip()
|
||||
isEOF = (content[seq].strip() == ":00000001FF") #this should be the last line in any valid intel HEX file
|
||||
result = -1
|
||||
bundledLines = 1
|
||||
|
||||
if isEOF==False:
|
||||
#{
|
||||
hexDataToSend = currentLine
|
||||
|
||||
#bundle 2 or 3 HEX lines in 1 RF packet (assuming: 1 HEX line has 16 bytes of data, following HEX lines also each being 16 bytes)
|
||||
if LINESPERPACKET > 1 and currentLine[1:3] == '10' and len(currentLine)==43:
|
||||
#{
|
||||
#check if next line != EOF, so we can bundle 2 lines
|
||||
nextLine = content[seq+1].strip()
|
||||
if nextLine != ":00000001FF" and nextLine[1:3] == "10" and len(nextLine) == 43:
|
||||
#{
|
||||
#need to sum: the 2 lines checksums + address bytes of nextLine (to arrive at a correct final checksum of combined 2 lines
|
||||
checksum = int(currentLine[41:43], 16) + int(nextLine[41:43], 16) + int(nextLine[3:5], 16) + int(nextLine[5:7], 16)
|
||||
|
||||
#check if a third line != EOF, so we can bundle 3 lines
|
||||
nextLine2 = content[seq+2].strip()
|
||||
if LINESPERPACKET==3 and nextLine2 != ":00000001FF" and nextLine2[1:3] == "10" and len(nextLine2) == 43:
|
||||
#{
|
||||
#need to sum: the previous checksum + address bytes of nextLine2 (to arrive at a correct final checksum of combined 3 lines
|
||||
checksum += int(nextLine2[41:43], 16) + int(nextLine2[3:5], 16) + int(nextLine2[5:7], 16)
|
||||
hexDataToSend = ":3" + hexDataToSend[2:(len(hexDataToSend)-2)] + nextLine[9:41] + nextLine2[9:41] + ('%0*X' % (2,checksum%256))
|
||||
bundledLines=3
|
||||
#}
|
||||
else:
|
||||
#{
|
||||
hexDataToSend = ":2" + hexDataToSend[2:(len(hexDataToSend)-2)] + nextLine[9:41] + ('%0*X' % (2,checksum%256))
|
||||
bundledLines=2;
|
||||
#}
|
||||
#}
|
||||
#}
|
||||
tx = "FLX:" + str(packetCounter) + hexDataToSend
|
||||
print "TX > " + tx
|
||||
serWriteln(ser, tx)
|
||||
result = waitForSEQ(packetCounter)
|
||||
#}
|
||||
elif waitForHandshake(True) == HANDSHAKE_OK:
|
||||
#{
|
||||
print "SUCCESS! (time elapsed: " + ("%.2f" % ((millis()-start)/1000.0)) + "s)"
|
||||
exit(0);
|
||||
#}
|
||||
else:
|
||||
#{
|
||||
print "FAIL, IMG REFUSED BY TARGET (size exceeded? verify target MCU matches compiled target)"
|
||||
exit(99)
|
||||
#}
|
||||
|
||||
if result == 1:
|
||||
#{
|
||||
seq+=bundledLines
|
||||
packetCounter+=1
|
||||
#}
|
||||
elif result == 2: # out of synch, retry
|
||||
#{
|
||||
if retries > 0:
|
||||
retries-=1
|
||||
print "OUT OF SYNC: retrying...\n"
|
||||
continue
|
||||
else:
|
||||
print "FAIL: out of sync (are you running the latest OTA libs/sources?)"
|
||||
exit(1)
|
||||
#}
|
||||
else:
|
||||
#{
|
||||
if retries > 0:
|
||||
retries-=1
|
||||
print "Timeout, retry...\n"
|
||||
continue
|
||||
else:
|
||||
#{
|
||||
print "FAIL: timeout (are you running the latest OTA libs/sources?)"
|
||||
exit(1)
|
||||
#}
|
||||
#}
|
||||
#}
|
||||
|
||||
while 1:
|
||||
#{
|
||||
rx = ser.readline()
|
||||
if (len(rx) > 0): print rx.strip()
|
||||
#}
|
||||
|
||||
elif (handshakeResponse == HANDSHAKE_FAIL_TIMEOUT):
|
||||
print "FAIL: No response from Moteino programmer, is it connected to " + port
|
||||
exit(1)
|
||||
elif (handshakeResponse == HANDSHAKE_FAIL_TIMEOUT):
|
||||
print "FAIL: No response from Moteino programmer, is it connected to " + port
|
||||
exit(1)
|
||||
else:
|
||||
print "FAIL: No response from Moteino Target, is Target listening on same Freq/NetworkID & OTA enabled?"
|
||||
exit(1)
|
||||
|
||||
except IOError:
|
||||
print "File [", HEX, "] not found, exiting..."
|
||||
exit(1)
|
||||
|
||||
finally:
|
||||
#print 'FINALLY' + '\n'
|
||||
ser.close()
|
||||
@@ -0,0 +1,164 @@
|
||||
// **********************************************************************************
|
||||
// This sketch is an example of how wireless programming can be achieved with a Moteino
|
||||
// that was loaded with a custom 1k bootloader (DualOptiboot) that is capable of loading
|
||||
// a new sketch from an external SPI flash chip
|
||||
// This is the GATEWAY node, it does not need a custom Optiboot nor any external FLASH memory chip
|
||||
// (ONLY the target node will need those)
|
||||
// The sketch includes logic to receive the new sketch from the serial port (from a host computer) and
|
||||
// transmit it wirelessly to the target node
|
||||
// The handshake protocol that receives the sketch from the serial port
|
||||
// is handled by the SPIFLash/WirelessHEX69 library, which also relies on the RFM69 library
|
||||
// These libraries and custom 1k Optiboot bootloader for the target node are at: http://github.com/lowpowerlab
|
||||
// **********************************************************************************
|
||||
// Copyright Felix Rusu 2016, http://www.LowPowerLab.com/contact
|
||||
// **********************************************************************************
|
||||
// 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.
|
||||
//
|
||||
// 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 <RFM69.h> //get it here: https://github.com/lowpowerlab/RFM69
|
||||
#include <RFM69_ATC.h> //get it here: https://github.com/lowpowerlab/RFM69
|
||||
#include <RFM69_OTA.h> //get it here: https://github.com/lowpowerlab/RFM69
|
||||
#include <SPIFlash.h> //get it here: https://www.github.com/lowpowerlab/spiflash
|
||||
#include <SPI.h> //included with Arduino IDE (www.arduino.cc)
|
||||
|
||||
//****************************************************************************************************************
|
||||
//**** IMPORTANT RADIO SETTINGS - YOU MUST CHANGE/CONFIGURE TO MATCH YOUR HARDWARE TRANSCEIVER CONFIGURATION! ****
|
||||
//****************************************************************************************************************
|
||||
#define NODEID 254 //this node's ID, should be unique among nodes on this NETWORKID
|
||||
#define NETWORKID 100 //what network this node is on
|
||||
//Match frequency to the hardware version of the radio on your Moteino (uncomment one):
|
||||
//#define FREQUENCY RF69_433MHZ
|
||||
//#define FREQUENCY RF69_868MHZ
|
||||
#define FREQUENCY RF69_915MHZ
|
||||
#define FREQUENCY_EXACT 916000000
|
||||
#define ENCRYPTKEY "sampleEncryptKey" //(16 bytes of your choice - keep the same on all encrypted nodes)
|
||||
#define IS_RFM69HW_HCW //uncomment only for RFM69HW/HCW! Leave out if you have RFM69W/CW!
|
||||
//*********************************************************************************************
|
||||
#define ENABLE_ATC //comment out this line to disable AUTO TRANSMISSION CONTROL
|
||||
//*********************************************************************************************
|
||||
//#define BR_300KBPS //run radio at max rate of 300kbps!
|
||||
//*********************************************************************************************
|
||||
#define DEBUG_MODE false //set 'true' to see verbose output from programming sequence
|
||||
|
||||
#define SERIAL_BAUD 115200
|
||||
#define ACK_TIME 50 // # of ms to wait for an ack
|
||||
#define TIMEOUT 3000
|
||||
|
||||
#ifdef __AVR_ATmega1284P__
|
||||
#define LED 15 // MoteinoMEGA has LED on D15
|
||||
#else
|
||||
#define LED 9 // Moteino has LED on D9
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_ATC
|
||||
RFM69_ATC radio;
|
||||
#else
|
||||
RFM69 radio;
|
||||
#endif
|
||||
|
||||
char c = 0;
|
||||
char input[64]; //serial input buffer
|
||||
byte targetID=0;
|
||||
|
||||
void setup(){
|
||||
Serial.begin(SERIAL_BAUD);
|
||||
radio.initialize(FREQUENCY,NODEID,NETWORKID);
|
||||
radio.encrypt(ENCRYPTKEY); //OPTIONAL
|
||||
|
||||
#ifdef FREQUENCY_EXACT
|
||||
radio.setFrequency(FREQUENCY_EXACT); //set frequency to some custom frequency
|
||||
#endif
|
||||
|
||||
#ifdef IS_RFM69HW_HCW
|
||||
radio.setHighPower(); //must include this only for RFM69HW/HCW!
|
||||
#endif
|
||||
Serial.println("Start wireless gateway...");
|
||||
|
||||
#ifdef BR_300KBPS
|
||||
radio.writeReg(0x03, 0x00); //REG_BITRATEMSB: 300kbps (0x006B, see DS p20)
|
||||
radio.writeReg(0x04, 0x6B); //REG_BITRATELSB: 300kbps (0x006B, see DS p20)
|
||||
radio.writeReg(0x19, 0x40); //REG_RXBW: 500kHz
|
||||
radio.writeReg(0x1A, 0x80); //REG_AFCBW: 500kHz
|
||||
radio.writeReg(0x05, 0x13); //REG_FDEVMSB: 300khz (0x1333)
|
||||
radio.writeReg(0x06, 0x33); //REG_FDEVLSB: 300khz (0x1333)
|
||||
radio.writeReg(0x29, 240); //set REG_RSSITHRESH to -120dBm
|
||||
#endif
|
||||
}
|
||||
|
||||
void loop(){
|
||||
byte inputLen = readSerialLine(input, 10, 64, 100); //readSerialLine(char* input, char endOfLineChar=10, byte maxLength=64, uint16_t timeout=1000);
|
||||
|
||||
if (inputLen==4 && input[0]=='F' && input[1]=='L' && input[2]=='X' && input[3]=='?') {
|
||||
if (targetID==0)
|
||||
Serial.println("TO?");
|
||||
else
|
||||
CheckForSerialHEX((byte*)input, inputLen, radio, targetID, TIMEOUT, ACK_TIME, DEBUG_MODE);
|
||||
}
|
||||
else if (inputLen>3 && inputLen<=6 && input[0]=='T' && input[1]=='O' && input[2]==':')
|
||||
{
|
||||
byte newTarget=0;
|
||||
for (byte i = 3; i<inputLen; i++) //up to 3 characters for target ID
|
||||
if (input[i] >=48 && input[i]<=57)
|
||||
newTarget = newTarget*10+input[i]-48;
|
||||
else
|
||||
{
|
||||
newTarget=0;
|
||||
break;
|
||||
}
|
||||
if (newTarget>0)
|
||||
{
|
||||
targetID = newTarget;
|
||||
Serial.print("TO:");
|
||||
Serial.print(newTarget);
|
||||
Serial.println(":OK");
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.print(input);
|
||||
Serial.print(":INV");
|
||||
}
|
||||
}
|
||||
else if (inputLen>0) { //just echo back
|
||||
Serial.print("SERIAL IN > ");Serial.println(input);
|
||||
}
|
||||
|
||||
if (radio.receiveDone())
|
||||
{
|
||||
for (byte i = 0; i < radio.DATALEN; i++)
|
||||
Serial.print((char)radio.DATA[i]);
|
||||
|
||||
if (radio.ACK_REQUESTED)
|
||||
{
|
||||
radio.sendACK();
|
||||
Serial.print(" - ACK sent");
|
||||
}
|
||||
|
||||
Serial.println();
|
||||
}
|
||||
Blink(LED,5); //heartbeat
|
||||
}
|
||||
|
||||
void Blink(byte PIN, int DELAY_MS)
|
||||
{
|
||||
pinMode(PIN, OUTPUT);
|
||||
digitalWrite(PIN,HIGH);
|
||||
delay(DELAY_MS);
|
||||
digitalWrite(PIN,LOW);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
# Wireless Programming for Moteinos
|
||||
|
||||
## How to use
|
||||
Since v1.5 you can now run this app in several ways:
|
||||
- natively via the WirelessProgramming.exe GUI app
|
||||
- the windows GUI can also invoke the OTA.py script via embedded IronPython engine (parameters from GUI pass to the OTA.py script)
|
||||
- cross platform straight from Python (2.7 runtime) by supplying parameters (run `python OTA.py -h` for details)
|
||||
<br/>
|
||||
Make sure to download the entire repo ZIP, not individual files.
|
||||
@@ -0,0 +1,198 @@
|
||||
// **********************************************************************************
|
||||
// This sketch is an example of how wireless programming can be achieved with a Moteino
|
||||
// that was loaded with a custom 1k bootloader (DualOptiboot) that is capable of loading
|
||||
// a new sketch from an external SPI flash chip
|
||||
// The sketch includes logic to receive the new sketch 'over-the-air' and store it in
|
||||
// the FLASH chip, then restart the Moteino so the bootloader can continue the job of
|
||||
// actually reflashing the internal flash memory from the external FLASH memory chip flash image
|
||||
// The handshake protocol that receives the sketch wirelessly by means of the RFM69 radio
|
||||
// is handled by the SPIFLash/RFM69_OTA library, which also relies on the RFM69 library
|
||||
// These libraries and custom 1k Optiboot bootloader are at: http://github.com/lowpowerlab
|
||||
// **********************************************************************************
|
||||
// Copyright Felix Rusu 2016, http://www.LowPowerLab.com/contact
|
||||
// **********************************************************************************
|
||||
// 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.
|
||||
//
|
||||
// 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 <RFM69.h> //get it here: https://github.com/lowpowerlab/RFM69
|
||||
#include <RFM69_ATC.h> //get it here: https://github.com/lowpowerlab/RFM69
|
||||
#include <RFM69_OTA.h> //get it here: https://github.com/lowpowerlab/RFM69
|
||||
#include <SPIFlash.h> //get it here: https://github.com/lowpowerlab/spiflash
|
||||
#include <SPI.h> //included with Arduino IDE install (www.arduino.cc)
|
||||
|
||||
//****************************************************************************************************************
|
||||
//**** IMPORTANT RADIO SETTINGS - YOU MUST CHANGE/CONFIGURE TO MATCH YOUR HARDWARE TRANSCEIVER CONFIGURATION! ****
|
||||
//****************************************************************************************************************
|
||||
#define NODEID 123 // node ID used for this unit
|
||||
#define NETWORKID 100
|
||||
//Match frequency to the hardware version of the radio on your Moteino (uncomment one):
|
||||
//#define FREQUENCY RF69_433MHZ
|
||||
//#define FREQUENCY RF69_868MHZ
|
||||
#define FREQUENCY RF69_915MHZ
|
||||
#define FREQUENCY_EXACT 916000000
|
||||
#define IS_RFM69HW_HCW //uncomment only for RFM69HW/HCW! Leave out if you have RFM69W/CW!
|
||||
//*****************************************************************************************************************************
|
||||
#define ENABLE_ATC //comment out this line to disable AUTO TRANSMISSION CONTROL
|
||||
#define ATC_RSSI -75
|
||||
//*****************************************************************************************************************************
|
||||
//#define BR_300KBPS //run radio at max rate of 300kbps!
|
||||
//*****************************************************************************************************************************
|
||||
#define SERIAL_BAUD 115200
|
||||
#define ACK_TIME 30 // # of ms to wait for an ack
|
||||
#define ENCRYPTKEY "sampleEncryptKey" //(16 bytes of your choice - keep the same on all encrypted nodes)
|
||||
#define BLINKPERIOD 500
|
||||
|
||||
#ifdef __AVR_ATmega1284P__
|
||||
#define LED 15 // Moteino MEGAs have LEDs on D15
|
||||
#define FLASH_SS 23 // and FLASH SS on D23
|
||||
#else
|
||||
#define LED 9 // Moteinos hsave LEDs on D9
|
||||
#define FLASH_SS 8 // and FLASH SS on D8
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_ATC
|
||||
RFM69_ATC radio;
|
||||
#else
|
||||
RFM69 radio;
|
||||
#endif
|
||||
|
||||
char input = 0;
|
||||
long lastPeriod = -1;
|
||||
|
||||
//*****************************************************************************************************************************
|
||||
// flash(SPI_CS, MANUFACTURER_ID)
|
||||
// SPI_CS - CS pin attached to SPI flash chip (8 in case of Moteino)
|
||||
// MANUFACTURER_ID - OPTIONAL, 0x1F44 for adesto(ex atmel) 4mbit flash
|
||||
// 0xEF30 for windbond 4mbit flash
|
||||
// 0xEF40 for windbond 16/64mbit flash
|
||||
//*****************************************************************************************************************************
|
||||
SPIFlash flash(FLASH_SS, 0xEF30); //EF30 for windbond 4mbit flash
|
||||
|
||||
void setup() {
|
||||
pinMode(LED, OUTPUT);
|
||||
Serial.begin(SERIAL_BAUD);
|
||||
radio.initialize(FREQUENCY,NODEID,NETWORKID);
|
||||
radio.encrypt(ENCRYPTKEY); //OPTIONAL
|
||||
|
||||
#ifdef FREQUENCY_EXACT
|
||||
radio.setFrequency(FREQUENCY_EXACT); //set frequency to some custom frequency
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_ATC
|
||||
radio.enableAutoPower(ATC_RSSI);
|
||||
#endif
|
||||
|
||||
#ifdef IS_RFM69HW_HCW
|
||||
radio.setHighPower(); //must include this only for RFM69HW/HCW!
|
||||
#endif
|
||||
Serial.print("Start node...");
|
||||
Serial.print("Node ID = ");
|
||||
Serial.println(NODEID);
|
||||
|
||||
if (flash.initialize())
|
||||
Serial.println("SPI Flash Init OK!");
|
||||
else
|
||||
Serial.println("SPI Flash Init FAIL!");
|
||||
|
||||
#ifdef BR_300KBPS
|
||||
radio.writeReg(0x03, 0x00); //REG_BITRATEMSB: 300kbps (0x006B, see DS p20)
|
||||
radio.writeReg(0x04, 0x6B); //REG_BITRATELSB: 300kbps (0x006B, see DS p20)
|
||||
radio.writeReg(0x19, 0x40); //REG_RXBW: 500kHz
|
||||
radio.writeReg(0x1A, 0x80); //REG_AFCBW: 500kHz
|
||||
radio.writeReg(0x05, 0x13); //REG_FDEVMSB: 300khz (0x1333)
|
||||
radio.writeReg(0x06, 0x33); //REG_FDEVLSB: 300khz (0x1333)
|
||||
radio.writeReg(0x29, 240); //set REG_RSSITHRESH to -120dBm
|
||||
#endif
|
||||
}
|
||||
|
||||
void loop(){
|
||||
// This part is optional, useful for some debugging.
|
||||
// Handle serial input (to allow basic DEBUGGING of FLASH chip)
|
||||
// ie: display first 256 bytes in FLASH, erase chip, write bytes at first 10 positions, etc
|
||||
if (Serial.available() > 0) {
|
||||
input = Serial.read();
|
||||
if (input == 'd') //d=dump first page
|
||||
{
|
||||
Serial.println("Flash content:");
|
||||
int counter = 0;
|
||||
|
||||
while(counter<=256){
|
||||
Serial.print(flash.readByte(counter++), HEX);
|
||||
Serial.print('.');
|
||||
}
|
||||
|
||||
Serial.println();
|
||||
}
|
||||
else if (input == 'e')
|
||||
{
|
||||
Serial.print("Erasing Flash chip ... ");
|
||||
flash.chipErase();
|
||||
while(flash.busy());
|
||||
Serial.println("DONE");
|
||||
}
|
||||
else if (input == 'i')
|
||||
{
|
||||
Serial.print("DeviceID: ");
|
||||
Serial.println(flash.readDeviceId(), HEX);
|
||||
}
|
||||
else if (input == 'r')
|
||||
{
|
||||
Serial.print("Rebooting");
|
||||
resetUsingWatchdog(true);
|
||||
}
|
||||
else if (input == 'R')
|
||||
{
|
||||
Serial.print("RFM69 registers:");
|
||||
radio.readAllRegs();
|
||||
}
|
||||
else if (input >= 48 && input <= 57) //0-9
|
||||
{
|
||||
Serial.print("\nWriteByte("); Serial.print(input); Serial.print(")");
|
||||
flash.writeByte(input-48, millis()%2 ? 0xaa : 0xbb);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for existing RF data, potentially for a new sketch wireless upload
|
||||
// For this to work this check has to be done often enough to be
|
||||
// picked up when a GATEWAY is trying hard to reach this node for a new sketch wireless upload
|
||||
if (radio.receiveDone())
|
||||
{
|
||||
Serial.print("Got [");
|
||||
Serial.print(radio.SENDERID);
|
||||
Serial.print(':');
|
||||
Serial.print(radio.DATALEN);
|
||||
Serial.print("] > ");
|
||||
for (byte i = 0; i < radio.DATALEN; i++)
|
||||
Serial.print((char)radio.DATA[i], HEX);
|
||||
Serial.println();
|
||||
CheckForWirelessHEX(radio, flash, false);
|
||||
Serial.println();
|
||||
}
|
||||
//else Serial.print('.');
|
||||
|
||||
//*****************************************************************************************************************************
|
||||
// Real sketch code here, let's blink the onboard LED
|
||||
if ((int)(millis()/BLINKPERIOD) > lastPeriod)
|
||||
{
|
||||
lastPeriod++;
|
||||
digitalWrite(LED, lastPeriod%2);
|
||||
}
|
||||
//*****************************************************************************************************************************
|
||||
}
|
||||
Binary file not shown.
Reference in new issue
Block a user