Version 3.1 InDev

This commit is contained in:
2026-03-16 19:20:27 +02:00
parent 81e6e0a7a2
commit d7a6f43af1
224 changed files with 2168 additions and 14011 deletions

View File

@@ -1,5 +0,0 @@
{
"label": "VERY Fun Stuff",
"isSubmenu": true,
"icon": "core://resources/actionIcons/fun.png"
}

View File

@@ -1,6 +0,0 @@
{
"label": "AAAAA",
"icon": "core://resources/actionIcons/clock.png",
"colors": "blue",
"skillId": "@be/clock"
}

View File

@@ -1,2 +1,10 @@
# JiboOs # JiboOs
- - -
# Release Version 3.3.0 InDev
Chagelog.....
you can make custom menu buttons!

View 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 $?

View 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 $?

View 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

View 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) { }
};

View 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.

View File

@@ -12,7 +12,7 @@ When Jibo boots up, the `main-menu-patch.js` module:
## Quick Start ## 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/`) 1. Create a folder in the `Skills` directory (e.g., `MySkill/`)
2. Add a `menuEntry.json` file inside: 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` 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! 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/ /opt/jibo/Jibo/Skills/

View File

@@ -10,11 +10,38 @@
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
// Skills root directory const menuEntries = require('./menu-entries');
const SKILLS_ROOT = '/opt/jibo/Jibo/Skills'; 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 // Cache for scanned skills
let _cachedMenuEntries = null; let _cachedMenuEntries = null;
let _cachedSkillsRoot = null;
let _cachedProvidersDir = null;
let _submenuConfigs = {}; let _submenuConfigs = {};
let _dynamicSkillIds = new Set(); // Track dynamic skill IDs for routing let _dynamicSkillIds = new Set(); // Track dynamic skill IDs for routing
let _beInstance = null; let _beInstance = null;
@@ -40,6 +67,11 @@ const LOG_FILE = '/tmp/menu-patch.log';
function log(msg, ...args) { function log(msg, ...args) {
const line = '[MENU-PATCH] ' + msg + ' ' + args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' '); const line = '[MENU-PATCH] ' + msg + ' ' + args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' ');
console.log(line); console.log(line);
try {
if (robotLogger && typeof robotLogger.raw === 'function') {
robotLogger.raw(line);
}
} catch (e) { }
try { try {
fs.appendFileSync(LOG_FILE, new Date().toISOString() + ' ' + line + '\n'); fs.appendFileSync(LOG_FILE, new Date().toISOString() + ' ' + line + '\n');
} catch (e) {} } catch (e) {}
@@ -48,6 +80,11 @@ function log(msg, ...args) {
function warn(msg, ...args) { function warn(msg, ...args) {
const line = '[MENU-PATCH WARN] ' + msg + ' ' + args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' '); const line = '[MENU-PATCH WARN] ' + msg + ' ' + args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' ');
console.warn(line); console.warn(line);
try {
if (robotLogger && typeof robotLogger.raw === 'function') {
robotLogger.raw(line);
}
} catch (e) { }
try { try {
fs.appendFileSync(LOG_FILE, new Date().toISOString() + ' ' + line + '\n'); fs.appendFileSync(LOG_FILE, new Date().toISOString() + ' ' + line + '\n');
} catch (e) {} } catch (e) {}
@@ -172,59 +209,37 @@ function scanSubmenuChildren(submenuDir) {
* Scan the Skills root directory for all menu entries * Scan the Skills root directory for all menu entries
*/ */
function scanAllMenuEntries() { function scanAllMenuEntries() {
if (_cachedMenuEntries) { if (_cachedMenuEntries && _cachedSkillsRoot === SKILLS_ROOT) {
log('Returning cached entries:', _cachedMenuEntries.length); log('Returning cached entries:', _cachedMenuEntries.length);
return _cachedMenuEntries; return _cachedMenuEntries;
} }
log('Scanning Skills directory:', SKILLS_ROOT); log('Scanning Skills directory:', SKILLS_ROOT);
_dynamicSkillIds.clear(); _dynamicSkillIds.clear();
const entries = [];
let children; const res = menuEntries.getMenuEntries({
skillsRoot: SKILLS_ROOT,
// Check if directory exists log: function () {
if (!fs.existsSync(SKILLS_ROOT)) { try { log.apply(null, arguments); } catch (e) { }
warn('Skills directory does not exist:', SKILLS_ROOT); }
return entries; });
}
_cachedMenuEntries = res.entries || [];
_cachedSkillsRoot = res.skillsRoot;
_cachedProvidersDir = res.providersDir;
// update dynamic id set
try { try {
children = fs.readdirSync(SKILLS_ROOT); if (res.dynamicSkillIds && typeof res.dynamicSkillIds.forEach === 'function') {
log('Found', children.length, 'items in Skills directory:', children.join(', ')); res.dynamicSkillIds.forEach(function (id) { _dynamicSkillIds.add(id); });
} catch (e) { }
warn('Failed to read skills directory:', SKILLS_ROOT, e.message); } catch (e) { }
return entries;
log('Total entries found:', _cachedMenuEntries.length);
if (_cachedProvidersDir) {
log('Providers dir:', _cachedProvidersDir);
} }
return _cachedMenuEntries;
children.forEach(name => {
if (name.startsWith('.') || name === '@be' || name === 'node_modules') {
log('Skipping:', name);
return;
}
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;
} }
/** /**
@@ -427,7 +442,7 @@ function patchMainMenuSkill(be) {
} }
// Handle dynamic submenus // Handle dynamic submenus
if (skill && skill.startsWith('dynamic-submenu-')) { if (skill && strStartsWith(skill, 'dynamic-submenu-')) {
log('Showing submenu:', skill); log('Showing submenu:', skill);
const submenuConfig = _submenuConfigs[skill]; const submenuConfig = _submenuConfigs[skill];
if (submenuConfig && _jibo && _jibo.face && _jibo.face.views) { if (submenuConfig && _jibo && _jibo.face && _jibo.face.views) {
@@ -443,11 +458,15 @@ function patchMainMenuSkill(be) {
if (result && result.on) { if (result && result.on) {
result.on('select', (selection) => { result.on('select', (selection) => {
log('Submenu item selected:', selection); log('Submenu item selected:', selection);
if (selection && selection.action && selection.action.data) { try {
const destination = selection.action.data.utterance?.entities?.destination; if (selection && selection.action && selection.action.data && selection.action.data.utterance && selection.action.data.utterance.entities) {
if (destination) { const destination = selection.action.data.utterance.entities.destination;
this.redirectToSkill(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:') // Handle dynamic skills (prefixed with 'dynamic:')
if (skill && skill.startsWith('dynamic:')) { if (skill && strStartsWith(skill, 'dynamic:')) {
const actualSkillId = skill.substring(8); // Remove 'dynamic:' prefix const actualSkillId = skill.substring(8); // Remove 'dynamic:' prefix
log('Launching dynamic skill:', actualSkillId); log('Launching dynamic skill:', actualSkillId);
@@ -530,6 +549,14 @@ function applyPatch(be) {
function patchLoader(jibo, skillsRoot) { function patchLoader(jibo, skillsRoot) {
log('patchLoader called'); log('patchLoader called');
_jibo = jibo; _jibo = jibo;
// Allow caller to override skills root.
if (skillsRoot && typeof skillsRoot === 'string') {
SKILLS_ROOT = skillsRoot;
} else {
SKILLS_ROOT = skillsRootUtil.resolveSkillsRoot();
}
clearCache();
if (!jibo) { if (!jibo) {
warn('jibo is null/undefined'); warn('jibo is null/undefined');
@@ -553,7 +580,7 @@ function patchLoader(jibo, skillsRoot) {
log('>>> jibo.loader.load called with:', resourcePath); log('>>> jibo.loader.load called with:', resourcePath);
// Check if this is loading a dynamic submenu // Check if this is loading a dynamic submenu
if (resourcePath && resourcePath.startsWith('dynamic-submenu-')) { if (resourcePath && strStartsWith(resourcePath, 'dynamic-submenu-')) {
const submenuConfig = _submenuConfigs[resourcePath]; const submenuConfig = _submenuConfigs[resourcePath];
if (submenuConfig) { if (submenuConfig) {
log('Loading dynamic submenu:', resourcePath); log('Loading dynamic submenu:', resourcePath);
@@ -566,10 +593,10 @@ function patchLoader(jibo, skillsRoot) {
// Check if this is the main menu - match various possible paths // Check if this is the main menu - match various possible paths
const isMainMenu = resourcePath && ( const isMainMenu = resourcePath && (
resourcePath.includes('main-menu-verbal.json') || strIncludes(resourcePath, 'main-menu-verbal.json') ||
resourcePath.includes('main-menu.json') || strIncludes(resourcePath, 'main-menu.json') ||
resourcePath === 'resources/views/main-menu-verbal.json' || resourcePath === 'resources/views/main-menu-verbal.json' ||
resourcePath.endsWith('main-menu-verbal.json') strEndsWith(resourcePath, 'main-menu-verbal.json')
); );
if (isMainMenu) { if (isMainMenu) {
@@ -584,12 +611,22 @@ function patchLoader(jibo, skillsRoot) {
} }
log('Original menu config loaded successfully'); 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 // Inject dynamic skills
const patchedConfig = injectDynamicSkills(config); 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); if (callback) callback(null, patchedConfig);
}); });
@@ -608,6 +645,8 @@ function patchLoader(jibo, skillsRoot) {
*/ */
function clearCache() { function clearCache() {
_cachedMenuEntries = null; _cachedMenuEntries = null;
_cachedSkillsRoot = null;
_cachedProvidersDir = null;
_submenuConfigs = {}; _submenuConfigs = {};
_dynamicSkillIds.clear(); _dynamicSkillIds.clear();
} }

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

View File

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

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

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

View 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;

View 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;

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

View File

@@ -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"
}
]

View File

@@ -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"
}
]

View File

@@ -0,0 +1,8 @@
{
"type": "skill",
"title": "Clock One (launch jibo-tbd)",
"icon": "resources/icons/clock.png",
"color": "blue",
"order": 20,
"skillId": "jibo-tbd"
}

View File

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

View File

@@ -0,0 +1,4 @@
{
"hidden": true,
"title": "FunStuffTest Root"
}

View File

@@ -0,0 +1,84 @@
# Skills logging (robot)
This modded version of the JiboOS includes a tiny “always works” logging stack for the robots 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 doesnt support SSE and its 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"`

View 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 arent 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.

View File

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

View File

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

View 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;

View File

@@ -1,2 +1,11 @@
let thisPackage = require('./package.json'); const LocalRadioPlayer = require('./LocalRadioPlayer');
module.exports = thisPackage.version;
/**
* @returns {RadioPlayer}
*
* i tried to make the radio work as well, coldnt get it to work :(
*/
module.exports = function createRadio() {
return new LocalRadioPlayer();
};

View File

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

View File

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

View 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

View 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/`

View 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:]))

View File

@@ -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:]))

View File

Binary file not shown.

View File

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

View File

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

View File

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

View File

@@ -1,5 +0,0 @@
/**
* @class BehaviorEmitter
* @extends EventEmitter
* @memberof jibo.bt
*/

View File

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

View File

@@ -1,4 +0,0 @@
/**
* @class Blackboard
* @memberof jibo.bt
*/

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +0,0 @@
/**
* Behavior Tree behavior classes.
* @namespace jibo.bt.behaviors
*/

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +0,0 @@
/**
* Behavior Tree decorator classes.
* @namespace jibo.bt.decorators
*/

View File

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

View File

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

Some files were not shown because too many files have changed in this diff Show More