This is the fourth in a series of blog posts about researching and rewiring Christmas lights to work with Arduino devices.

I received the radio modules that I ordered:

FS1000A receiver and transmitter

The transmitter module is on the right, the receiver on the left.

I want to capture the signal structure for the RF remote functions. Scott C wrote an excellent tutorial for using these same RF modules to decode and re-play signals for an RF-controlled fan/light.

I connected the receiver to the Arduino as shown below.

RF ReceiverArduino
GNDGND
DATA(No Connection)
DATAAnalog 0
VCC3.3V

FS1000A receiver connected to Arduino

The receiver is connected to 3.3V power as Scott C suggested, to reduce the amount of interference received. This may affect the distance at which the receiver works, but in this case the receiver is only being used temporarily to decode the remote signals.

I then loaded a modified version of Scott’s code into the Arduino IDE:

/* 
  RF Remote Capture sketch 
     Written by ScottC 24 Jun 2014
     Modified by James Badger 10 Nov 2016
     Arduino IDE version 1.6.11
     Website: http://arduinobasics.blogspot.com
     Receiver: XY-MK-5V
     Description: Use Arduino to Receive RF Remote signal          
 ------------------------------------------------------------- */

const int dataSize = 500;    // Arduino memory is limited (max=1700)
byte storedData[dataSize];   // Create an array to store the data
#define ledPin 13            // Onboard LED = digital pin 13
#define rfReceivePin A0      // RF Receiver data pin = Analog pin 0
const unsigned int upperThreshold = 100;  // upper threshold value
const unsigned int lowerThreshold = 80;  // lower threshold value
int maxSignalLength = 255;       // Set the maximum length of the signal
int dataCounter = 0;             // Variable to measure the length of the signal
unsigned long startTime = 0;       // Variable to record the start time 
unsigned long endTime = 0;         // Variable to record the end time 
unsigned long signalDuration = 0;  // Variable to record signal reading time


void setup() {
  Serial.begin(9600);
  pinMode(ledPin, OUTPUT);
  
  /* The following code will only run ONCE --------------
  ---Press the reset button on the Arduino to run again-- */
  
  while(analogRead(rfReceivePin) < 1) {
      // Wait here until a LOW signal is received
      startTime = micros();  // Update start time with every cycle.  
  }
  digitalWrite(ledPin, HIGH);  // Turn LED ON
  
  // Read and store the rest of the signal into the storedData array
  for(int i = 0; i < dataSize; i = i + 2) {
    // Identify the length of the HIGH signal---------------HIGH
    dataCounter = 0; // reset the counter
    while(analogRead(rfReceivePin) > upperThreshold && dataCounter < maxSignalLength) {
      dataCounter++;
    }
    storedData[i] = dataCounter;

    // Identify the length of the LOW signal---------------LOW
    dataCounter = 0; // reset the counter
    while(analogRead(rfReceivePin) < lowerThreshold && dataCounter < maxSignalLength) {
      dataCounter++;
    }
    storedData[i+1] = dataCounter;
    
    // Any readings between the two threshold values will be ignored.
    // The LOW or HIGH signal length must be less than the variable "maxSignalLength"
    // otherwise it will be truncated. All of the HIGH signals and LOW signals combined
    // must not exceed the variable "dataSize", otherwise it will be truncated.
    // The maximum number of signals is 1700 - if you try to extend this variable to a higher
    // number than 1700 - then the Arduino will freeze up and sketch will not work.
    //-------------------------------------------------------------
  }
  
  endTime = micros();  // Record the end time of the read period.
  signalDuration = endTime - startTime;
  
  digitalWrite(ledPin, LOW); // Turn LED OFF
  
  // Send report to the Serial Monitor
  Serial.println("=====================");
  Serial.print("Read duration: ");
  Serial.print(signalDuration);
  Serial.println(" microseconds");
  Serial.println("=====================");
  Serial.println("HIGH,LOW");
  delay(20);
  for(int i = 0; i < dataSize; i = i + 2) {
    Serial.print(storedData[i]);
    Serial.print(",");
    Serial.println(storedData[i+1]);
    delay(20);
  }
}

void loop() {
  //Do nothing here
}

There was a minor mix up in the original code where signals with low numbers (close to zero) were called “HIGH”, and vice versa. This isn’t quite correct as LOW signals should give lower analog readings and HIGH signals should give higher analog readings. I switched the code above to label the resulting data correctly.

