Remove wifi setup

pull/1122/head
Matthew D. Scholefield 2017-09-28 19:53:11 -05:00 committed by Arron Atchison
parent 4b4a48f796
commit a6709033d4
23 changed files with 1 additions and 1397 deletions

View File

@ -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/* *

View File

@ -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'
]

View File

@ -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()

View File

@ -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;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 555 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 730 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 792 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 157 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

View File

@ -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>

View File

@ -1,3 +0,0 @@
var Config = {
wsUrl: "ws://172.24.1.1:8181/core"
};

View File

@ -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);
}
};

View File

@ -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();
});
});

View File

@ -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