OpenHAB MiOS Log Message Bridge

This write-up covers my items, rules, and MiOS configuration for bridging log messages and configuration between OpenHAB and MiOS. Hopefully it provides some good example code for anybody looking to do something similar, or basic communication and control using MultiSwitch and MultiString.

Goal: Standard way to handle messages from my rules
Solution: Create a set of items and rules in OpenHAB that centralize my logging. All my rules post an update to a OpenHAB item, and then the rules act on the changes to that item by logging and alerting as appropriate for the level.

Goal: Display messages from my rules in MiOS
Solution: Use the MultiString MiOS plugin to display messages, with the date in the variable name and the message is the variable value.

Goal: Control the logging level of my rules from MiOS
Solution: Use the MulitSwitch MiOS plugin to create a set of virtual switches to control the logging level. A button press triggers an OpenHAB rule which changes the logging level and redisplays the messages.

Goal: See my OpenHAB messages and alerts on my iPhone
Solution: Both MultiString and MultiSwitch are supported by HomeWave, the iPhone app I use. I can send alerts from OpenHAB to my phone through the app. I also use pushover to send messages to my phone.

Notes

I use 4 logging levels. From most important to least important they are: ALERT, WARN, INFO, and DEBUG. I created a OpenHAB String item for each. To log a message, my rules simply postUpdate to the appropriate item.

I wanted more than 5 messages in OpenHAB, so I use 2 MultiString devices to display them. The rule is set up to work with any number. But a message change results in every item in all devices being updated, so perhaps it?s possible that too many could flood MiOS.

I spent a lot of time trying to avoid copy-and-paste in my rules. It?s hard to do this in OpenHAB rules. I take advantage of Xtend lambda functions for this. These seem to confuse OpenHAB designer at times and can make the rules file hard to work with. There might be a better way to do some of this, but it?s good enough for me for now. Using JSR223 scripting with like Python or Groovy might be a better approach. I might redo it when OpenHAB2 support gets finalized.

I?m still working out how I want to handle the important messages. Right now I send both ALERT and WARN messages both HomeWave and pushover. Eventually I will probably make what is sent where configurable and use MultiSwitch to control it.

I set up a logging category in OpenHAB specifically for my rules messages. It is called ?MyRules?.

I am using OpenHAB 1.8.2. I generated the MiOS items with an earlier version of the item file generator, so those might be a little different now. Some of the actions or mappings may be built-in now.

I’ve attached screenshots of the messages and control in UI7 and in HomeWave.

OpenHAB Items

Items for My Rules in OpenHAB

[code]Group gRuleMessages

String AlertMsg “Last AlertMsg [%s]” (gRuleMessages)
String WarnMsg “Last WarnMsg [%s]” (gRuleMessages)
String InfoMsg “Last InfoMsg [%s]” (gRuleMessages)
String DebugMsg “Last DebugMsg [%s]” (gRuleMessages)

String DisplayMsgLevel “Current display message level [%s]” (gRuleMessages)
[/code]

Items for the MultiString devices in MiOS

If you don?t want to display the messages in the OpenHAB web UI, you only need the device ID item.

[code]Group gDisplayMultiStrings

/* Device - OpenHabMsgs1 */
Number OpenHabMsgs1_Id “ID [%d]” (gRuleMessages,gDisplayMultiStrings) {mios=“unit:house,device:144/id”}
String OpenHabMsgs1_Name1 “OpenHabMsgs1 Name 1 [%s]” (gRuleMessages) {mios=“unit:house,device:144/service/urn:upnp-org:serviceId:VContainer1/VariableName1”}
String OpenHabMsgs1_Val1 “OpenHabMsgs1 Val 1 [%s]” (gRuleMessages) {mios=“unit:house,device:144/service/urn:upnp-org:serviceId:VContainer1/Variable1”}
String OpenHabMsgs1_Name2 “OpenHabMsgs1 Name 2 [%s]” (gRuleMessages) {mios=“unit:house,device:144/service/urn:upnp-org:serviceId:VContainer1/VariableName2”}
String OpenHabMsgs1_Val2 “OpenHabMsgs1 Val 2 [%s]” (gRuleMessages) {mios=“unit:house,device:144/service/urn:upnp-org:serviceId:VContainer1/Variable2”}
String OpenHabMsgs1_Name3 “OpenHabMsgs1 Name 3 [%s]” (gRuleMessages) {mios=“unit:house,device:144/service/urn:upnp-org:serviceId:VContainer1/VariableName3”}
String OpenHabMsgs1_Val3 “OpenHabMsgs1 Val 3 [%s]” (gRuleMessages) {mios=“unit:house,device:144/service/urn:upnp-org:serviceId:VContainer1/Variable3”}
String OpenHabMsgs1_Name4 “OpenHabMsgs1 Name 4 [%s]” (gRuleMessages) {mios=“unit:house,device:144/service/urn:upnp-org:serviceId:VContainer1/VariableName4”}
String OpenHabMsgs1_Val4 “OpenHabMsgs1 Val 4 [%s]” (gRuleMessages) {mios=“unit:house,device:144/service/urn:upnp-org:serviceId:VContainer1/Variable4”}
String OpenHabMsgs1_Name5 “OpenHabMsgs1 Name 5 [%s]” (gRuleMessages) {mios=“unit:house,device:144/service/urn:upnp-org:serviceId:VContainer1/VariableName5”}
String OpenHabMsgs1_Val5 “OpenHabMsgs1 Val 5 [%s]” (gRuleMessages) {mios=“unit:house,device:144/service/urn:upnp-org:serviceId:VContainer1/Variable5”}

/* Device - OpenHabMsgs2 */
Number OpenHabMsgs2_Id “ID [%d]” (gRuleMessages,gDisplayMultiStrings) {mios=“unit:house,device:174/id”}
String OpenHabMsgs2_Name1 “OpenHabMsgs2 Name 1 [%s]” (gRuleMessages) {mios=“unit:house,device:174/service/urn:upnp-org:serviceId:VContainer1/VariableName1”}
String OpenHabMsgs2_Val1 “OpenHabMsgs2 Val 1 [%s]” (gRuleMessages) {mios=“unit:house,device:174/service/urn:upnp-org:serviceId:VContainer1/Variable1”}
String OpenHabMsgs2_Name2 “OpenHabMsgs2 Name 2 [%s]” (gRuleMessages) {mios=“unit:house,device:174/service/urn:upnp-org:serviceId:VContainer1/VariableName2”}
String OpenHabMsgs2_Val2 “OpenHabMsgs2 Val 2 [%s]” (gRuleMessages) {mios=“unit:house,device:174/service/urn:upnp-org:serviceId:VContainer1/Variable2”}
String OpenHabMsgs2_Name3 “OpenHabMsgs2 Name 3 [%s]” (gRuleMessages) {mios=“unit:house,device:174/service/urn:upnp-org:serviceId:VContainer1/VariableName3”}
String OpenHabMsgs2_Val3 “OpenHabMsgs2 Val 3 [%s]” (gRuleMessages) {mios=“unit:house,device:174/service/urn:upnp-org:serviceId:VContainer1/Variable3”}
String OpenHabMsgs2_Name4 “OpenHabMsgs2 Name 4 [%s]” (gRuleMessages) {mios=“unit:house,device:174/service/urn:upnp-org:serviceId:VContainer1/VariableName4”}
String OpenHabMsgs2_Val4 “OpenHabMsgs2 Val 4 [%s]” (gRuleMessages) {mios=“unit:house,device:174/service/urn:upnp-org:serviceId:VContainer1/Variable4”}
String OpenHabMsgs2_Name5 “OpenHabMsgs2 Name 5 [%s]” (gRuleMessages) {mios=“unit:house,device:174/service/urn:upnp-org:serviceId:VContainer1/VariableName5”}
String OpenHabMsgs2_Val5 “OpenHabMsgs2 Val 5 [%s]” (gRuleMessages) {mios=“unit:house,device:174/service/urn:upnp-org:serviceId:VContainer1/Variable5”}
[/code]

Items for the MultiSwitch device in MiOS. Only the first 4 buttons are used.

