From b9af4f7894ff068dc87517906f56acb281455ade Mon Sep 17 00:00:00 2001
From: Christopher Willis-Ford <cwillisf@media.mit.edu>
Date: Mon, 2 May 2016 14:38:27 -0700
Subject: [PATCH] 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() {