I recently got alexa working just fine and I wanted to share my implementation in case anyone is reading:
rule "Right Blind"
when
Item ALBlindsRight_Position received command
then
val String device_mac_addr = "C7:23:9A:xx:xx:xx"
var Number pos = 0
if (receivedCommand == STOP) {
logWarn(filename, "Soma-Blinds | STOP is not yet supported. Aborting.")
//Workaround with get_position does not work either, because the device returns instantly the target position and not the current position while moving.
} else if (receivedCommand == ON || receivedCommand == UP) {
pos = 0
} else if (receivedCommand == OFF || receivedCommand == DOWN) {
pos = 100
} else {
try {
pos = receivedCommand as Number
} catch (Exception e) { // String is not a number
logError(filename, "Soma-Blinds | The command '" + receivedCommand + "' is not a number or a known value. Aborting.")
return;
}
}
val String cmd = "sudo /usr/bin/python /etc/openhab2/exec-scripts/Homebridge-SOMA-Smart-Shades/control.py -t "+ device_mac_addr +" -c move_target --motor_target " + pos
if (controllerIsLocked) {
logWarn(filename, "(Debug)Soma-Blinds | Controller was locked for "+triggeringItem.name+", retrying in "+cmdPause+" milliseconds.")
createTimer(now.plusMillis(cmdPause)) [ |
sendCommand(triggeringItem.name, receivedCommand.toString)
]
} else {
controllerIsLocked = true;
currentAttempt++
var String errorMsg = ""
logDebug(filename, "executing "+currentAttempt+"/"+maxAttempts+": " + cmd)
val String status_raw = executeCommandLine(cmd, cmdTimeout)
if (status_raw === null) {
errorMsg = "Status was null."
} else {
val String statusWithoutLinebreaks = status_raw.replaceAll("\\r?\\n|\\r","")
if (status_raw.length == 0) {
errorMsg = "Response was empty."
} else if ( //filtering known errors
status_raw.contains("function not implemented") ||
status_raw.contains("no route to host") ||
status_raw.contains("discover all characteristics failed") ||
status_raw.contains("discover all primary services failed") ||
status_raw.contains("software caused connection abort")
) {
errorMsg = statusWithoutLinebreaks
logError(filename, "Soma-Blinds | New kind of error detected: '" + errorMsg)
} else {
logInfo(filename, "Soma-Blinds | Success by executing try #" + currentAttemptLeft + " @"+triggeringItem.name+". Output for '"+cmd+"' is: '" + statusWithoutLinebreaks + "'.")
} }
if (errorMsg == "") { //success
currentAttempt = 0
logDebug(filename, "Soma-Blinds | Position has been set after "+currentAttempt+" attempts by executing '"+cmd+"'.")
} else { //retry
if (currentAttempt <= maxAttempts) {
logWarn(filename, "Soma-Blinds | Error in setting position in attempt #"+currentAttempt + ". Executing "+cmd+" caused: '" + errorMsg +"'.")
createTimer(now.plusMillis(cmdPause)) [ |
sendCommand(triggeringItem.name, receivedCommand.toString)
]
} else {
logError(filename, "Soma-Blinds | Not able to set position in "+maxAttempts+" attempts by executing '"+cmd+"'.")
}
}
controllerIsLocked = false;
}
end
//My room has two windows with seperate blinds. This rule helps in handling both as one, while still having the ability to use each one seperate.
rule "Blinds Meta Controll"
when
Item ALBlinds_Position received command
or Item ALBlinds_Position_Alt received command
then
//0 = up
//100=down
ALBlindsLeft_Position.sendCommand(receivedCommand.toString)
Thread::sleep(cmdPause) //todo fiddle with this
ALBlindsRight_Position.sendCommand(receivedCommand.toString)
if (triggeringItem.name === "ALBlinds_Position") {
ALBlinds_Position_Alt.postUpdate(receivedCommand.toString)
} else {
ALBlinds_Position.postUpdate(receivedCommand.toString)
}
end
rule "Rollo Batteriestatus"
when
Time cron "0 11 4,11,18 * * ? *" //4,12,19 Uhr
then
var String device_mac_addr = "CE:6F:3B:xx:xx:xx"
var String device_name = "ALBlindsLeft_Battery"
controllerIsLocked = true;
var String cmd = "sudo /usr/bin/python /etc/openhab2/exec-scripts/Homebridge-SOMA-Smart-Shades/control.py -t "+ device_mac_addr +" -c get_battery"
var int attemptNumber = 1
var String errorMsg = null
var String batteryValueAsString = null
while (attemptNumber < 10 && batteryValueAsString === null) {
val status_raw = executeCommandLine(cmd, cmdTimeout)
val statusWithoutLinebreaks = status_raw.replaceAll("\\r?\\n|\\r","")
val positionOfValue = statusWithoutLinebreaks.indexOf("get_battery ")
if (positionOfValue > 0) { //success
batteryValueAsString = statusWithoutLinebreaks.substring(positionOfValue+12).trim()
//val Number batteryValue = Integer::parseInt(batteryValueAsString)
sendCommand(device_name,batteryValueAsString)
attemptNumber = 0
} else if (status_raw.length == 0) {
errorMsg = "Response was empty."
} else if (status_raw.contains("function not implemented") ) { //occurs randomly
errorMsg = statusWithoutLinebreaks
} else {
errorMsg = "Unknown error: " + status_raw
}
if (batteryValueAsString === null) {
logWarn(filename, "Soma-Blinds | Error in getting battery state for "+device_name+" in attempt #"+attemptNumber + ". " + errorMsg)
attemptNumber = attemptNumber+1
}
}
//right
device_mac_addr = "C7:23:9A:xx:xx:xx"
device_name = "ALBlindsRight_Battery"
cmd = "sudo /usr/bin/python /etc/openhab2/exec-scripts/Homebridge-SOMA-Smart-Shades/control.py -t "+ device_mac_addr +" -c get_battery"
attemptNumber = 1
errorMsg = null
batteryValueAsString = null
while (attemptNumber < 10 && batteryValueAsString === null) {
val status_raw = executeCommandLine(cmd, cmdTimeout)
val statusWithoutLinebreaks = status_raw.replaceAll("\\r?\\n|\\r","")
val positionOfValue = statusWithoutLinebreaks.indexOf("get_battery ")
if (positionOfValue > 0) { //success
batteryValueAsString = statusWithoutLinebreaks.substring(positionOfValue+12).trim()
//val Number batteryValue = Integer::parseInt(batteryValueAsString)
sendCommand(device_name,batteryValueAsString)
attemptNumber = 0
} else if (status_raw.length == 0) {
errorMsg = "Response was empty."
} else if (status_raw.contains("function not implemented") ) { //occurs randomly
errorMsg = "random error: " + statusWithoutLinebreaks
} else {
errorMsg = "Unknown error: " + status_raw
}
if (batteryValueAsString === null) {
logWarn(filename, "Soma-Blinds | Error in getting battery state for "+device_name+" in attempt #"+attemptNumber + ". " + errorMsg)
attemptNumber = attemptNumber+1
}
}
controllerIsLocked = false;
end
and this is my item definition
//blinds.items
Group gBlinds "Rollos" <blinds>
Rollershutter ALBlindsLeft_Position "Rollo Links André [%d]" <blinds> (gBlinds)
Number ALBlindsLeft_Battery "Akku Rollo Links André [%d %%]" <battery> (gBlinds, gBattery, gMonitorUpdates)
Rollershutter ALBlindsRight_Position "Rollo Rechts André [%d]" <blinds> (gBlinds)
Number ALBlindsRight_Battery "Akku Rollo Rechts André [%d %%]" <battery> (gBlinds, gBattery, gMonitorUpdates)
//meta item for alexa and the likes
Rollershutter ALBlinds_Position "Rollo / Rollos [%d%% gesenkt]" <blinds> {homekit="WindowCovering", alexa="RangeController.rangeValue" [category="INTERIOR_BLIND", friendlyNames="@Setting.Opening", unitOfMeasure="Percent", actionMappings="Close=100,Open=0,Lower=(+25),Raise=(-25)", stateMappings="Open=0:25,Closed=26:100"] }
Sometimes it takes a moment because of the timeouts and the concurrency problems… but I am quite happy with it. However, I appreciate any improvements to my code 