可重复使用的家庭自动化规则。
项目描述
Zone API - 编写规则的另一种方法
在 OpenHab 中,项目以平面方式定义在/etc/openhab/items 文件夹下的.items文件中。它们通常链接到底层硬件公开的通道。这种扁平结构会影响规则(无论是在 Xtend 还是 Jython 中)的组织方式。由于没有更高级别的抽象,规则倾向于监听来自特定设备的变化。当规则需要与多个同类型的设备交互时,可以利用 组的概念。组的良好用法的一个示例是关闭所有灯。通过将所有智能灯链接到一个组开关,可以通过将组开关的状态更改为关闭来关闭所有灯。
更棘手的是当规则需要与同一区域内的不同设备交互时。典型的解决方案是通过使用命名模式或通过专用组对属于同一区域的不相关项目进行分组。例如,Foyer 区域的灯开关和运动传感器可以这样命名:“FF_Foyer_Light”和“FF_Foyer_MotionSensor”。当传感器被触发时,区域可以从触发项目的名称中派生出来,并且可以使用该命名约定检索其他设备/传感器。这可行,但由于没有足够的抽象,规则与命名模式高度耦合。
Zone API提供了另一种方法。它是设备/传感器之上的一层。每个ZoneManager (即房子)包含多个区域 (即房间),每个区域包含多个设备。每个区域都与由特定事件 触发的一组动作相关联。通常的 OpenHab 事件以这种方式路由:
OpenHab events --> ZoneManager --> Zones --> Actions
这些操作在抽象设备上运行,不关心项目或底层硬件的命名。它们取代了传统的 OpenHab 规则。可以使用各种级别的模拟对操作进行单元测试。
最重要的是,它可以重用动作逻辑。无需为开/关灯等常见规则重新发明轮子。所有需要做的就是填充区域和设备/传感器,然后将自动添加和处理适用的操作。
ZoneApi 带有一组内置操作。无需确定要向系统添加什么操作。相反,它们会根据区域结构和每个区域中可用的设备类型自动添加。
这是一个示例信息日志,说明了托管对象的结构。
Zone: Kitchen, floor: FIRST_FLOOR, internal, displayIcon: kitchen, displayOrder: 3, 7 devices
AstroSensor: VT_Time_Of_Day
HumiditySensor: FF_Kitchen_Humidity
IlluminanceSensor: FF_Kitchen_LightSwitch_Illuminance
Light: FF_Kitchen_LightSwitch, duration: 5 mins, illuminance: 8
MotionSensor: FF_Kitchen_SecurityMotionSensor, battery powered
MotionSensor: FF_Kitchen_LightSwitch_PantryMotionSensor, battery powered
TemperatureSensor: FF_Kitchen_Temperature
Action: HUMIDITY_CHANGED -> AlertOnHumidityOutOfRange
Action: MOTION -> TurnOnSwitch
Action: MOTION -> AnnounceMorningWeatherAndPlayMusic
Action: MOTION -> PlayMusicAtDinnerTime
Action: SWITCH_TURNED_ON -> TurnOffAdjacentZones
Action: TEMPERATURE_CHANGED -> AlertOnTemperatureOutOfRange
Action: TIMER -> TellKidsToGoToBed
Neighbor: FF_Foyer, OPEN_SPACE
Neighbor: FF_GreatRoom, OPEN_SPACE_MASTER
Zone: Foyer, floor: FIRST_FLOOR, internal, displayIcon: groundfloor, displayOrder: 4, 6 devices
AlarmPartition: FF_Foyer_AlarmPartition, armMode: ARM_STAY
AstroSensor: VT_Time_Of_Day
Door: FF_Foyer_Door
Light: FF_Foyer_LightSwitch, duration: 5 mins, illuminance: 8, no premature turn-off time range: 0-23:59
MotionSensor: FF_Foyer_LightSwitch_ClosetMotionSensor, battery powered
MotionSensor: FF_Foyer_LightSwitch_MotionSensor, battery powered
Action: MOTION -> TurnOnSwitch
Action: MOTION -> DisarmOnInternalMotion
Action: MOTION -> ManagePlugs
Action: PARTITION_ARMED_AWAY -> ChangeThermostatBasedOnSecurityArmMode
Action: PARTITION_ARMED_AWAY -> ManagePlugs
Action: PARTITION_ARMED_AWAY -> TurnOffDevicesOnAlarmModeChange
Action: PARTITION_DISARMED_FROM_AWAY -> ChangeThermostatBasedOnSecurityArmMode
Action: PARTITION_DISARMED_FROM_AWAY -> ManagePlugs
Action: PARTITION_DISARMED_FROM_AWAY -> TurnOffDevicesOnAlarmModeChange
Action: SWITCH_TURNED_ON -> TurnOffAdjacentZones
Action: TIMER -> ArmStayIfNoMovement
Action: TIMER -> ArmStayInTheNight
Action: TIMER -> ManagePlugs
Neighbor: SF_Lobby, OPEN_SPACE
Neighbor: FF_Office, OPEN_SPACE_MASTER
在 HABApp 之上运行,但依赖最小:
最初的 Zone API 模块 是用 Jython 编写的。它最近已迁移到HABApp 框架,只需对核心代码进行最少的更改。有关 HABApp 和 JSR223 Jython 之间的比较,请参见此处 。
有 3 个外围模块与 HABApp API 紧密耦合。其余模块是框架中立的。如果可用,可以将 Zone API 迁移到在 GravVM 之上运行的另一个框架。Zone API 现在是用 Python 3 编写的,因此与 Jython(相当于 Python 2.8)不兼容。
使用 zone_api 设置您的家庭自动化系统
1. 使用默认命名约定命名 OpenHab 项目
Zone_api附带一个默认解析器,它使用预定义的命名约定构建区域管理器。有关详细信息,请参阅本页末尾的 ZoneParser 部分。
以下是一些示例 .items 文件。请注意,文件组织无关紧要;如果需要,所有项目都可以在单个文件中定义。
zone.items:定义两个区域及其关系。
String Zone_Office
{ level="FF", displayIcon="office", displayOrder="2",
openSpaceSlaveNeighbors="FF_Foyer" }
String Zone_Foyer
{ level="FF", displayIcon="groundfloor", displayOrder="4",
openSpaceMasterNeighbors="FF_Office",
openSpaceNeighbors="SF_Lobby" }
foyer.items:定义大厅区域中的项目。
Switch FF_Foyer_LightSwitch "Foyer Light" (gWallSwitch, gLightSwitch, gFirstFloorLightSwitch)
{ channel="zwave:device:9e4ce05e:node2:switch_binary",
disableMotionTriggeringIfOtherLightIsOn="FF_Office_LightSwitch",
durationInMinutes="5"}
Switch FF_Foyer_LightSwitch_ClosetMotionSensor "Foyer Closet Motion Sensor"
(gWallSwitchMotionSensor)
{ channel="mqtt:topic:myBroker:xiaomiMotionSensors:FoyerMotionSensor"}
office.items:定义 Office 区域中的项目。
Switch FF_Office_LightSwitch "Office Light" (gWallSwitch, gLightSwitch, gFirstFloorLightSwitch)
[shared-motion-sensor]
{ channel="zwave:device:9e4ce05e:node8:switch_binary",
durationInMinutes="15" }
Switch FF_Office_LightSwitch_MotionSensor "Office Motion Sensor"
(gWallSwitchMotionSensor, gFirstFloorMotionSensors)
{ channel="mqtt:topic:myBroker:xiaomiMotionSensors:OfficeMotionSensor"}
而已。系统完全设置后,ZoneApi 的默认操作将根据可用设备自动注册。
在上面的示例中,两个区域都有电灯开关和运动传感器。因此,灯光规则适用,并在触发运动传感器时自动打开灯,如果在预定义的持续时间内没有活动,则将其关闭。它还将关闭相关区域的灯。
2. 克隆这个仓库
git clone git@github.com:yfaway/zone-apis.git
3.安装、配置和运行HABapp
请参阅HABApp 官方网站上的说明。下面的说明专门针对 zone_api。
sudo apt-get install python3-venv # to install python3-venv library
cd zone_api # the cloned project in the section above
python3 -m venv .
source bin/activate # to get into our virtual environment
python3 -m pip install --upgrade pip # to upgrade the pip library.
python3 -m pip install habapp request schedule # request and schedule are required by zone_api
要手动运行 HABApp,请在文件夹中执行以下命令zone_api:
./bin/habapp --config ./habapp/config.yml
该文件夹包含用于初始化框架./habapp/rules的引导规则。zone_api该规则非常简单,其全部内容如下。
import HABApp
from zone_api import zone_parser as zp
from zone_api.core.devices.activity_times import ActivityType, ActivityTimes
class ConfigureZoneManagerRule(HABApp.Rule):
def __init__(self):
super().__init__()
self.run_soon(self.configure_zone_manager)
# noinspection PyMethodMayBeStatic
def configure_zone_manager(self):
time_map = {
ActivityType.WAKE_UP: '6 - 9',
ActivityType.LUNCH: '12:00 - 13:30',
ActivityType.QUIET: '14:00 - 16:00, 20:00 - 22:59',
ActivityType.DINNER: '17:50 - 20:00',
ActivityType.SLEEP: '23:00 - 7:00',
ActivityType.AUTO_ARM_STAY: '20:00 - 2:00',
ActivityType.TURN_OFF_PLUGS: '23:00 - 2:00',
}
zone_manager = zp.parse(ActivityTimes(time_map))
ConfigureZoneManagerRule()
上面的代码定义了一个具有各种活动时间段的 ActivityTimes 对象,并将其传递给zone_parser 模块。zone_parser 按照特定的命名模式解析 OpenHab 项目,并构建区域和设备/传感器。然后它为与设备/传感器关联的事件注册处理程序。最后,它会加载所有动作,并根据与每个动作相关联的预先声明的执行规则将它们添加到区域(稍后会详细介绍)。而已; 从现在开始,设备/传感器产生的事件将触发相关的动作。
需要注意的是 zone_parser 只是构建 ZoneManager 的默认机制。自定义模块可用于解析 OpenHab 项目的不同命名模式,或者可以手动构建 ZoneManager。构建 ZoneManager 后,不再需要解析器的角色。
区域管理器
包含一组区域并负责将事件分派到区域。
区
包含一组设备、动作,并负责将事件分派给动作。
一个区域知道它的邻居。某些规则(例如灯的打开/关闭 )高度依赖于区域的布局。可以使用以下邻居 类型。
CLOSED_SPACEOPEN_SPACEOPEN_SPACE_MASTEROPEN_SPACE_SLAVE
设备
这些设备 包含一个或多个底层 OpenHab 项目。该设备不是在 SwitchItem 或 NumberItem 上操作,而是表示有意义的具体事物,例如MotionSensor或Light。设备包含属性(例如“门是否打开”)和行为(例如“武装安全系统”)。
活动
与设备的抽象类似,事件也更加具体。Zone API 将 OpenHab 项目事件映射到 ZoneEvent 中的事件枚举,
例如ZoneEvent.HUMIDITY_CHANGED或ZoneEvent.PARTITION_ARMED_AWAY。还有ZoneEvent.TIMER表示从调度程序触发的特殊事件。
事件被分派到适当的区域,然后调用为该事件注册的操作。有关详细信息,请参阅EventInfo 。
行动
所有的动作都实现了Action接口。动作的生命周期由三个函数表示:
on_startup()- 在 ZoneManager 完全填充后通过 event 调用ZoneEvent.STARTUP。on_action()- 在设备生成事件或触发定时器事件时调用(通过ZoneEvent.TIMER)。on_destroy()- 当前未调用。
装饰器@action为操作提供执行规则以及基本验证。如果条件(基于执行规则)不匹配,则不会执行该操作。以下是当前支持的装饰器参数:
- 设备- 区域必须拥有的设备列表才能调用该操作。
- events - 操作将响应的事件列表。
- internal - 如果设置,此操作仅适用于内部区域
- external - 如果设置,此操作仅适用于外部区域
- 级别- 此操作适用的区域级别。空列表默认值表示该操作适用于所有区域级别。
- unique_instance - 如果设置,则不要跨区域共享相同的操作实例。当操作是有状态的时就是这种情况。
- zone_name_pattern - 如果设置,则适用于此操作的区域名称正则表达式。
- external_events - 此操作处理的其他区域的事件列表。这些事件不会使用与来自其他区域的内部事件相同的机制进行过滤。
- priority - 相对于同一区域内其他操作的操作优先级。优先级值较低的操作首先执行。
这些参数也可用于操作,并可用作过滤机制以确保操作仅添加到适用的区域。
这是触发运动传感器时解除安全系统的简单操作:
from zone_api import security_manager as sm
from zone_api.core.devices.activity_times import ActivityTimes
from zone_api.core.devices.motion_sensor import MotionSensor
from zone_api.core.zone_event import ZoneEvent
from zone_api.core.action import action
from zone_api.core.devices.alarm_partition import AlarmPartition
@action(events=[ZoneEvent.MOTION], devices=[AlarmPartition, MotionSensor])
class DisarmOnInternalMotion:
"""
Automatically disarm the security system when the motion sensor in the zone containing the
security panel is triggered and the current time is not in the auto-arm-stay or sleep
time periods.
"""
def on_action(self, event_info):
events = event_info.get_event_dispatcher()
zone_manager = event_info.get_zone_manager()
if not sm.is_armed_stay(zone_manager):
return False
activity = zone_manager.get_first_device_by_type(ActivityTimes)
if activity is None:
self.log_warning("Missing activities time; can't determine wake-up time.")
return False
if activity.is_auto_arm_stay_time() or (activity.is_sleep_time() and not activity.is_wakeup_time()):
return False
sm.disarm(zone_manager, events)
return True
上述动作的装饰器表明它是由运动事件触发的,并且只能添加到同时包含 AlarmPartition 和运动设备的区域。
区域解析器
默认解析器对 OpenHab 项目使用此命名模式。
-
区域被定义为具有此模式 Zone_{name} 的字符串项:
String Zone_GreatRoom { level="FF", displayIcon="player", displayOrder="1", openSpaceSlaveNeighbors="FF_Kitchen" }- 级别是 Zone::Level 中枚举的反向映射。
- 以下是支持的属性列表:level、external、openSpaceNeighbors、openSpaceMasterNeighbors、openSpaceSlaveNeighbors、displayIcon、displayOrder。
-
各个 OpenHab 项目均以此约定命名:
{zone_id}_{device_type}_{device_name}.这是一个例子:
Switch FF_Office_LightSwitch "Office Light" (gWallSwitch, gLightSwitch, gFirstFloorLightSwitch) [shared-motion-sensor] { channel="zwave:device:9e4ce05e:node8:switch_binary", durationInMinutes="15" }
请参阅此处以获取可由 ZoneParser 解析的示例 .items 文件。