UNPKG

node-red-contrib-google-smarthome

Version:

Lets you control Node-Red via Google Assistant or the Google Home App

4,983 lines 235 kB
"use strict";
/**
 * node-red-contrib-google-smarthome
 * Copyright (C) 2025 Claudio Chimera and others.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const util_1 = __importDefault(require("util"));
const Formats_js_1 = require("../lib/Formats.js");
const SmartHome_js_1 = require("../lib/SmartHome.js");
const COOK_SUPPORTED_UNITS = ["UNKNOWN_UNITS", "NO_UNITS", "CENTIMETERS", "CUPS", "DECILITERS", "FEET", "FLUID_OUNCES", "GALLONS", "GRAMS", "INCHES", "KILOGRAMS", "LITERS", "METERS", "MILLIGRAMS", "MILLILITERS", "MILLIMETERS", "OUNCES", "PINCH", "PINTS", "PORTION", "POUNDS", "QUARTS", "TABLESPOONS", "TEASPOONS"];
const DISPENSE_SUPPORTED_UNITS = ["CENTIMETERS", "CUPS", "DECILITERS", "FLUID_OUNCES", "GALLONS", "GRAMS", "KILOGRAMS", "LITERS", "MILLIGRAMS", "MILLILITERS", "MILLIMETERS", "NO_UNITS", "OUNCES", "PINCH", "PINTS", "PORTION", "POUNDS", "QUARTS", "TABLESPOONS", "TEASPOONS"];
const ENERGY_STORAGE_UNITS = ['SECONDS', 'MILES', 'KILOMETERS', 'PERCENTAGE', 'KILOWATT_HOURS'];
const LANGUAGES = ["da", "nl", "en", "fr", "de", "hi", "id", "it", "ja", "ko", "no", "pt-BR", "es", "sv", "th", "zh-TW"];
/******************************************************************************************************************
 *
 *
 */
class DeviceNode {
    constructor(config) {
        SmartHome_js_1.RED.nodes.createNode(this, config);
        this.device = {};
        this.client = config.client;
        this.name = config.name || config.id;
        this.device_type = config.device_type;
        this.nicknames = config.nicknames;
        this.clientConn = SmartHome_js_1.RED.nodes.getNode(this.client);
        this._debug(".constructor config " + JSON.stringify(config));
        if (!this.clientConn) {
            this.error(SmartHome_js_1.RED._("device.errors.missing-config"));
            this.status({ fill: "red", shape: "dot", text: "Missing config" });
            return;
        }
        else if (typeof this.clientConn.register !== 'function') {
            this.error(SmartHome_js_1.RED._("device.errors.missing-bridge"));
            this.status({ fill: "red", shape: "dot", text: "Missing SmartHome" });
            return;
        }
        this.lang = this.clientConn.default_lang || 'en';
        this.state_types = {};
        this.errorCode = undefined;
        this.trait = {
            appselector: config.trait_appselector || false,
            armdisarm: config.trait_armdisarm || false,
            brightness: config.trait_brightness || false,
            camerastream: config.trait_camerastream || false,
            channel: config.trait_channel || false,
            colorsetting: config.trait_colorsetting || false,
            cook: config.trait_cook || false,
            dispense: config.trait_dispense || false,
            dock: config.trait_dock || false,
            energystorage: config.trait_energystorage || false,
            fanspeed: config.trait_fanspeed || false,
            fill: config.trait_fill || false,
            humiditysetting: config.trait_humiditysetting || false,
            inputselector: config.trait_inputselector || false,
            lighteffects: config.trait_lighteffects || false,
            locator: config.trait_locator || false,
            lockunlock: config.trait_lockunlock || false,
            mediastate: config.trait_mediastate || false,
            modes: config.trait_modes || false,
            networkcontrol: config.trait_networkcontrol || false,
            objectdetection: config.trait_objectdetection || false,
            occupancysensing: config.trait_occupancysensing || false,
            onoff: config.trait_onoff || false,
            openclose: config.trait_openclose || false,
            reboot: config.trait_reboot || false,
            rotation: config.trait_rotation || false,
            runcycle: config.trait_runcycle || false,
            sensorstate: config.trait_sensorstate || false,
            scene: config.trait_scene || false,
            softwareupdate: config.trait_softwareupdate || false,
            startstop: config.trait_startstop || false,
            statusreport: config.trait_statusreport || false,
            temperaturecontrol: config.trait_temperaturecontrol || false,
            temperaturesetting: config.trait_temperaturesetting || false,
            timer: config.trait_timer || false,
            toggles: config.trait_toggles || false,
            transportcontrol: config.trait_transportcontrol || false,
            volume: config.trait_volume || false,
        };
        if (this.device_type !== 'SCENE') {
            this.trait.scene = false;
        }
        // Sets required traits
        switch (this.device_type) {
            case "AC_UNIT": // Air conditioning unit
                this.trait.temperaturesetting = true;
                break;
            case "AIRCOOLER": // Air cooler
                this.trait.temperaturesetting = true;
                break;
            case "AIRFRESHENER": // Air freshener
                this.trait.onoff = true;
                break;
            case "AIRPURIFIER": // Air purifier
                this.trait.onoff = true;
                break;
            case "AUDIO_VIDEO_RECEIVER": // Audio-Video receiver
                this.trait.inputselector = true;
                this.trait.onoff = true;
                this.trait.volume = true;
                break;
            case "AWNING": // Awning
                this.trait.openclose = true;
                break;
            case "BATHTUB": // Bathtub
                this.trait.fill = true;
                this.trait.temperaturecontrol = true;
                this.trait.startstop = true;
                break;
            case "BED": // Bed
                this.trait.modes = true;
                break;
            case "BLENDER": // Blender
                this.trait.onoff = true;
                break;
            case "BLINDS": // Blinds
                this.trait.openclose = true;
                break;
            case "BOILER": // Boiler
                this.trait.onoff = true;
                break;
            case "CAMERA": // Camera
                this.trait.camerastream = true;
                break;
            case "CARBON_MONOXIDE_DETECTOR": // Carbon monoxide detector
                this.trait.sensorstate = true;
                break;
            case "CHARGER": // Charger
                this.trait.energystorage = true;
                break;
            case "CLOSET": // Closet
                this.trait.openclose = true;
                break;
            case "COFFEE_MAKER": // Coffee Maker
                this.trait.onoff = true;
                break;
            case "COOKTOP": // Cooktop
                this.trait.onoff = true;
                break;
            case "CURTAIN": // Curtain
                this.trait.openclose = true;
                break;
            case "DEHUMIDIFIER": // Dehumidifier
                this.trait.onoff = true;
                break;
            case "DEHYDRATOR": // Dehydrator
                this.trait.onoff = true;
                break;
            case "DISHWASHER": // Dishwasher
                this.trait.startstop = true;
                break;
            case "DOOR": // Door
                this.trait.openclose = true;
                break;
            case "DOORBELL": // Doorbell
                break;
            case "DRAWER": // Drawer
                this.trait.openclose = true;
                break;
            case "DRYER": // Dryer
                this.trait.startstop = true;
                break;
            case "FAN": // Fan
                this.trait.onoff = true;
                break;
            case "FAUCET": // Faucet
                break;
            case "FIREPLACE": // Fireplace
                break;
            case "FREEZER": // Freezer
                this.trait.temperaturecontrol = true;
                break;
            case "FRYER": // Fryer
                this.trait.onoff = true;
                break;
            case "GAME_CONSOLE": // Game console
                this.trait.appselector = true;
                this.trait.mediastate = true;
                this.trait.onoff = true;
                this.trait.transportcontrol = true;
                break;
            case "GARAGE": // Garage
                this.trait.openclose = true;
                break;
            case "GATE": // Gate
                this.trait.openclose = true;
                break;
            case "GRILL": // Grill
                this.trait.startstop = true;
                break;
            case "HEATER": // Heater
                this.trait.temperaturesetting = true;
                break;
            case "HOOD": // Hood
                this.trait.onoff = true;
                break;
            case "HUMIDIFIER": // Humidifier
                this.trait.onoff = true;
                break;
            case "KETTLE": // Kettle
                this.trait.onoff = true;
                break;
            case "LIGHT": // Light
                this.trait.onoff = true;
                break;
            case "LOCK": // Lock
                this.trait.lockunlock = true;
                break;
            case "MICROWAVE": // Microwave
                this.trait.startstop = true;
                break;
            case "MOP": // Mop
                this.trait.startstop = true;
                break;
            case "MOWER": // Mower
                this.trait.startstop = true;
                break;
            case "MULTICOOKER": // Multicooker
                this.trait.onoff = true;
                break;
            case "NETWORK": // Network
                this.trait.networkcontrol = true;
                break;
            case "OUTLET": // Outlet
                this.trait.onoff = true;
                break;
            case "OVEN": // Oven
                this.trait.onoff = true;
                break;
            case "PERGOLA": // Pergola
                this.trait.openclose = true;
                break;
            case "PETFEEDER": // Pet feeder
                this.trait.dispense = true;
                break;
            case "PRESSURECOOKER": // Pressure cooker
                this.trait.onoff = true;
                break;
            case "PUMP": // Pump
                this.trait.onoff = true;
                break;
            case "RADIATOR": // Radiator
                this.trait.onoff = true;
                break;
            case "REFRIGERATOR": // Refrigerator
                this.trait.temperaturecontrol = true;
                break;
            case "REMOTECONTROL": // Remote control
                this.trait.inputselector = true;
                this.trait.mediastate = true;
                this.trait.onoff = true;
                this.trait.transportcontrol = true;
                this.trait.volume = true;
                break;
            case "ROUTER": // Router
                this.trait.networkcontrol = true;
                break;
            case "SCENE": // Scene
                this.trait.scene = true;
                this.trait = {
                    scene: config.trait_scene || false
                };
                break;
            case "SECURITYSYSTEM": // Security system
                this.trait.armdisarm = true;
                break;
            case "SENSOR": // Sensor
                break;
            case "SETTOP": // Settop
                this.trait.appselector = true;
                this.trait.mediastate = true;
                this.trait.channel = true;
                this.trait.transportcontrol = true;
                break;
            case "SHOWER": // Shower
                this.trait.openclose = true;
                break;
            case "SHUTTER": // Shutter
                this.trait.openclose = true;
                break;
            case "SMOKE_DETECTOR": // Smoke detector
                this.trait.sensorstate = true;
                break;
            case "SOUNDBAR": // Soundbar
                this.trait.volume = true;
                break;
            case "SOUSVIDE": // Sousvide
                this.trait.onoff = true;
                break;
            case "SPEAKER": // Speaker
                this.trait.volume = true;
                break;
            case "SPRINKLER": // Sprinkler
                this.trait.startstop = true;
                break;
            case "STANDMIXER": // Stand mixer
                this.trait.onoff = true;
                break;
            case "STREAMING_BOX": // Streaming box
                this.trait.appselector = true;
                this.trait.mediastate = true;
                this.trait.transportcontrol = true;
                this.trait.volume = true;
                break;
            case "STREAMING_SOUNDBAR": // Streaming soundbar
                this.trait.appselector = true;
                this.trait.mediastate = true;
                this.trait.transportcontrol = true;
                this.trait.volume = true;
                break;
            case "STREAMING_STICK": // Streaming stick
                this.trait.appselector = true;
                this.trait.mediastate = true;
                this.trait.transportcontrol = true;
                this.trait.volume = true;
                break;
            case "SWITCH": // Switch
                this.trait.onoff = true;
                break;
            case "THERMOSTAT": // Thermostat
                this.trait.temperaturesetting = true;
                break;
            case "TV": // Television
                this.trait.appselector = true;
                this.trait.mediastate = true;
                this.trait.onoff = true;
                this.trait.transportcontrol = true;
                this.trait.volume = true;
                break;
            case "VACUUM": // Vacuum
                this.trait.startstop = true;
                break;
            case "VALVE": // Valve
                this.trait.openclose = true;
                break;
            case "WASHER": // Washer
                this.trait.startstop = true;
                break;
            case "WATERHEATER": // Water heater
                this.trait.onoff = true;
                break;
            case "WATERPURIFIER": // Water purifier
                break;
            case "WATERSOFTENER": // Water softener
                break;
            case "WINDOW": // Window
                this.trait.openclose = true;
                break;
            case "YOGURTMAKER": // Yogurt maker
                this.trait.onoff = true;
                break;
        }
        this.topicOut = config.topic;
        this.passthru = config.passthru;
        this.topic_filter = config.topic_filter || false;
        this.persistent_state = config.persistent_state || false;
        this.room_hint = config.room_hint;
        // AppSelector
        this.appselector_file = config.appselector_file;
        this.appselector_type = config.appselector_type || 'str';
        this.available_applications = [];
        // ArmDisarm
        this.available_arm_levels_file = config.available_arm_levels_file;
        this.available_arm_levels_type = config.available_arm_levels_type || 'str';
        this.arm_levels_ordered = config.arm_levels_ordered || false;
        this.available_arm_levels = [];
        // Brightness
        this.command_only_brightness = config.command_only_brightness;
        // CameraStream
        this.need_auth_token = true == config.need_auth_token;
        this.auth_token = (config.auth_token || '').trim();
        this.hls = (config.hls || '').trim();
        this.hls_app_id = (config.hls_app_id || '').trim();
        this.dash = (config.dash || '').trim();
        this.dash_app_id = (config.dash_app_id || '').trim();
        this.smooth_stream = (config.smooth_stream || '').trim();
        this.smooth_stream_app_id = (config.smooth_stream_app_id || '').trim();
        this.progressive_mp4 = (config.progressive_mp4 || '').trim();
        this.progressive_mp4_app_id = (config.progressive_mp4_app_id || '').trim();
        this.webrtc = (config.webrtc || '').trim();
        this.webrtc_offer = (config.webrtc_offer || '').trim();
        this.webrtc_ice_servers = (config.webrtc_ice_servers || '').trim();
        this.camera_stream_supported_protocols = [];
        if (this.hls) {
            this.camera_stream_supported_protocols.push('hls');
        }
        if (this.dash) {
            this.camera_stream_supported_protocols.push('dash');
        }
        if (this.smooth_stream) {
            this.camera_stream_supported_protocols.push('smooth_stream');
        }
        if (this.progressive_mp4) {
            this.camera_stream_supported_protocols.push('progressive_mp4');
        }
        if (this.webrtc) {
            this.camera_stream_supported_protocols.push('webrtc');
        }
        // Channel
        this.channel_file = config.channel_file;
        this.channel_type = config.channel_type || 'str';
        this.available_channels = [];
        this.last_channel_index = -1;
        this.current_channel_index = -1;
        // ColorSetting
        this.command_only_colorsetting = config.command_only_colorsetting;
        this.color_model = config.color_model || 'temp';
        this.temperature_min_k = parseInt(config.temperature_min_k) || 2000;
        this.temperature_max_k = parseInt(config.temperature_max_k) || 9000;
        // Cook
        this.supported_cooking_modes = config.supported_cooking_modes;
        this.food_presets_file = config.food_presets_file;
        this.food_presets_type = config.food_presets_type || 'str';
        this.food_presets = [];
        // Dispense
        this.supported_dispense_items_file = config.supported_dispense_items_file;
        this.supported_dispense_items_type = config.supported_dispense_items_type || 'str';
        this.supported_dispense_items = [];
        this.supported_dispense_presets_file = config.supported_dispense_presets_file;
        this.supported_dispense_presets_type = config.supported_dispense_presets_type || 'str';
        this.supported_dispense_presets = [];
        // Dock
        // EnergyStorage
        this.energy_storage_distance_unit_for_ux = config.energy_storage_distance_unit_for_ux;
        this.query_only_energy_storage = config.query_only_energy_storage;
        this.is_rechargeable = config.is_rechargeable;
        // FanSpeed
        this.reversible = config.reversible;
        this.command_only_fanspeed = config.command_only_fanspeed;
        this.supports_fan_speed_percent = config.supports_fan_speed_percent;
        this.available_fan_speeds_file = config.available_fan_speeds_file;
        this.available_fan_speeds_type = config.available_fan_speeds_type || 'str';
        this.fan_speeds_ordered = config.fan_speeds_ordered || false;
        this.available_fan_speeds = [];
        // Fill
        this.available_fill_levels_file = config.available_fill_levels_file;
        this.available_fill_levels_type = config.available_fill_levels_type || 'str';
        this.available_fill_levels = [];
        this.ordered_fill_levels = config.ordered_fill_levels;
        this.supports_fill_percent = config.supports_fill_percent;
        // HumiditySetting
        this.min_percent = parseInt(config.min_percent) || 0;
        this.max_percent = parseInt(config.max_percent) || 100;
        this.command_only_humiditysetting = config.command_query_humiditysetting === 'command';
        this.query_only_humiditysetting = config.command_query_humiditysetting === 'query';
        // InputSelector
        this.inputselector_file = config.inputselector_file;
        this.inputselector_type = config.inputselector_type || 'str';
        this.available_inputs = [];
        this.command_only_input_selector = config.command_only_input_selector;
        this.ordered_inputs = config.ordered_inputs;
        this.current_input_index = -1;
        // LightEffects
        this.default_sleep_duration = parseInt(config.default_sleep_duration) || 1800;
        this.default_wake_duration = parseInt(config.default_wake_duration) || 1800;
        this.supported_effects = config.supported_effects;
        // Locator
        // LockUnlock
        // MediaState
        this.support_activity_state = config.support_activity_state;
        this.support_playback_state = config.support_playback_state;
        // Modes
        this.modes_file = config.modes_file;
        this.modes_type = config.modes_type || 'str';
        this.available_modes = [];
        this.command_only_modes = config.command_query_modes === 'command';
        this.query_only_modes = config.command_query_modes === 'query';
        // NetworkControl
        this.supports_enabling_guest_network = config.supports_enabling_guest_network;
        this.supports_disabling_guest_network = config.supports_disabling_guest_network;
        this.supports_getting_guest_network_password = config.supports_getting_guest_network_password;
        this.network_profiles = config.network_profiles;
        this.supports_enabling_network_profile = config.supports_enabling_network_profile;
        this.supports_disabling_network_profile = config.supports_disabling_network_profile;
        this.supports_network_download_speedtest = config.supports_network_download_speedtest;
        this.supports_network_upload_speedtest = config.supports_network_upload_speedtest;
        this.guest_network_password = '';
        // ObjectDetection
        // OccupancySensing
        this.occupancy_sensing_pir = config.occupancy_sensing_pir || false;
        this.occupied_to_unoccupied_delay_sec_pir = parseInt(config.occupied_to_unoccupied_delay_sec_pir) || 0;
        this.unoccupied_to_occupied_delay_sec_pir = parseInt(config.unoccupied_to_occupied_delay_sec_pir) || 2;
        this.unoccupied_to_occupied_event_threshold_pir = parseInt(config.unoccupied_to_occupied_event_threshold_pir) || 2;
        this.occupancy_sensing_ultrasonic = config.occupancy_sensing_ultrasonic || false;
        this.occupied_to_unoccupied_delay_sec_ultrasonic = parseInt(config.occupied_to_unoccupied_delay_sec_ultrasonic) || 0;
        this.unoccupied_to_occupied_delay_sec_ultrasonic = parseInt(config.unoccupied_to_occupied_delay_sec_ultrasonic) || 2;
        this.unoccupied_to_occupied_event_threshold_ultrasonic = parseInt(config.unoccupied_to_occupied_event_threshold_ultrasonic) || 2;
        this.occupancy_sensing_physical_contact = config.occupancy_sensing_physical_contact || false;
        this.occupied_to_unoccupied_delay_sec_physical_contact = parseInt(config.occupied_to_unoccupied_delay_sec_physical_contact) || 0;
        this.unoccupied_to_occupied_delay_sec_physical_contact = parseInt(config.unoccupied_to_occupied_delay_sec_physical_contact) || 2;
        this.unoccupied_to_occupied_event_threshold_physical_contact = parseInt(config.unoccupied_to_occupied_event_threshold_physical_contact) || 2;
        // OnOff
        this.command_only_onoff = config.command_query_onoff === 'command';
        this.query_only_onoff = config.command_query_onoff === 'query';
        // OpenClose
        this.discrete_only_openclose = config.discrete_only_openclose;
        this.open_direction = config.open_direction;
        this.command_only_openclose = config.command_query_openclose === 'command';
        this.query_only_openclose = config.command_query_openclose === 'query';
        // Reboot
        // Rotation
        this.supports_degrees = config.supports_degrees;
        this.supports_percent = config.supports_percent;
        this.rotation_degrees_min = parseInt(config.rotation_degrees_min) || 0;
        this.rotation_degrees_max = parseInt(config.rotation_degrees_max) || 360;
        this.supports_continuous_rotation = config.supports_continuous_rotation;
        this.command_only_rotation = config.command_only_rotation;
        // RunCycle
        // Scene
        this.scene_reversible = config.scene_reversible;
        // SensorState
        this.sensor_states_supported = config.sensor_states_supported;
        // SoftwareUpdate
        // StartStop
        this.pausable = config.pausable;
        this.available_zones = config.available_zones;
        // StatusReport
        // TemperatireControl
        this.tc_min_threshold_celsius = parseInt(config.tc_min_threshold_celsius) || 0;
        this.tc_max_threshold_celsius = parseInt(config.tc_max_threshold_celsius) || 40;
        this.tc_temperature_step_celsius = parseInt(config.tc_temperature_step_celsius) || 1;
        this.tc_temperature_unit_for_ux = config.tc_temperature_unit_for_ux;
        this.command_only_temperaturecontrol = config.tc_command_query_temperaturecontrol === 'command';
        this.query_only_temperaturecontrol = config.tc_command_query_temperaturecontrol === 'query';
        // TemperatureSetting
        this.available_thermostat_modes = config.available_thermostat_modes;
        this.min_threshold_celsius = parseInt(config.min_threshold_celsius) || 10;
        this.max_threshold_celsius = parseInt(config.max_threshold_celsius) || 32;
        this.thermostat_temperature_setpoint = this.min_threshold_celsius;
        this.thermostat_temperature_setpoint_low = this.min_threshold_celsius;
        this.thermostat_temperature_setpoint_hight = this.max_threshold_celsius;
        this.thermostat_temperature_unit = config.thermostat_temperature_unit || "C";
        this.buffer_range_celsius = parseInt(config.buffer_range_celsius) || 2;
        this.command_only_temperaturesetting = config.command_query_temperaturesetting === 'command';
        this.query_only_temperaturesetting = config.command_query_temperaturesetting === 'query';
        this.target_temp_reached_estimate_unix_timestamp_sec = 360;
        this.thermostat_humidity_ambient = 60;
        // Timer
        this.max_timer_limit_sec = parseInt(config.max_timer_limit_sec) || 86400;
        this.command_only_timer = config.command_only_timer;
        this.timer_end_timestamp = -1;
        // Toggles
        this.toggles_file = config.toggles_file;
        this.toggles_type = config.toggles_type || 'str';
        this.available_toggles = [];
        this.command_only_toggles = config.command_query_toggles === 'command';
        this.query_only_toggles = config.command_query_toggles === 'query';
        // TransportControl
        this.supported_commands = config.supported_commands;
        // Volume
        this.volume_max_level = parseInt(config.volume_max_level) || 100;
        this.volume_can_mute_and_unmute = config.volume_can_mute_and_unmute;
        this.volume_default_percentage = parseInt(config.volume_default_percentage) || 40;
        this.level_step_size = parseInt(config.level_step_size) || 1;
        this.command_only_volume = config.command_only_volume;
        // Secondary User Verification
        this.ct_appselector = config.ct_appselector || '';
        this.pin_appselector = config.pin_appselector || '';
        this.ct_armdisarm = config.ct_armdisarm || '';
        this.pin_armdisarm = config.pin_armdisarm || '';
        this.ct_brightness = config.ct_brightness || '';
        this.pin_brightness = config.pin_brightness || '';
        this.ct_camerastream = config.ct_camerastream || '';
        this.pin_camerastream = config.pin_camerastream || '';
        this.ct_channel = config.ct_channel || '';
        this.pin_channel = config.pin_channel || '';
        this.ct_colorsetting = config.ct_colorsetting || '';
        this.pin_colorsetting = config.pin_colorsetting || '';
        this.ct_cook = config.ct_cook || '';
        this.pin_cook = config.pin_cook || '';
        this.ct_dispense = config.ct_dispense || '';
        this.pin_dispense = config.pin_dispense || '';
        this.ct_dock = config.ct_dock || '';
        this.pin_dock = config.pin_dock || '';
        this.ct_energystorage = config.ct_energystorage || '';
        this.pin_energystorage = config.pin_energystorage || '';
        this.ct_fanspeed = config.ct_fanspeed || '';
        this.pin_fanspeed = config.pin_fanspeed || '';
        this.ct_fill = config.ct_fill || '';
        this.pin_fill = config.pin_fill || '';
        this.ct_humiditysetting = config.ct_humiditysetting || '';
        this.pin_humiditysetting = config.pin_humiditysetting || '';
        this.ct_inputselector = config.ct_inputselector || '';
        this.pin_inputselector = config.pin_inputselector || '';
        this.ct_lighteffects = config.ct_lighteffects || '';
        this.pin_lighteffects = config.pin_lighteffects || '';
        this.ct_locator = config.ct_locator || '';
        this.pin_locator = config.pin_locator || '';
        this.ct_lockunlock = config.ct_lockunlock || '';
        this.pin_lockunlock = config.pin_lockunlock || '';
        this.ct_mediastate = config.ct_mediastate || '';
        this.pin_mediastate = config.pin_mediastate || '';
        this.ct_modes = config.ct_modes || '';
        this.pin_modes = config.pin_modes || '';
        this.ct_networkcontrol = config.ct_networkcontrol || '';
        this.pin_networkcontrol = config.pin_networkcontrol || '';
        this.ct_objectdetection = config.ct_objectdetection || '';
        this.pin_objectdetection = config.pin_objectdetection || '';
        this.ct_onoff = config.ct_onoff || '';
        this.pin_onoff = config.pin_onoff || '';
        this.ct_openclose = config.ct_openclose || '';
        this.pin_openclose = config.pin_openclose || '';
        this.ct_reboot = config.ct_reboot || '';
        this.pin_reboot = config.pin_reboot || '';
        this.ct_rotation = config.ct_rotation || '';
        this.pin_rotation = config.pin_rotation || '';
        this.ct_runcycle = config.ct_runcycle || '';
        this.pin_runcycle = config.pin_runcycle || '';
        this.ct_scene = config.ct_scene || '';
        this.pin_scene = config.pin_scene || '';
        this.ct_sensorstate = config.ct_sensorstate || '';
        this.pin_sensorstate = config.pin_sensorstate || '';
        this.ct_softwareupdate = config.ct_softwareupdate || '';
        this.pin_softwareupdate = config.pin_softwareupdate || '';
        this.ct_startstop = config.ct_startstop || '';
        this.pin_startstop = config.pin_startstop || '';
        this.ct_statusreport = config.ct_statusreport || '';
        this.pin_statusreport = config.pin_statusreport || '';
        this.ct_temperaturecontrol = config.ct_temperaturecontrol || '';
        this.pin_temperaturecontrol = config.pin_temperaturecontrol || '';
        this.ct_temperaturesetting = config.ct_temperaturesetting || '';
        this.pin_temperaturesetting = config.pin_temperaturesetting || '';
        this.ct_timer = config.ct_timer || '';
        this.pin_timer = config.pin_timer || '';
        this.ct_toggles = config.ct_toggles || '';
        this.pin_toggles = config.pin_toggles || '';
        this.ct_transportcontrol = config.ct_transportcontrol || '';
        this.pin_transportcontrol = config.pin_transportcontrol || '';
        this.ct_volume = config.ct_volume || '';
        this.pin_volume = config.pin_volume || '';
        if (this.trait.appselector) {
            if (this.appselector_type !== 'json') {
                this.available_applications = this.to_available_applications(this.loadJson('Applications', this.appselector_file.replace(/<id>/g, this.id), []));
            }
            else {
                this.available_applications = this.to_available_applications(this.parseJson('Applications', this.appselector_file, []));
            }
        }
        if (this.trait.armdisarm) {
            if (this.available_arm_levels_type !== 'json') {
                this.available_arm_levels = this.to_available_arm_levels(this.loadJson('Available arm levels', this.available_arm_levels_file.replace(/<id>/g, this.id), []));
            }
            else {
                this.available_arm_levels = this.to_available_arm_levels(this.parseJson('Available arm levels', this.available_arm_levels_file, []));
            }
        }
        if (this.trait.channel) {
            if (this.channel_type !== 'json') {
                this.available_channels = this.to_available_channels(this.loadJson('Channels', this.channel_file.replace(/<id>/g, this.id), []));
            }
            else {
                this.available_channels = this.to_available_channels(this.parseJson('Channels', this.channel_file, []));
            }
        }
        if (this.trait.cook) {
            if (this.food_presets_type !== 'json') {
                this.food_presets = this.to_food_presets(this.loadJson('Food presets', this.food_presets_file.replace(/<id>/g, this.id), []));
            }
            else {
                this.food_presets = this.to_food_presets(this.parseJson('Food presets', this.food_presets_file, []));
            }
        }
        if (this.trait.dispense) {
            if (this.supported_dispense_items_type !== 'json') {
                this.supported_dispense_items = this.to_supported_dispense_items(this.loadJson('Supported dispense', this.supported_dispense_items_file.replace(/<id>/g, this.id), []));
            }
            else {
                this.supported_dispense_items = this.to_supported_dispense_items(this.parseJson('Supported dispense', this.supported_dispense_items_file, []));
            }
            if (this.supported_dispense_presets_type !== 'json') {
                this.supported_dispense_presets = this.to_supported_dispense_presets(this.loadJson('Supported dispense presets', this.supported_dispense_presets_file.replace(/<id>/g, this.id), []));
            }
            else {
                this.supported_dispense_presets = this.to_supported_dispense_presets(this.parseJson('Supported dispense presets', this.supported_dispense_presets_file, []));
            }
        }
        if (this.trait.fanspeed) {
            if (this.available_fan_speeds_type !== 'json') {
                this.available_fan_speeds = this.to_available_fan_speeds(this.loadJson('Fan speeds', this.available_fan_speeds_file.replace(/<id>/g, this.id), []));
            }
            else {
                this.available_fan_speeds = this.to_available_fan_speeds(this.parseJson('Fan speeds', this.available_fan_speeds_file, []));
            }
        }
        if (this.trait.fill) {
            if (this.available_fill_levels_type !== 'json') {
                this.available_fill_levels = this.to_available_fill_levels(this.loadJson('Available fill levels', this.available_fill_levels_file.replace(/<id>/g, this.id), []));
            }
            else {
                this.available_fill_levels = this.to_available_fill_levels(this.parseJson('Available fill levels', this.available_fill_levels_file, []));
            }
        }
        if (this.trait.inputselector) {
            if (this.inputselector_type !== 'json') {
                this.available_inputs = this.to_available_inputs(this.loadJson('Available inputs', this.inputselector_file.replace(/<id>/g, this.id), []));
            }
            else {
                this.available_inputs = this.to_available_inputs(this.parseJson('Available inputs', this.inputselector_file, []));
            }
        }
        if (this.trait.modes) {
            if (this.modes_type !== 'json') {
                this.available_modes = this.to_available_modes(this.loadJson('Modes', this.modes_file.replace(/<id>/g, this.id), []));
            }
            else {
                this.available_modes = this.to_available_modes(this.parseJson('Modes', this.modes_file, []));
            }
        }
        if (this.trait.toggles) {
            if (this.toggles_type !== 'json') {
                this.available_toggles = this.to_available_toggles(this.loadJson('Toggles', this.toggles_file.replace(/<id>/g, this.id), []));
            }
            else {
                this.available_toggles = this.to_available_toggles(this.parseJson('Toggles', this.toggles_file, []));
            }
        }
        this.updateStateTypesForTraits();
        const default_name = SmartHome_js_1.RED._('device.device_type.' + this.device_type);
        const default_name_type = default_name.replace(/[_ ()/]+/g, '-').toLowerCase();
        // Google uses first nickname as the "real" name of the device. Therefore, report device name as the first nickname
        const nicknames = this.nicknames ? [this.name].concat(this.nicknames.split(',')) : [];
        this.states = {
            online: config.online != false
        };
        this.device = {
            id: this.id,
            states: this.states,
            properties: {
                type: 'action.devices.types.' + this.device_type,
                traits: this.getTraits(),
                name: {
                    defaultNames: ["Node-RED " + default_name],
                    name: this.name,
                    nicknames: nicknames,
                },
                roomHint: this.room_hint,
                willReportState: true,
                notificationSupportedByAgent: this.trait.objectdetection || this.trait.runcycle || this.trait.sensorstate
                    || this.trait.lockunlock || this.trait.networkcontrol || this.trait.openclose,
                attributes: {},
                deviceInfo: {
                    manufacturer: 'Node-RED',
                    model: 'nr-device-' + default_name_type + '-v1',
                    swVersion: '1.0',
                    hwVersion: '1.0'
                },
                otherDeviceIds: [{ deviceId: this.id }],
                customData: this.clientConn.app.getCustomData()
            }
        };
        this.updateAttributesForTraits(this.device);
        this.initializeStates(this.device);
        this._debug(".constructor: device = " + JSON.stringify(this.device));
        // GoogleSmartHomeNode -> (client.registerDevice -> DeviceNode.registerDevice), app.registerDevice
        this.clientConn.register(this, 'device');
        this.updateStatusIcon(false);
        this.on('input', this.onInput);
        this.on('close', this.onClose);
        this.clientConn.app.ScheduleRequestSync();
    }
    _debug(msg) {
        msg = 'google-smarthome:DeviceNode[' + this.name + '] ' + msg;
        if (this.clientConn && typeof this.clientConn._debug === 'function') {
            this.clientConn._debug(msg);
        }
        else {
            this.debug(msg);
        }
    }
    updateStateTypesForTraits() {
        let me = this;
        let state_types = me.state_types;
        state_types['online'] = Formats_js_1.Formats.BOOL + Formats_js_1.Formats.MANDATORY;
        if (me.trait.appselector) {
            let values = me.available_applications.map(application => application.key);
            if (values.length > 0) {
                state_types['currentApplication'] = {
                    type: Formats_js_1.Formats.STRING + Formats_js_1.Formats.MANDATORY,
                    values: values,
                    defaultValue: values[0],
                };
            }
        }
        if (me.trait.armdisarm) {
            let values = me.available_arm_levels.map(al => al.level_name);
            if (values.length > 0) {
                state_types['isArmed'] = Formats_js_1.Formats.BOOL + Formats_js_1.Formats.MANDATORY;
                state_types['currentArmLevel'] = {
                    type: Formats_js_1.Formats.STRING + Formats_js_1.Formats.MANDATORY,
                    values: values,
                    defaultValue: values[0],
                };
                state_types['exitAllowance'] = Formats_js_1.Formats.INT;
            }
        }
        if (me.trait.brightness && !me.command_only_brightness) {
            state_types['brightness'] = {
                type: Formats_js_1.Formats.INT,
                min: 0,
                max: 100,
            };
        }
        if (me.trait.colorsetting) {
            if (!me.command_only_colorsetting) {
                if ((me.color_model === "rgb") || (me.color_model === 'rgb_temp')) {
                    state_types['color'] = {
                        type: Formats_js_1.Formats.OBJECT + Formats_js_1.Formats.DELETE_MISSING,
                        attributes: {
                            spectrumRgb: {
                                type: Formats_js_1.Formats.INT + Formats_js_1.Formats.MANDATORY,
                                exclusiveStates: ['temperatureK', 'spectrumHsv']
                            },
                        }
                    };
                }
                else if ((me.color_model === "hsv") || (me.color_model === "hsv_temp")) {
                    state_types['color'] = {
                        type: Formats_js_1.Formats.OBJECT,
                        attributes: {
                            spectrumHsv: {
                                type: Formats_js_1.Formats.OBJECT,
                                attributes: {
                                    hue: {
                                        type: Formats_js_1.Formats.FLOAT + Formats_js_1.Formats.MANDATORY, // float, representing hue as positive degrees in the range of [0.0, 360.0)
                                        min: 0.0,
                                        max: 360.0,
                                    },
                                    saturation: {
                                        type: Formats_js_1.Formats.FLOAT + Formats_js_1.Formats.MANDATORY, // float, representing saturation as a percentage in the range [0.0, 1.0]
                                        min: 0.0,
                                        max: 1.0,
                                    },
                                    value: {
                                        type: Formats_js_1.Formats.FLOAT + Formats_js_1.Formats.MANDATORY, // float, representing value as a percentage in the range [0.0, 1.0]
                                        min: 0.0,
                                        max: 1.0,
                                    },
                                },
                                exclusiveStates: ['temperatureK', 'spectrumRgb']
                            },
                        }
                    };
                }
                else {
                    state_types['color'] = {
                        type: Formats_js_1.Formats.OBJECT,
                        attributes: {}
                    };
                }
                if (me.color_model !== "rgb" && me.color_model !== "hsv") {
                    state_types.color.attributes.temperatureK = {
                        type: Formats_js_1.Formats.INT + Formats_js_1.Formats.MANDATORY,
                        min: me.temperature_min_k,
                        max: me.temperature_max_k,
                        exclusiveStates: ['spectrumRgb', 'spectrumHsv']
                    };
                }
            }
        }
        if (me.trait.cook) {
            let cooking_mode_values = ['NONE'];
            cooking_mode_values.push(...me.supported_cooking_modes);
            state_types['currentCookingMode'] = {
                type: Formats_js_1.Formats.STRING + Formats_js_1.Formats.MANDATORY,
                values: cooking_mode_values,
            };
            let food_preset_values = ['NONE'];
            food_preset_values.push(...me.food_presets.map(food_preset => food_preset.food_preset_name));
            state_types['currentFoodPreset'] = {
                type: Formats_js_1.Formats.STRING + Formats_js_1.Formats.MANDATORY,
                values: food_preset_values,
            };
            state_types['currentFoodQuantity'] = Formats_js_1.Formats.FLOAT;
            state_types['currentFoodUnit'] = {
                type: Formats_js_1.Formats.STRING,
                values: COOK_SUPPORTED_UNITS,
            };
        }
        if (me.trait.dispense) {
            let dispense_items_values = me.supported_dispense_items.map(item => item.item_name);
            state_types['dispenseItems'] = {
                type: Formats_js_1.Formats.OBJECT + Formats_js_1.Formats.ARRAY,
                attributes: {
                    itemName: {
                        type: Formats_js_1.Formats.STRING,
                        value: dispense_items_values,
                    },
                    amountRemaining: {
                        type: Formats_js_1.Formats.OBJECT,
                        attributes: {
                            amount: Formats_js_1.Formats.FLOAT,
                            unit: {
                                type: Formats_js_1.Formats.STRING,
                                values: DISPENSE_SUPPORTED_UNITS,
                            },
                        }
                    },
                    amountLastDispensed: {
                        type: Formats_js_1.Formats.OBJECT,
                        attributes: {
                            amount: Formats_js_1.Formats.FLOAT,
                            unit: {
                                type: Formats_js_1.Formats.STRING,
                                values: DISPENSE_SUPPORTED_UNITS,
                            },
                        }
                    },
                    isCurrentlyDispensing: Formats_js_1.Formats.BOOL,
                },
                keyId: 'itemName',
                removeIfNoData: true,
            };
        }
        if (me.trait.dock) {
            state_types['isDocked'] = Formats_js_1.Formats.BOOL;
        }
        if (me.trait.energystorage) {
            state_types['descriptiveCapacityRemaining'] = Formats_js_1.Formats.STRING + Formats_js_1.Formats.MANDATORY;
            state_types['capacityRemaining'] = {
                type: Formats_js_1.Formats.OBJECT + Formats_js_1.Formats.ARRAY,
                attributes: {
                    rawValue: Formats_js_1.Formats.INT + Formats_js_1.Formats.MANDATORY,
                    unit: {
                        type: Formats_js_1.Formats.STRING + Formats_js_1.Formats.MANDATORY,
                        values: ENERGY_STORAGE_UNITS,
                        toUpperCase: true,
                        replaceAll: true,
                    },
                },
                keyId: "unit",
                addIfMissing: true,
                removeIfNoData: true,
                replaceAll: true,
                isValidKey: unit => ENERGY_STORAGE_UNITS.includes(unit)
            };
            if (me.is_rechargeable) {
                state_types['capacityUntilFull'] = {
                    type: Formats_js_1.Formats.OBJECT + Formats_js_1.Formats.ARRAY,
                    attributes: {
                        rawValue: Formats_js_1.Formats.INT + Formats_js_1.Formats.MANDATORY,
                        unit: {
                            type: Formats_js_1.Formats.STRING + Formats_js_1.Formats.MANDATORY,
                            values: ENERGY_STORAGE_UNITS,
                            toUpperCase: true,
                            replaceAll: true,
                        }
                    },
                    keyId: "unit",
                    addIfMissing: true,
                    removeIfNoData: true,
                    replaceAll: true,
                    isValidKey: unit => ENERGY_STORAGE_UNITS.includes(unit)
                };
                state_types['isCharging'] = Formats_js_1.Formats.BOOL;
            }
            state_types['isPluggedIn'] = Formats_js_1.Formats.BOOL;
        }
        if (me.trait.fanspeed) {
            if (!me.command_only_fanspeed) {
                if (me.supports_fan_speed_percent) {
                    state_types['currentFanSpeedPercent'] = Formats_js_1.Formats.INT + Formats_js_1.Formats.MANDATORY;
                }
                else {
                    state_types['currentFanSpeedPercent'] = Formats_js_1.Formats.INT;
                }
                if (me.available_fan_speeds.length > 0) {
                    let values = me.available_fan_speeds.map(fanspeed => fanspeed.speed_name);
                    state_types['currentFanSpeedSetting'] = {
                        type: Formats_js_1.Formats.STRING,
                        values: values,
                        defaultValue: values[0],
                    };
                }
            }
        }
        if (me.trait.fill) {
            state_types['isFilled'] = Formats_js_1.Formats.BOOL + Formats_js_1.Formats.MANDATORY;
            if (me.available_fill_levels.length > 0) {
                let values = me.available_fill_levels.map(fl => fl.level_name);
                state_types['currentFillLevel'] = {
                    type: Formats_js_1.Formats.STRING + Formats_js_1.Formats.MANDATORY,
                    values: values,
                    defaultValue: values[0],
                };
            }
            if (me.supports_fill_percent) {
                state_types['currentFillPercent'] = Formats_js_1.Formats.FLOAT + Formats_js_1.Formats.MANDATORY;
            }
            else {
                state_types['currentFillPercent'] = Formats_js_1.Formats.FLOAT;
            }
        }
        if (me.trait.humiditysetting) {
            if (!me.command_only_humiditysetting) {
                state_types['humiditySetpointPercent'] = Formats_js_1.Formats.INT;
                state_types['humidityAmbientPercent'] = Formats_js_1.Formats.INT;
            }
        }
        if (me.trait.inputselector) {
            if (!me.command_only_input_selector) {
                let values = me.available_inputs.map(input => input.key);
                if (values.length > 0) {
                    state_types['currentInput'] = {
                        type: Formats_js_1.Formats.STRING + Formats_js_1.Formats.MANDATORY,
                        values: values,
                    };
                }
            }
        }
        if (me.trait.lighteffects) {
            let light_effect_value = [''];
            light_effect_value.push(...me.supported_effects);
            if (light_effect_value.length > 0) {
                state_types['activeLightEffect'] = {
                    type: Formats_js_1.Formats.STRING + Formats_js_1.Formats.MANDATORY,
                    values: light_effect_value,
                };
                state_types['lightEffectEndUnixTimestampSec'] = Formats_js_1.Formats.INT;
            }
        }
        // Locator
        if (me.trait.lockunlock) {
            state_types['isLocked'] = Formats_js_1.Formats.BOOL;
            state_types['isJammed'] = Formats_js_1.Formats.BOOL;
        }
        if (me.trait.mediastate) {
            state_types['activityState'] = {
                type: Formats_js_1.Formats.STRING,
                values: ["INACTIVE", "STANDBY", "ACTIVE"],
                upperCase: true,
            };
            state_types['playbackState'] = {
                type: Formats_js_1.Formats.STRING,
                values: ["PAUSED", "PLAYING", "FAST_FORWARDING", "REWINDING", "BUFFERING", "STOPPED"],
                upperCase: true,
            };
        }
        if (me.trait.modes) {
            if (!me.command_only_modes) {
                let attributes = {};
                let ok = false;
                me.available_modes.forEach(function (mode) {
                    let values = mode.settings.map(setting => setting.setting_name);
                    if (values.length > 0) {
                        ok = true;
                        attributes[mode.name] = {
                            type: Formats_js_1.Formats.STRING + Formats_js_1.Formats.MANDATORY,
                            values: values,
                            defaultValue: values[0],
                        };
                    }
                });
                if (ok) {
                    state_types['currentModeSettings'] = {
                        type: Formats_js_1.Formats.OBJECT,
                        attributes: attributes,
                    };
                }
            }
        }
        if (me.trait.networkcontrol) {
            state_types['networkEnabled'] = Formats_js_1.Formats.BOOL;
            state_types['networkSettings'] = {
                type: Formats_js_1.Formats.OBJECT,
                attributes: {
                    ssid: Formats_js_1.Formats.STRING + Formats_js_1.Formats.MANDATORY
                }
            };
            state_types['guestNetworkEnabled'] = Formats_js_1.Formats.BOOL;
            state_types['guestNetworkSettings'] = {
                type: Formats_js_1.Formats.OBJECT,
                attributes: {
                    ssid: Formats_js_1.Formats.STRING + Formats_js_1.Formats.MANDATORY
                }
            };
            state_types['numConnectedDevices'] = Formats_js_1.Formats.INT;
            state_types['networkUsageMB'] = Formats_js_1.Formats.FLOAT;
            state_types['networkUsageLimitMB'] = Formats_js_1.Formats.FLOAT;
            state_types['networkUsageUnlimited'] = Formats_js_1.Formats.BOOL;
            state_types['lastNetworkDownloadSpeedTest'] = {
                type: Formats_js_1.Formats.OBJECT,
                attributes: {
                    downloadSpeedMbps: Formats_js_1.Formats.FLOAT,
                    unixTimestampSec: Formats_js_1.Formats.INT,
                    status: {
                        type: Formats_js_1.Formats.STRING,
                        values: ['SUCCESS', 'FAILURE'],
                    },
                }
            };
            state_types['lastNetworkUploadSpeedTest'] = {
                type: Formats_js_1.Formats.OBJECT,
                attributes: {
                    uploadSpeedMbps: Formats_js_1.Formats.FLOAT,
                    unixTimestampSec: Formats_js_1.Formats.INT,
                    status: {
                        type: Formats_js_1.Formats.STRING,
                        values: ['SUCCESS', 'FAILURE'],
                    },
                }
            };
            state_types['networkSpeedTestInProgress'] = Formats_js_1.Formats.BOOL;
        }
        // ObjectDetection
        // OccupancySensing
        if (me.trait.occupancysensing) {
            if (me.occupancy_sensing_pir || me.occupancy_sensing_ultrasonic || me.occupancy_sensing_physical_contact) {
                state_types['occupancy'] = {
                    type: Formats_js_1.Formats.STRING + Formats_js_1.Formats.MANDATORY,
                    values: ['OCCUPIED', 'UNOCCUPIED', 'UNKNOWN_OCCUPANCY_STATE'],
                    defaultValue: 'UNKNOWN_OCCUPANCY_STATE',
                    upperCase: true,
                };
            }
            else {
                me.trait.occupancysensing = false;
            }
        }
        if (me.trait.onoff) {
            if (!me.command_only_onoff) {
                state_types['on'] = Formats_js_1.Formats.BOOL;
            }
        }
        if (me.trait.openclose) {
            if (!me.command_only_openclose) {
                if (me.open_direction.length < 2) {
                    state_types['openPercent'] = Formats_js_1.Formats.FLOAT + Formats_js_1.Formats.MANDATORY;
                }
                else {
                    state_types['openState'] = {
                        type: Formats_js_1.Formats.OBJECT + Formats_js_1.Formats.ARRAY,
                        attributes: {
                            openPercent: Formats_js_1.Formats.FLOAT + Formats_js_1.Formats.MANDATORY,
                            openDirection: {
                                type: Formats_js_1.Formats.STRING + Formats_js_1.Formats.MANDATORY,
                                values: me.open_direction,
                                upperCase: true,
                            },
                        },
                        keyId: 'openDirection',
                        removeIfNoData: true,
                        replaceAll: false,
                        isValidKey: direction => me.open_direction.includes(direction.trim()) ? direction.trim() : undefined
                    };
                }
            }
        }
        // Reboot, no state
        if (me.trait.rotation) {
            if (!me.command_only_rotation) {
                if (me.supports_degrees) {
                    state_types['rotationDegrees'] = Formats_js_1.Formats.FLOAT;
                }
                if (me.supports_percent) {
                    state_types['rotationPercent'] = Formats_js_1.Formats.FLOAT;
                }
            }
        }
        if (me.trait.runcycle) {
            state_types['currentRunCycle'] = {
                type: Formats_js_1.Formats.OBJECT + Formats_js_1.Formats.ARRAY,
                attributes: {
                    currentCycle: Formats_js_1.Formats.STRING + Formats_js_1.Formats.MANDATORY,
                    nextCycle: Formats_js_1.Formats.STRING,
                    lang: {
                        type: Formats_js_1.Formats.STRING + Formats_js_1.Formats.MANDATORY,
                        defaultValue: me.lang,
                        values: LANGUAGES,
                    }
                },
                keyId: 'lang',
                addIfMissing: true,
                removeIfNoData: true,
                replaceAll: true,
            };
            state_types['currentTotalRemainingTime'] = Formats_js_1.Formats.INT + Formats_js_1.Formats.MANDATORY;
            state_types['currentCycleRemainingTime'] = Formats_js_1.Formats.INT + Formats_js_1.Formats.MANDATORY;
        }
        // Scene
        if (me.trait.sensorstate) {
            state_types['currentSensorStateData'] = {
                type: Formats_js_1.Formats.OBJECT + Formats_js_1.Formats.ARRAY,
                attributes: {
                    name: {
                        type: Formats_js_1.Formats.STRING + Formats_js_1.Formats.MANDATORY,
                        values: me.sensor_states_supported,
                    },
                    currentSensorState: Formats_js_1.Formats.STRING,
                    rawValue: Formats_js_1.Formats.FLOAT
                },
                keyId: 'name',
                addIfMissing: true,
                removeIfNoData: true,
                replaceAll: false,
                isValidKey: name => me.sensor_states_supported.includes(name.trim()) ? name.trim() : undefined
            };
        }
        if (me.trait.softwareupdate) {
            state_types['lastSoftwareUpdateUnixTimestampSec'] = Formats_js_1.Formats.INT + Formats_js_1.Formats.MANDATORY;
        }
        if (me.trait.startstop) {
            state_types['isRunning'] = Formats_js_1.Formats.BOOL + Formats_js_1.Formats.MANDATORY;
            state_types['isPaused'] = Formats_js_1.Formats.BOOL;
            state_types['activeZones'] = {
                type: Formats_js_1.Formats.STRING + Formats_js_1.Formats.ARRAY,
                values: me.available_zones,
                addIfMissing: true,
                removeIfNoData: true,
                replaceAll: true,
                isValidKey: zone => me.available_zones.includes(zone.trim()) ? zone.trim() : undefined
            };
        }
        if (me.trait.statusreport) {
            state_types['currentStatusReport'] = {
                type: Formats_js_1.Formats.OBJECT + Formats_js_1.Formats.ARRAY,
                attributes: {
                    blocking: Formats_js_1.Formats.BOOL,
                    deviceTarget: Formats_js_1.Formats.STRING,
                    priority: Formats_js_1.Formats.INT,
                    statusCode: Formats_js_1.Formats.STRING
                },
                keyId: ['deviceTarget', 'statusCode'],
                addIfMissing: true,
                removeIfNoData: true,
                replaceAll: true,
                isValidKey: nodeId => Object.keys(me.clientConn.getProperties([nodeId])).length > 0 ? nodeId : me.clientConn.getIdFromName(nodeId)
            };
        }
        if (me.trait.temperaturecontrol) {
            if (!me.command_only_temperaturecontrol) {
                if (me.query_only_temperaturecontrol) { // Required if queryOnlyTemperatureControl set to false
                    state_types['temperatureSetpointCelsius'] = Formats_js_1.Formats.FLOAT;
                }
                else {
                    state_types['temperatureSetpointCelsius'] = Formats_js_1.Formats.FLOAT + Formats_js_1.Formats.MANDATORY;
                }
                state_types['temperatureAmbientCelsius'] = Formats_js_1.Formats.FLOAT;
            }
        }
        if (me.trait.temperaturesetting) {
            if (!me.command_only_temperaturesetting) {
                state_types['activeThermostatMode'] = Formats_js_1.Formats.STRING;
                state_types['targetTempReachedEstimateUnixTimestampSec'] = Formats_js_1.Formats.INT;
                state_types['thermostatHumidityAmbient'] = Formats_js_1.Formats.FLOAT;
                state_types['thermostatMode'] = {
                    type: Formats_js_1.Formats.STRING + Formats_js_1.Formats.MANDATORY,
                    values: me.available_thermostat_modes,
                };
                state_types['thermostatTemperatureAmbient'] = Formats_js_1.Formats.FLOAT + Formats_js_1.Formats.MANDATORY;
                state_types['thermostatTemperatureSetpoint'] = {
                    type: Formats_js_1.Formats.FLOAT + Formats_js_1.Formats.MANDATORY,
                    exclusiveStates: ['thermostatTemperatureSetpointLow', 'thermostatTemperatureSetpointHigh'],
                };
                state_types['thermostatTemperatureSetpointHigh'] = {
                    type: Formats_js_1.Formats.FLOAT + Formats_js_1.Formats.MANDATORY,
                    exclusiveStates: ['thermostatTemperatureSetpoint'],
                };
                state_types['thermostatTemperatureSetpointLow'] = {
                    type: Formats_js_1.Formats.FLOAT + Formats_js_1.Formats.MANDATORY,
                    exclusiveStates: ['thermostatTemperatureSetpoint'],
                };
            }
        }
        if (me.trait.timer) {
            if (!me.command_only_timer) {
                state_types['timerRemainingSec'] = Formats_js_1.Formats.INT + Formats_js_1.Formats.MANDATORY;
                state_types['timerPaused'] = Formats_js_1.Formats.BOOL;
            }
        }
        if (me.trait.toggles) {
            if (!me.command_only_toggles) {
                let attributes = {};
                me.available_toggles.forEach(function (toggle) {
                    attributes[toggle.name] = Formats_js_1.Formats.BOOL + Formats_js_1.Formats.MANDATORY;
                });
                state_types['currentToggleSettings'] = {
                    type: Formats_js_1.Formats.OBJECT,
                    attributes: attributes,
                };
            }
        }
        // TransportControl
        if (me.trait.volume) {
            if (!me.command_only_volume) {
                state_types['currentVolume'] = Formats_js_1.Formats.INT + Formats_js_1.Formats.MANDATORY;
                if (me.volume_can_mute_and_unmute) {
                    state_types['isMuted'] = Formats_js_1.Formats.BOOL + Formats_js_1.Formats.MANDATORY;
                }
                else {
                    state_types['isMuted'] = Formats_js_1.Formats.BOOL;
                }
            }
        }
    }
    updateAttributesForTraits(device) {
        let me = this;
        let attributes = device.properties.attributes;
        if (me.trait.appselector) {
            attributes['availableApplications'] = me.available_applications;
        }
        if (me.trait.armdisarm) {
            attributes['availableArmLevels'] = {
                levels: me.available_arm_levels,
                ordered: me.arm_levels_ordered
            };
        }
        if (me.trait.brightness) {
            attributes['commandOnlyBrightness'] = me.command_only_brightness;
        }
        if (me.trait.channel) {
            attributes['availableChannels'] = me.available_channels;
        }
        if (me.trait.colorsetting) {
            attributes["commandOnlyColorSetting"] = me.command_only_colorsetting;
            if (me.color_model === "rgb" || me.color_model === "rgb_temp") {
                attributes['colorModel'] = "rgb";
            }
            else if (me.color_model === "hsv" || me.color_model === "hsv_temp") {
                attributes['colorModel'] = "hsv";
            }
            if (me.color_model !== "rgb" && me.color_model !== "hsv") {
                attributes['colorTemperatureRange'] = {
                    "temperatureMinK": me.temperature_min_k,
                    "temperatureMaxK": me.temperature_max_k
                };
            }
        }
        if (me.trait.camerastream) {
            attributes['cameraStreamSupportedProtocols'] = me.camera_stream_supported_protocols;
            attributes['cameraStreamNeedAuthToken'] = me.need_auth_token;
        }
        if (me.trait.cook) {
            attributes['supportedCookingModes'] = me.supported_cooking_modes;
            attributes['foodPresets'] = me.food_presets;
        }
        if (me.trait.dispense) {
            attributes['supportedDispenseItems'] = me.supported_dispense_items;
            attributes['supportedDispensePresets'] = me.supported_dispense_presets;
        }
        if (me.trait.energystorage) {
            attributes['queryOnlyEnergyStorage'] = me.query_only_energy_storage;
            if (me.energy_storage_distance_unit_for_ux) {
                attributes['energyStorageDistanceUnitForUX'] = me.energy_storage_distance_unit_for_ux;
            }
            attributes['isRechargeable'] = me.is_rechargeable;
        }
        if (me.trait.fanspeed) {
            attributes['reversible'] = me.reversible;
            attributes['commandOnlyFanSpeed'] = me.command_only_fanspeed;
            attributes['supportsFanSpeedPercent'] = me.supports_fan_speed_percent;
            attributes['availableFanSpeeds'] = {
                speeds: me.available_fan_speeds,
                ordered: me.fan_speeds_ordered
            };
        }
        if (me.trait.fill) {
            attributes['availableFillLevels'] = {
                levels: me.available_fill_levels,
                ordered: me.ordered_fill_levels,
                supportsFillPercent: me.supports_fill_percent
            };
        }
        if (me.trait.humiditysetting) {
            attributes['humiditySetpointRange'] = {
                minPercent: me.min_percent,
                maxPercent: me.max_percent
            };
            attributes['commandOnlyHumiditySetting'] = me.command_only_humiditysetting;
            attributes['queryOnlyHumiditySetting'] = me.query_only_humiditysetting;
        }
        if (me.trait.inputselector) {
            attributes['availableInputs'] = me.available_inputs;
            attributes['commandOnlyInputSelector'] = me.command_only_input_selector;
            attributes['orderedInputs'] = me.ordered_inputs;
        }
        if (me.trait.lighteffects) {
            attributes['defaultSleepDuration'] = me.default_sleep_duration;
            attributes['defaultWakeDuration'] = me.default_wake_duration;
            attributes['supportedEffects'] = me.supported_effects;
        }
        if (me.trait.mediastate) {
            attributes['supportActivityState'] = me.support_activity_state;
            attributes['supportPlaybackState'] = me.support_playback_state;
        }
        if (me.trait.modes) {
            attributes['availableModes'] = me.available_modes;
            attributes['commandOnlyModes'] = me.command_only_modes;
            attributes['queryOnlyModes'] = me.query_only_modes;
        }
        if (me.trait.networkcontrol) {
            attributes['supportsEnablingGuestNetwork'] = me.supports_enabling_guest_network;
            attributes['supportsDisablingGuestNetwork'] = me.supports_disabling_guest_network;
            attributes['supportsGettingGuestNetworkPassword'] = me.supports_getting_guest_network_password;
            attributes['networkProfiles'] = me.network_profiles;
            attributes['supportsEnablingNetworkProfile'] = me.supports_enabling_network_profile;
            attributes['supportsDisablingNetworkProfile'] = me.supports_disabling_network_profile;
            attributes['supportsNetworkDownloadSpeedTest'] = me.supports_network_download_speedtest;
            attributes['supportsNetworkUploadSpeedTest'] = me.supports_network_upload_speedtest;
        }
        if (me.trait.occupancysensing) {
            let occupancysensingattributes = [];
            if (me.occupancy_sensing_pir) {
                if (me.occupied_to_unoccupied_delay_sec_pir) {
                    occupancysensingattributes.push({
                        occupancySensorType: "PIR",
                        occupiedToUnoccupiedDelaySec: me.occupied_to_unoccupied_delay_sec_pir,
                        unoccupiedToOccupiedDelaySec: me.unoccupied_to_occupied_delay_sec_pir,
                        unoccupiedToOccupiedEventThreshold: me.unoccupied_to_occupied_event_threshold_pir
                    });
                }
                else {
                    occupancysensingattributes.push({
                        occupancySensorType: "PIR"
                    });
                }
            }
            if (me.occupancy_sensing_ultrasonic) {
                if (me.occupied_to_unoccupied_delay_sec_ultrasonic) {
                    occupancysensingattributes.push({
                        occupancySensorType: "ULTRASONIC",
                        occupiedToUnoccupiedDelaySec: me.occupied_to_unoccupied_delay_sec_ultrasonic,
                        unoccupiedToOccupiedDelaySec: me.unoccupied_to_occupied_delay_sec_ultrasonic,
                        unoccupiedToOccupiedEventThreshold: me.unoccupied_to_occupied_event_threshold_ultrasonic
                    });
                }
                else {
                    occupancysensingattributes.push({
                        occupancySensorType: "ULTRASONIC"
                    });
                }
            }
            if (me.occupancy_sensing_physical_contact) {
                if (me.occupied_to_unoccupied_delay_sec_physical_contact) {
                    occupancysensingattributes.push({
                        occupancySensorType: "PHYSICAL_CONTACT",
                        occupiedToUnoccupiedDelaySec: me.occupied_to_unoccupied_delay_sec_physical_contact,
                        unoccupiedToOccupiedDelaySec: me.unoccupied_to_occupied_delay_sec_physical_contact,
                        unoccupiedToOccupiedEventThreshold: me.unoccupied_to_occupied_event_threshold_physical_contact
                    });
                }
                else {
                    occupancysensingattributes.push({
                        occupancySensorType: "PHYSICAL_CONTACT"
                    });
                }
            }
            attributes['occupancySensorConfiguration'] = occupancysensingattributes;
        }
        if (me.trait.onoff) {
            attributes['commandOnlyOnOff'] = me.command_only_onoff;
            attributes['queryOnlyOnOff'] = me.query_only_onoff;
        }
        if (me.trait.openclose) {
            attributes['discreteOnlyOpenClose'] = me.discrete_only_openclose;
            attributes['openDirection'] = me.open_direction;
            attributes['commandOnlyOpenClose'] = me.command_only_openclose;
            attributes['queryOnlyOpenClose'] = me.query_only_openclose;
        }
        if (me.trait.rotation) {
            attributes['supportsDegrees'] = me.supports_degrees;
            attributes['supportsPercent'] = me.supports_percent;
            attributes['rotationDegreesRange'] = [{
                    rotationDegreesMin: me.rotation_degrees_min,
                    rotationDegreesMax: me.rotation_degrees_max
                }];
            attributes['supportsContinuousRotation'] = me.supports_continuous_rotation;
            attributes['commandOnlyRotation'] = me.command_only_rotation;
        }
        if (me.trait.scene) {
            attributes['sceneReversible'] = me.scene_reversible;
        }
        if (me.trait.sensorstate) {
            let sensor_states_supported = [];
            me.sensor_states_supported.forEach(function (sensor_state_name) {
                let sensor_state_supported = { name: sensor_state_name };
                let available_states = undefined;
                let raw_value_uUnit = undefined;
                switch (sensor_state_name) {
                    case "AirQuality":
                        available_states = [
                            "healthy",
                            "moderate",
                            "unhealthy",
                            "unhealthy for sensitive groups",
                            "very unhealthy",
                            "hazardous",
                            "good",
                            "fair",
                            "poor",
                            "very poor",
                            "severe",
                            "unknown"
                        ];
                        raw_value_uUnit = 'AQI';
                        break;
                    case "CarbonMonoxideLevel":
                        available_states = [
                            "carbon monoxide detected",
                            "high",
                            "no carbon monoxide detected",
                            "unknown"
                        ];
                        raw_value_uUnit = 'PARTS_PER_MILLION';
                        break;
                    case "SmokeLevel":
                        available_states = [
                            "smoke detected",
                            "high",
                            "no smoke detected",
                            "unknown"
                        ];
                        raw_value_uUnit = 'PARTS_PER_MILLION';
                        break;
                    case "FilterCleanliness":
                        available_states = [
                            "clean",
                            "dirty",
                            "needs replacement",
                            "unknown"
                        ];
                        break;
                    case "WaterLeak":
                        available_states = [
                            "leak",
                            "no leak",
                            "unknown"
                        ];
                        break;
                    case "RainDetection":
                        available_states = [
                            "rain detected",
                            "no rain detected",
                            "unknown"
                        ];
                        break;
                    case "FilterLifeTime":
                        available_states = [
                            "new",
                            "good",
                            "replace soon",
                            "replace now",
                            "unknown"
                        ];
                        raw_value_uUnit = 'PERCENTAGE';
                        break;
                    case "PreFilterLifeTime":
                    case "HEPAFilterLifeTime":
                    case "Max2FilterLifeTime":
                        raw_value_uUnit = 'PERCENTAGE';
                        break;
                    case "CarbonDioxideLevel":
                        raw_value_uUnit = 'PARTS_PER_MILLION';
                        break;
                    case "PM2.5":
                    case "PM10":
                        raw_value_uUnit = 'MICROGRAMS_PER_CUBIC_METER';
                        break;
                    case "VolatileOrganicCompounds":
                        raw_value_uUnit = 'PARTS_PER_MILLION';
                        break;
                }
                if (available_states !== undefined) {
                    sensor_state_supported['descriptiveCapabilities'] = {
                        availableStates: available_states
                    };
                }
                if (raw_value_uUnit !== undefined) {
                    sensor_state_supported['numericCapabilities'] = {
                        rawValueUnit: raw_value_uUnit
                    };
                }
                sensor_states_supported.push(sensor_state_supported);
            });
            attributes['sensorStatesSupported'] = sensor_states_supported;
        }
        if (me.trait.startstop) {
            attributes['pausable'] = me.pausable;
            attributes['availableZones'] = me.available_zones;
        }
        if (me.trait.temperaturecontrol) {
            attributes['temperatureRange'] = {
                minThresholdCelsius: me.tc_min_threshold_celsius,
                maxThresholdCelsius: me.tc_max_threshold_celsius
            };
            attributes['temperatureStepCelsius'] = me.tc_temperature_step_celsius;
            attributes['temperatureUnitForUX'] = me.tc_temperature_unit_for_ux;
            attributes['commandOnlyTemperatureControl'] = me.command_only_temperaturecontrol;
            attributes['queryOnlyTemperatureControl'] = me.query_only_temperaturecontrol;
        }
        if (me.trait.temperaturesetting) {
            attributes['availableThermostatModes'] = me.available_thermostat_modes;
            attributes['thermostatTemperatureRange'] = {
                minThresholdCelsius: me.min_threshold_celsius,
                maxThresholdCelsius: me.max_threshold_celsius
            };
            attributes['thermostatTemperatureUnit'] = me.thermostat_temperature_unit;
            attributes['bufferRangeCelsius'] = me.buffer_range_celsius;
            attributes['commandOnlyTemperatureSetting'] = me.command_only_temperaturesetting;
            attributes['queryOnlyTemperatureSetting'] = me.query_only_temperaturesetting;
        }
        if (me.trait.timer) {
            attributes['maxTimerLimitSec'] = me.max_timer_limit_sec;
            attributes['commandOnlyTimer'] = me.command_only_timer;
        }
        if (me.trait.toggles) {
            attributes['availableToggles'] = me.available_toggles;
            attributes['commandOnlyToggles'] = me.command_only_toggles;
            attributes['queryOnlyToggles'] = me.query_only_toggles;
        }
        if (me.trait.transportcontrol) {
            attributes['transportControlSupportedCommands'] = me.supported_commands;
        }
        if (me.trait.volume) {
            attributes['volumeMaxLevel'] = me.volume_max_level;
            attributes['volumeCanMuteAndUnmute'] = me.volume_can_mute_and_unmute;
            attributes['volumeDefaultPercentage'] = me.volume_default_percentage;
            attributes['levelStepSize'] = me.level_step_size;
            attributes['commandOnlyVolume'] = me.command_only_volume;
        }
    }
    getDispenseNewState() {
        let me = this;
        let dispense = [];
        me.supported_dispense_items.forEach(function (item) {
            dispense.push({
                itemName: item.item_name,
                amountRemaining: {
                    amount: 0,
                    unit: "NO_UNITS"
                },
                amountLastDispensed: {
                    amount: 0,
                    unit: "NO_UNITS"
                },
                isCurrentlyDispensing: false
            });
        });
        me.supported_dispense_presets.forEach(function (item) {
            dispense.push({
                itemName: item.preset_name,
                amountRemaining: {
                    amount: 0,
                    unit: "NO_UNITS"
                },
                amountLastDispensed: {
                    amount: 0,
                    unit: "NO_UNITS"
                },
                isCurrentlyDispensing: false
            });
        });
        return dispense;
    }
    /**
     * Initializes states to their default values.
     *
     * @returns {object}
     */
    initializeStates(device) {
        let me = this;
        let states = device.states;
        if (me.trait.appselector) {
            states['currentApplication'] = '';
        }
        if (me.trait.armdisarm) {
            states['isArmed'] = false;
            states['currentArmLevel'] = '';
            // states['exitAllowance'] = 60;
        }
        if (me.trait.brightness) {
            // states['brightness'] = 50;
        }
        if (me.trait.colorsetting) {
            if (!me.command_only_colorsetting) {
                if (me.color_model === "rgb") {
                    states['color'] = { spectrumRgb: 16777215 };
                }
                else if (me.color_model === "hsv") {
                    states['color'] = {
                        spectrumHsv: {
                            hue: 0.0, // float, representing hue as positive degrees in the range of [0.0, 360.0)
                            saturation: 0.0, // float, representing saturation as a percentage in the range [0.0, 1.0]
                            value: 1 // float, representing value as a percentage in the range [0.0, 1.0]
                        }
                    };
                }
                else {
                    states['color'] = { temperatureK: me.temperature_max_k || 6000 };
                }
            }
        }
        if (me.trait.cook) {
            states['currentCookingMode'] = "NONE";
            states['currentFoodPreset'] = "NONE";
            // states['currentFoodQuantity'] = 0;
            // states['currentFoodUnit'] = "UNKNOWN_UNITS";
        }
        if (me.trait.dispense) {
            states['dispenseItems'] = this.getDispenseNewState();
        }
        if (me.trait.dock) {
            // states['isDocked'] = false;
        }
        if (me.trait.energystorage) {
            states['descriptiveCapacityRemaining'] = "FULL";
            // states['capacityRemaining'] = [];
            if (me.is_rechargeable) {
                // states['capacityUntilFull'] = [];
                states['isCharging'] = false;
            }
            // states['isPluggedIn'] = false;
        }
        if (me.trait.fanspeed) {
            // states['currentFanSpeedSetting'] = "";
            if (!me.command_only_fanspeed && me.supports_fan_speed_percent) {
                states['currentFanSpeedPercent'] = 0;
            }
        }
        if (me.trait.fill) {
            states['isFilled'] = false;
            if (me.available_fill_levels.length > 0) {
                states['currentFillLevel'] = "";
            }
            if (me.supports_fill_percent) {
                states['currentFillPercent'] = 0;
            }
        }
        if (me.trait.humiditysetting) {
            // states['humiditySetpointPercent'] = 52;
            // states['humidityAmbientPercent'] = 52;
        }
        if (me.trait.inputselector) {
            if (!me.command_only_input_selector) {
                if (me.availableInputs && me.availableInputs.length > 0) {
                    states['currentInput'] = me.availableInputs[0].key;
                }
                else {
                    states['currentInput'] = '';
                }
            }
        }
        if (me.trait.lighteffects) {
            states['activeLightEffect'] = "";
            // states['lightEffectEndUnixTimestampSec'] = 60;
        }
        //if (me.trait.lockunlock) {
        // states['isLocked'] = false;
        // states['isJammed'] = false;
        //}
        //if (me.trait.mediastate) {
        // INACTIVE STANDBY ACTIVE
        // states['activityState'] = 'INACTIVE';
        // PAUSED PLAYING FAST_FORWARDING REWINDING BUFFERING STOPPED
        // states['playbackState'] = 'STOPPED';
        //}
        if (me.trait.modes) {
            if (!me.command_only_modes) {
                states['currentModeSettings'] = {};
                this.updateModesState(device);
            }
        }
        //if (me.trait.networkcontrol) {
        // states['networkEnabled'] = true;
        // states['networkSettings'] = { ssid: '' };
        // states['guestNetworkEnabled'] = false;
        // states['guestNetworkSettings'] = { ssid: '' };
        // states['numConnectedDevices'] = 1;
        // states['networkUsageMB'] = 0;
        // states['networkUsageLimitMB'] = 0;
        // states['networkUsageUnlimited'] = true;
        /* states['lastNetworkDownloadSpeedTest'] = {
            downloadSpeedMbps: 0,
            unixTimestampSec: 0,
            status: "FAILURE"
        }; */
        /* states['lastNetworkUploadSpeedTest'] = {
            uploadSpeedMbps: 0,
            unixTimestampSec: 0,
            status: "FAILURE"
        };*/
        // states['networkSpeedTestInProgress'] = false;
        //}
        //if (me.trait.onoff) {
        // states['on'] = false;
        //}
        if (me.trait.openclose) {
            if (!me.command_only_openclose) {
                if (me.open_direction.length < 2) {
                    states['openPercent'] = 0;
                }
                else {
                    let openState = [];
                    states['openState'] = openState;
                    me.open_direction.forEach(direction => {
                        openState.push({
                            openPercent: 0,
                            openDirection: direction
                        });
                    });
                }
            }
        }
        /*if (me.trait.rotation) {
            if (me.supports_degrees) {
                // states['rotationDegrees'] = 0;
            }
            if (me.supports_percent) {
                // states['rotationPercent'] = 0;
            }
        }*/
        if (me.trait.runcycle) {
            states['currentRunCycle'] = [{
                    currentCycle: "unknown",
                    lang: me.lang
                }];
            states['currentTotalRemainingTime'] = 0;
            states['currentCycleRemainingTime'] = 0;
        }
        if (me.trait.sensorstate) {
            let current_sensor_state_data = [];
            me.sensor_states_supported.forEach(function (sensor_state_name) {
                let current_sensor = { name: sensor_state_name };
                let current_sensor_state = undefined;
                let raw_value = undefined;
                switch (sensor_state_name) {
                    case "AirQuality":
                        current_sensor_state = "unknown";
                        raw_value = 0;
                        break;
                    case "CarbonMonoxideLevel":
                        current_sensor_state = "unknown";
                        raw_value = 0;
                        break;
                    case "SmokeLevel":
                        current_sensor_state = "unknown";
                        raw_value = 0;
                        break;
                    case "FilterCleanliness":
                        current_sensor_state = "unknown";
                        break;
                    case "WaterLeak":
                        current_sensor_state = "unknown";
                        break;
                    case "RainDetection":
                        current_sensor_state = "unknown";
                        break;
                    case "FilterLifeTime":
                        current_sensor_state = "unknown";
                        raw_value = 0;
                        break;
                    case "PreFilterLifeTime":
                    case "HEPAFilterLifeTime":
                    case "Max2FilterLifeTime":
                        raw_value = 0;
                        break;
                    case "CarbonDioxideLevel":
                        raw_value = 0;
                        break;
                    case "PM2.5":
                    case "PM10":
                        raw_value = 0;
                        break;
                    case "VolatileOrganicCompounds":
                        raw_value = 0;
                        break;
                }
                if (current_sensor_state !== undefined) {
                    current_sensor['currentSensorState'] = current_sensor_state;
                }
                if (raw_value !== undefined) {
                    current_sensor['rawValue'] = raw_value;
                }
                current_sensor_state_data.push(current_sensor);
            });
            if (current_sensor_state_data.length > 0) {
                states['currentSensorStateData'] = current_sensor_state_data;
            }
            else {
                delete me.state_types['currentSensorStateData'];
            }
        }
        if (me.trait.softwareupdate) {
            states['lastSoftwareUpdateUnixTimestampSec'] = 0;
        }
        if (me.trait.startstop) {
            states['isRunning'] = false;
            // states['isPaused'] = false;
            // states['activeZones'] = [];
        }
        /*if (me.trait.statusreport) {
            states['currentStatusReport'] = [];
        }*/
        if (me.trait.temperaturecontrol) {
            if (!me.query_only_temperaturecontrol) { // Required if queryOnlyTemperatureControl set to false
                states['temperatureSetpointCelsius'] = me.tc_min_threshold_celsius;
            }
            // states['temperatureAmbientCelsius'] = me.tc_min_threshold_celsius;
        }
        if (me.trait.temperaturesetting) {
            if (!me.command_only_temperaturesetting) {
                // states['activeThermostatMode'] = "none";
                // states['targetTempReachedEstimateUnixTimestampSec'] = me.target_temp_reached_estimate_unix_timestamp_sec;
                // states['thermostatHumidityAmbient'] = me.thermostat_humidity_ambient;
                states['thermostatMode'] = me.available_thermostat_modes.length > 0 ? me.available_thermostat_modes[0] : "";
                states['thermostatTemperatureAmbient'] = me.thermostat_temperature_setpoint;
                // 0
                states['thermostatTemperatureSetpoint'] = me.thermostat_temperature_setpoint;
                // 1
                // states['thermostatTemperatureSetpointHigh'] = me.thermostat_temperature_setpoint_hight;
                // states['thermostatTemperatureSetpointLow'] = me.thermostat_temperature_setpoint_low;
            }
        }
        if (me.trait.timer) {
            if (!me.command_only_timer) {
                states['timerRemainingSec'] = -1;
                // states['timerPaused'] = false;
            }
        }
        if (me.trait.toggles) {
            if (!me.command_only_toggles) {
                states['currentToggleSettings'] = {};
                this.updateTogglesState(device);
            }
        }
        if (me.trait.volume) {
            if (!me.command_only_volume) {
                states['currentVolume'] = me.volume_default_percentage;
                if (me.volume_can_mute_and_unmute) {
                    states['isMuted'] = false;
                }
            }
        }
    }
    /**
     * Updates the status icon of this device node.
     *
     * @param {boolean} is_local - Indicates whether the current command was issued using local fulfillment.
     */
    updateStatusIcon(is_local) {
        const me = this;
        let text = [];
        let fill = 'red';
        let shape = 'dot';
        if (me.states.online) {
            if (me.trait.scene) {
                text.push('OK');
                fill = 'green';
            }
            if (me.trait.onoff) {
                if (me.states.on !== undefined) {
                    if (me.states.on) {
                        text.push('ON');
                        fill = 'green';
                    }
                    else {
                        text.push('OFF');
                    }
                }
                else {
                    fill = 'blue';
                }
            }
            else {
                fill = 'blue';
            }
            if (me.trait.brightness && me.states.brightness !== undefined) {
                text.push(`${me.states.brightness} %`);
            }
            if (me.trait.colorsetting && me.states.color.temperatureK !== undefined) {
                text.push(`${me.states.color.temperatureK} K`);
            }
            if (me.trait.colorsetting && me.states.color.spectrumRgb !== undefined) {
                text.push('#' + me.states.color.spectrumRgb.toString(16).toUpperCase().padStart(6, '0'));
            }
            if (me.trait.colorsetting && me.states.color.spectrumHsv !== undefined) {
                text.push('H: ' + me.states.color.spectrumHsv.hue +
                    ' S: ' + me.states.color.spectrumHsv.saturation +
                    ' V: ' + me.states.color.spectrumHsv.value);
            }
            if (me.trait.openclose) {
                if (me.states.openPercent !== undefined) {
                    if (me.states.openPercent === 0) {
                        text.push('CLOSED');
                        fill = 'green';
                    }
                    else {
                        text.push(me.discrete_only_openclose ? 'OPEN' : util_1.default.format("OPEN %d%%", me.states.openPercent));
                    }
                }
            }
            if (me.trait.startstop) {
                if (me.pausable && me.states.isPaused) {
                    text.push('Paused');
                    fill = 'yellow';
                }
                else {
                    text.push(me.states.isRunning ? 'Started' : 'Stopped');
                    fill = me.states.isRunning ? 'green' : 'red';
                }
            }
            if (me.trait.humiditysetting) {
                if (me.states.humidityAmbientPercent !== undefined) {
                    text.push('H: ' + me.states.humidityAmbientPercent + "% ");
                }
                if (me.states.humiditySetpointPercent !== undefined) {
                    text.push('TH: ' + me.states.humiditySetpointPercent + "% ");
                }
            }
            if (me.trait.temperaturecontrol) {
                if (me.states.temperatureAmbientCelsius !== undefined) {
                    text.push('TC: ' + me.states.temperatureAmbientCelsius);
                }
                if (me.states.temperatureSetpointCelsius !== undefined) {
                    text.push(' SC: ' + me.states.temperatureSetpointCelsius);
                }
            }
            if (me.trait.temperaturesetting) {
                const thermostat_mode = me.states.thermostatMode;
                const st = " T: " + (me.states.thermostatTemperatureAmbient || '?') + "°C | S: " + (me.states.thermostatTemperatureSetpoint || '?');
                if (thermostat_mode === "off") {
                    text.push('OFF ' + st);
                }
                else if (thermostat_mode === "heat" || thermostat_mode === "cool") {
                    fill = "green";
                    text.push(thermostat_mode.substring(0, 1).toUpperCase() + st);
                }
                else if (thermostat_mode === "heatcool") {
                    fill = "green";
                    text.push('H/C T: ' + (me.states.thermostatTemperatureAmbient || '?') + "°C | S: [" + (me.states.thermostatTemperatureSetpointLow || '') + " - " + (me.states.thermostatTemperatureSetpointHigh || ''));
                }
                else {
                    fill = "green";
                    text.push(thermostat_mode.substring(0, 1).toUpperCase() + st);
                }
                if (me.states.thermostatHumidityAmbient !== undefined) {
                    text.push(me.states.thermostatHumidityAmbient + "%");
                }
            }
            if (me.trait.energystorage) {
                text.push(me.states.descriptiveCapacityRemaining);
            }
            if (me.trait.armdisarm) {
                if (me.states.isArmed) {
                    if (me.states.currentArmLevel) {
                        text.push(me.states.currentArmLevel);
                    }
                }
                else {
                    text.push('DISARMED');
                }
            }
            if (me.trait.fanspeed) {
                if (typeof me.states.currentFanSpeedPercent === 'number') {
                    text.push(me.states.currentFanSpeedPercent + '%');
                }
                if (typeof me.states.currentFanSpeedSetting === 'string') {
                    text.push(me.states.currentFanSpeedSetting);
                }
            }
            if (me.trait.sensorstate) {
                if (Array.isArray(me.states.currentSensorStateData)) {
                    me.states.currentSensorStateData.forEach(sensor => {
                        const currentSensorStateSet = sensor.currentSensorState !== undefined && sensor.currentSensorState !== 'unknown';
                        if (currentSensorStateSet || sensor.rawValue !== undefined) {
                            text.push(sensor.name);
                            if (currentSensorStateSet) {
                                text.push(sensor.currentSensorState);
                            }
                            if (sensor.rawValue !== undefined) {
                                text.push(sensor.rawValue);
                            }
                        }
                    });
                }
            }
            if (me.trait.lockunlock) {
                if (me.states.isJammed) {
                    text.push('JAMMED');
                }
                else if (typeof me.states.isLocked === 'boolean') {
                    text.push(me.states.isLocked ? ' LOCKED' : ' UNLOCKED');
                }
            }
            if (me.trait.runcycle) {
                if (typeof me.states.currentRunCycle !== 'undefined' &&
                    typeof me.states.currentRunCycle[0] !== 'undefined' &&
                    typeof me.states.currentRunCycle[0].currentCycle !== 'undefined' &&
                    me.states.currentRunCycle[0].currentCycle !== 'unknown') {
                    text.push(me.states.currentRunCycle[0].currentCycle);
                }
            }
            if (me.trait.occupancysensing) {
                if (typeof me.states.occupancy !== 'undefined')
                    text.push(me.states.occupancy);
            }
        }
        else {
            shape = 'ring';
            text.push("offline");
        }
        if (text.length === 0) {
            shape = fill = '';
        }
        if (is_local) {
            shape = 'ring';
        }
        text = text.join(' | ');
        me.status({ fill: fill, shape: shape, text: text });
    }
    /******************************************************************************************************************
     * called when state is updated from Google Assistant
     *
     */
    updated(g_command, exe_result, is_local) {
        const me = this;
        let command = g_command.command.startsWith('action.devices.commands.') ? g_command.command.substr(24) : g_command.command;
        let params = exe_result.params || {};
        me._debug(".updated: g_command = " + JSON.stringify(g_command));
        me._debug(".updated: exe_result = " + JSON.stringify(exe_result));
        const modified = me.updateState(params);
        if (modified) {
            if (me.persistent_state) {
                me.clientConn.app.ScheduleGetState();
            }
        }
        me.updateStatusIcon(is_local);
        let msg = {
            device_name: me.device.properties.name.name,
            command: command,
            params: g_command.params,
            payload: {},
        };
        if (me.topicOut)
            msg.topic = me.topicOut;
        // Copy the device state to the payload
        me.cloneObject(msg.payload, me.states, me.state_types);
        // Copy the exe_result params to the payload
        if (exe_result.params) {
            Object.keys(exe_result.params).forEach(function (key) {
                if (!Object.prototype.hasOwnProperty.call(msg.payload, key) && !exe_result.executionStates.includes(key)) {
                    msg.payload[key] = exe_result.params[key];
                }
            });
        }
        me.send(msg);
    }
    /**
     * respond to inputs from NodeRED
     *
     * @param {object} msgi - The incoming message
     * @param {Function} send - Function to send outgoing messages
     * @param {Function} done - Function to inform the runtime that this node has finished its operation
     */
    onInput(msgi, send, done) {
        const me = this;
        if (!send)
            send = function () { me.send.apply(me, arguments); };
        let msg = msgi;
        if (me.topic_filter && !(msg.topic || '').toString().startsWith(me.topicOut)) {
            if (done)
                done();
            return;
        }
        if (me.topic_filter && msg.payload.topic && (msg.topic || '').toString().startsWith(me.topicOut)) {
            msg.topic = msg.payload.topic;
        }
        me._debug(".input: topic = " + msg.topic);
        let upper_topic = '';
        if (msg.topic) {
            let topicArr = String(msg.topic).split(me.topicDelim);
            let topic = topicArr[topicArr.length - 1].trim(); // get last part of topic
            upper_topic = topic.toUpperCase();
        }
        try {
            if (upper_topic === 'GETSTATE') {
                let states = {};
                me.cloneObject(states, me.states, me.state_types);
                send({
                    topic: msg.topic,
                    payload: states,
                    device_id: me.device.id
                });
            }
            else if (upper_topic === 'ERRORCODE') {
                if (typeof msg.payload === 'string' && msg.payload.trim()) {
                    me.errorCode = msg.payload.trim();
                }
                else {
                    me.errorCode = undefined;
                }
            }
            else if (upper_topic === 'AVAILABLEAPPLICATIONS') {
                if (me.trait.appselector) {
                    if (me.appselector_type === 'str') {
                        const filename = me.appselector_file.replace(/<id>/g, me.id);
                        if (typeof msg.payload !== 'undefined') {
                            me.available_applications = me.to_available_applications(msg.payload);
                            me.writeJson('Applications', filename, me.available_applications);
                        }
                        else {
                            me.available_applications = me.to_available_applications(me.loadJson('Applications', filename, []));
                        }
                    }
                    else {
                        if (typeof msg.payload !== 'undefined') {
                            me.available_applications = me.to_available_applications(msg.payload);
                        }
                    }
                    me.device.properties.attributes.availableApplications = me.available_applications;
                    me.updateStateTypesForTraits();
                    me.clientConn.app.ScheduleRequestSync();
                }
                else {
                    me.error("Got AVAILABLEAPPLICATIONS message, but AppSelector trait is disabled");
                }
            }
            else if (upper_topic === 'AVAILABLEARMLEVELS') {
                if (me.trait.armdisarm) {
                    if (me.available_arm_levels_type === 'str') {
                        const filename = me.available_arm_levels_file.replace(/<id>/g, me.id);
                        if (typeof msg.payload !== 'undefined') {
                            me.available_arm_levels = me.to_available_arm_levels(msg.payload);
                            me.writeJson('Arm levels', filename, me.available_arm_levels);
                        }
                        else {
                            me.available_arm_levels = me.to_available_arm_levels(me.loadJson('Arm levels', filename, []));
                        }
                    }
                    else {
                        if (typeof msg.payload !== 'undefined') {
                            me.available_arm_levels = me.to_available_arm_levels(msg.payload);
                        }
                    }
                    me.device.properties.attributes.availableArmLevels.levels = me.available_arm_levels;
                    me.updateStateTypesForTraits();
                    me.clientConn.app.ScheduleRequestSync();
                }
                else {
                    me.error("Got AVAILABLEARMLEVELS message, but ArmDisarm trait is disabled");
                }
            }
            else if (upper_topic === 'AVAILABLECHANNELS') {
                if (me.trait.channel) {
                    if (me.channel_type === 'str') {
                        const filename = me.channel_file.replace(/<id>/g, me.id);
                        if (typeof msg.payload !== 'undefined') {
                            me.available_channels = me.to_available_channels(msg.payload);
                            me.writeJson('Channels', filename, me.available_channels);
                        }
                        else {
                            me.available_channels = me.to_available_channels(me.loadJson('Channels', filename, []));
                        }
                    }
                    else {
                        if (typeof msg.payload !== 'undefined') {
                            me.available_channels = [];
                        }
                    }
                    me.device.properties.attributes.availableChannels = me.available_channels;
                    me.updateStateTypesForTraits();
                    me.clientConn.app.ScheduleRequestSync();
                }
                else {
                    me.error("Got AVAILABLECHANNELS message, but Channel trait is disabled");
                }
            }
            else if (upper_topic === 'SUPPORTEDDISPENSEITEMS') {
                if (me.trait.dispense) {
                    if (me.supported_dispense_items_type === 'str') {
                        const filename = me.supported_dispense_items_file.replace(/<id>/g, me.id);
                        if (typeof msg.payload !== 'undefined') {
                            me.supported_dispense_items = me.to_supported_dispense_items(msg.payload);
                            me.writeJson('Dispense items', filename, me.supported_dispense_items);
                        }
                        else {
                            me.supported_dispense_items = me.to_supported_dispense_items(me.loadJson('Dispense items', filename, []));
                        }
                    }
                    else {
                        if (typeof msg.payload !== 'undefined') {
                            me.supported_dispense_items = me.to_supported_dispense_items(msg.payload);
                        }
                    }
                    me.device.properties.attributes.supportedDispenseItems = me.supported_dispense_items;
                    me.states['dispenseItems'] = me.getDispenseNewState();
                    me.updateStateTypesForTraits();
                    me.clientConn.app.ScheduleRequestSync();
                }
                else {
                    me.error("Got SUPPORTEDDISPENSEITEMS message, but Dispense trait is disabled");
                }
            }
            else if (upper_topic === 'SUPPORTEDDISPENSEPRESETS') {
                if (me.trait.dispense) {
                    if (me.supported_dispense_presets_type === 'str') {
                        const filename = me.supported_dispense_presets_file.replace(/<id>/g, me.id);
                        if (typeof msg.payload !== 'undefined') {
                            me.supported_dispense_presets = me.to_supported_dispense_presets(msg.payload);
                            me.writeJson('Dispense presets', filename, me.supported_dispense_presets);
                        }
                        else {
                            me.supported_dispense_presets = me.to_supported_dispense_presets(me.loadJson('Dispense presets', filename, []));
                        }
                    }
                    else {
                        if (typeof msg.payload !== 'undefined') {
                            me.supported_dispense_presets = me.to_supported_dispense_presets(msg.payload);
                        }
                    }
                    me.device.properties.attributes.supportedDispensePresets = me.supported_dispense_presets;
                    me.states['dispenseItems'] = me.getDispenseNewState();
                    me.updateStateTypesForTraits();
                    me.clientConn.app.ScheduleRequestSync();
                }
                else {
                    me.error("Got SUPPORTEDDISPENSEPRESETS message, but Dispense trait is disabled");
                }
            }
            else if (upper_topic === 'AVAILABLEFANSPEEDS') {
                if (me.trait.fanspeed) {
                    if (me.available_fan_speeds_type === 'str') {
                        const filename = me.available_fan_speeds_file.replace(/<id>/g, me.id);
                        if (typeof msg.payload !== 'undefined') {
                            me.available_fan_speeds = me.to_available_fan_speeds(msg.payload);
                            me.writeJson('Fan speeds', filename, me.available_fan_speeds);
                        }
                        else {
                            me.available_fan_speeds = me.to_available_fan_speeds(me.loadJson('Fan speeds', filename, []));
                        }
                    }
                    else {
                        if (typeof msg.payload !== 'undefined') {
                            me.available_fan_speeds = me.to_available_fan_speeds(msg.payload);
                        }
                    }
                    me.device.properties.attributes.availableFanSpeeds.speeds = me.available_fan_speeds;
                    me.updateStateTypesForTraits();
                    me.clientConn.app.ScheduleRequestSync();
                }
                else {
                    me.error("Got AVAILABLEFANSPEEDS message, but FanSpeed trait is disabled");
                }
            }
            else if (upper_topic === 'AVAILABLEFILLLEVELS') {
                if (me.trait.dispense) {
                    if (me.available_fill_levels_type === 'str') {
                        const filename = me.available_fill_levels_file.replace(/<id>/g, me.id);
                        if (typeof msg.payload !== 'undefined') {
                            me.available_fill_levels = me.to_available_fill_levels(msg.payload);
                            me.writeJson(' Fill levels', filename, me.available_fill_levels);
                        }
                        else {
                            me.available_fill_levels = me.to_available_fill_levels(me.loadJson(' Fill levels', filename, []));
                        }
                    }
                    else {
                        if (typeof msg.payload !== 'undefined') {
                            me.available_fill_levels = me.to_available_fill_levels(msg.payload);
                        }
                    }
                    me.device.properties.attributes.availableFillLevels.levels = me.available_fill_levels;
                    me.updateStateTypesForTraits();
                    me.clientConn.app.ScheduleRequestSync();
                }
                else {
                    me.error("Got AVAILABLEFILLLEVELS message, but Fill trait is disabled");
                }
            }
            else if (upper_topic === 'AVAILABLEFOODPRESETS') {
                if (me.trait.cook) {
                    if (me.food_presets_type === 'str') {
                        const filename = me.food_presets_file.replace(/<id>/g, me.id);
                        if (typeof msg.payload !== 'undefined') {
                            me.food_presets = me.to_food_presets(msg.payload);
                            me.writeJson('Food presets', filename, me.food_presets);
                        }
                        else {
                            me.food_presets = me.to_food_presets(me.loadJson('Food presets', filename, []));
                        }
                    }
                    else {
                        if (typeof msg.payload !== 'undefined') {
                            me.food_presets = me.to_food_presets(msg.payload);
                        }
                    }
                    me.device.properties.attributes.foodPresets = me.food_presets;
                    me.updateStateTypesForTraits();
                    me.clientConn.app.ScheduleRequestSync();
                }
                else {
                    me.error("Got AVAILABLEFOODPRESETS message, but Cook trait is disabled");
                }
            }
            else if (upper_topic === 'AVAILABLEINPUTS') {
                if (me.trait.inputselector) {
                    if (me.inputselector_type === 'json') {
                        const filename = me.inputselector_file.replace(/<id>/g, me.id);
                        if (typeof msg.payload !== 'undefined') {
                            me.available_inputs = me.to_available_inputs(msg.payload);
                            me.writeJson('Inputs', filename, me.available_inputs);
                        }
                        else {
                            me.available_inputs = me.to_available_inputs(me.loadJson('Inputs', filename, []));
                        }
                    }
                    else {
                        if (typeof msg.payload !== 'undefined') {
                            me.available_inputs = me.to_available_inputs(msg.payload);
                        }
                    }
                    me.device.properties.attributes.availableInputs = me.available_inputs;
                    me.updateStateTypesForTraits();
                    me.clientConn.app.ScheduleRequestSync();
                }
                else {
                    me.error("Got AVAILABLEINPUTS message, but InputSelector trait is disabled");
                }
            }
            else if (upper_topic === 'AVAILABLEMODES') {
                if (me.trait.modes) {
                    if (me.modes_type !== 'json') {
                        const filename = me.modes_file.replace(/<id>/g, me.id);
                        if (typeof msg.payload !== 'undefined') {
                            me.available_modes = me.to_available_modes(msg.payload);
                            me.writeJson('Modes', filename, me.available_modes);
                        }
                        else {
                            me.available_modes = me.to_available_modes(me.loadJson('Modes', filename, []));
                        }
                    }
                    else {
                        if (typeof msg.payload !== 'undefined') {
                            me.available_modes = me.to_available_modes(msg.payload);
                        }
                    }
                    me.device.properties.attributes.availableModes = me.available_modes;
                    me.updateStateTypesForTraits();
                    me.updateModesState(me);
                    me.clientConn.app.ScheduleRequestSync();
                }
                else {
                    me.error("Got AVAILABLEMODES message, but Modes trait is disabled");
                }
            }
            else if (upper_topic === 'AVAILABLETOGGLES') {
                if (me.trait.toggles) {
                    if (me.toggles_type === 'str') {
                        const filename = me.toggles_file.replace(/<id>/g, me.id);
                        if (typeof msg.payload !== 'undefined') {
                            me.available_toggles = me.to_available_toggles(msg.payload);
                            me.writeJson('Toggles', filename, me.available_toggles);
                        }
                        else {
                            me.available_toggles = me.to_available_toggles(me.loadJson('Toggles', filename, []));
                        }
                    }
                    else {
                        if (typeof msg.payload !== 'undefined') {
                            me.available_toggles = me.to_available_toggles(msg.payload);
                        }
                    }
                    me.device.properties.attributes.availableToggles = me.available_toggles;
                    me.updateStateTypesForTraits();
                    me.updateTogglesState(me);
                    me.clientConn.app.ScheduleRequestSync();
                }
                else {
                    me.error("Got AVAILABLETOGGLES message, but Toggles trait is disabled");
                }
            }
            else if (upper_topic === 'CAMERASTREAMAUTHTOKEN') {
                const auth_token = Formats_js_1.Formats.formatValue('cameraStreamAuthToken', msg.payload, Formats_js_1.Formats.STRING, '');
                //if (auth_token != me.auth_token) {
                me.auth_token = auth_token;
                /*if (Object.prototype.hasOwnProperty.call(me.device.properties.attributes, "cameraStreamNeedAuthToken")) {
                    let cameraStreamNeedAuthToken = me.device.properties.attributes.cameraStreamNeedAuthToken;
                    if (cameraStreamNeedAuthToken != (auth_token.length > 0)) {
                        me.device.properties.attributes['cameraStreamNeedAuthToken'] = auth_token.length > 0;
                        me.clientConn.app.ScheduleRequestSync();
                    }
                }*/
                //}
            }
            else if (upper_topic === 'GUESTNETWORKPASSWORD') {
                me.guest_network_password = Formats_js_1.Formats.formatValue('guestNetworkPassword', msg.payload, Formats_js_1.Formats.STRING);
            }
            else if (me.trait.objectdetection && upper_topic === 'OBJECTDETECTION') {
                let payload = {};
                if (typeof msg.payload.familiar === 'number') {
                    payload.familiar = msg.payload.familiar;
                }
                if (typeof msg.payload.unfamiliar === 'number') {
                    payload.unfamiliar = msg.payload.unfamiliar;
                }
                if (typeof msg.payload.unclassified === 'number') {
                    payload.unclassified = msg.payload.unclassified;
                }
                if (typeof msg.payload.named === 'string') {
                    payload.named = [msg.payload.named];
                }
                else if (Array.isArray(msg.payload.named)) {
                    payload.named = msg.payload.named;
                }
                me.clientConn.sendNotifications(me, {
                    ObjectDetection: {
                        objects: payload,
                        priority: 0,
                        detectionTimestamp: Date.now()
                    }
                }); // tell Google ...
            }
            else if (me.trait.runcycle && upper_topic === 'RUNCYCLE') {
                let payload = { priority: 0 };
                if (typeof msg.payload.status === 'string') {
                    payload.status = msg.payload.status;
                }
                if (typeof msg.payload.currentCycleRemainingTime === 'number') {
                    payload.currentCycleRemainingTime = msg.payload.currentCycleRemainingTime;
                }
                if (typeof msg.payload.errorCode === 'string') {
                    payload.errorCode = msg.payload.errorCode;
                }
                me.clientConn.sendNotifications(me, {
                    RunCycle: payload
                }); // tell Google ...
            }
            else if (me.trait.sensorstate && upper_topic === 'SENSORSTATE') {
                if (typeof msg.payload.name === 'string' && msg.payload.name.trim() && me.sensor_states_supported.includes(msg.payload.name.trim())) {
                    let payload = { priority: 0 };
                    payload.name = msg.payload.name.trim();
                    if (typeof msg.payload.currentSensorState === 'string' && msg.payload.currentSensorState.trim()) {
                        payload.currentSensorState = msg.payload.currentSensorState.trim();
                        me.clientConn.sendNotifications(me, {
                            SensorState: payload
                        }); // tell Google ...
                    }
                }
            }
            else if (me.trait.lockunlock && upper_topic === 'LOCKUNLOCK') {
                let payload = {};
                if (typeof msg.payload.followUpToken === 'string') {
                    payload.followUpToken = msg.payload.followUpToken;
                }
                if (typeof msg.payload.status === 'string') {
                    payload.status = msg.payload.status;
                }
                if (typeof msg.payload.isLocked === 'boolean') {
                    payload.isLocked = msg.payload.isLocked;
                }
                if (typeof msg.payload.errorCode === 'string') {
                    payload.errorCode = msg.payload.errorCode;
                }
                me.clientConn.sendNotifications(me, {
                    LockUnlock: {
                        priority: 0,
                        followUpResponse: payload
                    }
                }); // tell Google ...
            }
            else if (me.trait.networkcontrol && upper_topic === 'NETWORKCONTROL') {
                me.clientConn.sendNotifications(me, {
                    NetworkControl: {
                        priority: 0,
                        followUpResponse: msg.payload
                    }
                }); // tell Google ...
            }
            else if (me.trait.openclose && upper_topic === 'OPENCLOSE') {
                let payload = {};
                if (typeof msg.payload.followUpToken === 'string') {
                    payload.followUpToken = msg.payload.followUpToken;
                }
                if (typeof msg.payload.status === 'string') {
                    payload.status = msg.payload.status;
                }
                if (typeof msg.payload.openPercent === 'number') {
                    payload.openPercent = msg.payload.openPercent;
                }
                if (typeof msg.payload.errorCode === 'string') {
                    payload.errorCode = msg.payload.errorCode;
                }
                me.clientConn.sendNotifications(me, {
                    OpenClose: {
                        priority: 0,
                        followUpResponse: payload
                    }
                }); // tell Google ...
            }
            else if (me.trait.statusreport && upper_topic === 'STATUSREPORT') {
                // Update or Add reports based on deviceTarget and statusCode
                let payload = Array.isArray(msg.payload) ? msg.payload : [msg.payload];
                let new_payload = [];
                const status_report_type = me.state_types['currentStatusReport'].attributes;
                if (typeof me.states.currentStatusReport !== 'undefined') {
                    me.states.currentStatusReport.forEach(report => {
                        let new_report = { priority: 0 };
                        me.cloneObject(new_report, report, status_report_type);
                        new_payload.push(new_report);
                    });
                }
                let differs = false;
                payload.forEach(sr => {
                    let nodeId;
                    if (sr.deviceTarget) {
                        let properties = me.clientConn.getProperties([sr.deviceTarget]);
                        if (Object.keys(properties).length > 0) {
                            nodeId = sr.deviceTarget;
                        }
                        else {
                            nodeId = me.clientConn.getIdFromName(sr.deviceTarget);
                        }
                    }
                    else {
                        nodeId = me.device.id;
                    }
                    if (nodeId) {
                        let new_report = {};
                        me.cloneObject(new_report, sr, status_report_type);
                        if (new_report.statusCode) {
                            new_report.deviceTarget = nodeId;
                            let cur_reports = new_payload.filter(report => report.deviceTarget === nodeId && report.statusCode === new_report.statusCode);
                            if (cur_reports.length > 0) {
                                if (me.cloneObject(cur_reports[0], new_report, status_report_type)) {
                                    differs = true;
                                }
                            }
                            else {
                                new_payload.push(new_report);
                                differs = true;
                            }
                        }
                    }
                });
                if (me.updateState({ currentStatusReport: new_payload }) || differs) {
                    me.clientConn.reportState(me.id); // tell Google ...
                    if (me.persistent_state) {
                        me.clientConn.app.ScheduleGetState();
                    }
                    // if (me.passthru) {
                    //     msg.payload = new_payload;
                    //     me.send(msg);
                    // }
                }
            }
            else if (upper_topic === 'SETCHALLENGEPIN') {
                const pin = String(msg.payload.pin || '');
                switch (msg.payload.command) {
                    case 'action.devices.commands.appInstall':
                    case 'action.devices.commands.appSearch':
                    case 'action.devices.commands.appSelect':
                        me.pin_appselector = pin;
                        break;
                    case 'action.devices.commands.ArmDisarm':
                        me.pin_armdisarm = pin;
                        break;
                    case 'action.devices.commands.BrightnessAbsolute':
                    case 'action.devices.commands.BrightnessRelative':
                        me.pin_brightness = pin;
                        break;
                    case 'action.devices.commands.GetCameraStream':
                        me.pin_camerastream = pin;
                        break;
                    case 'action.devices.commands.selectChannel':
                    case 'action.devices.commands.relativeChannel':
                    case 'action.devices.commands.returnChannel':
                        me.pin_channel = pin;
                        break;
                    case 'action.devices.commands.ColorAbsolute':
                        me.pin_colorsetting = pin;
                        break;
                    case 'action.devices.commands.Cook':
                        me.pin_cook = pin;
                        break;
                    case 'action.devices.commands.Dispense':
                        me.pin_dispense = pin;
                        break;
                    case 'action.devices.commands.Dock':
                        me.pin_dock = pin;
                        break;
                    case 'action.devices.commands.Charge':
                        me.pin_energystorage = pin;
                        break;
                    case 'action.devices.commands.SetFanSpeed':
                    case 'action.devices.commands.SetFanSpeedRelative':
                    case 'action.devices.commands.Reverse':
                        me.pin_fanspeed = pin;
                        break;
                    case 'action.devices.commands.Fill':
                        me.pin_fill = pin;
                        break;
                    case 'action.devices.commands.SetHumidity':
                    case 'action.devices.commands.HumidityRelative':
                        me.pin_humiditysetting = pin;
                        break;
                    case 'action.devices.commands.SetInput':
                    case 'action.devices.commands.NextInput':
                    case 'action.devices.commands.PreviousInput':
                        me.pin_inputselector = pin;
                        break;
                    case 'action.devices.commands.ColorLoop':
                    case 'action.devices.commands.Sleep':
                    case 'action.devices.commands.StopEffect':
                    case 'action.devices.commands.Wake':
                        me.pin_colorsetting = pin;
                        break;
                    case 'action.devices.commands.Locate':
                        me.pin_locator = pin;
                        break;
                    case 'action.devices.commands.LockUnlock':
                        me.pin_lockunlock = pin;
                        break;
                    case 'action.devices.commands.SetModes':
                        me.pin_modes = pin;
                        break;
                    case 'action.devices.commands.EnableDisableGuestNetwork':
                    case 'action.devices.commands.EnableDisableNetworkProfile':
                    case 'action.devices.commands.GetGuestNetworkPassword':
                    case 'action.devices.commands.TestNetworkSpeed':
                        me.pin_networkcontrol = pin;
                        break;
                    case 'action.devices.commands.OnOff':
                        me.pin_onoff = pin;
                        break;
                    case 'action.devices.commands.OpenClose':
                    case 'action.devices.commands.OpenCloseRelative':
                        me.pin_openclose = pin;
                        break;
                    case 'action.devices.commands.Reboot':
                        me.pin_reboot = pin;
                        break;
                    case 'action.devices.commands.RotateAbsolute':
                        me.pin_rotation = pin;
                        break;
                    case 'action.devices.commands.ActivateScene':
                        me.pin_scene = pin;
                        break;
                    case 'action.devices.commands.SoftwareUpdate':
                        me.pin_softwareupdate = pin;
                        break;
                    case 'action.devices.commands.StartStop':
                    case 'action.devices.commands.PauseUnpause':
                        me.pin_startstop = pin;
                        break;
                    case 'action.devices.commands.SetTemperature':
                        me.pin_temperaturecontrol = pin;
                        break;
                    case 'action.devices.commands.ThermostatTemperatureSetpoint':
                    case 'action.devices.commands.ThermostatTemperatureSetRange':
                    case 'action.devices.commands.ThermostatSetMode':
                    case 'action.devices.commands.TemperatureRelative':
                        me.pin_temperaturesetting = pin;
                        break;
                    case 'action.devices.commands.TimerStart':
                    case 'action.devices.commands.TimerAdjust':
                    case 'action.devices.commands.TimerPause':
                    case 'action.devices.commands.TimerResume':
                    case 'action.devices.commands.TimerCancel':
                        me.pin_timer = pin;
                        break;
                    case 'action.devices.commands.SetToggles':
                        me.pin_toggles = pin;
                        break;
                    case 'action.devices.commands.mediaStop':
                    case 'action.devices.commands.mediaNext':
                    case 'action.devices.commands.mediaPrevious':
                    case 'action.devices.commands.mediaPause':
                    case 'action.devices.commands.mediaResume':
                    case 'action.devices.commands.mediaSeekRelative':
                    case 'action.devices.commands.mediaSeekToPosition':
                    case 'action.devices.commands.mediaRepeatMode':
                    case 'action.devices.commands.mediaShuffle':
                    case 'action.devices.commands.mediaClosedCaptioningOn':
                    case 'action.devices.commands.mediaClosedCaptioningOff':
                        me.pin_transportcontrol = pin;
                        break;
                    case 'action.devices.commands.mute':
                    case 'action.devices.commands.setVolume':
                    case 'action.devices.commands.volumeRelative':
                        me.pin_volume = pin;
                        break;
                }
            }
            else {
                let state_key = '';
                Object.keys(me.state_types).forEach(function (key) {
                    if (upper_topic === key.toUpperCase()) {
                        state_key = key;
                        me._debug(".input: found state " + state_key);
                    }
                });
                if (state_key !== '') {
                    let payload = {};
                    payload[state_key] = msg.payload;
                    msg = {
                        payload: payload
                    };
                }
                else {
                    me._debug(".input: some other topic");
                }
                const differs = me.updateState(msg.payload || {});
                if (differs) {
                    if (msgi.stateOutput) {
                        let states = {};
                        me.cloneObject(states, me.states, me.state_types);
                        send({ topic: me.topicOut, payload: states });
                    }
                    me.clientConn.reportState(me.id); // tell Google ...
                    if (me.persistent_state) {
                        me.clientConn.app.ScheduleGetState();
                    }
                    me.updateStatusIcon(false);
                }
                if (me.passthru) {
                    send(msgi);
                }
                if (done)
                    done();
            }
        }
        catch (err) {
            if (done)
                done(err);
            else
                me.error(err);
        }
    }
    /**
     * Called by the runtime when this node is being removed or restarted
     *
     * @param {boolean} removed - true if the is being removed, false on restart
     * @param {Function} done - Function to inform the runtime that this node has finished its operation
     */
    onClose(removed, done) {
        if (removed) {
            // this node has been deleted
            this.clientConn.remove(this, 'device');
        }
        else {
            // this node is being restarted
            this.clientConn.deregister(this, 'device');
        }
        done();
    }
    updateTogglesState(device) {
        // Key/value pair with the toggle name of the device as the key, and the current state as the value.
        const me = this;
        me._debug(".updateTogglesState");
        let states = device.states || {};
        const currentToggleSettings = states['currentToggleSettings'];
        let new_toggles = {};
        me.available_toggles.forEach(function (toggle) {
            let value = false;
            if (typeof currentToggleSettings[toggle.name] === 'boolean') {
                value = currentToggleSettings[toggle.name];
            }
            new_toggles[toggle.name] = value;
        });
        states['currentToggleSettings'] = new_toggles;
    }
    updateModesState(device) {
        // Key/value pair with the mode name of the device as the key, and the current setting_name as the value.
        const me = this;
        me._debug(".updateModesState");
        let states = device.states || {};
        const currentModeSettings = states['currentModeSettings'];
        let new_modes = {};
        me.available_modes.forEach(function (mode) {
            let value = '';
            if (typeof currentModeSettings[mode.name] === 'string') {
                value = currentModeSettings[mode.name];
            }
            new_modes[mode.name] = value;
        });
        states['currentModeSettings'] = new_modes;
    }
    getTraits() {
        const me = this;
        const trait = me.trait;
        let traits = [];
        if (trait.appselector) {
            traits.push("action.devices.traits.AppSelector");
        }
        if (trait.armdisarm) {
            traits.push("action.devices.traits.ArmDisarm");
        }
        if (trait.brightness) {
            traits.push("action.devices.traits.Brightness");
        }
        if (trait.camerastream) {
            traits.push("action.devices.traits.CameraStream");
        }
        if (trait.channel) {
            traits.push("action.devices.traits.Channel");
        }
        if (trait.colorsetting) {
            traits.push("action.devices.traits.ColorSetting");
        }
        if (trait.cook) {
            traits.push("action.devices.traits.Cook");
        }
        if (trait.dispense) {
            traits.push("action.devices.traits.Dispense");
        }
        if (trait.dock) {
            traits.push("action.devices.traits.Dock");
        }
        if (trait.energystorage) {
            traits.push("action.devices.traits.EnergyStorage");
        }
        if (trait.fanspeed) {
            traits.push("action.devices.traits.FanSpeed");
        }
        if (trait.fill) {
            traits.push("action.devices.traits.Fill");
        }
        if (trait.humiditysetting) {
            traits.push("action.devices.traits.HumiditySetting");
        }
        if (trait.inputselector) {
            traits.push("action.devices.traits.InputSelector");
        }
        if (trait.lighteffects) {
            traits.push("action.devices.traits.LightEffects");
        }
        if (trait.locator) {
            traits.push("action.devices.traits.Locator");
        }
        if (trait.lockunlock) {
            traits.push("action.devices.traits.LockUnlock");
        }
        if (trait.mediastate) {
            traits.push("action.devices.traits.MediaState");
        }
        if (trait.modes) {
            traits.push("action.devices.traits.Modes");
        }
        if (trait.networkcontrol) {
            traits.push("action.devices.traits.NetworkControl");
        }
        if (trait.objectdetection) {
            traits.push("action.devices.traits.ObjectDetection");
        }
        if (trait.onoff) {
            traits.push("action.devices.traits.OnOff");
        }
        if (trait.openclose) {
            traits.push("action.devices.traits.OpenClose");
        }
        if (trait.reboot) {
            traits.push("action.devices.traits.Reboot");
        }
        if (trait.rotation) {
            traits.push("action.devices.traits.Rotation");
        }
        if (trait.runcycle) {
            traits.push("action.devices.traits.RunCycle");
        }
        if (trait.sensorstate) {
            traits.push("action.devices.traits.SensorState");
        }
        if (trait.scene) {
            traits.push("action.devices.traits.Scene");
        }
        if (trait.softwareupdate) {
            traits.push("action.devices.traits.SoftwareUpdate");
        }
        if (trait.startstop) {
            traits.push("action.devices.traits.StartStop");
        }
        if (trait.statusreport) {
            traits.push("action.devices.traits.StatusReport");
        }
        if (trait.temperaturecontrol) {
            traits.push("action.devices.traits.TemperatureControl");
        }
        if (trait.temperaturesetting) {
            traits.push("action.devices.traits.TemperatureSetting");
        }
        if (trait.timer) {
            traits.push("action.devices.traits.Timer");
        }
        if (trait.toggles) {
            traits.push("action.devices.traits.Toggles");
        }
        if (trait.transportcontrol) {
            traits.push("action.devices.traits.TransportControl");
        }
        if (trait.volume) {
            traits.push("action.devices.traits.Volume");
        }
        return traits;
    }
    /**
     * Updates the states of this device with new values.
     *
     * @param {Object} from_states - The new states values.
     * @returns {string[]} - Array of the state keys that were modified.
     */
    updateState(from_states) {
        const me = this;
        let modified = [];
        me._debug('updateState current state ' + JSON.stringify(me.states));
        Object.keys(me.state_types).forEach(key => {
            if (Object.prototype.hasOwnProperty.call(from_states, key)) {
                let o_modified = me.setState(key, from_states[key], me.states, me.state_types[key]);
                if (o_modified) {
                    me._debug('.updateState set "' + key + '" to ' + JSON.stringify(from_states[key]));
                    modified.push(o_modified);
                }
            }
        });
        let thermostat_modified = false;
        if (modified.includes("thermostatTemperatureSetpoint")) {
            me.thermostat_temperature_setpoint = me.states.thermostatTemperatureSetpoint;
            thermostat_modified = true;
        }
        if (modified.includes("thermostatTemperatureSetpointLow")) {
            me.thermostat_temperature_setpoint_low = me.states.thermostatTemperatureSetpointLow;
            thermostat_modified = true;
        }
        if (modified.includes("thermostatTemperatureSetpointHigh")) {
            me.thermostat_temperature_setpoint_hight = me.states.thermostatTemperatureSetpointHigh;
            thermostat_modified = true;
        }
        if (thermostat_modified | modified.includes("thermostatMode")) {
            let keys_to_update = [];
            if (me.states.thermostatMode === 'heatcool') {
                keys_to_update = ['thermostatTemperatureSetpointLow', 'thermostatTemperatureSetpointHigh'];
                from_states = {
                    thermostatTemperatureSetpointLow: me.thermostat_temperature_setpoint_low,
                    thermostatTemperatureSetpointHigh: me.thermostat_temperature_setpoint_hight
                };
            }
            else {
                keys_to_update = ['thermostatTemperatureSetpoint'];
                from_states = {
                    thermostatTemperatureSetpoint: me.thermostat_temperature_setpoint
                };
            }
            keys_to_update.forEach(key => {
                if (me.setState(key, from_states[key], me.states, me.state_types[key])) {
                    me._debug('.updateState: set "' + key + '" to ' + JSON.stringify(me.states[key]));
                    modified.push(key);
                }
            });
        }
        me._debug('.updateState: new State ' + JSON.stringify(modified) + ' = ' + JSON.stringify(me.states));
        return modified;
    }
    //
    //
    //
    //
    cloneObject(to_obj, from_obj, state_values) {
        const me = this;
        let differs = false;
        Object.keys(state_values).forEach(function (key) {
            const value_type = typeof state_values[key] === 'number' ? state_values[key] : state_values[key].type;
            const default_value_defined = typeof state_values[key].defaultValue !== 'undefined';
            const new_value = typeof from_obj[key] !== 'undefined' ? from_obj[key] : state_values[key].defaultValue;
            if ((typeof from_obj[key] !== 'undefined' && from_obj[key] != null) || default_value_defined) {
                if (me.setState(key, new_value, to_obj, state_values[key] || {})) {
                    differs = true;
                }
            }
            else if (!(value_type & Formats_js_1.Formats.MANDATORY)) {
                delete to_obj[key];
            }
        });
        return differs;
    }
    //
    //
    //
    //
    setState(key, value, state, state_type) {
        const me = this;
        let differs = false;
        let old_state = typeof state === 'object' ? state[key] : {};
        let new_state = undefined;
        if (typeof state_type === 'number') {
            state_type = {
                type: state_type
            };
        }
        const exclusive_states = state_type.exclusiveStates || [];
        if (value == null) {
            if (state_type.type & Formats_js_1.Formats.MANDATORY) {
                me.error("key " + key + " is mandatory.");
            }
            else if (Object.prototype.hasOwnProperty.call(state, key)) {
                delete state[key];
                differs = key;
            }
        }
        else if (state_type.type & Formats_js_1.Formats.ARRAY) {
            if (!Array.isArray(value)) {
                value = [value];
            }
            // checks array
            if (!(state_type.type & Formats_js_1.Formats.OBJECT)) {
                let new_arr = [];
                let old_arr = Array.isArray(old_state) ? old_state : [];
                const allowed_values = state_type.values;
                value.forEach((elm, idx) => {
                    let new_val = Formats_js_1.Formats.formatValue(key + '[' + idx + ']', elm, state_type.type & Formats_js_1.Formats.PRIMITIVE);
                    if (state_type.upperCase && new_val) {
                        new_val = new_val.toUpperCase();
                    }
                    if (new_val !== undefined && new_val !== null && (allowed_values === undefined || allowed_values.includes(new_val))) {
                        new_arr.push(new_val);
                        if (old_arr.length > idx) {
                            if (old_arr[idx] != new_val) {
                                differs = key;
                            }
                        }
                        else {
                            differs = key;
                        }
                    }
                    else {
                        differs = key;
                    }
                });
                state[key] = new_arr;
            }
            else {
                // structure check
                let new_arr = [];
                let old_arr = Array.isArray(old_state) ? old_state : [];
                let key_id = state_type.keyId || undefined;
                let add_if_missing = typeof state_type.addIfMissing === 'boolean' ? state_type.addIfMissing : true;
                let remove_if_no_data;
                if (typeof state_type.removeIfNoData === 'boolean') {
                    remove_if_no_data = state_type.removeIfNoData;
                }
                else {
                    remove_if_no_data = !(state_type.type & Formats_js_1.Formats.MANDATORY);
                }
                let is_valid_key;
                let replace_all = state_type.replaceAll || key_id === undefined;
                if (typeof state_type.isValidKey === 'function') {
                    is_valid_key = state_type.isValidKey;
                }
                else {
                    is_valid_key = true;
                }
                value.forEach((new_obj, idx) => {
                    let cur_obj;
                    if (key_id) {
                        let f_arr;
                        let cloned_net_obj = {};
                        me.cloneObject(cloned_net_obj, new_obj, state_type.attributes);
                        if (typeof key_id === 'string') {
                            f_arr = old_arr.filter(obj => { return obj[key_id] === cloned_net_obj[key_id]; });
                        }
                        else {
                            f_arr = old_arr.filter(obj => {
                                let obj_equal = true;
                                key_id.forEach(key_idi => {
                                    if (obj[key_idi] !== cloned_net_obj[key_idi]) {
                                        obj_equal = false;
                                    }
                                });
                                return obj_equal;
                            });
                        }
                        if (f_arr.length > 1) {
                            me.error('More than one "' + key + '" for "' + key_id + '" "' + cloned_net_obj[key_id] + '"');
                        }
                        else if (f_arr.length > 0) {
                            cur_obj = f_arr[0];
                        }
                        else if (add_if_missing) {
                            let key_id0 = typeof key_id === 'string' ? key_id : key_id[0];
                            let key1 = is_valid_key(cloned_net_obj[key_id0]);
                            if (key1) {
                                cur_obj = {};
                                if (typeof key1 === 'string') {
                                    new_obj[key_id0] = key1;
                                }
                                old_arr.push(cur_obj);
                            }
                        }
                    }
                    else {
                        cur_obj = old_arr[idx];
                        if (cur_obj === undefined && add_if_missing) {
                            cur_obj = {};
                        }
                    }
                    if (cur_obj !== undefined) {
                        if (me.cloneObject(cur_obj, new_obj, state_type.attributes)) {
                            differs = key;
                        }
                        if (Object.keys(cur_obj).length > 0) {
                            new_arr.push(cur_obj);
                        }
                        else {
                            differs = key; // ??
                        }
                    }
                });
                if (replace_all && new_arr.length != old_arr.length) {
                    differs = key;
                }
                state[key] = replace_all ? new_arr : old_arr;
                if (remove_if_no_data && state[key].length === 0) {
                    delete state[key];
                }
            }
        }
        else if (state_type.type & Formats_js_1.Formats.OBJECT) {
            if (Array.isArray(value)) {
                me.error('key "' + key + '" must be an object.');
            }
            else {
                if (state[key] === undefined) {
                    state[key] = {};
                    old_state = state[key];
                }
                let mandatory_to_delete = [];
                let o_differs = [];
                Object.keys(state_type.attributes).forEach(function (ikey) {
                    // console.log("---> Attributes key " + ikey + " " + JSON.stringify(value[ikey]));
                    if (typeof value[ikey] !== 'undefined' && value[ikey] != null) {
                        if (typeof old_state[ikey] == 'undefined') {
                            old_state[ikey] = {};
                        }
                        if (me.setState(ikey, value[ikey], old_state, state_type.attributes[ikey])) {
                            o_differs.push(ikey);
                            differs = o_differs;
                        }
                    }
                    else if (state_type.type & Formats_js_1.Formats.DELETE_MISSING) {
                        const a_state_type = typeof state_type.attributes[ikey] === 'number' ? state_type.attributes[ikey] : state_type.attributes[ikey].type;
                        // console.log("a_state " + JSON.stringify(a_state_type));
                        if (a_state_type & Formats_js_1.Formats.MANDATORY) {
                            mandatory_to_delete.push(ikey);
                        }
                        else {
                            if (typeof state[ikey] != 'undefined') {
                                o_differs.push(ikey);
                                differs = o_differs;
                            }
                            delete state[key][ikey];
                            // console.log("Deleted " + ikey + " " + JSON.stringify(state[key]));
                        }
                    }
                });
                mandatory_to_delete.forEach(ikey => {
                    let exclusive_state_found = false;
                    exclusive_states.forEach(e_state => {
                        if (typeof state[e_state] !== 'undefined') {
                            exclusive_state_found = false;
                        }
                    });
                    if (!exclusive_state_found) {
                        if (typeof state[ikey] != 'undefined') {
                            o_differs.push(ikey);
                            differs = o_differs;
                        }
                        delete state[key][ikey];
                    }
                    else {
                        me.error('key "' + key + '.' + ikey + '" is mandatory.');
                    }
                });
                if (Object.keys(differs).length > 0) {
                    new_state = state[key];
                }
            }
        }
        else if (state_type.type & Formats_js_1.Formats.COPY_OBJECT) {
            if (typeof value !== 'object' || Array.isArray(value)) {
                me.error('key "' + key + '" must be an object.');
            }
            else {
                Object.keys(old_state).forEach(function (ikey) {
                    if (typeof value[ikey] !== 'undefined') {
                        if (me.setState(ikey, value[ikey], old_state, state_type.type - Formats_js_1.Formats.COPY_OBJECT)) {
                            differs = key;
                        }
                    }
                });
            }
        }
        else {
            new_state = Formats_js_1.Formats.formatValue(key, value, state_type.type & Formats_js_1.Formats.PRIMITIVE, state_type.defaultValue);
            if (state_type.min !== undefined && new_state < state_type.min) {
                me.error('key "' + key + '" must be greather or equal than ' + state_type.min);
                new_state = undefined;
            }
            if (new_state !== undefined && state_type.max !== undefined && new_state > state_type.max) {
                me.error('key "' + key + '" must be lower or equal than ' + state_type.max);
                new_state = undefined;
            }
            if (new_state !== undefined && Array.isArray(state_type.values) && !state_type.values.includes(new_state)) {
                me.error('key "' + key + '" must be one of ' + JSON.stringify(state_type.values));
                if (state_type.values.includes(state[key])) {
                    new_state = undefined;
                }
                else {
                    new_state = state_type.defaultValue;
                }
            }
        }
        if (new_state !== undefined && !(state_type.type & (Formats_js_1.Formats.OBJECT | Formats_js_1.Formats.ARRAY))) {
            if (old_state !== new_state) {
                differs = key;
            }
            state[key] = new_state;
        }
        if (new_state !== undefined && exclusive_states.length > 0) {
            exclusive_states.forEach(rkey => delete state[rkey]);
        }
        if (Array.isArray(differs)) {
            let o_differs = {};
            o_differs[key] = differs;
            return o_differs;
        }
        return differs;
    }
    /**
     * Converts an array of applications to the format expected by Google.
     *
     * @see key_name_synonym
     * @param {string[]} json_data - The items to convert
     * @returns {object[]} - Object with items as expected by Google
     */
    to_available_applications(json_data) {
        return this.key_name_synonym("Applications", json_data, 'key', 'names', 'name_synonym');
    }
    /**
     * Converts an array of arm levels to the format expected by Google.
     *
     * @see key_name_synonym
     * @param {string[]} json_data - The items to convert
     * @returns {object[]} - Object with items as expected by Google
     */
    to_available_arm_levels(json_data) {
        return this.key_name_synonym("Arm levels", json_data, 'level_name', 'level_values', 'level_synonym');
    }
    /**
     * Converts an array of channels to the format expected by Google.
     *
     * @see key_name_synonym
     * @param {string[]} json_data - The items to convert
     * @returns {object[]} - Object with items as expected by Google
     */
    to_available_channels(json_data) {
        let f = function (data_in, data_out) {
            if (typeof data_in.number === 'string') {
                data_out.number = data_in.number;
            }
            return true;
        };
        return this.key_name_synonym("Channels", json_data, 'key', 'names', undefined, f);
    }
    /**
     * Converts an array of food presets to the format expected by Google.
     *
     * @see key_name_synonym
     * @param {string[]} json_data - The items to convert
     * @returns {object[]} - Object with items as expected by Google
     */
    to_food_presets(json_data) {
        let f = function (data_in, data_out) {
            if (Array.isArray(data_in.supported_units)) {
                data_out.supported_units = [];
                data_in.supported_units.forEach(unit => {
                    if (typeof unit === 'string' && !data_out.supported_units.includes(unit.trim().toUpperCase()) && COOK_SUPPORTED_UNITS.includes(unit.trim().toUpperCase())) {
                        data_out.supported_units.push(unit.trim().toUpperCase());
                    }
                });
                return data_out.supported_units.length > 0;
            }
            return false;
        };
        return this.key_name_synonym("Food presets", json_data, 'food_preset_name', 'food_synonyms', 'synonym', f);
    }
    /**
     * Converts an array of dispense items to the format expected by Google.
     *
     * @see key_name_synonym
     * @param {string[]} json_data - The items to convert
     * @returns {object[]} - Object with items as expected by Google
     */
    to_supported_dispense_items(json_data) {
        let f = function (data_in, data_out) {
            if (Array.isArray(data_in.supported_units)) {
                data_out.supported_units = [];
                data_in.supported_units.forEach(unit => {
                    if (typeof unit === 'string' && !data_out.supported_units.includes(unit.trim().toUpperCase()) && DISPENSE_SUPPORTED_UNITS.includes(unit.trim().toUpperCase())) {
                        data_out.supported_units.push(unit.trim().toUpperCase());
                    }
                });
                if (typeof data_in.default_portion.amount === 'number' && typeof data_in.default_portion.unit === 'string' && data_out.supported_units.includes(data_in.default_portion.unit.trim().toUpperCase())) {
                    data_out.default_portion = {
                        amount: data_in.default_portion.amount,
                        unit: data_in.default_portion.unit.trim().toUpperCase()
                    };
                    return data_out.supported_units.length > 0;
                }
            }
            return false;
        };
        return this.key_name_synonym("Dispense items", json_data, 'item_name', 'item_name_synonyms', 'synonyms', f);
    }
    /**
     * Converts an array of dispense presets to the format expected by Google.
     *
     * @see key_name_synonym
     * @param {string[]} json_data - The items to convert
     * @returns {object[]} - Object with items as expected by Google
     */
    to_supported_dispense_presets(json_data) {
        return this.key_name_synonym("Dispense presets", json_data, 'preset_name', 'preset_name_synonyms', 'synonyms');
    }
    /**
     * Converts an array of fan speeds to the format expected by Google.
     *
     * @see key_name_synonym
     * @param {string[]} json_data - The items to convert
     * @returns {object[]} - Object with items as expected by Google
     */
    to_available_fan_speeds(json_data) {
        return this.key_name_synonym("Fan speeds", json_data, 'speed_name', 'speed_values', 'speed_synonym');
    }
    /**
     * Converts an array of fill levels to the format expected by Google.
     *
     * @see key_name_synonym
     * @param {string[]} json_data - The items to convert
     * @returns {object[]} - Object with items as expected by Google
     */
    to_available_fill_levels(json_data) {
        return this.key_name_synonym("Fill levels", json_data, 'level_name', 'level_values', 'level_synonym');
    }
    /**
     * Converts an array of inputs to the format expected by Google.
     *
     * @see key_name_synonym
     * @param {string[]} json_data - The items to convert
     * @returns {object[]} - Object with items as expected by Google
     */
    to_available_inputs(json_data) {
        return this.key_name_synonym("Inputs", json_data, 'key', 'names', 'name_synonym');
    }
    /**
     * Converts an array of toggles to the format expected by Google.
     *
     * @see key_name_synonym
     * @param {string[]} json_data - The items to convert
     * @returns {object[]} - Object with items as expected by Google
     */
    to_available_modes(json_data) {
        let me = this;
        let f = function (data_in, data_out) {
            if (Array.isArray(data_in.settings)) {
                data_out.settings = me.key_name_synonym("Modes settings", data_in.settings, 'setting_name', 'setting_values', 'setting_synonym');
                if (typeof data_in.ordered === 'boolean') {
                    data_out.ordered = data_in.ordered;
                }
                return data_out.settings.length > 0;
            }
            return false;
        };
        return this.key_name_synonym("Modes", json_data, 'name', 'name_values', 'name_synonym', f);
    }
    /**
     * Converts an array of toggles to the format expected by Google.
     *
     * @see key_name_synonym
     * @param {string[]} json_data - The items to convert
     * @returns {object[]} - Object with items as expected by Google
     */
    to_available_toggles(json_data) {
        return this.key_name_synonym("Toggles", json_data, 'name', 'name_values', 'name_synonym');
    }
    /**
     * Converts an array to a list of synonyms in the format expected by Google. Used to specify the list of items
     * in AppSelector, Modes or similar traits.
     *
     * Example output:
     * [
     *     {"name":"Heating", "name_values":[{"lang":"en", "name_synonym":["Heating"]}]},
     *     {"name":"Cooling", "name_values":[{"lang":"en", "name_synonym":["Cooling"]}]}
     * ]
     *
     * The keys "name", "name_values" and "name_synonym" can be renamed via the parameters key1, key2 and key3.
     *
     * @param {string} type - Type name to use in debug messages
     * @param {string[] | string} json_data - Array of items to convert
     * @param {string} key1 - Key for item name ("name" in example)
     * @param {string} key2 - Key for synonyms object ("name_values" in example)
     * @param {string} key3 - Key for synonyms array ("name_synonyms" in example)
     * @param {Function} manage_other_fields - Optional callback function to handle other fields
     * @returns {object[]} - Object with items as expected by Google
     */
    key_name_synonym(type, json_data, key1, key2, key3, manage_other_fields = undefined) {
        const me = this;
        me._debug(".key_name_synonym: Parsing " + type);
        let new_data = [];
        if (Array.isArray(json_data)) {
            if (typeof manage_other_fields !== 'function') {
                manage_other_fields = function () { return true; };
            }
            json_data.forEach((rec, pos) => {
                if (typeof rec === 'string') {
                    let val = rec;
                    rec = {};
                    rec[key1] = val;
                }
                else if (Array.isArray(rec)) {
                    if (rec.length > 0) {
                        let arr = rec.filter(element => typeof element === 'string' && element.trim().length > 0);
                        if (arr.length > 0) {
                            rec = {};
                            rec[key1] = arr[0];
                            if (key3) {
                                rec[key2] = [{}];
                                rec[key2][0][key3] = arr;
                            }
                            else {
                                rec[key2] = arr;
                            }
                        }
                    }
                }
                else if (typeof rec[key2] !== 'undefined') {
                    let val2 = rec[key2];
                    if (typeof val2 === 'string') {
                        val2 = [val2];
                    }
                    if (Array.isArray(val2)) {
                        let arr = val2.filter(element => typeof element === 'string' && element.trim().length > 0);
                        if (arr.length > 0) {
                            if (key3) {
                                rec[key2] = [{}];
                                rec[key2][0][key3] = arr;
                            }
                            else {
                                rec[key2] = arr;
                            }
                            if (typeof rec[key1] === 'undefined') {
                                rec[key1] = arr[0];
                            }
                        }
                    }
                }
                if (typeof rec[key1] === 'undefined' && typeof rec[key2] !== 'undefined' && Array.isArray(rec[key2])) {
                    let val2 = rec[key2];
                    if (typeof val2 === 'string') {
                        val2 = [val2];
                    }
                    let arr = val2.filter(element => typeof element === 'string' && element.trim().length > 0);
                    if (arr.length > 0 && arr[0]) {
                        rec[rec1] = arr[0];
                    }
                }
                if (typeof rec[key1] === 'string' && rec[key1].trim()) {
                    let new_rec = {};
                    new_rec[key1] = rec[key1].trim();
                    let found = new_data.filter(element => element[key1] === new_rec[key1]);
                    if (found.length === 0) {
                        let val3;
                        if (typeof rec[key2] === 'string') {
                            val3 = rec[key2];
                            if (key3) {
                                rec[key2] = [{}];
                                rec[key2][0][key3] = [val3];
                            }
                            else {
                                rec[key2] = [val3];
                            }
                        }
                        else if (!Array.isArray(rec[key2])) {
                            val3 = new_rec[key1];
                            if (key3) {
                                rec[key2] = [{}];
                                rec[key2][0][key3] = [val3];
                            }
                            else {
                                rec[key2] = [val3];
                            }
                        }
                        new_rec[key2] = [];
                        rec[key2].forEach(names => {
                            if (key3) {
                                if (typeof names === 'string') {
                                    let val2 = names;
                                    names = {};
                                    names[key3] = [val2];
                                }
                                let lang = typeof names.lang === 'string' ? names.lang.trim() : '';
                                if (lang.length === 0) {
                                    lang = me.lang;
                                }
                                if (Array.isArray(names[key3])) {
                                    let name_synonym = [];
                                    names[key3].forEach(name => {
                                        if (typeof name === 'string' && name.trim().length > 0 && !name_synonym.includes(name.trim())) {
                                            name_synonym.push(name.trim());
                                        }
                                    });
                                    if (name_synonym.length > 0) {
                                        let new_key2 = {};
                                        new_key2['lang'] = lang;
                                        new_key2[key3] = name_synonym;
                                        if (manage_other_fields(rec, new_rec)) {
                                            new_rec[key2].push(new_key2);
                                        }
                                    }
                                }
                                else {
                                    me.error("key_name_synonym error " + type + ": missing array key " + key3 + " for " + rec[key1]);
                                }
                            }
                            else {
                                if (typeof names === 'string' && names.trim() && !new_rec[key2].includes(names.trim())) {
                                    new_rec[key2].push(names.trim());
                                }
                                else {
                                    me.error("key_name_synonym error " + type + ": missing array key " + key2 + " for " + rec[key1]);
                                }
                            }
                        });
                        if (new_rec[key2].length > 0) {
                            let ok = true;
                            if (!key3) {
                                ok = manage_other_fields(rec, new_rec);
                                if (!ok) {
                                    me.error("key_name_synonym error " + type + ": manage_other_fields error for " + rec[key1]);
                                }
                            }
                            if (ok) {
                                new_data.push(new_rec);
                            }
                        }
                        else {
                            me.error("key_name_synonym error " + type + ": " + key2 + " empty for " + rec[key1]);
                        }
                    }
                    else {
                        me.error("key_name_synonym error " + type + ": " + new_rec[key1] + " already exists");
                    }
                }
                else {
                    me.error("key_name_synonym error " + type + ": missing key " + key1 + " at position " + pos);
                }
            });
        }
        me._debug(".key_name_synonym parser " + type + " : " + JSON.stringify(new_data));
        return new_data;
    }
    parseJson(text, json_text, defaultValue) {
        this._debug('.parseJson: ' + text);
        if (json_text.trim().length > 0) {
            try {
                return JSON.parse(json_text);
            }
            catch (err) {
                this._debug(".parseJson error " + text + ': ' + JSON.stringify(err));
                this.error('Error on parsing ' + text + ': ' + err.toString());
            }
        }
        return defaultValue;
    }
    loadJson(text, filename, defaultValue) {
        if (filename) {
            this._debug('.loadJson: ' + text);
            let full_filename;
            if (!filename.startsWith(path_1.default.sep)) {
                const userDir = SmartHome_js_1.RED.settings.userDir;
                full_filename = path_1.default.join(userDir, filename);
            }
            else {
                full_filename = filename;
            }
            this._debug('.loadJson: filename ' + full_filename);
            try {
                let jsonFile = fs_1.default.readFileSync(full_filename, {
                    'encoding': 'utf8',
                    'flag': fs_1.default.constants.R_OK | fs_1.default.constants.W_OK | fs_1.default.constants.O_CREAT
                });
                if (jsonFile === '') {
                    this._debug('.loadJson: file ' + filename + ' is empty');
                    return defaultValue;
                }
                else {
                    this._debug('.loadJson: data loaded');
                    const json = JSON.parse(jsonFile);
                    this._debug('.loadJson: json = ' + JSON.stringify(json));
                    return json;
                }
            }
            catch (err) {
                this._debug(".loadJson error " + text + ': ' + JSON.stringify(err));
                this.error('Error on loading ' + text + ' filename ' + filename + ': ' + err.toString());
                return defaultValue;
            }
        }
    }
    writeJson(text, filename, value) {
        if (filename) {
            this._debug('.writeJson: ' + text);
            if (!filename.startsWith(path_1.default.sep)) {
                const userDir = SmartHome_js_1.RED.settings.userDir;
                filename = path_1.default.join(userDir, filename);
            }
            this._debug('.writeJson: filename ' + filename);
            if (typeof value === 'object') {
                value = JSON.stringify(value);
            }
            try {
                fs_1.default.writeFileSync(filename, value, {
                    'encoding': 'utf8',
                    'flag': fs_1.default.constants.W_OK | fs_1.default.constants.O_CREAT | fs_1.default.constants.O_TRUNC
                });
                this._debug('.writeJson: ' + text + ' saved');
                return true;
            }
            catch (err) {
                this._debug(".writeJson error " + text + ': ' + JSON.stringify(err));
                this.error('Error on saving ' + text + ' filename + ' + filename + ': ' + err.toString());
                return false;
            }
        }
    }
    // Called by HttpActions.execCommand
    //
    //
    execCommand(command) {
        let me = this;
        let params = {};
        let executionStates = ['online'];
        const ok_result = {
            'params': params,
            'executionStates': executionStates
        };
        me._debug(".execCommand: command " + JSON.stringify(command));
        if (me.errorCode) {
            me._debug(".execCommand: errorCode " + JSON.stringify(me.errorCode));
            return {
                status: 'ERROR',
                errorCode: me.errorCode
            };
        }
        me._debug(".execCommand: states " + JSON.stringify(me.states));
        // me._debug(".execCommand: device " +  JSON.stringify(device));
        // me._debug(".execCommand: me.device " +  JSON.stringify(me.device));
        if (me.states.online !== true) {
            return {
                status: 'ERROR',
                errorCode: 'deviceOffline'
            };
        }
        let challenge_type = '';
        let challenge_pin = '';
        switch (command.command) {
            case 'action.devices.commands.appInstall':
            case 'action.devices.commands.appSearch':
            case 'action.devices.commands.appSelect':
                challenge_type = me.ct_appselector;
                challenge_pin = me.pin_appselector;
                break;
            case 'action.devices.commands.ArmDisarm':
                challenge_type = me.ct_armdisarm;
                challenge_pin = me.pin_armdisarm;
                break;
            case 'action.devices.commands.BrightnessAbsolute':
            case 'action.devices.commands.BrightnessRelative':
                challenge_type = me.ct_brightness;
                challenge_pin = me.pin_brightness;
                break;
            case 'action.devices.commands.GetCameraStream':
                challenge_type = me.ct_camerastream;
                challenge_pin = me.pin_camerastream;
                break;
            case 'action.devices.commands.selectChannel':
            case 'action.devices.commands.relativeChannel':
            case 'action.devices.commands.returnChannel':
                challenge_type = me.ct_channel;
                challenge_pin = me.pin_channel;
                break;
            case 'action.devices.commands.ColorAbsolute':
                challenge_type = me.ct_colorsetting;
                challenge_pin = me.pin_colorsetting;
                break;
            case 'action.devices.commands.Cook':
                challenge_type = me.ct_cook;
                challenge_pin = me.pin_cook;
                break;
            case 'action.devices.commands.Dispense':
                challenge_type = me.ct_dispense;
                challenge_pin = me.pin_dispense;
                break;
            case 'action.devices.commands.Dock':
                challenge_type = me.ct_dock;
                challenge_pin = me.pin_dock;
                break;
            case 'action.devices.commands.Charge':
                challenge_type = me.ct_energystorage;
                challenge_pin = me.pin_energystorage;
                break;
            case 'action.devices.commands.SetFanSpeed':
            case 'action.devices.commands.SetFanSpeedRelative':
            case 'action.devices.commands.Reverse':
                challenge_type = me.ct_fanspeed;
                challenge_pin = me.pin_fanspeed;
                break;
            case 'action.devices.commands.Fill':
                challenge_type = me.ct_fill;
                challenge_pin = me.pin_fill;
                break;
            case 'action.devices.commands.SetHumidity':
            case 'action.devices.commands.HumidityRelative':
                challenge_type = me.ct_humiditysetting;
                challenge_pin = me.pin_humiditysetting;
                break;
            case 'action.devices.commands.SetInput':
            case 'action.devices.commands.NextInput':
            case 'action.devices.commands.PreviousInput':
                challenge_type = me.ct_inputselector;
                challenge_pin = me.pin_inputselector;
                break;
            case 'action.devices.commands.ColorLoop':
            case 'action.devices.commands.Sleep':
            case 'action.devices.commands.StopEffect':
            case 'action.devices.commands.Wake':
                challenge_type = me.ct_colorsetting;
                challenge_pin = me.pin_colorsetting;
                break;
            case 'action.devices.commands.Locate':
                challenge_type = me.ct_locator;
                challenge_pin = me.pin_locator;
                break;
            case 'action.devices.commands.LockUnlock':
                challenge_type = me.ct_lockunlock;
                challenge_pin = me.pin_lockunlock;
                break;
            case 'action.devices.commands.SetModes':
                challenge_type = me.ct_modes;
                challenge_pin = me.pin_modes;
                break;
            case 'action.devices.commands.EnableDisableGuestNetwork':
            case 'action.devices.commands.EnableDisableNetworkProfile':
            case 'action.devices.commands.GetGuestNetworkPassword':
            case 'action.devices.commands.TestNetworkSpeed':
                challenge_type = me.ct_networkcontrol;
                challenge_pin = me.pin_networkcontrol;
                break;
            case 'action.devices.commands.OnOff':
                challenge_type = me.ct_onoff;
                challenge_pin = me.pin_onoff;
                break;
            case 'action.devices.commands.OpenClose':
            case 'action.devices.commands.OpenCloseRelative':
                challenge_type = me.ct_openclose;
                challenge_pin = me.pin_openclose;
                break;
            case 'action.devices.commands.Reboot':
                challenge_type = me.ct_reboot;
                challenge_pin = me.pin_reboot;
                break;
            case 'action.devices.commands.RotateAbsolute':
                challenge_type = me.ct_rotation;
                challenge_pin = me.pin_rotation;
                break;
            case 'action.devices.commands.ActivateScene':
                challenge_type = me.ct_scene;
                challenge_pin = me.pin_scene;
                break;
            case 'action.devices.commands.SoftwareUpdate':
                challenge_type = me.ct_softwareupdate;
                challenge_pin = me.pin_softwareupdate;
                break;
            case 'action.devices.commands.StartStop':
            case 'action.devices.commands.PauseUnpause':
                challenge_type = me.ct_startstop;
                challenge_pin = me.pin_startstop;
                break;
            case 'action.devices.commands.SetTemperature':
                challenge_type = me.ct_temperaturecontrol;
                challenge_pin = me.pin_temperaturecontrol;
                break;
            case 'action.devices.commands.ThermostatTemperatureSetpoint':
            case 'action.devices.commands.ThermostatTemperatureSetRange':
            case 'action.devices.commands.ThermostatSetMode':
            case 'action.devices.commands.TemperatureRelative':
                challenge_type = me.ct_temperaturesetting;
                challenge_pin = me.pin_temperaturesetting;
                break;
            case 'action.devices.commands.TimerStart':
            case 'action.devices.commands.TimerAdjust':
            case 'action.devices.commands.TimerPause':
            case 'action.devices.commands.TimerResume':
            case 'action.devices.commands.TimerCancel':
                challenge_type = me.ct_timer;
                challenge_pin = me.pin_timer;
                break;
            case 'action.devices.commands.SetToggles':
                challenge_type = me.ct_toggles;
                challenge_pin = me.pin_toggles;
                break;
            case 'action.devices.commands.mediaStop':
            case 'action.devices.commands.mediaNext':
            case 'action.devices.commands.mediaPrevious':
            case 'action.devices.commands.mediaPause':
            case 'action.devices.commands.mediaResume':
            case 'action.devices.commands.mediaSeekRelative':
            case 'action.devices.commands.mediaSeekToPosition':
            case 'action.devices.commands.mediaRepeatMode':
            case 'action.devices.commands.mediaShuffle':
            case 'action.devices.commands.mediaClosedCaptioningOn':
            case 'action.devices.commands.mediaClosedCaptioningOff':
                challenge_type = me.ct_transportcontrol;
                challenge_pin = me.pin_transportcontrol;
                break;
            case 'action.devices.commands.mute':
            case 'action.devices.commands.setVolume':
            case 'action.devices.commands.volumeRelative':
                challenge_type = me.ct_volume;
                challenge_pin = me.pin_volume;
                break;
        }
        const challenge = command.challenge || {};
        challenge_pin = challenge_pin.trim();
        if (challenge_type === 'ackNeeded') {
            if (challenge.ack !== true) {
                // ackNeeded with trait states
                return {
                    status: 'ERROR',
                    errorCode: "challengeNeeded",
                    challengeNeeded: {
                        type: "ackNeeded"
                    }
                };
            }
        }
        else if (challenge_type === 'pinNeeded') {
            if (challenge_pin.length === 0) {
                // challengeFailedNotSetup with trait states
                return {
                    status: 'ERROR',
                    errorCode: "challengeFailedNotSetup"
                };
            }
            if (challenge.pin === undefined) {
                me.send({
                    topic: 'ChallengePin',
                    payload: {
                        name: this.name,
                        pin: challenge_pin,
                        command: command.command,
                        topic: me.topicOut,
                    }
                });
                // ackNeeded with trait states
                return {
                    status: 'ERROR',
                    errorCode: "challengeNeeded",
                    challengeNeeded: {
                        type: "pinNeeded"
                    }
                };
            }
            if (challenge.pin !== challenge_pin) {
                return {
                    status: 'ERROR',
                    errorCode: "challengeNeeded",
                    challengeNeeded: {
                        type: "challengeFailedPinNeeded"
                    }
                };
            }
        }
        // Applications
        if ((command.command === 'action.devices.commands.appInstall') ||
            (command.command === 'action.devices.commands.appSearch') ||
            (command.command === 'action.devices.commands.appSelect')) {
            let foundApplication = null;
            for (const application of this.available_applications) {
                // Try to identify app by key
                if (Object.prototype.hasOwnProperty.call(command.params, 'newApplication') &&
                    command.params['newApplication'].localeCompare(application.key, undefined, { 'sensitivity': 'base' }) === 0) {
                    foundApplication = application;
                    break;
                }
                // Try to identify app by name
                if (Object.prototype.hasOwnProperty.call(command.params, 'newApplicationName')) {
                    for (const name of application.names) {
                        if (name.name_synonym.some((nameSynonym) => nameSynonym.localeCompare(command.params['newApplicationName'], undefined, { 'sensitivity': 'base' }) === 0)) {
                            foundApplication = application;
                            break;
                        }
                    }
                }
            }
            if (command.command === 'action.devices.commands.appInstall' && foundApplication !== null) {
                return {
                    status: 'ERROR',
                    errorCode: 'alreadyInstalledApp'
                };
            }
            if (foundApplication === null) {
                return {
                    status: 'ERROR',
                    errorCode: 'noAvailableApp'
                };
            }
            params['currentApplication'] = foundApplication.key;
            executionStates.push('online', 'currentApplication');
        }
        // ColorLoop
        else if (command.command === 'action.devices.commands.ColorLoop') {
            params['activeLightEffect'] = 'colorLoop';
            executionStates.push('activeLightEffect');
        }
        else if (command.command === 'action.devices.commands.Sleep') {
            params['activeLightEffect'] = 'sleep';
            executionStates.push('activeLightEffect');
        }
        else if (command.command === 'action.devices.commands.StopEffect') {
            params['activeLightEffect'] = '';
            executionStates.push('activeLightEffect');
        }
        else if (command.command === 'action.devices.commands.Wake') {
            params['activeLightEffect'] = 'wake';
            executionStates.push('activeLightEffect');
        }
        // Cook
        else if (command.command === 'action.devices.commands.Cook') {
            //const start = command.params['start'];
            if (Object.prototype.hasOwnProperty.call(command.params, 'cookingMode')) {
                const cooking_mode = command.params['cookingMode'];
                if (me.supported_cooking_modes.includes(cooking_mode)) {
                    params['currentCookingMode'] = cooking_mode;
                    executionStates.push('currentCookingMode');
                }
                else {
                    this.error(".execCommand unknown cooking mode " + cooking_mode);
                    return {
                        status: 'ERROR',
                        errorCode: 'transientError'
                    };
                }
            }
            let selected_preset = undefined;
            if (Object.prototype.hasOwnProperty.call(command.params, 'foodPreset')) {
                const food_preset_name = command.params['foodPreset'];
                me.food_presets.forEach(function (food_preset) {
                    if (food_preset.food_preset_name === food_preset_name) {
                        selected_preset = food_preset;
                    }
                });
                if (selected_preset !== undefined) {
                    params['currentFoodPreset'] = food_preset_name;
                    executionStates.push('currentFoodPreset');
                }
                else {
                    this.error(".execCommand unknown food preset " + food_preset_name);
                    return {
                        status: 'ERROR',
                        errorCode: 'unknownFoodPreset'
                    };
                }
            }
            if (Object.prototype.hasOwnProperty.call(command.params, 'quantity')) {
                const quantity = command.params['quantity'];
                params['currentFoodQuantity'] = quantity;
                executionStates.push('currentFoodQuantity');
            }
            if (Object.prototype.hasOwnProperty.call(command.params, 'unit')) {
                const unit = command.params['unit'];
                if (selected_preset !== undefined) {
                    if (!selected_preset.supported_units.includes(unit)) {
                        this.error(".execCommand unknown unit " + unit);
                        return {
                            status: 'ERROR',
                            errorCode: 'transientError'
                        };
                    }
                }
                params['currentFoodUnit'] = unit;
                executionStates.push('currentFoodUnit');
            }
        }
        // Dispense
        else if (command.command === 'action.devices.commands.Dispense') {
            const item_name = command.params['item'] || '';
            //const amount = command.params['amount'] || '';
            const unit = command.params['unit'] || '';
            const preset_name = command.params['presetName'] || '';
            if (preset_name) {
                let found = false;
                me.supported_dispense_presets.forEach(function (preset) {
                    if (preset.preset_name == preset_name) {
                        found = true;
                    }
                });
                if (!found) {
                    return {
                        status: 'ERROR',
                        errorCode: 'transientError'
                    };
                }
            }
            else if (item_name) {
                let item_found = undefined;
                me.supported_dispense_items.forEach(function (item) {
                    if (item.item_name == item_name) {
                        item_found = item;
                    }
                });
                if (item_found === undefined) {
                    return {
                        status: 'ERROR',
                        errorCode: 'transientError'
                    };
                }
                if (unit && !item_found.supported_units.includes(unit)) {
                    return {
                        status: 'ERROR',
                        errorCode: 'transientError'
                    };
                }
                item_found = undefined;
                this.states.dispenseItems.forEach(function (item) {
                    if (item.itemName == item_name) {
                        if (item_found === undefined || item_found.amountRemaining.unit !== unit) {
                            item_found = item;
                        }
                    }
                });
                if (item_found === undefined) {
                    return {
                        status: 'ERROR',
                        errorCode: 'transientError'
                    };
                }
                /*
                if (unit) {
                    if (item_found.amountRemaining.unit !== unit) {
                        return {
                            status: 'ERROR',
                            errorCode: 'dispenseUnitNotSupported'
                        };
                    }
                    // Check quantity
                    if (item_found.amountRemaining.amount < amount) {
                        return {
                            status: 'ERROR',
                            errorCode: 'dispenseAmountRemainingExceeded'
                        };
                    }
                    item_found.amountRemaining.amount -= amount;
                    params['dispenseItems'] = this.states.dispenseItems;
                    executionStates.push('dispenseItems');
                }
                */
            }
        }
        // Dock
        else if (command.command === 'action.devices.commands.Dock') {
            params['isDocked'] = true;
            executionStates.push('isDocked');
        }
        // ArmLevel
        else if (command.command === 'action.devices.commands.ArmDisarm') {
            if (Object.prototype.hasOwnProperty.call(command.params, 'arm')) {
                let new_armLevel = "";
                if (Object.prototype.hasOwnProperty.call(command.params, 'armLevel')) {
                    const armLevel = command.params['armLevel'];
                    this.available_arm_levels.forEach(function (al) {
                        if (al.level_name === armLevel) {
                            new_armLevel = al.level_name;
                        }
                    });
                }
                else {
                    if (this.available_arm_levels.length > 0) {
                        new_armLevel = this.available_arm_levels[0].level_name;
                    }
                }
                if (new_armLevel === '') {
                    return {
                        status: 'ERROR',
                        errorCode: 'transientError'
                    };
                }
                params['currentArmLevel'] = new_armLevel;
                params['isArmed'] = command.params['arm'];
                executionStates.push('isArmed', 'currentArmLevel');
            }
        }
        // FanSpeed
        else if (command.command === 'action.devices.commands.SetFanSpeed') {
            if (!me.command_only_fanspeed) {
                if (Object.prototype.hasOwnProperty.call(command.params, 'fanSpeed')) {
                    const fanSpeed = command.params['fanSpeed'];
                    let new_fanspeed = '';
                    this.available_fan_speeds.forEach(function (fanspeed) {
                        if (fanspeed.speed_name === fanSpeed) {
                            new_fanspeed = fanspeed.speed_name;
                        }
                    });
                    if (new_fanspeed === '') {
                        return {
                            status: 'ERROR',
                            errorCode: 'transientError'
                        };
                    }
                    params['currentFanSpeedSetting'] = fanSpeed;
                    executionStates.push('currentFanSpeedSetting');
                }
                if (Object.prototype.hasOwnProperty.call(command.params, 'fanSpeedPercent')) {
                    const fanSpeedPercent = command.params['fanSpeedPercent'];
                    params['currentFanSpeedPercent'] = fanSpeedPercent;
                    executionStates.push('currentFanSpeedPercent');
                }
            }
        }
        else if (command.command === 'action.devices.commands.SetFanSpeedRelative') {
            /*if (Object.prototype.hasOwnProperty.call(command.params, 'fanSpeedRelativeWeight')) {
                const fanSpeedRelativeWeight = command.params['fanSpeedRelativeWeight'];
                params['currentFanSpeedPercent'] = me.states['currentFanSpeedPercent'] + fanSpeedRelativeWeight;
                executionStates.push('currentFanSpeedPercent');
            }
            if (Object.prototype.hasOwnProperty.call(command.params, 'fanSpeedRelativePercent')) {
                const fanSpeedRelativePercent = command.params['fanSpeedRelativePercent'];
                params['currentFanSpeedPercent'] = Math.round(me.states['currentFanSpeedPercent'] * (1 + fanSpeedRelativePercent / 100));
                executionStates.push('currentFanSpeedPercent');
            }*/
        }
        // LockUnlock
        else if (command.command === 'action.devices.commands.LockUnlock') {
            const lock = command.params['lock'];
            params['isLocked'] = lock;
            executionStates.push('isLocked');
        }
        // HumiditySetting
        else if (command.command === 'action.devices.commands.SetHumidity') {
            if (!me.command_only_humiditysetting) {
                const humidity = command.params['humidity'];
                params['humiditySetpointPercent'] = humidity;
                executionStates.push('humiditySetpointPercent');
            }
        }
        else if (command.command === 'action.devices.commands.HumidityRelative') {
            /*if (Object.prototype.hasOwnProperty.call(command.params, 'humidityRelativePercent')) {
                const humidityRelativePercent = command.params['humidityRelativePercent'];
                params['humiditySetpointPercent'] = Math.round(me.states['humiditySetpointPercent'] * (1 + humidityRelativePercent / 100));
                executionStates.push('humiditySetpointPercent');
            }
            if (Object.prototype.hasOwnProperty.call(command.params, 'humidityRelativeWeight')) {
                const humidityRelativeWeight = command.params['humidityRelativeWeight'];
                params['humiditySetpointPercent'] = me.states['humiditySetpointPercent'] + humidityRelativeWeight;
                executionStates.push('humiditySetpointPercent');
            }*/
        }
        // NetworkControl
        else if (command.command === 'action.devices.commands.EnableDisableNetworkProfile') {
            const profile = command.params['profile'].toLowerCase();
            //const enable = command.params['enable'] || false;
            let found = false;
            this.network_profiles.forEach(function (p) {
                if (profile === p.toLowerCase()) {
                    found = true;
                }
            });
            if (!found) {
                return {
                    status: 'ERROR',
                    errorCode: 'networkProfileNotRecognized'
                };
            }
        }
        else if (command.command === 'action.devices.commands.EnableDisableGuestNetwork') {
            const enable = command.params['enable'] || false;
            params['guestNetworkEnabled'] = enable;
            executionStates.push('guestNetworkEnabled');
        }
        else if (command.command === 'action.devices.commands.GetGuestNetworkPassword') {
            params['guestNetworkPassword'] = me.guest_network_password;
            executionStates.push('guestNetworkPassword');
            return {
                status: 'SUCCESS',
                states: {
                    online: true,
                    guestNetworkPassword: me.guest_network_password
                },
                executionStates: executionStates,
            };
        }
        // Inputs
        else if (command.command === 'action.devices.commands.SetInput') {
            if (!me.command_only_input_selector) {
                if (Object.prototype.hasOwnProperty.call(command.params, 'newInput')) {
                    const newInput = command.params['newInput'];
                    let current_input_index = -1;
                    this.available_inputs.forEach(function (input_element, index) {
                        if (input_element.key === newInput) {
                            current_input_index = index;
                        }
                    });
                    if (current_input_index < 0) {
                        return {
                            status: 'ERROR',
                            errorCode: 'unsupportedInput'
                        };
                    }
                    this.current_input_index = current_input_index;
                    params['currentInput'] = newInput;
                    executionStates.push('currentInput');
                }
            }
        }
        else if (command.command === 'action.devices.commands.NextInput') {
            if (!me.command_only_input_selector) {
                this.current_input_index++;
                if (this.current_input_index >= this.available_inputs.length) {
                    this.current_input_index = 0;
                }
                executionStates.push('currentInput');
                params['currentInput'] = this.available_inputs[this.current_input_index].key;
            }
        }
        else if (command.command === 'action.devices.commands.PreviousInput') {
            if (!me.command_only_input_selector) {
                if (this.current_input_index <= 0) {
                    this.current_input_index = this.available_inputs.length;
                }
                this.current_input_index--;
                executionStates.push('currentInput');
                params['currentInput'] = this.available_inputs[this.current_input_index].key;
            }
        }
        // On/Off
        else if (command.command === 'action.devices.commands.OnOff') {
            if (!me.command_only_onoff) {
                if (Object.prototype.hasOwnProperty.call(command.params, 'on')) {
                    const on_param = command.params['on'];
                    executionStates.push('on');
                    params['on'] = on_param;
                }
            }
        }
        // OpenClose
        else if (command.command === 'action.devices.commands.OpenClose') {
            if (!me.command_only_openclose) {
                const open_percent = command.params['openPercent'] || 0;
                if (Object.prototype.hasOwnProperty.call(me.states, 'openPercent')) {
                    executionStates.push('openPercent');
                    params['openPercent'] = open_percent;
                }
                else if (Object.prototype.hasOwnProperty.call(command.params, 'openDirection')) {
                    const open_direction = command.params['openDirection'];
                    let new_open_directions = [];
                    me.states.openState.forEach(element => {
                        new_open_directions.push({
                            "openPercent": element.openDirection == open_direction ? open_percent : element.openPercent,
                            "openDirection": element.openDirection
                        });
                    });
                    executionStates.push('openState');
                    params['openState'] = new_open_directions;
                }
            }
        }
        else if (command.command === 'action.devices.commands.OpenCloseRelative') {
            /*const open_percent = command.params['openRelativePercent'] || 0;
            if (Object.prototype.hasOwnProperty.call(me.states, 'openPercent')) {
                executionStates.push('openPercent');
                params['openPercent'] = open_percent;
            } else if (Object.prototype.hasOwnProperty.call(command.params, 'openDirection')) {
                const open_direction = command.params['openDirection'];
                let new_open_directions = [];
                me.states.openState.forEach(element => {
                    new_open_directions.push({
                        "openPercent": element.openDirection == open_direction ? element.openPercent + open_percent : element.openPercent,
                        "openDirection": element.openDirection
                    });
                });
                executionStates.push('openState');
                params['openState'] = new_open_directions;
            }*/
        }
        // StartStop
        else if (command.command === 'action.devices.commands.StartStop') {
            const start = command.params['start'] || false;
            let zones = undefined;
            if (Object.prototype.hasOwnProperty.call(command.params, 'zone')) {
                zones = [command.params['zone']];
            }
            else if (Object.prototype.hasOwnProperty.call(command.params, 'multipleZones')) {
                zones = command.params['multipleZones'];
            }
            params['isRunning'] = start;
            executionStates.push('isRunning');
            if (start) {
                params['isPaused'] = false;
                executionStates.push('isPaused');
                if (zones !== undefined) {
                    let active_zones = [];
                    zones.forEach(function (zone) {
                        if (me.available_zones.includes(zone)) {
                            active_zones.push(zone);
                        }
                    });
                    params['activeZones'] = active_zones;
                    executionStates.push('activeZones');
                }
            }
        }
        else if (command.command === 'action.devices.commands.PauseUnpause') {
            const pause = command.params['pause'] || false;
            params['isPaused'] = pause;
            executionStates.push('isPaused');
        }
        // TransportControl
        else if (command.command === 'action.devices.commands.mediaStop') {
            params['playbackState'] = 'STOPPED';
            executionStates.push('playbackState');
        }
        else if (command.command === 'action.devices.commands.mediaNext') {
            params['playbackState'] = 'FAST_FORWARDING';
            executionStates.push('playbackState');
        }
        else if (command.command === 'action.devices.commands.mediaPrevious') {
            params['playbackState'] = 'REWINDING';
            executionStates.push('playbackState');
        }
        else if (command.command === 'action.devices.commands.mediaPause') {
            params['playbackState'] = 'PAUSED';
            executionStates.push('playbackState');
        }
        else if (command.command === 'action.devices.commands.mediaResume') {
            params['playbackState'] = 'PLAYING';
            executionStates.push('playbackState');
        }
        else if (command.command === 'action.devices.commands.mediaSeekRelative') {
            if (Object.prototype.hasOwnProperty.call(command.params, 'relativePositionMs')) {
                //const relative_position_ms = command.params['relativePositionMs'];
                params['playbackState'] = 'PLAYING';
                executionStates.push('playbackState');
            }
        }
        else if (command.command === 'action.devices.commands.mediaSeekToPosition') {
            if (Object.prototype.hasOwnProperty.call(command.params, 'absPositionMs')) {
                //const abs_position_ms = command.params['absPositionMs'];
                params['playbackState'] = 'PLAYING';
                executionStates.push('playbackState');
            }
        }
        else if (command.command === 'action.devices.commands.mediaRepeatMode') {
            if (Object.prototype.hasOwnProperty.call(command.params, 'isOn')) {
                //const is_on = command.params['isOn'];
            }
            if (Object.prototype.hasOwnProperty.call(command.params, 'isSingle')) {
                //const is_single = command.params['isSingle'];
            }
        }
        else if (command.command === 'action.devices.commands.mediaShuffle') {
        }
        else if (command.command === 'action.devices.commands.mediaClosedCaptioningOn') {
            if (Object.prototype.hasOwnProperty.call(command.params, 'closedCaptioningLanguage')) {
                //const closedCaptioningLanguage = command.params['closedCaptioningLanguage'];
                params['playbackState'] = me.states['playbackState'];
            }
            if (Object.prototype.hasOwnProperty.call(command.params, 'userQueryLanguage')) {
                //const userQueryLanguage = command.params['userQueryLanguage'];
                params['playbackState'] = me.states['playbackState'];
            }
            executionStates.push('playbackState');
        }
        else if (command.command === 'action.devices.commands.mediaClosedCaptioningOff') {
            executionStates.push('playbackState');
        }
        // TemperatureControl
        else if (command.command === 'action.devices.commands.SetTemperature') {
            if (!me.command_only_temperaturecontrol) {
                const temperature = command.params['temperature'];
                params['temperatureSetpointCelsius'] = temperature;
                executionStates.push('temperatureSetpointCelsius');
            }
        }
        // TemperatureSetting
        else if (command.command === 'action.devices.commands.ThermostatTemperatureSetpoint') {
            if (!me.command_only_temperaturesetting) {
                const thermostatTemperatureSetpoint = command.params['thermostatTemperatureSetpoint'];
                params['thermostatTemperatureSetpoint'] = thermostatTemperatureSetpoint;
                me.thermostat_temperature_setpoint = thermostatTemperatureSetpoint;
                executionStates.push('thermostatTemperatureSetpoint');
            }
        }
        else if (command.command === 'action.devices.commands.ThermostatTemperatureSetRange') {
            if (!me.command_only_temperaturesetting) {
                const thermostatTemperatureSetpointHigh = command.params['thermostatTemperatureSetpointHigh'];
                const thermostatTemperatureSetpointLow = command.params['thermostatTemperatureSetpointLow'];
                params['thermostatTemperatureSetpointHigh'] = thermostatTemperatureSetpointHigh;
                params['thermostatTemperatureSetpointLow'] = thermostatTemperatureSetpointLow;
                me.thermostat_temperature_setpoint_hight = thermostatTemperatureSetpointHigh;
                me.thermostat_temperature_setpoint_low = thermostatTemperatureSetpointLow;
                executionStates.push('thermostatTemperatureSetpointHigh', 'thermostatTemperatureSetpointLow');
            }
        }
        else if (command.command === 'action.devices.commands.ThermostatSetMode') {
            if (!me.command_only_temperaturesetting) {
                const thermostatMode = command.params.thermostatMode;
                params['thermostatMode'] = thermostatMode;
                executionStates.push('thermostatMode');
                if (thermostatMode === "heatcool") {
                    params['thermostatTemperatureSetpointHigh'] = me.thermostat_temperature_setpoint_hight;
                    params['thermostatTemperatureSetpointLow'] = me.thermostat_temperature_setpoint_low;
                    executionStates.push('thermostatTemperatureSetpointHigh', 'thermostatTemperatureSetpointLow');
                }
                else {
                    params['thermostatTemperatureSetpoint'] = me.thermostat_temperature_setpoint;
                    executionStates.push('thermostatTemperatureSetpoint');
                }
            }
        }
        else if (command.command === 'action.devices.commands.TemperatureRelative') {
            /*if (Object.prototype.hasOwnProperty.call(command.params, 'thermostatTemperatureRelativeDegree')) {
                const thermostatTemperatureRelativeDegree = command.params['thermostatTemperatureRelativeDegree'];
                params['thermostatTemperatureSetpoint'] = me.states['thermostatTemperatureSetpoint'] + thermostatTemperatureRelativeDegree;
                executionStates.push('thermostatTemperatureSetpoint');
            }
            if (Object.prototype.hasOwnProperty.call(command.params, 'thermostatTemperatureRelativeWeight')) {
                const thermostatTemperatureRelativeWeight = command.params['thermostatTemperatureRelativeWeight'];
                me._debug("C CHI thermostatTemperatureRelativeWeight " + thermostatTemperatureRelativeWeight);
                me._debug("C CHI thermostatTemperatureSetpoint " + me.states['thermostatTemperatureSetpoint']);
                params['thermostatTemperatureSetpoint'] = me.states['thermostatTemperatureSetpoint'] + thermostatTemperatureRelativeWeight;
                executionStates.push('thermostatTemperatureSetpoint');
            }*/
        }
        // Timer
        else if (command.command === 'action.devices.commands.TimerStart') {
            if (!me.command_only_timer) {
                const timer_time_sec = command.params['timerTimeSec'];
                const now = Math.floor(Date.now() / 1000);
                params['timerRemainingSec'] = timer_time_sec;
                params['timerPaused'] = null;
                executionStates.push('timerPaused', 'timerRemainingSec');
                me.timer_end_timestamp = now + timer_time_sec;
            }
        }
        else if (command.command === 'action.devices.commands.TimerResume') {
            if (!me.command_only_timer) {
                const now = Math.floor(Date.now() / 1000);
                const timer_remaining_sec = me.states['timerRemainingSec'];
                if (timer_remaining_sec > 0 && me.states['timerPaused']) {
                    params['timerPaused'] = false;
                    me.timer_end_timestamp = now + timer_remaining_sec;
                    executionStates.push('timerPaused', 'timerRemainingSec');
                }
                else {
                    return {
                        status: 'ERROR',
                        errorCode: 'noTimerExists'
                    };
                }
            }
        }
        else if (command.command === 'action.devices.commands.TimerPause') {
            if (!me.command_only_timer) {
                const now = Math.floor(Date.now() / 1000);
                if (me.states['timerPaused']) {
                    executionStates.push('timerPaused');
                }
                else if (now < me.timer_end_timestamp) {
                    params['timerPaused'] = true;
                    params['timerRemainingSec'] = me.timer_end_timestamp - now;
                    executionStates.push('timerPaused', 'timerRemainingSec');
                }
                else {
                    return {
                        status: 'ERROR',
                        errorCode: 'noTimerExists'
                    };
                }
            }
        }
        else if (command.command === 'action.devices.commands.TimerCancel') {
            if (!me.command_only_timer) {
                const now = Math.floor(Date.now() / 1000);
                if (now < me.timer_end_timestamp) {
                    me.states['timerRemainingSec'] = 0;
                    params['timerPaused'] = false;
                    me.timer_end_timestamp = -1;
                    executionStates.push('timerPaused', 'timerRemainingSec');
                }
                else {
                    return {
                        status: 'ERROR',
                        errorCode: 'noTimerExists'
                    };
                }
            }
        }
        else if (command.command === 'action.devices.commands.TimerAdjust') {
            if (!me.command_only_timer) {
                const now = Math.floor(Date.now() / 1000);
                const timer_time_sec = command.params['timerTimeSec'];
                if (me.states['timerPaused']) {
                    me.states['timerRemainingSec'] = me.states['timerRemainingSec'] + timer_time_sec;
                    executionStates.push('timerRemainingSec');
                }
                else if (now < me.timer_end_timestamp) {
                    me.timer_end_timestamp = me.timer_end_timestamp + timer_time_sec;
                    me.states['timerRemainingSec'] = me.timer_end_timestamp - now;
                    executionStates.push('timerRemainingSec');
                }
                else {
                    return {
                        status: 'ERROR',
                        errorCode: 'noTimerExists'
                    };
                }
            }
        }
        // Volume
        else if (command.command === 'action.devices.commands.mute') {
            if (!me.command_only_volume) {
                if (Object.prototype.hasOwnProperty.call(command.params, 'mute')) {
                    const mute = command.params['mute'];
                    params['isMuted'] = mute;
                    executionStates.push('isMuted', 'currentVolume');
                }
            }
        }
        else if (command.command === 'action.devices.commands.setVolume') {
            if (!me.command_only_volume) {
                if (Object.prototype.hasOwnProperty.call(command.params, 'volumeLevel')) {
                    let volumeLevel = command.params['volumeLevel'];
                    if (volumeLevel > me.volume_max_level) {
                        volumeLevel = me.volume_max_level;
                    }
                    params['currentVolume'] = volumeLevel;
                    params['isMuted'] = false;
                    executionStates.push('isMuted', 'currentVolume');
                }
            }
        }
        else if (command.command === 'action.devices.commands.volumeRelative') {
            if (!me.command_only_volume) {
                if (Object.prototype.hasOwnProperty.call(command.params, 'relativeSteps')) {
                    const relativeSteps = command.params['relativeSteps'];
                    let current_volume = me.states['currentVolume'];
                    if (current_volume >= me.volume_max_level && relativeSteps > 0) {
                        return {
                            status: 'ERROR',
                            errorCode: 'volumeAlreadyMax'
                        };
                    }
                    else if (current_volume <= 0 && relativeSteps < 0) {
                        return {
                            status: 'ERROR',
                            errorCode: 'volumeAlreadyMin'
                        };
                    }
                    current_volume += relativeSteps;
                    if (current_volume > me.volume_max_level) {
                        current_volume = me.volume_max_level;
                    }
                    else if (current_volume < 0) {
                        current_volume = 0;
                    }
                    params['currentVolume'] = current_volume;
                    executionStates.push('currentVolume');
                }
            }
        }
        // Channels
        else if (command.command === 'action.devices.commands.selectChannel') {
            if (Object.prototype.hasOwnProperty.call(command.params, 'channelCode')) {
                const channelCode = command.params['channelCode'];
                let new_channel_index = -1;
                let new_channel_key = '';
                let new_channel_number = '';
                this.available_channels.forEach(function (channel, index) {
                    if (channel.key === channelCode) {
                        new_channel_index = index;
                        new_channel_key = channel.key;
                        new_channel_number = channel.number || '';
                    }
                });
                if (new_channel_index < 0) {
                    return {
                        status: 'ERROR',
                        errorCode: 'noAvailableChannel'
                    };
                }
                this.current_channel_index = new_channel_index;
                params['currentChannel'] = new_channel_key;
                params['currentChannelNumber'] = new_channel_number;
                // executionStates.push('currentChannel');
            }
            /*if (Object.prototype.hasOwnProperty.call(command.params, 'channelName')) {
                const channelName = command.params['channelName'];
            }*/
            if (Object.prototype.hasOwnProperty.call(command.params, 'channelNumber')) {
                const channelNumber = command.params['channelNumber'];
                let new_channel_index = -1;
                let new_channel_key = '';
                let new_channel_number = '';
                this.available_channels.forEach(function (channel, index) {
                    if (channel.number === channelNumber) {
                        new_channel_index = index;
                        new_channel_key = channel.key;
                        new_channel_number = channel.number || '';
                    }
                });
                if (new_channel_index < 0) {
                    return {
                        status: 'ERROR',
                        errorCode: 'noAvailableChannel'
                    };
                }
                me.current_channel_index = new_channel_index;
                params['currentChannel'] = new_channel_key;
                params['currentChannelNumber'] = new_channel_number;
                // executionStates.push('currentChannel');
            }
        }
        else if (command.command === 'action.devices.commands.relativeChannel') {
            if (Object.prototype.hasOwnProperty.call(command.params, 'relativeChannelChange')) {
                const relativeChannelChange = command.params['relativeChannelChange'];
                let current_channel_index = this.current_channel_index;
                if (current_channel_index < 0) {
                    current_channel_index = 0;
                }
                current_channel_index += relativeChannelChange;
                const channels_num = this.available_channels.length;
                if (current_channel_index < 0) {
                    current_channel_index += channels_num;
                }
                else if (current_channel_index >= channels_num) {
                    current_channel_index -= channels_num;
                }
                if (this.current_channel_index != current_channel_index) {
                    this.last_channel_index = this.current_channel_index;
                    this.current_channel_index = current_channel_index;
                }
                params['currentChannel'] = this.available_channels[current_channel_index].key;
                params['currentChannelNumber'] = this.available_channels[current_channel_index].number || '';
                // executionStates.push('currentChannel');
            }
        }
        else if (command.command === 'action.devices.commands.returnChannel') {
            if (this.last_channel_index >= 0) {
                const current_channel_index = this.current_channel_index;
                this.current_channel_index = this.last_channel_index;
                this.last_channel_index = current_channel_index;
            }
            if (this.current_channel_index < 0) {
                this.current_channel_index = 0;
            }
            params['currentChannel'] = this.available_channels[this.current_channel_index].key;
            params['currentChannelNumber'] = this.available_channels[this.current_channel_index].number || '';
            // executionStates.push('currentChannel');
        }
        // Modes
        else if (command.command === 'action.devices.commands.SetModes') {
            if (!me.command_only_modes) {
                if (Object.prototype.hasOwnProperty.call(command.params, 'updateModeSettings')) {
                    const updateModeSettings = command.params['updateModeSettings'];
                    let current_modes = me.states['currentModeSettings'];
                    let new_modes = {};
                    this.available_modes.forEach(function (mode) {
                        if (typeof updateModeSettings[mode.name] === 'string') {
                            let mode_value = updateModeSettings[mode.name];
                            mode.settings.forEach(function (setting) {
                                if (setting.setting_name === mode_value) {
                                    new_modes[mode.name] = mode_value;
                                }
                            });
                        }
                        if (typeof new_modes[mode.name] === 'undefined') {
                            new_modes[mode.name] = current_modes[mode.name];
                        }
                    });
                    params['currentModeSettings'] = new_modes;
                    executionStates.push('currentModeSettings');
                }
            }
        }
        // Rotation
        else if (command.command === 'action.devices.commands.RotateAbsolute') {
            if (!me.command_only_rotation) {
                if (Object.prototype.hasOwnProperty.call(command.params, 'rotationDegrees')) {
                    const rotationDegrees = command.params['rotationDegrees'];
                    params['rotationDegrees'] = rotationDegrees;
                    executionStates.push('rotationDegrees');
                }
                if (Object.prototype.hasOwnProperty.call(command.params, 'rotationPercent')) {
                    const rotationPercent = command.params['rotationPercent'];
                    params['rotationPercent'] = rotationPercent;
                    executionStates.push('rotationPercent');
                }
            }
        }
        // Traits
        else if (command.command === 'action.devices.commands.SetToggles') {
            if (!me.command_only_toggles) {
                if (Object.prototype.hasOwnProperty.call(command.params, 'updateToggleSettings')) {
                    const updateToggleSettings = command.params['updateToggleSettings'];
                    let current_toggle = me.states['currentToggleSettings'];
                    let toggles = {};
                    this.available_toggles.forEach(function (toggle) {
                        if (typeof updateToggleSettings[toggle.name] === 'boolean') {
                            toggles[toggle.name] = updateToggleSettings[toggle.name];
                        }
                        else {
                            toggles[toggle.name] = current_toggle[toggle.name] || false;
                        }
                    });
                    params['currentToggleSettings'] = toggles;
                    executionStates.push('currentToggleSettings');
                }
            }
        }
        // Brightness
        else if (command.command === 'action.devices.commands.BrightnessAbsolute') {
            if (!me.command_only_brightness) {
                const brightness = command.params['brightness'];
                params['brightness'] = brightness;
                executionStates.push('brightness');
            }
        }
        else if (command.command === 'action.devices.commands.BrightnessRelative') {
            /*
            let brightness = me.states['brightness'];
            if (Object.prototype.hasOwnProperty.call(command.params, 'brightnessRelativePercent')) {
                const brightnessRelativePercent = command.params['brightnessRelativePercent'];
                brightness = Math.round(brightness * (1 + parseInt(brightnessRelativePercent) / 100));
            }
            if (Object.prototype.hasOwnProperty.call(command.params, 'brightnessRelativeWeight')) {
                //const brightnessRelativeWeight = command.params['brightnessRelativeWeight'];
                brightness = brightness + parseInt(brightnessRelativePercent);
            }
            params['brightness'] = brightness;
            executionStates.push('brightness');
            */
        }
        // ColorSetting
        else if (command.command === 'action.devices.commands.ColorAbsolute') {
            if (!me.command_only_colorsetting) {
                if (Object.prototype.hasOwnProperty.call(command.params, 'color')) {
                    if (Object.prototype.hasOwnProperty.call(command.params.color, 'temperature') || Object.prototype.hasOwnProperty.call(command.params.color, 'temperatureK')) {
                        const temperature = Object.prototype.hasOwnProperty.call(command.params.color, 'temperature') ? command.params.color.temperature : command.params.color.temperatureK;
                        params['color'] = { temperatureK: temperature };
                    }
                    else if (Object.prototype.hasOwnProperty.call(command.params.color, 'spectrumRGB') || Object.prototype.hasOwnProperty.call(command.params.color, 'spectrumRgb')) {
                        const spectrum_RGB = Object.prototype.hasOwnProperty.call(command.params.color, 'spectrumRGB') ? command.params.color.spectrumRGB : command.params.color.spectrumRgb;
                        params['color'] = { spectrumRgb: spectrum_RGB };
                    }
                    else if (Object.prototype.hasOwnProperty.call(command.params.color, 'spectrumHSV') || Object.prototype.hasOwnProperty.call(command.params.color, 'spectrumHsv')) {
                        const spectrum_HSV = Object.prototype.hasOwnProperty.call(command.params.color, 'spectrumHSV') ? command.params.color.spectrumHSV : command.params.color.spectrumHsv;
                        params['color'] = {
                            spectrumHsv: {
                                hue: spectrum_HSV.hue,
                                saturation: spectrum_HSV.saturation,
                                value: spectrum_HSV.value
                            }
                        };
                    }
                    executionStates.push('color');
                }
            }
        }
        // Camera
        else if (command.command === 'action.devices.commands.GetCameraStream') {
            if (Object.prototype.hasOwnProperty.call(command.params, 'SupportedStreamProtocols')) {
                const supported_protocols = command.params['SupportedStreamProtocols'];
                let protocol = '';
                let stream_url = '';
                supported_protocols.forEach(function (supported_protocol) {
                    let url = me.getStreamUrl(supported_protocol);
                    if (url) {
                        protocol = supported_protocol;
                        stream_url = url;
                    }
                });
                if (protocol.length > 0) {
                    executionStates.push('cameraStreamProtocol');
                    if (me.auth_token.length > 0) {
                        executionStates.push('cameraStreamAuthToken');
                    }
                    if (protocol === 'webrtc') {
                        executionStates.push('cameraStreamIceServers', 'cameraStreamSignalingUrl', 'cameraStreamOffer');
                        return {
                            reportState: false,
                            states: {
                                online: true,
                                cameraStreamAuthToken: me.auth_token,
                                cameraStreamIceServers: me.webrtc_ice_servers,
                                cameraStreamSignalingUrl: stream_url,
                                cameraStreamOffer: me.webrtc_offer,
                                cameraStreamProtocol: protocol
                            },
                            executionStates: executionStates,
                        };
                    }
                    else {
                        executionStates.push('cameraStreamAccessUrl');
                        const app_id = this.getAppId(protocol);
                        if (app_id.length > 0) {
                            executionStates.push('cameraStreamReceiverAppId');
                        }
                        return {
                            reportState: false,
                            states: {
                                online: true,
                                cameraStreamAccessUrl: stream_url,
                                cameraStreamReceiverAppId: app_id,
                                cameraStreamAuthToken: me.auth_token,
                                cameraStreamProtocol: protocol
                            },
                            executionStates: executionStates,
                        };
                    }
                }
            }
        }
        return ok_result;
    }
    getStreamUrl(protocol_type) {
        switch (protocol_type) {
            case 'hls':
                return this.hls;
            case 'dash':
                return this.dash;
            case 'smooth_stream':
                return this.smooth_stream;
            case 'progressive_mp4':
                return this.progressive_mp4;
            case 'webrtc':
                return this.webrtc;
        }
        return '';
    }
    getAppId(protocol_type) {
        switch (protocol_type) {
            case 'hls':
                return this.hls_app_id;
            case 'dash':
                return this.dash_app_id;
            case 'smooth_stream':
                return this.smooth_stream_app_id;
            case 'progressive_mp4':
                return this.progressive_mp4_app_id;
        }
        return '';
    }
}
module.exports = function (RED) {
    (0, SmartHome_js_1.setRED)(RED);
    RED.nodes.registerType('google-device', DeviceNode);
};
//# sourceMappingURL=device.js.map