The good news is I see they have an API to gain direct control using API calls to their servers (not directly to the device). It would still be dependent on the internet at all times, but it should be possible for someone to build a driver.
You can try having AI write you a driver for Hubitat. Give AI the links to the API references on their site and ask it to create a driver to control the lock on Hubitat.
I just asked Deep Seek:
"Write a driver for hubitat to control a DEVO lock. Use the information found at https://docs.devo.com/space/latest/94658823/Sending+data+to+the+HTTP+API+endpoint to create the API calls to the device.
It gave me a driver that looks like it could work, if you want to try the same or similar prompt, and paste what it gives you into drivers code, or try the result I added below. I don't have a DEVO lock, so I cannot test it.
/**
* DEVO Lock Driver for Hubitat Elevation
*
* Based on DEVO HTTP API documentation:
* https://docs.devo.com/space/latest/94658823/Sending+data+to+the+HTTP+API+endpoint
*
* Features:
* - Lock/Unlock commands
* - Battery level monitoring
* - Lock status reporting
* - Configurable API endpoint and credentials
*/
metadata {
definition(name: "DEVO Lock Driver", namespace: "yourNamespace", author: "Your Name") {
capability "Lock"
capability "Battery"
capability "Refresh"
attribute "lockStatus", "string"
attribute "apiStatus", "string"
command "unlock"
command "lock"
command "refresh"
}
preferences {
input name: "ipAddress", type: "text", title: "Device IP Address", description: "The IP address of your DEVO lock", required: true
input name: "port", type: "number", title: "Port", description: "The port number for the API", defaultValue: 8080, required: true
input name: "username", type: "text", title: "API Username", description: "Username for API authentication", required: true
input name: "password", type: "password", title: "API Password", description: "Password for API authentication", required: true
input name: "refreshInterval", type: "number", title: "Refresh Interval (minutes)", description: "How often to refresh status (0 to disable)", defaultValue: 5, required: true
input name: "debugLogging", type: "bool", title: "Enable debug logging", defaultValue: false
}
}
def installed() {
log.info "DEVO Lock Driver installed"
initialize()
}
def updated() {
log.info "DEVO Lock Driver updated"
initialize()
}
def initialize() {
unschedule()
if (refreshInterval > 0) {
runEvery1Minute(refresh)
log.info "Scheduled refresh every ${refreshInterval} minutes"
}
// Initial refresh
refresh()
}
def parse(String description) {
if (debugLogging) log.debug "parse($description)"
// Handle incoming messages if needed
}
def lock() {
if (debugLogging) log.debug "Executing 'lock' command"
def params = [
uri: "http://${ipAddress}:${port}",
path: "/api/lock",
requestContentType: "application/json",
contentType: "application/json",
headers: [
'Authorization': "Basic ${"${username}:${password}".bytes.encodeBase64().toString()}"
],
body: [
command: "lock"
],
timeout: 10
]
try {
httpPost(params) { resp ->
if (resp.status == 200) {
sendEvent(name: "lock", value: "locked")
sendEvent(name: "lockStatus", value: "locked")
if (debugLogging) log.debug "Lock command successful"
} else {
log.error "Lock command failed with status ${resp.status}"
sendEvent(name: "apiStatus", value: "error")
}
}
} catch (Exception e) {
log.error "Lock command failed: ${e.message}"
sendEvent(name: "apiStatus", value: "offline")
}
}
def unlock() {
if (debugLogging) log.debug "Executing 'unlock' command"
def params = [
uri: "http://${ipAddress}:${port}",
path: "/api/unlock",
requestContentType: "application/json",
contentType: "application/json",
headers: [
'Authorization': "Basic ${"${username}:${password}".bytes.encodeBase64().toString()}"
],
body: [
command: "unlock"
],
timeout: 10
]
try {
httpPost(params) { resp ->
if (resp.status == 200) {
sendEvent(name: "lock", value: "unlocked")
sendEvent(name: "lockStatus", value: "unlocked")
if (debugLogging) log.debug "Unlock command successful"
} else {
log.error "Unlock command failed with status ${resp.status}"
sendEvent(name: "apiStatus", value: "error")
}
}
} catch (Exception e) {
log.error "Unlock command failed: ${e.message}"
sendEvent(name: "apiStatus", value: "offline")
}
}
def refresh() {
if (debugLogging) log.debug "Executing 'refresh' command"
def params = [
uri: "http://${ipAddress}:${port}",
path: "/api/status",
requestContentType: "application/json",
contentType: "application/json",
headers: [
'Authorization': "Basic ${"${username}:${password}".bytes.encodeBase64().toString()}"
],
timeout: 10
]
try {
httpGet(params) { resp ->
if (resp.status == 200) {
def data = resp.data
if (debugLogging) log.debug "Status response: ${data}"
// Update lock status
def lockState = data.lockStatus?.toLowerCase() == "locked" ? "locked" : "unlocked"
sendEvent(name: "lock", value: lockState)
sendEvent(name: "lockStatus", value: lockState)
// Update battery level if available
if (data.batteryLevel != null) {
sendEvent(name: "battery", value: data.batteryLevel, unit: "%")
}
sendEvent(name: "apiStatus", value: "online")
} else {
log.error "Status check failed with status ${resp.status}"
sendEvent(name: "apiStatus", value: "error")
}
}
} catch (Exception e) {
log.error "Status check failed: ${e.message}"
sendEvent(name: "apiStatus", value: "offline")
}
}