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
JavaScript
/**
* 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'] = {