├── dump ├── auth.bin ├── auth.on.off.bin ├── multi.on.off.bin └── auth.on.off.boil.keepwarm.bin ├── connect.sh └── README.md /dump/auth.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PimenovAlexander/r4s-bluetooth/HEAD/dump/auth.bin -------------------------------------------------------------------------------- /dump/auth.on.off.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PimenovAlexander/r4s-bluetooth/HEAD/dump/auth.on.off.bin -------------------------------------------------------------------------------- /dump/multi.on.off.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PimenovAlexander/r4s-bluetooth/HEAD/dump/multi.on.off.bin -------------------------------------------------------------------------------- /dump/auth.on.off.boil.keepwarm.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PimenovAlexander/r4s-bluetooth/HEAD/dump/auth.on.off.boil.keepwarm.bin -------------------------------------------------------------------------------- /connect.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | devicemac="$1" 4 | command="$2" 5 | 6 | 7 | function tracemen() 8 | { 9 | echo -e -n $1 10 | } 11 | 12 | function traceme() 13 | { 14 | echo -e $1 15 | } 16 | 17 | if [[ $devicemac == "_" ]]; then 18 | $devicemac=`cat kettle.mac` 19 | fi; 20 | 21 | if [[ $devicemac =~ ^([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$ ]]; then 22 | traceme "Will Control: $devicemac"; 23 | else 24 | traceme "Usage " 25 | exit; 26 | fi 27 | 28 | 29 | 30 | traceme "Reading primary info" 31 | #gatttool -b $devicemac -t random --primary 32 | 33 | traceme "Reading characteristics info" 34 | #gatttool -b $devicemac -t random --characteristics 35 | 36 | traceme "Reading characteristics desc" 37 | #gatttool -b $devicemac -t random --char-desc 38 | 39 | 40 | traceme "Attempt to write something -> 0x000c" 41 | #gatttool -b $devicemac -t random --char-write-req --handle=0x000c --value=0100 42 | 43 | 44 | # 55:00:ff:b5:4c:75:b1:b4:0c:88:ef:aa 45 | # 55:01:ff:b5:4c:75:b1:b4:0c:88:ef:aa 46 | # 55:02:ff:b5:4c:75:b1:b4:0c:88:ef:aa 47 | # 55:03:ff:b5:4c:75:b1:b4:0c:88:ef:aa 48 | #sdf 49 | 50 | (( i=0 )) 51 | 52 | if true; then # Auth sequence. 53 | while true; do 54 | traceme "Attempt to write something -> 0x000c" 55 | gatttool -b $devicemac -t random --char-write-req --handle=0x000c --value=0100 56 | sleep 0.5; 57 | 58 | #gatttool -b $devicemac -t random --listen & 59 | magic=`printf "55%02xffb54c75b1b40c88efaa" $i` 60 | 61 | 62 | traceme "Attempt to write something -> 0x000e ($magic)" 63 | sleep 0.5; 64 | gatttool -b $devicemac -t random --char-write-req --handle=0x000e --value=$magic --listen >response & 65 | gettpid=$! 66 | #echo "Forked to pid $gettpid" 67 | sleep 0.5; 68 | #echo "Killing to pid $gettpid" 69 | kill $gettpid >/dev/null 2>/dev/null 70 | wait $gettpid 2>/dev/null 71 | response=`cat response`; 72 | reply=`echo $response | grep "value:" | sed "s/.*value: \(.*\)/\\1/g"` 73 | echo -n "REPLY:$reply" 74 | 75 | is_authorized=`echo $reply | awk '{print $4}'` 76 | traceme " <$is_authorized> " 77 | if [[ $is_authorized == "01" ]]; then 78 | traceme "Authorized" 79 | break; 80 | fi; 81 | 82 | if [[ $is_authorized == "00" ]]; then 83 | traceme "HOLD '+' button" 84 | fi; 85 | 86 | if [[ $reply == "" ]]; then 87 | traceme "No reply" 88 | (( i = (i + 1) % 256 )); 89 | else 90 | (( i = (i + 1) % 256 )); 91 | fi 92 | done; 93 | fi; 94 | 95 | traceme "Ok"; 96 | 97 | 98 | # Requesting status 99 | 100 | if [[ $command == "query" || $command == "queryone" ]]; then 101 | while true; do 102 | #echo "Attempt to write something -> 0x000c" 103 | gatttool -b $devicemac -t random --char-write-req --handle=0x000c --value=0100 104 | sleep 0.5; 105 | 106 | #gatttool -b $devicemac -t random --listen & 107 | magic=`printf "55%02x06aa" $i` 108 | 109 | 110 | #echo "Attempt to write something -> 0x000e ($magic)" 111 | sleep 0.5; 112 | gatttool -b $devicemac -t random --char-write-req --handle=0x000e --value=$magic --listen >response & 113 | gettpid=$! 114 | #echo "Forked to pid $gettpid" 115 | sleep 0.5; 116 | #echo "Killing to pid $gettpid" 117 | kill $gettpid >/dev/null 2>/dev/null 118 | wait $gettpid 2>/dev/null 119 | response=`cat response`; 120 | reply=`echo $response | grep "value:" | sed "s/.*value: \(.*\)/\\1/g"` 121 | 122 | 123 | if [[ $reply == "" ]]; then 124 | echo "No reply" 125 | (( i = (i + 1) % 256 )); 126 | else 127 | kettle_keeptemp=`echo $reply | awk '{print $6}'` 128 | kettle_on=`echo $reply | awk '{print $12}'` 129 | kettle_temp=`echo $reply | awk '{print $14}'` 130 | 131 | if [[ $kettle_on == "00" ]]; then 132 | echo -n "OFF" 133 | else 134 | echo -n "ON" 135 | fi 136 | 137 | echo -n " " 138 | echo -n "$((16#$kettle_temp))C " 139 | echo -n "Will keep: $((16#$kettle_keeptemp))C " 140 | echo "REPLY:$reply" 141 | 142 | if [[ $command == "queryone" ]]; then 143 | break; 144 | fi; 145 | (( i = (i + 1) % 256 )); 146 | fi 147 | 148 | done; 149 | fi; 150 | 151 | if [[ $command == "on" ]]; then 152 | while true; do 153 | #echo "Attempt to write something -> 0x000c" 154 | gatttool -b $devicemac -t random --char-write-req --handle=0x000c --value=0100 155 | sleep 0.5; 156 | magic=`printf "55%02x0500000000aa" $i` 157 | 158 | #echo "Attempt to write something -> 0x000e ($magic)" 159 | sleep 0.5; 160 | gatttool -b $devicemac -t random --char-write-req --handle=0x000e --value=$magic --listen >response & 161 | gettpid=$! 162 | #echo "Forked to pid $gettpid" 163 | sleep 0.5; 164 | #echo "Killing to pid $gettpid" 165 | kill $gettpid >/dev/null 2>/dev/null 166 | wait $gettpid 2>/dev/null 167 | response=`cat response`; 168 | reply=`echo $response | grep "value:" | sed "s/.*value: \(.*\)/\\1/g"` 169 | 170 | is_on=`echo $reply | awk '{print $4}'` 171 | echo " <$is_on> " 172 | if [[ $is_on == "01" ]]; then 173 | echo "Success" 174 | break; 175 | else 176 | echo "Trying again..." 177 | fi; 178 | 179 | if [[ $reply == "" ]]; then 180 | echo "No reply" 181 | (( i = (i + 1) % 256 )); 182 | else 183 | (( i = (i + 1) % 256 )); 184 | fi 185 | done; 186 | 187 | fi; 188 | 189 | if [[ $command == "keeptemp" ]]; then 190 | keep_temp="$3" 191 | 192 | while true; do 193 | #echo "Attempt to write something -> 0x000c" 194 | gatttool -b $devicemac -t random --char-write-req --handle=0x000c --value=0100 195 | sleep 0.5; 196 | magic=`printf "55%02x050000%02x00aa" $i $keep_temp` 197 | 198 | #echo "Attempt to write something -> 0x000e ($magic)" 199 | sleep 0.5; 200 | gatttool -b $devicemac -t random --char-write-req --handle=0x000e --value=$magic --listen >response & 201 | gettpid=$! 202 | #echo "Forked to pid $gettpid" 203 | sleep 0.5; 204 | #echo "Killing to pid $gettpid" 205 | kill $gettpid >/dev/null 2>/dev/null 206 | wait $gettpid 2>/dev/null 207 | response=`cat response`; 208 | reply=`echo $response | grep "value:" | sed "s/.*value: \(.*\)/\\1/g"` 209 | 210 | is_on=`echo $reply | awk '{print $4}'` 211 | echo " <$is_on> " 212 | if [[ $is_on == "01" ]]; then 213 | echo "Success" 214 | break; 215 | else 216 | echo "Trying again..." 217 | fi; 218 | 219 | if [[ $reply == "" ]]; then 220 | echo "No reply" 221 | (( i = (i + 1) % 256 )); 222 | else 223 | (( i = (i + 1) % 256 )); 224 | fi 225 | done; 226 | 227 | fi; 228 | 229 | if [[ $command == "off" ]]; then 230 | while true; do 231 | #echo "Attempt to write something -> 0x000c" 232 | gatttool -b $devicemac -t random --char-write-req --handle=0x000c --value=0100 233 | sleep 0.5; 234 | magic=`printf "55%02x04aa" $i` 235 | 236 | #echo "Attempt to write something -> 0x000e ($magic)" 237 | sleep 0.5; 238 | gatttool -b $devicemac -t random --char-write-req --handle=0x000e --value=$magic --listen >response & 239 | gettpid=$! 240 | #echo "Forked to pid $gettpid" 241 | sleep 0.5; 242 | #echo "Killing to pid $gettpid" 243 | kill $gettpid >/dev/null 2>/dev/null 244 | wait $gettpid 2>/dev/null 245 | response=`cat response`; 246 | reply=`echo $response | grep "value:" | sed "s/.*value: \(.*\)/\\1/g"` 247 | 248 | is_on=`echo $reply | awk '{print $4}'` 249 | echo " <$is_on> " 250 | if [[ $is_on == "01" ]]; then 251 | echo "Success" 252 | break; 253 | else 254 | echo "Trying again..." 255 | fi; 256 | 257 | if [[ $reply == "" ]]; then 258 | echo "No reply" 259 | (( i = (i + 1) % 256 )); 260 | else 261 | (( i = (i + 1) % 256 )); 262 | fi 263 | done; 264 | fi; 265 | 266 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # r4s-bluetooth 2 | Hacking Ready For Sky (R4S) home appliances 3 | 4 | This repository holds some data for controlling Redmond SkyKettle RK-M171S from GNU/Linux 5 | 6 | I welcome suggestions and ideas for simplification and making code more reliable. 7 | Using script wrapper around gatttool is so far the simplest approach. But it is terribly ugly. 8 | 9 | Prerequisits: 10 | You need bluez installed. Version 4.01 is fine 11 | 12 | Usage: 13 | * ./connect.sh [KETTLE MAC] auth 14 | * ./connect.sh [KETTLE MAC] query 15 | * ./connect.sh [KETTLE MAC] queryone 16 | * ./connect.sh [KETTLE MAC] keeptemp 17 | * ./connect.sh [KETTLE MAC] on 18 | * ./connect.sh [KETTLE MAC] off 19 | 20 | You can get your [KETTLE MAC] by entering "bt-device -l" 21 | 22 | ## Dumps 23 | 24 | You don't need to dump anything, you can just reverse the application itself. Later data had been acquired with decompile method 25 | 26 | * dump/auth.on.off.bin 27 | Initiate an auth from application. Hold "+" on kettle. Switch kettle "ON". Press "OFF" just a second before 100C 28 | * dump/auth.bin 29 | Initiate an auth from application. do nothing on kettle 30 | 31 | 32 | 33 | ## Hardware 34 | 35 | * Bluetooth dongle (CSR4.0) - http://ru.aliexpress.com/item/Bluetooth-4-0-Dongles-Mini-USB-2-0-3-0-Bluetooth-Dongle-Adapters-Dual-Mode-adapter/32292553074.html 36 | 37 | Insert dmesg 38 | ``` 39 | [ 8058.302793] usb 3-13: USB disconnect, device number 5 40 | [ 8059.355045] usb 3-14: USB disconnect, device number 6 41 | [ 8062.905322] usb 3-13: new full-speed USB device number 12 using xhci_hcd 42 | [ 8062.922927] usb 3-13: New USB device found, idVendor=e0ff, idProduct=0002 43 | [ 8062.922928] usb 3-13: New USB device strings: Mfr=1, Product=2, SerialNumber=0 44 | [ 8062.922930] usb 3-13: Product: G3 45 | [ 8062.922930] usb 3-13: Manufacturer: A..... 46 | [ 8062.924416] input: A..... G3 as /devices/pci0000:00/0000:00:14.0/usb3/3-13/3-13:1.0/input/input22 47 | [ 8062.924479] hid-generic 0003:E0FF:0002.0006: input,hidraw0: USB HID v1.00 Mouse [A..... G3] on usb-0000:00:14.0-13/input0 48 | [ 8062.925984] input: A..... G3 as /devices/pci0000:00/0000:00:14.0/usb3/3-13/3-13:1.1/input/input23 49 | [ 8062.926091] hid-generic 0003:E0FF:0002.0007: input,hiddev0,hidraw1: USB HID v1.00 Keyboard [A..... G3] on usb-0000:00:14.0-13/input1 50 | [ 8071.616958] usb 3-10.3: new full-speed USB device number 13 using xhci_hcd 51 | [ 8071.640199] usb 3-10.3: New USB device found, idVendor=0a12, idProduct=0001 52 | [ 8071.640217] usb 3-10.3: New USB device strings: Mfr=0, Product=2, SerialNumber=0 53 | [ 8071.640223] usb 3-10.3: Product: CSR8510 A10 54 | ``` 55 | 56 | ## Protocol 57 | 58 | Protocol had been reversed with the following techique. Dumps were made with "Enable Bluetoog HCI snoop log" Android feature. Analised then with wireshark 59 | 60 | ## Bluetooth level 61 | 62 | Device info: 63 | 64 | ``` 65 | [E7:5A:53:79:82:A4] 66 | Name: RK-M171S 67 | Alias: RK-M171S [rw] 68 | Address: E7:5A:53:79:82:A4 69 | Icon: undefined 70 | Class: 0x0 71 | Paired: 0 72 | Trusted: 0 [rw] 73 | Blocked: 0 [rw] 74 | Connected: 0 75 | UUIDs: [00001800-0000-1000-8000-00805f9b34fb, 00001801-0000-1000-8000-00805f9b34fb, 6e400001-b5a3-f393-e0a9-e50e24dcca9e] 76 | ``` 77 | 78 | Primary handles 79 | 80 | ``` 81 | attr handle = 0x0001, end grp handle = 0x0007 uuid: 00001800-0000-1000-8000-00805f9b34fb 82 | attr handle = 0x0008, end grp handle = 0x0008 uuid: 00001801-0000-1000-8000-00805f9b34fb 83 | attr handle = 0x0009, end grp handle = 0xffff uuid: 6e400001-b5a3-f393-e0a9-e50e24dcca9e 84 | 85 | 00001800-0000-1000-8000-00805f9b34fb Generic Access 86 | 00001800-0000-1000-8000-00805f9b34fb Generic Attribute 87 | 6e400001-b5a3-f393-e0a9-e50e24dcca9e UART Service 88 | ``` 89 | 90 | Detailed capabilites: 91 | 92 | ``` 93 | handle = 0x0002, char properties = 0x0a, char value handle = 0x0003, uuid = 00002a00-0000-1000-8000-00805f9b34fb read write 94 | handle = 0x0004, char properties = 0x02, char value handle = 0x0005, uuid = 00002a01-0000-1000-8000-00805f9b34fb read 95 | handle = 0x0006, char properties = 0x02, char value handle = 0x0007, uuid = 00002a04-0000-1000-8000-00805f9b34fb read 96 | 97 | handle = 0x000a, char properties = 0x10, char value handle = 0x000b, uuid = 6e400003-b5a3-f393-e0a9-e50e24dcca9e notify read 98 | handle = 0x000d, char properties = 0x0c, char value handle = 0x000e, uuid = 6e400002-b5a3-f393-e0a9-e50e24dcca9e write 99 | ``` 100 | 101 | 102 | ### Protocol summary 103 | 104 | To start talking to device, you need to connect and write 0x0100 to handle 0x000c (is not listed by the device?). 105 | This needs to be done after every reconnect. 106 | Gatttool doesn't allow you to know when reconnect happend. So I send it every time. 107 | 108 | Now you can send commands to handle 0x000e 109 | You will get back the answers from handle 0x000b 110 | 111 | #### Command structure 112 | 113 | Commands start with 0x55 byte, and end with 0xaa 114 | Second byte is a counter, you should increment it with any new request. I don't know yet what happens when you overflow 115 | Third byte is a command itself 116 | * 0x01 - happens sometimes, unknown 117 | * 0x05 - switch the kettle on to keepwarm (on with 0 temperature) 118 | * 0x04 - switch the kettle off 119 | * 0x06 - request status 120 | * 0xFF - authorize 121 | 122 | 123 | Next go the parameters. 124 | 125 | ``` 126 | 0x55 0x76 0x06 0xaa 127 | | | | | 128 | start | | end 129 | counter | 130 | command id 131 | ``` 132 | 133 | As a reply you will recive a sequence that start with 0x55 byte, and end with 0xAA 134 | Second byte is a counter which is the same as in the request it replies to. 135 | Third byte is the command - same as in the request. It depends on the command. 136 | Next goes the data. 137 | 138 | #### AUTH 139 | I can guess you should do the following. Generate a 8byte random ID. This will be your key. 140 | I don't know yet if any key is ok. These are - 55:3a:57:47:f8:c2:62:4a, b5:4c:75:b1:b4:0c:88:ef 141 | 142 | Send auth command, starting with counter 0 143 | 144 | ``` 145 | -> 55::ff:<8 bytes. Seem random. ID? MAC?>:aa 146 | ``` 147 | 148 | If you are not yet authorized (you need to hold "+" on the kettle for this) the kettle will reply 149 | 150 | ``` 151 | <- 55::ff:00:aa 152 | ``` 153 | Send the request again with incrementing counter. Meanwhile hold "+" key. At some point the auth will be passed. And you will get: 154 | 155 | ``` 156 | <- 55::ff:01:aa 157 | ``` 158 | 159 | Next time with the same key you should be able to connect from the first attempt. 160 | In the auth state you can issue next commands. 161 | 162 | #### Unknown command usually at start 163 | 164 | ``` 165 | -> 55::01:aa 166 | reply example 167 | <- 55::01:02:06:aa 168 | ``` 169 | 170 | #### BOLING AND HEATING command 171 | ``` 172 | request examples 173 | -> 55::05:00:00:28:00:aa 174 | -> 55::05:00:00:00:00:aa - just switch on. 175 | -> 55::05:01:00:5f:00:aa - keepwarm at 95 176 | 177 | 55::05:00:00::00:aa 178 | 179 | reply example 180 | <- 55::05:01:aa // 01 status meens OK 181 | ``` 182 | 183 | #### OFF command 184 | ``` 185 | -> 55::04:aa 186 | reply example 187 | <- 55::04:01:aa // 01 status meens OK 188 | ``` 189 | 190 | #### STATUS command 191 | ``` 192 | -> 55::06:aa 193 | reply examples: 194 | <- 55::06:00:00:00:00:00:0c:00:00:00:00:51:00:00:00:00:00:aa - kettle is on in boiling 195 | 196 | <- 55::06:00:00:28:00:00:0c:00:01:02:00:33:00:00:00:00:00:aa - kettle is on 197 | <- 55::06:00:00:00:00:00:0c:00:00:00:00:3e:00:00:00:00:00:aa - kettle is off 198 | <- 55::06 00 00 28 00 00 0c 00 01 00 00 64 00 00 00 00 00 aa 199 | 200 | <- 55::06 01 00 28 00 00 0c 00 01 02 00 64 00 00 00 00 00 aa - kettle finished boiling () 201 | <- 55::06 01 00 28 00 00 0c 00 01 02 00 63 00 00 00 00 00 aa - a while after finished boiling 202 | 203 | external num 1 2 3 4 5 6 7 8 9 10 11 12 13 14 204 | internal num 0 1 2 3 4 5 6 7 8 9 10 11 205 | 55::06::00::00:00:0c:00:01::00::00:00:00:00:00:aa 206 | 207 | ``` 208 | 209 | 210 | | Byte ext | Byte int | Action | 211 | | ---------|----------| ---------------| 212 | | 4 | 0 | Program | 213 | | 5 | 1 | | 214 | | 6 | 2 | Target Temperature | 215 | | 7 | 3 | | 216 | | 8 | 4 | | 217 | | 9 | 5 | Remaining hours | 218 | |10 | 6 | Remaining minutes | 219 | |11 | 7 | Heater on | 220 | |12 | 8 | State | 221 | |13 | 9 | Error status | 222 | |14 | 10 | Current Temp | 223 | 224 | --------------------------------------------------------------------------------