Building on the simple switch driver that I recently released, here's a driver for an RGB light controller running the Sonoff Tasmota firmware. This firmware can be loaded on various ESP-based RGB controllers, such as MagicHome or H801 (the latter of which can be bought for under $10). All of my development and testing has been using the H801, but the interface should be the same for all RGBW controllers. After installing the driver, you simply need to set the connection preferences with the IP address, port, and authentication info (if necessary).
In addition to the standard commands, the driver has a custom command called setColorDirect that accepts a string. The value is passed directly to the Color command of the Tasmota device. This supports multiple arguments, such as a comma separated list of decimal RGB values, hex values in one of the following forms (RRGGBB, RRGGBBWW, RRGGBBCCWW, CCWW) or a decimal value of 1 through 12 representing a solid color. A full list of the options can be found here.
// Hubitat driver for controlling Sonoff-Tasmota RGB light
// Version 1.0.0
metadata {
definition(name: "Sonoff-Tasmota RGB Light", namespace: "DKK", author: "dkkohler") {
capability "Actuator"
capability "Color Control"
capability "Color Temperature"
capability "Refresh"
capability "Switch"
capability "Switch Level"
capability "Light"
capability "ColorMode"
command "setColorDirect", ["String"]
}
preferences {
section("Sonoff Host") {
input(name: "ipAddress", type: "string", title: "IP Address", displayDuringSetup: true, required: true)
input(name: "port", type: "number", title: "Port", displayDuringSetup: true, required: true, defaultValue: 80)
}
section("Authentication") {
input(name: "username", type: "string", title: "Username", displayDuringSetup: false, required: false)
input(name: "password", type: "password", title: "Password (sent cleartext)", displayDuringSetup: false, required: false)
}
}
}
def parse(String description) {
def message = parseLanMessage(description)
def isParsed = false;
// parse result from current formats
def resultJson = {}
if (message?.json) {
// current json data format
resultJson = message.json
log.debug resultJson
}
// determine switch state
if ((resultJson?."POWER" in ["ON", 1, "1"])) {
setSwitchState(true)
}
else if ((resultJson?."POWER" in ["OFF", 0, "0"])) {
setSwitchState(false)
}
else {
log.error "Unable to parse POWER state"
}
// determine dimmer level
if (resultJson?."Dimmer" != null) {
def level = resultJson."Dimmer".toInteger()
setLevelState(level)
}
// determine HSB value and color mode
if (resultJson?."HSBColor" != null) {
def hsb = resultJson."HSBColor".split(',')
setHsbState(hsb[0].toInteger(), hsb[1].toInteger(), hsb[2].toInteger())
}
// determine color temperature
if (resultJson?."CT" != null) {
def ct = resultJson."CT".toInteger()
setColorTemperatureState(((ct - 153) * (30000-1)/(500-153)).toInteger())
}
}
def setColorModeState(String mode) {
sendEvent(name: "colorMode", value: mode);
}
def setSwitchState(Boolean on) {
sendEvent(name: "switch", value: on ? "on" : "off")
}
def setHsbState(Integer h, Integer s, Integer b) {
// Convert H from 0:255 to 0:360
h = (h*(360/255)).toInteger()
// If HSB all zero, likely in color temperature mode (or off, but we handle them the same way)
// Update last H and S values so they can be restored
if ((h == 0) && (s == 0) && (b == 0)) {
setColorModeState("colorTemperature")
} else {
setColorModeState("color")
state.lastHue = h
state.lastSaturation = s
}
sendEvent(name: "hue", value: h);
sendEvent(name: "saturation", value: s);
}
def setLevelState(Integer level) {
sendEvent(name: "level", value: level);
}
def setColorTemperatureState(Integer value) {
sendEvent(name: "colorTemperature", value: value)
}
def push() {
sendCommand("Power", "Toggle")
}
def on() {
sendCommand("Power", "On")
}
def off() {
sendCommand("Power", "Off")
}
def poll() {
sendCommand("State", null)
}
def refresh() {
sendCommand("State", null)
}
def setLevel(value) {
if (value == null) return
sendCommand("Dimmer", value)
}
def setHsb(Integer h, Integer s, Integer b) {
// if hue or saturation is null, use last known value
h = ((h == null) ? state.lastHue : h)
s = ((s == null) ? state.lastSaturation : s)
// If brightness is null, use current level
// Set to 100 if 0 so light will turn on (otherwise H and S won't be set)
if (b == null) {
b = device.currentValue("level").toInteger()
if (b == 0) b = 100
}
// send command, converting H from 0:360 to 0:255
sendCommand("HsbColor", "${(h*(255/360)).toInteger()},${s},${b}")
}
def setColor(value){
if (value instanceof Map) {
def h = value.containsKey("hue") ? value.hue : null
def s = value.containsKey("saturation") ? value.saturation : null
def b = value.containsKey("level") ? value.level : null
setHsb(h, s, b)
} else {
log.warn "Invalid argument for setColor: ${value}"
}
}
def setColorDirect(String value) {
sendCommand("Color", value)
}
def setHue(value) {
if (value == null) return
setHsb(value, null, null)
}
def setSaturation(value) {
if (value == null) return
setHsb(null, value, null)
}
def setColorTemperature(rawValue) {
if (rawValue == null) return
// Convert color temperature range from 1:30000 to 153:500
def scaledValue = 153 + (rawValue * (500-153)/(30000-1)).toInteger()
sendCommand("CT", scaledValue)
}
private def sendCommand(String command, payload) {
sendCommand(command, payload.toString())
}
private def sendCommand(String command, String payload) {
log.debug "sendCommand(${command}:${payload}) to device at $ipAddress:$port"
if (!ipAddress || !port) {
log.warn "aborting. ip address or port of device not set"
return null;
}
def hosthex = convertIPtoHex(ipAddress)
def porthex = convertPortToHex(port)
def path = "/cm"
if (payload){
path += "?cmnd=${command}%20${payload}"
}
else{
path += "?cmnd=${command}"
}
if (username){
path += "&user=${username}"
if (password){
path += "&password=${password}"
}
}
def result = new hubitat.device.HubAction(
method: "GET",
path: path,
headers: [
HOST: "${ipAddress}:${port}"
]
)
return result
}
private String convertIPtoHex(ipAddress) {
String hex = ipAddress.tokenize( '.' ).collect { String.format( '%02x', it.toInteger() ) }.join()
return hex
}
private String convertPortToHex(port) {
String hexport = port.toString().format('%04x', port.toInteger())
return hexport
}