Control Livolo switches / Livolo switch library

Hi folks,

Sorry for not answering questions in timely manner. I didn't get immediate follow up to this post, so I thought that everyone was quite satisfied with initial idea, or not interested at all.

I'v been a bit busy doing some home automation, mostly light automation, of course (but there is auto music in bathroom and automatic wireless cat feeder. All controlled over internet, as you may have guessed :wink:

I did my best to answer your questions by PM. Here is a summary for all.

  1. Wiring to control switches is extremely simple. Regarding this code: transmitter DATA to Arduino digital pin 9, GND to GND, VCC to 5V.

  2. Wiring to record and analyze raw command includes some additional hardware: http://wiki.nethome.nu/lib/exe/detail.php/upm_scheme.png?id=analyzer%3Ahardware and a PC with your favourite audio recording software (I strongly recommend Audactiy)

Here you connect receiver DATA to RECEIVE point, GND to GND and GND of external 5V DC power supply, VCC to external 5V DC power supply.

MIC output should be connected to MIC in of your PC.

Please note that this does not require Arduino. Only receiver, divider, power supply and PC to record and analyze pulses.

  1. I didn't try to break into Livolo protocol. Mostly because I don't have the skills to do so. Sorry guys. But I have two recorded raw samples from two different Livolo remotes. That, I guess, shoud be enough to understand protocol.

Here are download links to WAVs with 11 keypresses (order: 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, All Off).

  1. Here is an automated (sorry) translation of how I did Livolo remote imitation:

Search for "MANAGEMENT Radio switch" in the text to read about Livolo remote imitation.

So, I guess that's all. I'll try to answer your questions if there are any.

hi thanks for your work,

could you share your latest and impoved code with us?

i copied the codes with audacity , i dont get the same waves, i guess i ll be able to reconstruct the signal thanks to your code

thanks

could you share your latest and impoved code with us?

Hi,

Sure. I was going to, anyway. But I didn't do anything to transmitting part, sorry. I've only optimized for memory.

Here is more neat (as I guess) code that should use far less precious RAM than previous. It's been cut from working code, that is why here are only few buttons.

#include <avr/pgmspace.h> // needed to use PROGMEM

#define  txPin  8 // pin connected to RF transmitter (pin 8)
byte i; // command pulses counter for Livolo (0 - 100)
byte pulse; // counter for command repeat

// commands stored in PROGMEM arrays (see on PROGMEM use here: http://arduino.cc/forum/index.php?topic=53240.0)
// first array element is length of command
const prog_uchar button1[45] PROGMEM ={44, 1, 2, 4, 2, 4, 2, 4, 3, 5, 2, 4, 2, 4, 3, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2};
const prog_uchar button2[43] PROGMEM ={42, 1, 2, 4, 2, 4, 2, 4, 3, 5, 2, 4, 2, 4, 3, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 5, 3, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2};
const prog_uchar button3[41] PROGMEM ={40, 1, 2, 4, 2, 4, 2, 4, 3, 5, 2, 4, 2, 4, 3, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 5, 3, 5, 3, 4, 2, 4, 2, 4, 2};
const prog_uchar button4[43] PROGMEM ={42, 1, 2, 4, 2, 4, 2, 4, 3, 5, 2, 4, 2, 4, 3, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 5, 3, 4, 2, 4, 2, 4, 2};
const prog_uchar button5[43] PROGMEM ={42, 1, 2, 4, 2, 4, 2, 4, 3, 5, 2, 4, 2, 4, 3, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 5, 2, 4, 3, 4, 2, 4, 2, 4, 2, 4, 2};
const prog_uchar button7[41] PROGMEM ={40, 1, 2, 4, 2, 4, 2, 4, 3, 5, 2, 4, 2, 4, 3, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 5, 3, 4, 2, 5, 3, 4, 2, 4, 2};
const prog_uchar button11[41] PROGMEM ={40, 1, 2, 4, 2, 4, 2, 4, 3, 5, 2, 4, 2, 4, 3, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 5, 3, 4, 2, 5, 2, 4, 3, 4, 2};

// pointers to command arrays
PROGMEM const prog_uchar *buttonPointer[] = {button1, button2, button3, button4, button5, button7, button11};

void setup() {

// sipmle example: send button "button2" once. Note that array elements numbered starting from "0" (so button1 is 0, button2 is 1 and so on)

txButton(1);

}

void loop() {
}

