Із лютого всі ми стали трошки іншими, але я абсолютно впевнений - для досягнення результату, як глобального так і локального всі мають робити те шо вміють та не впадати у відчай, я буду потроху відроджувати тему та свій медіум.
Цього разу хочу розповісти про штуку, яку ніде більше не бачив і яку придумав та реалізував сам. Теза, якою можна охарактеризувати все подальше - контроль над автоматизацією.
Як я писав раніше - я вважаю шо вся магія автоматизації має бути легко відключаєма, бо завжди може виникнути ситуація за якої шось десь пішло не так і ваш будинок "зійшов з глузду". Сценарій коли скайнет захоче вам врубити опалення на всі гроші бо якийсь датчик помер чи банально якась помилка в алгоритмах - досить реалістична і станеться не раз і не два. Так, безумовно завжди можна вимкнути залізку з опенхабом із розетки, але в цьому випадку ви вимкнете і всю іншу логіку, яка може працювати без проблем роками, да і взагалі це не шлях самурая.
В якийсь момент я вирішив шо проблему контролю над автоматизацією треба робити системно і максимально просто, щоби сама логіка контролю не створювала проблем.
Вимоги яки сформулював:
Механізм має бути простим та прозорим
Він має максимально-просто натягуватися як на існуючий так і на майбутній функционал
Має мати максимально-просте керування
Вирішив шо максимально просте що можна придумати це вимикач "робити магію / не робити магію", оскільки магії в автоматизації багато то перемикачів має бути декілька, на кожну ділянку автоматизації, далі ці вимикачі можна зібрати на одну сторінку інтерфейсу де будуть тільки вони, що максимально спростить маніпуляції.
Якщо ці всі "перемикачі" забрати в одну вертикальну группу то можна їх автоматично додавати на сторінку без додаткових рухів. Це все довгий час так і існувало, але згодом я в якійсь програмі смартхоуму наткнувся на дуже зручну штуку - щось вимикати можна не на постійно а на якийсь проміжок часу, щоб згодом воно само увімкнулося без участі користувача. Мені це так сподобалося шо я вирішив додати це і собі, зрозуміло шо простими вимикачами це вже не розрулити, але хотілося нічого не переписувати особливо в існуючій автоматизації.
Далі просто, без води, як я це зробив і як воно працює.
Заводимо спеціальну вертикальну не семантичну групу і в цій группі створюємо стільки *стрінгових* айтемів скільки нам треба під керування різніми ділянками автоматизації:
Automation.items
Group gAutomation "Автоматизація [%d]" <status>
String Automation_Humidification "Керування зволоженням" <hvac_humidity> (Home, gAutomation) ["Humidity", "Switch"] {description="Автоматичне керування наявними зволожувачами по окремим приміщенням"}
String Automation_EcoMode "Керування енергозбереженням" <ecohouse> (Home, gAutomation) ["Presence", "Switch"] {description="Автоматичне керування енергоспоживанням задля економії"}
String Automation_HotWaterRecirculation "Автокерування наcосом рециркуляції" <ecohouse> (Home, gAutomation) ["Water", "Switch"] {description="Автоматичне керування рециркуляцією задля економії"}
Тут з цікавого те шо я для атемів придумав description опцію, яку використаю далі
Сценарій автоматичного перетворення цих *стрінгових* айтемів в спеціальні перемикачі на декілька положень
scriptLoaded = function () {
// для всіх айтемів в групі навішую автоматично варіанти перемикання, це сам опенхаб показує як перемиках на декілька позицій
items.getItem('gAutomation').members.forEach(function(item) {
try {
metadata.addValue(item.name, 'stateDescription', "", {
'options': 'ON=Увімкнено,OFF=Вимкнено,1HOFF=Вимкнено на 1 годину, 12HOFF=Вимкнено на 12 годин, 24OFF=Вимкнено на добу'
});
metadata.addValue(item.name, 'commandDescription', "", {
'options': 'ON=Увімкнено,OFF=Вимкнено,1HOFF=Вимкнено на 1 годину, 12HOFF=Вимкнено на 12 годин, 24OFF=Вимкнено на добу'
});
} catch (e) {
// нажаль метод upsert у js бібліотеці 3.4 виявився кривим і доводиться через addValue та try/catch
}
});
}
Сценарій який при зміні положення будь якого перемикача зберігає дату зміни, насправді це костиль через те що існуюча бібліотека JS чомусь не дає доступу до дати *зміни* значення (є тільки оновлення, а оновлення може бути і на старе значення)
rules.JSRule({
name: "Track automation time",
description: "Set meta auto timer",
triggers: [triggers.GroupStateChangeTrigger('gAutomation')],
execute: (event) => {
let t = time.ZonedDateTime.now().toString();
items.getItem(event.itemName).upsertMetadataValue('updated', `${t}`);
items.getItem(event.itemName).upsertMetadataValue('automation', '');
},
id: `automation_times`
});
Сценарій який робить ту саму магію яку я підгледів в якомусь смартхоумі, а саме котролюємо коли треба перемикач повернути в положення "увімкнуто"
rules.JSRule({
name: "Track automation iterval",
description: "Set back auto timer",
triggers: [triggers.GenericCronTrigger("0 0/1 * * * ?")],
execute: (event) => {
items.getItem('gAutomation').members.forEach(function(item) {
let minutes = {
'1HOFF': 60,
'12HOFF': 12*60,
'24OFF': 24*60,
};
// раз в хвиилину перевіряємо всі айтеми в групі автоматизації
// якщо у айтема стен "тимчасовий" і якщо він не мінявся певний час то переводимо в стан ON
// янакше додатково айтему виставляюмо в мета-дані час в який він теоретично має перемкнутися (лоск)
if (item.state in minutes) {
let t = time.ZonedDateTime.now().minusMinutes(minutes[item.state]);
let elapsed = '';
try {
let updated = time.toZDT(item.getMetadataValue('updated'));
elapsed = updated.format(time.DateTimeFormatter.ofPattern('H:m'));
} catch (e) {
elapsed = '-';
}
if (!item.history.changedSince(t)) {
item.postUpdate('ON');
}
item.upsertMetadataValue('automation', `${elapsed}`);
} else {
item.upsertMetadataValue('automation', '');
}
});
},
id: `automation_intervals`
});
Далі як виглядає код сторінки на яку автоматично виводятся всі перемикачі та як ця сторінка виглядає в браузері та телефоні
config:
label: Автоматизація
sidebar: true
blocks:
- component: oh-block
config:
style:
padding: 0
slots:
default:
- component: oh-grid-row
config: {}
slots:
default:
- component: oh-grid-col
config:
large: "50"
xlarge: "50"
xsmall: "100"
slots:
default:
- component: oh-repeater
config:
fetchMetadata: description, automation
for: item
fragment: true
groupItem: gAutomation
sourceType: itemsInGroup
slots:
default:
- component: f7-card
slots:
default:
- component: f7-card-header
config:
class3:
- no-border
color: red
slots:
default:
- component: Label
config:
text: =loop.item.label
- component: oh-link
config:
action: options
actionItem: =loop.item.name
badge: '=loop.item.metadata ? loop.item.metadata.automation.value : ""'
color: '=(loop.item.state == "ON") ? "green" : "gray"'
item: =loop.item.name
style:
border: 1px solid var(--f7-theme-color)
border-radius: var(--f7-card-border-radius)
padding: 5px 10px
text: =items[loop.item.name].displayState
- component: f7-card-content
config:
style3:
min-height: 64px
slots:
default:
- component: oh-icon
config:
icon: =items[loop.item.name].icon
style:
filter: '=(themeOptions.dark == "dark") ? "invert(70%)" : "invert(30%)"'
float: left
width: 48
- component: Label
config:
padding-left: 75px
style:
float3: none
text: '=loop.item.metadata ? loop.item.metadata.description.value : ""'
masonry: []
grid: []
canvas: []
Найголовніше, раді чого це все затівалося - як це використовувати в сценаріях:
if (items.getItem('Automation_EcoMode').state == 'ON') {
// тут будь яка магія, яка може зламатися
} else {
// нічого не робимо
}
Всім нам перемоги найскорішої із найменшою кількістю жертв. Слава Україні.