//DMD(Dot Matrix Display) used in this project:
//https://github.com/freetronics/DMD
// #include <Arduino.h>
#include <RFM69.h> //get it here: https://github.com/lowpowerlab/rfm69
#include <RFM69_ATC.h> //get it here: https://github.com/lowpowerlab/RFM69
#include <TimeAlarms.h>
#include <Time.h>
#include <SPI.h> //SPI.h must be included as DMD is written by SPI (the IDE complains otherwise)
#include <DMD.h>
#include <TimerOne.h>
#include "SystemFont5x7.h"
#include "Arial_black_16.h"
//Fire up the DMD library as dmd
#define DISPLAYS_ACROSS 1
#define DISPLAYS_DOWN 1
DMD dmd(DISPLAYS_ACROSS, DISPLAYS_DOWN);
//LED, blinking every x seconds
#define LED_PIN 9
bool ledIsON = false;
//-------
// IMPORTANT RADIO SETTINGS - YOU MUST CHANGE/CONFIGURE TO MATCH YOUR HARDWARE TRANSCEIVER CONFIGURATION!
//-------
#define GATEWAYID 1
#define NODEID 111
#define NETWORKID 123
#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 "Removed" //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
#ifdef ENABLE_ATC
RFM69_ATC radio;
#else
RFM69 radio;
#endif
#define USE_SERIAL // to remove all Serial code when testing actual power consumption
#define SERIAL_BAUD_RATE 115200
//New temperature data is sent every 10 minutes. In case we don't receive new temperature data, we erase old data
#define KEEP_DATA_SEC 0 // 0 seconds
#define KEEP_DATA_MIN 15 //15 minutes
#ifdef USE_SERIAL
#define PRINT(x) Serial.print(x)
#define PRINTLN(x) Serial.println(x)
#define SERIAL_BEGIN Serial.begin(SERIAL_BAUD_RATE)
#else
#define PRINT(x)
#define PRINTLN(x)
#define SERIAL_BEGIN
#endif
//TIME: constants for time sync
#define TIMECMDLEN 11 //T + 10 digits (time_t unix timestamp) e.g. T1398273354
#define TIMEHEADER 'T' //Time sync header (1 char)
#define TIMENEEDSSYNC "SNC" //Message that is sent to server note for sync request
//DMD - currently displayed minute (default -1)
int currentMinute = -1;
//check if the command is time sync command (e.g. T1398096798)
boolean processTimeSync(char* sourceCommand)
{
if (sourceCommand[0] != TIMEHEADER) {
return false;
}
PRINT("Time sync:"); PRINTLN(sourceCommand);
time_t serverTime = 0;
char c;
int timeLength = 0;
for(int i=0; i < TIMECMDLEN ; i++) {
c = sourceCommand[i];
if( c >= '0' && c <= '9') {
serverTime = (10 * serverTime) + (c - '0') ; //convert char to number
timeLength++;
}
}
//10 digits, without "T" header
if (timeLength == TIMECMDLEN - 1) {
setTime(serverTime);
}
return true;
}
//format time string HH:MM
void formatCurrentTime(char *timeString) {
if (timeStatus() == timeNotSet) {
timeString[0] = '\0';
strcat(timeString, "--:--");
timeString[5] = '\0';
} else {
timeString[0] = '\0';
char cstr[16];
itoa(hour(), cstr, 10);
strcat(timeString, cstr);
strcat(timeString, ":");
int min = minute();
if (min < 10) {
strcat(timeString, '0');
}
itoa(min, cstr, 10);
strcat(timeString, cstr);
}
}
/*--------------------------------------------------------------------------------------
Interrupt handler for Timer1 (TimerOne) driven DMD refresh scanning, this gets
called at the period set in Timer1.initialize();
--------------------------------------------------------------------------------------*/
void ScanDMD()
{
dmd.scanDisplayBySPI();
}
//moteino radio
void initRadio() {
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
}
void initDMD() {
//initialize TimerOne's interrupt/CPU usage used to scan and refresh the display
Timer1.initialize(5000); //period in microseconds to call ScanDMD. Anything longer than 5000 (5ms) and you can see flicker.
Timer1.attachInterrupt(ScanDMD); //attach the Timer1 interrupt to ScanDMD which goes to dmd.scanDisplayBySPI()
//clear/init the DMD pixels held in RAM
dmd.clearScreen(true); //true is normal (all pixels off), false is negative (all pixels on)
//dmd.selectFont(Arial_Black_8);
dmd.selectFont(System5x7);
}
void setup() {
SERIAL_BEGIN; // start the serial interface
pinMode(LED_PIN, OUTPUT);
delay(200);
initRadio();
radio.sendWithRetry(GATEWAYID, "START", 6);
initDMD();
PRINTLN("Started");
//zahteva za uskladitev časa
radio.sendWithRetry(GATEWAYID, TIMENEEDSSYNC, 6);
}
//-99.9°C //7 chars + \0
char tempAir[8] = "\0";
char tempWater[8] = "\0";
char currentTime[6];// = "\0";
AlarmId airTimer; //timer - used for deleting old air temperature data
// AlarmId waterTimer;
//Turn on/off led diode every 5 seconds
AlarmId blinkTimer = Alarm.timerRepeat(5, blinkLed);
//messages:
//M#Hello world\0
void loop() {
Alarm.delay(0); //za TimeAlarm, moramo klicati to funkcijo namesto delaya
//display messages on DMD screen (time, temperature, custom messages,...)
displayMessages();
if (radio.receiveDone()) {
PRINT('[');PRINT(radio.SENDERID);PRINT("] ");
for (byte i = 0; i < radio.DATALEN; i++)
PRINT((char)radio.DATA[i]);
PRINT(" [RX_RSSI:");PRINT(radio.RSSI);PRINTLN("]");
//TODO: GatewayMightyHatNode.ino - message is converted to uppercase
//Time sync command?(T...)
if (processTimeSync(radio.DATA)) {
formatCurrentTime(currentTime);
PRINT("Time set to:"); PRINTLN(currentTime);
//Print Time
} else if (radio.DATALEN > 1 && radio.DATA[0]=='P' && radio.DATA[1]=='T') {
formatCurrentTime(currentTime);
PRINT("Current time is:"); PRINTLN(currentTime);
//Temperature (air)
//AT#-12.3
} else if (radio.DATALEN > 3 && radio.DATA[0]=='A' && radio.DATA[1]=='T' && radio.DATA[2]=='#') {
char *ptrTok;
ptrTok = strtok(radio.DATA, "#"); //1. token = "TA"
ptrTok = strtok(NULL, "#"); //2. token = '-12.3'
strcpy(tempAir, ptrTok); //temp is stored to global variable, which is displayed on DMD
Alarm.free(airTimer); //we delete old data
airTimer = Alarm.timerOnce(0,KEEP_DATA_MIN,KEEP_DATA_SEC, airDelete);
PRINT("Setting temperature Air to:"); PRINTLN(ptrTok);
//M#message
} else if (radio.DATALEN > 2 && radio.DATA[0]=='M' && radio.DATA[1]=='#') {
char *ptrMsg;
ptrMsg = strtok(radio.DATA, "#"); //1. token = 'M'
ptrMsg = strtok(NULL, "#"); //2. token = 'message'
PRINT("Message:"); PRINTLN(ptrMsg);
PRINT("Air temperature:"); PRINTLN(tempAir);
}
//first send any ACK to request
if (radio.ACKRequested())
{
radio.sendACK();
PRINTLN(" - ACK sent.");
}
PRINTLN();
}
}
//Blink led diode every x sec
void blinkLed() {
if (ledIsON) {
digitalWrite(LED_PIN, LOW); // turn the LED off by making the voltage LOW
} else {
digitalWrite(LED_PIN, HIGH); // turn the LED on (HIGH is the voltage level)
}
ledIsON = !ledIsON;
}
void airDelete() {
PRINTLN("Deleting air temperature data.");
tempAir[0] = '\0';
}
void drawCurrentTime() {
formatCurrentTime(currentTime);
dmd.drawString( 2+(32*0), 1+(16*0), currentTime, /*8*/strlen(currentTime), GRAPHICS_NORMAL );
dmd.drawString( 2+(32*0), 9+(16*0), tempAir, /*8*/strlen(tempAir), GRAPHICS_NORMAL );
}
//Prikaz časa
void displayTime() {
if (timeStatus() == timeNotSet) {
//TODO
//send time sync request
} else {
if (currentMinute != minute()) {
currentMinute = minute();
drawCurrentTime();
}
}
}
void displayMessages() {
//PRINT("Current time is:"); PRINTLN(currentTime);
//PRINT("Message:"); PRINTLN(ptrMsg);
//PRINT("Air temperature:"); PRINTLN(tempAir);
displayTime();
}