Next I had to run the program once for each remote function: “Power On”, “Power Off”, “Function Cycle”, and “Sync”. I turned opened the Serial Monitor and uploaded the program, waited until the LED turned on, then pressed the Power button on the remote to turn the lights on. I then repeated the process with Power Off, Cycle Function, and Sync, producing the following four files.

In each file, a definitive repeated pattern shows up although the pattern is different in each file. Each number represents the duration of a HIGH or LOW signal that was detected. Each pattern repeats itself multiple times; 5 for Power ON, 6 for Power OFF, 3 for CYCLE, and 5 for SYNC. This may be caused by how long I held down the buttons on the remote, but for each case the signals are typically repeated.

If I take the Power On set and put the results into a table, the pattern is even more apparent.

"Power On" Signal Durations
HLHLHLHLHL
26132612251326132612
3443343443
3848383848
4334444334
4838384838
3848393848
4334344334
4334344334
4839384839
3838483838
4839384839
3434433434
3838483938
4443343443
3443343443
3443343443
3443343443
3714713713714255

As with Scott’s tutorial, some of the numbers are estimates and can be smoothed a bit — for example, 3 durations replaced by 4 and so on. With some formatting, the signal becomes obvious:

"Power On" Signal Pattern
HLHLHLHLHL
26122612261226122612
4444444444
4848484848
4444444444
4848484848
4848484848
4444444444
4444444444
4848484848
4848484848
4848484848
4444444444
4848484848
4444444444
4444444444
4444444444
4444444444
471471471471471

And with the remaining signals, their specific commands are displayed.

"Power Off" Signal Codes
HLHLHLHLHLHL
261226122612261226122612
444444444444
484848484848
444444444444
484848484848
484848484848
444444444444
444444444444
484848484848
484848484848
484848484848
444444444444
484848484848
444444444444
444444444444
444444444444
484848484848
471471471471471471
"Cycle Function" Signal Pattern
HLHLHL
261226122612
444444
484848
444444
484848
484848
444444
444444
484848
484848
484848
484848
444444
484848
484848
444444
444444
471471471
"Sync Lights" Signal Pattern
HLHLHLHLHL
26122612261226122612
4444444444
4444444444
4848484848
4848484848
4444444444
4848484848
4848484848
4848484848
4444444444
4444444444
4444444444
4848484848
4848484848
4848484848
4444444444
4444444444
471471471471471

The next step was creating an Arduino program that can repeat these patterns, although the specific length of each signal is not yet known, only the relative lengths were captured in the receiver program above. To determine the actual delay length the codes will have to be tried repeatedly with an increasing delay to find the ones that actually activate the lights.

To send codes I connected the transmitter to the Arduino as shown below.

RF ReceiverArduino
GNDGND
DATADigital 4
VCC5V

Next I uploaded the following program, based on the one Scott C used in his tutorial.

/* 
  Transmit sketch - RF Calibration
     Written by ScottC 17 July 2014
     Edited by James Badger 10 November 2016
     Arduino IDE version 1.6.11
     Website: http://arduinobasics.blogspot.com
     Transmitter: FS1000A/XY-FST
     Description: A simple sketch used to calibrate RF transmission.          
 ------------------------------------------------------------- */

#define rfTransmitPin 4  // RF Transmitter pin = digital pin 4
#define ledPin 13        // Onboard LED = digital pin 13

const int codeSize = 18;
int powerOnSignal[18][2] = {
  {26,12},
  {4,4},
  {4,8},
  {4,4},
  {4,8},
  {4,8},
  {4,4},
  {4,4},
  {4,8},
  {4,8},
  {4,8},
  {4,4},
  {4,8},
  {4,4},
  {4,4},
  {4,4},
  {4,4},
  {4,71}
};
int timeDelay = 50; // The variable used to calibrate the RF signal lengths.

void setup() {
  Serial.begin(9600);        // Turn the Serial Protocol ON
  pinMode(rfTransmitPin, OUTPUT);   //Transmit pin is an output  
  pinMode(ledPin, OUTPUT);
  
  // LED initialisation sequence - gives us some time to get ready
  digitalWrite(ledPin, HIGH); 
  delay(3000);
  digitalWrite(ledPin, LOW); 
  delay(1000);
}

