Particle Echo Connection

This is Echo. She stole my seat and is now has access to my Particle accounts. Easiest Particle/Echo connection ever. Disconnecting her was the real problem.

All joking aside, I’m working on an Echo tutorial and would love any suggestions anyone has for things to cover.

5 Likes

A long time ago @AlbertZeroK shared his project here

Unfortunately it seems it was never expanded to support other languages than English.
Having a generic bridge of that sort would be great.

1 Like

Thanks @ScruffR that’s a great idea!

Hello Colleen,

I personally would like to see how to invoke commands in my Particle devices using the Echo natural language processor. IFTTT works, but having to use “trigger” is awkward and new users dislike it. I would also like to be able to ask the Echo about the state of variables. “Is the garage door closed?”, or “Are the patio lights on?” for example.

I’m happy you are working on this as I haven’t had time to dive in myself. Thanks!

1 Like

OMG, I can’t believe this is happening. There is so much potential for IOT interfaced to digital assistants.
I can imagine a few extensions to the Automotive versions.
I use the EchoPhotonBridge that @ScruffR has linked. It works well, but adding devices is a bit laborious and does not support notifications. I had trouble porting it to newer Particle OS and gave up. It works well on OS 0.7 :worried: If it ain’t broke…
You probably already have some ideas on what to cover. I would start with the three fundamental functions.
Reading variables (Particle.variable):
– Alexa, what is the temperature in the attic?
– Alexa, what is the state of the garage door? (should translate 1/0 to open/close)
Setting variables of various types (Particle.function):
– Alexa, turn on/off the porch light.
– Alexa, set the thingymajig to 0.2346 Tesla (clunky but could evolve into increase/decrease commands).
– Alexa, set the pool temperature to 72 F
Notifications (Particle.publish):
– The LED ring would light up and upon asking what are the notifications, the Echo would utter whatever text is in the code: “Yo! The boiler has stalled. Press the restart button.”
Dashboard:
– This has been a fantasy of mine and more advanced topic. I would like to see the status of my
devices instead of attention grabbing news displayed on devices with a screen. It could be requested by the user with a voice command. Most devices have a touch screen, so I could set variables, touch buttons, etc.

I am eager to help and participate.

1 Like

These suggestions are excellent- thanks for this level of detail!

Language support beyond english is limited, not due to the skill, but due to limitations with the authentication server particle uses. Might have changed since I wrote this, but that was the orginial issue.

I am the original author of this, haven’t touched it in a while, still have some stuff running this and it’s still very solid with the right fw version.

Much of this would be a custom Alexa skill, not home automation. Alexa has 3 types of skills, home automation with VERY strict implementation capabilities, Updates which support zip code sharing with alexa and only reads text / delivers an mp3 file via audio, and standard alexa skills which would provide most of the integrations you’re seeking.

IFTTT has a good integration for Alexa & Particle & Google Home. I automated remote starting my truck with Google Voice using it and a Boron, simple, easy, clean.

Hi Jeremy. Yes, your Alexa skill is still working for me as well. We exchanged messages a few years ago about a bug with reporting variables, which is fixed. Thank you.

I tried to reverse engineer what you did and I get lost every time.
I thought I could learn from Amazon’s tutorials using the developer console. Turns out the tutorials are not updated to the latest implementation of the console/ASK/APL, etc. So I can’t even follow their own instructions. I found questions about this on their forum and they are left unanswered.
Following their tutorial is not a walk in the park either. There is so much to setup that I lose track of what’s going on. The Cake Time Tutorial is interesting because it shows how to use the VUI. That would be a start for making a status console. I couldn’t get it to work.
I found a YouTube channel on this topic. Even those videos warn you that things could have changed and to look for an updated video.
Maybe that’s why there is no formal Particle Alexa skill. I hope @Colleen is still working on this.

I agree about IFTTT. I dedicated a Photon to that job, listening for events. It’s not as clean to manage as IFTTT, but I don’t have to learn yet another external service.
My trek continued with HASS. Well, that’s a really nice environment. Again, yet another thing to manage outside of the Particle ecosystem. I turned it off.

My lambda code for the skill backend, don’t laugh, my NodeJS isn’t as good as my c# or GoLang. @Colleen this might something you want. Pair this with this write up: Full Alexa Home Automation for the Particle + Skill - Particle Projects

