diff --git a/Example Custom Broadcaster.indigoPlugin/Contents/Info.plist b/Example Custom Broadcaster.indigoPlugin/Contents/Info.plist index 238f64c..c2cbfcd 100644 --- a/Example Custom Broadcaster.indigoPlugin/Contents/Info.plist +++ b/Example Custom Broadcaster.indigoPlugin/Contents/Info.plist @@ -2,24 +2,24 @@ - PluginVersion - 2022.1.0 - ServerApiVersion - 3.0 - IwsApiVersion - 1.0.0 - CFBundleDisplayName - Example Custom Broadcaster - CFBundleIdentifier - com.perceptiveautomation.indigoplugin.custom-broadcaster - CFBundleVersion - 1.0.0 - CFBundleURLTypes - - - CFBundleURLName - https://wiki.indigodomo.com/doku.php?id=plugins:example_custom_broadcaster_1 - - + PluginVersion + 2022.1.0 + ServerApiVersion + 3.0 + IwsApiVersion + 1.0.0 + CFBundleDisplayName + Example Custom Broadcaster + CFBundleIdentifier + com.perceptiveautomation.indigoplugin.custom-broadcaster + CFBundleVersion + 1.0.0 + CFBundleURLTypes + + + CFBundleURLName + https://wiki.indigodomo.com/doku.php?id=plugins:example_custom_broadcaster_1 + + diff --git a/Example Custom Broadcaster.indigoPlugin/Contents/Server Plugin/MenuItems.xml b/Example Custom Broadcaster.indigoPlugin/Contents/Server Plugin/MenuItems.xml index 0ac49ec..aecccaf 100644 --- a/Example Custom Broadcaster.indigoPlugin/Contents/Server Plugin/MenuItems.xml +++ b/Example Custom Broadcaster.indigoPlugin/Contents/Server Plugin/MenuItems.xml @@ -1,15 +1,15 @@ - + diff --git a/Example Custom Broadcaster.indigoPlugin/Contents/Server Plugin/plugin.py b/Example Custom Broadcaster.indigoPlugin/Contents/Server Plugin/plugin.py index d2b8b71..2f6602f 100644 --- a/Example Custom Broadcaster.indigoPlugin/Contents/Server Plugin/plugin.py +++ b/Example Custom Broadcaster.indigoPlugin/Contents/Server Plugin/plugin.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- #################### # Copyright (c) 2022, Perceptive Automation, LLC. All rights reserved. -# http://www.indigodomo.com +# https://www.indigodomo.com import indigo @@ -13,38 +13,38 @@ ################################################################################ class Plugin(indigo.PluginBase): - ######################################## - def __init__(self, pluginId, pluginDisplayName, pluginVersion, pluginPrefs): - super(Plugin, self).__init__(pluginId, pluginDisplayName, pluginVersion, pluginPrefs) - self.debug = True + ######################################## + def __init__(self, plugin_id, plugin_display_name, plugin_version, plugin_prefs): + super().__init__(plugin_id, plugin_display_name, plugin_version, plugin_prefs) + self.debug = True - ######################################## - def startup(self): - self.logger.debug("startup called -- broadcasting startup to all subscribers") - # Broadcast to all listeners that we have started using the "broadcasterStarted" - # broadcast key. Note the key is arbitrary and will just be used by the - # subscribers in their subscribeToBroadcast() call. - indigo.server.broadcastToSubscribers("broadcasterStarted") + ######################################## + def startup(self): + self.logger.debug("startup called -- broadcasting startup to all subscribers") + # Broadcast to all listeners that we have started using the "broadcasterStarted" + # broadcast key. Note the key is arbitrary and will just be used by the + # subscribers in their subscribeToBroadcast() call. + indigo.server.broadcastToSubscribers("broadcasterStarted") - def shutdown(self): - self.logger.debug("shutdown called -- broadcasting shutdown to all subscribers") - # Broadcast to all listeners that we have shutdown using the "broadcasterShutdown" - # broadcast key. - indigo.server.broadcastToSubscribers(u"broadcasterShutdown") + def shutdown(self): + self.logger.debug("shutdown called -- broadcasting shutdown to all subscribers") + # Broadcast to all listeners that we have shutdown using the "broadcasterShutdown" + # broadcast key. + indigo.server.broadcastToSubscribers("broadcasterShutdown") - ######################################## - def runConcurrentThread(self): - try: - # Every 3 seconds broadcast to subscribers a new random color from our list: - colorList = ["red", "green", "blue", "indigo", "orange", "black", "white", "magento", "silver", "gold"] - while True: - color = colorList[random.randint(0, len(colorList)-1)] - # broadcastToSubscribers can take an additional argument to be passed to - # the subscribers. Allowed types include basic python objects: string, number, - # boolean, dict, or list. For server performance please keep the data size - # sent small (a few kilobytes at most), and try not to broadcast more frequently - # than once per second. Bursts of higher data rates should be fine. - indigo.server.broadcastToSubscribers("colorChanged", color) - self.sleep(3) - except self.StopThread: - pass # Optionally catch the StopThread exception and do any needed cleanup. + ######################################## + def runConcurrentThread(self): + try: + # Every 3 seconds broadcast to subscribers a new random color from our list: + color_list = ["red", "green", "blue", "indigo", "orange", "black", "white", "magento", "silver", "gold"] + while True: + color = color_list[random.randint(0, len(color_list)-1)] + # broadcastToSubscribers can take an additional argument to be passed to + # the subscribers. Allowed types include basic python objects: string, number, + # boolean, dict, or list. For server performance please keep the data size + # sent small (a few kilobytes at most), and try not to broadcast more frequently + # than once per second. Bursts of higher data rates should be fine. + indigo.server.broadcastToSubscribers("colorChanged", color) + self.sleep(3) + except self.StopThread: + pass # Optionally catch the StopThread exception and do any needed cleanup. diff --git a/Example Custom Subscriber.indigoPlugin/Contents/Info.plist b/Example Custom Subscriber.indigoPlugin/Contents/Info.plist index bffbc3a..dd30d56 100644 --- a/Example Custom Subscriber.indigoPlugin/Contents/Info.plist +++ b/Example Custom Subscriber.indigoPlugin/Contents/Info.plist @@ -2,24 +2,24 @@ - PluginVersion - 2022.1.0 - ServerApiVersion - 3.0 - IwsApiVersion - 1.0.0 - CFBundleDisplayName - Example Custom Subscriber - CFBundleIdentifier - com.perceptiveautomation.indigoplugin.custom-subscriber - CFBundleVersion - 1.0.0 - CFBundleURLTypes - - - CFBundleURLName - https://wiki.indigodomo.com/doku.php?id=plugins:example_custom_subscriber_1 - - + PluginVersion + 2022.1.0 + ServerApiVersion + 3.0 + IwsApiVersion + 1.0.0 + CFBundleDisplayName + Example Custom Subscriber + CFBundleIdentifier + com.perceptiveautomation.indigoplugin.custom-subscriber + CFBundleVersion + 1.0.0 + CFBundleURLTypes + + + CFBundleURLName + https://wiki.indigodomo.com/doku.php?id=plugins:example_custom_subscriber_1 + + diff --git a/Example Custom Subscriber.indigoPlugin/Contents/Server Plugin/MenuItems.xml b/Example Custom Subscriber.indigoPlugin/Contents/Server Plugin/MenuItems.xml index 0ac49ec..aecccaf 100644 --- a/Example Custom Subscriber.indigoPlugin/Contents/Server Plugin/MenuItems.xml +++ b/Example Custom Subscriber.indigoPlugin/Contents/Server Plugin/MenuItems.xml @@ -1,15 +1,15 @@ - + diff --git a/Example Custom Subscriber.indigoPlugin/Contents/Server Plugin/plugin.py b/Example Custom Subscriber.indigoPlugin/Contents/Server Plugin/plugin.py index a689afd..ae413e5 100644 --- a/Example Custom Subscriber.indigoPlugin/Contents/Server Plugin/plugin.py +++ b/Example Custom Subscriber.indigoPlugin/Contents/Server Plugin/plugin.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- #################### # Copyright (c) 2022, Perceptive Automation, LLC. All rights reserved. -# http://www.indigodomo.com +# https://www.indigodomo.com import indigo @@ -10,36 +10,36 @@ # our global name space by the host process. # Plugin ID of the Example Custom Broadcaster plugin (taken from its Info.plist file): -kBroadcasterPluginId = "com.perceptiveautomation.indigoplugin.custom-broadcaster" +BROADCASTER_PLUGINID = "com.perceptiveautomation.indigoplugin.custom-broadcaster" ################################################################################ class Plugin(indigo.PluginBase): - ######################################## - def __init__(self, pluginId, pluginDisplayName, pluginVersion, pluginPrefs): - super(Plugin, self).__init__(pluginId, pluginDisplayName, pluginVersion, pluginPrefs) - self.debug = True - - ######################################## - def startup(self): - self.debugLog("startup called -- subscribing to messages from Example Custom Broadcaster plugin") - # The Example Custom Broadcaster plugin defines three broadcast keys: broadcasterStarted, - # broadcasterShutdown, and colorChanged. We subscribe to notifications of all three. The - # second argument is the broadcast key used by the broadcasting plugin, the third argument - # is the name of our callback method. In this case they are the same, but they don't have - # to be. - indigo.server.subscribeToBroadcast(kBroadcasterPluginId, "broadcasterStarted", "broadcasterStarted") - indigo.server.subscribeToBroadcast(kBroadcasterPluginId, "broadcasterShutdown", "broadcasterShutdown") - indigo.server.subscribeToBroadcast(kBroadcasterPluginId, "colorChanged", "colorChanged") - - def shutdown(self): - self.logger.debug("shutdown called") - - ######################################## - def broadcasterStarted(self): - self.logger.info("received broadcasterStarted message") - - def broadcasterShutdown(self): - self.logger.info("received broadcasterShutdown message") - - def colorChanged(self, arg): - self.logger.info(f"received colorChanged message: {arg}") + ######################################## + def __init__(self, plugin_id, plugin_display_name, plugin_version, plugin_prefs): + super().__init__(plugin_id, plugin_display_name, plugin_version, plugin_prefs) + self.debug = True + + ######################################## + def startup(self): + self.logger.debug("startup called -- subscribing to messages from Example Custom Broadcaster plugin") + # The Example Custom Broadcaster plugin defines three broadcast keys: broadcasterStarted, + # broadcasterShutdown, and colorChanged. We subscribe to notifications of all three. The + # second argument is the broadcast key used by the broadcasting plugin, the third argument + # is the name of our callback method. In this case they are the same, but they don't have + # to be. + indigo.server.subscribeToBroadcast(BROADCASTER_PLUGINID, "broadcasterStarted", "broadcasterStarted") + indigo.server.subscribeToBroadcast(BROADCASTER_PLUGINID, "broadcasterShutdown", "broadcasterShutdown") + indigo.server.subscribeToBroadcast(BROADCASTER_PLUGINID, "colorChanged", "colorChanged") + + def shutdown(self): + self.logger.debug("shutdown called") + + ######################################## + def broadcasterStarted(self): + self.logger.info("received broadcasterStarted message") + + def broadcasterShutdown(self): + self.logger.info("received broadcasterShutdown message") + + def colorChanged(self, arg): + self.logger.info(f"received colorChanged message: {arg}") diff --git a/Example Database Traverse.indigoPlugin/Contents/Info.plist b/Example Database Traverse.indigoPlugin/Contents/Info.plist index d1f72e1..c97e031 100644 --- a/Example Database Traverse.indigoPlugin/Contents/Info.plist +++ b/Example Database Traverse.indigoPlugin/Contents/Info.plist @@ -2,24 +2,24 @@ - PluginVersion - 2022.1.0 - ServerApiVersion - 3.0 - IwsApiVersion - 1.0.0 - CFBundleDisplayName - Example Database Traverse - CFBundleIdentifier - com.perceptiveautomation.indigoplugin.example-db-traverse - CFBundleVersion - 1.0.0 - CFBundleURLTypes - - - CFBundleURLName - https://wiki.indigodomo.com/doku.php?id=plugins:example_db_traverse_1 - - + PluginVersion + 2022.1.0 + ServerApiVersion + 3.0 + IwsApiVersion + 1.0.0 + CFBundleDisplayName + Example Database Traverse + CFBundleIdentifier + com.perceptiveautomation.indigoplugin.example-db-traverse + CFBundleVersion + 1.0.0 + CFBundleURLTypes + + + CFBundleURLName + https://wiki.indigodomo.com/doku.php?id=plugins:example_db_traverse_1 + + diff --git a/Example Database Traverse.indigoPlugin/Contents/Server Plugin/MenuItems.xml b/Example Database Traverse.indigoPlugin/Contents/Server Plugin/MenuItems.xml index cf3646d..ec0058b 100644 --- a/Example Database Traverse.indigoPlugin/Contents/Server Plugin/MenuItems.xml +++ b/Example Database Traverse.indigoPlugin/Contents/Server Plugin/MenuItems.xml @@ -1,31 +1,31 @@ - - Log Entire Database - traverseDatabase - - - Log Devices - traverseDevices - - - Log Triggers - traverseTriggers - - - Log Schedules - traverseSchedules - - - Log Action Groups - traverseActionGroups - - - Log Control Pages - traverseControlPages - - - Log Variables - traverseVariables - + + Log Entire Database + traverse_database + + + Log Devices + traverse_devices + + + Log Triggers + traverse_triggers + + + Log Schedules + traverse_schedules + + + Log Action Groups + traverse_action_groups + + + Log Control Pages + traverse_control_pages + + + Log Variables + traverse_variables + diff --git a/Example Database Traverse.indigoPlugin/Contents/Server Plugin/plugin.py b/Example Database Traverse.indigoPlugin/Contents/Server Plugin/plugin.py index e30fba1..c3c4e36 100644 --- a/Example Database Traverse.indigoPlugin/Contents/Server Plugin/plugin.py +++ b/Example Database Traverse.indigoPlugin/Contents/Server Plugin/plugin.py @@ -1,8 +1,8 @@ #! /usr/bin/env python # -*- coding: utf-8 -*- #################### -# Copyright (c) 2014, Perceptive Automation, LLC. All rights reserved. -# http://www.indigodomo.com +# Copyright (c) 2022, Perceptive Automation, LLC. All rights reserved. +# https://www.indigodomo.com import indigo @@ -11,282 +11,282 @@ ################################################################################ class Plugin(indigo.PluginBase): - ######################################## - def __init__(self, pluginId, pluginDisplayName, pluginVersion, pluginPrefs): - super(Plugin, self).__init__(pluginId, pluginDisplayName, pluginVersion, pluginPrefs) + ######################################## + def __init__(self, plugin_id, plugin_display_name, plugin_version, plugin_prefs): + super().__init__(plugin_id, plugin_display_name, plugin_version, plugin_prefs) - ######################################## - # IOM logging methods - #################### - def logListDivider(self, sectionName): - self.logger.info("===================================================") - self.logger.info(sectionName) - self.logElemDivider() + ######################################## + # IOM logging methods + #################### + def log_list_divider(self, section_name): + self.logger.info("===================================================") + self.logger.info(section_name) + self.log_elem_divider() - def logElemDivider(self): - self.logger.info("---------------------------------------------------") + def log_elem_divider(self): + self.logger.info("---------------------------------------------------") - def logBaseElem(self, elem, folders): - self.logger.info(f" INSTANCE: {elem.__class__.__name__}") - if len(elem.description) > 0: - self.logger.info(f" DESCRIPTION: {elem.description}") - if folders and elem.folderId != 0: - self.logger.info(f" IN FOLDER: {folders.getName(elem.folderId)}") - self.logger.info(f" REMOTE DISPLAY: {elem.remoteDisplay}") + def log_base_elem(self, elem, folders): + self.logger.info(f" INSTANCE: {elem.__class__.__name__}") + if len(elem.description) > 0: + self.logger.info(f" DESCRIPTION: {elem.description}") + if folders and elem.folderId != 0: + self.logger.info(f" IN FOLDER: {folders.getName(elem.folderId)}") + self.logger.info(f" REMOTE DISPLAY: {elem.remoteDisplay}") - def logBaseFolder(self, elem): - if len(elem.description) > 0: - self.logger.info(f" DESCRIPTION: {elem.description}") - self.logger.info(f" REMOTE DISPLAY: {elem.remoteDisplay}") + def log_base_folder(self, elem): + if len(elem.description) > 0: + self.logger.info(f" DESCRIPTION: {elem.description}") + self.logger.info(f" REMOTE DISPLAY: {elem.remoteDisplay}") - ######################################## - def logDeviceBase(self, elem): - self.logBaseElem(elem, indigo.devices.folders) - self.logger.info(f" PROTOCOL: {elem.protocol}") - self.logger.info(f" MODEL NAME: {elem.model}") - self.logger.info(f" ADDRESS: {elem.address}") - if elem.protocol == indigo.kProtocol.Insteon and elem.buttonGroupCount > 0: - self.logger.info(f" BUTTON COUNT: {elem.buttonGroupCount}") - self.logger.info(f" LAST CHANGED: {elem.lastChanged}") + ######################################## + def log_device_base(self, elem): + self.log_base_elem(elem, indigo.devices.folders) + self.logger.info(f" PROTOCOL: {elem.protocol}") + self.logger.info(f" MODEL NAME: {elem.model}") + self.logger.info(f" ADDRESS: {elem.address}") + if elem.protocol == indigo.kProtocol.Insteon and elem.buttonGroupCount > 0: + self.logger.info(f" BUTTON COUNT: {elem.buttonGroupCount}") + self.logger.info(f" LAST CHANGED: {elem.lastChanged}") - supports = "" - if elem.supportsAllLightsOnOff: - supports += "AllLightsOnOff " - if elem.supportsAllOff: - supports += "AllOff " - if elem.supportsStatusRequest: - supports += "StatusRequest " - if len(supports) == 0: - supports = "--" - self.logger.info(f" SUPPORTS: {supports}") + supports = "" + if elem.supportsAllLightsOnOff: + supports += "AllLightsOnOff " + if elem.supportsAllOff: + supports += "AllOff " + if elem.supportsStatusRequest: + supports += "StatusRequest " + if len(supports) == 0: + supports = "--" + self.logger.info(f" SUPPORTS: {supports}") - #################### - def logDeviceSensor(self, elem): - self.logDeviceBase(elem) - self.logger.info(f" IS ON: {elem.onState}") + #################### + def log_device_sensor(self, elem): + self.log_device_base(elem) + self.logger.info(f" IS ON: {elem.onState}") - #################### - def logDeviceRelay(self, elem): - self.logDeviceBase(elem) - self.logger.info(f" IS ON: {elem.onState}") + #################### + def log_device_relay(self, elem): + self.log_device_base(elem) + self.logger.info(f" IS ON: {elem.onState}") - #################### - def logDeviceDimmer(self, elem): - self.logDeviceRelay(elem) - self.logger.info(f" BRIGHTNESS: {elem.brightness}") + #################### + def log_device_dimmer(self, elem): + self.log_device_relay(elem) + self.logger.info(f" BRIGHTNESS: {elem.brightness}") - #################### - def logDeviceMultiIO(self, elem): - self.logDeviceBase(elem) - if elem.analogInputCount > 0: - self.logger.info(f" ANALOG INPUTS: {elem.analogInputs}") - if elem.binaryInputCount > 0: - self.logger.info(f" BINARY INPUTS: {elem.binaryInputs}") - if elem.sensorInputCount > 0: - self.logger.info(f" SENSOR INPUTS: {elem.sensorInputs}") - if elem.binaryOutputCount > 0: - self.logger.info(f" BINARY OUTPUTS: {elem.binaryOutputs}") + #################### + def log_device_multi_io(self, elem): + self.log_device_base(elem) + if elem.analogInputCount > 0: + self.logger.info(f" ANALOG INPUTS: {elem.analogInputs}") + if elem.binaryInputCount > 0: + self.logger.info(f" BINARY INPUTS: {elem.binaryInputs}") + if elem.sensorInputCount > 0: + self.logger.info(f" SENSOR INPUTS: {elem.sensorInputs}") + if elem.binaryOutputCount > 0: + self.logger.info(f" BINARY OUTPUTS: {elem.binaryOutputs}") - #################### - def logDeviceSprinkler(self, elem): - self.logDeviceBase(elem) - self.logger.info(f" ZONE COUNT: {elem.zoneCount}") - self.logger.info(f" ZONE NAMES: {elem.zoneNames}") - self.logger.info(f" MAX DURATIONS: {elem.zoneMaxDurations}") - if len(elem.zoneScheduledDurations) > 0: - self.logger.info(f" SCHEDULED DURA.: {elem.zoneScheduledDurations}") - if elem.activeZone: - self.logger.info(f" ACTIVE ZONE: {elem.zoneNames[elem.activeZone]}") + #################### + def log_device_sprinkler(self, elem): + self.log_device_base(elem) + self.logger.info(f" ZONE COUNT: {elem.zoneCount}") + self.logger.info(f" ZONE NAMES: {elem.zoneNames}") + self.logger.info(f" MAX DURATIONS: {elem.zoneMaxDurations}") + if len(elem.zoneScheduledDurations) > 0: + self.logger.info(f" SCHEDULED DURA.: {elem.zoneScheduledDurations}") + if elem.activeZone: + self.logger.info(f" ACTIVE ZONE: {elem.zoneNames[elem.activeZone]}") - #################### - def logDeviceThermostat(self, elem): - self.logDeviceBase(elem) - self.logger.info(f" HVAC MODE: {elem.hvacMode}") - self.logger.info(f" FAN MODE: {elem.fanMode}") - self.logger.info(f" COOL SETPOINT: {elem.coolSetpoint}") - self.logger.info(f" HEAT SETPOINT: {elem.heatSetpoint}") - self.logger.info(f" TEMP COUNT: {elem.temperatureSensorCount}") - self.logger.info(f" TEMPS: {elem.temperatures}") - self.logger.info(f" HUMIDITY COUNT: {elem.humiditySensorCount}") - self.logger.info(f" HUMIDITY: {elem.humidities}") - self.logger.info(f" COOL IS ON: {elem.coolIsOn}") - self.logger.info(f" HEAT IS ON: {elem.heatIsOn}") - self.logger.info(f" FAN IS ON: {elem.fanIsOn}") + #################### + def log_device_thermostat(self, elem): + self.log_device_base(elem) + self.logger.info(f" HVAC MODE: {elem.hvacMode}") + self.logger.info(f" FAN MODE: {elem.fanMode}") + self.logger.info(f" COOL SETPOINT: {elem.coolSetpoint}") + self.logger.info(f" HEAT SETPOINT: {elem.heatSetpoint}") + self.logger.info(f" TEMP COUNT: {elem.temperatureSensorCount}") + self.logger.info(f" TEMPS: {elem.temperatures}") + self.logger.info(f" HUMIDITY COUNT: {elem.humiditySensorCount}") + self.logger.info(f" HUMIDITY: {elem.humidities}") + self.logger.info(f" COOL IS ON: {elem.coolIsOn}") + self.logger.info(f" HEAT IS ON: {elem.heatIsOn}") + self.logger.info(f" FAN IS ON: {elem.fanIsOn}") - #################### - def logDevice(self, elem): - if isinstance(elem, indigo.DimmerDevice): - self.logDeviceDimmer(elem) - elif isinstance(elem, indigo.RelayDevice): - self.logDeviceRelay(elem) - elif isinstance(elem, indigo.SensorDevice): - self.logDeviceSensor(elem) - elif isinstance(elem, indigo.MultiIODevice): - self.logDeviceMultiIO(elem) - elif isinstance(elem, indigo.SprinklerDevice): - self.logDeviceSprinkler(elem) - elif isinstance(elem, indigo.ThermostatDevice): - self.logDeviceThermostat(elem) - else: - self.logDeviceBase(elem) + #################### + def log_device(self, elem): + if isinstance(elem, indigo.DimmerDevice): + self.log_device_dimmer(elem) + elif isinstance(elem, indigo.RelayDevice): + self.log_device_relay(elem) + elif isinstance(elem, indigo.SensorDevice): + self.log_device_sensor(elem) + elif isinstance(elem, indigo.MultiIODevice): + self.log_device_multi_io(elem) + elif isinstance(elem, indigo.SprinklerDevice): + self.log_device_sprinkler(elem) + elif isinstance(elem, indigo.ThermostatDevice): + self.log_device_thermostat(elem) + else: + self.log_device_base(elem) - ######################################## - def logEventBase(self, elem, folders): - self.logBaseElem(elem, folders) - self.logger.info(f" ENABLED: {elem.enabled}") - self.logger.info(f" UPLOAD: {elem.upload}") - if elem.suppressLogging: - self.logger.info("SUPPRESS LOGGING: True") - # TODO: Need to add conditional tree and action list traversal here. + ######################################## + def log_event_base(self, elem, folders): + self.log_base_elem(elem, folders) + self.logger.info(f" ENABLED: {elem.enabled}") + self.logger.info(f" UPLOAD: {elem.upload}") + if elem.suppressLogging: + self.logger.info("SUPPRESS LOGGING: True") + # TODO: Need to add conditional tree and action list traversal here. - #################### - def logTrigger(self, elem): - self.logEventBase(elem, indigo.triggers.folders) + #################### + def log_trigger(self, elem): + self.log_event_base(elem, indigo.triggers.folders) - if isinstance(elem, indigo.DeviceStateChangeTrigger): - self.logger.info(f" DEVICE: {indigo.devices.getName(elem.deviceId)}") - self.logger.info(f" CHANGE TYPE: {elem.stateChangeType}") - self.logger.info(f" SELECTOR KEY: {elem.stateSelector}") - if elem.stateSelectorIndex > 0: - self.logger.info(f" SELECTOR INDEX: {elem.stateSelectorIndex}") - if len(elem.stateValue) > 0: - self.logger.info(f" STATE VALUE: {elem.stateValue}") - elif isinstance(elem, indigo.VariableValueChangeTrigger): - self.logger.info(f" VARIABLE: {indigo.variables.getName(elem.variableId)}") - self.logger.info(f" CHANGE TYPE: {elem.variableChangeType}") - if len(elem.variableValue) > 0: - self.logger.info(f" VARIABLE VALUE: {elem.variableValue}") - elif isinstance(elem, indigo.InsteonCommandReceivedTrigger): - self.logger.info(f" INSTEON COMMAND: {elem.command}") - self.logger.info(f" SOURCE TYPE: {elem.commandSourceType}") - if elem.commandSourceType == indigo.kDeviceSourceType.DeviceId: - self.logger.info(f" DEVICE: {indigo.devices.getName(elem.deviceId)}") - self.logger.info(f" GROUP NUM: {elem.buttonOrGroup}") - elif isinstance(elem, indigo.X10CommandReceivedTrigger): - self.logger.info(f" X10 COMMAND: {elem.command}") - self.logger.info(f" SOURCE TYPE: {elem.commandSourceType}") - if elem.commandSourceType == indigo.kDeviceSourceType.DeviceId: - self.logger.info(f" DEVICE: {indigo.devices.getName(elem.deviceId)}") - elif elem.commandSourceType == indigo.kDeviceSourceType.RawAddress: - self.logger.info(f" ADDRESS: {elem.address}") - elif elem.command == indigo.kX10Cmd.AvButtonPressed: - self.logger.info(f" A/V BUTTON: {elem.avButton}") - elif isinstance(elem, indigo.EmailReceivedTrigger): - self.logger.info(f" EMAIL FILTER: {elem.emailFilter}") - if elem.emailFilter == indigo.kEmailFilter.MatchEmailFields: - self.logger.info(f" FROM FILTER: {elem.emailFrom}") - self.logger.info(f" SUBJECT FILTER: {elem.emailSubject}") + if isinstance(elem, indigo.DeviceStateChangeTrigger): + self.logger.info(f" DEVICE: {indigo.devices.getName(elem.deviceId)}") + self.logger.info(f" CHANGE TYPE: {elem.stateChangeType}") + self.logger.info(f" SELECTOR KEY: {elem.stateSelector}") + if elem.stateSelectorIndex > 0: + self.logger.info(f" SELECTOR INDEX: {elem.stateSelectorIndex}") + if len(elem.stateValue) > 0: + self.logger.info(f" STATE VALUE: {elem.stateValue}") + elif isinstance(elem, indigo.VariableValueChangeTrigger): + self.logger.info(f" VARIABLE: {indigo.variables.getName(elem.variableId)}") + self.logger.info(f" CHANGE TYPE: {elem.variableChangeType}") + if len(elem.variableValue) > 0: + self.logger.info(f" VARIABLE VALUE: {elem.variableValue}") + elif isinstance(elem, indigo.InsteonCommandReceivedTrigger): + self.logger.info(f" INSTEON COMMAND: {elem.command}") + self.logger.info(f" SOURCE TYPE: {elem.commandSourceType}") + if elem.commandSourceType == indigo.kDeviceSourceType.DeviceId: + self.logger.info(f" DEVICE: {indigo.devices.getName(elem.deviceId)}") + self.logger.info(f" GROUP NUM: {elem.buttonOrGroup}") + elif isinstance(elem, indigo.X10CommandReceivedTrigger): + self.logger.info(f" X10 COMMAND: {elem.command}") + self.logger.info(f" SOURCE TYPE: {elem.commandSourceType}") + if elem.commandSourceType == indigo.kDeviceSourceType.DeviceId: + self.logger.info(f" DEVICE: {indigo.devices.getName(elem.deviceId)}") + elif elem.commandSourceType == indigo.kDeviceSourceType.RawAddress: + self.logger.info(f" ADDRESS: {elem.address}") + elif elem.command == indigo.kX10Cmd.AvButtonPressed: + self.logger.info(f" A/V BUTTON: {elem.avButton}") + elif isinstance(elem, indigo.EmailReceivedTrigger): + self.logger.info(f" EMAIL FILTER: {elem.emailFilter}") + if elem.emailFilter == indigo.kEmailFilter.MatchEmailFields: + self.logger.info(f" FROM FILTER: {elem.emailFrom}") + self.logger.info(f" SUBJECT FILTER: {elem.emailSubject}") - #################### - def logSchedule(self, elem): - self.logEventBase(elem, indigo.schedules.folders) - self.logger.info(f" DATE TYPE: {elem.dateType}") - self.logger.info(f" TIME TYPE: {elem.timeType}") - if elem.dateType == indigo.kDateType.Absolute and elem.timeType == indigo.kTimeType.Absolute: - self.logger.info(f" DATE AND TIME: {elem.absoluteDateTime}") - elif elem.dateType == indigo.kDateType.Absolute: - self.logger.info(f" ABSOLUTE DATE: {elem.absoluteDate.date()}") - elif elem.timeType == indigo.kTimeType.Absolute: - self.logger.info(f" ABSOLUTE TIME: {elem.absoluteTime.time()}") - if elem.sunDelta > 0: - self.logger.info(f" SUN DELTA: {elem.sunDelta} seconds") - if elem.randomizeBy > 0: - self.logger.info(f" RANDOMIZE BY: {elem.randomizeBy} seconds") - try: - self.logger.info(f" NEXT EXECUTION: {elem.nextExecution}") - except: - self.logger.info(f" NEXT EXECUTION: - none scheduled -") - # TODO: Need to log additional properties after they are implemented here. + #################### + def log_schedule(self, elem): + self.log_event_base(elem, indigo.schedules.folders) + self.logger.info(f" DATE TYPE: {elem.dateType}") + self.logger.info(f" TIME TYPE: {elem.timeType}") + if elem.dateType == indigo.kDateType.Absolute and elem.timeType == indigo.kTimeType.Absolute: + self.logger.info(f" DATE AND TIME: {elem.absoluteDateTime}") + elif elem.dateType == indigo.kDateType.Absolute: + self.logger.info(f" ABSOLUTE DATE: {elem.absoluteDate.date()}") + elif elem.timeType == indigo.kTimeType.Absolute: + self.logger.info(f" ABSOLUTE TIME: {elem.absoluteTime.time()}") + if elem.sunDelta > 0: + self.logger.info(f" SUN DELTA: {elem.sunDelta} seconds") + if elem.randomizeBy > 0: + self.logger.info(f" RANDOMIZE BY: {elem.randomizeBy} seconds") + try: + self.logger.info(f" NEXT EXECUTION: {elem.nextExecution}") + except: + self.logger.info(" NEXT EXECUTION: - none scheduled -") + # TODO: Need to log additional properties after they are implemented here. - #################### - def logActionGroup(self, elem): - self.logBaseElem(elem, indigo.actionGroups.folders) - # TODO: Need to add action list traversal here. + #################### + def log_action_group(self, elem): + self.log_base_elem(elem, indigo.actionGroups.folders) + # TODO: Need to add action list traversal here. - #################### - def logControlPage(self, elem): - self.logBaseElem(elem, indigo.controlPages.folders) - self.logger.info(f" HIDE TABBAR: {elem.hideTabBar}") - if len(elem.backgroundImage) > 0: - self.logger.info(f"BACKGROUND IMAGE: {elem.backgroundImage}") - # TODO: Need to log additional properties after they are implemented here. - # TODO: Need to add control list traversal here. + #################### + def log_control_page(self, elem): + self.log_base_elem(elem, indigo.controlPages.folders) + self.logger.info(f" HIDE TABBAR: {elem.hideTabBar}") + if len(elem.backgroundImage) > 0: + self.logger.info(f"BACKGROUND IMAGE: {elem.backgroundImage}") + # TODO: Need to log additional properties after they are implemented here. + # TODO: Need to add control list traversal here. - #################### - def logVariable(self, elem): - self.logBaseElem(elem, indigo.variables.folders) - self.logger.info(f" VALUE: {elem.value}") - if elem.readOnly: - self.logger.info(" READ ONLY: True") + #################### + def log_variable(self, elem): + self.log_base_elem(elem, indigo.variables.folders) + self.logger.info(f" VALUE: {elem.value}") + if elem.readOnly: + self.logger.info(" READ ONLY: True") - ######################################## - # Actions defined in MenuItems.xml: - #################### - def traverseDevices(self): - self.logListDivider("DEVICES") - for folder in indigo.devices.folders: - self.logger.info(f" FOLDER: {folder.name}") - self.logBaseFolder(folder) - for elem in indigo.devices: - self.logElemDivider() - self.logger.info(f" DEVICE: {elem.name}") - self.logDevice(elem) + ######################################## + # Actions defined in MenuItems.xml: + #################### + def traverse_devices(self): + self.log_list_divider("DEVICES") + for folder in indigo.devices.folders: + self.logger.info(f" FOLDER: {folder.name}") + self.log_base_folder(folder) + for elem in indigo.devices: + self.log_elem_divider() + self.logger.info(f" DEVICE: {elem.name}") + self.log_device(elem) - def traverseTriggers(self): - self.logListDivider("TRIGGERS") - for folder in indigo.triggers.folders: - self.logger.info(f" FOLDER: {folder.name}") - self.logBaseFolder(folder) - for elem in indigo.triggers: - self.logElemDivider() - self.logger.info(f" TRIGGER: {elem.name}") - self.logTrigger(elem) + def traverse_triggers(self): + self.log_list_divider("TRIGGERS") + for folder in indigo.triggers.folders: + self.logger.info(f" FOLDER: {folder.name}") + self.log_base_folder(folder) + for elem in indigo.triggers: + self.log_elem_divider() + self.logger.info(f" TRIGGER: {elem.name}") + self.log_trigger(elem) - def traverseSchedules(self): - self.logListDivider("SCHEDULES") - for folder in indigo.schedules.folders: - self.logger.info(f" FOLDER: {folder.name}") - self.logBaseFolder(folder) - for elem in indigo.schedules: - self.logElemDivider() - self.logger.info(f" SCHEDULE: {elem.name}") - self.logSchedule(elem) + def traverse_schedules(self): + self.log_list_divider("SCHEDULES") + for folder in indigo.schedules.folders: + self.logger.info(f" FOLDER: {folder.name}") + self.log_base_folder(folder) + for elem in indigo.schedules: + self.log_elem_divider() + self.logger.info(f" SCHEDULE: {elem.name}") + self.log_schedule(elem) - def traverseActionGroups(self): - self.logListDivider("ACTION GROUPS") - for folder in indigo.actionGroups.folders: - self.logger.info(f" FOLDER: {folder.name}") - self.logBaseFolder(folder) - for elem in indigo.actionGroups: - self.logElemDivider() - self.logger.info(f" ACTION GROUP: {elem.name}") - self.logActionGroup(elem) + def traverse_action_groups(self): + self.log_list_divider("ACTION GROUPS") + for folder in indigo.actionGroups.folders: + self.logger.info(f" FOLDER: {folder.name}") + self.log_base_folder(folder) + for elem in indigo.actionGroups: + self.log_elem_divider() + self.logger.info(f" ACTION GROUP: {elem.name}") + self.log_action_group(elem) - def traverseControlPages(self): - self.logListDivider("CONTROL PAGES") - for folder in indigo.controlPages.folders: - self.logger.info(f" FOLDER: {folder.name}") - self.logBaseFolder(folder) - for elem in indigo.controlPages: - self.logElemDivider() - self.logger.info(f" CONTROL PAGE: {elem.name}") - self.logControlPage(elem) + def traverse_control_pages(self): + self.log_list_divider("CONTROL PAGES") + for folder in indigo.controlPages.folders: + self.logger.info(f" FOLDER: {folder.name}") + self.log_base_folder(folder) + for elem in indigo.controlPages: + self.log_elem_divider() + self.logger.info(f" CONTROL PAGE: {elem.name}") + self.log_control_page(elem) - def traverseVariables(self): - self.logListDivider("VARIABLES") - for folder in indigo.variables.folders: - self.logger.info(f" FOLDER: {folder.name}") - self.logBaseFolder(folder) - for elem in indigo.variables: - self.logElemDivider() - self.logger.info(f" VARIABLE: {elem.name}") - self.logVariable(elem) + def traverse_variables(self): + self.log_list_divider("VARIABLES") + for folder in indigo.variables.folders: + self.logger.info(f" FOLDER: {folder.name}") + self.log_base_folder(folder) + for elem in indigo.variables: + self.log_elem_divider() + self.logger.info(f" VARIABLE: {elem.name}") + self.log_variable(elem) - #################### - def traverseDatabase(self): - self.traverseDevices() - self.traverseTriggers() - self.traverseSchedules() - self.traverseActionGroups() - self.traverseControlPages() - self.traverseVariables() + #################### + def traverse_database(self): + self.traverse_devices() + self.traverse_triggers() + self.traverse_schedules() + self.traverse_action_groups() + self.traverse_control_pages() + self.traverse_variables() diff --git a/Example Device - Custom.indigoPlugin/Contents/Info.plist b/Example Device - Custom.indigoPlugin/Contents/Info.plist index 319ae50..df65011 100644 --- a/Example Device - Custom.indigoPlugin/Contents/Info.plist +++ b/Example Device - Custom.indigoPlugin/Contents/Info.plist @@ -3,9 +3,9 @@ PluginVersion - 2021.1.0 + 2022.1.0 ServerApiVersion - 2.5 + 3.0 IwsApiVersion 1.0.0 CFBundleDisplayName @@ -18,7 +18,7 @@ CFBundleURLName - http://wiki.indigodomo.com/doku.php?id=plugins:example_dev_1 + https://wiki.indigodomo.com/doku.php?id=plugins:example_dev_1 diff --git a/Example Device - Custom.indigoPlugin/Contents/Server Plugin/Actions.xml b/Example Device - Custom.indigoPlugin/Contents/Server Plugin/Actions.xml index 2a2d617..36a4a7d 100644 --- a/Example Device - Custom.indigoPlugin/Contents/Server Plugin/Actions.xml +++ b/Example Device - Custom.indigoPlugin/Contents/Server Plugin/Actions.xml @@ -1,26 +1,26 @@ - http://wiki.indigodomo.com/doku.php?id=plugins:example_dev_1 - - Reset Hardware - resetHardware - - - Update Hardware Firmware - updateHardwareFirmware - - http://wiki.indigodomo.com/doku.php?id=plugins:example_dev_1 - - - - Update to the latest beta version (not really) - - - - Send notifications when there's a new beta version (not really) - - - + https://wiki.indigodomo.com/doku.php?id=plugins:example_dev_1 + + Reset Hardware + reset_hardware + + + Update Hardware Firmware + update_hardware_firmware + + https://wiki.indigodomo.com/doku.php?id=plugins:example_dev_1 + + + + Update to the latest beta version (not really) + + + + Send notifications when there's a new beta version (not really) + + + diff --git a/Example Device - Custom.indigoPlugin/Contents/Server Plugin/Devices.xml b/Example Device - Custom.indigoPlugin/Contents/Server Plugin/Devices.xml index de76ebc..76787c4 100644 --- a/Example Device - Custom.indigoPlugin/Contents/Server Plugin/Devices.xml +++ b/Example Device - Custom.indigoPlugin/Contents/Server Plugin/Devices.xml @@ -1,149 +1,149 @@ - - - Current Server Time - - - - - - - - - - - Integer - Current Server Seconds - Current Server Seconds - - - Boolean - Current Server Seconds is Even - Current Server Seconds is Even - - - Separator - - - String - Current Server Date and Time - Current Server Date and Time - - - serverTimeSeconds - + + + Current Server Time + + + + + + + + + + + Integer + Current Server Seconds + Current Server Seconds + + + Boolean + Current Server Seconds is Even + Current Server Seconds is Even + + + Separator + + + String + Current Server Date and Time + Current Server Date and Time + + + serverTimeSeconds + - - - State Value Updater - - - Integer - Always Integer Value - Always Integer Value - - - Number - Always Float Value - Always Float Value - - - String - String Value Toggle 0.0 and 0.000 - String Value Toggle 0.0 and 0.000 - - - String - String Value Toggle abc to def - String Value Toggle abc to def - - - Integer - Integer to Float Value - Integer to Float Value - - - Integer - Integer to String Value - Integer to String Value - - - Number - Float to String Value - Float to String Value - - - Integer - Timestamp - Timestamp - - - timeStamp - + + + State Value Updater + + + Integer + Always Integer Value + Always Integer Value + + + Number + Always Float Value + Always Float Value + + + String + String Value Toggle 0.0 and 0.000 + String Value Toggle 0.0 and 0.000 + + + String + String Value Toggle abc to def + String Value Toggle abc to def + + + Integer + Integer to Float Value + Integer to Float Value + + + Integer + Integer to String Value + Integer to String Value + + + Number + Float to String Value + Float to String Value + + + Integer + Timestamp + Timestamp + + + timeStamp + - - - Scene - - - - - - - - - - - - - - - - - + + + Scene + + + + + + + + + + + + + + + + + diff --git a/Example Device - Custom.indigoPlugin/Contents/Server Plugin/MenuItems.xml b/Example Device - Custom.indigoPlugin/Contents/Server Plugin/MenuItems.xml index c4e995c..4210815 100644 --- a/Example Device - Custom.indigoPlugin/Contents/Server Plugin/MenuItems.xml +++ b/Example Device - Custom.indigoPlugin/Contents/Server Plugin/MenuItems.xml @@ -1,13 +1,13 @@ - - Time Warp! - timeWarp - + + Time Warp! + time_warp + diff --git a/Example Device - Custom.indigoPlugin/Contents/Server Plugin/plugin.py b/Example Device - Custom.indigoPlugin/Contents/Server Plugin/plugin.py index 1f80ea1..7b365ab 100644 --- a/Example Device - Custom.indigoPlugin/Contents/Server Plugin/plugin.py +++ b/Example Device - Custom.indigoPlugin/Contents/Server Plugin/plugin.py @@ -1,13 +1,10 @@ #! /usr/bin/env python # -*- coding: utf-8 -*- #################### -# Copyright (c) 2014, Perceptive Automation, LLC. All rights reserved. -# http://www.indigodomo.com +# Copyright (c) 2022, Perceptive Automation, LLC. All rights reserved. +# https://www.indigodomo.com import indigo - -import os -import sys import time # Note the "indigo" module is automatically imported and made available inside @@ -15,270 +12,293 @@ ################################################################################ class Plugin(indigo.PluginBase): - ######################################## - def __init__(self, pluginId, pluginDisplayName, pluginVersion, pluginPrefs): - super(Plugin, self).__init__(pluginId, pluginDisplayName, pluginVersion, pluginPrefs) - self.debug = True - self.timeWarpOn = False - self.timeWarpCount = 0 + ######################################## + def __init__(self, plugin_id, plugin_display_name, plugin_version, plugin_prefs): + super().__init__(plugin_id, plugin_display_name, plugin_version, plugin_prefs) + self.debug = True + self.time_warp_on = False + self.time_warp_count = 0 + self.state_updater_dev = None + self.server_time_dev = None - ######################################## - def startup(self): - self.debugLog(u"startup called") + ######################################## + def startup(self): + self.logger.debug("startup called") + # Most plugins that expose new device types will depend on the user + # creating the new device from the Indigo UI (just like a native device). + # + # However, it is also possible for the plugin to create the devices + # automatically at runtime: + if "Example Server Time" in indigo.devices: + self.server_time_dev = indigo.devices["Example Server Time"] + else: + self.logger.info("creating test device: Example Server Time") + self.server_time_dev = indigo.device.create( + indigo.kProtocol.Plugin, + "Example Server Time", + "test device created by example plugin", + deviceTypeId="serverTimeDevice" + ) + # Override the state icon shown (in Indigo Touch and client Main Window) + # for this device to be the timer image icon: + self.server_time_dev.updateStateImageOnServer(indigo.kStateImageSel.TimerOn) + if "Example State Updater" in indigo.devices: + self.state_updater_dev = indigo.devices["Example State Updater"] + else: + self.logger.info("creating test device: Example State Updater") + self.state_updater_dev = indigo.device.create( + indigo.kProtocol.Plugin, + "Example State Updater", + "test state value updating device created by example plugin device", + deviceTypeId="stateUpdater" + ) + # Override the state icon shown (in Indigo Touch and client Main Window) + # for this device to be the timer image icon: + self.state_updater_dev.updateStateImageOnServer(indigo.kStateImageSel.TimerOn) - # Most plugins that expose new device types will depend on the user - # creating the new device from the Indigo UI (just like a native device). - # - # However, it is also possible for the plugin to create the devices - # automatically at runtime: - if "Example Server Time" in indigo.devices: - self.serverTimeDev = indigo.devices["Example Server Time"] - else: - indigo.server.log(u"creating test device: Example Server Time") - self.serverTimeDev = indigo.device.create(indigo.kProtocol.Plugin, "Example Server Time", "test device created by example plugin", deviceTypeId="serverTimeDevice") - # Override the state icon shown (in Indigo Touch and client Main Window) - # for this device to be the timer image icon: - self.serverTimeDev.updateStateImageOnServer(indigo.kStateImageSel.TimerOn) + def shutdown(self): + self.logger.debug("shutdown called") + key_value_list = [ + {'key': 'serverTimeSeconds', 'value': 0}, + {'key': 'serverSecondsEven', 'value': False}, + {'key': 'serverDateTime', 'value': "--"}, + ] + self.server_time_dev.updateStatesOnServer(key_value_list) - if "Example State Updater" in indigo.devices: - self.stateUpdaterDev = indigo.devices["Example State Updater"] - else: - indigo.server.log(u"creating test device: Example State Updater") - self.stateUpdaterDev = indigo.device.create(indigo.kProtocol.Plugin, "Example State Updater", "test state value updating device created by example plugin device", deviceTypeId="stateUpdater") - # Override the state icon shown (in Indigo Touch and client Main Window) - # for this device to be the timer image icon: - self.stateUpdaterDev.updateStateImageOnServer(indigo.kStateImageSel.TimerOn) + ######################################## + # If runConcurrentThread() is defined, then a new thread is automatically created + # and runConcurrentThread() is called in that thread after startup() has been called. + # + # runConcurrentThread() should loop forever and only return after self.stopThread + # becomes True. If this function returns prematurely then the plugin host process + # will log an error and attempt to call runConcurrentThread() again after several seconds. + def runConcurrentThread(self): + try: + while True: + server_time = indigo.server.getTime() + server_time_second = server_time.second + if self.time_warp_on: + self.time_warp_count += 1 + server_time_second = self.time_warp_count - def shutdown(self): - self.debugLog(u"shutdown called") + key_value_list = [ + {'key': 'serverTimeSeconds', 'value': server_time_second}, + {'key': 'serverSecondsEven', 'value': not bool(server_time_second % 2)}, + {'key': 'serverDateTime', 'value': str(server_time)} + ] + self.server_time_dev.updateStatesOnServer(key_value_list) - keyValueList = [] - keyValueList.append({'key':'serverTimeSeconds', 'value':0}) - keyValueList.append({'key':'serverSecondsEven', 'value':False}) - keyValueList.append({'key':'serverDateTime', 'value':"--"}) - self.serverTimeDev.updateStatesOnServer(keyValueList) + key_value_list = [] + if server_time_second % 2: + key_value_list.append({'key': 'alwaysInteger', 'value': 0}) + key_value_list.append({'key': 'alwaysFloat', 'value': 0.01}) + key_value_list.append({'key': 'stringToggleFloats', 'value': "0.0"}) + key_value_list.append({'key': 'stringToggleStrings', 'value': "abc"}) + key_value_list.append({'key': 'integerToFloat', 'value': 0}) + key_value_list.append({'key': 'integerToString', 'value': 0}) + key_value_list.append({'key': 'floatToString', 'value': 0.1}) + else: + key_value_list.append({'key': 'alwaysInteger', 'value': 1}) + key_value_list.append({'key': 'alwaysFloat', 'value': 0.123456, 'decimalPlaced': 4}) + key_value_list.append({'key': 'stringToggleFloats', 'value': "0.0000"}) + key_value_list.append({'key': 'stringToggleStrings', 'value': "def"}) + key_value_list.append({'key': 'integerToFloat', 'value': 0.1}) + key_value_list.append({'key': 'integerToString', 'value': "abc"}) + key_value_list.append({'key': 'floatToString', 'value': "abc"}) + key_value_list.append({'key': 'timeStamp', 'value': str(time.time()).split(".")[0]}) + try: + self.state_updater_dev.updateStatesOnServer(key_value_list) + except Exception as exc: + self.logger.exception(exc) + if self.time_warp_on: + self.sleep(0.12) + else: + self.sleep(1) + except self.StopThread: + pass # Optionally catch the StopThread exception and do any needed cleanup. - ######################################## - # If runConcurrentThread() is defined, then a new thread is automatically created - # and runConcurrentThread() is called in that thread after startup() has been called. - # - # runConcurrentThread() should loop forever and only return after self.stopThread - # becomes True. If this function returns prematurely then the plugin host process - # will log an error and attempt to call runConcurrentThread() again after several seconds. - def runConcurrentThread(self): - try: - while True: - serverTime = indigo.server.getTime() - serverTimeSecond = serverTime.second - if self.timeWarpOn: - self.timeWarpCount += 1 - serverTimeSecond = self.timeWarpCount + ######################################## + # Actions defined in MenuItems.xml: + #################### + def time_warp(self): + if not self.time_warp_on: + self.logger.info("starting mega time warp") + self.time_warp_on = True + else: + self.logger.info("stopping mega time warp") + self.time_warp_on = False - keyValueList = [] - keyValueList.append({'key':'serverTimeSeconds', 'value':serverTimeSecond}) - keyValueList.append({'key':'serverSecondsEven', 'value':not bool(serverTimeSecond % 2)}) - keyValueList.append({'key':'serverDateTime', 'value':str(serverTime)}) - self.serverTimeDev.updateStatesOnServer(keyValueList) + ######################################## + """ + Buttons and dynamic list methods defined for the scenes custom device - keyValueList = [] - if serverTimeSecond % 2: - keyValueList.append({'key':'alwaysInteger', 'value':0}) - keyValueList.append({'key':'alwaysFloat', 'value':0.01}) - keyValueList.append({'key':'stringToggleFloats', 'value':"0.0"}) - keyValueList.append({'key':'stringToggleStrings', 'value':"abc"}) - keyValueList.append({'key':'integerToFloat', 'value':0}) - keyValueList.append({'key':'integerToString', 'value':0}) - keyValueList.append({'key':'floatToString', 'value':0.1}) - else: - keyValueList.append({'key':'alwaysInteger', 'value':1}) - keyValueList.append({'key':'alwaysFloat', 'value':0.123456, 'decimalPlaced':4}) - keyValueList.append({'key':'stringToggleFloats', 'value':"0.0000"}) - keyValueList.append({'key':'stringToggleStrings', 'value':"def"}) - keyValueList.append({'key':'integerToFloat', 'value':0.1}) - keyValueList.append({'key':'integerToString', 'value':"abc"}) - keyValueList.append({'key':'floatToString', 'value':"abc"}) - keyValueList.append({'key':'timeStamp', 'value':str(time.time()).split(".")[0]}) - self.stateUpdaterDev.updateStatesOnServer(keyValueList) - if self.timeWarpOn: - self.sleep(0.12) - else: - self.sleep(1) - except self.StopThread: - pass # Optionally catch the StopThread exception and do any needed cleanup. + Overview of scene devices: + Scene devices are custom devices that will contain multiple devices. + We implement this custom device by storing a comma-delimited list of + device IDs which is manipulated by clicking Add and Delete buttons + in the device config dialog. There are two dynamic list controls in + the dialog: + 1) one popup button control on which the user selects a device + to add then clicks the Add Device button. + 2) one list control which shows all the devices that have already + been added to the scene and in which the user can select devices + and click the Delete Devices button + There is a hidden field "memberDevices" that stores a comma-delimited + list of device ids for each member of the scene. The add_device and + delete_devices methods will take the selections from the respective + dynamic lists and do the right thing with the list. + Finally, there are the two methods that build the dynamic lists. + The method that builds the source list will inspect the "memberDevices" + field and won't include those devices in the source list (so the user + won't be confused by seeing a device that's already in the member list + in the source list). The method that builds the member list of course + uses "memberDevices" to build the list. - ######################################## - # Actions defined in MenuItems.xml: - #################### - def timeWarp(self): - if not self.timeWarpOn: - indigo.server.log(u"starting mega time warp") - self.timeWarpOn = True - else: - indigo.server.log(u"stopping mega time warp") - self.timeWarpOn = False + One other thing that should be done probably - in the deviceStartComm + method (or the appropriate CRUD methods if you're using them instead) + you should check the IDs to make sure they're still around and if not + remove them from the device id list. - ######################################## - # Buttons and dynamic list methods defined for the scenes custom device - # - # Overview of scene devices: - # Scene devices are custom devices that will contain multiple devices. - # We implement this custom device by storing a comma-delimited list of - # device IDs which is manipulated by clicking Add and Delete buttons - # in the device config dialog. There are two dynamic list controls in - # the dialog: - # 1) one popup button control on which the user selects a device - # to add then clicks the Add Device button. - # 2) one list control which shows all the devices that have already - # been added to the scene and in which the user can select devices - # and click the Delete Devices button - # There is a hidden field "memberDevices" that stores a comma-delimited - # list of device ids for each member of the scene. The addDevice and - # deleteDevices methods will take the selections from the respective - # dynamic lists and do the right thing with the list. - # Finally, there are the two methods that build the dynamic lists. - # The method that builds the source list will inspect the "memberDevices" - # field and won't include those devices in the source list (so the user - # won't be confused by seeing a device that's already in the member list - # in the source list). The method that builds the member list of course - # uses "memberDevices" to build the list. - # - # One other thing that should be done probably - in the deviceStartComm - # method (or the appropriate CRUD methods if you're using them instead) - # you should check the IDs to make sure they're still around and if not - # remove them from the device id list. - # - # The device id list property ("memberDevices") could, of course, be - # formatted in some other way besides a comma-delimited list of ids - # if you need to store more information. You could, for instance, store - # some kind of formatted text like JSON or XML that had much more - # information. + The device id list property ("memberDevices") could, of course, be + formatted in some other way besides a comma-delimited list of ids + if you need to store more information. You could, for instance, store + some kind of formatted text like JSON or XML that had much more + information. + """ - #################### - # This is the method that's called by the Add Device button in the scene - # device config UI. - #################### - def addDevice(self, valuesDict, typeId, devId): - self.debugLog(u"addDevice called") - # just making sure that they have selected a device in the source - # list - it shouldn't be possible not to but it's safer - if "sourceDeviceMenu" in valuesDict: - # Get the device ID of the selected device - deviceId = valuesDict["sourceDeviceMenu"] - if deviceId == "": - return - # Get the list of devices that have already been added to the "scene" - # If the key doesn't exist then return an empty string indicating - # no devices have yet been added. "memberDevices" is a hidden text - # field in the dialog that holds a comma-delimited list of device - # ids, one for each of the devices in the scene. - selectedDevicesString = valuesDict.get("memberDevices","") - self.debugLog(u"adding device: %s to %s" % (deviceId, selectedDevicesString)) - # If no devices have been added then just set the selected device string to - # the device id of the device they selected in the popup - if selectedDevicesString == "": - selectedDevicesString = deviceId - # Otherwise append it to the end separated by a comma - else: - selectedDevicesString += "," + str(deviceId) - # Set the device string back to the hidden text field that contains the - # list of device ids that are in the scene - valuesDict["memberDevices"] = selectedDevicesString - self.debugLog(u"valuesDict = " + str(valuesDict)) - # Delete the selections on both dynamic lists since we don't - # want to preserve those across dialog runs - if "memberDeviceList" in valuesDict: - del valuesDict["memberDeviceList"] - if "sourceDeviceMenu" in valuesDict: - del valuesDict["sourceDeviceMenu"] - # return the new dict - return valuesDict + #################### + # This is the method that's called by the Add Device button in the scene + # device config UI. + #################### + def add_device(self, values_dict, type_id, dev_id): + self.logger.debug("add_device called") + # just making sure that they have selected a device in the source + # list - it shouldn't be possible not to but it's safer + if "sourceDeviceMenu" in values_dict: + # Get the device ID of the selected device + device_id = values_dict["sourceDeviceMenu"] + if device_id == "": + return None + # Get the list of devices that have already been added to the "scene" + # If the key doesn't exist then return an empty string indicating + # no devices have yet been added. "memberDevices" is a hidden text + # field in the dialog that holds a comma-delimited list of device + # ids, one for each of the devices in the scene. + dev_list_str = values_dict.get("memberDevices", "") + self.logger.debug(f"adding device: {device_id} to {dev_list_str}") + # If no devices have been added then just set the selected device string to + # the device id of the device they selected in the popup + if dev_list_str == "": + dev_list_str = device_id + # Otherwise append it to the end separated by a comma + else: + dev_list_str += f",{device_id}" + # Set the device string back to the hidden text field that contains the + # list of device ids that are in the scene + values_dict["memberDevices"] = dev_list_str + self.logger.debug(f"values_dict: {values_dict}") + # Delete the selections on both dynamic lists since we don't + # want to preserve those across dialog runs + if "memberDeviceList" in values_dict: + del values_dict["memberDeviceList"] + if "sourceDeviceMenu" in values_dict: + del values_dict["sourceDeviceMenu"] + # return the new dict + return values_dict - #################### - # This is the method that's called by the Delete Device button in the scene - # device config UI. - #################### - def deleteDevices(self, valuesDict, typeId, devId): - self.debugLog(u"deleteDevices called") - if "memberDevices" in valuesDict: - # Get the list of devices that are already in the scene - devicesInScene = valuesDict.get("memberDevices","").split(",") - # Get the devices they've selected in the list that they want - # to remove - selectedDevices = valuesDict.get("memberDeviceList", []) - # Loop through the devices to be deleted list and remove them - for deviceId in selectedDevices: - self.debugLog(u"remove deviceId: " + deviceId) - if deviceId in devicesInScene: - devicesInScene.remove(deviceId) - # Set the "memberDevices" field back to the new list which - # has the devices deleted from it. - valuesDict["memberDevices"] = ",".join(devicesInScene) - # Delete the selections on both dynamic lists since we don't - # want to preserve those across dialog runs - if "memberDeviceList" in valuesDict: - del valuesDict["memberDeviceList"] - if "sourceDeviceMenu" in valuesDict: - del valuesDict["sourceDeviceMenu"] - return valuesDict + #################### + # This is the method that's called by the Delete Device button in the scene + # device config UI. + #################### + def delete_devices(self, values_dict, type_id, dev_id): + self.logger.debug("delete_devices called") + if "memberDevices" in values_dict: + # Get the list of devices that are already in the scene + devs_in_scene = [] + dev_list_str = values_dict.get("memberDevices", "") + if dev_list_str: + devs_in_scene = dev_list_str.split(",") + # Get the devices they've selected in the list that they want + # to remove + sel_devs = values_dict.get("memberDeviceList", []) + # Loop through the devices to be deleted list and remove them + for device_id in sel_devs: + self.logger.debug(f"remove device_id: {device_id}") + if device_id in devs_in_scene: + devs_in_scene.remove(device_id) + # Set the "memberDevices" field back to the new list which + # has the devices deleted from it. + values_dict["memberDevices"] = ",".join(devs_in_scene) + # Delete the selections on both dynamic lists since we don't + # want to preserve those across dialog runs + if "memberDeviceList" in values_dict: + del values_dict["memberDeviceList"] + if "sourceDeviceMenu" in values_dict: + del values_dict["sourceDeviceMenu"] + return values_dict - #################### - # This is the method that's called to build the source device list. Note - # that valuesDict is read-only so any changes you make to it will be discarded. - #################### - def sourceDevices(self, filter="", valuesDict=None, typeId="", targetId=0): - self.debugLog(u"sourceDevices called with filter: %s typeId: %s targetId: %s" % (filter, typeId, str(targetId))) - returnList = list() - # if valuesDict doesn't exist yet - if this is a brand new device - # then we just create an empty dict so the rest of the logic will - # work correctly. Many other ways to skin that particular cat. - if not valuesDict: - valuesDict = {} - # Get the member device id list, loop over all devices, and if the device - # id isn't in the member list then include it in the source list. - deviceList = valuesDict.get("memberDevices","").split(",") - for devId in indigo.devices.iterkeys(): - if str(devId) not in deviceList: - returnList.append((str(devId),indigo.devices.get(devId).name)) - return returnList + #################### + # This is the method that's called to build the source device list. Note + # that values_dict is read-only so any changes you make to it will be discarded. + #################### + def source_devices(self, filter_str="", values_dict=None, type_id="", target_id=0): + self.logger.debug(f"source_devices called with filter: {filter_str} type_id: {type_id} target_id: {target_id}") + return_list = [] + # if values_dict doesn't exist yet - if this is a brand new device + # then we just create an empty dict so the rest of the logic will + # work correctly. Many other ways to skin that particular cat. + if not values_dict: + values_dict = {} + # Get the member device id list, loop over all devices, and if the device + # id isn't in the member list then include it in the source list. + device_list = [] + dev_list_str = values_dict.get("memberDevices", "") + if dev_list_str: + device_list = dev_list_str.split(",") + for dev_id in indigo.devices.keys(): + if str(dev_id) not in device_list: + return_list.append((str(dev_id), indigo.devices.get(dev_id).name)) + return return_list - #################### - # This is the method that's called to build the member device list. Note - # that valuesDict is read-only so any changes you make to it will be discarded. - #################### - def memberDevices(self, filter="", valuesDict=None, typeId="", targetId=0): - self.debugLog(u"memberDevices called with filter: %s typeId: %s targetId: %s" % (filter, typeId, str(targetId))) - returnList = list() - # valuesDict may be empty or None if it's a brand new device - if valuesDict and "memberDevices" in valuesDict: - # Get the list of devices - deviceListString = valuesDict["memberDevices"] - self.debugLog(u"memberDeviceString: " + deviceListString) - deviceList = deviceListString.split(",") - # Iterate over the list and if the device exists (it could have been - # deleted) then add it to the list. - for devId in deviceList: - if int(devId) in indigo.devices: - returnList.append((devId, indigo.devices[int(devId)].name)) - return returnList + #################### + # This is the method that's called to build the member device list. Note + # that values_dict is read-only so any changes you make to it will be discarded. + #################### + def member_devices(self, filter_str="", values_dict=None, type_id="", target_id=0): + self.logger.debug(f"member_devices called with filter: {filter_str} type_id: {type_id} target_id: {target_id}") + return_list = [] + # values_dict may be empty or None if it's a brand new device + if values_dict and "memberDevices" in values_dict: + # Get the list of devices + dev_list_str = values_dict["memberDevices"] + self.logger.debug(f"memberDeviceString: {dev_list_str}") + if dev_list_str: + device_list = dev_list_str.split(",") + # Iterate over the list and if the device exists (it could have been + # deleted) then add it to the list. + for dev_id in device_list: + if int(dev_id) in indigo.devices: + return_list.append((dev_id, indigo.devices[int(dev_id)].name)) + return return_list - ######################################## - def validateDeviceConfigUi(self, valuesDict, typeId, devId): - # If the typeId is "scene", we want to clear the selections on both - # dynamic lists so that they're not stored since we really don't - # care about those. - self.debugLog(u"validateDeviceConfigUi: typeId: %s devId: %s" % (typeId, str(devId))) - if typeId == "scene": - if "memberDeviceList" in valuesDict: - valuesDict["memberDeviceList"] = "" - if "sourceDeviceMenu" in valuesDict: - valuesDict["sourceDeviceMenu"] = "" - return (True, valuesDict) + ######################################## + def validateDeviceConfigUi(self, values_dict, type_id, dev_id): + # If the type_id is "scene", we want to clear the selections on both + # dynamic lists so that they're not stored since we really don't + # care about those. + self.logger.debug(f"validateDeviceConfigUi: type_id: {type_id} dev_id: {dev_id}") + if type_id == "scene": + if "memberDeviceList" in values_dict: + values_dict["memberDeviceList"] = "" + if "sourceDeviceMenu" in values_dict: + values_dict["sourceDeviceMenu"] = "" + return (True, values_dict) - ######################################## - # Plugin Actions object callbacks (pluginAction is an Indigo plugin action instance) - ###################### - def resetHardware(self, pluginAction): - self.debugLog(u"resetHardware action called:\n" + str(pluginAction)) + ######################################## + # Plugin Actions object callbacks (action is an Indigo plugin action instance) + ###################### + def reset_hardware(self, action): + self.logger.debug(f"reset_hardware action called:\n {action}") - def updateHardwareFirmware(self, pluginAction): - self.debugLog(u"updateHardwareFirmware action called:\n" + str(pluginAction)) + def update_hardware_firmware(self, action): + self.logger.debug(f"update_hardware_firmware action called:\n {action}") diff --git a/Example Device - Energy Meter.indigoPlugin/Contents/Info.plist b/Example Device - Energy Meter.indigoPlugin/Contents/Info.plist index 3d601d3..09c441b 100644 --- a/Example Device - Energy Meter.indigoPlugin/Contents/Info.plist +++ b/Example Device - Energy Meter.indigoPlugin/Contents/Info.plist @@ -2,24 +2,24 @@ - PluginVersion - 2022.1.0 - ServerApiVersion - 3.0 - IwsApiVersion - 1.0.0 - CFBundleDisplayName - Example Device - Energy Meter - CFBundleIdentifier - com.perceptiveautomation.indigoplugin.example-device-energymeter - CFBundleVersion - 1.0.0 - CFBundleURLTypes - - - CFBundleURLName - https://wiki.indigodomo.com/doku.php?id=plugins:example_dev_energymeter_1 - - + PluginVersion + 2022.1.0 + ServerApiVersion + 3.0 + IwsApiVersion + 1.0.0 + CFBundleDisplayName + Example Device - Energy Meter + CFBundleIdentifier + com.perceptiveautomation.indigoplugin.example-device-energymeter + CFBundleVersion + 1.0.0 + CFBundleURLTypes + + + CFBundleURLName + https://wiki.indigodomo.com/doku.php?id=plugins:example_dev_energymeter_1 + + diff --git a/Example Device - Energy Meter.indigoPlugin/Contents/Server Plugin/Actions.xml b/Example Device - Energy Meter.indigoPlugin/Contents/Server Plugin/Actions.xml index 0e044e2..4bdbf87 100644 --- a/Example Device - Energy Meter.indigoPlugin/Contents/Server Plugin/Actions.xml +++ b/Example Device - Energy Meter.indigoPlugin/Contents/Server Plugin/Actions.xml @@ -1,25 +1,25 @@ - - Set Backlight Brightness - set_backlight_brightness - - - - - - - + + Set Backlight Brightness + set_backlight_brightness + + + + + + + diff --git a/Example Device - Energy Meter.indigoPlugin/Contents/Server Plugin/Devices.xml b/Example Device - Energy Meter.indigoPlugin/Contents/Server Plugin/Devices.xml index 922f724..c0ded95 100644 --- a/Example Device - Energy Meter.indigoPlugin/Contents/Server Plugin/Devices.xml +++ b/Example Device - Energy Meter.indigoPlugin/Contents/Server Plugin/Devices.xml @@ -1,73 +1,73 @@ - - - Example Energy Meter - - - - - + + + Example Energy Meter + + + + + - - - - - - Integer - Backlight Brightness - Backlight Brightness - - - + accumEnergyTotal (kWh) + curEnergyLevel (W) - only exists SupportsPowerMeter is True + + The plugin can specify additional custom states and custom + actions (in Actions.xml) to modify custom states. As an example + here, we define a new custom state, backlightBrightness, which + is used to control the brightness of the backlit display of + the module. + --> + + Integer + Backlight Brightness + Backlight Brightness + + + diff --git a/Example Device - Energy Meter.indigoPlugin/Contents/Server Plugin/plugin.py b/Example Device - Energy Meter.indigoPlugin/Contents/Server Plugin/plugin.py index a4f5ff4..aa4593f 100644 --- a/Example Device - Energy Meter.indigoPlugin/Contents/Server Plugin/plugin.py +++ b/Example Device - Energy Meter.indigoPlugin/Contents/Server Plugin/plugin.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- #################### # Copyright (c) 2022, Perceptive Automation, LLC. All rights reserved. -# http://www.indigodomo.com +# https://www.indigodomo.com import random @@ -12,125 +12,122 @@ ################################################################################ class Plugin(indigo.PluginBase): - ######################################## - def __init__(self, plugin_id, plugin_display_name, plugin_version, plugin_prefs): - super(Plugin, self).__init__(plugin_id, plugin_display_name, plugin_version, plugin_prefs) - self.debug = True + ######################################## + def __init__(self, plugin_id, plugin_display_name, plugin_version, plugin_prefs): + super().__init__(plugin_id, plugin_display_name, plugin_version, plugin_prefs) + self.debug = True - ######################################## - def startup(self): - self.logger.debug("startup called") + ######################################## + def startup(self): + self.logger.debug("startup called") - def shutdown(self): - self.logger.debug("shutdown called") + def shutdown(self): + self.logger.debug("shutdown called") - ######################################## - # Poll all of the states from the energy meter and pass new values to - # Indigo Server. - def _refresh_states_from_hardware(self, dev, log_refresh): - # As an example here we update the current power (Watts) to a random - # value, and we increase the kWh by a smidge. - # - # Note the states are automatically created based on the SupportsEnergyMeter - # and SupportsPowerMeter device properties. - # - # The plugin instance property is updated by updating the states. - key_value_list = list() - if "curEnergyLevel" in dev.states: - simulate_watts = random.randint(0, 500) - simulate_watts_str = f"{simulate_watts} W" - if log_refresh: - self.logger.info(f'received "{dev.name}" power load to {simulate_watts_str}') - key_value_list.append({'key': 'curEnergyLevel', 'value': simulate_watts, 'uiValue': simulate_watts_str}) - if "accumEnergyTotal" in dev.states: - simulate_kwh = dev.states.get("accumEnergyTotal", 0) + 0.001 - simulate_kwh_str = f"{simulate_kwh:.3f} kWh" - if log_refresh: - self.logger.info(f'received "{dev.name}" energy total to {simulate_kwh_str}') - key_value_list.append({'key': 'accumEnergyTotal', 'value': simulate_kwh, 'uiValue': simulate_kwh_str}) - dev.updateStatesOnServer(key_value_list) + ######################################## + # Poll all of the states from the energy meter and pass new values to + # Indigo Server. + def _refresh_states_from_hardware(self, dev, log_refresh): + # As an example here we update the current power (Watts) to a random + # value, and we increase the kWh by a smidge. + # + # Note the states are automatically created based on the SupportsEnergyMeter + # and SupportsPowerMeter device properties. + # + # The plugin instance property is updated by updating the states. + key_value_list = [] + if "curEnergyLevel" in dev.states: + simulate_watts = random.randint(0, 500) + simulate_watts_str = f"{simulate_watts} W" + if log_refresh: + self.logger.info(f'received "{dev.name}" power load to {simulate_watts_str}') + key_value_list.append({'key': 'curEnergyLevel', 'value': simulate_watts, 'uiValue': simulate_watts_str}) + if "accumEnergyTotal" in dev.states: + simulate_kwh = dev.states.get("accumEnergyTotal", 0) + 0.001 + simulate_kwh_str = f"{simulate_kwh:.3f} kWh" + if log_refresh: + self.logger.info(f'received "{dev.name}" energy total to {simulate_kwh_str}') + key_value_list.append({'key': 'accumEnergyTotal', 'value': simulate_kwh, 'uiValue': simulate_kwh_str}) + dev.updateStatesOnServer(key_value_list) - ######################################## - def runConcurrentThread(self): - try: - while True: - for dev in indigo.devices.iter("self"): - if not dev.enabled or not dev.configured: - continue - # Plugins that need to poll out the status from the meter - # could do so here, then broadcast back the new values to the - # Indigo Server. - self._refresh_states_from_hardware(dev, False) - self.sleep(2) - except self.StopThread: - pass # Optionally catch the StopThread exception and do any needed cleanup. + ######################################## + def runConcurrentThread(self): + try: + while True: + for dev in indigo.devices.iter("self"): + if not dev.enabled or not dev.configured: + continue + # Plugins that need to poll out the status from the meter + # could do so here, then broadcast back the new values to the + # Indigo Server. + self._refresh_states_from_hardware(dev, False) + self.sleep(2) + except self.StopThread: + pass # Optionally catch the StopThread exception and do any needed cleanup. - ######################################## - def validateDeviceConfigUi(self, values_dict, type_id, dev_id): - return (True, values_dict) + ######################################## + def validateDeviceConfigUi(self, values_dict, type_id, dev_id): + return (True, values_dict) - ######################################## - def deviceStartComm(self, dev): - # Called when communication with the hardware should be established. - # Here would be a good place to poll out the current states from the - # meter. If periodic polling of the meter is needed (that is, it - # doesn't broadcast changes back to the plugin somehow), then consider - # adding that to runConcurrentThread() above. - self._refresh_states_from_hardware(dev, True) + ######################################## + def deviceStartComm(self, dev): + # Called when communication with the hardware should be established. + # Here would be a good place to poll out the current states from the + # meter. If periodic polling of the meter is needed (that is, it + # doesn't broadcast changes back to the plugin somehow), then consider + # adding that to runConcurrentThread() above. + self._refresh_states_from_hardware(dev, True) - def deviceStopComm(self, dev): - # Called when communication with the hardware should be shutdown. - pass + def deviceStopComm(self, dev): + # Called when communication with the hardware should be shutdown. + pass - ######################################## - # General Action callback - ###################### - def actionControlUniversal(self, action, dev): - ###### BEEP ###### - if action.deviceAction == indigo.kUniversalAction.Beep: - # Beep the hardware module (dev) here: - # FIXME: add implementation here - self.logger.info(f"sent '{dev.name}' beep request") - ###### ENERGY UPDATE ###### - elif action.deviceAction == indigo.kUniversalAction.EnergyUpdate: - # Request hardware module (dev) for its most recent meter data here: - # FIXME: add implementation here - self._refresh_states_from_hardware(dev, True) - ###### ENERGY RESET ###### - elif action.deviceAction == indigo.kUniversalAction.EnergyReset: - # Request that the hardware module (dev) reset its accumulative energy usage data here: - # FIXME: add implementation here - self.logger.info(f"sent '{dev.name}' energy usage reset") - # And then tell Indigo to reset it by just setting the value to 0. - # This will automatically reset Indigo's time stamp for the accumulation. - dev.updateStateOnServer("accumEnergyTotal", 0.0) - ###### STATUS REQUEST ###### - elif action.deviceAction == indigo.kUniversalAction.RequestStatus: - # Query hardware module (dev) for its current status here: - # FIXME: add implementation here - self._refresh_states_from_hardware(dev, True) + ######################################## + # General Action callback + ###################### + def actionControlUniversal(self, action, dev): + ###### BEEP ###### + if action.deviceAction == indigo.kUniversalAction.Beep: + # Beep the hardware module (dev) here: + # FIXME: add implementation here + self.logger.info(f"sent '{dev.name}' beep request") + ###### ENERGY UPDATE ###### + elif action.deviceAction == indigo.kUniversalAction.EnergyUpdate: + # Request hardware module (dev) for its most recent meter data here: + # FIXME: add implementation here + self._refresh_states_from_hardware(dev, True) + ###### ENERGY RESET ###### + elif action.deviceAction == indigo.kUniversalAction.EnergyReset: + # Request that the hardware module (dev) reset its accumulative energy usage data here: + # FIXME: add implementation here + self.logger.info(f"sent '{dev.name}' energy usage reset") + # And then tell Indigo to reset it by just setting the value to 0. + # This will automatically reset Indigo's time stamp for the accumulation. + dev.updateStateOnServer("accumEnergyTotal", 0.0) + ###### STATUS REQUEST ###### + elif action.deviceAction == indigo.kUniversalAction.RequestStatus: + # Query hardware module (dev) for its current status here: + # FIXME: add implementation here + self._refresh_states_from_hardware(dev, True) - ######################################## - # Custom Plugin Action callbacks (defined in Actions.xml) - ###################### - def set_backlight_brightness(self, action, dev): - try: - new_brightness = int(action.props.get("brightness", 100)) - except ValueError: - # The int() cast above might fail if the user didn't enter a number: - self.logger.info( - f"set backlight brightness action to device '{dev.name}' -- invalid brightness value", - isError=True - ) - return - # Command hardware module (dev) to set backlight brightness here: - # FIXME: add implementation here - send_success = True # Set to False if it failed. - if send_success: - # If success then log that the command was successfully sent. - self.logger.info(f"sent '{dev.name}' set backlight brightness to {new_brightness}") - # And then tell the Indigo Server to update the state: - dev.updateStateOnServer("backlightBrightness", new_brightness) - else: - # Else log failure but do NOT update state on Indigo Server. - self.logger.info(f"send '{dev.name}' set backlight brightness to {new_brightness} failed", isError=True) + ######################################## + # Custom Plugin Action callbacks (defined in Actions.xml) + ###################### + def set_backlight_brightness(self, plugin_action, dev): + try: + new_brightness = int(plugin_action.props.get("brightness", 100)) + except ValueError: + # The int() cast above might fail if the user didn't enter a number: + self.logger.error(f"set backlight brightness action to device \"{dev.name}\" -- invalid brightness value") + return + # Command hardware module (dev) to set backlight brightness here: + # FIXME: add implementation here + send_success = True # Set to False if it failed. + if send_success: + # If success then log that the command was successfully sent. + self.logger.info(f"sent \"{dev.name}\" set backlight brightness to {new_brightness}") + # And then tell the Indigo Server to update the state: + dev.updateStateOnServer("backlightBrightness", new_brightness) + else: + # Else log failure but do NOT update state on Indigo Server. + self.logger.error(f"send \"{dev.name}\" set backlight brightness to {new_brightness} failed") diff --git a/Example Device - Factory.indigoPlugin/Contents/Info.plist b/Example Device - Factory.indigoPlugin/Contents/Info.plist index 566b482..67a3805 100644 --- a/Example Device - Factory.indigoPlugin/Contents/Info.plist +++ b/Example Device - Factory.indigoPlugin/Contents/Info.plist @@ -2,24 +2,24 @@ - PluginVersion - 2021.1.0 - ServerApiVersion - 2.5 - IwsApiVersion - 1.0.0 - CFBundleDisplayName - Example Device - Factory - CFBundleIdentifier - com.perceptiveautomation.indigoplugin.example-device-factory1 - CFBundleVersion - 1.0.0 - CFBundleURLTypes - - - CFBundleURLName - http://wiki.indigodomo.com/doku.php?id=plugins:example_dev_factory1 - - + PluginVersion + 2022.1.0 + ServerApiVersion + 3.0 + IwsApiVersion + 1.0.0 + CFBundleDisplayName + Example Device - Factory + CFBundleIdentifier + com.perceptiveautomation.indigoplugin.example-device-factory1 + CFBundleVersion + 1.0.0 + CFBundleURLTypes + + + CFBundleURLName + https://wiki.indigodomo.com/doku.php?id=plugins:example_dev_factory1 + + diff --git a/Example Device - Factory.indigoPlugin/Contents/Server Plugin/Actions.xml b/Example Device - Factory.indigoPlugin/Contents/Server Plugin/Actions.xml index 5939fe1..098ebf7 100644 --- a/Example Device - Factory.indigoPlugin/Contents/Server Plugin/Actions.xml +++ b/Example Device - Factory.indigoPlugin/Contents/Server Plugin/Actions.xml @@ -1,24 +1,24 @@ - - Set Backlight Brightness - setBacklightBrightness - - - - - - - + + Set Backlight Brightness + set_backlight_brightness + + + + + + + diff --git a/Example Device - Factory.indigoPlugin/Contents/Server Plugin/Devices.xml b/Example Device - Factory.indigoPlugin/Contents/Server Plugin/Devices.xml index 3c2580a..fdfd494 100644 --- a/Example Device - Factory.indigoPlugin/Contents/Server Plugin/Devices.xml +++ b/Example Device - Factory.indigoPlugin/Contents/Server Plugin/Devices.xml @@ -1,141 +1,141 @@ - - - Define Device Group... - Close - - - - - + + + Define Device Group... + Close + + + + + - + - - Add Plugin Relay - _addRelay - - - Add Plugin Dimmer - _addDimmer - + + Add Plugin Relay + _add_relay + + + Add Plugin Dimmer + _add_dimmer + - + - - Remove All Relay Devices - _removeRelayDevices - - - Remove All Dimmer Devices - _removeDimmerDevices - - - Remove All Devices - _removeAllDevices - + + Remove All Relay Devices + _remove_relay_devices + + + Remove All Dimmer Devices + _remove_dimmer_devices + + + Remove All Devices + _remove_all_devices + - - - - Relay - - - - - - - - - - Integer - Backlight Brightness - Backlight Brightness - - - + + + + Relay + + + + + + + + + + Integer + Backlight Brightness + Backlight Brightness + + + - - - Dimmer - - - - - - - - - - Integer - Backlight Brightness - Backlight Brightness - - - + + + Dimmer + + + + + + + + + + Integer + Backlight Brightness + Backlight Brightness + + + diff --git a/Example Device - Factory.indigoPlugin/Contents/Server Plugin/plugin.py b/Example Device - Factory.indigoPlugin/Contents/Server Plugin/plugin.py index f71c102..8660de9 100644 --- a/Example Device - Factory.indigoPlugin/Contents/Server Plugin/plugin.py +++ b/Example Device - Factory.indigoPlugin/Contents/Server Plugin/plugin.py @@ -1,8 +1,8 @@ #! /usr/bin/env python # -*- coding: utf-8 -*- #################### -# Copyright (c) 2014, Perceptive Automation, LLC. All rights reserved. -# http://www.indigodomo.com +# Copyright (c) 2022, Perceptive Automation, LLC. All rights reserved. +# https://www.indigodomo.com import indigo @@ -14,283 +14,275 @@ ################################################################################ class Plugin(indigo.PluginBase): - ######################################## - def __init__(self, pluginId, pluginDisplayName, pluginVersion, pluginPrefs): - super(Plugin, self).__init__(pluginId, pluginDisplayName, pluginVersion, pluginPrefs) - self.debug = True - - ######################################## - def startup(self): - self.debugLog(u"startup called") - - def shutdown(self): - self.debugLog(u"shutdown called") - - ######################################## - # DeviceFactory methods (specified in Devices.xml): - # - # All device factory methods are passed a devIdList argument, which is - # a list of device IDs in the current group being edited. Plugins add - # and remove devices to/from the group by making indigo.device.create() - # and indigo.device.delete(). On subsequent factory method calls the - # devIdList will automatically be updated to reflect any changes. - # - # Plugins should set the main model type using dev.model, and the sub- - # type using dev.subType, which is used for the tabbed UI. Be sure - # and call dev.replaceOnServer() after modifying the .model and .subType - # attributes to push those changes to the server. - #################### - def getDeviceFactoryUiValues(self, devIdList): - valuesDict = indigo.Dict() - errorMsgDict = indigo.Dict() - return (valuesDict, errorMsgDict) - - def validateDeviceFactoryUi(self, valuesDict, devIdList): - errorsDict = indigo.Dict() - return (True, valuesDict, errorsDict) - - def closedDeviceFactoryUi(self, valuesDict, userCancelled, devIdList): - return - - #################### - def _getDeviceGroupList(self, filter, valuesDict, devIdList): - menuItems = [] - for devId in devIdList: - if devId in indigo.devices: - dev = indigo.devices[devId] - devName = dev.name - else: - devName = u"- device not found -" - menuItems.append((devId, devName)) - return menuItems - - def _addRelay(self, valuesDict, devIdList): - newdev = indigo.device.create(indigo.kProtocol.Plugin, deviceTypeId="myRelayType") - newdev.model = "Example Multi-Device" - newdev.subType = "Relay" # Manually need to set the model and subType names (for UI only) - newdev.replaceOnServer() - return valuesDict - - def _addDimmer(self, valuesDict, devIdList): - newdev = indigo.device.create(indigo.kProtocol.Plugin, deviceTypeId="myDimmerType") - newdev.model = "Example Multi-Device" - newdev.subType = "Dimmer" # Manually need to set the model and subType names (for UI only) - newdev.replaceOnServer() - return valuesDict - - def _addX10MotionSensor(self, valuesDict, devIdList): - # Not fully supported -- device groups currently should only contain - # devices defined by the plugin. The UI doesn't properly handle showing - # and editing X10 / INSTEON / etc. devices as part of the group. - # - # newdev = indigo.device.create(indigo.kProtocol.X10, deviceTypeId="Motion Detector") - # newdev.model = "Example Multi-Device" - # newdev.subType = "Motion" # Manually need to set the model and subType names (for UI only) - # newdev.replaceOnServer() - return valuesDict - - def _addX10SprinklerDevice(self, valuesDict, devIdList): - # Not fully supported -- device groups currently should only contain - # devices defined by the plugin. The UI doesn't properly handle showing - # and editing X10 / INSTEON / etc. devices as part of the group. - # - # newdev = indigo.device.create(indigo.kProtocol.X10, deviceTypeId="Rain8 (8 zone)") - # newdev.model = "Example Multi-Device" - # newdev.subType = "Sprinkler" # Manually need to set the model and subType names (for UI only) - # newdev.replaceOnServer() - return valuesDict - - def _removeDimmerDevices(self, valuesDict, devIdList): - for devId in devIdList: - try: - dev = indigo.devices[devId] - if dev.deviceTypeId == "myDimmerType": - indigo.device.delete(dev) - except: - pass # delete doesn't allow (throws) on root elem - return valuesDict - - def _removeRelayDevices(self, valuesDict, devIdList): - for devId in devIdList: - try: - dev = indigo.devices[devId] - if dev.deviceTypeId == "myRelayType": - indigo.device.delete(dev) - except: - pass # delete doesn't allow (throws) on root elem - return valuesDict - - def _removeAllDevices(self, valuesDict, devIdList): - for devId in devIdList: - try: - indigo.device.delete(devId) - except: - pass # delete doesn't allow (throws) on root elem - return valuesDict - - ######################################## - def validateDeviceConfigUi(self, valuesDict, typeId, devId): - return (True, valuesDict) - - ######################################## - # Relay / Dimmer Action callback - ###################### - def actionControlDevice(self, action, dev): - ###### TURN ON ###### - if action.deviceAction == indigo.kDeviceAction.TurnOn: - # Command hardware module (dev) to turn ON here: - # ** IMPLEMENT ME ** - sendSuccess = True # Set to False if it failed. - - if sendSuccess: - # If success then log that the command was successfully sent. - indigo.server.log(u"sent \"%s\" %s" % (dev.name, "on")) - - # And then tell the Indigo Server to update the state. - dev.updateStateOnServer("onOffState", True) - else: - # Else log failure but do NOT update state on Indigo Server. - indigo.server.log(u"send \"%s\" %s failed" % (dev.name, "on"), isError=True) - - ###### TURN OFF ###### - elif action.deviceAction == indigo.kDeviceAction.TurnOff: - # Command hardware module (dev) to turn OFF here: - # ** IMPLEMENT ME ** - sendSuccess = True # Set to False if it failed. - - if sendSuccess: - # If success then log that the command was successfully sent. - indigo.server.log(u"sent \"%s\" %s" % (dev.name, "off")) - - # And then tell the Indigo Server to update the state: - dev.updateStateOnServer("onOffState", False) - else: - # Else log failure but do NOT update state on Indigo Server. - indigo.server.log(u"send \"%s\" %s failed" % (dev.name, "off"), isError=True) - - ###### TOGGLE ###### - elif action.deviceAction == indigo.kDeviceAction.Toggle: - # Command hardware module (dev) to toggle here: - # ** IMPLEMENT ME ** - newOnState = not dev.onState - sendSuccess = True # Set to False if it failed. - - if sendSuccess: - # If success then log that the command was successfully sent. - indigo.server.log(u"sent \"%s\" %s" % (dev.name, "toggle")) - - # And then tell the Indigo Server to update the state: - dev.updateStateOnServer("onOffState", newOnState) - else: - # Else log failure but do NOT update state on Indigo Server. - indigo.server.log(u"send \"%s\" %s failed" % (dev.name, "toggle"), isError=True) - - ###### SET BRIGHTNESS ###### - elif action.deviceAction == indigo.kDeviceAction.SetBrightness: - # Command hardware module (dev) to set brightness here: - # ** IMPLEMENT ME ** - newBrightness = action.actionValue - sendSuccess = True # Set to False if it failed. - - if sendSuccess: - # If success then log that the command was successfully sent. - indigo.server.log(u"sent \"%s\" %s to %d" % (dev.name, "set brightness", newBrightness)) - - # And then tell the Indigo Server to update the state: - dev.updateStateOnServer("brightnessLevel", newBrightness) - else: - # Else log failure but do NOT update state on Indigo Server. - indigo.server.log(u"send \"%s\" %s to %d failed" % (dev.name, "set brightness", newBrightness), isError=True) - - ###### BRIGHTEN BY ###### - elif action.deviceAction == indigo.kDeviceAction.BrightenBy: - # Command hardware module (dev) to do a relative brighten here: - # ** IMPLEMENT ME ** - newBrightness = dev.brightness + action.actionValue - if newBrightness > 100: - newBrightness = 100 - sendSuccess = True # Set to False if it failed. - - if sendSuccess: - # If success then log that the command was successfully sent. - indigo.server.log(u"sent \"%s\" %s to %d" % (dev.name, "brighten", newBrightness)) - - # And then tell the Indigo Server to update the state: - dev.updateStateOnServer("brightnessLevel", newBrightness) - else: - # Else log failure but do NOT update state on Indigo Server. - indigo.server.log(u"send \"%s\" %s to %d failed" % (dev.name, "brighten", newBrightness), isError=True) - - ###### DIM BY ###### - elif action.deviceAction == indigo.kDeviceAction.DimBy: - # Command hardware module (dev) to do a relative dim here: - # ** IMPLEMENT ME ** - newBrightness = dev.brightness - action.actionValue - if newBrightness < 0: - newBrightness = 0 - sendSuccess = True # Set to False if it failed. - - if sendSuccess: - # If success then log that the command was successfully sent. - indigo.server.log(u"sent \"%s\" %s to %d" % (dev.name, "dim", newBrightness)) - - # And then tell the Indigo Server to update the state: - dev.updateStateOnServer("brightnessLevel", newBrightness) - else: - # Else log failure but do NOT update state on Indigo Server. - indigo.server.log(u"send \"%s\" %s to %d failed" % (dev.name, "dim", newBrightness), isError=True) - - - ######################################## - # General Action callback - ###################### - def actionControlUniversal(self, action, dev): - ###### BEEP ###### - if action.deviceAction == indigo.kUniversalAction.Beep: - # Beep the hardware module (dev) here: - # ** IMPLEMENT ME ** - indigo.server.log(u"sent \"%s\" %s" % (dev.name, "beep request")) - - ###### ENERGY UPDATE ###### - elif action.deviceAction == indigo.kUniversalAction.EnergyUpdate: - # Request hardware module (dev) for its most recent meter data here: - # ** IMPLEMENT ME ** - indigo.server.log(u"sent \"%s\" %s" % (dev.name, "energy update request")) - - ###### ENERGY RESET ###### - elif action.deviceAction == indigo.kUniversalAction.EnergyReset: - # Request that the hardware module (dev) reset its accumulative energy usage data here: - # ** IMPLEMENT ME ** - indigo.server.log(u"sent \"%s\" %s" % (dev.name, "energy reset request")) - - ###### STATUS REQUEST ###### - elif action.deviceAction == indigo.kUniversalAction.RequestStatus: - # Query hardware module (dev) for its current status here: - # ** IMPLEMENT ME ** - indigo.server.log(u"sent \"%s\" %s" % (dev.name, "status request")) - - ######################################## - # Custom Plugin Action callbacks (defined in Actions.xml) - ###################### - def setBacklightBrightness(self, pluginAction, dev): - try: - newBrightness = int(pluginAction.props.get(u"brightness", 100)) - except ValueError: - # The int() cast above might fail if the user didn't enter a number: - indigo.server.log(u"set backlight brightness action to device \"%s\" -- invalid brightness value" % (dev.name,), isError=True) - return - - # Command hardware module (dev) to set backlight brightness here: - # ** IMPLEMENT ME ** - sendSuccess = True # Set to False if it failed. - - if sendSuccess: - # If success then log that the command was successfully sent. - indigo.server.log(u"sent \"%s\" %s to %d" % (dev.name, "set backlight brightness", newBrightness)) - - # And then tell the Indigo Server to update the state: - dev.updateStateOnServer("backlightBrightness", newBrightness) - else: - # Else log failure but do NOT update state on Indigo Server. - indigo.server.log(u"send \"%s\" %s to %d failed" % (dev.name, "set backlight brightness", newBrightness), isError=True) - + ######################################## + def __init__(self, plugin_id, plugin_display_name, plugin_version, plugin_prefs): + super().__init__(plugin_id, plugin_display_name, plugin_version, plugin_prefs) + self.debug = True + + ######################################## + def startup(self): + self.logger.debug("startup called") + + def shutdown(self): + self.logger.debug("shutdown called") + + ######################################## + # DeviceFactory methods (specified in Devices.xml): + # + # All device factory methods are passed a dev_id_list argument, which is + # a list of device IDs in the current group being edited. Plugins add + # and remove devices to/from the group by making indigo.device.create() + # and indigo.device.delete(). On subsequent factory method calls the + # dev_id_list will automatically be updated to reflect any changes. + # + # Plugins should set the main model type using dev.model, and the sub- + # type using dev.subType, which is used for the tabbed UI. Be sure + # and call dev.replaceOnServer() after modifying the .model and .subType + # attributes to push those changes to the server. + #################### + def getDeviceFactoryUiValues(self, dev_id_list): + values_dict = indigo.Dict() + error_msg_dict = indigo.Dict() + return (values_dict, error_msg_dict) + + def validateDeviceFactoryUi(self, values_dict, dev_id_list): + errors_dict = indigo.Dict() + return (True, values_dict, errors_dict) + + def closedDeviceFactoryUi(self, values_dict, user_cancelled, dev_id_list): + return + + #################### + def _get_device_group_list(self, filter, values_dict, dev_id_list): + menu_items = [] + for dev_id in dev_id_list: + if dev_id in indigo.devices: + dev = indigo.devices[dev_id] + dev_name = dev.name + else: + dev_name = "- device not found -" + menu_items.append((dev_id, dev_name)) + return menu_items + + def _add_relay(self, values_dict, dev_id_list): + newdev = indigo.device.create(indigo.kProtocol.Plugin, deviceTypeId="myRelayType") + newdev.model = "Example Multi-Device" + newdev.subType = "Relay" # Manually need to set the model and subType names (for UI only) + newdev.replaceOnServer() + return values_dict + + def _add_dimmer(self, values_dict, dev_id_list): + newdev = indigo.device.create(indigo.kProtocol.Plugin, deviceTypeId="myDimmerType") + newdev.model = "Example Multi-Device" + newdev.subType = "Dimmer" # Manually need to set the model and subType names (for UI only) + newdev.replaceOnServer() + return values_dict + + def _add_x10_motion_sensor(self, values_dict, dev_id_list): + # Not fully supported -- device groups currently should only contain + # devices defined by the plugin. The UI doesn't properly handle showing + # and editing X10 / INSTEON / etc. devices as part of the group. + # + # newdev = indigo.device.create(indigo.kProtocol.X10, deviceTypeId="Motion Detector") + # newdev.model = "Example Multi-Device" + # newdev.subType = "Motion" # Manually need to set the model and subType names (for UI only) + # newdev.replaceOnServer() + return values_dict + + def _add_x10_sprinkler_device(self, values_dict, dev_id_list): + # Not fully supported -- device groups currently should only contain + # devices defined by the plugin. The UI doesn't properly handle showing + # and editing X10 / INSTEON / etc. devices as part of the group. + # + # newdev = indigo.device.create(indigo.kProtocol.X10, deviceTypeId="Rain8 (8 zone)") + # newdev.model = "Example Multi-Device" + # newdev.subType = "Sprinkler" # Manually need to set the model and subType names (for UI only) + # newdev.replaceOnServer() + return values_dict + + def _remove_dimmer_devices(self, values_dict, dev_id_list): + for dev_id in dev_id_list: + try: + dev = indigo.devices[dev_id] + if dev.deviceTypeId == "myDimmerType": + indigo.device.delete(dev) + except: + pass # delete doesn't allow (throws) on root elem + return values_dict + + def _remove_relay_devices(self, values_dict, dev_id_list): + for dev_id in dev_id_list: + try: + dev = indigo.devices[dev_id] + if dev.deviceTypeId == "myRelayType": + indigo.device.delete(dev) + except: + pass # delete doesn't allow (throws) on root elem + return values_dict + + def _remove_all_devices(self, values_dict, dev_id_list): + for dev_id in dev_id_list: + try: + indigo.device.delete(dev_id) + except: + pass # delete doesn't allow (throws) on root elem + return values_dict + + ######################################## + def validateDeviceConfigUi(self, values_dict, type_id, dev_id): + return (True, values_dict) + + ######################################## + # Relay / Dimmer Action callback + ###################### + def actionControlDevice(self, action, dev): + ###### TURN ON ###### + if action.deviceAction == indigo.kDeviceAction.TurnOn: + # Command hardware module (dev) to turn ON here: + # ** IMPLEMENT ME ** + send_success = True # Set to False if it failed. + + if send_success: + # If success then log that the command was successfully sent. + self.logger.info(f"sent \"{dev.name}\" on") + + # And then tell the Indigo Server to update the state. + dev.updateStateOnServer("onOffState", True) + else: + # Else log failure but do NOT update state on Indigo Server. + self.logger.error(f"send \"{dev.name}\" on failed") + + ###### TURN OFF ###### + elif action.deviceAction == indigo.kDeviceAction.TurnOff: + # Command hardware module (dev) to turn OFF here: + # ** IMPLEMENT ME ** + send_success = True # Set to False if it failed. + + if send_success: + # If success then log that the command was successfully sent. + self.logger.info(f"sent \"{dev.name}\" off") + + # And then tell the Indigo Server to update the state: + dev.updateStateOnServer("onOffState", False) + else: + # Else log failure but do NOT update state on Indigo Server. + self.logger.error(f"send \"{dev.name}\" off failed") + + ###### TOGGLE ###### + elif action.deviceAction == indigo.kDeviceAction.Toggle: + # Command hardware module (dev) to toggle here: + # ** IMPLEMENT ME ** + new_on_state = not dev.onState + send_success = True # Set to False if it failed. + + if send_success: + # If success then log that the command was successfully sent. + self.logger.info(f"sent \"{dev.name}\" toggle") + + # And then tell the Indigo Server to update the state: + dev.updateStateOnServer("onOffState", new_on_state) + else: + # Else log failure but do NOT update state on Indigo Server. + self.logger.error(f"send \"{dev.name}\" toggle failed") + + ###### SET BRIGHTNESS ###### + elif action.deviceAction == indigo.kDeviceAction.SetBrightness: + # Command hardware module (dev) to set brightness here: + # ** IMPLEMENT ME ** + new_brightness = action.actionValue + send_success = True # Set to False if it failed. + + if send_success: + # If success then log that the command was successfully sent. + self.logger.info(f"sent \"{dev.name}\" set brightness to {new_brightness}") + + # And then tell the Indigo Server to update the state: + dev.updateStateOnServer("brightnessLevel", new_brightness) + else: + # Else log failure but do NOT update state on Indigo Server. + self.logger.error(f"send \"{dev.name}\" set brightness to {new_brightness} failed") + + ###### BRIGHTEN BY ###### + elif action.deviceAction == indigo.kDeviceAction.BrightenBy: + # Command hardware module (dev) to do a relative brighten here: + # ** IMPLEMENT ME ** + new_brightness = min(dev.brightness + action.actionValue, 100) + send_success = True # Set to False if it failed. + + if send_success: + # If success then log that the command was successfully sent. + self.logger.info(f"sent \"{dev.name}\" brighten to {new_brightness}") + + # And then tell the Indigo Server to update the state: + dev.updateStateOnServer("brightnessLevel", new_brightness) + else: + # Else log failure but do NOT update state on Indigo Server. + self.logger.error(f"send \"{dev.name}\" brighten to {new_brightness} failed") + + ###### DIM BY ###### + elif action.deviceAction == indigo.kDeviceAction.DimBy: + # Command hardware module (dev) to do a relative dim here: + # ** IMPLEMENT ME ** + new_brightness = max(dev.brightness - action.actionValue, 0) + send_success = True # Set to False if it failed. + + if send_success: + # If success then log that the command was successfully sent. + self.logger.info(f"sent \"{dev.name}\" dim to {new_brightness}") + + # And then tell the Indigo Server to update the state: + dev.updateStateOnServer("brightnessLevel", new_brightness) + else: + # Else log failure but do NOT update state on Indigo Server. + self.logger.error(f"send \"{dev.name}\" dim to {new_brightness} failed") + + + ######################################## + # General Action callback + ###################### + def actionControlUniversal(self, action, dev): + ###### BEEP ###### + if action.deviceAction == indigo.kUniversalAction.Beep: + # Beep the hardware module (dev) here: + # ** IMPLEMENT ME ** + self.logger.info(f"sent \"{dev.name}\" beep request") + + ###### ENERGY UPDATE ###### + elif action.deviceAction == indigo.kUniversalAction.EnergyUpdate: + # Request hardware module (dev) for its most recent meter data here: + # ** IMPLEMENT ME ** + self.logger.info(f"sent \"{dev.name}\" energy update request") + + ###### ENERGY RESET ###### + elif action.deviceAction == indigo.kUniversalAction.EnergyReset: + # Request that the hardware module (dev) reset its accumulative energy usage data here: + # ** IMPLEMENT ME ** + self.logger.info(f"sent \"{dev.name}\" energy reset request") + + ###### STATUS REQUEST ###### + elif action.deviceAction == indigo.kUniversalAction.RequestStatus: + # Query hardware module (dev) for its current status here: + # ** IMPLEMENT ME ** + self.logger.info(f"sent \"{dev.name}\" status request") + + ######################################## + # Custom Plugin Action callbacks (defined in Actions.xml) + ###################### + def set_backlight_brightness(self, plugin_action, dev): + try: + new_brightness = int(plugin_action.props.get("brightness", 100)) + except ValueError: + # The int() cast above might fail if the user didn't enter a number: + self.logger.error(f"set backlight brightness action to device \"{dev.name}\" -- invalid brightness value") + return + # Command hardware module (dev) to set backlight brightness here: + # FIXME: add implementation here + send_success = True # Set to False if it failed. + if send_success: + # If success then log that the command was successfully sent. + self.logger.info(f"sent \"{dev.name}\" set backlight brightness to {new_brightness}") + # And then tell the Indigo Server to update the state: + dev.updateStateOnServer("backlightBrightness", new_brightness) + else: + # Else log failure but do NOT update state on Indigo Server. + self.logger.error(f"send \"{dev.name}\" set backlight brightness to {new_brightness} failed") diff --git a/Example Device - Relay and Dimmer.indigoPlugin/Contents/Info.plist b/Example Device - Relay and Dimmer.indigoPlugin/Contents/Info.plist index e2d9fe9..fea8a89 100644 --- a/Example Device - Relay and Dimmer.indigoPlugin/Contents/Info.plist +++ b/Example Device - Relay and Dimmer.indigoPlugin/Contents/Info.plist @@ -2,24 +2,24 @@ - PluginVersion - 2021.1.0 - ServerApiVersion - 2.5 - IwsApiVersion - 1.0.0 - CFBundleDisplayName - Example Device - Relay/Dimmer - CFBundleIdentifier - com.perceptiveautomation.indigoplugin.example-device-relay1 - CFBundleVersion - 1.0.0 - CFBundleURLTypes - - - CFBundleURLName - http://wiki.indigodomo.com/doku.php?id=plugins:example_dev_relay_1 - - + PluginVersion + 2022.1.0 + ServerApiVersion + 3.0 + IwsApiVersion + 1.0.0 + CFBundleDisplayName + Example Device - Relay/Dimmer + CFBundleIdentifier + com.perceptiveautomation.indigoplugin.example-device-relay1 + CFBundleVersion + 1.0.0 + CFBundleURLTypes + + + CFBundleURLName + https://wiki.indigodomo.com/doku.php?id=plugins:example_dev_relay_1 + + diff --git a/Example Device - Relay and Dimmer.indigoPlugin/Contents/Server Plugin/Actions.xml b/Example Device - Relay and Dimmer.indigoPlugin/Contents/Server Plugin/Actions.xml index 5939fe1..098ebf7 100644 --- a/Example Device - Relay and Dimmer.indigoPlugin/Contents/Server Plugin/Actions.xml +++ b/Example Device - Relay and Dimmer.indigoPlugin/Contents/Server Plugin/Actions.xml @@ -1,24 +1,24 @@ - - Set Backlight Brightness - setBacklightBrightness - - - - - - - + + Set Backlight Brightness + set_backlight_brightness + + + + + + + diff --git a/Example Device - Relay and Dimmer.indigoPlugin/Contents/Server Plugin/Devices.xml b/Example Device - Relay and Dimmer.indigoPlugin/Contents/Server Plugin/Devices.xml index 8b9ffc0..262e570 100644 --- a/Example Device - Relay and Dimmer.indigoPlugin/Contents/Server Plugin/Devices.xml +++ b/Example Device - Relay and Dimmer.indigoPlugin/Contents/Server Plugin/Devices.xml @@ -1,203 +1,203 @@ - - - Example Relay Module - - - - - - - - - - Integer - Backlight Brightness - Backlight Brightness - - - + + + Example Relay Module + + + + + + + + + + Integer + Backlight Brightness + Backlight Brightness + + + - - - Example Dimmer Module - - - - - - - - - - Integer - Backlight Brightness - Backlight Brightness - - - + + + Example Dimmer Module + + + + + + + + + + Integer + Backlight Brightness + Backlight Brightness + + + - - - Example Color Module - - - - - + + + Example Color Module + + + + + - - - - - Shows RGB control and level fields in UI - - - - - Shows White level fields in UI - - - - Shows Two White level fields in UI - - - - - - - Shows White Temperature field in UI - - - - Minimum White Temperature used on UI controls - - - - Maximum White Temperature used on UI controls - + Two white level fields (SupportsTwoWhiteLevels) is used by some hardware + to mix cool and warm white levels. Other hardware provides this capability + by using a white temperature value (SupportsWhiteTemperature). The two + techniques are mutually exlcusive, so choosing to enable two white levels + will override (and not show) the white temperature UI. + --> + + + + Shows RGB control and level fields in UI + + + + + Shows White level fields in UI + + + + Shows Two White level fields in UI + + + + + + + Shows White Temperature field in UI + + + + Minimum White Temperature used on UI controls + + + + Maximum White Temperature used on UI controls + - - + + - - - Example Lock Module - - - - - - - - - - Integer - Backlight Brightness - Backlight Brightness - - - + + + Example Lock Module + + + + + + + + + + Integer + Backlight Brightness + Backlight Brightness + + + diff --git a/Example Device - Relay and Dimmer.indigoPlugin/Contents/Server Plugin/plugin.py b/Example Device - Relay and Dimmer.indigoPlugin/Contents/Server Plugin/plugin.py index 803674c..2499f8a 100644 --- a/Example Device - Relay and Dimmer.indigoPlugin/Contents/Server Plugin/plugin.py +++ b/Example Device - Relay and Dimmer.indigoPlugin/Contents/Server Plugin/plugin.py @@ -1,8 +1,8 @@ #! /usr/bin/env python # -*- coding: utf-8 -*- #################### -# Copyright (c) 2016, Perceptive Automation, LLC. All rights reserved. -# http://www.indigodomo.com +# Copyright (c) 2022, Perceptive Automation, LLC. All rights reserved. +# https://www.indigodomo.com import indigo @@ -15,317 +15,310 @@ ################################################################################ class Plugin(indigo.PluginBase): - ######################################## - def __init__(self, pluginId, pluginDisplayName, pluginVersion, pluginPrefs): - super(Plugin, self).__init__(pluginId, pluginDisplayName, pluginVersion, pluginPrefs) - self.debug = True - - ######################################## - def startup(self): - self.debugLog(u"startup called") - - def shutdown(self): - self.debugLog(u"shutdown called") - - ######################################## - # deviceStartComm() is called on application launch for all of our plugin defined - # devices, and it is called when a new device is created immediately after its - # UI settings dialog has been validated. This is a good place to force any properties - # we need the device to have, and to cleanup old properties. - def deviceStartComm(self, dev): - # self.debugLog(u"deviceStartComm: %s" % (dev.name,)) - - props = dev.pluginProps - if dev.deviceTypeId == 'myColorType': - # Set SupportsColor property so Indigo knows device accepts color actions and should use color UI. - props["SupportsColor"] = True - - # Cleanup properties used by other device types. These can exist if user switches the device type. - if "IsLockSubType" in props: - del props["IsLockSubType"] - - dev.replacePluginPropsOnServer(props) - elif dev.deviceTypeId == 'myLockType': - # Set IsLockSubType property so Indigo knows device accepts lock actions and should use lock UI. - props["IsLockSubType"] = True - - # Cleanup properties used by other device types. These can exist if user switches the device type. - if "SupportsColor" in props: - del props["SupportsColor"] - - dev.replacePluginPropsOnServer(props) - - ######################################## - def validateDeviceConfigUi(self, valuesDict, typeId, devId): - return (True, valuesDict) - - ######################################## - # Relay / Dimmer Action callback - ###################### - def actionControlDevice(self, action, dev): - ###### TURN ON ###### - if action.deviceAction == indigo.kDeviceAction.TurnOn: - # Command hardware module (dev) to turn ON here: - # ** IMPLEMENT ME ** - sendSuccess = True # Set to False if it failed. - - if sendSuccess: - # If success then log that the command was successfully sent. - indigo.server.log(u"sent \"%s\" %s" % (dev.name, "on")) - - # And then tell the Indigo Server to update the state. - dev.updateStateOnServer("onOffState", True) - else: - # Else log failure but do NOT update state on Indigo Server. - indigo.server.log(u"send \"%s\" %s failed" % (dev.name, "on"), isError=True) - - ###### TURN OFF ###### - elif action.deviceAction == indigo.kDeviceAction.TurnOff: - # Command hardware module (dev) to turn OFF here: - # ** IMPLEMENT ME ** - sendSuccess = True # Set to False if it failed. - - if sendSuccess: - # If success then log that the command was successfully sent. - indigo.server.log(u"sent \"%s\" %s" % (dev.name, "off")) - - # And then tell the Indigo Server to update the state: - dev.updateStateOnServer("onOffState", False) - else: - # Else log failure but do NOT update state on Indigo Server. - indigo.server.log(u"send \"%s\" %s failed" % (dev.name, "off"), isError=True) - - ###### LOCK ###### - if action.deviceAction == indigo.kDeviceAction.Lock: - # Command hardware module (dev) to LOCK here: - # ** IMPLEMENT ME ** - sendSuccess = True # Set to False if it failed. - - if sendSuccess: - # If success then log that the command was successfully sent. - indigo.server.log(u"sent \"%s\" %s" % (dev.name, "lock")) - - # And then tell the Indigo Server to update the state. - dev.updateStateOnServer("onOffState", True) - else: - # Else log failure but do NOT update state on Indigo Server. - indigo.server.log(u"send \"%s\" %s failed" % (dev.name, "lock"), isError=True) - - ###### UNLOCK ###### - elif action.deviceAction == indigo.kDeviceAction.Unlock: - # Command hardware module (dev) to turn UNLOCK here: - # ** IMPLEMENT ME ** - sendSuccess = True # Set to False if it failed. - - if sendSuccess: - # If success then log that the command was successfully sent. - indigo.server.log(u"sent \"%s\" %s" % (dev.name, "unlock")) - - # And then tell the Indigo Server to update the state: - dev.updateStateOnServer("onOffState", False) - else: - # Else log failure but do NOT update state on Indigo Server. - indigo.server.log(u"send \"%s\" %s failed" % (dev.name, "unlock"), isError=True) - - ###### TOGGLE ###### - elif action.deviceAction == indigo.kDeviceAction.Toggle: - # Command hardware module (dev) to toggle here: - # ** IMPLEMENT ME ** - newOnState = not dev.onState - sendSuccess = True # Set to False if it failed. - - if sendSuccess: - # If success then log that the command was successfully sent. - indigo.server.log(u"sent \"%s\" %s" % (dev.name, "toggle")) - - # And then tell the Indigo Server to update the state: - dev.updateStateOnServer("onOffState", newOnState) - else: - # Else log failure but do NOT update state on Indigo Server. - indigo.server.log(u"send \"%s\" %s failed" % (dev.name, "toggle"), isError=True) - - ###### SET BRIGHTNESS ###### - elif action.deviceAction == indigo.kDeviceAction.SetBrightness: - # Command hardware module (dev) to set brightness here: - # ** IMPLEMENT ME ** - newBrightness = action.actionValue - sendSuccess = True # Set to False if it failed. - - if sendSuccess: - # If success then log that the command was successfully sent. - indigo.server.log(u"sent \"%s\" %s to %d" % (dev.name, "set brightness", newBrightness)) - - # And then tell the Indigo Server to update the state: - dev.updateStateOnServer("brightnessLevel", newBrightness) - else: - # Else log failure but do NOT update state on Indigo Server. - indigo.server.log(u"send \"%s\" %s to %d failed" % (dev.name, "set brightness", newBrightness), isError=True) - - ###### BRIGHTEN BY ###### - elif action.deviceAction == indigo.kDeviceAction.BrightenBy: - # Command hardware module (dev) to do a relative brighten here: - # ** IMPLEMENT ME ** - newBrightness = dev.brightness + action.actionValue - if newBrightness > 100: - newBrightness = 100 - sendSuccess = True # Set to False if it failed. - - if sendSuccess: - # If success then log that the command was successfully sent. - indigo.server.log(u"sent \"%s\" %s to %d" % (dev.name, "brighten", newBrightness)) - - # And then tell the Indigo Server to update the state: - dev.updateStateOnServer("brightnessLevel", newBrightness) - else: - # Else log failure but do NOT update state on Indigo Server. - indigo.server.log(u"send \"%s\" %s to %d failed" % (dev.name, "brighten", newBrightness), isError=True) - - ###### DIM BY ###### - elif action.deviceAction == indigo.kDeviceAction.DimBy: - # Command hardware module (dev) to do a relative dim here: - # ** IMPLEMENT ME ** - newBrightness = dev.brightness - action.actionValue - if newBrightness < 0: - newBrightness = 0 - sendSuccess = True # Set to False if it failed. - - if sendSuccess: - # If success then log that the command was successfully sent. - indigo.server.log(u"sent \"%s\" %s to %d" % (dev.name, "dim", newBrightness)) - - # And then tell the Indigo Server to update the state: - dev.updateStateOnServer("brightnessLevel", newBrightness) - else: - # Else log failure but do NOT update state on Indigo Server. - indigo.server.log(u"send \"%s\" %s to %d failed" % (dev.name, "dim", newBrightness), isError=True) - - ###### SET COLOR LEVELS ###### - elif action.deviceAction == indigo.kDeviceAction.SetColorLevels: - # action.actionValue is a dict containing the color channel key/value - # pairs. All color channel keys (redLevel, greenLevel, etc.) are optional - # so plugin should handle cases where some color values are not specified - # in the action. - actionColorVals = action.actionValue - - # Construct a list of channel keys that are possible for what this device - # supports. It may not support RGB or may not support white levels, for - # example, depending on how the device's properties (SupportsColor, SupportsRGB, - # SupportsWhite, SupportsTwoWhiteLevels, SupportsWhiteTemperature) have - # been specified. - channelKeys = [] - usingWhiteChannels = False - if dev.supportsRGB: - channelKeys.extend(['redLevel', 'greenLevel', 'blueLevel']) - if dev.supportsWhite: - channelKeys.extend(['whiteLevel']) - usingWhiteChannels = True - if dev.supportsTwoWhiteLevels: - channelKeys.extend(['whiteLevel2']) - elif dev.supportsWhiteTemperature: - channelKeys.extend(['whiteTemperature']) - # Note having 2 white levels (cold and warm) takes precedence over - # the use of a white temperature value. You cannot have both, although - # you can have a single white level and a white temperature value. - - # Next enumerate through the possible color channels and extract that - # value from the actionValue (actionColorVals). - keyValueList = [] - resultVals = [] - for channel in channelKeys: - if channel in actionColorVals: - brightness = float(actionColorVals[channel]) - brightnessByte = int(round(255.0 * (brightness / 100.0))) - - # Command hardware module (dev) to change its color level here: - # ** IMPLEMENT ME ** - - if channel in dev.states: - keyValueList.append({'key':channel, 'value':brightness}) - result = str(int(round(brightness))) - elif channel in dev.states: - # If the action doesn't specify a level that is needed (say the - # hardware API requires a full RGB triplet to be specified, but - # the action only contains green level), then the plugin could - # extract the currently cached red and blue values from the - # dev.states[] dictionary: - cachedBrightness = float(dev.states[channel]) - cachedBrightnessByte = int(round(255.0 * (cachedBrightness / 100.0))) - # Could show in the Event Log '--' to indicate this level wasn't - # passed in by the action: - result = '--' - # Or could show the current device state's cached level: - # result = str(int(round(cachedBrightness))) - - # Add a comma to separate the RGB values from the white values for logging. - if channel == 'blueLevel' and usingWhiteChannels: - result += "," - elif channel == 'whiteTemperature' and result != '--': - result += " K" - resultVals.append(result) - # Set to False if it failed. - sendSuccess = True - - resultValsStr = ' '.join(resultVals) - if sendSuccess: - # If success then log that the command was successfully sent. - indigo.server.log(u"sent \"%s\" %s to %s" % (dev.name, "set color", resultValsStr)) - - # And then tell the Indigo Server to update the color level states: - if len(keyValueList) > 0: - dev.updateStatesOnServer(keyValueList) - else: - # Else log failure but do NOT update state on Indigo Server. - indigo.server.log(u"send \"%s\" %s to %s failed" % (dev.name, "set color", resultValsStr), isError=True) - - ######################################## - # General Action callback - ###################### - def actionControlUniversal(self, action, dev): - ###### BEEP ###### - if action.deviceAction == indigo.kUniversalAction.Beep: - # Beep the hardware module (dev) here: - # ** IMPLEMENT ME ** - indigo.server.log(u"sent \"%s\" %s" % (dev.name, "beep request")) - - ###### ENERGY UPDATE ###### - elif action.deviceAction == indigo.kUniversalAction.EnergyUpdate: - # Request hardware module (dev) for its most recent meter data here: - # ** IMPLEMENT ME ** - indigo.server.log(u"sent \"%s\" %s" % (dev.name, "energy update request")) - - ###### ENERGY RESET ###### - elif action.deviceAction == indigo.kUniversalAction.EnergyReset: - # Request that the hardware module (dev) reset its accumulative energy usage data here: - # ** IMPLEMENT ME ** - indigo.server.log(u"sent \"%s\" %s" % (dev.name, "energy reset request")) - - ###### STATUS REQUEST ###### - elif action.deviceAction == indigo.kUniversalAction.RequestStatus: - # Query hardware module (dev) for its current status here: - # ** IMPLEMENT ME ** - indigo.server.log(u"sent \"%s\" %s" % (dev.name, "status request")) - - ######################################## - # Custom Plugin Action callbacks (defined in Actions.xml) - ###################### - def setBacklightBrightness(self, pluginAction, dev): - try: - newBrightness = int(pluginAction.props.get(u"brightness", 100)) - except ValueError: - # The int() cast above might fail if the user didn't enter a number: - indigo.server.log(u"set backlight brightness action to device \"%s\" -- invalid brightness value" % (dev.name,), isError=True) - return - - # Command hardware module (dev) to set backlight brightness here: - # ** IMPLEMENT ME ** - sendSuccess = True # Set to False if it failed. - - if sendSuccess: - # If success then log that the command was successfully sent. - indigo.server.log(u"sent \"%s\" %s to %d" % (dev.name, "set backlight brightness", newBrightness)) - - # And then tell the Indigo Server to update the state: - dev.updateStateOnServer("backlightBrightness", newBrightness) - else: - # Else log failure but do NOT update state on Indigo Server. - indigo.server.log(u"send \"%s\" %s to %d failed" % (dev.name, "set backlight brightness", newBrightness), isError=True) + ######################################## + def __init__(self, plugin_id, plugin_display_name, plugin_version, plugin_prefs): + super().__init__(plugin_id, plugin_display_name, plugin_version, plugin_prefs) + self.debug = True + + ######################################## + def startup(self): + self.logger.debug("startup called") + + def shutdown(self): + self.logger.debug("shutdown called") + + ######################################## + # deviceStartComm() is called on application launch for all of our plugin defined + # devices, and it is called when a new device is created immediately after its + # UI settings dialog has been validated. This is a good place to force any properties + # we need the device to have, and to cleanup old properties. + def deviceStartComm(self, dev): + # self.logger.debug(f"deviceStartComm: {dev.name}") + + props = dev.pluginProps + if dev.deviceTypeId == 'myColorType': + # Set SupportsColor property so Indigo knows device accepts color actions and should use color UI. + props["SupportsColor"] = True + + # Cleanup properties used by other device types. These can exist if user switches the device type. + if "IsLockSubType" in props: + del props["IsLockSubType"] + + dev.replacePluginPropsOnServer(props) + elif dev.deviceTypeId == 'myLockType': + # Set IsLockSubType property so Indigo knows device accepts lock actions and should use lock UI. + props["IsLockSubType"] = True + + # Cleanup properties used by other device types. These can exist if user switches the device type. + if "SupportsColor" in props: + del props["SupportsColor"] + + dev.replacePluginPropsOnServer(props) + + ######################################## + def validateDeviceConfigUi(self, values_dict, type_id, dev_id): + return (True, values_dict) + + ######################################## + # Relay / Dimmer Action callback + ###################### + def actionControlDevice(self, action, dev): + ###### TURN ON ###### + if action.deviceAction == indigo.kDeviceAction.TurnOn: + # Command hardware module (dev) to turn ON here: + # ** IMPLEMENT ME ** + send_success = True # Set to False if it failed. + + if send_success: + # If success then log that the command was successfully sent. + self.logger.info(f"sent \"{dev.name}\" on") + + # And then tell the Indigo Server to update the state. + dev.updateStateOnServer("onOffState", True) + else: + # Else log failure but do NOT update state on Indigo Server. + self.logger.error(f"send \"{dev.name}\" on failed") + + ###### TURN OFF ###### + elif action.deviceAction == indigo.kDeviceAction.TurnOff: + # Command hardware module (dev) to turn OFF here: + # ** IMPLEMENT ME ** + send_success = True # Set to False if it failed. + + if send_success: + # If success then log that the command was successfully sent. + self.logger.info(f"sent \"{dev.name}\" off") + + # And then tell the Indigo Server to update the state: + dev.updateStateOnServer("onOffState", False) + else: + # Else log failure but do NOT update state on Indigo Server. + self.logger.error(f"send \"{dev.name}\" off failed") + + ###### LOCK ###### + if action.deviceAction == indigo.kDeviceAction.Lock: + # Command hardware module (dev) to LOCK here: + # ** IMPLEMENT ME ** + send_success = True # Set to False if it failed. + + if send_success: + # If success then log that the command was successfully sent. + self.logger.info(f"sent \"{dev.name}\" lock") + + # And then tell the Indigo Server to update the state. + dev.updateStateOnServer("onOffState", True) + else: + # Else log failure but do NOT update state on Indigo Server. + self.logger.error(f"send \"{dev.name}\" lock failed") + + ###### UNLOCK ###### + elif action.deviceAction == indigo.kDeviceAction.Unlock: + # Command hardware module (dev) to turn UNLOCK here: + # ** IMPLEMENT ME ** + send_success = True # Set to False if it failed. + + if send_success: + # If success then log that the command was successfully sent. + self.logger.info(f"sent \"{dev.name}\" unlock") + + # And then tell the Indigo Server to update the state: + dev.updateStateOnServer("onOffState", False) + else: + # Else log failure but do NOT update state on Indigo Server. + self.logger.error(f"send \"{dev.name}\" unlock failed") + + ###### TOGGLE ###### + elif action.deviceAction == indigo.kDeviceAction.Toggle: + # Command hardware module (dev) to toggle here: + # ** IMPLEMENT ME ** + new_on_state = not dev.onState + send_success = True # Set to False if it failed. + + if send_success: + # If success then log that the command was successfully sent. + self.logger.info(f"sent \"{dev.name}\" toggle") + + # And then tell the Indigo Server to update the state: + dev.updateStateOnServer("onOffState", new_on_state) + else: + # Else log failure but do NOT update state on Indigo Server. + self.logger.error(f"send \"{dev.name}\" toggle failed") + + ###### SET BRIGHTNESS ###### + elif action.deviceAction == indigo.kDeviceAction.SetBrightness: + # Command hardware module (dev) to set brightness here: + # ** IMPLEMENT ME ** + new_brightness = action.actionValue + send_success = True # Set to False if it failed. + + if send_success: + # If success then log that the command was successfully sent. + self.logger.info(f"sent \"{dev.name}\" set brightness to {new_brightness}") + + # And then tell the Indigo Server to update the state: + dev.updateStateOnServer("brightnessLevel", new_brightness) + else: + # Else log failure but do NOT update state on Indigo Server. + self.logger.error(f"send \"{dev.name}\" set brightness to {new_brightness} failed") + + ###### BRIGHTEN BY ###### + elif action.deviceAction == indigo.kDeviceAction.BrightenBy: + # Command hardware module (dev) to do a relative brighten here: + # ** IMPLEMENT ME ** + new_brightness = min(dev.brightness + action.actionValue, 100) + send_success = True # Set to False if it failed. + + if send_success: + # If success then log that the command was successfully sent. + self.logger.info(f"sent \"{dev.name}\" brighten to {new_brightness}") + + # And then tell the Indigo Server to update the state: + dev.updateStateOnServer("brightnessLevel", new_brightness) + else: + # Else log failure but do NOT update state on Indigo Server. + self.logger.error(f"send \"{dev.name}\" brighten to {new_brightness} failed") + + ###### DIM BY ###### + elif action.deviceAction == indigo.kDeviceAction.DimBy: + # Command hardware module (dev) to do a relative dim here: + # ** IMPLEMENT ME ** + new_brightness = max(dev.brightness - action.actionValue, 0) + send_success = True # Set to False if it failed. + + if send_success: + # If success then log that the command was successfully sent. + self.logger.info(f"sent \"{dev.name}\" dim to {new_brightness}") + + # And then tell the Indigo Server to update the state: + dev.updateStateOnServer("brightnessLevel", new_brightness) + else: + # Else log failure but do NOT update state on Indigo Server. + self.logger.error(f"send \"{dev.name}\" dim to {new_brightness} failed") + + ###### SET COLOR LEVELS ###### + elif action.deviceAction == indigo.kDeviceAction.SetColorLevels: + # action.actionValue is a dict containing the color channel key/value + # pairs. All color channel keys (redLevel, greenLevel, etc.) are optional + # so plugin should handle cases where some color values are not specified + # in the action. + action_color_vals = action.actionValue + + # Construct a list of channel keys that are possible for what this device + # supports. It may not support RGB or may not support white levels, for + # example, depending on how the device's properties (SupportsColor, SupportsRGB, + # SupportsWhite, SupportsTwoWhiteLevels, SupportsWhiteTemperature) have + # been specified. + channel_keys = [] + using_white_channels = False + if dev.supportsRGB: + channel_keys.extend(['redLevel', 'greenLevel', 'blueLevel']) + if dev.supportsWhite: + channel_keys.extend(['whiteLevel']) + using_white_channels = True + if dev.supportsTwoWhiteLevels: + channel_keys.extend(['whiteLevel2']) + elif dev.supportsWhiteTemperature: + channel_keys.extend(['whiteTemperature']) + # Note having 2 white levels (cold and warm) takes precedence over + # the use of a white temperature value. You cannot have both, although + # you can have a single white level and a white temperature value. + + # Next enumerate through the possible color channels and extract that + # value from the actionValue (action_color_vals). + kv_list = [] + result_vals = [] + for channel in channel_keys: + if channel in action_color_vals: + brightness = float(action_color_vals[channel]) + brightness_byte = int(round(255.0 * (brightness / 100.0))) + + # Command hardware module (dev) to change its color level here: + # ** IMPLEMENT ME ** + + if channel in dev.states: + kv_list.append({'key':channel, 'value':brightness}) + result = str(int(round(brightness))) + elif channel in dev.states: + # If the action doesn't specify a level that is needed (say the + # hardware API requires a full RGB triplet to be specified, but + # the action only contains green level), then the plugin could + # extract the currently cached red and blue values from the + # dev.states[] dictionary: + cached_brightness = float(dev.states[channel]) + cached_brightness_byte = int(round(255.0 * (cached_brightness / 100.0))) + # Could show in the Event Log '--' to indicate this level wasn't + # passed in by the action: + result = '--' + # Or could show the current device state's cached level: + # result = str(int(round(cached_brightness))) + + # Add a comma to separate the RGB values from the white values for logging. + if channel == 'blueLevel' and using_white_channels: + result += "," + elif channel == 'whiteTemperature' and result != '--': + result += " K" + result_vals.append(result) + # Set to False if it failed. + send_success = True + + result_vals_str = ' '.join(result_vals) + if send_success: + # If success then log that the command was successfully sent. + self.logger.info(f"sent \"{dev.name}\" set color to {result_vals_str}") + + # And then tell the Indigo Server to update the color level states: + if len(kv_list) > 0: + dev.updateStatesOnServer(kv_list) + else: + # Else log failure but do NOT update state on Indigo Server. + self.logger.error(f"send \"{dev.name}\" set color to {result_vals_str} failed") + + ######################################## + # General Action callback + ###################### + def actionControlUniversal(self, action, dev): + ###### BEEP ###### + if action.deviceAction == indigo.kUniversalAction.Beep: + # Beep the hardware module (dev) here: + # ** IMPLEMENT ME ** + self.logger.info(f"sent \"{dev.name}\" beep request") + + ###### ENERGY UPDATE ###### + elif action.deviceAction == indigo.kUniversalAction.EnergyUpdate: + # Request hardware module (dev) for its most recent meter data here: + # ** IMPLEMENT ME ** + self.logger.info(f"sent \"{dev.name}\" energy update request") + + ###### ENERGY RESET ###### + elif action.deviceAction == indigo.kUniversalAction.EnergyReset: + # Request that the hardware module (dev) reset its accumulative energy usage data here: + # ** IMPLEMENT ME ** + self.logger.info(f"sent \"{dev.name}\" energy reset request") + + ###### STATUS REQUEST ###### + elif action.deviceAction == indigo.kUniversalAction.RequestStatus: + # Query hardware module (dev) for its current status here: + # ** IMPLEMENT ME ** + self.logger.info(f"sent \"{dev.name}\" status request") + + ######################################## + # Custom Plugin Action callbacks (defined in Actions.xml) + ###################### + def set_backlight_brightness(self, plugin_action, dev): + try: + new_brightness = int(plugin_action.props.get("brightness", 100)) + except ValueError: + # The int() cast above might fail if the user didn't enter a number: + self.logger.error(f"set backlight brightness action to device \"{dev.name}\" -- invalid brightness value") + return + # Command hardware module (dev) to set backlight brightness here: + # FIXME: add implementation here + send_success = True # Set to False if it failed. + if send_success: + # If success then log that the command was successfully sent. + self.logger.info(f"sent \"{dev.name}\" set backlight brightness to {new_brightness}") + # And then tell the Indigo Server to update the state: + dev.updateStateOnServer("backlightBrightness", new_brightness) + else: + # Else log failure but do NOT update state on Indigo Server. + self.logger.error(f"send \"{dev.name}\" set backlight brightness to {new_brightness} failed") diff --git a/Example Device - Sensor.indigoPlugin/Contents/Info.plist b/Example Device - Sensor.indigoPlugin/Contents/Info.plist index a4dbe63..2e95a7a 100644 --- a/Example Device - Sensor.indigoPlugin/Contents/Info.plist +++ b/Example Device - Sensor.indigoPlugin/Contents/Info.plist @@ -2,10 +2,10 @@ - PluginVersion - 2021.1.0 - ServerApiVersion - 2.5 + PluginVersion + 2022.1.0 + ServerApiVersion + 3.0 IwsApiVersion 1.0.0 CFBundleDisplayName @@ -18,7 +18,7 @@ CFBundleURLName - http://wiki.indigodomo.com/doku.php?id=plugins:example_dev_sensor_1 + https://wiki.indigodomo.com/doku.php?id=plugins:example_dev_sensor_1 diff --git a/Example Device - Sensor.indigoPlugin/Contents/Server Plugin/Actions.xml b/Example Device - Sensor.indigoPlugin/Contents/Server Plugin/Actions.xml index 9534414..a96c236 100644 --- a/Example Device - Sensor.indigoPlugin/Contents/Server Plugin/Actions.xml +++ b/Example Device - Sensor.indigoPlugin/Contents/Server Plugin/Actions.xml @@ -1,24 +1,24 @@ - - Set Backlight Brightness - setBacklightBrightness - - - - - - - + + Set Backlight Brightness + set_backlight_brightness + + + + + + + diff --git a/Example Device - Sensor.indigoPlugin/Contents/Server Plugin/Devices.xml b/Example Device - Sensor.indigoPlugin/Contents/Server Plugin/Devices.xml index f7314fb..e8d071f 100644 --- a/Example Device - Sensor.indigoPlugin/Contents/Server Plugin/Devices.xml +++ b/Example Device - Sensor.indigoPlugin/Contents/Server Plugin/Devices.xml @@ -1,105 +1,105 @@ - - - Example Adjustable Sensor Module - - - - - - - - - - - - - - - - - - - - - - + + Example Adjustable Sensor Module + + + + + + + + + + + + + + + + + + + + + + - - Integer - Backlight Brightness - Backlight Brightness - - - - - Example Smoke Sensor Module - - - - - - - - - - - - - - Example Door/Window Sensor Module - - - - - - - - - - - - + The plugin can specify additional custom states and custom + actions (in Actions.xml) to modify custom states. As an example + here, we define a new custom state, backlightBrightness, which + is used to control the brightness of the backlit display of + the module. + --> + + Integer + Backlight Brightness + Backlight Brightness + + + + + Example Smoke Sensor Module + + + + + + + + + + + + + + Example Door/Window Sensor Module + + + + + + + + + + + + diff --git a/Example Device - Sensor.indigoPlugin/Contents/Server Plugin/plugin.py b/Example Device - Sensor.indigoPlugin/Contents/Server Plugin/plugin.py index f683ab3..4e881d7 100644 --- a/Example Device - Sensor.indigoPlugin/Contents/Server Plugin/plugin.py +++ b/Example Device - Sensor.indigoPlugin/Contents/Server Plugin/plugin.py @@ -1,8 +1,8 @@ #! /usr/bin/env python # -*- coding: utf-8 -*- #################### -# Copyright (c) 2021, Perceptive Automation, LLC. All rights reserved. -# http://www.indigodomo.com +# Copyright (c) 2022, Perceptive Automation, LLC. All rights reserved. +# https://www.indigodomo.com import indigo @@ -15,136 +15,132 @@ ################################################################################ class Plugin(indigo.PluginBase): - ######################################## - def __init__(self, pluginId, pluginDisplayName, pluginVersion, pluginPrefs): - super(Plugin, self).__init__(pluginId, pluginDisplayName, pluginVersion, pluginPrefs) - self.debug = True - - ######################################## - def startup(self): - self.debugLog(u"startup called") - - def shutdown(self): - self.debugLog(u"shutdown called") - - ######################################## - def runConcurrentThread(self): - try: - while True: - for dev in indigo.devices.iter("self"): - if not dev.enabled or not dev.configured: - continue - - # Plugins that need to poll out the status from the sensor - # could do so here, then broadcast back the new values to the - # Indigo Server via updateStateOnServer. For this example, we - # could toggle the onOffState every 2 seconds. If the sensor - # always broadcasts out changes (or is just 1-way), then this - # entire runConcurrentThread() method can be deleted. - if dev.deviceTypeId == u"myTempSensor": - if dev.sensorValue is not None: - exampleTempFloat = random.randint(560, 880) / 10.0 # random between 56.0 and 88.0 degrees F - exampleTempStr = "%.1f °F" % (exampleTempFloat) - - keyValueList = [] - keyValueList.append({'key':'sensorValue', 'value':exampleTempFloat, 'uiValue':exampleTempStr}) - # Override the state icon shown (in Indigo Touch and client Main Window) - # for this device to be the a temperature sensor: - if dev.onState is not None: - keyValueList.append({'key':'onOffState', 'value':not dev.onState}) - dev.updateStatesOnServer(keyValueList) - if dev.onState: - dev.updateStateImageOnServer(indigo.kStateImageSel.TemperatureSensorOn) - else: - dev.updateStateImageOnServer(indigo.kStateImageSel.TemperatureSensor) - else: - dev.updateStatesOnServer(keyValueList) - dev.updateStateImageOnServer(indigo.kStateImageSel.TemperatureSensor) - elif dev.onState is not None: - dev.updateStateOnServer("onOffState", not dev.onState) - dev.updateStateImageOnServer(indigo.kStateImageSel.Auto) - else: - dev.updateStateImageOnServer(indigo.kStateImageSel.Auto) - self.sleep(2) - except self.StopThread: - pass # Optionally catch the StopThread exception and do any needed cleanup. - - ######################################## - def validateDeviceConfigUi(self, valuesDict, typeId, devId): - return (True, valuesDict) - - ######################################## - def deviceStartComm(self, dev): - # Called when communication with the hardware should be started. - pass - - def deviceStopComm(self, dev): - # Called when communication with the hardware should be shutdown. - pass - - ######################################## - # Sensor Action callback - ###################### - def actionControlSensor(self, action, dev): - ###### TURN ON ###### - # Ignore turn on/off/toggle requests from clients since this is a read-only sensor. - if action.sensorAction == indigo.kSensorAction.TurnOn: - indigo.server.log(u"ignored \"%s\" %s request (sensor is read-only)" % (dev.name, "on")) - # But we could request a sensor state update if we wanted like this: - # dev.updateStateOnServer("onOffState", True) - - ###### TURN OFF ###### - # Ignore turn on/off/toggle requests from clients since this is a read-only sensor. - elif action.sensorAction == indigo.kSensorAction.TurnOff: - indigo.server.log(u"ignored \"%s\" %s request (sensor is read-only)" % (dev.name, "off")) - # But we could request a sensor state update if we wanted like this: - # dev.updateStateOnServer("onOffState", False) - - ###### TOGGLE ###### - # Ignore turn on/off/toggle requests from clients since this is a read-only sensor. - elif action.sensorAction == indigo.kSensorAction.Toggle: - indigo.server.log(u"ignored \"%s\" %s request (sensor is read-only)" % (dev.name, "toggle")) - # But we could request a sensor state update if we wanted like this: - # dev.updateStateOnServer("onOffState", not dev.onState) - - ######################################## - # General Action callback - ###################### - def actionControlUniversal(self, action, dev): - ###### BEEP ###### - if action.deviceAction == indigo.kUniversalAction.Beep: - # Beep the hardware module (dev) here: - # ** IMPLEMENT ME ** - indigo.server.log(u"sent \"%s\" %s" % (dev.name, "beep request")) - - ###### STATUS REQUEST ###### - elif action.deviceAction == indigo.kUniversalAction.RequestStatus: - # Query hardware module (dev) for its current status here: - # ** IMPLEMENT ME ** - indigo.server.log(u"sent \"%s\" %s" % (dev.name, "status request")) - - ######################################## - # Custom Plugin Action callbacks (defined in Actions.xml) - ###################### - def setBacklightBrightness(self, pluginAction, dev): - try: - newBrightness = int(pluginAction.props.get(u"brightness", 100)) - except ValueError: - # The int() cast above might fail if the user didn't enter a number: - indigo.server.log(u"set backlight brightness action to device \"%s\" -- invalid brightness value" % (dev.name,), isError=True) - return - - # Command hardware module (dev) to set backlight brightness here: - # ** IMPLEMENT ME ** - sendSuccess = True # Set to False if it failed. - - if sendSuccess: - # If success then log that the command was successfully sent. - indigo.server.log(u"sent \"%s\" %s to %d" % (dev.name, "set backlight brightness", newBrightness)) - - # And then tell the Indigo Server to update the state: - dev.updateStateOnServer("backlightBrightness", newBrightness) - else: - # Else log failure but do NOT update state on Indigo Server. - indigo.server.log(u"send \"%s\" %s to %d failed" % (dev.name, "set backlight brightness", newBrightness), isError=True) - + ######################################## + def __init__(self, plugin_id, plugin_display_name, plugin_version, plugin_prefs): + super().__init__(plugin_id, plugin_display_name, plugin_version, plugin_prefs) + self.debug = True + + ######################################## + def startup(self): + self.logger.debug("startup called") + + def shutdown(self): + self.logger.debug("shutdown called") + + ######################################## + def runConcurrentThread(self): + try: + while True: + for dev in indigo.devices.iter("self"): + if not dev.enabled or not dev.configured: + continue + + # Plugins that need to poll out the status from the sensor + # could do so here, then broadcast back the new values to the + # Indigo Server via updateStateOnServer. For this example, we + # could toggle the onOffState every 2 seconds. If the sensor + # always broadcasts out changes (or is just 1-way), then this + # entire runConcurrentThread() method can be deleted. + if dev.deviceTypeId == "myTempSensor": + if dev.sensorValue is not None: + example_temp_float = random.randint(560, 880) / 10.0 # random between 56.0 and 88.0 degrees F + example_temp_str = f"{example_temp_float:.1f} °F" + + key_val_list = [] + key_val_list.append({'key':'sensorValue', 'value':example_temp_float, 'uiValue':example_temp_str}) + # Override the state icon shown (in Indigo Touch and client Main Window) + # for this device to be the a temperature sensor: + if dev.onState is not None: + key_val_list.append({'key':'onOffState', 'value':not dev.onState}) + dev.updateStatesOnServer(key_val_list) + if dev.onState: + dev.updateStateImageOnServer(indigo.kStateImageSel.TemperatureSensorOn) + else: + dev.updateStateImageOnServer(indigo.kStateImageSel.TemperatureSensor) + else: + dev.updateStatesOnServer(key_val_list) + dev.updateStateImageOnServer(indigo.kStateImageSel.TemperatureSensor) + elif dev.onState is not None: + dev.updateStateOnServer("onOffState", not dev.onState) + dev.updateStateImageOnServer(indigo.kStateImageSel.Auto) + else: + dev.updateStateImageOnServer(indigo.kStateImageSel.Auto) + self.sleep(2) + except self.StopThread: + pass # Optionally catch the StopThread exception and do any needed cleanup. + + ######################################## + def validateDeviceConfigUi(self, values_dict, type_id, dev_id): + return (True, values_dict) + + ######################################## + def deviceStartComm(self, dev): + # Called when communication with the hardware should be started. + pass + + def deviceStopComm(self, dev): + # Called when communication with the hardware should be shutdown. + pass + + ######################################## + # Sensor Action callback + ###################### + def actionControlSensor(self, action, dev): + ###### TURN ON ###### + # Ignore turn on/off/toggle requests from clients since this is a read-only sensor. + if action.sensorAction == indigo.kSensorAction.TurnOn: + self.logger.info(f"ignored \"{dev.name}\" on request (sensor is read-only)") + # But we could request a sensor state update if we wanted like this: + # dev.updateStateOnServer("onOffState", True) + + ###### TURN OFF ###### + # Ignore turn on/off/toggle requests from clients since this is a read-only sensor. + elif action.sensorAction == indigo.kSensorAction.TurnOff: + self.logger.info(f"ignored \"{dev.name}\" off request (sensor is read-only)") + # But we could request a sensor state update if we wanted like this: + # dev.updateStateOnServer("onOffState", False) + + ###### TOGGLE ###### + # Ignore turn on/off/toggle requests from clients since this is a read-only sensor. + elif action.sensorAction == indigo.kSensorAction.Toggle: + self.logger.info(f"ignored \"{dev.name}\" toggle request (sensor is read-only)") + # But we could request a sensor state update if we wanted like this: + # dev.updateStateOnServer("onOffState", not dev.onState) + + ######################################## + # General Action callback + ###################### + def actionControlUniversal(self, action, dev): + ###### BEEP ###### + if action.deviceAction == indigo.kUniversalAction.Beep: + # Beep the hardware module (dev) here: + # ** IMPLEMENT ME ** + self.logger.info(f"sent \"{dev.name}\" beep request") + + ###### STATUS REQUEST ###### + elif action.deviceAction == indigo.kUniversalAction.RequestStatus: + # Query hardware module (dev) for its current status here: + # ** IMPLEMENT ME ** + self.logger.info(f"sent \"{dev.name}\" status request") + + ######################################## + # Custom Plugin Action callbacks (defined in Actions.xml) + ###################### + def set_backlight_brightness(self, plugin_action, dev): + try: + new_brightness = int(plugin_action.props.get("brightness", 100)) + except ValueError: + # The int() cast above might fail if the user didn't enter a number: + self.logger.error(f"set backlight brightness action to device \"{dev.name}\" -- invalid brightness value") + return + # Command hardware module (dev) to set backlight brightness here: + # FIXME: add implementation here + send_success = True # Set to False if it failed. + if send_success: + # If success then log that the command was successfully sent. + self.logger.info(f"sent \"{dev.name}\" set backlight brightness to {new_brightness}") + # And then tell the Indigo Server to update the state: + dev.updateStateOnServer("backlightBrightness", new_brightness) + else: + # Else log failure but do NOT update state on Indigo Server. + self.logger.error(f"send \"{dev.name}\" set backlight brightness to {new_brightness} failed") diff --git a/Example Device - Speed Control.indigoPlugin/Contents/Info.plist b/Example Device - Speed Control.indigoPlugin/Contents/Info.plist index 04b7719..092db83 100644 --- a/Example Device - Speed Control.indigoPlugin/Contents/Info.plist +++ b/Example Device - Speed Control.indigoPlugin/Contents/Info.plist @@ -2,24 +2,24 @@ - PluginVersion - 2021.1.0 - ServerApiVersion - 2.5 - IwsApiVersion - 1.0.0 - CFBundleDisplayName - Example Device - Speed Control (ceiling fan) - CFBundleIdentifier - com.perceptiveautomation.indigoplugin.example-device-speedcontrol1 - CFBundleVersion - 1.0.0 - CFBundleURLTypes - - - CFBundleURLName - http://wiki.indigodomo.com/doku.php?id=plugins:example_dev_speedcontrol_1 - - + PluginVersion + 2022.1.0 + ServerApiVersion + 3.0 + IwsApiVersion + 1.0.0 + CFBundleDisplayName + Example Device - Speed Control (ceiling fan) + CFBundleIdentifier + com.perceptiveautomation.indigoplugin.example-device-speedcontrol1 + CFBundleVersion + 1.0.0 + CFBundleURLTypes + + + CFBundleURLName + https://wiki.indigodomo.com/doku.php?id=plugins:example_dev_speedcontrol_1 + + diff --git a/Example Device - Speed Control.indigoPlugin/Contents/Server Plugin/Actions.xml b/Example Device - Speed Control.indigoPlugin/Contents/Server Plugin/Actions.xml index c149a1b..150aa2f 100644 --- a/Example Device - Speed Control.indigoPlugin/Contents/Server Plugin/Actions.xml +++ b/Example Device - Speed Control.indigoPlugin/Contents/Server Plugin/Actions.xml @@ -1,24 +1,24 @@ - - Set Backlight Brightness - setBacklightBrightness - - - - - - - + + Set Backlight Brightness + set_backlight_brightness + + + + + + + diff --git a/Example Device - Speed Control.indigoPlugin/Contents/Server Plugin/Devices.xml b/Example Device - Speed Control.indigoPlugin/Contents/Server Plugin/Devices.xml index 6ace7b9..7debef2 100644 --- a/Example Device - Speed Control.indigoPlugin/Contents/Server Plugin/Devices.xml +++ b/Example Device - Speed Control.indigoPlugin/Contents/Server Plugin/Devices.xml @@ -1,58 +1,58 @@ - - - Example Ceiling Fan Module - - - - - - - - + + Example Ceiling Fan Module + + + + + + + + - - Integer - Backlight Brightness - Backlight Brightness - - - + The plugin can specify additional custom states and custom + actions (in Actions.xml) to modify custom states. As an example + here, we define a new custom state, backlightBrightness, which + is used to control the brightness of the backlit display of + the module. + --> + + Integer + Backlight Brightness + Backlight Brightness + + + diff --git a/Example Device - Speed Control.indigoPlugin/Contents/Server Plugin/plugin.py b/Example Device - Speed Control.indigoPlugin/Contents/Server Plugin/plugin.py index 2b35ac2..b6a43fe 100644 --- a/Example Device - Speed Control.indigoPlugin/Contents/Server Plugin/plugin.py +++ b/Example Device - Speed Control.indigoPlugin/Contents/Server Plugin/plugin.py @@ -1,8 +1,8 @@ #! /usr/bin/env python # -*- coding: utf-8 -*- #################### -# Copyright (c) 2014, Perceptive Automation, LLC. All rights reserved. -# http://www.indigodomo.com +# Copyright (c) 2022, Perceptive Automation, LLC. All rights reserved. +# https://www.indigodomo.com import indigo @@ -14,200 +14,192 @@ ################################################################################ class Plugin(indigo.PluginBase): - ######################################## - def __init__(self, pluginId, pluginDisplayName, pluginVersion, pluginPrefs): - super(Plugin, self).__init__(pluginId, pluginDisplayName, pluginVersion, pluginPrefs) - self.debug = True - self.speedLabels = [u"off", u"low", u"medium", u"high"] - - ######################################## - def startup(self): - self.debugLog(u"startup called") - - def shutdown(self): - self.debugLog(u"shutdown called") - - ######################################## - def validateDeviceConfigUi(self, valuesDict, typeId, devId): - return (True, valuesDict) - - ######################################## - # Speed Control Action callback - ###################### - def actionControlSpeedControl(self, action, dev): - ###### TURN ON ###### - if action.speedControlAction == indigo.kSpeedControlAction.TurnOn: - # Command hardware module (dev) to turn ON here: - # ** IMPLEMENT ME ** - sendSuccess = True # Set to False if it failed. - - if sendSuccess: - # If success then log that the command was successfully sent. - indigo.server.log(u"sent \"%s\" %s" % (dev.name, "on")) - - # And then tell the Indigo Server to update the state. - dev.updateStateOnServer("onOffState", True) - else: - # Else log failure but do NOT update state on Indigo Server. - indigo.server.log(u"send \"%s\" %s failed" % (dev.name, "on"), isError=True) - - ###### TURN OFF ###### - elif action.speedControlAction == indigo.kSpeedControlAction.TurnOff: - # Command hardware module (dev) to turn OFF here: - # ** IMPLEMENT ME ** - sendSuccess = True # Set to False if it failed. - - if sendSuccess: - # If success then log that the command was successfully sent. - indigo.server.log(u"sent \"%s\" %s" % (dev.name, "off")) - - # And then tell the Indigo Server to update the state: - dev.updateStateOnServer("onOffState", False) - else: - # Else log failure but do NOT update state on Indigo Server. - indigo.server.log(u"send \"%s\" %s failed" % (dev.name, "off"), isError=True) - - ###### TOGGLE ###### - elif action.speedControlAction == indigo.kSpeedControlAction.Toggle: - # Command hardware module (dev) to toggle here: - # ** IMPLEMENT ME ** - newOnState = not dev.onState - sendSuccess = True # Set to False if it failed. - - if sendSuccess: - # If success then log that the command was successfully sent. - indigo.server.log(u"sent \"%s\" %s" % (dev.name, "toggle")) - - # And then tell the Indigo Server to update the state: - dev.updateStateOnServer("onOffState", newOnState) - else: - # Else log failure but do NOT update state on Indigo Server. - indigo.server.log(u"send \"%s\" %s failed" % (dev.name, "toggle"), isError=True) - - ###### SET SPEED INDEX ###### - elif action.speedControlAction == indigo.kSpeedControlAction.SetSpeedIndex: - # Command hardware module (dev) to change the speed here to a specific - # speed index (0=off, 1=low, ..., 3=high): - # ** IMPLEMENT ME ** - newSpeedIndex = action.actionValue - sendSuccess = True # Set to False if it failed. - - if sendSuccess: - # If success then log that the command was successfully sent. - indigo.server.log(u"sent \"%s\" %s to %s" % (dev.name, "set motor speed", self.speedLabels[newSpeedIndex])) - - # And then tell the Indigo Server to update the state: - dev.updateStateOnServer("speedIndex", newSpeedIndex) - else: - # Else log failure but do NOT update state on Indigo Server. - indigo.server.log(u"send \"%s\" %s to %s failed" % (dev.name, "set motor speed", self.speedLabels[newSpeedIndex]), isError=True) - - ###### SET SPEED LEVEL ###### - elif action.speedControlAction == indigo.kSpeedControlAction.SetSpeedLevel: - # Command hardware module (dev) to change the speed here to an absolute - # speed level (0 to 100): - # ** IMPLEMENT ME ** - newSpeedLevel = action.actionValue - sendSuccess = True # Set to False if it failed. - - if sendSuccess: - # If success then log that the command was successfully sent. - indigo.server.log(u"sent \"%s\" %s to %d" % (dev.name, "set motor speed", newSpeedLevel)) - - # And then tell the Indigo Server to update the state: - dev.updateStateOnServer("speedLevel", newSpeedLevel) - else: - # Else log failure but do NOT update state on Indigo Server. - indigo.server.log(u"send \"%s\" %s to %d failed" % (dev.name, "set motor speed", newSpeedLevel), isError=True) - - ###### INCREASE SPEED INDEX BY ###### - elif action.speedControlAction == indigo.kSpeedControlAction.IncreaseSpeedIndex: - # Command hardware module (dev) to do a relative speed increase here: - # ** IMPLEMENT ME ** - newSpeedIndex = dev.speedIndex + action.actionValue - if newSpeedIndex > 3: - newSpeedIndex = 3 - sendSuccess = True # Set to False if it failed. - - if sendSuccess: - # If success then log that the command was successfully sent. - indigo.server.log(u"sent \"%s\" %s to %s" % (dev.name, "motor speed increase", self.speedLabels[newSpeedIndex])) - - # And then tell the Indigo Server to update the state: - dev.updateStateOnServer("speedIndex", newSpeedIndex) - else: - # Else log failure but do NOT update state on Indigo Server. - indigo.server.log(u"send \"%s\" %s to %s failed" % (dev.name, "motor speed increase", self.speedLabels[newSpeedIndex]), isError=True) - - ###### DECREASE SPEED INDEX BY ###### - elif action.speedControlAction == indigo.kSpeedControlAction.DecreaseSpeedIndex: - # Command hardware module (dev) to do a relative speed decrease here: - # ** IMPLEMENT ME ** - newSpeedIndex = dev.speedIndex - action.actionValue - if newSpeedIndex < 0: - newSpeedIndex = 0 - sendSuccess = True # Set to False if it failed. - - if sendSuccess: - # If success then log that the command was successfully sent. - indigo.server.log(u"sent \"%s\" %s to %s" % (dev.name, "motor speed decrease", self.speedLabels[newSpeedIndex])) - - # And then tell the Indigo Server to update the state: - dev.updateStateOnServer("speedIndex", newSpeedIndex) - else: - # Else log failure but do NOT update state on Indigo Server. - indigo.server.log(u"send \"%s\" %s to %s failed" % (dev.name, "motor speed decrease", self.speedLabels[newSpeedIndex]), isError=True) - - ######################################## - # General Action callback - ###################### - def actionControlUniversal(self, action, dev): - ###### BEEP ###### - if action.deviceAction == indigo.kUniversalAction.Beep: - # Beep the hardware module (dev) here: - # ** IMPLEMENT ME ** - indigo.server.log(u"sent \"%s\" %s" % (dev.name, "beep request")) - - ###### ENERGY UPDATE ###### - elif action.deviceAction == indigo.kUniversalAction.EnergyUpdate: - # Request hardware module (dev) for its most recent meter data here: - # ** IMPLEMENT ME ** - indigo.server.log(u"sent \"%s\" %s" % (dev.name, "energy update request")) - - ###### ENERGY RESET ###### - elif action.deviceAction == indigo.kUniversalAction.EnergyReset: - # Request that the hardware module (dev) reset its accumulative energy usage data here: - # ** IMPLEMENT ME ** - indigo.server.log(u"sent \"%s\" %s" % (dev.name, "energy reset request")) - - ###### STATUS REQUEST ###### - elif action.deviceAction == indigo.kUniversalAction.RequestStatus: - # Query hardware module (dev) for its current status here: - # ** IMPLEMENT ME ** - indigo.server.log(u"sent \"%s\" %s" % (dev.name, "status request")) - - ######################################## - # Custom Plugin Action callbacks (defined in Actions.xml) - ###################### - def setBacklightBrightness(self, pluginAction, dev): - try: - newBrightness = int(pluginAction.props.get(u"brightness", 100)) - except ValueError: - # The int() cast above might fail if the user didn't enter a number: - indigo.server.log(u"set backlight brightness action to device \"%s\" -- invalid brightness value" % (dev.name,), isError=True) - return - - # Command hardware module (dev) to set backlight brightness here: - # ** IMPLEMENT ME ** - sendSuccess = True # Set to False if it failed. - - if sendSuccess: - # If success then log that the command was successfully sent. - indigo.server.log(u"sent \"%s\" %s to %d" % (dev.name, "set backlight brightness", newBrightness)) - - # And then tell the Indigo Server to update the state: - dev.updateStateOnServer("backlightBrightness", newBrightness) - else: - # Else log failure but do NOT update state on Indigo Server. - indigo.server.log(u"send \"%s\" %s to %d failed" % (dev.name, "set backlight brightness", newBrightness), isError=True) - + ######################################## + def __init__(self, plugin_id, plugin_display_name, plugin_version, plugin_prefs): + super().__init__(plugin_id, plugin_display_name, plugin_version, plugin_prefs) + self.debug = True + self.speed_labels = ["off", "low", "medium", "high"] + + ######################################## + def startup(self): + self.logger.debug("startup called") + + def shutdown(self): + self.logger.debug("shutdown called") + + ######################################## + def validateDeviceConfigUi(self, values_dict, type_id, dev_id): + return (True, values_dict) + + ######################################## + # Speed Control Action callback + ###################### + def actionControlSpeedControl(self, action, dev): + ###### TURN ON ###### + if action.speedControlAction == indigo.kSpeedControlAction.TurnOn: + # Command hardware module (dev) to turn ON here: + # ** IMPLEMENT ME ** + send_success = True # Set to False if it failed. + + if send_success: + # If success then log that the command was successfully sent. + self.logger.info(f"sent \"{dev.name}\" on") + + # And then tell the Indigo Server to update the state. + dev.updateStateOnServer("onOffState", True) + else: + # Else log failure but do NOT update state on Indigo Server. + self.logger.error(f"send \"{dev.name}\" on failed") + + ###### TURN OFF ###### + elif action.speedControlAction == indigo.kSpeedControlAction.TurnOff: + # Command hardware module (dev) to turn OFF here: + # ** IMPLEMENT ME ** + send_success = True # Set to False if it failed. + + if send_success: + # If success then log that the command was successfully sent. + self.logger.info(f"sent \"{dev.name}\" off") + + # And then tell the Indigo Server to update the state: + dev.updateStateOnServer("onOffState", False) + else: + # Else log failure but do NOT update state on Indigo Server. + self.logger.error(f"send \"{dev.name}\" off failed") + + ###### TOGGLE ###### + elif action.speedControlAction == indigo.kSpeedControlAction.Toggle: + # Command hardware module (dev) to toggle here: + # ** IMPLEMENT ME ** + new_on_state = not dev.onState + send_success = True # Set to False if it failed. + + if send_success: + # If success then log that the command was successfully sent. + self.logger.info(f"sent \"{dev.name}\" toggle") + + # And then tell the Indigo Server to update the state: + dev.updateStateOnServer("onOffState", new_on_state) + else: + # Else log failure but do NOT update state on Indigo Server. + self.logger.error(f"send \"{dev.name}\" toggle failed") + + ###### SET SPEED INDEX ###### + elif action.speedControlAction == indigo.kSpeedControlAction.SetSpeedIndex: + # Command hardware module (dev) to change the speed here to a specific + # speed index (0=off, 1=low, ..., 3=high): + # ** IMPLEMENT ME ** + new_speed_index = action.actionValue + send_success = True # Set to False if it failed. + + if send_success: + # If success then log that the command was successfully sent. + self.logger.info(f"sent \"{dev.name}\" set motor speed to {self.speed_labels[new_speed_index]}") + + # And then tell the Indigo Server to update the state: + dev.updateStateOnServer("speedIndex", new_speed_index) + else: + # Else log failure but do NOT update state on Indigo Server. + self.logger.error(f"send \"{dev.name}\" set motor speed to {self.speed_labels[new_speed_index]} failed") + + ###### SET SPEED LEVEL ###### + elif action.speedControlAction == indigo.kSpeedControlAction.SetSpeedLevel: + # Command hardware module (dev) to change the speed here to an absolute + # speed level (0 to 100): + # ** IMPLEMENT ME ** + new_speed_level = action.actionValue + send_success = True # Set to False if it failed. + + if send_success: + # If success then log that the command was successfully sent. + self.logger.info(f"sent \"{dev.name}\" set motor speed to {new_speed_level}") + + # And then tell the Indigo Server to update the state: + dev.updateStateOnServer("speedLevel", new_speed_level) + else: + # Else log failure but do NOT update state on Indigo Server. + self.logger.error(f"send \"{dev.name}\" set motor speed to {new_speed_level} failed") + + ###### INCREASE SPEED INDEX BY ###### + elif action.speedControlAction == indigo.kSpeedControlAction.IncreaseSpeedIndex: + # Command hardware module (dev) to do a relative speed increase here: + # ** IMPLEMENT ME ** + new_speed_index = min(dev.speedIndex + action.actionValue, 3) + send_success = True # Set to False if it failed. + + if send_success: + # If success then log that the command was successfully sent. + self.logger.info(f"sent \"{dev.name}\" motor speed increase to {self.speed_labels[new_speed_index]}") + + # And then tell the Indigo Server to update the state: + dev.updateStateOnServer("speedIndex", new_speed_index) + else: + # Else log failure but do NOT update state on Indigo Server. + self.logger.error(f"send \"{dev.name}\" motor speed increase to {self.speed_labels[new_speed_index]} failed") + + ###### DECREASE SPEED INDEX BY ###### + elif action.speedControlAction == indigo.kSpeedControlAction.DecreaseSpeedIndex: + # Command hardware module (dev) to do a relative speed decrease here: + # ** IMPLEMENT ME ** + new_speed_index = max(dev.speedIndex - action.actionValue, 0) + send_success = True # Set to False if it failed. + + if send_success: + # If success then log that the command was successfully sent. + self.logger.info(f"sent \"{dev.name}\" motor speed decrease to {self.speed_labels[new_speed_index]}") + + # And then tell the Indigo Server to update the state: + dev.updateStateOnServer("speedIndex", new_speed_index) + else: + # Else log failure but do NOT update state on Indigo Server. + self.logger.error(f"send \"{dev.name}\" motor speed decrease to {self.speed_labels[new_speed_index]} failed") + + ######################################## + # General Action callback + ###################### + def actionControlUniversal(self, action, dev): + ###### BEEP ###### + if action.deviceAction == indigo.kUniversalAction.Beep: + # Beep the hardware module (dev) here: + # ** IMPLEMENT ME ** + self.logger.info(f"sent \"{dev.name}\" beep request") + + ###### ENERGY UPDATE ###### + elif action.deviceAction == indigo.kUniversalAction.EnergyUpdate: + # Request hardware module (dev) for its most recent meter data here: + # ** IMPLEMENT ME ** + self.logger.info(f"sent \"{dev.name}\" energy update request") + + ###### ENERGY RESET ###### + elif action.deviceAction == indigo.kUniversalAction.EnergyReset: + # Request that the hardware module (dev) reset its accumulative energy usage data here: + # ** IMPLEMENT ME ** + self.logger.info(f"sent \"{dev.name}\" energy reset request") + + ###### STATUS REQUEST ###### + elif action.deviceAction == indigo.kUniversalAction.RequestStatus: + # Query hardware module (dev) for its current status here: + # ** IMPLEMENT ME ** + self.logger.info(f"sent \"{dev.name}\" status request") + + ######################################## + # Custom Plugin Action callbacks (defined in Actions.xml) + ###################### + def set_backlight_brightness(self, plugin_action, dev): + try: + new_brightness = int(plugin_action.props.get("brightness", 100)) + except ValueError: + # The int() cast above might fail if the user didn't enter a number: + self.logger.error(f"set backlight brightness action to device \"{dev.name}\" -- invalid brightness value") + return + # Command hardware module (dev) to set backlight brightness here: + # FIXME: add implementation here + send_success = True # Set to False if it failed. + if send_success: + # If success then log that the command was successfully sent. + self.logger.info(f"sent \"{dev.name}\" set backlight brightness to {new_brightness}") + # And then tell the Indigo Server to update the state: + dev.updateStateOnServer("backlightBrightness", new_brightness) + else: + # Else log failure but do NOT update state on Indigo Server. + self.logger.error(f"send \"{dev.name}\" set backlight brightness to {new_brightness} failed") diff --git a/Example Device - Sprinkler.indigoPlugin/Contents/Info.plist b/Example Device - Sprinkler.indigoPlugin/Contents/Info.plist index f2b30c9..7e6ac69 100644 --- a/Example Device - Sprinkler.indigoPlugin/Contents/Info.plist +++ b/Example Device - Sprinkler.indigoPlugin/Contents/Info.plist @@ -2,24 +2,24 @@ - PluginVersion - 2021.1.0 - ServerApiVersion - 2.5 - IwsApiVersion - 1.0.0 - CFBundleDisplayName - Example Device - Sprinkler - CFBundleIdentifier - com.perceptiveautomation.indigoplugin.example-device-sprinkler1 - CFBundleVersion - 1.0.0 - CFBundleURLTypes - - - CFBundleURLName - http://wiki.indigodomo.com/doku.php?id=plugins:example_dev_sprinkler_1 - - + PluginVersion + 2022.1.0 + ServerApiVersion + 3.0 + IwsApiVersion + 1.0.0 + CFBundleDisplayName + Example Device - Sprinkler + CFBundleIdentifier + com.perceptiveautomation.indigoplugin.example-device-sprinkler1 + CFBundleVersion + 1.0.0 + CFBundleURLTypes + + + CFBundleURLName + https://wiki.indigodomo.com/doku.php?id=plugins:example_dev_sprinkler_1 + + diff --git a/Example Device - Sprinkler.indigoPlugin/Contents/Server Plugin/Actions.xml b/Example Device - Sprinkler.indigoPlugin/Contents/Server Plugin/Actions.xml index 6e2cf67..2705e66 100644 --- a/Example Device - Sprinkler.indigoPlugin/Contents/Server Plugin/Actions.xml +++ b/Example Device - Sprinkler.indigoPlugin/Contents/Server Plugin/Actions.xml @@ -1,24 +1,24 @@ - - Set Backlight Brightness - setBacklightBrightness - - - - - - - + + Set Backlight Brightness + set_backlight_brightness + + + + + + + diff --git a/Example Device - Sprinkler.indigoPlugin/Contents/Server Plugin/Devices.xml b/Example Device - Sprinkler.indigoPlugin/Contents/Server Plugin/Devices.xml index 22c794b..2f3ae86 100644 --- a/Example Device - Sprinkler.indigoPlugin/Contents/Server Plugin/Devices.xml +++ b/Example Device - Sprinkler.indigoPlugin/Contents/Server Plugin/Devices.xml @@ -1,116 +1,116 @@ - - - Example Sprinkler Module - - - - - + + + Example Sprinkler Module + + + + + - - - - - - - - - - - - - - - - - - - - - Last zone of controller is used as a pump - - + Plugins can update these properties either in device ConfigUI + (like below), or can update them from python by using the + dev.replacePluginPropsOnServer() method, most likely inside your + deviceStartComm method. Both will trigger the Indigo Server to + automatically rebuild the device's states list based on the needed + changes. + --> + + + + + + + + + + + + + + + + + + + + Last zone of controller is used as a pump + + - - - - Plugin overrides high-level actions - - - - + + + Plugin overrides high-level actions + + + + - - Integer - Backlight Brightness - Backlight Brightness - - - + The plugin can specify additional custom states and custom + actions (in Actions.xml) to modify custom states. As an example + here, we define a new custom state, backlightBrightness, which + is used to control the brightness of the backlit display of + the module. + --> + + Integer + Backlight Brightness + Backlight Brightness + + + diff --git a/Example Device - Sprinkler.indigoPlugin/Contents/Server Plugin/plugin.py b/Example Device - Sprinkler.indigoPlugin/Contents/Server Plugin/plugin.py index ceb083b..b613df7 100644 --- a/Example Device - Sprinkler.indigoPlugin/Contents/Server Plugin/plugin.py +++ b/Example Device - Sprinkler.indigoPlugin/Contents/Server Plugin/plugin.py @@ -1,8 +1,8 @@ #! /usr/bin/env python # -*- coding: utf-8 -*- #################### -# Copyright (c) 2014, Perceptive Automation, LLC. All rights reserved. -# http://www.indigodomo.com +# Copyright (c) 2022, Perceptive Automation, LLC. All rights reserved. +# https://www.indigodomo.com import indigo @@ -14,254 +14,250 @@ ################################################################################ class Plugin(indigo.PluginBase): - ######################################## - def __init__(self, pluginId, pluginDisplayName, pluginVersion, pluginPrefs): - super(Plugin, self).__init__(pluginId, pluginDisplayName, pluginVersion, pluginPrefs) - self.debug = True - - ######################################## - def startup(self): - # self.debugLog(u"startup called") - pass - - def shutdown(self): - # self.debugLog(u"shutdown called") - pass - - ######################################## - def validateDeviceConfigUi(self, valuesDict, typeId, devId): - return (True, valuesDict) - - ######################################## - # Sprinkler Control Action callback - ###################### - def actionControlSprinkler(self, action, dev): - ######################################## - # Required plugin sprinkler actions: These actions must be handled by the plugin. - ######################################## - ###### ZONE ON ###### - if action.sprinklerAction == indigo.kSprinklerAction.ZoneOn: - # Command hardware module (dev) to turn ON a specific zone here. - # ** IMPLEMENT ME ** - zoneName = u"no zone" - if action.zoneIndex is not None: - zoneName = dev.zoneNames[action.zoneIndex - 1] - sendSuccess = True # Set to False if it failed. - - if sendSuccess: - # If success then log that the command was successfully sent. - indigo.server.log(u"sent \"%s - %s\" on" % (dev.name, zoneName)) - - # And then tell the Indigo Server to update the state. - dev.updateStateOnServer("activeZone", action.zoneIndex) - else: - # Else log failure but do NOT update state on Indigo Server. - indigo.server.log(u"send \"%s - %s\" on failed" % (dev.name, zoneName), isError=True) - - ###### ALL ZONES OFF ###### - elif action.sprinklerAction == indigo.kSprinklerAction.AllZonesOff: - # Command hardware module (dev) to turn OFF here: - # ** IMPLEMENT ME ** - sendSuccess = True # Set to False if it failed. - - if sendSuccess: - # If success then log that the command was successfully sent. - indigo.server.log(u"sent \"%s\" %s" % (dev.name, "all zones off")) - - # And then tell the Indigo Server to update the state. - dev.updateStateOnServer("activeZone", 0) - else: - # Else log failure but do NOT update state on Indigo Server. - indigo.server.log(u"send \"%s\" %s failed" % (dev.name, "all zones off"), isError=True) - - ######################################## - # Optional plugin sprinkler actions: These actions are *only* dispatched to the plugin - # if the device property "OverrideScheduleActions" is set to True. The default behavior - # (False) is for the Indigo Server to handle these higher level commands and scheduling - # automatically, which will dispatch the required lower-level indigo.kSprinklerAction.ZoneOn - # and indigo.kSprinklerAction.AllZonesOff actions above at the appropriate times. - # - # Note if a plugin defines the higher level actions below then it must handle all zone - # scheduling, pausing, resuming, and next/previous skipping. Given the complexity in - # handling the individual zone scheduling, pausing, resuming, and next/previous zones - # it is recommended to use the default value of OverrideScheduleActions False, so that - # IndigoServer can handle this higher level scheduling and complexity. - ######################################## - ###### RUN SCHEDULE OF ZONE DURATIONS ###### - elif action.sprinklerAction == indigo.kSprinklerAction.RunNewSchedule: - # Plugin should handle its own scheduling here and tell the device - # to turn on the first zone in the schedule, _or_ (if supported by - # the device) send the entire schedule to the device. - # ** IMPLEMENT ME ** - indigo.server.log(u"scheduled \"%s\" zone durations: %s" % (dev.name, str(action.zoneDurations))) - - # The ScheduledZoneDurations property is used by Indigo to show the - # currently running schedule (in the client, Web, and Indigo Touch UI), - # so set based on the durations requested in the action. - props = dev.pluginProps - # Plugin should use dev.zoneEnableList, dev.zoneMaxDurations and - # dev.zoneCount to make sure action.zoneDurations are within the - # range and bounds specified. - zoneListStr = ', '.join(str(dur) for dur in action.zoneDurations) - props["PreviousZoneDurations"] = zoneListStr - props["ScheduledZoneDurations"] = zoneListStr - dev.replacePluginPropsOnServer(props) - - # Plugin will then need to update the activeZone state appropriately. - # As an example here we'll just turn on zone 1 (plugin should really - # inspect action.zoneDurations for the first non-zero item). - dev.updateStateOnServer("activeZone", 1) - - ###### RUN LAST USED SCHEDULE ###### - elif action.sprinklerAction == indigo.kSprinklerAction.RunPreviousSchedule: - # Plugin should re-run the last schedule that was used here and tell - # the device to turn on the first zone in the schedule, _or_ (if - # supported by the device) send the device a run last schedule command. - # ** IMPLEMENT ME ** - - # Plugin will then need to update the ScheduledZoneDurations property - # and the activeZone state appropriately. - props = dev.pluginProps - if "PreviousZoneDurations" in props: - indigo.server.log(u"running last used scheduled for \"%s\": %s" % (dev.name, props["PreviousZoneDurations"])) - - props["ScheduledZoneDurations"] = props["PreviousZoneDurations"] - dev.replacePluginPropsOnServer(props) - dev.updateStateOnServer("activeZone", 1) - - ###### PAUSE SCHEDULE ###### - elif action.sprinklerAction == indigo.kSprinklerAction.PauseSchedule: - # Plugin should pause its schedule here and tell the device to - # turn off the active zone, _or_ (if supported by the device) - # send the device a pause command. - # ** IMPLEMENT ME ** - indigo.server.log(u"pausing \"%s\" schedule" % (dev.name)) - - # Plugin will then need to update the ScheduledZoneDurations property - # and the activeZone state appropriately. - props = dev.pluginProps - props["PauseScheduleZoneIndex"] = dev.activeZone - props["PauseScheduleRemainingZoneDuration"] = 15 # plugin needs to calc and store remaining duration for active zone - props["ScheduledZoneDurations"] = '' # empty so UI doesn't show active schedule - dev.replacePluginPropsOnServer(props) - dev.updateStateOnServer("activeZone", 0) - - ###### RESUME SCHEDULE ###### - elif action.sprinklerAction == indigo.kSprinklerAction.ResumeSchedule: - # Plugin should resume the active schedule here and tell the device - # to turn on the paused zone, _or_ (if supported by the device) send - # the device a resume command. - # ** IMPLEMENT ME ** - - # Plugin will then need to update the ScheduledZoneDurations property - # and the activeZone state appropriately. - props = dev.pluginProps - if "PreviousZoneDurations" in props and "PauseScheduleZoneIndex" in props and "PauseScheduleRemainingZoneDuration" in props: - indigo.server.log(u"resuming \"%s\" schedule zone durations: %s" % (dev.name, props["PreviousZoneDurations"])) - - resumeZone = int(props["PauseScheduleZoneIndex"]) - resumeDuration = int(props["PauseScheduleRemainingZoneDuration"]) - - props["PauseScheduleZoneIndex"] = 0 - props["PauseScheduleRemainingZoneDuration"] = 0 - props["ScheduledZoneDurations"] = props["PreviousZoneDurations"] - dev.replacePluginPropsOnServer(props) - dev.updateStateOnServer("activeZone", resumeZone) - - # Plugin should now use resumeDuration for its own internal timer of how long - # the activeZone (resumeZone) should remain ON before advancing to next zone. - - ###### STOP SCHEDULE ###### - elif action.sprinklerAction == indigo.kSprinklerAction.StopSchedule: - # Plugin should stop the active schedule here and tell the device - # to turn off all zones, _or_ (if supported by the device) send - # the device both a stop schedule and all zones off command. - # ** IMPLEMENT ME ** - indigo.server.log(u"stopping \"%s\" schedule" % (dev.name)) - - # Clear out ScheduledZoneDurations so the UI doesn't show an active schedule anymore. - props = dev.pluginProps - props["PauseScheduleZoneIndex"] = 0 - props["PauseScheduleRemainingZoneDuration"] = 0 - props["ScheduledZoneDurations"] = '' - dev.replacePluginPropsOnServer(props) - - # Plugin will then need to update the activeZone state appropriately. - dev.updateStateOnServer("activeZone", 0) - - ###### JUMP TO PREVIOUS ZONE ###### - elif action.sprinklerAction == indigo.kSprinklerAction.PreviousZone: - # Plugin should jump to the previous zone in the current - # schedule here and tell the device to turn that zone on, _or_ - # (if supported by the device) send the device a previous zone - # command. - # ** IMPLEMENT ME ** - indigo.server.log(u"skipping \"%s\" to previous zone" % (dev.name)) - - # Plugin will then need to update the activeZone state appropriately. - # dev.updateStateOnServer("activeZone", current zone - 1) - - ###### SKIP TO NEXT ZONE ###### - elif action.sprinklerAction == indigo.kSprinklerAction.NextZone: - # Plugin should jump to the next zone in the current schedule - # here and tell the device to turn that zone on, _or_ (if - # supported by the device) send the device a next zone command. - # ** IMPLEMENT ME ** - indigo.server.log(u"advancing \"%s\" to next zone" % (dev.name)) - - # Plugin will then need to update the activeZone state appropriately. - # dev.updateStateOnServer("activeZone", current zone + 1) - - ######################################## - # General Action callback - ###################### - def actionControlUniversal(self, action, dev): - ###### BEEP ###### - if action.deviceAction == indigo.kUniversalAction.Beep: - # Beep the hardware module (dev) here: - # ** IMPLEMENT ME ** - indigo.server.log(u"sent \"%s\" %s" % (dev.name, "beep request")) - - ###### ENERGY UPDATE ###### - elif action.deviceAction == indigo.kUniversalAction.EnergyUpdate: - # Request hardware module (dev) for its most recent meter data here: - # ** IMPLEMENT ME ** - indigo.server.log(u"sent \"%s\" %s" % (dev.name, "energy update request")) - - ###### ENERGY RESET ###### - elif action.deviceAction == indigo.kUniversalAction.EnergyReset: - # Request that the hardware module (dev) reset its accumulative energy usage data here: - # ** IMPLEMENT ME ** - indigo.server.log(u"sent \"%s\" %s" % (dev.name, "energy reset request")) - - ###### STATUS REQUEST ###### - elif action.deviceAction == indigo.kUniversalAction.RequestStatus: - # Query hardware module (dev) for its current status here: - # ** IMPLEMENT ME ** - indigo.server.log(u"sent \"%s\" %s" % (dev.name, "status request")) - - ######################################## - # Custom Plugin Action callbacks (defined in Actions.xml) - ###################### - def setBacklightBrightness(self, pluginAction, dev): - try: - newBrightness = int(pluginAction.props.get(u"brightness", 100)) - except ValueError: - # The int() cast above might fail if the user didn't enter a number: - indigo.server.log(u"set backlight brightness action to device \"%s\" -- invalid brightness value" % (dev.name,), isError=True) - return - - # Command hardware module (dev) to set backlight brightness here: - # ** IMPLEMENT ME ** - sendSuccess = True # Set to False if it failed. - - if sendSuccess: - # If success then log that the command was successfully sent. - indigo.server.log(u"sent \"%s\" %s to %d" % (dev.name, "set backlight brightness", newBrightness)) - - # And then tell the Indigo Server to update the state: - dev.updateStateOnServer("backlightBrightness", newBrightness) - else: - # Else log failure but do NOT update state on Indigo Server. - indigo.server.log(u"send \"%s\" %s to %d failed" % (dev.name, "set backlight brightness", newBrightness), isError=True) - + ######################################## + def __init__(self, plugin_id, plugin_display_name, plugin_version, plugin_prefs): + super().__init__(plugin_id, plugin_display_name, plugin_version, plugin_prefs) + self.debug = True + + ######################################## + def startup(self): + # self.logger.debug("startup called") + pass + + def shutdown(self): + # self.logger.debug("shutdown called") + pass + + ######################################## + def validateDeviceConfigUi(self, values_dict, type_id, dev_id): + return (True, values_dict) + + ######################################## + # Sprinkler Control Action callback + ###################### + def actionControlSprinkler(self, action, dev): + ######################################## + # Required plugin sprinkler actions: These actions must be handled by the plugin. + ######################################## + ###### ZONE ON ###### + if action.sprinklerAction == indigo.kSprinklerAction.ZoneOn: + # Command hardware module (dev) to turn ON a specific zone here. + # ** IMPLEMENT ME ** + zone_name = "no zone" + if action.zoneIndex is not None: + zone_name = dev.zoneNames[action.zoneIndex - 1] + send_success = True # Set to False if it failed. + + if send_success: + # If success then log that the command was successfully sent. + self.logger.info(f"sent \"{dev.name} - {zone_name}\" on") + + # And then tell the Indigo Server to update the state. + dev.updateStateOnServer("activeZone", action.zoneIndex) + else: + # Else log failure but do NOT update state on Indigo Server. + self.logger.error(f"send \"{dev.name} - {zone_name}\" on failed") + + ###### ALL ZONES OFF ###### + elif action.sprinklerAction == indigo.kSprinklerAction.AllZonesOff: + # Command hardware module (dev) to turn OFF here: + # ** IMPLEMENT ME ** + send_success = True # Set to False if it failed. + + if send_success: + # If success then log that the command was successfully sent. + self.logger.info(f"sent \"{dev.name}\" all zones off") + + # And then tell the Indigo Server to update the state. + dev.updateStateOnServer("activeZone", 0) + else: + # Else log failure but do NOT update state on Indigo Server. + self.logger.error(f"send \"{dev.name}\" all zones off failed") + + ######################################## + # Optional plugin sprinkler actions: These actions are *only* dispatched to the plugin + # if the device property "OverrideScheduleActions" is set to True. The default behavior + # (False) is for the Indigo Server to handle these higher level commands and scheduling + # automatically, which will dispatch the required lower-level indigo.kSprinklerAction.ZoneOn + # and indigo.kSprinklerAction.AllZonesOff actions above at the appropriate times. + # + # Note if a plugin defines the higher level actions below then it must handle all zone + # scheduling, pausing, resuming, and next/previous skipping. Given the complexity in + # handling the individual zone scheduling, pausing, resuming, and next/previous zones + # it is recommended to use the default value of OverrideScheduleActions False, so that + # IndigoServer can handle this higher level scheduling and complexity. + ######################################## + ###### RUN SCHEDULE OF ZONE DURATIONS ###### + elif action.sprinklerAction == indigo.kSprinklerAction.RunNewSchedule: + # Plugin should handle its own scheduling here and tell the device + # to turn on the first zone in the schedule, _or_ (if supported by + # the device) send the entire schedule to the device. + # ** IMPLEMENT ME ** + self.logger.info(f"scheduled \"{dev.name}\" zone durations: {action.zoneDurations}") + + # The ScheduledZoneDurations property is used by Indigo to show the + # currently running schedule (in the client, Web, and Indigo Touch UI), + # so set based on the durations requested in the action. + props = dev.pluginProps + # Plugin should use dev.zoneEnableList, dev.zoneMaxDurations and + # dev.zoneCount to make sure action.zoneDurations are within the + # range and bounds specified. + zone_list_str = ', '.join(str(dur) for dur in action.zoneDurations) + props["PreviousZoneDurations"] = zone_list_str + props["ScheduledZoneDurations"] = zone_list_str + dev.replacePluginPropsOnServer(props) + + # Plugin will then need to update the activeZone state appropriately. + # As an example here we'll just turn on zone 1 (plugin should really + # inspect action.zoneDurations for the first non-zero item). + dev.updateStateOnServer("activeZone", 1) + + ###### RUN LAST USED SCHEDULE ###### + elif action.sprinklerAction == indigo.kSprinklerAction.RunPreviousSchedule: + # Plugin should re-run the last schedule that was used here and tell + # the device to turn on the first zone in the schedule, _or_ (if + # supported by the device) send the device a run last schedule command. + # ** IMPLEMENT ME ** + + # Plugin will then need to update the ScheduledZoneDurations property + # and the activeZone state appropriately. + props = dev.pluginProps + if "PreviousZoneDurations" in props: + self.logger.info(f"running last used scheduled for \"{dev.name}\": {props['PreviousZoneDurations']}") + + props["ScheduledZoneDurations"] = props["PreviousZoneDurations"] + dev.replacePluginPropsOnServer(props) + dev.updateStateOnServer("activeZone", 1) + + ###### PAUSE SCHEDULE ###### + elif action.sprinklerAction == indigo.kSprinklerAction.PauseSchedule: + # Plugin should pause its schedule here and tell the device to + # turn off the active zone, _or_ (if supported by the device) + # send the device a pause command. + # ** IMPLEMENT ME ** + self.logger.info(f"pausing \"{dev.name}\" schedule") + + # Plugin will then need to update the ScheduledZoneDurations property + # and the activeZone state appropriately. + props = dev.pluginProps + props["PauseScheduleZoneIndex"] = dev.activeZone + props["PauseScheduleRemainingZoneDuration"] = 15 # plugin needs to calc and store remaining duration for active zone + props["ScheduledZoneDurations"] = '' # empty so UI doesn't show active schedule + dev.replacePluginPropsOnServer(props) + dev.updateStateOnServer("activeZone", 0) + + ###### RESUME SCHEDULE ###### + elif action.sprinklerAction == indigo.kSprinklerAction.ResumeSchedule: + # Plugin should resume the active schedule here and tell the device + # to turn on the paused zone, _or_ (if supported by the device) send + # the device a resume command. + # ** IMPLEMENT ME ** + + # Plugin will then need to update the ScheduledZoneDurations property + # and the activeZone state appropriately. + props = dev.pluginProps + if "PreviousZoneDurations" in props and "PauseScheduleZoneIndex" in props and "PauseScheduleRemainingZoneDuration" in props: + self.logger.info(f"resuming \"{dev.name}\" schedule zone durations: {props['PreviousZoneDurations']}") + + resume_zone = int(props["PauseScheduleZoneIndex"]) + resume_duration = int(props["PauseScheduleRemainingZoneDuration"]) + + props["PauseScheduleZoneIndex"] = 0 + props["PauseScheduleRemainingZoneDuration"] = 0 + props["ScheduledZoneDurations"] = props["PreviousZoneDurations"] + dev.replacePluginPropsOnServer(props) + dev.updateStateOnServer("activeZone", resume_zone) + + # Plugin should now use resume_duration for its own internal timer of how long + # the activeZone (resume_zone) should remain ON before advancing to next zone. + + ###### STOP SCHEDULE ###### + elif action.sprinklerAction == indigo.kSprinklerAction.StopSchedule: + # Plugin should stop the active schedule here and tell the device + # to turn off all zones, _or_ (if supported by the device) send + # the device both a stop schedule and all zones off command. + # ** IMPLEMENT ME ** + self.logger.info(f"stopping \"{dev.name}\" schedule") + + # Clear out ScheduledZoneDurations so the UI doesn't show an active schedule anymore. + props = dev.pluginProps + props["PauseScheduleZoneIndex"] = 0 + props["PauseScheduleRemainingZoneDuration"] = 0 + props["ScheduledZoneDurations"] = '' + dev.replacePluginPropsOnServer(props) + + # Plugin will then need to update the activeZone state appropriately. + dev.updateStateOnServer("activeZone", 0) + + ###### JUMP TO PREVIOUS ZONE ###### + elif action.sprinklerAction == indigo.kSprinklerAction.PreviousZone: + # Plugin should jump to the previous zone in the current + # schedule here and tell the device to turn that zone on, _or_ + # (if supported by the device) send the device a previous zone + # command. + # ** IMPLEMENT ME ** + self.logger.info(f"skipping \"{dev.name}\" to previous zone") + + # Plugin will then need to update the activeZone state appropriately. + # dev.updateStateOnServer("activeZone", current zone - 1) + + ###### SKIP TO NEXT ZONE ###### + elif action.sprinklerAction == indigo.kSprinklerAction.NextZone: + # Plugin should jump to the next zone in the current schedule + # here and tell the device to turn that zone on, _or_ (if + # supported by the device) send the device a next zone command. + # ** IMPLEMENT ME ** + self.logger.info(f"advancing \"{dev.name}\" to next zone") + + # Plugin will then need to update the activeZone state appropriately. + # dev.updateStateOnServer("activeZone", current zone + 1) + + ######################################## + # General Action callback + ###################### + def actionControlUniversal(self, action, dev): + ###### BEEP ###### + if action.deviceAction == indigo.kUniversalAction.Beep: + # Beep the hardware module (dev) here: + # ** IMPLEMENT ME ** + self.logger.info(f"sent \"{dev.name}\" beep request") + + ###### ENERGY UPDATE ###### + elif action.deviceAction == indigo.kUniversalAction.EnergyUpdate: + # Request hardware module (dev) for its most recent meter data here: + # ** IMPLEMENT ME ** + self.logger.info(f"sent \"{dev.name}\" energy update request") + + ###### ENERGY RESET ###### + elif action.deviceAction == indigo.kUniversalAction.EnergyReset: + # Request that the hardware module (dev) reset its accumulative energy usage data here: + # ** IMPLEMENT ME ** + self.logger.info(f"sent \"{dev.name}\" energy reset request") + + ###### STATUS REQUEST ###### + elif action.deviceAction == indigo.kUniversalAction.RequestStatus: + # Query hardware module (dev) for its current status here: + # ** IMPLEMENT ME ** + self.logger.info(f"sent \"{dev.name}\" status request") + + ######################################## + # Custom Plugin Action callbacks (defined in Actions.xml) + ###################### + def set_backlight_brightness(self, plugin_action, dev): + try: + new_brightness = int(plugin_action.props.get("brightness", 100)) + except ValueError: + # The int() cast above might fail if the user didn't enter a number: + self.logger.error(f"set backlight brightness action to device \"{dev.name}\" -- invalid brightness value") + return + # Command hardware module (dev) to set backlight brightness here: + # FIXME: add implementation here + send_success = True # Set to False if it failed. + if send_success: + # If success then log that the command was successfully sent. + self.logger.info(f"sent \"{dev.name}\" set backlight brightness to {new_brightness}") + # And then tell the Indigo Server to update the state: + dev.updateStateOnServer("backlightBrightness", new_brightness) + else: + # Else log failure but do NOT update state on Indigo Server. + self.logger.error(f"send \"{dev.name}\" set backlight brightness to {new_brightness} failed") diff --git a/Example Device - Thermostat.indigoPlugin/Contents/Info.plist b/Example Device - Thermostat.indigoPlugin/Contents/Info.plist index e0d0300..358ebb1 100644 --- a/Example Device - Thermostat.indigoPlugin/Contents/Info.plist +++ b/Example Device - Thermostat.indigoPlugin/Contents/Info.plist @@ -2,24 +2,24 @@ - PluginVersion - 2021.1.0 - ServerApiVersion - 2.5 - IwsApiVersion - 1.0.0 - CFBundleDisplayName - Example Device - Thermostat - CFBundleIdentifier - com.perceptiveautomation.indigoplugin.example-device-thermo1 - CFBundleVersion - 1.0.0 - CFBundleURLTypes - - - CFBundleURLName - http://wiki.indigodomo.com/doku.php?id=plugins:example_dev_thermo_1 - - + PluginVersion + 2022.1.0 + ServerApiVersion + 3.0 + IwsApiVersion + 1.0.0 + CFBundleDisplayName + Example Device - Thermostat + CFBundleIdentifier + com.perceptiveautomation.indigoplugin.example-device-thermo1 + CFBundleVersion + 1.0.0 + CFBundleURLTypes + + + CFBundleURLName + https://wiki.indigodomo.com/doku.php?id=plugins:example_dev_thermo_1 + + diff --git a/Example Device - Thermostat.indigoPlugin/Contents/Server Plugin/Actions.xml b/Example Device - Thermostat.indigoPlugin/Contents/Server Plugin/Actions.xml index 640119e..d0520a6 100644 --- a/Example Device - Thermostat.indigoPlugin/Contents/Server Plugin/Actions.xml +++ b/Example Device - Thermostat.indigoPlugin/Contents/Server Plugin/Actions.xml @@ -1,24 +1,24 @@ - - Set Backlight Brightness - setBacklightBrightness - - - - - - - + + Set Backlight Brightness + set_backlight_brightness + + + + + + + diff --git a/Example Device - Thermostat.indigoPlugin/Contents/Server Plugin/Devices.xml b/Example Device - Thermostat.indigoPlugin/Contents/Server Plugin/Devices.xml index 404adfd..46b8d8e 100644 --- a/Example Device - Thermostat.indigoPlugin/Contents/Server Plugin/Devices.xml +++ b/Example Device - Thermostat.indigoPlugin/Contents/Server Plugin/Devices.xml @@ -1,141 +1,141 @@ - - - Example Thermostat Module - - - - - - + + Example Thermostat Module + + + + + + - - - - - - - - - - - - - - - - - - - - - - Show heat setpoint controls in UI - - - - Show cool setpoint controls in UI - - - - Show thermostat mode controls (heat/cool/auto) in UI - - - - Show fan mode controls (auto/always on) in UI - - - - Show compressor/furnace states in UI - - - - + + + + + + + + + + + + + + + + + + + + + + Show heat setpoint controls in UI + + + + Show cool setpoint controls in UI + + + + Show thermostat mode controls (heat/cool/auto) in UI + + + + Show fan mode controls (auto/always on) in UI + + + + Show compressor/furnace states in UI + + + + - - Integer - Backlight Brightness - Backlight Brightness - - - + The plugin can specify additional custom states and custom + actions (in Actions.xml) to modify custom states. As an example + here, we define a new custom state, backlightBrightness, which + is used to control the brightness of the backlit display of + the thermostat. + --> + + Integer + Backlight Brightness + Backlight Brightness + + + diff --git a/Example Device - Thermostat.indigoPlugin/Contents/Server Plugin/MenuItems.xml b/Example Device - Thermostat.indigoPlugin/Contents/Server Plugin/MenuItems.xml index d14a46e..3132c73 100644 --- a/Example Device - Thermostat.indigoPlugin/Contents/Server Plugin/MenuItems.xml +++ b/Example Device - Thermostat.indigoPlugin/Contents/Server Plugin/MenuItems.xml @@ -1,37 +1,37 @@ - - Set All to 1 Temperature Sensor - changeTempSensorCountTo1 - - - Set All to 2 Temperature Sensors - changeTempSensorCountTo2 - - - Set All to 3 Temperature Sensors - changeTempSensorCountTo3 - - - Set All to No Humidity Sensors - changeHumiditySensorCountTo0 - - - Set All to 1 Humidity Sensors - changeHumiditySensorCountTo1 - - - Set All to 2 Humidity Sensors - changeHumiditySensorCountTo2 - - - Set All to 3 Humidity Sensors - changeHumiditySensorCountTo3 - + + Set All to 1 Temperature Sensor + change_temp_sensor_count_to_1 + + + Set All to 2 Temperature Sensors + change_temp_sensor_count_to_2 + + + Set All to 3 Temperature Sensors + change_temp_sensor_count_to_3 + + + Set All to No Humidity Sensors + change_humidity_sensor_count_to_0 + + + Set All to 1 Humidity Sensors + change_humidity_sensor_count_to_1 + + + Set All to 2 Humidity Sensors + change_humidity_sensor_count_to_2 + + + Set All to 3 Humidity Sensors + change_humidity_sensor_count_to_3 + diff --git a/Example Device - Thermostat.indigoPlugin/Contents/Server Plugin/plugin.py b/Example Device - Thermostat.indigoPlugin/Contents/Server Plugin/plugin.py index 616e062..b3cce89 100644 --- a/Example Device - Thermostat.indigoPlugin/Contents/Server Plugin/plugin.py +++ b/Example Device - Thermostat.indigoPlugin/Contents/Server Plugin/plugin.py @@ -1,8 +1,8 @@ #! /usr/bin/env python # -*- coding: utf-8 -*- #################### -# Copyright (c) 2016, Perceptive Automation, LLC. All rights reserved. -# http://www.indigodomo.com +# Copyright (c) 2022, Perceptive Automation, LLC. All rights reserved. +# https://www.indigodomo.com import indigo @@ -14,380 +14,380 @@ # our global name space by the host process. ################################################################################ -kHvacModeEnumToStrMap = { - indigo.kHvacMode.Cool : u"cool", - indigo.kHvacMode.Heat : u"heat", - indigo.kHvacMode.HeatCool : u"auto", - indigo.kHvacMode.Off : u"off", - indigo.kHvacMode.ProgramHeat : u"program heat", - indigo.kHvacMode.ProgramCool : u"program cool", - indigo.kHvacMode.ProgramHeatCool : u"program auto" +HVAC_MODE_ENUM_TO_STR_MAP = { + indigo.kHvacMode.Cool : "cool", + indigo.kHvacMode.Heat : "heat", + indigo.kHvacMode.HeatCool : "auto", + indigo.kHvacMode.Off : "off", + indigo.kHvacMode.ProgramHeat : "program heat", + indigo.kHvacMode.ProgramCool : "program cool", + indigo.kHvacMode.ProgramHeatCool : "program auto" } -kFanModeEnumToStrMap = { - indigo.kFanMode.AlwaysOn : u"always on", - indigo.kFanMode.Auto : u"auto" +FAN_MODE_ENUM_TO_STR_MAP = { + indigo.kFanMode.AlwaysOn : "always on", + indigo.kFanMode.Auto : "auto" } -def _lookupActionStrFromHvacMode(hvacMode): - return kHvacModeEnumToStrMap.get(hvacMode, u"unknown") +def _lookup_action_str_from_hvac_mode(hvac_mode): + return HVAC_MODE_ENUM_TO_STR_MAP.get(hvac_mode, "unknown") -def _lookupActionStrFromFanMode(fanMode): - return kFanModeEnumToStrMap.get(fanMode, u"unknown") +def _lookup_action_str_from_fan_mode(fan_mode): + return FAN_MODE_ENUM_TO_STR_MAP.get(fan_mode, "unknown") ################################################################################ class Plugin(indigo.PluginBase): - ######################################## - def __init__(self, pluginId, pluginDisplayName, pluginVersion, pluginPrefs): - super(Plugin, self).__init__(pluginId, pluginDisplayName, pluginVersion, pluginPrefs) - self.debug = False - self.simulateTempChanges = True # Every few seconds update to random temperature values - self.simulateHumidityChanges = True # Every few seconds update to random humidity values - self.refreshDelay = 2 # Simulate new temperature values every 2 seconds - - ######################################## - # Internal utility methods. Some of these are useful to provide - # a higher-level abstraction for accessing/changing thermostat - # properties or states. - ###################### - def _changeTempSensorCount(self, dev, count): - newProps = dev.pluginProps - newProps["NumTemperatureInputs"] = count - dev.replacePluginPropsOnServer(newProps) - - def _changeHumiditySensorCount(self, dev, count): - newProps = dev.pluginProps - newProps["NumHumidityInputs"] = count - dev.replacePluginPropsOnServer(newProps) - - def _changeAllTempSensorCounts(self, count): - for dev in indigo.devices.iter("self"): - self._changeTempSensorCount(dev, count) - - def _changeAllHumiditySensorCounts(self, count): - for dev in indigo.devices.iter("self"): - self._changeHumiditySensorCount(dev, count) - - ###################### - def _changeTempSensorValue(self, dev, index, value, keyValueList): - # Update the temperature value at index. If index is greater than the "NumTemperatureInputs" - # an error will be displayed in the Event Log "temperature index out-of-range" - stateKey = u"temperatureInput" + str(index) - keyValueList.append({'key':stateKey, 'value':value, 'uiValue':"%d °F" % (value)}) - self.debugLog(u"\"%s\" updating %s %d" % (dev.name, stateKey, value)) - - def _changeHumiditySensorValue(self, dev, index, value, keyValueList): - # Update the humidity value at index. If index is greater than the "NumHumidityInputs" - # an error will be displayed in the Event Log "humidity index out-of-range" - stateKey = u"humidityInput" + str(index) - keyValueList.append({'key':stateKey, 'value':value, 'uiValue':"%d%%" % (value)}) - self.debugLog(u"\"%s\" updating %s %d" % (dev.name, stateKey, value)) - - ###################### - # Poll all of the states from the thermostat and pass new values to - # Indigo Server. - def _refreshStatesFromHardware(self, dev, logRefresh, commJustStarted): - # As an example here we update the temperature and humidity - # sensor states to random values. - keyValueList = [] - if self.simulateTempChanges: - # Simulate changing temperature values coming in from the - # hardware by updating all temp values randomly: - numTemps = dev.temperatureSensorCount - for index in range(1, numTemps + 1): - exampleTemp = random.randint(62, 88) - self._changeTempSensorValue(dev, index, exampleTemp, keyValueList) - if logRefresh: - indigo.server.log(u"received \"%s\" temperature%d update to %.1f°" % (dev.name, index, exampleTemp)) - if dev.pluginProps.get("ShowCoolHeatEquipmentStateUI", False) and "hvacOperationMode" in dev.states and "setpointCool" in dev.states and "setpointHeat" in dev.states: - if dev.states["hvacOperationMode"] in [indigo.kHvacMode.Cool, indigo.kHvacMode.HeatCool, indigo.kHvacMode.ProgramCool, indigo.kHvacMode.ProgramHeatCool]: - keyValueList.append({'key':'hvacCoolerIsOn', 'value':exampleTemp > dev.states["setpointCool"]}) - if dev.states["hvacOperationMode"] in [indigo.kHvacMode.Heat, indigo.kHvacMode.HeatCool, indigo.kHvacMode.ProgramHeat, indigo.kHvacMode.ProgramHeatCool]: - keyValueList.append({'key':'hvacHeaterIsOn', 'value':exampleTemp < dev.states["setpointHeat"]}) - if self.simulateHumidityChanges: - # Simulate changing humidity values coming in from the - # hardware by updating all humidity values randomly: - numSensors = dev.humiditySensorCount - for index in range(1, numSensors + 1): - exampleHumidity = random.randint(15, 90) - self._changeHumiditySensorValue(dev, index, exampleHumidity, keyValueList) - if logRefresh: - indigo.server.log(u"received \"%s\" humidity%d update to %.0f%%" % (dev.name, index, exampleHumidity)) - - # Other states that should also be updated: - # ** IMPLEMENT ME ** - # keyValueList.append({'key':'setpointHeat', 'value':floating number here}) - # keyValueList.append({'key':'setpointCool', 'value':floating number here}) - # keyValueList.append({'key':'hvacOperationMode', 'value':some indigo.kHvacMode.* value here}) - # keyValueList.append({'key':'hvacFanMode', 'value':some indigo.kFanMode.* value here}) - # keyValueList.append({'key':'hvacCoolerIsOn', 'value':True or False here}) - # keyValueList.append({'key':'hvacHeaterIsOn', 'value':True or False here}) - # keyValueList.append({'key':'hvacFanIsOn', 'value':True or False here}) - if commJustStarted: - # As an example, we force these thermostat states to specific values. - if "setpointHeat" in dev.states: - keyValueList.append({'key':'setpointHeat', 'value':66.5, 'uiValue':"66.5 °F"}) - if "setpointCool" in dev.states: - keyValueList.append({'key':'setpointCool', 'value':77.5, 'uiValue':"77.5 °F"}) - if "hvacOperationMode" in dev.states: - keyValueList.append({'key':'hvacOperationMode', 'value':indigo.kHvacMode.HeatCool}) - if "hvacFanMode" in dev.states: - keyValueList.append({'key':'hvacFanMode', 'value':indigo.kFanMode.Auto}) - keyValueList.append({'key':'backlightBrightness', 'value':85, 'uiValue':"85%"}) - - if len(keyValueList) > 0: - dev.updateStatesOnServer(keyValueList) - - if logRefresh: - if "setpointHeat" in dev.states: - indigo.server.log(u"received \"%s\" cool setpoint update to %.1f°" % (dev.name, dev.states["setpointHeat"])) - if "setpointCool" in dev.states: - indigo.server.log(u"received \"%s\" heat setpoint update to %.1f°" % (dev.name, dev.states["setpointCool"])) - if "hvacOperationMode" in dev.states: - indigo.server.log(u"received \"%s\" main mode update to %s" % (dev.name, _lookupActionStrFromHvacMode(dev.states["hvacOperationMode"]))) - if "hvacFanMode" in dev.states: - indigo.server.log(u"received \"%s\" fan mode update to %s" % (dev.name, _lookupActionStrFromFanMode(dev.states["hvacFanMode"]))) - indigo.server.log(u"received \"%s\" backlight brightness update to %d%%" % (dev.name, dev.states["backlightBrightness"])) - - ###################### - # Process action request from Indigo Server to change main thermostat's main mode. - def _handleChangeHvacModeAction(self, dev, newHvacMode): - # Command hardware module (dev) to change the thermostat mode here: - # ** IMPLEMENT ME ** - sendSuccess = True # Set to False if it failed. - - actionStr = _lookupActionStrFromHvacMode(newHvacMode) - if sendSuccess: - # If success then log that the command was successfully sent. - indigo.server.log(u"sent \"%s\" mode change to %s" % (dev.name, actionStr)) - - # And then tell the Indigo Server to update the state. - if "hvacOperationMode" in dev.states: - dev.updateStateOnServer("hvacOperationMode", newHvacMode) - else: - # Else log failure but do NOT update state on Indigo Server. - indigo.server.log(u"send \"%s\" mode change to %s failed" % (dev.name, actionStr), isError=True) - - ###################### - # Process action request from Indigo Server to change thermostat's fan mode. - def _handleChangeFanModeAction(self, dev, newFanMode): - # Command hardware module (dev) to change the fan mode here: - # ** IMPLEMENT ME ** - sendSuccess = True # Set to False if it failed. - - actionStr = _lookupActionStrFromFanMode(newFanMode) - if sendSuccess: - # If success then log that the command was successfully sent. - indigo.server.log(u"sent \"%s\" fan mode change to %s" % (dev.name, actionStr)) - - # And then tell the Indigo Server to update the state. - if "hvacFanMode" in dev.states: - dev.updateStateOnServer("hvacFanMode", newFanMode) - else: - # Else log failure but do NOT update state on Indigo Server. - indigo.server.log(u"send \"%s\" fan mode change to %s failed" % (dev.name, actionStr), isError=True) - - ###################### - # Process action request from Indigo Server to change a cool/heat setpoint. - def _handleChangeSetpointAction(self, dev, newSetpoint, logActionName, stateKey): - if newSetpoint < 40.0: - newSetpoint = 40.0 # Arbitrary -- set to whatever hardware minimum setpoint value is. - elif newSetpoint > 95.0: - newSetpoint = 95.0 # Arbitrary -- set to whatever hardware maximum setpoint value is. - - sendSuccess = False - - if stateKey == u"setpointCool": - # Command hardware module (dev) to change the cool setpoint to newSetpoint here: - # ** IMPLEMENT ME ** - sendSuccess = True # Set to False if it failed. - elif stateKey == u"setpointHeat": - # Command hardware module (dev) to change the heat setpoint to newSetpoint here: - # ** IMPLEMENT ME ** - sendSuccess = True # Set to False if it failed. - - if sendSuccess: - # If success then log that the command was successfully sent. - indigo.server.log(u"sent \"%s\" %s to %.1f°" % (dev.name, logActionName, newSetpoint)) - - # And then tell the Indigo Server to update the state. - if stateKey in dev.states: - dev.updateStateOnServer(stateKey, newSetpoint, uiValue="%.1f °F" % (newSetpoint)) - else: - # Else log failure but do NOT update state on Indigo Server. - indigo.server.log(u"send \"%s\" %s to %.1f° failed" % (dev.name, logActionName, newSetpoint), isError=True) - - ######################################## - def startup(self): - self.debugLog(u"startup called") - - def shutdown(self): - self.debugLog(u"shutdown called") - - ######################################## - def runConcurrentThread(self): - try: - while True: - for dev in indigo.devices.iter("self"): - if not dev.enabled or not dev.configured: - continue - - # Plugins that need to poll out the status from the thermostat - # could do so here, then broadcast back the new values to the - # Indigo Server. - self._refreshStatesFromHardware(dev, False, False) - - self.sleep(self.refreshDelay) - except self.StopThread: - pass # Optionally catch the StopThread exception and do any needed cleanup. - - ######################################## - def validateDeviceConfigUi(self, valuesDict, typeId, devId): - return (True, valuesDict) - - ######################################## - def deviceStartComm(self, dev): - # Called when communication with the hardware should be established. - # Here would be a good place to poll out the current states from the - # thermostat. If periodic polling of the thermostat is needed (that - # is, it doesn't broadcast changes back to the plugin somehow), then - # consider adding that to runConcurrentThread() above. - self._refreshStatesFromHardware(dev, True, True) - - def deviceStopComm(self, dev): - # Called when communication with the hardware should be shutdown. - pass - - ######################################## - # Thermostat Action callback - ###################### - # Main thermostat action bottleneck called by Indigo Server. - def actionControlThermostat(self, action, dev): - ###### SET HVAC MODE ###### - if action.thermostatAction == indigo.kThermostatAction.SetHvacMode: - self._handleChangeHvacModeAction(dev, action.actionMode) - - ###### SET FAN MODE ###### - elif action.thermostatAction == indigo.kThermostatAction.SetFanMode: - self._handleChangeFanModeAction(dev, action.actionMode) - - ###### SET COOL SETPOINT ###### - elif action.thermostatAction == indigo.kThermostatAction.SetCoolSetpoint: - newSetpoint = action.actionValue - self._handleChangeSetpointAction(dev, newSetpoint, u"change cool setpoint", u"setpointCool") - - ###### SET HEAT SETPOINT ###### - elif action.thermostatAction == indigo.kThermostatAction.SetHeatSetpoint: - newSetpoint = action.actionValue - self._handleChangeSetpointAction(dev, newSetpoint, u"change heat setpoint", u"setpointHeat") - - ###### DECREASE/INCREASE COOL SETPOINT ###### - elif action.thermostatAction == indigo.kThermostatAction.DecreaseCoolSetpoint: - newSetpoint = dev.coolSetpoint - action.actionValue - self._handleChangeSetpointAction(dev, newSetpoint, u"decrease cool setpoint", u"setpointCool") - - elif action.thermostatAction == indigo.kThermostatAction.IncreaseCoolSetpoint: - newSetpoint = dev.coolSetpoint + action.actionValue - self._handleChangeSetpointAction(dev, newSetpoint, u"increase cool setpoint", u"setpointCool") - - ###### DECREASE/INCREASE HEAT SETPOINT ###### - elif action.thermostatAction == indigo.kThermostatAction.DecreaseHeatSetpoint: - newSetpoint = dev.heatSetpoint - action.actionValue - self._handleChangeSetpointAction(dev, newSetpoint, u"decrease heat setpoint", u"setpointHeat") - - elif action.thermostatAction == indigo.kThermostatAction.IncreaseHeatSetpoint: - newSetpoint = dev.heatSetpoint + action.actionValue - self._handleChangeSetpointAction(dev, newSetpoint, u"increase heat setpoint", u"setpointHeat") - - ###### REQUEST STATE UPDATES ###### - elif action.thermostatAction in [indigo.kThermostatAction.RequestStatusAll, indigo.kThermostatAction.RequestMode, - indigo.kThermostatAction.RequestEquipmentState, indigo.kThermostatAction.RequestTemperatures, indigo.kThermostatAction.RequestHumidities, - indigo.kThermostatAction.RequestDeadbands, indigo.kThermostatAction.RequestSetpoints]: - self._refreshStatesFromHardware(dev, True, False) - - ######################################## - # General Action callback - ###################### - def actionControlUniversal(self, action, dev): - ###### BEEP ###### - if action.deviceAction == indigo.kUniversalAction.Beep: - # Beep the hardware module (dev) here: - # ** IMPLEMENT ME ** - indigo.server.log(u"sent \"%s\" %s" % (dev.name, "beep request")) - - ###### ENERGY UPDATE ###### - elif action.deviceAction == indigo.kUniversalAction.EnergyUpdate: - # Request hardware module (dev) for its most recent meter data here: - # ** IMPLEMENT ME ** - indigo.server.log(u"sent \"%s\" %s" % (dev.name, "energy update request")) - - ###### ENERGY RESET ###### - elif action.deviceAction == indigo.kUniversalAction.EnergyReset: - # Request that the hardware module (dev) reset its accumulative energy usage data here: - # ** IMPLEMENT ME ** - indigo.server.log(u"sent \"%s\" %s" % (dev.name, "energy reset request")) - - ###### STATUS REQUEST ###### - elif action.deviceAction == indigo.kUniversalAction.RequestStatus: - # Query hardware module (dev) for its current status here. This differs from the - # indigo.kThermostatAction.RequestStatusAll action - for instance, if your thermo - # is battery powered you might only want to update it only when the user uses - # this status request (and not from the RequestStatusAll). This action would - # get all possible information from the thermostat and the other call - # would only get thermostat-specific information: - # ** GET BATTERY INFO ** - # and call the common function to update the thermo-specific data - self._refreshStatesFromHardware(dev, True, False) - indigo.server.log(u"sent \"%s\" %s" % (dev.name, "status request")) - - ######################################## - # Custom Plugin Action callbacks (defined in Actions.xml) - ###################### - def setBacklightBrightness(self, pluginAction, dev): - try: - newBrightness = int(pluginAction.props.get(u"brightness", 100)) - except ValueError: - # The int() cast above might fail if the user didn't enter a number: - indigo.server.log(u"set backlight brightness action to device \"%s\" -- invalid brightness value" % (dev.name,), isError=True) - return - - # Command hardware module (dev) to set backlight brightness here: - # ** IMPLEMENT ME ** - sendSuccess = True # Set to False if it failed. - - if sendSuccess: - # If success then log that the command was successfully sent. - indigo.server.log(u"sent \"%s\" %s to %d" % (dev.name, "set backlight brightness", newBrightness)) - - # And then tell the Indigo Server to update the state: - dev.updateStateOnServer("backlightBrightness", newBrightness, uiValue="%d%%" % (newBrightness)) - else: - # Else log failure but do NOT update state on Indigo Server. - indigo.server.log(u"send \"%s\" %s to %d failed" % (dev.name, "set backlight brightness", newBrightness), isError=True) - - ######################################## - # Actions defined in MenuItems.xml. In this case we just use these menu actions to - # simulate different thermostat configurations (how many temperature and humidity - # sensors they have). - #################### - def changeTempSensorCountTo1(self): - self._changeAllTempSensorCounts(1) - - def changeTempSensorCountTo2(self): - self._changeAllTempSensorCounts(2) - - def changeTempSensorCountTo3(self): - self._changeAllTempSensorCounts(3) - - def changeHumiditySensorCountTo0(self): - self._changeAllHumiditySensorCounts(0) - - def changeHumiditySensorCountTo1(self): - self._changeAllHumiditySensorCounts(1) - - def changeHumiditySensorCountTo2(self): - self._changeAllHumiditySensorCounts(2) - - def changeHumiditySensorCountTo3(self): - self._changeAllHumiditySensorCounts(3) + ######################################## + def __init__(self, plugin_id, plugin_display_name, plugin_version, plugin_prefs): + super().__init__(plugin_id, plugin_display_name, plugin_version, plugin_prefs) + self.debug = False + self.simulate_temp_changes = True # Every few seconds update to random temperature values + self.simulate_humidity_changes = True # Every few seconds update to random humidity values + self.refresh_delay = 2 # Simulate new temperature values every 2 seconds + + ######################################## + # Internal utility methods. Some of these are useful to provide + # a higher-level abstraction for accessing/changing thermostat + # properties or states. + ###################### + def _change_temp_sensor_count(self, dev, count): + new_props = dev.pluginProps + new_props["NumTemperatureInputs"] = count + dev.replacePluginPropsOnServer(new_props) + + def _change_humidity_sensor_count(self, dev, count): + new_props = dev.pluginProps + new_props["NumHumidityInputs"] = count + dev.replacePluginPropsOnServer(new_props) + + def _change_all_temp_sensor_counts(self, count): + for dev in indigo.devices.iter("self"): + self._change_temp_sensor_count(dev, count) + + def _change_all_humidity_sensor_counts(self, count): + for dev in indigo.devices.iter("self"): + self._change_humidity_sensor_count(dev, count) + + ###################### + def _change_temp_sensor_value(self, dev, index, value, key_val_list): + # Update the temperature value at index. If index is greater than the "NumTemperatureInputs" + # an error will be displayed in the Event Log "temperature index out-of-range" + state_key = "temperatureInput" + str(index) + key_val_list.append({'key':state_key, 'value':value, 'uiValue':f"{value} °F"}) + self.logger.debug(f"\"{dev.name}\" updating {state_key} {value}") + + def _change_humidity_sensor_value(self, dev, index, value, key_val_list): + # Update the humidity value at index. If index is greater than the "NumHumidityInputs" + # an error will be displayed in the Event Log "humidity index out-of-range" + state_key = "humidityInput" + str(index) + key_val_list.append({'key':state_key, 'value':value, 'uiValue':f"{value}%"}) + self.logger.debug(f"\"{dev.name}\" updating {state_key} {value}") + + ###################### + # Poll all of the states from the thermostat and pass new values to + # Indigo Server. + def _refresh_states_from_hardware(self, dev, log_refresh, comm_just_started): + # As an example here we update the temperature and humidity + # sensor states to random values. + key_val_list = [] + if self.simulate_temp_changes: + # Simulate changing temperature values coming in from the + # hardware by updating all temp values randomly: + num_temps = dev.temperatureSensorCount + for index in range(1, num_temps + 1): + example_temp = random.randint(62, 88) + self._change_temp_sensor_value(dev, index, example_temp, key_val_list) + if log_refresh: + self.logger.info(f"received \"{dev.name}\" temperature{index} update to {example_temp:.1f}°") + if dev.pluginProps.get("ShowCoolHeatEquipmentStateUI", False) and "hvacOperationMode" in dev.states and "setpointCool" in dev.states and "setpointHeat" in dev.states: + if dev.states["hvacOperationMode"] in [indigo.kHvacMode.Cool, indigo.kHvacMode.HeatCool, indigo.kHvacMode.ProgramCool, indigo.kHvacMode.ProgramHeatCool]: + key_val_list.append({'key':'hvacCoolerIsOn', 'value':example_temp > dev.states["setpointCool"]}) + if dev.states["hvacOperationMode"] in [indigo.kHvacMode.Heat, indigo.kHvacMode.HeatCool, indigo.kHvacMode.ProgramHeat, indigo.kHvacMode.ProgramHeatCool]: + key_val_list.append({'key':'hvacHeaterIsOn', 'value':example_temp < dev.states["setpointHeat"]}) + if self.simulate_humidity_changes: + # Simulate changing humidity values coming in from the + # hardware by updating all humidity values randomly: + num_sensors = dev.humiditySensorCount + for index in range(1, num_sensors + 1): + example_humidity = random.randint(15, 90) + self._change_humidity_sensor_value(dev, index, example_humidity, key_val_list) + if log_refresh: + self.logger.info(f"received \"{dev.name}\" humidity{index} update to {example_humidity:.0f}%") + + # Other states that should also be updated: + # ** IMPLEMENT ME ** + # key_val_list.append({'key':'setpointHeat', 'value':floating number here}) + # key_val_list.append({'key':'setpointCool', 'value':floating number here}) + # key_val_list.append({'key':'hvacOperationMode', 'value':some indigo.kHvacMode.* value here}) + # key_val_list.append({'key':'hvacFanMode', 'value':some indigo.kFanMode.* value here}) + # key_val_list.append({'key':'hvacCoolerIsOn', 'value':True or False here}) + # key_val_list.append({'key':'hvacHeaterIsOn', 'value':True or False here}) + # key_val_list.append({'key':'hvacFanIsOn', 'value':True or False here}) + if comm_just_started: + # As an example, we force these thermostat states to specific values. + if "setpointHeat" in dev.states: + key_val_list.append({'key':'setpointHeat', 'value':66.5, 'uiValue':"66.5 °F"}) + if "setpointCool" in dev.states: + key_val_list.append({'key':'setpointCool', 'value':77.5, 'uiValue':"77.5 °F"}) + if "hvacOperationMode" in dev.states: + key_val_list.append({'key':'hvacOperationMode', 'value':indigo.kHvacMode.HeatCool}) + if "hvacFanMode" in dev.states: + key_val_list.append({'key':'hvacFanMode', 'value':indigo.kFanMode.Auto}) + key_val_list.append({'key':'backlightBrightness', 'value':85, 'uiValue':"85%"}) + + if len(key_val_list) > 0: + dev.updateStatesOnServer(key_val_list) + + if log_refresh: + if "setpointHeat" in dev.states: + self.logger.info(f"received \"{dev.name}\" cool setpoint update to {dev.states['setpointHeat']:.1f}°") + if "setpointCool" in dev.states: + self.logger.info(f"received \"{dev.name}\" heat setpoint update to {dev.states['setpointCool']:.1f}°") + if "hvacOperationMode" in dev.states: + action_str = _lookup_action_str_from_hvac_mode(dev.states["hvacOperationMode"]) + self.logger.info(f"received \"{dev.name}\" main mode update to {action_str}") + if "hvacFanMode" in dev.states: + action_str = _lookup_action_str_from_fan_mode(dev.states["hvacFanMode"]) + self.logger.info(f"received \"{dev.name}\" fan mode update to {action_str}") + self.logger.info(f"received \"{dev.name}\" backlight brightness update to {dev.states['backlightBrightness']}%") + + ###################### + # Process action request from Indigo Server to change main thermostat's main mode. + def _handle_change_hvac_mode_action(self, dev, new_hvac_mode): + # Command hardware module (dev) to change the thermostat mode here: + # ** IMPLEMENT ME ** + send_success = True # Set to False if it failed. + + action_str = _lookup_action_str_from_hvac_mode(new_hvac_mode) + if send_success: + # If success then log that the command was successfully sent. + self.logger.info(f"sent \"{dev.name}\" mode change to {action_str}") + + # And then tell the Indigo Server to update the state. + if "hvacOperationMode" in dev.states: + dev.updateStateOnServer("hvacOperationMode", new_hvac_mode) + else: + # Else log failure but do NOT update state on Indigo Server. + self.logger.error(f"send \"{dev.name}\" mode change to {action_str} failed") + + ###################### + # Process action request from Indigo Server to change thermostat's fan mode. + def _handle_change_fan_mode_action(self, dev, new_fan_mode): + # Command hardware module (dev) to change the fan mode here: + # ** IMPLEMENT ME ** + send_success = True # Set to False if it failed. + + action_str = _lookup_action_str_from_fan_mode(new_fan_mode) + if send_success: + # If success then log that the command was successfully sent. + self.logger.info(f"sent \"{dev.name}\" fan mode change to {action_str}") + + # And then tell the Indigo Server to update the state. + if "hvacFanMode" in dev.states: + dev.updateStateOnServer("hvacFanMode", new_fan_mode) + else: + # Else log failure but do NOT update state on Indigo Server. + self.logger.error(f"send \"{dev.name}\" fan mode change to {action_str} failed") + + ###################### + # Process action request from Indigo Server to change a cool/heat setpoint. + def _handle_change_setpoint_action(self, dev, new_setpoint, log_action_name, state_key): + if new_setpoint < 40.0: + new_setpoint = 40.0 # Arbitrary -- set to whatever hardware minimum setpoint value is. + elif new_setpoint > 95.0: + new_setpoint = 95.0 # Arbitrary -- set to whatever hardware maximum setpoint value is. + + send_success = False + + if state_key == "setpointCool": + # Command hardware module (dev) to change the cool setpoint to new_setpoint here: + # ** IMPLEMENT ME ** + send_success = True # Set to False if it failed. + elif state_key == "setpointHeat": + # Command hardware module (dev) to change the heat setpoint to new_setpoint here: + # ** IMPLEMENT ME ** + send_success = True # Set to False if it failed. + + if send_success: + # If success then log that the command was successfully sent. + self.logger.info(f"sent \"{dev.name}\" {log_action_name} to {new_setpoint:.1f}°") + + # And then tell the Indigo Server to update the state. + if state_key in dev.states: + dev.updateStateOnServer(state_key, new_setpoint, uiValue=f"{new_setpoint:.1f} °F") + else: + # Else log failure but do NOT update state on Indigo Server. + self.logger.error(f"send \"{dev.name}\" {log_action_name} to {new_setpoint:.1f}° failed") + + ######################################## + def startup(self): + self.logger.debug("startup called") + + def shutdown(self): + self.logger.debug("shutdown called") + + ######################################## + def runConcurrentThread(self): + try: + while True: + for dev in indigo.devices.iter("self"): + if not dev.enabled or not dev.configured: + continue + + # Plugins that need to poll out the status from the thermostat + # could do so here, then broadcast back the new values to the + # Indigo Server. + self._refresh_states_from_hardware(dev, False, False) + + self.sleep(self.refresh_delay) + except self.StopThread: + pass # Optionally catch the StopThread exception and do any needed cleanup. + + ######################################## + def validateDeviceConfigUi(self, values_dict, type_id, dev_id): + return (True, values_dict) + + ######################################## + def deviceStartComm(self, dev): + # Called when communication with the hardware should be established. + # Here would be a good place to poll out the current states from the + # thermostat. If periodic polling of the thermostat is needed (that + # is, it doesn't broadcast changes back to the plugin somehow), then + # consider adding that to runConcurrentThread() above. + self._refresh_states_from_hardware(dev, True, True) + + def deviceStopComm(self, dev): + # Called when communication with the hardware should be shutdown. + pass + + ######################################## + # Thermostat Action callback + ###################### + # Main thermostat action bottleneck called by Indigo Server. + def actionControlThermostat(self, action, dev): + ###### SET HVAC MODE ###### + if action.thermostatAction == indigo.kThermostatAction.SetHvacMode: + self._handle_change_hvac_mode_action(dev, action.actionMode) + + ###### SET FAN MODE ###### + elif action.thermostatAction == indigo.kThermostatAction.SetFanMode: + self._handle_change_fan_mode_action(dev, action.actionMode) + + ###### SET COOL SETPOINT ###### + elif action.thermostatAction == indigo.kThermostatAction.SetCoolSetpoint: + new_setpoint = action.actionValue + self._handle_change_setpoint_action(dev, new_setpoint, "change cool setpoint", "setpointCool") + + ###### SET HEAT SETPOINT ###### + elif action.thermostatAction == indigo.kThermostatAction.SetHeatSetpoint: + new_setpoint = action.actionValue + self._handle_change_setpoint_action(dev, new_setpoint, "change heat setpoint", "setpointHeat") + + ###### DECREASE/INCREASE COOL SETPOINT ###### + elif action.thermostatAction == indigo.kThermostatAction.DecreaseCoolSetpoint: + new_setpoint = dev.coolSetpoint - action.actionValue + self._handle_change_setpoint_action(dev, new_setpoint, "decrease cool setpoint", "setpointCool") + + elif action.thermostatAction == indigo.kThermostatAction.IncreaseCoolSetpoint: + new_setpoint = dev.coolSetpoint + action.actionValue + self._handle_change_setpoint_action(dev, new_setpoint, "increase cool setpoint", "setpointCool") + + ###### DECREASE/INCREASE HEAT SETPOINT ###### + elif action.thermostatAction == indigo.kThermostatAction.DecreaseHeatSetpoint: + new_setpoint = dev.heatSetpoint - action.actionValue + self._handle_change_setpoint_action(dev, new_setpoint, "decrease heat setpoint", "setpointHeat") + + elif action.thermostatAction == indigo.kThermostatAction.IncreaseHeatSetpoint: + new_setpoint = dev.heatSetpoint + action.actionValue + self._handle_change_setpoint_action(dev, new_setpoint, "increase heat setpoint", "setpointHeat") + + ###### REQUEST STATE UPDATES ###### + elif action.thermostatAction in [indigo.kThermostatAction.RequestStatusAll, indigo.kThermostatAction.RequestMode, + indigo.kThermostatAction.RequestEquipmentState, indigo.kThermostatAction.RequestTemperatures, indigo.kThermostatAction.RequestHumidities, + indigo.kThermostatAction.RequestDeadbands, indigo.kThermostatAction.RequestSetpoints]: + self._refresh_states_from_hardware(dev, True, False) + + ######################################## + # General Action callback + ###################### + def actionControlUniversal(self, action, dev): + ###### BEEP ###### + if action.deviceAction == indigo.kUniversalAction.Beep: + # Beep the hardware module (dev) here: + # ** IMPLEMENT ME ** + self.logger.info(f"sent \"{dev.name}\" beep request") + + ###### ENERGY UPDATE ###### + elif action.deviceAction == indigo.kUniversalAction.EnergyUpdate: + # Request hardware module (dev) for its most recent meter data here: + # ** IMPLEMENT ME ** + self.logger.info(f"sent \"{dev.name}\" energy update request") + + ###### ENERGY RESET ###### + elif action.deviceAction == indigo.kUniversalAction.EnergyReset: + # Request that the hardware module (dev) reset its accumulative energy usage data here: + # ** IMPLEMENT ME ** + self.logger.info(f"sent \"{dev.name}\" energy reset request") + + ###### STATUS REQUEST ###### + elif action.deviceAction == indigo.kUniversalAction.RequestStatus: + # Query hardware module (dev) for its current status here. This differs from the + # indigo.kThermostatAction.RequestStatusAll action - for instance, if your thermo + # is battery powered you might only want to update it only when the user uses + # this status request (and not from the RequestStatusAll). This action would + # get all possible information from the thermostat and the other call + # would only get thermostat-specific information: + # ** GET BATTERY INFO ** + # and call the common function to update the thermo-specific data + self._refresh_states_from_hardware(dev, True, False) + self.logger.info(f"sent \"{dev.name}\" status request") + + ######################################## + # Custom Plugin Action callbacks (defined in Actions.xml) + ###################### + def set_backlight_brightness(self, plugin_action, dev): + try: + new_brightness = int(plugin_action.props.get("brightness", 100)) + except ValueError: + # The int() cast above might fail if the user didn't enter a number: + self.logger.error(f"set backlight brightness action to device \"{dev.name}\" -- invalid brightness value") + return + # Command hardware module (dev) to set backlight brightness here: + # FIXME: add implementation here + send_success = True # Set to False if it failed. + if send_success: + # If success then log that the command was successfully sent. + self.logger.info(f"sent \"{dev.name}\" set backlight brightness to {new_brightness}") + # And then tell the Indigo Server to update the state: + dev.updateStateOnServer("backlightBrightness", new_brightness) + else: + # Else log failure but do NOT update state on Indigo Server. + self.logger.error(f"send \"{dev.name}\" set backlight brightness to {new_brightness} failed") + + ######################################## + # Actions defined in MenuItems.xml. In this case we just use these menu actions to + # simulate different thermostat configurations (how many temperature and humidity + # sensors they have). + #################### + def change_temp_sensor_count_to_1(self): + self._change_all_temp_sensor_counts(1) + + def change_temp_sensor_count_to_2(self): + self._change_all_temp_sensor_counts(2) + + def change_temp_sensor_count_to_3(self): + self._change_all_temp_sensor_counts(3) + + def change_humidity_sensor_count_to_0(self): + self._change_all_humidity_sensor_counts(0) + + def change_humidity_sensor_count_to_1(self): + self._change_all_humidity_sensor_counts(1) + + def change_humidity_sensor_count_to_2(self): + self._change_all_humidity_sensor_counts(2) + + def change_humidity_sensor_count_to_3(self): + self._change_all_humidity_sensor_counts(3) + diff --git a/Example HTTP Responder.indigoPlugin/Contents/Info.plist b/Example HTTP Responder.indigoPlugin/Contents/Info.plist index 07a27ba..9bb3883 100644 --- a/Example HTTP Responder.indigoPlugin/Contents/Info.plist +++ b/Example HTTP Responder.indigoPlugin/Contents/Info.plist @@ -2,24 +2,24 @@ - PluginVersion - 2021.2.0 - ServerApiVersion - 2.6 - IwsApiVersion - 1.0.0 - CFBundleDisplayName - Example HTTP Responder - CFBundleIdentifier - com.indigodomo.indigoplugin.example-http-responder - CFBundleVersion - 1.0.0 - CFBundleURLTypes - - - CFBundleURLName - /com.indigodomo.indigoplugin.example-http-responder/static/html/about.html - - + PluginVersion + 2022.1.0 + ServerApiVersion + 3.0 + IwsApiVersion + 1.0.0 + CFBundleDisplayName + Example HTTP Responder + CFBundleIdentifier + com.indigodomo.indigoplugin.example-http-responder + CFBundleVersion + 1.0.0 + CFBundleURLTypes + + + CFBundleURLName + /com.indigodomo.indigoplugin.example-http-responder/static/html/about.html + + diff --git a/Example HTTP Responder.indigoPlugin/Contents/Resources/templates/base.html b/Example HTTP Responder.indigoPlugin/Contents/Resources/templates/base.html index 8ee13a7..164e725 100644 --- a/Example HTTP Responder.indigoPlugin/Contents/Resources/templates/base.html +++ b/Example HTTP Responder.indigoPlugin/Contents/Resources/templates/base.html @@ -10,7 +10,7 @@
{% block content %}{% endblock %}
diff --git a/Example HTTP Responder.indigoPlugin/Contents/Server Plugin/Actions.xml b/Example HTTP Responder.indigoPlugin/Contents/Server Plugin/Actions.xml index 590768e..75a7fd3 100644 --- a/Example HTTP Responder.indigoPlugin/Contents/Server Plugin/Actions.xml +++ b/Example HTTP Responder.indigoPlugin/Contents/Server Plugin/Actions.xml @@ -1,15 +1,15 @@ - - static file request - handle_static_file_request - - - example config web page - sample_config - - - simple api example - api - + + static file request + handle_static_file_request + + + example config web page + sample_config + + + simple api example + api + \ No newline at end of file diff --git a/Example HTTP Responder.indigoPlugin/Contents/Server Plugin/plugin.py b/Example HTTP Responder.indigoPlugin/Contents/Server Plugin/plugin.py index 6557bc3..5505d44 100644 --- a/Example HTTP Responder.indigoPlugin/Contents/Server Plugin/plugin.py +++ b/Example HTTP Responder.indigoPlugin/Contents/Server Plugin/plugin.py @@ -1,231 +1,224 @@ #! /usr/bin/env python # -*- coding: utf-8 -*- #################### -# Copyright (c) 2021, Indigo Domotics. All rights reserved. -# http://www.indigodomo.com +# Copyright (c) 2022, Indigo Domotics. All rights reserved. +# https://www.indigodomo.com try: - # This is primarily for IDEs so that they won't mark indigo stuff as undefined. The module itself is always imported - # by the host process. - import indigo + # This is primarily for IDEs so that they won't mark indigo stuff as undefined. The module itself is always imported + # by the host process. + import indigo except: - pass + pass import os -import jinja2 -from datetime import datetime, date +from datetime import datetime import json +import jinja2 import dicttoxml NO_FILE_SPECIFIED = "No File Specified" ################################################################################ class Plugin(indigo.PluginBase): - ######################################## - def __init__(self, pluginId, pluginDisplayName, pluginVersion, pluginPrefs, **kwargs): - super(Plugin, self).__init__(pluginId, pluginDisplayName, pluginVersion, pluginPrefs, **kwargs) - self.debug = True - # Set up the environment for Jinja templates. The most important to to configure the file system loader so that - # it points to our Resources/templates directory. Then we can just load the template via name (or name and - # relative path within that folder if we choose). - self.templates = jinja2.Environment( - loader=jinja2.FileSystemLoader("../Resources/templates"), - autoescape=True, - auto_reload=self.debug, # this makes the templates load from disk every time you use them - good for debugging - ) - # Next, we add some global variables that will be available to all templates automatically. - self.templates.globals = { - "plugin": self, # used primarily here to construct paths to the static/css directory - "year_string": datetime.now().strftime("%Y"), # used for the copyright - } - self.pluginPrefs = pluginPrefs - - ######################################## - def startup(self): - self.logger.debug(u"startup called") - - def shutdown(self): - self.logger.debug(u"shutdown called") - - ######################################## - def api(self, action, dev=None, callerWaitingForResult=None): - ''' - This handler is used to generate a simple api that returns device details for the specified device id. - Example url: - - http://localhost:8176/message/com.indigodomo.indigoplugin.example-http-responder/api/123456.json - http://localhost:8176/message/com.indigodomo.indigoplugin.example-http-responder/api/123456.json?condense-json=true - - The first part is the ID of a device and the extension as the type of content to return. We only do XML and JSON - in this example. There is an optional arg to return JSON that doesn't have indents, which means it would be - smaller. condense-json can be any value at all and it will skip the formatting with indents. - - :param action: action.props contains all the information passed from the web server - :param dev: unused - :param callerWaitingForResult: always True - :return: a reply dict with the content value being JSON or XML representation of a device instance. - ''' - self.logger.debug("Handling API request") - props_dict = dict(action.props) - file_path = list(props_dict["file_path"]) - reply = indigo.Dict() - try: - file_name = file_path[-1] - id_string, format = file_name.split(".") - dev_id = int(id_string) - format = format.lower() - except: - self.logger.error("no file was specified in the request or the file name was incorrect") - reply["content"] = "no file was specified in the request or the file name was incorrect" - reply["status"] = 500 - return reply - try: - device = indigo.devices[dev_id] - self.logger.debug("...for device {}".format(device.name)) - reply["status"] = 200 - if format == "xml": - reply["content"] = dicttoxml.dicttoxml(dict(device), custom_root="Device") - elif format == "json": - try: - condense = props_dict.get("url_query_args", {}).get("condense-json", False) - if condense: - reply["content"] = json.dumps(dict(device), separators=(',',':'), cls=indigo.utils.JSONDateEncoder) - else: - reply["content"] = json.dumps(dict(device), indent=4, cls=indigo.utils.JSONDateEncoder) - except Exception as exc: - self.logger.exception(exc) - else: - self.logger.error("specified format isn't supported") - reply["content"] = "specified format isn't supported" - reply["status"] = 500 - return reply - reply["headers"] = {"Content-Type": "application/{}".format(format)} - return reply - except KeyError: - self.logger.error("device id doesn't exist in database") - # Here, we illustrate how to return a custom dynamic 404 page - template = self.templates.get_template("device_missing.html") - reply["status"] = 404 - reply["headers"] = indigo.Dict({"Content-Type": "text/html"}) - reply["content"] = template.render({"device_id": dev_id}) - return reply - - ######################################## - def handle_static_file_request(self, action, dev=None, callerWaitingForResult=None): - ''' - This handler just opens the file specified in the query string's "file-name" argument on the URL and returns - the content. We just assume here it's some kind of text file. We'll look for the file in the Resources folder - in this plugin. If it's not there, we'll return a 401. - - This example is here to illustrate one way to return a file from a plugin without putting it into one of the - directories that are automatically served within the Resources folder (see the README.txt file in the Server Plugin - folder for details on how the Indigo Web Server can automatically serve up content from your plugin. - - http://localhost:8176/message/com.indigodomo.indigoplugin.example-http-responder/handle_static_file_request/?file-name=test.csv - - :param action: action.props contains all the information passed from the web server - :param dev: unused - :param callerWaitingForResult: always True - :return: a dict that contains the status, the Content-Type header, and the contents of the specified file. - ''' - self.logger.debug("Handling HTTP request") - props_dict = dict(action.props) - reply = indigo.Dict() - ####################### - # reply["content"] = "a 200 return from the plugin" - # reply["status"] = 200 - # return reply - ####################### - try: - file_path = "../Resources/{}".format(props_dict["url_query_args"]["file-name"]) - self.logger.debug("...looking for file: {}".format(file_path)) - except: - # file name wasn't specified, set a flag - file_path = NO_FILE_SPECIFIED - try: - with open(file_path, "r") as f: - file_contents = f.read() - reply["status"] = 200 - reply["headers"] = indigo.Dict( - { - "Content-Type": "{}".format( - indigo.utils.FILE_EXTENSION_MIME_MAP.get( - os.path.splitext(file_path[-1])[-1].strip("."), - "text/plain" - ) - ) - } - ) - reply["content"] = file_contents - except: - # file wasn't found - if file_path == NO_FILE_SPECIFIED: - self.logger.error("no file was specified in the request query arguments") - reply["content"] = "no file was specified in the request query arguments" - reply["status"] = 400 - else: - # Here, we illustrate how to return a custom static 404 HTML page - self.logger.error("requested file was not found: {}".format(props_dict["url_query_args"]["file-name"])) - return indigo.utils.return_static_file( - "{}/Contents/Resources/static/html/static_404.html".format(self.pluginFolderPath), - status=404, - path_is_relative=False, - ) - return reply - - ######################################## - def sample_config(self, action, dev=None, callerWaitingForResult=None): - ''' - This handler represents a simple plugin configuration example. It will have the following URLs: - - http://localhost:8176/message/com.indigodomo.indigoplugin.example-http-responder/config - - It uses the config.html Jinja template in the templates directory. That template extends the base.html template - file, which includes the static/css/config.css file using the built-in static file serving process for plugins. - - :param action: action.props contains all the information passed from the web server - :param dev: unused - :param callerWaitingForResult: always True - :return: a dict that contains the status, the Content-Type header, and the contents of the specified file. - ''' - self.logger.debug("Handling config request") - props_dict = dict(action.props) - reply = indigo.Dict() - context = { - "date_string": str(datetime.now()), # Used in the config.html template - "prefs": self.pluginPrefs, - } - if props_dict.get(u'incoming_request_method', "GET") == "POST": - post_params = dict(props_dict["body_params"]) - if "operation" in post_params: - if post_params["operation"] == "add": - k = post_params.get("key", None) - v = post_params.get("value", None) - if not k or not v: - context["error"] = "the key and value must not be empty to add them to the plugin config" - else: - self.pluginPrefs[k] = v - else: - context["error"] = "'add' is the only valid operation for this form" - else: - for k, v in post_params.items(): - if v == "delete": - try: - del(self.pluginPrefs[k]) - except: - # probably a stale browser trying to delete a key that's already gone, just ignore it - pass - indigo.server.savePluginPrefs() - try: - template = self.templates.get_template("config.html") - reply["status"] = 200 - reply["headers"] = indigo.Dict({"Content-Type": "text/html"}) - reply["content"] = template.render(context) - except Exception as exc: - # some error happened - self.logger.error("some error occurred: {}".format(str(exc))) - reply["status"] = 500 - return reply - - ######################################## - def do_nothing(self, valuesDict, typeId): - pass + ######################################## + def __init__(self, plugin_id, plugin_display_name, plugin_version, plugin_prefs, **kwargs): + super().__init__(plugin_id, plugin_display_name, plugin_version, plugin_prefs, **kwargs) + self.debug = True + # Set up the environment for Jinja templates. The most important to to configure the file system loader so that + # it points to our Resources/templates directory. Then we can just load the template via name (or name and + # relative path within that folder if we choose). + self.templates = jinja2.Environment( + loader=jinja2.FileSystemLoader("../Resources/templates"), + autoescape=True, + auto_reload=self.debug, # this makes the templates load from disk every time you use them - good for debugging + ) + # Next, we add some global variables that will be available to all templates automatically. + self.templates.globals = { + "plugin": self, # used primarily here to construct paths to the static/css directory + "year_string": datetime.now().strftime("%Y"), # used for the copyright + } + + ######################################## + def startup(self): + self.logger.debug("startup called") + + def shutdown(self): + self.logger.debug("shutdown called") + + ######################################## + def api(self, action, dev=None, caller_waiting_for_result=None): + ''' + This handler is used to generate a simple api that returns device details for the specified device id. + Example url: + + http://localhost:8176/message/com.indigodomo.indigoplugin.example-http-responder/api/123456.json + http://localhost:8176/message/com.indigodomo.indigoplugin.example-http-responder/api/123456.json?condense-json=true + + The first part is the ID of a device and the extension as the type of content to return. We only do XML and JSON + in this example. There is an optional arg to return JSON that doesn't have indents, which means it would be + smaller. condense-json can be any value at all and it will skip the formatting with indents. + + :param action: action.props contains all the information passed from the web server + :param dev: unused + :param caller_waiting_for_result: always True + :return: a reply dict with the content value being JSON or XML representation of a device instance. + ''' + self.logger.debug("Handling API request") + props_dict = dict(action.props) + file_path = list(props_dict["file_path"]) + reply = indigo.Dict() + try: + file_name = file_path[-1] + id_string, frmt = file_name.split(".") + dev_id = int(id_string) + frmt = frmt.lower() + except: + self.logger.error("no file was specified in the request or the file name was incorrect") + reply["content"] = "no file was specified in the request or the file name was incorrect" + reply["status"] = 500 + return reply + try: + device = indigo.devices[dev_id] + self.logger.debug(f"...for device {device.name}") + reply["status"] = 200 + if frmt == "xml": + reply["content"] = dicttoxml.dicttoxml(dict(device), custom_root="Device") + elif frmt == "json": + try: + condense = props_dict.get("url_query_args", {}).get("condense-json", False) + if condense: + reply["content"] = json.dumps(dict(device), separators=(',',':'), cls=indigo.utils.JSONDateEncoder) + else: + reply["content"] = json.dumps(dict(device), indent=4, cls=indigo.utils.JSONDateEncoder) + except Exception as exc: + self.logger.exception(exc) + else: + self.logger.error("specified format isn't supported") + reply["content"] = "specified format isn't supported" + reply["status"] = 500 + return reply + reply["headers"] = {"Content-Type": f"application/{frmt}"} + return reply + except KeyError: + self.logger.error("device id doesn't exist in database") + # Here, we illustrate how to return a custom dynamic 404 page + template = self.templates.get_template("device_missing.html") + reply["status"] = 404 + reply["headers"] = indigo.Dict({"Content-Type": "text/html"}) + reply["content"] = template.render({"device_id": dev_id}) + return reply + + ######################################## + def handle_static_file_request(self, action, dev=None, caller_waiting_for_result=None): + ''' + This handler just opens the file specified in the query string's "file-name" argument on the URL and returns + the content. We just assume here it's some kind of text file. We'll look for the file in the Resources folder + in this plugin. If it's not there, we'll return a 401. + + This example is here to illustrate one way to return a file from a plugin without putting it into one of the + directories that are automatically served within the Resources folder (see the README.txt file in the Server Plugin + folder for details on how the Indigo Web Server can automatically serve up content from your plugin. + + http://localhost:8176/message/com.indigodomo.indigoplugin.example-http-responder/handle_static_file_request/?file-name=test.csv + + :param action: action.props contains all the information passed from the web server + :param dev: unused + :param caller_waiting_for_result: always True + :return: a dict that contains the status, the Content-Type header, and the contents of the specified file. + ''' + self.logger.debug("Handling HTTP request") + props_dict = dict(action.props) + reply = indigo.Dict() + ####################### + # reply["content"] = "a 200 return from the plugin" + # reply["status"] = 200 + # return reply + ####################### + try: + file_name = props_dict["url_query_args"]["file-name"] + file_path = f"../Resources/{file_name}" + self.logger.debug(f"...looking for file: {file_path}") + except: + # file name wasn't specified, set a flag + file_path = NO_FILE_SPECIFIED + try: + with open(file_path, "r", encoding='utf-8') as file: + file_contents = file.read() + content_type = indigo.utils.FILE_EXTENSION_MIME_MAP.get(os.path.splitext(file_path[-1])[-1].strip("."), "text/plain") + reply["status"] = 200 + reply["headers"] = indigo.Dict({ "Content-Type":f"{content_type}" }) + reply["content"] = file_contents + except: + # file wasn't found + if file_path == NO_FILE_SPECIFIED: + self.logger.error("no file was specified in the request query arguments") + reply["content"] = "no file was specified in the request query arguments" + reply["status"] = 400 + else: + # Here, we illustrate how to return a custom static 404 HTML page + file_name = props_dict["url_query_args"]["file-name"] + self.logger.error(f"requested file was not found: {file_name}") + return indigo.utils.return_static_file( + f"{self.pluginFolderPath}/Contents/Resources/static/html/static_404.html", + status=404, + path_is_relative=False, + ) + return reply + + ######################################## + def sample_config(self, action, dev=None, caller_waiting_for_result=None): + ''' + This handler represents a simple plugin configuration example. It will have the following URLs: + + http://localhost:8176/message/com.indigodomo.indigoplugin.example-http-responder/config + + It uses the config.html Jinja template in the templates directory. That template extends the base.html template + file, which includes the static/css/config.css file using the built-in static file serving process for plugins. + + :param action: action.props contains all the information passed from the web server + :param dev: unused + :param caller_waiting_for_result: always True + :return: a dict that contains the status, the Content-Type header, and the contents of the specified file. + ''' + self.logger.debug("Handling config request") + props_dict = dict(action.props) + reply = indigo.Dict() + context = { + "date_string": str(datetime.now()), # Used in the config.html template + "prefs": self.pluginPrefs, + } + if props_dict.get('incoming_request_method', "GET") == "POST": + post_params = dict(props_dict["body_params"]) + if "operation" in post_params: + if post_params["operation"] == "add": + key = post_params.get("key", None) + val = post_params.get("value", None) + if not key or not val: + context["error"] = "the key and value must not be empty to add them to the plugin config" + else: + self.pluginPrefs[key] = val + else: + context["error"] = "'add' is the only valid operation for this form" + else: + for key, val in post_params.items(): + if val == "delete": + try: + del self.pluginPrefs[key] + except: + # probably a stale browser trying to delete a key that's already gone, just ignore it + pass + indigo.server.savePluginPrefs() + try: + template = self.templates.get_template("config.html") + reply["status"] = 200 + reply["headers"] = indigo.Dict({"Content-Type": "text/html"}) + reply["content"] = template.render(context) + except Exception as exc: + # some error happened + self.logger.error(f"some error occurred: {exc}") + reply["status"] = 500 + return reply + + ######################################## + def do_nothing(self, values_dict, type_id): + pass diff --git a/Example INSTEON:X10 Listener.indigoPlugin/Contents/Info.plist b/Example INSTEON:X10 Listener.indigoPlugin/Contents/Info.plist index 17f74d0..c43fb64 100644 --- a/Example INSTEON:X10 Listener.indigoPlugin/Contents/Info.plist +++ b/Example INSTEON:X10 Listener.indigoPlugin/Contents/Info.plist @@ -2,24 +2,24 @@ - PluginVersion - 2021.1.0 - ServerApiVersion - 2.5 - IwsApiVersion - 1.0.0 - CFBundleDisplayName - Example INSTEON/X10 Listener - CFBundleIdentifier - com.perceptiveautomation.indigoplugin.example-insteon-x10-listener - CFBundleVersion - 1.0.0 - CFBundleURLTypes - - - CFBundleURLName - http://wiki.indigodomo.com/doku.php?id=plugins:example_insteon_x10_listener_1 - - + PluginVersion + 2022.1.0 + ServerApiVersion + 3.0 + IwsApiVersion + 1.0.0 + CFBundleDisplayName + Example INSTEON/X10 Listener + CFBundleIdentifier + com.perceptiveautomation.indigoplugin.example-insteon-x10-listener + CFBundleVersion + 1.0.0 + CFBundleURLTypes + + + CFBundleURLName + https://wiki.indigodomo.com/doku.php?id=plugins:example_insteon_x10_listener_1 + + diff --git a/Example INSTEON:X10 Listener.indigoPlugin/Contents/Server Plugin/MenuItems.xml b/Example INSTEON:X10 Listener.indigoPlugin/Contents/Server Plugin/MenuItems.xml index 0ac49ec..aecccaf 100644 --- a/Example INSTEON:X10 Listener.indigoPlugin/Contents/Server Plugin/MenuItems.xml +++ b/Example INSTEON:X10 Listener.indigoPlugin/Contents/Server Plugin/MenuItems.xml @@ -1,15 +1,15 @@ - + diff --git a/Example INSTEON:X10 Listener.indigoPlugin/Contents/Server Plugin/plugin.py b/Example INSTEON:X10 Listener.indigoPlugin/Contents/Server Plugin/plugin.py index 8535b0a..20a5454 100644 --- a/Example INSTEON:X10 Listener.indigoPlugin/Contents/Server Plugin/plugin.py +++ b/Example INSTEON:X10 Listener.indigoPlugin/Contents/Server Plugin/plugin.py @@ -1,8 +1,8 @@ #! /usr/bin/env python # -*- coding: utf-8 -*- #################### -# Copyright (c) 2014, Perceptive Automation, LLC. All rights reserved. -# http://www.indigodomo.com +# Copyright (c) 2022, Perceptive Automation, LLC. All rights reserved. +# https://www.indigodomo.com import indigo @@ -14,40 +14,39 @@ ################################################################################ class Plugin(indigo.PluginBase): - ######################################## - def __init__(self, pluginId, pluginDisplayName, pluginVersion, pluginPrefs): - super(Plugin, self).__init__(pluginId, pluginDisplayName, pluginVersion, pluginPrefs) - self.debug = True - - ######################################## - def startup(self): - self.debugLog(u"startup called -- subscribing to all X10 and INSTEON commands") - indigo.insteon.subscribeToIncoming() - indigo.insteon.subscribeToOutgoing() - indigo.x10.subscribeToIncoming() - indigo.x10.subscribeToOutgoing() - - def shutdown(self): - self.debugLog(u"shutdown called") - - ######################################## - def insteonCommandReceived(self, cmd): - self.debugLog(u"insteonCommandReceived: \n" + str(cmd)) - - def insteonCommandSent(self, cmd): - self.debugLog(u"insteonCommandSent: \n" + str(cmd)) - - ######################################## - def x10CommandReceived(self, cmd): - self.debugLog(u"x10CommandReceived: \n" + str(cmd)) - - if cmd.cmdType == "sec": # or "x10" for power line commands - if cmd.secCodeId == 6: - if cmd.secFunc == "sensor alert (max delay)": - indigo.server.log(u"SENSOR OPEN") - elif cmd.secFunc == "sensor normal (max delay)": - indigo.server.log(u"SENSOR CLOSED") - - def x10CommandSent(self, cmd): - self.debugLog(u"x10CommandSent: \n" + str(cmd)) - + ######################################## + def __init__(self, plugin_id, plugin_display_name, plugin_version, plugin_prefs): + super().__init__(plugin_id, plugin_display_name, plugin_version, plugin_prefs) + self.debug = True + + ######################################## + def startup(self): + self.logger.debug("startup called -- subscribing to all X10 and INSTEON commands") + indigo.insteon.subscribeToIncoming() + indigo.insteon.subscribeToOutgoing() + indigo.x10.subscribeToIncoming() + indigo.x10.subscribeToOutgoing() + + def shutdown(self): + self.logger.debug("shutdown called") + + ######################################## + def insteonCommandReceived(self, cmd): + self.logger.debug(f"insteonCommandReceived: \n{str(cmd)}") + + def insteonCommandSent(self, cmd): + self.logger.debug(f"insteonCommandSent: \n{str(cmd)}") + + ######################################## + def x10CommandReceived(self, cmd): + self.logger.debug(f"x10CommandReceived: \n{str(cmd)}") + + if cmd.cmdType == "sec": # or "x10" for power line commands + if cmd.secCodeId == 6: + if cmd.secFunc == "sensor alert (max delay)": + self.logger.info("SENSOR OPEN") + elif cmd.secFunc == "sensor normal (max delay)": + self.logger.info("SENSOR CLOSED") + + def x10CommandSent(self, cmd): + self.logger.debug(f"x10CommandSent: \n{str(cmd)}") diff --git a/Example Twisted Telnet.indigoPlugin/Contents/Info.plist b/Example Twisted Telnet.indigoPlugin/Contents/Info.plist deleted file mode 100644 index c87d9c5..0000000 --- a/Example Twisted Telnet.indigoPlugin/Contents/Info.plist +++ /dev/null @@ -1,25 +0,0 @@ - - - - - PluginVersion - 2021.1.0 - ServerApiVersion - 2.5 - IwsApiVersion - 1.0.0 - CFBundleDisplayName - Example Twisted Telnet - CFBundleIdentifier - com.perceptiveautomation.indigoplugin.twisted_test1 - CFBundleVersion - 1.0.0 - CFBundleURLTypes - - - CFBundleURLName - http://wiki.indigodomo.com/doku.php?id=plugins:example_twisted_telnet_1 - - - - diff --git a/Example Twisted Telnet.indigoPlugin/Contents/Server Plugin/MenuItems.xml b/Example Twisted Telnet.indigoPlugin/Contents/Server Plugin/MenuItems.xml deleted file mode 100644 index f8d5581..0000000 --- a/Example Twisted Telnet.indigoPlugin/Contents/Server Plugin/MenuItems.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - Enable Telnet Client Device Flashing - enableClientFlashingAction - - - Disable Telnet Client Device Flashing - disableClientFlashingAction - - diff --git a/Example Twisted Telnet.indigoPlugin/Contents/Server Plugin/PluginConfig.xml b/Example Twisted Telnet.indigoPlugin/Contents/Server Plugin/PluginConfig.xml deleted file mode 100644 index a5cb5fa..0000000 --- a/Example Twisted Telnet.indigoPlugin/Contents/Server Plugin/PluginConfig.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - http://wiki.indigodomo.com/doku.php?id=plugins:example_twisted_telnet_1 - - - - Enabled - - diff --git a/Example Twisted Telnet.indigoPlugin/Contents/Server Plugin/plugin.py b/Example Twisted Telnet.indigoPlugin/Contents/Server Plugin/plugin.py deleted file mode 100644 index 0033f78..0000000 --- a/Example Twisted Telnet.indigoPlugin/Contents/Server Plugin/plugin.py +++ /dev/null @@ -1,133 +0,0 @@ -#! /usr/bin/env python -# -*- coding: utf-8 -*- -#################### -# Copyright (c) 2014, Perceptive Automation, LLC. All rights reserved. -# http://www.indigodomo.com - -# *** Using Twisted inside a plugin *** -# -# Twisted (http://twistedmatrix.com) can be started inside a plugin -# runConcurrentThread() function. All factory creating and reactor.run() calls -# should be made inside runConcurrentThread(). -# -# Note that callback methods from the Indigo host (for menu items, UI actions, -# etc.) are executed in a different thread than runConcurrentThread(). Because -# twisted is not threadsafe, use the reactor.callFromThread() function to -# execute any twisted functions from these callbacks. See stopConcurrentThread() -# for an example that stops the reactor safely. -# -# To test from the Terminal enter: -# -# telnet 127.0.0.1 9176 -# -# Then you can enter device names which will be toggled for 2 seconds. -# -# You can use the plugin's installed menu items to disable and enable -# the remote flashing capability. The enable state, along with a global -# flash count, are automatically saved in the plugin's preferences file. - -import indigo - -import os -import sys - -from twisted.internet.protocol import Factory, Protocol -from twisted.protocols.basic import LineReceiver -from twisted.internet import reactor - -# Note the "indigo" module is automatically imported and made available inside -# our global name space by the host process. - -kTelnetPort = 9176 - -################################################################################ -class TelnetProtocol(LineReceiver): - ######################################## - def connectionMade(self): - indigo.server.log(u"client connection established") - self.sendLine("Hello there, welcome to the Indigo Twisted Plugin device flasher server.") - if indigo.activePlugin.pluginPrefs["flashingEnabled"]: - self.sendLine("Enter a device name:") - else: - self.sendLine("Sorry, but device flashing is currently disabled.") - - def connectionLost(self, reason): - indigo.server.log(u"client connection closed") - - def lineReceived(self, deviceName): - flashCount = int(indigo.activePlugin.pluginPrefs["flashingCount"]) - indigo.server.log(u"received client request (#%d) to flash: %s" % (flashCount, deviceName)) - if indigo.activePlugin.pluginPrefs["flashingEnabled"]: - try: - indigo.device.toggle(deviceName, duration=2) # toggle the device for 2 seconds - indigo.activePlugin.pluginPrefs["flashingCount"] += 1 - except Exception, e: - errStr = str(e) - self.sendLine(errStr) - if "ElementNotFoundError" in errStr: - indigo.activePlugin.errorLog(errStr) - else: - indigo.activePlugin.exceptionLog() - else: - self.sendLine("Sorry, but device flashing is currently disabled.") - indigo.activePlugin.errorLog(u"** request denied -- flashing is disabled **") - -################################################################################ -class TelnetFactory(Factory): - ######################################## - protocol = TelnetProtocol - - def __init__(self): - pass - -################################################################################ -class Plugin(indigo.PluginBase): - ######################################## - telnetFactory = None - listeningPort = None - - def __init__(self, pluginId, pluginDisplayName, pluginVersion, pluginPrefs): - super(Plugin, self).__init__(pluginId, pluginDisplayName, pluginVersion, pluginPrefs) - - ######################################## - def startup(self): - if "flashingEnabled" not in self.pluginPrefs: - self.pluginPrefs["flashingEnabled"] = True # default (first launch) pref values - self.pluginPrefs["flashingCount"] = 0 - - def shutdown(self): # called after runConcurrentThread() exits - pass - - ######################################## - # If runConcurrentThread() is defined, then a new thread is automatically created - # and runConcurrentThread() is called in that thread after startup() has been called. - # - # runConcurrentThread() should loop forever and only return after self.stopThread - # becomes True. If this function returns prematurely then the plugin host process - # will log an error and attempt to call runConcurrentThread() again after several seconds. - def runConcurrentThread(self): - try: - indigo.server.log(u"starting telnet server on port %d" % (kTelnetPort,)) - indigo.server.log(u"to test, open the Terminal application and enter:\n\ntelnet 127.0.0.1 %d\n" % (kTelnetPort,)) - self.telnetFactory = TelnetFactory() - self.listeningPort = reactor.listenTCP(kTelnetPort, self.telnetFactory) - reactor.run() - except self.StopThread: - pass # Optionally catch the StopThread exception and do any needed cleanup. - - def stopConcurrentThread(self): - super(Plugin, self).stopConcurrentThread() - reactor.callFromThread(self.listeningPort.stopListening) - reactor.callFromThread(reactor.stop) - - ######################################## - # Actions defined in MenuItems.xml: - #################### - def enableClientFlashingAction(self): - indigo.server.log(u"telnet client device flashing enabled") - self.pluginPrefs["flashingEnabled"] = True - - def disableClientFlashingAction(self): - indigo.server.log(u"telnet client device flashing disabled") - self.pluginPrefs["flashingEnabled"] = False - diff --git a/Example Variable Change Subscriber.indigoPlugin/Contents/Info.plist b/Example Variable Change Subscriber.indigoPlugin/Contents/Info.plist index f999d68..7778575 100644 --- a/Example Variable Change Subscriber.indigoPlugin/Contents/Info.plist +++ b/Example Variable Change Subscriber.indigoPlugin/Contents/Info.plist @@ -2,24 +2,24 @@ - PluginVersion - 2021.1.0 - ServerApiVersion - 2.5 - IwsApiVersion - 1.0.0 - CFBundleDisplayName - Example Variable Change Subscriber - CFBundleIdentifier - com.perceptiveautomation.indigoplugin.example-variable-change-subscriber - CFBundleVersion - 1.0.0 - CFBundleURLTypes - - - CFBundleURLName - http://wiki.indigodomo.com/doku.php?id=plugins:example_variable_subscriber_1 - - + PluginVersion + 2022.1.0 + ServerApiVersion + 3.0 + IwsApiVersion + 1.0.0 + CFBundleDisplayName + Example Variable Change Subscriber + CFBundleIdentifier + com.perceptiveautomation.indigoplugin.example-variable-change-subscriber + CFBundleVersion + 1.0.0 + CFBundleURLTypes + + + CFBundleURLName + https://wiki.indigodomo.com/doku.php?id=plugins:example_variable_subscriber_1 + + diff --git a/Example Variable Change Subscriber.indigoPlugin/Contents/Server Plugin/plugin.py b/Example Variable Change Subscriber.indigoPlugin/Contents/Server Plugin/plugin.py index b96ea02..bc14802 100644 --- a/Example Variable Change Subscriber.indigoPlugin/Contents/Server Plugin/plugin.py +++ b/Example Variable Change Subscriber.indigoPlugin/Contents/Server Plugin/plugin.py @@ -1,8 +1,8 @@ #! /usr/bin/env python # -*- coding: utf-8 -*- #################### -# Copyright (c) 2018, Perceptive Automation, LLC. All rights reserved. -# http://www.indigodomo.com +# Copyright (c) 2022, Perceptive Automation, LLC. All rights reserved. +# https://www.indigodomo.com try: import indigo @@ -16,18 +16,18 @@ ################################################################################ class Plugin(indigo.PluginBase): ######################################## - def __init__(self, pluginId, pluginDisplayName, pluginVersion, pluginPrefs): - super(Plugin, self).__init__(pluginId, pluginDisplayName, pluginVersion, pluginPrefs) + def __init__(self, plugin_id, plugin_display_name, plugin_version, plugin_prefs): + super().__init__(plugin_id, plugin_display_name, plugin_version, plugin_prefs) self.debug = True ######################################## def startup(self): - self.logger.debug(u"startup called -- subscribing to variable changes") + self.logger.debug("startup called -- subscribing to variable changes") # Subscribe to all variable changes indigo.variables.subscribeToChanges() def shutdown(self): - self.logger.debug(u"shutdown called") + self.logger.debug("shutdown called") ######################################## def variableCreated(self, var): @@ -39,26 +39,26 @@ def variableCreated(self, var): :return: No return value required ''' # You must call the superclass method - super(Plugin, self).variableCreated(var) - self.logger.debug(u"variableCreated called for variable '{}': value: {}".format(var.name, var.value)) + super().variableCreated(var) + self.logger.debug(f"variableCreated called for variable '{var.name}': value: {var.value}") # Do your stuff - def variableUpdated(self, origVar, newVar): + def variableUpdated(self, orig_var, new_var): ''' This method is called every time a variable is changed in any way (name, value, etc). Your plugin will likely need to inspect the variable to see if it's one you're interested in. You can compare the previous value to the new value if you need to. :param self: Your plugin instance - :param origVar: An instance of the indigo.Variable object as it was before the change - :param newVar: An instance of the indigo.Variable object as it is currently (after the change) + :param orig_var: An instance of the indigo.Variable object as it was before the change + :param new_var: An instance of the indigo.Variable object as it is currently (after the change) :return: No return value required ''' # You must call the superclass method - super(Plugin, self).variableUpdated(origVar, newVar) - self.logger.debug(u"variableUpdated called") - self.logger.debug(u"old name: {}, old value: {}".format(origVar.name, origVar.value)) - self.logger.debug(u"new name: {}, new value: {}".format(newVar.name, newVar.value)) + super().variableUpdated(orig_var, new_var) + self.logger.debug("variableUpdated called") + self.logger.debug(f"old name: {orig_var.name}, old value: {orig_var.value}") + self.logger.debug(f"new name: {new_var.name}, new value: {new_var.value}") # Do your stuff def variableDeleted(self, var): @@ -72,6 +72,6 @@ def variableDeleted(self, var): :return: No return value required ''' # You must call the superclass method - super(Plugin, self).variableDeleted(var) - self.logger.debug(u"variableDeleted called for variable '{}': value: {}".format(var.name, var.value)) + super().variableDeleted(var) + self.logger.debug(f"variableDeleted called for variable '{var.name}': value: {var.value}") # Do your stuff diff --git a/Example ZWave Listener.indigoPlugin/Contents/Info.plist b/Example ZWave Listener.indigoPlugin/Contents/Info.plist index 74efe64..650da3f 100644 --- a/Example ZWave Listener.indigoPlugin/Contents/Info.plist +++ b/Example ZWave Listener.indigoPlugin/Contents/Info.plist @@ -2,24 +2,24 @@ - PluginVersion - 2021.1.0 - ServerApiVersion - 2.5 - IwsApiVersion - 1.0.0 - CFBundleDisplayName - Example Z-Wave Listener - CFBundleIdentifier - com.perceptiveautomation.indigoplugin.example-zwave-listener - CFBundleVersion - 1.0.0 - CFBundleURLTypes - - - CFBundleURLName - http://wiki.indigodomo.com/doku.php?id=plugins:example_zwave_listener_1 - - + PluginVersion + 2022.1.0 + ServerApiVersion + 3.0 + IwsApiVersion + 1.0.0 + CFBundleDisplayName + Example Z-Wave Listener + CFBundleIdentifier + com.perceptiveautomation.indigoplugin.example-zwave-listener + CFBundleVersion + 1.0.0 + CFBundleURLTypes + + + CFBundleURLName + https://wiki.indigodomo.com/doku.php?id=plugins:example_zwave_listener_1 + + diff --git a/Example ZWave Listener.indigoPlugin/Contents/Server Plugin/MenuItems.xml b/Example ZWave Listener.indigoPlugin/Contents/Server Plugin/MenuItems.xml index 0ac49ec..aecccaf 100644 --- a/Example ZWave Listener.indigoPlugin/Contents/Server Plugin/MenuItems.xml +++ b/Example ZWave Listener.indigoPlugin/Contents/Server Plugin/MenuItems.xml @@ -1,15 +1,15 @@ - + diff --git a/Example ZWave Listener.indigoPlugin/Contents/Server Plugin/plugin.py b/Example ZWave Listener.indigoPlugin/Contents/Server Plugin/plugin.py index 15c982a..4781ed1 100644 --- a/Example ZWave Listener.indigoPlugin/Contents/Server Plugin/plugin.py +++ b/Example ZWave Listener.indigoPlugin/Contents/Server Plugin/plugin.py @@ -1,8 +1,8 @@ #! /usr/bin/env python # -*- coding: utf-8 -*- #################### -# Copyright (c) 2016, Perceptive Automation, LLC. All rights reserved. -# http://www.indigodomo.com +# Copyright (c) 2022, Perceptive Automation, LLC. All rights reserved. +# https://www.indigodomo.com import indigo @@ -15,51 +15,51 @@ ######################################## # Tiny function to convert a list of integers (bytes in this case) to a # hexidecimal string for pretty logging. -def convertListToHexStr(byteList): - return ' '.join(["%02X" % byte for byte in byteList]) +def convert_list_to_hex_str(byte_list): + return ' '.join([f"{byte:02X}" for byte in byte_list]) ################################################################################ class Plugin(indigo.PluginBase): - ######################################## - def __init__(self, pluginId, pluginDisplayName, pluginVersion, pluginPrefs): - super(Plugin, self).__init__(pluginId, pluginDisplayName, pluginVersion, pluginPrefs) - self.debug = True + ######################################## + def __init__(self, plugin_id, plugin_display_name, plugin_version, plugin_prefs): + super().__init__(plugin_id, plugin_display_name, plugin_version, plugin_prefs) + self.debug = True - ######################################## - def startup(self): - self.debugLog(u"startup called -- subscribing to all incoming and outgoing Z-Wave commands") - indigo.zwave.subscribeToIncoming() - indigo.zwave.subscribeToOutgoing() + ######################################## + def startup(self): + self.logger.debug("startup called -- subscribing to all incoming and outgoing Z-Wave commands") + indigo.zwave.subscribeToIncoming() + indigo.zwave.subscribeToOutgoing() - def shutdown(self): - self.debugLog(u"shutdown called") + def shutdown(self): + self.logger.debug("shutdown called") - ######################################## - def zwaveCommandReceived(self, cmd): - byteList = cmd['bytes'] # List of the raw bytes just received. - byteListStr = convertListToHexStr(byteList) - nodeId = cmd['nodeId'] # Can be None! - endpoint = cmd['endpoint'] # Often will be None! + ######################################## + def zwaveCommandReceived(self, cmd): + byte_list = cmd['bytes'] # List of the raw bytes just received. + byte_list_str = convert_list_to_hex_str(byte_list) + node_id = cmd['nodeId'] # Can be None! + endpoint = cmd['endpoint'] # Often will be None! - if nodeId and endpoint: - self.debugLog(u"received: %s (node %03d, endpoint %s)" % (byteListStr, nodeId, endpoint)) - elif nodeId: - self.debugLog(u"received: %s (node %03d)" % (byteListStr, nodeId)) - else: - self.debugLog(u"received: %s" % (byteListStr)) + if node_id and endpoint: + self.logger.debug(f"received: {byte_list_str} (node {node_id:03d}, endpoint {endpoint})") + elif node_id: + self.logger.debug(f"received: {byte_list_str} (node {node_id:03d})") + else: + self.logger.debug(f"received: {byte_list_str}") - def zwaveCommandSent(self, cmd): - byteList = cmd['bytes'] # List of the raw bytes just sent. - byteListStr = convertListToHexStr(byteList) - timeDelta = cmd['timeDelta'] # The time duration it took to receive an Z-Wave ACK for the command. - cmdSuccess = cmd['cmdSuccess'] # True if an ACK was received (or no ACK expected), false if NAK. - nodeId = cmd['nodeId'] # Can be None! - endpoint = cmd['endpoint'] # Often will be None! + def zwaveCommandSent(self, cmd): + byte_list = cmd['bytes'] # List of the raw bytes just sent. + byte_list_str = convert_list_to_hex_str(byte_list) + time_delta = cmd['timeDelta'] # The time duration it took to receive an Z-Wave ACK for the command. + cmd_success = cmd['cmdSuccess'] # True if an ACK was received (or no ACK expected), false if NAK. + node_id = cmd['nodeId'] # Can be None! + # endpoint = cmd['endpoint'] # Often will be None! - if cmdSuccess: - if nodeId: - self.debugLog(u"sent: %s (node %03d ACK after %d milliseconds)" % (byteListStr, nodeId, timeDelta)) - else: - self.debugLog(u"sent: %s (ACK after %d milliseconds)" % (byteListStr, timeDelta)) - else: - self.debugLog(u"sent: %s (failed)" % (byteListStr)) + if cmd_success: + if node_id: + self.logger.debug(f"sent: {byte_list_str} (node {node_id:03d} ACK after {time_delta} milliseconds)") + else: + self.logger.debug(f"sent: {byte_list_str} (ACK after {time_delta} milliseconds)") + else: + self.logger.debug(f"sent: {byte_list_str} (failed)") diff --git a/README.md b/README.md index 213cb97..7870db3 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,6 @@ Included in this SDK are several Indigo Plugin Examples. These examples include **Development Support** -For help with plugin development, or to report any API problems or requests, join us on our [active and helpful developer forum.](http://forums.indigodomo.com/viewforum.php?f=18) +For help with plugin development, or to report any API problems or requests, join us on our [active and helpful developer forum.](https://forums.indigodomo.com/viewforum.php?f=18) Copyright © 2022 Perceptive Automation, LLC. All rights reserved.