Initial Commit

pull/8/head
Merlin Schumacher 2019-01-16 20:32:16 +01:00
commit f443485a6a
No known key found for this signature in database
GPG Key ID: 3738FEE8C06C57DA
20 changed files with 750 additions and 0 deletions

107
.gitignore vendored Normal file
View File

@ -0,0 +1,107 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
# Node
node_modules/

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 VTRUST
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

56
README.md Normal file
View File

@ -0,0 +1,56 @@
# smarthome-smarthack
Here you can find scripts to upgrade certain IoT-devices using esp8266 to any firmware without soldering etc.
The devices and methods were analyzed and described at the 35c3 conference in Germany.
The talk "Smart home - Smart hack" from Michael Steigerwald can be viewed here:
https://media.ccc.de/v/35c3-9723-smart_home_-_smart_hack
!!! USE THIS SCRIPTS AT YOUR OWN RISK !!!
## REQUIREMENTS
These scripts were tested in
* Kali-Linux 2018.4 in VMWARE
* a Raspberry Pi 3B+ + USB-WIFI with this image from [here](https://www.offensive-security.com/kali-linux-arm-images/)
https://images.offensive-security.com/arm-images/kali-linux-2018.4a-rpi3-nexmon-64.img.xz
Any Linux with a Wifi which can act as an Access Point should work.
We did not succeed to use the built-in Wifi of a Raspberry Pi 3B+ or 3B
## PROCEDURE
### INSTALLATION
# git clone https://github.com/vtrust-de/smarthome-smarthack
# cd smarthome-smarthack
# ./install_prereq.sh
### FLASH loader firmware + backup
# ./start_flash.sh
Follow the instructions and our FLASH loader will be installed in the esp8266.
After it will connect with a static IP to the WIFI
WIFI: vtrust-flash
PASS: flashmeifyoucan
IP: 10.42.42.42
A backup of the original firmware will be created and stored locally
### Device information
During loading some information about your device will been shown.
You can see them in a browser or by using following command:
# curl http://10.42.42.42
### BACKUP only and UNDO
You can use the FLASH loader to create a backup only.
If you want to delete the FLASH loader out of the flash again and go back to the stock software just do following:
# curl http://10.42.42.42/undo
### FLASH loader to user2
The FLASH loader only allows flashing the thirdparty firmware, if the loader is running in the userspace user2 starting from 0x81000.
This will flash the FLASH loader in user2 if it is not already there.
It will destroy your ability to undo and go back to the original firmware
# curl http://10.42.42.42/flash2
### FLASH third-party firmware
BE SURE THE FIRMWARE FITS YOUR DEVICE!
Place or link your binary file to ./files/thirdparty.bin
# curl http://10.42.42.42/flash3
## EXAMPLE
Here you can see a recording of the full process:
https://asciinema.org/a/2aDZweVGfliwc9TjB1ncwmKvm

10
config.txt Normal file
View File

@ -0,0 +1,10 @@
# Please input the good wlan device (most of the time it is wlan0 or wlan1)
WLAN=wlan0
# The ETH device should be connected to the internet but it should also work if it is a local network only
ETH=eth0
# Here you could change the WIFI-name and password but most likely most scripts won't work after
# Because the WIFI-credentials are hardcoded in the esp8266-ota-flash-convert
AP=vtrust-flash
PASS=flashmeifyoucan

1
files/1.bin Symbolic link
View File

@ -0,0 +1 @@
esp8266-ota-flash-convert_upg.bin

Binary file not shown.

Binary file not shown.

Binary file not shown.

1
files/thirdparty.bin Symbolic link
View File

@ -0,0 +1 @@
Put_a_Link_To_a_File_You_Like_to_upload(with_OTA)_here

1
files/user2.bin Symbolic link
View File

@ -0,0 +1 @@
esp8266-ota-flash-convert.ino-0x81000.bin

15
install_prereq.sh Executable file
View File

@ -0,0 +1,15 @@
#!/bin/bash
curl -sL https://deb.nodesource.com/setup_11.x | sudo -E bash -
sudo apt-get update
sudo apt-get upgrade -y
sudo apt-get install -y dnsmasq hostapd screen curl python-pip python-setuptools python-wheel mosquitto nodejs
sudo pip install paho-mqtt pyaes tornado http
pushd scripts/smartconfig
npm i
popd
echo "Ready to start upgrade"

44
scripts/backup.py Executable file
View File

@ -0,0 +1,44 @@
#!/usr/bin/python2
# encoding: utf-8
"""
backup.py
Created by nano on 2019-01-10.
Copyright (c) 2019 VTRUST GmbH. All rights reserved.
"""
import sys
import os
import http.client
from binascii import unhexlify
import datetime
DEVICE_IP = "10.42.42.42"
def main():
print("Create backup of entire FLASH from %s" % DEVICE_IP )
data = b""
conn = http.client.HTTPConnection(DEVICE_IP)
conn.request("GET","/flashsize")
flashsize = int(conn.getresponse().read())
print("Connected... Flashsize=",flashsize)
for address in range(0,flashsize,1024):
conn.request("GET", "/get?read=%X" % address )
r1 = conn.getresponse()
# print(r1.status, r1.reason)
block = r1.read().split(b'\n')
print(block[0])
data += block[1]
conn.close()
bindata = unhexlify(data)
f= open(datetime.datetime.now().strftime("../%Y-%m-%d_%H-%M-%S_readout.bin"),"wb")
f.write(bindata)
f.close
pass
if __name__ == '__main__':
main()

View File

@ -0,0 +1,132 @@
#!/usr/bin/env python2
# encoding: utf-8
"""
mq_pub_15.py
Created by nano on 2018-11-22.
Copyright (c) 2018 VTRUST. All rights reserved.
"""
import tornado.web
import os
import hashlib
def file_as_bytes(file):
with file:
return file.read()
from tornado.options import define, options, parse_command_line
define("port", default=80, help="run on the given port", type=int)
define("debug", default=True, help="run in debug mode")
class FilesHandler(tornado.web.StaticFileHandler):
def parse_url_path(self, url_path):
if not url_path or url_path.endswith('/'):
url_path = url_path + str('index.html')
return url_path
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world")
class JSONHandler(tornado.web.RequestHandler):
def get(self):
print('\n')
print('URI:'+str(self.request.uri))
self.write('Hello Human, Do you have IOT?')
def post(self):
print('\n')
uri = str(self.request.uri)
a = str(self.get_argument('a'))
print('URI:'+uri)
if(a == "s.gw.token.get"):
print("Answer s.gw.token.get")
answer =(b'{"result":{"gwApiUrl":"http://10.42.42.1/gw.json","stdTimeZone":"-05:00","mqttRanges":"","timeZone":"-05:00",'
b'"httpsPSKUrl":"https://10.42.42.1/gw.json","mediaMqttUrl":"10.42.42.1","gwMqttUrl":"10.42.42.1","dstIntervals":[]},"t":7,"e":false,"success":true}\n'
)
self.set_header("Content-Type", "application/json;charset=UTF-8")
self.set_header('Content-Length', str(len(answer)))
self.set_header('Content-Language', 'zh-CN')
self.write(answer)
#os.system("killall smartconfig.js")
elif(".active" in a):
print("Answer s.gw.dev.pk.active")
index = uri.find("gwId=")+5
gwId = uri[index:index+20]
print("READ GW ID",gwId)
answer =(b'{"result":'
b'{'
b'"schema":"[{'
b'\\"mode\\":\\"rw\\",'
b'\\"property\\":{\\"type\\":\\"bool\\"},\\"id\\":1,\\"type\\":\\"obj\\"},'
b'{\\"mode\\":\\"rw\\",'
b'\\"property\\":{\\"min\\":0,\\"max\\":86400,\\"scale\\":0,\\"step\\":1,\\"type\\":\\"value\\"},'
b'\\"id\\":9,\\"type\\":\\"obj\\"'
b'}]",'
b'"uid":"00000000000000000000","devEtag":"0000000000","secKey":"0000000000000000","schemaId":"0000000000","localKey":"0000000000000000"'
b'},'
b'"t":7,"e":false,"success":true}\n'
)
self.set_header("Content-Type", "application/json;charset=UTF-8")
self.set_header('Content-Length', str(len(answer)))
self.set_header('Content-Language', 'zh-CN')
self.write(answer)
print("TRIGGER UPGRADE IN 10 SECONDS")
os.system("./trigger_upgrade.sh %s &" % str(gwId))
elif(".upgrade" in a):
print("Answer s.gw.upgrade")
#Fixme
#Calculate MD5 and Filesize
file_md5 = hashlib.md5(file_as_bytes(open('../files/1.bin', 'rb'))).hexdigest()
file_len = os.path.getsize('../files/1.bin')
answer = b'{"result":{"auto":3,"fileSize":"%d","etag":"0000000000","version":"9.0.0","url":"http://10.42.42.1/files/1.bin","md5":"%s"},"t":100,"e":false,"success":true}' % (file_len,file_md5.encode('utf-8'))
print(answer)
self.set_header("Content-Type", "application/json;charset=UTF-8")
self.set_header('Content-Length', str(len(answer)))
self.set_header('Content-Language', 'zh-CN')
self.write(answer)
elif(".debug.log" in a):
print("Answer atop.online.debug.log")
answer =b'{"result":true,"t":7,"e":false,"success":true}'
self.set_header("Content-Type", "application/json;charset=UTF-8")
self.set_header('Content-Length', str(len(answer)))
self.set_header('Content-Language', 'zh-CN')
self.write(answer)
elif(".update" in a):
print("Answer s.gw.update")
answer =b'{"t":7,"e":false,"success":true}'
self.set_header("Content-Type", "application/json;charset=UTF-8")
self.set_header('Content-Length', str(len(answer)))
self.set_header('Content-Language', 'zh-CN')
self.write(answer)
else:
print("WARN: unknown request: {} ({})".format(a,uri))
self.write("WARN: unknown request: "+uri)
def main():
parse_command_line()
app = tornado.web.Application(
[
(r"/", MainHandler),
(r"/gw.json", JSONHandler),
('/files/(.*)', FilesHandler, {'path': str('../files/')}),
],
#template_path=os.path.join(os.path.dirname(__file__), "templates"),
#static_path=os.path.join(os.path.dirname(__file__), "static"),
debug=options.debug,
)
app.listen(options.port)
print("Listening on port "+str(options.port))
tornado.ioloop.IOLoop.current().start()
if __name__ == "__main__":
main()

110
scripts/mq_pub_15.py Executable file
View File

@ -0,0 +1,110 @@
#!/usr/bin/env python2
# encoding: utf-8
"""
mq_pub_15.py
Created by nano on 2018-11-22.
Copyright (c) 2018 VTRUST. All rights reserved.
"""
import sys, getopt, time, pyaes, base64
import paho.mqtt.client as mqtt #pip install paho-mqtt
from hashlib import md5
help_message = '''USAGE:
"-i"/"--deviceID"
"-l"/"--localKey" [default=0000000000000000]
"-b"/"--broker" [default=127.0.0.1]
iot:
%s -i 43511212112233445566 -l a1b2c3d4e5f67788''' % (sys.argv[0].split("/")[-1])
class iotAES(object):
def __init__(self, key):
self.bs = 16
self.key = key
def encrypt(self, raw):
_ = self._pad(raw)
cipher = pyaes.blockfeeder.Encrypter(pyaes.AESModeOfOperationECB(self.key))
crypted_text = cipher.feed(raw)
crypted_text += cipher.feed()
crypted_text_b64 = base64.b64encode(crypted_text)
return crypted_text_b64
def decrypt(self, enc):
enc = base64.b64decode(enc)
cipher = pyaes.blockfeeder.Decrypter(pyaes.AESModeOfOperationECB(self.key))
plain_text = cipher.feed(enc)
plain_text += cipher.feed()
return plain_text
def _pad(self, s):
padnum = self.bs - len(s) % self.bs
return s + padnum * chr(padnum).encode()
@staticmethod
def _unpad(s):
return s[:-ord(s[len(s)-1:])]
def iot_dec(message,local_key):
iot_aes = iotAES(local_key)
message_clear = iot_aes.decrypt(message[19:])
print (message_clear)
return message_clear
def iot_enc(message,local_key):
iot_aes = iotAES(local_key)
messge_enc = iot_aes.encrypt(message)
m = md5()
PROTOCOL_VERSION_BYTES = b'2.1'
# preMd5String = b'data=' + messge_enc + b'||lpv=' + PROTOCOL_VERSION_BYTES + b'||' + local_key
preMd5String = b'data=' + messge_enc + b'||pv=' + PROTOCOL_VERSION_BYTES + b'||' + local_key
# print (preMd5String)
m.update(preMd5String)
md5sum = m.hexdigest()
print (md5sum) #ca2b66d33d3f50a1 #ca2b66d33d3f50a1
messge_enc = PROTOCOL_VERSION_BYTES + md5sum[8:][:16].encode('latin1') + messge_enc
print (messge_enc)
return messge_enc
class Usage(Exception):
def __init__(self, msg):
self.msg = msg
def main(argv=None):
broker='127.0.0.1'
localKey = "0000000000000000"
deviceID = ""
if argv is None:
argv = sys.argv
try: #getopt
try:
opts, args = getopt.getopt(argv[1:], "hl:i:vb:", ["help", "localKey=", "deviceID=", "broker="])
except:
raise Usage(help_message)
# option processing
for option, value in opts:
if option == "-v":
verbose = True
if option in ("-h", "--help"):
raise Usage(help_message)
if option in ("-l", "--localKey"):
localKey = value
if option in ("-i", "--deviceID"):
deviceID = value
if option in ("-b", "--broker"):
broker = value
if (len(localKey)<10):
raise Usage(help_message)
if (len(deviceID)<10):
raise Usage(help_message) #
except Usage:
print (sys.argv[0].split("/")[-1] + ": ")
print ("\t for help use --help")
print (help_message)
return 2
message = '{"data":{"gwId":"%s"},"protocol":15,"s":%d,"t":%d}' %(deviceID,1523715,time.time())
m1 = iot_enc(message,localKey)
client = mqtt.Client("P1")
client.connect(broker)
client.publish("smart/device/in/%s" % (deviceID),m1)
client.disconnect()
if __name__ == "__main__":
sys.exit(main())

94
scripts/setup_ap.sh Executable file
View File

@ -0,0 +1,94 @@
#!/bin/sh
# Source config
. ../config.txt
echo "Backing up NetworkManager.cfg..."
sudo cp /etc/NetworkManager/NetworkManager.conf /etc/NetworkManager/NetworkManager.conf.backup
cat <<- EOF > /etc/NetworkManager/NetworkManager.conf
[main]
plugins=keyfile
[keyfile]
unmanaged-devices=interface-name:$WLAN
EOF
echo "Restarting NetworkManager..."
sudo service network-manager restart
sudo ifconfig $WLAN up
echo "Backing up /etc/dnsmasq.conf..."
sudo cp /etc/dnsmasq.conf /etc/dnsmasq.conf.backup
echo "Writing dnsmasq config file..."
echo "Creating new /etc/dnsmasq.conf..."
cat <<- EOF >/etc/dnsmasq.conf
# disables dnsmasq reading any other files like /etc/resolv.conf for nameservers
no-resolv
# Interface to bind to
interface=$WLAN
#Specify starting_range,end_range,lease_time
dhcp-range=10.42.42.10,10.42.42.40,12h
# dns addresses to send to the clients
server=9.9.9.9
server=1.1.1.1
address=/tuya.com/10.42.42.1
address=/tuyaeu.com/10.42.42.1
address=/tuyaus.com/10.42.42.1
address=/tuyacn.com/10.42.42.1
EOF
echo "Writing hostapd config file..."
cat <<- EOF >/etc/hostapd/hostapd.conf
interface=$WLAN
driver=nl80211
ssid=$AP
hw_mode=g
channel=1
macaddr_acl=0
auth_algs=1
ignore_broadcast_ssid=0
wpa=2
wpa_passphrase=$PASS
wpa_key_mgmt=WPA-PSK
wpa_pairwise=TKIP
rsn_pairwise=CCMP
EOF
echo "Configuring AP interface..."
sudo ifconfig $WLAN up 10.42.42.1 netmask 255.255.255.0
echo "Applying iptables rules..."
sudo iptables --flush
sudo iptables --table nat --flush
sudo iptables --delete-chain
sudo iptables --table nat --delete-chain
sudo iptables --table nat --append POSTROUTING --out-interface $ETH -j MASQUERADE
sudo iptables --append FORWARD --in-interface $WLAN -j ACCEPT
echo "Starting DNSMASQ server..."
sudo /etc/init.d/dnsmasq stop > /dev/null 2>&1
sudo pkill dnsmasq
sudo dnsmasq
sudo sysctl -w net.ipv4.ip_forward=1 > /dev/null 2>&1
sudo ip route add 255.255.255.255 dev $WLAN
echo "Starting AP on $WLAN in screen terminal..."
sudo hostapd /etc/hostapd/hostapd.conf
sudo rm /etc/NetworkManager/NetworkManager.conf > /dev/null 2>&1
sudo mv /etc/NetworkManager/NetworkManager.conf.backup /etc/NetworkManager/NetworkManager.conf
sudo service network-manager restart
sudo /etc/init.d/dnsmasq stop > /dev/null 2>&1
sudo pkill dnsmasq
sudo rm /etc/dnsmasq.conf > /dev/null 2>&1
sudo mv /etc/dnsmasq.conf.backup /etc/dnsmasq.conf > /dev/null 2>&1
sudo rm /etc/dnsmasq.hosts > /dev/null 2>&1
sudo iptables --flush
sudo iptables --flush -t nat
sudo iptables --delete-chain
sudo iptables --table nat --delete-chain

View File

@ -0,0 +1,25 @@
{
"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

@ -0,0 +1,34 @@
#!/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.');

5
scripts/trigger_upgrade.sh Executable file
View File

@ -0,0 +1,5 @@
#!/bin/sh
echo "Trigger upgrade in 10 seconds"
sleep 10
python mq_pub_15.py -i $1

87
start_flash.sh Executable file
View File

@ -0,0 +1,87 @@
#!/bin/bash
. ./config.txt
./stop_flash.sh >/dev/null
pushd scripts
echo "======================================================"
echo "SMARTHOME-SMARTHACK"
echo "https://github.com/vtrust-de/smarthome-smarthack"
echo "USE AT YOUR OWN RISK!!!"
echo "======================================================"
echo " Starting AP in a screen"
sudo screen -S smarthack-wifi -m -d ./setup_ap.sh
echo " Stopping any Webserver"
sudo service apache2 stop >/dev/null 2>&1
echo " Starting Websever in a screen"
sudo screen -S smarthack-web -m -d ./fake-registration-server.py
service mosquitto stop >/dev/null 2>&1
echo " Starting Mosquitto in a screen"
sudo screen -S smarthack-mqtt -m -d mosquitto -v
echo
echo "======================================================"
echo
echo
echo "USE AT YOUR OWN RISK!!!"
echo "IMPORTANT"
echo "1. Connect any another device (a smartphone or something) to the WIFI $AP"
echo " The wpa-password is $PASS"
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)"
echo "3. Press ENTER to continue"
read x
echo
echo
echo "======================================================"
echo "Starting pairing procedure in screen"
sudo ip route add 255.255.255.255 dev $WLAN
sudo screen -S smarthack-smartconfig -m -d ./smartconfig/smartconfig.js
echo "Waiting for the upgraded device to appear"
while ! timeout 0.2 ping -c 1 -n 10.42.42.42 &> /dev/null
do
printf "."
sleep 1
done
echo
echo "IoT-device is online with ip 10.42.42.42"
echo "Fetching firmware backup"
sleep 2
./backup.py
popd
echo "======================================================"
echo "Getting Info from IoT-device"
curl http://10.42.42.42 2> /dev/null | tee device-info.txt
echo
echo "======================================================"
echo
echo
echo
echo "Next steps:"
echo "1. To go back to the orginal software"
echo " # curl http://10.42.42.42/undo"
echo
echo "2. Be sure the conversion software runs in user2"
echo " # curl http://10.42.42.42/flash2"
echo
echo "3. Flash a third party firmware to the device"
echo "BE SURE THE FIRMWARE FITS TO THE DEVICE"
echo "MAXIMUM SIZE IS 512KB"
echo "put or link it to ./files/thirdparty.bin"
echo " # curl http://10.42.42.42/flash3"
echo
echo
echo "HAVE FUN!"

7
stop_flash.sh Executable file
View File

@ -0,0 +1,7 @@
#!/bin/bash
echo "Stopping AP in a screen"
sudo screen -S smarthack-wifi -X stuff '^C'
sudo screen -S smarthack-web -X stuff '^C'
sudo screen -S smarthack-smartconfig -X stuff '^C'
sudo screen -S smarthack-mqtt -X stuff '^C'