RFM69_LowPowerLab/Examples/MightyHat/MightyHat.ino

984 lines
37 KiB
Arduino
Raw Normal View History

2015-12-11 02:06:18 +00:00
// **********************************************************************************************************
// 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) and vice versa.
// It will buffer the serial data to ensure host serial requests are not missed.
2015-12-11 02:06:18 +00:00
// http://LowPowerLab.com/MightyHat
// PiGateway project: http://LowPowerLab.com/gateway
// **********************************************************************************
// Copyright Felix Rusu 2020, http://www.LowPowerLab.com/contact
// **********************************************************************************
#define MHAT_VERSION 3 //latest is R4, only change to "2" if you have a MightyHat R2
2016-03-09 03:59:41 +00:00
// ****************************************************************************************
#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 <PString.h> //easy string manipulator: http://arduiniana.org/libraries/pstring/
#include <Streaming.h> //easy C++ style output operators: http://arduiniana.org/libraries/streaming/
#include "U8glib.h" //https://bintray.com/olikraus/u8glib/Arduino
//u8g compared to adafruit lib: https://www.youtube.com/watch?v=lkWZuAnHa2Y
//drawing bitmaps: https://www.coconauts.net/blog/2015/01/19/easy-draw-bitmaps-arduino/
//*****************************************************************************************************************************
// ADJUST THE SETTINGS BELOW DEPENDING ON YOUR HARDWARE/SCENARIO !
//*****************************************************************************************************************************
2015-12-17 23:31:22 +00:00
#define NODEID 1 //the gateway has ID=1
#define NETWORKID 200 //all nodes on the same network can talk to each other
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)
//#define FREQUENCY_EXACT 916000000 //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!
2020-04-17 17:33:46 +01:00
#define IS_RFM69HW_HCW //required for RFM69HW/HCW, comment out for RFM69W/CW!
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/
#define ENABLE_WIRELESS_PROGRAMMING //comment out this line to disable Wireless Programming of this gateway node
2020-05-07 16:55:38 +01: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
//*****************************************************************************************************************************
2020-04-17 17:33:46 +01:00
#define SERIAL_BAUD 19200
#define DEBUG_EN //comment out if you don't want any serial verbose output (keep out in real use)
2015-12-11 02:06:18 +00:00
#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 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
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
#define LED_HIGH digitalWrite(LED_BUILTIN, HIGH)
#define LED_LOW digitalWrite(LED_BUILTIN, LOW)
//******************************************** BEGIN ADVANCED variables ********************************************************************************
#define RAMSIZE 2048
#define MAX_BUFFER_LENGTH 25 //limit parameter update requests to 20 chars. ex: Parameter:LongRequest
#define MAX_ACK_REQUEST_LENGTH 30 //60 is max for ACK (with ATC enabled), but need to allow appending :OK and :INV to confirmations from node
typedef struct req {
uint16_t nodeId;
char data[MAX_BUFFER_LENGTH]; //+1 for the null terminator
struct req *next;
}REQUEST;
//dynamically allocated queue (FIFO) data structure
REQUEST* queue = NULL;
byte size_of_queue = 0;
//******************************************** END ADVANCED variables ********************************************************************************
//******************************************** BEGIN GENERAL variables ********************************************************************************
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(SS_FLASHMEM, 0xEF30); //EF30 for 4mbit Windbond FLASH MEM
2015-12-24 02:10:05 +00:00
#ifdef ENABLE_ATC
RFM69_ATC radio;
#else
RFM69 radio;
#endif
//******************************************** END GENERAL variables ********************************************************************************
2015-12-24 02:10:05 +00:00
//******************************************** BEGIN LCD STUFF ********************************************************************************
char buff[80];
PString Pbuff(buff, sizeof(buff)); //easy string manipulator
2016-03-09 03:59:41 +00:00
#ifdef ENABLE_LCD
#if defined(MHAT_VERSION) && (MHAT_VERSION >= 3)
2016-03-09 03:59:41 +00:00
#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
#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
//******************************************** 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 **************************************************************************
2015-12-11 02:06:18 +00:00
void refreshLCD() {
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 = buff;
if (historyLength > 0)
textp = messageHistory[currMessageIndex].data;
2015-12-11 02:06:18 +00:00
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;
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
lcd.print(systemVoltage);
2020-04-17 15:39:36 +01:00
lcd.setPrintPos(0, 40);
uint16_t uptimeSeconds = millis()/1000;
Pbuff="";
if (uptimeSeconds<60)
Pbuff << "up" << uptimeSeconds << 's';
else
Pbuff << "up:" << (uptimeSeconds/60) << 'm';
lcd.print(buff);
lcd.setPrintPos(45, 40);
Pbuff="";
Pbuff << "RAM:" << freeRAM();
lcd.print(buff);
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);
if (rssi !=0) {
Pbuff="";
Pbuff << rssi << "dBm";
lcd.drawStr(xbmp_rssi_width+1, 48, buff);
}
2015-12-11 02:06:18 +00:00
} while(lcd.nextPage());
2016-01-18 15:33:29 +00:00
digitalWrite(PIN_LCD_CS, HIGH);
interrupts(); //re-enable interrupts
2015-12-11 02:06:18 +00:00
}
#endif //ENABLE_LCD
2015-12-24 02:10:05 +00:00
//******************************************** END LCD STUFF ********************************************************************************
2015-12-11 02:06:18 +00:00
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);
2020-05-07 16:55:38 +01:00
#ifdef ENABLE_LCD
2016-01-18 15:33:29 +00:00
pinMode(PIN_LCD_CS, OUTPUT);
digitalWrite(PIN_LCD_CS, HIGH);
2020-05-07 16:55:38 +01:00
#endif
2015-12-11 02:06:18 +00:00
pinMode(LATCH_VAL, OUTPUT);
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() {
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
Pbuff="";
Pbuff << "Rebooting Pi..";
saveToHistory(buff, 0);
2015-12-11 02:06:18 +00:00
refreshLCD();
#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
Pbuff="";
Pbuff << "Reboot OK!";
saveToHistory(buff, 0);
2015-12-17 15:13:03 +00:00
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
Pbuff="";
Pbuff << "Battery low! Shutting down Pi..";
saveToHistory(buff, 0);
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 {
Pbuff="";
Pbuff << "Shutting down Pi..";
saveToHistory(buff, 0);
}
2015-12-11 02:06:18 +00:00
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);
//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)
{
Pbuff="";
Pbuff << "Pi is now OFF";
saveToHistory(buff, 0);
2015-12-17 15:01:00 +00:00
refreshLCD();
}
2015-12-24 02:10:05 +00:00
#endif
2015-12-11 02:06:18 +00:00
digitalWrite(SIG_SHUTOFF, LOW);
}
else if (PowerState == ON && !BOOTOK())
{
2015-12-24 02:10:05 +00:00
#ifdef ENABLE_LCD
Pbuff="";
Pbuff << "Forced shutdown..";
saveToHistory(buff, 0);
2015-12-11 02:06:18 +00:00
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
Pbuff="";
Pbuff << "Pi is now OFF";
saveToHistory(buff, 0);
2015-12-17 15:01:00 +00:00
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
Pbuff="";
Pbuff << "Pi is now ON";
saveToHistory(buff, 0);
2016-01-28 21:40:05 +00:00
refreshLCD();
#endif
2015-12-11 02:06:18 +00:00
}
}
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);
2016-01-28 03:16:06 +00:00
#ifdef ENABLE_LCD
if (backlightLevel==BACKLIGHTLEVELS) backlightLevel=0;
else backlightLevel++;
LCD_BACKLIGHT(backlightLevel);
2016-01-28 03:16:06 +00:00
#endif
}
//button 2 - message history
if (digitalRead(BUTTON2)==LOW)
{
buttonsLastChanged=millis();
Beep(3, false);
2020-05-07 16:55:38 +01:00
#ifdef ENABLE_LCD
if (historyLength > 0) //if at least 1 data packet was received and saved to history...
{
rssi = messageHistory[currMessageIndex].rssi; //save the history rssi for the LCDRefresh signal icon
2016-01-28 03:16:06 +00:00
#ifdef ENABLE_LCD
Pbuff="";
Pbuff << "<HIST[" << currMessageIndex+1 << '/' << historyLength << "]>" << endl << messageHistory[currMessageIndex].data;
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
}
2020-05-07 16:55:38 +01:00
#endif
}
}
2015-12-11 02:06:18 +00:00
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)
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);
}
}
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);
#ifdef ENCRYPTKEY
2015-12-11 02:06:18 +00:00
radio.encrypt(ENCRYPTKEY);
#endif
2020-04-17 17:33:46 +01:00
#ifdef IS_RFM69HW_HCW
radio.setHighPower();
#endif
Pbuff="";
Pbuff << "Listening @ " << radio.getFrequency() << "Hz";
DEBUGln(buff);
2020-04-17 17:33:46 +01:00
if (flash.initialize()) DEBUGln("SPI_Flash_Init_OK");
else DEBUGln(F("SPI_Flash_Init_FAIL"));
2016-01-08 20:24:07 +00:00
#ifdef FREQUENCY_EXACT
radio.setFrequency(FREQUENCY_EXACT); //set frequency to some custom frequency
#endif
2015-12-11 02:06:18 +00:00
2015-12-24 02:10:05 +00:00
readBattery();
2020-04-17 17:33:46 +01:00
DEBUG(F("DEBUG:freeRAM():"));DEBUGln(freeRAM());
2015-12-24 02:10:05 +00:00
#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
2015-12-11 02:06:18 +00:00
drawLogo();
LCD_BACKLIGHT(backlightLevel);
2015-12-11 02:06:18 +00:00
delay(2000);
refreshLCD();
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
handle2Buttons(); //checks the general purpose buttons next to the LCD (R2+)
handleSerialData(); //checks for any serial input from the Pi computer
2015-12-11 02:06:18 +00:00
//process any received radio packets
if (radio.receiveDone())
{
LED_HIGH;
rssi = radio.RSSI; //get this asap from transceiver
if (radio.DATALEN > 0) //data packets have a payload
2015-12-11 02:06:18 +00:00
{
for (byte i=9;i<radio.DATALEN;i++) {
if (radio.DATA[i]=='\n' || radio.DATA[i]=='\r')
radio.DATA[i]=' '; //remove any newlines in the payload - this should only ever happen with noise data that actually made it through
}
Pbuff="";
Pbuff << '[' << radio.SENDERID << "] " << (char*)radio.DATA;
Serial << buff << F(" SS:") << rssi << endl; //this passes data to MightyHat / RaspberryPi
#ifdef ENABLE_LCD
saveToHistory(buff, rssi);
#endif
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
#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())
{
REQUEST* aux=queue;
Pbuff="";
//walk queue and add pending commands to ACK payload (as many it can fit)
while (aux!=NULL) {
if (aux->nodeId==radio.SENDERID)
{
//check if payload has room to add this queued command
if (Pbuff.length() + 1 + strlen(aux->data) <= MAX_ACK_REQUEST_LENGTH)
{
if (Pbuff.length()) Pbuff.print(' '); //prefix with a space any previous command in buffer
Pbuff.print(aux->data); //append command
}
}
aux=aux->next;
}
if (Pbuff.length())
radio.sendACK(buff, Pbuff.length());
else
radio.sendACK();
2015-12-11 02:06:18 +00:00
}
LED_LOW;
2015-12-11 02:06:18 +00:00
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();
}
LCD_BACKLIGHT(batteryLow ? 0 : backlightLevel);
2015-12-24 02:10:05 +00:00
#endif
}
boolean insert(uint16_t new_id, char new_data[]) {
REQUEST* aux;
REQUEST* new_node = (REQUEST*)malloc(sizeof(REQUEST));
if (new_node == NULL) return false;
new_node->nodeId = new_id;
strcpy(new_node->data, new_data);
new_node->next = NULL;
if (queue == NULL) queue = new_node;
else {
aux = queue;
while(aux->next != NULL) aux=aux->next;
aux->next=new_node;
}
return true;
}
//processCommand - parse the command and send it to target
//if target is non-responsive it(sleeppy node?) then queue command to send when target wakes and asks for an ACK
//SPECIAL COMMANDS FROM HOST:
// - 123:VOID - removes all queued commands for node 123
// - 123:VOID:command - removes 'command' from queue (if found)
// - REQUESTQUEUE - prints the queued list of nodes on serial port, to host (Pi?)
// - FREERAM - returns # of unallocated bytes at end of heap
// - SYSFREQ - returns operating frequency in Hz
// - UPTIME - returns millis()
void processCommand(char data[], boolean allowDuplicate=false) {
char *ptr;
char dataPart[MAX_BUFFER_LENGTH];
uint16_t targetId;
byte sendLen = 0;
ptr = strtok(data, ":");
if (strcmp(data, "FREERAM")==0)
Serial << F("FREERAM:") << freeRAM() << ':' << RAMSIZE << endl;
if (strcmp(data, "REQUESTQUEUE")==0)
printQueue(queue);
if (strcmp(data, "SYSFREQ")==0)
Serial << F("SYSFREQ:") << radio.getFrequency() << endl;
if (strcmp(data, "UPTIME")==0)
Serial << F("UPTIME:") << millis() << endl;
if (strcmp(data, "BEEP")==0) Beep(5, false);
if (strcmp(data, "BEEP2")==0) Beep(10, false);
if (strcmp(data, "ENCRYPTKEY")==0)
#ifdef ENCRYPTKEY
Serial << F("ENCRYPTKEY:") << ENCRYPTKEY << endl;
#else
Serial << F("ENCRYPTKEY:NONE") << endl;
#endif
if(ptr != NULL) { // delimiter found, valid command
sprintf(dataPart, "%s", ptr);
targetId = atoi(dataPart); // get nodeID part
ptr = strtok(NULL, ""); // get command part
sprintf(dataPart, "%s", ptr);
//check for empty command
if (strlen(dataPart) == 0) return;
//check target nodeID is valid
if (targetId > 0 && targetId != NODEID && targetId<=1023)
{
REQUEST* aux;
byte removed=0;
//check if VOID command - if YES then remove command(s) to that target nodeID
if (strstr(dataPart, "VOID")==dataPart) //string starts with VOID
{
//if 'nodeId:VOID' then remove all commands to that node
//if 'nodeId:VOID:REQUEST' then remove just 'REQUEST' (if found & identical match)
boolean removeAll=true;
if (dataPart[4]==':' && strlen(dataPart)>5)
removeAll=false;
//iterate over queue
aux = queue;
while(aux != NULL)
{
if (aux->nodeId==targetId)
{
if (removeAll || (!removeAll && strcmp(aux->data, dataPart+5)==0))
{
if (aux == queue)
{
if (aux->next == NULL)
{
free(queue);
queue=NULL;
removed++;
break;
}
else
{
queue = queue->next;
free(aux);
removed++;
aux = queue;
continue;
}
}
else
{
REQUEST* prev=queue;
while(prev->next != NULL && prev->next != aux) prev = prev->next; //find previous
if (prev->next == NULL) break;
prev->next=prev->next->next;
free(aux);
removed++;
aux=prev->next;
}
}
else aux=aux->next;
}
else aux=aux->next;
}
2020-04-17 17:33:46 +01:00
DEBUG("DEBUG:VOIDED_commands:");DEBUGln(removed);
size_of_queue = size_of_queue - removed;
return;
}
//try sending to node, if it fails, continue & add to pending commands queue
LED_HIGH;
if (radio.sendWithRetry(targetId, dataPart, strlen(dataPart)))
{
LED_LOW;
return;
}
LED_LOW;
//check for duplicate
if (!allowDuplicate) {
//walk queue and check for duplicates
aux = queue;
while(aux != NULL)
{
2020-04-17 17:33:46 +01:00
//DEBUGln("While");
if (aux->nodeId==targetId)
{
if (strcmp(aux->data, dataPart)==0)
{
2020-04-17 17:33:46 +01:00
//DEBUGln(F("processCommand() skip (duplicate)"));
return;
}
}
aux = aux->next;
}
}
//all checks OK, attempt to add to queue
if (insert(targetId, dataPart))
{
2020-04-17 17:33:46 +01:00
//DEBUG(F("-> inserted: "));
//DEBUG(targetId);
//DEBUG("_");
//DEBUGln(dataPart);
size_of_queue++;
}
else
{
2020-04-17 17:33:46 +01:00
DEBUGln(F("DEBUG:INSERT_FAIL:MEM_FULL"));
Serial << F("[") << targetId << F("] ") << dataPart << F(":MEMFULL") << endl;
}
}
}
}
void printQueue(REQUEST* p) {
if (!size_of_queue) {
Serial << F("REQUESTQUEUE:VOID") << endl;
return;
}
REQUEST* aux=p;
while (aux!=NULL) {
Serial << F("REQUESTQUEUE:") << aux->nodeId << ':' << aux->data << endl;
aux=aux->next;
}
}
// here's the processing of single char/bytes as soon as they're coming from UART
void handleSerialData() {
static char input_line[100]; //static = these get allocated ONCE!
static byte input_pos = 0;
if(Serial.available() > 0)
{
char inByte = Serial.read();
switch (inByte)
{
case '\r': //ignore carriage return
break;
case '\n':
if (input_pos==0) break; // ignore empty lines
input_line[input_pos] = 0; // null terminate the string
2020-04-17 17:33:46 +01:00
DEBUG("DEBUG:handleSerialData:");
DEBUGln(input_line);
processCommand(input_line); // fill up queue
input_pos = 0; // reset buffer for next time
break;
default:
// keep adding if not full ... allow for terminating byte
if (input_pos < MAX_BUFFER_LENGTH-1) {
input_line[input_pos] = inByte;
input_pos++;
} else {
// if theres no EOL coming before MAX_BUFF_CHARS is exceeded we'll just terminate and send it, last char is then lost
input_line[input_pos] = 0; // null terminate the string
2020-04-17 17:33:46 +01:00
DEBUG("DEBUG:MAX_BUFF_CHARS is exceeded - attempting to add (default): ");
DEBUGln(input_line);
processCommand(input_line); //add to queue
input_pos = 0; //reset buffer for next line
}
break;
}
}
}
//returns # of unfragmented free RAM bytes (free end of heap)
int freeRAM() {
#ifdef __arm__
char top;
return &top - reinterpret_cast<char*>(sbrk(0));
#else
extern int __heap_start, *__brkval;
int v;
return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
#endif
}
//returns total # of free RAM bytes (all free heap, including fragmented memory)
int allFreeRAM()
{
int size = 1024;
byte *buf;
while ((buf = (byte *) malloc(--size)) == NULL);
free(buf);
return size;
2020-05-07 16:55:38 +01:00
}