And check this out, though I’m not sure it covers home automation skills: Alexa Developer Tutorials & Code Samples - Alexa Skills Kit Official Site

var https = require(‘https’);

var particleHttpsHost = “api.particle.io”;
var particleHttpsPath = “/v1/devices”

var particleV2Endpoint = “echoDeviceV2”;
var particleV3Endpoint = “echoDeviceV3”;
var particleV2CmdEndPoint = “echoCmdV2”;
var particleV3CmdEndPoint = “echoCmdV3”;
/**

  • Main entry point.

  • Incoming events from Alexa Lighting APIs are processed via this method.
    */
    exports.handler = function(event, context) {

    log(‘Input’, event);

    switch (event.header.namespace) {

     /**
      * The namespace of "Discovery" indicates a request is being made to the lambda for
      * discovering all appliances associated with the customer's appliance cloud account.
      * can use the accessToken that is made available as part of the payload to determine
      * the customer.
      */
     case 'Alexa.ConnectedHome.Discovery':
         handleDiscovery(event.payload.accessToken, context);
         break;
    
         /**
          * The namespace of "Control" indicates a request is being made to us to turn a
          * given device on, off or brighten. This message comes with the "appliance"
          * parameter which indicates the appliance that needs to be acted on.
          */
     case 'Alexa.ConnectedHome.Query':
     case 'Alexa.ConnectedHome.Control':
         handleControl(event, context);
         break;
     
         /**
          * We received an unexpected message
          */
     default:
         log('Err', 'No supported namespace: ' + event.header.namespace);
         context.fail('Something went wrong');
         break;
    

    }
    };

/**

  • This method is invoked when we receive a “Discovery” message from Alexa Connected Home Skill.

  • We are expected to respond back with a list of appliances that we have discovered for a given

  • customer.
    */
    function handleDiscovery(accessToken, context) {

    getDevices(“alexa”, getDeviceCapabilitiesV2, accessToken, context);

}

