talking works & menus work plus ai documentation : )

This commit is contained in:
2026-03-24 18:23:39 +02:00
parent 5617886ebe
commit 03b51ffd0c
6 changed files with 294 additions and 0 deletions

View File

@@ -0,0 +1,45 @@
# BE-API: External Eye/Lightring Trigger
This API lets you trigger Jibo's eye (and, in the future, lightring) from any external tool or script via HTTP.
## Endpoint
- **URL:** `http://<robot-ip>:15141/be-eye`
- **Method:** `POST`
- **Content-Type:** `application/json`
## Actions
### 1. Trigger Eye (Blue)
- **Request Body:**
```json
{ "action": "eye" }
```
- **Effect:** Forces Jibo's eye to blue (listening state).
- **Response:**
- `200 OK` with `{ "ok": true }` if successful
- `500` if failed
### 2. (Planned) Trigger Lightring
- **Request Body:**
```json
{ "action": "lightring", "color": "blue" }
```
- **Effect:** (Not implemented yet)
- **Response:**
- `501` with `{ "ok": false, "msg": "not implemented" }`
## Example: curl
```
curl -X POST -H 'Content-Type: application/json' \
-d '{"action":"eye"}' \
http://<robot-ip>:15141/be-eye
```
## Logging
- All API activity is logged to the skills log and web log panel.
## Notes
- The API server starts automatically with the BE skill.
- Lightring support is a placeholder for future hardware/API support.

View File

@@ -0,0 +1,105 @@
// Simple HTTP API for BE skill to control eye and lightring
// Usage: POST /be-eye { "action": "eye" | "lightring", "color": "blue" }
const http = require('http');
let jibo = null;
let beLogger = null;
let rlog = null;
function setJiboRef(jiboInstance, logger, robotLogger) {
jibo = jiboInstance;
beLogger = logger || null;
try {
if (!robotLogger) {
robotLogger = require('./robot-logger');
}
rlog = robotLogger;
} catch (e) {
rlog = null;
}
}
function forceEyeView() {
if (rlog && typeof rlog.raw === 'function') rlog.raw('[BE-API] forceEyeView called');
if (jibo && jibo.loader && typeof jibo.loader.load === 'function') {
// This is the animation file loaded when "hey jibo" is triggered
const animPath = '/opt/jibo/Jibo/Skills/@be/be/node_modules/jibo-anim-db-animations/animations/eye-globals/perceptual/hj-dsp-transition-to-pop-ns.keys';
try {
jibo.loader.load({
id: animPath,
cache: 'global',
options: { cache: 'global', dofs: {} },
data: null,
src: animPath,
upload: true,
root: '/opt/jibo/Jibo/Skills/@be/be/node_modules/jibo-anim-db-animations',
type: 'keys'
});
if (rlog && typeof rlog.raw === 'function') rlog.raw('[BE-API] forceEyeView animation loaded');
return true;
} catch (e) {
if (rlog && typeof rlog.raw === 'function') rlog.raw('[BE-API] forceEyeView animation load failed: ' + String(e && (e.stack || e.message || e)));
return false;
}
}
if (rlog && typeof rlog.raw === 'function') rlog.raw('[BE-API] forceEyeView failed');
return false;
}
// TODO: Implement lightring control if API is available
function setLightring(color) {
// Placeholder: No direct API found
return false;
}
const server = http.createServer((req, res) => {
if (rlog && typeof rlog.raw === 'function') {
rlog.raw('[BE-API] Incoming request ' + req.method + ' ' + req.url);
} else if (beLogger && typeof beLogger.info === 'function') {
beLogger.info('[BE-API] Incoming request', { method: req.method, url: req.url });
} else {
console.log('[BE-API] Incoming request', req.method, req.url);
}
if (req.method === 'POST' && req.url === '/be-eye') {
let body = '';
req.on('data', chunk => { body += chunk; });
req.on('end', () => {
try {
const data = JSON.parse(body);
if (data.action === 'eye') {
const ok = forceEyeView();
res.writeHead(ok ? 200 : 500);
res.end(JSON.stringify({ ok }));
} else if (data.action === 'lightring') {
const ok = setLightring(data.color);
res.writeHead(ok ? 200 : 501);
res.end(JSON.stringify({ ok, msg: ok ? 'ok' : 'not implemented' }));
} else {
res.writeHead(400);
res.end(JSON.stringify({ error: 'invalid action' }));
}
} catch (e) {
res.writeHead(400);
res.end(JSON.stringify({ error: 'bad json' }));
}
});
} else {
res.writeHead(404);
res.end();
}
});
function start(port = 15141) {
server.listen(port, () => {
if (rlog && typeof rlog.raw === 'function') {
rlog.raw('[BE-API] Listening on port ' + port);
} else if (beLogger && typeof beLogger.info === 'function') {
beLogger.info('[BE-API] Listening on port ' + port);
} else {
console.log('[BE-API] Listening on port', port);
}
});
}
module.exports = { setJiboRef, start };

View File

@@ -9,6 +9,8 @@ try {
// ignore
}
const beApi = require('./be-api');
exports.postInit = function (err) {
this.log.debug('postInit !!');
@@ -23,6 +25,21 @@ exports.postInit = function (err) {
// ignore
}
// Start BE HTTP API for eye/lightring control (always attempt, even if error)
try {
beApi.setJiboRef(jibo, this.log, rlog);
beApi.start();
if (rlog && typeof rlog.raw === 'function') {
rlog.raw('[BE-API] HTTP server started');
} else {
this.log.info('BE-API HTTP server started');
}
} catch (e) {
if (rlog && typeof rlog.raw === 'function') {
rlog.raw('[BE-API] HTTP server failed to start: ' + String(e && (e.stack || e.message || e)));
}
this.log.warn('BE-API HTTP server failed to start', e);
}
if (err) {
this.log.error(err);
this.initDoneCallback(err);

View File

@@ -128,6 +128,10 @@ class Be {
}
catch (err) {
this.log.warn('Error setting up listening for log level notifications', err);
// Explicitly call postInit on main BE skill instance to ensure BE-API server starts
if (typeof this.postInit === 'function') {
this.postInit();
}
}
}
if (this.packageInfo.jibo.debug.resourceLeak) {