Skip to content

Instantly share code, notes, and snippets.

@cweitkamp
Last active December 4, 2021 23:14
Show Gist options
  • Save cweitkamp/d0a1f7a2c0186c0ce13390a036fa2f11 to your computer and use it in GitHub Desktop.
Save cweitkamp/d0a1f7a2c0186c0ce13390a036fa2f11 to your computer and use it in GitHub Desktop.
Bayesian Sensor Aggregation
Switch proxySwitch "I am ... [MAP(sleeping.map):%s]"
Number proxyNumber "Probability [%.5f %%]"
Group gBAYESIAN "Bayesian Sleep Sensor" {
bayesian="proxySwitch" [ prior=0.5, threshold=0.85, state_given_true="ON", state_given_false="OFF" ]
}
Switch testSwitch1 "I am at home ... [MAP(yes-no.map):%s]" <presence> (gBAYESIAN) {
bayesian="ON" [ prob_given_true=0.99, prob_given_false=0.5 ]
}
Switch testSwitch2 "It is after sunset ... [MAP(yes-no.map):%s]" <sunset> (gBAYESIAN) {
bayesian="ON" [ prob_given_true=0.75, prob_given_false=0.3 ]
}
String testString1 "The time of day is ... [%s]" <sunset> (gBAYESIAN) {
bayesian="NIGHT" [ prob_given_true=0.75, prob_given_false=0.3 ]
}
Switch testSwitch3 "The lights are ... [%s]" <light> (gBAYESIAN) {
bayesian="OFF" [ prob_given_true=0.9, prob_given_false=0.4 ]
}
Switch testSwitch4 "My phone is muted ... [MAP(yes-no.map):%s]" <soundvolume-muted> (gBAYESIAN) {
bayesian="ON" [ prob_given_true=0.95, prob_given_false=0.5 ]
}
from core.rules import rule
from core.triggers import when
from core.actions import LogAction
from core.metadata import get_metadata
RULE_NAME = "bayesian"
GROUP_NAME = "gBAYESIAN"
def update_probability(prior, prob_given_true, prob_given_false):
numerator = prob_given_true * prior
denominator = numerator + prob_given_false * (1 - prior)
return numerator / denominator
@rule("Bayesian Sensor Aggregation")
@when("Member of " + GROUP_NAME + " changed")
def bayesianSensorAggregation(event):
LogAction.logDebug(RULE_NAME, "Item '{}' changed from '{}' to '{}'.", event.itemName, event.oldItemState, event.itemState)
group = ir.getItem(GROUP_NAME)
if group is None:
LogAction.logError(RULE_NAME, "Group '{}' is missing.", GROUP_NAME)
return
groupMetadata = get_metadata(GROUP_NAME, "bayesian")
LogAction.logDebug(RULE_NAME, "Group '{}': metadata = {}", GROUP_NAME, groupMetadata)
if groupMetadata is None:
LogAction.logError(RULE_NAME, "Group '{}': metadata is missing.", GROUP_NAME)
return
if ir.getItem(groupMetadata.value) is None:
LogAction.logError(RULE_NAME, "Group '{}': targetItem is missing.", GROUP_NAME)
return
if groupMetadata.configuration["prior"] is None:
LogAction.logError(RULE_NAME, "Group '{}': prior is missing.", GROUP_NAME)
return
prior = groupMetadata.configuration["prior"].floatValue()
LogAction.logDebug(RULE_NAME, "Group '{}': prior = {}", GROUP_NAME, prior)
threshold = groupMetadata.configuration["threshold"]
if threshold is None:
LogAction.logError(RULE_NAME, "Group '{}': threshold is missing.", GROUP_NAME)
return
# iterate group members
for item in ir.getItem(GROUP_NAME).members:
itemMetadata = get_metadata(item.name, "bayesian")
LogAction.logDebug(RULE_NAME, "Item '{}': metadata = {}", item.name, itemMetadata)
if itemMetadata is None:
LogAction.logError(RULE_NAME, "Item '{}': metadata is missing.", item.name)
continue
prob_given_true = itemMetadata.configuration["prob_given_true"]
if prob_given_true is None:
LogAction.logError(RULE_NAME, "Item '{}': prob_given_true is missing.", item.name)
continue
prob_given_false = itemMetadata.configuration["prob_given_false"]
if prob_given_false is None:
LogAction.logDebug(RULE_NAME, "Item '{}': prob_given_false is missing - using default: (1 - prob_given_true)", item.name)
prob_given_false = 1 - prob_given_true.floatValue()
else:
prob_given_false = prob_given_false.floatValue()
# apply bayesian rule
if str(item.state) == itemMetadata.value:
prior = update_probability(prior, prob_given_true.floatValue(), prob_given_false)
LogAction.logDebug(RULE_NAME, "Group '{}': posterior = {}", GROUP_NAME, prior)
if not ir.getItem("proxyNumber") is None:
# update posterior probability
events.sendCommand("proxyNumber", str(prior * 100))
state_given_true = groupMetadata.configuration["state_given_true"]
if state_given_true is None:
LogAction.logDebug(RULE_NAME, "Group '{}': state_given_true is missing - using default: ON", GROUP_NAME)
state_given_true = "ON"
state_given_false = groupMetadata.configuration["state_given_false"]
if state_given_false is None:
LogAction.logDebug(RULE_NAME, "Group '{}': state_given_false is missing - using default: OFF", GROUP_NAME)
state_given_false = "OFF"
# update proxy item
events.sendCommand(groupMetadata.value, str(state_given_true if prior >= threshold.floatValue() else state_given_false))
sitemap bayesian label="Bayesian Sensor Aggregation" {
Frame label="Bayesian Sleep Sensor" {
Switch item=proxySwitch
Text item=proxyNumber
}
Frame label="Presence Sensor" {
Switch item=testSwitch1
}
Frame label="Time Of Day Sensor" {
Switch item=testSwitch2
Selection item=testString1 mappings=["DAY"="Day", "NIGHT"="Night"]
}
Frame label="Lights" {
Switch item=testSwitch3
}
Frame label="Phone" {
Switch item=testSwitch4
}
}
ON=sleeping
OFF=not sleeping
NULL=-
UNDEF=-
-=-
ON=YES
OFF=NO
NULL=-
UNDEF=-
-=-
@robitasmo
Copy link

Hi Christoph i've found your presentation very intresting. Forgive my ignorance...may I ask you how to use the bayesian.pi file? I've an OH installation up and running on a ARMBIAN based hardware. I'd like to replicate your sensor aggregation idea!

@cweitkamp
Copy link
Author

Hi @robitasmo,

Thanks for your interest in my demo. To use it you have to install OH 2.5.x and the following requirements: Map Transformation and Jython. It should be possible to use the demo setup out-of-the-box by dropping it into your OPENHAB_CONF/ folder.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment