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 changespull/23/head
parent
df38bc33c0
commit
379b26ffd7
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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.')
|
||||
|
|
@ -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
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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.');
|
||||
|
|
@ -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)
|
||||
|
|
@ -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!"
|
||||
|
||||
|
|
Loading…
Reference in New Issue