Physicalgraph.device.HubAction and POST


(Robert M) #1

I’ve been battling with this for several days trying all sorts of combinations. I’m trying to do a local network POST from within a new device type (garage door controller on my Raspberry Pi). See below.

def myEventHandler() {
testpost()
}

def testpost() { //also had as private testpost
new physicalgraph.device.HubAction(""“POST /GPIO/17/value/1 HTTP/1.1\r\nHost: 192.168.1.121:8000\r\n\r\n”"", physicalgraph.device.Protocol.LAN, “c0a80179:1f40”)
//my hub’s ip and port: 192.168.1.147:39500
//ip and port trying to do POST to: 192.168.1.121:8000 which should be hex: c0a80179:1f40
}

In the log I get:
No reply configured so ignoring
3:47:46 PM:
trace
Received command 'POST /GPIO/17/value/1 HTTP/1.1
Host: 192.168.1.147
Accept: /

’ for device ‘Device Name here’

Looking at the live log on the IP I’m trying to hit shows nothing. If I use a web tool to do a POST to this internal IP (or externally available through my router) it shows traffic going through. It’s just I can’t get ST to do it.

Please help. Thanks.
Rob


(Patrick Stuart [@pstuart]) #2

For device types you need to have a DNI in hex set and subscribe to the location event to get the response. Take a look at my generic camera device in my github for examples of how to make this work.


(Robert M) #3

Thanks Patrick. I’ve actually looked extensively at your code already, but somehow something’s not working.

I’ve copied your code and made a new device and pasted it in, then I changed the device type for the device to your code. The DNI is set in hex for the device as per the IPs above.

The only part of your code I did comment out was this line:
hubAction.options = [outputMsgToS3:true]
If I just want to hit the URL with nothing else, should I comment anything else out?

I get the exact same result as my code in the log as before, see below:
POST /GPIO/17/value/1 HTTP/1.1
Accept: /
User-Agent: Linux UPnP/1.0 SmartThings
HOST: 192.168.1.121:8000

4:55:41 PM: debug The method is POST
4:55:41 PM: debug The Header is [HOST:192.168.1.121:8000]
4:55:41 PM: debug Uses which method: POST
4:55:41 PM: debug Requires Auth: false
4:55:41 PM: debug path is: /GPIO/17/value/1
4:55:41 PM: debug The device id configured is: c0a80179:1f40
4:55:41 PM: debug 1f40
4:55:41 PM: debug IP address entered is 192.168.1.121 and the converted hex code is c0a80179


(Patrick Stuart [@pstuart]) #4

without seeing the whole code, I don’t have much to go on, are you setting the device.devicenetworkid? Are you subscribing to the location event?


(Robert M) #5

To test from scratch I copied and pasted your entire code sample from github and commented out the 1 line as below.

In the Device Settings these are the details:
Device Network Id c0a80179:1f40
Hub Home Hub

Camera IP Address: 192.168.1.121
Camera Port: 8000
Camera Path to Image: /GPIO/17/value/1
Does Camera require User Auth?: false
Does Camera use a Post or Get, normally Get?: POST
Camera User:
Camera Password:

CODE:

metadata {
    definition (name: "TestPost2", namespace: "ps", author: "patrick@patrickstuart.com") {
        capability "Image Capture"
        capability "Sensor"
        capability "Actuator"
        
    }

    preferences {
    input("CameraIP", "string", title:"Camera IP Address", description: "Please enter your camera's IP Address", required: true, displayDuringSetup: true)
    input("CameraPort", "string", title:"Camera Port", description: "Please enter your camera's Port", defaultValue: 80 , required: true, displayDuringSetup: true)
    input("CameraPath", "string", title:"Camera Path to Image", description: "Please enter the path to the image", defaultValue: "/SnapshotJPEG?Resolution=640x480&Quality=Clarity", required: true, displayDuringSetup: true)
    input("CameraAuth", "bool", title:"Does Camera require User Auth?", description: "Please choose if the camera requires authentication (only basic is supported)", defaultValue: true, displayDuringSetup: true)
    input("CameraPostGet", "string", title:"Does Camera use a Post or Get, normally Get?", description: "Please choose if the camera uses a POST or a GET command to retreive the image", defaultValue: "GET", displayDuringSetup: true)
    input("CameraUser", "string", title:"Camera User", description: "Please enter your camera's username", required: false, displayDuringSetup: true)
    input("CameraPassword", "string", title:"Camera Password", description: "Please enter your camera's password", required: false, displayDuringSetup: true)
    }
    
    simulator {
    
    }

    tiles {
        standardTile("camera", "device.image", width: 1, height: 1, canChangeIcon: false, inactiveLabel: true, canChangeBackground: true) {
            state "default", label: "", action: "", icon: "st.camera.dropcam-centered", backgroundColor: "#FFFFFF"
        }

        carouselTile("cameraDetails", "device.image", width: 3, height: 2) { }

        standardTile("take", "device.image", width: 1, height: 1, canChangeIcon: false, inactiveLabel: true, canChangeBackground: false) {
            state "take", label: "Take", action: "Image Capture.take", icon: "st.camera.camera", backgroundColor: "#FFFFFF"
            state "taking", label:'Taking', action: "", icon: "st.camera.take-photo", backgroundColor: "#53a7c0"
            state "image", label: "Take", action: "Image Capture.take", icon: "st.camera.camera", backgroundColor: "#FFFFFF"
        }

        main "camera"
        details(["cameraDetails", "take", "error"])
    }
}

