├── .gitignore ├── README.md ├── generator.js ├── hex.js ├── index.html ├── index.js ├── pageActions.js ├── protocol.png ├── rolcode.html └── rolcode.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### JetBrains template 3 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 4 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 5 | 6 | # User-specific stuff: 7 | .idea/**/workspace.xml 8 | .idea/**/tasks.xml 9 | .idea/dictionaries 10 | 11 | # Sensitive or high-churn files: 12 | .idea/**/dataSources/ 13 | .idea/**/dataSources.ids 14 | .idea/**/dataSources.xml 15 | .idea/**/dataSources.local.xml 16 | .idea/**/sqlDataSources.xml 17 | .idea/**/dynamic.xml 18 | .idea/**/uiDesigner.xml 19 | 20 | # Gradle: 21 | .idea/**/gradle.xml 22 | .idea/**/libraries 23 | 24 | # CMake 25 | cmake-build-debug/ 26 | 27 | # Mongo Explorer plugin: 28 | .idea/**/mongoSettings.xml 29 | 30 | ## File-based project format: 31 | *.iws 32 | 33 | ## Plugin-specific files: 34 | 35 | # IntelliJ 36 | out/ 37 | 38 | # mpeltonen/sbt-idea plugin 39 | .idea_modules/ 40 | 41 | # JIRA plugin 42 | atlassian-ide-plugin.xml 43 | 44 | # Cursive Clojure plugin 45 | .idea/replstate.xml 46 | 47 | # Crashlytics plugin (for Android Studio and IntelliJ) 48 | com_crashlytics_export_strings.xml 49 | crashlytics.properties 50 | crashlytics-build.properties 51 | fabric.properties 52 | 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Random Broadlink RM Code Generator And Utils 2 | 3 | Every time i want to buy some new smart component for my smart home, i need a remote, just to have 4 | some random RF code for the component. why should i buy a remote that i use only once just to learn the code? 5 | why cant i just generate some random one? 6 | 7 | So I Looked at the net for a long time and did not find any generator for RF codes. so i just created one. 8 | 9 | use https://dimagoltsman.github.io/Random-Broadlink-RM-Code-Generator/ 10 | 11 | to generate your codes 12 | 13 | 14 | 15 | also, you are more than welcome to contribute and add more types (IR). 16 | you can use the protocol description here: 17 | https://github.com/mjg59/python-broadlink/blob/master/protocol.md 18 | 19 | -------------------------------------------------------------------------------- /generator.js: -------------------------------------------------------------------------------- 1 | /* 2 | PROTOCOL: 3 | 4 | b2 RF 5 | 6 | 0c repeats 7 | 8 | 34 00 52 bytes follow (big endian) 24 pairs + 4 for the footer 9 | 10 | ## ## 24 0d for a 1, 0d 24 for a 0 11 | 12 | 0c 00 01 6f (Footer) 13 | 14 | */ 15 | 16 | const HIGH_BIT = "240d"; 17 | const LOW_BIT = "0d24"; 18 | const BITS_ARRAY = [HIGH_BIT, LOW_BIT]; 19 | const RF433 = "b2"; 20 | const RF315 = "d7"; 21 | const FOOTER = "0c00016f00000000"; 22 | const REPEATS = "0c"; 23 | const LONG_REPEAT = "5c"; 24 | const BYTES = 24; 25 | const DATA_LENGTH = "3400"; 26 | 27 | String.prototype.rightJustify = function (length, char) { 28 | var fill = []; 29 | while (fill.length + this.length < length) { 30 | fill[fill.length] = char; 31 | } 32 | return this + fill.join(''); 33 | } 34 | 35 | String.prototype.leftJustify = function (length, char) { 36 | var fill = []; 37 | while (fill.length + this.length < length) { 38 | fill[fill.length] = char; 39 | } 40 | return fill.join('') + this; 41 | } 42 | 43 | function typePrefixOf(type) { 44 | if (type === "RF433") { 45 | return RF433; 46 | } else if (type === "RF315") { 47 | return RF315; 48 | } else { 49 | throw new Error("Unsupported transmission type."); 50 | } 51 | } 52 | 53 | function randomPulse() { 54 | return BITS_ARRAY[Math.floor(Math.random() * 2)]; 55 | } 56 | 57 | function generate(type) { 58 | var code = ""; 59 | for (i = 0; i < BYTES; i++) { 60 | var rand = randomPulse(); 61 | code = code + rand; 62 | } 63 | 64 | var typePrefix = typePrefixOf(type); 65 | 66 | var res = typePrefix + REPEATS + DATA_LENGTH + code + FOOTER; 67 | var resWithRepeat = typePrefix + LONG_REPEAT + DATA_LENGTH + code + FOOTER; 68 | 69 | return { 70 | regular: hexToBase64(res), 71 | long: hexToBase64(resWithRepeat) 72 | } 73 | } 74 | 75 | function getRepeats(b64) { 76 | var hex = base64ToHex(b64).replace(/ /g, ''); 77 | var repeats = hex.substr(2, 2); 78 | var decimal = parseInt(repeats, 16); 79 | return decimal; 80 | } 81 | 82 | function getNewCode(b64, repeats) { 83 | var hex = base64ToHex(b64).replace(/ /g, ''); 84 | var start = hex.substr(0, 2); 85 | var end = hex.substr(4); 86 | 87 | var hexrepeats = parseInt(repeats).toString(16); 88 | 89 | if (hexrepeats.length == 1) { 90 | hexrepeats = "0" + hexrepeats; 91 | } 92 | 93 | var res = (start + hexrepeats + end); 94 | return hexToBase64(res); 95 | } 96 | 97 | function generateLivolo(remoteId, btn) { 98 | // the livolo code came from https://www.tyjtyj.com/livolo.php, dont know who wrote it, but big thanx 99 | header = "b280260013"; 100 | id_bin = (+remoteId).toString(2); 101 | id_bin = id_bin.leftJustify(16, 0); 102 | btn_bin = (+btn).toString(2); 103 | btn_bin = btn_bin.leftJustify(7, 0); 104 | 105 | id_btn_bin = id_bin.concat(btn_bin); 106 | 107 | id_btn_bin = id_btn_bin.replace(/0/g, "0606"); 108 | id_btn_bin = id_btn_bin.replace(/1/g, "0c"); 109 | 110 | hex_out = header + id_btn_bin; 111 | 112 | pad_len = 32 - (hex_out.length - 24) % 32; 113 | 114 | hex_out = hex_out + ('').leftJustify(pad_len, 0); 115 | 116 | return hexToBase64(hex_out); 117 | 118 | } 119 | 120 | function generateEnergenie() { 121 | 122 | function randomBinary(n) { 123 | for (var i = "", a = 0; a < n; a++) i += parseInt(2 * Math.random(), 10).toString(); 124 | return i 125 | } 126 | 127 | function generateCode(d0d3) { 128 | // Generate transmission binary 129 | const binary = remoteIDBinary.toString() + d0d3.toString(); 130 | const dec = parseInt(binary, 2); 131 | 132 | const ENER_HIGH = "1507"; 133 | const ENER_LOW = "0815"; 134 | const ENER_ARRAY = [ENER_LOW, ENER_HIGH]; 135 | const ENER_FOOTER = "08dc000000000000"; 136 | const ENER_APP_REPEATS = "08"; 137 | 138 | // Turn transmission binary in to HIGH/LOW hex transmission code 139 | let code = ''; 140 | for (var i = 0; i < binary.length; i++) { 141 | code += ENER_ARRAY[parseInt(binary.charAt(i), 10)]; 142 | } 143 | 144 | // Construct outputs 145 | broadlink_hex = RF433 + ENER_APP_REPEATS + DATA_LENGTH + code + ENER_FOOTER; 146 | broadlink = hexToBase64(broadlink_hex); 147 | broadlink_long_hex = (RF433 + LONG_REPEAT + DATA_LENGTH + code + ENER_FOOTER); 148 | broadlink_long = hexToBase64(broadlink_long_hex); 149 | 150 | return { binary, dec, broadlink, broadlink_long, broadlink_hex, broadlink_long_hex }; 151 | } 152 | 153 | let remoteIDBinary = randomBinary(20); 154 | let remoteID = parseInt(remoteIDBinary, 2) 155 | 156 | let sockets = [ 157 | ['1', '1111', '1110'], 158 | ['2', '0111', '0110'], 159 | ['3', '1011', '1010'], 160 | ['4', '0011', '0010'], 161 | ['Group', '1101', '1100'] 162 | ] 163 | 164 | $('#gentable, #genjson').html(''); 165 | 166 | for (i in sockets) { 167 | let socket = sockets[i]; 168 | let name = socket[0]; 169 | let d0d3_on = socket[1]; 170 | let d0d3_off = socket[2]; 171 | 172 | let on_codes = generateCode(d0d3_on); 173 | let off_codes = generateCode(d0d3_off); 174 | 175 | const on_html = `${name} On${remoteID}${on_codes['binary']} // ${on_codes['dec']}${on_codes['broadlink']}${on_codes['broadlink_long']}`; 176 | $('#gentable').append(on_html); 177 | const off_html = `${name} Off${remoteID}${off_codes['binary']} // ${off_codes['dec']}${off_codes['broadlink']}${off_codes['broadlink_long']}`; 178 | $('#gentable').append(off_html); 179 | 180 | var json = `{\n "name": "Switch ${name}",\n "type": "switch",\n "resendHexAfterReload": false,\n "data": {\n "on": "${on_codes['broadlink_long_hex']}",\n "off": "${off_codes['broadlink_long_hex']}"\n }\n},\n`; 181 | $('#genjson').append(json); 182 | 183 | } 184 | 185 | } -------------------------------------------------------------------------------- /hex.js: -------------------------------------------------------------------------------- 1 | 2 | if (!window.atob) { 3 | var tableStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 4 | var table = tableStr.split(""); 5 | 6 | window.atob = function (base64) { 7 | if (/(=[^=]+|={3,})$/.test(base64)) throw new Error("String contains an invalid character"); 8 | base64 = base64.replace(/=/g, ""); 9 | var n = base64.length & 3; 10 | if (n === 1) throw new Error("String contains an invalid character"); 11 | for (var i = 0, j = 0, len = base64.length / 4, bin = []; i < len; ++i) { 12 | var a = tableStr.indexOf(base64[j++] || "A"), b = tableStr.indexOf(base64[j++] || "A"); 13 | var c = tableStr.indexOf(base64[j++] || "A"), d = tableStr.indexOf(base64[j++] || "A"); 14 | if ((a | b | c | d) < 0) throw new Error("String contains an invalid character"); 15 | bin[bin.length] = ((a << 2) | (b >> 4)) & 255; 16 | bin[bin.length] = ((b << 4) | (c >> 2)) & 255; 17 | bin[bin.length] = ((c << 6) | d) & 255; 18 | }; 19 | return String.fromCharCode.apply(null, bin).substr(0, bin.length + n - 4); 20 | }; 21 | 22 | window.btoa = function (bin) { 23 | for (var i = 0, j = 0, len = bin.length / 3, base64 = []; i < len; ++i) { 24 | var a = bin.charCodeAt(j++), b = bin.charCodeAt(j++), c = bin.charCodeAt(j++); 25 | if ((a | b | c) > 255) throw new Error("String contains an invalid character"); 26 | base64[base64.length] = table[a >> 2] + table[((a << 4) & 63) | (b >> 4)] + 27 | (isNaN(b) ? "=" : table[((b << 2) & 63) | (c >> 6)]) + 28 | (isNaN(b + c) ? "=" : table[c & 63]); 29 | } 30 | return base64.join(""); 31 | }; 32 | 33 | } 34 | 35 | function hexToBase64(str) { 36 | return btoa(String.fromCharCode.apply(null, 37 | str.replace(/\r|\n/g, "").replace(/([\da-fA-F]{2}) ?/g, "0x$1 ").replace(/ +$/, "").split(" ")) 38 | ); 39 | } 40 | 41 | function base64ToHex(str) { 42 | for (var i = 0, bin = atob(str.replace(/[ \r\n]+$/, "")), hex = []; i < bin.length; ++i) { 43 | var tmp = bin.charCodeAt(i).toString(16); 44 | if (tmp.length === 1) tmp = "0" + tmp; 45 | hex[hex.length] = tmp; 46 | } 47 | return hex.join(" "); 48 | } 49 | 50 | 51 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Broadlink RM Random Code Generator 7 | 8 | 9 | 10 | 11 | 12 | 15 | 18 | 20 | 23 | 24 | 40 | 41 | 42 | 43 | 44 |
45 |

