Version 3.1 InDev
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"label": "VERY Fun Stuff",
|
||||
"isSubmenu": true,
|
||||
"icon": "core://resources/actionIcons/fun.png"
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"label": "AAAAA",
|
||||
"icon": "core://resources/actionIcons/clock.png",
|
||||
"colors": "blue",
|
||||
"skillId": "@be/clock"
|
||||
}
|
||||
@@ -1,2 +1,10 @@
|
||||
# JiboOs
|
||||
|
||||
- - -
|
||||
|
||||
# Release Version 3.3.0 InDev
|
||||
|
||||
Chagelog.....
|
||||
|
||||
you can make custom menu buttons!
|
||||
|
||||
|
||||
99
V3.1/build/etc/init.d/S02jibo-skills-logd
Normal file
@@ -0,0 +1,99 @@
|
||||
#!/bin/sh
|
||||
### BEGIN INIT INFO
|
||||
# Provides: jibo-skills-logd
|
||||
# Required-Start: $local_fs
|
||||
# Required-Stop: $local_fs
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: UDP log daemon for Jibo Skills
|
||||
### END INIT INFO
|
||||
|
||||
# Install path on robot:
|
||||
# /etc/init.d/jibo-skills-logd (this file)
|
||||
# and ensure executable: chmod +x /etc/init.d/jibo-skills-logd
|
||||
# Enable (varies by distro):
|
||||
# update-rc.d jibo-skills-logd defaults
|
||||
# or (BusyBox init): create symlink in /etc/rc.d/rcS.d/ or /etc/rc?.d/
|
||||
|
||||
PYTHON_BIN=${PYTHON_BIN:-/usr/bin/python}
|
||||
DAEMON=${DAEMON:-/opt/jibo/Jibo/Skills/tools/robot/logd/jibo_logd.py}
|
||||
PIDFILE=${PIDFILE:-/tmp/jibo-skills-logd.pid}
|
||||
HOST=${JIBO_LOGD_HOST:-127.0.0.1}
|
||||
PORT=${JIBO_LOGD_PORT:-15140}
|
||||
LOGFILE=${JIBO_LOGD_FILE:-/tmp/jibo-skills.log}
|
||||
|
||||
start() {
|
||||
echo "Starting jibo-skills-logd"
|
||||
if [ -f "$PIDFILE" ]; then
|
||||
PID=$(cat "$PIDFILE" 2>/dev/null)
|
||||
if [ -n "$PID" ] && kill -0 "$PID" 2>/dev/null; then
|
||||
echo "Already running (pid $PID)"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# best effort: ensure logfile dir exists
|
||||
mkdir -p "$(dirname "$LOGFILE")" 2>/dev/null
|
||||
|
||||
"$PYTHON_BIN" "$DAEMON" --host "$HOST" --port "$PORT" --logfile "$LOGFILE" --daemonize --pidfile "$PIDFILE"
|
||||
sleep 1
|
||||
if [ -f "$PIDFILE" ]; then
|
||||
echo "Started (pid $(cat "$PIDFILE" 2>/dev/null))"
|
||||
return 0
|
||||
fi
|
||||
echo "Failed to start"
|
||||
return 1
|
||||
}
|
||||
|
||||
stop() {
|
||||
echo "Stopping jibo-skills-logd"
|
||||
if [ ! -f "$PIDFILE" ]; then
|
||||
echo "Not running (no pidfile)"
|
||||
return 0
|
||||
fi
|
||||
PID=$(cat "$PIDFILE" 2>/dev/null)
|
||||
if [ -z "$PID" ]; then
|
||||
rm -f "$PIDFILE"
|
||||
return 0
|
||||
fi
|
||||
kill "$PID" 2>/dev/null
|
||||
sleep 1
|
||||
kill -9 "$PID" 2>/dev/null
|
||||
rm -f "$PIDFILE"
|
||||
echo "Stopped"
|
||||
return 0
|
||||
}
|
||||
|
||||
status() {
|
||||
if [ -f "$PIDFILE" ]; then
|
||||
PID=$(cat "$PIDFILE" 2>/dev/null)
|
||||
if [ -n "$PID" ] && kill -0 "$PID" 2>/dev/null; then
|
||||
echo "Running (pid $PID)"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
echo "Not running"
|
||||
return 3
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
start
|
||||
;;
|
||||
stop)
|
||||
stop
|
||||
;;
|
||||
restart)
|
||||
stop
|
||||
start
|
||||
;;
|
||||
status)
|
||||
status
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {start|stop|restart|status}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
exit $?
|
||||
90
V3.1/build/etc/init.d/S03jibo-skills-logpanel
Normal file
@@ -0,0 +1,90 @@
|
||||
#!/bin/sh
|
||||
### BEGIN INIT INFO
|
||||
# Provides: jibo-skills-logpanel
|
||||
# Required-Start: $local_fs
|
||||
# Required-Stop: $local_fs
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: HTTP panel for live Jibo Skills logs
|
||||
### END INIT INFO
|
||||
|
||||
PYTHON_BIN=${PYTHON_BIN:-/usr/bin/python}
|
||||
DAEMON=${DAEMON:-/opt/jibo/Jibo/Skills/tools/robot/logpanel/jibo_logpanel.py}
|
||||
PIDFILE=${PIDFILE:-/tmp/jibo-skills-logpanel.pid}
|
||||
BIND=${JIBO_LOGPANEL_BIND:-0.0.0.0}
|
||||
PORT=${JIBO_LOGPANEL_PORT:-15150}
|
||||
LOGFILE=${JIBO_LOGD_FILE:-/tmp/jibo-skills.log}
|
||||
|
||||
start() {
|
||||
echo "Starting jibo-skills-logpanel"
|
||||
if [ -f "$PIDFILE" ]; then
|
||||
PID=$(cat "$PIDFILE" 2>/dev/null)
|
||||
if [ -n "$PID" ] && kill -0 "$PID" 2>/dev/null; then
|
||||
echo "Already running (pid $PID)"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
"$PYTHON_BIN" "$DAEMON" --bind "$BIND" --port "$PORT" --logfile "$LOGFILE" >/tmp/jibo-skills-logpanel.log 2>&1 &
|
||||
echo $! > "$PIDFILE"
|
||||
sleep 1
|
||||
if [ -f "$PIDFILE" ]; then
|
||||
echo "Started (pid $(cat "$PIDFILE" 2>/dev/null))"
|
||||
return 0
|
||||
fi
|
||||
echo "Failed to start"
|
||||
return 1
|
||||
}
|
||||
|
||||
stop() {
|
||||
echo "Stopping jibo-skills-logpanel"
|
||||
if [ ! -f "$PIDFILE" ]; then
|
||||
echo "Not running (no pidfile)"
|
||||
return 0
|
||||
fi
|
||||
PID=$(cat "$PIDFILE" 2>/dev/null)
|
||||
if [ -z "$PID" ]; then
|
||||
rm -f "$PIDFILE"
|
||||
return 0
|
||||
fi
|
||||
kill "$PID" 2>/dev/null
|
||||
sleep 1
|
||||
kill -9 "$PID" 2>/dev/null
|
||||
rm -f "$PIDFILE"
|
||||
echo "Stopped"
|
||||
return 0
|
||||
}
|
||||
|
||||
status() {
|
||||
if [ -f "$PIDFILE" ]; then
|
||||
PID=$(cat "$PIDFILE" 2>/dev/null)
|
||||
if [ -n "$PID" ] && kill -0 "$PID" 2>/dev/null; then
|
||||
echo "Running (pid $PID)"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
echo "Not running"
|
||||
return 3
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
start
|
||||
;;
|
||||
stop)
|
||||
stop
|
||||
;;
|
||||
restart)
|
||||
stop
|
||||
start
|
||||
;;
|
||||
status)
|
||||
status
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {start|stop|restart|status}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
exit $?
|
||||
127
V3.1/build/etc/init.d/S21firewall
Normal file
@@ -0,0 +1,127 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Jibo Firewall init script
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
IPTABLES_CMDS="/usr/sbin/iptables /usr/sbin/ip6tables"
|
||||
|
||||
flush_rules() {
|
||||
for iptables in $IPTABLES_CMDS; do
|
||||
$iptables -t filter -F
|
||||
$iptables -t filter -P INPUT ACCEPT
|
||||
$iptables -t filter -P FORWARD ACCEPT
|
||||
$iptables -t filter -P OUTPUT ACCEPT
|
||||
# add the DYNAMIC_ACCESS chain unconditionally
|
||||
$iptables -t filter -X
|
||||
$iptables -t filter -N DYNAMIC_ACCESS
|
||||
done
|
||||
}
|
||||
|
||||
normal_rules() {
|
||||
for iptables in $IPTABLES_CMDS; do
|
||||
$iptables -t filter -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
|
||||
$iptables -t filter -A INPUT -p icmp -j ACCEPT
|
||||
$iptables -t filter -A INPUT -i lo -j ACCEPT
|
||||
|
||||
# --- Custom Allowed Ports ---
|
||||
# Allow SSH
|
||||
$iptables -t filter -A INPUT -p tcp --dport 22 -j ACCEPT
|
||||
# Allow Jibo Skills Service panel at 8779
|
||||
$iptables -t filter -A INPUT -p tcp --dport 8779 -j ACCEPT
|
||||
# Allow Custom Port 15150 for loggging
|
||||
$iptables -t filter -A INPUT -p tcp --dport 15150 -j ACCEPT
|
||||
# ----------------------------
|
||||
|
||||
# allow dynamic access rules from system-manager
|
||||
$iptables -t filter -A INPUT -j DYNAMIC_ACCESS
|
||||
|
||||
# Reject everything else
|
||||
$iptables -t filter -A INPUT -j REJECT
|
||||
$iptables -t filter -A FORWARD -j REJECT
|
||||
done
|
||||
}
|
||||
developer_rules() {
|
||||
for iptables in $IPTABLES_CMDS; do
|
||||
# jibo-dev-shell
|
||||
$iptables -t filter -A INPUT -p tcp --syn --dport 8686 -j ACCEPT
|
||||
# jibo-skills-service
|
||||
$iptables -t filter -A INPUT -p tcp --syn --dport 8779 -j ACCEPT
|
||||
# jibo-sync
|
||||
$iptables -t filter -A INPUT -p tcp --syn --dport 8989 -j ACCEPT
|
||||
# jibo-debug-proxy
|
||||
$iptables -t filter -A INPUT -p tcp --syn --dport 9191 -j ACCEPT
|
||||
# avahi
|
||||
$iptables -t filter -A INPUT -p udp --dport 5353 -j ACCEPT
|
||||
done
|
||||
normal_rules
|
||||
}
|
||||
|
||||
certification_rules() {
|
||||
for iptables in $IPTABLES_CMDS; do
|
||||
# jibo-certification-service
|
||||
$iptables -t filter -A INPUT -p tcp --syn --dport 9292 -j ACCEPT
|
||||
done
|
||||
normal_rules
|
||||
}
|
||||
|
||||
service_rules() {
|
||||
for iptables in $IPTABLES_CMDS; do
|
||||
# jibo-certification-service
|
||||
$iptables -t filter -A INPUT -p tcp --syn --dport 9292 -j ACCEPT
|
||||
# jibo-service-center-service
|
||||
$iptables -t filter -A INPUT -p tcp --syn --dport 9797 -j ACCEPT
|
||||
# avahi
|
||||
$iptables -t filter -A INPUT -p udp --dport 5353 -j ACCEPT
|
||||
done
|
||||
normal_rules
|
||||
}
|
||||
|
||||
start() {
|
||||
echo -n "Configuring firewall: "
|
||||
flush_rules
|
||||
my_mode=$(/usr/bin/jibo-getmode)
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Unspecified mode. SKIP"
|
||||
elif [ "$my_mode" == "identified" ]; then
|
||||
echo "IDENTIFIED"
|
||||
elif [ "$my_mode" == "int-developer" ]; then
|
||||
echo "INT-DEVELOPER"
|
||||
elif [ "$my_mode" == "developer" ]; then
|
||||
developer_rules
|
||||
test $? -eq 0 && echo "DEVELOPER" || echo "ERROR"
|
||||
elif [ "$my_mode" == "certification" ]; then
|
||||
certification_rules
|
||||
test $? -eq 0 && echo "CERTIFICATION" || echo "ERROR"
|
||||
elif [ "$my_mode" == "service" ]; then
|
||||
service_rules
|
||||
test $? -eq 0 && echo "SERVICE" || echo "ERROR"
|
||||
else
|
||||
normal_rules
|
||||
test $? -eq 0 && echo "OK" || echo "ERROR"
|
||||
fi
|
||||
}
|
||||
|
||||
stop() {
|
||||
echo -n "Unconfiguring firewall: "
|
||||
flush_rules
|
||||
test $? -eq 0 && echo "OK" || echo "ERROR"
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
start
|
||||
;;
|
||||
stop)
|
||||
stop
|
||||
;;
|
||||
restart)
|
||||
stop
|
||||
start
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {start|stop|restart}" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
60
V3.1/build/opt/jibo/Jibo/Skills/@be/be/be/robot-logger.js
Normal file
@@ -0,0 +1,60 @@
|
||||
"use strict";
|
||||
|
||||
// Minimal UDP logger client for old robot environments.
|
||||
// Safe to require from any module; if logd is not running, it will just no-op.
|
||||
|
||||
const dgram = require("dgram");
|
||||
|
||||
const DEFAULT_HOST = process.env.JIBO_LOGD_HOST || "127.0.0.1";
|
||||
const DEFAULT_PORT = parseInt(process.env.JIBO_LOGD_PORT || "15140", 10);
|
||||
|
||||
let _socket = null;
|
||||
|
||||
function getSocket() {
|
||||
if (_socket) return _socket;
|
||||
try {
|
||||
_socket = dgram.createSocket("udp4");
|
||||
_socket.unref();
|
||||
return _socket;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function send(obj) {
|
||||
const sock = getSocket();
|
||||
if (!sock) return;
|
||||
|
||||
let payload;
|
||||
try {
|
||||
payload = Buffer.from(JSON.stringify(obj));
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
sock.send(payload, 0, payload.length, DEFAULT_PORT, DEFAULT_HOST, function () { });
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
function mk(tag, level, msg, data) {
|
||||
const o = { tag: tag || "skill", level: level || "info", msg: String(msg || "") };
|
||||
if (typeof data !== "undefined") o.data = data;
|
||||
return o;
|
||||
}
|
||||
|
||||
exports.info = function (tag, msg, data) { send(mk(tag, "info", msg, data)); };
|
||||
exports.warn = function (tag, msg, data) { send(mk(tag, "warn", msg, data)); };
|
||||
exports.error = function (tag, msg, data) { send(mk(tag, "error", msg, data)); };
|
||||
exports.debug = function (tag, msg, data) { send(mk(tag, "debug", msg, data)); };
|
||||
|
||||
exports.raw = function (line) {
|
||||
const sock = getSocket();
|
||||
if (!sock) return;
|
||||
const payload = Buffer.from(String(line || ""));
|
||||
try {
|
||||
sock.send(payload, 0, payload.length, DEFAULT_PORT, DEFAULT_HOST, function () { });
|
||||
} catch (e) { }
|
||||
};
|
||||
65
V3.1/build/opt/jibo/Jibo/Skills/@be/be/menu/PROVIDERS.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# Menu providers (drop-in menu customization)
|
||||
|
||||
Providers let you add/override menu entries without editing the core menu patch.
|
||||
|
||||
Default entries directory (on robot):
|
||||
- `/opt/jibo/Jibo/Skills/@be/menu-entries.d/`
|
||||
|
||||
Legacy fallback (yea ik its the same stop talking):
|
||||
- `/opt/jibo/Jibo/Skills/@be/menu-providers.d/`
|
||||
|
||||
## Provider file types
|
||||
|
||||
### JSON provider (`*.json`)
|
||||
Supported shapes:
|
||||
|
||||
- An array of entries: `[ { ... }, { ... } ]`
|
||||
- Or `{ "entries": [ ... ] }`
|
||||
|
||||
Entry schema (same as `menuEntry.json` scan output):
|
||||
|
||||
- `id` (string, required)
|
||||
- `type` (`skill` or `submenu`)
|
||||
- `title`
|
||||
- `icon`
|
||||
- `color`
|
||||
- `description`
|
||||
- `order` (number)
|
||||
- `skillId` (for type `skill`)
|
||||
- `submenuTitle` (for type `submenu`)
|
||||
- `children` (array of skill entries, for type `submenu`)
|
||||
- `childrenDir` (for type `submenu`, optional):
|
||||
- Absolute path (starts with `/`) or relative to `skillsRoot`
|
||||
- If provided and `children` is missing/empty, the patch will scan this directory for child skills (subfolders containing `menuEntry.json`).
|
||||
|
||||
Example submenu that lists a directory:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "fun_stuff",
|
||||
"type": "submenu",
|
||||
"title": "Fun Stuff",
|
||||
"icon": "resources/icons/fun-stuff.png",
|
||||
"order": 20,
|
||||
"childrenDir": "FunStuff"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### JS provider (`*.js`)
|
||||
Exports one of:
|
||||
|
||||
- `module.exports = function(ctx) { return [ ...entries... ]; }`
|
||||
- `exports.getEntries = function(ctx) { return [ ...entries... ]; }`
|
||||
- `exports.entries = [ ...entries... ]`
|
||||
|
||||
`ctx` includes:
|
||||
- `skillsRoot`
|
||||
- `providersDir`
|
||||
- `log` (function)
|
||||
|
||||
## Conflict rules
|
||||
|
||||
- If a provider entry has the same `id` as a scanned entry, the provider entry wins.
|
||||
- Sorting: by `order` then by title.
|
||||
@@ -12,7 +12,7 @@ When Jibo boots up, the `main-menu-patch.js` module:
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Adding a Simple Skill Button to the Menu
|
||||
### Adding a Simple Skill Button to the Menu (you really should follow PROVIDERS.md at this point)
|
||||
|
||||
1. Create a folder in the `Skills` directory (e.g., `MySkill/`)
|
||||
2. Add a `menuEntry.json` file inside:
|
||||
@@ -53,7 +53,7 @@ When Jibo boots up, the `main-menu-patch.js` module:
|
||||
3. Inside that folder, create subfolders for each skill, each with their own `menuEntry.json`
|
||||
4. A button will appear in the main menu that opens a submenu with those skills!
|
||||
|
||||
## Directory Structure Example
|
||||
## Directory Structure Example (OLD)
|
||||
|
||||
```
|
||||
/opt/jibo/Jibo/Skills/
|
||||
|
||||
@@ -10,11 +10,38 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Skills root directory
|
||||
const SKILLS_ROOT = '/opt/jibo/Jibo/Skills';
|
||||
const menuEntries = require('./menu-entries');
|
||||
const skillsRootUtil = require('./skills-root');
|
||||
|
||||
function strStartsWith(s, prefix) {
|
||||
return (typeof s === 'string') && (s.indexOf(prefix) === 0);
|
||||
}
|
||||
|
||||
function strEndsWith(s, suffix) {
|
||||
if (typeof s !== 'string' || typeof suffix !== 'string') return false;
|
||||
if (suffix.length > s.length) return false;
|
||||
return s.indexOf(suffix, s.length - suffix.length) !== -1;
|
||||
}
|
||||
|
||||
function strIncludes(s, needle) {
|
||||
return (typeof s === 'string') && (s.indexOf(needle) !== -1);
|
||||
}
|
||||
|
||||
// Optional UDP logger (Python logd). Safe fallback if unavailable.
|
||||
let robotLogger = null;
|
||||
try {
|
||||
robotLogger = require('../be/robot-logger');
|
||||
} catch (e) {
|
||||
robotLogger = null;
|
||||
}
|
||||
|
||||
// Skills root directory (configurable)
|
||||
let SKILLS_ROOT = skillsRootUtil.resolveSkillsRoot();
|
||||
|
||||
// Cache for scanned skills
|
||||
let _cachedMenuEntries = null;
|
||||
let _cachedSkillsRoot = null;
|
||||
let _cachedProvidersDir = null;
|
||||
let _submenuConfigs = {};
|
||||
let _dynamicSkillIds = new Set(); // Track dynamic skill IDs for routing
|
||||
let _beInstance = null;
|
||||
@@ -40,6 +67,11 @@ const LOG_FILE = '/tmp/menu-patch.log';
|
||||
function log(msg, ...args) {
|
||||
const line = '[MENU-PATCH] ' + msg + ' ' + args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' ');
|
||||
console.log(line);
|
||||
try {
|
||||
if (robotLogger && typeof robotLogger.raw === 'function') {
|
||||
robotLogger.raw(line);
|
||||
}
|
||||
} catch (e) { }
|
||||
try {
|
||||
fs.appendFileSync(LOG_FILE, new Date().toISOString() + ' ' + line + '\n');
|
||||
} catch (e) {}
|
||||
@@ -48,6 +80,11 @@ function log(msg, ...args) {
|
||||
function warn(msg, ...args) {
|
||||
const line = '[MENU-PATCH WARN] ' + msg + ' ' + args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' ');
|
||||
console.warn(line);
|
||||
try {
|
||||
if (robotLogger && typeof robotLogger.raw === 'function') {
|
||||
robotLogger.raw(line);
|
||||
}
|
||||
} catch (e) { }
|
||||
try {
|
||||
fs.appendFileSync(LOG_FILE, new Date().toISOString() + ' ' + line + '\n');
|
||||
} catch (e) {}
|
||||
@@ -172,59 +209,37 @@ function scanSubmenuChildren(submenuDir) {
|
||||
* Scan the Skills root directory for all menu entries
|
||||
*/
|
||||
function scanAllMenuEntries() {
|
||||
if (_cachedMenuEntries) {
|
||||
if (_cachedMenuEntries && _cachedSkillsRoot === SKILLS_ROOT) {
|
||||
log('Returning cached entries:', _cachedMenuEntries.length);
|
||||
return _cachedMenuEntries;
|
||||
}
|
||||
|
||||
log('Scanning Skills directory:', SKILLS_ROOT);
|
||||
|
||||
_dynamicSkillIds.clear();
|
||||
const entries = [];
|
||||
let children;
|
||||
|
||||
// Check if directory exists
|
||||
if (!fs.existsSync(SKILLS_ROOT)) {
|
||||
warn('Skills directory does not exist:', SKILLS_ROOT);
|
||||
return entries;
|
||||
const res = menuEntries.getMenuEntries({
|
||||
skillsRoot: SKILLS_ROOT,
|
||||
log: function () {
|
||||
try { log.apply(null, arguments); } catch (e) { }
|
||||
}
|
||||
});
|
||||
|
||||
_cachedMenuEntries = res.entries || [];
|
||||
_cachedSkillsRoot = res.skillsRoot;
|
||||
_cachedProvidersDir = res.providersDir;
|
||||
|
||||
// update dynamic id set
|
||||
try {
|
||||
children = fs.readdirSync(SKILLS_ROOT);
|
||||
log('Found', children.length, 'items in Skills directory:', children.join(', '));
|
||||
} catch (e) {
|
||||
warn('Failed to read skills directory:', SKILLS_ROOT, e.message);
|
||||
return entries;
|
||||
if (res.dynamicSkillIds && typeof res.dynamicSkillIds.forEach === 'function') {
|
||||
res.dynamicSkillIds.forEach(function (id) { _dynamicSkillIds.add(id); });
|
||||
}
|
||||
} catch (e) { }
|
||||
|
||||
children.forEach(name => {
|
||||
if (name.startsWith('.') || name === '@be' || name === 'node_modules') {
|
||||
log('Skipping:', name);
|
||||
return;
|
||||
log('Total entries found:', _cachedMenuEntries.length);
|
||||
if (_cachedProvidersDir) {
|
||||
log('Providers dir:', _cachedProvidersDir);
|
||||
}
|
||||
|
||||
const skillDir = path.join(SKILLS_ROOT, name);
|
||||
if (!isDirectory(skillDir)) {
|
||||
log('Not a directory:', name);
|
||||
return;
|
||||
}
|
||||
|
||||
const entry = scanSkillEntry(skillDir, name);
|
||||
if (entry) {
|
||||
entries.push(entry);
|
||||
log('Added entry:', entry.title, '(type:', entry.type + ')');
|
||||
}
|
||||
});
|
||||
|
||||
// Sort by order, then alphabetically
|
||||
entries.sort((a, b) => {
|
||||
if (a.order !== b.order) return a.order - b.order;
|
||||
return a.title.localeCompare(b.title);
|
||||
});
|
||||
|
||||
log('Total entries found:', entries.length);
|
||||
_cachedMenuEntries = entries;
|
||||
return entries;
|
||||
return _cachedMenuEntries;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -427,7 +442,7 @@ function patchMainMenuSkill(be) {
|
||||
}
|
||||
|
||||
// Handle dynamic submenus
|
||||
if (skill && skill.startsWith('dynamic-submenu-')) {
|
||||
if (skill && strStartsWith(skill, 'dynamic-submenu-')) {
|
||||
log('Showing submenu:', skill);
|
||||
const submenuConfig = _submenuConfigs[skill];
|
||||
if (submenuConfig && _jibo && _jibo.face && _jibo.face.views) {
|
||||
@@ -443,12 +458,16 @@ function patchMainMenuSkill(be) {
|
||||
if (result && result.on) {
|
||||
result.on('select', (selection) => {
|
||||
log('Submenu item selected:', selection);
|
||||
if (selection && selection.action && selection.action.data) {
|
||||
const destination = selection.action.data.utterance?.entities?.destination;
|
||||
try {
|
||||
if (selection && selection.action && selection.action.data && selection.action.data.utterance && selection.action.data.utterance.entities) {
|
||||
const destination = selection.action.data.utterance.entities.destination;
|
||||
if (destination) {
|
||||
this.redirectToSkill(destination);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
warn('Submenu selection handler error:', e && e.message ? e.message : e);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -459,7 +478,7 @@ function patchMainMenuSkill(be) {
|
||||
}
|
||||
|
||||
// Handle dynamic skills (prefixed with 'dynamic:')
|
||||
if (skill && skill.startsWith('dynamic:')) {
|
||||
if (skill && strStartsWith(skill, 'dynamic:')) {
|
||||
const actualSkillId = skill.substring(8); // Remove 'dynamic:' prefix
|
||||
log('Launching dynamic skill:', actualSkillId);
|
||||
|
||||
@@ -531,6 +550,14 @@ function patchLoader(jibo, skillsRoot) {
|
||||
log('patchLoader called');
|
||||
_jibo = jibo;
|
||||
|
||||
// Allow caller to override skills root.
|
||||
if (skillsRoot && typeof skillsRoot === 'string') {
|
||||
SKILLS_ROOT = skillsRoot;
|
||||
} else {
|
||||
SKILLS_ROOT = skillsRootUtil.resolveSkillsRoot();
|
||||
}
|
||||
clearCache();
|
||||
|
||||
if (!jibo) {
|
||||
warn('jibo is null/undefined');
|
||||
return;
|
||||
@@ -553,7 +580,7 @@ function patchLoader(jibo, skillsRoot) {
|
||||
log('>>> jibo.loader.load called with:', resourcePath);
|
||||
|
||||
// Check if this is loading a dynamic submenu
|
||||
if (resourcePath && resourcePath.startsWith('dynamic-submenu-')) {
|
||||
if (resourcePath && strStartsWith(resourcePath, 'dynamic-submenu-')) {
|
||||
const submenuConfig = _submenuConfigs[resourcePath];
|
||||
if (submenuConfig) {
|
||||
log('Loading dynamic submenu:', resourcePath);
|
||||
@@ -566,10 +593,10 @@ function patchLoader(jibo, skillsRoot) {
|
||||
|
||||
// Check if this is the main menu - match various possible paths
|
||||
const isMainMenu = resourcePath && (
|
||||
resourcePath.includes('main-menu-verbal.json') ||
|
||||
resourcePath.includes('main-menu.json') ||
|
||||
strIncludes(resourcePath, 'main-menu-verbal.json') ||
|
||||
strIncludes(resourcePath, 'main-menu.json') ||
|
||||
resourcePath === 'resources/views/main-menu-verbal.json' ||
|
||||
resourcePath.endsWith('main-menu-verbal.json')
|
||||
strEndsWith(resourcePath, 'main-menu-verbal.json')
|
||||
);
|
||||
|
||||
if (isMainMenu) {
|
||||
@@ -584,12 +611,22 @@ function patchLoader(jibo, skillsRoot) {
|
||||
}
|
||||
|
||||
log('Original menu config loaded successfully');
|
||||
log('Original button count:', config?.viewConfig?.list?.length || 0);
|
||||
try {
|
||||
const origCount = (config && config.viewConfig && config.viewConfig.list && config.viewConfig.list.length) ? config.viewConfig.list.length : 0;
|
||||
log('Original button count:', origCount);
|
||||
} catch (e) {
|
||||
log('Original button count: (unknown)');
|
||||
}
|
||||
|
||||
// Inject dynamic skills
|
||||
const patchedConfig = injectDynamicSkills(config);
|
||||
|
||||
log('Patched button count:', patchedConfig?.viewConfig?.list?.length || 0);
|
||||
try {
|
||||
const patchedCount = (patchedConfig && patchedConfig.viewConfig && patchedConfig.viewConfig.list && patchedConfig.viewConfig.list.length) ? patchedConfig.viewConfig.list.length : 0;
|
||||
log('Patched button count:', patchedCount);
|
||||
} catch (e) {
|
||||
log('Patched button count: (unknown)');
|
||||
}
|
||||
|
||||
if (callback) callback(null, patchedConfig);
|
||||
});
|
||||
@@ -608,6 +645,8 @@ function patchLoader(jibo, skillsRoot) {
|
||||
*/
|
||||
function clearCache() {
|
||||
_cachedMenuEntries = null;
|
||||
_cachedSkillsRoot = null;
|
||||
_cachedProvidersDir = null;
|
||||
_submenuConfigs = {};
|
||||
_dynamicSkillIds.clear();
|
||||
}
|
||||
|
||||
72
V3.1/build/opt/jibo/Jibo/Skills/@be/be/menu/menu-entries.js
Normal file
@@ -0,0 +1,72 @@
|
||||
"use strict";
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const skillsRoot = require("./skills-root");
|
||||
const scanner = require("./menu-entry-scanner");
|
||||
const providers = require("./menu-providers");
|
||||
|
||||
function isDirectory(p) {
|
||||
try {
|
||||
return fs.statSync(p).isDirectory();
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function expandSubmenuChildren(entries, root, opts) {
|
||||
(entries || []).forEach(function (e) {
|
||||
if (!e || e.type !== "submenu") return;
|
||||
|
||||
if (e.children && e.children.length) return;
|
||||
if (!e.childrenDir) return;
|
||||
|
||||
let dir = e.childrenDir;
|
||||
if (typeof dir !== "string" || dir.trim().length === 0) return;
|
||||
dir = dir.trim();
|
||||
|
||||
const resolved = (dir.charAt(0) === "/") ? dir : path.join(root, dir);
|
||||
if (!isDirectory(resolved)) {
|
||||
if (opts && opts.log) opts.log("submenu childrenDir not a directory", e.id, resolved);
|
||||
e.children = [];
|
||||
return;
|
||||
}
|
||||
|
||||
if (opts && opts.log) opts.log("submenu childrenDir scan", e.id, resolved);
|
||||
e.children = scanner.scanSubmenuChildren(resolved, { log: opts && opts.log, defaultIcon: opts && opts.defaultIcon, defaultColor: opts && opts.defaultColor, defaultOrder: opts && opts.defaultOrder });
|
||||
});
|
||||
}
|
||||
|
||||
function getMenuEntries(opts) {
|
||||
opts = opts || {};
|
||||
const root = skillsRoot.resolveSkillsRoot(opts.skillsRoot);
|
||||
const providerDir = opts.providersDir || skillsRoot.providersDirForRoot(root);
|
||||
|
||||
const scanned = scanner.scanAllMenuEntries(root, {
|
||||
log: opts.log,
|
||||
defaultIcon: opts.defaultIcon,
|
||||
defaultColor: opts.defaultColor,
|
||||
defaultOrder: opts.defaultOrder
|
||||
});
|
||||
|
||||
const provided = providers.loadProviderEntries(providerDir, { log: opts.log, skillsRoot: root, providersDir: providerDir });
|
||||
const merged = providers.mergeById(scanned, provided);
|
||||
|
||||
// Allow provider-defined submenus to be backed by an arbitrary directory.
|
||||
// Example: { type: "submenu", id: "fun", childrenDir: "FunStuff" }
|
||||
expandSubmenuChildren(merged, root, opts);
|
||||
|
||||
const dynamicIds = scanner.collectDynamicSkillIds(merged);
|
||||
|
||||
return {
|
||||
skillsRoot: root,
|
||||
providersDir: providerDir,
|
||||
entries: merged,
|
||||
dynamicSkillIds: dynamicIds
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getMenuEntries: getMenuEntries
|
||||
};
|
||||
@@ -0,0 +1,150 @@
|
||||
"use strict";
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
function safeReadJson(filePath, logFn) {
|
||||
try {
|
||||
const txt = fs.readFileSync(filePath, "utf8");
|
||||
if (!txt || txt.trim().length === 0) return null;
|
||||
return JSON.parse(txt);
|
||||
} catch (e) {
|
||||
if (logFn) logFn("safeReadJson failed", filePath, e && e.message ? e.message : e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function isDirectory(p) {
|
||||
try {
|
||||
return fs.statSync(p).isDirectory();
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function scanSkillEntry(skillDir, folderName, opts) {
|
||||
opts = opts || {};
|
||||
const menuJsonPath = path.join(skillDir, "menuEntry.json");
|
||||
const menuJson = safeReadJson(menuJsonPath, opts.log);
|
||||
|
||||
if (!menuJson) return null;
|
||||
if (menuJson.hidden === true) return null;
|
||||
|
||||
const entry = {
|
||||
id: folderName,
|
||||
type: menuJson.type || "skill",
|
||||
title: menuJson.title || menuJson.label || folderName,
|
||||
icon: menuJson.icon || menuJson.iconSrc || (opts.defaultIcon || "resources/icons/settings.png"),
|
||||
color: menuJson.color || menuJson.colors || (opts.defaultColor || "default"),
|
||||
description: menuJson.description || "",
|
||||
path: skillDir,
|
||||
order: typeof menuJson.order === "number" ? menuJson.order : (typeof opts.defaultOrder === "number" ? opts.defaultOrder : 100),
|
||||
skillId: menuJson.skillId || folderName,
|
||||
submenuTitle: menuJson.submenuTitle || menuJson.title || folderName
|
||||
};
|
||||
|
||||
if (entry.type === "submenu") {
|
||||
entry.children = scanSubmenuChildren(skillDir, opts);
|
||||
}
|
||||
|
||||
// Legacy support
|
||||
if (menuJson.isSubmenu === true && entry.type !== "submenu") {
|
||||
entry.type = "submenu";
|
||||
entry.children = scanSubmenuChildren(skillDir, opts);
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
function scanSubmenuChildren(submenuDir, opts) {
|
||||
const children = [];
|
||||
let items;
|
||||
try {
|
||||
items = fs.readdirSync(submenuDir);
|
||||
} catch (e) {
|
||||
return children;
|
||||
}
|
||||
|
||||
items.forEach(function (name) {
|
||||
if (!name || name.charAt(0) === ".") return;
|
||||
if (name === "@be" || name === "node_modules") return;
|
||||
|
||||
const childPath = path.join(submenuDir, name);
|
||||
if (!isDirectory(childPath)) return;
|
||||
|
||||
const entry = scanSkillEntry(childPath, name, opts);
|
||||
if (entry && entry.type === "skill") {
|
||||
children.push(entry);
|
||||
}
|
||||
});
|
||||
|
||||
children.sort(function (a, b) {
|
||||
if (a.order !== b.order) return a.order - b.order;
|
||||
return String(a.title || "").localeCompare(String(b.title || ""));
|
||||
});
|
||||
|
||||
return children;
|
||||
}
|
||||
|
||||
function scanAllMenuEntries(skillsRoot, opts) {
|
||||
opts = opts || {};
|
||||
const entries = [];
|
||||
|
||||
if (!skillsRoot || !fs.existsSync(skillsRoot)) {
|
||||
if (opts.log) opts.log("skills root missing", skillsRoot);
|
||||
return entries;
|
||||
}
|
||||
|
||||
let children;
|
||||
try {
|
||||
children = fs.readdirSync(skillsRoot);
|
||||
} catch (e) {
|
||||
if (opts.log) opts.log("failed to read skills root", skillsRoot, e && e.message ? e.message : e);
|
||||
return entries;
|
||||
}
|
||||
|
||||
children.forEach(function (name) {
|
||||
if (!name || name.charAt(0) === ".") return;
|
||||
if (name === "@be" || name === "node_modules") return;
|
||||
|
||||
const skillDir = path.join(skillsRoot, name);
|
||||
if (!isDirectory(skillDir)) return;
|
||||
|
||||
const entry = scanSkillEntry(skillDir, name, opts);
|
||||
if (entry) entries.push(entry);
|
||||
});
|
||||
|
||||
entries.sort(function (a, b) {
|
||||
if (a.order !== b.order) return a.order - b.order;
|
||||
return String(a.title || "").localeCompare(String(b.title || ""));
|
||||
});
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
function collectDynamicSkillIds(entries) {
|
||||
const ids = new Set();
|
||||
|
||||
(entries || []).forEach(function (e) {
|
||||
if (!e) return;
|
||||
if (e.type === "skill") {
|
||||
if (e.skillId) ids.add(e.skillId);
|
||||
if (e.id) ids.add(e.id);
|
||||
}
|
||||
if (e.type === "submenu" && e.children && e.children.length) {
|
||||
e.children.forEach(function (c) {
|
||||
if (!c) return;
|
||||
if (c.skillId) ids.add(c.skillId);
|
||||
if (c.id) ids.add(c.id);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return ids;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
scanAllMenuEntries: scanAllMenuEntries,
|
||||
scanSubmenuChildren: scanSubmenuChildren,
|
||||
collectDynamicSkillIds: collectDynamicSkillIds
|
||||
};
|
||||
115
V3.1/build/opt/jibo/Jibo/Skills/@be/be/menu/menu-providers.js
Normal file
@@ -0,0 +1,115 @@
|
||||
"use strict";
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
function isDirectory(p) {
|
||||
try {
|
||||
return fs.statSync(p).isDirectory();
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function safeReadJson(filePath, logFn) {
|
||||
try {
|
||||
const txt = fs.readFileSync(filePath, "utf8");
|
||||
if (!txt || txt.trim().length === 0) return null;
|
||||
return JSON.parse(txt);
|
||||
} catch (e) {
|
||||
if (logFn) logFn("provider json read failed", filePath, e && e.message ? e.message : e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function loadFromJson(filePath, ctx) {
|
||||
const obj = safeReadJson(filePath, ctx && ctx.log);
|
||||
if (!obj) return [];
|
||||
|
||||
if (Array.isArray(obj)) return obj;
|
||||
if (obj && Array.isArray(obj.entries)) return obj.entries;
|
||||
if (obj && Array.isArray(obj.buttons)) return obj.buttons;
|
||||
return [];
|
||||
}
|
||||
|
||||
function loadFromJs(filePath, ctx) {
|
||||
try {
|
||||
// eslint-disable-next-line global-require, import/no-dynamic-require
|
||||
const mod = require(filePath);
|
||||
if (!mod) return [];
|
||||
if (typeof mod === "function") return mod(ctx) || [];
|
||||
if (typeof mod.getEntries === "function") return mod.getEntries(ctx) || [];
|
||||
if (Array.isArray(mod.entries)) return mod.entries;
|
||||
return [];
|
||||
} catch (e) {
|
||||
if (ctx && ctx.log) ctx.log("provider js load failed", filePath, e && e.message ? e.message : e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function loadProviderEntries(providersDir, ctx) {
|
||||
const out = [];
|
||||
|
||||
if (!providersDir || !fs.existsSync(providersDir) || !isDirectory(providersDir)) {
|
||||
return out;
|
||||
}
|
||||
|
||||
let files;
|
||||
try {
|
||||
files = fs.readdirSync(providersDir);
|
||||
} catch (e) {
|
||||
return out;
|
||||
}
|
||||
|
||||
files.sort();
|
||||
|
||||
files.forEach(function (name) {
|
||||
if (!name || name.charAt(0) === ".") return;
|
||||
const full = path.join(providersDir, name);
|
||||
if (isDirectory(full)) return;
|
||||
|
||||
if (name.indexOf(".json", name.length - 5) !== -1) {
|
||||
const entries = loadFromJson(full, ctx);
|
||||
entries.forEach(function (e) { out.push(e); });
|
||||
return;
|
||||
}
|
||||
|
||||
if (name.indexOf(".js", name.length - 3) !== -1) {
|
||||
const entries = loadFromJs(full, ctx);
|
||||
entries.forEach(function (e) { out.push(e); });
|
||||
}
|
||||
});
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
// Merge provider entries into scanned entries by id.
|
||||
// Provider entries win on conflicts.
|
||||
function mergeById(scannedEntries, providerEntries) {
|
||||
const byId = {};
|
||||
|
||||
(scannedEntries || []).forEach(function (e) {
|
||||
if (!e || !e.id) return;
|
||||
byId[e.id] = e;
|
||||
});
|
||||
|
||||
(providerEntries || []).forEach(function (p) {
|
||||
if (!p || !p.id) return;
|
||||
byId[p.id] = p;
|
||||
});
|
||||
|
||||
const merged = Object.keys(byId).map(function (k) { return byId[k]; });
|
||||
merged.sort(function (a, b) {
|
||||
const ao = typeof a.order === "number" ? a.order : 100;
|
||||
const bo = typeof b.order === "number" ? b.order : 100;
|
||||
if (ao !== bo) return ao - bo;
|
||||
return String(a.title || a.label || a.id).localeCompare(String(b.title || b.label || b.id));
|
||||
});
|
||||
|
||||
return merged;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
loadProviderEntries: loadProviderEntries,
|
||||
mergeById: mergeById
|
||||
};
|
||||
31
V3.1/build/opt/jibo/Jibo/Skills/@be/be/menu/skills-root.js
Normal file
@@ -0,0 +1,31 @@
|
||||
"use strict";
|
||||
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
|
||||
const DEFAULT_ROOT = "/opt/jibo/Jibo/Skills";
|
||||
|
||||
function resolveSkillsRoot(overrideRoot) {
|
||||
if (overrideRoot && typeof overrideRoot === "string") return overrideRoot;
|
||||
if (process && process.env && process.env.JIBO_SKILLS_ROOT) return process.env.JIBO_SKILLS_ROOT;
|
||||
return DEFAULT_ROOT;
|
||||
}
|
||||
|
||||
function providersDirForRoot(skillsRoot) {
|
||||
// Keep providers inside Skills so they can be synced easily.
|
||||
// Default: /opt/jibo/Jibo/Skills/@be/menu-entries.d
|
||||
// Legacy fallback: /opt/jibo/Jibo/Skills/@be/menu-providers.d
|
||||
const v2 = path.join(skillsRoot, "@be", "menu-entries.d");
|
||||
const v1 = path.join(skillsRoot, "@be", "menu-providers.d");
|
||||
try {
|
||||
if (fs.existsSync(v2)) return v2;
|
||||
if (fs.existsSync(v1)) return v1;
|
||||
} catch (e) { /* ignore */ }
|
||||
return v2;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
DEFAULT_ROOT: DEFAULT_ROOT,
|
||||
resolveSkillsRoot: resolveSkillsRoot,
|
||||
providersDirForRoot: providersDirForRoot
|
||||
};
|
||||
78
V3.1/build/opt/jibo/Jibo/Skills/@be/be/node_modules/@be/radio/node_modules/jibo-radio/LocalRadioPlayer.js
generated
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
const EventEmitter = require('events');
|
||||
|
||||
class LocalRadioPlayer extends EventEmitter {
|
||||
constructor() {
|
||||
super();
|
||||
// These are example streams. You will need to set up your own
|
||||
// Icecast/HTTP streams and replace these URLs with your server's IP.
|
||||
this._stations = {
|
||||
'Rock': 'http://192.168.1.100:8000/rock',
|
||||
'Pop': 'http://192.168.1.100:8000/pop',
|
||||
'Jazz': 'http://192.168.1.100:8000/jazz',
|
||||
'Classical': 'http://192.168.1.100:8000/classical',
|
||||
'Electronic': 'http://192.168.1.100:8000/electronic',
|
||||
'Hip-Hop': 'http://192.168.1.100:8000/hiphop'
|
||||
};
|
||||
this._audio = null;
|
||||
console.log("LocalRadioPlayer initialized");
|
||||
}
|
||||
|
||||
getStations(options) {
|
||||
console.log("LocalRadioPlayer: getStations called");
|
||||
const stationList = Object.keys(this._stations).map(genre => ({
|
||||
name: genre,
|
||||
id: genre
|
||||
}));
|
||||
return Promise.resolve(stationList);
|
||||
}
|
||||
|
||||
play(stationData) {
|
||||
console.log(`LocalRadioPlayer: play called with`, stationData);
|
||||
if (this._audio) {
|
||||
this._audio.pause();
|
||||
}
|
||||
const streamUrl = this._stations[stationData.id];
|
||||
if (!streamUrl) {
|
||||
const err = new Error(`Station ${stationData.id} not found.`);
|
||||
console.error("LocalRadioPlayer Error:", err);
|
||||
this.emit('error', err);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`LocalRadioPlayer: Playing stream from ${streamUrl}`);
|
||||
this._audio = new Audio(streamUrl);
|
||||
this._audio.addEventListener('error', (e) => {
|
||||
console.error('LocalRadioPlayer Audio Error:', e);
|
||||
this.emit('error', new Error('Audio playback failed.'));
|
||||
});
|
||||
|
||||
this._audio.play()
|
||||
.then(() => {
|
||||
console.log("LocalRadioPlayer: Playback started.");
|
||||
this.emit('song-data', {
|
||||
title: `Streaming ${stationData.id}`,
|
||||
artist: 'Local Radio',
|
||||
albumArt: '' // No artwork for local streams
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
console.error("LocalRadioPlayer Playback Error:", err);
|
||||
this.emit('error', err);
|
||||
});
|
||||
}
|
||||
|
||||
stop() {
|
||||
console.log("LocalRadioPlayer: stop called");
|
||||
if (this._audio) {
|
||||
this._audio.pause();
|
||||
this._audio = null;
|
||||
}
|
||||
}
|
||||
|
||||
resizeArtwork(options) {
|
||||
// Not applicable for local streaming
|
||||
return Promise.resolve('');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = LocalRadioPlayer;
|
||||
73
V3.1/build/opt/jibo/Jibo/Skills/@be/be/node_modules/jibo-radio/LocalRadioPlayer.js
generated
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
const EventEmitter = require('events');
|
||||
|
||||
class LocalRadioPlayer extends EventEmitter {
|
||||
constructor() {
|
||||
super();
|
||||
// These are example streams. You will need to set up your own
|
||||
// Icecast/HTTP streams and replace these URLs.
|
||||
this._stations = {
|
||||
'My Station': 'http://192.168.1.5:6767/Ninja%20Tuna.mp3'
|
||||
};
|
||||
this._audio = null;
|
||||
console.log("LocalRadioPlayer initialized");
|
||||
}
|
||||
|
||||
getStations(options) {
|
||||
console.log("LocalRadioPlayer: getStations called");
|
||||
const stationList = Object.keys(this._stations).map(genre => ({
|
||||
name: genre,
|
||||
id: genre
|
||||
}));
|
||||
return Promise.resolve(stationList);
|
||||
}
|
||||
|
||||
play(stationData) {
|
||||
console.log(`LocalRadioPlayer: play called with`, stationData);
|
||||
if (this._audio) {
|
||||
this._audio.pause();
|
||||
}
|
||||
const streamUrl = this._stations[stationData.id];
|
||||
if (!streamUrl) {
|
||||
const err = new Error(`Station ${stationData.id} not found.`);
|
||||
console.error("LocalRadioPlayer Error:", err);
|
||||
this.emit('error', err);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`LocalRadioPlayer: Playing stream from ${streamUrl}`);
|
||||
this._audio = new Audio(streamUrl);
|
||||
this._audio.addEventListener('error', (e) => {
|
||||
console.error('LocalRadioPlayer Audio Error:', e);
|
||||
this.emit('error', new Error('Audio playback failed.'));
|
||||
});
|
||||
|
||||
this._audio.play()
|
||||
.then(() => {
|
||||
console.log("LocalRadioPlayer: Playback started.");
|
||||
this.emit('song-data', {
|
||||
title: `Streaming ${stationData.id}`,
|
||||
artist: 'Local Radio',
|
||||
albumArt: '' // No artwork for local streams
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
console.error("LocalRadioPlayer Playback Error:", err);
|
||||
this.emit('error', err);
|
||||
});
|
||||
}
|
||||
|
||||
stop() {
|
||||
console.log("LocalRadioPlayer: stop called");
|
||||
if (this._audio) {
|
||||
this._audio.pause();
|
||||
this._audio = null;
|
||||
}
|
||||
}
|
||||
|
||||
resizeArtwork(options) {
|
||||
// Not applicable for local streaming
|
||||
return Promise.resolve('');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = LocalRadioPlayer;
|
||||
1028
V3.1/build/opt/jibo/Jibo/Skills/@be/be/node_modules/jibo-radio/lib/jibo-radio.js
generated
vendored
|
After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 173 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 177 KiB |
BIN
V3.1/build/opt/jibo/Jibo/Skills/@be/be/resources/JiboSplash.kra
Normal file
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 24 KiB |
BIN
V3.1/build/opt/jibo/Jibo/Skills/@be/be/resources/jibo_logo.png
Normal file
|
After Width: | Height: | Size: 111 KiB |
BIN
V3.1/build/opt/jibo/Jibo/Skills/@be/be/resources/jibo_logo.png~
Normal file
|
After Width: | Height: | Size: 107 KiB |
BIN
V3.1/build/opt/jibo/Jibo/Skills/@be/be/resources/jibo_logo1.png
Normal file
|
After Width: | Height: | Size: 76 KiB |
@@ -0,0 +1,13 @@
|
||||
[
|
||||
{
|
||||
"id": "__example__",
|
||||
"type": "submenu",
|
||||
"title": "Example (Entry Dir)",
|
||||
"submenuTitle": "Example Submenu",
|
||||
"icon": "resources/icons/settings.png",
|
||||
"color": "teal",
|
||||
"description": "Example submenu from @be/menu-entries.d (scans childrenDir for menuEntry.json)",
|
||||
"order": 5,
|
||||
"childrenDir": "TestSubMenuA"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,12 @@
|
||||
[
|
||||
{
|
||||
"id": "__submenu_test__",
|
||||
"type": "submenu",
|
||||
"title": "Test Folder",
|
||||
"submenuTitle": "Test Submenu",
|
||||
"icon": "resources/icons/fun-stuff.png",
|
||||
"color": "purple",
|
||||
"order": 1,
|
||||
"childrenDir": "FunStuffTest"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"type": "skill",
|
||||
"title": "Clock One (launch jibo-tbd)",
|
||||
"icon": "resources/icons/clock.png",
|
||||
"color": "blue",
|
||||
"order": 20,
|
||||
"skillId": "jibo-tbd"
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"type": "skill",
|
||||
"title": "Fun One (launch jibo-tbd)",
|
||||
"icon": "resources/icons/fun-stuff.png",
|
||||
"color": "orange",
|
||||
"order": 10,
|
||||
"skillId": "jibo-tbd"
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"hidden": true,
|
||||
"title": "FunStuffTest Root"
|
||||
}
|
||||
84
V3.1/build/opt/jibo/Jibo/Skills/LOGGING.md
Normal file
@@ -0,0 +1,84 @@
|
||||
# Skills logging (robot)
|
||||
|
||||
This modded version of the JiboOS includes a tiny “always works” logging stack for the robot’s old BusyBox + Python 2.7 environment:
|
||||
|
||||
- **UDP log daemon**: receives log messages and appends to a file
|
||||
- **Web log panel**: lets you watch the same logfile live in a browser
|
||||
- **Node helper**: a tiny client so JS skills can log without pain
|
||||
|
||||
Default logfile:
|
||||
- `/tmp/jibo-skills.log`
|
||||
|
||||
Default ports:
|
||||
- log daemon UDP: `15140`
|
||||
- web panel HTTP: `15150`
|
||||
|
||||
## 1) Start services on the robot - if you used the installer they should be enabled automatically
|
||||
|
||||
### Log daemon (required)
|
||||
Init.d:
|
||||
- `/etc/init.d/jibo-skills-logd start`
|
||||
- `/etc/init.d/jibo-skills-logd status`
|
||||
|
||||
### Web panel (optional)
|
||||
Init.d:
|
||||
- `/etc/init.d/jibo-skills-logpanel start`
|
||||
- `/etc/init.d/jibo-skills-logpanel status`
|
||||
|
||||
Open in a browser:
|
||||
- `http://<robot-ip>:15150/`
|
||||
|
||||
## 2) Send logs from programs
|
||||
|
||||
### Node.js (skills)
|
||||
Use the helper:
|
||||
|
||||
- `const rlog = require('/opt/jibo/Jibo/Skills/@be/be/be/robot-logger');`
|
||||
|
||||
API:
|
||||
- `rlog.info(tag, msg, data)`
|
||||
- `rlog.warn(tag, msg, data)`
|
||||
- `rlog.error(tag, msg, data)`
|
||||
- `rlog.debug(tag, msg, data)`
|
||||
- `rlog.raw(line)` (sends a plain text line)
|
||||
|
||||
Examples:
|
||||
- `rlog.info('menu', 'opened main menu')`
|
||||
- `rlog.error('radio', 'failed to tune', { station: freq, err: String(e) })`
|
||||
|
||||
One-liner test:
|
||||
- `node -e "require('/opt/jibo/Jibo/Skills/@be/be/be/robot-logger').info('node-test','hello testajhdgjhasgjd',{x:1})"`
|
||||
|
||||
### Python 2.7
|
||||
Send a JSON log packet via UDP:
|
||||
- could be broken atm idk
|
||||
- `python -c 'import socket, time; s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM); s.sendto("{\\\"tag\\\":\\\"py-test\\\",\\\"level\\\":\\\"info\\\",\\\"msg\\\":\\\"hello\\\",\\\"data\\\":{\\\"t\\\":%d}}"%int(time.time()),("127.0.0.1",15140))'`
|
||||
|
||||
### Shell
|
||||
This bypasses UDP and just appends to the logfile (still useful to verify the panel is tailing):
|
||||
|
||||
- `echo "RAW $(date) hello" >> /tmp/jibo-skills.log`
|
||||
|
||||
## 3) View logs
|
||||
|
||||
- `tail -f /tmp/jibo-skills.log`
|
||||
|
||||
## 4) Configuration (env vars)
|
||||
|
||||
Log daemon:
|
||||
- `JIBO_LOGD_HOST` (default `127.0.0.1`)
|
||||
- `JIBO_LOGD_PORT` (default `15140`)
|
||||
- `JIBO_LOGD_FILE` (default `/tmp/jibo-skills.log`)
|
||||
|
||||
Web panel:
|
||||
- `JIBO_LOGPANEL_BIND` (default `0.0.0.0`)
|
||||
- `JIBO_LOGPANEL_PORT` (default `15150`)
|
||||
|
||||
Node client:
|
||||
- `JIBO_LOGD_HOST` / `JIBO_LOGD_PORT`
|
||||
|
||||
## Notes
|
||||
|
||||
- If the web panel shows `(polling)`, thats ok it means the browser doesn’t support SSE and it’s using `/tail` polling instead.
|
||||
- If the panel is blank, confirm `/tail` works:
|
||||
- `wget -qO- "http://127.0.0.1:15150/tail?pos=0&max=5000"`
|
||||
80
V3.1/build/opt/jibo/Jibo/Skills/MODULARITY_PLAN.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# Modularity ground-work (Skills FS)
|
||||
|
||||
Target: make the main menu + skill launching as modular as possible while staying compatible with the existing precompiled BE bundle.
|
||||
|
||||
## What you have today (important choke points)
|
||||
|
||||
### 1) BE is a big precompiled bundle
|
||||
- The BE “brain” is shipped as [@be/be/index.js](@be/be/index.js) (UMD/browserify bundle).
|
||||
- A number of runtime steps are already split into editable modules under [@be/be/be/](@be/be/be/):
|
||||
- [@be/be/be/postinit.js](@be/be/be/postinit.js) is especially valuable because it runs after init and is easy to patch.
|
||||
|
||||
### 2) Menu is already semi-modular (via patch)
|
||||
- The menu injection is wired in here: [@be/be/be/postinit.js](@be/be/be/postinit.js)
|
||||
- The injection logic is here: [@be/be/menu/main-menu-patch.js](@be/be/menu/main-menu-patch.js)
|
||||
- There are two alternate menu implementations that aren’t currently the default path:
|
||||
- [@be/be/menu/skills-scanner.js](@be/be/menu/skills-scanner.js)
|
||||
- [@be/be/menu/menu-manager.js](@be/be/menu/menu-manager.js)
|
||||
|
||||
### 3) Hard limit: “menu entry exists” ≠ “skill is loadable”
|
||||
- BE loads skills listed in [@be/be/package.json](@be/be/package.json) → `jibo.skills[]`.
|
||||
- So the current menu patch can *show* buttons for new skills, but launching them will fail unless BE can actually load them.
|
||||
|
||||
## What “max modular menu” realistically means
|
||||
|
||||
You can make the **menu definition** fully modular without touching the BE bundle. Making **skill loading** fully modular is harder because the bundle pre-bakes skill modules.
|
||||
|
||||
## Step-by-step path (least risky first)
|
||||
|
||||
### Phase A — Make menu config fully modular (low risk)
|
||||
1. **Single source of truth for scanning**
|
||||
- Stop duplicating scanner logic between `main-menu-patch.js` and `skills-scanner.js`.
|
||||
- Export a stable `scanSkills(rootDir, options)` function that returns the exact menu schema expected by the main-menu skill.
|
||||
|
||||
2. **Make Skills root configurable**
|
||||
- Replace hard-coded `/opt/jibo/Jibo/Skills` with:
|
||||
- env var `JIBO_SKILLS_ROOT`, else
|
||||
- fallback `/opt/jibo/Jibo/Skills`.
|
||||
|
||||
3. **Allow multiple menu providers (plugin style)**
|
||||
- Add an entries directory, e.g. `/opt/jibo/Jibo/Skills/@be/menu-entries.d/`.
|
||||
- Providers can be:
|
||||
- `.js` files that export `getEntries()`
|
||||
- `.json` static entries
|
||||
- Patch merges provider entries + scanned `menuEntry.json` entries.
|
||||
|
||||
4. **Move icon/color policy into config**
|
||||
- Keep defaults, but load overrides from a JSON config file (ex: `@be/be/menu/menu-config.json`).
|
||||
|
||||
### Phase B — Add Python-friendly menu generation (low/medium risk)
|
||||
1. Implement a “generated menu file” contract:
|
||||
- Python script writes a JSON file, e.g. `/tmp/jibo-menu.generated.json`.
|
||||
- Menu patch reads it (if present) and merges into the menu.
|
||||
2. This lets you write menu logic in Python 2.7 without fighting JS runtime quirks.
|
||||
|
||||
### Phase C — Make skill launching modular (hard)
|
||||
This is the real blocker for “true modularity”. Options:
|
||||
|
||||
1. **Soft approach** (menu remains modular; launching only works for known skills)
|
||||
- Accept that new entries are “shortcuts” to existing/registered skills.
|
||||
|
||||
2. **Medium approach** (dynamic skill registry)
|
||||
- Maintain a small registry mapping `skillId -> folder path` by scanning `/opt/jibo/Jibo/Skills`.
|
||||
- On launch, route through a single “launcher” skill (registered in BE) that loads content/assets from disk.
|
||||
|
||||
3. **Hard approach** (replace BE bundle / build chain)
|
||||
- Rebuild BE so skills are not baked into the browserify bundle.
|
||||
- This is the cleanest long-term solution but requires toolchain alignment and more invasive changes.
|
||||
|
||||
## Logging groundwork (so you can debug all of the above)
|
||||
|
||||
To make JS/Python logging painless on a BusyBox robot, the repo now includes a tiny UDP log daemon + init.d template:
|
||||
|
||||
- tools/robot/logd/jibo_logd.py
|
||||
- tools/robot/init.d/jibo-skills-logd
|
||||
|
||||
This gives you a single log file you can tail on robot (default `/tmp/jibo-skills.log`) and a trivial Node client under:
|
||||
|
||||
- @be/be/be/robot-logger.js
|
||||
|
||||
Next step is to switch noisy modules (menu patch, loaders) to send logs to the daemon.
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"type": "submenu",
|
||||
"title": "Test Submenu",
|
||||
"submenuTitle": "Test Skills",
|
||||
"icon": "resources/icons/fun-stuff.png",
|
||||
"color": "purple",
|
||||
"description": "A submenu containing additional test skills",
|
||||
"order": 20
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"type": "skill",
|
||||
"title": "Test Skill B",
|
||||
"icon": "resources/icons/surprise.png",
|
||||
"color": "green",
|
||||
"skillId": "testSkillB",
|
||||
"description": "A skill inside a submenu",
|
||||
"order": 1
|
||||
}
|
||||
61
V3.1/build/opt/jibo/Jibo/Skills/jibo-tbd/LocalRadioPlayer.js
Normal file
@@ -0,0 +1,61 @@
|
||||
const EventEmitter = require('events');
|
||||
|
||||
class LocalRadioPlayer extends EventEmitter {
|
||||
constructor() {
|
||||
super();
|
||||
this._stations = {
|
||||
'Rock': 'http://localhost:8000/rock',
|
||||
'Pop': 'http://localhost:8000/pop',
|
||||
'Jazz': 'http://localhost:8000/jazz',
|
||||
'Classical': 'http://localhost:8000/classical',
|
||||
'Electronic': 'http://localhost:8000/electronic',
|
||||
'Hip-Hop': 'http://localhost:8000/hiphop'
|
||||
};
|
||||
this._audio = null;
|
||||
}
|
||||
|
||||
getStations(options) {
|
||||
return Promise.resolve(Object.keys(this._stations).map(genre => ({
|
||||
name: genre,
|
||||
id: genre
|
||||
})));
|
||||
}
|
||||
|
||||
play(stationData) {
|
||||
if (this._audio) {
|
||||
this._audio.pause();
|
||||
}
|
||||
const streamUrl = this._stations[stationData.id];
|
||||
if (!streamUrl) {
|
||||
this.emit('error', new Error(`Station ${stationData.id} not found.`));
|
||||
return;
|
||||
}
|
||||
|
||||
this._audio = new Audio(streamUrl);
|
||||
this._audio.play()
|
||||
.then(() => {
|
||||
this.emit('song-data', {
|
||||
title: `Streaming ${stationData.id}`,
|
||||
artist: 'Local Radio',
|
||||
albumArt: ''
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
this.emit('error', err);
|
||||
});
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (this._audio) {
|
||||
this._audio.pause();
|
||||
this._audio = null;
|
||||
}
|
||||
}
|
||||
|
||||
resizeArtwork(options) {
|
||||
// Not applicable for local streaming without metadata
|
||||
return Promise.resolve('');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = LocalRadioPlayer;
|
||||
@@ -1,2 +1,11 @@
|
||||
let thisPackage = require('./package.json');
|
||||
module.exports = thisPackage.version;
|
||||
const LocalRadioPlayer = require('./LocalRadioPlayer');
|
||||
|
||||
/**
|
||||
* @returns {RadioPlayer}
|
||||
*
|
||||
* i tried to make the radio work as well, coldnt get it to work :(
|
||||
*/
|
||||
module.exports = function createRadio() {
|
||||
return new LocalRadioPlayer();
|
||||
};
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"type": "skill",
|
||||
"title": "Test Skill A",
|
||||
"icon": "resources/icons/settings.png",
|
||||
"color": "blue",
|
||||
"skillId": "testSkillA",
|
||||
"description": "A test skill to demonstrate menu integration",
|
||||
"order": 10
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"type": "skill",
|
||||
"title": "AAAAAAAAA",
|
||||
"icon": "resources/icons/gallery.png",
|
||||
"color": "blue",
|
||||
"skillId": "testSkillA",
|
||||
"description": "A test skill to demonstrate menu integration",
|
||||
"order": 11
|
||||
|
||||
}
|
||||
5
V3.1/build/opt/jibo/Jibo/Skills/tomove.md
Normal file
@@ -0,0 +1,5 @@
|
||||
^BG
|
||||
/tools/robot/init.d/ -> /etc/init.d/
|
||||
/tools/robot/logd/ -> /opt/jibo/Jibo/Skills/tools/robot/logd/
|
||||
/tools/robot/logpanel/ -> /opt/jibo/Jibo/Skills/tools/robot/logpanel/
|
||||
^ND
|
||||
55
V3.1/build/opt/jibo/Jibo/Skills/tools/robot/logd/README.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# jibo-skills-logd
|
||||
|
||||
A tiny UDP logging daemon intended for **very old BusyBox + Python 2.7** robot environments.
|
||||
|
||||
## What it gives you
|
||||
|
||||
- A single place where all your scripts can log (JS, Python, shell)
|
||||
- A single file you can `tail -f` on robot
|
||||
- Minimal moving parts (no external deps)
|
||||
|
||||
## Files
|
||||
|
||||
- `tools/robot/logd/jibo_logd.py` — Python daemon (UDP → append to file)
|
||||
- `tools/robot/init.d/jibo-skills-logd` — init.d service template
|
||||
|
||||
## Quick test (on robot)
|
||||
|
||||
Start the daemon in foreground:
|
||||
|
||||
- `python /opt/jibo/Jibo/Skills/tools/robot/logd/jibo_logd.py --host 127.0.0.1 --port 15140 --logfile /tmp/jibo-skills.log`
|
||||
|
||||
Send a message:
|
||||
|
||||
- `echo '{"tag":"test","level":"info","msg":"hello"}' | nc -u -w1 127.0.0.1 15140`
|
||||
|
||||
View:
|
||||
|
||||
- `tail -f /tmp/jibo-skills.log`
|
||||
|
||||
## Using from Node
|
||||
|
||||
In your skill code:
|
||||
|
||||
- `const rlog = require('@be/be/be/robot-logger');`
|
||||
- `rlog.info('menu', 'injected entries', {count: 12});`
|
||||
|
||||
Env vars (optional):
|
||||
|
||||
- `JIBO_LOGD_HOST` (default `127.0.0.1`)
|
||||
- `JIBO_LOGD_PORT` (default `15140`)
|
||||
|
||||
## Live web panel (optional)
|
||||
|
||||
There is also a tiny HTTP panel that streams the same logfile in real time (SSE).
|
||||
|
||||
- Script: `tools/robot/logpanel/jibo_logpanel.py`
|
||||
- Init script: `/init.d/jibo-skills-logpanel`
|
||||
|
||||
Run (foreground):
|
||||
|
||||
- `python /opt/jibo/Jibo/Skills/tools/robot/logpanel/jibo_logpanel.py --bind 0.0.0.0 --port 15150 --logfile /tmp/jibo-skills.log`
|
||||
|
||||
Open in a browser:
|
||||
|
||||
- `http://<robot-ip>:15150/`
|
||||
252
V3.1/build/opt/jibo/Jibo/Skills/tools/robot/logd/jibo_logd.py
Normal file
@@ -0,0 +1,252 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""jibo_logd.py
|
||||
|
||||
Tiny logging daemon for old BusyBox environments.
|
||||
- Python 2.7 compatible
|
||||
- Listens on UDP (default 127.0.0.1:15140)
|
||||
- Appends newline-delimited logs to a file (default /tmp/jibo-skills.log)
|
||||
- Optional daemonize + pidfile
|
||||
|
||||
Protocol:
|
||||
- UDP payload may be plain text (written as-is)
|
||||
- OR JSON object with optional fields: tag, level, msg, data
|
||||
|
||||
Examples:
|
||||
echo '{"tag":"menu","level":"info","msg":"hello"}' | nc -u -w1 127.0.0.1 15140
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import datetime
|
||||
import errno
|
||||
import json
|
||||
import os
|
||||
import signal
|
||||
import socket
|
||||
import sys
|
||||
import time
|
||||
|
||||
DEFAULT_HOST = '127.0.0.1'
|
||||
DEFAULT_PORT = 15140
|
||||
DEFAULT_LOGFILE = '/tmp/jibo-skills.log'
|
||||
DEFAULT_MAX_BYTES = 2 * 1024 * 1024
|
||||
DEFAULT_BACKUPS = 3
|
||||
|
||||
|
||||
class Logd(object):
|
||||
def __init__(self, host, port, logfile, max_bytes, backups, flush_every):
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.logfile = logfile
|
||||
self.max_bytes = max_bytes
|
||||
self.backups = backups
|
||||
self.flush_every = flush_every
|
||||
|
||||
self._sock = None
|
||||
self._stop = False
|
||||
self._line_count = 0
|
||||
|
||||
def stop(self, *_args):
|
||||
self._stop = True
|
||||
|
||||
def _ensure_parent_dir(self):
|
||||
parent = os.path.dirname(self.logfile)
|
||||
if parent and not os.path.isdir(parent):
|
||||
try:
|
||||
os.makedirs(parent)
|
||||
except OSError as e:
|
||||
if e.errno != errno.EEXIST:
|
||||
raise
|
||||
|
||||
def _rotate_if_needed(self):
|
||||
if self.max_bytes <= 0:
|
||||
return
|
||||
try:
|
||||
st = os.stat(self.logfile)
|
||||
except OSError:
|
||||
return
|
||||
if st.st_size < self.max_bytes:
|
||||
return
|
||||
|
||||
# Rotate: logfile -> logfile.1 -> logfile.2 ...
|
||||
for i in range(self.backups, 0, -1):
|
||||
src = self.logfile + ('' if i == 0 else '.%d' % i)
|
||||
dst = self.logfile + '.%d' % (i + 1)
|
||||
if i == self.backups:
|
||||
# drop oldest
|
||||
try:
|
||||
os.unlink(self.logfile + '.%d' % (i + 1))
|
||||
except OSError:
|
||||
pass
|
||||
try:
|
||||
if os.path.exists(src):
|
||||
os.rename(src, dst)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
try:
|
||||
os.rename(self.logfile, self.logfile + '.1')
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def _format_line(self, payload, addr):
|
||||
ts = datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%fZ')
|
||||
src = '%s:%s' % (addr[0], addr[1])
|
||||
|
||||
txt = payload.strip()
|
||||
if not txt:
|
||||
return None
|
||||
|
||||
if txt.startswith('{') and txt.endswith('}'):
|
||||
try:
|
||||
obj = json.loads(txt)
|
||||
tag = obj.get('tag', 'skill')
|
||||
level = obj.get('level', 'info')
|
||||
msg = obj.get('msg', '')
|
||||
data = obj.get('data', None)
|
||||
if data is not None:
|
||||
try:
|
||||
data_txt = json.dumps(data, separators=(',', ':'), sort_keys=True)
|
||||
except Exception:
|
||||
data_txt = repr(data)
|
||||
return '%s [%s] %s %s %s\n' % (ts, level, tag, msg, data_txt)
|
||||
return '%s [%s] %s %s\n' % (ts, level, tag, msg)
|
||||
except Exception:
|
||||
# fall back to raw
|
||||
pass
|
||||
|
||||
return '%s [info] raw %s %s\n' % (ts, src, txt)
|
||||
|
||||
def serve_forever(self):
|
||||
self._ensure_parent_dir()
|
||||
|
||||
self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
self._sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
self._sock.bind((self.host, self.port))
|
||||
self._sock.settimeout(0.5)
|
||||
|
||||
f = None
|
||||
try:
|
||||
f = open(self.logfile, 'a')
|
||||
while not self._stop:
|
||||
try:
|
||||
data, addr = self._sock.recvfrom(65535)
|
||||
except socket.timeout:
|
||||
continue
|
||||
except socket.error:
|
||||
continue
|
||||
|
||||
try:
|
||||
if isinstance(data, bytes):
|
||||
try:
|
||||
payload = data.decode('utf-8', 'replace')
|
||||
except Exception:
|
||||
payload = str(data)
|
||||
else:
|
||||
payload = str(data)
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
line = self._format_line(payload, addr)
|
||||
if not line:
|
||||
continue
|
||||
|
||||
self._rotate_if_needed()
|
||||
try:
|
||||
f.write(line)
|
||||
self._line_count += 1
|
||||
if self.flush_every > 0 and (self._line_count % self.flush_every) == 0:
|
||||
try:
|
||||
f.flush()
|
||||
os.fsync(f.fileno())
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
# best effort: reopen file if it moved/rotated
|
||||
try:
|
||||
f.close()
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
f = open(self.logfile, 'a')
|
||||
except Exception:
|
||||
time.sleep(0.25)
|
||||
finally:
|
||||
try:
|
||||
if f:
|
||||
f.flush()
|
||||
f.close()
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
if self._sock:
|
||||
self._sock.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def daemonize(pidfile):
|
||||
# Double-fork daemonize (POSIX)
|
||||
try:
|
||||
pid = os.fork()
|
||||
if pid > 0:
|
||||
os._exit(0)
|
||||
except OSError:
|
||||
raise
|
||||
|
||||
os.setsid()
|
||||
|
||||
try:
|
||||
pid = os.fork()
|
||||
if pid > 0:
|
||||
os._exit(0)
|
||||
except OSError:
|
||||
raise
|
||||
|
||||
# Redirect stdio
|
||||
sys.stdout.flush()
|
||||
sys.stderr.flush()
|
||||
si = open('/dev/null', 'r')
|
||||
so = open('/dev/null', 'a+')
|
||||
se = open('/dev/null', 'a+')
|
||||
os.dup2(si.fileno(), sys.stdin.fileno())
|
||||
os.dup2(so.fileno(), sys.stdout.fileno())
|
||||
os.dup2(se.fileno(), sys.stderr.fileno())
|
||||
|
||||
if pidfile:
|
||||
try:
|
||||
with open(pidfile, 'w') as pf:
|
||||
pf.write(str(os.getpid()))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def main(argv):
|
||||
ap = argparse.ArgumentParser(description='Jibo Skills UDP log daemon')
|
||||
ap.add_argument('--host', default=os.environ.get('JIBO_LOGD_HOST', DEFAULT_HOST))
|
||||
ap.add_argument('--port', type=int, default=int(os.environ.get('JIBO_LOGD_PORT', str(DEFAULT_PORT))))
|
||||
ap.add_argument('--logfile', default=os.environ.get('JIBO_LOGD_FILE', DEFAULT_LOGFILE))
|
||||
ap.add_argument('--max-bytes', type=int, default=int(os.environ.get('JIBO_LOGD_MAX_BYTES', str(DEFAULT_MAX_BYTES))))
|
||||
ap.add_argument('--backups', type=int, default=int(os.environ.get('JIBO_LOGD_BACKUPS', str(DEFAULT_BACKUPS))))
|
||||
ap.add_argument('--flush-every', type=int, default=int(os.environ.get('JIBO_LOGD_FLUSH_EVERY', '1')))
|
||||
ap.add_argument('--daemonize', action='store_true')
|
||||
ap.add_argument('--pidfile', default=os.environ.get('JIBO_LOGD_PIDFILE', '/tmp/jibo-skills-logd.pid'))
|
||||
|
||||
args = ap.parse_args(argv)
|
||||
|
||||
logd = Logd(args.host, args.port, args.logfile, args.max_bytes, args.backups, args.flush_every)
|
||||
|
||||
signal.signal(signal.SIGTERM, logd.stop)
|
||||
signal.signal(signal.SIGINT, logd.stop)
|
||||
|
||||
if args.daemonize:
|
||||
daemonize(args.pidfile)
|
||||
|
||||
logd.serve_forever()
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main(sys.argv[1:]))
|
||||
@@ -0,0 +1,503 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""jibo_logpanel.py
|
||||
|
||||
Minimal HTTP log panel for old BusyBox + Python 2.7 environments.
|
||||
|
||||
- Serves a tiny HTML page at `/`
|
||||
- Streams log lines via Server-Sent Events (SSE) at `/events`
|
||||
- Tails a logfile (default: /tmp/jibo-skills.log)
|
||||
|
||||
This is intentionally dependency-free (stdlib only).
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
try:
|
||||
import json # stdlib
|
||||
except Exception:
|
||||
json = None
|
||||
|
||||
try:
|
||||
import urlparse # Py2
|
||||
except ImportError:
|
||||
from urllib import parse as urlparse # Py3
|
||||
|
||||
try:
|
||||
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
|
||||
except ImportError:
|
||||
# Py3 fallback (dev hosts)
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
|
||||
try:
|
||||
from SocketServer import ThreadingMixIn
|
||||
except ImportError:
|
||||
from socketserver import ThreadingMixIn
|
||||
|
||||
|
||||
INDEX_HTML = """<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset=\"utf-8\" />
|
||||
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />
|
||||
<title>Jibo Skills Logs</title>
|
||||
<style>
|
||||
body { font-family: sans-serif; margin: 0; }
|
||||
header { padding: 10px 12px; background: #111; color: #eee; position: sticky; top: 0; }
|
||||
#status { opacity: 0.8; font-size: 12px; }
|
||||
#controls { margin-top: 6px; font-size: 12px; }
|
||||
#log { white-space: pre-wrap; font-family: monospace; font-size: 12px; padding: 10px 12px; }
|
||||
.dim { opacity: 0.65; }
|
||||
button { font-size: 12px; padding: 4px 8px; }
|
||||
input { font-size: 12px; padding: 4px 6px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div><strong>Jibo Skills Logs</strong> <span id=\"status\" class=\"dim\">(connecting)</span></div>
|
||||
<div id=\"controls\">
|
||||
<button id=\"pause\">Pause</button>
|
||||
<button id=\"clear\">Clear</button>
|
||||
<label class=\"dim\">Filter:</label>
|
||||
<input id=\"filter\" placeholder=\"e.g. MENU-PATCH or [error]\" size=\"28\" />
|
||||
</div>
|
||||
</header>
|
||||
<div id=\"log\"></div>
|
||||
|
||||
<script>
|
||||
var logEl = document.getElementById('log');
|
||||
var statusEl = document.getElementById('status');
|
||||
var pauseBtn = document.getElementById('pause');
|
||||
var clearBtn = document.getElementById('clear');
|
||||
var filterEl = document.getElementById('filter');
|
||||
|
||||
var paused = false;
|
||||
var filter = '';
|
||||
var es = null;
|
||||
var pollTimer = null;
|
||||
var pollPos = 0;
|
||||
|
||||
function requestJSON(url, onOk, onErr) {
|
||||
// Older embedded browsers may not have fetch(); use XHR.
|
||||
try {
|
||||
if (typeof fetch === 'function') {
|
||||
fetch(url).then(function(r){ return r.json(); }).then(onOk).catch(onErr);
|
||||
return;
|
||||
}
|
||||
} catch (e) { /* fall through */ }
|
||||
|
||||
try {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', url, true);
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState !== 4) return;
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
try {
|
||||
onOk(JSON.parse(xhr.responseText));
|
||||
} catch (e) {
|
||||
onErr(e);
|
||||
}
|
||||
} else {
|
||||
onErr(new Error('HTTP ' + xhr.status));
|
||||
}
|
||||
};
|
||||
xhr.send(null);
|
||||
} catch (e) {
|
||||
onErr(e);
|
||||
}
|
||||
}
|
||||
|
||||
function setStatus(s) { statusEl.textContent = s; }
|
||||
|
||||
function appendLine(line) {
|
||||
if (paused) return;
|
||||
if (filter && line.indexOf(filter) === -1) return;
|
||||
logEl.textContent += line + "\\n";
|
||||
window.scrollTo(0, document.body.scrollHeight);
|
||||
}
|
||||
|
||||
function stopPolling() {
|
||||
if (pollTimer) { clearInterval(pollTimer); pollTimer = null; }
|
||||
}
|
||||
|
||||
function startPolling() {
|
||||
if (pollTimer) return;
|
||||
setStatus('(polling)');
|
||||
function tick() {
|
||||
var url = 'tail?pos=' + encodeURIComponent(String(pollPos)) + '&max=8192';
|
||||
requestJSON(url, function(obj){
|
||||
if (!obj) return;
|
||||
if (typeof obj.pos === 'number') pollPos = obj.pos;
|
||||
if (obj.lines && obj.lines.length) {
|
||||
for (var i=0; i<obj.lines.length; i++) appendLine(obj.lines[i]);
|
||||
}
|
||||
}, function(){ /* ignore */ });
|
||||
}
|
||||
tick();
|
||||
pollTimer = setInterval(tick, 500);
|
||||
}
|
||||
|
||||
function connect() {
|
||||
if (es) { try { es.close(); } catch(e) {} }
|
||||
stopPolling();
|
||||
setStatus('(connecting)');
|
||||
var opened = false;
|
||||
|
||||
// If EventSource isn't supported, go straight to polling.
|
||||
if (typeof EventSource !== 'function') {
|
||||
startPolling();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
es = new EventSource('events?lines=200');
|
||||
} catch (e) {
|
||||
startPolling();
|
||||
return;
|
||||
}
|
||||
var openTimeout = setTimeout(function(){
|
||||
if (!opened) startPolling();
|
||||
}, 1500);
|
||||
|
||||
es.onopen = function() {
|
||||
opened = true;
|
||||
clearTimeout(openTimeout);
|
||||
setStatus('(live)');
|
||||
stopPolling();
|
||||
};
|
||||
es.onerror = function() {
|
||||
setStatus('(disconnected; retrying)');
|
||||
startPolling();
|
||||
};
|
||||
es.onmessage = function(ev) { appendLine(ev.data); };
|
||||
}
|
||||
|
||||
pauseBtn.onclick = function() {
|
||||
paused = !paused;
|
||||
pauseBtn.textContent = paused ? 'Resume' : 'Pause';
|
||||
};
|
||||
|
||||
clearBtn.onclick = function() {
|
||||
logEl.textContent = '';
|
||||
};
|
||||
|
||||
filterEl.oninput = function() { filter = filterEl.value || ''; };
|
||||
|
||||
connect();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
|
||||
def _tail_last_lines(path, max_lines):
|
||||
if max_lines <= 0:
|
||||
return []
|
||||
|
||||
try:
|
||||
f = open(path, 'rb')
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
try:
|
||||
# naive but safe: read entire file if small, else chunk backwards
|
||||
try:
|
||||
size = os.path.getsize(path)
|
||||
except Exception:
|
||||
size = 0
|
||||
|
||||
if size <= 0:
|
||||
return []
|
||||
|
||||
# read up to ~256KB from end
|
||||
read_size = min(size, 256 * 1024)
|
||||
f.seek(-read_size, os.SEEK_END)
|
||||
data = f.read(read_size)
|
||||
|
||||
try:
|
||||
txt = data.decode('utf-8', 'replace')
|
||||
except Exception:
|
||||
try:
|
||||
txt = data.decode('latin-1', 'replace')
|
||||
except Exception:
|
||||
txt = str(data)
|
||||
|
||||
lines = txt.splitlines()
|
||||
if len(lines) > max_lines:
|
||||
lines = lines[-max_lines:]
|
||||
return lines
|
||||
finally:
|
||||
try:
|
||||
f.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
|
||||
daemon_threads = True
|
||||
|
||||
|
||||
class Handler(BaseHTTPRequestHandler):
|
||||
server_version = "JiboLogPanel/0.1"
|
||||
# Use HTTP/1.0 semantics to avoid needing chunked encoding.
|
||||
protocol_version = "HTTP/1.0"
|
||||
|
||||
try:
|
||||
_text_type = unicode # Py2
|
||||
except NameError:
|
||||
_text_type = str # Py3
|
||||
|
||||
def _send(self, code, content_type, body):
|
||||
if isinstance(body, self._text_type):
|
||||
body = body.encode('utf-8')
|
||||
self.send_response(code)
|
||||
self.send_header('Content-Type', content_type)
|
||||
self.send_header('Content-Length', str(len(body)))
|
||||
self.send_header('Cache-Control', 'no-cache')
|
||||
self.end_headers()
|
||||
self.wfile.write(body)
|
||||
|
||||
def do_GET(self):
|
||||
parsed = urlparse.urlparse(self.path)
|
||||
path = parsed.path
|
||||
qs = urlparse.parse_qs(parsed.query)
|
||||
|
||||
if path == '/' or path == '/index.html':
|
||||
return self._send(200, 'text/html; charset=utf-8', INDEX_HTML)
|
||||
|
||||
if path == '/health':
|
||||
return self._send(200, 'text/plain; charset=utf-8', 'ok')
|
||||
|
||||
if path == '/events':
|
||||
return self._handle_events(qs)
|
||||
|
||||
if path == '/tail':
|
||||
return self._handle_tail(qs)
|
||||
|
||||
return self._send(404, 'text/plain; charset=utf-8', 'not found')
|
||||
|
||||
def _handle_tail(self, qs):
|
||||
logfile = getattr(self.server, 'logfile', '/tmp/jibo-skills.log')
|
||||
try:
|
||||
pos = int((qs.get('pos') or ['0'])[0])
|
||||
except Exception:
|
||||
pos = 0
|
||||
try:
|
||||
max_bytes = int((qs.get('max') or ['8192'])[0])
|
||||
except Exception:
|
||||
max_bytes = 8192
|
||||
if max_bytes <= 0:
|
||||
max_bytes = 8192
|
||||
if max_bytes > 65536:
|
||||
max_bytes = 65536
|
||||
|
||||
out = { 'pos': 0, 'lines': [] }
|
||||
|
||||
try:
|
||||
st = os.stat(logfile)
|
||||
size = st.st_size
|
||||
if pos < 0 or pos > size:
|
||||
pos = 0
|
||||
with open(logfile, 'rb') as f:
|
||||
f.seek(pos)
|
||||
data = f.read(max_bytes)
|
||||
new_pos = f.tell()
|
||||
except Exception:
|
||||
out['pos'] = 0
|
||||
out['lines'] = []
|
||||
body = json.dumps(out) if json else '{"pos":0,"lines":[]}'
|
||||
return self._send(200, 'application/json; charset=utf-8', body)
|
||||
|
||||
try:
|
||||
try:
|
||||
txt = data.decode('utf-8', 'replace')
|
||||
except Exception:
|
||||
txt = data.decode('latin-1', 'replace')
|
||||
# splitlines() drops trailing newline; that's fine for display
|
||||
lines = txt.replace('\r', '').splitlines()
|
||||
except Exception:
|
||||
lines = []
|
||||
|
||||
out['pos'] = new_pos
|
||||
out['lines'] = lines
|
||||
body = json.dumps(out) if json else '{"pos":%d,"lines":[]}' % new_pos
|
||||
return self._send(200, 'application/json; charset=utf-8', body)
|
||||
|
||||
def _handle_events(self, qs):
|
||||
logfile = getattr(self.server, 'logfile', '/tmp/jibo-skills.log')
|
||||
try:
|
||||
lines = int((qs.get('lines') or ['0'])[0])
|
||||
except Exception:
|
||||
lines = 0
|
||||
|
||||
self.send_response(200)
|
||||
self.send_header('Content-Type', 'text/event-stream; charset=utf-8')
|
||||
self.send_header('Cache-Control', 'no-cache')
|
||||
self.send_header('X-Accel-Buffering', 'no')
|
||||
self.send_header('Access-Control-Allow-Origin', '*')
|
||||
self.send_header('Connection', 'keep-alive')
|
||||
self.end_headers()
|
||||
|
||||
# Ensure the connection stays open.
|
||||
try:
|
||||
self.close_connection = False
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _b(s):
|
||||
try:
|
||||
if isinstance(s, bytes):
|
||||
return s
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
if isinstance(s, self._text_type):
|
||||
return s.encode('utf-8')
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
return str(s).encode('utf-8')
|
||||
except Exception:
|
||||
return b''
|
||||
|
||||
def _write(data_bytes):
|
||||
if not data_bytes:
|
||||
return
|
||||
try:
|
||||
self.wfile.write(data_bytes)
|
||||
try:
|
||||
self.wfile.flush()
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
raise
|
||||
|
||||
# Advise client to retry quickly.
|
||||
try:
|
||||
_write(_b('retry: 1000\n\n'))
|
||||
except Exception:
|
||||
return
|
||||
|
||||
# initial payload: last N lines
|
||||
try:
|
||||
for line in _tail_last_lines(logfile, lines):
|
||||
_write(_b('data: ' + line.replace('\r', '') + '\n\n'))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# follow
|
||||
f = None
|
||||
|
||||
last_inode = None
|
||||
try:
|
||||
try:
|
||||
st = os.stat(logfile)
|
||||
last_inode = st.st_ino
|
||||
except Exception:
|
||||
last_inode = None
|
||||
|
||||
last_ping = time.time()
|
||||
|
||||
def _try_open_follow():
|
||||
try:
|
||||
fh = open(logfile, 'rb')
|
||||
fh.seek(0, os.SEEK_END)
|
||||
return fh
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
f = _try_open_follow()
|
||||
|
||||
while True:
|
||||
# handle rotation: reopen if inode changes
|
||||
try:
|
||||
st2 = os.stat(logfile)
|
||||
if last_inode is not None and st2.st_ino != last_inode:
|
||||
try:
|
||||
if f:
|
||||
f.close()
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
f = open(logfile, 'rb')
|
||||
f.seek(0, os.SEEK_END)
|
||||
last_inode = st2.st_ino
|
||||
except Exception:
|
||||
f = None
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if not f:
|
||||
# keep-alive heartbeat even if file missing
|
||||
now = time.time()
|
||||
if now - last_ping >= 5.0:
|
||||
last_ping = now
|
||||
try:
|
||||
_write(_b(': ping\n\n'))
|
||||
except Exception:
|
||||
break
|
||||
time.sleep(0.5)
|
||||
continue
|
||||
|
||||
pos = f.tell()
|
||||
chunk = f.readline()
|
||||
if not chunk:
|
||||
f.seek(pos)
|
||||
now = time.time()
|
||||
if now - last_ping >= 5.0:
|
||||
last_ping = now
|
||||
try:
|
||||
_write(_b(': ping\n\n'))
|
||||
except Exception:
|
||||
break
|
||||
time.sleep(0.25)
|
||||
continue
|
||||
|
||||
try:
|
||||
try:
|
||||
line = chunk.decode('utf-8', 'replace')
|
||||
except Exception:
|
||||
line = chunk.decode('latin-1', 'replace')
|
||||
line = line.rstrip('\n').rstrip('\r')
|
||||
_write(_b('data: ' + line + '\n\n'))
|
||||
except Exception:
|
||||
# client disconnected
|
||||
break
|
||||
finally:
|
||||
try:
|
||||
if f:
|
||||
f.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def log_message(self, fmt, *args):
|
||||
# keep quiet (panel should not spam stdout)
|
||||
return
|
||||
|
||||
|
||||
def main(argv):
|
||||
ap = argparse.ArgumentParser(description='Jibo Skills Log Panel (SSE)')
|
||||
ap.add_argument('--bind', default=os.environ.get('JIBO_LOGPANEL_BIND', '0.0.0.0'))
|
||||
ap.add_argument('--port', type=int, default=int(os.environ.get('JIBO_LOGPANEL_PORT', '15150')))
|
||||
ap.add_argument('--logfile', default=os.environ.get('JIBO_LOGD_FILE', '/tmp/jibo-skills.log'))
|
||||
|
||||
args = ap.parse_args(argv)
|
||||
|
||||
httpd = ThreadedHTTPServer((args.bind, args.port), Handler)
|
||||
httpd.logfile = args.logfile
|
||||
|
||||
print('logpanel listening on %s:%d, logfile=%s' % (args.bind, args.port, args.logfile))
|
||||
try:
|
||||
httpd.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main(sys.argv[1:]))
|
||||
BIN
docs.tar.gz
199
docs/Runtime.js
@@ -1,199 +0,0 @@
|
||||
/**
|
||||
* Enum of RunMode types.
|
||||
*
|
||||
* ```
|
||||
* jibo.RunMode.SIMULATOR
|
||||
* ```
|
||||
*
|
||||
* @typedef module:jibo.Runtime#RunMode
|
||||
* @prop {string} SIMULATOR Running in the simulator.
|
||||
* @prop {string} REMOTELY Running in remote mode.
|
||||
* @prop {string} ON_ROBOT Running on the robot.
|
||||
* @prop {string} UNIT_TESTS Running in dev mode, no SSM.
|
||||
*/
|
||||
|
||||
/** Running in the simulator. */
|
||||
|
||||
/** Running in remote mode. */
|
||||
|
||||
/** Running on the robot. */
|
||||
|
||||
/** Running in dev mode, no SSM */
|
||||
|
||||
/**
|
||||
* Internal object for an installed plugin
|
||||
*/
|
||||
|
||||
/**
|
||||
* @prop {jibo.animdb} animdb The Jibo Animation Database.
|
||||
* @prop {jibo.bt} bt SDK behaviors and decorators.
|
||||
* @prop {jibo.context} context Context Service (on robot context provider for Cloud).
|
||||
* @prop {jibo.embodied} embodied Jibo Embodied Dialog.
|
||||
* @prop {jibo.emotion} emotion Jibo Emotion System.
|
||||
* @prop {jibo.expression} expression Jibo's expression client.
|
||||
* @prop {jibo.face} face API to jibo's face.
|
||||
* @prop {jibo.flow} flow API for the Jibo Flow Editor.
|
||||
* @prop {jibo.globalEvents} globalEvents Typed global events for events module.
|
||||
* @prop {jibo.kb} kb Knowledge Base API
|
||||
* @prop {jibo.loader} loader Load skill assets.
|
||||
* @prop {jibo.lps} lps Jibo's Local Perceptual Space.
|
||||
* @prop {jibo.media} media Jibo's Media API (camera and pictures).
|
||||
* @prop {jibo.mim} mim Jibo's multimodal interaction manager.
|
||||
* @prop {jibo.sound} sound Manage playback of audio media.
|
||||
* @prop {jibo.system} system Non-motion related body services.
|
||||
* @prop {jibo.timer} timer Jibo's main update loop.
|
||||
* @prop {jibo.tts} tts Text-to-speech.
|
||||
* @prop {jibo.utils} utils Utility methods for animation, audio, and pathing.
|
||||
* @prop {jibo.versions} versions Versions for skills-service-manager, platform, and jibo.
|
||||
* @module jibo
|
||||
* @description The Jibo SDK singleton.
|
||||
*/
|
||||
|
||||
/**
|
||||
* `true` if Jibo is currently being initialized.
|
||||
* @name module:jibo.Runtime.isInitializing
|
||||
* @type {Boolean}
|
||||
* @readOnly
|
||||
* @default false
|
||||
*/
|
||||
|
||||
/**
|
||||
* `true` if Jibo has finished being initialized.
|
||||
* @name module:jibo.Runtime.isInitialized
|
||||
* @type {Boolean}
|
||||
* @readOnly
|
||||
* @default false
|
||||
*/
|
||||
|
||||
/**
|
||||
* Runtime is running in Electron-based environment.
|
||||
* @name module:jibo.Runtime.electron
|
||||
* @type {Boolean}
|
||||
* @readOnly
|
||||
*/
|
||||
|
||||
/**
|
||||
* Collection of installed plugins.
|
||||
* @name module:jibo.Runtime._plugins
|
||||
* @type {Boolean}
|
||||
* @readOnly
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* Singleton instance of jibo.
|
||||
* @name module:jibo.Runtime._instance
|
||||
* @type {jibo.Runtime}
|
||||
* @readOnly
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* Current version of the Jibo Runtime.
|
||||
* @name module:jibo.Runtime#version
|
||||
* @type {String}
|
||||
* @readOnly
|
||||
*/
|
||||
|
||||
/**
|
||||
* Electron IPCRenderer context of the Jibo Runtime.
|
||||
* @name module:jibo.Runtime#electronContext
|
||||
* @type {Object}
|
||||
* @readOnly
|
||||
*/
|
||||
|
||||
/** For the SDK tool */
|
||||
|
||||
/**
|
||||
* @name module:jibo.Runtime#behaviorEmitter
|
||||
* @type {BehaviorEmitter}
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* @name module:jibo.Runtime#session
|
||||
* @type {SessionManager}
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* @name module:jibo.Runtime#systemManager
|
||||
* @type {SystemManager}
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
// * @name module:jibo.Runtime#visualize
|
||||
// * @type {Object}
|
||||
// * @private
|
||||
// */
|
||||
|
||||
/**
|
||||
* Initialization options
|
||||
* @name module:jibo.Runtime#options
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* @description
|
||||
* Initializes the jibo SDK.
|
||||
*
|
||||
* ```
|
||||
* let jibo = require('jibo');
|
||||
* jibo.init('face', (e) => {
|
||||
* if (e) return console.error(e);
|
||||
* // Setup!
|
||||
* });
|
||||
* ```
|
||||
* @method module:jibo.Runtime#init
|
||||
* @param {Object|Function|String|HTMLElement} [options] Either the options, canvas DOM ID or DOM Element for canvas or callback
|
||||
* @param {DOMElement|String} [options.display] Either the canvas DOM or DOM ID
|
||||
* @param {Function} [callback] Called when the SDK is finished initializing.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Register an external plugin with the jibo runtime.
|
||||
* @method module:jibo.Runtime#registerPlugin
|
||||
* @private
|
||||
* @param {jibo.PluginConstructor} pluginClass The class definition.
|
||||
* @param {String} id The identifier of the plugin.
|
||||
* @param {Object} [options] Additional register options or the API name.
|
||||
* @param {Boolean} [options.api=false] If the plugin is also an API on jibo.
|
||||
* @param {String[]} [options.depends] The plugin dependencies.
|
||||
* @param {Boolean} [options.electron=false] If the plugin is electron-only.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Stores which mode the current skill is running. It can be: `jibo.RunMode.SIMULATOR`, `jibo.RunMode.REMOTELY`, or `jibo.RunMode.ON_ROBOT`.
|
||||
*
|
||||
* ```
|
||||
* let jibo = require('jibo');
|
||||
* if( jibo.runMode === jibo.RunMode.SIMULATOR ){
|
||||
* // ...
|
||||
* }
|
||||
* ```
|
||||
* @name module:jibo.Runtime.runMode
|
||||
* @type {RunMode}
|
||||
* @readOnly
|
||||
*/
|
||||
|
||||
/**
|
||||
* Setup plugins to create the jibo API.
|
||||
* @method module:jibo.Runtime#_installPlugins
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* Setup plugins to create the jibo API.
|
||||
* @method module:jibo.Runtime#_initPlugins
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* Gets singleton instance of Jibo.
|
||||
* @name module:jibo.Runtime.instance
|
||||
* @type {jibo}
|
||||
* @private
|
||||
* @readOnly
|
||||
*/
|
||||
@@ -1,135 +0,0 @@
|
||||
/**
|
||||
* @class BaseElement
|
||||
* @memberof jibo.bt
|
||||
* @private
|
||||
* @description Parent class for Behavior and Decorator.
|
||||
*
|
||||
* Subclasses: {@link jibo.bt.behaviors.Behavior|Behavior}, {@link jibo.bt.behaviors.ParentBehavior|ParentBehavior}, {@link jibo.bt.behaviors.Decorator|Decorator}
|
||||
*
|
||||
* @param {Object} [options] Options for the behavior
|
||||
*/
|
||||
|
||||
/**
|
||||
* The list of options
|
||||
* @name jibo.bt.Behavior#options
|
||||
* @type {Object}
|
||||
* @readOnly
|
||||
*/
|
||||
|
||||
/**
|
||||
* The list of options
|
||||
* @name jibo.bt.Decorator#options
|
||||
* @type {Object}
|
||||
* @readOnly
|
||||
*/
|
||||
|
||||
/**
|
||||
* The current internal status of the element
|
||||
* @name jibo.bt.BaseElement#_currentStatus
|
||||
* @type {String}
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* Instance of the blackboard
|
||||
* @name jibo.bt.Behavior#blackboard
|
||||
* @type {jibo.bt.Blackboard}
|
||||
* @readOnly
|
||||
*/
|
||||
|
||||
/**
|
||||
* Instance of the blackboard
|
||||
* @name jibo.bt.Decorator#blackboard
|
||||
* @type {jibo.bt.Blackboard}
|
||||
* @readOnly
|
||||
*/
|
||||
|
||||
/**
|
||||
* Instance of the behavior emitter
|
||||
* @name jibo.bt.Behavior#emitter
|
||||
* @type {jibo.bt.BehaviorEmitter}
|
||||
* @readOnly
|
||||
*/
|
||||
|
||||
/**
|
||||
* Instance of the behavior emitter
|
||||
* @name jibo.bt.Decorator#emitter
|
||||
* @type {jibo.bt.BehaviorEmitter}
|
||||
* @readOnly
|
||||
*/
|
||||
|
||||
/**
|
||||
* Name of the asset pack
|
||||
* @name jibo.bt.Behavior#assetPack
|
||||
* @type {String}
|
||||
* @readOnly
|
||||
*/
|
||||
|
||||
/**
|
||||
* Name of the asset pack
|
||||
* @name jibo.bt.Decorator#assetPack
|
||||
* @type {String}
|
||||
* @readOnly
|
||||
*/
|
||||
|
||||
/**
|
||||
* Name of the behavior
|
||||
* @name jibo.bt.Behavior#name
|
||||
* @type {String}
|
||||
* @readOnly
|
||||
*/
|
||||
|
||||
/**
|
||||
* Name of the behavior
|
||||
* @name jibo.bt.Decorator#name
|
||||
* @type {String}
|
||||
* @readOnly
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get the current status
|
||||
* @name jibo.bt.Behavior#currentStatus
|
||||
* @type {String}
|
||||
* @readOnly
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get the current status
|
||||
* @name jibo.bt.Decorator#currentStatus
|
||||
* @type {String}
|
||||
* @readOnly
|
||||
*/
|
||||
|
||||
/**
|
||||
* Stops the behavior or decorator, must override
|
||||
* @method jibo.bt.Behavior#stop
|
||||
* @return {Promise<void>} A promise that resolves after any asynchronous cleanup has been performed.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Stops the behavior or decorator, must override
|
||||
* @method jibo.bt.Decorator#stop
|
||||
* @return {Promise<void>} A promise that resolves after any asynchronous cleanup has been performed.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Destroys the behavior, removes all references.
|
||||
* @method jibo.bt.Behavior#destroy
|
||||
*/
|
||||
|
||||
/**
|
||||
* Destroys the decorator, removes all references.
|
||||
* @method jibo.bt.Decorator#destroy
|
||||
*/
|
||||
|
||||
/**
|
||||
* Stops and Destroys the behavior or decorator
|
||||
* @method jibo.bt.Behavior#stopAndDestroy
|
||||
* @return {Promise<void>} A promise that resolves after any asynchronous cleanup has been performed.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Stops and Destroys the behavior or decorator
|
||||
* @method jibo.bt.Decorator#stopAndDestroy
|
||||
* @return {Promise<void>} A promise that resolves after any asynchronous cleanup has been performed.
|
||||
*/
|
||||
@@ -1,91 +0,0 @@
|
||||
/**
|
||||
* @class Behavior
|
||||
* @memberof jibo.bt
|
||||
* @description The baseclass to all behaviors.
|
||||
*
|
||||
* Subclasses: {@link jibo.bt.behaviors}
|
||||
* @param {Object} [options] Options for the behavior
|
||||
* @param {String} [options.name=''] Name of the behavior instance
|
||||
* @param {Array<jibo.bt.Decorator>} [options.decorators=[]] Decorators
|
||||
* @param {jibo.bt.Blackboard} [options.blackboard=null] Blackboard instance
|
||||
* @param {jibo.bt.BehaviorEmitter} [options.emitter=null] Emitter instance
|
||||
* @param {String} [options.assetPack=''] Name of the asset pack
|
||||
* @param {Object} [defaultOptions] Defaults for options
|
||||
*/
|
||||
|
||||
/**
|
||||
* Reference to the composite parent behavior
|
||||
* @name jibo.bt.Behavior#parent
|
||||
* @type {jibo.bt.ParentBehavior}
|
||||
* @readOnly
|
||||
*/
|
||||
|
||||
/**
|
||||
* List of decorators to wait on start
|
||||
* @name jibo.bt.Behavior#waitDecorators
|
||||
* @type {Array<jibo.bt.Decorator>}
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* Number of wait decorators
|
||||
* @name jibo.bt.Behavior#waitDecoratorsLength
|
||||
* @type {int}
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* Destroy this
|
||||
*/
|
||||
|
||||
/**
|
||||
* Collection of decorators
|
||||
* @name jibo.bt.Behavior#decorators
|
||||
* @type {Array<jibo.bt.Decorator>}
|
||||
* @readOnly
|
||||
*/
|
||||
|
||||
/**
|
||||
* Starts the behavior or decorator
|
||||
* @method jibo.bt.Behavior#start
|
||||
* @returns {Boolean} `true` if this element is started successfully. `false` otherwise.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Called every frame. Must be overridden in subclass.
|
||||
* @method jibo.bt.Behavior#update
|
||||
* @returns {jibo.bt.Status} The current status of this behavior.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Internal pause the behavior
|
||||
* @method jibo.bt.Behavior#_pause
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* Internal unpause the behavior
|
||||
* @method jibo.bt.Behavior#_unpause
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* Internal start the behavior
|
||||
* @method jibo.bt.Behavior#_start
|
||||
* @private
|
||||
* @return {Boolean} Status result
|
||||
*/
|
||||
|
||||
/**
|
||||
* Internal stop the behavior
|
||||
* @method jibo.bt.Behavior#_stop
|
||||
* @return {Promise<void>} A promise that resolves after any asynchronous cleanup has been performed.
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* Internal update the behavior
|
||||
* @method jibo.bt.Behavior#_update
|
||||
* @private
|
||||
* @return {jibo.bt.Status} Resulting status
|
||||
*/
|
||||
@@ -1,5 +0,0 @@
|
||||
/**
|
||||
* @class BehaviorEmitter
|
||||
* @extends EventEmitter
|
||||
* @memberof jibo.bt
|
||||
*/
|
||||
@@ -1,85 +0,0 @@
|
||||
/**
|
||||
* @class BehaviorTree
|
||||
* @extends EventEmitter
|
||||
* @memberof jibo.bt
|
||||
* @description Object for controlling the playback of the behavior tree.
|
||||
*
|
||||
* @param {jibo.bt.Behavior} root The root behavior of the tree.
|
||||
* @param {jibo.bt.Blackboard} blackboard Reference to the global Blackboard instance.
|
||||
* @param {Object} notepad Reference to a temporary object to use.
|
||||
* @param {Object} result Reference to the tree's result.
|
||||
* @param {jibo.bt.BehaviorEmitter} emitter Reference to the behavior's emitter.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get the current status of the behavior tree
|
||||
* @name jibo.bt.BehaviorTree#currentStatus
|
||||
* @type {jibo.bt.Status}
|
||||
* @readOnly
|
||||
*/
|
||||
|
||||
/**
|
||||
* Start the behavior tree.
|
||||
* @method jibo.bt.BehaviorTree#start
|
||||
* @return {Boolean} Status result
|
||||
*/
|
||||
|
||||
/**
|
||||
* When behavior tree starts
|
||||
* @event jibo.bt.BehaviorTree#start
|
||||
*/
|
||||
|
||||
/**
|
||||
* Stop the behavior tree.
|
||||
* @method jibo.bt.BehaviorTree#stop
|
||||
* @return {Promise<void>} A promise that resolves after any asynchronous cleanup has been performed.
|
||||
*/
|
||||
|
||||
/**
|
||||
* When behavior tree stops
|
||||
* @event jibo.bt.BehaviorTree#stop
|
||||
*/
|
||||
|
||||
/**
|
||||
* Pause the behavior tree.
|
||||
* @private
|
||||
* @method jibo.bt.BehaviorTree#pause
|
||||
*/
|
||||
|
||||
/**
|
||||
* When behavior tree pauses
|
||||
* @event jibo.bt.BehaviorTree#pause
|
||||
*/
|
||||
|
||||
/**
|
||||
* Resume the behavior tree.
|
||||
* @private
|
||||
* @method jibo.bt.BehaviorTree#pause
|
||||
*/
|
||||
|
||||
/**
|
||||
* When behavior tree resumes
|
||||
* @event jibo.bt.BehaviorTree#unpause
|
||||
*/
|
||||
|
||||
/**
|
||||
* Called every frame.
|
||||
* @method jibo.bt.BehaviorTree#update
|
||||
* @returns {jibo.bt.Status} The current status of this behavior.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Destroy and don't use after this
|
||||
* @method jibo.bt.BehaviorTree#destroy
|
||||
*/
|
||||
|
||||
/**
|
||||
* When behavior tree is destroyed
|
||||
* @event jibo.bt.BehaviorTree#destroy
|
||||
*/
|
||||
|
||||
/**
|
||||
* Stop and destroy, don't use after this
|
||||
* @method jibo.bt.BehaviorTree#stopAndDestroy
|
||||
* @return {Promise<void>} A promise that resolves after any asynchronous cleanup has been performed.
|
||||
*/
|
||||
@@ -1,4 +0,0 @@
|
||||
/**
|
||||
* @class Blackboard
|
||||
* @memberof jibo.bt
|
||||
*/
|
||||
@@ -1,60 +0,0 @@
|
||||
/**
|
||||
* @class Decorator
|
||||
* @memberof jibo.bt
|
||||
* @description Baseclass for all decorators. Decorators can force a behavior to succeed or fail. They can restart
|
||||
* a behavior once it's failed or succeeded, and they can modify when a behavior starts.
|
||||
*
|
||||
* Subclasses: {@link jibo.bt.decorators}
|
||||
* @param {Object} [options] Options for the decorator
|
||||
* @param {String} [options.name=''] Name of the decorator instance
|
||||
* @param {jibo.bt.Blackboard} [options.blackboard=null] Blackobard instance
|
||||
* @param {jibo.bt.BehaviorEmitter} [options.emitter=null] Emitter instance
|
||||
* @param {String} [options.assetPack=''] Name of the asset pack
|
||||
* @param {Object} [defaultOptions] Defaults for options
|
||||
*/
|
||||
|
||||
/**
|
||||
* The parent behavior
|
||||
* @name jibo.bt.Decorator#behavior
|
||||
* @type {Behavior}
|
||||
* @readOnly
|
||||
*/
|
||||
|
||||
/**
|
||||
* Starts the behavior or decorator
|
||||
* @method jibo.bt.Decorator#start
|
||||
* @returns {Boolean|Status.WAIT} `true` if this element is started successfully. `false` otherwise. `Status.WAIT` will be returned if the decorated behavior should not start yet.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Called every frame. Gives a chance for this decorator to change the status of a behavior.
|
||||
* @method jibo.bt.Decorator#update
|
||||
* @param result {jibo.bt.Status} The current status of the behavior this decorator is decorating.
|
||||
* @returns {jibo.bt.Status} The modified status of the behavior this decorator is decorating.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Destroy this
|
||||
*/
|
||||
|
||||
/**
|
||||
* Internal start from the behavior tree level
|
||||
* @method jibo.bt.Decorator#_start
|
||||
* @private
|
||||
* @return {Boolean} Success
|
||||
*/
|
||||
|
||||
/**
|
||||
* Internal stop from the behavior tree level
|
||||
* @method jibo.bt.Decorator#_stop
|
||||
* @return {Promise<void>} A promise that resolves after any asynchronous cleanup has been performed.
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* Internal update from the behavior tree level
|
||||
* @method jibo.bt.Decorator#_update
|
||||
* @private
|
||||
* @param {jibo.bt.Status} result
|
||||
* @return {jibo.bt.Status} resulting status
|
||||
*/
|
||||
@@ -1,86 +0,0 @@
|
||||
/**
|
||||
* @class BehaviorConstructor
|
||||
* @memberof jibo.bt
|
||||
* @description Abstract type alias (needed by the Factory function) for the constructors of the behavior and decorator classes.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Utility methods for registering behaviors and creating behaviors trees.
|
||||
* @namespace jibo.bt
|
||||
*/
|
||||
|
||||
/**
|
||||
* Map of behaviors
|
||||
* @name jibo.bt#_behaviors
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* The default blackboard
|
||||
* @name jibo.bt#blackboard
|
||||
* @type {jibo.bt.Blackboard}
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* Register a behavior or decorator globally
|
||||
* @name jibo.bt#register
|
||||
* @method
|
||||
* @param {String} name The PascalCased name for the behavior
|
||||
* @param {String} namespace This behavior's namespace. Pass in a globally unique name for this namesapce.
|
||||
* @param {Module} classRef Class reference for the behavior or decorator
|
||||
*/
|
||||
|
||||
/**
|
||||
* Add all behaviors
|
||||
* @method jibo.bt#registerCore
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates a runnable behavior tree from a bt file import.
|
||||
* @method jibo.bt#create
|
||||
* @param {String|Function} uri Relative or absolute path to a `.bt` file or module export of behavior tree.
|
||||
* @param {Object} [overrides] Options for populating behavior tree globals.
|
||||
* @param {jibo.bt.Blackboard} [overrides.blackboard] Override the default blackboard object for this behavior tree.
|
||||
* @param {Object} [overrides.notepad] Provide your own notepad object instead of the default one.
|
||||
* @param {String} [overrides.assetPack] The asset pack name to use for loading assets in this tree.
|
||||
* @returns {jibo.bt.BehaviorTree}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates and runs a runnable behavior tree from a bt file import.
|
||||
* ```
|
||||
* const jibo = require('jibo');
|
||||
* jibo.init('face', (err) => {
|
||||
* jibo.bt.run('./behaviors/main', function(status){});
|
||||
* });
|
||||
*
|
||||
* // Other supported APIs
|
||||
* jibo.bt.run('./behaviors/main');
|
||||
* jibo.bt.run('./behaviors/main', (status) => {});
|
||||
* const overrides = {};
|
||||
* jibo.bt.run('./behaviors/main', overrides);
|
||||
* jibo.bt.run('./behaviors/main', overrides, (status) => {});
|
||||
* ```
|
||||
*
|
||||
* @method jibo.bt#run
|
||||
* @param {String|Function} uri Relative or absolute path to a `.bt` file or module export of behavior tree.
|
||||
* @param {Object} [overrides] Options for populating behavior tree globals.
|
||||
* @param {jibo.bt.Blackboard} [overrides.blackboard] Override the default blackboard object for this behavior tree.
|
||||
* @param {Object} [overrides.notepad] Provide your own notepad object instead of the default one.
|
||||
* @param {String} [overrides.assetPack] The asset pack name to use for loading assets in this tree.
|
||||
* @param {Function} onFinishedCallback The callback which gets called when the behavior tree has reached a status of FAILED, SUCCEEDED, or INTERRUPTED.
|
||||
* @param {Status} status The status which the behavior tree finished with.
|
||||
* @returns {jibo.bt.BehaviorTree}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Resolves the path in relation to a calleeDirname directory. If the supplied uri is an absolute path, the uri is
|
||||
* returned unmodified
|
||||
* @param {String} calleeDirname The directory name where the uri is relative to
|
||||
* @param {String} uri The relative or absolute uri to a file in relation to the calleeDirname
|
||||
* @returns {String} The result absolute path
|
||||
*
|
||||
*/
|
||||
@@ -1,29 +0,0 @@
|
||||
/**
|
||||
* @class ParentBehavior
|
||||
* @extends jibo.bt.Behavior
|
||||
* @memberof jibo.bt
|
||||
* @description The baseclass to all behaviors that contain a collection of child Behaviors.
|
||||
*
|
||||
* Subclasses: {@link jibo.bt.behaviors.Parallel|Parallel}, {@link jibo.bt.behaviors.Random|Random}, {@link jibo.bt.behaviors.Sequence|Sequence}, {@link jibo.bt.behaviors.Switch|Switch}
|
||||
*
|
||||
* @param {Object} [options] See {@link jibo.bt.Behavior|Behavior} for all options.
|
||||
* @param {Array<jibo.bt.Behavior>} [options.children=[]] Child behaviors
|
||||
* @param {Object} [defaultOptions] Defaults for options
|
||||
*/
|
||||
|
||||
/**
|
||||
* Destroy this
|
||||
*/
|
||||
|
||||
/**
|
||||
* Set the collection of children
|
||||
* @name jibo.bt.ParentBehavior#children
|
||||
* @type {Array<jibo.bt.Behavior>}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Internal stop the behavior
|
||||
* @method jibo.bt.ParentBehavior#_stop
|
||||
* @return {Promise<void>} A promise that resolves after any asynchronous cleanup has been performed.
|
||||
* @private
|
||||
*/
|
||||
@@ -1,64 +0,0 @@
|
||||
/**
|
||||
* Enumeration of behavior states
|
||||
* @class Status
|
||||
* @memberof jibo.bt
|
||||
*/
|
||||
|
||||
/**
|
||||
* The behavior or flow succeeded.
|
||||
* @name jibo.bt.Status.SUCCEEDED
|
||||
* @type {String}
|
||||
* @readOnly
|
||||
* @default "SUCCEEDED"
|
||||
*/
|
||||
|
||||
/**
|
||||
* The behavior or flow failed.
|
||||
* @name jibo.bt.Status.FAILED
|
||||
* @type {String}
|
||||
* @readOnly
|
||||
* @default "FAILED"
|
||||
*/
|
||||
|
||||
/**
|
||||
* The behavior or flow was interrupted.
|
||||
* @name jibo.bt.Status.INTERRUPTED
|
||||
* @type {String}
|
||||
* @readOnly
|
||||
* @default "INTERRUPTED"
|
||||
*/
|
||||
|
||||
/**
|
||||
* The behavior is in progress and hasn't returned.
|
||||
* (Not returned for flows)
|
||||
* @name jibo.bt.Status.IN_PROGRESS
|
||||
* @type {String}
|
||||
* @readOnly
|
||||
* @default "IN_PROGRESS"
|
||||
*/
|
||||
|
||||
/**
|
||||
* The behavior is in an invalid state.
|
||||
* (Not returned for flows)
|
||||
* @name jibo.bt.Status.INVALID
|
||||
* @type {String}
|
||||
* @readOnly
|
||||
* @default "INVALID"
|
||||
*/
|
||||
|
||||
/**
|
||||
* Don't update the state.
|
||||
* (Not returned for flows)
|
||||
* @name jibo.bt.Status.PAUSED
|
||||
* @type {String}
|
||||
* @readOnly
|
||||
* @default "PAUSED"
|
||||
*/
|
||||
|
||||
/**
|
||||
* Only used by decorators.
|
||||
* @name jibo.bt.Status.WAIT
|
||||
* @type {String}
|
||||
* @readOnly
|
||||
* @default "WAIT"
|
||||
*/
|
||||
@@ -1,7 +0,0 @@
|
||||
/**
|
||||
* @class Blink
|
||||
* @extends jibo.bt.Behavior
|
||||
* @memberof jibo.bt.behaviors
|
||||
* @description Plays a single blink animation. Succeeds immediately.
|
||||
* @param {Object} options See {@link jibo.bt.Behavior|Behavior} for all options.
|
||||
*/
|
||||
@@ -1,9 +0,0 @@
|
||||
/**
|
||||
* @class ExecuteScript
|
||||
* @extends jibo.bt.Behavior
|
||||
* @memberof jibo.bt.behaviors
|
||||
* @description
|
||||
* Executes arbitrary synchronous JavaScript.
|
||||
* @param {Object} options See {@link jibo.bt.Behavior|Behavior} for all options.
|
||||
* @param {Function} options.exec Function to execute.
|
||||
*/
|
||||
@@ -1,17 +0,0 @@
|
||||
/**
|
||||
* @callback jibo.bt.behaviors.ExecuteScriptAsync~ExecuteFunction
|
||||
* @description This behavior calls this function. ExecuteScriptAsync will succeed or fail only when one of the
|
||||
* two callbacks are called.
|
||||
* @param {Function} succeed Call this function when you want this behavior to succeed.
|
||||
* @param {Function} fail Call this function when you want this behavior to fail.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @class ExecuteScriptAsync
|
||||
* @extends jibo.bt.Behavior
|
||||
* @memberof jibo.bt.behaviors
|
||||
* @description Asynchronously executes JavaScript.
|
||||
* @param {Object} options See {@link jibo.bt.Behavior|Behavior} for all options.
|
||||
* @param {jibo.bt.behaviors.ExecuteScriptAsync~ExecuteFunction} options.exec Function to execute. Behavior will not stop unless one of the callbacks
|
||||
* are called.
|
||||
*/
|
||||
@@ -1,32 +0,0 @@
|
||||
/**
|
||||
* @typedef {Object} jibo.bt.behaviors.Listen~Options
|
||||
* @property {boolean} heyJibo Listen for "Hey, Jibo" first.
|
||||
* @property {boolean} detectEnd Listen for end of speech.
|
||||
* @property {boolean} incremental Return incremental ASR results as they are streamed from the cloud.
|
||||
* @property {String} authenticateSpeaker Authenticates against that person.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Function that returns the options object.
|
||||
* @callback jibo.bt.behaviors.Listen~GetOptions
|
||||
* @returns {jibo.bt.behaviors.Listen~Options}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Function that returns the options object.
|
||||
* @callback jibo.bt.behaviors.Listen~OnResult
|
||||
* @param {jibo.bt.ListenEmitter} listener Use this instance to listen for listen events.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @class Listen
|
||||
* @extends jibo.bt.Behavior
|
||||
* @memberof jibo.bt.behaviors
|
||||
* @description Performs audio speech recognition and applies and parses the results according to a rules file.
|
||||
* @param {Object} options See {@link jibo.bt.Behavior|Behavior} for all options.
|
||||
* @param {jibo.bt.behaviors.Listen~GetOptions} options.getOptions Returns the options object.
|
||||
* @param {String} options.rule Path the to `.rule` file. This assumes the path starts at `${project}/rules`.
|
||||
* @param {jibo.bt.behaviors.Listen~OnResult} options.onResult Called and passed a {@link jibo.bt.behaviors.ListenEmitter|ListenEmitter} object. Events are fired from the Listener
|
||||
* at certain points in this behavior's lifecycle.
|
||||
*/
|
||||
@@ -1,29 +0,0 @@
|
||||
/**
|
||||
* @typedef {Object} jibo.bt.behaviors.ListenEmbedded~Options
|
||||
*/
|
||||
|
||||
/**
|
||||
* Called when the {@link jibo.bt.EmbeddedListenEmitter|EmbeddedListenEmitter} is constructed.
|
||||
* @callback jibo.bt.behaviors.ListenEmbedded~OnResult
|
||||
* @param {jibo.bt.EmbeddedListenEmitter} listener Use this instance to listen for listen events.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @class ListenEmbedded
|
||||
* @extends jibo.bt.Behavior
|
||||
* @memberof jibo.bt.behaviors
|
||||
* @description Succeeds when when the specified audio phrase is spotted.
|
||||
* @param {Object} options See {@link jibo.bt.Behavior|Behavior} for all options.
|
||||
* @param {jibo.bt.behaviors.ListenEmbedded~Rules} options.rule The embedded rule to listen for.
|
||||
* @param {jibo.bt.behaviors.ListenEmbedded~OnResult} options.onResult Called and passed a {@link jibo.bt.EmbeddedListenEmitter|EmbeddedListenEmitter} object. Events are
|
||||
* fired from the emitter at certain points in this behavior's lifecycle.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Enum for embedded listen rule types.
|
||||
* @name jibo.bt.behaviors.ListenEmbedded#Rules
|
||||
* @readOnly
|
||||
* @type {enum}
|
||||
* @prop {string} HEY_jibo Listed for "Hey Jibo".
|
||||
*/
|
||||
@@ -1,33 +0,0 @@
|
||||
/**
|
||||
* @typedef {Object} jibo.bt.behaviors.ListenJs~Options
|
||||
* @property {boolean} heyJibo Listen for "Hey, Jibo" first.
|
||||
* @property {boolean} detectEnd Listen for end of speech.
|
||||
* @property {boolean} incremental Return incremental ASR results as they are streamed from the cloud.
|
||||
* @property {String} authenticateSpeaker Authenticates against that person.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Function that returns the options object.
|
||||
* @callback jibo.bt.behaviors.ListenJs~GetOptions
|
||||
* @returns {jibo.bt.behaviors.ListenJs~Options}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Called when the {@link jibo.bt.behaviors.ListenEmitter|ListenEmitter} is constructed.
|
||||
* @callback jibo.bt.behaviors.ListenJs~OnResult
|
||||
* @param {jibo.bt.ListenEmitter} listener Use this instance to listen for listen events.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @class ListenJs
|
||||
* @extends jibo.bt.Behavior
|
||||
* @memberof jibo.bt.behaviors
|
||||
* @description Performs audio speech recognition and applies and parses the results according to a rules file.
|
||||
* @param {Object} options See {@link jibo.bt.Behavior|Behavior} for all options.
|
||||
* @param {jibo.bt.behaviors.ListenJs~GetOptions} options.getOptions Returns the options object.
|
||||
* @param {Function} options.getRule This function returns a string representation of a rule. Use this behavior to dynamically generate rules files instead
|
||||
* of loading a rule file from disk.
|
||||
* @param {jibo.bt.behaviors.ListenJs~OnResult} options.onResult Called and passed a {@link jibo.bt.behaviors.ListenEmitter|ListenEmitter} object. Events are fired from the Listener
|
||||
* at certain points in this behavior's lifecycle.
|
||||
*/
|
||||
@@ -1,23 +0,0 @@
|
||||
/**
|
||||
* @class Point3D
|
||||
* @memberof jibo.bt.behaviors.LookAt
|
||||
* @description Defines a point in 3D space.
|
||||
* @prop {number} x The forward-facing vector in Jibo's coordinate frame (meters).
|
||||
* @prop {number} y The left-facing vector in Jibo's coordinate frame (meters).
|
||||
* @prop {number} z The up-facing vector in Jibo's coordinate frame (meters).
|
||||
*/
|
||||
|
||||
/**
|
||||
* @class LookAt
|
||||
* @extends jibo.bt.Behavior
|
||||
* @memberof jibo.bt.behaviors
|
||||
* @description Makes Jibo look at a 3D point in space.
|
||||
* `LookAt` has two modes. In single-shot mode, this behavior makes Jibo look at the 3D point returned
|
||||
* by `getTarget` and succeeds when Jibo reaches his this target. In continuous mode, `getTarget` is
|
||||
* called every frame, and this behavior will remain in progress indefinitely until explicitly stopped by a
|
||||
* parent bahevior or a decorator.
|
||||
* @param {Object} options See {@link jibo.bt.Behavior|Behavior} for all options.
|
||||
* @param {Function} options.getTarget Returns a {@link jibo.bt.behaviors.LookAt.Point3D} object.
|
||||
* @param {boolean} [options.isContinuous=false] `true` if this behavior is in continous-mode. `false` if in single-shot look-at.
|
||||
* @param {Function} options.config Called and passed a {@link LookatBuilder} object for configuration purposes. Do not call {@link LookatBuilder#startLookat}. This is called automatically by the behavior.
|
||||
*/
|
||||
@@ -1,57 +0,0 @@
|
||||
/**
|
||||
* Load or generate the menu configuration data. For the menu to function, the button actions must
|
||||
* be an 'utterance' or 'press' event - no other button actions will be responded to.
|
||||
* @callback jibo.bt.behaviors.Menu~GetConfig
|
||||
* @param {Function} callback When config has been loaded or generated, pass it to this callback.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Called when a Menu times out or closes. Return value finds an exception handler within the flow. *Unhandled exceptions use the default behavior of returning to Jibo's idle state.
|
||||
* @callback jibo.bt.behaviors.Menu~OnMenuClosed
|
||||
* @param {boolean} timedOut `true` if the menu timed out, `false` if it was closed by the user.
|
||||
* @param {jibo.face.views.MenuView} menu Created menu. Use to read menu position or any other information before the menu closes.
|
||||
* @param {Function} overrideMenuTransition Override the transition out of the menu. Pass a [ChangeOptions]{@link jibo.face.views~ChangeOptions} to control the transition, or `remain` in order to leave the menu in place.
|
||||
* @param {String} results.exception (Flow Only) Exception for the closed menu; either `~InteractionError.MenuTimeout` or `~InteractionError.MenuClosed`.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Called when an item has been chosen.
|
||||
* @callback jibo.bt.behaviors.Menu~OnItemChosen
|
||||
* @param {any} results Either the listen results or the button press event, depending on how the menu is configured.
|
||||
* @param {jibo.face.views.MenuView} menu Created menu. Use to read menu position or any other information before the menu closes.
|
||||
* @param {Function} overrideMenuTransition Call to override the transition out of the menu. Pass a [ChangeOptions]{@link jibo.face.views~ChangeOptions} to control the transition, or `remain` in order to leave the menu in place.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Called to confirm that a positional selection makes sense, allowing custom handling to select certain types of items, if desired. This is called when a user says something like 'open the one on the right', and is called before the onItemChosen callback.
|
||||
* @callback jibo.bt.behaviors.Menu~OnPositionalSelect
|
||||
* @param {jibo.mim.AsrResult} commandASR Data from NLU service.
|
||||
* @param {Number} intendedIndex Index that the menu plans to use for selection.
|
||||
* @param {jibo.face.views.MenuView} menu Created menu. Use to read button data, if you didn't keep track of it yourself.
|
||||
* @returns {number} An item index to change what will be selected, or NaN to select nothing. Returning undefined will use the default behavior (selecting intendedIndex).
|
||||
*/
|
||||
|
||||
/**
|
||||
* @class Menu
|
||||
* @extends jibo.bt.Behavior
|
||||
* @memberof jibo.bt.behaviors
|
||||
* @description A single stage menu.
|
||||
* @param {Object} options See {@link jibo.bt.Behavior|Behavior} for all options.
|
||||
* @param {jibo.bt.behaviors.Menu~GetConfig} options.getConfig Load or generate the menu config data.
|
||||
* @param {jibo.bt.behaviors.Menu~OnMenuClosed} options.onMenuClosed Called if the menu times out or is closed.
|
||||
* @param {jibo.bt.behaviors.Menu~OnItemChosen} options.onItemChosen Called when the user picks an item from the list.
|
||||
* @param {jibo.bt.behaviors.Menu~OnPositionalSelect} options.onPositionalSelect Called when the user uses a positional selection voice command
|
||||
*/
|
||||
|
||||
/**
|
||||
* Callback for button 'press' events from the menu.
|
||||
* @method jibo.bt.Menu#onPressEvent
|
||||
* @param {any} event Whatever the press event from the menu was, as defined in the view config.
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* Callback for when the list is closed for any reason.
|
||||
* @method jibo.bt.Menu#onListClosed
|
||||
* @private
|
||||
*/
|
||||
@@ -1,327 +0,0 @@
|
||||
/**
|
||||
* @class ListenBundle
|
||||
* @memberof jibo.mim
|
||||
* @description Internal bundle of listen specific data.
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* Timeout duration in milliseconds. If NaN, do not timeout.
|
||||
* @name jibo.mim.ListenBundle#timeout
|
||||
* @type {Number}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Beginning of timeout in epoch time. Timeout is triggered when the difference between this
|
||||
* time and current time is greater than timeout.
|
||||
* @name jibo.mim.ListenBundle#startTime
|
||||
* @type {Number}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Listener from jibo.mim#listenDelegate.
|
||||
* @name jibo.mim.ListenBundle#listener
|
||||
* @type {any}
|
||||
*/
|
||||
|
||||
/**
|
||||
* If we had failed to get a listener previously. Skill will be exited on another failure.
|
||||
* @name jibo.mim.ListenBundle#failedToGetListener
|
||||
* @type {boolean}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @class MimStates
|
||||
* @memberof jibo.mim
|
||||
* @description Bundle all the Mim's state machine states, with strong typing. Each
|
||||
* property of MimStates is a State from jibo-state-machine. All actions are performed
|
||||
* by functions of the Mim class.
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* Return prompt data for MIM. Properties are used as variables
|
||||
* for MIM condition evaluation and prompt text insertion.
|
||||
* @callback jibo.bt.behaviors.Mim~GetPromptData
|
||||
* @returns {any}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Called on listen complete during Optional Response or Question, before the MIM analyzes the
|
||||
* result. Allows skills to implement their own check on the result to tell the MIM how to handle it.
|
||||
* @callback jibo.bt.behaviors.Mim~CheckResult
|
||||
* @param {jibo.jetstream.types.ListenResult} result Data from NLU service.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Called when an Optional Response or Question MIM completes successfully.
|
||||
* Optional Response MIMs always complete;
|
||||
* check `options.state.lastResultState` to see if user responded.
|
||||
* @callback jibo.bt.behaviors.Mim~OnSuccess
|
||||
* @param {any} results Bundles data from MIM.
|
||||
* @param {jibo.jetstream.types.ListenResult} results.asrResults Data from NLU service.
|
||||
* @param {jibo.mim.SpeakerIds} results.speakerIds If available, the ID of the speaker.
|
||||
* @param {jibo.mim.MimState} results.state Current state of the MIM.
|
||||
* @param {String} results.firstGrammarTag (Flow use only) The value of the first rule slot (results.asrResults.entities[results.asrResults.slotActions[0]]). The default transition from a MIM. If a listen has multiple potential slots, this value should not be considered reliable.
|
||||
*/
|
||||
|
||||
/**
|
||||
* (Flow use only) Called when a Question MIM fails entirely. Either the user did not
|
||||
* respond to Jibo or Jibo could not understand the user and has run out of error prompts.
|
||||
* Return value finds an error handler within the flow. Unhandled
|
||||
* errors use Jibo's standard MIM exception handling.
|
||||
* @callback jibo.bt.behaviors.Mim~OnFailure
|
||||
* @param {any} results Bundles data from MIM.
|
||||
* @param {jibo.jetstream.types.ListenResult} results.asrResults Data from NLU service.
|
||||
* @param {jibo.mim.SpeakerIds} results.speakerIds If available, the ID of the speaker.
|
||||
* @param {jibo.mim.MimState} results.state Current state of the MIM.
|
||||
* @param {String} results.exception Exception for this failure; either `~InteractionError.noMatch` or `~InteractionError.noInput`.
|
||||
*/
|
||||
|
||||
/**
|
||||
* (Internal to Menu behavior only) Called to confirm that a positional selection makes sense, allowing custom handling to select certain types of items, if desired.
|
||||
* @callback jibo.bt.behaviors.Mim~OnPositionalSelect
|
||||
* @param {jibo.jetstream.types.ListenResult} commandASR Data from NLU service.
|
||||
* @param {Number} intendedIndex Index that the menu plans to use for selection.
|
||||
* @param {jibo.face.views.MenuView} menu Created menu. Use to read button data, if you didn't keep track of it yourself.
|
||||
* @returns {number} Return an item index to change what will be selected, or null to select nothing. Returning undefined will use the default behavior (selecting intendedIndex).
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* @class Mim
|
||||
* @extends jibo.bt.Behavior
|
||||
* @memberof jibo.bt.behaviors
|
||||
* @description Perform a dialogue interaction. Can be an announcement, an
|
||||
* announcement with optional user response, or a
|
||||
* question and attempt to get answer from the user.
|
||||
* @param {Object} options See {@link jibo.bt.Behavior|Behavior} for all options.
|
||||
* @param {String} options.mimPath Path to the MIM file to use.
|
||||
* @param {jibo.bt.behaviors.Mim~GetPromptData} options.getPromptData Returns the prompt variable data.
|
||||
* @param {jibo.bt.behaviors.Mim~CheckResult} options.checkResult Called and passed data about what the Mim is hearing when a listen is complete. Optional Response and Question MIMs only.
|
||||
* @param {jibo.bt.behaviors.Mim~OnSuccess} options.onSuccess Called and passed data about what the Mim heard when it completes. Optional Response and Question MIMs only.
|
||||
* @param {jibo.bt.behaviors.Mim~OnFailure} options.onFailure Called and passed data about what the Mim heard or did not hear when it completes. Question MIMs only.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Stops the MIM. The optional parameter here is considered private - this should be used
|
||||
* like the stop() method it inherits from Behavior, with no parameters.
|
||||
* @method jibo.bt.Mim#stop
|
||||
* @param {boolean} [isMenuTap] If the stop() was due to tapping a menu button.
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* If this Mim has an active view, pops the top view from the list and destroys this Mim's
|
||||
* active view.
|
||||
* @method jibo.bt.Mim#removeView
|
||||
* @private
|
||||
* @param {string} intent Reason for removing the view - 'success', 'failure', or null if view should never be left blank.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Remove combinedRuleHandle and ruleHandle from NLU memory.
|
||||
* @method jibo.bt.Mim#unloadRules
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* Cleans up global event listeners that the MIM added.
|
||||
* @method jibo.bt.Mim#cleanUpEvents
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* Opens the MIM's GUI if it is not already open.
|
||||
* @method jibo.bt.Mim#openGUI
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* Callback for events from `jibo.mim.openGUI`. Opens the GUI, if it is not already open.
|
||||
* @method jibo.bt.Mim#onOpenGUIEvent
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* Callback for events from `jibo.mim.handleSpeech`. Stops active speech or listen to parse a
|
||||
* spoofed utterance.
|
||||
* @method jibo.bt.Mim#onSpeechEvent
|
||||
* @param {string} utterance The spoofed utterance in need of parsing.
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* Callback for events from `jibo.mim.end`. Ends the MIM immediately.
|
||||
* @method jibo.bt.Mim#onEndEvent
|
||||
* @param {any} data Not currently used. In the future, data about how the MIM should end.
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* Callback for events from `jibo.mim.heyJibo`. Handles barge in via "Hey Jibo".
|
||||
* @method jibo.bt.Mim#onHeyJiboHeard
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* State entry for states.loadConfig. Load .mim file(s) and then go to states.loadRule for OR/Q
|
||||
* mims or states.choosePrompt for AN mims.
|
||||
* @method jibo.bt.Mim#loadMim
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* State entry for states.loadRule. Load .mim file(s) and then go to states.loadRule for OR/Q
|
||||
* mims or states.choosePrompt for AN mims. Sets up loading of rule for mim and unionizing it
|
||||
* with mim global rule as a Promise. Immediately go to states.choosePrompt.
|
||||
* @method jibo.bt.Mim#loadRule
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* State entry for states.choosePrompt. Choose prompt text to be played. Go to states.listen
|
||||
* if no prompt or silent prompt chosen. Go to states.speak to speak the prompt.
|
||||
* @method jibo.bt.Mim#choosePrompt
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* State entry for states.speak. Speaks the chosen prompt through the TTS system. When finished,
|
||||
* goes to states.reportResults for an AN mim or states.listen for an OR/Q mim.
|
||||
* @method jibo.bt.Mim#speak
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* State interuption for states.speak. Stops jibo.mim#speakDelegate playback.
|
||||
* @method jibo.bt.Mim#stopSpeaking
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* State entry for states.listen. Starts a listener from jibo.mim#listenDelegate.
|
||||
* If appropriate, starts timeout. If appropriate, displays the mim GUI. When listen completes,
|
||||
* transitions to states.analyzeResults for mim results or states.analyzeMimGlobal for mim
|
||||
* global commands.
|
||||
* @method jibo.bt.Mim#startListen
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* Callback for screen interaction. Resets timeouts during listen.
|
||||
* @method jibo.bt.Mim#resetTimeout
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* State interuption for states.listen. Stops listener and removes mim GUI if present.
|
||||
* @method jibo.bt.Mim#stopListen
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* State exit for states.listen. Stops listener and removes screen touch listeners.
|
||||
* @method jibo.bt.Mim#listenExit
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* State update for states.listen. Tracks timeout. If timeout triggered, change state to
|
||||
* states.analyzeResults.
|
||||
* @method jibo.bt.Mim#updateListen
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* State entry for states.analyzeMimGlobal. If global command is 'repeat', go to
|
||||
* states.choosePrompt. Otherwise, redirect to states.analyzeResults.
|
||||
* @method jibo.bt.Mim#analyzeMimGlobal
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* State entry for states.analyzeMenuGlobal. If global command is 'left' or right' and GUI is
|
||||
* active, scroll list and return to states.listen. Otherwise, redirect to
|
||||
* states.analyzeResults.
|
||||
* @method jibo.bt.Mim#analyzeMenuGlobal
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* State entry for states.parseSpoof. Perform NLU parse on utterance text from GUI, followed * by going to states.reportResults.
|
||||
* @method jibo.bt.Mim#parseSpoofedUtterance
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* State entry for states.analyzeResults. Analyze results to determine if result of listen is
|
||||
* 'noMatch', 'noInput', or 'match'. If result is 'match' or mim has run out of error prompts,
|
||||
* go to states.reportResults. Otherwise, go to states.choosePrompt.
|
||||
* @method jibo.bt.Mim#analyzeResults
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* State entry for states.reportResults. If mim has failed due to noInput or noMatch errors,
|
||||
* call onFailure to allow flow to handle exception. If exception not handled, go to
|
||||
* states.handleException. If mim successful, call onSuccess and end Mim behavior.
|
||||
* @method jibo.bt.Mim#reportResults
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* State entry for states.handleException. Begins global mim exception flow for either 'noInput'
|
||||
* or 'noMatch' failures.
|
||||
* @method jibo.bt.Mim#handleException
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* State update for states.handleException. Update exception flow. If flow is complete act upon
|
||||
* flow result. If flow result is 'exit', exit skill and return to be/idle. If flow result is
|
||||
* 'restart', go to states.choosePrompt.
|
||||
* @method jibo.bt.Mim#updateException
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* State interuption for states.handleException. Stop and clean up exception flow. Remove any
|
||||
* flow created view.
|
||||
* @method jibo.bt.Mim#stopException
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* Display text that mim rules could not match. Provided to noMatch exception flow as a
|
||||
* callback.
|
||||
* @method jibo.bt.Mim#showInputText
|
||||
* @param {string} notMatchedText Text from speech to text that NLU could not match.
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* Remove text display created during noMatch exception flow.
|
||||
* @method jibo.bt.Mim#hideInputText
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* State entry for states.waitForHJEnd. Begins waiting for the Hey Jibo listen to end in a way
|
||||
* that the MIM should recover from.
|
||||
* @method jibo.bt.Mim#waitForHJEnd
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* Standard method to tell the waitForHJEnd state to go back to prompt selection.
|
||||
* @method jibo.bt.Mim#recoverFromHJ
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* State interuption and exiting for states.waitForHJEnd. Removesevent listeners added in the
|
||||
* state entry.
|
||||
* @method jibo.bt.Mim#hjEnded
|
||||
* @private
|
||||
*/
|
||||
@@ -1,7 +0,0 @@
|
||||
/**
|
||||
* @class Null
|
||||
* @extends jibo.bt.Behavior
|
||||
* @memberof jibo.bt.behaviors
|
||||
* @description `Null` has no behavior. It will remain in-progress until a decorator changes its state.
|
||||
* @param {Object} options See {@link jibo.bt.Behavior|Behavior} for all options.
|
||||
*/
|
||||
@@ -1,10 +0,0 @@
|
||||
/**
|
||||
* @class Parallel
|
||||
* @extends jibo.bt.ParentBehavior
|
||||
* @memberof jibo.bt.behaviors
|
||||
* @description Runs its child nodes in parallel. Returns with `Status.FAILED` if one of the children failed
|
||||
* and `Status.SUCCEEDED` if all children succeeded.
|
||||
* @param {Object} options See {@link jibo.bt.Behavior} for all options.
|
||||
* @param {boolean} options.succeedOnOne If the Parallel should succeed when the first child succeeds.
|
||||
* @param {Array<jibo.bt.Behavior>} [options.children=[]] An array of the Parallel's child behaviors.
|
||||
*/
|
||||
@@ -1,15 +0,0 @@
|
||||
/**
|
||||
* @class PlayAnimation
|
||||
* @extends jibo.bt.Behavior
|
||||
* @memberof jibo.bt.behaviors
|
||||
* @description Plays the animation specified by `animPath`, `animName` or `animQuery`.
|
||||
* Succeeds when the animation is finished playing. Playing an
|
||||
* animation consists of two phases: the transition phase and the play phase. The transition phase will
|
||||
* transition Jibo from his current position to the start position of the specified animation. The play
|
||||
* phase plays the animation with the current configuration.
|
||||
* @param {Object} options See {@link jibo.bt.Behavior|Behavior} for all options.
|
||||
* @param {String} options.animPath The path to the `.keys` file. This behavior assumes `${project}/animations` is the root for
|
||||
* all the animations.
|
||||
* @param {Function} options.config Called and passed a {@link AnimationBuilder} object for configuration purposes. Do not call
|
||||
* {@link AnimationBuilder#play}. This is done automatically by the behavior.
|
||||
*/
|
||||
@@ -1,10 +0,0 @@
|
||||
/**
|
||||
* @class PlayAudio
|
||||
* @extends jibo.bt.Behavior
|
||||
* @memberof jibo.bt.behaviors
|
||||
* @description Plays the audio specified by `audioPath`. Succeeds when the audio file is finished playing.
|
||||
* @param {Object} options See {@link jibo.bt.Behavior|Behavior} for all options.
|
||||
* @param {String} options.audioPath The path to any audo file format supported by HTML5 Audio. This behaviuor assumes `${project}/audio`
|
||||
* is the root for all the audio files.
|
||||
* @param {Boolean} options.cache=true `true` to cache the audio. `false` to play once and destroy.
|
||||
*/
|
||||
@@ -1,8 +0,0 @@
|
||||
/**
|
||||
* @class Random
|
||||
* @extends jibo.bt.ParentBehavior
|
||||
* @memberof jibo.bt.behaviors
|
||||
* @description On start, will choose a random node in its children and run that node.
|
||||
* @param {Object} options See {@link jibo.bt.Behavior|Behavior} for all options.
|
||||
* @param {Array<jibo.bt.Behavior>} [options.children=[]] An array of child behaviors.
|
||||
*/
|
||||
@@ -1,18 +0,0 @@
|
||||
/**
|
||||
* Function that returns the options object.
|
||||
* @callback jibo.bt.behaviors.ReadBarcode~onBarcode
|
||||
* @param {string|null} error `null` if no error.
|
||||
* @param {Object} data The result of the barcode if one is found.
|
||||
* @param {number} data.type The type of barcode found. The types are 0 (EAN8), 1 (UPCE), 2 (ISBN10), 3 (UPCA), 4 (EAN13),
|
||||
* 5 (ISBN13), 6 (Interleaved 2 of 5), 7 (Code 39), 8 (PDF417), 9 (QR-Code), 10 (Code 128).
|
||||
* @param {String} data.content The payload of the detected barcode.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @class ReadBarcode
|
||||
* @extends jibo.bt.Behavior
|
||||
* @memberof jibo.bt.behaviors
|
||||
* @description Makes Jibo take a photo and search for a barcode or QR code in that image.
|
||||
* @param {Object} options See {@link jibo.bt.Behavior|Behavior} for all options.
|
||||
* @param {jibo.bt.behaviors.ReadBarcode~onBarcode} options.onBarcode Called after Jibo takes a picture. The results are passed to this function.
|
||||
*/
|
||||
@@ -1,9 +0,0 @@
|
||||
/**
|
||||
* @class Sequence
|
||||
* @extends jibo.bt.ParentBehavior
|
||||
* @memberof jibo.bt.behaviors
|
||||
* @description `Sequence` Runs its child nodes in sequence until one fails. Fails if one of the children fails
|
||||
* and succeeds if all its children succeeded.
|
||||
* @param {Object} options See {@link jibo.bt.Behavior|Behavior} for all options.
|
||||
* @param {Array<jibo.bt.Behavior>} [options.children=[]] - An array of child behaviors.
|
||||
*/
|
||||
@@ -1,14 +0,0 @@
|
||||
/**
|
||||
* @class Subtree
|
||||
* @extends jibo.bt.Behavior
|
||||
* @memberof jibo.bt.behaviors
|
||||
* @description Encapsulates an external subtree file (`.bt` file) into a single behavior. This behavior fails if the
|
||||
* `.bt` tree fails, and succeeds if that tree succeeds.
|
||||
* @param {Object} options See {@link jibo.bt.Behavior|Behavior} for all options.
|
||||
* @param {String} options.behaviorPath The path to the `.bt` file this behavior encapsulates. This assumes the root
|
||||
* of all `.bt` files is in `${project}/behaviors`.
|
||||
* @param {Function} options.getNotepad Returns an object that will become this referenced tree's notepad. Think of this as the
|
||||
* arguments to this subtree.
|
||||
* @param {Function} options.onResult Called when the external tree either fails or succeeds. A result object is passed as an argument.
|
||||
* This result object is populated by the external tree. Think of this as the return value.
|
||||
*/
|
||||
@@ -1,14 +0,0 @@
|
||||
/**
|
||||
* @class SubtreeJs
|
||||
* @extends jibo.bt.Behavior
|
||||
* @memberof jibo.bt.behaviors
|
||||
* @description Encapsulates an external subtree file (`.bt` file) into a single behavior. This behavior fails if the
|
||||
* `.bt` tree fails and succeeds if that tree succeeds.
|
||||
* @param {Object} options See {@link jibo.bt.Behavior|Behavior} for all options.
|
||||
* @param {Function} options.getFileName Returns the path to the `.bt` file this behavior encapsulates. This assumes the root
|
||||
* of all `.bt` files is in `${project}/behaviors`.
|
||||
* @param {Function} options.getNotepad Returns an object that will become this referenced tree's notepad. Think of this as the
|
||||
* arguments to this subtree.
|
||||
* @param {Function} options.onResult Called when the external tree either fails or succeeds. A result object is passed as an argument.
|
||||
* This result object is populated by the external tree. Think of this as the return value.
|
||||
*/
|
||||
@@ -1,9 +0,0 @@
|
||||
/**
|
||||
* @class Switch
|
||||
* @extends jibo.bt.ParentBehavior
|
||||
* @memberof jibo.bt.behaviors
|
||||
* @description Runs its child nodes in sequence until one succeeds. Fails if all of the children failed
|
||||
* and succeeds if one child succeeded.
|
||||
* @param {Object} options See {@link jibo.bt.Behavior|Behavior} for all options.
|
||||
* @param {Array<jibo.bt.Behavior>} [options.children=[]] - An array of the Switch's child behaviors.
|
||||
*/
|
||||
@@ -1,22 +0,0 @@
|
||||
/**
|
||||
* Callback for when Jibo takes a photo.
|
||||
* ```
|
||||
* let behavior = TakePhoto(2, 2, (url) => {
|
||||
* let img = new Image();
|
||||
* img.src = url;
|
||||
* });
|
||||
* ```
|
||||
* @callback jibo.bt.behaviors.TakePhoto~OnPhoto
|
||||
* @param {String} url The image URL.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @class TakePhoto
|
||||
* @extends jibo.bt.Behavior
|
||||
* @memberof jibo.bt.behaviors
|
||||
* @description Makes Jibo take a photo and will return a URL to get the contents of that photo.
|
||||
* @param {Object} options Options
|
||||
* @param {number} options.resolution `1` for a 672x380 photo. `2` for 1280x720 photo. `3` for 1920x1080 photo. `4` for 2688x1520 photo.
|
||||
* @param {jibo.bt.behaviors.TakePhoto~OnPhoto} options.onPhoto Called after Jibo takes a picture. The image URL is passed to this function.
|
||||
* @param {boolean} [options.noDistortion=true] Removes camera distortion from photo.
|
||||
*/
|
||||
@@ -1,9 +0,0 @@
|
||||
/**
|
||||
* @class TextToSpeech
|
||||
* @extends jibo.bt.Behavior
|
||||
* @memberof jibo.bt.behaviors
|
||||
* @description Makes Jibo speak.
|
||||
* @param {Object} options See {@link jibo.bt.Behavior|Behavior} for all options.
|
||||
* @param {String} options.words The words for Jibo to speak.
|
||||
* @param {Function} options.onWord Called each time a word is spoken. The word that is spoken is passed as an argument.
|
||||
*/
|
||||
@@ -1,9 +0,0 @@
|
||||
/**
|
||||
* @class TextToSpeechJs
|
||||
* @extends jibo.bt.Behavior
|
||||
* @memberof jibo.bt.behaviors
|
||||
* @description Makes Jibo speak.
|
||||
* @param {Object} options See {@link jibo.bt.Behavior|Behavior} for all options.
|
||||
* @param {Function} options.getWords Function that returns a string of the words for Jibo to speak.
|
||||
* @param {Function} options.onWord Called each time a word is spoken. The word that is spoken is passed as an argument.
|
||||
*/
|
||||
@@ -1,14 +0,0 @@
|
||||
/**
|
||||
* @callback jibo.bt.behaviors.TimeoutJs~GetTime
|
||||
* @returns {number} Time in milliseconds until the behavior succeeds.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @class TimeoutJs
|
||||
* @extends jibo.bt.Behavior
|
||||
* @memberof jibo.bt.behaviors
|
||||
* @description
|
||||
* Succeeds only after the specified amount of time.
|
||||
* @param {Object} options See {@link jibo.bt.Behavior|Behavior} for all options.
|
||||
* @param {jibo.bt.behaviors.TimeoutJs~GetTime} options.getTime - Time in milliseconds until `TimeoutJs` succeeds.
|
||||
*/
|
||||
@@ -1,4 +0,0 @@
|
||||
/**
|
||||
* Behavior Tree behavior classes.
|
||||
* @namespace jibo.bt.behaviors
|
||||
*/
|
||||
@@ -1,16 +0,0 @@
|
||||
/**
|
||||
* @callback jibo.bt.decorators.Case~CaseConditional
|
||||
* @returns {boolean} Return `true` to succeed the decorated behavior. `false` otherwise.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @class Case
|
||||
* @extends jibo.bt.Decorator
|
||||
* @memberof jibo.bt.decorators
|
||||
* @description
|
||||
* Performs a single check before the behavior it's decorating starts. If that check fails, `Case` fails the
|
||||
* behavior. Useful for decorating behaviors under a {@link jibo.bt.behaviors.Switch|Switch} behavior.
|
||||
* @param {Object} options See {@link jibo.bt.Decorator|Decorator} for all options.
|
||||
* @param {jibo.bt.decorators.Case~CaseConditional} options.conditional Function called every frame.
|
||||
* Return `false` when you want component to fail.
|
||||
*/
|
||||
@@ -1,18 +0,0 @@
|
||||
/**
|
||||
* @callback jibo.bt.decorators.FailOnCondition~FailOnConditionConditional
|
||||
* @extends jibo.bt.Behavior
|
||||
* @returns {boolean} `true` when you want component to Fail.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @class FailOnCondition
|
||||
* @extends jibo.bt.Decorator
|
||||
* @memberof jibo.bt.decorators
|
||||
* @description Explicitly interrupts the behavior it's decorating and returns Status.FAILED if the
|
||||
* conditional evaluates to `true`.
|
||||
* @param {Object} options See {@link jibo.bt.Decorator|Decorator} for all options.
|
||||
* @param {Function} [options.init] Function called at the start of this behavior. Used to initialize any
|
||||
* variables or data.
|
||||
* @param {jibo.bt.decorators.FailOnCondition~FailOnConditionConditional} options.conditional Function called every frame. Return `true`
|
||||
* when you want component to fail.
|
||||
*/
|
||||
@@ -1,15 +0,0 @@
|
||||
/**
|
||||
* @callback jibo.bt.decorators.StartOnAnimEvent~StartOnEventOnReceived
|
||||
* @argument {AnimationInstance} instance The animation instance the event was dispatched from.
|
||||
* @argument {Object} payload A payload defined in the `.keys` file.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @class StartOnAnimEvent
|
||||
* @extends jibo.bt.Decorator
|
||||
* @memberof jibo.bt.decorators
|
||||
* @description
|
||||
* `StartOnAnimEvent` will begin the execution of its behavior when an animation fires an event from its event layer.
|
||||
* @param {String} options.eventName - Name of the event this behavior listens for.
|
||||
* @param {jibo.bt.decorators.StartOnAnimEvent~StartOnEventOnReceived} options.onReceived - Callback for when `eventName` is dispatched.
|
||||
*/
|
||||
@@ -1,17 +0,0 @@
|
||||
/**
|
||||
* @callback jibo.bt.decorators.StartOnCondition~StartOnConditionConditional
|
||||
* @returns {Boolean} `true` when you want component to start.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @class StartOnCondition
|
||||
* @extends jibo.bt.Decorator
|
||||
* @memberof jibo.bt.decorators
|
||||
* @description
|
||||
* Prevents the behavior it's decorating from starting until its conditional evaluates to
|
||||
* true.
|
||||
* @param {Object} options See {@link jibo.bt.Decorator|Decorator} for all options.
|
||||
* @param {Function} [options.init] Function called at the start of this behavior. Used to initialize any
|
||||
* variables or data.
|
||||
* @argument {jibo.bt.decorators.StartOnCondition~StartOnConditionConditional} options.conditional - The conditional to evaluate.
|
||||
*/
|
||||
@@ -1,11 +0,0 @@
|
||||
/**
|
||||
* @class StartOnEvent
|
||||
* @extends jibo.bt.Decorator
|
||||
* @memberof jibo.bt.decorators
|
||||
* @description
|
||||
* Prevents the behavior it's decorating from starting until an event is emitter from a behavior tree's
|
||||
* global `emitter`.
|
||||
* @param {Object} options See {@link jibo.bt.Decorator|Decorator} for all options.
|
||||
* @param {String} options.eventName - The name of the event to listen for.
|
||||
* @param {Function} options.onEvent - called when the event is fired. Any payload with the event is passed in.
|
||||
*/
|
||||
@@ -1,18 +0,0 @@
|
||||
/**
|
||||
* @callback jibo.bt.decorators.SucceedOnCondition~SucceedOnConditionConditional
|
||||
* @returns {Boolean} `true` to succeed the decorated behavior. `false` otherwise.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @class SucceedOnCondition
|
||||
* @extends jibo.bt.Decorator
|
||||
* @memberof jibo.bt.decorators
|
||||
* @description
|
||||
* Explicitly interrupts the behavior it's decorating and returns Status.SUCCEEDED if the
|
||||
* conditional evaluates to `true`.
|
||||
* @param {Object} options See {@link jibo.bt.Decorator|Decorator} for all options.
|
||||
* @param {Function} [options.init] Function called at the start of this behavior. Used to initialize any
|
||||
* variables or data.
|
||||
* @param {jibo.bt.decorators.SucceedOnCondition~SucceedOnConditionConditional} options.conditional Function called every frame. Return `true`
|
||||
* to force behavior to succeed.
|
||||
*/
|
||||
@@ -1,44 +0,0 @@
|
||||
/**
|
||||
* @class EmbeddedListenEmitter
|
||||
* @description Passed to the {@link jibo.bt.decorators.SucceedOnEmbedded~OnResult} function of the {@link jibo.bt.decorators.SucceedOnEmbedded|SucceedOnEmbedded} behavior.
|
||||
* @extends EventEmitter
|
||||
* @memberof jibo.bt
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} jibo.bt.decorators.SucceedOnEmbedded~Options
|
||||
*/
|
||||
|
||||
/**
|
||||
* Called when the {@link jibo.bt.EmbeddedListenEmitter|EmbeddedListenEmitter} is constructed.
|
||||
* @callback jibo.bt.decorators.SucceedOnEmbedded~OnResult
|
||||
* @param {jibo.bt.EmbeddedListenEmitter} listener Use this instance to listen for listen events.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @class SucceedOnEmbedded
|
||||
* @extends jibo.bt.Decorator
|
||||
* @memberof jibo.bt.decorators
|
||||
* @description Succeeds the behavior its decorating when the specified audio phrase is spotted.
|
||||
* @param {Object} options See {@link jibo.bt.Decorator|Decorator} for all options.
|
||||
* @param {jibo.bt.decorators.SucceedOnEmbedded.Rules} options.rule The embedded rule to listen for.
|
||||
* @param {jibo.bt.decorators.SucceedOnEmbedded~OnResult} options.onResult Called and passed a {@link jibo.bt.EmbeddedListenEmitter|EmbeddedListenEmitter} object. Events are
|
||||
* fired from the emitter at certain points in this behavior's lifecycle.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Embedded listen rule types.
|
||||
* @name jibo.bt.decorators.SucceedOnEmbedded.Rules
|
||||
* @property {String} HEY_jibo Listen for "Hey, Jibo"
|
||||
* @static
|
||||
* @enum {String}
|
||||
*/
|
||||
|
||||
/** Listen for "Hey Jibo" */
|
||||
|
||||
/**
|
||||
* Fired when the phrase is spotted.
|
||||
* @event jibo.bt.EmbeddedListenEmitter#hey-jibo
|
||||
* @param {Object} result Reference to the behavior's result.
|
||||
* @param {Array} speakers Text independent speaker ID results.
|
||||
*/
|
||||
@@ -1,11 +0,0 @@
|
||||
/**
|
||||
* @class SucceedOnEvent
|
||||
* @extends jibo.bt.Decorator
|
||||
* @memberof jibo.bt.decorators
|
||||
* @description
|
||||
* Succeeds the behavior it's decorating when an event is emitter from a behavior tree's
|
||||
* global `emitter`.
|
||||
* @param {Object} options See {@link jibo.bt.Decorator|Decorator} for all options.
|
||||
* @param {String} options.eventName - The name of the event to listen for.
|
||||
* @param {Function} options.onEvent - Called when the event is fired. Any payload with the event is passed in.
|
||||
*/
|
||||
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* @typedef {Object} jibo.bt.decorators.SucceedOnListen~Options
|
||||
* @property {boolean} heyJibo Listen for "Hey, Jibo" first.
|
||||
* @property {boolean} detectEnd Listen for end of speech.
|
||||
* @property {boolean} incremental Return incremental ASR results as they are streamed from the cloud.
|
||||
* @property {String} authenticateSpeaker Authenticates against that person.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Function that returns the options object.
|
||||
* @callback jibo.bt.decorators.SucceedOnListen~GetOptions
|
||||
* @returns {jibo.bt.decorators.SucceedOnListen~Options}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Called when the {@link jibo.bt.behaviors.ListenEmitter|ListenEmitter} is constructed.
|
||||
* @callback jibo.bt.decorators.SucceedOnListen~OnResult
|
||||
* @param {jibo.bt.ListenEmitter} listener Use this instance to listen for listen events.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @class SucceedOnListen
|
||||
* @extends jibo.bt.Decorator
|
||||
* @memberof jibo.bt.decorators
|
||||
* @description Performs audio speech recognition and applies and parses the results according to a rules file.
|
||||
* @param {Object} options See {@link jibo.bt.Decorator|Decorator} for all options.
|
||||
* @param {jibo.bt.decorators.SucceedOnListen~GetOptions} options.getOptions Returns the options object.
|
||||
* @param {String} options.rule Path the to `.rule` file. This assumes the path starts at `${project}/rules`.
|
||||
* @param {jibo.bt.decorators.SucceedOnListen~OnResult} options.onResult Called and passed a {@link jibo.bt.behaviors.ListenEmitter|ListenEmitter} object. Events are fired from the Listener
|
||||
* at certain points in this behavior's lifecycle.
|
||||
*/
|
||||
@@ -1,47 +0,0 @@
|
||||
/**
|
||||
* @class ListenEmitter
|
||||
* @description Used to listen for cloud events.
|
||||
* @extends EventEmitter
|
||||
* @memberof jibo.bt
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} jibo.bt.decorators.SucceedOnListenJs~Options
|
||||
* @property {boolean} heyJibo Listen for "Hey, Jibo" first.
|
||||
* @property {boolean} detectEnd Listen for end of speech.
|
||||
* @property {boolean} incremental Return incremental ASR results as they are streamed from the cloud.
|
||||
* @property {String} authenticateSpeaker Authenticates against that person.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Function that returns the options object.
|
||||
* @callback jibo.bt.decorators.SucceedOnListenJs~GetOptions
|
||||
* @returns {jibo.bt.decorators.SucceedOnListenJs~Options}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Called when the {@link jibo.bt.behaviors.ListenEmitter|ListenEmitter} is constructed.
|
||||
* @callback jibo.bt.decorators.SucceedOnListenJs~OnResult
|
||||
* @param {jibo.bt.ListenEmitter} listener Use this instance to listen for listen events.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @class SucceedOnListenJs
|
||||
* @extends jibo.bt.Decorator
|
||||
* @memberof jibo.bt.decorators
|
||||
* @description Performs audio speech recognition and applies and parses the results according to a rules file.
|
||||
* @param {Object} options See {@link jibo.bt.Decorator|Decorator} for all options.
|
||||
* @param {jibo.bt.decorators.SucceedOnListenJs~GetOptions} options.getOptions Returns the options object.
|
||||
* @param {Function} options.getRule This function returns a string representation of a rule. Use this decorator to dynamically generate rules files instead
|
||||
* of loading a rule file from disk.
|
||||
* @param {jibo.bt.decorators.SucceedOnListenJs~OnResult} options.onResult Called and passed a {@link jibo.bt.behaviors.ListenEmitter|ListenEmitter} object. Events are fired from the Listener
|
||||
* at certain points in this behavior's lifecycle.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Definition of the reusable dialog listen behavior class
|
||||
*/
|
||||
|
||||
/**
|
||||
* Only gets called when stopped from Behavior tree
|
||||
*/
|
||||
@@ -1,8 +0,0 @@
|
||||
/**
|
||||
* @class TimeoutFail
|
||||
* @extends jibo.bt.Decorator
|
||||
* @memberof jibo.bt.decorators
|
||||
* @description Forces the behavior it's decorating to fail after the specified amount of time.
|
||||
* @param {Object} options See {@link jibo.bt.Decorator|Decorator} for all options.
|
||||
* @param {number} options.timeout - Time in milliseconds until `TimeoutFail` forces the behavior it's decorarting to fail.
|
||||
*/
|
||||
@@ -1,9 +0,0 @@
|
||||
/**
|
||||
* @class TimeoutSucceed
|
||||
* @extends jibo.bt.Decorator
|
||||
* @memberof jibo.bt.decorators
|
||||
* @description
|
||||
* Forces the behavior it's decorating to succeed after the specified amount of time.
|
||||
* @param {Object} options See {@link jibo.bt.Decorator|Decorator} for all options.
|
||||
* @param {number} options.timeout - Time in milliseconds until `TimeoutSucceed` forces the behavior it's decorating to succeed.
|
||||
*/
|
||||
@@ -1,14 +0,0 @@
|
||||
/**
|
||||
* @callback jibo.bt.decorators.TimeoutSucceedJs~GetTime
|
||||
* @returns {number} Time in milliseconds.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @class TimeoutSucceedJs
|
||||
* @extends jibo.bt.Decorator
|
||||
* @memberof jibo.bt.decorators
|
||||
* @description
|
||||
* `TimeoutSucceedJs` forces the behavior it's decorating to succeed after the specified amount of time.
|
||||
* @param {Object} options See {@link jibo.bt.Decorator|Decorator} for all options.
|
||||
* @param {jibo.bt.decorators.TimeoutSucceedJs~GetTime} options.getTime - Time in milliseconds until `TimeoutJs` forces the behavior it's decorating to succeed.
|
||||
*/
|
||||
@@ -1,17 +0,0 @@
|
||||
/**
|
||||
* @callback jibo.bt.decorators.WhileCondition~Conditional
|
||||
* @returns {boolean} `true` when you want component to restart.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @class WhileCondition
|
||||
* @extends jibo.bt.Decorator
|
||||
* @memberof jibo.bt.decorators
|
||||
* @description When `WhileCondition`'s component succeeds, `WhileCondition`
|
||||
* will evaluate its conditional. If it evaluates to `true`, `WhileCondition` will
|
||||
* start its component again. If the conditional evaluates to `false`, `WhileCondition`
|
||||
* returns the status of its component.
|
||||
* @param {Object} options See {@link jibo.bt.Decorator|Decorator} for all options.
|
||||
* @param {Function} [options.init] - Initialization function.
|
||||
* @param {jibo.bt.decorators.WhileCondition~Conditional} options.conditional - The conditional to evaluate.
|
||||
*/
|
||||
@@ -1,4 +0,0 @@
|
||||
/**
|
||||
* Behavior Tree decorator classes.
|
||||
* @namespace jibo.bt.decorators
|
||||
*/
|
||||
@@ -1,16 +0,0 @@
|
||||
/**
|
||||
* @interface jibo.bt.behaviors.Mim~AsrMetadata
|
||||
* @private
|
||||
* @description Type of metadata supplied to ASR about MIM listens. Not exposed outside the library.
|
||||
* @prop {String} interactionId - UUID for that instance of a MIM.
|
||||
* @prop {String} mimId - ID for the MIM file being used.
|
||||
* @prop {String} mimType - The type of MIM.
|
||||
* @prop {String} skill - The name of the skill in use.
|
||||
* @prop {String} mimState - The state of the MIM.
|
||||
* @prop {Number} noMatches - The number of no match errors that the MIM has had.
|
||||
* @prop {Number} noInput - The number of no input errors that the MIM has had.
|
||||
* @prop {String} lastPromptId - The ID of the last MIM prompt to play.
|
||||
* @prop {String} [activityName] - Flow only: The name of the MIM activity in the flow.
|
||||
* @prop {String} [activityId] - Flow only: The uuid of the MIM activity in the flow.
|
||||
* @prop {String} [flowFile] - Flow only: The flow file that the MIM activity is in.
|
||||
*/
|
||||
@@ -1,70 +0,0 @@
|
||||
/**
|
||||
* MIM configuration data generated from the .mim files.
|
||||
* @class MimConfig
|
||||
* @memberof jibo.mim
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* Promised loading of a single .mim file.
|
||||
* @method jibo.mim.MimConfig~load
|
||||
* @param {string} filePath Path to the .mim file.
|
||||
* @returns {Promise<MimConfig>} Promise to load a new MimConfig instance.
|
||||
* @static
|
||||
*/
|
||||
|
||||
/**
|
||||
* If the Mim has a gui available, either as sampleUtterances or guiConfig.
|
||||
* @name jibo.mim.MimConfig~hasGui
|
||||
* @type {Boolean}
|
||||
* @readOnly
|
||||
*/
|
||||
|
||||
/**
|
||||
* Find a prompt by id and get the original prompt text for it.
|
||||
* @method jibo.mim.MimConfig#getOriginalPromptText
|
||||
* @param {string} id Prompt id to get the text for.
|
||||
* @return {String} The original prompt text string.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Pick a prompt and get the text for it.
|
||||
* @method jibo.mim.MimConfig~getPromptText
|
||||
* @param {jibo.mim.MimState} mimState State of the active MIM.
|
||||
* @param {any} promptData Dictionary of variables to use for condition evaluation and text replacement.
|
||||
* @param {jibo.kb.Node} [rotationNode] Node used to store data
|
||||
* @return {Object} An object with 'text', 'id', and 'autoRuleOverride' properties.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Generate a view config object for the MIM GUI, using the javascript code from the .mim file.
|
||||
* @method jibo.mim.MimConfig~getJavascriptGUI
|
||||
* @param {any} promptData Prompt data for the MIM
|
||||
* @return {any} The generated view config object
|
||||
*/
|
||||
|
||||
/**
|
||||
* See if MimConfig has error prompts of the corresponding type and index.
|
||||
* @method jibo.mim.MimConfig~hasErrorPrompt
|
||||
* @param {String} error The error type
|
||||
* @param {Number} index The prompt index
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
/**
|
||||
* See if MimConfig has at least one verbose prompt.
|
||||
* @method jibo.mim.MimConfig~hasVerbosePrompt
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
/**
|
||||
* See if MimConfig has at least one truncated prompt.
|
||||
* @method jibo.mim.MimConfig~hasTruncatedPrompt
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
/**
|
||||
* See if MimConfig has at least one thanks prompt.
|
||||
* @method jibo.mim.MimConfig~hasThanksPrompt
|
||||
* @return {Boolean}
|
||||
*/
|
||||