2015-12-11 02:06:18 +00:00
// **********************************************************************************************************
// MightyHat gateway base unit sketch that works with MightyHat with onboard RFM69W/RFM69HW
// This will relay all RF data over serial to the host computer (RaspberryPi, PC etc) and vice versa
// http://LowPowerLab.com/MightyHat
// Copyright http://www.LowPowerLab.com (2015)
// Also see http://LowPowerLab.com/gateway
//**********************************************************************************
// 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
// **********************************************************************************
2016-03-09 03:59:41 +00:00
# define MHAT_VERSION 3 //latest version is R3, change to "2" if you have a MightyHat R2
// ****************************************************************************************
2015-12-11 02:06:18 +00:00
# include <RFM69.h> //get it here: http://github.com/lowpowerlab/rfm69
# include <RFM69_ATC.h> //get it here: https://www.github.com/lowpowerlab/rfm69
# include <SPIFlash.h> //get it here: http://github.com/lowpowerlab/spiflash
# include <WirelessHEX69.h> //get it here: https://github.com/LowPowerLab/WirelessProgramming
# include <SPI.h> //comes 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/
//*****************************************************************************************************************************
2015-12-24 02:10:05 +00:00
// ADJUST THE SETTINGS BELOW DEPENDING ON YOUR HARDWARE/SCENARIO !
2015-12-11 02:06:18 +00:00
//*****************************************************************************************************************************
2015-12-17 23:31:22 +00:00
# define NODEID 1 //the gateway has ID=1
# define NETWORKID 100 //all nodes on the same network can talk to each other
2016-01-28 03:16:06 +00:00
//#define FREQUENCY RF69_433MHZ //Match this with the version of your Moteino! (others: RF69_433MHZ, RF69_868MHZ)
2015-12-11 02:06:18 +00:00
# define FREQUENCY RF69_915MHZ //Match this with the version of your Moteino! (others: RF69_433MHZ, RF69_868MHZ)
2016-01-22 19:39:51 +00:00
//#define FREQUENCY_EXACT 917000000 //uncomment and set to a specific frequency in Hz, if commented the center frequency is used
2015-12-11 02:06:18 +00:00
# define ENCRYPTKEY "sampleEncryptKey" //has to be same 16 characters/bytes on all nodes, not more not less!
# define IS_RFM69HW //uncomment only for RFM69HW! Leave out if you have RFM69W!
2015-12-24 02:10:05 +00:00
# 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/
2016-01-22 19:39:51 +00:00
//#define ENABLE_WIRELESS_PROGRAMMING //comment out this line to disable Wireless Programming of this gateway node
2015-12-24 02:10:05 +00:00
# define ENABLE_LCD //comment this out if you don't have or don't want to use the LCD
2015-12-11 02:06:18 +00:00
//*****************************************************************************************************************************
# define ACK_TIME 30 // # of ms to wait for an ack
2016-07-27 01:15:58 +01:00
# define SERIAL_BAUD 19200
2015-12-11 02:06:18 +00:00
# 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
2016-01-22 19:39:51 +00:00
# define BUTTON1 A4 // Backlight control button
# define BUTTON2 A5 // Backlight control button
2015-12-11 02:06:18 +00:00
# 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 )
2015-12-17 15:01:00 +00:00
# define LOWBATTERYTHRESHOLD 3.5 // a shutdown will be triggered to the target device when battery voltage drops below this (Volts)
2015-12-11 02:06:18 +00:00
# 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
2015-12-17 23:31:22 +00:00
# define BATTERYREADINTERVAL 2000
2015-12-11 02:06:18 +00:00
# ifdef DEBUG_EN
# define DEBUG(input) Serial.print(input)
# define DEBUGln(input) Serial.println(input)
# else
# define DEBUG(input)
# define DEBUGln(input)
# endif
2015-12-24 02:10:05 +00:00
//general variables
byte ackCount = 0 ;
String inputstr ;
byte inputLen = 0 ;
char RSSIstr [ ] = " -100dBm " ;
2016-01-22 19:39:51 +00:00
char temp [ 64 ] ;
2015-12-24 02:10:05 +00:00
2016-01-22 19:39:51 +00:00
byte lastValidReading = 1 ;
2015-12-24 02:10:05 +00:00
unsigned long lastValidReadingTime = 0 ;
unsigned long NOW = 0 ;
byte PowerState = OFF ;
long lastPeriod = - 1 ;
int rssi = 0 ;
float systemVoltage = 5 ;
2016-01-08 20:24:07 +00:00
float systemVoltagePrevious = 5 ;
2015-12-24 02:10:05 +00:00
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
2016-03-09 03:59:41 +00:00
# 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
2015-12-11 02:06:18 +00:00
# define PIN_LCD_DC A0 //Pin 3 on LCD
# define PIN_LCD_LIGHT 3 //Backlight pin
2015-12-24 02:10:05 +00:00
# define xbmp_logo_width 30
# define xbmp_logo_height 27
2016-01-22 19:39:51 +00:00
# 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
2015-12-11 02:06:18 +00:00
const uint8_t xbmp_logo [ ] PROGMEM = {
2015-12-24 02:10:05 +00:00
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 } ;
2015-12-11 02:06:18 +00:00
# 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 } ;
2015-12-24 02:10:05 +00:00
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
2015-12-11 02:06:18 +00:00
2015-12-24 02:10:05 +00:00
//******************************************** LCD FUNCTIONS ********************************************************************************
2015-12-11 02:06:18 +00:00
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 ( ) ) ;
}
2016-01-28 03:16:06 +00:00
void clearDisplay ( ) { lcd . firstPage ( ) ; do { } while ( lcd . nextPage ( ) ) ; }
2015-12-11 02:06:18 +00:00
void refreshLCD ( ) {
2016-01-22 19:39:51 +00:00
noInterrupts ( ) ; //while messing with LCD need to pause interrups from radio to avoid SPI conflicts!
2015-12-11 02:06:18 +00:00
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 ;
2015-12-17 23:31:22 +00:00
//this section splits the textp string into chunks that fit on the screen width and prints each to a new line
2015-12-11 02:06:18 +00:00
while ( textLength & & ! done )
{
for ( i = 1 ; i < = textLength ; i + + )
{
c = textp [ i ] ;
textp [ i ] = 0 ;
swidth = lcd . getStrWidth ( textp ) ;
textp [ i ] = c ;
2016-01-22 19:39:51 +00:00
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
2015-12-11 02:06:18 +00:00
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 ( ) ;
2015-12-17 23:31:22 +00:00
//print battery voltage and icon
2015-12-11 02:06:18 +00:00
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 ) ;
2015-12-24 02:10:05 +00:00
2016-01-08 20:24:07 +00:00
lcd . setPrintPos ( 54 , 48 ) ;
2015-12-11 02:06:18 +00:00
if ( systemVoltage > = CHARGINGTHRESHOLD )
2016-01-08 20:24:07 +00:00
lcd . print ( " CHRG " ) ;
else
2015-12-24 02:10:05 +00:00
lcd . print ( systemVoltage ) ; //sprintf(BATvstr, "%sv", BATstr);
2016-01-22 19:39:51 +00:00
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 ) ;
2015-12-11 02:06:18 +00:00
2015-12-17 23:31:22 +00:00
//print rssi and icon
2015-12-11 02:06:18 +00:00
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 ( ) ) ;
2016-01-18 15:33:29 +00:00
digitalWrite ( PIN_LCD_CS , HIGH ) ;
2016-01-22 19:39:51 +00:00
interrupts ( ) ; //re-enable interrupts
2015-12-11 02:06:18 +00:00
}
2015-12-24 02:10:05 +00:00
# endif
//******************************************** END LCD STUFF ********************************************************************************
2015-12-11 02:06:18 +00:00
2016-01-22 19:39:51 +00:00
//******************************************** 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 + + ;
2016-01-22 20:21:22 +00:00
currMessageIndex = historyLength - 1 ; //reset history pointer back to latest message
2016-01-22 19:39:51 +00:00
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 **************************************************************************
2015-12-11 02:06:18 +00:00
//parse through any serial commands from the host (Pi)
void handleSerialInput ( ) {
2016-01-22 19:39:51 +00:00
inputLen = readSerialLine ( temp , 10 , 64 , 10 ) ; //readSerialLine(char* input, char endOfLineChar=10, byte maxLength=64, uint16_t timeout=10);
2015-12-11 02:06:18 +00:00
if ( inputLen > 0 )
{
2016-01-28 21:40:05 +00:00
DEBUG ( " GTWCMD: " ) ; DEBUGln ( temp ) ;
2016-01-22 19:39:51 +00:00
inputstr = String ( temp ) ;
2015-12-24 02:10:05 +00:00
inputstr . toUpperCase ( ) ;
2016-01-28 03:16:06 +00:00
2016-01-22 19:39:51 +00:00
if ( inputstr . equals ( " BEEP " ) ) Beep ( 5 , false ) ;
2015-12-11 02:06:18 +00:00
if ( inputstr . equals ( " BEEP2 " ) ) Beep ( 10 , true ) ;
if ( inputstr . equals ( " RAM " ) ) { DEBUG ( F ( " Free RAM bytes: " ) ) ; DEBUGln ( checkFreeRAM ( ) ) ; }
2016-01-28 21:40:05 +00:00
if ( inputstr . equals ( " KEY? " ) ) { Serial . print ( F ( " ENCRYPTKEY: " ) ) ; Serial . println ( ENCRYPTKEY ) ; }
2015-12-11 02:06:18 +00:00
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 )
{
2016-01-22 19:39:51 +00:00
inputstr . getBytes ( ( byte * ) temp , 61 ) ;
if ( radio . sendWithRetry ( targetId , ( byte * ) temp , inputstr . length ( ) ) )
2016-01-28 21:40:05 +00:00
DEBUGln ( F ( " ACK:OK " ) ) ;
2015-12-11 02:06:18 +00:00
else
2016-01-28 21:40:05 +00:00
DEBUGln ( F ( " ACK:NOK " ) ) ;
2015-12-11 02:06:18 +00:00
}
}
}
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 ) ;
2016-01-08 20:24:07 +00:00
digitalWrite ( LATCH_EN , LOW ) ;
2016-01-18 15:33:29 +00:00
pinMode ( PIN_LCD_CS , OUTPUT ) ;
digitalWrite ( PIN_LCD_CS , HIGH ) ;
2015-12-11 02:06:18 +00:00
pinMode ( LATCH_VAL , OUTPUT ) ;
2016-01-22 19:39:51 +00:00
pinMode ( BUTTON1 , INPUT_PULLUP ) ;
pinMode ( BUTTON2 , INPUT_PULLUP ) ;
2015-12-11 02:06:18 +00:00
pinMode ( BATTERYSENSE , INPUT ) ;
digitalWrite ( SIG_SHUTOFF , LOW ) ; //added after sudden shutdown quirks, DO NOT REMOVE!
}
void handlePowerControl ( ) {
2016-01-22 19:39:51 +00:00
byte reading = digitalRead ( BUTTON ) ;
2015-12-11 02:06:18 +00:00
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
{
2015-12-24 02:10:05 +00:00
# ifdef ENABLE_LCD
2015-12-11 02:06:18 +00:00
sprintf ( lcdbuff , " Rebooting Pi.. " ) ;
refreshLCD ( ) ;
2015-12-24 02:10:05 +00:00
# endif
2015-12-11 02:06:18 +00:00
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 )
2015-12-17 15:13:03 +00:00
{
2015-12-24 02:10:05 +00:00
# ifdef ENABLE_LCD
2015-12-17 15:13:03 +00:00
sprintf ( lcdbuff , " Reboot OK! " ) ;
refreshLCD ( ) ;
2015-12-24 02:10:05 +00:00
# endif
2015-12-11 02:06:18 +00:00
return ;
2015-12-17 15:13:03 +00:00
}
2015-12-11 02:06:18 +00:00
}
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 ) {
2015-12-24 02:10:05 +00:00
# ifdef ENABLE_LCD
2015-12-11 02:06:18 +00:00
sprintf ( lcdbuff , " Battery low! Shutting down Pi.. " ) ;
2015-12-24 02:10:05 +00:00
# endif
2015-12-11 02:06:18 +00:00
batteryLowShutdown = true ;
}
2015-12-24 02:10:05 +00:00
# ifdef ENABLE_LCD
else
2015-12-11 02:06:18 +00:00
sprintf ( lcdbuff , " Shutting down Pi.. " ) ;
refreshLCD ( ) ;
2015-12-24 02:10:05 +00:00
# endif
2015-12-11 02:06:18 +00:00
// 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 ) ;
}
2015-12-24 02:10:05 +00:00
# ifdef ENABLE_LCD
2015-12-17 15:01:00 +00:00
if ( PowerState = = OFF )
{
sprintf ( lcdbuff , " Pi is now OFF " ) ;
refreshLCD ( ) ;
}
2015-12-24 02:10:05 +00:00
# endif
2015-12-11 02:06:18 +00:00
digitalWrite ( SIG_SHUTOFF , LOW ) ;
//DEBUGln("SIG_SHUTOFF - LOW");
}
else if ( PowerState = = ON & & ! BOOTOK ( ) )
{
2015-12-24 02:10:05 +00:00
# ifdef ENABLE_LCD
2015-12-11 02:06:18 +00:00
sprintf ( lcdbuff , " Forced shutdown.. " ) ;
refreshLCD ( ) ;
2015-12-24 02:10:05 +00:00
# endif
2015-12-11 02:06:18 +00:00
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 ) ;
2015-12-24 02:10:05 +00:00
# ifdef ENABLE_LCD
2015-12-17 15:01:00 +00:00
sprintf ( lcdbuff , " Pi is now OFF " ) ;
refreshLCD ( ) ;
2015-12-24 02:10:05 +00:00
# endif
2015-12-11 02:06:18 +00:00
break ;
}
}
}
else if ( PowerState = = OFF )
{
PowerState = ON ;
batteryLowShutdown = false ;
POWER ( PowerState ) ;
2016-01-28 21:40:05 +00:00
# ifdef ENABLE_LCD
sprintf ( lcdbuff , " Pi is now ON " ) ;
refreshLCD ( ) ;
# endif
2015-12-11 02:06:18 +00:00
}
}
if ( PowerState = = ON ) POWER_LED_GRN ( ) else POWER_LED_OFF ( ) ; //digitalWrite(POWER_LED, PowerState);
}
}
2016-01-22 19:39:51 +00:00
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 ) ;
2016-01-28 03:16:06 +00:00
# ifdef ENABLE_LCD
2016-01-22 19:39:51 +00:00
if ( backlightLevel = = BACKLIGHTLEVELS ) backlightLevel = 0 ;
else backlightLevel + + ;
LCD_BACKLIGHT ( backlightLevel ) ;
sprintf ( lcdbuff , " LCDlight:%d/100 " , 100 * backlightLevel / BACKLIGHTLEVELS ) ;
refreshLCD ( ) ;
2016-01-28 03:16:06 +00:00
# endif
2016-01-22 19:39:51 +00:00
}
//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
2016-01-28 03:16:06 +00:00
# ifdef ENABLE_LCD
2016-01-22 19:39:51 +00:00
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
2016-01-28 03:16:06 +00:00
# endif
2016-01-22 20:21:22 +00:00
if ( currMessageIndex = = 0 ) currMessageIndex = historyLength - 1 ; else currMessageIndex - - ; //this makes it cycle from the latest message towards oldest as you press BTN2
2016-01-22 19:39:51 +00:00
}
}
}
2015-12-11 02:06:18 +00:00
boolean BOOTOK ( ) {
2016-01-22 19:39:51 +00:00
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)
2015-12-11 02:06:18 +00:00
}
void POWER ( uint8_t ON_OFF ) {
digitalWrite ( LATCH_EN , HIGH ) ;
2016-01-28 03:16:06 +00:00
digitalWrite ( LATCH_VAL , ON_OFF ) ;
2015-12-11 02:06:18 +00:00
delay ( 5 ) ;
digitalWrite ( LATCH_EN , LOW ) ;
2016-01-28 03:16:06 +00:00
delay ( 5 ) ;
# ifdef ENABLE_LCD
2016-01-18 15:33:29 +00:00
digitalWrite ( PIN_LCD_CS , HIGH ) ; //if shared with LATCH_VAL, should be HIGH when not used by latch
2016-01-28 03:16:06 +00:00
# endif
2015-12-11 02:06:18 +00:00
}
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
2015-12-17 23:31:22 +00:00
int currPeriod = millis ( ) / BATTERYREADINTERVAL ;
2015-12-11 02:06:18 +00:00
if ( currPeriod ! = lastPeriod )
{
lastPeriod = currPeriod ;
systemVoltage = BATTERY_VOLTS ( analogRead ( BATTERYSENSE ) ) ;
2015-12-24 02:10:05 +00:00
//dtostrf(systemVoltage, 3,2, BATstr);
2015-12-11 02:06:18 +00:00
batteryLow = systemVoltage < LOWBATTERYTHRESHOLD ;
return true ; //signal that batt has been read
}
return false ;
}
void setup ( ) {
2016-01-08 20:24:07 +00:00
Beep ( 20 , false ) ; delay ( 50 ) ; Beep ( 20 , false ) ; delay ( 50 ) ; Beep ( 20 , false ) ;
2015-12-11 02:06:18 +00:00
setupPowerControl ( ) ;
Serial . begin ( SERIAL_BAUD ) ;
radio . initialize ( FREQUENCY , NODEID , NETWORKID ) ;
radio . encrypt ( ENCRYPTKEY ) ;
# ifdef IS_RFM69HW
radio . setHighPower ( ) ; //only for RFM69HW!
# endif
sprintf ( lcdbuff , " Listening @ %dmhz... " , FREQUENCY = = RF69_433MHZ ? 433 : FREQUENCY = = RF69_868MHZ ? 868 : 915 ) ;
DEBUGln ( lcdbuff ) ;
if ( flash . initialize ( ) ) DEBUGln ( " SPI Flash Init OK! " ) ;
2016-01-08 20:24:07 +00:00
else DEBUGln ( F ( " SPI Flash MEM FAIL! " ) ) ;
# ifdef FREQUENCY_EXACT
radio . setFrequency ( FREQUENCY_EXACT ) ; //set frequency to some custom frequency
# endif
2015-12-11 02:06:18 +00:00
# ifdef ENABLE_ATC
DEBUGln ( F ( " RFM69_ATC Enabled (Auto Transmission Control) " ) ) ;
# endif
2015-12-24 02:10:05 +00:00
readBattery ( ) ;
DEBUG ( F ( " Free RAM bytes: " ) ) ; DEBUG ( checkFreeRAM ( ) ) ;
# ifdef ENABLE_LCD
2016-01-22 19:39:51 +00:00
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
2015-12-11 02:06:18 +00:00
drawLogo ( ) ;
2016-01-22 19:39:51 +00:00
LCD_BACKLIGHT ( backlightLevel ) ;
2015-12-11 02:06:18 +00:00
delay ( 2000 ) ;
refreshLCD ( ) ;
2016-01-22 19:39:51 +00:00
delay ( 1000 ) ;
2015-12-24 02:10:05 +00:00
# endif
2015-12-11 02:06:18 +00:00
}
boolean newPacketReceived ;
void loop ( ) {
handlePowerControl ( ) ; //checks any button presses and takes action
2016-01-22 19:39:51 +00:00
handle2Buttons ( ) ; //checks the general purpose buttons next to the LCD (R2+)
2015-12-11 02:06:18 +00:00
handleSerialInput ( ) ; //checks for any serial input from the Pi computer
//process any received radio packets
if ( radio . receiveDone ( ) )
{
rssi = radio . RSSI ;
2016-01-22 19:39:51 +00:00
if ( radio . DATALEN > 0 ) //data packets have a payload
2015-12-11 02:06:18 +00:00
{
sprintf ( lcdbuff , " [%d] %s " , radio . SENDERID , radio . DATA ) ;
sprintf ( RSSIstr , " %ddBm " , rssi ) ;
2016-01-22 19:39:51 +00:00
Serial . print ( lcdbuff ) ; //this passes data to MightyHat / RaspberryPi
2015-12-11 02:06:18 +00:00
Serial . print ( F ( " [RSSI: " ) ) ; Serial . print ( rssi ) ; Serial . print ( ' ] ' ) ;
2016-01-22 19:39:51 +00:00
saveToHistory ( lcdbuff , rssi ) ;
2015-12-11 02:06:18 +00:00
}
//check if the packet is a wireless programming request
2015-12-17 15:01:00 +00:00
//removing this line will save 3kb+ of flash space
# ifdef ENABLE_WIRELESS_PROGRAMMING
2015-12-11 02:06:18 +00:00
CheckForWirelessHEX ( radio , flash , false ) ; //non verbose DEBUG
2015-12-17 15:01:00 +00:00
# endif
2015-12-11 02:06:18 +00:00
//respond to any ACK if requested
if ( radio . ACKRequested ( ) )
{
radio . sendACK ( ) ;
DEBUG ( F ( " [ACK-sent] " ) ) ;
}
2015-12-17 23:31:22 +00:00
//DEBUG(F("Free RAM bytes: "));DEBUG(checkFreeRAM());
2015-12-11 02:06:18 +00:00
Serial . println ( ) ;
Blink ( LED , 2 ) ;
newPacketReceived = true ;
}
2015-12-17 23:31:22 +00:00
2015-12-18 04:49:57 +00:00
readBattery ( ) ;
2015-12-24 02:10:05 +00:00
# ifdef ENABLE_LCD
2016-01-08 20:24:07 +00:00
if ( newPacketReceived | | systemVoltagePrevious - systemVoltage > 0.01 | | systemVoltagePrevious - systemVoltage < - 0.1 )
2015-12-17 23:31:22 +00:00
{
2016-01-08 20:24:07 +00:00
systemVoltagePrevious = systemVoltage ;
2015-12-17 23:31:22 +00:00
newPacketReceived = false ;
refreshLCD ( ) ;
}
2016-01-22 19:39:51 +00:00
LCD_BACKLIGHT ( batteryLow ? 0 : backlightLevel ) ;
2015-12-24 02:10:05 +00:00
# endif
2015-12-17 15:01:00 +00:00
}