My school uses an A/B day schedule. I have made a few signs using a 16x16 array and Feather 32u4 boards, as seen below.
I have a transmitter, also using a feather 32u4 on my desk. When I press the button on the transmitter it cycles through: Standby, A, and B. When A or B is selected it also displays the word DAY.
I would like to add to the program so that it will automatically change to the correct day. For it to do this, it needs to know what the day is, and what is expected on that day. I considered using mills to count the day, and an array or a code-switch and restarting the transmitter on each Monday.
What I would like the best would be to tie it to a Google sheet (google Sheets is preferred because it does not require any particular computer to be on) that would then compare the current date to a lookup table. An advantage of a table is that it can accommodate mid-year changes to the schedule.
I am open to other methods to automate the process of changing the sign between Standby, A, and B.
Here is what the device Transmitter code looks like:
```cpp
// Last changed 8 NOV 2023
#include <SPI.h>
#include <RH_RF95.h>
#include <LiquidCrystal_I2C.h> // by Frank de Brabander
// 16 x 2 display connecter to GND, +5Vdc, SCL(3), SDA(2)
int lastPacket;
// Change to 434.0 or other frequency, must match RX's freq!
#define RF95_FREQ 915.0
// These are used for the button function
const int buttonPin = 12; // the number of the pushbutton pin
const int ledPin = 13; // The number of the LED pin
int buttonState; // the current reading from the input pin
int lastButtonState = LOW; // the previous reading from the input pin
int ledCount = 3; // The default setting is standby
int lastLEDCount = 1;
int theButtonValue;
int TxCount; // This is used to limit the number of transmissions
int TxButtonState; // This is used to limit the number of transmissions
const int TxAttempts = 10; // This is the number of times the transmitter will send after each button press
//for feather32u4
// Singleton instance of the radio driver
const int RFM95_CS = 8; // This pin is used by the radio transceiver
const int RFM95_RST = 4; // This pin is used by the radio transceiver
const int RFM95_INT = 7; // This pin is used by the radio transceiver
// This is for the Millis delay
unsigned long previousMillis = 0;
const long interval = 1000;
// the following variables are unsigned longs because the time, measured in
// milliseconds, will quickly become a bigger number than can be stored in an int.
unsigned long lastDebounceTime = 0; // the last time the output pin was toggled
unsigned long debounceDelay = 50; // the debounce time; increase if the output flickers
LiquidCrystal_I2C lcd(0x27, 16, 2); // set the LCD address to 0x3F for a 16 chars and 2 line display
RH_RF95 rf95(RFM95_CS, RFM95_INT);
void setup()
{
pinMode(buttonPin, INPUT);
pinMode(RFM95_RST, OUTPUT);
digitalWrite(RFM95_RST, HIGH);
pinMode(ledPin, OUTPUT);
lcd.init();
lcd.clear();
lcd.backlight(); // Make sure backlight is on
Serial.begin(9600);
// while (!Serial) {
// delay(1);
// }
delay(100);
Serial.println("Feather LoRa TX Test!");
// manual reset
digitalWrite(RFM95_RST, LOW);
delay(10);
digitalWrite(RFM95_RST, HIGH);
delay(10);
while (!rf95.init()) {
Serial.println("LoRa radio init failed");
Serial.println("Uncomment '#define SERIAL_DEBUG' in RH_RF95.cpp for detailed debug info");
while (1);
}
Serial.println("LoRa radio init OK!");
// Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, +13dbM
if (!rf95.setFrequency(RF95_FREQ)) {
Serial.println("setFrequency failed");
while (1);
}
Serial.print("Set Freq to: "); Serial.println(RF95_FREQ);
// Defaults after init are 434.0MHz, 13dBm, Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on
// The default transmitter power is 13dBm, using PA_BOOST.
// If you are using RFM95/96/97/98 modules which uses the PA_BOOST transmitter pin, then
// you can set transmitter powers from 5 to 23 dBm:
rf95.setTxPower(23, false);
}
int16_t packetnum = 0; // packet counter, we increment per xmission
void loop()
{
readTheButton(ledCount);
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
int radiopacket = 0;
radiopacket = ledCount;
lcdDisplay(radiopacket);
TxButtonState = digitalRead(buttonPin);
// digitalRead(buttonPin);
if (TxButtonState == HIGH) {
TxCount = 0;
}
if (TxCount < TxAttempts) {
Serial.print("The TX Count is ");
Serial.println(TxCount);
itoa(packetnum++, radiopacket, radiopacket);
Serial.print("Sending "); Serial.println(radiopacket);
Serial.println("Sending...");
digitalWrite(ledPin, HIGH);
delay(10);
rf95.send((uint8_t *)&radiopacket, sizeof(radiopacket));
Serial.println("Waiting for packet to complete...");
delay(10);
rf95.waitPacketSent();
// Now wait for a reply
uint8_t buf[RH_RF95_MAX_MESSAGE_LEN];
uint8_t len = sizeof(buf);
Serial.println("Waiting for reply...");
if (rf95.waitAvailableTimeout(1000))
{
// Should be a reply message for us now
if (rf95.recv(buf, &len))
{
Serial.print("Got reply: ");
Serial.println((char*)buf);
Serial.print("RSSI: ");
Serial.println(rf95.lastRssi(), DEC);
}
else
{
Serial.println("Receive failed");
}
}
else
{
Serial.println("No reply, is there a listener around?");
}
digitalWrite(ledPin, LOW);
TxCount++;
}
}
}
void lcdDisplay(int radiopacket) {
if (lastPacket != radiopacket) {
lcd.clear(); // clear the display
}
lcd.setCursor(0, 0); // Character Position 0, Line 1
lcd.print("Radio Packet = ");
lcd.setCursor(15, 0);// Character Position 15, Line 1
lcd.print(radiopacket);
if (radiopacket == 1) {
lcd.setCursor(0, 1);// Character Position 0, Line 2
lcd.print("A Day");
}
if (radiopacket == 2) {
lcd.setCursor(0, 1);// Character Position 0, Line 2
lcd.print("B Day");
}
if (radiopacket == 3) {
lcd.setCursor(0, 1);// Character Position 0, Line 2
lcd.print("Standby");
}
lastPacket = radiopacket;
}
void readTheButton (int theButtonValue) {
// read the state of the switch into a local variable:
int reading = digitalRead(buttonPin);
// check to see if you just pressed the button
// (i.e. the input went from LOW to HIGH), and you've waited long enough
// since the last press to ignore any noise:
// If the switch changed, due to noise or pressing:
if (reading != lastButtonState) {
// reset the debouncing timer
lastDebounceTime = millis();
}
if ((millis() - lastDebounceTime) > debounceDelay) {
// whatever the reading is at, it's been there for longer than the debounce
// delay, so take it as the actual current state:
// if the button state has changed:
if (reading != buttonState) {
buttonState = reading;
// only increment the LED count if the new button state is HIGH
if (buttonState == HIGH) {
ledCount++;
if (ledCount > 3) {
ledCount = 1;
}
}
}
}
// save the reading. Next time through the loop, it'll be the lastButtonState:
lastButtonState = reading;
return ledCount;
}
This is the code on the receive side:
```cpp
#include <FastLED.h>
#include <SPI.h>
#include <RH_RF95.h>
#define RF95_FREQ 915.0 // Change to 434.0 or other frequency, must match RX's freq!
#define LED 13 // Builtin LED
#define LED_PIN 11 //this pin connects to the panel
#define NUM_LEDS 256
//for feather32u4
//#define RFM95_CS 8
//#define RFM95_RST 4
//#define RFM95_INT 7
// Note, the Rotary Switch is not implemented. This is a receive only device.
int inPin1 = 2; // from the rotary switch
int val1 = 0;
int inPin2 = 3; // from the rotary switch
int val2 = 0;
int inPin3 = 5; // from the rotary switch
int val3 = 0;
int previousVal;
int RxValue;
CRGB leds[NUM_LEDS];
//#define LED1 11
//#define LED2 12
const int RFM95_CS = 8; // this is added by me
const int RFM95_RST = 4; // this is added by me
const int RFM95_INT = 7; // this is added by me
RH_RF95 rf95(RFM95_CS, RFM95_INT);
#define LED 13 // Blinky on receipt
void setup() {
FastLED.addLeds<WS2812, LED_PIN, GRB>(leds, NUM_LEDS);
//*********************************************************//
// If we are not using a Poteometer to adjust Brightness, //
// setBrightness is where we adjust it from 0 to 200 //
//*********************************************************//
FastLED.setBrightness(20);
FastLED.clear();
pinMode(LED, OUTPUT);
pinMode(RFM95_RST, OUTPUT);
digitalWrite(RFM95_RST, HIGH);
Serial.begin(9600);
// Version Notes
Serial.println("16x16_wSwitch_dimmer_Feather_Rx_No_Tx_Array");
Serial.print("Last modified on :");
Serial.print("/t");
Serial.println("7 Nov 2023");
delay(100);
Serial.println("Feather LoRa RX Test!");
// manual reset
digitalWrite(RFM95_RST, LOW);
delay(10);
digitalWrite(RFM95_RST, HIGH);
delay(10);
while (!rf95.init()) {
Serial.println("LoRa radio init failed");
Serial.println("Uncomment '#define SERIAL_DEBUG' in RH_RF95.cpp for detailed debug info");
while (1)
;
}
Serial.println("LoRa radio init OK!");
// Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, +13dbM
if (!rf95.setFrequency(RF95_FREQ)) {
Serial.println("setFrequency failed");
while (1)
;
}
Serial.print("Set Freq to: ");
Serial.println(RF95_FREQ);
// Defaults after init are 434.0MHz, 13dBm, Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on
// The default transmitter power is 13dBm, using PA_BOOST.
// If you are using RFM95/96/97/98 modules which uses the PA_BOOST transmitter pin, then
// you can set transmitter powers from 5 to 23 dBm:
rf95.setTxPower(23, false);
}
void loop() {
int buf = 0;
int RxReceive;
if (rf95.available()) {
uint8_t buf = 200;
uint8_t len = sizeof(buf);
if (rf95.recv((uint8_t*)&buf, &len)) {
digitalWrite(LED, HIGH);
// RH_RF95::printBuffer("Received: ", buf, len); // test to see if we need this line
Serial.print("Got: ");
// Serial.println((char*)buf);
Serial.println(buf);
Serial.print("RSSI: ");
Serial.println(rf95.lastRssi(), DEC);
RxReceive = buf;
// Send a reply has been remarked out because it was interfeering with other units
/* uint8_t data[] = "AF7JA says hello back to you";
rf95.send(data, sizeof(data));
rf95.waitPacketSent();
Serial.println("Sent a reply");*/
digitalWrite(LED, LOW);
} else {
Serial.println("Receive failed");
}
}
// int readPot = analogRead(A1); // This is where the 10K Poteometer is connected
// int brightness = map(readPot, 0, 1023, 1, 200); // Maping the 0-1023 value from the pot to 1-200, we use 200 because 200-226 had no real impact
// FastLED.setBrightness(brightness); // here we change the brightness of the LED array
// if (val1 == HIGH) { // switch position 1
Serial.print("at the if, buf = ");
Serial.println(RxReceive);
//*************************************************************//
// Led letter decision and call code starts here //
//*************************************************************//
if (RxReceive == 1) {
if (previousVal != 1) { // checks to see if the display is set to another letter
FastLED.clear(); // if the screen was set to a different letter, it clears the screen
} else
wordDay(); // prints "day"
letterA(); // prints "A"
previousVal = 1;
} else if (RxReceive == 2) {
// else if (val2 == HIGH) { // switch position 2
if (previousVal != 2) { // checks to see if the display is set to another letter
FastLED.clear(); // if the screen was set to a different letter, it clears the screen
} else
wordDay(); // prints "day"
letterB(); // prints "B"
previousVal = 2;
// This is the standby LED
} else if (RxReceive == 48 || 3 || -48) {
FastLED.clear();
leds[0] = CRGB(50, 50, 50); // this was changed from "leds[1]"
FastLED.show();
previousVal = 48;
}
}
//*********************************************************************
//* Below this line are the functions that form the words and letters *
//*********************************************************************
void wordDay() {
// Spells "DAY"
int ledDayNumber[] = { 2, 3, 30, 28, 33, 35, 62, 60, 66, 67, 98, 124, 126, 131, 130, 129, 156, 158, 188,
190, 195, 193, 221, 226, 253 }; //
for (int iD = 0; iD < sizeof(ledDayNumber) / sizeof(ledDayNumber[0]); iD++) {
int ledToUseDay = ledDayNumber[iD];
leds[ledToUseDay] = CRGB(random(255), random(255), random(255));
FastLED.show();
}
}
void letterA() {
// Letter"A"
int ledLetterA[] = { 10, 20, 21, 22, 40, 41, 43, 44, 55, 54, 52, 51, 71, 72, 76, 77, 88, 87, 83, 82, 102, 103, 109, 110, 121, 120,
114, 113, 134, 135, 136, 137, 138, 139, 140, 141, 142, 145, 146, 147, 148, 149, 150, 151, 152, 153, 166, 167,
173, 174, 177, 178, 184, 185, 198, 199, 205, 206, 209, 210, 216, 217, 229, 230, 231, 237, 238, 239, 240, 241,
242, 248, 249, 250 };
for (int iA = 0; iA < sizeof(ledLetterA) / sizeof(ledLetterA[0]); iA++) {
int ledToUseA = ledLetterA[iA];
leds[ledToUseA] = CRGB(random(255), random(255), random(255));
FastLED.show();
}
}
void letterB() {
// Letter"B"
int ledLetterB[] = { 11, 12, 13, 14, 17, 18, 19, 20, 21, 41, 42, 45, 46, 49, 50, 54, 73, 77, 78, 81, 82, 86, 110, 109, 106,
105, 113, 114, 115, 116, 117, 138, 139, 140, 141, 142, 145, 146, 149, 150, 169, 173, 174, 177, 178, 182,
201, 205, 206, 209, 210, 213, 214, 234, 235, 236, 237, 238, 241, 242, 243, 244, 40, 55, 72, 87, 104, 151,
168, 183, 200, 215, 22, 233 };
for (int iB = 0; iB < sizeof(ledLetterB) / sizeof(ledLetterB[0]); iB++) {
int ledToUseB = ledLetterB[iB];
leds[ledToUseB] = CRGB(random(255), random(255), random(255));
FastLED.show();
}
}
/*********************************************************************
** The word "TEST" and the letter "M" are not currently implemented **
*********************************************************************/
void wordTest() {
// Spells "TEST"
// This is not currently implemented
int ledwordTest[] = { 0, 1, 2, 30, 33, 62, 64, 65, 66, 93, 97, 98, 125, 128, 129, 130, 157, 160, 161, 162, 191, 192, 194, 222,
225, 254 };
for (int iWT = 0; iWT < sizeof(ledwordTest) / sizeof(ledwordTest[0]); iWT++) {
int ledToUseTest = ledwordTest[iWT];
leds[ledToUseTest] = CRGB(random(255), random(255), random(255));
FastLED.show();
}
}
void letterM() {
// Letter"M"
// This is not currently implemented
int ledLetterM[] = { 4, 5, 6, 13, 14, 15, 16, 17, 18, 25, 26, 27, 37, 38, 45, 46, 49, 51, 56, 58, 69, 71, 76, 78, 81, 83, 88,
90, 101, 104, 107, 110, 113, 116, 119, 122, 133, 136, 139, 142, 145, 149, 150, 154, 165, 169, 170, 174,
177, 181, 182, 186, 197, 206, 209, 218, 228, 229, 238, 239, 240, 241, 250, 251 };
for (int iM = 0; iM < sizeof(ledLetterM) / sizeof(ledLetterM[0]); iM++) {
int ledToUseM = ledLetterM[iM];
leds[ledToUseM] = CRGB(random(255), random(255), random(255));
FastLED.show();
}
}
/************************************
** End of letter LED assignments **
** The function below is handeling **
** the radio recieve details **
************************************/
void RxRecieve() {
int buf = 0;
if (rf95.available()) {
// Should be a message for us now
// uint8_t buf[RH_RF95_MAX_MESSAGE_LEN];
uint8_t buf = 200;
uint8_t len = sizeof(buf);
if (rf95.recv((uint8_t*)&buf, &len)) {
digitalWrite(LED, HIGH);
RH_RF95::printBuffer("Received: ", buf, len);
Serial.print("Got: ");
// Serial.println((char*)buf);
Serial.println(buf);
Serial.print("RSSI: ");
Serial.println(rf95.lastRssi(), DEC);
int RxValue = buf;
// Send a reply
uint8_t data[] = "AF7JA says hello back to you";
rf95.send(data, sizeof(data));
rf95.waitPacketSent();
Serial.println("Sent a reply");
digitalWrite(LED, LOW);
} else {
Serial.println("Receive failed");
}
return RxValue;
}
}