UNPKG

node-red-contrib-google-smarthome

Version:

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

972 lines (938 loc) 249 kB
/** * node-red-contrib-google-smarthome * Copyright (C) 2024 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/>. */ module.exports = function (RED) { "use strict"; const fs = require('fs'); const path = require('path'); const util = require('util'); const Formats = require('../lib/Formats.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) { 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 = RED.nodes.getNode(this.client); this._debug(".constructor config " + JSON.stringify(config)); if (!this.clientConn) { this.error(RED._("device.errors.missing-config")); this.status({ fill: "red", shape: "dot", text: "Missing config" }); return; } else if (typeof this.clientConn.register !== 'function') { this.error(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 = 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.BOOL + Formats.MANDATORY; if (me.trait.appselector) { let values = me.available_applications.map(application => application.key); if (values.length > 0) { state_types['currentApplication'] = { type: Formats.STRING + 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.BOOL + Formats.MANDATORY; state_types['currentArmLevel'] = { type: Formats.STRING + Formats.MANDATORY, values: values, defaultValue: values[0], }; state_types['exitAllowance'] = Formats.INT; } } if (me.trait.brightness && !me.command_only_brightness) { state_types['brightness'] = { type: 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.OBJECT + Formats.DELETE_MISSING, attributes: { spectrumRgb: { type: Formats.INT + Formats.MANDATORY, exclusiveStates: ['temperatureK', 'spectrumHsv'] }, } }; } else if ((me.color_model === "hsv") || (me.color_model === "hsv_temp")) { state_types['color'] = { type: Formats.OBJECT, attributes: { spectrumHsv: { type: Formats.OBJECT, attributes: { hue: { type: Formats.FLOAT + 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.FLOAT + Formats.MANDATORY, // float, representing saturation as a percentage in the range [0.0, 1.0] min: 0.0, max: 1.0, }, value: { type: Formats.FLOAT + 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.OBJECT, attributes: {} }; } if (me.color_model !== "rgb" && me.color_model !== "hsv") { state_types.color.attributes.temperatureK = { type: Formats.INT + 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.STRING + 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.STRING + Formats.MANDATORY, values: food_preset_values, }; state_types['currentFoodQuantity'] = Formats.FLOAT; state_types['currentFoodUnit'] = { type: 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.OBJECT + Formats.ARRAY, attributes: { itemName: { type: Formats.STRING, value: dispense_items_values, }, amountRemaining: { type: Formats.OBJECT, attributes: { amount: Formats.FLOAT, unit: { type: Formats.STRING, values: DISPENSE_SUPPORTED_UNITS, }, } }, amountLastDispensed: { type: Formats.OBJECT, attributes: { amount: Formats.FLOAT, unit: { type: Formats.STRING, values: DISPENSE_SUPPORTED_UNITS, }, } }, isCurrentlyDispensing: Formats.BOOL, }, keyId: 'itemName', removeIfNoData: true, }; } if (me.trait.dock) { state_types['isDocked'] = Formats.BOOL; } if (me.trait.energystorage) { state_types['descriptiveCapacityRemaining'] = Formats.STRING + Formats.MANDATORY; state_types['capacityRemaining'] = { type: Formats.OBJECT + Formats.ARRAY, attributes: { rawValue: Formats.INT + Formats.MANDATORY, unit: { type: Formats.STRING + 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'] = {