hivemind webchat

This commit is contained in:
jarbasal
2020-02-26 05:07:56 +00:00
parent 331cea9b9d
commit 6297a808ae
46 changed files with 240 additions and 1006 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

186
README.md
View File

@@ -1,190 +1,32 @@
## Mycroft Webcaht Client and Bluetooth presence service for Mycroft
Mycroft Webchat Client and Mycroft bluetooth service getting the MAC address of a Bluetooth device.
# HiveMind - WebChat Terminal
Mycroft Webchat Terminal - Connecting to the HiveMind with javascript reference implementation
## Requirements
This uses tornado to serve the webchat
This code requires the bluetooth module to be installed. On Mark1, Picroft or Ubuntu/Debian systems, this can usually be done with the following commands:
![](./webchat.png)
sudo apt-get install bluetooth bluez blueman
sudo reboot now
## Usage
For use with Mark1 or if you have problem with Picroft you need a [USB Bluetooth dongle ](https://www.amazon.com/Bluetooth-Dongle-Adapter-Raspberry-Windows/dp/B073H4GQ9Q/ref=sr_1_8?ie=UTF8&qid=1531953940&sr=8-8&keywords=raspberry+pi+3+usb+bluetooth+dongle) like this
![Screenshot](usb_donle.jpeg?raw=true)
change hivemind settings at ```tornado_webchat/static/js/app.js```
run ```python tornado_webchat/__main__.py```
## Installation
Access from web browser http://your_ip_address:9090
For all installations
## Privacy
cd /opt/mycroft
Securing tornado is out of scope for this repo, it is currently served by HTTP, you probably want to set up nginx or equivalent with [let's encrypt](https://letsencrypt.org/) certificates
if the installation is in Mark1 or Picroft
Hivemind connection is http by default! This is because browsers reject self signed certs, you probably want to use [let's encrypt](https://letsencrypt.org/) certificates and configure [HiveMind-core](https://github.com/OpenJarbas/HiveMind-core) to use them
sudo su mycroft
Hivemind Encryption is not supported yet (don't set crypto key in [HiveMind-core](https://github.com/OpenJarbas/HiveMind-core))
For all installations
git clone https://github.com/jcasoft/external-services.git
if the installation is in Mark1 or Picroft
exit
For all installations
sudo chmod -R a+rwx /opt/mycroft/external-services
## Installation of USB bluetooth dongle on Mark1 or if you have problem with Picroft
Plug the USB bluetooth dongle
Mark1 command line
hciconfig -a hci1
sudo hciconfig hci1 up
sudo reboot now
## Configuration
Locate your configuration file and add the section detailed belove
Linux
nano ~/.mycroft/mycroft.conf
Mark1 or Picroft
nano /home/pi/.mycroft/mycroft.conf
and
sudo su mycroft
nano /home/mycroft/.mycroft/mycroft.conf
exit
Add this section to mycroft.conf
"Proximity": {
"proximity_enabled": true,
"proximity_data": [
{"bt_id": "00:00:00:00:00:1A","name": "Juan Carlos"},
{"bt_id": "00:00:00:00:00:1B","name": "Adriana"},
{"bt_id": "00:00:00:00:00:1C","name": "Daniella"},
{"bt_id": "00:00:00:00:00:1D","name": "Enzo"},
{"bt_id": "00:00:00:00:00:1E","name": "Gianluca"}
]
}
Fill the bt_id with bluetooth MAC address of your smartphone
![Screenshot](IOS-Bluetooth-MAC-Address.png?raw=true)
## How to start Presence service
Linux
cd /opt/mycroft/external-services
bash start-presence.sh linux
cd ~/mycroft-core
bash start-mycroft.sh debug
Mark1 or Picroft
cd /opt/mycroft/external-services
bash start-presence.sh mark1
or
bash start-presence.sh picroft
## How to stop Presence service
Linux, Mark1 and Picroft
cd /opt/mycroft/external-services
bash stop-presence.sh
## How to start Webchat service
Linux
cd /opt/mycroft/external-services
bash start-webchat.sh linux
cd ~/mycroft-core
bash start-mycroft.sh debug
Mark1 or Picroft
cd /opt/mycroft/external-services
bash start-webchat.sh mark1
or
bash start-webchat.sh picroft
## How to stop Webchat service
Linux, Mark1 and Picroft
cd /opt/mycroft/external-services
bash stop-webchat.sh
## How to start both service
Linux
cd /opt/mycroft/external-services
bash start-all.sh linux
cd ~/mycroft-core
bash start-mycroft.sh debug
Mark1 or Picroft
cd /opt/mycroft/external-services
bash start-all.sh mark1
or
bash start-all.sh picroft
## How to stop both services
Linux, Mark1 and Picroft
cd /opt/mycroft/external-services
bash stop-all.sh
## How to access Web chat from your browser
http://localhost:9090
or
http://MYCROFT-IP-ADDRESS:9090
## How to use Webcaht Client and Bluetooth presence service
[![Webcaht Client and Bluetooth presence service](https://img.youtube.com/vi/8cRSmOTRSBI/0.jpg)](https://www.youtube.com/watch?v=8cRSmOTRSBI)
## Notes
Give me a Github star and follow me on YouTube !, to continue developing and improving skills and services in Mycroft
Basically this is unsafe to run outside your home network with default configurations
## Credits
Author: jcasoft
Juan Carlos Argueta
Original Webchat UI: [jcasoft](https://github.com/jcasoft/external-services)

View File

@@ -1,154 +0,0 @@
# Copyright 2017 Mycroft AI Inc.
#
# 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 random
from io import open
# from os.path import join, expanduser
import os
import re
from mycroft.util.log import LOG
__doc__ = """
"""
class MustacheDialogRenderer(object):
"""
A dialog template renderer based on the mustache templating language.
"""
def __init__(self):
self.templates = {}
def load_template_file(self, template_name, filename):
"""
Load a template by file name into the templates cache.
Args:
template_name (str): a unique identifier for a group of templates
filename (str): a fully qualified filename of a mustache template.
"""
with open(filename, 'r') as f:
for line in f:
template_text = line.strip()
if template_name not in self.templates:
self.templates[template_name] = []
# convert to standard python format string syntax. From
# double (or more) '{' followed by any number of whitespace
# followed by actual key followed by any number of whitespace
# followed by double (or more) '}'
template_text = re.sub('\{\{+\s*(.*?)\s*\}\}+', r'{\1}',
template_text)
self.templates[template_name].append(template_text)
def render(self, template_name, context=None, index=None):
"""
Given a template name, pick a template and render it using the context
Args:
template_name (str): the name of a template group.
context (dict): dictionary representing values to be rendered
index (int): optional, the specific index in the collection of
templates
Returns:
str: the rendered string
Raises:
NotImplementedError: if no template can be found identified by
template_name
"""
context = context or {}
if template_name not in self.templates:
raise NotImplementedError("Template not found: %s" % template_name)
template_functions = self.templates.get(template_name)
if index is None:
index = random.randrange(len(template_functions))
else:
index %= len(template_functions)
line = template_functions[index]
line = line.format(**context)
return line
class DialogLoader(object):
"""
Loads a collection of dialog files into a renderer implementation.
"""
def __init__(self, renderer_factory=MustacheDialogRenderer):
self.__renderer = renderer_factory()
def load(self, dialog_dir):
"""
Load all dialog files within the specified directory.
Args:
dialog_dir (str): directory that contains dialog files
Returns:
a loaded instance of a dialog renderer
"""
if not os.path.exists(dialog_dir) or not os.path.isdir(dialog_dir):
LOG.warning("No dialog found: " + dialog_dir)
return self.__renderer
for f in sorted(
filter(lambda x: os.path.isfile(
os.path.join(dialog_dir, x)), os.listdir(dialog_dir))):
dialog_entry_name = os.path.splitext(f)[0]
self.__renderer.load_template_file(
dialog_entry_name, os.path.join(dialog_dir, f))
return self.__renderer
def get(phrase, lang=None, context=None):
"""
Looks up a resource file for the given phrase. If no file
is found, the requested phrase is returned as the string.
This will use the default language for translations.
Args:
phrase (str): resource phrase to retrieve/translate
lang (str): the language to use
context (dict): values to be inserted into the string
Returns:
str: a randomized and/or translated version of the phrase
"""
if not lang:
from mycroft.configuration import Configuration
lang = Configuration.get().get("lang")
filename = "res/text/" + lang.lower() + "/" + phrase + ".dialog"
#template = resolve_resource_file(filename)
template = filename
if not template:
LOG.debug("Resource file not found: " + filename)
return phrase
stache = MustacheDialogRenderer()
stache.load_template_file("template", template)
if not context:
context = {}
return stache.render("template", context)

View File

@@ -1,157 +0,0 @@
#!/usr/bin/env python
# Developed by: jcasoft
# Juan Carlos Argueta
#
import sys
import os, time, platform
from datetime import datetime
import json
import dialog
from mycroft.configuration import Configuration
from mycroft.messagebus.client.ws import WebsocketClient
from mycroft.messagebus.message import Message
from mycroft.util.log import LOG
from mycroft.util import create_daemon, wait_for_exit_signal
__author__ = 'jcasoft'
# ----- Variables for Websocket
configWS = Configuration.get().get('websocket')
host = configWS.get('host')
port = configWS.get('port')
route = configWS.get('route')
ssl = configWS.get('ssl')
# ----- Variables for Proximity
configPr = Configuration.get().get('Proximity')
proximity_enabled = configPr.get('proximity_enabled')
proximity_data_array = configPr.get('proximity_data')
# ----- Variables for Language
lang = Configuration.get().get("lang")
ws = None
scanner_id = os.popen('uname -n').readline().strip()
devices_here = {}
AWAY_HEURISTIC = 10 # *** Can not be less than 10
# *** Get all nearby IDs
def get_bt_ids():
sys = platform.system()
ids = []
f = os.popen('hcitool inq --flush')
unparsed_data = f.readlines()[1:]
for u in unparsed_data:
id = u.split()[0]
ids.append(id)
return ids
# *** Scan the area for bluetooth devices. If a new device is seen, send data to messagebus.
def scan():
time = datetime.now()
ids = get_bt_ids()
if (sys.version_info > (3, 0)):
# Python 3 code in this block
for id in ids:
if not id in devices_here:
send_to_messagebus({'time': time, 'device_id': id, 'scanner_id': scanner_id, 'event_type': 'ENTER'})
devices_here[id] = time
else:
# Python 2 code in this block
for id in ids:
if not devices_here.has_key(id):
send_to_messagebus({'time': time, 'device_id': id, 'scanner_id': scanner_id, 'event_type': 'ENTER'})
devices_here[id] = time
# *** Clean up the list of nearby devices
def cleanup():
del_keys = []
time = datetime.now()
for device in devices_here:
last_seen = devices_here[device]
if (time - last_seen).seconds > AWAY_HEURISTIC:
del_keys.append(device)
send_to_messagebus({'time': time, 'device_id': device, 'scanner_id': scanner_id, 'event_type': 'EXIT'})
for k in del_keys:
del devices_here[k]
def send_to_messagebus(params):
LOG.debug('***** %s: device %s at time %s' % (params['event_type'], params['device_id'], params['time']))
currentTime = datetime.now()
currentTime.hour
dialog_name = 'greeting afternoon'
if params['event_type'] == "ENTER":
if currentTime.hour < 12:
dialog_name = 'greeting morning'
elif 12 <= currentTime.hour < 19:
dialog_name = 'greeting afternoon'
else:
dialog_name = 'greeting night'
elif params['event_type'] == "EXIT":
dialog_name = 'bye bye'
name = ""
found = False
for x in range(0,len(proximity_data_array)):
if ("bt_id",params['device_id']) in proximity_data_array[x].items():
name = proximity_data_array[x]["name"]
LOG.debug("***** Devide found, Owner " + name)
found = True
break
else:
found = False
if found:
msg_data = {'name': name}
payload = {'utterance':dialog.get(dialog_name, lang, msg_data)}
ws.emit(Message("speak", payload))
else:
name = ""
LOG.debug("***** Devide not found")
msg_data = {'name': name}
payload = {'utterance':dialog.get(dialog_name, lang, msg_data)}
ws.emit(Message("speak", payload))
def BT_Scanner():
LOG.debug("***** Proximity Bluetooth Enabled --> : Scanning Bluetooth devices... ")
while True:
# continuously scan for new devices
scan()
# and clean up the list of devices that are present
cleanup()
def main():
global ws
ws = WebsocketClient()
Configuration.init(ws)
create_daemon(ws.run_forever)
if proximity_enabled:
BT_Scanner()
wait_for_exit_signal()
if __name__ == '__main__':
main()

1
requirements.txt Normal file
View File

@@ -0,0 +1 @@
tornado

View File

@@ -1,3 +0,0 @@
Bye bye {{name}}, see you later
Bye bye {{name}}, nice to see you
{{name}} hasta la vista baby!

View File

@@ -1,3 +0,0 @@
Hello {{name}}, how are you
Hello {{name}}, nice to see you

View File

@@ -1,2 +0,0 @@
Good morning {{name}}, how are you
Hello {{name}}, Good morning!

View File

@@ -1,3 +0,0 @@
Good night {{name}}, how was your day?
Good night {{name}}
Hello {{name}}, Good night!

View File

@@ -1,3 +0,0 @@
Adios{{name}}
Hasta luego {{name}}
{{name}}, espero verte pronto

View File

@@ -1,3 +0,0 @@
Hola {{name}} como estas
Hola {{name}} que gusto verte
Hola {{name}} que tal tu día

View File

@@ -1,3 +0,0 @@
Buenos días {{name}}, como estas?
Buenos días {{name}}, como te encuentras?
Hola {{name}}, buenos dias!

View File

@@ -1,3 +0,0 @@
Buenos noches {{name}}, como estuvo tu día?
Buenos noches {{name}}, que tal tu día?
Hola {{name}}, buenas noches!

View File

@@ -1,18 +0,0 @@
#!/bin/bash
if [ "$1" = "mark1" ] || [ "$1" = "picroft" ]
then
echo "Selected Mark1 or Picroft"
source /opt/venvs/mycroft-core/bin/activate
python /opt/mycroft/external-services/presence.py > presence.log &
python /opt/mycroft/external-services/webchat.py > webchat.log &
elif [ "$1" = "linux" ]
then
echo "Selected mycroft-core"
source ~/mycroft-core/.venv/bin/activate
python -u /opt/mycroft/external-services/presence.py > presence.log &
python -u /opt/mycroft/external-services/webchat.py > webchat.log &
else
echo "Usage: ./start-all.sh mark1, ./start-all.sh picroft or ./start-all.sh linux"
fi

View File

@@ -1,27 +0,0 @@
#!/bin/bash
if ! dpkg-query -s bluetooth 1> /dev/null 2>&1 ; then
echo "Package wireless-tools is not currently installed."
echo "--------------------------------------------------"
echo "Install Bluetooth with: sudo apt-get install bluetooth"
echo "Reboot after install"
exit 1
else
echo "Package Bluetooth is currently installed."
fi
if [ "$1" = "mark1" ] || [ "$1" = "picroft" ]
then
echo "Selected Mark1 or Picroft"
source /opt/venvs/mycroft-core/bin/activate
python /opt/mycroft/external-services/presence.py > presence.log &
elif [ "$1" = "linux" ]
then
echo "Selected mycroft-core"
source ~/mycroft-core/.venv/bin/activate
python -u /opt/mycroft/external-services/presence.py > presence.log &
else
echo "Usage: ./start-presence.sh mark1, ./start-presence.sh picroft or ./start-presence.sh linux"
fi

View File

@@ -1,16 +0,0 @@
#!/bin/bash
if [ "$1" = "mark1" ] || [ "$1" = "picroft" ]
then
echo "Selected Mark1 or Picroft"
source /opt/venvs/mycroft-core/bin/activate
python /opt/mycroft/external-services/webchat.py > webchat.log &
elif [ "$1" = "linux" ]
then
echo "Selected mycroft-core"
source ~/mycroft-core/.venv/bin/activate
python -u /opt/mycroft/external-services/webchat.py > webchat.log &
else
echo "Usage: ./start-webchat.sh mark1, ./start-webchat.sh picroft or ./start-webchat.sh linux"
fi

View File

@@ -1,209 +0,0 @@
/*
-------------------------------------------------------------
Author: jcasoft - Juan Carlos Argueta
Web Chat Client for Mycroft
to do:
reload or refresh
mic_on
mic_off
mute
vol_up
vol_down
backward
forward
play
pause
-------------------------------------------------------------
*/
var ip = ""
var por = ""
function set_ip(val)
{
ip_alt=val
console.log("Local computer IP="+ ip_alt)
}
function set_port(val)
{
port_alt=val
console.log("Socket Port="+ port_alt)
}
$(document).ready(function(){
var received = $('#received');
var parser = document.createElement('a');
parser.href = document.URL;
ip = parser.hostname;
port = parser.port;
// Define Socket with local computer IP
var socket = new WebSocket("ws://"+ip+":"+port+"/ws");
console.log("ws://"+ip+":"+port+"/ws")
socket.onopen = function(){
console.log("connected");
push_response('Web Chat Client by JCASOFT')
};
socket.onmessage = function (message) {
console.log("receiving: " + message.data);
received.append(message.data);
push_response(message.data)
};
socket.onclose = function(){
console.log("disconnected");
};
var sendMessage = function(message) {
console.log("sending:" + message.data);
socket.send(message.data);
};
$('.chat[data-chat=person2]').addClass('active-chat')
$('.person[data-chat=person2]').addClass('active')
$('.left .person').mousedown(function () {
if ($(this).hasClass('.active')) {
return false
}
const findChat = $(this).attr('data-chat')
const personName = $(this).find('.name').text()
$('.right .top .name').html(personName)
$('.chat').removeClass('active-chat')
$('.left .person').removeClass('active')
$(this).addClass('active')
$('.chat[data-chat = ' + findChat + ']').addClass('active-chat')
})
function push_statment(msg) {
$('.chat').append('<div class="bubble me"><i class="fa fa-user-circle" aria-hidden="true"></i>&nbsp;&nbsp;' + msg + '</div>')
}
function push_response(msg, callback) {
if (msg == 'stop') {
$('.chat').append('<div class="bubble you loading"><i class="fa fa-window-close" aria-hidden="true"></i>&nbsp;&nbsp;' + msg + '</div>')
} else if (msg == 'reload') {
$('.chat').append('<div class="bubble you loading"><i class="fa fa-refresh" aria-hidden="true"></i>&nbsp;&nbsp;' + msg + '</div>')
} else if (msg == 'play') {
$('.chat').append('<div class="bubble you loading"><i class="fa fa-play" aria-hidden="true"></i>&nbsp;&nbsp;' + msg + '</div>')
} else if (msg == 'pause') {
$('.chat').append('<div class="bubble you loading"><i class="fa fa-pause" aria-hidden="true"></i>&nbsp;&nbsp;' + msg + '</div>')
} else if (msg == 'mute') {
$('.chat').append('<div class="bubble you loading"><i class="fa fa-volume-off" aria-hidden="true"></i>&nbsp;&nbsp;' + msg + '</div>')
} else if (msg == 'vol_up') {
$('.chat').append('<div class="bubble you loading"><i class="fa fa-volume-up" aria-hidden="true"></i>&nbsp;&nbsp;' + msg + '</div>')
} else if (msg == 'vol_down') {
$('.chat').append('<div class="bubble you loading"><i class="fa fa-volume-down" aria-hidden="true"></i>&nbsp;&nbsp;' + msg + '</div>')
} else if (msg == 'mic_on') {
$('.chat').append('<div class="bubble you loading"><i class="fa fa-microphone" aria-hidden="true"></i>&nbsp;&nbsp;' + msg + '</div>')
} else if (msg == 'mic_off') {
$('.chat').append('<div class="bubble you loading"><i class="fa fa-microphone-slash" aria-hidden="true"></i>&nbsp;&nbsp;' + msg + '</div>')
} else if (msg == 'forward') {
$('.chat').append('<div class="bubble you loading"><i class="fa fa-forward" aria-hidden="true"></i>&nbsp;&nbsp;' + msg + '</div>')
} else if (msg == 'backward') {
$('.chat').append('<div class="bubble you loading"><i class="fa fa-backward" aria-hidden="true"></i>&nbsp;&nbsp;' + msg + '</div>')
} else if (msg == 'Web Chat Client from JCASOFT') {
$('.chat').append('<div class="bubble you loading"><i class="fa fa-thumbs-o-up" aria-hidden="true"></i>&nbsp;&nbsp;' + msg + '</div>')
} else {
$('.chat').append('<div class="bubble you"><i class="fa fa-commenting" aria-hidden="true"></i>&nbsp;&nbsp;' + msg + '</div>')
}
}
function get_resp(q, is_response = false) {
let query = q
console.log("*****Utterance to send to main.py server="+query) // Send to tornado and wait for response
sendMessage({ 'data' : query});
}
$('#stop').click(function(){
sendMessage({ 'data' : 'stop'});
push_response('stop')
});
$('#pause').click(function(){
sendMessage({ 'data' : 'pause'});
push_response('pause')
});
$('#play').click(function(){
sendMessage({ 'data' : 'play'});
push_response('play')
});
$('#next_track').click(function(){
sendMessage({ 'data' : 'next track'});
push_response('next track')
});
$('#previous_track').click(function(){
sendMessage({ 'data' : 'previous track'});
push_response('previous track')
});
$('#reduce_volume').click(function(){
sendMessage({ 'data' : 'reduce volume'});
push_response('reduce volume')
});
$('#increase_volume').click(function(){
sendMessage({ 'data' : 'increase volume'});
push_response('increase volume')
});
$('#mute_volume').click(function(){
sendMessage({ 'data' : 'mute volume'});
push_response('mute volume')
});
$('#mic_on').click(function(){
sendMessage({ 'data' : 'mic_on'});
push_response('start listening on Mycroft mic')
});
$('#mic_off').click(function(){
sendMessage({ 'data' : 'mic_off'});
push_response('mic off')
});
$('#reload').click(function(){
sendMessage({ 'data' : 'reload'});
push_response('reload')
});
$('#textbox_submit').click(function(){
$(this).blur()
$('.chat').append('<div class="bubble me"><i class="fa fa-user-circle" aria-hidden="true"></i>&nbsp;<i class="fa fa-volume-up"></i>&nbsp;' + $('#textbox').val() + '</div>')
get_resp($('#textbox').val())
document.getElementById('textbox').value = ''
return false
})
$('#textbox').keypress(function (e) {
if (e.which == 13) {
$(this).blur()
push_statment($('#textbox').val())
get_resp($('#textbox').val()+"|SILENT")
document.getElementById('textbox').value = ''
return false
}
})
});

View File

@@ -1,4 +0,0 @@
#!/bin/bash
pkill -f "presence.py"
pkill -f "webchat.py"

View File

@@ -1,3 +0,0 @@
#!/bin/bash
pkill -f "presence.py"

View File

@@ -1,3 +0,0 @@
#!/bin/bash
pkill -f "webchat.py"

View File

@@ -0,0 +1,50 @@
import tornado.httpserver
import tornado.ioloop
import tornado.web
import os.path
import os
import tornado.websocket
import tornado.options
import threading
import asyncio
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.render('index.html')
class StaticFileHandler(tornado.web.RequestHandler):
def get(self):
self.render('js/app.js')
class WebChat(threading.Thread):
def __init__(self, port, *args, **kwargs):
super().__init__(*args, **kwargs)
self.port = port
def run(self):
asyncio.set_event_loop(asyncio.new_event_loop())
routes = [
tornado.web.url(r"/", MainHandler, name="main"),
tornado.web.url(r"/static/(.*)", tornado.web.StaticFileHandler,
{'path': './'})
]
settings = {
"debug": True,
"template_path": os.path.join(os.path.dirname(__file__),
"templates"),
"static_path": os.path.join(os.path.dirname(__file__), "static"),
}
application = tornado.web.Application(routes, **settings)
httpServer = tornado.httpserver.HTTPServer(application)
tornado.options.parse_command_line()
httpServer.listen(self.port)
tornado.ioloop.IOLoop.instance().start()
def stop(self):
tornado.ioloop.IOLoop.instance().stop()

View File

@@ -0,0 +1,45 @@
import socket
import sys
from time import sleep
from tornado_webchat import WebChat
def get_ip():
return [l for l in (
[ip for ip in socket.gethostbyname_ex(socket.gethostname())[2] if
not ip.startswith("127.")][:1], [
[(s.connect(('8.8.8.8', 53)), s.getsockname()[0], s.close()) for s
in
[socket.socket(socket.AF_INET, socket.SOCK_DGRAM)]][0][1]]) if l][
0][0]
if __name__ == "__main__":
# TODO argpase
import tornado.options
tornado.options.parse_command_line()
port = 9090
# TODO pass hivemind options
# currently defined in tornado_webchat/static/js/app.js
webchat = WebChat(port)
try:
webchat.start()
ip = get_ip()
url = "http://" + str(ip) + ":" + str(port)
print("*********************************************************")
print("* HiveMind WebChat Client ")
print("*")
print("* Access from web browser " + url)
print("*********************************************************")
while True:
sleep(5)
except KeyboardInterrupt:
webchat.stop()
webchat.join()
sys.exit()

View File

Before

Width:  |  Height:  |  Size: 434 KiB

After

Width:  |  Height:  |  Size: 434 KiB

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

121
tornado_webchat/static/js/app.js Executable file
View File

@@ -0,0 +1,121 @@
/*
-------------------------------------------------------------
Author: jcasoft - Juan Carlos Argueta
Web Chat Client for Mycroft
Modified for the HiveMind by JarbasAI
-------------------------------------------------------------
*/
$(document).ready(function () {
// HiveMind socket
var user = "dummy_user";
var key = "dummy_key";
let ip = "0.0.0.0";
let port = 5678;
var authToken = btoa(user + ":" + key);
document.cookie = 'X-Authorization=' + authToken + '; path=/';
var ws = new WebSocket('ws://' + ip + ":" + port);
console.log('ws://' + ip + ":" + port);
ws.onopen = function () {
console.log("connected");
push_response("Welcome to the HiveMind Webchat client!")
};
ws.onmessage = function (message) {
message = JSON.parse(message.data);
if (message.msg_type === "bus"){
let mycroft_message =JSON.parse(message.payload);
console.log("Received: " + mycroft_message.type);
if (mycroft_message.type === "speak"){
let utterance = mycroft_message.data.utterance;
push_response(utterance)
}
}
};
ws.onclose = function () {
console.log("disconnected");
};
var sendMessage = function (message) {
// TODO hivemind message
let hive_msg = message
console.log("sending:" + JSON.stringify(hive_msg));
ws.send(JSON.stringify(message));
};
$('.chat[data-chat=person2]').addClass('active-chat')
$('.person[data-chat=person2]').addClass('active')
$('.left .person').mousedown(function () {
if ($(this).hasClass('.active')) {
return false
}
const findChat = $(this).attr('data-chat')
const personName = $(this).find('.name').text()
$('.right .top .name').html(personName)
$('.chat').removeClass('active-chat')
$('.left .person').removeClass('active')
$(this).addClass('active')
$('.chat[data-chat = ' + findChat + ']').addClass('active-chat')
});
function push_statment(msg) {
$('.chat').append('<div class="bubble me"><i class="fa fa-user-circle" aria-hidden="true"></i>&nbsp;&nbsp;' + msg + '</div>')
}
function push_response(msg) {
$('.chat').append('<div class="bubble you"><i class="fa fa-commenting" aria-hidden="true"></i>&nbsp;&nbsp;' + msg + '</div>')
}
function get_resp(q) {
let payload = {
'type': "recognizer_loop:utterance",
"data":{"utterances": [q]},
"context": {"source": "WebChat", "destination": "HiveMind", "platform": "JarbasWebchatTerminalV0.1"}
};
sendMessage({
'msg_type': "bus",
"payload": payload,
"context": {"source": "WebChat", "destination": "HiveMind", "platform": "JarbasWebchatTerminalV0.1"}
});
}
$('#textbox_submit').click(function () {
$(this).blur()
$('.chat').append('<div class="bubble me"><i class="fa fa-user-circle" aria-hidden="true"></i>&nbsp;<i class="fa fa-volume-up"></i>&nbsp;' + $('#textbox').val() + '</div>')
get_resp($('#textbox').val())
document.getElementById('textbox').value = ''
return false
})
$('#textbox').keypress(function (e) {
if (e.which == 13) {
$(this).blur()
push_statment($('#textbox').val())
get_resp($('#textbox').val())
document.getElementById('textbox').value = ''
return false
}
})
});

View File

@@ -3,17 +3,7 @@
Author: jcasoft - Juan Carlos Argueta
Web Chat Client for Mycroft
to do:
reload or refresh
mic_on
mic_off
mute
vol_up
vol_down
backward
forward
play
pause
Modified by JarbasAI
-------------------------------------------------------------
-->
@@ -29,10 +19,11 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="cleartype" content="on">
<link rel="stylesheet" href="/static/css/bootstrap.min.css" type="text/css">
<link rel="stylesheet" href="/static/css/todc-bootstrap.min.css" type="text/css">
<link rel="stylesheet" href="/static/css/app.css">
<link rel="stylesheet" href="/static/css/font-awesome.css">
<link rel="stylesheet" href="static/css/bootstrap.min.css" type="text/css">
<link rel="stylesheet" href="static/css/todc-bootstrap.min.css"
type="text/css">
<link rel="stylesheet" href="static/css/app.css">
<link rel="stylesheet" href="static/css/font-awesome.css">
<script type="text/javascript">
@@ -50,27 +41,13 @@
<div class="mycroft">
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="navbar-header">
<a class="navbar-brand" href="#"><img src="/static/img/Logo2.png" alt="Mycroft Logo">
<a class="navbar-brand" href="#"><img src="static/img/Logo2.png" alt="Mycroft Logo">
</a>
</div>
<ul class="nav nav-pills navbar-right">
<li class="active"><a href="#" id="mic_on"><i class="fa fa-microphone"></i></a></li>
<li class="active"><a href="#" id="mute_volume"><i class="fa fa-volume-off"></i></a></li>
<li class="active"><a href="#" id="increase_volume"><i class="fa fa-volume-up"></i></a></li>
<li class="active"><a href="#" id="reduce_volume"><i class="fa fa-volume-down"></i></a></li>
<li class="active"><a href="#" id="previous_track"><i class="fa fa-backward"></i></a></li>
<li class="active"><a href="#" id="next_track"><i class="fa fa-forward"></i></a></li>
<li class="active"><a href="#" id="play"><i class="fa fa-play"></i></a></li>
<li class="active"><a href="#" id="pause"><i class="fa fa-pause"></i></a></li>
<li class="active"><a href="#" id="stop" style="margin-right:25px;"><i class="fa fa-stop"></i></a></li>
</ul>
</nav>
</div>
<div class="container">
<div class="right">
<div class="chat" data-chat="person2">
@@ -91,10 +68,8 @@
</div>
<script src="/static/js/jquery-3.2.1.min.js"></script>
<script src="/static/js/app.js"></script>
<script type="application/javascript">set_ip("{{ip}}");</script>
<script type="application/javascript">set_port("{{port}}");</script>
<script src="static/js/jquery-3.2.1.min.js"></script>
<script src="static/js/app.js"></script>
<script type="text/javascript">
document.getElementById('writer').style.display = 'block';
</script>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

BIN
webchat.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

View File

@@ -1,186 +0,0 @@
#!/usr/bin/env python
# Developed by: jcasoft
# Juan Carlos Argueta
#
from mycroft.configuration import ConfigurationManager
from mycroft.messagebus.service.ws import WebsocketEventHandler
from mycroft.util import validate_param, create_signal
__author__ = 'jcasoft'
from mycroft.messagebus.client.ws import WebsocketClient
from mycroft.messagebus.message import Message
from threading import Thread
ws = None
import tornado.httpserver
import tornado.websocket
import tornado.ioloop
import tornado.web
import tornado.options
import os.path
from tornado.options import define, options
import multiprocessing
import json
import time
import os
import socket
from subprocess import check_output
global ip
ip = [l for l in ([ip for ip in socket.gethostbyname_ex(socket.gethostname())[2] if not ip.startswith("127.")][:1], [[(s.connect(('8.8.8.8', 53)), s.getsockname()[0], s.close()) for s in [socket.socket(socket.AF_INET, socket.SOCK_DGRAM)]][0][1]]) if l][0][0]
clients = []
input_queue = multiprocessing.Queue()
output_queue = multiprocessing.Queue()
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.render('index.html', ip=ip, port=port)
class StaticFileHandler(tornado.web.RequestHandler):
def get(self):
self.render('js/app.js')
class WebSocketHandler(tornado.websocket.WebSocketHandler):
def open(self):
clients.append(self)
self.write_message("Welcome to Mycroft")
def on_message(self, message):
utterance = json.dumps(message)
print("*****Utterance : ", utterance)
if utterance:
if utterance == '"mic_on"':
create_signal('startListening')
else:
if "|SILENT" in utterance:
utterance = utterance.split("|")
utterance = utterance[0]
data = {
"lang": lang,
"session": "",
"utterances": [utterance],
"client": "WebChat"}
ws.emit(Message('chat_response', data))
ws.emit(Message('recognizer_loop:utterance', data))
else:
data = {
"lang": lang,
"session": "",
"utterances": [utterance]}
ws.emit(Message('recognizer_loop:utterance', data))
t = Thread(target=self.newThread)
t.start()
def newThread(self):
global wait_response
global skill_response
timeout = 0
while wait_response:
wait_response = True
time.sleep(1)
timeout = timeout + 1
time.sleep(1)
time.sleep(1)
if len(skill_response) > 0:
self.write_message(skill_response)
skill_response = ""
wait_response = True
timeout = 0
while timeout < 5 or wait_response:
time.sleep(1)
timeout = timeout + 1
if len(skill_response) > 0:
self.write_message(skill_response)
wait_response = True
skill_response = ""
def on_close(self):
clients.remove(self)
def connect():
ws.run_forever()
def handle_speak(event):
response = event.data['utterance']
global skill_response, wait_response
skill_response = ""
wait_response = False
skill_response = response
def main():
global skill_response, wait_response, port, lang
wait_response = True
skill_response = ""
global ws
ws = WebsocketClient()
event_thread = Thread(target=connect)
event_thread.setDaemon(True)
event_thread.start()
ws.on('speak', handle_speak)
import tornado.options
tornado.options.parse_command_line()
config = ConfigurationManager.get().get("websocket")
lang = ConfigurationManager.get().get("lang")
port = "9090"
url = ("http://" + str(ip) + ":" + str(port))
print("*********************************************************")
print("* JCASOFT - Mycroft Web Cliento ")
print("*")
print("* Access from web browser " + url)
print("*********************************************************")
routes = [
tornado.web.url(r"/", MainHandler, name="main"),
tornado.web.url(r"/static/(.*)", tornado.web.StaticFileHandler, {'path': './'}),
tornado.web.url(r"/ws", WebSocketHandler)
]
settings = {
"debug": True,
"template_path": os.path.join(os.path.dirname(__file__), "templates"),
"static_path": os.path.join(os.path.dirname(__file__), "static"),
}
application = tornado.web.Application(routes, **settings)
httpServer = tornado.httpserver.HTTPServer(application)
tornado.options.parse_command_line()
httpServer.listen(port)
try:
tornado.ioloop.IOLoop.instance().start()
except KeyboardInterrupt:
logger.exception(e)
event_thread.exit()
tornado.ioloop.IOLoop.instance().stop()
sys.exit()
if __name__ == "__main__":
main()