// transmitting part
// zeroes and ones here are not actual 0 and 1. I just called these pulses for my own convenience. 
// also note that I had to invert pulses to get everything working
// that said in actual command "start pulse" is long low; "zero" = short high; "one" = long high; "pause" is short low; "low" is long low.

void txButton(byte cmd) { 
prog_uchar *currentPointer = (prog_uchar *)pgm_read_word(&buttonPointer[cmd]); // current pointer to command array passed as txButton(cmd) argument
byte cmdCounter = pgm_read_byte(&currentPointer[0]); // read array length

for (pulse= 0; pulse <= 180; pulse = pulse+1) { // how many times to transmit a command
for (i = 1; i < cmdCounter+1; i = i + 1) { // counter for reading command array
  byte currentCmd = pgm_read_byte(&currentPointer[i]); // readpulse type from array
  switch(currentCmd) { // transmit pulse
   case 1: // start pulse
   digitalWrite(txPin, HIGH);
   delayMicroseconds(550);
   digitalWrite(txPin, LOW);
   break;
   case 2: // "zero"
   digitalWrite(txPin, LOW);
   delayMicroseconds(110);
   digitalWrite(txPin, HIGH);
   break;   
   case 3: // "one"
   digitalWrite(txPin, LOW);
   delayMicroseconds(303);
   digitalWrite(txPin, HIGH);
   break;      
   case 4: // "pause"
   digitalWrite(txPin, HIGH);
   delayMicroseconds(110);
   digitalWrite(txPin, LOW);
   break;      
   case 5: // "low"
   digitalWrite(txPin, HIGH);
   delayMicroseconds(290);
   digitalWrite(txPin, LOW);
   break;      
  } 
  }
 } 
 digitalWrite(txPin, LOW);
}

Have fun!

spch,
quick question, I've done pretty much the same thing with rf remotes but have a problem, if I do a page refresh for whatever reason in the browser, the switches are retriggered again turning them off if they're on and vice versa, are u running your app in a browser and if so have you experienced the same thing and have you been able to correct it, how? Thanks much....

comptrmedic,

I've never experienced such an issue. I'm using web server as in RC-Switch example.

