
Introduction
BOM
- 2x PTSolns Uno R3+ microcontroller development board.
- 2x PTSolns NRF-Shields.
- 1x PTSolns Proto-Shield.
- 2x nRF24L01+ RF modules. Any form factor of the standard three will work. In our project we used the +PA+LNA to get further reach.
- 1x relay that can be driven by a microcontroller. We used the LCA110 solid state relay. We also used a matching IC socket, but this is not required if you don't plan on removing the IC from the shield. Though, we typically recommend to never solder ICs directly and use an IC socket instead.
- A couple of 2-Pin screw terminals to connect the wires to (both the Tx and Rx nodes have wires going into a screw terminal). We used 5.08mm/0.2in pitched screw terminals to accommodate for heavier gauge wire.
- A custom bottom as shown in the cover picture. This can be any sort of button, switch or toggle as long as when activated it closes the circuit it is attached to.
- Power sources for both nodes. We just used wall adapters with 7V DC output and plugged right into the barrel jack.
Assembly
TxN Hardware Setup
- VCC Pin: 3V3
- CSN Pin: D10
- CE Pin: D7
- Interrupt Pin: Not used
Note that the user can change these hardware selection pins, but then must take care to update the corresponding sketch accordingly.
To finish the TxN we added a 2-Pin 5.08mm/0.2in screw terminal on the prototyping section onboard the NRF-Shield. One pin of the screw terminal is connected to the ground pin (GND), while the other is connected to D5 (which is conveniently made available on the prototyping section). With this screw terminal setup, the user can attach any custom button module. The Cuckoo Clock is triggered when D5 is pulled to GND via activation of the button (or switch, or toggle, etc.).

RxN Hardware Setup
The RxN has a similar setup for the NRF-Shield. The only difference in hardware selection pins is CSN is moved to the D8 pin, for no particular reason other than to change things up. Where the difference in the RxN comes in is in the custom prototyping shield that is stacked in the middle.
We are using the LCA110 solid state relay, which is driven by the D4 pin on the Uno below. This relay works great for these low power applications. It is small, fast, and cheap. So we love using it. In fact, we've used this relay before in a previous Tinker Thoughts Blog post: Getting Started with the ESP32 microWatt Making IoT Projects.
The following pin connections are made to the LCA110.
- Pin 1: D4
- Pin 2: GND
- Pin 3: Left floating, not connected
- Pin 4 jumper wire to Pin 6
- Pin 5 jumper wire to screw terminal
- Pin 6 jumper wire to screw terminal
With this arrangement the relay controls the two pin on the screw terminal. When D4 is driven HIGH, Pin 5 and Pin 6 close. Therefore, we run wires from the screw terminal into the Cuckoo Clock, added in parallel into the manual activation button on the back of the clock (more on that below), then simply driving D4 HIGH closes the relay on Pin 5 and 6, and this mimics the activation.

Hacking the Cuckoo Clock
On the back of the Cuckoo clock is a manual push button labeled "Adjust". When this is pressed the little birdie comes out and makes one extra cuckoo sounds than the previous time he made an appearance. This continues until 12 sounds, and repeats back to one.
We opened up the clock and ran a two stranded wire into it from the back, right next to this adjust button. From there we simply cut the wires of the button and wired in, in parallel, our wires that are coming from the screw terminal on the RxN. That's it, it really was that simple. Took about five minutes to set up.
Code
#include <SPI.h>
#include <RF24.h>
const uint8_t CE = 7;
const uint8_t CSN = 10;
const uint8_t TRIG = 5; // pull to GND to send
RF24 radio(CE, CSN);
const byte ADDR[6] = "CH01A";
uint32_t counter = 0;
bool lastState = HIGH; // using INPUT_PULLUP
void setup() {
Serial.begin(115200);
pinMode(TRIG, INPUT_PULLUP);
if (!radio.begin()) { Serial.println("nRF init failed"); while (1) {} }
radio.setPALevel(RF24_PA_LOW);
radio.setDataRate(RF24_1MBPS);
radio.setChannel(108);
radio.setRetries(3, 5);
radio.setAutoAck(true);
radio.stopListening();
radio.openWritingPipe(ADDR);
Serial.println("TX ready");
}
void loop() {
bool s = digitalRead(TRIG);
if (lastState == HIGH && s == LOW) {
counter++;
bool ok = radio.write(&counter, sizeof(counter));
Serial.print("TX send cnt=");
Serial.print(counter);
Serial.println(ok ? " ok" : " fail");
delay(50); // cheap debounce
}
lastState = s;
}
#include <SPI.h>
#include <RF24.h>
const uint8_t CE = 8;
const uint8_t CSN = 10;
const uint8_t PULSE = 4; // output that pulses on RX
RF24 radio(CE, CSN);
const byte ADDR[6] = "CH01A";
void setup() {
Serial.begin(115200);
pinMode(PULSE, OUTPUT);
digitalWrite(PULSE, LOW);
if (!radio.begin()) {
Serial.println("nRF init failed");
while (1) {}
}
radio.setPALevel(RF24_PA_LOW);
radio.setDataRate(RF24_1MBPS);
radio.setChannel(108);
radio.setAutoAck(true);
radio.openReadingPipe(1, ADDR);
radio.startListening();
Serial.println("RX listening");
}
void loop() {
if (radio.available()) {
uint32_t cnt = 0;
while (radio.available()) {
radio.read(&cnt, sizeof(cnt));
}
// Pulse D4 HIGH for 0.4 seconds
digitalWrite(PULSE, HIGH);
delay(400);
digitalWrite(PULSE, LOW);
Serial.print("RX got cnt=");
Serial.println(cnt);
}
}