Compare commits

...

3 Commits

Author SHA1 Message Date
Ralph Estes
e54e18bbea MSA-1315: power 2016-05-26 18:27:35 -05:00
Jason Botello
aa3a18f421 Merge pull request #865 from SmartThingsCommunity/MSA-1255-1
MSA-1255: Gidjit - Smart Launcher with SmartThings support
2016-05-25 15:13:55 -05:00
Matthew Page
ab2ba8104d MSA-1255: The following app submitted will be an endpoint for our IOS app Gidjit.
Gidjit is like having a home screen based on your environment. It scans your surroundings looking for home devices, personal electronic devices, times and locations of interest, and more. It will present a list of corresponding launcher panels that allow you to quickly access actions like open an app, control/monitor a device, execute a SmartThings Routine, or link to the control/launch screen of a device. For example based on the users location they will quickly be able to access a preset routine. We are really looking forward to having SmartThings in integration with the app. 

Link: https://itunes.apple.com/us/app/gidjit-smart-launcher/id961388659?mt=8

The app currently will not have the SmartThing's support until the next version that will be released when this is hopefully approved, as we will also have to go through the Apple approval process allowing them to test the features.

Thank you for your time!

Matt
2016-05-07 16:51:30 -05:00
2 changed files with 646 additions and 0 deletions

View File

