Warning! This topic is for development testing only. Do not install, it will probably cause issues with your system.
version: 1
blocks:
astro_newyaml_marketplace:
tags:
- Test
props:
parameters: []
parameterGroups: []
component: BlockLibrary
config:
name: Astro Marketplace
slots:
blocks:
- component: BlockType
config:
type: compare_event_time
message0: the %1 of today's %2 according to %3 %4
args0:
- type: field_dropdown
name: MOMENT
options:
- - start
- START
- - end
- END
- type: field_dropdown
name: PHASE
options:
- - sunrise
- SUN_RISE
- - sunset
- SUN_SET
- - astro dawn
- ASTRO_DAWN
- - nautic dawn
- NAUTIC_DAWN
- - civil dawn
- CIVIL_DAWN
- - astro dusk
- ASTRO_DUSK
- - nautic dusk
- NAUTIC_DUSK
- - civil dusk
- CIVIL_DUSK
- - daylight
- DAYLIGHT
- - noon
- NOON
- - night
- NIGHT
- - morning night
- MORNING_NIGHT
- - evening night
- EVENING_NIGHT
- type: input_value
name: THING
- type: field_dropdown
name: DIRECTION
options:
- - is still to come
- After
- - has already passed
- Before
output: Boolean
colour: 0
tooltip: ""
helpUrl: ""
slots:
code:
- component: BlockCodeTemplate
config:
template: "{{utility:things}}.getActions('astro', {{input:THING}}).getEventTime('{{field:PHASE}}', null, '{{field:MOMENT}}').is{{field:DIRECTION}}({{utility:zdt}}.now())"
toolbox:
- component: PresetInput
config:
name: THING
type: oh_thing
shadow: true
- component: BlockType
config:
type: time_until_event
message0: number of %1 until the %2 of today's %3 according to %4
args0:
- type: field_dropdown
name: UNIT
options:
- - seconds
- SECONDS
- - minutes
- MINUTES
- - hours
- HOURS
- type: field_dropdown
name: MOMENT
options:
- - start
- START
- - end
- END
- type: field_dropdown
name: PHASE
options:
- - sunrise
- SUN_RISE
- - sunset
- SUN_SET
- - astro dawn
- ASTRO_DAWN
- - nautic dawn
- NAUTIC_DAWN
- - civil dawn
- CIVIL_DAWN
- - astro dusk
- ASTRO_DUSK
- - nautic dusk
- NAUTIC_DUSK
- - civil dusk
- CIVIL_DUSK
- - daylight
- DAYLIGHT
- - noon
- NOON
- - night
- NIGHT
- - morning night
- MORNING_NIGHT
- - evening night
- EVENING_NIGHT
- type: input_value
name: THING
output: Boolean
colour: 0
tooltip: Determines the amount of time until the specified event. If it has already passed, the result will be negative.
helpUrl: ""
slots:
code:
- component: BlockCodeTemplate
config:
template: "{{utility:zdt}}.now().until({{utility:things}}.getActions('astro', {{input:THING}}).getEventTime('{{field:PHASE}}', null, '{{field:MOMENT}}'), {{utility:ChronoUnit}}.{{field:UNIT}})"
toolbox:
- component: PresetInput
config:
name: THING
type: oh_thing
shadow: true
utilities:
- component: UtilityJavaType
config:
name: things
javaClass: org.openhab.core.model.script.actions.Things
- component: UtilityJavaType
config:
name: zdt
javaClass: java.time.ZonedDateTime
- component: UtilityJavaType
config:
name: ChronoUnit
javaClass: java.time.temporal.ChronoUnit
version: 1
widgets:
colorbands_gauge_newyaml:
tags:
- gauge
- marketplace:161215
- colorbands
props:
parameters:
- context: item
description: The Item
label: Item
name: item
required: true
type: TEXT
groupName: gaugeConfig
- default: "0"
description: The minimum value to show
label: Min Value
name: min
required: false
type: TEXT
groupName: gaugeConfig
- default: "100"
description: The maximum value to show
label: Max Value
name: max
required: false
type: TEXT
groupName: gaugeConfig
- description: The title text
label: Title
name: title
required: false
type: TEXT
groupName: appearance
- default: 18
description: The size of the title font
label: Title Font Size
name: titleFontSize
required: false
type: INTEGER
groupName: appearance
advanced: true
- default: 0
description: The number of digits after the decimal separator
label: Precision
name: precision
required: false
type: INTEGER
min: 0
max: 4
groupName: appearance
- default: true
description: Whether to print the unit on the dial
label: Show Unit
name: showUnit
required: false
type: BOOLEAN
groupName: appearance
- default: 5
description: The number of splits
label: Splits
name: splits
required: false
type: INTEGER
min: 1
max: 20
groupName: appearance
- default: 3
description: The number of ticks between each split
label: Ticks
name: ticks
required: false
type: INTEGER
min: 1
max: 20
groupName: appearance
- default: "#e64a19"
description: The needle color
label: Needle Color
name: needleColor
required: false
type: TEXT
groupName: appearance
- default: "#ddd"
description: The gauge frame color
label: Frame Color
name: frameColor
required: false
type: TEXT
groupName: appearance
- default: transparent
description: The dial color
label: Dial Color
name: dialColor
required: false
type: TEXT
groupName: appearance
- default: "#464646"
description: The dial print color
label: Print Color
name: printColor
required: false
type: TEXT
groupName: appearance
- default: transparent
description: The shadow color, e.g. "black" or "#0008"
label: Shadow Color
name: shadowColor
required: false
type: TEXT
groupName: appearance
advanced: true
- default: 10
description: The blur value for the dial shadow (no effect if the dial is transparent)
label: Shadow Blur
name: shadowBlur
required: false
type: INTEGER
groupName: appearance
advanced: true
- default: 3
description: The offset value for the dial shadow (no effect if the dial is transparent)
label: Shadow Offset
name: shadowOffset
required: false
type: INTEGER
groupName: appearance
advanced: true
- default: 15
description: The axis font size
label: AxisFontSize
name: axisFontSize
required: false
type: INTEGER
min: 6
max: 50
groupName: appearance
advanced: true
- default: false
label: Enable LCD
name: enableLCD
required: false
type: BOOLEAN
groupName: lcd
- default: 0
description: The number of digits after the decimal separator
label: LCD Precision
name: lcdPrecision
required: false
type: INTEGER
min: 0
max: 4
groupName: lcd
- description: The height of widget
label: Height
name: height
required: false
type: TEXT
groupName: appearance
- default: 0.08
description: The decimal value between 0 and 1 that determines when the color band ends
label: Color Band 1 End
name: colorBand1End
required: false
type: DECIMAL
groupName: bands
- default: "#039be688"
description: The color value for the band
label: Color Band 1 Color
name: colorBand1Color
required: false
type: TEXT
groupName: bands
- default: 0.6
description: The decimal value between 0 and 1 that determines when the color band ends
label: Color Band 2 End
name: colorBand2End
required: false
type: DECIMAL
groupName: bands
- default: "#43a04788"
description: The color value for the band
label: Color Band 2 Color
name: colorBand2Color
required: false
type: TEXT
groupName: bands
- default: 0.8
description: The decimal value between 0 and 1 that determines when the color band ends
label: Color Band 3 End
name: colorBand3End
required: false
type: DECIMAL
groupName: bands
- default: "#ffa60088"
description: The color value for the band
label: Color Band 3 Color
name: colorBand3Color
required: false
type: TEXT
groupName: bands
- default: 1
description: The decimal value between 0 and 1 that determines when the color band ends
label: Color Band 4 End
name: colorBand4End
required: false
type: DECIMAL
groupName: bands
- default: "#db443788"
description: The color value for the band
label: Color Band 4 Color
name: colorBand4Color
required: false
type: TEXT
groupName: bands
- default: 1
description: The decimal value between 0 and 1 that determines when the color band ends
label: Color Band 5 End
name: colorBand5End
required: false
type: DECIMAL
groupName: bands
- default: "#db443788"
description: The color value for the band
label: Color Band 5 Color
name: colorBand5Color
required: false
type: TEXT
groupName: bands
- default: 1
description: The decimal value between 0 and 1 that determines when the color band ends
label: Color Band 6 End
name: colorBand6End
required: false
type: DECIMAL
groupName: bands
- default: "#db443788"
description: The color value for the band
label: Color Band 6 Color
name: colorBand6Color
required: false
type: TEXT
groupName: bands
- default: 1
description: The decimal value between 0 and 1 that determines when the color band ends
label: Color Band 7 End
name: colorBand7End
required: false
type: DECIMAL
groupName: bands
- default: "#db443788"
description: The color value for the band
label: Color Band 7 Color
name: colorBand7Color
required: false
type: TEXT
groupName: bands
- default: 1
description: The decimal value between 0 and 1 that determines when the color band ends
label: Color Band 8 End
name: colorBand8End
required: false
type: DECIMAL
groupName: bands
- default: "#db443788"
description: The color value for the band
label: Color Band 8 Color
name: colorBand8Color
required: false
type: TEXT
groupName: bands
- default: 1
description: The decimal value between 0 and 1 that determines when the color band ends
label: Color Band 9 End
name: colorBand9End
required: false
type: DECIMAL
groupName: bands
- default: "#db443788"
description: The color value for the band
label: Color Band 9 Color
name: colorBand9Color
required: false
type: TEXT
groupName: bands
parameterGroups:
- name: gaugeConfig
label: Gauge Configuration
- name: appearance
label: Appearance
- name: lcd
label: LCD
- name: bands
label: Color bands
- name: clickAction
context: action
label: Click Action
description: Action to execute if the widget it clicked. Disables tooltips.
component: f7-block
config:
class:
- no-margin
slots:
default:
- component: oh-link
config:
actionPropsParameterGroup: clickAction
style:
display: =(props.click_action&&props.click_action!='false')?'block':'none'
height: 100%
position: absolute
width: 100%
- component: oh-chart
config:
height: =props.height
slots:
series:
- component: oh-data-series
config:
emptyCircleStyle:
color: =props.dialColor
shadowBlur: =props.shadowBlur
shadowColor: =props.shadowColor
shadowOffsetX: =props.shadowOffset
shadowOffsetY: =props.shadowOffset
radius: 85%
type: pie
- component: oh-data-series
config:
anchor:
itemStyle:
color: "#000"
offsetCenter:
- 0
- 35.5%
show: true
size: 20
axisLabel:
show: false
axisLine:
lineStyle:
color:
- - 1
- =props.frameColor
shadowBlur: 6
shadowColor: "#0008"
width: 6
axisTick:
show: false
data:
- name: =items[props.item].state.split(' ')[1]
detail:
show: false
endAngle: -270
pointer:
show: false
radius: 85%
splitLine:
show: false
startAngle: 90
title:
color: =props.printColor
fontWeight: 600
offsetCenter:
- 0
- -23%
show: =props.showUnit && items[props.item].state.split(' ').length > 1
type: gauge
- component: oh-data-series
config:
anchor:
itemStyle:
color: =props.needleColor
shadowBlur: 0
show: true
showAbove: true
size: 16
axisLabel:
color: =props.printColor
distance: 20
fontSize: =props.axisFontSize
formatter: =v=>v.toFixed(props.precision)
axisLine:
lineStyle:
color:
- - =props.colorBand1End
- =props.colorBand1Color
- - =props.colorBand2End
- =props.colorBand2Color
- - =props.colorBand3End
- =props.colorBand3Color
- - =props.colorBand4End
- =props.colorBand4Color
- - =props.colorBand5End
- =props.colorBand5Color
- - =props.colorBand6End
- =props.colorBand6Color
- - =props.colorBand7End
- =props.colorBand7Color
- - =props.colorBand8End
- =props.colorBand8Color
- - =props.colorBand9End
- =props.colorBand9Color
width: 15
axisTick:
distance: -15
length: 15
lineStyle:
color: =props.printColor
splitNumber: =props.ticks
center:
- 50%
- 65%
clockwise: true
data:
- itemStyle:
color: =props.needleColor
name: =props.title
value: =Number.parseFloat(items[props.item].state)
detail:
backgroundColor: rgb(137,150,96)
borderColor: "#000"
borderWidth: 1
color: "#000"
fontFamily: Courier New
fontSize: 1em
fontWeight: 600
formatter: "=v=>v.toFixed(props.lcdPrecision)+(items[props.item].state.split(' ').length > 1 ? ' '+items[props.item].state.split(' ')[1] : '')"
offsetCenter:
- 0
- 25%
padding:
- 0
- 2
- 0
- 2
show: =props.enableLCD?true:false
valueAnimation: true
width: 50%
endAngle: 45
max: =Number.parseFloat(props.max)
min: =Number.parseFloat(props.min)
pointer:
icon: path://M2090.36389,615.30999 L2090.36389,615.30999 C2091.48372,615.30999 2092.40383,616.194028 2092.44859,617.312956 L2096.90698,728.755929 C2097.05155,732.369577 2094.2393,735.416212 2090.62566,735.56078 C2090.53845,735.564269 2090.45117,735.566014 2090.36389,735.566014 L2090.36389,735.566014 C2086.74736,735.566014 2083.81557,732.63423 2083.81557,729.017692 C2083.81557,728.930412 2083.81732,728.84314 2083.82081,728.755929 L2088.2792,617.312956 C2088.32396,616.194028 2089.24407,615.30999 2090.36389,615.30999 Z
itemStyle:
color: =props.needleColor
shadowBlur: 2
shadowColor: "#000a"
shadowOffsetX: 2
shadowOffsetY: 2
keepAspect: false
length: 105%
offsetCenter:
- 0
- 10%
radius: 90%
splitLine:
distance: -15
length: 20
lineStyle:
color: =props.printColor
width: 2
splitNumber: =props.splits
startAngle: 135
title:
color: =props.printColor
fontSize: =props.titleFontSize
offsetCenter:
- 0
- -30%
tooltip:
formatter: "=props.title ? p=>props.title+': <b>'+p.value.toFixed(props.precision)+(items[props.item].state.split(' ').length > 1 ? ' '+items[props.item].state.split(' ')[1] : '')+'</b>' : NULL"
valueFormatter: "=v=>v.toFixed(props.precision)+(items[props.item].state.split(' ').length > 1 ? ' '+items[props.item].state.split(' ')[1] : '')"
type: gauge
tooltip:
- component: oh-chart-tooltip
config:
show: true
version: 1
ruleTemplates:
rules_tools:open_reminder_mp:
label: Open Reminder Marketplace
tags:
- marketplace:138199
description: This will trigger on a change to an Item in a Group. If the change is different from a "good state" (or the same as the "good state" if the match is inverted) and it remains that state for a specified amount of time it will call another script and reschedule to repeat until the Item returns or a do not distrurb period is entered.
configDescriptions:
groupTriggers:
context: item
description: Group whose members should trigger this rule.
label: Group of Triggering Items
required: true
type: TEXT
filterCriteria:
- value: Group
name: type
alertState:
description: Any state different from this will cause the rule to run.
label: Alert State
required: true
type: TEXT
invert:
description: Inverts the match so an state that is the same as Alert State will cause the rule to run.
label: Invert
type: BOOLEAN
default: "false"
defaultTimeout:
description: "If a triggering Item doesn't have a timeout metadata, the default initial time to wait before calling the script the first time. Use \"PTVdWhXmYsZz\" format where d=days, h=hours, m=minutes, s=seconds, and z=milliseconds (e.g. \"PT2d5s\" = two days, five seconds)."
label: Default Initial Timeout
required: true
type: TEXT
default: PT15m
timeoutMetadata:
description: "Item metadata namespace to check for a custom initial timeout for the triggering Item. Use \"PTVdWhXmYsZz\" format where d=days, h=hours, m=minutes, s=seconds, and z=milliseconds (e.g. \"PT2d5s\" = two days, five seconds)."
label: Default Item Initial Timeout Metadata
type: TEXT
default: rem_time
repeatPeriod:
description: "How often to repeat calling the alert rule after the initial call while the Item remains in a \"not good\" state. Use \"PTVdWhXmYsZz\" format where d=days, h=hours, m=minutes, s=seconds, and z=milliseconds (e.g. \"PT2d5s\" = two days, five seconds). If left blank no repeat is performed."
label: Repeat Period
type: TEXT
default: ""
reschedule:
description: "Reschedule the initial looping timer when the Item changes to the alerting state again or ignore the state change. Put another way, set the timer to run after the first alert event, or after the last alert event. Usually you'll make this true for motion sensor rules."
label: Reschedule
type: BOOLEAN
default: "false"
alertRuleUID:
context: rule
description: Rule called after the initial timeout and every repeat period thereafter as long as the Item remains in a "not good" state.
label: Alert Rule
type: TEXT
dndStart:
context: time
description: "The start of the time where alerts are not allowed. Alerts are only sent outside of the time between this time and the alerting end time. If both are the same, alerts will be sent any time."
label: Do Not Distrub Start Time
type: TEXT
default: 00:00
dndEnd:
context: time
description: "The end of the time where alerts are not allowed. Alerts are only sent outside of the alerting start time and alerting end time. If both are the same, alerts will be send any time."
label: Do Not Disturb End Time
type: TEXT
default: 00:00
actions:
- id: "2"
config:
type: application/javascript;version=ECMAScript-2021
script: |
var {loopingTimer, timeUtils} = require('openhab_rules_tools');
var logger = log('rules_tools.'+ruleUID);
// Rule Properties
var alertState = "{{alertState}}";
var defaultTimeout = "{{defaultTimeout}}";
var timeoutMetadata = "{{timeoutMetadata}}";
var alertRuleUID = "{{alertRuleUID}}";
var repeatPeriod = "{{repeatPeriod}}";
var reschedule = {{reschedule}};
var dndStart = "{{dndStart}}";
var dndEnd = "{{dndEnd}}";
var noAlertPeriod = (dndStart == dndEnd);
var isAlertState = (curr) => { return ({{invert}}) ? (curr == alertState) : (curr != alertState); };
var TIMERS_KEY = ruleUID+'_timers';
var TIMER_BUFFER = 600*1000000; // Nano seconds
// If you edit this rule, run it manually to clear out the cache or else
// errors will occur complaining about the context being closed.
if(this.event === undefined) {
logger.info('Resetting looping timers');
cache.put(TIMERS_KEY, null);
}
else {
const timers = cache.get(TIMERS_KEY, () => new Map());
const item = event.itemName;
const state = event.itemState.toString();
// Temporary work around until PR to fix openhab-js gets merged
const isBetweenTimes = function (t, start, end) {
const startTime = time.toZDT(start).toLocalTime();
const endTime = time.toZDT(end).toLocalTime();
// const currTime = this.toLocalTime();
const currTime = time.toZDT(t).toLocalTime();
// time range spans midnight
if (endTime.isBefore(startTime)) {
return currTime.isAfter(startTime) || currTime.isBefore(endTime);
} else {
return currTime.isAfter(startTime) && currTime.isBefore(endTime);
}
};
// Looks at the currently scheduled timers and makes the time for a new one
// doesn't overlap.
// Loop through the timers and check the timeout time against the existing timers.
// If it's within half a second of one, move the timer forward 500 msec.
// If the timeout was moved at least once, loop through the timers again to make
// sure we didn't move it on top of an already checked timer.
const avoidOverlapping = (timeout) => {
let rval = time.toZDT(timeout);
let recheck = true;
while(recheck) {
recheck = false;
logger.debug('There are ' + timers.size + ' timers already scheduled');
timers.forEach((value, key) => {
if(!value.hasTerminated() &&
rval.isClose(time.toZDT(value.timer.getExecutionTime()), time.Duration.ofMillis(250))) {
logger.debug('Found timer ' + value.name + ' that has not terminated and is too close to timeout');
rval = rval.plusNanos(TIMER_BUFFER);
recheck = true;
}
});
}
return rval;
};
// Moves the alert time to the end of the DND period if the timeout is
// in the DND period. Then it makes sure it's at least TIMER_BUFFER nanosecs
// from any existing timer
const generateAlertTime = (timeout, dnds, dnde) => {
let rval = time.toZDT(timeout);
if(isBetweenTimes(rval, dnds, dnde)) { // temporary work around until PR gets merged to openhab-js
logger.debug('Timer is scheduled during DND, moving to end of DND');
rval = dndEnd;
if(time.toZDT(dndEnd).isBefore(time.toZDT())) {
rval = timeUtils.toTomorrow(dndEnd);
}
}
return avoidOverlapping(rval);
};
// This returns the function called by the LoopingTimer. LoopingTimer will
// reschedule the timer based on what the function passed to it returns. If
// a duration is returned it will be reschuled for that time into the future.
// If null is returned the looping stops.
const sendAlertGenerator = (alertItem, repeat, dnds, dnde) => {
return () => {
const currState = items.getItem(alertItem).state;
logger.debug('Timer expired for ' + alertItem + ' with dnd between ' + dnds + ' and ' + dnde);
// Calculate the next repeat time
let repeatTime = (repeat) ? generateAlertTime(repeat, dnds, dnde) : null;
// Send alert
if(isAlertState(currState)) {
logger.debug('Item is still in an alert state.')
rules.runRule(alertRuleUID, { 'alertItem': alertItem, 'currState': currState }, true);
}
// Cancel the repeats
else {
logger.debug('Item has returned to the non-alert state');
repeatTime = null;
}
// If there is no repeat delete the timer from the map
if(!repeatTime) {
logger.debug('No repeat time');
timers.delete(alertItem);
}
logger.debug('Rescheduling for ' + repeatTime);
return repeatTime;
};
};
logger.debug('Running door alert rule with: \n' +
' item: ' + item + '\n' +
' state: ' + state + '\n' +
' alertState: ' + alertState + '\n' +
' invert: ' + true + '\n' +
' defaultTimeout: ' + defaultTimeout + '\n' +
' repeatPeriod: ' + repeatPeriod + '\n' +
' dndStart: ' + dndStart + '\n' +
' dndEnd: ' + dndEnd + '\n' +
' noAlertPeriod: ' + noAlertPeriod + '\n' +
' alertRuleUID: ' + alertRuleUID);
// Returned to alertState and timer exists, cancel the looping timer
if(!isAlertState(state) && timers.has(item)) {
logger.debug(item + ' has returned to a non-alerting state of ' + state + ', canceling the timer if it exists.');
timers.get(item).cancel();
timers.delete(item);
}
// Item changed to a not alertState, create a looping timer if one doesn't already exist
else if(isAlertState(state)) {
logger.debug(item + ' is in the alert state of ' + state);
// There shouldn't be a Timer if the Item just changed to the alertState, log to show
// something went wrong.
if(timers.has(item)) {
logger.warn(item + ' state is now ' + state + ' but an alert timer already exists! This should not have happened!');
}
// Schedule a looping timer to start at the initial timeout (from metadata) and repeat
// on repeatPeriod
else {
const metadata = items.getItem(item).getMetadataValue(timeoutMetadata);
let timeout = generateAlertTime((metadata) ? metadata : defaultTimeout, dndStart, dndEnd);
timers.set(item, new loopingTimer.LoopingTimer()); // add name as argument when openhab_rules_tools is updated to support that
logger.debug('Starting timer for ' + item + ' with ' + timeout);
timers.get(item).loop(sendAlertGenerator(item, repeatPeriod, dndStart, dndEnd), timeout);
}
}
}
type: Script
triggers:
- id: "1"
config:
groupName: "{{groupTriggers}}"
type: MemberChanged
Version: 0.0.1
DependsOn: ui-basic
Keywords: test, debug
Countries: ; NO, SE,
License: GPL
Connection: local
LoggerPackages: com.example.test
Documentation: https://github.com/Nadahar/Mill-LAN-openHAB-Binding/blob/master/bundles/org.openhab.binding.milllan/README.md
Issues: https://github.com/Nadahar/Mill-LAN-openHAB-Binding/issues
Version: 0.0.2
Keywords: test, debug, testing
Countries: DE
CoreRange: [3.1.0:5)
Maturity: immature
Documentation: https://github.com/Nadahar/Mill-LAN-openHAB-Binding/blob/master/README.md
Description: An even more unstable version
LoggerPackages: com.example.test2
URL: https://github.com/Nadahar/Mill-LAN-openHAB-Binding/releases/download/v1.0.0/org.openhab.binding.milllan-1.0.0.jar
[
{
"actions": [
{
"configuration": {
"script": "// Version 1.0\nvar {TimerMgr, helpers} = require('openhab_rules_tools');\nconsole.loggerName = 'org.openhab.automation.rules_tools.TimeStateMachine';\n//osgi.getService('org.apache.karaf.log.core.LogService').setLevel(console.loggerName, 'DEBUG');\n\nhelpers.validateLibraries('4.2.0', '2.0.3');\n\nconsole.debug('Starting state machine in ten seconds...');\n\n// Properties\nvar STATE_ITEM = \"{{timeOfDay}}\";\nvar DT_GROUP = \"{{timesOfDayGrp}}\";\nvar DAY_TYPES = ['custom', 'holiday', 'dayset', 'weekend', 'weekday', 'default'];\nvar NAMESPACE = '{{namespace}}';\nvar USAGE = 'Time Based State Machine Usage:\\n'\n + 'All date times must be a member of ' + DT_GROUP + '.\\n'\n + 'Each member of the Group must have ' + NAMESPACE + ' Item metadata of the following format:\\n'\n + ' .items file: ' + NAMESPACE +'=\"STATE\"[type=\"daytype\", set=\"dayset\", file=\"uri\"]\\n'\n + \" UI YAML: use '\" + NAMESPACE + \"' for the namespace and metadata format:\\n\"\n + ' value: STATE\\n'\n + ' config:\\n'\n + ' type: daytype\\n'\n + ' set: dayset\\n'\n + ' file: uri\\n'\n + 'Where \"STATE\" is the state machine state that begins at the time stored in that Item, '\n + '\"daytype\" is one of \"default\", \"weekday\", \"weekend\", \"dayset\", \"holiday\", or \"custom\". '\n + 'If \"dayset\" is chosen for the type, the \"set\" property is required indicating the name of the '\n + 'custom dayset configured in Ephemeris. If \"custom\" is chosen as the type, the \"file\" property '\n + 'is required and should be the fully qualified path the the Ephemeris XML file with the custom '\n + 'holidays defined. The \"set\" and \"file\" properties are invalid when choosing any of the other '\n + '\"types\".';\n\n/**\n * Validates the passed in Item has valid NAMESPACE metadata.\n *\n * @param {string} itemName name of the Item to check\n * @throws exception if the metadata doesn't exist or is invalid\n */\nvar validateItemConfig = (itemName) => {\n const md = items[itemName].getMetadata()[NAMESPACE];\n\n if(md.value === undefined || md.value === null || md.value === '') {\n throw itemName + ' has malformed ' + NAMESPACE + ' metadata, no value found!';\n }\n\n const dayType = md.configuration['type'];\n if(!dayType) {\n throw itemName + ' has malformed ' + NAMESPACE + ' metadata, required \"type\" property is not found!';\n }\n\n if(dayType == 'dayset' && !md.configuration['set']) {\n throw itemName + ' has malformed ' + NAMESPACE + ' metadata, type is \"dayset\" but required \"set\" property is not found!';\n }\n\n if(dayType == 'custom' && !md.configuration['file']) {\n throw itemName + ' has malformed ' + NAMESPACE + ' metadata, type is \"custom\" but required \"file\" property is not found!';\n }\n\n if(!items[itemName].type.startsWith('DateTime')) {\n throw itemName + ' is not a DateTime Item!';\n }\n\n if(items[itemName].isUninitialized) {\n throw itemName + \" is not initialized!: \" + items[itemName].state;\n }\n\n console.debug(itemName+ ' is valid');\n};\n\n/**\n * Return all members of the DT_GROUP that has a \"type\" metadata configuration property that\n * matches the passed in type.\n *\n * @param {string} type the day type defined in the metadata we want to get the Items for\n * @returns {Array} all the Items with the matching type in the metadata\n */\nvar getItemsOfType = (type) => {\n const allItems = items[DT_GROUP].members;\n return allItems.filter( item => item.getMetadata()[NAMESPACE].configuration['type'] == type);\n};\n\n/**\n * Returns true if all the Items of the given type have a unique \"state\" value\n * in the metadata.\n *\n * @param {string} the day type\n * @returns {boolean} true if all states are unique, false otherwise\n */\nvar checkUniqueStates = (type) => {\n const allItems = getItemsOfType(type);\n const states = new Set(allItems.map(i => { return i.getMetadata()[NAMESPACE].value; }));\n return !allItems.length || allItems.length == states.size;\n};\n\n/**\n * Check that all Items are configured correctly.\n */\nvar validateAllConfigs = () => {\n console.debug('Validating Item types, Item metadata, and Group membership');\n\n // Check that all members of the Group have metadata\n const itemsWithMD = items[DT_GROUP].members.filter(item => item.getMetadata(NAMESPACE)).length;\n if(itemsWithMD != items[DT_GROUP].members.length) {\n const noMdItems = items[DT_GROUP].members.filter(item => !item.getMetadata(NAMESPACE));\n console.warn('The following Items do not have required ' + NAMESPACE + ' metadata: ' + noMdItems.map(item => item.name).join(', '));\n return false; // no sense on performing any additional tests\n }\n\n // Check each Item's metadata\n let isGood = helpers.checkGrpAndMetadata(NAMESPACE, DT_GROUP, validateItemConfig, USAGE);\n\n // Check the state item\n if(!items[STATE_ITEM]){\n console.warn('The state Item ' + STATE_ITEM + ' does not exist!');\n isGood = false;\n }\n\n if(!items[STATE_ITEM].type.startsWith('String')) {\n console.warn('The state Item ' + STATE_ITEM + ' is not a String Item!');\n isGood = false;\n }\n\n // Check to see if we have a default set of Items\n if(!getItemsOfType('default')) {\n console.warn('There are no \"default\" day type Items defined! Make sure you have all day types covered!');\n // we'll not invalidate if there are no \"default\" items\n }\n\n // Check that each data set has a unique state for each Item\n DAY_TYPES.forEach(type => {\n if(!checkUniqueStates(type)) {\n console.warn('Not all the metadata values for Items of type ' + type + ' are unique!');\n isGood = false;\n }\n })\n\n // Report if all configs are good or not\n if(isGood) {\n console.debug('All ' + NAMESPACE + ' Items are configured correctly');\n }\n return isGood;\n};\n\n/**\n * Pull the set of Items for today based on Ephemeris. The Ephemeris hierarchy is\n * - custom\n * - holiday\n * - dayset\n * - weeekend\n * - weekday\n * - default\n *\n * If there are no DateTime Items defined for today's type, null is returned.\n */\nvar getTodayItems = () => {\n // Get all the DateTime Items that might apply to today given what type of day it is\n // For example, if it's a weekend, there will be no weekday Items pulled. Whether or not\n // the entry in this dict has an array of Items determines whether today is of that day\n // type.\n const startTimes = [\n { 'type': 'custom', 'times' : getItemsOfType('custom').filter(item => actions.Ephemeris.isBankHoliday(0, item.getMetadata()[NAMESPACE].configuration['file'])) },\n { 'type': 'holiday', 'times' : (actions.Ephemeris.isBankHoliday()) ? getItemsOfType('holiday') : [] },\n { 'type': 'dayset', 'times' : getItemsOfType('dayset').filter(item => actions.Ephemeris.isInDayset(items.getMetadata()[NAMESPACE].configuration['set'])) },\n { 'type': 'weekend', 'times' : (actions.Ephemeris.isWeekend()) ? getItemsOfType('weekend') : [] },\n { 'type': 'weekday', 'times' : (!actions.Ephemeris.isWeekend()) ? getItemsOfType('weekday') : [] },\n { 'type': 'default', 'times' : getItemsOfType('default') }\n ];\n\n // Go through startTimes in order and choose the first one that has a non-empty list of Items\n const dayStartTimes = startTimes.find(dayset => dayset.times.length);\n\n if(dayStartTimes === null) {\n console.warn('No DateTime Items found for today');\n return null;\n }\n else {\n console.info('Today is a ' + dayStartTimes.type + ' day.');\n return dayStartTimes.times;\n }\n};\n\n/**\n * Returns a function called to transition the state machine from one state to the next\n *\n * @param {string} state the new state to transition to\n * @param {function} the function that transitions the state\n */\nvar stateTransitionGenerator = (state) => {\n return function() {\n console.info('Transitioning Time State Machine from ' + items[STATE_ITEM].state + ' to ' + state);\n items[STATE_ITEM].sendCommand(state);\n }\n}\n\n/**\n * Returns a function that generates the timers for all the passed in startTimes\n *\n * @param {Array} startTimes list of today's state start times\n * @param {timerMgr.TimerMgr} timers collection of timers\n * @returns {function} called to generate the timers to transition between the states\n */\nvar createTimersGenerator = (timers) => {\n return function() {\n\n if(validateAllConfigs()) {\n\n // Cancel the timers, skipping the debounce timer\n console.debug('Cancelling existing timers');\n timers.cancelAll();\n\n // Get the set of Items for today's state machine\n console.debug(\"Acquiring today's state start times\");\n const startTimes = getTodayItems();\n\n // Get the state and start time, sort them ignoring the date, skip the ones that have\n // already passed and create a timer to transition for the rest.\n console.debug('Creating timers for times that have not already passed');\n var mapped = startTimes.map(i => { return { 'state': i.getMetadata()[NAMESPACE].value,\n 'time' : time.toZDT(i.state).toToday() } });\n mapped.sort((a,b) => {\n if(a.time.isBefore(b.time)) return -1;\n else if(a.time.isAfter(b.time)) return 1;\n else return 0;\n })\n .filter(tod => tod.time.isAfter(time.toZDT()))\n .forEach(tod => {\n // TODO: see if we can move to rules instead of timers\n console.debug('Creating timer for ' + tod.state + ' at ' + tod.time);\n timers.check(tod.state, tod.time.toString(), stateTransitionGenerator(tod.state));\n });\n\n // Figure out the current time of day and move to that state if necessary\n var beforeTimes = mapped.sort((a,b) => {\n if(a.time.isAfter(b.time)) return -1;\n else if(a.time.isBefore(b.time)) return 1;\n else return 0;\n })\n .filter(tod => tod.time.isBefore(time.toZDT()));\n if(!beforeTimes.length) {\n console.debug(\"There is no date time for today before now, we can't know what the current state is, keeping the current time of day state of \" + items[STATE_ITEM].state + \".\");\n }\n else {\n const currState = beforeTimes[0].state\n const stateItem = items[STATE_ITEM];\n console.info('The current state is ' + currState);\n if(stateItem.state != currState) stateItem.sendCommand(currState)\n }\n }\n else {\n console.warn('The config is not valid, cannot proceed!');\n }\n\n };\n};\n\nvar timers = cache.private.get('timers', () => TimerMgr());\n\n// Wait a minute after the last time the rule is triggered to make sure all Items are done changing (e.g.\n// Astro Items) before calculating the new state.\ntimers.check('debounce',\n 'PT10S',\n createTimersGenerator(timers),\n true,\n () => { console.debug('Flapping detected, waiting before creating timers for today'); });\n",
"type": "application/javascript"
},
"id": "3",
"inputs": {
},
"type": "script.ScriptAction"
}
],
"conditions": [
],
"configDescriptions": [
{
"context": "item",
"description": "String Item that holds the current time of day's state.",
"filterCriteria": [
{
"name": "type",
"value": "String"
}
],
"label": "Time of Day State Item",
"name": "timeOfDay",
"required": true,
"type": "TEXT"
},
{
"context": "item",
"description": "Has as members all the DateTime Items that define time of day states.",
"filterCriteria": [
{
"name": "type",
"value": "Group"
}
],
"label": "Times of Day Group",
"name": "timesOfDayGrp",
"required": true,
"type": "TEXT"
},
{
"description": "The Item metadata namespace (e.g. \"tsm\").",
"label": "Time of Day Namespace",
"name": "namespace",
"required": true,
"type": "TEXT"
}
],
"description": "Creates timers to transition a state Item to a new state at defined times of day.",
"label": "Time Based State Machine4",
"triggers": [
{
"configuration": {
"groupName": "{{timesOfDayGrp}}"
},
"id": "1",
"type": "core.GroupStateChangeTrigger"
},
{
"configuration": {
"startlevel": 100
},
"id": "2",
"type": "core.SystemStartlevelTrigger"
},
{
"configuration": {
"time": "00:05"
},
"id": "4",
"type": "timer.TimeOfDayTrigger"
}
],
"uid": "rules_tools:tsm_m"
},
{
"actions": [
{
"configuration": {
"script": "var from = parseFloat(oldState.toString().split(' ')[0]);\nvar to = parseFloat(newState.toString().split(' ')[0]);\n\nprint(from + '>' + to);\n\nif (to < {{threshold}} && from >= {{threshold}}) {\n events.sendCommand('{{alertItem}}', '{{alertCommand}}');\n}\n",
"type": "application/javascript"
},
"id": "2",
"inputs": {
},
"type": "script.ScriptAction"
}
],
"conditions": [
],
"configDescriptions": [
{
"context": "item",
"description": "Item that holds the power (in watts) of the washing machine. Can be a quantity type (Number:Power).",
"label": "Power Item",
"name": "powerItem",
"required": true,
"type": "TEXT"
},
{
"defaultValue": 2,
"description": "When the power measurement was at or above the threshold and crosses below it, trigger the alert.",
"label": "Threshold",
"name": "threshold",
"required": true,
"type": "DECIMAL"
},
{
"context": "item",
"description": "Item to send a command to when the measured power gets below the threshold. For instance, a Hue light advanced Alert channel.",
"label": "Alert Item",
"name": "alertItem",
"required": true,
"type": "TEXT"
},
{
"defaultValue": "LSELECT",
"description": "Command to send to the alert item (for an item linked to a Hue light alert channel, LSELECT will flash the light for a few seconds).",
"label": "Alert Command",
"name": "alertCommand",
"required": true,
"type": "TEXT"
}
],
"description": "This will monitor the power consumption of a washing machine and send an alert command when it gets below a threshold, meaning it has finished.",
"label": "Alert when Washing Machine Finished2",
"triggers": [
{
"configuration": {
"itemName": "{{powerItem}}",
"state": ""
},
"id": "1",
"type": "core.ItemStateChangeTrigger"
}
],
"uid": "ysc:washing_machine_alert_m"
}
]
Version: 0.0.2-alpha
Keywords: test, debug, testing
Countries: DE, CZ
CoreRange: [3.1.0:5)
DependsOn:
Maturity: alpha
Documentation: https://github.com/Nadahar/Mill-LAN-openHAB-Binding/blob/master/README.md
Description: An extremely unstable version
LoggerPackages: com.example.test2a
URL: https://github.com/Nadahar/Mill-LAN-openHAB-Binding/releases/download/v1.0.1/org.openhab.binding.milllan-1.0.1.jar
some:alpha:json
Version: 0.0.3
CoreRange: [4.2.2:6)
uid: test_widget
props:
parameterGroups: []
parameters:
- name: prop1
label: Prop 1
type: TEXT
description: A text prop
- name: item
label: Item
type: TEXT
context: item
description: An item to control
tags: []
component: f7-card
config:
title: '=(props.item) ? "State of " + props.item : "Set props to test!"'
footer: =props.prop1
content: =items[props.item].displayState || items[props.item].state
Version: 0.0.4.beta1
Maturity: beta
coreRange: [4.2..5.1]
Description: The old tried and tested version
Issues: https://github.com/Nadahar/Mill-LAN-openHAB-Binding/blob/master/bundles/org.openhab.binding.milllan/README.md
BLOCKURL: https://raw.githubusercontent.com/deibich/openhab-libraries/23565b891b4399aaca3e878071dc963b43673d19/blockly/libraries/datetime/datetime.yaml
URL: https://github.com/Nadahar/Mill-LAN-openHAB-Binding/releases/download/v1.0.0/org.openhab.binding.milllan-1.0.0.jar
RULEURL: https://raw.githubusercontent.com/mherwege/openhab-rule-templates/refs/heads/main/wasteBelgium/wasteBelgium-ES6.yaml
Countries: UA
uid: config:dsl:doNothing
label: Does Nothing
type: DSL
configuration:
function: >
logInfo("transform", "this transform does nothing")
input
Version: 0.0.5.alpha
Maturity: beta
coreRange: [4.2..5.2]
Description: The latest hype
Issues: https://github.com/Nadahar/Mill-LAN-openHAB-Binding/blob/master/bundles/org.openhab.binding.milllan/README.md
Countries: UA, ES
uid: config:dsl:doNothing
label: Still Does Nothing
type: DSL
configuration:
function: >
logInfo("transform", "this transform does nothing either")
input