void loop() {
  Serial.print("Delay: ");
  Serial.print(timeDelay);
  Serial.print(" microseconds\n");
  transmitCode();  // transmit the code to RF receiver on the lights
  
  timeDelay += 10; // Increment the timeDelay by 10 microseconds with every transmission
  delay(5000); // Each transmission will be about 5 seconds apart.
}
  
void transmitCode() {
  // The LED will be turned on to create a visual signal transmission indicator.
  digitalWrite(ledPin, HIGH);
 
  // initialise the variables 
  int highLength = 0;
  int lowLength = 0;
  
  // The signal is transmitted 6 times in succession - this may vary with your remote.       
  int time = millis();
  for(int j = 0; j < 6; j++) {
    for(int i = 0; i < codeSize; i++) {
      highLength = powerOnSignal[i][0];
      lowLength = powerOnSignal[i][1];
      
       /* Transmit a HIGH signal - the duration of transmission will be determined 
          by the highLength and timeDelay variables */
       digitalWrite(rfTransmitPin, HIGH);     
       delayMicroseconds(highLength * timeDelay); 
       
       /* Transmit a LOW signal - the duration of transmission will be determined 
          by the lowLength and timeDelay variables */
       digitalWrite(rfTransmitPin,LOW);     
       delayMicroseconds(lowLength * timeDelay);  
    }
  }
  int duration = millis() - time;
  Serial.print("Duration: ");
  Serial.print(duration);
  Serial.print(" milliseconds\n");
  //Turn the LED off after the code has been transmitted.
  digitalWrite(ledPin, LOW); 
}

I embedded the “Power On” code directly in a 2D array to simplify the code. I will use remote code aliases later when the other codes are needed. I updated the wait between signal tests to 5 seconds as I had to use the real remote to turn the lights off between tests if the Arduino had turned the lights on.

I ran the program and opened the Serial Monitor to monitor the delays. I found that delays from 100 to 140 microseconds all worked and turned on the lights! Splitting that down the middle would give 120 microseconds.

With that in hand I wrote a program that takes input over Serial and uses that to control the lights.

// Remote Control Sketch
#define rfTransmitPin 4
#define ledPin 13

const int codeSize = 18;
int codeTable[4][2] = {
  {4,4},  // 0
  {4,8},  // 1
  {4,71}, // 2
  {26,12} // 3
};
int powerOn[codeSize] = {3,0,1,0,1,1,0,0,1,1,1,0,1,0,0,0,0,2};
int powerOff[codeSize] = {3,0,1,0,1,1,0,0,1,1,1,0,1,0,0,0,1,2};
int cycle[codeSize] = {3,0,1,0,1,1,0,0,1,1,1,1,0,1,1,0,0,2};
int sync[codeSize] = {3,0,0,1,1,0,1,1,1,0,0,0,1,1,1,0,0,2};

// The length of the RF signals, in microseconds
int timeDelay = 120;
int incomingByte = 0;
int codeAlias;
int highLength;
int lowLength;

void setup() {
  Serial.begin(9600);
  pinMode(rfTransmitPin, OUTPUT);
  pinMode(ledPin, OUTPUT);
  
  digitalWrite(ledPin, HIGH); 
  delay(3000);
  digitalWrite(ledPin, LOW); 
  delay(1000);
  Serial.println("Ready to receive commands.");
  Serial.read();
}

void loop() {
  if (Serial.available() >= 0) {
    incomingByte = Serial.read();

    switch(incomingByte) {
      case '1':
      Serial.println("Power on!");
      transmitCode(powerOn);
      break;
      case '0':
      Serial.println("Power off!");
      transmitCode(powerOff);
      break;
      case 'C':
      Serial.println("Cycle function!");
      transmitCode(cycle);
      break;
      case 'S':
      Serial.println("Sync!");
      transmitCode(sync);
      break;
      default:
      break;
    }
  }
}

void transmitCode(int *code) {
  for (int i = 0; i < codeSize; i++) {
    codeAlias = code[i];

    highLength = codeTable[codeAlias][0];
    lowLength = codeTable[codeAlias][1];

     digitalWrite(rfTransmitPin, HIGH);     
     delayMicroseconds(highLength * timeDelay); 
     
     digitalWrite(rfTransmitPin, LOW);     
     delayMicroseconds(lowLength * timeDelay);
  }
}