There are, however, certain browser issues. For example, when using Chrome, I need to type command two times to get it working (I guess that is the way Chrome's cache is working). So when I need to use browser, I prefer Firefox over Chrome.

My best guess is that re-triggering happens because of browser's cache. Try using another browser, or turning off cache (note that this could also slow down your normal browsing). If that doesn't help, there may be something special about web server that is running on your Arduino.

ps. apart from browsers I use Tasker to for direct HTTP POST requests when I need to trigger a switch or change automation settings. I doesn't require to load/reload pages and works flawlessly.

Recently I took another look at the waveform of Livolo remote. And as I had some time to spare, that is what I saw.

  1. Each keypress sends remote ID and a command itself (approximately 8000 us, repeated about 100 times or more)
  2. Remote ID is unique to the remote and never changes during transmission
  3. Each command can be represented by 24 pulses: long start pulse plus 23 data pulses (bits). Of that 23 bits 16 are remote ID and the rest are the command.
  4. I've compared commands (7 bit) of two remotes, and they look identical.

Regarding pulses to bits conversion. I took for granted that data pulses are:

  1. short highs (I took them as "0")
  2. long highs and lows (excluding start pulse) (I took them as "1"). Right, "1" is therefore represented either by long high or long low - doesn't matter

This approach gives exactly 24 pulses (including start) for all avaliable to me commands (buttons 1 to 0). Here is my vision of data packet structure:

Then I thought there must be some kind of a rule which pulses must follow. It looks like:

  1. Each data pulse is followed by short low pulse, excluding two long pulses in a row and single long low pulse which is followed by short high
  2. First long pulse ("1") is always high, then they are interleaving.

I could have missed something, but first result is simplified code that takes about 200 bytes less memory when compiled for Mega.

Here is an example, which can be further improved (as there is no need to store in command arrays short low pulses):

#include <avr/pgmspace.h> // needed to use PROGMEM

#define  txPin  8 // pin connected to RF transmitter (pin 8)
byte i; // command pulses counter for Livolo (0 - 100)
byte pulse; // counter for command repeat

// commands stored in PROGMEM arrays (see on PROGMEM use here: http://arduino.cc/forum/index.php?topic=53240.0)
// first array element is length of command
const prog_uchar start[30] PROGMEM = {1, 2, 4, 2, 4, 2, 4, 3, 5, 2, 4, 2, 4, 3, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2}; // remote ID - no need to store it with each command
const prog_uchar button1[15] PROGMEM ={14, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2}; // only command bits
const prog_uchar button2[13] PROGMEM ={12, 5, 3, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2};
const prog_uchar button3[11] PROGMEM ={10, 5, 3, 5, 3, 4, 2, 4, 2, 4, 2};
const prog_uchar button4[13] PROGMEM ={12, 4, 2, 4, 2, 5, 3, 4, 2, 4, 2, 4, 2};
const prog_uchar button5[13] PROGMEM ={12, 5, 2, 4, 3, 4, 2, 4, 2, 4, 2, 4, 2};
const prog_uchar button7[11] PROGMEM ={10, 5, 3, 4, 2, 5, 3, 4, 2, 4, 2};
const prog_uchar button11[11] PROGMEM ={10, 5, 3, 4, 2, 5, 2, 4, 3, 4, 2};

// pointers to command arrays
PROGMEM const prog_uchar *buttonPointer[] = {start, button1, button2, button3, button4, button5, button7, button11};

void setup() {

// sipmle example: send button "button2" once. Note that array elements numbered starting from "0" (so button1 is 0, button2 is 1 and so on)
// Serial.begin(9600);


}

void loop() {

txButton(3);
delay(1000);
}

// transmitting part
// zeroes and ones here are not actual 0 and 1. I just called these pulses for my own convenience. 
// also note that I had to invert pulses to get everything working
// that said in actual command "start pulse" is long low; "zero" = short high; "one" = long high; "pause" is short low; "low" is long low.

void txButton(byte cmd) { 
prog_uchar *currentPointer = (prog_uchar *)pgm_read_word(&buttonPointer[cmd]); // current pointer to command array passed as txButton(cmd) argument
byte cmdCounter = pgm_read_byte(&currentPointer[0]); // read array length

prog_uchar *currentPointerStart = (prog_uchar *)pgm_read_word(&buttonPointer[0]); // current pointer to start command array


for (pulse= 0; pulse <= 180; pulse = pulse+1) { // how many times to transmit a command
for (i = 0; i<30; i=i+1) {

byte currentCmd = pgm_read_byte(&currentPointerStart[i]);
sendPulse(currentCmd);
// Serial.print(currentCmd);
// Serial.print(", ");
}


for (i = 1; i < cmdCounter+1; i = i + 1) { // counter for reading command array
  byte currentCmd = pgm_read_byte(&currentPointer[i]); // readpulse type from array

  sendPulse(currentCmd);
//  Serial.print(currentCmd);
// Serial.print(", ");
    }
  }
}

void sendPulse(byte txPulse) {

  switch(txPulse) { // transmit pulse
   case 1: // start pulse
   digitalWrite(txPin, HIGH);
   delayMicroseconds(550);
   digitalWrite(txPin, LOW);
   break;
   case 2: // "zero"
   digitalWrite(txPin, LOW);
   delayMicroseconds(110);
   digitalWrite(txPin, HIGH);
   break;   
   case 3: // "one"
   digitalWrite(txPin, LOW);
   delayMicroseconds(303);
   digitalWrite(txPin, HIGH);
   break;      
   case 4: // "pause"
   digitalWrite(txPin, HIGH);
   delayMicroseconds(110);
   digitalWrite(txPin, LOW);
   break;      
   case 5: // "low"
   digitalWrite(txPin, HIGH);
   delayMicroseconds(290);
   digitalWrite(txPin, LOW);
   break;      
  } 
 digitalWrite(txPin, LOW);
}

Hi spch
I am following your post quite sometime after I bought livolo switch and face same problem.
It is very useful information in your post and codes are working perfectly.
What I am doing right now is, to have more codes ( I wanted around 20 codes to control 20 different switches) by doing try and error method on your codes. I have to admit that it is not successful at all. I cannot get even one more code.
Is it possible to get more codes to control livolo switches and how?

Sorry, I am very new to RF and Arduino.

Regards

Hi, winnaing,

I'm new to Arduino/RF too :wink:

Lately I've been a bit busy with debugging some pieces of code and other things, but I'm planning to rewrite Livolo code since I've studied waveforms more carefully. You could wait a bit, or modify code yourself. I hope the following information would be of use in case you choose the latter.

The key is that command consists of unique remote ID (never changes) and key code (they appear to be fixed too, but change depending on key pressed). So you only need to change remote ID to get more keys, and there are plenty of these "virtual remotes" as each remote ID is 16 bit. Additionally, it is highly probable that Livolo switches can learn key codes other than I've discovered by recording remotes. So there could be even more remote ID and key code combinations.

But the thing is that changing remote ID (or key code) requires following strict rules, i.e.: pulses must interleave, that is high pulse should be always followed by low pulse; two short pulses are "0" and every single long pulse (apart from start) is "1" or vice versa, it doesn't matter as long as you code them right.

Hi spch

Thanks for your reply. I follow your advice as follow:

  1. I changed ID for a few combinations, not successful.
  2. Yes, other third party remote can control the Livolo and I try to decode it, also not successful.

Maybe the way I did is not correct. By the way, what is interleave? :roll_eyes:

Regards

Winnaing,

I was pretty sure that I could say "interleaving" to describe a rule of high pulse following low pulse and vice versa. Sorry for bad English, if I got that wrong.

Here is more "user friendly" code. No more strange arrays full of strange numbers :wink: It requires only two values: Remote ID and a keycode of button to "press".

There are three predefined working Remote IDs (and it is possible to use many more as long as Remote IDs are 16 bit), and 11 predefined buttons (and, maybe, it is possible to use any other as long as keycodes are 7 bit).

Bottom line: it should work. If it doesn't, please tell me what went wrong and I'll try to figure out how to fix it.

#define  txPin  8 // pin connected to RF transmitter (pin 8)
byte i; // just a counter
byte pulse; // counter for command repeat
boolean high = true; // pulse "sign"

// keycodes #1: 0, #2: 96, #3: 120, #4: 24, #5: 80, #6: 48, #7: 108, #8: 12, #9: 72; #10: 40, #OFF: 106
// real remote IDs: 6400; 19303
// tested "virtual" remote ID: 8500, other IDs could work too, as long as they do not exceed 16 bit
// known issue: not all 16 bit remote ID are valid
// have not tested other buttons, but as there is dimmer control, some keycodes could be strictly system
// use: sendButton(remoteID, keycode); 
// see void loop for an example of use

void setup() {


}

void loop() {

sendButton(6400, 120); // blink button #3 every 3 seconds using remote with remoteID #6400
delay(3000);

}

void sendButton(unsigned int remoteID, byte keycode) {

  for (pulse= 0; pulse <= 180; pulse = pulse+1) { // how many times to transmit a command
  sendPulse(1); // Start  
  high = true; // first pulse is always high

  for (i = 16; i>0; i--) { // transmit remoteID
    byte txPulse=bitRead(remoteID, i-1); // read bits from remote ID
    selectPulse(txPulse);    
    }

  for (i = 7; i>0; i--) { // transmit keycode
    byte txPulse=bitRead(keycode, i-1); // read bits from keycode
    selectPulse(txPulse);    
    }    
  }
   digitalWrite(txPin, LOW);
}

// build transmit sequence so that every high pulse is followed by low and vice versa

void selectPulse(byte inBit) {
  
      switch (inBit) {
      case 0: 
       for (byte ii=1; ii<3; ii++) {
        if (high == true) {   // if current pulse should be high, send High Zero
          sendPulse(2); 
        } else {              // else send Low Zero
                sendPulse(4);
        }
        high=!high; // invert next pulse
       }
        break;
      case 1:                // if current pulse should be high, send High One
        if (high == true) {
          sendPulse(3);
        } else {             // else send Low One
                sendPulse(5);
        }
        high=!high; // invert next pulse
        break;        
      }
}

// transmit pulses
// slightly corrected pulse length, use old (commented out) values if these not working for you

void sendPulse(byte txPulse) {

  switch(txPulse) { // transmit pulse
   case 1: // Start
   digitalWrite(txPin, HIGH);
   delayMicroseconds(500); // 550
   digitalWrite(txPin, LOW);
   break;
   case 2: // "High Zero"
   digitalWrite(txPin, LOW);
   delayMicroseconds(100); // 110
   digitalWrite(txPin, HIGH);
   break;   
   case 3: // "High One"
   digitalWrite(txPin, LOW);
   delayMicroseconds(300); // 303
   digitalWrite(txPin, HIGH);
   break;      
   case 4: // "Low Zero"
   digitalWrite(txPin, HIGH);
   delayMicroseconds(100); // 110
   digitalWrite(txPin, LOW);
   break;      
   case 5: // "Low One"
   digitalWrite(txPin, HIGH);
   delayMicroseconds(300); // 290
   digitalWrite(txPin, LOW);
   break;      
  } 
}

Hi spch

It is wonderful. All ID key are working. Thank you. :slight_smile:
I try to 'decode' your ID key but still don't understand and no luck.
Anyway I try to digest your advice and try to look for other IDs. For the time being, 30 codes are enough for me.

Have you try Koti light switch? see http://www.aliexpress.com/store/529450
The good thing about these switches is that they are bicommunication; ie switch send back the feedback signal.
So we know exactly if switch on or off. Whereas Livolo is not.
Again, I have checked with supplier. They said their code is propitiatory and do not work with 3rd party RC.
I don't have the talent to decode them. So I did not buy.
Anybody ever decode them successfully?

Regards

Hello Winnaing,

Glad to know the code is working. Koti switches could use proprietary protocol (Livolo, by the way, does use its own proprietary protocol too) or they can use Z-Wave protocol as store name suggests. In the latter case you don't need to decode anything as there are Z-wave modules for Arduino.

But if Koti switches use fixed codes just like Livolo, they can be decoded as well.

Thanks for decoding the Livolo protocol and your sketch, I already got it to work with my Livolo switches! I was wondering if there is any way to use the Livolo dimmers with your code?

Regards, Max

Max,

I guess it is highly probable. At least, manual for VL-RMT-01 remote that I have says that buttons 7 (on), 9 (off), "+" (more light) and "-" (less light) can be used to control dimmer switch.

I don't have dimmer switches, that is why I have not scanned "+" and "-" buttons yet. I'm planning to do that this or next week, and after that you will be able to test dimmer feature.

thanks for your really fast reply - I'm really looking forward to test the dimmer feature :slight_smile: keep up the great work!

Hi spch
Thanks for your reply.
I still cannot find new ID yet. For example, i use 6500 for ID which is 0001 1001 0110 0100. I though it follow your rule.
But it did not work either. Which pulse is not following your rule?

I understand that KOTI use FSK protocol. Still can decode?

Regards

baustromverteiler,

I've just recorded what I believe are dimmer buttons. I have to sets of dimmer buttons (+/-) on the remote, and they have different keycodes. So here are both sets:

  1. "+": 92 ; "-": 116

  2. "+": 126; "-": 36

It's in the manual that I've found on the net that dimmer should be "paired" with buttons 7 and 9. Regretfully there is nothing on pairing and I don't have dimmer switch, so I can not test it.

winnaing,

It seems, you are right. Not all remote IDs are working. Sorry, I can not find the reason - it is really beyond my understanding. Maybe, I missed something or there is a bug in the code.

So there is a hit or miss approach: try different remote IDs to find ones that work. For example, I found that 7400, 6550, 10550 can be used with Livolo switches.

As to FSK, it is not really a protocol - it is method of modulation used to transmit data over RF. Basically it can be easily demodulated using FSK receiver (the same way as I demodulated Livolo's signal using ASK receiver). And there are two ways to use received data:

  1. Try to "copy" code sequence without trying to understand underlying protocol;
  2. Try to decode and use original protocol.

First will do if it is about fixed codes. Second is good to deal with fixed and dynamic codes, although to me dynamic codes are really hard to hack.

Regards,
Sergey.

Thanks for the keycodes - I'll let you know how it worked out

Hi spch
Thanks for new IDs.
As for FSK ; are you saying that it works with fsk receiver and will not work with ask receiver?
Then where can I find fsk receiver? It is like all arduino store sell ask receiver.

Regards

Hi winnaing,

Of course, ASK receiver won't be able to receive FSK transmission. Here is a simple FSK receiver:
http://www.ebay.com/itm/Very-Good-FSK-Replace-ASK-Transparent-Wireless-Data-Receier-RX-Module-433MHZ-/271302707346?pt=LH_DefaultDomain_0&hash=item3f2ae6c892

and here are both FSK receiver and transmitter:

http://www.ebay.com/itm/FSK-Directly-Replace-ASK-200-300-Meters-Transparent-Wireless-Data-433MHZ-Modules-/261050654636?pt=Vintage_Electronics_R2&hash=item3cc7d4dfac