├── .Screenshots ├── ScreenShot.png ├── configure.png ├── create_sch.png ├── delete.png ├── discover.png ├── extract.png └── schedule.png ├── .gitignore ├── Auto Extract Parameters.pdf ├── README.md ├── Switcher V2 Rooted Device.pdf ├── Switcher V2 UnRooted Device.pdf ├── extractV3.py └── switcher.py /.Screenshots/ScreenShot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NightRang3r/Switcher-V2-Python/7240bf1faef87d3dc1d642420bb66fe83538579b/.Screenshots/ScreenShot.png -------------------------------------------------------------------------------- /.Screenshots/configure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NightRang3r/Switcher-V2-Python/7240bf1faef87d3dc1d642420bb66fe83538579b/.Screenshots/configure.png -------------------------------------------------------------------------------- /.Screenshots/create_sch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NightRang3r/Switcher-V2-Python/7240bf1faef87d3dc1d642420bb66fe83538579b/.Screenshots/create_sch.png -------------------------------------------------------------------------------- /.Screenshots/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NightRang3r/Switcher-V2-Python/7240bf1faef87d3dc1d642420bb66fe83538579b/.Screenshots/delete.png -------------------------------------------------------------------------------- /.Screenshots/discover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NightRang3r/Switcher-V2-Python/7240bf1faef87d3dc1d642420bb66fe83538579b/.Screenshots/discover.png -------------------------------------------------------------------------------- /.Screenshots/extract.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NightRang3r/Switcher-V2-Python/7240bf1faef87d3dc1d642420bb66fe83538579b/.Screenshots/extract.png -------------------------------------------------------------------------------- /.Screenshots/schedule.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NightRang3r/Switcher-V2-Python/7240bf1faef87d3dc1d642420bb66fe83538579b/.Screenshots/schedule.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore Mac DS_Store files 2 | .DS_Store 3 | .gitignore 4 | -------------------------------------------------------------------------------- /Auto Extract Parameters.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NightRang3r/Switcher-V2-Python/7240bf1faef87d3dc1d642420bb66fe83538579b/Auto Extract Parameters.pdf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | This script is a result of an extensive reasearch of the Switcher V2 Protocol which was performed by Aviad Golan ([@AviadGolan](https://twitter.com/AviadGolan)) and Shai rod ([@NightRang3r](https://twitter.com/NightRang3r)) 4 | 5 | 6 | ### Important Update 7 | 8 | Recent changes in the Switcher firmware broke some functionality in our script and the home assistant components mainly the required values extraction process. 9 | 10 | * Switcher V2: Firmware 3.21 (Based on ESP chipset) and 72.32 (Qualcomm chipset) 11 | * Switcher Touch with firmware 1.51 12 | 13 | But don't worry...the good news is that the **"phone_id"** and **"device_password"** values doesn't really matter anymore 14 | The only required value you need to extract is the **"device_id"** which is still supported by the script using the discover feature: 15 | 16 | ``` 17 | ~# python switcher.py discover 18 | ``` 19 | 20 | So an exmaple configuration of the script would look like this: 21 | 22 | ``` 23 | ########## CHANGE TO YOUR PARAMS ########## 24 | switcherIP = "10.0.0.18" 25 | phone_id = "0000" 26 | device_id = "ceb91c" 27 | device_pass = "00000000" 28 | ########## DO NOT CHANGE BYOND THIS LINE ########## 29 | ``` 30 | 31 | **Earlier firmaware versions can still use the extract feature in this script in order to get the required values** 32 | 33 | 34 | **Features:** 35 | 36 | * Turn Device ON/OFF 37 | * Turn Device ON/OFF using a timer 38 | * Get Device State and Information 39 | * Create and Delete Schedules 40 | * Enable/Disable a Schedule 41 | * Change Device name 42 | * Change Auto shutdown configuration 43 | * Auto discover device IP Address and State 44 | * Configure Switcher in Access Point Mode 45 | 46 | 47 |  48 | 49 | 50 | Product homepage: [https://www.switcher.co.il/](https://www.switcher.co.il/) 51 | 52 | ### Home Asssistant Component 53 | 54 | Home assistant users can use this script as a command line [switch](https://home-assistant.io/components/switch.command_line/) or use the following component created by [@TomerFI](https://github.com/TomerFi/) based on this script: 55 | 56 | [https://github.com/TomerFi/home-assistant-custom-components/tree/master/switcher_aio](https://github.com/TomerFi/home-assistant-custom-components/tree/master/switcher_aio) 57 | 58 | ### Requirements 59 | 60 | This script requires Python 2.7 61 | 62 | 63 | In order to use the script you will need to extract the following parameters from a rooted android device, from a packet capture or using the "extract" feature and adjust the script parameters accordingly: 64 | 65 | * switcherIP 66 | * phone_id 67 | * device_id 68 | * device_pass 69 | 70 | [i] The **"extract"**, **"Auto Discover"** and **"Configure Switcher in AP Mode"** features **DOES NOT require** the **switcherIP, phone_id, device_id, device_pass** parameters in order to operate 71 | 72 | 73 | There are 3 ways to extract the required values: 74 | 75 | 1. Manually from a rooted Android Device from the switcher app sqlite db 76 | 2. Manually an unrooted Android device using a packet capture application 77 | 3. Automatically using **THIS script** 78 | 79 | **To extract the values using this script please look at the [usage example in the usage section](https://github.com/NightRang3r/Switcher-V2-Python#extract-required-values)** 80 | 81 | #### switcherIP = "0.0.0.0" 82 | 83 | Change IP Address to your switcher IP 84 | 85 | #### phone_id = "0000" 86 | 87 | Can be found in the sqlite db on a rooted android device (/data/data/com.ogemary.smarthome/databases/myDB) in "phone" table **"uid"** column, value needs to be converted into hex and turn hex value to Little Endian 88 | or from packet capture of an "on/off" command (a record with 147 length in wireshark, 93 bytes data (186 chars)) copy 4 chars value from 89-93 or bytes 44-46 89 | 90 | #### device_id = "000000" 91 | 92 | Can be found in the sqlite db on a rooted android device (/data/data/com.ogemary.smarthome/databases/myDB) in "device" table **"did"** column needs to be converted into hex and turn hex value to Little Endian 93 | or from packet capture of an "on/off" command (a record with 147 length in wireshark, 93 bytes data (186 chars)) copy 6 chars value from 81-87 or bytes 40-43 94 | 95 | #### device_pass = "00000000" 96 | 97 | Can be found in the sqlite db on a rooted android device (/data/data/com.ogemary.smarthome/databases/myDB) in "device" table **"devicepass"** column needs to be viewd in binary mode, copy the 8 digits password 98 | or from packet capture of an "on/off" command (a record with 147 length in wireshark, 93 bytes data (186 chars)) copy 8 chars value from 97-105 or bytes 48-52 99 | 100 | **Example of captured ON/OFF Packet from local lan connection to switcher device (Required values are highlighted):** 101 | 102 | fef05d0002320102c49d8448340001000000000000000000bccbb05a00000000000000000000f0fe**44bc1e**00**711a**0000**36373731**0000000000000000000000000000000000000000000000000000000001060001000000000073290d1d 103 | 104 | 105 | You can use this tool to convert the values into HEX: 106 | 107 | [https://www.binaryhexconverter.com/decimal-to-hex-converter](https://www.binaryhexconverter.com/decimal-to-hex-converter) 108 | 109 | Use this tool to convert to little endian: 110 | 111 | [https://www.scadacore.com/tools/programming-calculators/online-hex-converter/](https://www.scadacore.com/tools/programming-calculators/online-hex-converter/) 112 | 113 | 114 | **Example conversion of "did" to little endian :** 115 |
116 | did from db = 2004123 117 | Converted to hex = 1E949B 118 | Little endian = 9B941E 119 |120 | 121 | 122 | # Usage: 123 | 124 | ### Extract Required Values 125 | 126 | This feature will attempt to extract the required parameters for the operation of this script or the home assistant component 127 | 128 | How it works ? 129 | 130 | 1. The script will listen and wait for a broadcast message from your switcher device and will extract the **device_id** paramter from the broadcast message. 131 | 2. The script will send a "magic" packet which will retrun the **phone_id** parameter 132 | 3. The script will Perform a Brute Force attack in order to find the **device_password** 133 | 4. A file with the extracted information will be created in the same directory of the script. 134 | 135 | **Please notice the process can take between 10 seconds to 10 minutes!** 136 | 137 | This is a guided process, which requires user intervention, **You will need to click the "Update" button in the "Auto close" screen or turn ON your switcher device in the Switcher App when prompted**. 138 | 139 |
~# python switcher.py extract140 | 141 |  142 | 143 | ### Discover Switcher IP Address and State 144 | 145 | 146 | This is a "passive" feature that will **listen** to broadcasts sent by your switcher device which contains the following information: 147 | 148 | * Device IP Address 149 | * Device MAC Address 150 | * Device Name 151 | * Device ID 152 | * Device state (ON/OFF) 153 | * Electric Current 154 | * Power Consumption 155 | * Auto shutdown value as configured in the switcher app 156 | * Auto shutdown counter 157 | 158 | **[i]** This feature **DOES NOT** require the **switcherIP**, **phone_id**, **device_id**, **device_pass** parameters in order to operate 159 | 160 |
~# python switcher.py discover161 | 162 |  163 | 164 | 165 | ### Configure Switcher in Access Point Mode 166 | 167 | **[i]** This feature **DOES NOT** require the **switcherIP**, **phone_id**, **device_id**, **device_pass** parameters in order to operate 168 | 169 | This feature will allow you to configure your switcher device without the official application directly from this script! 170 | This is a guided process that allowes you to associate you device to your WiFi network. 171 | 172 | Please make sure the switcher device is in **ACCESS POINT MODE** by holding the WiFi configuration button on the switcher device for 10 seconds (until you see the blue led blinking fast) and that you are connected to the WiFi network **"Switcher Boiler XXXX"** 173 | 174 | **WARNING::: You will not be able to control your switcher from your mobile device anymore**, But, You can always reconfigure your switcher using the official app in order to gain back control via the app but, in order to use with this script/home assistant as well you will need to extract the **switcherIP**, **phone_id**, **device_id**, **device_pass** and configure them in the script/home assistant component as described in this readme file. 175 | 176 | __Why and when use this feature ?__ 177 | 178 | 1. You don't own a smart phone / tablet or you want to control your device using this script or Home Assistant **ONLY!** 179 | 2. The switcher app or service are not available anymore. 180 | 181 | You don't need to configure anything in the script in order to use this feature, It will produce the necessary parameters required by this script or the home assistant component for controling the device. 182 | 183 |
~# python switcher.py configure184 | 185 |  186 | 187 | 188 | 189 | 190 | ### Turn off Switcher 191 | 192 |
~# python switcher.py 0193 | 194 | ### Turn on Switcher 195 | 196 |
~# python switcher.py 1197 | 198 | ### Turn on Switcher for X minutes 199 | 200 | Value can be 1-60 minutes 201 | 202 |
~# python switcher.py t10203 |
~# python switcher.py t30204 |
~# python switcher.py t60205 | 206 | ### Get Switcher State 207 | 208 | Get state will disaply the following information: 209 | 210 | * Device Name 211 | * Device state (ON/OFF) 212 | * Electric Current 213 | * Power Consumption 214 | * Auto shutdown value as configured in the switcher app 215 | * Auto shutdown counter 216 | 217 | Example: 218 | 219 |
~# python switcher.py 2220 | 221 | 222 | ### Set Auto shutdown 223 | 224 | This will change the Auto shutdown value for your switcher device and will override the app setting, the change will reflect in the switcher app. 225 | 226 | Value can be 01:00 - 23:59 227 | 228 | Format: 229 | 230 |
~# python switcher.py mHH:MM231 | 232 | Here are some examples: 233 | 234 | 3 hours example: 235 |
~# python switcher.py m03:00236 | 237 | 3 hours and 30 minutes example: 238 |
~# python switcher.py m03:30239 | 240 | ### Schedules Information 241 | 242 | The schedules time zone is set based on the time configured in the computer that is running this script, In case of clock/time zone changes (SUMMER/WINTER) you will need to delete the existing schedules and create them again! 243 | 244 | ### Schedule retrieval 245 | 246 |
~# python switcher.py list247 | 248 |  249 | 250 | ### Create Schedule 251 | 252 |
~# python switcher.py create253 | 254 | Step 1: When prompted enter a day you would like to schedule and type "exit" or press the enter key when finished 255 | Available options: sun, mon, tue, wed, thu, fri, sat 256 | Or use: "all" to select all days or "once" for one time only schedule 257 | 258 | Step 2: When prompted enter start time in the following format: HH:MM 259 | 260 | Step 3: When prompted enter end time in the following format: HH:MM 261 | 262 |  263 | 264 | 265 | 266 | ### Delete Schedule 267 | 268 | This will show a all the schedule entries with the ID's, You will be prompted to enter a valid ID 269 | 270 |
~# python switcher.py del271 | 272 |  273 | 274 | 275 | ### Enable Schedule 276 | 277 | This will show a all the schedule entries with the ID's, You will be prompted to enter a valid ID 278 | 279 |
~# python switcher.py enable280 | 281 | 282 | ### Disable Schedule 283 | 284 | This will show a all the schedule entries with the ID's, You will be prompted to enter a valid ID 285 | 286 |
~# python switcher.py disable287 | 288 | 289 | 290 | ### Change device name 291 | 292 | This will allow you to change your switcher device name 293 | 294 |
~# python switcher.py nNAME295 | 296 |
~# python switcher.py nBoiler297 | 298 |
~# python switcher.py nSwitcher299 | -------------------------------------------------------------------------------- /Switcher V2 Rooted Device.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NightRang3r/Switcher-V2-Python/7240bf1faef87d3dc1d642420bb66fe83538579b/Switcher V2 Rooted Device.pdf -------------------------------------------------------------------------------- /Switcher V2 UnRooted Device.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NightRang3r/Switcher-V2-Python/7240bf1faef87d3dc1d642420bb66fe83538579b/Switcher V2 UnRooted Device.pdf -------------------------------------------------------------------------------- /extractV3.py: -------------------------------------------------------------------------------- 1 | # Reverse Engineering and coding by Aviad Golan @AviadGolan and Shai Rod @NightRang3r 2 | 3 | #!/usr/bin/env python 4 | 5 | import binascii as ba 6 | import time 7 | import struct 8 | import socket 9 | import sys 10 | import os 11 | import datetime 12 | import re 13 | import signal 14 | 15 | ########## CHANGE TO YOUR PARAMS ########## 16 | switcherIP = "0.0.0.0" 17 | device_id = "000000" 18 | device_pass = "00000000" 19 | UDP_IP = "0.0.0.0" 20 | UDP_PORT = 20002 21 | sCommand = "0" 22 | ########## DO NOT CHANGE BYOND THIS LINE ########## 23 | 24 | if len(sys.argv) != 2: 25 | print "\r\n********* Usage: ./" + sys.argv[0] + " phone_id *********\r\n" 26 | sys.exit() 27 | 28 | phone_id = sys.argv[1] 29 | 30 | def crcSignFullPacketComKey(pData, pKey): 31 | crc = ba.hexlify(struct.pack('>I', ba.crc_hqx(ba.unhexlify(pData), 0x1021))) 32 | pData = pData + crc[6:8] + crc[4:6] 33 | crc = crc[6:8] + crc[4:6] + ba.hexlify( pKey ) 34 | crc = ba.hexlify(struct.pack('>I', ba.crc_hqx(ba.unhexlify(crc), 0x1021))) 35 | pData = pData + crc[6:8] + crc[4:6] 36 | return pData 37 | 38 | def getTS(): 39 | return ba.hexlify(struct.pack(' 44 and len(res) < 60: 119 | brute_end = time.time() 120 | total_time= time.strftime("%H:%M:%S", time.gmtime(brute_end-brute_start)) 121 | print "\r\n[+] Found password in: " + total_time 122 | print "[+] Switcher IP: " + addr[0] 123 | print "[+] Device ID: " + device_id 124 | print "[+] Phone ID:" + phone_id 125 | print "[+] Device Password: " + device_pass 126 | s.close() 127 | file = open('switcher.txt', 'w') 128 | file.write("switcherIP = " + '"' + addr[0] + '"\r') 129 | file.write("phone_id = " + '"' + phone_id + '"\r') 130 | file.write("device_id = " + '"' + device_id + '"\r') 131 | file.write("device_pass = " + '"' + device_pass + '"\r') 132 | file.close() 133 | print "[+] Information was written to " + os.getcwd() + "/switcher.txt" 134 | sys.exit() 135 | except Exception as e: 136 | print("[!] Something went wrong...") 137 | print "[!] " + str(e) 138 | 139 | 140 | -------------------------------------------------------------------------------- /switcher.py: -------------------------------------------------------------------------------- 1 | # Reverse Engineering and coding by Aviad Golan @AviadGolan and Shai Rod @NightRang3r 2 | 3 | #!/usr/bin/env python 4 | 5 | import binascii as ba 6 | import time 7 | import struct 8 | import socket 9 | import sys 10 | import os 11 | import datetime 12 | import re 13 | import signal 14 | 15 | ########## CHANGE TO YOUR PARAMS ########## 16 | switcherIP = "0.0.0.0" 17 | phone_id = "0000" 18 | device_id = "000000" 19 | device_pass = "00000000" 20 | ########## DO NOT CHANGE BYOND THIS LINE ########## 21 | 22 | 23 | UDP_IP = "0.0.0.0" 24 | UDP_PORT = 20002 25 | 26 | id_list = [] 27 | data_list = [] 28 | 29 | 30 | def banner(): 31 | print """ 32 | ========================================================= 33 | + Switcher V2 Python + 34 | + Reverse Engineering and Coding By: + 35 | + Aviad Golan (@AviadGolan) and Shai Rod (@NightRang3r) + 36 | ========================================================= 37 | """ 38 | print "Usage:\r" 39 | print "======\r" 40 | print "Off command:" + " ./" + sys.argv[0] + " 0\r" 41 | print "On command:" + " ./" + sys.argv[0] + " 1\r" 42 | print "On command duration in minutes:" + " ./" + sys.argv[0] + " t30\r" 43 | print "Get State:" + " ./" + sys.argv[0] + " 2\r" 44 | print "Set Auto shutdown setting (in hours):" + " ./" + sys.argv[0] + " m03:00\r" 45 | print "Retrieve Schedule from device:" + " ./" + sys.argv[0] + " list\r" 46 | print "Create a Schedule:" + " ./" + sys.argv[0] + " create\r" 47 | print "Delete Schedule from device:" + " ./" + sys.argv[0] + " del\r" 48 | print "Enable Schedule:" + " ./" + sys.argv[0] + " enable\r" 49 | print "Disable Schedule:" + " ./" + sys.argv[0] + " disable\r" 50 | print "Change switcher name:" " ./" + sys.argv[0] + " nNAME\r" 51 | print "Auto detect Switcher IP Address and state:" + " ./" + sys.argv[0] + " discover\r" 52 | print "Configure Switcher in AP Mode:" + " ./" + sys.argv[0] + " configure\r" 53 | print "Auto Extract the needed values for this script (device_id, phone_id and device_pass):" + " ./" + sys.argv[0] + " extract\r\n" 54 | sys.exit (1) 55 | 56 | if len (sys.argv) != 2: 57 | banner() 58 | elif sys.argv[1] == "0": 59 | sCommand = "0" 60 | elif sys.argv[1] == "1": 61 | sCommand = "1" 62 | elif sys.argv[1] == "2": 63 | sCommand = "2" 64 | elif sys.argv[1].startswith('t'): 65 | sCommand = "1" 66 | elif sys.argv[1].startswith('m'): 67 | sCommand = "2" 68 | elif sys.argv[1].startswith('n'): 69 | sCommand = "2" 70 | elif sys.argv[1] == "list": 71 | sCommand = "3" 72 | elif sys.argv[1] == "del": 73 | sCommand = "3" 74 | elif sys.argv[1] == "create": 75 | sCommand = "3" 76 | elif sys.argv[1] == "enable": 77 | sCommand = "3" 78 | elif sys.argv[1] == "disable": 79 | sCommand = "3" 80 | elif sys.argv[1] == "discover": 81 | sCommand = "3" 82 | elif sys.argv[1] == "configure": 83 | sCommand = "3" 84 | elif sys.argv[1] == "extract": 85 | sCommand = "3" 86 | else: 87 | banner() 88 | 89 | # CRC 90 | def crcSignFullPacketComKey(pData, pKey): 91 | crc = ba.hexlify(struct.pack('>I', ba.crc_hqx(ba.unhexlify(pData), 0x1021))) 92 | pData = pData + crc[6:8] + crc[4:6] 93 | crc = crc[6:8] + crc[4:6] + ba.hexlify( pKey ) 94 | crc = ba.hexlify(struct.pack('>I', ba.crc_hqx(ba.unhexlify(crc), 0x1021))) 95 | pData = pData + crc[6:8] + crc[4:6] 96 | return pData 97 | 98 | # Generate Time Stamp 99 | def getTS(): 100 | return ba.hexlify(struct.pack(' 86340: 129 | print "[!] Value can't be more than 23 hours and 59 minutes!" 130 | sys.exit() 131 | else: 132 | print "[+] Auto shutdown was set to " + str(hours) + " Hour(s)" 133 | return ba.hexlify(struct.pack(' 44 and len(res) < 60: 286 | brute_end = time.time() 287 | total_time= time.strftime("%H:%M:%S", time.gmtime(brute_end-brute_start)) 288 | print "\r\n[+] Found password in: " + total_time 289 | print "[+] Switcher IP: " + addr[0] 290 | print "[+] Device ID: " + device_id 291 | print "[+] Phone ID:" + phone_id 292 | print "[+] Device Password: " + device_pass 293 | s.close() 294 | file = open('switcher.txt', 'w') 295 | file.write("switcherIP = " + '"' + addr[0] + '"\r') 296 | file.write("phone_id = " + '"' + phone_id + '"\r') 297 | file.write("device_id = " + '"' + device_id + '"\r') 298 | file.write("device_pass = " + '"' + device_pass + '"\r') 299 | file.close() 300 | print "[+] Information was written to " + os.getcwd() + "/switcher.txt" 301 | sys.exit() 302 | except Exception as e: 303 | print("[!] Something went wrong...") 304 | print "[!] " + str(e) 305 | 306 | 307 | if sys.argv[1] == "discover": 308 | try: 309 | sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 310 | sock.bind((UDP_IP, UDP_PORT)) 311 | while True: 312 | print "[*] Waiting for broadcast from Switcher device...(Press CTRL+C to abort)" 313 | data, addr = sock.recvfrom(1024) 314 | if ba.hexlify(data)[0:4] != "fef0" and len(data) != 165: 315 | print "[!] Not a switcher broadcast message!" 316 | else: 317 | b = ba.hexlify(data)[152:160] 318 | ip_addr = int(b[6:8] + b[4:6] + b[2:4] + b[0:2] , 16) 319 | print "[+] Switcher IP Address: " + socket.inet_ntoa(struct.pack("