def parse(String description) {
    log.debug "Parsing '${description}'"
    def map = stringToMap(description)

    def result = []

    if (map.bucket && map.key) { //got a s3 pointer
        putImageInS3(map)
    }
    else if (map.headers && map.body) { //got device info response
        def headerString = new String(map.headers.decodeBase64())
        if (headerString.contains("404 Not Found")) {
            state.snapshot = "/snapshot.cgi"
           }

        if (map.body) {
        def bodyString = new String(map.body.decodeBase64())
        def body = new XmlSlurper().parseText(bodyString)
        def productName = body?.productName?.text()
        if (productName) {
            log.trace "Product Name: $productName"
            state.snapshot = ""
        }
    }
    }

    result
}

def putImageInS3(map) {

    def s3ObjectContent

    try {
        def imageBytes = getS3Object(map.bucket, map.key + ".jpg")

        if(imageBytes)
        {
            s3ObjectContent = imageBytes.getObjectContent()
            def bytes = new ByteArrayInputStream(s3ObjectContent.bytes)
            storeImage(getPictureName(), bytes)
        }
    }
    catch(Exception e) {
        log.error e
    }
    finally {
        if (s3ObjectContent) { s3ObjectContent.close() }
    }
}

// handle commands
def take() {
    def userpassascii = "${CameraUser}:${CameraPassword}"
    def userpass = "Basic " + userpassascii.encodeAsBase64().toString()
    def host = CameraIP 
    def hosthex = convertIPtoHex(host)
    def porthex = convertPortToHex(CameraPort)
    device.deviceNetworkId = "$hosthex:$porthex" 
    
    log.debug "The device id configured is: $device.deviceNetworkId"
    
    def path = CameraPath 
    log.debug "path is: $path"
    log.debug "Requires Auth: $CameraAuth"
    log.debug "Uses which method: $CameraPostGet"
    
    def headers = [:] 
    headers.put("HOST", "$host:$CameraPort")
       if (CameraAuth == "true") {
        headers.put("Authorization", userpass)
    }
    
    log.debug "The Header is $headers"
    
    def method = "GET"
    try {
        if (CameraPostGet.toUpperCase() == "POST") {
            method = "POST"
            }
        }
    catch (Exception e) { // HACK to get around default values not setting in devices
        settings.CameraPostGet = "GET"
        log.debug e
        log.debug "You must not of set the perference for the CameraPOSTGET option"
    }
    
    log.debug "The method is $method"
    
    try {
    def hubAction = new physicalgraph.device.HubAction(
        method: method,
        path: path,
        headers: headers
        )
        
    //hubAction.options = [outputMsgToS3:true]
    log.debug hubAction
    hubAction
    }
    catch (Exception e) {
        log.debug "Hit Exception $e on $hubAction"
    }
    
}



private getPictureName() {
    def pictureUuid = java.util.UUID.randomUUID().toString().replaceAll('-', '')
    return device.deviceNetworkId + "_$pictureUuid" + ".jpg"
}

private String convertIPtoHex(ipAddress) { 
    String hex = ipAddress.tokenize( '.' ).collect {  String.format( '%02x', it.toInteger() ) }.join()
    log.debug "IP address entered is $ipAddress and the converted hex code is $hex"
    return hex

}

private String convertPortToHex(port) {
    String hexport = port.toString().format( '%04x', port.toInteger() )
    log.debug hexport
    return hexport
}

private Integer convertHexToInt(hex) {
    Integer.parseInt(hex,16)
}


private String convertHexToIP(hex) {
    log.debug("Convert hex to ip: $hex") 
    [convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
}

private getHostAddress() {
    def parts = device.deviceNetworkId.split(":")
    log.debug device.deviceNetworkId
    def ip = convertHexToIP(parts[0])
    def port = convertHexToInt(parts[1])
    return ip + ":" + port
}

(Robert M) #6

How would I subscribe to the location event?


(Patrick Stuart [@pstuart]) #7

Forget the location event, that’s for smartapps, your issue is not binding to a known device id. It won’t work until you add an actual device so you can set the devicenetworkid.

The virtual device doesn’t listen for a response.


(Robert M) #8

Hi Patricks,

I got this working. I realized while driving home tonight that I had stopped the service on my raspberry pi while debugging last night. Turned it on when I got home and it worked.

Things I learnt though:

  1. I was running the code in the simulator using a virtual device. I thought that would work with pointing in the code to the IP:Port, but it doesn’t. Seems you have to actually configure an actual device with the IP:Port set then use that.
  2. Don’t forget to make sure the service is running, else all will fail.

Thanks for all your help! I appreciate it. Now I can get some sleep! :smile:

Rob


(Robert M) #9

Strange thing. This was working great, and now suddenly isn’t working. Nothing changed in between. It seems HubAction isn’t triggering the POST. Anyone else having issues lately? I’ve rebooted the hub, router, Pi. No traffic seems to be leaving the hub. I am able to manually do a POST and it works from a test tool. Yes, the Pi is running and worked in tests.


(Robert M) #10

I tried creating a new device with the same device type pointing to a different local webserver of mine and it’s working. So I need to troubleshoot a bit. My Pi was off for a week or so while I was busy / sidetracked on another project so maybe ST figured it couldn’t connect to the device and so it “blacklisted” it and that’s why no traffic is going there. I’ll play around by changing the IP address of the Pi and see if that makes a difference. I find it odd that I can connect locally on my network to the Pi, do GET/POST requests to it locally but the hub cannot seem to send anything to it, but can send to other IPs on my network. So I’ll start with the IP change on the Pi and then change the device and device settings to reflect the new IP and see what happens.


(Robert M) #11

Well this is odd. I made no changes to the device type, just modified the IP of the device and the device settings IP from 192.168.1.121 to 192.168.1.141 (c0a80179 to c0a8018d). Port stayed at 8000 (1f40). And now it works! Very strange!