/**

  • Control events are processed here.

  • This is called when Alexa requests an action (IE turn off appliance).
    */
    function handleControl(event, context) {
    console.log(“handleControl Called.”);
    //console.log("event: " + JSON.stringify(event, null, 4));
    //console.log("context: " + JSON.stringify(context, null, 4));
    if (event.header.namespace === ‘Alexa.ConnectedHome.Control’ || event.header.namespace === ‘Alexa.ConnectedHome.Query’) {

     /**
      * Retrieve the appliance id and accessToken from the incoming message.
      */
     var accessToken = event.payload.accessToken;
     var particleDeviceId = event.payload.appliance.additionalApplianceDetails.particleDeviceId
     var particleDeviceIndex = event.payload.appliance.additionalApplianceDetails.deviceIndex
     var message_id = event.header.messageId;
     var param = "";
     var index = "0";
     var state = 0;
     var confirmation;
    
     var particleFunctionString = particleDeviceIndex + ":";
    
     //log("Access Token: ", accessToken);
     //log("DeviceID: ", particleDeviceId);
     //log("Device Index: ", particleDeviceIndex);
     log("event ",event.header.name);
    
     var payload = {
             achievedState : {}   
         };
    
     switch (event.header.name) 
     {
         case "TurnOnRequest":
             particleFunctionString += "1:1";
             confirmation = "TurnOnConfirmation";
             break;
    
         case "TurnOffRequest":
             particleFunctionString += "1:0";
             confirmation = "TurnOffConfirmation";
             break;
    
         case "SetPercentageRequest":
             particleFunctionString += "2:" + event.payload.percentageState.value;
             confirmation = "SetPercentageConfirmation";
             break;
    
         case "IncrementPercentageRequest":
             particleFunctionString += "2:U" + event.payload.deltaPercentage.value;
             confirmation = "IncrementPercentageConfirmation";
             break;
    
         case "DecrementPercentageRequest":
             particleFunctionString += "2:D" + event.payload.deltaPercentage.value;
             confirmation = "DecrementPercentageConfirmation";
             break;
    
         case "SetColorRequest":
             //console.log("Getting RGB");
             var rgb = hsvToRgb(event.payload.color.hue / 360, event.payload.color.saturation, event.payload.color.brightness);
             //console.log("RGB: " + JSON.stringify(rgb,null,4));
             particleFunctionString += "3:" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ",HSV " + event.payload.color.hue + "," + event.payload.color.saturation + "," + event.payload.color.brightness;
             confirmation = "SetColorConfirmation";
             payload = {achievedState:{color: event.payload.color}};
             break;
    
         case "GetLockStateRequest":
             log("Getting Lock Request");
             particleFunctionString += "4:1,0";
             log("ParticleFunctionString: ", particleFunctionString);
             confirmation = "GetLockStateResponse";
             break;
    
         case "SetLockStateRequest":
             log("Setting Lock Request",event.payload.lockState);
             particleFunctionString += "4:0," + ((event.payload.lockState == "LOCKED") ? "1" : "0");
             log("ParticleFunctionString: ", particleFunctionString);
             confirmation = "SetLockStateConfirmation";
             payload = {lockState: event.payload.lockState};
             break;
    
         case "GetTemperatureReadingRequest":
             particleFunctionString += "5:1,0";
             confirmation = "GetTemperatureReadingResponse";
             break;
    
         case "GetTargetTemperatureRequest":
             particleFunctionString += "5:2,0";
             confirmation = "GetTargetTemperatureResponse";
             break;
    
         case "SetTargetTemperatureRequest":
             particleFunctionString += "5:0," + parseInt(cToF(event.payload.targetTemperature.value));
             confirmation = "SetTargetTemperatureConfirmation";
             payload = {targetTemperature: {value: parseInt(event.payload.targetTemperature.value)},temperatureMode: {value: "HEAT"}};
             break;
    
         case "IncrementTargetTemperatureRequest":
             particleFunctionString += "5:0,U" + parseInt(event.payload.deltaTemperature.value);
             confirmation = "IncrementTargetTemperatureConfirmation";
             break;
    
         case "DecrementTargetTemperatureRequest":
             particleFunctionString += "5:0,D" + parseInt(event.payload.deltaTemperature.value);
             confirmation = "DecrementTargetTemperatureConfirmation";
             break;
    
         case "SetColorTemperatureRequest":
             particleFunctionString += "6:" + event.payload.colorTemperature.value;
             confirmation = "SetColorTemperatureConfirmation";
             break;
    
         case "IncrementColorTemperatureRequest":
             particleFunctionString += "6:U100" + event.payload.deltaTemperature.value;
             confirmation = "IncrementColorTemperatureConfirmation";
             break;
    
         case "DecrementColorTemperatureRequest":
             particleFunctionString += "6:D100" + event.payload.colorTemperature.value;
             confirmation = "DecrementColorTemperatureConfirmation";
             break;
    
     }
     
     var options = {
         hostname: particleHttpsHost,
         port: 443,
         path: particleHttpsPath + "/" + particleDeviceId + "/" + particleV2CmdEndPoint,
         method: 'POST',
         headers: {
             'Content-Type': 'application/x-www-form-urlencoded'
         }
     };
     
     var data = "access_token=" + accessToken + "&" + "args=" + particleFunctionString;
     
     var serverError = function (e) {
         log('Error', e.message);
         context.fail(generateControlError('TurnOnRequest', 'DEPENDENT_SERVICE_UNAVAILABLE', 'Unable to connect to server'));
     };
    
     var callback = function(response) {
         var str = '';
    
         response.on('data', function(chunk) {
             str += chunk.toString('utf-8');
         });
    
         response.on('end', function() {
    
             var particleResponse = JSON.parse(str);
    
             var headers = {
                 namespace: 'Alexa.ConnectedHome.Control',
                 name: confirmation,
                 payloadVersion: '2',
                 messageId: message_id
             };
    
             log('Return Value: ' + JSON.stringify(particleResponse,null,4));
             
             switch (event.header.name) 
             {
                 case "GetLockStateRequest":
                     payload = (particleResponse.return_value == "1") ? {lockState:"LOCKED"} : {lockState:"UNLOCKED"};
                     headers.namespace = "Alexa.ConnectedHome.Query";
                     break;
    
                 case "GetTargetTemperatureRequest":
                 case "GetTemperatureReadingRequest":
                     payload = {"temperatureReading": { value: parseFloat(fToC(particleResponse.return_value)), scale: "FAHRENHEIT" }};
                     headers.namespace = "Alexa.ConnectedHome.Query";
                     break;
             }
    
             
            
             var result = {
                 header: headers,
                 payload: payload
             };
             console.log(result);
             context.succeed(result);
         });
    
         response.on('error', serverError);
     };
    
     var req = https.request(options, callback);
         
     req.on('error', serverError);
     
     req.write(data);
     req.end();
    

    }
    }

/**

  • Utility functions.
    */
    function log(title, msg) {
    console.log(title + ": " + msg);
    }

function generateControlError(name, code, description) {
var headers = {
namespace: ‘Control’,
name: name,
payloadVersion: ‘1’
};

var payload = {
    exception: {
        code: code,
        description: description
    }
};

var result = {
    header: headers,
    payload: payload
};

return result;

}

/*
exports.getJSON = function(options, onResultCallback, callbackParams)
{
//console.log(“rest::getJSON”);

var prot = options.port == 443 ? https : http;
var req = prot.request(options, function(res)
{
    var output = '';
    //console.log(options.host + ':' + res.statusCode);
    res.setEncoding('utf8');

    res.on('data', function (chunk) {
        output += chunk;
    });
    
    req.on('socket', function (socket) {
        socket.setTimeout(3000);  
        socket.on('timeout', function() {
            log('Timeout calling URL', JSON.stringify(options,null,4));
            var errorObject = {statusCode: 500, error: "TimeOut"};
            onResultCallback(500, errorObject, callbackParams);
        });
    });

    res.on('end', function() {
        console.log("PreJSON Output: " + output);
        var obj = JSON.parse(output);
        onResultCallback(res.statusCode, obj, callbackParams);
    });
});

req.on('error', function(err) {
    log("Error calling URL", JSON.stringify(err,null,4));
    console.log('error: ' + err.message);
    var errorObject = {statusCode: 500, error: err.message};
    onResultCallback(500, errorObject, callbackParams);
});

req.end();

};
*/

exports.getJSON = function(options, onResultCallback, callbackParams)
{
//console.log(“rest::getJSON”);

var http = require('http');
var prot = options.port == 443 ? https : http;
var req = prot.request(options, function(res)
{

    var output = '';
    //console.log(options.host + ':' + res.statusCode);
    res.setEncoding('utf8');

    res.on('data', function (chunk) {
        output += chunk;
    });

    res.on('end', function() {
        console.log("PreJSON Output: " + output);
        var obj = JSON.parse(output);
        onResultCallback(res.statusCode, obj, callbackParams);
    });
});

 req.on('socket', function (socket) {
        socket.setTimeout(2000);  
        socket.on('timeout', function() {
            console.log('Timeout calling URL', JSON.stringify(options,null,4));
            var errorObject = {statusCode: 504, error: "TimeOut"};
            onResultCallback(504, errorObject, callbackParams);
            socket.destroy();
        });
    });

req.on('error', function(err) {
    console.log("Error calling URL", JSON.stringify(err,null,4));
    console.log('error: ' + err.message);
    var errorObject = {statusCode: 500, error: err.message};
    onResultCallback(500, errorObject, callbackParams);
});

req.end();

};
var deviceDiscoveryComplete = function(context) {
console.log("Discovery Response: " + JSON.stringify(responseTemplateV2, null, 4));
context.succeed(responseTemplateV2);
}

var serverError = function (e) {console.log(‘Error’, e.message);};

function isBitSet(num, bit){
return ((num>>bit) % 2 != 0)
}

var responseTemplateV2 = {
header:{
name: “DiscoverAppliancesResponse”,
namespace: “Alexa.ConnectedHome.Discovery”,
payloadVersion: “2”
},
payload:{
discoveredAppliances:
}
};

var deviceTemplateV2 = {
applianceId: ‘’,
manufacturerName: ‘Echo Particle Bridge’,
modelName: ‘Particle Bridge V2’,
version: ‘1’,
friendlyName: ‘’,
friendlyDescription: ‘’,
isReachable: true,
actions:[
],
applicanceTypes:[
],
additionalApplianceDetails: {
deviceIndex: 0,
particleDeviceId: “”
}
};

var getDeviceCapabilitiesV2 = function(callback, finalCallback, params) {
console.log("Params: " + JSON.stringify(params,null,4));
var deviceId;
deviceId = params.devices[params.deviceIndex];

var options = {
    hostname: particleHttpsHost,
    port: 443,
    path: particleHttpsPath + "/" + deviceId + "/" + particleV2Endpoint,
    method: 'GET',
    headers: {
        'Authorization' : "Bearer " + params.accessToken
    }
};

console.log(JSON.stringify(options,null,4));

var callback = function(status, reply, params) { 
    console.log("Reply: " + JSON.stringify(reply,null,4));
    if (reply.ok == false) {
        log("API Errors: ", reply.errors);
    }
    else if (reply.error) {
        log("API Errors: ", reply.error);
    }
    else 
    {
    
        //make a copy of the json object
        reply = JSON.parse(reply.result.replace("\\\"","\""));
        
        if (reply.error) {
            log('Device Capabillities API Error: ',reply.error);
        } 
        else
        {
            //console.log(JSON.stringify(reply,null,4));
            for (var i = 0; i < reply.Devices.length; i++) {
                //console.log(i);
                console.log("Creating Device Details for: " + reply.Devices[i].name);
                //console.log(reply.Devices[i].type);
                var device = JSON.parse(JSON.stringify(deviceTemplateV2));
                device.applianceId = deviceId + "_" + i;
                device.friendlyName = reply.Devices[i].name;
                device.friendlyDescription = "Particle IO Bridge";
                device.additionalApplianceDetails.deviceIndex = i;
                device.additionalApplianceDetails.particleDeviceId = deviceId
    
                //Turn On/Off
                if (isBitSet(reply.Devices[i].type,1)) {
                    device.actions.push("turnOn");
                    device.actions.push("turnOff");
                }
    
                //Percent
                if (isBitSet(reply.Devices[i].type,2)) {
                    device.actions.push("setPercentage")
                    device.actions.push("decrementPercentage")
                    device.actions.push("incrementPercentage")
                }
                
                //SetColor
                if (isBitSet(reply.Devices[i].type,3)) {
                    device.actions.push("setColor")
                }
    
                //Lock
                if (isBitSet(reply.Devices[i].type,4)) {
                    device.actions.push("getLockState")
                    device.actions.push("setLockState")
                    device.applicanceTypes.push("SMARTLOCK");
                }
    
                //Temperature
                if (isBitSet(reply.Devices[i].type,5)) {
                    device.actions.push("incrementTargetTemperature");
                    device.actions.push("decrementTargetTemperature");
                    device.actions.push("setTargetTemperature");
                    device.actions.push("getTargetTemperature");
                    device.actions.push("getTemperatureReading");
                    device.applicanceTypes.push("THERMOSTAT");
                }
    
                //Color Temp
                if (isBitSet(reply.Devices[i].type,6)) {
                    device.actions.push("setColorTemperature")
                    device.actions.push("decrementColorTemperature")
                    device.actions.push("incrementColorTemperature")
                }
    
                responseTemplateV2.payload.discoveredAppliances.push(device);
            }
        }
    }
    
    params.deviceIndex++;
    if (params.deviceIndex < params.devices.length) 
    {
        log("Calling getDeviceCapabilitesV2",JSON.stringify(params,null,4));
        getDeviceCapabilitiesV2(getDeviceCapabilitiesV2, deviceDiscoveryComplete, params)
    }
    else 
    {
        deviceDiscoveryComplete(params.context);
    }
};

var q = exports.getJSON(options, callback, params);

}

function getDevices(key, deviceProccessingCallback, accessToken, context) {

var options = {
        hostname: particleHttpsHost,
        port: 443,
        path: particleHttpsPath,
        method: 'GET',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
            'Authorization' : "Bearer " + accessToken
        }
    };

var callback = function(status, reply, params) { 
    console.log("Device Discovery JSON Response: " + JSON.stringify(reply,null,4));
    if (reply.ok == false) {
        return console.log("API Errors: ", reply.errors);
    }
    var devices = [];
    key = key.toUpperCase();
    for (var i = 0; i < reply.length; i++) {
        //console.log(i);
        //console.log(reply[i].id);
        console.log("Reviewing Settings for Device Named: " + reply[i].name);
        if (key.length == 0) 
        {
            devices.push(reply[i].id);
        }
        //console.log(reply[i].name.toUpperCase());
        //console.log(reply[i].name.toUpperCase().indexOf(key));
        if (reply[i].name != null) {
            if (reply[i].connected == true)
            {
                devices.push(reply[i].id);
                //console.log("Found");
            }
        }
    }
    console.log("Found Devices: " + JSON.stringify(devices,null,4));
    params.devices = devices;
    getDeviceCapabilitiesV2(getDeviceCapabilitiesV2, deviceDiscoveryComplete, params)
};