Switch MsgLevelMS_Alert "MsgLevelMS Alert" (gMultiSwitch) {mios="unit:house,device:175/service/urn:dcineco-com:serviceId:MSwitch1/Status1,command:ON=urn:dcineco-com:serviceId:MSwitch1/SetStatus1(newStatus1=1)|OFF=urn:dcineco-com:serviceId:MSwitch1/SetStatus1(newStatus1=0),in:MAP(miosSwitchIn.map),out:MAP(miosSwitchOut.map)"} Switch MsgLevelMS_Warn "MsgLevelMS Warn" (gMultiSwitch) {mios="unit:house,device:175/service/urn:dcineco-com:serviceId:MSwitch1/Status2,command:ON=urn:dcineco-com:serviceId:MSwitch1/SetStatus2(newStatus2=1)|OFF=urn:dcineco-com:serviceId:MSwitch1/SetStatus2(newStatus2=0),in:MAP(miosSwitchIn.map),out:MAP(miosSwitchOut.map)"} Switch MsgLevelMS_Info "MsgLevelMS Info" (gMultiSwitch) {mios="unit:house,device:175/service/urn:dcineco-com:serviceId:MSwitch1/Status3,command:ON=urn:dcineco-com:serviceId:MSwitch1/SetStatus3(newStatus3=1)|OFF=urn:dcineco-com:serviceId:MSwitch1/SetStatus3(newStatus3=0),in:MAP(miosSwitchIn.map),out:MAP(miosSwitchOut.map)"} Switch MsgLevelMS_Debug "MsgLevelMS_Debug" (gMultiSwitch) {mios="unit:house,device:175/service/urn:dcineco-com:serviceId:MSwitch1/Status4,command:ON=urn:dcineco-com:serviceId:MSwitch1/SetStatus4(newStatus4=1)|OFF=urn:dcineco-com:serviceId:MSwitch1/SetStatus4(newStatus4=0),in:MAP(miosSwitchIn.map),out:MAP(miosSwitchOut.map)"} Switch MsgLevelMS_5 "MsgLevelMS Button 5" (gMultiSwitch) {mios="unit:house,device:175/service/urn:dcineco-com:serviceId:MSwitch1/Status5,command:ON=urn:dcineco-com:serviceId:MSwitch1/SetStatus5(newStatus5=1)|OFF=urn:dcineco-com:serviceId:MSwitch1/SetStatus5(newStatus5=0),in:MAP(miosSwitchIn.map),out:MAP(miosSwitchOut.map)"} Switch MsgLevelMS_6 "MsgLevelMS Button 6" (gMultiSwitch) {mios="unit:house,device:175/service/urn:dcineco-com:serviceId:MSwitch1/Status6,command:ON=urn:dcineco-com:serviceId:MSwitch1/SetStatus6(newStatus6=1)|OFF=urn:dcineco-com:serviceId:MSwitch1/SetStatus6(newStatus6=0),in:MAP(miosSwitchIn.map),out:MAP(miosSwitchOut.map)"} Switch MsgLevelMS_7 "MsgLevelMS Button 7" (gMultiSwitch) {mios="unit:house,device:175/service/urn:dcineco-com:serviceId:MSwitch1/Status7,command:ON=urn:dcineco-com:serviceId:MSwitch1/SetStatus7(newStatus7=1)|OFF=urn:dcineco-com:serviceId:MSwitch1/SetStatus7(newStatus7=0),in:MAP(miosSwitchIn.map),out:MAP(miosSwitchOut.map)"} Switch MsgLevelMS_8 "MsgLevelMS Button 8" (gMultiSwitch) {mios="unit:house,device:175/service/urn:dcineco-com:serviceId:MSwitch1/Status8,command:ON=urn:dcineco-com:serviceId:MSwitch1/SetStatus8(newStatus8=1)|OFF=urn:dcineco-com:serviceId:MSwitch1/SetStatus8(newStatus8=0),in:MAP(miosSwitchIn.map),out:MAP(miosSwitchOut.map)"}

Item for HomeWave notification push

Group gHomeWavePush Number HomeWavePushId "ID [%d]" (gHomewavePush) {mios="unit:house,device:21/id"}

OpenHAB rule file for my message handler. Some of the indentation is a little off because I seem to have mixed up some leading spaces with leading tabs. Sorry about that.

[code]import org.openhab.core.library.types.*
import org.openhab.core.persistence.*
import org.openhab.model.script.actions.*
import org.openhab.core.library.items.*
import org.eclipse.xtext.xbase.lib.*

import java.util.*
import java.util.Comparator
import java.lang.Integer
import java.util.Collections.*
import java.util.concurrent.locks.ReentrantLock

// lock is used to make sure that one message is completely processed before the next one starts.
// That will ensure they are handled in the order they are received.
var java.util.concurrent.locks.ReentrantLock msgLock = new java.util.concurrent.locks.ReentrantLock()

// the message history, stored by level. By keeping it organized by level we
// can be sure to have the last N messages for each time, preventing, for example,
// alert messages from being crowded out of the list by debug messages
val HashMap<String,List> histByLevel = newHashMap(
“ALERT” → newArrayList() as List,
“WARN” → newArrayList() as List,
“INFO” → newArrayList() as List,
“DEBUG” → newArrayList() as List
)

