kopia lustrzana https://github.com/backface/turtlestitch
163 wiersze
6.0 KiB
JavaScript
163 wiersze
6.0 KiB
JavaScript
/* Signada - a network remote control procotol
|
|
* ===========================================
|
|
* By Bernat Romagosa, August 2021
|
|
*
|
|
* Enables Snap! to talk to wifi-enabled
|
|
* MicroBlocks devices.
|
|
*
|
|
* This protocol was designed for the Citilab
|
|
* ED1 microcontroller board, but it can be
|
|
* used for any ESP32 board that has a display
|
|
* and two buttons, like the M5Stack.
|
|
*
|
|
* First, load the Signada example project
|
|
* from the MicroBlocks IDE and follow the
|
|
* instructions on the long comment in that
|
|
* project.
|
|
*
|
|
* Get MicroBlocks at https://microblocks.fun
|
|
*/
|
|
|
|
SnapExtensions.primitives.set(
|
|
'sgd_connect(ip)',
|
|
function (ip) {
|
|
if (location.protocol.indexOf('https') > -1) {
|
|
throw new Error(
|
|
'Signada requires an HTTP only instance of Snap!, like:\n' +
|
|
'http://extensions.snap.berkeley.edu'
|
|
);
|
|
}
|
|
var socket = new WebSocket('ws://' + ip + ':81'),
|
|
stage = this.parentThatIsA(StageMorph),
|
|
cache;
|
|
stage.signada = {};
|
|
stage.signada.ip = ip;
|
|
stage.signada.socket = socket;
|
|
stage.signada.responses = {};
|
|
stage.signada.responseCache = {};
|
|
stage.signada.lastID = 0;
|
|
stage.signada.eventListener = function (event) {
|
|
response = JSON.parse(event.data);
|
|
if (Array.isArray(response[1])) {
|
|
response[1] = new List(response[1]);
|
|
}
|
|
stage.signada.responses[response[0]] = response[1];
|
|
|
|
// Cache the response for when hat blocks
|
|
cache = Object.values(stage.signada.responseCache).find(
|
|
each => each.requestID === response[0]
|
|
);
|
|
if (cache) {
|
|
cache.value = response[1];
|
|
cache.updating = false;
|
|
}
|
|
};
|
|
|
|
socket.addEventListener('message', stage.signada.eventListener);
|
|
}
|
|
);
|
|
|
|
SnapExtensions.primitives.set(
|
|
'sgd_disconnect()',
|
|
function () {
|
|
var stage = this.parentThatIsA(StageMorph);
|
|
if (SnapExtensions.primitives.get('sgd_connected()').call(this)) {
|
|
stage.signada.socket.removeEventListener(
|
|
'message',
|
|
stage.signada.eventListener
|
|
);
|
|
stage.signada.socket.close();
|
|
}
|
|
}
|
|
);
|
|
|
|
SnapExtensions.primitives.set(
|
|
'sgd_connected()',
|
|
function () {
|
|
var signada = this.parentThatIsA(StageMorph).signada;
|
|
return (signada !== undefined) && (signada.socket.readyState === 1);
|
|
}
|
|
);
|
|
|
|
SnapExtensions.primitives.set(
|
|
'sgd_call(blockname, params, defaultresponse)',
|
|
function (blockname, params, defaultresponse, proc) {
|
|
var signada = this.parentThatIsA(StageMorph).signada,
|
|
hatBlock = proc.topBlock.parentThatIsA(HatBlockMorph),
|
|
needsCaching = !isNil(hatBlock) &&
|
|
(hatBlock.selector === 'receiveCondition'),
|
|
updatingCache = (signada.responseCache[blockname]?.updating),
|
|
defaultresponse =
|
|
(defaultresponse === undefined) ? 0 : defaultresponse;
|
|
|
|
if (!SnapExtensions.primitives.get('sgd_connected()').call(this)) {
|
|
throw new Error(
|
|
'You are not connected to any device.\n' +
|
|
'Please use the connect block to establish a connection and' +
|
|
'try again.'
|
|
);
|
|
}
|
|
|
|
if (!proc.requestID) {
|
|
if (!needsCaching || !updatingCache) {
|
|
proc.startTime = new Date();
|
|
proc.requestID = signada.lastID;
|
|
|
|
// Cache responses for reporters inside when hat blocks
|
|
if (signada.responseCache[blockname]) {
|
|
// Let's mark the cached response as updating so we don't
|
|
// request it again until we get a response from the device
|
|
signada.responseCache[blockname].updating = true;
|
|
signada.responseCache[blockname].requestID = proc.requestID;
|
|
signada.responseCache[blockname].requestTime =
|
|
(new Date()).getTime();
|
|
} else {
|
|
// Never sent a similar request before. Let's give it a
|
|
// default value.
|
|
signada.responseCache[blockname] = {
|
|
requestID: proc.requestID,
|
|
updating: true,
|
|
value: defaultresponse,
|
|
requestTime: (new Date()).getTime()
|
|
};
|
|
}
|
|
|
|
signada.socket.send(
|
|
JSON.stringify([signada.lastID, blockname, params.contents])
|
|
);
|
|
|
|
// Last ID wraps at 1.000.000 to make sure it doesn't grow too
|
|
// much and doesn't wrap too early
|
|
signada.lastID = (signada.lastID + 1) % 1000000;
|
|
}
|
|
} else {
|
|
if (signada.responses[proc.requestID] !== undefined) {
|
|
var response = signada.responses[proc.requestID];
|
|
proc.requestID = null;
|
|
return response;
|
|
} else if ((new Date() - proc.startTime) > 1000) {
|
|
// Timeout after 1 second. Return last cached value
|
|
proc.requestID = null;
|
|
signada.responseCache[blockname].updating = false;
|
|
return signada.responseCache[blockname].value;
|
|
}
|
|
}
|
|
|
|
if (needsCaching) {
|
|
// This reporter needs caching. Let's return the last value for this
|
|
// particular block name.
|
|
if (updatingCache &&
|
|
(((new Date()).getTime() -
|
|
signada.responseCache[blockname].requestTime) > 250)) {
|
|
// We've been waiting for the cache to update for a long time.
|
|
// Let's invalidate it so the value is requested again.
|
|
signada.responseCache[blockname].updating = false;
|
|
}
|
|
return signada.responseCache[blockname].value;
|
|
}
|
|
|
|
proc.pushContext('doYield');
|
|
proc.pushContext();
|
|
}
|
|
);
|