var params = {
    "devices" : [],
    "deviceIndex" : 0,
    "context" : context,
    "accessToken" : accessToken
};
var q = exports.getJSON(options, callback, params);

};

//from RGB, HSV, and HSL color conversion algorithms in JavaScript · GitHub
/**

  • Converts an HSL color value to RGB. Conversion formula
  • adapted from HSL and HSV - Wikipedia.
  • Assumes h, s, and l are contained in the set [0, 1] and
  • returns r, g, and b in the set [0, 255].
  • @param Number h The hue
  • @param Number s The saturation
  • @param Number l The lightness
  • @return Array The RGB representation
    */
    function hslToRgb(h, s, l) {
    console.log(“called hslToRgb(” + h + “,” + s + “,” + l + “)”);
    var r, g, b;

if (s == 0) {
r = g = b = l; // achromatic
} else {
function hue2rgb(p, q, t) {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1/6) return p + (q - p) * 6 * t;
if (t < 1/2) return q;
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
}

var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
var p = 2 * l - q;

r = hue2rgb(p, q, h + 1/3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1/3);

}

return [ r * 255, g * 255, b * 255 ];
}

// expected hue range: [0, 360)
// expected saturation range: [0, 1]
// expected lightness range: [0, 1]
var hslToRgb2 = function(hue, saturation, lightness){
console.log(“called hslToRgb(” + hue + “,” + saturation + “,” + lightness + “)”);
// based on algorithm from http://en.wikipedia.org/wiki/HSL_and_HSV#Converting_to_RGB
if( hue == undefined ){
return [0, 0, 0];
}

var chroma = (1 - Math.abs((2 * lightness) - 1)) * saturation;
var huePrime = hue / 60;
var secondComponent = chroma * (1 - Math.abs((huePrime % 2) - 1));

huePrime = Math.floor(huePrime);
var red;
var green;
var blue;

if( huePrime === 0 ){
red = chroma;
green = secondComponent;
blue = 0;
}else if( huePrime === 1 ){
red = secondComponent;
green = chroma;
blue = 0;
}else if( huePrime === 2 ){
red = 0;
green = chroma;
blue = secondComponent;
}else if( huePrime === 3 ){
red = 0;
green = secondComponent;
blue = chroma;
}else if( huePrime === 4 ){
red = secondComponent;
green = 0;
blue = chroma;
}else if( huePrime === 5 ){
red = chroma;
green = 0;
blue = secondComponent;
}

var lightnessAdjustment = lightness - (chroma / 2);
red += lightnessAdjustment;
green += lightnessAdjustment;
blue += lightnessAdjustment;

return [Math.round(red * 255), Math.round(green * 255), Math.round(blue * 255)];

};

/**

  • Converts an HSV color value to RGB. Conversion formula
  • adapted from http://en.wikipedia.org/wiki/HSV_color_space.
  • Assumes h, s, and v are contained in the set [0, 1] and
  • returns r, g, and b in the set [0, 255].
  • @param Number h The hue
  • @param Number s The saturation
  • @param Number v The value
  • @return Array The RGB representation
    */
    function hsvToRgb(h, s, v) {
    console.log(“called hsvToRgb(” + h + “,” + s + “,” + v + “)”);
    var r, g, b;

var i = Math.floor(h * 6);
var f = h * 6 - i;
var p = v * (1 - s);
var q = v * (1 - f * s);
var t = v * (1 - (1 - f) * s);

switch (i % 6) {
case 0: r = v, g = t, b = p; break;
case 1: r = q, g = v, b = p; break;
case 2: r = p, g = v, b = t; break;
case 3: r = p, g = q, b = v; break;
case 4: r = t, g = p, b = v; break;
case 5: r = v, g = p, b = q; break;
}

return [ r * 255, g * 255, b * 255 ];
}

function cToF(celsius)
{
return parseInt(parseFloat(celsius) * 9.0 / 5.0 + 32.0);
}

function fToC(fahrenheit)
{
return parseInt((parseFloat(fahrenheit) - 32.0) * 5.0 / 9.0);
}

1 Like