FastclockRF/src/main.cpp

436 lines
14 KiB
C++

/*
* Copyright (c) 2014-2017 Cesanta Software Limited
* All rights reserved
*/
//#define RF_RadioHead
//#define RF_RF24
#include <Arduino.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
//#include <SPI.h>
#ifdef RF_RadioHead
#include "RadioHead/RH_NRF24.h"
#include "RadioHead/RHDatagram.h"
#endif
#ifdef RF_RF24
#include "RF24/RF24.h"
#endif
// #include <Fonts/Picopixel.h>
#include <Fonts/Org_01.h>
#include <Fonts/TomThumb.h>
#include <Fonts/FreeMonoBold9pt7b.h>
#include "common/cs_dbg.h"
#include "mgos.h"
#include "mgos_app.h"
#include "mgos_timers.h"
#include "mgos_shadow.h"
#include "config.h"
#include "clockMsg.h"
static Adafruit_SSD1306 *display = nullptr;
#define LOGO16_GLCD_HEIGHT 16
#define LOGO16_GLCD_WIDTH 16
static const unsigned char PROGMEM logo16_glcd_bmp[] =
{ 0x00, 0xc0, // B00000000, B11000000,
0x01, 0xc0, // B00000001, B11000000,
0x01, 0xc0, // B00000001, B11000000,
0x03, 0xe0, // B00000011, B11100000,
0xf3, 0xe0, // B11110011, B11100000,
0xfe, 0xf8, // B11111110, B11111000,
0x7e, 0xff, // B01111110, B11111111,
0x33, 0x9f, // B00110011, B10011111,
0x1f, 0xfc, // B00011111, B11111100,
0x0d, 0x70, // B00001101, B01110000,
0x1b, 0xa0, // B00011011, B10100000,
0x3f, 0xe0, // B00111111, B11100000,
0x3f, 0xf0, // B00111111, B11110000,
0x7c, 0xf0, // B01111100, B11110000,
0x70, 0x70, // B01110000, B01110000,
0x00, 0x30 // B00000000, B00110000
};
#if defined(CHECK_DISPLAY_RESOLUTION)
#if (SSD1306_LCDHEIGHT != 32)
#error("Height incorrect, please fix Adafruit_SSD1306.h!");
#endif
#endif
#ifdef RF_RadioHead
// Singleton instance of the radio driver
RH_NRF24 nrf24(PIN_NRF24_CSN, PIN_NRF24_CE);
// Address RH_BROADCAST_ADDRESS can be used for broadcasts as destination
RHDatagram Datagram(nrf24, THIS_ADRESS);
#endif
#ifdef RF_RF24
RF24 radio(PIN_NRF24_CE, PIN_NRF24_CSN); // Set up nRF24L01 radio on SPI bus plus pins 7 & 8
const uint64_t pipes[2] = { 0xABCDABCD71LL, 0x544d52687CLL }; // Radio pipe addresses for the 2 nodes to communicate.
byte data[32]; //Data buffer for testing data transfer speeds
unsigned long sendFailedCounter=0, rxTimer; //Counter and timer for keeping track transfer info
unsigned long receivedCounter=0;
unsigned long startTime, stopTime, pauseTime;
bool TX=1,RX=0,role=0;
#endif
//static SPIImpl SPI;
static void setSmallTextSize(void) { display->setFont(&TomThumb); }
static uint8_t getSmallTextHeight() { return TomThumb.yAdvance; }
//static uint8_t getSmallTextCharsPerLine() { return 39; }
static void setNormalTextSize(void) { display->setFont(&Org_01); }
static uint8_t getNormalTextHeight() { return Org_01.yAdvance; }
//static uint8_t getNormalTextCharsPerLine() { return 24; }
static void setLargeTextSize(void) { display->setFont(&FreeMonoBold9pt7b); }
//static uint8_t getLargeTextHeight() { return FreeMonoBold9pt7b.yAdvance; }
//static uint8_t getLargeTextCharsPerLine() { return 12; }
// Forward declarations:
static void updateDisplay_cb(void *arg);
static void fastclockRF_receive_cb(void *arg);
static void fastclockRF_send_cb(void *arg);
static void initFastclockRF_cb(void *arg);
static void displayBegin(boolean reset) {
//display->begin(SSD1306_SWITCHCAPVCC, 0x3C, reset /* reset */);
display->begin(); (void) reset;
}
static void initDisplay_cb(void *arg) {
static int step=0;
(void) arg;
display = new Adafruit_SSD1306(16 /* RST GPIO */, Adafruit_SSD1306::RES_128_32);
switch (step) {
case 0:
displayBegin(true);
display->display();
LOG(LL_INFO, ("*** Display initialized, height=%d, width=%d", display->height(), display->width()));
break;
case 1:
displayBegin(false);
display->clearDisplay();
display->drawPixel(10, 10, WHITE);
display->drawPixel(12, 12, WHITE);
display->drawPixel(14, 14, WHITE);
display->drawPixel(16, 16, WHITE);
display->display();
LOG(LL_INFO, ("*** Pixel drawn, height=%d, width=%d", display->height(), display->width()));
break;
case 2:
displayBegin(false);
display->drawLine(0, display->height()-1, display->width(), display->height()/2, WHITE);
display->drawCircle(display->width()-20, display->height()-10, 10, WHITE);
display->display();
LOG(LL_INFO, ("*** Line & Circle drawn"));
break;
case 3:
displayBegin(false);
display->drawBitmap(60, 0, logo16_glcd_bmp, 16, 16, 1);
display->display();
LOG(LL_INFO, ("*** Icon/Bitmap drawn"));
break;
default: // do nothing
break;
}
++step;
if (step <= 3) mgos_set_timer(400 /* ms */, false /* repeat */, initDisplay_cb, NULL);
}
void setup(void) {
LOG(LL_INFO, ("*** Setup started"));
#if 0
struct mgos_spi *spi;
spi = mgos_spi_get_global();
if (spi == NULL) {
LOG(LL_ERROR, ("SPI is not configured, make sure spi.enable is true"));
return;
}
#endif
LOG(LL_INFO, ("*** Setting timer"));
mgos_set_timer(1000 /* ms */, false /* repeat */, initDisplay_cb, NULL);
mgos_set_timer(5000 /* ms */, false /* repeat */, initFastclockRF_cb, NULL);
LOG(LL_INFO, ("*** Setup done"));
}
static void show_dashboard(int hour, int minute) {
display->clearDisplay();
display->setTextColor(WHITE, BLACK);
// ***** clock name *****
setNormalTextSize();
display->setCursor(0, getNormalTextHeight()-1);
display->printf("N-RE");
// ****** speed *****
setNormalTextSize();
display->setCursor(55, 2*getNormalTextHeight()-1);
display->printf("1:3,5");
// ***** time *****
setLargeTextSize();
display->setCursor(0, display->height()-1);
display->printf("%02d:%02d", hour, minute);
// ***** halt/go *****
setSmallTextSize();
display->setTextColor(BLACK, WHITE);
display->fillRect(55, display->height() - 2*getSmallTextHeight()-3, 5*4+2, getSmallTextHeight(), WHITE);
display->setCursor(57, display->height() - getSmallTextHeight()-3); display->printf("HALT");
display->setTextColor(WHITE, BLACK);
// **** weekday *****
setSmallTextSize();
display->setCursor(60, display->height()); display->printf("Mo");
// ***** # of clients *****
setSmallTextSize();
display->setCursor(55, getNormalTextHeight()); display->printf("7 -->");
// ***** client list *****
display->writeFastVLine(79, 0, display->height(), WHITE);
for (int i=0; i<5; ++i) {
display->setTextColor(BLACK, WHITE);
display->fillRect(81, i * getSmallTextHeight(), 3*3+1, getSmallTextHeight(), WHITE);
display->setCursor(82, (i+1) * getSmallTextHeight());
display->printf("%02d", i);
display->setTextColor(WHITE, BLACK);
display->setCursor(82+3*3+1+1, (i+1) * getSmallTextHeight());
display->printf("Client-%d", i);
}
display->display();
}
static long lastSentTimeTick = 0;
static long msPerModelSecond = 50; // 500 = real time
static struct clock_s {
uint8_t day;
uint8_t hour;
uint8_t minute;
uint8_t second;
uint16_t millisecond;
} fastclock;
static void incrementClockByMilliseconds(int amount) {
fastclock.millisecond += amount;
if (fastclock.millisecond >= 1000) {
int carryover = fastclock.millisecond / 1000;
fastclock.millisecond = fastclock.millisecond % 1000;
fastclock.second += carryover;
if (fastclock.second >= 60) {
carryover = fastclock.second / 60;
fastclock.second = fastclock.second % 60;
fastclock.minute += carryover;
if (fastclock.minute >= 60) {
carryover = fastclock.minute / 60;
fastclock.minute = fastclock.minute % 60;
fastclock.hour += carryover;
if (fastclock.hour >= 24) {
carryover = fastclock.hour / 24;
fastclock.hour = fastclock.hour % 24;
fastclock.day += carryover;
if (fastclock.day >= 7) {
fastclock.day = fastclock.day % 7;
}
}
}
}
}
LOG(LL_INFO, ("*** new clock: %02d:%02d:%02d.%03d day %d, incBy_ms=%d", fastclock.hour, fastclock.minute, fastclock.second, fastclock.millisecond, fastclock.day, amount));
}
#ifdef RF_RF24
static void switchToSenderRole()
{
LOG(LL_INFO, ("*** CHANGING TO TRANSMIT ROLE"));
radio.openWritingPipe(pipes[1]);
radio.openReadingPipe(1,pipes[0]);
radio.stopListening();
role = TX; // Become the primary transmitter (ping out)
}
static void switchToReceiverRole()
{
LOG(LL_INFO, ("*** CHANGING TO RECEIVER ROLE"));
radio.openWritingPipe(pipes[0]);
radio.openReadingPipe(1,pipes[1]);
radio.startListening();
role = RX; // Become the primary receiver (pong back)
}
#endif
static void fastclockRF_receive_cb(void *arg) {
(void) arg;
LOG(LL_INFO, ("*** Rcv RF"));
#ifdef RF_RadioHead
// check for incoming messages
if (Datagram.available())
{
// Should be a message for us now
uint8_t buf[RH_MAX_MESSAGE_LEN];
uint8_t len = sizeof(buf);
uint8_t from, to, id, flags;
if (Datagram.recvfrom(buf, &len, &from, &to, &id, &flags)) {
LOG(LL_INFO, ("got request: %d bytes, from=%d, to=%d, id=%d, msg=%02x %02x %02x %02x", len, from, to, id, buf[0], buf[1], buf[2], buf[3]));
}
else
{
LOG(LL_INFO, ("*** Datagram.recvfrom failed"));
}
}
#endif
#ifdef RF_RF24
#define RF24_MAX_MESSAGE_LEN 32
uint8_t buf[RF24_MAX_MESSAGE_LEN];
uint8_t len = sizeof(buf);
unsigned int counter=0;
switchToReceiverRole();
if (radio.available()) {
radio.read(buf, len);
counter++;
LOG(LL_INFO, ("%04d: %02x %02x %02x %02x %02x %02x", counter, buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]));
}
if (millis() - rxTimer > 1000) {
rxTimer = millis();
receivedCounter += counter;
unsigned long numBytes = counter*len;
LOG(LL_INFO, ("Bytes: %ld, Msg count: %d", numBytes, counter));
counter = 0;
}
#endif
}
static struct clockMsg_s clockMsg;
static void fastclockRF_send_cb(void *arg) {
(void) arg;
clockMsg.msgType = msgType_Clock;
clockMsg.hour = fastclock.hour;
clockMsg.minute = fastclock.minute;
clockMsg.second = fastclock.second;
// send clock info as a broadcast message
LOG(LL_INFO, ("*** Sending clock packet (broadcast)"));
#ifdef RF_RadioHead
if (Datagram.sendto((uint8_t *) &clockMsg, sizeof(clockMsg), RH_BROADCAST_ADDRESS)) {
LOG(LL_INFO, ("%02d:%02d:%02d - Sent new clock tick", fastclock.hour, fastclock.minute, fastclock.second));
}
#endif
#ifdef RF_RF24
switchToSenderRole();
if (!radio.write(&clockMsg, sizeof(clockMsg), true /*multicast*/)) {
sendFailedCounter++;
}
//This is only required when NO ACK ( enableAutoAck(0) ) payloads are used
if (millis() - pauseTime > 3) {
pauseTime = millis();
radio.txStandBy(); // Need to drop out of TX mode every 4ms if sending a steady stream of multicast data
//delayMicroseconds(130); // This gives the PLL time to sync back up
}
stopTime = millis();
//This should be called to wait for completion and put the radio in standby mode after transmission, returns 0 if data still in FIFO (timed out), 1 if success
if (!radio.txStandBy()) { sendFailedCounter += 3; } //Standby, block only until FIFO empty or auto-retry timeout. Flush TX FIFO if failed
//radio.txStandBy(1000); //Standby, using extended timeout period of 1 second
#endif
LOG(LL_INFO, ("*** send finished"));
}
static void timeTick_cb(void *arg) {
(void) arg;
long newTimeTick = millis();
int fastclockTimeAdvance = (newTimeTick - lastSentTimeTick) * 1000 / msPerModelSecond;
incrementClockByMilliseconds(fastclockTimeAdvance);
//lastSentTimeTick += fastclockTimeAdvance * msPerModelSecond/1000;
lastSentTimeTick = newTimeTick;
LOG(LL_INFO, ("*** tick (adv=%d)", fastclockTimeAdvance));
}
static void initFastclockRF_cb(void *arg) {
(void) arg;
lastSentTimeTick = millis();
LOG(LL_INFO, ("*** Setting up RF, init lastSentTimeTick=%ld", lastSentTimeTick));
fastclock.day = 0;
fastclock.hour = 0;
fastclock.minute = 0;
fastclock.second = 0;
fastclock.millisecond = 0;
#ifdef RF_RadioHead
if (!Datagram.init()) {
LOG(LL_ERROR, ("*** Datagram init failed"));
}
#endif
#ifdef RF_RF24
radio.begin();
if (radio.isChipConnected()) { LOG(LL_INFO, ("*** RF chip found")); }
else { LOG(LL_ERROR, ("*** ERROR: RF chip not found!")); }
radio.setChannel(1);
radio.setPALevel(RF24_PA_MAX);
radio.setDataRate(RF24_2MBPS);
radio.setAutoAck(0);
//radio.setRetries(2,15); // Optionally, increase the delay between retries & # of retries
radio.setCRCLength(RF24_CRC_8);
radio.openWritingPipe(pipes[0]);
radio.openReadingPipe(1,pipes[1]);
radio.startListening();
radio.printDetails();
// @TODO: real random seed!
//randomSeed(analogRead(0));
//randomSeed(22);
radio.powerUp();
LOG(LL_INFO, ("*** RF payload size=%d bytes", radio.getPayloadSize()));
if (radio.testCarrier() || radio.testRPD()) { LOG(LL_INFO, ("*** Carrier/RPD seen on radio")); }
if (radio.failureDetected) { LOG(LL_ERROR, ("*** Radio error detected!")); }
#endif
LOG(LL_INFO, ("*** Setting up timer tasks"));
mgos_set_timer(100 /* ms */, true /* repeat */, fastclockRF_receive_cb, NULL);
mgos_set_timer(500 /* ms */, true /* repeat */, timeTick_cb, NULL);
mgos_set_timer(3000 /* ms */, true /* repeat */, fastclockRF_send_cb, NULL);
mgos_set_timer(1000 /* ms */, true /* repeat */, updateDisplay_cb, NULL);
LOG(LL_INFO, ("*** Setting up RF done"));
}
static void updateDisplay_cb(void *arg) {
// static int hour = 0, minute = 0;
show_dashboard(fastclock.hour, fastclock.minute);
LOG(LL_INFO, ("%02d:%02d", fastclock.hour, fastclock.minute));
/*
minute++;
if (minute >= 60) { hour++; minute=0; }
if (hour >= 24) { hour=0; }
*/
(void) arg;
}
#if 0
void loop(void) {
/* do not use loop(), use timers instead; otherwise the watchdog timer reboots your device */
}
#endif