/**
 *  Shelly 2 as Roller Shutter Device Handler
 *
 *  Copyright 2018 Scott Grayban
 *
 *  Licensed under the GNU GENERAL PUBLIC LICENSE version 3 (the "License"); you may not use this file except
 *  in compliance with the License. You may obtain a copy of the License at:
 *
 *      https://www.gnu.org/licenses/gpl-3.0.txt  
 *
 *  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.
 *
 *
 *
 * Shelly http POST at http://IP/roller/0 (roller 1 does not exist) with body form-urlencoded:
 *   go=open
 *   go=close
 *   go=to_pos&roller_pos=[value 0-100]
 *   go=stop
 *
 */
 
metadata {
	definition (name: "Shelly 2 as Roller Shutter", namespace: "sgrayban", author: "Scott Grayban") {
	capability "Actuator"
	capability "Sensor"
        capability "Refresh" // refresh command
        capability "Health Check"
        capability "Switch Level" // attribute: level (integer, setter: setLevel), command setLevel(level)
        capability "Switch"
        capability "Window Shade" // windowShade.value ( closed, closing, open, opening, partially open, unknown ), methods: close(), open(), presetPosition()
    
        // @TODO: this IP or preferences IP?
	    // attribute "IP", "string"
        command "stop"
	}

	tiles(scale: 2) {
        multiAttributeTile(name:"windowShade", type: "generic", width: 6, height: 4){
            tileAttribute ("device.windowShade", key: "PRIMARY_CONTROL") {
                attributeState "unknown", label:'${name}', action:"close", icon:"st.shades.shade-closed", backgroundColor:"#ffffff", nextState:"closing"
                attributeState "open", label:'${name}', action:"close", icon:"st.shades.shade-open", backgroundColor:"#79b821", nextState:"closing"
                attributeState "closed", label:'${name}', action:"open", icon:"st.shades.shade-closed", backgroundColor:"#ffffff", nextState:"opening"
                attributeState "partially open", label:'${name}', action:"close", icon:"st.shades.shade-open", backgroundColor:"#79b821", nextState:"closing"
                attributeState "opening", label:'${name}', action:"stop", icon:"st.shades.shade-opening", backgroundColor:"#79b821", nextState:"partially open"
                attributeState "closing", label:'${name}', action:"stop", icon:"st.shades.shade-closing", backgroundColor:"#ffffff", nextState:"partially open"
            }
            tileAttribute ("device.level", key: "SLIDER_CONTROL") {
                attributeState "level", action:"setLevel", label:'${currentValue} %'
            }
        }

        standardTile("up", "device.level", width: 2, height: 2, decoration: "flat") {
            state "default", label: "open", action:"open", icon:"st.shades.shade-open"
        }

        standardTile("home", "device.level", width: 2, height: 2, decoration: "flat") {
            state "default", label: "preset", action:"presetPosition", icon:"st.Home.home9" //st.Home.home9
        }

        standardTile("down", "device.level", width: 2, height: 2, decoration: "flat") {
            state "default", label: "close", action:"close", icon:"st.shades.shade-closed"
        }


        standardTile("refresh", "device.refresh", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
            state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh", nextState: "disabled"
            state "disabled", label:'', action:"", icon:"st.secondary.refresh"
        }


        preferences {
            input("ip", "string", title:"IP", description:"Shelly IP Address", defaultValue:"" , required: false, displayDuringSetup: true)
            input("preset", "number", title: "Pre-defined position (1-100)", defaultValue: 50, required: false, displayDuringSetup: true)
            input("closedif", "number", title: "Closed if at most (1-100)", defaultvalue: 5, required: false, displayDuringSetup: false)
            input("openif", "number", title: "Open if at least (1-100)", defaultvalue: 85, required: false, displayDuringSetup: false)

        }

        main(["windowShade"])
        details(["windowShade", "up", "home", "down", "refresh"])
	}
}