@@ -0,0 +1,394 @@
/**
* Aeon HEM1
*
* 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.
*
* Aeon Home Energy Meter v1 (US)
*
* Version: v3.1
*
* Updates:
* -------
* 02-15-2016 : Removed posting to the Activity Feed in the phone app and event log.
* 02-17-2016 : Fixed preferences for kWh cost from string to number.
* 02-20-2016 : Enabled battery reporting (parameter 103, value 1), and documented the parameters better.
* 02-21-2016 : Made certain configuration parameters changeable via device preferences instead of having to tweak code all the time.
* 02-22-2016 : Fixed kWh cost entry in Preferences not allowing decimals.
* 02-27-2016 : Changed date formats to be MM-dd-yyyy h:mm a.
* 02-29-2016 : Changed reportType variable from 0 to 1.
* 03-11-2016 : Due to ST's v2.1.0 app totally hosing up SECONDARY_CONTROL, implemented a workaround to display that info in a separate tile.
* 03-19-2016 : Added clarity for preferences.
* 03-21-2016 : Fixed issue when resetting energy would also reset watts.
* 03-25-2016 : Removed the \n from the two tiles for resetting watta and energy due to rendering issues on iOS
*
*/
metadata {
definition (name: "My Aeon Home Energy Monitor", namespace: "jscgs350", author: "SmartThings")
{
capability "Energy Meter"
capability "Power Meter"
capability "Configuration"
capability "Sensor"
capability "Refresh"
capability "Polling"
capability "Battery"
attribute "energy", "string"
attribute "energyDisp", "string"
attribute "energyOne", "string"
attribute "energyTwo", "string"
attribute "power", "string"
attribute "powerDisp", "string"
attribute "powerOne", "string"
attribute "powerTwo", "string"
command "reset"
command "configure"
command "resetmaxmin"
fingerprint deviceId: "0x2101", inClusters: " 0x70,0x31,0x72,0x86,0x32,0x80,0x85,0x60"
}
// tile definitions
tiles(scale: 2) {
multiAttributeTile(name:"powerDisp", type: "lighting", width: 6, height: 4, decoration: "flat", canChangeIcon: true, canChangeBackground: true){
tileAttribute ("device.powerDisp", key: "PRIMARY_CONTROL") {
attributeState "default", action: "refresh", label: '${currentValue}', icon: "st.switches.light.on", backgroundColor: "#79b821"
}
tileAttribute ("statusText", key: "SECONDARY_CONTROL") {
// attributeState "statusText", label:'${currentValue}'
attributeState "statusText", label:''
}
}
valueTile("energyDisp", "device.energyDisp", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state("default", label: '${currentValue}', backgroundColor:"#ffffff")
}
valueTile("energyOne", "device.energyOne", width: 6, height: 2, inactiveLabel: false, decoration: "flat") {
state("default", label: '${currentValue}', backgroundColor:"#ffffff")
}
valueTile("energyTwo", "device.energyTwo", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state("default", label: '${currentValue}', backgroundColor:"#ffffff")
}
standardTile("refresh", "device.power", width: 3, height: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
}
standardTile("configure", "device.power", width: 3, height: 2, inactiveLabel: false, decoration: "flat") {
state "configure", label:'', action:"configure", icon:"st.secondary.configure"
}
valueTile("battery", "device.battery", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "battery", label:'${currentValue}%\nbattery', unit:""
}
valueTile("statusText", "statusText", inactiveLabel: false, decoration: "flat", width: 6, height: 2) {
state "statusText", label:'${currentValue}', backgroundColor:"#ffffff"
}
valueTile("min", "powerOne", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:'Min:\n${currentValue}', backgroundColor:"#ffffff"
}
valueTile("max", "powerTwo", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:'Max:\n${currentValue}', backgroundColor:"#ffffff"
}
standardTile("resetmaxmin", "device.energy", width: 3, height: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:'Reset Watts', action:"resetmaxmin", icon:"st.secondary.refresh-icon"
}
standardTile("reset", "device.energy", width: 3, height: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:'Reset Energy', action:"reset", icon:"st.secondary.refresh-icon"
}
main (["powerDisp"])
details(["powerDisp", "statusText", "battery", "energyDisp", "energyTwo", "energyOne", "resetmaxmin", "resetenergy", "reset", "refresh", "configure"])
}
preferences {
input "kWhCost", "string",
title: "Enter your cost per kWh (or just use the default, or use 0 to not calculate):",
defaultValue: 0.16,
required: false,
displayDuringSetup: true
input "reportType", "number",
title: "ReportType: Send watt/kWh data on a time interval (0), or on a change in wattage (1)? Enter a 0 or 1:",
defaultValue: 1,
required: false,
displayDuringSetup: true
input "wattsChanged", "number",
title: "For ReportType = 1, Don't send unless watts have changed by this many watts: (range 0 - 32,000W)",
defaultValue: 50,
required: false,
displayDuringSetup: true
input "wattsPercent", "number",
title: "For ReportType = 1, Don't send unless watts have changed by this percent: (range 0 - 99%)",
defaultValue: 10,
required: false,
displayDuringSetup: true
input "secondsWatts", "number",
title: "For ReportType = 0, Send Watts data every how many seconds? (range 0 - 65,000 seconds)",
defaultValue: 15,
required: false,
displayDuringSetup: true
input "secondsKwh", "number",
title: "For ReportType = 0, Send kWh data every how many seconds? (range 0 - 65,000 seconds)",
defaultValue: 60,
required: false,
displayDuringSetup: true
input "secondsBattery", "number",
title: "Send battery data every how many seconds? (range 0 - 65,000 seconds)",
defaultValue: 900,
required: false,
displayDuringSetup: true
}
}
def updated() {
log.debug "updated (kWhCost: ${kWhCost}, reportType: ${reportType}, wattsChanged: ${wattsChanged}, wattsPercent: ${wattsPercent}, secondsWatts: ${secondsWatts}, secondsKwh: ${secondsKwh}, secondsBattery: ${secondsBattery})"
response(configure())
}
def parse(String description) {
// log.debug "Parse received ${description}"
def result = null
def cmd = zwave.parse(description, [0x31: 1, 0x32: 1, 0x60: 3, 0x80: 1])
if (cmd) {
result = createEvent(zwaveEvent(cmd))
}
// if (result) log.debug "Parse returned ${result}"
def statusTextmsg = ""
statusTextmsg = "Min was ${device.currentState('powerOne')?.value}\nMax was ${device.currentState('powerTwo')?.value}"
sendEvent("name":"statusText", "value":statusTextmsg)
// log.debug statusTextmsg
return result
}
def zwaveEvent(physicalgraph.zwave.commands.meterv1.MeterReport cmd) {
//log.debug "zwaveEvent received ${cmd}"
def dispValue
def newValue
def timeString = new Date().format("MM-dd-yyyy h:mm a", location.timeZone)
if (cmd.meterType == 33) {
if (cmd.scale == 0) {
newValue = cmd.scaledMeterValue
if (newValue != state.energyValue) {
dispValue = String.format("%5.2f",newValue)+"\nkWh"
sendEvent(name: "energyDisp", value: dispValue as String, unit: "", displayed: false)
state.energyValue = newValue
BigDecimal costDecimal = newValue * ( kWhCost as BigDecimal)
def costDisplay = String.format("%3.2f",costDecimal)
sendEvent(name: "energyTwo", value: "Cost\n\$${costDisplay}", unit: "", displayed: false)
[name: "energy", value: newValue, unit: "kWh", displayed: false]
}
} else if (cmd.scale == 1) {
newValue = cmd.scaledMeterValue
if (newValue != state.energyValue) {
dispValue = String.format("%5.2f",newValue)+"\nkVAh"
sendEvent(name: "energyDisp", value: dispValue as String, unit: "", displayed: false)
state.energyValue = newValue
[name: "energy", value: newValue, unit: "kVAh", displayed: false]
}
}
else if (cmd.scale==2) {
newValue = Math.round( cmd.scaledMeterValue ) // really not worth the hassle to show decimals for Watts
if (newValue != state.powerValue) {
dispValue = newValue+"w"
sendEvent(name: "powerDisp", value: dispValue as String, unit: "", displayed: false)
if (newValue < state.powerLow) {
dispValue = newValue+"w"+" on "+timeString
sendEvent(name: "powerOne", value: dispValue as String, unit: "", displayed: false)
state.powerLow = newValue
}
if (newValue > state.powerHigh) {
dispValue = newValue+"w"+" on "+timeString
sendEvent(name: "powerTwo", value: dispValue as String, unit: "", displayed: false)
state.powerHigh = newValue
}
state.powerValue = newValue
[name: "power", value: newValue, unit: "W", displayed: false]
}
}
}
}
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
def map = [:]
map.name = "battery"
map.unit = "%"
if (cmd.batteryLevel == 0xFF) {
map.value = 1
map.descriptionText = "${device.displayName} has a low battery"
map.isStateChange = true
} else {
map.value = cmd.batteryLevel
sendEvent(name: "battery", value: map.value as String, displayed: false)
}
return map
}
def zwaveEvent(physicalgraph.zwave.Command cmd) {
// Handles all Z-Wave commands we aren't interested in
log.debug "Unhandled event ${cmd}"
[:]
}
def refresh() {
log.debug "Refreshed ${device.name}"
delayBetween([
zwave.meterV2.meterGet(scale: 0).format(),
zwave.meterV2.meterGet(scale: 2).format()
])
}
def poll() {
refresh()
}
def reset() {
log.debug "${device.name} reset kWh/Cost values"
def timeString = new Date().format("MM-dd-yyyy h:mm a", location.timeZone)
sendEvent(name: "energyOne", value: "Energy Data (kWh/Cost) Reset On:\n"+timeString, unit: "")
sendEvent(name: "energyDisp", value: "", unit: "")
sendEvent(name: "energyTwo", value: "Cost\n--", unit: "")
def cmd = delayBetween( [
zwave.meterV2.meterReset().format(),
zwave.meterV2.meterGet(scale: 0).format(),
zwave.meterV2.meterGet(scale: 2).format()
])
cmd
}
def resetmaxmin() {
log.debug "${device.name} reset max/min values"
state.powerHigh = 0
state.powerLow = 99999
def timeString = new Date().format("MM-dd-yyyy h:mm a", location.timeZone)
sendEvent(name: "energyOne", value: "Watts Data (min/max) Reset On:\n"+timeString, unit: "")
sendEvent(name: "powerOne", value: "", unit: "")
sendEvent(name: "powerTwo", value: "", unit: "")
def cmd = delayBetween( [
zwave.meterV2.meterGet(scale: 0).format(),
zwave.meterV2.meterGet(scale: 2).format()
])
cmd
}
def configure() {
log.debug "${device.name} configuring..."
if (reportType == 0) {
log.debug "Setting reportType to ${reportType} per user request."
} else if (reportType == 1) {
log.debug "Setting reportType to ${reportType} per user request."
}
else {
def reportType = 1
log.debug "Setting reportType to ${reportType} because an invalid value was provided."
}
if (wattsChanged < 0) {
def wattsChanged = 50
log.debug "Setting wattsChanged to ${wattsChanged} (device default) because an invalid value was provided."
} else if (wattsChanged < 32001) {
log.debug "Setting wattsChanged to ${wattsChanged} per user request."
}
else {
def wattsChanged = 50
log.debug "Setting wattsChanged to ${wattsChanged} (device default) because an invalid value was provided."
}
if (wattsPercent < 0) {
def wattsPercent = 10
log.debug "Setting wattsPercent to ${wattsPercent} (device default) because an invalid value was provided."
} else if (wattsPercent < 100) {
log.debug "Setting wattsPercent to ${wattsPercent} per user request."
}
else {
def wattsPercent = 10
log.debug "Setting wattsPercent to ${wattsPercent} (device default) because an invalid value was provided."
}
if (secondsWatts < 0) {
def secondsWatts = 600
log.debug "Setting secondsWatts to ${secondsWatts} (device default) because an invalid value was provided."
} else if (secondsWatts < 65000) {
log.debug "Setting secondsWatts to ${secondsWatts} per user request."
}
else {
def secondsWatts = 600
log.debug "Setting secondsWatts to ${secondsWatts} (device default) because an invalid value was provided."
}
if (secondsKwh < 0) {
def secondsKwh = 600
log.debug "Setting secondsKwh to ${secondsKwh} (device default) because an invalid value was provided."
} else if (secondsKwh < 65000) {
log.debug "Setting secondsKwh to ${secondsKwh} per user request."
}
else {
def secondsKwh = 600
log.debug "Setting secondsKwh to ${secondsKwh} (device default) because an invalid value was provided."
}
if (secondsBattery < 0) {
def secondsBattery = 3600
log.debug "Setting secondsBattery to ${secondsBattery} (device default) because an invalid value was provided."
} else if (secondsBattery < 65000) {
log.debug "Setting secondsBattery to ${secondsBattery} per user request."
}
else {
def secondsBattery = 3600
log.debug "Setting secondsBattery to ${secondsBattery} (device default) because an invalid value was provided."
}
def cmd = delayBetween([
// Performs a complete factory reset. Use this all by itself and comment out all others below. Once reset, comment this line out and uncomment the others to go back to normal
// zwave.configurationV1.configurationSet(parameterNumber: 255, size: 4, scaledConfigurationValue: 1).format()
// Send data based on a time interval (0), or based on a change in wattage (1). 0 is default and enables parameters 111, 112, and 113. 1 enables parameters 4 and 8.
zwave.configurationV1.configurationSet(parameterNumber: 3, size: 1, scaledConfigurationValue: reportType).format(),
// If parameter 3 is 1, don't send unless watts have changed by 50 <default> for the whole device.
zwave.configurationV1.configurationSet(parameterNumber: 4, size: 2, scaledConfigurationValue: wattsChanged).format(),
// If parameter 3 is 1, don't send unless watts have changed by 10% <default> for the whole device.
zwave.configurationV1.configurationSet(parameterNumber: 8, size: 1, scaledConfigurationValue: wattsPercent).format(),
// Defines the type of report sent for Reporting Group 1 for the whole device. 1->Battery Report, 4->Meter Report for Watt, 8->Meter Report for kWh
zwave.configurationV1.configurationSet(parameterNumber: 101, size: 4, scaledConfigurationValue: 4).format(), //watts
// If parameter 3 is 0, report every XX Seconds (for Watts) for Reporting Group 1 for the whole device.
zwave.configurationV1.configurationSet(parameterNumber: 111, size: 4, scaledConfigurationValue: secondsWatts).format(),
// Defines the type of report sent for Reporting Group 2 for the whole device. 1->Battery Report, 4->Meter Report for Watt, 8->Meter Report for kWh
zwave.configurationV1.configurationSet(parameterNumber: 102, size: 4, scaledConfigurationValue: 8).format(), //kWh
// If parameter 3 is 0, report every XX seconds (for kWh) for Reporting Group 2 for the whole device.
zwave.configurationV1.configurationSet(parameterNumber: 112, size: 4, scaledConfigurationValue: secondsKwh).format(),
// Defines the type of report sent for Reporting Group 3 for the whole device. 1->Battery Report, 4->Meter Report for Watt, 8->Meter Report for kWh
zwave.configurationV1.configurationSet(parameterNumber: 103, size: 4, scaledConfigurationValue: 1).format(), //battery
// If parameter 3 is 0, report every XX seconds (for battery) for Reporting Group 2 for the whole device.
zwave.configurationV1.configurationSet(parameterNumber: 113, size: 4, scaledConfigurationValue: secondsBattery).format()
])
cmd
}

View File

@@ -0,0 +1,252 @@
/**
* Gidjit Hub
*
* Copyright 2016 Matthew Page
*
* 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.
*
*/
definition(
name: "Gidjit Hub",
namespace: "com.gidjit.smartthings.hub",
author: "Matthew Page",
description: "Act as an endpoint so user's of Gidjit can quickly access and control their devices and execute routines. Users can do this quickly as Gidjit filters these actions based on their environment",
category: "Convenience",
iconUrl: "http://www.gidjit.com/appicon.png",
iconX2Url: "http://www.gidjit.com/appicon@2x.png",
iconX3Url: "http://www.gidjit.com/appicon@3x.png",
oauth: [displayName: "Gidjit", displayLink: "www.gidjit.com"])
preferences {
section ("Allow Gidjit to have access, there by allowing you to quickly control and monitor the following devices") {
input "switches", "capability.switch", title: "Control/Monitor your switches", multiple: true, required: false
input "thermostats", "capability.thermostat", title: "Control/Monitor your thermostats", multiple: true, required: false
input "windowShades", "capability.windowShade", title: "Control/Monitor your window shades", multiple: true, required: false //windowShade
//input "bulbs", "capability.colorControl", title: "Control your lights", multiple: true, required: false //windowShade
}
}
mappings {
path("/structureinfo") {
action: [
GET: "structureInfo"
]
}
path("/helloactions") {
action: [
GET: "helloActions"
]
}
path("/helloactions/:label") {
action: [
PUT: "executeAction"
]
}
path("/switch/:id/:command") {
action: [
PUT: "updateSwitch"
]
}
path("/thermostat/:id/:command") {
action: [
PUT: "updateThermostat"
]
}
path("/windowshade/:id/:command") {
action: [
PUT: "updateWindowShade"
]
}
path("/acquiredata/:id") {
action: [
GET: "acquiredata"
]
}
}
def installed() {
log.debug "Installed with settings: ${settings}"
initialize()
}
def updated() {
log.debug "Updated with settings: ${settings}"
unsubscribe()
initialize()
}
def initialize() {
// subscribe to attributes, devices, locations, etc.
}
def helloActions() {
def actions = location.helloHome?.getPhrases()*.label
if(!actions) {
return []
}
return actions
}
def executeAction() {
def actions = location.helloHome?.getPhrases()*.label
def a = actions?.find() { it == params.label }
if (!a) {
httpError(400, "invalid label $params.label")
return
}
location.helloHome?.execute(params.label)
}
/* this is the primary function called to query at the structure and its devices */
def structureInfo() { //list all devices
def list = [:]
def currId = location.id
list[currId] = [:]
list[currId].name = location.name
list[currId].id = location.id
list[currId].temperatureScale = location.temperatureScale
list[currId].devices = [:]
def setValues = {
if (params.brief) {
return [id: it.id, name: it.displayName]
}
def newList = [id: it.id, name: it.displayName, suppCapab: it.capabilities.collect {
"$it.name"
}, suppAttributes: it.supportedAttributes.collect {
"$it.name"
}, suppCommands: it.supportedCommands.collect {
"$it.name"
}]
return newList
}
switches?.each {
list[currId].devices[it.id] = setValues(it)
}
thermostats?.each {
list[currId].devices[it.id] = setValues(it)
}
windowShades?.each {
list[currId].devices[it.id] = setValues(it)
}
return list
}
/* This function returns all of the current values of the specified Devices attributes */
def acquiredata() {
def resp = [:]
if (!params.id) {
httpError(400, "invalid id $params.id")
return
}
def dev = switches.find() { it.id == params.id } ?: windowShades.find() { it.id == params.id } ?:
thermostats.find() { it.id == params.id }
if (!dev) {
httpError(400, "invalid id $params.id")
return
}
def att = dev.supportedAttributes
att.each {
resp[it.name] = dev.currentValue("$it.name")
}
return resp
}
void updateSwitch() {
// use the built-in request object to get the command parameter
def command = params.command
def sw = switches.find() { it.id == params.id }
if (!sw) {
httpError(400, "invalid id $params.id")
return
}
switch(command) {
case "on":
if ( sw.currentSwitch != "on" ) {
sw.on()
}
break
case "off":
if ( sw.currentSwitch != "off" ) {
sw.off()
}
break
default:
httpError(400, "$command is not a valid")
}
}
void updateThermostat() {
// use the built-in request object to get the command parameter
def command = params.command
def therm = thermostats.find() { it.id == params.id }
if (!therm || !command) {
httpError(400, "invalid id $params.id")
return
}
def passComm = [
"off",
"heat",
"emergencyHeat",
"cool",
"fanOn",
"fanAuto",
"fanCirculate",
"auto"
]
def passNumParamComm = [
"setHeatingSetpoint",
"setCoolingSetpoint",
]
def passStringParamComm = [
"setThermostatMode",
"setThermostatFanMode",
]
if (command in passComm) {
therm."$command"()
} else if (command in passNumParamComm && params.p1 && params.p1.isFloat()) {
therm."$command"(Float.parseFloat(params.p1))
} else if (command in passStringParamComm && params.p1) {
therm."$command"(params.p1)
} else {
httpError(400, "$command is not a valid command")
}
}
void updateWindowShade() {
// use the built-in request object to get the command parameter
def command = params.command
def ws = windowShades.find() { it.id == params.id }
if (!ws || !command) {
httpError(400, "invalid id $params.id")
return
}
def passComm = [
"open",
"close",
"presetPosition",
]
if (command in passComm) {
ws."$command"()
} else {
httpError(400, "$command is not a valid command")
}
}
// TODO: implement event handlers