Skip to content

Commit dbeab1b

Browse files
authored
Release v2.3.0
2 parents d0984d1 + 6ac6ab5 commit dbeab1b

14 files changed

+200
-72
lines changed

README.md

+7-34
Original file line numberDiff line numberDiff line change
@@ -43,52 +43,25 @@ On January 28th, 2019, Tuya started [distributing a patch](https://www.heise.de/
4343
# git clone https://github.com/ct-Open-Source/tuya-convert
4444
# cd tuya-convert
4545
# ./install_prereq.sh
46-
### flash loader firmware + backup
47-
# ./start_flash.sh
48-
49-
Follow the instructions in the start_flash script. It will install our flash loader onto the ESP and connect to the access point created by your wifi adapter.
50-
51-
WIFI: vtrust-flash
52-
IP: 10.42.42.42
53-
A backup of the original firmware will be created and stored locally
54-
55-
### Device information
56-
After the firmware backup procedure, the retrieved device information will be shown.
57-
Please make sure to write down your devices flash mode and size!
58-
You can show this information again by executing:
59-
60-
# curl http://10.42.42.42
61-
### BACKUP only and UNDO
62-
You can use the flash loader to create a backup only.
63-
If you want to delete the FLASH loader out of the flash again and go back to the stock software just do following:
64-
65-
# curl http://10.42.42.42/undo
66-
### FLASH loader to user2
67-
The FLASH loader only allows flashing the third party firmware if the loader is running in the userspace user2 starting from 0x81000.
68-
This will flash the FLASH loader in user2 if it is not already there.
69-
It will destroy your ability to undo and go back to the original firmware
70-
71-
# curl http://10.42.42.42/flash2
7246

7347
### FLASH third-party firmware
7448
BE SURE THE FIRMWARE FITS YOUR DEVICE!
75-
1. Place or link your binary file to ./files/thirdparty.bin.
49+
1. Place your binary file in the `/files/` directory or use one of the included firmware images.
7650

7751
Currently a Tasmota [v7.0.0.3](https://github.com/arendst/Tasmota/releases) `tasmota-wifiman.bin` build is included. You can update to a [current version](http://thehackbox.org/tasmota) via OTA after the Tuya-Convert process completes successfully. Please note that while we include this for your convenience, we are not affiliated with the Tasmota project and cannot provide support for post installation issues. Please refer to [the respective project](https://github.com/arendst/Tasmota) for configuration and support.
78-
52+
7953
An ESPurna [1.13.5](https://github.com/xoseperez/espurna/releases/tag/1.13.5) binary is also included (`espurna-base.bin`). Like before, the binary included does not have any specific hardware defined. Once flashed using Tuya-Convert you can update to the device-specific version via any of the means that ESPurna provides (OTA, web interface update, update via telnet or MQTT). Please refer to the [ESPurna project page](http://espurna.io) for more info and support.
8054

8155
Binary requirements:
82-
* full binary including first-stage bootloader
56+
* full binary including first-stage bootloader (tested with Arduino eboot and Open-RTOS rBoot)
8357
* maximum filesize 512KB for first flash
8458

8559
2. Start flashing process
8660

87-
# curl http://10.42.42.42/flash3
88-
89-
Alternatively you can request a certain file to be requested and flashed by the device:
90-
91-
# curl http://10.42.42.42/flash3?url=http://10.42.42.1/files/certain_file.bin
61+
Execute `./start_flash.sh` and follow the instructions.
62+
It will install our flash loader onto the ESP and connect to the access point created by your wifi adapter.
63+
A backup of the original firmware will be automatically downloaded and stored locally.
64+
You can then proceed to flash your desired firmware or revert to the stock firmware.
9265

9366
3. Initial Configuration
9467

File renamed without changes.
File renamed without changes.

files/upgrade.bin

-80 Bytes
Binary file not shown.

files/user2.bin

-247 KB
Binary file not shown.

scripts/fake-registration-server.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ def post(self):
113113
print(self.request.headers)
114114
if payload:
115115
try:
116-
decrypted_payload = unpad(AES.new(options.secKey, AES.MODE_ECB).decrypt(binascii.unhexlify(payload))).decode()
116+
decrypted_payload = unpad(AES.new(options.secKey.encode(), AES.MODE_ECB).decrypt(binascii.unhexlify(payload))).decode()
117117
if decrypted_payload[0] != "{":
118118
raise ValueError("payload is not JSON")
119119
print("payload", decrypted_payload)

scripts/firmware_picker.sh

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
#!/bin/bash
2+
3+
MAGIC=$(printf "\xe9")
4+
5+
while true; do
6+
echo
7+
echo "Available options:"
8+
echo " 0) return to stock"
9+
index=0
10+
for file in ../files/*.bin; do
11+
# skip null glob
12+
[[ -e $file ]] || continue
13+
# get short name
14+
filename=$(basename "$file")
15+
# skip files too large or too small
16+
filesize=$(stat -c%s "$file")
17+
[[ "$filesize" -gt 0x1000 && "$filesize" -le 0x80000 ]] || continue
18+
# skip files without magic byte
19+
[[ $(head -c 1 "$file") == "$MAGIC" ]] || continue
20+
echo " $((++index))) flash $filename"
21+
options[$index]="$filename"
22+
# only show first 9 options, accessible with a single keypress
23+
if (( index == 9 )); then
24+
break
25+
fi
26+
done
27+
echo " q) quit; do nothing"
28+
echo -n "Please select 0-$index: "
29+
while true; do
30+
read -n 1 -r
31+
echo
32+
if [[ "$REPLY" =~ ^[0-9]$ && "$REPLY" -ge 0 && "$REPLY" -le $index ]]; then
33+
break
34+
fi
35+
if [[ "$REPLY" =~ ^[Qq]$ ]]; then
36+
echo "Leaving device as is..."
37+
exit
38+
fi
39+
echo -n "Invalid selection, please select 0-$index: "
40+
done
41+
42+
if [[ "$REPLY" == 0 ]]; then
43+
if curl -s http://10.42.42.42/undo; then
44+
echo "Disconnect the device to prevent it from repeating the upgrade"
45+
echo "You will need to put the device back into pairing mode and register to use again"
46+
else
47+
echo "Could not reach the device!"
48+
fi
49+
break
50+
fi
51+
52+
selection="${options[$REPLY]}"
53+
read -p "Are you sure you want to flash $selection? This is the point of no return [y/N] " -n 1 -r
54+
echo
55+
[[ "$REPLY" =~ ^[Yy]$ ]] || continue
56+
57+
echo "Attempting to flash $selection, this may take a few seconds..."
58+
RESULT=$(curl -s "http://10.42.42.42/flash?url=http://10.42.42.1/files/$selection") ||
59+
echo "Could not reach the device!"
60+
61+
echo "$RESULT"
62+
if [[ "$RESULT" =~ failed || -z "$RESULT" ]]; then
63+
read -p "Do you want to try something else? [y/N] " -n 1 -r
64+
echo
65+
[[ "$REPLY" =~ ^[Yy]$ ]] || break
66+
else
67+
if [[ "$selection" == "tasmota.bin" ]]; then
68+
echo "Look for a tasmota-xxxx SSID to which you can connect and configure"
69+
echo "Be sure to configure your device for proper function!"
70+
elif [[ "$selection" == "espurna.bin" ]]; then
71+
echo "Look for an ESPURNA-XXXXXX SSID to which you can connect and configure"
72+
echo "Default password is \"fibonacci\""
73+
echo "Be sure to upgrade to your device specific firmware for proper function!"
74+
fi
75+
echo
76+
echo "HAVE FUN!"
77+
break
78+
fi
79+
done
80+

scripts/mq_pub_15.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121
from Crypto.Cipher import AES
2222
pad = lambda s: s + (16 - len(s) % 16) * chr(16 - len(s) % 16)
2323
unpad = lambda s: s[:-ord(s[len(s) - 1:])]
24-
encrypt = lambda msg, key: AES.new(key, AES.MODE_ECB).encrypt(pad(msg))
25-
decrypt = lambda msg, key: AES.new(key, AES.MODE_ECB).decrypt(unpad(msg))
24+
encrypt = lambda msg, key: AES.new(key.encode(), AES.MODE_ECB).encrypt(pad(msg))
25+
decrypt = lambda msg, key: unpad(AES.new(key.encode(), AES.MODE_ECB).decrypt(msg))
2626

2727
def iot_dec(message, local_key):
2828
message_clear = decrypt(base64.b64decode(message[19:]), local_key)

scripts/psk-frontend.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from hashlib import md5
1010
from binascii import hexlify, unhexlify
1111

12+
IDENTITY_PREFIX = "BAohbmd6aG91IFR1"
1213

1314
def listener(host, port):
1415
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
@@ -24,10 +25,15 @@ def client(host, port):
2425

2526
def gen_psk(identity, hint):
2627
print("ID: %s" % hexlify(identity))
28+
# sometimes the device only sends part of the prefix
29+
# since it is always the same, we can correct it
30+
if identity[1:17] != IDENTITY_PREFIX:
31+
print("Prefix: %s" % identity[1:17])
32+
identity = IDENTITY_PREFIX + identity[17:]
2733
key = md5(hint[-16:]).digest()
28-
iv = md5(identity[1:]).digest()
34+
iv = md5(identity).digest()
2935
cipher = AES.new(key, AES.MODE_CBC, iv)
30-
psk = cipher.encrypt(identity[1:33])
36+
psk = cipher.encrypt(identity[:32])
3137
print("PSK: %s" % hexlify(psk))
3238
return psk
3339

scripts/setup_ap.sh

+4-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ setup () {
3232
--address=/#/$GATEWAY
3333

3434
echo "Starting AP on $WLAN..."
35-
sudo hostapd hostapd.conf -i $WLAN
35+
36+
# Read hostapd.conf with interface from stdin for
37+
# backward compatibility (hostapd < v2.6). See #398
38+
printf "$(cat hostapd.conf)\ninterface=$WLAN" | sudo hostapd /dev/stdin
3639
}
3740

3841
cleanup () {

scripts/setup_checks.sh

+14
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,19 @@ check_port () {
8282
fi
8383
}
8484

85+
check_blacklist () {
86+
if [ -e /etc/modprobe.d/blacklist-rtl8192cu.conf ]; then
87+
echo "Detected /etc/modprobe.d/blacklist-rtl8192cu.conf"
88+
echo "This has been known to cause kernel panic in hostapd"
89+
echo "See https://github.com/ct-Open-Source/tuya-convert/issues/373"
90+
read -p "Do you wish to remove this file? [y/N] " -n 1 -r
91+
echo
92+
if [[ $REPLY =~ ^[Yy]$ ]]; then
93+
sudo rm /etc/modprobe.d/blacklist-rtl8192cu.conf
94+
fi
95+
fi
96+
}
97+
8598
check_eula
8699
check_config
87100
check_port udp 53 "resolve DNS queries"
@@ -92,4 +105,5 @@ check_port udp 6666 "detect unencrypted Tuya firmware"
92105
check_port udp 6667 "detect encrypted Tuya firmware"
93106
check_port tcp 1883 "run MQTT"
94107
check_port tcp 8886 "run MQTTS"
108+
check_blacklist
95109

scripts/tuya-discovery.py

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#!/usr/bin/env python3
2+
# encoding: utf-8
3+
"""
4+
tuya-discovery.py
5+
Created by kueblc on 2019-11-13.
6+
Discover Tuya devices on the LAN via UDP broadcast
7+
"""
8+
9+
import asyncio
10+
import json
11+
12+
from Crypto.Cipher import AES
13+
pad = lambda s: s + (16 - len(s) % 16) * chr(16 - len(s) % 16)
14+
unpad = lambda s: s[:-ord(s[len(s) - 1:])]
15+
encrypt = lambda msg, key: AES.new(key, AES.MODE_ECB).encrypt(pad(msg))
16+
decrypt = lambda msg, key: unpad(AES.new(key, AES.MODE_ECB).decrypt(msg))
17+
18+
from hashlib import md5
19+
udpkey = md5(b"yGAdlopoPVldABfn").digest()
20+
decrypt_udp = lambda msg: decrypt(msg, udpkey)
21+
22+
class TuyaDiscovery(asyncio.DatagramProtocol):
23+
def datagram_received(self, data, addr):
24+
# remove message frame
25+
data = data[20:-8]
26+
# decrypt if encrypted
27+
try:
28+
data = decrypt_udp(data)
29+
except:
30+
pass
31+
# parse json
32+
try:
33+
data = json.loads(data)
34+
except:
35+
pass
36+
print(addr[0], data)
37+
38+
def main():
39+
loop = asyncio.get_event_loop()
40+
listener = loop.create_datagram_endpoint(TuyaDiscovery, local_addr=('0.0.0.0', 6666))
41+
encrypted_listener = loop.create_datagram_endpoint(TuyaDiscovery, local_addr=('0.0.0.0', 6667))
42+
loop.run_until_complete(listener)
43+
print("Listening for Tuya broadcast on UDP 6666")
44+
loop.run_until_complete(encrypted_listener)
45+
print("Listening for encrypted Tuya broadcast on UDP 6667")
46+
try:
47+
loop.run_forever()
48+
except KeyboardInterrupt:
49+
loop.stop()
50+
51+
if __name__ == "__main__":
52+
main()
53+

start_flash.sh

+30-32
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
#!/bin/bash
22
bold=$(tput bold)
33
normal=$(tput sgr0)
4-
screen_minor=`screen --version | cut -d . -f 2`
5-
if [ $screen_minor -gt 5 ]; then
4+
screen_minor=$(screen --version | cut -d . -f 2)
5+
if [ "$screen_minor" -gt 5 ]; then
66
screen_with_log="sudo screen -L -Logfile"
7-
elif [ $screen_minor -eq 5 ]; then
7+
elif [ "$screen_minor" -eq 5 ]; then
88
screen_with_log="sudo screen -L"
99
else
1010
screen_with_log="sudo screen -L -t"
@@ -13,14 +13,14 @@ fi
1313

1414
./stop_flash.sh >/dev/null
1515

16-
pushd scripts >/dev/null
16+
pushd scripts >/dev/null || exit
1717

1818
. ./setup_checks.sh
1919

2020
echo "======================================================"
2121
echo -n " Starting AP in a screen"
2222
$screen_with_log smarthack-wifi.log -S smarthack-wifi -m -d ./setup_ap.sh
23-
while ! ping -c 1 -W 1 -n $GATEWAY &> /dev/null; do
23+
while ! ping -c 1 -W 1 -n "$GATEWAY" &> /dev/null; do
2424
printf .
2525
done
2626
echo
@@ -31,18 +31,21 @@ echo " Starting Mosquitto in a screen"
3131
$screen_with_log smarthack-mqtt.log -S smarthack-mqtt -m -d mosquitto -v
3232
echo " Starting PSK frontend in a screen"
3333
$screen_with_log smarthack-psk.log -S smarthack-psk -m -d ./psk-frontend.py -v
34+
echo " Starting Tuya Discovery in a screen"
35+
$screen_with_log smarthack-udp.log -S smarthack-udp -m -d ./tuya-discovery.py
3436
echo
3537
REPLY=y
3638
while [[ $REPLY =~ ^[Yy]$ ]]; do
3739
echo "======================================================"
3840
echo
3941
echo "IMPORTANT"
4042
echo "1. Connect any other device (a smartphone or something) to the WIFI $AP"
41-
echo " This step is IMPORTANT otherwise the smartconfig will not work!"
43+
echo " This step is IMPORTANT otherwise the smartconfig may not work!"
4244
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"
45+
echo " Make sure nothing else is plugged into your IoT device while attempting to flash."
4346
echo "3. Press ${bold}ENTER${normal} to continue"
44-
read x
45-
echo ""
47+
read -r
48+
echo
4649
echo "======================================================"
4750

4851
echo "Starting smart config pairing procedure"
@@ -71,45 +74,40 @@ pkill -f smartconfig/main.py && echo "Stopping smart config"
7174

7275
echo "Fetching firmware backup"
7376
sleep 2
74-
timestamp=`date +%Y%m%d_%H%M%S`
75-
mkdir -p "../backups/$timestamp"
76-
pushd "../backups/$timestamp" >/dev/null
77+
timestamp=$(date +%Y%m%d_%H%M%S)
78+
backupfolder="../backups/$timestamp"
79+
mkdir -p "$backupfolder"
80+
pushd "$backupfolder" >/dev/null || exit
7781
curl -JO http://10.42.42.42/backup
7882

7983
echo "======================================================"
8084
echo "Getting Info from IoT-device"
8185
curl -s http://10.42.42.42 | tee device-info.txt
82-
popd >/dev/null
86+
popd >/dev/null || exit
8387

8488
echo "======================================================"
85-
echo "Please make sure to note the correct SPI flash mode!"
86-
echo "Installing an alternative firmware with the wrong flash mode will leave the ESP unable to boot!"
87-
echo
88-
echo "Next steps:"
89-
echo "1. To go back to the orginal software"
90-
echo " # curl http://10.42.42.42/undo"
89+
echo "Ready to flash third party firmware!"
9190
echo
92-
echo "2. Be sure the conversion software runs in user2"
93-
echo " # curl http://10.42.42.42/flash2"
91+
echo "For your convenience, the following firmware images are already included in this repository:"
92+
echo " Tasmota v7.0.0.3 (wifiman)"
93+
echo " ESPurna 1.13.5 (base)"
9494
echo
95-
echo "3. Flash a third party firmware to the device"
96-
echo "BE SURE THE FIRMWARE FITS THE DEVICE AND USES THE CORRECT FLASH MODE!"
95+
echo "You can also provide your own image by placing it in the /files directory"
96+
echo "Please ensure the firmware fits the device and includes the bootloader"
9797
echo "MAXIMUM SIZE IS 512KB"
98-
echo "put or link it to ./files/thirdparty.bin"
99-
echo "A build of Tasmota v7.0.0.3 is already included in this repository."
100-
echo " # curl http://10.42.42.42/flash3"
101-
echo "If you want to flash the included ESPurna 1.13.5 image use this command:"
102-
echo " # curl http://10.42.42.42/flash3?url=http://10.42.42.1/files/espurna-base.bin"
103-
echo "Alternatively let the device download and flash a file via HTTP:"
104-
echo " # curl http://10.42.42.42/flash3?url=http://10.42.42.1/files/thirdparty.bin"
105-
echo
106-
echo "HAVE FUN!"
98+
99+
./firmware_picker.sh
100+
107101
echo "======================================================"
108102
read -p "Do you want to flash another device? [y/N] " -n 1 -r
109103
echo
104+
105+
sudo mv *.log "$backupfolder/"
110106
done
111107

112108
echo "Exiting..."
113109

114-
popd >/dev/null
110+
popd >/dev/null || exit
111+
112+
./stop_flash.sh >/dev/null
115113

0 commit comments

Comments
 (0)