Random Broadlink RM Code Generator

46 | 47 | 61 | 62 |
63 |
64 |
65 |

Generate Random Code

66 |
67 | 68 | 73 |
74 | 75 |
76 |
77 |
78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 |
TypeRegular codesame code with long repeat (for learning)
89 |
90 |
91 | 92 | 93 |
94 |
95 |
96 |

Generate Random Livolo Code

97 |
98 |
99 | 100 | 101 | 102 |
103 |
104 | 105 | 124 |
125 |
126 | 127 |
128 |
129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 |
TypeCode
138 |
139 |
140 |
141 |
142 | 143 |
144 |
145 |

Generate Random Energenie Socket Set

146 |
147 | 148 |

149 | While I've written this for my Energenie remote-control sockets, it should work for any "Type D" socket as defined in the rc-switch wiki - i.e, 433mhz learning sockets.
150 | Many thanks to Energenie for documenting their accessories; extremely helpful.

151 |

152 | Here's what I sniffed using rc-switch/figured out by bashing my head against IHC for iOS backups:
153 | RF HIGH BIT: 1507, RF LOW BIT: 0815, RF FOOTER: 08dc000000000000
154 | Format: RF (b2), Repeats: 08 (8), Data: 20 pairs (remote ID), 4 pairs (socket command), 4 pairs (footer) 155 |

156 |

157 | I was thrown for a while looking at RF dumps as HIGH (1507) could come in as 1506 or 1508, and LOW (0816) could be 0815 or 0817. Realise now that's just the radios being vague, not actual important information. Once we had that figured out (and rounded off!), here's the steps to this generation: 158 |

    159 |
  1. Generate 20 random binary bits. This is our shiny new remote group ID!
  2. 160 |
  3. Construct the transmission hexadecimal: RF + REPEATS + DATA LENGTH + DATA + FOOTER. 161 |
      162 |
    1. RF: b2, which is RF433 in the world of Broadlink
    2. 163 |
    3. REPEATS: 08, 8 repeats; this is what IHC for iOS recorded when I pressed the buttons, but it's probably a bit conservative for a good transmission.
    4. 164 |
    5. DATA LENGTH: 34 00, 52 hex pairs follow (big endian). For us, that's 40 pairs (remote group ID), 4 pairs (socket command), 8 pairs (RF footer)
    6. 165 |
    7. DATA: 1507 0815 (...), the HIGH/LOW bits to transmit as defined above - remote group, socket command.
    8. 166 |
    9. FOOTER: 08dc000000000000, the footer extracted from the IHC for IOS app backups. Always the same.
    10. 167 |
    168 |
  4. 169 |
  5. Generate commands with the above formula for each switch group entry: 1 On, 1 Off, 2 On, 2 Off, 3 On, 3 Off, 4 On, 4 Off, All On, All Off.
  6. 170 |
  7. Optionally, convert the hexadecimal to base64. Done!
  8. 171 | 172 |
