Remove wifi setup
|
@ -2,8 +2,6 @@ recursive-include mycroft/client/speech/recognizer/model *
|
|||
include requirements.txt
|
||||
include mycroft/configuration/*.conf
|
||||
#include mycroft/tts/mycroft_voice_4.0.flitevox
|
||||
recursive-include mycroft/client/wifisetup/web/* *
|
||||
include mycroft/client/wifisetup/web/index.html
|
||||
recursive-include mycroft/res *
|
||||
recursive-include mycroft/res/snd *
|
||||
recursive-include mycroft/res/text/* *
|
||||
|
|
|
@ -14,7 +14,7 @@ place_manifest('mycroft-base-MANIFEST.in')
|
|||
setup(
|
||||
name="mycroft-core",
|
||||
version=get_version(),
|
||||
install_requires=[required('requirements.txt'), 'wifi'],
|
||||
install_requires=[required('requirements.txt')],
|
||||
packages=find_all_packages("mycroft"),
|
||||
include_package_data=True,
|
||||
|
||||
|
@ -27,7 +27,6 @@ setup(
|
|||
'mycroft-echo-observer=mycroft.messagebus.client.ws:echo',
|
||||
'mycroft-audio-test=mycroft.util.audio_test:main',
|
||||
'mycroft-enclosure-client=mycroft.client.enclosure.main:main',
|
||||
'mycroft-wifi-setup-client=mycroft.client.wifisetup.main:main',
|
||||
'mycroft-skill-container=mycroft.skills.container:main',
|
||||
'mycroft-cli-client=mycroft.client.text.main:main'
|
||||
]
|
||||
|
|
|
@ -1,595 +0,0 @@
|
|||
# Copyright 2016 Mycroft AI, Inc.
|
||||
#
|
||||
# This file is part of Mycroft Core.
|
||||
#
|
||||
# Mycroft Core is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Mycroft Core is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Mycroft Core. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
This module implements a mechanism that allows the wifi connection of
|
||||
a Linux system to be selected by end users. This is achieved by:
|
||||
* creating a websocket for communication between the pieces of this
|
||||
mechanism
|
||||
* temporarilly creating a virtual access point
|
||||
* directing the end user to connect to that access point with another device
|
||||
(phone or tablet or laptop)
|
||||
* having them open a captive portal in that device's web browser
|
||||
* selecting the desired wifi within that browser
|
||||
* configuring this device based on that selection
|
||||
"""
|
||||
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import traceback
|
||||
from SimpleHTTPServer import SimpleHTTPRequestHandler
|
||||
from SocketServer import TCPServer
|
||||
from shutil import copyfile
|
||||
from subprocess import Popen, PIPE, call
|
||||
from threading import Thread
|
||||
from time import sleep
|
||||
|
||||
import os
|
||||
from os.path import dirname, realpath
|
||||
from pyric import pyw
|
||||
from wifi import Cell
|
||||
|
||||
from mycroft.client.enclosure.api import EnclosureAPI
|
||||
from mycroft.messagebus.client.ws import WebsocketClient
|
||||
from mycroft.messagebus.message import Message
|
||||
from mycroft.util import wait_while_speaking, is_speaking, \
|
||||
stop_speaking
|
||||
from mycroft.util.log import LOG
|
||||
|
||||
__author__ = 'aatchison and penrods'
|
||||
|
||||
|
||||
SCRIPT_DIR = dirname(realpath(__file__))
|
||||
|
||||
WPA_SUPPLICANT = '''#mycroft_p2p_start
|
||||
ctrl_interface=/var/run/wpa_supplicant
|
||||
driver_param=p2p_device=1
|
||||
update_config=1
|
||||
device_name=mycroft-holmes-i
|
||||
device_type=1-0050F204-1
|
||||
p2p_go_intent=10
|
||||
p2p_go_ht40=1
|
||||
|
||||
network={
|
||||
ssid="MYCROFT"
|
||||
psk="12345678"
|
||||
proto=RSN
|
||||
key_mgmt=WPA-PSK
|
||||
pairwise=CCMP
|
||||
auth_alg=OPEN
|
||||
mode=3
|
||||
disabled=2
|
||||
}
|
||||
|
||||
network={
|
||||
ssid="MYCROFT"
|
||||
psk="12345678"
|
||||
proto=RSN
|
||||
key_mgmt=WPA-PSK
|
||||
pairwise=CCMP
|
||||
auth_alg=OPEN
|
||||
mode=3
|
||||
disabled=2
|
||||
}
|
||||
#mycroft_p2p_end'''
|
||||
|
||||
# This sequence of commands deletes all the skills except for the
|
||||
# pairing skill. This is used for factory reset.
|
||||
# The rest will be restored upon connection to the internet.
|
||||
RM_SKILLS = """mkdir /opt/mycroft/safety &&
|
||||
mv /opt/mycroft/skills/skill-pairing /opt/mycroft/safety &&
|
||||
rm -rf /opt/mycroft/skills/* &&
|
||||
mv /opt/mycroft/safety/skill-pairing /opt/mycroft/skills &&
|
||||
rm -rf /opt/mycroft/safety"""
|
||||
|
||||
|
||||
def cli_no_output(*args):
|
||||
''' Invoke a command line and return result '''
|
||||
LOG.info("Command: %s" % list(args))
|
||||
proc = Popen(args=args, stdout=PIPE, stderr=PIPE)
|
||||
stdout, stderr = proc.communicate()
|
||||
return {'code': proc.returncode, 'stdout': stdout, 'stderr': stderr}
|
||||
|
||||
|
||||
def cli(*args):
|
||||
''' Invoke a command line, then log and return result '''
|
||||
LOG.info("Command: %s" % list(args))
|
||||
proc = Popen(args=args, stdout=PIPE, stderr=PIPE)
|
||||
stdout, stderr = proc.communicate()
|
||||
result = {'code': proc.returncode, 'stdout': stdout, 'stderr': stderr}
|
||||
LOG.info("Command result: %s" % result)
|
||||
return result
|
||||
|
||||
|
||||
def wpa(*args):
|
||||
idx = 0
|
||||
result = cli('wpa_cli', '-i', *args)
|
||||
out = result.get("stdout", "\n")
|
||||
if "interface" in out:
|
||||
idx = 1
|
||||
return str(out.split("\n")[idx])
|
||||
|
||||
|
||||
def sysctrl(*args):
|
||||
return cli('systemctl', *args)
|
||||
|
||||
|
||||
class CaptiveHTTPRequestHandler(SimpleHTTPRequestHandler):
|
||||
''' Serve a single website, 303 redirecting all other requests to it '''
|
||||
|
||||
def do_HEAD(self):
|
||||
LOG.info("do_HEAD being called....")
|
||||
if not self.redirect():
|
||||
SimpleHTTPRequestHandler.do_HEAD(self)
|
||||
|
||||
def do_GET(self):
|
||||
LOG.info("do_GET being called....")
|
||||
if not self.redirect():
|
||||
SimpleHTTPRequestHandler.do_GET(self)
|
||||
|
||||
def redirect(self):
|
||||
try:
|
||||
LOG.info("***********************")
|
||||
LOG.info("** HTTP Request ***")
|
||||
LOG.info("***********************")
|
||||
LOG.info("Requesting: " + self.path)
|
||||
LOG.info("REMOTE_ADDR:" + self.client_address[0])
|
||||
LOG.info("SERVER_NAME:" + self.server.server_address[0])
|
||||
LOG.info("SERVER_PORT:" + str(self.server.server_address[1]))
|
||||
LOG.info("SERVER_PROTOCOL:" + self.request_version)
|
||||
LOG.info("HEADERS...")
|
||||
LOG.info(self.headers)
|
||||
LOG.info("***********************")
|
||||
|
||||
# path = self.translate_path(self.path)
|
||||
if "mycroft.ai" in self.headers['host']:
|
||||
LOG.info("No redirect")
|
||||
return False
|
||||
else:
|
||||
LOG.info("303 redirect to http://start.mycroft.ai")
|
||||
self.send_response(303)
|
||||
self.send_header("Location", "http://start.mycroft.ai")
|
||||
self.end_headers()
|
||||
return True
|
||||
except:
|
||||
tb = traceback.format_exc()
|
||||
LOG.info("exception caught")
|
||||
LOG.info(tb)
|
||||
return False
|
||||
|
||||
|
||||
class WebServer(Thread):
|
||||
''' Web server for devices connected to the temporary access point '''
|
||||
|
||||
def __init__(self, host, port):
|
||||
super(WebServer, self).__init__()
|
||||
self.daemon = True
|
||||
LOG.info("Creating TCPServer...")
|
||||
self.server = TCPServer((host, port), CaptiveHTTPRequestHandler)
|
||||
LOG.info("Created TCPServer")
|
||||
|
||||
def run(self):
|
||||
LOG.info("Starting Web Server at %s:%s" % self.server.server_address)
|
||||
LOG.info("Serving from: %s" % os.path.join(SCRIPT_DIR, 'web'))
|
||||
os.chdir(os.path.join(SCRIPT_DIR, 'web'))
|
||||
self.server.serve_forever()
|
||||
LOG.info("Web Server stopped!")
|
||||
|
||||
|
||||
class AccessPoint:
|
||||
template = """interface={interface}
|
||||
bind-interfaces
|
||||
server={server}
|
||||
domain-needed
|
||||
bogus-priv
|
||||
dhcp-range={dhcp_range_start}, {dhcp_range_end}, 12h
|
||||
address=/#/{server}
|
||||
"""
|
||||
|
||||
def __init__(self, wiface):
|
||||
self.wiface = wiface
|
||||
self.iface = 'p2p-wlan0-0'
|
||||
self.subnet = '172.24.1'
|
||||
self.ip = self.subnet + '.1'
|
||||
self.ip_start = self.subnet + '.50'
|
||||
self.ip_end = self.subnet + '.150'
|
||||
self.password = None
|
||||
|
||||
def up(self):
|
||||
try:
|
||||
card = pyw.getcard(self.iface)
|
||||
except:
|
||||
wpa(self.wiface, 'p2p_group_add', 'persistent=0')
|
||||
self.iface = self.get_iface()
|
||||
self.password = wpa(self.iface, 'p2p_get_passphrase')
|
||||
card = pyw.getcard(self.iface)
|
||||
pyw.inetset(card, self.ip)
|
||||
copyfile('/etc/dnsmasq.conf', '/tmp/dnsmasq-bk.conf')
|
||||
self.save()
|
||||
sysctrl('restart', 'dnsmasq.service')
|
||||
|
||||
def get_iface(self):
|
||||
for iface in pyw.winterfaces():
|
||||
if "p2p" in iface:
|
||||
return iface
|
||||
|
||||
def down(self):
|
||||
sysctrl('stop', 'dnsmasq.service')
|
||||
sysctrl('disable', 'dnsmasq.service')
|
||||
wpa(self.wiface, 'p2p_group_remove', self.iface)
|
||||
copyfile('/tmp/dnsmasq-bk.conf', '/etc/dnsmasq.conf')
|
||||
|
||||
def save(self):
|
||||
data = {
|
||||
"interface": self.iface,
|
||||
"server": self.ip,
|
||||
"dhcp_range_start": self.ip_start,
|
||||
"dhcp_range_end": self.ip_end
|
||||
}
|
||||
try:
|
||||
LOG.info("Writing to: /etc/dnsmasq.conf")
|
||||
with open('/etc/dnsmasq.conf', 'w') as f:
|
||||
f.write(self.template.format(**data))
|
||||
except Exception as e:
|
||||
LOG.error("Fail to write: /etc/dnsmasq.conf")
|
||||
raise e
|
||||
|
||||
|
||||
class WiFi:
|
||||
def __init__(self):
|
||||
self.iface = pyw.winterfaces()[0]
|
||||
self.ap = AccessPoint(self.iface)
|
||||
self.server = None
|
||||
self.ws = WebsocketClient()
|
||||
self.enclosure = EnclosureAPI(self.ws)
|
||||
self.init_events()
|
||||
self.conn_monitor = None
|
||||
self.conn_monitor_stop = threading.Event()
|
||||
self.starting = False
|
||||
|
||||
def init_events(self):
|
||||
'''
|
||||
Register handlers for various websocket events used
|
||||
to communicate with outside systems.
|
||||
'''
|
||||
|
||||
# This event is generated by an outside mechanism. On a
|
||||
# Mark 1 unit this comes from the Enclosure's WIFI menu
|
||||
# item being selected.
|
||||
self.ws.on('mycroft.wifi.start', self.start)
|
||||
|
||||
# Similar to the above. Resets to factory defaults
|
||||
self.ws.on('mycroft.wifi.reset', self.reset)
|
||||
|
||||
# Similar to the above. Enable/disable SSH
|
||||
self.ws.on('mycroft.enable.ssh', self.ssh_enable)
|
||||
self.ws.on('mycroft.disable.ssh', self.ssh_disable)
|
||||
|
||||
# These events are generated by Javascript in the captive
|
||||
# portal.
|
||||
self.ws.on('mycroft.wifi.stop', self.stop)
|
||||
self.ws.on('mycroft.wifi.scan', self.scan)
|
||||
self.ws.on('mycroft.wifi.connect', self.connect)
|
||||
|
||||
def start(self, event=None):
|
||||
'''
|
||||
Fire up the MYCROFT access point for the user to connect to
|
||||
with a phone or computer.
|
||||
'''
|
||||
if self.starting:
|
||||
return
|
||||
|
||||
self.starting = True
|
||||
LOG.info("Starting access point...")
|
||||
|
||||
self.intro_msg = ""
|
||||
if event and event.data.get("msg"):
|
||||
self.intro_msg = event.data.get("msg")
|
||||
self.allow_timeout = True
|
||||
if event and event.data.get("allow_timeout") is False:
|
||||
self.allow_timeout = event.data.get("allow_timeout")
|
||||
|
||||
# Fire up our access point
|
||||
self.ap.up()
|
||||
if not self.server:
|
||||
LOG.info("Creating web server...")
|
||||
self.server = WebServer(self.ap.ip, 80)
|
||||
LOG.info("Starting web server...")
|
||||
self.server.start()
|
||||
LOG.info("Created web server.")
|
||||
|
||||
LOG.info("Access point started!\n%s" % self.ap.__dict__)
|
||||
self._start_connection_monitor()
|
||||
|
||||
def _connection_prompt(self, intro):
|
||||
while self.ap.password is None or self.ap.password == "":
|
||||
sleep(1) # give it time to load
|
||||
|
||||
# Speak the connection instructions and show the password on screen
|
||||
passwordSpelled = ", ".join(self.ap.password)
|
||||
self._speak_and_show(intro +
|
||||
" Use your mobile device or computer to connect "
|
||||
"to the wifi network 'MYCROFT'. Then enter the "
|
||||
"password " + passwordSpelled,
|
||||
self.ap.password)
|
||||
|
||||
def _speak_and_show(self, speak, show):
|
||||
''' Communicate with the user throughout the process '''
|
||||
self.ws.emit(Message("speak", {'utterance': speak}))
|
||||
if show is None:
|
||||
return
|
||||
|
||||
wait_while_speaking()
|
||||
self.enclosure.mouth_text(show)
|
||||
|
||||
def _start_connection_monitor(self):
|
||||
LOG.info("Starting monitor thread...\n")
|
||||
if self.conn_monitor is not None:
|
||||
LOG.info("Killing old thread...\n")
|
||||
self.conn_monitor_stop.set()
|
||||
self.conn_monitor_stop.wait()
|
||||
|
||||
self.conn_monitor = threading.Thread(
|
||||
target=self._do_connection_monitor,
|
||||
args={})
|
||||
self.conn_monitor.daemon = True
|
||||
self.conn_monitor.start()
|
||||
LOG.info("Monitor thread setup complete.\n")
|
||||
|
||||
def _stop_connection_monitor(self):
|
||||
''' Set flag that will let monitoring thread close '''
|
||||
self.conn_monitor_stop.set()
|
||||
|
||||
def _do_connection_monitor(self):
|
||||
LOG.info("Invoked monitor thread...\n")
|
||||
mtimeLast = os.path.getmtime('/var/lib/misc/dnsmasq.leases')
|
||||
bHasConnected = False
|
||||
cARPFailures = 0
|
||||
timeStarted = time.time()
|
||||
timeLastAnnounced = timeStarted - 45 # first reminder in 90 secs
|
||||
self.conn_monitor_stop.clear()
|
||||
|
||||
while not self.conn_monitor_stop.isSet():
|
||||
# do our monitoring...
|
||||
mtime = os.path.getmtime('/var/lib/misc/dnsmasq.leases')
|
||||
if mtimeLast != mtime:
|
||||
# Something changed in the dnsmasq lease file -
|
||||
# presumably a (re)new lease
|
||||
bHasConnected = True
|
||||
cARPFailures = 0
|
||||
mtimeLast = mtime
|
||||
timeStarted = time.time() # reset start time after connection
|
||||
timeLastAnnounced = time.time() - 45 # announce how to connect
|
||||
|
||||
if time.time() - timeStarted > 60 * 5 and self.allow_timeout:
|
||||
# After 5 minutes, shut down the access point (unless the
|
||||
# system has never been setup, in which case we stay up
|
||||
# indefinitely)
|
||||
LOG.info("Auto-shutdown of access point after 5 minutes")
|
||||
self.stop()
|
||||
continue
|
||||
|
||||
if time.time() - timeLastAnnounced >= 45:
|
||||
if bHasConnected:
|
||||
self._speak_and_show(
|
||||
"Follow the prompt on your mobile device or computer "
|
||||
"and choose a wifi network. If you don't get a "
|
||||
"prompt, open your browser and go to start dot "
|
||||
"mycroft dot A I.", "start.mycroft.ai")
|
||||
else:
|
||||
if self.intro_msg:
|
||||
self._connection_prompt(self.intro_msg)
|
||||
self.intro_msg = None # only speak the intro once
|
||||
else:
|
||||
self._connection_prompt("Allow me to walk you through "
|
||||
"the wifi setup process.")
|
||||
|
||||
timeLastAnnounced = time.time()
|
||||
|
||||
if bHasConnected:
|
||||
# Flush the ARP entries associated with our access point
|
||||
# This will require all network hardware to re-register
|
||||
# with the ARP tables if still present.
|
||||
if cARPFailures == 0:
|
||||
res = cli_no_output('ip', '-s', '-s', 'neigh', 'flush',
|
||||
self.ap.subnet + '.0/24')
|
||||
# Give ARP system time to re-register hardware
|
||||
sleep(5)
|
||||
|
||||
# now look at the hardware that has responded, if no entry
|
||||
# shows up on our access point after 2*5=10 seconds, the user
|
||||
# has disconnected
|
||||
if not self._is_ARP_filled():
|
||||
cARPFailures += 1
|
||||
if cARPFailures > 5:
|
||||
self._connection_prompt("Connection lost.")
|
||||
bHasConnected = False
|
||||
else:
|
||||
cARPFailures = 0
|
||||
sleep(5) # wait a bit to prevent thread from hogging CPU
|
||||
|
||||
LOG.info("Exiting monitor thread...\n")
|
||||
self.conn_monitor_stop.clear()
|
||||
|
||||
def _is_ARP_filled(self):
|
||||
res = cli_no_output('/usr/sbin/arp', '-n')
|
||||
out = str(res.get("stdout"))
|
||||
if out:
|
||||
# Parse output, skipping header
|
||||
for o in out.split("\n")[1:]:
|
||||
if o[0:len(self.ap.subnet)] == self.ap.subnet:
|
||||
if "(incomplete)" in o:
|
||||
# ping the IP to get the ARP table entry reloaded
|
||||
ip_disconnected = o.split(" ")[0]
|
||||
cli_no_output('/bin/ping', '-c', '1', '-W', '3',
|
||||
ip_disconnected)
|
||||
else:
|
||||
return True # something on subnet is connected!
|
||||
return False
|
||||
|
||||
def scan(self, event=None):
|
||||
LOG.info("Scanning wifi connections...")
|
||||
networks = {}
|
||||
status = self.get_status()
|
||||
|
||||
for cell in Cell.all(self.iface):
|
||||
if "x00" in cell.ssid:
|
||||
continue # ignore hidden networks
|
||||
update = True
|
||||
ssid = cell.ssid
|
||||
quality = self.get_quality(cell.quality)
|
||||
|
||||
# If there are duplicate network IDs (e.g. repeaters) only
|
||||
# report the strongest signal
|
||||
if networks.__contains__(ssid):
|
||||
update = networks.get(ssid).get("quality") < quality
|
||||
if update and ssid:
|
||||
networks[ssid] = {
|
||||
'quality': quality,
|
||||
'encrypted': cell.encrypted,
|
||||
'connected': self.is_connected(ssid, status)
|
||||
}
|
||||
self.ws.emit(Message("mycroft.wifi.scanned",
|
||||
{'networks': networks}))
|
||||
LOG.info("Wifi connections scanned!\n%s" % networks)
|
||||
|
||||
@staticmethod
|
||||
def get_quality(quality):
|
||||
values = quality.split("/")
|
||||
return float(values[0]) / float(values[1])
|
||||
|
||||
def connect(self, event=None):
|
||||
if event and event.data:
|
||||
ssid = event.data.get("ssid")
|
||||
connected = self.is_connected(ssid)
|
||||
|
||||
if connected:
|
||||
LOG.warning("Mycroft is already connected to %s" % ssid)
|
||||
else:
|
||||
self.disconnect()
|
||||
LOG.info("Connecting to: %s" % ssid)
|
||||
nid = wpa(self.iface, 'add_network')
|
||||
wpa(self.iface, 'set_network', nid, 'ssid', '"' + ssid + '"')
|
||||
|
||||
if event.data.__contains__("pass"):
|
||||
psk = '"' + event.data.get("pass") + '"'
|
||||
wpa(self.iface, 'set_network', nid, 'psk', psk)
|
||||
else:
|
||||
wpa(self.iface, 'set_network', nid, 'key_mgmt', 'NONE')
|
||||
|
||||
wpa(self.iface, 'enable', nid)
|
||||
connected = self.get_connected(ssid)
|
||||
if connected:
|
||||
wpa(self.iface, 'save_config')
|
||||
|
||||
self.ws.emit(Message("mycroft.wifi.connected",
|
||||
{'connected': connected}))
|
||||
LOG.info("Connection status for %s = %s" % (ssid, connected))
|
||||
|
||||
def disconnect(self):
|
||||
status = self.get_status()
|
||||
nid = status.get("id")
|
||||
if nid:
|
||||
ssid = status.get("ssid")
|
||||
wpa(self.iface, 'disable', nid)
|
||||
LOG.info("Disconnecting %s id: %s" % (ssid, nid))
|
||||
|
||||
def get_status(self):
|
||||
res = cli('wpa_cli', '-i', self.iface, 'status')
|
||||
out = str(res.get("stdout"))
|
||||
if out:
|
||||
return dict(o.split("=") for o in out.split("\n")[:-1])
|
||||
return {}
|
||||
|
||||
def get_connected(self, ssid, retry=5):
|
||||
connected = self.is_connected(ssid)
|
||||
while not connected and retry > 0:
|
||||
sleep(2)
|
||||
retry -= 1
|
||||
connected = self.is_connected(ssid)
|
||||
return connected
|
||||
|
||||
def is_connected(self, ssid, status=None):
|
||||
status = status or self.get_status()
|
||||
state = status.get("wpa_state")
|
||||
return status.get("ssid") == ssid and state == "COMPLETED"
|
||||
|
||||
def stop(self, event=None):
|
||||
LOG.info("Stopping access point...")
|
||||
if is_speaking():
|
||||
stop_speaking() # stop any assistance being spoken
|
||||
self._stop_connection_monitor()
|
||||
self.ap.down()
|
||||
self.enclosure.mouth_reset() # remove "start.mycroft.ai"
|
||||
self.starting = False
|
||||
if self.server:
|
||||
self.server.server.shutdown()
|
||||
self.server.server.server_close()
|
||||
self.server.join()
|
||||
self.server = None
|
||||
LOG.info("Access point stopped!")
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
self.ws.run_forever()
|
||||
except Exception as e:
|
||||
LOG.error("Error: {0}".format(e))
|
||||
self.stop()
|
||||
|
||||
def reset(self, event=None):
|
||||
"""Reset the unit to the factory defaults """
|
||||
LOG.info("Resetting the WPA_SUPPLICANT File")
|
||||
try:
|
||||
call(
|
||||
"echo '" + WPA_SUPPLICANT +
|
||||
"'> /etc/wpa_supplicant/wpa_supplicant.conf",
|
||||
shell=True)
|
||||
# UGLY BUT WORKS
|
||||
call(RM_SKILLS)
|
||||
except Exception as e:
|
||||
LOG.error("Error: {0}".format(e))
|
||||
|
||||
def ssh_enable(self, event=None):
|
||||
LOG.info("Enabling SSH")
|
||||
try:
|
||||
call('systemctl enable ssh.service', shell=True)
|
||||
call('systemctl start ssh.service', shell=True)
|
||||
except Exception as e:
|
||||
LOG.error("Error: {0}".format(e))
|
||||
|
||||
def ssh_disable(self, event=None):
|
||||
LOG.info("Disabling SSH")
|
||||
try:
|
||||
call('systemctl stop ssh.service', shell=True)
|
||||
call('systemctl disable ssh.service', shell=True)
|
||||
except Exception as e:
|
||||
LOG.error("Error: {0}".format(e))
|
||||
|
||||
|
||||
def main():
|
||||
wifi = WiFi()
|
||||
try:
|
||||
wifi.run()
|
||||
except Exception as e:
|
||||
print(e)
|
||||
finally:
|
||||
sys.exit()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -1,391 +0,0 @@
|
|||
* {
|
||||
font-family: sans-serif;
|
||||
user-select: none;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: text;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
|
||||
/* Prevent ridiculously long network names from screwing up the layout
|
||||
by forcing them to wrap. */
|
||||
.ssid {
|
||||
max-width: 280px;
|
||||
}
|
||||
|
||||
/* A bug in Safari (as of 10-10-2016) breaks password input when
|
||||
user-select:none is used. This prevents that bug by allowing
|
||||
text selection for all input fields (which is probably expected
|
||||
by users anyway). */
|
||||
input {
|
||||
user-select: text;
|
||||
-moz-user-select: text;
|
||||
-webkit-user-select: text;
|
||||
-ms-user-select: text;
|
||||
}
|
||||
|
||||
.panel {
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 0 10px #AAA;
|
||||
max-width: 500px;
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
.panel .title {
|
||||
background: #2b3344;
|
||||
border-top-left-radius: 15px;
|
||||
border-top-right-radius: 15px;
|
||||
padding: 30px 20px;
|
||||
text-align: center;
|
||||
color: #FFF;
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.panel .body {
|
||||
min-height: 520px;
|
||||
display: table;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.panel .body li {
|
||||
width: 100%;
|
||||
display: table;
|
||||
border-bottom: 1px solid #DDD;
|
||||
padding: 10px;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
height: 61px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.panel .body li div {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
transition: 500ms;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.panel .body li div.show {
|
||||
transition: 500ms;
|
||||
display: table;
|
||||
}
|
||||
|
||||
.panel .body li span {
|
||||
float: left;
|
||||
font-size: 18px;
|
||||
margin: 9px 0;
|
||||
width: calc(100% - 60px);
|
||||
text-overflow: ellipsis;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.panel .body li img {
|
||||
width: 40px;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.panel .body li img.lock {
|
||||
width: 11px;
|
||||
position: relative;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
top: 24px;
|
||||
left: 40px;
|
||||
}
|
||||
|
||||
.panel .body li .connect-item img, .panel .body li .error-item img {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.panel .body li .connect-item label {
|
||||
color: #488fe2;
|
||||
padding: 9.5px 10px 9.5px 0;
|
||||
display: table;
|
||||
float: left;
|
||||
font-size: 18px;
|
||||
width: 80px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.panel .body li .connect-item label.public {
|
||||
width: calc(100% - 60px);
|
||||
}
|
||||
|
||||
.panel .body li .connect-item input {
|
||||
padding: 5px;
|
||||
height: 40px;
|
||||
border: 1px solid #DDD;
|
||||
width: calc(100% - 145px);
|
||||
font-size: 16px;
|
||||
padding-right: 50px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.password-toggle {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
position: absolute;
|
||||
background: url("../img/eye.png") no-repeat center;
|
||||
border: none;
|
||||
background-size: 30px;
|
||||
outline: none;
|
||||
margin-left: -42px;
|
||||
}
|
||||
|
||||
.panel .body li .error-item span {
|
||||
color: #ff6565;
|
||||
font-weight: bold;
|
||||
padding: 2px;
|
||||
display: table;
|
||||
float: left;
|
||||
font-size: 16px;
|
||||
text-align: left;
|
||||
width: calc(100% - 60px);
|
||||
}
|
||||
|
||||
#success .title {
|
||||
background: #b8e986;
|
||||
color: #2b3344;
|
||||
}
|
||||
|
||||
#success span img {
|
||||
position: relative;
|
||||
top: 4px;
|
||||
}
|
||||
|
||||
#success small {
|
||||
text-align: left;
|
||||
width: 80%;
|
||||
margin: 35px auto;
|
||||
font-size: 18px;
|
||||
display: table;
|
||||
color: #777;
|
||||
}
|
||||
|
||||
div.connected span {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
div.connected span:not(.connected) {
|
||||
color: #488fe2;
|
||||
}
|
||||
|
||||
div.connected .lock {
|
||||
top: 3px !important;
|
||||
}
|
||||
|
||||
span.connected {
|
||||
display: table;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
font-size: 14px !important;
|
||||
color: #777;
|
||||
}
|
||||
|
||||
.backButton {
|
||||
color: #4b90e2;
|
||||
font-weight: bold;
|
||||
display: table;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.backButton img {
|
||||
width: 15px;
|
||||
float: left;
|
||||
margin: 2.5px 0;
|
||||
}
|
||||
|
||||
.backButton span {
|
||||
float: left;
|
||||
padding: 0 3px;
|
||||
display: table;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.message {
|
||||
width: 100%;
|
||||
display: table;
|
||||
text-align: center;
|
||||
margin: 30px 0 40px 0;
|
||||
box-sizing: border-box;
|
||||
padding: 10px 20px;
|
||||
}
|
||||
|
||||
.passForm {
|
||||
display: table;
|
||||
width: 280px;
|
||||
box-sizing: border-box;
|
||||
margin: 0 auto 50px auto;
|
||||
}
|
||||
|
||||
.passForm label {
|
||||
color: #444;
|
||||
}
|
||||
|
||||
.passForm label input {
|
||||
width: 100%;
|
||||
display: table;
|
||||
height: 40px;
|
||||
border: 1px solid #444;
|
||||
border-radius: 5px;
|
||||
margin: 8px 0;
|
||||
box-sizing: border-box;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.button {
|
||||
margin: 0 auto;
|
||||
display: table;
|
||||
background: #4a90e2;
|
||||
border: none;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
color: #FFF;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#centered {
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
#footer {
|
||||
position: fixed;
|
||||
bottom: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#cancelBtn {
|
||||
background: white;
|
||||
z-index: 999;
|
||||
color: #ff6666;
|
||||
padding: 7px; /* 10px of .button -3px to account for the thick border */
|
||||
border: 3px solid #ff6666;
|
||||
}
|
||||
|
||||
.alert {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
margin-bottom: 10px;
|
||||
color: #ff9595;
|
||||
font-weight: bold;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
transition: 500ms;
|
||||
}
|
||||
|
||||
.alert.show {
|
||||
border: 1px solid #ff9595;
|
||||
padding: 10px;
|
||||
height: 40px;
|
||||
transition: 500ms;
|
||||
}
|
||||
|
||||
.hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#home span, #success span {
|
||||
color: #4a4a4a;
|
||||
font-weight: bold;
|
||||
font-size: 22px;
|
||||
text-align: left;
|
||||
width: 80%;
|
||||
margin: 80px auto 0 auto;
|
||||
display: table;
|
||||
}
|
||||
|
||||
#home img {
|
||||
width: 100%;
|
||||
margin: 60px 0;
|
||||
}
|
||||
|
||||
@media (min-width: 500px) {
|
||||
.panel {
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
/* loading */
|
||||
.loader {
|
||||
color: #4a90e2;
|
||||
font-size: 20px;
|
||||
margin: 150px auto;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
text-indent: -9999em;
|
||||
-webkit-animation: load4 1.3s infinite linear;
|
||||
animation: load4 1.3s infinite linear;
|
||||
-webkit-transform: translateZ(0);
|
||||
-ms-transform: translateZ(0);
|
||||
transform: translateZ(0);
|
||||
}
|
||||
|
||||
@-webkit-keyframes load4 {
|
||||
0%,
|
||||
100% {
|
||||
box-shadow: 0 -3em 0 0.2em, 2em -2em 0 0em, 3em 0 0 -1em, 2em 2em 0 -1em, 0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 0;
|
||||
}
|
||||
12.5% {
|
||||
box-shadow: 0 -3em 0 0, 2em -2em 0 0.2em, 3em 0 0 0, 2em 2em 0 -1em, 0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 -1em;
|
||||
}
|
||||
25% {
|
||||
box-shadow: 0 -3em 0 -0.5em, 2em -2em 0 0, 3em 0 0 0.2em, 2em 2em 0 0, 0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 -1em;
|
||||
}
|
||||
37.5% {
|
||||
box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0em 0 0, 2em 2em 0 0.2em, 0 3em 0 0em, -2em 2em 0 -1em, -3em 0em 0 -1em, -2em -2em 0 -1em;
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 0em, 0 3em 0 0.2em, -2em 2em 0 0, -3em 0em 0 -1em, -2em -2em 0 -1em;
|
||||
}
|
||||
62.5% {
|
||||
box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 -1em, 0 3em 0 0, -2em 2em 0 0.2em, -3em 0 0 0, -2em -2em 0 -1em;
|
||||
}
|
||||
75% {
|
||||
box-shadow: 0em -3em 0 -1em, 2em -2em 0 -1em, 3em 0em 0 -1em, 2em 2em 0 -1em, 0 3em 0 -1em, -2em 2em 0 0, -3em 0em 0 0.2em, -2em -2em 0 0;
|
||||
}
|
||||
87.5% {
|
||||
box-shadow: 0em -3em 0 0, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 -1em, 0 3em 0 -1em, -2em 2em 0 0, -3em 0em 0 0, -2em -2em 0 0.2em;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes load4 {
|
||||
0%,
|
||||
100% {
|
||||
box-shadow: 0 -3em 0 0.2em, 2em -2em 0 0em, 3em 0 0 -1em, 2em 2em 0 -1em, 0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 0;
|
||||
}
|
||||
12.5% {
|
||||
box-shadow: 0 -3em 0 0, 2em -2em 0 0.2em, 3em 0 0 0, 2em 2em 0 -1em, 0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 -1em;
|
||||
}
|
||||
25% {
|
||||
box-shadow: 0 -3em 0 -0.5em, 2em -2em 0 0, 3em 0 0 0.2em, 2em 2em 0 0, 0 3em 0 -1em, -2em 2em 0 -1em, -3em 0 0 -1em, -2em -2em 0 -1em;
|
||||
}
|
||||
37.5% {
|
||||
box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0em 0 0, 2em 2em 0 0.2em, 0 3em 0 0em, -2em 2em 0 -1em, -3em 0em 0 -1em, -2em -2em 0 -1em;
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 0em, 0 3em 0 0.2em, -2em 2em 0 0, -3em 0em 0 -1em, -2em -2em 0 -1em;
|
||||
}
|
||||
62.5% {
|
||||
box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 -1em, 0 3em 0 0, -2em 2em 0 0.2em, -3em 0 0 0, -2em -2em 0 -1em;
|
||||
}
|
||||
75% {
|
||||
box-shadow: 0em -3em 0 -1em, 2em -2em 0 -1em, 3em 0em 0 -1em, 2em 2em 0 -1em, 0 3em 0 -1em, -2em 2em 0 0, -3em 0em 0 0.2em, -2em -2em 0 0;
|
||||
}
|
||||
87.5% {
|
||||
box-shadow: 0em -3em 0 0, 2em -2em 0 -1em, 3em 0 0 -1em, 2em 2em 0 -1em, 0 3em 0 -1em, -2em 2em 0 0, -3em 0em 0 0, -2em -2em 0 0.2em;
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 555 B |
Before Width: | Height: | Size: 9.3 KiB |
Before Width: | Height: | Size: 730 B |
Before Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 792 B |
Before Width: | Height: | Size: 157 B |
Before Width: | Height: | Size: 7.4 KiB |
Before Width: | Height: | Size: 7.1 KiB |
Before Width: | Height: | Size: 6.7 KiB |
Before Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 5.7 KiB |
|
@ -1,56 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Wifi Setup</title>
|
||||
<link rel="stylesheet" href="css/style.css">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<script type="application/javascript" src="js/Config.js"></script>
|
||||
<script type="application/javascript" src="js/WS.js"></script>
|
||||
<script type="application/javascript" src="js/main.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="home" class="panel hide">
|
||||
<div class="title">HI, MYCROFT HERE</div>
|
||||
<div class="body">
|
||||
<span>This process is for connecting me to the internet through your wifi.</span>
|
||||
<img src="img/connect.png"/>
|
||||
<a id="connectBtn" class="button">LET'S CONNECT</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="list-panel" class="panel hide">
|
||||
<div class="title">CHOOSE YOUR USUAL WIFI</div>
|
||||
<ul id="list" class="body"></ul>
|
||||
</div>
|
||||
|
||||
<div id="loading" class="panel">
|
||||
<div class="title">LOADING</div>
|
||||
<div class="body">
|
||||
<div class="loader"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="connecting" class="panel hide">
|
||||
<div class="title">CONNECTING</div>
|
||||
<div class="body">
|
||||
<div class="loader"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="success" class="panel hide">
|
||||
<div class="title">CONNECTED</div>
|
||||
<div class="body">
|
||||
<span>I'm connected to the internet now. Feels goood <img src="img/love.png"/></span>
|
||||
<small>If I am registered with your account, I'm ready to roll. If not, go ahead and register me at home.mycroft.ai.</small>
|
||||
<a class="button hide" id="registerBtn">REGISTER ME</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="centered">
|
||||
<a class="button" id="cancelBtn">CANCEL SETUP</a>
|
||||
</p>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -1,3 +0,0 @@
|
|||
var Config = {
|
||||
wsUrl: "ws://172.24.1.1:8181/core"
|
||||
};
|
|
@ -1,55 +0,0 @@
|
|||
var WS = {
|
||||
ws: null,
|
||||
wsConnected: false,
|
||||
listeners: {},
|
||||
onOpenListeners: [],
|
||||
|
||||
connect: function () {
|
||||
this.ws = new WebSocket(Config.wsUrl);
|
||||
this.setWSListeners();
|
||||
},
|
||||
|
||||
setWSListeners: function () {
|
||||
this.ws.onmessage = this.onMessage.bind(this);
|
||||
this.ws.onopen = this.onOpen.bind(this);
|
||||
},
|
||||
|
||||
setOnOpenListener: function (cb) {
|
||||
this.onOpenListeners.push(cb);
|
||||
},
|
||||
|
||||
onMessage: function (evt) {
|
||||
var msg = JSON.parse(evt.data);
|
||||
if (this.listeners[msg.type]) {
|
||||
this.listeners[msg.type].forEach(function (cb) {
|
||||
cb(msg.data);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
onOpen: function () {
|
||||
this.wsConnected = true;
|
||||
this.onOpenListeners.forEach(function (cb) {
|
||||
cb();
|
||||
});
|
||||
},
|
||||
|
||||
send: function (type, data) {
|
||||
this.ws.send(JSON.stringify({
|
||||
type: type,
|
||||
data: data
|
||||
}));
|
||||
},
|
||||
|
||||
close: function () {
|
||||
this.ws.close();
|
||||
this.wsConnected = false;
|
||||
this.ws = null;
|
||||
},
|
||||
|
||||
addMessageListener: function (type, callback) {
|
||||
this.listeners[type] = this.listeners[type] || [];
|
||||
this.listeners[type].push(callback);
|
||||
}
|
||||
|
||||
};
|
|
@ -1,291 +0,0 @@
|
|||
function getImagePath(strength) {
|
||||
if (strength > 0.8) {
|
||||
return "img/wifi_4.png";
|
||||
} else if (strength > 0.6) {
|
||||
return "img/wifi_3.png";
|
||||
} else if (strength > 0.4) {
|
||||
return "img/wifi_2.png";
|
||||
} else if (strength > 0.2) {
|
||||
return "img/wifi_1.png";
|
||||
} else {
|
||||
return "img/wifi_0.png";
|
||||
}
|
||||
}
|
||||
|
||||
function showPanel(id) {
|
||||
var panels = document.querySelectorAll(".panel");
|
||||
|
||||
for (var i=0; i < panels.length; i++)
|
||||
panels[i].classList.add("hide");
|
||||
|
||||
document.querySelector("#" + id).classList.remove("hide");
|
||||
}
|
||||
|
||||
var WifiSetup = {
|
||||
|
||||
selectedNetword: null,
|
||||
|
||||
setListeners: function () {
|
||||
WS.addMessageListener("mycroft.wifi.connected", this.onConnected.bind(this));
|
||||
WS.addMessageListener("mycroft.wifi.scanned", this.onScanned.bind(this));
|
||||
},
|
||||
|
||||
onConnected: function (data) {
|
||||
if (data.connected) {
|
||||
// NOTE: Once we send the "mycroft.wifi.stop", the unit will
|
||||
// be shutting down the wifi access point. So the device
|
||||
// hosting the browser session is probably being disconnected
|
||||
// and hopefully automatically reconnecting to the internet.
|
||||
//
|
||||
// Until the reconnect happens, the user cannot actually
|
||||
// follow the link to http://home.mycroft.ai to register
|
||||
// their device. That is part of why we are doing this 2 sec
|
||||
// delay.
|
||||
//
|
||||
WS.send("mycroft.wifi.stop");
|
||||
WS.close();
|
||||
|
||||
setTimeout(function () {
|
||||
var btnCancel = document.querySelector("#cancelBtn");
|
||||
btnCancel.classList.add("hide");
|
||||
|
||||
showPanel("success");
|
||||
startPing();
|
||||
}, 2000);
|
||||
} else {
|
||||
showPanel("list-panel");
|
||||
this.renderErrorItem(this.selectedNetword.el);
|
||||
}
|
||||
},
|
||||
|
||||
onScanned: function (data) {
|
||||
var networks = data.networks,
|
||||
fragment = document.createDocumentFragment(),
|
||||
list = document.querySelector("#list"),
|
||||
item = null,
|
||||
li = null;
|
||||
|
||||
showPanel("list-panel");
|
||||
|
||||
Object.keys(networks).sort(function (a, b) {
|
||||
if (networks[a].quality < networks[b].quality) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}).forEach(function (network) {
|
||||
li = document.createElement("li");
|
||||
networks[network].ssid = network;
|
||||
networks[network].el = li;
|
||||
item = this.renderListItem(networks[network]);
|
||||
li.appendChild(item);
|
||||
fragment.appendChild(li);
|
||||
}.bind(this));
|
||||
|
||||
list.innerHTML = null;
|
||||
list.appendChild(fragment);
|
||||
},
|
||||
|
||||
renderListItem: function (network) {
|
||||
var listItem = document.createElement("div"),
|
||||
span = document.createElement("span"),
|
||||
imgSignal = document.createElement("img");
|
||||
listItem.className = "list-item show";
|
||||
span.textContent = network.ssid;
|
||||
span.className = "ssid";
|
||||
imgSignal.src = getImagePath(network.quality);
|
||||
imgSignal.className = "wifi";
|
||||
listItem.appendChild(span);
|
||||
listItem.appendChild(imgSignal);
|
||||
if (network.connected) {
|
||||
var connected = document.createElement("span");
|
||||
connected.className = "connected";
|
||||
connected.textContent = "Connected";
|
||||
listItem.classList.add("connected");
|
||||
listItem.appendChild(connected);
|
||||
} else {
|
||||
listItem.addEventListener("click", this.clickNetwork.bind(this, network));
|
||||
}
|
||||
if (network.encrypted) {
|
||||
var imgLock = document.createElement("img");
|
||||
imgLock.src = "img/lock.png";
|
||||
imgLock.className = "lock";
|
||||
listItem.appendChild(imgLock);
|
||||
}
|
||||
return listItem;
|
||||
},
|
||||
|
||||
ItemDefaultState: function () {
|
||||
var li = document.querySelector(".list-item:not(.show)");
|
||||
if (!li) {
|
||||
return;
|
||||
}
|
||||
var divs = li.parentNode.childNodes;
|
||||
|
||||
Object.keys(divs).forEach(function (div) {
|
||||
divs[div].classList.remove("show");
|
||||
if (divs[div].classList.contains("list-item")) {
|
||||
divs[div].classList.add("show");
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
renderConnectItem: function (li) {
|
||||
var connect = li.querySelector(".connect-item");
|
||||
if (!connect) {
|
||||
connect = document.createElement("div");
|
||||
var imgArrow = document.createElement("img");
|
||||
var label = document.createElement("label");
|
||||
connect.className = "connect-item";
|
||||
imgArrow.src = "img/next.png";
|
||||
imgArrow.addEventListener("click", this.clickConnect.bind(this));
|
||||
connect.appendChild(label);
|
||||
if (this.selectedNetword.encrypted) {
|
||||
connect.passwordInput = document.createElement("input");
|
||||
label.textContent = "Password: ";
|
||||
connect.passwordInput.type = "password";
|
||||
connect.passwordToggle = document.createElement("button");
|
||||
connect.passwordToggle.className = "password-toggle";
|
||||
connect.passwordToggle.addEventListener("click", function () {
|
||||
connect.passwordInput.type = connect.passwordInput.type == "text" ? "password" : "text";
|
||||
});
|
||||
connect.appendChild(connect.passwordInput);
|
||||
connect.appendChild(connect.passwordToggle);
|
||||
} else {
|
||||
label.className = "public";
|
||||
label.textContent = this.selectedNetword.ssid;
|
||||
}
|
||||
connect.appendChild(imgArrow);
|
||||
|
||||
li.appendChild(connect);
|
||||
}
|
||||
li.querySelector(".list-item").classList.remove("show");
|
||||
connect.classList.add("show");
|
||||
if ('passwordInput' in connect)
|
||||
connect.passwordInput.focus();
|
||||
},
|
||||
|
||||
renderErrorItem: function (li) {
|
||||
var error = li.querySelector(".error-item");
|
||||
if (error) {
|
||||
li.querySelector(".connect-item").classList.remove("show");
|
||||
error.classList.add("show");
|
||||
return;
|
||||
}
|
||||
error = document.createElement("div");
|
||||
var imgClose = document.createElement("img");
|
||||
error.classList.add("error-item");
|
||||
imgClose.src = "img/error.png";
|
||||
var message = document.createElement("span");
|
||||
message.textContent = "Try again or connect to a different wifi.";
|
||||
error.appendChild(message);
|
||||
error.appendChild(imgClose);
|
||||
li.appendChild(error);
|
||||
li.querySelector(".connect-item").classList.remove("show");
|
||||
error.classList.add("show");
|
||||
error.addEventListener("click", this.ItemDefaultState);
|
||||
},
|
||||
|
||||
clickNetwork: function (network) {
|
||||
this.selectedNetword = network;
|
||||
this.ItemDefaultState();
|
||||
this.renderConnectItem(network.el);
|
||||
}
|
||||
,
|
||||
|
||||
sendScan: function () {
|
||||
showPanel("loading");
|
||||
document.querySelector("#cancelBtn").classList.remove("hide");
|
||||
WS.send("mycroft.wifi.scan");
|
||||
}
|
||||
,
|
||||
|
||||
/***
|
||||
* @param data is a object with ssid and pass
|
||||
*/
|
||||
sendConnect: function (data) {
|
||||
WS.send("mycroft.wifi.connect", data);
|
||||
}
|
||||
,
|
||||
|
||||
clickConnect: function () {
|
||||
showPanel("connecting");
|
||||
var network = {
|
||||
ssid: this.selectedNetword.ssid
|
||||
};
|
||||
if (this.selectedNetword.encrypted) {
|
||||
var pass = this.selectedNetword.el.querySelector("input");
|
||||
network.pass = pass.value;
|
||||
}
|
||||
this.sendConnect(network);
|
||||
}
|
||||
,
|
||||
|
||||
cancelSetup: function () {
|
||||
WS.send("mycroft.wifi.stop");
|
||||
WS.close();
|
||||
}
|
||||
,
|
||||
|
||||
init: function () {
|
||||
this.setListeners();
|
||||
showPanel("home");
|
||||
document.querySelector("#connectBtn").addEventListener("click", this.sendScan);
|
||||
document.querySelector("#registerBtn").addEventListener("click", function () {
|
||||
setTimeout(function() {
|
||||
location.href="https://home.mycroft.ai";
|
||||
}, 2000);
|
||||
});
|
||||
document.querySelector("#cancelBtn").addEventListener("click", this.cancelSetup);
|
||||
}
|
||||
};
|
||||
|
||||
function startPing() {
|
||||
ping("home.mycroft.ai",
|
||||
function(status,e) {
|
||||
if (status == 'responded') {
|
||||
// Un-hide the register button once we detect an
|
||||
// active internet connection.
|
||||
document.querySelector("#registerBtn").classList.remove("hide");
|
||||
}
|
||||
else
|
||||
setTimeout(function() { startPing(); }, 1000);
|
||||
});
|
||||
}
|
||||
|
||||
function ping(domain, callback) {
|
||||
if (!this.inUse) {
|
||||
this.status = 'unchecked';
|
||||
this.inUse = true;
|
||||
this.callback = callback;
|
||||
this.ip = domain;
|
||||
var _that = this;
|
||||
this.img = new Image();
|
||||
this.img.onload = function () {
|
||||
_that.inUse = false;
|
||||
_that.callback('responded');
|
||||
|
||||
};
|
||||
this.img.onerror = function (e) {
|
||||
if (_that.inUse) {
|
||||
_that.inUse = false;
|
||||
_that.callback('responded', e);
|
||||
}
|
||||
|
||||
};
|
||||
this.start = new Date().getTime();
|
||||
this.img.src = "http://" + domain;
|
||||
this.timer = setTimeout(function () {
|
||||
if (_that.inUse) {
|
||||
_that.inUse = false;
|
||||
_that.callback('timeout');
|
||||
}
|
||||
}, 1500);
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("load", function () {
|
||||
WS.connect();
|
||||
WS.setOnOpenListener(function () {
|
||||
WifiSetup.init();
|
||||
});
|
||||
});
|
|
@ -28,8 +28,6 @@ psutil==5.2.1
|
|||
pep8==1.7.0
|
||||
multi_key_dict==2.0.3
|
||||
pocketsphinx==0.1.0
|
||||
wifi==0.3.8
|
||||
pyric==0.1.6
|
||||
inflection==0.3.1
|
||||
pytz==2017.2
|
||||
pillow==4.1.1
|
||||
|
|