By typing “1”, “0”, “C”, or “S” in the Serial Monitor console I could then send the commands to the lights. This worked great, except for one thing: The cycle function only loads a specific lights program instead of cycling them. This could mean that each lights program has its own RF code pattern!

I loaded the receiver program again, and ran it a few times to capture the output of the functions button on the remote. The box for the Home Collection lights advertises 8 different programs, so I am assuming that if I collect 9 remote function signals that I should loop around and collect the first one twice.

"Cycle Function" Code Patterns
FunctionCode Pairs
Function 126,124,44,84,44,84,84,44,44,84,84,84,84,44,44,44,44,44,71
Function 226,124,44,84,44,84,84,44,44,84,84,84,84,44,44,44,44,84,71
Function 326,124,44,84,44,84,84,44,44,84,84,84,84,44,44,44,84,44,71
Function 426,124,44,84,44,84,84,44,44,84,84,84,84,44,44,44,84,84,71
Function 526,124,44,84,44,84,84,44,44,84,84,84,84,44,44,84,44,44,71
Function 626,124,44,84,44,84,84,44,44,84,84,84,84,44,44,84,44,84,71
Function 726,124,44,84,44,84,84,44,44,84,84,84,84,44,44,84,84,44,71
Function 826,124,44,84,44,84,84,44,44,84,84,84,84,44,44,84,84,84,71
Function 926,124,44,84,44,84,84,44,44,84,84,84,84,44,84,44,44,44,71
Function 1026,124,44,84,44,84,84,44,44,84,84,84,84,44,84,44,44,84,71
Function 1126,124,44,84,44,84,84,44,44,84,84,84,84,44,84,44,84,44,71
Function 1226,124,44,84,44,84,84,44,44,84,84,84,84,44,84,44,84,84,71
Function 1326,124,44,84,44,84,84,44,44,84,84,84,84,44,84,84,44,44,71
Function 1426,124,44,84,44,84,84,44,44,84,84,84,84,44,84,84,44,84,71
Function 1526,124,44,84,44,84,84,44,44,84,84,84,84,44,84,84,84,44,71
Function 1626,124,44,84,44,84,84,44,44,84,84,84,84,44,84,84,84,84,71
Function 1726,124,44,84,44,84,84,44,44,84,84,84,84,44,44,44,44,44,71

As seen in the table above, there were more than eight codes and actually sixteen in total. You may have noticed the progression on the right side of the code table. Replacing blue cells with 0 and green cells with 1 produces the following sequence:

00000
00001
00010
00011
00100
...

This is binary for 0, 1, 2, 3, and so on. I updated my control program to support these codes:

// Remote Control Sketch
#define rfTransmitPin 4
#define ledPin 13

const int codeSize = 18;
int codeTable[4][2] = {
  {4,4},  // 0
  {4,8},  // 1
  {4,71}, // 2
  {26,12} // 3
};
int powerOn[codeSize] = {3,0,1,0,1,1,0,0,1,1,1,0,1,0,0,0,0,2};
int powerOff[codeSize] = {3,0,1,0,1,1,0,0,1,1,1,0,1,0,0,0,1,2};
int sync[codeSize] = {3,0,0,1,1,0,1,1,1,0,0,0,1,1,1,0,0,2};
int programs[16][codeSize] = {
  {3,0,1,0,1,1,0,0,1,1,1,1,0,0,0,0,0,2},
  {3,0,1,0,1,1,0,0,1,1,1,1,0,0,0,0,1,2},
  {3,0,1,0,1,1,0,0,1,1,1,1,0,0,0,1,0,2},
  {3,0,1,0,1,1,0,0,1,1,1,1,0,0,0,1,1,2},
  {3,0,1,0,1,1,0,0,1,1,1,1,0,0,1,0,0,2},
  {3,0,1,0,1,1,0,0,1,1,1,1,0,0,1,0,1,2},
  {3,0,1,0,1,1,0,0,1,1,1,1,0,0,1,1,0,2},
  {3,0,1,0,1,1,0,0,1,1,1,1,0,0,1,1,1,2},
  {3,0,1,0,1,1,0,0,1,1,1,1,0,1,0,0,0,2},
  {3,0,1,0,1,1,0,0,1,1,1,1,0,1,0,0,1,2},
  {3,0,1,0,1,1,0,0,1,1,1,1,0,1,0,1,0,2},
  {3,0,1,0,1,1,0,0,1,1,1,1,0,1,0,1,1,2},
  {3,0,1,0,1,1,0,0,1,1,1,1,0,1,1,0,0,2},
  {3,0,1,0,1,1,0,0,1,1,1,1,0,1,1,0,1,2},
  {3,0,1,0,1,1,0,0,1,1,1,1,0,1,1,1,0,2},
  {3,0,1,0,1,1,0,0,1,1,1,1,0,1,1,1,1,2}
};

