├── README.md ├── ArduinoDoorbellTransmitter └── ArduinoDoorbellTransmitter.ino └── ArduinoDoorbellReceiver └── ArduinoDoorbellReceiver.ino /README.md: -------------------------------------------------------------------------------- 1 | # Long-Range-Arduino-Doorbell 2 | 3 | The goal of this project was to create a cheap, very reliable and efficient long-range wireless doorbell. 4 | 5 | - Price: < $10 6 | - Range: Up to 1000 meters 7 | - Power consumption (Idle): < 1µA @ 3V (at 3.27V I measured 193nA) 8 | 9 | ## Features 10 | 11 | - The reliability is ensured by two-way communication between the transmitter and receiver. (If the receiver fails to receive a signal due to interference, the transmitter sends another signal until it verifies that it has arrived at the receiver.) 12 | - The transmitter (which runs on two AAA batteries) reports its remaining battery capacity back to the receiver which can then warn you once it gets too low. 13 | - The receiver uses a simple module that plays MP3 files from a micro SD card, so you can make the doorbell sound any way you want. 14 | 15 | ## Required parts 16 | 17 | - 1x Arduino Pro Mini (3.3v 8Mhz edition) 18 | - 1x Another Arduino (doesn't matter which one) 19 | - 2x Button 20 | - 2x D-SUN CC1101 module 21 | - 1x DF-Player module 22 | - 1x micro SD card (any size) 23 | - 2x AAA battery (alkaline) 24 | - 1x speaker (I will update this once I've settled on a specific one) 25 | - 1x amplifier (should match power and impedence of speaker) 26 | 27 | ## How does it work 28 | 29 | ``` 30 | [Battery]--[Arduino]--[CC1101]-))))))))))) (((((((((((-[CC1101]--[Arduino]--[5v power] 31 | | | 32 | [Button] [speaker] 33 | ``` 34 | 35 | - You press the button 36 | - First Arduino wakes up out of power-saving (sleep) mode 37 | - Signal is sent to the second Arduino 38 | - Second Arduino plays a doorbell sound 39 | - Second Arduino sends a response to the first Arduino 40 | - First Arduino receives the response and goes back to deep sleep 41 | - (If no response is received the first Arduino tries again...) 42 | 43 | ## Schematics 44 | 45 | Coming soon... 46 | 47 | ## How assemble 48 | 49 | (It is highly recommended to desolder the LED and voltage regulator from the Arduino Pro Mini for minimal power consumption <1µA.) 50 | 51 | Connection between D-SUN CC1101 modules and Arduinos: 52 | 53 | ``` 54 | GDO0 -> 6 55 | MISO -> 12 56 | MOSI -> 11 57 | GND -> GND 58 | CSN -> 10 59 | SCK -> 13 60 | VCC -> 3v 61 | ``` 62 | 63 | Connection between DF-Player and Arduino Uno (or whatever you decided on using): 64 | 65 | ``` 66 | VCC -> VCC 67 | GND -> GND 68 | TX -> 9 69 | RX -> 8 70 | ``` 71 | 72 | Connection between button and Arduino Pro Mini: 73 | 74 | ``` 75 | Pin A -> 2 76 | Pin B -> GND 77 | ``` 78 | 79 | ## Credits 80 | 81 | Big thanks to [LSatan](https://github.com/LSatan) for his [CC1101 library](https://github.com/LSatan/SmartRC-CC1101-Driver-Lib) and [for implementing two-way communication and CRC checksum verification on request and even proving complete examples](https://github.com/LSatan/SmartRC-CC1101-Driver-Lib/issues/43). This project is very much based on the work of LSatan. So leave a star on his repository and consider [donating to him](https://github.com/LSatan/SmartRC-CC1101-Driver-Lib#donation). 82 | -------------------------------------------------------------------------------- /ArduinoDoorbellTransmitter/ArduinoDoorbellTransmitter.ino: -------------------------------------------------------------------------------- 1 | //#define DEBUG true // Uncomment to get some basic Serial output 2 | //#define DEBUG verbose // Uncomment to get more advanced Serial output 3 | 4 | #if DEBUG == verbose 5 | #include 6 | #endif 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | typedef union { // makes converting the voltage int into 2 bytes easier 13 | int asInt; 14 | byte asBytes[2]; 15 | } voltage; 16 | 17 | // CONSTANTS (Change to your liking) 18 | const int WAKE_BUTTON_PIN = 2; // interrupt pin to wake the Arduino out of deep sleep 19 | #ifdef ESP32 20 | const int GDO0 = 2; // for esp32! GDO0 on GPIO pin 2. 21 | #elif ESP8266 22 | const int GDO0 = 5; // for esp8266! GDO0 on pin 5 = D1. 23 | #else 24 | const int GDO0 = 6; // for Arduino! GDO0 on pin 6. 25 | #endif 26 | const byte DOORBELL_ID[8] = {127, 33, 45, 91, 27, 60, 8, 16}; 27 | 28 | int DELAY_BETWEEN_RETRIES = 500; // milliseconds 29 | int MAX_RETRIES = 3; 30 | 31 | // CONSTANTS (Do not touch) 32 | const byte TRANSMITTER_ID = 1; 33 | const byte RECEIVER_ID = 2; 34 | byte currentRetryCount = 0; 35 | 36 | // Other variables that change during runtime (Do not touch) 37 | static int interruptPin; 38 | 39 | byte adcsraSave; // to save the ADCSRA value 40 | voltage batteryVoltage; 41 | static unsigned long lastTxTime = 0; 42 | bool shouldGoToSleep = true; 43 | 44 | #if defined(DEBUG) 45 | static unsigned long lastWakeupTime = 0; 46 | #endif 47 | 48 | void setup() { 49 | 50 | #if defined(DEBUG) 51 | Serial.begin(9600); 52 | Serial.println("Doorbell Transmitter"); 53 | #endif 54 | 55 | #if defined(DEBUG) 56 | Serial.println("Setting up power saving settings..."); 57 | #endif 58 | for (int pin = 0; pin < 20; pin++) { // all pins to output - power saving 59 | pinMode(pin,OUTPUT); 60 | digitalWrite(pin,LOW); 61 | } 62 | wdt_disable(); // disable WDT for power saving 63 | EIFR = 3; // clear external interrupt flag register 64 | //ADCSRA = 0; // disable ADC for power saving 65 | 66 | #if defined(DEBUG) 67 | Serial.println("Setting Up Interrupt Pin..."); 68 | #endif 69 | pinMode(WAKE_BUTTON_PIN, INPUT_PULLUP); 70 | //pinMode(WAKE_BUTTON_PIN, INPUT); 71 | interruptPin = digitalPinToInterrupt(WAKE_BUTTON_PIN); 72 | 73 | #if defined(DEBUG) 74 | Serial.println("Initializing CC1101 Library..."); 75 | #endif 76 | ELECHOUSE_cc1101.setGDO(GDO0, 0); // set lib internal gdo pins (GDO0,GDO2). GDO2 not used for this example. 77 | ELECHOUSE_cc1101.Init(); // must be set to initialize the cc1101! 78 | ELECHOUSE_cc1101.setCCMode(1); // set config for internal transmission mode. 79 | ELECHOUSE_cc1101.setModulation(0); // set modulation mode. 0 = 2-FSK, 1 = GFSK, 2 = ASK/OOK, 3 = 4-FSK, 4 = MSK. 80 | ELECHOUSE_cc1101.setMHZ(433.92); // Here you can set your basic frequency. The lib calculates the frequency automatically (default = 433.92).The cc1101 can: 300-348 MHZ, 387-464MHZ and 779-928MHZ. Read More info from datasheet. 81 | ELECHOUSE_cc1101.setSyncMode(2); // Combined sync-word qualifier mode. 0 = No preamble/sync. 1 = 16 sync word bits detected. 2 = 16/16 sync word bits detected. 3 = 30/32 sync word bits detected. 4 = No preamble/sync, carrier-sense above threshold. 5 = 15/16 + carrier-sense above threshold. 6 = 16/16 + carrier-sense above threshold. 7 = 30/32 + carrier-sense above threshold. 82 | // ELECHOUSE_cc1101.setPA(10); // set TxPower. The following settings are possible depending on the frequency band. (-30 -20 -15 -10 -6 0 5 7 10 11 12) Default is max! 83 | ELECHOUSE_cc1101.setCrc(1); // 1 = CRC calculation in TX and CRC check in RX enabled. 0 = CRC disabled for TX and RX. 84 | 85 | #if defined(DEBUG) 86 | Serial.println("Doorbell Transmitter Is Ready!"); 87 | #endif 88 | delay(100); 89 | } 90 | 91 | void loop() { 92 | #if DEBUG == verbose 93 | cc1101_debug.debug(); 94 | #endif 95 | 96 | if (shouldGoToSleep) { 97 | enterSleep(); 98 | shouldGoToSleep = false; 99 | currentRetryCount = 0; 100 | batteryVoltage.asInt = getVoltage(); 101 | #if DEBUG == verbose 102 | Serial.print("Battery voltage: "); 103 | Serial.print(batteryVoltage.asInt); 104 | Serial.println("mV"); 105 | #endif 106 | } 107 | 108 | if (!shouldGoToSleep) { 109 | unsigned long now = millis(); 110 | if (currentRetryCount == 0 || now - lastTxTime > DELAY_BETWEEN_RETRIES) { 111 | sendDoorbellSignal(); 112 | lastTxTime = now; 113 | 114 | #if defined(DEBUG) 115 | Serial.println("Waiting For A Response..."); 116 | #endif 117 | } 118 | 119 | awaitDoorbellReceiverResponse(); 120 | if (currentRetryCount > MAX_RETRIES) { 121 | #if defined(DEBUG) 122 | Serial.println("Failed To Get A Response From The Doorbell Reveicer!"); 123 | #endif 124 | shouldGoToSleep = true; 125 | } 126 | } 127 | } 128 | 129 | void sendDoorbellSignal() { 130 | byte packet[12]; 131 | 132 | for (int i = 0; i < 8; i++) { 133 | packet[i] = DOORBELL_ID[i]; 134 | } 135 | packet[8] = TRANSMITTER_ID; 136 | packet[9] = currentRetryCount; 137 | packet[10] = batteryVoltage.asBytes[0]; 138 | packet[11] = batteryVoltage.asBytes[1]; 139 | 140 | currentRetryCount++; 141 | 142 | #if defined(DEBUG) 143 | Serial.println("Sending Doorbell Signal..."); 144 | #endif 145 | 146 | ELECHOUSE_cc1101.SendData(packet, 12); 147 | 148 | #if defined(DEBUG) 149 | Serial.println("Sent Doorbell Signal!"); 150 | #endif 151 | 152 | #if DEBUG == verbose 153 | Serial.print("Transmit data "); 154 | for (int i = 0; i < 12; i++) { 155 | Serial.print(packet[i], DEC); 156 | Serial.print(","); 157 | } 158 | Serial.println(); 159 | #endif 160 | 161 | ELECHOUSE_cc1101.SetRx(); 162 | } 163 | 164 | void awaitDoorbellReceiverResponse() { 165 | if (ELECHOUSE_cc1101.CheckRxFifo(50)) { 166 | if (ELECHOUSE_cc1101.CheckCRC()) { 167 | 168 | byte buffer[61] = {0}; 169 | int len = ELECHOUSE_cc1101.ReceiveData(buffer); 170 | buffer[len] = '\0'; 171 | 172 | for (int i = 0; i < 8; i++) { 173 | if (buffer[i] != DOORBELL_ID[i]) { 174 | return; 175 | } 176 | } 177 | if (buffer[8] != RECEIVER_ID) { 178 | return; 179 | } 180 | 181 | #if DEBUG == verbose 182 | Serial.print("Received data "); 183 | for (int i = 0; i < 9; i++) { 184 | Serial.print(buffer[i]); 185 | Serial.print(","); 186 | } 187 | Serial.println(); 188 | #endif 189 | 190 | #if defined(DEBUG) 191 | Serial.println("Successfully Received A Response!"); 192 | #endif 193 | 194 | shouldGoToSleep = true; 195 | } 196 | } 197 | } 198 | 199 | // Read the voltage of the battery the Arduino is currently running on (in millivolts) 200 | int getVoltage(void) { 201 | #if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) // For mega boards 202 | const long InternalReferenceVoltage = 1115L; // Adjust this value to your boards specific internal BG voltage x1000 203 | ADMUX = (0< 6 | #endif 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | using namespace ace_button; 14 | 15 | typedef union { // makes converting the voltage bytes back into an int easier 16 | int asInt; 17 | byte asBytes[2]; 18 | } voltage; 19 | 20 | // CONSTANTS (Change to your liking) 21 | const int DOORBELL_RINGTONE_CHANGE_BUTTON_PIN = 9; 22 | const int DF_PLAYER_RX_PIN = 8; 23 | const int DF_PLAYER_TX_PIN = 9; 24 | #ifdef ESP32 25 | const int GDO0 = 2; // for esp32! GDO0 on GPIO pin 2. 26 | #elif ESP8266 27 | const int GDO0 = 5; // for esp8266! GDO0 on pin 5 = D1. 28 | #else 29 | const int GDO0 = 6; // for Arduino! GDO0 on pin 6. 30 | #endif 31 | const byte DOORBELL_ID[8] = {127, 33, 45, 91, 27, 60, 8, 16}; 32 | const int MIN_TIME_BETWEEN_RINGS = 1000; // milliseconds 33 | 34 | // CONSTANTS (Do not touch) 35 | const byte TRANSMITTER_ID = 1; 36 | const byte RECEIVER_ID = 2; 37 | const int RINGTONE_SETTING_ADDRESS = 0x0; 38 | 39 | // Other variables that change during runtime (Do not touch) 40 | static unsigned long lastPlayTime = 0; 41 | byte lastTransmitterRetryCount = 255; 42 | bool shouldRingDoorbell = false; 43 | bool shouldTransmitResponse = false; 44 | voltage batteryVoltage; 45 | byte currentDoorbellRingtone; 46 | int volume = 30; // 0-30 (30 = 100%) 47 | int totalTrackCount; 48 | 49 | // Instantiate some classes 50 | SoftwareSerial mp3PlayerSerial(DF_PLAYER_TX_PIN, DF_PLAYER_RX_PIN); 51 | DFPlayerMini_Fast mp3Player; 52 | 53 | AceButton changeRingtoneButton(DOORBELL_RINGTONE_CHANGE_BUTTON_PIN); 54 | void handleChangeRingtoneButtonEvent(AceButton*, uint8_t, uint8_t); 55 | 56 | void setup() { 57 | #if defined(DEBUG) 58 | Serial.begin(9600); 59 | Serial.println("Doorbell Receiver"); 60 | #endif 61 | 62 | #if defined(DEBUG) 63 | Serial.println("Setting Up Internal LED..."); 64 | #endif 65 | pinMode(LED_BUILTIN, OUTPUT); 66 | digitalWrite(LED_BUILTIN, LOW); 67 | 68 | #if defined(DEBUG) 69 | Serial.println("Setting up button..."); 70 | #endif 71 | pinMode(DOORBELL_RINGTONE_CHANGE_BUTTON_PIN, INPUT_PULLUP); 72 | 73 | ButtonConfig* changeRingtoneButtonConfig = changeRingtoneButton.getButtonConfig(); 74 | changeRingtoneButtonConfig->setEventHandler(handleChangeRingtoneButtonEvent); 75 | changeRingtoneButtonConfig->setFeature(ButtonConfig::kFeatureClick); 76 | 77 | 78 | #if defined(DEBUG) 79 | Serial.println("Loading settings..."); 80 | #endif 81 | currentDoorbellRingtone = EEPROM.read(RINGTONE_SETTING_ADDRESS); 82 | if (currentDoorbellRingtone < 1 || currentDoorbellRingtone > 254) 83 | currentDoorbellRingtone = 1; 84 | 85 | #if defined(DEBUG) 86 | Serial.println("Initializing CC1101 Library..."); 87 | #endif 88 | ELECHOUSE_cc1101.Init(); // must be set to initialize the cc1101! 89 | ELECHOUSE_cc1101.setGDO(GDO0, 0); // set lib internal gdo pins (GDO0,GDO2). GDO2 not used for this example. 90 | ELECHOUSE_cc1101.setCCMode(1); // set config for internal transmission mode. 91 | ELECHOUSE_cc1101.setModulation(0); // set modulation mode. 0 = 2-FSK, 1 = GFSK, 2 = ASK/OOK, 3 = 4-FSK, 4 = MSK. 92 | ELECHOUSE_cc1101.setMHZ(433.92); // Here you can set your basic frequency. The lib calculates the frequency automatically (default = 433.92).The cc1101 can: 300-348 MHZ, 387-464MHZ and 779-928MHZ. Read More info from datasheet. 93 | ELECHOUSE_cc1101.setSyncMode(2); // Combined sync-word qualifier mode. 0 = No preamble/sync. 1 = 16 sync word bits detected. 2 = 16/16 sync word bits detected. 3 = 30/32 sync word bits detected. 4 = No preamble/sync, carrier-sense above threshold. 5 = 15/16 + carrier-sense above threshold. 6 = 16/16 + carrier-sense above threshold. 7 = 30/32 + carrier-sense above threshold. 94 | // ELECHOUSE_cc1101.setPA(10); // set TxPower. The following settings are possible depending on the frequency band. (-30 -20 -15 -10 -6 0 5 7 10 11 12) Default is max! 95 | ELECHOUSE_cc1101.setCrc(1); // 1 = CRC calculation in TX and CRC check in RX enabled. 0 = CRC disabled for TX and RX. 96 | ELECHOUSE_cc1101.SetRx(); 97 | 98 | #if defined(DEBUG) 99 | Serial.println("Initializing Audio Library..."); 100 | #endif 101 | mp3PlayerSerial.begin(9600); 102 | mp3Player.begin(mp3PlayerSerial); 103 | totalTrackCount = mp3Player.numSdTracks(); 104 | #if defined(DEBUG) 105 | Serial.print("Setting volume to "); 106 | Serial.print((float)volume/30.0*100.0); 107 | Serial.println("%..."); 108 | #endif 109 | mp3Player.volume(volume); 110 | delay(20); 111 | 112 | #if defined(DEBUG) 113 | Serial.println("Doorbell Receiver Is Ready"); 114 | Serial.println(); 115 | #endif 116 | } 117 | 118 | void loop() { 119 | #if DEBUG == verbose 120 | cc1101_debug.debug(); 121 | #endif 122 | 123 | unsigned long now = millis(); 124 | 125 | awaitDoorbellTransmitterSignal(); 126 | 127 | if (shouldTransmitResponse) { 128 | sendResponse(); 129 | } 130 | 131 | if (shouldRingDoorbell) { 132 | if (now > lastPlayTime+MIN_TIME_BETWEEN_RINGS) { 133 | ringDoorbell(); 134 | lastPlayTime = now; 135 | } 136 | shouldRingDoorbell = false; 137 | } 138 | 139 | changeRingtoneButton.check(); 140 | } 141 | 142 | void handleChangeRingtoneButtonEvent(AceButton* /* button */, uint8_t eventType, uint8_t buttonState) { 143 | currentDoorbellRingtone++; 144 | if (currentDoorbellRingtone >= totalTrackCount) 145 | currentDoorbellRingtone = 0; 146 | #if defined(DEBUG) 147 | Serial.print("Ringtone changed to file #"); 148 | Serial.println(currentDoorbellRingtone); 149 | Serial.println("Writing ringtone setting to EEPROM..."); 150 | #endif 151 | EEPROM.update(RINGTONE_SETTING_ADDRESS, currentDoorbellRingtone); 152 | } 153 | 154 | void ringDoorbell() { 155 | #if defined(DEBUG) 156 | Serial.println("Doorbell Signal Received!"); 157 | float cellVoltage = millivoltToVolt(batteryVoltage.asInt/2); 158 | float capacity = convertAlkalineVoltageToCapacity(cellVoltage); 159 | float usableCapacity = calculateUsableCapacity(capacity); 160 | Serial.print(usableCapacity); 161 | Serial.println("% Usable Battery Left"); 162 | #if DEBUG == verbose 163 | Serial.print(cellVoltage); 164 | Serial.println("V Per Cell Left"); 165 | Serial.print(capacity); 166 | Serial.println("% Theorecical Battery Left"); 167 | #endif 168 | #endif 169 | digitalWrite(LED_BUILTIN, HIGH); 170 | #if defined(DEBUG) 171 | Serial.print("Playing MP3 file #"); 172 | Serial.println(currentDoorbellRingtone); 173 | #endif 174 | mp3Player.play(currentDoorbellRingtone); 175 | digitalWrite(LED_BUILTIN, LOW); 176 | } 177 | 178 | void sendResponse() { 179 | byte packet[9]; 180 | 181 | for (int i = 0; i < 8; i++) { 182 | packet[i] = DOORBELL_ID[i]; 183 | } 184 | packet[8] = RECEIVER_ID; 185 | 186 | ELECHOUSE_cc1101.SendData(packet, 9); 187 | 188 | #if DEBUG == verbose 189 | Serial.print("Transmit data "); 190 | for (int i = 0; i < 9; i++) { 191 | Serial.print(packet[i], DEC); 192 | Serial.print(","); 193 | } 194 | Serial.println(); 195 | #endif 196 | 197 | #if defined(DEBUG) 198 | Serial.println(); 199 | #endif 200 | 201 | ELECHOUSE_cc1101.SetRx(); 202 | shouldTransmitResponse = false; 203 | lastTransmitterRetryCount = 255; 204 | } 205 | 206 | void awaitDoorbellTransmitterSignal() { 207 | if (ELECHOUSE_cc1101.CheckRxFifo(50)) { 208 | if (ELECHOUSE_cc1101.CheckCRC()) { 209 | 210 | byte buffer[61] = {0}; 211 | int len = ELECHOUSE_cc1101.ReceiveData(buffer); 212 | buffer[len] = '\0'; 213 | 214 | for (int i = 0; i < 8; i++) { 215 | if (buffer[i] != DOORBELL_ID[i]) { 216 | return; 217 | } 218 | } 219 | if (buffer[8] != TRANSMITTER_ID) { 220 | return; 221 | } 222 | 223 | #if DEBUG == verbose 224 | Serial.print("Data Received: "); 225 | for (int i = 0; i < 8; i++) { 226 | Serial.print(buffer[i]); 227 | Serial.print(","); 228 | } 229 | Serial.println(); 230 | #endif 231 | 232 | batteryVoltage.asBytes[0] = buffer[10]; 233 | batteryVoltage.asBytes[1] = buffer[11]; 234 | 235 | if (buffer[9] <= lastTransmitterRetryCount) { 236 | lastTransmitterRetryCount = buffer[9]; 237 | shouldRingDoorbell = true; 238 | } 239 | shouldTransmitResponse = true; 240 | } 241 | } 242 | } 243 | 244 | float millivoltToVolt(int millivolt) { 245 | return (float)millivolt/1000; 246 | } 247 | 248 | // For alkaline-based 1.5V AA cells 249 | float convertAlkalineVoltageToCapacity(float v) { 250 | if (v >= 1.55) 251 | return 100.0; //static value 252 | else if (v <= 0) 253 | return 0.0; //static value 254 | else if (v > 1.4) 255 | return 60.60606*v + 6.060606; //linear regression 256 | else if (v < 1.1) 257 | return 8.3022*v; //linear regression 258 | else 259 | return 9412 - 23449*v + 19240*v*v - 5176*v*v*v; // cubic regression 260 | } 261 | 262 | // Since the Arduino requires at least 2.4V to run, we pretend the battery capacity is 0% when it goes below 2.4V (2 * 1.2V) 263 | // This happens at about 27% 264 | const float USABLE_CAPACITY_LOWER_LIMIT = 27; 265 | float calculateUsableCapacity(float realCapacity) { 266 | float usableCapacity = (realCapacity-USABLE_CAPACITY_LOWER_LIMIT) / (100-USABLE_CAPACITY_LOWER_LIMIT) * 100; 267 | if (usableCapacity < USABLE_CAPACITY_LOWER_LIMIT) 268 | return 0.0; 269 | else 270 | return usableCapacity; 271 | } 272 | --------------------------------------------------------------------------------