Skip to content

Plugin file

Plugin configuration file
{
    "name": "greenme-cube",
    "version": "1.0.0",
    "description": "Indoor environment sensor",
    "author": "Thinger.io",
    "license": "MIT",
    "repository": {
        "type": "git",
        "url": "https://github.com/thinger-io/plugins.git",
        "directory": "greenme-cube"
    },
    "metadata": {
        "name": "Greenme CUBE",
        "description": "Indoor environment sensor",
        "image": "assets/greenme-cube.png",
        "category": "devices",
        "vendor": "greenme"
    },
    "resources": {
        "products": [
            {
                "description": "Indoor environment sensor",
                "enabled": true,
                "name": "Greenme CUBE",
                "product": "greenme_cube",
                "profile": {
                    "api": {
                        "downlink": {
                            "enabled": true,
                            "handle_connectivity": false,
                            "request": {
                                "data": {
                                    "path": "/downlink",
                                    "payload": "{\n    \"data\"    : \"{{payload.data=\"\"}}\",\n    \"port\"    :  {{payload.port=85}},\n    \"priority\":  {{payload.priority=3}},\n    \"confirmed\" :  {{payload.confirmed=false}},\n    \"uplink\"  :  {{property.uplink}} \n}",
                                    "payload_function": "",
                                    "payload_type": "",
                                    "plugin": "{{property.uplink.source}}",
                                    "target": "plugin_endpoint"
                                }
                            }
                        },
                        "uplink": {
                            "device_id_resolver": "getId",
                            "enabled": true,
                            "handle_connectivity": true,
                            "request": {
                                "data": {
                                    "payload": "{{payload}}",
                                    "payload_function": "",
                                    "payload_type": "source_payload",
                                    "resource_stream": "uplink",
                                    "target": "resource_stream"
                                }
                            }
                        }
                    },
                    "autoprovisions": {
                        "device_autoprovisioning": {
                            "config": {
                                "mode": "pattern",
                                "pattern": "greenme-cube-.*"
                            },
                            "enabled": true
                        }
                    },
                    "buckets": {
                        "greenme_cube_data": {
                            "backend": "mongodb",
                            "data": {
                                "payload": "{{payload}}",
                                "payload_function": "",
                                "payload_type": "source_payload",
                                "resource_stream": "uplink_decoded",
                                "source": "resource_stream"
                            },
                            "enabled": true,
                            "retention": {
                                "period": 3,
                                "unit": "months"
                            },
                            "tags": [
                                "environment",
                                "sensors"
                            ]
                        }
                    },
                    "code": {
                        "code": "\nfunction getTemperature(payload) {\n    return payload.temperature;\n}\n\nfunction getHygrometry(payload) {\n    return payload.hygrometry;\n}\n\nfunction getLux(payload) {\n    return payload.lux;\n}\n\nfunction getNoiseAvg(payload) {\n    return payload.noiseAvg;\n}\n\nfunction getNoiseMax(payload) {\n    return payload.noiseMax;\n}\n\nfunction getCO2(payload) {\n    return payload.co2;\n}\n\nfunction getTVOC(payload) {\n    return payload.tvoc;\n}\n\nfunction decodeThingerUplink(thingerData) {\n    // 0. If data has already been decoded, we will return it\n    if (thingerData.decodedPayload) return thingerData.decodedPayload;\n    \n    // 1. Extract and Validate Input\n    // We need 'payload' (hex string) and 'fPort' (integer)\n    const hexPayload = thingerData.payload || \"\";\n    const port = thingerData.fPort || 1;\n\n    // 2. Convert Hex String to Byte Array\n    const bytes = [];\n    for (let i = 0; i < hexPayload.length; i += 2) {\n        bytes.push(parseInt(hexPayload.substr(i, 2), 16));\n    }\n\n    // 3. Dynamic Function Detection and Execution\n    \n    // CASE A: (The Things Stack v3)\n    if (typeof decodeUplink === 'function') {\n        try {\n            const input = {\n                bytes: bytes,\n                fPort: port\n            };\n            var result = decodeUplink(input);\n            \n            if (result.data) return result.data;\n\n            return result; \n        } catch (e) {\n            console.error(\"Error inside decodeUplink:\", e);\n            throw e;\n        }\n    }\n\n    // CASE B: Legacy TTN (v2)\n    else if (typeof Decoder === 'function') {\n        try {\n            return Decoder(bytes, port);\n        } catch (e) {\n            console.error(\"Error inside Decoder:\", e);\n            throw e;\n        }\n    }\n\n    // CASE C: No decoder found\n    else {\n        throw new Error(\"No compatible TTN decoder function (decodeUplink or Decoder) found in scope.\");\n    }\n}\n\n\n// TTN decoder\n/**\n * THIS SOFTWARE IS PROVIDED BY GREENME SAS  AND ITS CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES,\n * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n * IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,\n * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;\n * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\n * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,\n * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n * */\n\n///////////////////////////////////\n// Helper class and definitions\n///////////////////////////////////\nvar cp437ToUnicode = [0x0000,0x0001,0x0002,0x0003,0x0004,0x0005,0x0006,0x0007,0x0008,0x0009,0x000a,0x000b,0x000c,0x000d,0x000e,0x000f,0x0010,0x0011,0x0012,0x0013,0x0014,0x0015,0x0016,0x0017,0x0018,0x0019,0x001a,0x001b,0x001c,0x001d,0x001e,0x001f,0x0020,0x0021,0x0022,0x0023,0x0024,0x0025,0x0026,0x0027,0x0028,0x0029,0x002a,0x002b,0x002c,0x002d,0x002e,0x002f,0x0030,0x0031,0x0032,0x0033,0x0034,0x0035,0x0036,0x0037,0x0038,0x0039,0x003a,0x003b,0x003c,0x003d,0x003e,0x003f,0x0040,0x0041,0x0042,0x0043,0x0044,0x0045,0x0046,0x0047,0x0048,0x0049,0x004a,0x004b,0x004c,0x004d,0x004e,0x004f,0x0050,0x0051,0x0052,0x0053,0x0054,0x0055,0x0056,0x0057,0x0058,0x0059,0x005a,0x005b,0x005c,0x005d,0x005e,0x005f,0x0060,0x0061,0x0062,0x0063,0x0064,0x0065,0x0066,0x0067,0x0068,0x0069,0x006a,0x006b,0x006c,0x006d,0x006e,0x006f,0x0070,0x0071,0x0072,0x0073,0x0074,0x0075,0x0076,0x0077,0x0078,0x0079,0x007a,0x007b,0x007c,0x007d,0x007e,0x007f,0x00c7,0x00fc,0x00e9,0x00e2,0x00e4,0x00e0,0x00e5,0x00e7,0x00ea,0x00eb,0x00e8,0x00ef,0x00ee,0x00ec,0x00c4,0x00c5,0x00c9,0x00e6,0x00c6,0x00f4,0x00f6,0x00f2,0x00fb,0x00f9,0x00ff,0x00d6,0x00dc,0x00a2,0x00a3,0x00a5,0x20a7,0x0192,0x00e1,0x00ed,0x00f3,0x00fa,0x00f1,0x00d1,0x00aa,0x00ba,0x00bf,0x2310,0x00ac,0x00bd,0x00bc,0x00a1,0x00ab,0x00bb,0x2591,0x2592,0x2593,0x2502,0x2524,0x2561,0x2562,0x2556,0x2555,0x2563,0x2551,0x2557,0x255d,0x255c,0x255b,0x2510,0x2514,0x2534,0x252c,0x251c,0x2500,0x253c,0x255e,0x255f,0x255a,0x2554,0x2569,0x2566,0x2560,0x2550,0x256c,0x2567,0x2568,0x2564,0x2565,0x2559,0x2558,0x2552,0x2553,0x256b,0x256a,0x2518,0x250c,0x2588,0x2584,0x258c,0x2590,0x2580,0x03b1,0x00df,0x0393,0x03c0,0x03a3,0x03c3,0x00b5,0x03c4,0x03a6,0x0398,0x03a9,0x03b4,0x221e,0x03c6,0x03b5,0x2229,0x2261,0x00b1,0x2265,0x2264,0x2320,0x2321,0x00f7,0x2248,0x00b0,0x2219,0x00b7,0x221a,0x207f,0x00b2,0x25a0];\n\n/**\n * Main class for encoding message.\n */\nclass Encoder\n{\n    MSG_LEN_SET_CALIB    = 21;\n    MSG_LEN_SET_CFG      = 45;\n    MSG_LEN_SET_DISPLAY  = 38;\n    MSG_LEN_RESET         = 6;\n    MSG_LEN_SET_VALUE    = 10;\n    MSG_LEN_SET_ALERT    = 8;\n     \n\n    RADIOMSG_HEADER1 = \t\t0x65;\n    RADIOMSG_HEADER2 = \t\t0xef;       //0xee on devices with fw < 2.5\n\n    RADIOCMD_SET_CALIB  =  \t0x04;\n    RADIOCMD_SET_CFG =\t\t0x05;\n    RADIOCMD_REQUEST_CFG = \t0x06;\n    RADIOCMD_SET_DISPLAY = \t0x07;\n    RADIOCMD_RESET       = \t0x08;\n    RADIOCMD_SET_VALUE   = \t0x09;\n    RADIOCMD_SET_ALERT   = \t0x0A;\n\n    TOGGLE_ACTION_IMG\t=\t0x00;\n    TOGGLE_ACTION_TEXT\t=\t0x01;   \n\n    POLL_RESPONSE_HAPPY =\t0x00;\n    POLL_RESPONSE_YESNO =\t0x01;\n    POLL_ACK_OK =\t\t0x00;\n    POLL_ACK_SENT =\t0x01;\n\n    LANG_FR_FR = 0x00;\n    LANG_EN_US = 0x01;\n    LANG_EN_GB = 0x02;\n    LANG_DE_DE = 0x03;\n    LANG_NL_NL = 0x04;\n    LANG_PT_PT = 0x05;\n    LANG_ES_ES = 0x06;\n\n    MAX_TEXT_LENGTH = 30;\n    MAX_TOGGLE_TEXT_LENGTH = 10;\n\n    VALUETYPE_TEMP\t= 1;\n    VALUETYPE_HYGR\t= 2;\n    VALUETYPE_DBA\t= 3;\n    VALUETYPE_LUX = 4;\n    VALUETYPE_COLORR = 5;\n    VALUETYPE_COLORG = 6;\n    VALUETYPE_COLORB = 7;\n    VALUETYPE_COLORW = 8;\n\n    constructor()\n    {        \n    }\n\n\t/***\n\t * Calibrate device with deltas or gain to raw electronic measurement.\n\t * @param deltaTemp_deg\n\t * @param deltaHygr\n\t * @param gainLux\n\t * @param deltaDBA\n\t * @param deltaOctave1\n\t * @param deltaOctave2\n\t * @param deltaOctave3\n\t * @param deltaOctave4\n\t * @param deltaOctave5\n\t * @param deltaOctave6\n\t * @param deltaOctave7\n\t * @param deltaOctave8\n\t * @return\n\t */\n\tMakeMsgCalib(\n        deltaTemp_deg,\n        deltaHygr,\n        gainLux,\n        deltaDBA,\n        gainColorR,\n        gainColorG,\n        gainColorB,\n        gainColorW\n        )\n    {\n        let i= 0;\n        let buffer = new Array(this.MSG_LEN_SET_CALIB).fill(0);\n        i=0;\n        buffer[i] = this.RADIOMSG_HEADER1;\n        i++;\n        buffer[i] = this.RADIOMSG_HEADER2;\n        i++;\n        //cmd\n        buffer[i] = this.RADIOCMD_SET_CALIB; \n        i++;\n        this.WriteInt16ToBuffer((deltaTemp_deg*100), buffer, i);\n        i=i+2;\n        this.WriteInt16ToBuffer((deltaHygr*100), buffer, i);\n        i=i+2;\n        this.WriteU16ToBuffer((gainLux*100), buffer, i);\n        i=i+2;\n        this.WriteInt16ToBuffer((deltaDBA*100), buffer, i);\n        i=i+2;\n        this.WriteU16ToBuffer((gainColorR*100), buffer, i);\n        i=i+2;\n        this.WriteU16ToBuffer((gainColorG*100), buffer, i);\n        i=i+2;\n        this.WriteU16ToBuffer((gainColorB*100), buffer, i);\n        i=i+2;\n        this.WriteInt16ToBuffer((gainColorW*100), buffer, i);\n        i=i+2;\n        \n        //crc\n        let crc = this.Crc16_ccit_false(buffer, i);\n        this.WriteCrc16ToBuffer(crc, buffer, i); \n        i=i+2;\n\n        let result = {b64:null, hex:null};\n        let str = '';\n        for (let i=0; i<buffer.length; i++)\n        {\n            if (buffer[i] < 16)\n                str += '0'; \n            str += buffer[i].toString(16);\n        }\n        result.hex = str;\n        str = String.fromCharCode.apply(String, buffer);\n        result.b64 = btoa(str);\n        return result;\n    }\n\n    /**\n     * Calibrate a sensor. Output value will take given value.\n     * @param {*} sensorType \n     * @param {*} measure \n     * @returns \n     */\n    MakeMsgSetValue(sensorType,  measure)\n    {\n        if ((sensorType < 1) || (sensorType > 8))\n            return;\n\n        let i= 0;\n        let buffer = new Array(this.MSG_LEN_SET_VALUE).fill(0);\n        i=0;\n        buffer[i] = this.RADIOMSG_HEADER1;\n        i++;\n        buffer[i] = this.RADIOMSG_HEADER2;\n        i++;\n        //cmd\n        buffer[i] = this.RADIOCMD_SET_VALUE; \n        i++;\n        \n        buffer[i] = sensorType;\n        i++;\n        this.WriteFloatToBuffer(measure, buffer, i);\n        i=i+4;\n        \n        //crc\n        let crc = this.Crc16_ccit_false(buffer, i);\n        this.WriteCrc16ToBuffer(crc, buffer, i); \n        i=i+2;\n\n        let result = {b64:null, hex:null};\n        let str = '';\n        for (let i=0; i<buffer.length; i++)\n        {\n            if (buffer[i] < 16)\n                str += '0'; \n            str += buffer[i].toString(16);\n        }\n        result.hex = str;\n        str = String.fromCharCode.apply(String, buffer);\n        result.b64 = btoa(str);\n        return result;\n    }\n\n    /**\n     * Reset device.\n     * @param {} factoryReset true to perform full reset\n     * @returns \n     */\n    MakeMsgReset(factoryReset = false)\n    {\n        let i= 0;\n        let buffer = new Array(this.MSG_LEN_RESET).fill(0);\n\n       \n        buffer[i] = this.RADIOMSG_HEADER1;\n        i++;\n        buffer[i] = this.RADIOMSG_HEADER2;\n        i++;\n        //cmd\n        buffer[i] = this.RADIOCMD_RESET; \n        i++;\n        \n        if (factoryReset)\n            buffer[i] = 0x11;\n        else\n            buffer[i] = 0x01;\n        i++;\n        \n        //crc\n        let crc = this.Crc16_ccit_false(buffer, i);\n        this.WriteCrc16ToBuffer(crc, buffer, i); \n        i=i+2;\n\n        let result = {b64:null, hex:null};\n        let str = '';\n        for (let i=0; i<buffer.length; i++)\n        {\n            if (buffer[i] < 16)\n                str += '0'; \n            str += buffer[i].toString(16);\n        }\n        result.hex = str;\n        str = String.fromCharCode.apply(String, buffer);\n        result.b64 = btoa(str);\n        return result;\n    }\n\n    /**\n     * Configure the device\n     * @param {*} shortMsgInterval_min  interval between short messages, in minutes \n     * @param {*} longMsgInterval_min   interval between long messages, in minutes\n     * @param {*} showTemp true if temperature screen must be displayed\n     * @param {*} showHygr true if hygrometry screen must be displayed\n     * @param {*} showLux true if light screen must be displayed\n     * @param {*} showNoise true if noise screen must be displayed\n     * @param {*} showAir true if air quality screen must be displayed\n     * @param {*} showToggle true if the device should react on toggle left/right\n     * @param {*} BleBeaconEnabled //unused\n     * @param {*} disableSound disable sound measurment (will send zeros)\n     * @param {*} disableVOC  disable voc measurement\n     * @param {*} toggleModeText show text rather than smileys\n     * @param {*} imgToggleLeft  unused\n     * @param {*} imgToggleRight unused\n     * @param {*} imgToggleBack unused\n     * @param {*} extSensorType none, co2 or pm\n     * @param {*} textToggleLeft text to display on toggle left\n     * @param {*} textToggleRight text to display on toggle right \n     * @param {*} textAcknowledgment text to display after toggle end\n     * @param {*} lang language\n     * @param {*} eventMode unused\n     * @param {*} eventFrom unused\n     * @param {*} eventThreshold unused\n     * @param {*} eventWindow_s unused\n     * @returns \n     */\n    MakeMsgConfig(shortMsgInterval_min, longMsgInterval_min, showTemp, showHygr, showLux, showNoise, showAir, showToggle, BleBeaconEnabled, disableSound, disableVOC,\n        toggleModeText, imgToggleLeft, imgToggleRight, imgToggleBack, extSensorType, textToggleLeft, textToggleRight, textAcknowledgment, \n        lang, eventMode, eventFrom, eventThreshold, eventWindow_s)\n    {\n        let i= 0;\n        let val;\n        let buffer = new Array(this.MSG_LEN_SET_CFG).fill(0);\n\n       \n        buffer[i] = this.RADIOMSG_HEADER1;\n        i++;\n        buffer[i] = this.RADIOMSG_HEADER2;\n        i++;\n        //cmd\n        buffer[i] = this.RADIOCMD_SET_CFG; \n        i++;\n        \n        buffer[i] = (shortMsgInterval_min > 255)?255:shortMsgInterval_min;\n        i++;\n        buffer[i] = (longMsgInterval_min > 255)?255:longMsgInterval_min;\n        i++;\n\n        buffer [i] = 0;\n        buffer[i] |= showTemp?0x80:0;\n        buffer[i] |= showHygr?0x40:0;\n        buffer[i] |= showLux?0x20:0;\n        buffer[i] |= showNoise?0x10:0;\n        buffer[i] |= showAir?0x08:0;\n        buffer[i] |= showToggle?0x04:0;\n        buffer[i] |= BleBeaconEnabled?0x02:0;\n        buffer[i] |= disableSound?0x01:0;\n        i++;\n        \n        buffer [i] = 0;\n        buffer[i] |= disableVOC?0x80:0;\n        buffer[i] |= toggleModeText?0x40:0;\n        val = (imgToggleLeft>7)?7:imgToggleLeft;\n        buffer[i] |= val << 3;\n        val = (imgToggleRight>7)?7:imgToggleRight;\n        buffer[i] |= val;\n        i++;\n\n        buffer[i] = 0;\n        val = (imgToggleBack>7)?7:imgToggleBack;\n        buffer[i] |= val << 5;\n        buffer[i] |= extSensorType & 0x1f;\n        i++;\n        \n        let cp437str = this.Utf8TextToCp437(textToggleLeft);\n        for (let j=0; j < cp437str.length; j++)\n        {\n            buffer[i+j] = cp437str[j];\n            if (j >= this.MAX_TOGGLE_TEXT_LENGTH - 1) \n                break;\n        }\n        i+= this.MAX_TOGGLE_TEXT_LENGTH;\n\n        cp437str = this.Utf8TextToCp437(textToggleRight);\n        for (let j=0; j < cp437str.length; j++)\n        {\n            buffer[i+j] = cp437str[j];\n            if (j >= this.MAX_TOGGLE_TEXT_LENGTH - 1) \n                break;\n        }\n        i+= this.MAX_TOGGLE_TEXT_LENGTH;\n\n        cp437str = this.Utf8TextToCp437(textAcknowledgment);\n        for (let j=0; j < cp437str.length; j++)\n        {\n            buffer[i+j] = cp437str[j];\n            if (j >= this.MAX_TOGGLE_TEXT_LENGTH - 1) \n                break;\n        }\n        i+= this.MAX_TOGGLE_TEXT_LENGTH;\n\n\n        switch (lang)\n        {\n            case \"FR_FR\":\n                buffer[i] = this.LANG_FR_FR;\n                break;\n            case \"EN_US\":\n                buffer[i] = this.LANG_EN_US;\n                break;\n            case \"EN_GB\":\n                buffer[i] = this.LANG_EN_GB;\n                    break;\n            case \"DE_DE\":\n                buffer[i] = this.LANG_DE_DE;\n                break;\n            case \"NL_NL\":\n                    buffer[i] = this.LANG_NL_NL;\n                    break;\n                case \"PT_PT\":\n                buffer[i] = this.LANG_PT_PT;\n                break;\n            case \"ES_ES\":\n                buffer[i] = this.LANG_ES_ES;\n                break;\n            default:\n                buffer[i] = this.LANG_EN_GB;\n                break;\n        }\n        i++;\n\n        buffer[i] = eventMode & 0xff;\n        i++;\n\n        buffer[i] = eventFrom & 0xff;\n        i++;\n        \n        buffer[i] = eventThreshold & 0xff;\n        i++;\n        \n        buffer[i] = eventWindow_s & 0xff;\n        i++;\n\n        //crc\n        let crc = this.Crc16_ccit_false(buffer, i);\n        this.WriteCrc16ToBuffer(crc, buffer, i); \n        i=i+2;\n\n        let result = {b64:null, hex:null};\n        let str = '';\n        for (let i=0; i<buffer.length; i++)\n        {\n            if (buffer[i] < 16)\n                str += '0'; \n            str += buffer[i].toString(16);\n        }\n        result.hex = str;\n        str = String.fromCharCode.apply(String, buffer);\n        result.b64 = btoa(str);\n        return result;\n    }\n\n    /**\n     * Configure the cube's display (beta)\n     * \n     * @param {*} endOnToggle set to true if display should return to normal after toggle\n     * @param {*} endOnReboot set to true if display should return to normal after reboot\n     * @param {*} expiresAfter_h how many hours should this message to show\n     * @param {*} repeat_every_h how often this message should repeat\n     * @param {*} responseChoices smileys (0x00) or yes/no (0x01)\n     * @param {*} acknowledgement ok (0x00) or sent (0x01)\n     * @param {*} text text to display, 30 characters max\n     * @returns \n     */\n    MakeMsgDisplay(endOnToggle, endOnReboot, expiresAfter_h, repeat_every_h, responseChoices, acknowledgement, text)\n    {\n        let i= 0;\n        let buffer = new Array(this.MSG_LEN_SET_DISPLAY).fill(0);\n        let totalMsgLength = this.MAX_TEXT_LENGTH + 7;\n\n        for (i=0 ; i<totalMsgLength; i++)\n        {\n            buffer[i] = 0;\n        }\n        i=0;\n        buffer[i] = this.RADIOMSG_HEADER1;\n        i++;\n        buffer[i] = this.RADIOMSG_HEADER2;\n        i++;\n        buffer[i] = this.RADIOCMD_SET_DISPLAY; \n        i++;\n\n        let cfg = 0;\n        if (endOnToggle)\n            cfg |= 0x80;\n        if (endOnReboot)\n            cfg |= 0x40;\n        \n        \n        cfg |= (responseChoices & 0x7) << 3;\n        cfg |= (acknowledgement & 0x7) ;\n        cfg &= 0xff;    //cleanup\n        buffer[i] = cfg;\n        i++;\n        \n        buffer[i] = expiresAfter_h & 0xff;\n        i++;\n\n        buffer[i] =  repeat_every_h & 0xff;\n        i++;\n\n        let cp437str = this.Utf8TextToCp437(text);\n        for (let j=0; j < cp437str.length; j++)\n        {\n            buffer[i+j] = cp437str[j];\n            if (j >= this.MAX_TEXT_LENGTH - 1) \n                break;\n        }\n        i+= this.MAX_TEXT_LENGTH;\n        \n\n        //crc\n        let crc = this.Crc16_ccit_false(buffer, i);\n        this.WriteCrc16ToBuffer(crc, buffer, i); \n        i=i+2;\n\n        let result = {b64:null, hex:null};\n        let str = '';\n        for (let i=0; i<buffer.length; i++)\n        {\n            if (buffer[i] < 16)\n                str += '0'; \n            str += buffer[i].toString(16);\n        }\n        result.hex = str;\n        str = String.fromCharCode.apply(String, buffer);\n        result.b64 = btoa(str);\n        return result;\n    \n    }\n\n\n    /**\n     * Convert UTF8 string to CP437 char array\n     * @param {*} utf8str \n     * @returns \n     */\n    Utf8TextToCp437(utf8str)\n    {\n        let cp437 = new Array();\n        let len = utf8str.length;\n        for (let i=0; i<len; i++)\n        {\n            let code = utf8str.charCodeAt(i);\n            for (let j=0; j<cp437ToUnicode.length; j++)\n            {\n                //find char\n                if (cp437ToUnicode[j] == code)\n                {\n                    if ((j >= 0x20) && (j <= 0xA9))\n                        cp437.push(j);\n                    break;\n                }\n            }\n\n        }\n        return cp437;\n    }\n\n    Base64ToHexString(b64Str)\n    {\n        var str = atob(b64Str);\n    }\n\n    /**\n     * Calculate CRC16\n     * @param {*} buffer \n     * @param {*} Size \n     * @returns \n     */\n    Crc16_ccit_false(buffer, Size)\n    {\n        var crcTable = [\n            0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7,\n            0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,\n            0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6,\n            0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,\n            0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485,\n            0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,\n            0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,\n            0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,\n            0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,\n            0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,\n            0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12,\n            0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,\n            0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41,\n            0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,\n            0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70,\n            0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,\n            0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F,\n            0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,\n            0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E,\n            0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,\n            0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,\n            0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,\n            0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C,\n            0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,\n            0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB,\n            0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,\n            0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,\n            0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,\n            0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9,\n            0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,\n            0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8,\n            0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0,\n        ];\n        var crc = 0xFFFF;\n        var j, i,c;\n    \n        for (i = 0; i < Size; i++) {\n    \n            c = buffer[i];\n            j = (c ^ (crc >> 8)) & 0xFF;\n            crc = crcTable[j] ^ (crc << 8);\n        }\n    \n        return crc & 0xffff;\n    }\n\n\n    /**\n     * Write CRC value to the end of the byte array.\n     * @param {*} value \n     * @param {*} buffer \n     * @param {*} pos \n     */\n    WriteCrc16ToBuffer(value, buffer, pos)\n    {\n        let u16 = new Uint16Array([value])[0]; \n        buffer[pos+1]   = u16 >> 8;\n        buffer[pos] = u16 & 0x00ff;\n    }\n\n    /**\n     * Add UINT16 to byte array\n     * @param {*} value \n     * @param {*} buffer \n     * @param {*} pos \n     */\n    WriteU16ToBuffer(value, buffer, pos)\n    {\n        let u16Array = new Int16Array(1);\n        u16Array[0] = value;\n        let byteArray = new Uint8Array(u16Array.buffer);\n        for (let i=0; i<2; i++)\n            buffer[pos+(1-i)] = byteArray[i]; \n    }\n\n    /**\n     * Add 16 bit int to byte array\n     * @param {*} value \n     * @param {*} buffer \n     * @param {*} pos \n     */\n    WriteInt16ToBuffer(value, buffer, pos)\n    {\n        let shortArray = new Int16Array(1);\n        shortArray[0] = value;\n        let byteArray = new Uint8Array(shortArray.buffer);\n        for (let i=0; i<2; i++)\n            buffer[pos+(1-i)] = byteArray[i]; \n    }\n\n    /**\n     * Add float to byte array.\n     * @param {*} value \n     * @param {*} buffer \n     * @param {*} pos \n     */\n    WriteFloatToBuffer(value, buffer, pos)\n    {\n        let floatArray = new Float32Array(1);\n        floatArray[0] = value;\n        let byteArray = new Uint8Array(floatArray.buffer);\n        for (let i=0; i<4; i++)\n            buffer[pos+i] = byteArray[i];\n    }\n}\n\n/////////////////////////////////\n// TTN Encode Decode\n/////////////////////////////////\n\nfunction decodeUplink(input) {\n  var data = {};\n  var errors = [];\n  var warnings = [];\n\n  //constants\n  var MESSAGEV2_SHORT = 5;\n  var MESSAGEV2_FULL  = 6;\n  var MESSAGEV2_FEEL  = 7;\n  var MESSAGEEXT_NONE = 0;\n  var MESSAGEEXT_CO2ONLY = 1;\n  var MESSAGEEXT_COVONLY = 2;\n  var MESSAGEEXT_COV_CO2 = 3;\n  var MESSAGEEXT_PMONLY  = 4;\n  var MESSAGEEXT_COV_PM  = 5;\n  var MESSAGEEXT_COV_CO2_PM  = 6;\n          \n  var FEEL_UNHAPPY  = 1;\n  var FEEL_HAPPY   = -1;\n  var FEEL_UNKNOWN = 0;\n  \n  var ValidateMessageSize = function (bytes)\n  {\n      var msgSizes = new Map();\n      msgSizes[MESSAGEV2_SHORT] = 11;\n      msgSizes[MESSAGEV2_FULL] = 26;\n      msgSizes[MESSAGEV2_FEEL] = 26;\n      \n      var msgExtSizes = new Map();\n      msgExtSizes[MESSAGEEXT_NONE] = 0;\n      msgExtSizes[MESSAGEEXT_CO2ONLY] = 2;\n      msgExtSizes[MESSAGEEXT_COVONLY] = 2;\n      msgExtSizes[MESSAGEEXT_COV_CO2] = 4;\n  \n      var msgType = bytes[0] & 0x0f;\n      var msgSize = bytes.length;\n      var bodySize = msgSizes[msgType];\n\n      if (bodySize === undefined)\n      {\n          return false;\n      }\n      \n      if (msgSize < bodySize)\n          return false;\n      else \n      {\n          var msgExtType = bytes[bodySize - 1];\n          var extSize = msgExtSizes[msgExtType];\n          if (extSize === undefined)\n          {\n              return false;\n          }\n          else \n          {\n              if (msgSize == bodySize + extSize)\n                  return true;\n              else \n                  return false;\n          }\n      }\n  };\n  \n  var bytes = input.bytes;\n  if (!ValidateMessageSize(bytes))\n  {\n      errors.push(\"invalid message size\");\n  }\n  else \n  {\n      var i = 0;\n  \n      //decode header\n      var header = bytes[0];\n      i++;\n\n      var val = header & 0xc0;\n      if (val == 0x40)\n          data.lastFeel = FEEL_UNHAPPY;\n      else if (val == 0x80)\n          data.lastFeel = FEEL_HAPPY;\n      else \n          data.lastFeel = FEEL_UNKNOWN;\n      \n      var status = (header & 0x30) >> 4;\n      var messageType = header & 0x0f;\n\n      //decode code data\n      if ((messageType == MESSAGEV2_SHORT) || (messageType == MESSAGEV2_FULL) || (messageType == MESSAGEV2_FEEL))\n      {\n          data.temperature = (bytes[i+1]*256 + bytes[i])/100.0;\n          i+=2;\n          data.hygrometry = (bytes[i+1]*256 + bytes[i])/100.0;\n          i+=2;\n          data.noiseMax = bytes[i]/2;\n          i++;\n          data.noiseAvg = bytes[i]/2;\n          i++;\n          data.lux = (bytes[i+1]*256 + bytes[i]);\n          i+=2;\n      }\n\n      //decode full et feel messages\n      if ((messageType == MESSAGEV2_FULL) || (messageType == MESSAGEV2_FEEL))\n      {\n          data.lightColorR = bytes[i] & 0xff;\n          i++;\n          data.lightColorG = bytes[i] & 0xff;\n          i++;\n          data.lightColorB = bytes[i] & 0xff;\n          i++;\n          data.lightColorW =  (bytes[i+1]*256 + bytes[i]);\n          i=i+2;\n          data.flicker = bytes[i];\n          i++;\n          data.octave1 = bytes[i];\n          i++;\n          data.octave2 = bytes[i];\n          i++;\n          data.octave3 = bytes[i];\n          i++;\n          data.octave4 = bytes[i];\n          i++;\n          data.octave5 = bytes[i];\n          i++;\n          data.octave6 = bytes[i];\n          i++;\n          data.octave7 = bytes[i];\n          i++;\n          data.octave8 = bytes[i];\n          i++;\n          data.octave9 = bytes[i];\n          i++;\n      }\n      \n      if ((messageType == MESSAGEV2_SHORT) || (messageType == MESSAGEV2_FULL) ||\n          (messageType == MESSAGEV2_FEEL))\n      {\n          //battery level and status: unused\n          i++;\n          \n          //extended messages\n          var extMsgType = bytes[i];\n          i++;\n          \n          //var extMessageType = extMsgType;\n          if (extMsgType == MESSAGEEXT_CO2ONLY)\n          {\n              data.co2 = bytes[i+1]*256 + bytes[i];\n              i+=2;\n          }\n          else if (extMsgType == MESSAGEEXT_COVONLY)\n          {\n              data.tvoc = bytes[i+1]*256 + bytes[i];\n              i+=2;\n          }\n          else if (extMsgType == MESSAGEEXT_COV_CO2)\n          {\n              data.tvoc = bytes[i+1]*256 + bytes[i];\n              i+=2;\n              data.co2 = bytes[i+1]*256 + bytes[i];\n              i+=2;\n          }\n      }\n  }\n  \n  return {\n    data: data,\n    warnings: warnings,\n    errors: errors\n  };\n}\n\nfunction encodeDownlink(input) {\n  let encoder = new Encoder();\n  let bytes = null;\n\n  if (input.msgType == 'MakeMsgCalib')\n  {\n    bytes = encoder.MakeMsgCalib(\n      input.deltaTemp_deg,\n      input.deltaHygr,\n      input.gainLux,\n      input.deltaDBA,\n      input.gainColorR,\n      input.gainColorG,\n      input.gainColorB,\n      input.gainColorW);\n  }\n  else if (input.msgType == 'MakeMsgSetValue')\n  {\n    bytes = encoder.MakeMsgSetValue(\n      input.sensorType, \n      input.measure);\n  }\n  else if (input.msgType == 'MakeMsgConfig')\n  {\n    bytes = encoder.MakeMsgConfig(\n      input.shortMsgInterval_min, \n      input.longMsgInterval_min, \n      input.showTemp, \n      input.showHygr, \n      input.showLux, \n      input.showNoise, \n      input.showAir, \n      input.showToggle, \n      0, \n      input.disableSound, \n      input.disableVOC,\n      input.toggleModeText, \n      input.imgToggleLeft, \n      input.imgToggleRight, \n      input.imgToggleBack, \n      input.extSensorType, \n      input.textToggleLeft, \n      input.textToggleRight, \n      input.textAcknowledgment, \n      input.lang, \n      0, \n      0, \n      0, \n      0);\n  \n  }\n  else if (input.msgType == 'MakeMsgDisplay')\n  {\n    bytes = encoder.MakeMsgDisplay(\n      input.endOnToggle,\n      input.endOnReboot, \n      input.expiresAfter_h, \n      input.repeat_every_h, \n      input.responseChoices, \n      input.acknowledgement, \n      input.text);\n  }\n  else\n  {\n    return {errors:['bad message type.']};\n  }\n\n  if (bytes == null)\n  {\n    return {errors:['could not encode message.']};\n  } \n  else {\n    return {\n      // LoRaWAN FPort used for the downlink message\n      fPort: 1,\n      // Encoded bytes\n      bytes: bytes,\n    };\n  }\n}\n\nfunction decodeDownlink(input) {\n  return null;\n}\n",
                        "environment": "javascript",
                        "storage": "",
                        "version": "1.0"
                    },
                    "flows": {
                        "greenme_decoder_flow": {
                            "data": {
                                "payload": "{{payload}}",
                                "payload_function": "decodeThingerUplink",
                                "payload_type": "source_payload",
                                "resource": "uplink",
                                "source": "resource",
                                "update": "events"
                            },
                            "enabled": true,
                            "handle_connectivity": false,
                            "sink": {
                                "payload": "{{payload}}",
                                "payload_function": "",
                                "payload_type": "source_payload",
                                "resource_stream": "uplink_decoded",
                                "target": "resource_stream"
                            },
                            "split_data": false
                        }
                    },
                    "properties": {
                        "greenme_cube_co2": {
                            "data": {
                                "payload": "{{payload}}",
                                "payload_function": "getCO2",
                                "payload_type": "source_payload",
                                "resource_stream": "uplink_decoded",
                                "source": "resource_stream"
                            },
                            "default": {
                                "source": "value",
                                "value": 0
                            },
                            "enabled": true
                        },
                        "greenme_cube_hygrometry": {
                            "data": {
                                "payload": "{{payload}}",
                                "payload_function": "getHygrometry",
                                "payload_type": "source_payload",
                                "resource_stream": "uplink_decoded",
                                "source": "resource_stream"
                            },
                            "default": {
                                "source": "value",
                                "value": 0
                            },
                            "enabled": true
                        },
                        "greenme_cube_lux": {
                            "data": {
                                "payload": "{{payload}}",
                                "payload_function": "getLux",
                                "payload_type": "source_payload",
                                "resource_stream": "uplink_decoded",
                                "source": "resource_stream"
                            },
                            "default": {
                                "source": "value",
                                "value": 0
                            },
                            "enabled": true
                        },
                        "greenme_cube_noise_avg": {
                            "data": {
                                "payload": "{{payload}}",
                                "payload_function": "getNoiseAvg",
                                "payload_type": "source_payload",
                                "resource_stream": "uplink_decoded",
                                "source": "resource_stream"
                            },
                            "default": {
                                "source": "value",
                                "value": 0
                            },
                            "enabled": true
                        },
                        "greenme_cube_noise_max": {
                            "data": {
                                "payload": "{{payload}}",
                                "payload_function": "getNoiseMax",
                                "payload_type": "source_payload",
                                "resource_stream": "uplink_decoded",
                                "source": "resource_stream"
                            },
                            "default": {
                                "source": "value",
                                "value": 0
                            },
                            "enabled": true
                        },
                        "greenme_cube_temperature": {
                            "data": {
                                "payload": "{{payload}}",
                                "payload_function": "getTemperature",
                                "payload_type": "source_payload",
                                "resource_stream": "uplink_decoded",
                                "source": "resource_stream"
                            },
                            "default": {
                                "source": "value",
                                "value": 0
                            },
                            "enabled": true
                        },
                        "greenme_cube_tvoc": {
                            "data": {
                                "payload": "{{payload}}",
                                "payload_function": "getTVOC",
                                "payload_type": "source_payload",
                                "resource_stream": "uplink_decoded",
                                "source": "resource_stream"
                            },
                            "default": {
                                "source": "value",
                                "value": 0
                            },
                            "enabled": true
                        },
                        "uplink": {
                            "data": {
                                "payload": "{{payload}}",
                                "payload_function": "",
                                "payload_type": "source_payload",
                                "resource": "uplink",
                                "source": "resource",
                                "update": "events"
                            },
                            "default": {
                                "source": "value"
                            },
                            "description": "Last Raw uplink",
                            "enabled": true
                        }
                    }
                },
                "_resources": {
                    "properties": [
                        {
                            "description": "",
                            "name": "",
                            "property": "greenme_cube_tvoc",
                            "type": "",
                            "value": 0
                        },
                        {
                            "description": "",
                            "name": "",
                            "property": "greenme_cube_temperature",
                            "type": "",
                            "value": 0
                        },
                        {
                            "description": "",
                            "name": "",
                            "property": "greenme_cube_noise_max",
                            "type": "",
                            "value": 0
                        },
                        {
                            "description": "",
                            "name": "",
                            "property": "greenme_cube_noise_avg",
                            "type": "",
                            "value": 0
                        },
                        {
                            "description": "",
                            "name": "",
                            "property": "greenme_cube_lux",
                            "type": "",
                            "value": 0
                        },
                        {
                            "description": "",
                            "name": "",
                            "property": "greenme_cube_hygrometry",
                            "type": "",
                            "value": 0
                        },
                        {
                            "description": "",
                            "name": "",
                            "property": "greenme_cube_co2",
                            "type": "",
                            "value": 0
                        },
                        {
                            "property": "dashboard",
                            "value": {
                                "tabs": [
                                    {
                                        "name": "Environment Monitor",
                                        "widgets": [
                                            {
                                                "layout": {
                                                    "col": 0,
                                                    "row": 0,
                                                    "sizeX": 2,
                                                    "sizeY": 6
                                                },
                                                "panel": {
                                                    "color": "#ffffff",
                                                    "currentColor": "#ffffff",
                                                    "showOffline": {
                                                        "type": "none"
                                                    },
                                                    "title": "Temperature"
                                                },
                                                "properties": {
                                                    "color": "#ff0000",
                                                    "max": 50,
                                                    "min": -10,
                                                    "unit": "°C"
                                                },
                                                "sources": [
                                                    {
                                                        "bucket": {
                                                            "backend": "mongodb",
                                                            "id": "greenme_cube_data",
                                                            "mapping": "temperature",
                                                            "tags": {
                                                                "device": [],
                                                                "group": []
                                                            }
                                                        },
                                                        "color": "#e74c3c",
                                                        "name": "Temperature",
                                                        "source": "bucket",
                                                        "timespan": {
                                                            "mode": "latest"
                                                        }
                                                    }
                                                ],
                                                "type": "donutchart"
                                            },
                                            {
                                                "layout": {
                                                    "col": 2,
                                                    "row": 0,
                                                    "sizeX": 2,
                                                    "sizeY": 6
                                                },
                                                "panel": {
                                                    "color": "#ffffff",
                                                    "currentColor": "#ffffff",
                                                    "showOffline": {
                                                        "type": "none"
                                                    },
                                                    "title": "Humidity"
                                                },
                                                "properties": {
                                                    "color": "#0000ff",
                                                    "max": 100,
                                                    "min": 0,
                                                    "unit": "%"
                                                },
                                                "sources": [
                                                    {
                                                        "bucket": {
                                                            "backend": "mongodb",
                                                            "id": "greenme_cube_data",
                                                            "mapping": "hygrometry",
                                                            "tags": {
                                                                "device": [],
                                                                "group": []
                                                            }
                                                        },
                                                        "color": "#3498db",
                                                        "name": "Humidity",
                                                        "source": "bucket",
                                                        "timespan": {
                                                            "mode": "latest"
                                                        }
                                                    }
                                                ],
                                                "type": "donutchart"
                                            },
                                            {
                                                "layout": {
                                                    "col": 0,
                                                    "row": 6,
                                                    "sizeX": 2,
                                                    "sizeY": 6
                                                },
                                                "panel": {
                                                    "color": "#ffffff",
                                                    "currentColor": "#ffffff",
                                                    "showOffline": {
                                                        "type": "none"
                                                    },
                                                    "title": "Light Level"
                                                },
                                                "properties": {
                                                    "color": "#ffcc00",
                                                    "max": 1000,
                                                    "min": 0,
                                                    "unit": "lux"
                                                },
                                                "sources": [
                                                    {
                                                        "bucket": {
                                                            "backend": "mongodb",
                                                            "id": "greenme_cube_data",
                                                            "mapping": "lux",
                                                            "tags": {
                                                                "device": [],
                                                                "group": []
                                                            }
                                                        },
                                                        "color": "#f39c12",
                                                        "name": "Light",
                                                        "source": "bucket",
                                                        "timespan": {
                                                            "mode": "latest"
                                                        }
                                                    }
                                                ],
                                                "type": "donutchart"
                                            },
                                            {
                                                "layout": {
                                                    "col": 2,
                                                    "row": 6,
                                                    "sizeX": 2,
                                                    "sizeY": 6
                                                },
                                                "panel": {
                                                    "color": "#ffffff",
                                                    "currentColor": "#ffffff",
                                                    "showOffline": {
                                                        "type": "none"
                                                    },
                                                    "title": "Noise Level (Avg)"
                                                },
                                                "properties": {
                                                    "color": "#9933ff",
                                                    "max": 100,
                                                    "min": 0,
                                                    "unit": "dBA"
                                                },
                                                "sources": [
                                                    {
                                                        "bucket": {
                                                            "backend": "mongodb",
                                                            "id": "greenme_cube_data",
                                                            "mapping": "noiseAvg",
                                                            "tags": {
                                                                "device": [],
                                                                "group": []
                                                            }
                                                        },
                                                        "color": "#9b59b6",
                                                        "name": "Noise Avg",
                                                        "source": "bucket",
                                                        "timespan": {
                                                            "mode": "latest"
                                                        }
                                                    }
                                                ],
                                                "type": "donutchart"
                                            },
                                            {
                                                "layout": {
                                                    "col": 0,
                                                    "row": 12,
                                                    "sizeX": 2,
                                                    "sizeY": 6
                                                },
                                                "panel": {
                                                    "color": "#ffffff",
                                                    "currentColor": "#ffffff",
                                                    "showOffline": {
                                                        "type": "none"
                                                    },
                                                    "title": "CO2"
                                                },
                                                "properties": {
                                                    "color": "#00aa00",
                                                    "max": 2000,
                                                    "min": 400,
                                                    "unit": "ppm"
                                                },
                                                "sources": [
                                                    {
                                                        "bucket": {
                                                            "backend": "mongodb",
                                                            "id": "greenme_cube_data",
                                                            "mapping": "co2",
                                                            "tags": {
                                                                "device": [],
                                                                "group": []
                                                            }
                                                        },
                                                        "color": "#27ae60",
                                                        "name": "CO2",
                                                        "source": "bucket",
                                                        "timespan": {
                                                            "mode": "latest"
                                                        }
                                                    }
                                                ],
                                                "type": "donutchart"
                                            },
                                            {
                                                "layout": {
                                                    "col": 2,
                                                    "row": 12,
                                                    "sizeX": 2,
                                                    "sizeY": 6
                                                },
                                                "panel": {
                                                    "color": "#ffffff",
                                                    "currentColor": "#ffffff",
                                                    "showOffline": {
                                                        "type": "none"
                                                    },
                                                    "title": "TVOC"
                                                },
                                                "properties": {
                                                    "color": "#ff6600",
                                                    "max": 1000,
                                                    "min": 0,
                                                    "unit": "ppb"
                                                },
                                                "sources": [
                                                    {
                                                        "bucket": {
                                                            "backend": "mongodb",
                                                            "id": "greenme_cube_data",
                                                            "mapping": "tvoc",
                                                            "tags": {
                                                                "device": [],
                                                                "group": []
                                                            }
                                                        },
                                                        "color": "#e67e22",
                                                        "name": "TVOC",
                                                        "source": "bucket",
                                                        "timespan": {
                                                            "mode": "latest"
                                                        }
                                                    }
                                                ],
                                                "type": "donutchart"
                                            },
                                            {
                                                "layout": {
                                                    "col": 4,
                                                    "row": 0,
                                                    "sizeX": 2,
                                                    "sizeY": 12
                                                },
                                                "panel": {
                                                    "color": "#ffffff",
                                                    "currentColor": "#ffffff",
                                                    "showOffline": {
                                                        "type": "none"
                                                    },
                                                    "title": "Temperature & Humidity History"
                                                },
                                                "properties": {
                                                    "axis": true,
                                                    "fill": false,
                                                    "legend": true,
                                                    "multiple_axes": true
                                                },
                                                "sources": [
                                                    {
                                                        "bucket": {
                                                            "backend": "mongodb",
                                                            "id": "greenme_cube_data",
                                                            "mapping": "temperature",
                                                            "tags": {
                                                                "device": [],
                                                                "group": []
                                                            }
                                                        },
                                                        "color": "#e74c3c",
                                                        "name": "Temperature",
                                                        "source": "bucket",
                                                        "timespan": {
                                                            "magnitude": "hour",
                                                            "mode": "relative",
                                                            "period": "latest",
                                                            "value": 24
                                                        }
                                                    },
                                                    {
                                                        "bucket": {
                                                            "backend": "mongodb",
                                                            "id": "greenme_cube_data",
                                                            "mapping": "hygrometry",
                                                            "tags": {
                                                                "device": [],
                                                                "group": []
                                                            }
                                                        },
                                                        "color": "#3498db",
                                                        "name": "Humidity",
                                                        "source": "bucket",
                                                        "timespan": {
                                                            "magnitude": "hour",
                                                            "mode": "relative",
                                                            "period": "latest",
                                                            "value": 24
                                                        }
                                                    }
                                                ],
                                                "type": "chart"
                                            },
                                            {
                                                "layout": {
                                                    "col": 4,
                                                    "row": 12,
                                                    "sizeX": 2,
                                                    "sizeY": 6
                                                },
                                                "panel": {
                                                    "color": "#ffffff",
                                                    "currentColor": "#ffffff",
                                                    "showOffline": {
                                                        "type": "none"
                                                    },
                                                    "title": "Air Quality History"
                                                },
                                                "properties": {
                                                    "axis": true,
                                                    "fill": false,
                                                    "legend": true,
                                                    "multiple_axes": true
                                                },
                                                "sources": [
                                                    {
                                                        "bucket": {
                                                            "backend": "mongodb",
                                                            "id": "greenme_cube_data",
                                                            "mapping": "co2",
                                                            "tags": {
                                                                "device": [],
                                                                "group": []
                                                            }
                                                        },
                                                        "color": "#27ae60",
                                                        "name": "CO2",
                                                        "source": "bucket",
                                                        "timespan": {
                                                            "magnitude": "hour",
                                                            "mode": "relative",
                                                            "period": "latest",
                                                            "value": 24
                                                        }
                                                    },
                                                    {
                                                        "bucket": {
                                                            "backend": "mongodb",
                                                            "id": "greenme_cube_data",
                                                            "mapping": "tvoc",
                                                            "tags": {
                                                                "device": [],
                                                                "group": []
                                                            }
                                                        },
                                                        "color": "#e67e22",
                                                        "name": "TVOC",
                                                        "source": "bucket",
                                                        "timespan": {
                                                            "magnitude": "hour",
                                                            "mode": "relative",
                                                            "period": "latest",
                                                            "value": 24
                                                        }
                                                    }
                                                ],
                                                "type": "chart"
                                            }
                                        ]
                                    }
                                ]
                            }
                        }
                    ]
                }
            }
        ]
    }
}