/**

  • add the message pair to the end of the list, then trim the list

  • size down to the maximum size by removing the first element.

  • It returns the message that was added.
    */
    val Functions$Function2<Pair, List, Pair> addMsg = [
    Pair newMsg,
    List msgList |

    msgList.add(newMsg)
    if ( msgList.size() > (gDisplayMultiStrings?.members.size() * 5) ) {
    msgList.remove(0)
    }

    return newMsg
    ]

/**

  • raise alerts, to my iPhone through Homewave, using pushover, or just log it,

  • depending upon the level
    */
    val Functions$Function2<String,Pair> raiseAlert = [
    String level,
    Pair msgObj |

    var text = msgObj.value as String
    // Send the messages before updating the display
    // TODO: Control pushover and iPhone from MiOS buttons
    // ALERT is my own level - used to send high-priority messages, like for leaks in the basement
    if ( level == “ALERT” ) {
    // send to pushover and iphone
    logWarn(“MyRules”, text)
    sendMiosAction(HomeWavePushId, “urn:intvelt-com:serviceId:HWPush1/SendMessage”, newArrayList(‘Msg’ → text))
    pushover(text, 1, “”, “”, “siren”)
    }
    else if ( level == “WARN” ) {
    // send to pushover and iphone
    logWarn(“MyRules”, text)
    sendMiosAction(HomeWavePushId, “urn:intvelt-com:serviceId:HWPush1/SendMessage”, newArrayList(‘Msg’ → text))
    pushover(text)
    }
    else if ( level == “INFO” ) {
    // send to pushover
    logInfo(“MyRules”, text)
    pushover(text)
    }
    else {
    // debug, just log
    logDebug(“MyRules”, text)
    }
    return null
    ]

/**

  • get the most recent messages and update the multistring devices to display them.

  • The messages selected depend upon the level, with messages with a severity equal

  • to or greater than the given level displayed.

  • The severity order is ALERT > WARN > INFO > DEBUG
    */
    val Functions$Function1<HashMap<String,List>,List> updateMsWithMessages = [
    HashMap<String,List> msgMap |

    val String level = DisplayMsgLevel.state.toString()
    val devices = gDisplayMultiStrings?.members as List

    var List msgs = newArrayList() as List

    if ( level == “ALERT” ) {
    msgs.addAll(msgMap.get(“ALERT”))
    }
    else if ( level == “WARN” ) {
    msgs.addAll(msgMap.get(“ALERT”))
    msgs.addAll(msgMap.get(“WARN”))
    }
    else if ( level == “INFO” ) {
    msgs.addAll(msgMap.get(“ALERT”))
    msgs.addAll(msgMap.get(“WARN”))
    msgs.addAll(msgMap.get(“INFO”))
    }
    else {
    msgs.addAll(msgMap.get(“ALERT”))
    msgs.addAll(msgMap.get(“WARN”))
    msgs.addAll(msgMap.get(“INFO”))
    msgs.addAll(msgMap.get(“DEBUG”))
    }

    // sort by date, with the newest first
    var v = [Pair a, Pair b |
    (b.key as Date).compareTo(a.key as Date)
    ] as Comparator<Pair,Pair>

    Collections::sort(msgs,v)

    // truncate or pad our list to exactly the right size
    var msgCnt = devices.size() * 5
    if (msgs.size() > msgCnt) {
    msgs.subList(msgCnt, msgs.size()).clear();
    }
    else {
    while ( msgs.size() < msgCnt ) {
    msgs.add((new Date()) → ‘’)
    }
    }

    // update the multi-string devices
    var int idx = 0
    for(NumberItem msId : devices) {
    var int msRow = 1
    while (msRow <= 5) {
    var msgPair = msgs.get(idx)
    if ( msgPair.value.equals(‘’) ) {
    sendMiosAction(msId,
    (“urn:upnp-org:serviceId:VContainer1/SetVariableName”+msRow),
    newArrayList((‘newVariableName’+msRow) → ‘’)
    )
    sendMiosAction(msId,
    (“urn:upnp-org:serviceId:VContainer1/SetVariable” +msRow),
    newArrayList((‘newVariable’+msRow) → ‘’)
    )
    }
    else {
    sendMiosAction(msId,
    (“urn:upnp-org:serviceId:VContainer1/SetVariableName”+msRow),
    newArrayList((‘newVariableName’+msRow) → String::format(“%1$tm/%1$td %1$tH:%1$tM:%1$tS”,msgPair.key as Date)))
    sendMiosAction(msId,
    (“urn:upnp-org:serviceId:VContainer1/SetVariable”+msRow),
    newArrayList((‘newVariable’+msRow) → msgPair.value as String)
    )
    }
    idx = idx + 1
    msRow = msRow +1
    }
    }

    return msgs
    ]

rule “Alert Message Received”
when
Item AlertMsg received update
then
// save the message right away to avoid it getting overwritten by another thread.
var msgPair = new Date()-> AlertMsg.state.toString()
val level = “ALERT”

logDebug("MyRules", "got AlertMsg update")

msgLock.lock()

try {
	addMsg.apply(msgPair, histByLevel.get(level))
	raiseAlert.apply(level, msgPair)
	updateMsWithMessages.apply(histByLevel)
} 
catch (Exception e) {
	logError("MyRules", "Caught exception in AlertMsg handler", e)
}
finally {
    msgLock.unlock()
}

end

rule “Warn Message Received”
when
Item WarnMsg received update
then
// save the message right away to avoid it getting overwritten by another thread.
var msgPair = new Date()-> WarnMsg.state.toString()
val level = “WARN”

logDebug("MyRules", "got WarnMsg update")

msgLock.lock()

try {
	addMsg.apply(msgPair, histByLevel.get(level))
	raiseAlert.apply(level, msgPair)
	updateMsWithMessages.apply(histByLevel)
} 
catch (Exception e) {
	logError("MyRules", "Caught exception in WarnMsg handler", e)
}
finally {
    msgLock.unlock()
}

end

rule “Info Message Received”
when
Item InfoMsg received update
then
// save the message right away to avoid it getting overwritten by another thread.
var msgPair = new Date()-> InfoMsg.state.toString()
val level = “INFO”

logDebug("MyRules", "got InfoMsg update")

msgLock.lock()

try {
	addMsg.apply(msgPair, histByLevel.get(level))
	raiseAlert.apply(level, msgPair)
	updateMsWithMessages.apply(histByLevel)
}
catch (Exception e) {
	logError("MyRules", "Caught exception in InfoMsg handler", e)
}
finally {
    msgLock.unlock()
}

end

rule “Debug Message Received”
when
Item DebugMsg received update
then
// save the message right away to avoid it getting overwritten by another thread.
var msgPair = new Date()-> DebugMsg.state.toString()
val level = “DEBUG”

logDebug("MyRules", "got Debug update")

msgLock.lock()

try {
	addMsg.apply(msgPair, histByLevel.get(level))
	raiseAlert.apply(level, msgPair)
	updateMsWithMessages.apply(histByLevel)
} 
catch (Exception e) {
	logError("MyRules", "Caught exception in Debug handler", e)
}
finally {
    msgLock.unlock()
}

end

/**

  • if the level changes, display the messages with the new level
    */
    rule “Display message level changed”
    when
    Item DisplayMsgLevel changed
    then
    msgLock.lock()
    try {
    updateMsWithMessages.apply(histByLevel)
    }
    catch (Exception e) {
    logError(“MyRules”, “Caught exception after DisplayMsgLevel changed”, e)
    }
    finally {
    msgLock.unlock()
    }
    end

rule “Message level changed to ALERT”
when
Item MsgLevelMS_Alert changed to ON
then
postUpdate(DisplayMsgLevel, “ALERT”)
end

rule “Message level changed to WARN”
when
Item MsgLevelMS_Warn changed to ON
then
postUpdate(DisplayMsgLevel, “WARN”)
end

rule “Message level changed to INFO”
when
Item MsgLevelMS_Info changed to ON
then
postUpdate(DisplayMsgLevel, “INFO”)
end

rule “Message level changed to DEBUG”
when
Item MsgLevelMS_Debug changed to ON
then
postUpdate(DisplayMsgLevel, “DEBUG”)
end
[/code]

Example usage in simple rules

[code]rule “Water by sump pump”
when
Item LeakDetectorbySumpPumpTripped changed to OPEN
then
postUpdate(AlertMsg, “Water by sump pump”)
end

rule “Liquor cabinet open”
when
Item LiquorCabinetTripped changed to OPEN
then
if ( LiquorCabinetArmed.state == ON ) {
postUpdate(WarnMsg, “Liquor cabinet open”)
}
else {
postUpdate(InfoMsg, “Liquor cabinet open”)
}
end

[/code]