Neo Smart Controller for blinds

Shall do. Thanks again for your help thus far.

1 Like

Does the "name" matter in the sendEvent calls? I've mixed them a bit. I've used one "name" for the open/close and a different name for the stop/favourite. Does that matter? What should it be?

e.g. sendEvent(name: "shade", value: "stop", isStateChange: true)
e.g. sendEvent(name: "doorControl", value: "open", isStateChange: true)

It looks like its sending kinda like a json string so you need to make sure that name and value match what the device is expecting.

API docs would come in handy for that.

That "name" string has nothing to do with the string sent to the controller. The API docs are the ones I PMed you. The URI sent to the controller is in the format as follows. The "up" part is the command that tells the blind to go up.

http://192.168.1.73:8838/neo/v1/transmit?command=188.128-00-up&id="controllerID"

This is the string I input for each URI in the device handler. A unique string for each of the four commands for up/down/stop/favourite. None of the other stuff in the device handler gets passed to the controller.

ok

I tried it and, lo and behold, it works! Does exactly what I want it to do. I had to change the "name" to be the same to show the current state. Now the next trick is to set it up in dashboard. I don't know how that'll work since there's no such device in dashboard.

So can I change my status to 'Developer' now? :wink:

2 Likes

So now you are a coder :smile:

You can create a virtual device to control it or you can use makerAPI to send the command.

Feels good when you created new code and it works....

BTW you should post your modified code and edit it to give yourself credit so that others can build off what you did.

Yes, very rewarding when it works! I've created a github site for myself and will upload the driver for others to share and improve. It's very rudimentary but it's a starting point where nothing existed previously.

2 Likes

That is why for most coders we share. And why you will find out there are bad characters that will steal your work and claim they made it. Another part of being a coder you have to deal with.

Hi mate. No sooner had I managed to get a working driver, I've already started on the next version! I'm trying to make it simpler so you enter in the controller IP, controller ID and blind ID and the code works out the http string needed to pass to the blind. I came up with the following. Trying to define openURI, closeURI, faveURI and stopURI using inputs for controllerIP, controllerID and blindCode but it's failing. Says cannot have null params. I'm guessing I've got it wrong. Any suggestions?

/*
 * Neo Smart Controller
 *
 * Calls URIs with HTTP GET for shade open/close/stop/favourite
 * 
 * Based on the community driver httpGetSwitch
 */
metadata {
    definition(name: "Neo Smart Controller v2", namespace: "bigrizzo", author: "bigrizz") {
        capability "Actuator"
        capability "DoorControl"
        capability "Sensor"
		
		command "stop"
		command "favourite"
    }
}

preferences {
    section("inputVariables") {
        input "controllerIP", "text", title: "Controller IP Address", description: "Enter NEO Smart controller local IP address", required: true, displayDuringSetup: true
        input "controllerID", "text", title: "Controller ID Number", description: "Enter controller 24-digit ID number displayed in Neo app", required: true, displayDuringSetup: true
		input "blindCode", "text", title: "Blind Code", description: "Enter Blind Code displayed in Neo app", required: true, displayDuringSetup: true
		input name: "logEnable", type: "bool", title: "Enable debug logging", defaultValue: true
    }
}

def closeURI = 'http://' + ':8838/neo/v1/transmit?command=' + blindCode + '-dn&id=' + controllerID
def openURI = 'http://' + ':8838/neo/v1/transmit?command=' + blindCode + '-up&id=' + controllerID
def stopURI =  'http://' + ':8838/neo/v1/transmit?command=' + blindCode + '-sp&id=' + controllerID
def faveURaI =  'http://' + ':8838/neo/v1/transmit?command=' + blindCode + '-gp&id=' + controllerID

def logsOff() {
    log.warn "debug logging disabled..."
    device.updateSetting("logEnable", [value: "false", type: "bool"])
}

def updated() {
    log.info "updated..."
    log.warn "debug logging is: ${logEnable == true}"
    if (logEnable) runIn(1800, logsOff)
}

def parse(String description) {
    if (logEnable) log.debug(description)
}

