From b9af4f7894ff068dc87517906f56acb281455ade Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford Date: Mon, 2 May 2016 14:38:27 -0700 Subject: [PATCH 1/6] WIP implementation for WeDo2 blocks Hat blocks are still TBD. Motor blocks assume a `util` argument which has methods for `yield()` and `done()`. --- src/blocks/wedo2.js | 149 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 141 insertions(+), 8 deletions(-) diff --git a/src/blocks/wedo2.js b/src/blocks/wedo2.js index 4adab36a4..ca91442ad 100644 --- a/src/blocks/wedo2.js +++ b/src/blocks/wedo2.js @@ -5,6 +5,20 @@ function WeDo2Blocks(runtime) { * @type {Runtime} */ this.runtime = runtime; + + /** + * Current motor speed, as a percentage (100 = full speed). + * @type {number} + * @private + */ + this._motorSpeed = 100; + + /** + * The timeout ID for a pending motor action. + * @type {?int} + * @private + */ + this._motorTimeout = null; } /** @@ -22,20 +36,139 @@ WeDo2Blocks.prototype.getPrimitives = function() { }; }; -WeDo2Blocks.prototype.motorClockwise = function() { - console.log('Running: wedo_motorclockwise'); +/** + * Clamp a value between a minimum and maximum value. + * @todo move this to a common utility class. + * @param val The value to clamp. + * @param min The minimum return value. + * @param max The maximum return value. + * @returns {number} The clamped value. + * @private + */ +WeDo2Blocks.prototype._clamp = function(val, min, max) { + return Math.max(min, Math.min(val, max)); }; -WeDo2Blocks.prototype.motorCounterClockwise = function() { - console.log('Running: wedo_motorcounterclockwise'); +/** + * Convert HSV to RGB. + * See https://en.wikipedia.org/wiki/HSL_and_HSV#From_HSV + * @todo move this to a common utility class. + * @param hueDegrees Hue, in degrees. + * @param saturation Saturation in the range [0,1]. + * @param value Value in the range [0,1]. + * @returns {number[]} An array of [r,g,b], each in the range [0,1]. + * @private + */ +WeDo2Blocks.prototype._HSVToRGB = function(hueDegrees, saturation, value) { + hueDegrees %= 360; + if (hueDegrees < 0) hueDegrees += 360; + saturation = this._clamp(saturation, 0, 1); + value = this._clamp(value, 0, 1); + + var chroma = value * saturation; + var huePrime = hueDegrees / 60; + var x = chroma * (1 - Math.abs(huePrime % 2 - 1)); + var rgb; + switch (Math.floor(huePrime)) { + case 0: + rgb = [chroma, x, 0]; + break; + case 1: + rgb = [x, chroma, 0]; + break; + case 2: + rgb = [0, chroma, x]; + break; + case 3: + rgb = [0, x, chroma]; + break; + case 4: + rgb = [x, 0, chroma]; + break; + case 5: + rgb = [chroma, 0, x]; + break; + } + + var m = value - chroma; + rgb[0] += m; + rgb[1] += m; + rgb[2] += m; + + return rgb; }; -WeDo2Blocks.prototype.motorSpeed = function() { - console.log('Running: wedo_motorspeed'); +/** + * Common implementation for motor blocks. + * @param direction The direction to turn ('left' or 'right'). + * @param durationSeconds The number of seconds to run. + * @param util The util instance to use for yielding and finishing. + * @private + */ +WeDo2Blocks.prototype._motorOnFor = function(direction, durationSeconds, util) { + if (this._motorTimeout > 0) { + clearTimeout(this._motorTimeout); + this._motorTimeout = null; + } + if (window.native && window.native.motorRun) { + window.native.motorRun(direction, this._motorSpeed); + } + + var instance = this; + var myTimeout = setTimeout(function() { + if (instance._motorTimeout == myTimeout) { + instance._motorTimeout = null; + } + if (window.native && window.native.motorStop) { + window.native.motorStop(); + } + util.done(); + }, 1000 * durationSeconds); + + util.yield(); }; -WeDo2Blocks.prototype.setColor = function() { - console.log('Running: wedo_setcolor'); +WeDo2Blocks.prototype.motorClockwise = function(argValues, util) { + this._motorOnFor('right', argValues[0], util); +}; + +WeDo2Blocks.prototype.motorCounterClockwise = function(argValues, util) { + this._motorOnFor('left', argValues[0], util); +}; + +WeDo2Blocks.prototype.motorSpeed = function(argValues) { + this._motorSpeed = this._clamp(argValues[0], 1, 100); +}; + +/** + * Convert a color name to an [r,b,g] array. + * Supports 'mystery' for a random hue. + * @param colorName The color to retrieve. + * @returns {number[]} The [r,g,b] values for the color in [0,255] range. + * @private + */ +WeDo2Blocks.prototype._getColor = function(colorName) { + if (colorName == 'mystery') { + return this._HSVToRGB(Math.random() * 360, 1, 1); + } + return { + 'yellow': [255, 255, 0], + 'orange': [255, 165, 0], + 'coral': [255, 127, 80], + 'magenta': [255, 0, 255], + 'purple': [128, 0, 128], + 'blue': [0, 0, 255], + 'green': [0, 255, 0], + 'white': [255, 255, 255] + }[colorName]; +}; + +WeDo2Blocks.prototype.setColor = function(argValues) { + if (window.native && window.native.setLedColor) { + var rgbColor = this._getColor(argValues[0]); + window.native.setLedColor( + 255 * rgbColor[0], 255 * rgbColor[1], 255 * rgbColor[2]); + } }; WeDo2Blocks.prototype.whenDistanceClose = function() { From 66e4a55b7435744b4dd7b29721fec69a1327ba33 Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford Date: Mon, 2 May 2016 14:43:13 -0700 Subject: [PATCH 2/6] Fix _motorOnFor not setting _motorTimeout --- src/blocks/wedo2.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blocks/wedo2.js b/src/blocks/wedo2.js index ca91442ad..786b761b8 100644 --- a/src/blocks/wedo2.js +++ b/src/blocks/wedo2.js @@ -115,7 +115,7 @@ WeDo2Blocks.prototype._motorOnFor = function(direction, durationSeconds, util) { } var instance = this; - var myTimeout = setTimeout(function() { + var myTimeout = this._motorTimeout = setTimeout(function() { if (instance._motorTimeout == myTimeout) { instance._motorTimeout = null; } From e1109e8ca694d6f02f8df422dec93b3cce2f8f25 Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford Date: Tue, 3 May 2016 08:55:55 -0700 Subject: [PATCH 3/6] Improve error exception report for Android Monitor Android Monitor doesn't support passing multiple arguments to `console.error`, so this change instead builds a single string for the report. --- src/engine/sequencer.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/engine/sequencer.js b/src/engine/sequencer.js index f7d78352d..70eca4726 100644 --- a/src/engine/sequencer.js +++ b/src/engine/sequencer.js @@ -132,8 +132,9 @@ Sequencer.prototype.stepThread = function (thread) { }); } catch(e) { - console.error('Exception calling block function', - {opcode: opcode, exception: e}); + console.error( + 'Exception calling block function for opcode: ' + + opcode + '\n' + e); } finally { // Update if the thread has set a yield timer ID // @todo hack From e8089cd2a50ac97d0a77c770a7ae6c8d35b803dc Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford Date: Tue, 3 May 2016 09:29:51 -0700 Subject: [PATCH 4/6] Don't check for methods on window.native It turns out that `window.native.someMethod` always evaluates as `undefined` -- even if calling that method would succeed. This change removes checks for such methods so that the WeDo2 blocks can work. Note that the hat blocks are still unimplemented, and some assumptions are made about the form that arguments and `util` will take. --- src/blocks/wedo2.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/blocks/wedo2.js b/src/blocks/wedo2.js index 786b761b8..67ea1115d 100644 --- a/src/blocks/wedo2.js +++ b/src/blocks/wedo2.js @@ -110,7 +110,7 @@ WeDo2Blocks.prototype._motorOnFor = function(direction, durationSeconds, util) { clearTimeout(this._motorTimeout); this._motorTimeout = null; } - if (window.native && window.native.motorRun) { + if (window.native) { window.native.motorRun(direction, this._motorSpeed); } @@ -119,7 +119,7 @@ WeDo2Blocks.prototype._motorOnFor = function(direction, durationSeconds, util) { if (instance._motorTimeout == myTimeout) { instance._motorTimeout = null; } - if (window.native && window.native.motorStop) { + if (window.native) { window.native.motorStop(); } util.done(); @@ -164,7 +164,7 @@ WeDo2Blocks.prototype._getColor = function(colorName) { }; WeDo2Blocks.prototype.setColor = function(argValues) { - if (window.native && window.native.setLedColor) { + if (window.native) { var rgbColor = this._getColor(argValues[0]); window.native.setLedColor( 255 * rgbColor[0], 255 * rgbColor[1], 255 * rgbColor[2]); From 6f949596d82a523d6b71118fa0c4c760e9c5396a Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford Date: Tue, 3 May 2016 09:53:37 -0700 Subject: [PATCH 5/6] Use util.timeout instead of setTimeout --- src/blocks/wedo2.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/blocks/wedo2.js b/src/blocks/wedo2.js index 67ea1115d..c38b8f62d 100644 --- a/src/blocks/wedo2.js +++ b/src/blocks/wedo2.js @@ -1,4 +1,6 @@ +var YieldTimers = require('../util/yieldtimers.js'); + function WeDo2Blocks(runtime) { /** * The runtime instantiating this block package. @@ -107,7 +109,8 @@ WeDo2Blocks.prototype._HSVToRGB = function(hueDegrees, saturation, value) { */ WeDo2Blocks.prototype._motorOnFor = function(direction, durationSeconds, util) { if (this._motorTimeout > 0) { - clearTimeout(this._motorTimeout); + // @todo maybe this should go through util + YieldTimers.reject(this._motorTimeout); this._motorTimeout = null; } if (window.native) { @@ -115,7 +118,7 @@ WeDo2Blocks.prototype._motorOnFor = function(direction, durationSeconds, util) { } var instance = this; - var myTimeout = this._motorTimeout = setTimeout(function() { + var myTimeout = this._motorTimeout = util.timeout(function() { if (instance._motorTimeout == myTimeout) { instance._motorTimeout = null; } From 63d4f559e31cdd7f03d24fe0adda562559505af3 Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford Date: Tue, 3 May 2016 10:01:52 -0700 Subject: [PATCH 6/6] Fix inconsistent color scale The HSV-to-RGB function had been returning values in the range [0,1]. Now it uses [0,255] to be consistent with the other values returned by _getColor. --- src/blocks/wedo2.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/blocks/wedo2.js b/src/blocks/wedo2.js index c38b8f62d..f69d557eb 100644 --- a/src/blocks/wedo2.js +++ b/src/blocks/wedo2.js @@ -58,7 +58,7 @@ WeDo2Blocks.prototype._clamp = function(val, min, max) { * @param hueDegrees Hue, in degrees. * @param saturation Saturation in the range [0,1]. * @param value Value in the range [0,1]. - * @returns {number[]} An array of [r,g,b], each in the range [0,1]. + * @returns {number[]} An array of [r,g,b], each in the range [0,255]. * @private */ WeDo2Blocks.prototype._HSVToRGB = function(hueDegrees, saturation, value) { @@ -93,9 +93,9 @@ WeDo2Blocks.prototype._HSVToRGB = function(hueDegrees, saturation, value) { } var m = value - chroma; - rgb[0] += m; - rgb[1] += m; - rgb[2] += m; + rgb[0] = (rgb[0] + m) * 255; + rgb[1] = (rgb[1] + m) * 255; + rgb[2] = (rgb[2] + m) * 255; return rgb; }; @@ -169,8 +169,7 @@ WeDo2Blocks.prototype._getColor = function(colorName) { WeDo2Blocks.prototype.setColor = function(argValues) { if (window.native) { var rgbColor = this._getColor(argValues[0]); - window.native.setLedColor( - 255 * rgbColor[0], 255 * rgbColor[1], 255 * rgbColor[2]); + window.native.setLedColor(rgbColor[0], rgbColor[1], rgbColor[2]); } };