def getCheckInterval() {
    // These are battery-powered devices, and it's not very critical
    // to know whether they're online or not â€“ 12 hrs
    log.debug "getCheckInterval"
    return 4 * 60 * 60
}

def installed() {
    log.debug "Installed"
    sendEvent(name: "checkInterval", value: checkInterval, displayed: false)
    refresh()
}

def updated() {
    log.debug "Updated"
    if (device.latestValue("checkInterval") != checkInterval) {
        sendEvent(name: "checkInterval", value: checkInterval, displayed: false)
    }
    refresh()
}

def parse(description) {
    log.debug "Parsing result $description"
    
    def msg = parseLanMessage(description)
    def headersAsString = msg.header // => headers as a string
    def headerMap = msg.headers      // => headers as a Map
    def body = msg.body              // => request body as a string
    def status = msg.status          // => http status code of the response
    def data = msg.data              // => either JSON or XML in response body (whichever is specified by content-type header in response)
    
    log.debug "Position ${data.current_pos}%"
    log.debug "State ${data.state}"
    
    def evt1 = createEvent(name: "level", value: data.current_pos, displayed: false)
    def evt2 = null
    def evt3 = null
    if ( data.current_pos < closedif ) {
        log.debug "CreateEvent closed"
        evt2 = createEvent(name: "windowShade", value: "closed", displayed: false)
        evt3 = createEvent(name: "switch", value: "off", displayed: false)
    } else  if ( data.current_pos > openif ) {
        log.debug "CreateEvent open"
        evt2 = createEvent(name: "windowShade", value: "on", displayed: false)
        evt3 = createEvent(name: "switch", value: "on", displayed: false)
    } else {
        log.debug "CreateEvent Partially open"
        evt2 = createEvent(name: "windowShade", value: "partially open", displayed: false)
        evt3 = createEvent(name: "switch", value: "on", displayed: false)
    }

    //log.debut "Parsed to ${evt1.inspect()} and ${evt2.inspect()}"
    return [evt1, evt2]
}

def open() {
    log.debug "Executing 'open'"
    sendRollerCommand "go=open"
}

def close() {
    log.debug "Executing 'close'"
    sendRollerCommand "go=close"
}

//switch.on
def on() {
    log.debug "Executing switch.on"
    open()
}

//switch.off
def off() {
    log.debug "Executing switch.off"
    close()
}

def setLevel(value, duration = null) {
    log.debug "Executing setLevel value with $value"
    sendRollerCommand "go=to_pos&roller_pos="+value
}

def presetPosition() {
    log.debug "Executing 'presetPosition'"
    setLevel(preset)
}

def stop() {
    log.debug "Executing stop()"
    sendRollerCommand "go=to_pos&roller_pos="+value
}

def ping() {
    log.debug "Ping"
    refresh()
}

def refresh() {
    log.debug "Refresh - Getting Status"
    sendHubCommand(new physicalgraph.device.HubAction(
      method: "GET",
      path: "/roller/0",
      headers: [
        HOST: getShellyAddress(),
        "Content-Type": "application/x-www-form-urlencoded"
      ]
    ))
}

def sendRollerCommand(action) {
    log.debug "Calling /roller/0 with $action"
    sendHubCommand(new physicalgraph.device.HubAction(
      method: "POST",
      path: "/roller/0",
      body: action,
      headers: [
        HOST: getShellyAddress(),
        "Content-Type": "application/x-www-form-urlencoded"
      ]
    ))
    runIn(25, refresh)
}

private getShellyAddress() {
    def port = 80
    def iphex = ip.tokenize( '.' ).collect { String.format( '%02x', it.toInteger() ) }.join().toUpperCase()
    def porthex = String.format('%04x', port.toInteger())
    def shellyAddress = iphex + ":" + porthex
    device.deviceNetworkId = shellyAddress.toUpperCase()
    log.debug "Using IP " + ip + ", PORT 80 and HEX ADDRESS " + shellyAddress + " for device: ${device.id}"
    return device.deviceNetworkId
}
