Skip to content

Instantly share code, notes, and snippets.

@bdwilson
Created January 5, 2020 20:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bdwilson/4fa4621fd2ba782e6a037dd5a26c921b to your computer and use it in GitHub Desktop.
Save bdwilson/4fa4621fd2ba782e6a037dd5a26c921b to your computer and use it in GitHub Desktop.
/*
* Neo Smart Controller
*
* Calls URIs with HTTP GET for shade open/close/stop/favourite using the Neo Smart Controller
*
* Based on the Hubitat community driver httpGetSwitch
*/
metadata {
definition(name: "Neo Smart Controller-alpha", namespace: "bigrizzo", author: "bigrizz", importUrl: "https://raw.githubusercontent.com/bdwilson/hubitatDrivers/master/NeoSmart.groovy") {
capability "WindowShade"
capability "Switch"
capability "Actuator"
capability "ChangeLevel"
capability "Switch Level"
command "stop"
command "favorite"
command "up"
command "down"
}
}
preferences {
section("URIs") {
input "blindCode", "text", title: "Blind or Room Code (from Neo App)", required: true
input "controllerID", "text", title: "Controller ID (from Neo App)", required: true
input "controllerIP", "text", title: "Controller IP Address (from Neo App)", required: true
input "timeToClose", "number", title: "Time in seconds it takes to close the blinds completely", required: true
input "timeToFav", "number", title: "Time in seconds it takes to reach your favorite setting when closing the blinds", required: true
input name: "logEnable", type: "bool", title: "Enable debug logging", defaultValue: true
}
}
def date() {
//def date = new Date().getTime().toString().drop(6)
def origdate = new Date().getTime().toString().drop(6) // API docs say only 7 chars
def random = Math.random().toString().reverse().take(4) // get four random #'s
log.debug "origdate: ${origdate} random: ${random}"
def date = origdate.toInteger() + random.toInteger() // add 4 random #'s to millisecs
//if (logEnable) log.debug "Using ${date}"
return date
}
def get(url,state) {
log.debug "Call to ${url}; setting ${state}"
try {
httpGet(url) { resp ->
if (resp.success) {
sendEvent(name: "windowShade", value: "${state}", isStateChange: true)
}
if (logEnable)
if (resp.data) log.debug "${resp.data}"
}
} catch (Exception e) {
log.warn "Call to ${url} failed: ${e.message}"
}
}
def logsOff() {
log.warn "debug logging disabled..."
device.updateSetting("logEnable", [value: "false", type: "bool"])
}
def installed() {
log.info "installed..."
if (!controllerID || !controllerIP || !blindCode || !timeToClose || !timeToFav) {
log.error "Please make sure controller ID, IP, blind/room code, time to close and time to favorite are configured."
}
log.warn "debug logging is: ${logEnable == true}"
if (logEnable) runIn(1800, logsOff)
}
def updated() {
log.info "updated..."
if (!controllerID || !controllerIP || !blindCode || !timeToClose || !timeToFav) {
log.error "Please make sure controller ID, IP, blind/room code, time to close and time to favorite are configured."
}
log.warn "debug logging is: ${logEnable == true}"
if (logEnable) runIn(1800, logsOff)
}
def parse(String description) {
if (logEnable) log.debug(description)
}
def up() {
startLevelChange("up")
}
def down() {
startLevelChange("down")
}
def on() {
open()
}
def off() {
close()
}
def close() {
url = "http://" + controllerIP + ":8838/neo/v1/transmit?command=" + blindCode + "-dn&id=" + controllerID + "&hash=" + date()
if (logEnable) log.debug "Sending close GET request to ${url}"
sendEvent(name: "windowShade", value: "closing", isStateChange: true)
UpdateTimeRunning()
if (state.lastCmd != "stop") { // someone went from open to close without stopping. how do we deal?
}
get(url,"closed")
state.level=0
state.secs=timeToClose // this is being set before we know it's closed...
state.lastCmd = close
sendEvent(name: "level", value: "${state.level}", isStateChange: true)
}
def open() {
url = "http://" + controllerIP + ":8838/neo/v1/transmit?command=" + blindCode + "-up&id=" + controllerID + "&hash=" + date()
if (logEnable) log.debug "Sending open GET request to ${url}"
sendEvent(name: "windowShade", value: "opening", isStateChange: true)
UpdateTimeRunning()
if (state.lastCmd != "stop") { // someone went from close to open without stopping. how do we deal?
}
get(url,"open")
state.level=100
state.secs=0
state.lastCmd = open
sendEvent(name: "level", value: "${state.level}", isStateChange: true)
}
def stop() {
// todo, it would be nice if we could start a timer when someone opens/closes so if they stop we have an idea of where the shade is
// then we could reflect it by setting the level here...
url = "http://" + controllerIP + ":8838/neo/v1/transmit?command=" + blindCode + "-sp&id=" + controllerID + "&hash=" + date()
if (logEnable) log.debug "Sending stop GET request to ${url}"
get(url,"partially open")
UpdateTimeRunning()
state.lastCmd = stop
state.level=100-((state.secs/timeToClose)*100)
}
def UpdateTimeRunning() {
def now = new Date().getTime()
if (state.stateChangeTime) {
def timeRunning = now.toInteger()-state.stateChangeTime.toInteger()
log.debug "Found that ${now} - ${state.stateChangeTime} = ${timeRunning}"
def timeRunningSecs = timeRunning/1000 // convert MS to Secs
}
// if state.stateChangeTime is beyond how long it takes to open/close completely
// then we'll assume it's already been successful at that open/close command.
if (timeRunningSecs > timeToClose) {
log.debug "Found that ${timeRunningSecs} was greater than ${timeToClose}"
log.debug "Resetting state.stateChangeTime = ${now}, state.timeRan=0"
state.stateChangeTime = now
state.timeRan=0
} else if ((state.stateChangeTime < now) && (timeRunningSecs)) {
//def timeRunning = now - state.stateChangeTime.toInteger()
state.timeRan = timeRunningSecs
log.debug "UpdateTimeRunning- Time Running: ${timeRunningSecs} Old State.secs: ${state.secs}"
if ((state.secs != 0) && (state.secs != timeToClose)) { //
if (state.lastCmd == "close") {
state.secs=timeRunningSecs + state.secs
} else if (state.lastCmd == "open") {
state.secs=state.secs - timeRunningSecs
}
}
log.debug "UpdateTimeRunning- Time Running: ${timeRunningSecs} New State.secs: ${state.secs}"
} else {
log.debug "Setting state.stateChangeTime = ${now}"
state.stateChangeTime = now
}
}
def stopPosition() {
url = "http://" + controllerIP + ":8838/neo/v1/transmit?command=" + blindCode + "-sp&id=" + controllerID + "&hash=" + date()
if (logEnable) log.debug "Sending stop GET request to ${url}"
get(url,"partially open")
sendEvent(name: "level", value: "${state.newposition}", isStateChange: true)
log.debug "Stopped ${state.newposition} ${state.difference}"
state.level=state.newposition
state.difference=0
}
def runAndStop() {
if (state.direction == "up") {
url = "http://" + controllerIP + ":8838/neo/v1/transmit?command=" + blindCode + "-up&id=" + controllerID + "&hash=" + date()
} else {
url = "http://" + controllerIP + ":8838/neo/v1/transmit?command=" + blindCode + "-dn&id=" + controllerID + "&hash=" + date()
}
//if (logEnable) log.debug "Adjusting ${direction} to ${position} for ${secs} request to ${url}"
log.debug "Adjusting ${state.direction} to ${state.newposition} for ${state.difference} request to ${url}"
get(url,"partially open")
runInMillis(state.difference.toInteger(),stopPosition)
}
def favorite() {
url = "http://" + controllerIP + ":8838/neo/v1/transmit?command=" + blindCode + "-gp&id=" + controllerID + "&hash=" + date()
if (logEnable) log.debug "Sending favorite GET request to ${url}"
state.secs=timeToFav
state.level=100-((timeToFav/timeToClose)*100)
get(url,"partially open")
sendEvent(name: "level", value: "${state.level}", isStateChange: true)
}
def setPosition(position) { // timeToClose= closed/down, 0=open/up
//log.debug "Opening to ${blindCode} to ${position}"
secs = timeToClose-((position/100)*timeToClose) // get percentage based on how long it takes to open.
log.debug "Setting Position for ${blindCode} to ${position} (maps to travel time from open of ${secs} secs)"
if (secs >= timeToClose) {
secs = timeToClose
}
if (position != state.level) {
state.lastCmd="partial"
log.debug "Position: ${position} StateLevel: ${state.level} StateSecs: ${state.secs} Secs: ${secs} Last Cmd: ${partial}"
if ((position > state.level) || (state.secs > secs)) { // requested location is more closed than current.
if (position == 100) {
open()
state.level=100
state.secs=0
} else {
pos = ((state.secs - secs) * 1000) //-2000
if (pos < 1000) {
pos = 1000
}
state.direction = "up"
state.difference = pos
state.newposition = position
state.secs = secs
log.debug "Opening... Stopping at ${pos} secs"
runInMillis(10,runAndStop)
}
} else { // location is less closed than current
if (position == 0) {
close()
state.level=0
state.secs=timeToClose
} else {
def pos = ((secs - state.secs)*1000) //-2000
if (pos < 1000) {
pos = 1000
}
state.direction = "down"
state.difference = pos
state.newposition = position
state.secs = secs
log.debug "Closing... Stopping at ${pos} secs"
runInMillis(10,runAndStop)
}
}
}
}
def startLevelChange(direction) {
// https://github.com/hubitat/HubitatPublic/blob/master/examples/drivers/genericComponentDimmer.groovy
if (direction == "up") {
url = "http://" + controllerIP + ":8838/neo/v1/transmit?command=" + blindCode + "-mu&id=" + controllerID + "&hash=" + date()
} else {
url = "http://" + controllerIP + ":8838/neo/v1/transmit?command=" + blindCode + "-md&id=" + controllerID + "&hash=" + date()
}
if (logEnable) log.debug "Sending startLevel Change ${direction} GET request to ${url}"
get(url,"partially open")
}
def setLevel(level) {
setPosition(level)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment