Not every driver is published there, but there are a number of virtual device drivers in the HubitatPublic repo, which should be a pretty good starting point:
The biggest difference between virtual drivers would mostly be what capabilities you need to implement. That list is here: Driver Capability List | Hubitat Documentation. For a contact sensor, you'd probably want capability "ContactSensor"
(and likely the do-nothing capability "Sensor"
just for the sake of completenes--I'd say nearly any driver should have either that or "Actuator"
, both useful as generic selectors in rare but not unheard of situations).
The other differences fall out from that, mostly: what attributes you need, for example. You can see from the docs that capability "ContactSensor
" should have a contact
attribute, with possible values open
or closed
. In a "real" driver, you'd really just wait for those events to come in from the device (this traffic would come into a parse()
method you'd need to define), but for virtual devices, you'd generally create custom commands to make the same effect. In Hubitat's virtual driver, these are called open()
and close()
, though you could really call them whatever you want. These are not a part of the capability because you do not need them for "real" devices. In some cases, like a virtual switch with capability "Switch"
, the on()
and off()
commands are required by the capability (because unlike sensors, actuators accept commands) and you'd generally just use those to manipulate the simulated state (in this case the switch
attribute to a value of on
or off
).
A very simple virtual switch driver (coming soon to docs near you) might look like:
metadata {
definition (name: "Custom Virtual Switch", namespace: "MyNamespace", author: "My Name") {
capability "Actuator"
capability "Switch"
}
preferences {
// none in this driver
}
}
def installed() {
log.debug "installed()"
}
def updated() {
log.debug "updated()"
}
def on() {
// With a real device, you would normally send a Z-Wave/Zigbee/etc. command to the device here
// For a virtual device, we are simply generating an event to make the "switch" attribute "on"
// (with a real device, you would usually wait to hear back from the device in parse() before doing this)
sendEvent(name: "switch", value: "on", descriptionText: "${device.displayName} switch is on")
}
def off() {
// Same notes as for on() apply here...
sendEvent(name: "switch", value: "off", descriptionText: "${device.displayName} switch is off")
}
For other kinds of devices, it's just a matter of modifying the capabilities, commands, and events (for attributes) as needed. That last thing brings us to this question:
This is what sendEvent()
does in this virtual driver. An attribute state is the result of an event. The event name is the name of the attribute, and the event value is the new value of the attribute. Those are the two required parameters, though for others you'll see things like units (e.g., %
or °F
), type (physical vs. digital), and descriptionText (something like "[Device display name] [attribute name] is [attribute value] ([units])" and conventionally logged if descriptionText logging is enabled in the driver--not demonstrated above).
In related news, in case you aren't aware: what you're looking for cannot be done in a driver alone. A driver cannot talk to other devices (parent/child relationships excepted), so you'd need an app to tie your "real" devices into whatever state you want to track with this "virtual" device.