Wrote Python 3 implementation of smartconfig

Includes broadcast strategy ported from tuyapi/link
Also includes multicast strategy not yet employed in tuyapi/link
Removed JS script and updated start_flash script to reflect changes
pull/23/head
Colin Kuebler 2019-01-26 18:25:16 -05:00
parent df38bc33c0
commit 379b26ffd7
8 changed files with 232 additions and 61 deletions

View File

@ -0,0 +1,41 @@
#!/usr/bin/env python3
# encoding: utf-8
"""
broadcast.py
Created by kueblc on 2019-01-25.
Encode data for Tuya smartconfig via broadcast
broadcast strategy ported from https://github.com/tuyapi/link
"""
from crc import crc_8
broadcast_head = [1, 3, 6, 10]
def encode_broadcast_body( password, ssid, token_group ):
r = []
r.append( len(password) )
r.extend([ ord(l) for l in password ])
r.append( len(token_group) )
r.extend([ ord(l) for l in token_group ])
r.extend([ ord(l) for l in ssid ])
e = []
length = len(r)
length_crc = crc_8([ length ])
e.append( length >> 4 | 16 )
e.append( length & 0xF | 32 )
e.append( length_crc >> 4 | 48 )
e.append( length_crc & 0xF | 64 )
sequence = 0
for i in range(0, length, 4):
group = []
group.append( sequence )
group.extend( r[ i : i+4 ] )
group.extend( [0] * (5 - len(group)) )
group_crc = crc_8(group)
e.append( group_crc & 0x7F | 128 )
e.append( sequence | 128 )
e.extend([ b | 256 for b in r[ i : i+4 ] ])
sequence += 1
e.extend( [256] * (length - i) )
return e

35
scripts/smartconfig/crc.py Executable file
View File

@ -0,0 +1,35 @@
#!/usr/bin/env python3
# encoding: utf-8
"""
crc.py
Created by kueblc on 2019-01-25.
Compute 8bit and 32bit CRCs for Tuya communication
based in part on https://github.com/tuyapi/link
"""
def crc_8_byte( b ):
r = 0
for i in range(8):
if (r ^ b) & 1:
r ^= 0x18
r >>= 1
r |= 0x80
else:
r >>= 1
b >>= 1
return r
def crc_8( a ):
r = 0
for b in a:
r = crc_8_byte( r ^ b )
return r
crc_32_table = [0, 1996959894, -301047508, -1727442502, 124634137, 1886057615, -379345611, -1637575261, 249268274, 2044508324, -522852066, -1747789432, 162941995, 2125561021, -407360249, -1866523247, 498536548, 1789927666, -205950648, -2067906082, 450548861, 1843258603, -187386543, -2083289657, 325883990, 1684777152, -43845254, -1973040660, 335633487, 1661365465, -99664541, -1928851979, 997073096, 1281953886, -715111964, -1570279054, 1006888145, 1258607687, -770865667, -1526024853, 901097722, 1119000684, -608450090, -1396901568, 853044451, 1172266101, -589951537, -1412350631, 651767980, 1373503546, -925412992, -1076862698, 565507253, 1454621731, -809855591, -1195530993, 671266974, 1594198024, -972236366, -1324619484, 795835527, 1483230225, -1050600021, -1234817731, 1994146192, 31158534, -1731059524, -271249366, 1907459465, 112637215, -1614814043, -390540237, 2013776290, 251722036, -1777751922, -519137256, 2137656763, 141376813, -1855689577, -429695999, 1802195444, 476864866, -2056965928, -228458418, 1812370925, 453092731, -2113342271, -183516073, 1706088902, 314042704, -1950435094, -54949764, 1658658271, 366619977, -1932296973, -69972891, 1303535960, 984961486, -1547960204, -725929758, 1256170817, 1037604311, -1529756563, -740887301, 1131014506, 879679996, -1385723834, -631195440, 1141124467, 855842277, -1442165665, -586318647, 1342533948, 654459306, -1106571248, -921952122, 1466479909, 544179635, -1184443383, -832445281, 1591671054, 702138776, -1328506846, -942167884, 1504918807, 783551873, -1212326853, -1061524307, -306674912, -1698712650, 62317068, 1957810842, -355121351, -1647151185, 81470997, 1943803523, -480048366, -1805370492, 225274430, 2053790376, -468791541, -1828061283, 167816743, 2097651377, -267414716, -2029476910, 503444072, 1762050814, -144550051, -2140837941, 426522225, 1852507879, -19653770, -1982649376, 282753626, 1742555852, -105259153, -1900089351, 397917763, 1622183637, -690576408, -1580100738, 953729732, 1340076626, -776247311, -1497606297, 1068828381, 1219638859, -670225446, -1358292148, 906185462, 1090812512, -547295293, -1469587627, 829329135, 1181335161, -882789492, -1134132454, 628085408, 1382605366, -871598187, -1156888829, 570562233, 1426400815, -977650754, -1296233688, 733239954, 1555261956, -1026031705, -1244606671, 752459403, 1541320221, -1687895376, -328994266, 1969922972, 40735498, -1677130071, -351390145, 1913087877, 83908371, -1782625662, -491226604, 2075208622, 213261112, -1831694693, -438977011, 2094854071, 198958881, -2032938284, -237706686, 1759359992, 534414190, -2118248755, -155638181, 1873836001, 414664567, -2012718362, -15766928, 1711684554, 285281116, -1889165569, -127750551, 1634467795, 376229701, -1609899400, -686959890, 1308918612, 956543938, -1486412191, -799009033, 1231636301, 1047427035, -1362007478, -640263460, 1088359270, 936918000, -1447252397, -558129467, 1202900863, 817233897, -1111625188, -893730166, 1404277552, 615818150, -1160759803, -841546093, 1423857449, 601450431, -1285129682, -1000256840, 1567103746, 711928724, -1274298825, -1022587231, 1510334235, 755167117]
def crc_32( a ):
r = -1
for b in a:
r = ( (r & 0xFFFFFFFF) >> 8 ) ^ crc_32_table[(r ^ b) & 255]
return r ^ -1

View File

@ -0,0 +1,27 @@
#!/usr/bin/env python3
# encoding: utf-8
"""
main.py
Created by kueblc on 2019-01-25.
Configure Tuya devices via smartconfig for tuya-convert
"""
ssid = "vtrust-flash"
passwd = "flashmeifyoucan"
region = "EU"
token = "00000000"
secret = "0101"
from smartconfig import smartconfig
print('Put Device in Learn Mode! Sending SmartConfig Packets now')
print('Sending SSID '+ssid)
print('Sending wifiPassword '+passwd)
print('SmartConfig in progress')
smartconfig( passwd, ssid, region, token, secret )
print()
print('SmartConfig complete.')

View File

@ -0,0 +1,65 @@
#!/usr/bin/env python3
# encoding: utf-8
"""
multicast.py
Created by kueblc on 2019-01-25.
Encode data for Tuya smartconfig via multicast
multicast strategy reverse engineered by kueblc
"""
from crc import crc_32
from Crypto.Cipher import AES
pad = lambda data, block_size : data + ('\0' * ( (block_size - len(data)) % block_size ) )
aes = AES.new( "a3c6794oiu876t54", AES.MODE_ECB, '' )
encrypt = lambda data : aes.encrypt( pad(data, 16) )
def encode_pw( pw ):
r = []
pw_bytes = [ ord(c) for c in pw ]
encrypted_pw = encrypt(pw)
# CRC and length are from plaintext
crc = crc_32(pw_bytes)
# length, twice
r.append( len(pw) )
r.append( len(pw) )
# encode CRC as LE
r.extend([ (crc >> i) & 255 for i in range(0,32,8) ])
# payload, AES encrypted
r.extend( encrypted_pw )
return r
def encode_plain( s ):
r = []
s_bytes = [ ord(c) for c in s ]
crc = crc_32(s_bytes)
# length, twice
r.append( len(s) )
r.append( len(s) )
# encode CRC as LE
r.extend([ (crc >> i) & 255 for i in range(0,32,8) ])
# payload, plaintext
r.extend( s_bytes )
return r
def bytes_to_ips( data, sequence ):
r = []
if len(data) & 1:
data.append(0)
for i in range(0, len(data), 2):
r.append( "226." + str(sequence) + "." + str(data[i+1]) + "." + str(data[i]) )
sequence += 1
return r
multicast_head = bytes_to_ips([ ord(c) for c in "TYST01" ], 120)
def encode_multicast_body( password, ssid, token_group ):
r = []
ssid_encoded = encode_plain(ssid)
r.extend( bytes_to_ips( ssid_encoded, 64 ) )
password_encoded = encode_pw(password)
r.extend( bytes_to_ips( password_encoded, 0 ) )
token_group_encoded = encode_plain(token_group)
r.extend( bytes_to_ips( token_group_encoded, 32 ) )
return r

View File

@ -1,25 +0,0 @@
{
"name": "tuya-helper",
"version": "1.0.0",
"description": "Send Smartlink Packets to Devices",
"main": "smartconfig.js",
"keywords": [
"tuya",
"cli",
"iot"
],
"author": "Zimbo Boyd <zimbo.boyd@gmail.com> (https://vtrust.de)",
"license": "MIT",
"homepage": "https://github.com/vtrust-de/smarthack",
"dependencies": {
"@tuyapi/link": "^0.2.0",
"debug": "^4.1.1",
"ora": "^3.0.0"
},
"devDependencies": {
"xo": "^0.21.1"
},
"engines": {
"node": ">=8"
}
}

View File

@ -1,34 +0,0 @@
#!/usr/bin/env node
//cmd = system("ip route add 255.255.255.255 dev wlan0")
// --> Broadcast must be routed over WIFI
// --> Any other WIFI device needs to be connected during pairing process!
const ssid = "vtrust-flash"
const passwd = "flashmeifyoucan"
const wait_time = 60; // seconds
function sleep(seconds){
var waitUntil = new Date().getTime() + seconds*1000;
while(new Date().getTime() < waitUntil) true;
}
const debug = require('debug')('Main');
//const ora = require('ora');
const TuyaLink = require('@tuyapi/link');
const manual = new TuyaLink.manual();
console.log('Put Device in Learn Mode!, sending Smartlink Packets now');
manual.registerSmartLink({region: 'EU',
token: '00000000',
secret: '0101',
ssid: ssid,
wifiPassword: passwd});
console.log('Smartlink in progress');
console.log('Sending SSID '+ssid);
console.log('Sending wifiPassword '+passwd);
console.log('Sending Packets and wait '+wait_time+' seconds.');

View File

@ -0,0 +1,62 @@
#!/usr/bin/env python3
# encoding: utf-8
"""
smartconfig.py
Created by kueblc on 2019-01-25.
Configure Tuya devices via smartconfig without the Tuya cloud or app
broadcast strategy ported from https://github.com/tuyapi/link
multicast strategy reverse engineered by kueblc
"""
# Defaults
# time to sleep inbetween packets, 5ms
GAP = 5 / 1000.
BIND_ADDRESS = ''
MULTICAST_TTL = 1
from socket import *
from time import sleep
class SmartConfigSocket(object):
def __init__( self, address = BIND_ADDRESS, gap = GAP ):
self._socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
self._socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
self._socket.setsockopt(SOL_SOCKET, SO_BROADCAST, 1)
self._socket.setsockopt(IPPROTO_IP, IP_MULTICAST_TTL, MULTICAST_TTL)
self._socket.bind((address, 0))
self._gap = gap
def send_broadcast( self, data ):
for length in data:
self._socket.sendto( b'\0' * length, ('255.255.255.255', 30011))
sleep(self._gap)
def send_multicast( self, data ):
for ip in data:
self._socket.sendto( b'\0', (ip, 30012))
sleep(self._gap)
from broadcast import broadcast_head, encode_broadcast_body
from multicast import multicast_head, encode_multicast_body
def smartconfig( password, ssid, region, token, secret ):
sock = SmartConfigSocket()
token_group = region + token + secret
broadcast_body = encode_broadcast_body( password, ssid, token_group )
# print(broadcast_body)
multicast_body = encode_multicast_body( password, ssid, token_group )
# print(multicast_body)
# print("sending header")
for i in range(40): # originally 143, that's more than we really need
sock.send_multicast(multicast_head)
sock.send_broadcast(broadcast_head)
for i in range(10): # originally 30, again, more than necessary
# print("sending body iteration " + str(i))
print('.', end='', flush=True) # quick and dirty progress meter
sock.send_multicast(multicast_head)
sock.send_multicast(multicast_body)
sock.send_broadcast(broadcast_body)

View File

@ -43,14 +43,14 @@ echo "IMPORTANT"
echo "1. Connect any another device (a smartphone or something) to the WIFI $AP"
echo " The wpa-password is ${bold}$PASS${normal}"
echo " This step is IMPORTANT otherwise the smartconfig will not work!"
echo "2. Put your IoT device in autoconfig/smartconfig/paring mode (LED will blink fast). This is usually done by pressing and holding the primary button of the device"
echo "2. Put your IoT device in autoconfig/smartconfig/pairing mode (LED will blink fast). This is usually done by pressing and holding the primary button of the device"
echo "3. Press ${bold}ENTER${normal} to continue"
read x
echo ""
echo "======================================================"
echo "Starting pairing procedure in screen"
sudo ip route add 255.255.255.255 dev $WLAN
sudo screen -L smarthack-smartconfig.log -S smarthack-smartconfig -m -d ./smartconfig/smartconfig.js
sudo screen -L smarthack-smartconfig.log -S smarthack-smartconfig -m -d ./smartconfig/main.py
echo "Waiting for the upgraded device to appear"
echo "If this does not work have a look at the '*.log'-files in the 'scripts' subfolder!"