// The length of the RF signals, in microseconds
int timeDelay = 120;
char inBuffer[4];
int codeAlias;
int highLength;
int lowLength;
int programID;

void setup() {
  Serial.begin(9600);
  pinMode(rfTransmitPin, OUTPUT);
  pinMode(ledPin, OUTPUT);
  
  digitalWrite(ledPin, HIGH); 
  delay(3000);
  digitalWrite(ledPin, LOW); 
  delay(1000);
  Serial.println("Ready to receive commands.");
  Serial.read();
}

void loop() {
  if (Serial.available() > 0) {
    Serial.readBytes(inBuffer, 4);

    switch(inBuffer[0]) {
      case '1':
      Serial.println("Power on!");
      transmitCode(powerOn);
      break;
      case '0':
      Serial.println("Power off!");
      transmitCode(powerOff);
      break;
      case 'P':
      // Check if input is 1 followed by 0 through 5
      if (inBuffer[1] == '1' && inBuffer[2] >= 48 && inBuffer[2] <= 53) {
        programID = 10 + inBuffer[2] - 48;
        Serial.print("Running Program: ");
        Serial.println(programID, DEC);
        transmitCode(programs[programID]);
      } else if (inBuffer[1] >= 48 && inBuffer[1] <= 57) {
        programID = inBuffer[1] - 48;
        Serial.print("Running Program: ");
        Serial.println(programID, DEC);
        transmitCode(programs[programID]);
      } else {
        Serial.println(inBuffer[1]);
        Serial.println("Invalid program number.");
      }
      break;
      case 'S':
      Serial.println("Sync!");
      transmitCode(sync);
      break;
      default:
      break;
    }
  }
  inBuffer[0] = 0;
}

void transmitCode(int *code) {
  for (int j = 0; j < 6; j++) {
    for (int i = 0; i < codeSize; i++) {
      codeAlias = code[i];
  
      highLength = codeTable[codeAlias][0];
      lowLength = codeTable[codeAlias][1];
  
       digitalWrite(rfTransmitPin, HIGH);     
       delayMicroseconds(highLength * timeDelay); 
       
       digitalWrite(rfTransmitPin, LOW);     
       delayMicroseconds(lowLength * timeDelay);
    }
  }
}

Upon testing the codes, here are their actual names:

Function IDFunction Activity
0Combination
1Steady On
22Hz Blink
3Fast Blink
4Slow Fade-in/Fade-out
5Fast Fade-in/Fade-out
6Blink-Blink-Steady
7Discrete Fade-in/Fade-out
8Combination
9Steady On
102Hz Blink
11Fast Blink
12Slow Fade-in/Fade-out
13Fast Fade-in/Fade-out
14Blink-Blink-Steady
15Discrete Fade-in/Fade-out

It looks like the codes are repeated and while the remote and controller could support up to 16 functions, only 8 have been programmed.

So what’s next?

Secondary Project

  1. Get 433.920 MHz radio boards for Arduino
  2. Test HC lights remote with Arduino and radio receiver to determine signals for controlling HC lights
  3. Use Arduino and radio transmitter to simulate the remote and control the lights
  4. Connect Arduino to WiFi or Bluetooth and setup remote control from iOS or from a Raspberry Pi
  5. Find a housing for the Arduino — it is wireless so it doesn’t need to go outdoors but it will need power
  6. Test range of the remote, then the range of the Arduino
  7. Get antenna if necessary
  8. Set up lights outdoors, then control them from indoors!

I have an ESP8266 WiFi module that I can connect to the Arduino and run a small TCP server, allowing me to send commands over my local network and control the lights. I could also program the ESP8266 directly, although I don’t want to deal with breadboards yet.