173 |

174 | 175 |
176 | 177 |
178 |
179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 |
TypeRemote IDBinary // rc-switch numericalBroadlink Regular CodeBroadlink long repeat (for learning)
192 |
193 |
194 |
Homebridge JSON, for homebridge-broadlink-rm
195 |
196 |

197 |                 
198 |
199 |
200 | 201 |
202 |
203 |

Change number of repeats in existing code (also works for IR codes)

204 |

205 |
206 |
207 | 208 | 211 |
212 | 213 |
0
214 |
215 |
216 | 217 |
218 | 219 |
220 | 221 |
222 |
223 | 224 | 225 |
226 |
227 | 228 |
229 |
230 | You are welcome to contribute: https://github.com/dimagoltsman/Random-Broadlink-RM-Code-Generator 232 | 233 |
234 | 235 |
236 | 237 | 238 | 239 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | function startGenerate() { 2 | 3 | var type = $('#signal_type').val(); 4 | var generated = generate(type); 5 | console.log(generated.regular); 6 | console.log(generated.long); 7 | 8 | var html = '' + type + '' + generated.regular + '' + generated.long + ''; 9 | 10 | $('#restable').append(html); 11 | } 12 | 13 | function calcRepeats() { 14 | var code = $("#usercode").val(); 15 | var repeats = getRepeats(code); 16 | $("#repeats").html(repeats); 17 | } 18 | 19 | function generateNewRepeat() { 20 | var code = $("#usercode").val(); 21 | var repeats = $("#newrepeat").val(); 22 | var newCode = getNewCode(code, repeats); 23 | $("#newcode").val(newCode); 24 | } 25 | 26 | 27 | function startGenerateLivolo(){ 28 | var remote = $('#remoteId').val(); 29 | if(remote == ""){ 30 | alert("please select remote id"); 31 | return; 32 | } 33 | var btn = $('#liv_btn').val(); 34 | var code = generateLivolo(remote, btn); 35 | var html = ''+ '' + 'Livolo' + '' +'' + code + ''; 36 | 37 | $('#livtable').append(html); 38 | } -------------------------------------------------------------------------------- /pageActions.js: -------------------------------------------------------------------------------- 1 | window.onload = function() { 2 | showRandomCodeTab(); 3 | }; 4 | 5 | function showRandomCodeTab() { 6 | hideAll(); 7 | $('#random-code-section').show(); 8 | $('#tab1').addClass('active'); 9 | }; 10 | 11 | 12 | function showLivoloTab() { 13 | hideAll(); 14 | $('#livolo-section').show(); 15 | $('#tab2').addClass('active'); 16 | }; 17 | 18 | function showEnergenieTab() { 19 | hideAll(); 20 | $('#energenie-section').show(); 21 | generateEnergenie(); 22 | $('#tab3').addClass('active'); 23 | }; 24 | 25 | function showRepeatsTab() { 26 | hideAll(); 27 | $('#repeats-section').show(); 28 | $('#tab4').addClass('active'); 29 | }; 30 | 31 | function hideAll() { 32 | $('#repeats-section').hide(); 33 | $('#random-code-section').hide(); 34 | $('#energenie-section').hide(); 35 | $('#livolo-section').hide(); 36 | $('#tab1, #tab2, #tab3, #tab4').removeClass('active'); 37 | } -------------------------------------------------------------------------------- /protocol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimagoltsman/Random-Broadlink-RM-Code-Generator/4d3e4edf8ea249d0a862adf6367df6000d1bf6a9/protocol.png -------------------------------------------------------------------------------- /rolcode.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Rolling code rev eng test 6 | 7 | 8 | 9 | 10 | 12 | 14 | 16 | 18 | 19 | 25 | 26 | 27 | 28 | 29 | 30 |
31 | 32 | 33 | 34 |
35 |
36 |