def close() {
    if (logEnable) log.debug "Sending close GET request to [${settings.closeURI}]"

    try {
        httpGet(settings.closeURI) { resp ->
            if (resp.success) {
                sendEvent(name: "shade", value: "close", isStateChange: true)
            }
            if (logEnable)
                if (resp.data) log.debug "${resp.data}"
        }
    } catch (Exception e) {
        log.warn "Call to close failed: ${e.message}"
    }
}

def open() {
    if (logEnable) log.debug "Sending open GET request to [${settings.openURI}]"

    try {
        httpGet(settings.openURI) { resp ->
            if (resp.success) {
                sendEvent(name: "shade", value: "open", isStateChange: true)
            }
            if (logEnable)
                if (resp.data) log.debug "${resp.data}"
        }
    } catch (Exception e) {
        log.warn "Call to open failed: ${e.message}"
    }
}

def stop() {
    if (logEnable) log.debug "Sending stop GET request to [${settings.stopURI}]"

    try {
        httpGet(settings.stopURI) { resp ->
            if (resp.success) {
                sendEvent(name: "shade", value: "stop", isStateChange: true)
            }
            if (logEnable)
                if (resp.data) log.debug "${resp.data}"
        }
    } catch (Exception e) {
        log.warn "Call to stop failed: ${e.message}"
    }
}

def favourite() {
    if (logEnable) log.debug "Sending favourite GET request to [${settings.faveURI}]"

    try {
        httpGet(settings.faveURI) { resp ->
            if (resp.success) {
                sendEvent(name: "shade", value: "favourite", isStateChange: true)
            }
            if (logEnable)
                if (resp.data) log.debug "${resp.data}"
        }
    } catch (Exception e) {
        log.warn "Call to favourite failed: ${e.message}"
    }
}

So glad to see someone has started on this. We have several of these on order. Here are the API docs in case that helps with the commands.


I'll be glad to further assist on this driver when they arrive.

Hi Brian. I got the API docs from Neo as well. I used them to get a working driver but it's not very elegant. You have to enter the entire command string for each command in the driver. The working driver is here.

I'm trying to modify it so that you only enter the controller IP, controller ID and blind code and the code does the concatenation but my Groovy skills are next to zero, so I don't know how to do it. How are your Groovy skills?

Code below. You can see where I'm trying to define openURI, closeURI, stopURI, faveURI. I need to understand the syntax better to figure it out. Hoping someone can short-circuit my knowledge gap here.

/*
 * Neo Smart Controller
 *
 * Calls URIs with HTTP GET for shade open/close/stop/favourite
 * 
 * Based on the community driver httpGetSwitch
 */
metadata {
    definition(name: "Neo Smart Controller v2", namespace: "bigrizzo", author: "bigrizz") {
        capability "Actuator"
        capability "DoorControl"
        capability "Sensor"
		
		command "stop"
		command "favourite"
    }
}

preferences {
    section("inputVariables") {
        input "controllerIP", "text", title: "Controller IP Address", description: "Enter NEO Smart controller local IP address", required: true, displayDuringSetup: true
        input "controllerID", "text", title: "Controller ID Number", description: "Enter controller 24-digit ID number displayed in Neo app", required: true, displayDuringSetup: true
		input "blindCode", "text", title: "Blind Code", description: "Enter Blind Code displayed in Neo app", required: true, displayDuringSetup: true
		input name: "logEnable", type: "bool", title: "Enable debug logging", defaultValue: true
    }
}

def closeURI = 'http://'.concat(controllerIP).':8838/neo/v1/transmit?command='.concat(blindCode).'-dn&id='.concat(controllerID) 
def openURI = 'http://'.concat(controllerIP).':8838/neo/v1/transmit?command='.concat(blindCode).'-up&id='.concat(controllerID) 
def stopURI =  'http://'.concat(controllerIP).':8838/neo/v1/transmit?command='.concat(blindCode).'-sp&id='.concat(controllerID) 
def faveURI =  'http://'.concat(controllerIP).':8838/neo/v1/transmit?command='.concat(blindCode).'-gp&id='.concat(controllerID) 

def logsOff() {
    log.warn "debug logging disabled..."
    device.updateSetting("logEnable", [value: "false", type: "bool"])
}

def updated() {
    log.info "updated..."
    log.warn "debug logging is: ${logEnable == true}"
    if (logEnable) runIn(1800, logsOff)
}

def parse(String description) {
    if (logEnable) log.debug(description)
}

def close() {
    if (logEnable) log.debug "Sending close GET request to [${settings.closeURI}]"

    try {
        httpGet(settings.closeURI) { resp ->
            if (resp.success) {
                sendEvent(name: "shade", value: "close", isStateChange: true)
            }
            if (logEnable)
                if (resp.data) log.debug "${resp.data}"
        }
    } catch (Exception e) {
        log.warn "Call to close failed: ${e.message}"
    }
}

def open() {
    if (logEnable) log.debug "Sending open GET request to [${settings.openURI}]"

    try {
        httpGet(settings.openURI) { resp ->
            if (resp.success) {
                sendEvent(name: "shade", value: "open", isStateChange: true)
            }
            if (logEnable)
                if (resp.data) log.debug "${resp.data}"
        }
    } catch (Exception e) {
        log.warn "Call to open failed: ${e.message}"
    }
}

def stop() {
    if (logEnable) log.debug "Sending stop GET request to [${settings.stopURI}]"

    try {
        httpGet(settings.stopURI) { resp ->
            if (resp.success) {
                sendEvent(name: "shade", value: "stop", isStateChange: true)
            }
            if (logEnable)
                if (resp.data) log.debug "${resp.data}"
        }
    } catch (Exception e) {
        log.warn "Call to stop failed: ${e.message}"
    }
}

def favourite() {
    if (logEnable) log.debug "Sending favourite GET request to [${settings.faveURI}]"

    try {
        httpGet(settings.faveURI) { resp ->
            if (resp.success) {
                sendEvent(name: "shade", value: "favourite", isStateChange: true)
            }
            if (logEnable)
                if (resp.data) log.debug "${resp.data}"
        }
    } catch (Exception e) {
        log.warn "Call to favourite failed: ${e.message}"
    }
}

I’m pretty comfortable. I’ll fork your code and see what I can come up with.

1 Like

Give this a shot and see if it works. I don't have my hub or blinds yet to test. https://raw.githubusercontent.com/bdwilson/hubitat/master/NeoSmart/NeoSmart.groovy

I set it up to use the windowShade capability. Using this will allow you to more naturally interact with it like any other window shade in Hubitat.

Sorry for changing how favorite is spelled :wink:

I'm curious if the setPosition values work on your blinds. The API docs say that it is "rare" to find a motor that works. Also, I assume you can only have 1 favorite position (I didn't see a way to go to favorite #1 or favorite #2 in the API docs).

Thanks Brian. I gave it a shot and there are a couple of things to fix.

Firstly, port 8838 needs to be defined. I added that into the string and it worked to close the blind. Thereafter I couldn't get it to work, getting the following message.

2019-11-11 04:01:23.643 pm [warn](http://192.168.1.19/device/edit/201)Call to open failed: Conflict

The problem seems to be that the hash you generate with the date is 13 characters long instead of 7, as prescribed in the API. If I remove the hash from the code, it works. Do you know how to concatenate it to 7 characters?

The last thing we should include is the final two actions in the API, for completeness. There's a 'micro step up' and 'micro step down' prescribed in the API. No idea what it does but may as well add it in.

I think you're correct on the single favourite position (note the correct spelling :wink:). I'll test out the setPosition and see what it does.

I tested the setPosition and nothing happens. I also implemented the micro step up and down and neither does anything with my blinds. The correct string is being passed. Obviously not supported by my blinds. Up/down/stop/favourite all work as expected. The favourite position is whatever you set in the Neo mobile app.

I made a few more changes on my copy if you want to give it a shot. I changed the port as you mentioned, adjusted the time length, added the up/down commands and tried to add rudimentary setLevel (100 being down, 0 being up).

1 Like

Nice work, Brian. Did a quick test and the open/close/stop/favourite all work for me. None of the other stuff does anything with my blind motors. I'd call that good to go, at least until someone whose blinds might support the other features can give it more of a test.

Hi @brianwilson, was just about to dive into building a driver for this and it looks like you’ve already done it!

Is this the latest version?

https://raw.githubusercontent.com/bdwilson/hubitatDrivers/master/NeoSmart.groovy

Cheers!

(had my blinds installed today!)