diff --git a/.gitmodules b/.gitmodules
index afc7ddca..39430261 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -12,3 +12,8 @@
path = buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-mycroftos-gui
url = https://github.com/j1nx/skill-mycroftos-gui.git
branch = master
+
+[submodule "skill-balena-wifi-setup"]
+ path = buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup
+ url = https://github.com/OpenVoiceOS/skill-balena-wifi-setup.git
+ branch = master
diff --git a/buildroot-external/package/wifi-connect/0002-Change-default-settings.patch b/buildroot-external/package/wifi-connect/0002-Change-default-settings.patch
index a813da4e..d2323187 100644
--- a/buildroot-external/package/wifi-connect/0002-Change-default-settings.patch
+++ b/buildroot-external/package/wifi-connect/0002-Change-default-settings.patch
@@ -20,7 +20,7 @@ index 3afad7a..456fb67 100644
-const DEFAULT_SSID: &str = "WiFi Connect";
+const DEFAULT_GATEWAY: &str = "172.16.127.1";
+const DEFAULT_DHCP_RANGE: &str = "172.16.127.2,172.16.127.27";
-+const DEFAULT_SSID: &str = "MYCROFT";
++const DEFAULT_SSID: &str = "OpenVoiceOS";
const DEFAULT_ACTIVITY_TIMEOUT: &str = "0";
const DEFAULT_UI_DIRECTORY: &str = "ui";
const DEFAULT_LISTENING_PORT: &str = "80";
diff --git a/buildroot-external/package/wifi-connect/start-wifi-connect b/buildroot-external/package/wifi-connect/start-wifi-connect
deleted file mode 100755
index ea569ff1..00000000
--- a/buildroot-external/package/wifi-connect/start-wifi-connect
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/usr/bin/env bash
-export DBUS_SYSTEM_BUS_ADDRESS=unix:path=/run/dbus/system_bus_socket
-sleep 15
-
-iwgetid -r
-
-if [ $? -eq 0 ]; then
- printf 'Skipping WiFi Connect\n'
-else
- printf 'Starting WiFi Connect\n'
- /usr/local/sbin/wifi-connect
-fi
-
-rm /etc/wifi-connect
diff --git a/buildroot-external/package/wifi-connect/wifi-connect.mk b/buildroot-external/package/wifi-connect/wifi-connect.mk
index 46eb85d2..344ef2a6 100644
--- a/buildroot-external/package/wifi-connect/wifi-connect.mk
+++ b/buildroot-external/package/wifi-connect/wifi-connect.mk
@@ -32,12 +32,4 @@ define WIFI_CONNECT_INSTALL_TARGET_CMDS
$(TARGET_DIR)/usr/local/sbin/wifi-connect
endef
-define WIFI_CONNECT_INSTALL_INIT_SYSTEMD
- $(INSTALL) -D -m 0644 $(BR2_EXTERNAL_OPENVOICEOS_PATH)/package/wifi-connect/wifi-connect.service \
- $(TARGET_DIR)/usr/lib/systemd/system/wifi-connect.service
- $(INSTALL) -D -m 0755 $(BR2_EXTERNAL_OPENVOICEOS_PATH)/package/wifi-connect/start-wifi-connect \
- $(TARGET_DIR)/usr/local/sbin/start-wifi-connect
- touch $(TARGET_DIR)/etc/wifi-connect
-endef
-
$(eval $(generic-package))
diff --git a/buildroot-external/package/wifi-connect/wifi-connect.service b/buildroot-external/package/wifi-connect/wifi-connect.service
deleted file mode 100644
index c71b51fc..00000000
--- a/buildroot-external/package/wifi-connect/wifi-connect.service
+++ /dev/null
@@ -1,12 +0,0 @@
-[Unit]
-Description=OpenVoiceOS wifi-connect service
-After=NetworkManager.service
-ConditionPathIsReadWrite=/etc
-ConditionPathExists=/etc/wifi-connect
-
-[Service]
-Type=simple
-ExecStart=/usr/local/sbin/start-wifi-connect
-
-[Install]
-WantedBy=multi-user.target
diff --git a/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/.gitignore b/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/.gitignore
new file mode 100644
index 00000000..5eca3d22
--- /dev/null
+++ b/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/.gitignore
@@ -0,0 +1,5 @@
+__pycache__/
+*.qmlc
+*.idea
+settings.json
+
diff --git a/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/LICENSE.md b/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/LICENSE.md
new file mode 100644
index 00000000..d6456956
--- /dev/null
+++ b/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/LICENSE.md
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/README.md b/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/README.md
new file mode 100644
index 00000000..304c8918
--- /dev/null
+++ b/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/README.md
@@ -0,0 +1,21 @@
+# Wifi Connect
+Connect your mycroft device to Wifi
+
+## About
+Provides the audio and visual elements for Connecting to Wifi via a secondary mobile device or computer.
+
+## Examples
+
+## Credits
+JarbasAI
+
+## Category
+**Configuration**
+
+## Tags
+#Wifi
+#Network
+#Internet
+#Connection
+#Connect
+
diff --git a/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/__init__.py b/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/__init__.py
new file mode 100644
index 00000000..9a2da1dc
--- /dev/null
+++ b/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/__init__.py
@@ -0,0 +1,277 @@
+from mycroft import MycroftSkill, intent_handler
+from mycroft.util import create_daemon, connected
+from mycroft.configuration import LocalConf, USER_CONFIG
+import subprocess
+import pexpect
+from time import sleep
+
+
+class WifiConnect(MycroftSkill):
+ def __init__(self):
+ MycroftSkill.__init__(self)
+ self.monitoring = False
+ self.in_setup = False
+ self.wifi_process = None
+ self.debug = False # dev setting, VERY VERBOSE DIALOGS
+ # TODO skill settings
+ self.ssid = "OVOS"
+ self.pswd = None
+ self.time_between_checks = 30 # seconds
+ self.wifi_command = "sudo /usr/local/sbin/wifi-connect --portal-ssid {ssid}"
+ if self.pswd:
+ self.wifi_command += " --portal-passphrase {pswd}"
+
+ def initialize(self):
+ self.make_priority()
+ self.add_event("mycroft.internet.connected",
+ self.handle_internet_connected)
+ self.start_internet_check()
+
+ def make_priority(self):
+ if not self.skill_id:
+ # might not be set yet....
+ return
+ # load the current list of already blacklisted skills
+ priority_list = self.config_core["skills"]["priority_skills"]
+
+ # add the skill to the blacklist
+ if self.skill_id not in priority_list:
+ priority_list.insert(0, self.skill_id)
+
+ # load the user config file (~/.mycroft/mycroft.conf)
+ conf = LocalConf(USER_CONFIG)
+ if "skills" not in conf:
+ conf["skills"] = {}
+
+ # update the blacklist field
+ conf["skills"]["priority_skills"] = priority_list
+
+ # save the user config file
+ conf.store()
+
+ # internet watchdog
+ def start_internet_check(self):
+ create_daemon(self._watchdog)
+
+ def stop_internet_check(self):
+ self.monitoring = False
+
+ def _watchdog(self):
+ self.monitoring = True
+ while self.monitoring:
+ if self.in_setup:
+ sleep(1) # let setup do it's thing
+ continue
+
+ if not connected():
+ self.log.info("NO INTERNET")
+ if not self.is_connected_to_wifi():
+ self.log.info("LAUNCH SETUP")
+ try:
+ self.launch_wifi_setup() # blocking
+ except Exception as e:
+ self.log.exception(e)
+ else:
+ self.log.warning("CONNECTED TO WIFI, BUT NO INTERNET!!")
+ sleep(self.time_between_checks)
+
+ # wifi setup
+ @staticmethod
+ def get_wifi_ssid():
+ SSID = None
+ try:
+ SSID = subprocess.check_output(["iwgetid", "-r"]).strip()
+ except subprocess.CalledProcessError:
+ # If there is no connection subprocess throws a 'CalledProcessError'
+ pass
+ return SSID
+
+ @staticmethod
+ def is_connected_to_wifi():
+ return WifiConnect.get_wifi_ssid() is not None
+
+ def launch_wifi_setup(self):
+ self.stop_setup()
+ self.in_setup = True
+ self.wifi_process = pexpect.spawn(
+ self.wifi_command.format(ssid=self.ssid)
+ )
+ # https://github.com/pexpect/pexpect/issues/462
+ self.wifi_process.delayafterclose = 1
+ self.wifi_process.delayafterterminate = 1
+ prev = ""
+ restart = False
+ if self.debug:
+ self.speak_dialog("start_setup")
+ while self.in_setup:
+ try:
+ out = self.wifi_process.readline().decode("utf-8").strip()
+ if out == prev:
+ continue
+ prev = out
+ if out.startswith("Access points: "):
+ aps = list(out.split("Access points: ")[-1])
+ self.log.info(out)
+ if self.debug:
+ self.speak_dialog("wifi_scanned")
+ elif out.startswith("Starting access point..."):
+ if self.debug:
+ self.speak_dialog("ap_start")
+ elif out.startswith("Access point ") and \
+ out.endswith("created"):
+ self.prompt_to_join_ap()
+ if self.debug:
+ self.speak_dialog("ap_created")
+ elif out.startswith("Starting HTTP server on"):
+ self.log.debug(out)
+ if self.debug:
+ self.speak_dialog("http_started")
+ elif out.startswith("Stopping access point"):
+ if self.debug:
+ self.speak_dialog("ap_stop")
+ elif out.startswith("Access point ") and \
+ out.endswith("stopped"):
+ if self.debug:
+ self.speak_dialog("ap_stopped")
+ elif out == "User connected to the captive portal":
+ self.log.info(out)
+ self.prompt_to_select_network()
+ if self.debug:
+ self.speak_dialog("user_connected")
+ elif out.startswith("Connecting to access point"):
+ if self.debug:
+ self.speak_dialog("connecting")
+ elif out.startswith("Internet connectivity established"):
+ self.log.info(out)
+ self.report_setup_complete()
+ if self.debug:
+ self.speak_dialog("wifi_connected")
+ elif "Error" in out or "[Errno" in out:
+ self.log.error(out)
+ self.report_setup_failed()
+
+ # TODO figure out at least the errors handled gracefully
+ accepted_errors = [
+ "Password length should be at least 8 characters"
+ ]
+ for e in accepted_errors:
+ if e in out:
+ continue
+ else:
+ restart = True
+ break
+
+ if self.debug:
+ self.log.debug(out)
+ except pexpect.exceptions.EOF:
+ # exited
+ self.log.info("Exited wifi setup process")
+ break
+ except pexpect.exceptions.TIMEOUT:
+ # nothing happened for a while
+ pass
+ except KeyboardInterrupt:
+ break
+ except Exception as e:
+ self.log.exception(e)
+ break
+ self.stop_setup()
+ if restart:
+ # handle bugs in balena, sometimes it fails to come back up
+ # seems to happen on
+ # Error: Getting access points failed
+ self.launch_wifi_setup()
+ elif self.debug:
+ self.speak_dialog("end_setup")
+
+ # intents
+ @intent_handler("launch_setup.intent")
+ def wifi_intent(self, message):
+ self.launch_wifi_setup()
+
+ # bus events
+ def handle_internet_connected(self, message=None):
+ """System came online later after booting."""
+ self.enclosure.mouth_reset()
+ self.stop_setup() # just in case
+ self.gui.release()
+
+ # GUI events
+ def prompt_to_join_ap(self, message=None):
+ """Provide instructions for setting up wifi."""
+ self.gui.remove_page("status.qml")
+ self.gui["phone_image"] = "1_phone_connect-to-ap.png"
+ self.gui["prompt"] = "Connect to the \nWifi network"
+ self.gui["highlight"] = self.ssid
+ self.gui.show_page("prompt.qml")
+ # allow GUI to linger around for a bit, will block the wifi setup loop
+ sleep(2)
+
+ def prompt_to_select_network(self, message=None):
+ """Prompt user to select network and login."""
+ self.gui.remove_page("status.qml")
+ self.gui.clear()
+ self.gui["phone_image"] = "3_phone_choose-wifi.png"
+ self.gui["prompt"] = "Choose the \nWifi network to \nconnect your \ndevice"
+ self.gui["highlight"] = ""
+ self.gui.show_page("prompt.qml")
+ # allow GUI to linger around for a bit, will block the wifi setup loop
+ sleep(2)
+
+ def report_setup_complete(self, message=None):
+ """Wifi setup complete, network is connected."""
+ self.gui.remove_page("prompt.qml")
+ self.gui.clear()
+ self.gui["icon"] = "check-circle.svg"
+ self.gui["label"] = "Connected"
+ self.gui["bgColor"] = "#40DBB0"
+ self.gui.remove_page("prompt.qml")
+ self.gui.show_page("status.qml")
+ # allow GUI to linger around for a bit, will block the wifi setup loop
+ sleep(3)
+ self.gui.release()
+
+ def report_setup_failed(self, message=None):
+ """Wifi setup failed"""
+ self.speak_dialog("wifi_error")
+ self.gui.remove_page("prompt.qml")
+ self.gui.clear()
+ self.gui["icon"] = "times-circle.svg"
+ self.gui["label"] = "Connection Failed"
+ self.gui["bgColor"] = "#FF0000"
+ self.gui.show_page("status.qml")
+ # allow GUI to linger around for a bit, will block the wifi setup loop
+ sleep(2)
+
+ # cleanup
+ def stop_setup(self):
+ if self.wifi_process is not None:
+ try:
+ if self.wifi_process.isalive():
+ self.log.debug("terminating wifi setup process")
+ self.wifi_process.sendcontrol('c')
+ sleep(1)
+ self.wifi_process.close()
+ sleep(1)
+ if self.wifi_process.isalive():
+ self.log.warning('wifi setup did not exit gracefully.')
+ self.wifi_process.close(force=True)
+ sleep(1)
+ if self.wifi_process.isalive():
+ self.log.warning('trying to terminate wifi setup process')
+ self.wifi_process.terminate()
+ sleep(1)
+ else:
+ self.log.debug('wifi setup exited gracefully.')
+ except Exception as e:
+ self.log.exception(e)
+ self.wifi_process = None
+ self.in_setup = False
+
+ def shutdown(self):
+ self.monitoring = False
+ self.stop_setup()
+
+
+def create_skill():
+ return WifiConnect()
diff --git a/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/locale/en-us/ap_created.dialog b/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/locale/en-us/ap_created.dialog
new file mode 100644
index 00000000..15e41a0b
--- /dev/null
+++ b/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/locale/en-us/ap_created.dialog
@@ -0,0 +1 @@
+access point created
\ No newline at end of file
diff --git a/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/locale/en-us/ap_start.dialog b/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/locale/en-us/ap_start.dialog
new file mode 100644
index 00000000..1cdd5a10
--- /dev/null
+++ b/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/locale/en-us/ap_start.dialog
@@ -0,0 +1 @@
+starting captive portal
\ No newline at end of file
diff --git a/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/locale/en-us/ap_stop.dialog b/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/locale/en-us/ap_stop.dialog
new file mode 100644
index 00000000..12b81ce2
--- /dev/null
+++ b/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/locale/en-us/ap_stop.dialog
@@ -0,0 +1 @@
+stopping captive portal
\ No newline at end of file
diff --git a/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/locale/en-us/ap_stopped.dialog b/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/locale/en-us/ap_stopped.dialog
new file mode 100644
index 00000000..cce25b60
--- /dev/null
+++ b/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/locale/en-us/ap_stopped.dialog
@@ -0,0 +1 @@
+captive portal stopped
\ No newline at end of file
diff --git a/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/locale/en-us/connecting.dialog b/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/locale/en-us/connecting.dialog
new file mode 100644
index 00000000..28d78b73
--- /dev/null
+++ b/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/locale/en-us/connecting.dialog
@@ -0,0 +1 @@
+Connecting to selected wifi, see logs
\ No newline at end of file
diff --git a/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/locale/en-us/end_setup.dialog b/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/locale/en-us/end_setup.dialog
new file mode 100644
index 00000000..980ee78a
--- /dev/null
+++ b/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/locale/en-us/end_setup.dialog
@@ -0,0 +1 @@
+wifi setup finished
\ No newline at end of file
diff --git a/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/locale/en-us/http_started.dialog b/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/locale/en-us/http_started.dialog
new file mode 100644
index 00000000..9d54b6a2
--- /dev/null
+++ b/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/locale/en-us/http_started.dialog
@@ -0,0 +1 @@
+started http server
\ No newline at end of file
diff --git a/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/locale/en-us/launch_setup.intent b/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/locale/en-us/launch_setup.intent
new file mode 100644
index 00000000..eb15b71f
--- /dev/null
+++ b/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/locale/en-us/launch_setup.intent
@@ -0,0 +1,4 @@
+launch wifi connect
+launch wifi setup
+change wifi network
+add wifi network
\ No newline at end of file
diff --git a/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/locale/en-us/start_setup.dialog b/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/locale/en-us/start_setup.dialog
new file mode 100644
index 00000000..ba971a92
--- /dev/null
+++ b/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/locale/en-us/start_setup.dialog
@@ -0,0 +1 @@
+wifi setup started
\ No newline at end of file
diff --git a/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/locale/en-us/user_connected.dialog b/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/locale/en-us/user_connected.dialog
new file mode 100644
index 00000000..e2c2583a
--- /dev/null
+++ b/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/locale/en-us/user_connected.dialog
@@ -0,0 +1 @@
+user connected to captive portal
\ No newline at end of file
diff --git a/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/locale/en-us/wifi_connected.dialog b/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/locale/en-us/wifi_connected.dialog
new file mode 100644
index 00000000..a6358263
--- /dev/null
+++ b/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/locale/en-us/wifi_connected.dialog
@@ -0,0 +1 @@
+Connected to wifi
\ No newline at end of file
diff --git a/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/locale/en-us/wifi_error.dialog b/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/locale/en-us/wifi_error.dialog
new file mode 100644
index 00000000..909d5d86
--- /dev/null
+++ b/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/locale/en-us/wifi_error.dialog
@@ -0,0 +1 @@
+failed to connect to wifi, verify credentials and try again
\ No newline at end of file
diff --git a/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/locale/en-us/wifi_scanned.dialog b/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/locale/en-us/wifi_scanned.dialog
new file mode 100644
index 00000000..138587ef
--- /dev/null
+++ b/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/locale/en-us/wifi_scanned.dialog
@@ -0,0 +1 @@
+I have scanned for access points, see logs for results
\ No newline at end of file
diff --git a/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/requirements.txt b/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/requirements.txt
new file mode 100644
index 00000000..389a6c3a
--- /dev/null
+++ b/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/requirements.txt
@@ -0,0 +1 @@
+pexpect
\ No newline at end of file
diff --git a/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/ui/1_phone_connect-to-ap.png b/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/ui/1_phone_connect-to-ap.png
new file mode 100644
index 00000000..b267dd56
Binary files /dev/null and b/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/ui/1_phone_connect-to-ap.png differ
diff --git a/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/ui/3_phone_choose-wifi.png b/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/ui/3_phone_choose-wifi.png
new file mode 100644
index 00000000..c09780c4
Binary files /dev/null and b/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/ui/3_phone_choose-wifi.png differ
diff --git a/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/ui/icons/check-circle.svg b/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/ui/icons/check-circle.svg
new file mode 100644
index 00000000..b0619c06
--- /dev/null
+++ b/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/ui/icons/check-circle.svg
@@ -0,0 +1,52 @@
+
+
diff --git a/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/ui/icons/info-circle.svg b/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/ui/icons/info-circle.svg
new file mode 100644
index 00000000..e6bb98d6
--- /dev/null
+++ b/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/ui/icons/info-circle.svg
@@ -0,0 +1,52 @@
+
+
diff --git a/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/ui/icons/times-circle.svg b/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/ui/icons/times-circle.svg
new file mode 100644
index 00000000..44befca9
--- /dev/null
+++ b/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/ui/icons/times-circle.svg
@@ -0,0 +1,52 @@
+
+
diff --git a/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/ui/prompt.qml b/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/ui/prompt.qml
new file mode 100644
index 00000000..0b65cdd5
--- /dev/null
+++ b/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/ui/prompt.qml
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2018 by Aditya Mehra
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import QtQuick.Layouts 1.4
+import QtQuick 2.4
+import QtQuick.Controls 2.0
+import org.kde.kirigami 2.4 as Kirigami
+
+import Mycroft 1.0 as Mycroft
+
+Mycroft.ProportionalDelegate {
+ id: root
+ property var spacingUnit: 30
+ property var phone: sessionData.phone_image
+ property var prompt: sessionData.prompt
+ property var highlight: sessionData.highlight
+
+ Row {
+ spacing: spacingUnit
+ Image {
+ id: img
+ source: Qt.resolvedUrl(phone)
+ Layout.fillHeight: true
+ height: root.height * 0.8
+ fillMode: Image.PreserveAspectFit
+ verticalAlignment: Image.AlignBottom
+ anchors.bottom: parent.bottom
+ }
+ ColumnLayout {
+ Kirigami.Heading {
+ id: sentence
+ Layout.fillWidth: true
+ Layout.alignment: Qt.AlignLeft
+ Layout.leftMargin: spacingUnit
+ Layout.topMargin: 80
+ horizontalAlignment: Text.AlignHLeft
+ wrapMode: Text.WordWrap
+ elide: Text.ElideRight
+ font.family: "Noto Sans"
+ font.bold: true
+ font.weight: Font.Bold
+ font.pixelSize: 38
+ visible: !content.visible
+ text: prompt
+ }
+ Kirigami.Heading {
+ id: url
+ Layout.fillWidth: true
+ Layout.alignment: Qt.AlignLeft
+ Layout.leftMargin: spacingUnit
+ horizontalAlignment: Text.AlignHLeft
+ wrapMode: Text.WordWrap
+ elide: Text.ElideRight
+ font.family: "Noto Sans"
+ font.bold: true
+ font.weight: Font.Bold
+ font.pixelSize: 36
+ visible: !content.visible
+ color: "#22a7f0"
+ text: highlight
+ }
+ }
+ }
+}
diff --git a/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/ui/status.qml b/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/ui/status.qml
new file mode 100644
index 00000000..595a025e
--- /dev/null
+++ b/buildroot-external/rootfs-overlay/opt/mycroft/skills/skill-balena-wifi-setup/ui/status.qml
@@ -0,0 +1,44 @@
+import QtQuick.Layouts 1.4
+import QtQuick 2.4
+import QtQuick.Controls 2.0
+import org.kde.kirigami 2.4 as Kirigami
+
+import Mycroft 1.0 as Mycroft
+
+Mycroft.ProportionalDelegate {
+ id: root
+ skillBackgroundColorOverlay: sessionData.bgColor
+
+ ColumnLayout {
+ id: grid
+ anchors.centerIn: parent
+
+ Image {
+ id: statusIcon
+ visible: true
+ enabled: true
+ anchors.horizontalCenter: grid.horizontalCenter
+ Layout.preferredWidth: proportionalGridUnit * 50
+ Layout.preferredHeight: proportionalGridUnit * 50
+ source: Qt.resolvedUrl(`icons/${sessionData.icon}`)
+ }
+
+ /* Add some spacing between icon and text */
+ Item {
+ height: Kirigami.Units.largeSpacing
+ }
+
+ Label {
+ id: statusLabel
+ Layout.alignment: Qt.AlignHCenter
+ font.pixelSize: 65
+ wrapMode: Text.WordWrap
+ renderType: Text.NativeRendering
+ font.family: "Noto Sans Display"
+ font.styleName: "Black"
+ font.capitalization: Font.AllUppercase
+ text: sessionData.label
+ color: "white"
+ }
+ }
+}
\ No newline at end of file