Rolling code rev eng

37 |

38 | 39 | 40 | 41 | 42 | 43 |
44 |
45 | 46 | 47 | 48 |
49 | 50 | 51 | 52 |
53 |
54 | 55 | 56 | 57 |
58 | 59 | 60 | 61 |
62 | 63 | 64 | 65 | 66 |
67 | 68 |
69 | 70 | 71 | 75 | 76 | 77 |
78 |
79 | 80 |
81 |
82 | 83 | 84 |
85 | 86 |
87 | 88 | 89 |
90 | 91 |
92 | 93 | 94 |
95 |
96 | 97 | 98 |
99 |
100 | 101 | 102 |
103 | 104 | 105 |
106 | 107 |

108 |
109 |
110 | Protocol: 111 |
112 |
113 | 114 |
115 |
116 | 117 |
118 |
119 | 120 |
121 |
122 | 123 | 124 |
125 |
126 | 127 |
128 | 129 |
130 | you are welcome to contribute: https://github.com/dimagoltsman/Random-Broadlink-RM-Code-Generator 131 | 132 |
133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /rolcode.js: -------------------------------------------------------------------------------- 1 | 2 | var startHeader = "0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c89"; 3 | 4 | function startDecode() { 5 | 6 | var b64 = $("#usercode").val(); 7 | var hex = base64ToHex(b64) 8 | var hexArr = hex.split(" "); 9 | var strippedBroadlink = hexArr.slice(4); 10 | var normilized = normilizeArray(strippedBroadlink); 11 | var joined = normilized.join(""); 12 | 13 | if(joined.indexOf(startHeader) == -1){ 14 | throw new Error("cant find start header") 15 | } 16 | 17 | var codeStartIndex = joined.indexOf(startHeader) + startHeader.length; 18 | var code = joined.substr(codeStartIndex); 19 | var singleCode = code.substr(0, 66 * 4 ); 20 | var singleSplitted = singleCode.match(/.{1,4}/g); 21 | var bitstream = reverse(singleSplitted.map(toBits).join("")); //reverse because code sent with LSB 22 | var encrypted = bitstream.substr(34) 23 | var fixed = bitstream.substr(0,34) 24 | 25 | // console.log(fixed.length + " " + encrypted.length) 26 | 27 | var rv = fixed.substr(0, 2); 28 | var buttons = fixed.substr(2, 4); 29 | var sn = fixed.substr(4, 28); 30 | 31 | $("#hex").val(hex); 32 | $("#singlehex").val(singleSplitted.join(" ")); 33 | $("#fullbits").val(bitstream); 34 | $("#encpart").val(encrypted); 35 | $("#fixedpart").val(fixed); 36 | $("#rv").val(rv); 37 | $("#buttons").val(buttons); 38 | $("#sn").val(sn); 39 | 40 | } 41 | 42 | function toBits(byte) { 43 | if(byte == "0c18"){ 44 | return "1"; 45 | } 46 | if(byte == "180c"){ 47 | return "0"; 48 | } 49 | 50 | if(byte.substr(2, 2) == "00"){ 51 | return "1"; 52 | } 53 | 54 | throw new Error("cant decode " + byte) 55 | } 56 | 57 | 58 | function normilizeArray(arr) { 59 | return arr.map(normilizeByte) 60 | } 61 | 62 | 63 | function normilizeByte(byte) { 64 | if(is0c(byte)){ 65 | return "0c"; 66 | }else if(is18(byte)){ 67 | return "18"; 68 | }else if(is89(byte)){ 69 | return "89"; 70 | } 71 | 72 | else{ 73 | return byte; 74 | } 75 | } 76 | 77 | 78 | function is0c(byte){ 79 | var is = Array("0c", "0d", "0e", "0f", "0b", "0a", "09"); 80 | return (is.indexOf(byte) > -1); 81 | } 82 | 83 | function is18(byte){ 84 | var is = Array("19", "1a", "1b", "1c", "18", "17", "16"); 85 | return (is.indexOf(byte) > -1); 86 | } 87 | 88 | function is89(byte){ 89 | var is = Array("89", "8a", "8b", "8c", "88", "87", "86", "7c", "7d", "7e", "7b", "7a", "79"); 90 | return (is.indexOf(byte) > -1); 91 | } 92 | 93 | 94 | function reverse(s){ 95 | return s.split("").reverse().join(""); 96 | } 97 | --------------------------------------------------------------------------------