-
-
Save mew1033/1bd287b4d6dd99fe8c1d42220893de23 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* ST_Anything_Ethernet.device.groovy | |
* | |
* Copyright 2017 Dan G Ogorchock | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except | |
* in compliance with the License. You may obtain a copy of the License at: | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed | |
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License | |
* for the specific language governing permissions and limitations under the License. | |
* | |
* Change History: | |
* | |
* Date Who What | |
* ---- --- ---- | |
* 2017-02-08 Dan Ogorchock Original Creation | |
* 2017-02-12 Dan Ogorchock Modified to work with Ethernet based devices instead of ThingShield | |
* | |
*/ | |
metadata { | |
definition (name: "ST_Anything_Ethernet", namespace: "ogiewon", author: "Dan Ogorchock") { | |
capability "Configuration" | |
capability "Illuminance Measurement" | |
capability "Temperature Measurement" | |
capability "Relative Humidity Measurement" | |
capability "Motion Sensor" | |
capability "Switch" | |
capability "Sensor" | |
capability "Alarm" | |
capability "Contact Sensor" | |
capability "Polling" | |
capability "Button" | |
capability "Holdable Button" | |
capability "Switch Level" | |
capability "Actuator" | |
capability "Color Control" | |
capability "Light" | |
command "test" | |
command "generateEvent", ["string", "string"] | |
command "softwhite" | |
command "daylight" | |
command "warmwhite" | |
command "red" | |
command "green" | |
command "blue" | |
command "cyan" | |
command "magenta" | |
command "orange" | |
command "purple" | |
command "yellow" | |
command "white" | |
} | |
simulator { | |
} | |
// Preferences | |
preferences { | |
input "ip", "text", title: "Arduino IP Address", description: "ip", required: true, displayDuringSetup: true | |
input "port", "text", title: "Arduino Port", description: "port", required: true, displayDuringSetup: true | |
input "mac", "text", title: "Arduino MAC Addr", description: "mac", required: true, displayDuringSetup: true | |
input "illuminanceSampleRate", "number", title: "Light Sensor Inputs", description: "Sampling Interval (seconds)", defaultValue: 30, required: true, displayDuringSetup: true | |
input "temphumidSampleRate", "number", title: "Temperature/Humidity Sensor Inputs", description: "Sampling Interval (seconds)", defaultValue: 30, required: true, displayDuringSetup: true | |
input "waterSampleRate", "number", title: "Water Sensor Inputs", description: "Sampling Interval (seconds)", defaultValue: 30, required: true, displayDuringSetup: true | |
input "numButtons", "number", title: "Number of Buttons", description: "Number of Buttons to be implemented", defaultValue: 0, required: true, displayDuringSetup: true | |
} | |
// Tile Definitions | |
tiles(scale: 2) { | |
valueTile("illuminance", "device.illuminance1", width: 2, height: 2) { | |
state("illuminance", label:'${currentValue} ${unit}', unit:"lux", | |
backgroundColors:[ | |
[value: 9, color: "#767676"], | |
[value: 315, color: "#ffa81e"], | |
[value: 1000, color: "#fbd41b"] | |
] | |
) | |
} | |
valueTile("temperature", "device.temperature1", width: 6, height: 4) { | |
state("temperature", label:'${currentValue}', | |
backgroundColors:[ | |
[value: 31, color: "#153591"], | |
[value: 44, color: "#1e9cbb"], | |
[value: 59, color: "#90d2a7"], | |
[value: 74, color: "#44b621"], | |
[value: 84, color: "#f1d801"], | |
[value: 95, color: "#d04e00"], | |
[value: 96, color: "#bc2323"] | |
] | |
) | |
} | |
valueTile("humidity", "device.humidity1", width: 2, height: 2, inactiveLabel: false) { | |
state "humidity", label:'${currentValue}% humidity', unit:"" | |
} | |
standardTile("motion", "device.motion1", width: 2, height: 2) { | |
state("active", label:'motion', icon:"st.motion.motion.active", backgroundColor:"#53a7c0") | |
state("inactive", label:'no motion', icon:"st.motion.motion.inactive", backgroundColor:"#ffffff") | |
} | |
standardTile("configure", "device.configure", width: 2, height: 2, inactiveLabel: false, decoration: "flat") { | |
state "configure", label:'', action:"configuration.configure", icon:"st.secondary.configure" | |
} | |
multiAttributeTile(name:"rgbSwitch1", type: "lighting", width: 6, height: 4, canChangeIcon: true){ | |
tileAttribute ("device.rgbSwitch1", key: "PRIMARY_CONTROL") { | |
attributeState "on", label:'${name}', action:"switch.off", icon:"st.illuminance.illuminance.bright", backgroundColor:"#00A0DC", nextState:"turningOff" | |
attributeState "off", label:'${name}', action:"switch.on", icon:"st.illuminance.illuminance.dark", backgroundColor:"#ffffff", nextState:"turningOn" | |
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.illuminance.illuminance.light", backgroundColor:"#00A0DC", nextState:"turningOff" | |
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.illuminance.illuminance.light", backgroundColor:"#ffffff", nextState:"turningOn" | |
} | |
tileAttribute ("device.level", key: "SLIDER_CONTROL") { | |
attributeState "level", action:"switch level.setLevel" | |
} | |
tileAttribute ("device.color", key: "COLOR_CONTROL") { | |
attributeState "color", action:"color control.setColor" | |
} | |
} | |
valueTile("lastUpdated", "device.lastUpdated", inactiveLabel: false, decoration: "flat", width: 6, height: 2) { | |
state "default", label:'Last Updated ${currentValue}', backgroundColor:"#ffffff" | |
} | |
standardTile("softwhite", "device.softwhite", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false) { | |
state "offsoftwhite", label:"soft white", action:"softwhite", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" | |
state "onsoftwhite", label:"soft white", action:"softwhite", icon:"st.illuminance.illuminance.bright", backgroundColor:"#FFF1E0" | |
} | |
standardTile("daylight", "device.daylight", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false) { | |
state "offdaylight", label:"daylight", action:"daylight", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" | |
state "ondaylight", label:"daylight", action:"daylight", icon:"st.illuminance.illuminance.bright", backgroundColor:"#FFFFFB" | |
} | |
standardTile("warmwhite", "device.warmwhite", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false) { | |
state "offwarmwhite", label:"warm white", action:"warmwhite", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" | |
state "onwarmwhite", label:"warm white", action:"warmwhite", icon:"st.illuminance.illuminance.bright", backgroundColor:"#FFF4E5" | |
} | |
standardTile("red", "device.red", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false) { | |
state "offred", label:"red", action:"red", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" | |
state "onred", label:"red", action:"red", icon:"st.illuminance.illuminance.bright", backgroundColor:"#FF0000" | |
} | |
standardTile("green", "device.green", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false) { | |
state "offgreen", label:"green", action:"green", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" | |
state "ongreen", label:"green", action:"green", icon:"st.illuminance.illuminance.bright", backgroundColor:"#00FF00" | |
} | |
standardTile("blue", "device.blue", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false) { | |
state "offblue", label:"blue", action:"blue", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" | |
state "onblue", label:"blue", action:"blue", icon:"st.illuminance.illuminance.bright", backgroundColor:"#0000FF" | |
} | |
standardTile("cyan", "device.cyan", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false) { | |
state "offcyan", label:"cyan", action:"cyan", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" | |
state "oncyan", label:"cyan", action:"cyan", icon:"st.illuminance.illuminance.bright", backgroundColor:"#00FFFF" | |
} | |
standardTile("magenta", "device.magenta", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false) { | |
state "offmagenta", label:"magenta", action:"magenta", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" | |
state "onmagenta", label:"magenta", action:"magenta", icon:"st.illuminance.illuminance.bright", backgroundColor:"#FF00FF" | |
} | |
standardTile("orange", "device.orange", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false) { | |
state "offorange", label:"orange", action:"orange", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" | |
state "onorange", label:"orange", action:"orange", icon:"st.illuminance.illuminance.bright", backgroundColor:"#FF6600" | |
} | |
standardTile("purple", "device.purple", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false) { | |
state "offpurple", label:"purple", action:"purple", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" | |
state "onpurple", label:"purple", action:"purple", icon:"st.illuminance.illuminance.bright", backgroundColor:"#BF00FF" | |
} | |
standardTile("yellow", "device.yellow", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false) { | |
state "offyellow", label:"yellow", action:"yellow", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" | |
state "onyellow", label:"yellow", action:"yellow", icon:"st.illuminance.illuminance.bright", backgroundColor:"#FFFF00" | |
} | |
standardTile("white", "device.white", width: 2, height: 2, inactiveLabel: false, canChangeIcon: false) { | |
state "offwhite", label:"White", action:"white", icon:"st.illuminance.illuminance.dark", backgroundColor:"#D8D8D8" | |
state "onwhite", label:"White", action:"white", icon:"st.illuminance.illuminance.bright", backgroundColor:"#FFFFFF" | |
} | |
main("temperature") | |
details(["temperature","motion","humidity","illuminance","configure","rgbSwitch1", "level", "color", "softwhite","daylight","warmwhite","red","green","blue","white","cyan", | |
"magenta","orange","purple","yellow","test","lastUpdated"]) | |
} | |
} | |
// parse events into attributes | |
def parse(String description) { | |
//log.debug "Parsing '${description}'" | |
def msg = parseLanMessage(description) | |
def headerString = msg.header | |
if (!headerString) { | |
//log.debug "headerstring was null for some reason :(" | |
} | |
def bodyString = msg.body | |
if (bodyString) { | |
log.debug "BodyString: $bodyString" | |
def parts = bodyString.split(" ") | |
def name = parts.length>0?parts[0].trim():null | |
def value = parts.length>1?parts[1].trim():null | |
def results = [] | |
if (name.startsWith("button")) { | |
def pieces = name.split(":") | |
def btnName = pieces.length>0?pieces[0].trim():null | |
def btnNum = pieces.length>1?pieces[1].trim():null | |
//log.debug "In parse: name = ${name}, value = ${value}, btnName = ${btnName}, btnNum = ${btnNum}" | |
results = createEvent([name: btnName, value: value, data: [buttonNumber: btnNum], descriptionText: "${btnName} ${btnNum} was ${value} ", isStateChange: true, displayed: true]) | |
} | |
else { | |
results = createEvent(name: name, value: value) | |
} | |
log.debug results | |
return results | |
} | |
} | |
private getHostAddress() { | |
def ip = settings.ip | |
def port = settings.port | |
log.debug "Using ip: ${ip} and port: ${port} for device: ${device.id}" | |
return ip + ":" + port | |
} | |
def sendEthernet(message) { | |
log.debug "Executing 'sendEthernet' ${message}" | |
new physicalgraph.device.HubAction( | |
method: "POST", | |
path: "/${message}?", | |
headers: [ HOST: "${getHostAddress()}" ] | |
) | |
} | |
// handle commands | |
def poll() { | |
//temporarily implement poll() to issue a configure() command to send the polling interval settings to the arduino | |
configure() | |
} | |
def configure() { | |
log.debug "Executing 'configure'" | |
updateDeviceNetworkID() | |
//log.debug "illuminance " + illuminanceSampleRate + "|temphumid " + temphumidSampleRate + "|water " + waterSampleRate | |
log.debug "water " + waterSampleRate | |
log.debug "illuminance " + illuminanceSampleRate | |
log.debug "temphumid " + temphumidSampleRate | |
[ | |
sendEthernet("water1 " + waterSampleRate), | |
"delay 1000", | |
sendEthernet("illuminance1 " + illuminanceSampleRate), | |
"delay 1000", | |
sendEthernet("temphumid1 " + temphumidSampleRate) | |
] | |
} | |
def updateDeviceNetworkID() { | |
log.debug "Executing 'updateDeviceNetworkID'" | |
if(device.deviceNetworkId!=mac) { | |
log.debug "setting deviceNetworkID = ${mac}" | |
device.setDeviceNetworkId("${mac}") | |
} | |
} | |
def updated() { | |
if (!state.updatedLastRanAt || now() >= state.updatedLastRanAt + 5000) { | |
state.updatedLastRanAt = now() | |
log.debug "Executing 'updated'" | |
runIn(3, updateDeviceNetworkID) | |
} | |
else { | |
log.trace "updated(): Ran within last 5 seconds so aborting." | |
} | |
} | |
def initialize() { | |
def noop = "Nothing" | |
} | |
// Stuff for the rgbswitch: | |
def on() { | |
sendEvent(name: "switch", value: "on") | |
def lastColor = device.latestValue("color") | |
//log.debug("On pressed. Sending last known color value of $lastColor or if null command to white.") | |
// parent.childOn(device.deviceNetworkId) | |
sendEthernet("rgbSwitch1 on") | |
if ( lastColor == Null ) { // For initial run | |
white() | |
} else { | |
//log.debug "on event lastColor: $lastColor" | |
adjustColor(lastColor) | |
} | |
} | |
def off() { | |
toggleTiles("off") | |
sendEvent(name: "switch", value: "off") | |
//log.debug("Off pressed. Update parent device.") | |
// parent.childOff(device.deviceNetworkId) | |
sendEthernet("rgbSwitch1 off") | |
} | |
def setColor(Map color) { | |
log.debug "raw color map passed in: ${color}" | |
// Turn off the hard color tiles | |
toggleTiles("off") | |
// If the color picker was selected we will have Red, Green, Blue, HEX, Hue, Saturation, and Alpha all present. | |
// Any other control will most likely only have Hue and Saturation | |
if (color.hex){ | |
// came from the color picker. Since the color selector takes into account lightness we have to reconvert | |
// the color values and adjust the level slider to better represent where it should be at based on the color picked | |
def colorHSL = rgbToHSL(color) | |
//log.debug "colorHSL: $colorHSL" | |
sendEvent(name: "level", value: (colorHSL.l * 100)) | |
adjustColor(color.hex) | |
} else if (color.hue || color.saturation) { | |
// came from the ST cloud which only contains hue and saturation. So convert to hex and pass it. | |
def colorRGB = hslToRGB(color.hue, color.saturation, 0.5) | |
def colorHEX = rgbToHex(colorRGB) | |
adjustColor(colorHEX) | |
} | |
} | |
def setLevel(value) { | |
def level = Math.min(value as Integer, 100) | |
// log.debug("Level value in percentage: $level") | |
sendEvent(name: "level", value: level) | |
// Turn on or off based on level selection | |
if (level == 0) { | |
off() | |
} else { | |
if (device.latestValue("switch") == "off") { on() } | |
def color = device.latestValue("color") | |
adjustColor(color) | |
} | |
} | |
def adjustColor(colorInHEX) { | |
sendEvent(name: "color", value: colorInHEX) | |
def level = device.latestValue("level") | |
// log.debug("level value is $level") | |
if(level == null){level = 50} | |
//log.debug "level from adjustColor routine: ${level}" | |
//log.debug "color from adjustColor routine: ${colorInHEX}" | |
def c = hexToRgb(colorInHEX) | |
def r = hex(c.red * (level/100)) | |
def g = hex(c.green * (level/100)) | |
def b = hex(c.blue * (level/100)) | |
def adjustedColor = "#${r}${g}${b}" | |
log.debug("Adjusted color is $adjustedColor") | |
// parent.childSetColorRGB(device.deviceNetworkId, adjustedColor) | |
sendEthernet("rgbSwitch1 " + adjustedColor) | |
} | |
def generateEvent(String name, String value) { | |
//log.debug("Passed values to routine generateEvent in device named $device: Name - $name - Value - $value") | |
// The name coming in from ST_Anything will be "dimmerSwitch", but we want to the ST standard "switch" attribute for compatibility with normal SmartApps | |
sendEvent(name: "switch", value: value) | |
// Update lastUpdated date and time | |
def nowDay = new Date().format("MMM dd", location.timeZone) | |
def nowTime = new Date().format("h:mm a", location.timeZone) | |
sendEvent(name: "lastUpdated", value: nowDay + " at " + nowTime, displayed: false) | |
} | |
def doColorButton(colorName) { | |
toggleTiles(colorName.toLowerCase().replaceAll("\\s","")) | |
def colorButtonData = getColorData(colorName) | |
adjustColor(colorButtonData) | |
} | |
def getColorData(colorName) { | |
//log.debug "getColorData colorName: ${colorName}" | |
def colorRGB = colorNameToRgb(colorName) | |
//log.debug "getColorData colorRGB: $colorRGB" | |
def colorHEX = rgbToHex(colorRGB) | |
//log.debug "getColorData colorHEX: $colorHEX" | |
colorHEX | |
} | |
private hex(value, width=2) { | |
def s = new BigInteger(Math.round(value).toString()).toString(16) | |
while (s.size() < width) { | |
s = "0" + s | |
} | |
s | |
} | |
def hexToRgb(colorHex) { | |
//log.debug("passed in colorHex: $colorHex") | |
def rrInt = Integer.parseInt(colorHex.substring(1,3),16) | |
def ggInt = Integer.parseInt(colorHex.substring(3,5),16) | |
def bbInt = Integer.parseInt(colorHex.substring(5,7),16) | |
def colorData = [:] | |
colorData = [red: rrInt, green: ggInt, blue: bbInt] | |
colorData | |
} | |
def rgbToHex(rgb) { | |
//log.debug "rgbToHex rgb value: $rgb" | |
def r = hex(rgb.red) | |
def g = hex(rgb.green) | |
def b = hex(rgb.blue) | |
def hexColor = "#${r}${g}${b}" | |
hexColor | |
} | |
def rgbToHSL(color) { | |
def r = color.red / 255 | |
def g = color.green / 255 | |
def b = color.blue / 255 | |
def h = 0 | |
def s = 0 | |
def l = 0 | |
def var_min = [r,g,b].min() | |
def var_max = [r,g,b].max() | |
def del_max = var_max - var_min | |
l = (var_max + var_min) / 2 | |
if (del_max == 0) { | |
h = 0 | |
s = 0 | |
} else { | |
if (l < 0.5) { s = del_max / (var_max + var_min) } | |
else { s = del_max / (2 - var_max - var_min) } | |
def del_r = (((var_max - r) / 6) + (del_max / 2)) / del_max | |
def del_g = (((var_max - g) / 6) + (del_max / 2)) / del_max | |
def del_b = (((var_max - b) / 6) + (del_max / 2)) / del_max | |
if (r == var_max) { h = del_b - del_g } | |
else if (g == var_max) { h = (1 / 3) + del_r - del_b } | |
else if (b == var_max) { h = (2 / 3) + del_g - del_r } | |
if (h < 0) { h += 1 } | |
if (h > 1) { h -= 1 } | |
} | |
def hsl = [:] | |
hsl = [h: h * 100, s: s * 100, l: l] | |
hsl | |
} | |
def hslToRGB(float var_h, float var_s, float var_l) { | |
float h = var_h / 100 | |
float s = var_s / 100 | |
float l = var_l | |
def r = 0 | |
def g = 0 | |
def b = 0 | |
if (s == 0) { | |
r = l * 255 | |
g = l * 255 | |
b = l * 255 | |
} else { | |
float var_2 = 0 | |
if (l < 0.5) { | |
var_2 = l * (1 + s) | |
} else { | |
var_2 = (l + s) - (s * l) | |
} | |
float var_1 = 2 * l - var_2 | |
r = 255 * hueToRgb(var_1, var_2, h + (1 / 3)) | |
g = 255 * hueToRgb(var_1, var_2, h) | |
b = 255 * hueToRgb(var_1, var_2, h - (1 / 3)) | |
} | |
def rgb = [:] | |
rgb = [red: Math.round(r), green: Math.round(g), blue: Math.round(b)] | |
rgb | |
} | |
def hueToRgb(v1, v2, vh) { | |
if (vh < 0) { vh += 1 } | |
if (vh > 1) { vh -= 1 } | |
if ((6 * vh) < 1) { return (v1 + (v2 - v1) * 6 * vh) } | |
if ((2 * vh) < 1) { return (v2) } | |
if ((3 * vh) < 2) { return (v1 + (v2 - v1) * ((2 / 3 - vh) * 6)) } | |
return (v1) | |
} | |
def colorNameToRgb(color) { | |
final colors = [ | |
[name:"Soft White", red: 255, green: 241, blue: 224], | |
[name:"Daylight", red: 255, green: 255, blue: 251], | |
[name:"Warm White", red: 255, green: 244, blue: 229], | |
[name:"Red", red: 255, green: 0, blue: 0 ], | |
[name:"Green", red: 0, green: 255, blue: 0 ], | |
[name:"Blue", red: 0, green: 0, blue: 255], | |
[name:"Cyan", red: 0, green: 255, blue: 255], | |
[name:"Magenta", red: 255, green: 0, blue: 33], | |
[name:"Orange", red: 255, green: 102, blue: 0 ], | |
[name:"Purple", red: 170, green: 0, blue: 255], | |
[name:"Yellow", red: 255, green: 255, blue: 0 ], | |
[name:"White", red: 255, green: 255, blue: 255] | |
] | |
def colorData = [:] | |
colorData = colors.find { it.name == color } | |
colorData | |
} | |
def toggleTiles(color) { | |
state.colorTiles = [] | |
if ( !state.colorTiles ) { | |
state.colorTiles = ["softwhite","daylight","warmwhite","red","green","blue","cyan","magenta", | |
"orange","purple","yellow","white"] | |
} | |
def cmds = [] | |
state.colorTiles.each({ | |
if ( it == color ) { | |
//log.debug "Turning ${it} on" | |
cmds << sendEvent(name: it, value: "on${it}", displayed: True, descriptionText: "${device.displayName} ${color} is 'ON'", isStateChange: true) | |
} else { | |
//log.debug "Turning ${it} off" | |
cmds << sendEvent(name: it, value: "off${it}", displayed: false) | |
} | |
}) | |
} | |
// rows of buttons | |
def softwhite() { doColorButton("Soft White") } | |
def daylight() { doColorButton("Daylight") } | |
def warmwhite() { doColorButton("Warm White") } | |
def red() { doColorButton("Red") } | |
def green() { doColorButton("Green") } | |
def blue() { doColorButton("Blue") } | |
def cyan() { doColorButton("Cyan") } | |
def magenta() { doColorButton("Magenta") } | |
def orange() { doColorButton("Orange") } | |
def purple() { doColorButton("Purple") } | |
def yellow() { doColorButton("Yellow") } | |
def white() { doColorButton("White") } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment