DIY Ultrasonic sensor with Zigbee Interface

I wanted to sense if my vehicle was in the garage without using a "presence sensor". I decided the best way (for me) was to sense the distance from the rafters down to either the floor or to the roof of my vehicle. So I built an ultrasonic distance sensor with a zigbee interface.
The most difficult step was mounting it in an enclosure. I used a box I had lying around. It was a little larger than needed but in my garage room was no issue and the larger box made assembly easier. The most difficult part was mounting the Adafruit micro USB breakout. I chose the Adafruit because the connector was mounted to the board with 4 thru "pins" making it very robust.

If anyone is interested I'll be glad to share more details.

John

10 Likes

NIce implementation! I would def like to learn more, especially the zigbee interface part which would be very helpful for other future projects.

2 Likes

Yeah this is VERY cool...

1 Like

Would also like more details.

The key (for me) was Dr Haas' firmware for the TI C2530 Zigbee board. You can search "Haas" on this forum and find out more about the neat thing he has done. The Dr Haas implementation was on an arduino "shield" board. I'm not a shield kind of guy so after I purchased and played with a Haas shield I bought some bare TI cc2530 boards from ebay.
To make a long story short I ended up designing my own "carrier board" (the purple board you see in the photo) which is simply a breakout board with some filtering available for the analog inputs.
I can make the bare board available on OSH Park if anyone in interested.

What this means:
The Haas firmware allows anyone to communicate with the ZIgbee board using simple text "messages"
For instance to request the CC2530 board to measure and return the voltage on analog input #1 the command is getadct1. the board will reply 1_549 where 549 is the counts measured by adc1.

The real useful part of this firmware/board is:
There are about 30 commands recognized by the board. Any command that is not recognized by the board is passed through the serial port to what ever is connected. In my case it is an Arduino Pro Mini. The commands are limited to 15 bytes of ASCII characters providing numerous possibilities. The possibility of talking between the Hubitat and the arduino is the real key.

For this design, I used three leading characters to communicate between the Hubitat and the Arduino:

  1. @123. the @ char in the first location is sent to the Arduino when the Hub wants a measurement of distance. the 123 is meaningless and not really needed.

  2. #234. the # char indicates the retuned value is a measurement of the distance in CM.

  3. !OK. is a periodic "heartbeat" sent from the arduino the the Hub.

That is all the needed commands.

My driver below is pretty simple. But remember I'm great with hardware but not so much software. So I clawed my way through the driver with help from this forum. Keep that in mind when you read my code :slight_smile:

There is a lot more I can say but don't know what folks are looking for. I provide any details requested (if I can).

John

My initial driver code:

/**
 *  Garage Ultrasonic Sensor
 *
 * 2020-12-25 - v02b - "saves" in hubitat and can request measurement via device page button.
 *                      able to compare rec'd distance value to threshold
 *              v02c -  add events putting results in db
 *              v02e -  release candidate, still needs testing.
 *                      with v02e everything seems to work.
 *              v02f -  added distance status
 *              v02g -  converted to PresenceSensor due to issues with RM and (bool?) custom attributes
 *              v02h -  Changed the variable echo to echo for clarity.
 *              v02j -  added runin to clear Distance and Presence after 5 minutes (as they could be invalid)
 *                        won't work.  We will have to count #OK.
 *
 *              todo -  transfer initial configure to Hub instead of Arduino.
 *                   -  add ability to change "ping." in cc2430 and #OK. in Arduino.
 *                   -
 *                   -  clean up some of the log commands etc
 *
 */

import hubitat.device.HubAction
import hubitat.device.HubMultiAction
import hubitat.device.Protocol

metadata {
    definition (name: "Garage Ultrasonic v02j(tst)", namespace: "johnrob", author: "various") {
        capability "Actuator"       // our device has commands...
        capability "Sensor"         // our device has attributes...
        capability "PresenceSensor" // ENUM["present", "not present"]
        capability "Configuration"  // capability "Configuration"  commands: configure()

        command "requestMeasRM", ["string"] //allows arbitrary command to be sent

        attribute "vehiclePresent", "bool"
        attribute "lastActivity", "String"
        attribute "distance", "Integer"
    }

    preferences {           // see: Hubitat Notes (HTTP, driver and app).docx for more input options (there are lots of them)
        input name: "threshold", type: "num", title: "Presence Threshold (cm)", defaultValue: 200, range: "50..250", required: false
        input name: "logEnable", type: "bool", title: "Enable debug logging", defaultValue: true
        input name: "txtEnable", type: "bool", title: "Enable descriptionecho logging", defaultValue: true
    }
}

// Parse incoming device messages to generate events
def parse(String description) {
    if (logEnable) log.debug " description is $description"
    state.lastRan = now()
//    runIn(300, clearOldData)   //seconds, watchdogAlarm,[overwrite: true])  // [] because overwrite is a map

    Map map = [:]

    def event = zigbee.getEvent(description)
    if (event) {
        if (txtEnable) log.debug " parsed zigbee event = '${event}"
        sendEvent(event)
    }

    else if (description?.startsWith("catchall:")) {
        if (logEnable) log.debug " catchall is $description"
    }


    else if (description?.startsWith("read attr -")) {          // our returned measurement is in this Map
        def descMap = zigbee.parseDescriptionAsMap(description)
        if (logEnable) log.debug " Desc Map: $descMap"
        if (descMap.clusterInt == 0) {
            def echo = descMap.value            // def = define untyped variable
            if (txtEnable) log.info " parsing '${echo}'"
            echo = echo.replace(".","").trim()
            if (echo.startsWith("!")) {
                echo = echo.replace("!","")
                sendEvent(name: "distance", value: echo, isStateChange: true, unit: "cm")

                int echoValue = Integer.parseInt(echo)
                int threshold = Integer.parseInt(threshold)

                if (txtEnable) log.info "echo=${echoValue}"

                if (echoValue > threshold) {
                    vehState = "not present"
                    log.info " > threshold}"
                }
                else{
                    vehState = "present"
                    if (txtEnable) log.info "${echo} < threshold}"
                }

                return sendEvent(name: "vehiclePresent", value: vehState, isStateChange: true)
            }
            if (echo.startsWith("ping")) return    // trailing . removed above
            else if (echo.startsWith("#OK")){
                clearOldData()        // by the time we get the next OK, the data will be obsolete.
                return
            }

            else log.warn "Not an attribute we can decode"
        }

    } // --- 2nd else if ---


    else {
        log.warn "DID NOT PARSE MESSAGE for description : $description"
        if (logEnable) log.debug zigbee.parseDescriptionAsMap(description)
    }

}   // --- parse ---

//def getecho(){ // read some attribute string from the device
//  if (txtEnable) log.info "gettext"
//    //zigbee.readAttribute(0x000, 0x0006) // gets the last thing the device tried to send to us
//    zigbee.readAttribute(0x000, 0x0010) // gets the last command the device heard us send
//}

def requestMeasRM(String msg) {
    if (txtEnable) log.info "requestMeasRM - ${msg}"
    sendHubCommand(new HubAction(sendtodevice(msg), Protocol.ZIGBEE))
}

def sendtodevice(String mystr){
    if (txtEnable) log.info "sending '${mystr}'"
    mystr=mystr.padRight(16,".") // mystr should be 16 bytes!
    def packed = hubitat.helper.HexUtils.byteArrayToHexString(mystr.getBytes())
    if (logEnable) log.info "sending '${mystr}', packed is ${packed}"

    def commandtosend = "he wattr 0x${device.deviceNetworkId} 8 0x000 0x010 0x42 {10"+packed+"}" // SAMPLELIGHT_ENDPOINT is defined as 8 in device code // the 10 on the end means 16 bytes length
    if (logEnable) log.debug "$commandtosend"
    return commandtosend
}

def sendCommand(String msg) {
    if (txtEnable) log.info "sendCommand - ${msg}"
    sendHubCommand(new HubAction(sendtodevice(msg), Protocol.ZIGBEE))  // "new" Creates a new HubAction object
}

def configure() {
    if (txtEnable) log.info "Configuring Reporting and Bindings."
    zigbee.onOffRefresh() + zigbee.onOffConfig()
}

def installed() {
    if (txtEnable) log.info "Executing 'installed()'"
    updated()
}

def initialize() {
    if (txtEnable) log.info "Executing 'initialize()'"
}

def updated() {
    if (txtEnable) log.info "Executing 'updated()'"

    if (logEnable) {
        log.info "Enabling Debug Logging for 30 minutes"
        runIn(1800,logsOff)
    } else {
        unschedule(logsoff)
    }
}

def now() {
    if(location.timeZone)
    now = new Date().format("yyyy MMM dd EEE h:mm:ss a", location.timeZone)
    else
    now = new Date().format("yyyy MMM dd EEE h:mm:ss a")
    sendEvent(name: "lastActivity", value: now, displayed:false)
    result
}


def clearOldData() {
     sendEvent(name: "distance", value: "null", isStateChange: true, unit: "cm")
     sendEvent(name: "vehiclePresent", value: "null", isStateChange: true)
}


//  --- code ---


/*

def configure() {
    if (txtEnable) log.info "Configuring Reporting and Bindings."
    // disable internal functions
    List<String> cmds = []
    cmds.add sendtodevice("ping_0.")    //ping_0. disables pings, pings are in units of 10 seconds
    cmds.add sendtodevice("poll_100.")    // not sure this matters with the nopoll's below and we don't know the max limit.
    cmds.add sendtodevice("off.")
    cmds.add sendtodevice("nopollbutt1.")
    cmds.add sendtodevice("nopollbutt2.")
    cmds.add sendtodevice("nopollbutt3.")
    cmds.add sendtodevice("nopolladc0.")
    cmds.add sendtodevice("nopolladc2.")
    cmds.add sendtodevice("nopolladc4.")
    cmds.add sendtodevice("nopolladc5.")

    sendHubCommand(new HubMultiAction(delayBetween(cmds,500), Protocol.ZIGBEE))
}


*/


//  --- eof ---
4 Likes