/* * LM: This is two sketches in one. Select the approprite role to load. * Set the constant MASTER = true (This is the application master role) or * false (This is the application remote role) * * The remote application monitors the state of the mailbox door and * transmits a message to the master, whenever the state changes from * shut to open or from open to shut. * * The master application indicates the most recent activity visually, * either via LED or OLED or both. * * The master application also records notification messages in EEPROM. * * Display of recent activity can be cleared or reset. * */ const boolean MASTER = true ; // See multi-line comment above const boolean SET_RTC = false; // If true, set RTC to the date & time this sketch was compiled const boolean RTC_TEST_MODE = false; // Displays time, updating once per second const boolean SOUND_TEST = false; // For tweaking audio filter etc. const boolean INIT_EEPROM = false; // Set true once before deployment, then false // Ground DIO pin 4 for EEPROM dump to Serial #include #include // OLED #include #include "nRF24L01.h" // Radio #include "RF24.h" #include // Non-volatile storage #include "RTClib.h" // Real-time clock #include // OLED display address #define OLED_ADDR 0x3C Adafruit_SSD1306 display(-1); #if (SSD1306_LCDHEIGHT != 64) #error("Height incorrect, please fix Adafruit_SSD1306.h!"); #endif RF24 radio(9,10); RTC_DS3231 rtc; // Oscillator i2c address is 0x68 (can't be changed) const uint64_t pipe = 0xE8E8F0F0E1LL; //Define pipe const int MAX_MESSAGE = 64; char TXmsg [MAX_MESSAGE] = ""; char RXmsg [MAX_MESSAGE] = ""; #define BATTERY_STATUS A0 // Analog input for sensing battery voltage #define DOOR_SWITCH 3 // DIO pin for door switch (Remote sender context) #define DUMP_SWITCH 4 // DIO pin for indicating EEPROM dump to serial port (Master receiver context) #define DOOR_STATE_LED 5 // Indicator that mailbox door is open or shut (Not presently used) #define TONE_PWM 7 #define OPEN HIGH #define SHUT LOW // Reverse sense if necessary (mailbox end) // Tones const int BOO = 500; const int BEE = 900; const int BOOP = BOO; const int BEEP = BEE; // General const int ADD_SECONDS = 17; // Number of seconds to add to adjust for compile and upload const long DEBOUNCE = 100; const long TONE_DURATION = 150; const long MILLISECONDS_BETWEEN_TONES = 150; const long HALF_SECOND = 500; const long ONESEC = 1000; const long TWOSEC = 2000; // Startup delay const long FIVESEC = 5000; // Reserved boolean lastShut = true; // Last stable state of mailbox door int door = SHUT; // Alternative to boolean variable lastShut // Time - i2c from RTC module: A4=SDA, A5=SCL #define STX 0x02 #define ETX 0x03 //RTC boolean rtcON; // Flag indicating RTC present (found) and successfully initialized const char SLANT = '/'; // Date formatting const char COLON = ':'; // Time formatting boolean timeUpdateReceived = false; DateTime rtcNow; // From rtc.now() int iYR; int iMON; int iDAY; int iHR; int iMN; int iSC; String sDate = ""; String sTime = ""; long last_time_update = 0; // EEPROM const int EE_RESERVED = 4; // Number of reserved bytes, starting at 0 String sTmp; // Avoid encapsulated string declaration void setup() { radio.begin(); if (MASTER) { pinMode(DUMP_SWITCH, INPUT_PULLUP); pinMode(DOOR_STATE_LED, OUTPUT); pinMode(TONE_PWM, OUTPUT); // initialize read from radio radio.openReadingPipe(1,pipe); radio.startListening(); // initialize real time clock rtcON = rtc.begin(); if (rtcON && SET_RTC) rtc.adjust(DateTime(F(__DATE__), F(__TIME__)) + TimeSpan(0, 0, 0, ADD_SECONDS)); } else { pinMode(DOOR_SWITCH, INPUT); // Switch has 10K pullup resistor radio.openWritingPipe(pipe); analogReference(INTERNAL); } // initialize and clear display display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR); display.clearDisplay(); display.display(); oledDisplayText("Initialization", 1, 0, 0); oledDisplayText("complete", 1, 4, 10); delay(TWOSEC); oledClear(); if (MASTER) { if (digitalRead(DUMP_SWITCH) == LOW) { Serial.begin(9600); dumpEEPROM(); } if (INIT_EEPROM) { eraseEEPROM(EEPROM.length()); recordPlaceEEPROM(EE_RESERVED); } } } void loop() { if (MASTER) { // Data receiver if (rtcON && RTC_TEST_MODE) { if (last_time_update + ONESEC < millis()) { // oledClear(); oledDisplayTime(); last_time_update = millis(); } } // Wait for data from radio if ( radio.available() ) { while (radio.available()) { int len = radio.getDynamicPayloadSize(); radio.read(&RXmsg, len); } processReceivedData(); } } else { // Remote sensor / transmitter // Rewrite if (digitalRead(DOOR_SWITCH) == OPEN) { if (door == SHUT) { door = OPEN; transmitShut2Open(); transmitBatteryStatus(); } } else if (door == OPEN) { door = SHUT; transmitOpen2Shut(); transmitBatteryStatus(); } } } // RADIO void clearRxBuffer() { RXmsg[0] = (byte) 0; } void clearTxBuffer() { TXmsg[0] = (byte) 0; } void processReceivedData() { // Check validity and complete processing if (RXmsg[0] == '1') { receiveShut2Open(); } else if (RXmsg[0] == '2') { receiveOpen2Shut(); } else if (RXmsg[0] == '3') { receiveBatteryStatus((byte) RXmsg[1]); } // Other messages here clearRxBuffer(); } void transmitBatteryStatus() { TXmsg[0] = '3'; TXmsg[1] = (byte) (analogRead(BATTERY_STATUS) / 4); TXmsg[2] = (byte) 0; transmitMessage(3); // Length } void transmitShut2Open() { TXmsg[0] = '2'; TXmsg[1] = (byte) 0; transmitMessage(2); // Length } void transmitOpen2Shut() { TXmsg[0] = '1'; TXmsg[1] = (byte) 0; transmitMessage(2); // Length } void receiveShut2Open() { boobeep(); oledDisplayOpenMessage(); digitalWrite(DOOR_STATE_LED, OPEN); storeEventToEEPROM('1'); // Record to EEPROM // Post to IOT delay(HALF_SECOND); } void receiveOpen2Shut() { beeboop(); oledDisplayShutMessage(); digitalWrite(DOOR_STATE_LED, SHUT); storeEventToEEPROM('2'); // Record to EEPROM // Post to IOT delay(HALF_SECOND); } void receiveBatteryStatus(byte b) { int v = b * 44 / 255; String sV = String(v); sV = sV.substring(0, 1) + '.' + sV.substring(1); if (b >= 250) oledDisplayText("B.Full", 1, 80, 45); else oledDisplayText("B " + sV + " v", 1, 80, 45); } void transmitMessage(int msgLength) { radio.write(&TXmsg, msgLength); // transmitting the string clearTxBuffer(); } // DISPLAY (Master) void oledClear() { display.clearDisplay(); display.display(); } void oledDisplayText(String s, int size, int x_coord, int y_coord) { // display a line of text display.setTextSize(size); display.setTextColor(WHITE); display.setCursor(x_coord,y_coord); display.print(s); display.display(); } void oledDisplayOpenMessage() { oledClear(); oledDisplayText("Mailbox", 1, 0, 0); oledDisplayText("opened.", 1, 0, 15); oledDisplayTime(); } void oledDisplayShutMessage() { oledClear(); oledDisplayText("Mailbox", 1, 0, 0); oledDisplayText("closed.", 1, 0, 15); oledDisplayTime(); } void oledDisplayTime() { if (rtcON) { readRTC(); formatDate(); oledClearLine(30, 1); oledDisplayText(sDate, 1, 0, 30); formatTime(); oledClearLine(45, 1); oledDisplayText(sTime, 1, 0, 45); if (SOUND_TEST) if (iSC == 0) beeboop(); else if (iSC % 30 == 0) boobeep(); } } void oledDisplayDebug(char c) { oledDisplayText("Debug " + String(c), 1, 0, 50); delay(HALF_SECOND); oledClearLine(50,1); } void oledClearLine(int jPos, int jVsize) { display.fillRect(0, jPos, display.width(), jVsize*10, BLACK); } // EEPROM (Master) void writeEEPROM(String s) { int sLen = s.length(); if (sLen > EEPROM.length()) return; for (int i=0; i (EEPROM.length() - jStart)) return; for (int i=0; i EEPROM.length()) sLen = EEPROM.length(); sTmp = ""; for (int i=0; i (EEPROM.length() - jStart)) sLen = EEPROM.length() - jStart; sTmp = ""; for (int i=0; i EEPROM.length()) len = EEPROM.length(); for (int i=0; i (EEPROM.length() - jStart)) len = EEPROM.length() - jStart; for (int i=0; i EEPROM.length()) jPlace = EE_RESERVED; writeEEPROM(sTmp, jPlace); recordPlaceEEPROM(jPlace +sTmp.length()); } void dumpEEPROM() { // Format and print contents of EEPROM to serial port char c; for (int i=EE_RESERVED; i 31) && ((byte) c < 127)) Serial.print(c); } } // Sounds cribbed from Dave Benson's Hilltopper sketch - void boobeep () { tone (TONE_PWM,BOO,TONE_DURATION); delay(MILLISECONDS_BETWEEN_TONES); tone (TONE_PWM,BEEP,TONE_DURATION); } void beeboop () { tone (TONE_PWM,BEE,TONE_DURATION); delay (MILLISECONDS_BETWEEN_TONES); tone (TONE_PWM,BOOP,TONE_DURATION); } // Adapted from NTP sketch void readRTC() { // Read date and time from RTC rtcNow = rtc.now(); iYR = rtcNow.year(); iMON = rtcNow.month(); iDAY = rtcNow.day(); iHR = rtcNow.hour(); iMN = rtcNow.minute(); iSC = rtcNow.second(); } void formatDate() { sDate = String(iMON); sDate.concat(SLANT); sDate.concat(String(iDAY)); sDate.concat(SLANT); sDate.concat(String(iYR).substring(2)); } void formatTime() { sTime = String(iHR); sTime.concat(COLON); if (String(iMN).length() == 1) sTime.concat("0"); sTime.concat(String(iMN)); sTime.concat(COLON); // Do seconds matter? if (String(iSC).length() == 1) sTime.concat("0"); sTime.concat(String(iSC)); }