Skip to content

Commit 67c4681

Browse files
committed
Add lastStateUpdate, lastStateChange to ItemStateUpdatedEvent/ItemStateChangedEvent
Signed-off-by: Jimmy Tanagra <[email protected]>
1 parent a94908c commit 67c4681

File tree

21 files changed

+329
-74
lines changed

21 files changed

+329
-74
lines changed

bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/GroupStateTriggerHandler.java

+5-2
Original file line numberDiff line numberDiff line change
@@ -122,12 +122,13 @@ public void receive(Event event) {
122122
if (item != null && item.getGroupNames().contains(groupName)) {
123123
State state = isEvent.getItemState();
124124
if ((this.state == null || state.toFullString().equals(this.state))) {
125-
Map<String, Object> values = new HashMap<>();
125+
Map<String, @Nullable Object> values = new HashMap<>();
126126
if (group != null) {
127127
values.put("triggeringGroup", group);
128128
}
129129
values.put("triggeringItem", item);
130130
values.put("state", state);
131+
values.put("lastStateUpdate", isEvent.getLastStateUpdate());
131132
values.put("event", event);
132133
cb.triggered(this.module, values);
133134
}
@@ -142,13 +143,15 @@ public void receive(Event event) {
142143
State oldState = iscEvent.getOldItemState();
143144

144145
if (stateMatches(this.state, state) && stateMatches(this.previousState, oldState)) {
145-
Map<String, Object> values = new HashMap<>();
146+
Map<String, @Nullable Object> values = new HashMap<>();
146147
if (group != null) {
147148
values.put("triggeringGroup", group);
148149
}
149150
values.put("triggeringItem", item);
150151
values.put("oldState", oldState);
151152
values.put("newState", state);
153+
values.put("lastStateUpdate", iscEvent.getLastStateUpdate());
154+
values.put("lastStateChange", iscEvent.getLastStateChange());
152155
values.put("event", event);
153156
cb.triggered(this.module, values);
154157
}

bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/module/handler/ItemStateTriggerHandler.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -123,14 +123,15 @@ public void receive(Event event) {
123123
if (callback != null) {
124124
logger.trace("Received Event: Source: {} Topic: {} Type: {} Payload: {}", event.getSource(),
125125
event.getTopic(), event.getType(), event.getPayload());
126-
Map<String, Object> values = new HashMap<>();
126+
Map<String, @Nullable Object> values = new HashMap<>();
127127
if (event instanceof ItemStateUpdatedEvent updatedEvent
128128
&& UPDATE_MODULE_TYPE_ID.equals(module.getTypeUID())) {
129129
String state = this.state;
130130
State itemState = updatedEvent.getItemState();
131131
if ((state == null || state.equals(itemState.toFullString()))) {
132132
values.put("state", itemState);
133133
}
134+
values.put("lastStateUpdate", updatedEvent.getLastStateUpdate());
134135
} else if (event instanceof ItemStateChangedEvent changedEvent
135136
&& CHANGE_MODULE_TYPE_ID.equals(module.getTypeUID())) {
136137
State itemState = changedEvent.getItemState();
@@ -140,6 +141,8 @@ public void receive(Event event) {
140141
values.put("oldState", oldItemState);
141142
values.put("newState", itemState);
142143
}
144+
values.put("lastStateUpdate", changedEvent.getLastStateUpdate());
145+
values.put("lastStateChange", changedEvent.getLastStateChange());
143146
}
144147
if (!values.isEmpty()) {
145148
values.put("event", event);

bundles/org.openhab.core.automation/src/main/resources/OH-INF/automation/moduletypes/ItemTriggers.json

+39-3
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,12 @@
125125
"state"
126126
]
127127
},
128+
{
129+
"name": "lastStateUpdate",
130+
"type": "java.time.ZonedDateTime",
131+
"description": "the time of the previous state update",
132+
"label": "Last State Update"
133+
},
128134
{
129135
"name": "event",
130136
"type": "org.openhab.core.events.Event",
@@ -220,17 +226,29 @@
220226
{
221227
"name": "newState",
222228
"type": "state",
223-
"description": "the new item state",
224229
"label": "New State",
230+
"description": "the new item state",
225231
"tags": [
226232
"state"
227233
]
228234
},
229235
{
230236
"name": "oldState",
231237
"type": "state",
232-
"description": "the old item state",
233-
"label": "Old State"
238+
"label": "Old State",
239+
"description": "the old item state"
240+
},
241+
{
242+
"name": "lastStateUpdate",
243+
"type": "java.time.ZonedDateTime",
244+
"label": "Last State Update",
245+
"description": "the time of the previous state update"
246+
},
247+
{
248+
"name": "lastStateChange",
249+
"type": "java.time.ZonedDateTime",
250+
"label": "Last State Change",
251+
"description": "the time of the previous state change"
234252
},
235253
{
236254
"name": "event",
@@ -402,6 +420,12 @@
402420
"state"
403421
]
404422
},
423+
{
424+
"name": "lastStateUpdate",
425+
"type": "java.time.ZonedDateTime",
426+
"description": "the time of the previous state update",
427+
"label": "Last State Update"
428+
},
405429
{
406430
"name": "event",
407431
"type": "org.openhab.core.events.Event",
@@ -518,6 +542,18 @@
518542
"description": "the old item state",
519543
"label": "Old State"
520544
},
545+
{
546+
"name": "lastStateUpdate",
547+
"type": "java.time.ZonedDateTime",
548+
"label": "Last State Update",
549+
"description": "the time of the previous state update"
550+
},
551+
{
552+
"name": "lastStateChange",
553+
"type": "java.time.ZonedDateTime",
554+
"label": "Last State Change",
555+
"description": "the time of the previous state change"
556+
},
521557
{
522558
"name": "event",
523559
"type": "org.openhab.core.events.Event",

bundles/org.openhab.core.io.websocket/src/test/java/org/openhab/core/io/websocket/EventWebSocketTest.java

+6-3
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,8 @@ public void eventFromBusFilterIncludeTopic() throws IOException {
258258
eventWebSocket.processEvent(event);
259259
verify(remoteEndpoint).sendString(gson.toJson(new EventDTO(event)));
260260

261-
event = ItemEventFactory.createStateChangedEvent(TEST_ITEM_NAME, DecimalType.ZERO, DecimalType.ZERO);
261+
event = ItemEventFactory.createStateChangedEvent(TEST_ITEM_NAME, DecimalType.ZERO, DecimalType.ZERO, null,
262+
null);
262263
eventWebSocket.processEvent(event);
263264
verify(remoteEndpoint).sendString(gson.toJson(new EventDTO(event)));
264265

@@ -285,7 +286,8 @@ public void eventFromBusFilterExcludeTopic() throws IOException {
285286
verify(remoteEndpoint, times(0)).sendString(any());
286287

287288
// not excluded topics are sent
288-
event = ItemEventFactory.createStateChangedEvent(TEST_ITEM_NAME, DecimalType.ZERO, DecimalType.ZERO);
289+
event = ItemEventFactory.createStateChangedEvent(TEST_ITEM_NAME, DecimalType.ZERO, DecimalType.ZERO, null,
290+
null);
289291
eventWebSocket.processEvent(event);
290292
verify(remoteEndpoint).sendString(gson.toJson(new EventDTO(event)));
291293

@@ -309,7 +311,8 @@ public void eventFromBusFilterIncludeAndExcludeTopic() throws IOException {
309311
clearInvocations(remoteEndpoint);
310312

311313
// included topics are sent
312-
Event event = ItemEventFactory.createStateChangedEvent(TEST_ITEM_NAME, DecimalType.ZERO, DecimalType.ZERO);
314+
Event event = ItemEventFactory.createStateChangedEvent(TEST_ITEM_NAME, DecimalType.ZERO, DecimalType.ZERO, null,
315+
null);
313316
eventWebSocket.processEvent(event);
314317
verify(remoteEndpoint).sendString(gson.toJson(new EventDTO(event)));
315318

bundles/org.openhab.core.model.rule/src/org/openhab/core/model/rule/jvmmodel/RulesJvmModelInferrer.xtend

+13-4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
package org.openhab.core.model.rule.jvmmodel
1414

1515
import com.google.inject.Inject
16+
import java.time.ZonedDateTime
1617
import java.util.Set
1718
import org.openhab.core.items.Item
1819
import org.openhab.core.items.ItemRegistry
@@ -145,10 +146,22 @@ class RulesJvmModelInferrer extends ScriptJvmModelInferrer {
145146
val commandTypeRef = ruleModel.newTypeRef(Command)
146147
parameters += rule.toParameter(VAR_RECEIVED_COMMAND, commandTypeRef)
147148
}
149+
if ((containsStateChangeTrigger(rule) || containsStateUpdateTrigger(rule)) && !containsParam(parameters, VAR_NEW_STATE)) {
150+
val stateTypeRef = ruleModel.newTypeRef(State)
151+
parameters += rule.toParameter(VAR_NEW_STATE, stateTypeRef)
152+
}
148153
if (containsStateChangeTrigger(rule) && !containsParam(parameters, VAR_PREVIOUS_STATE)) {
149154
val stateTypeRef = ruleModel.newTypeRef(State)
150155
parameters += rule.toParameter(VAR_PREVIOUS_STATE, stateTypeRef)
151156
}
157+
if (containsStateChangeTrigger(rule) || containsStateUpdateTrigger(rule)) {
158+
val lastStateUpdateTypeRef = ruleModel.newTypeRef(ZonedDateTime)
159+
parameters += rule.toParameter(VAR_LAST_STATE_UPDATE, lastStateUpdateTypeRef)
160+
}
161+
if (containsStateChangeTrigger(rule)) {
162+
val lastStateChangeTypeRef = ruleModel.newTypeRef(ZonedDateTime)
163+
parameters += rule.toParameter(VAR_LAST_STATE_CHANGE, lastStateChangeTypeRef)
164+
}
152165
if (containsEventTrigger(rule)) {
153166
val eventTypeRef = ruleModel.newTypeRef(String)
154167
parameters += rule.toParameter(VAR_RECEIVED_EVENT, eventTypeRef)
@@ -163,10 +176,6 @@ class RulesJvmModelInferrer extends ScriptJvmModelInferrer {
163176
val newStatusRef = ruleModel.newTypeRef(String)
164177
parameters += rule.toParameter(VAR_NEW_STATUS, newStatusRef)
165178
}
166-
if ((containsStateChangeTrigger(rule) || containsStateUpdateTrigger(rule)) && !containsParam(parameters, VAR_NEW_STATE)) {
167-
val stateTypeRef = ruleModel.newTypeRef(State)
168-
parameters += rule.toParameter(VAR_NEW_STATE, stateTypeRef)
169-
}
170179

171180
body = rule.script
172181
]

bundles/org.openhab.core.model.script.runtime/src/org/openhab/core/model/script/runtime/internal/engine/DSLScriptEngine.java

+10-5
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,16 @@ public class DSLScriptEngine implements javax.script.ScriptEngine {
6161

6262
public static final String MIMETYPE_OPENHAB_DSL_RULE = "application/vnd.openhab.dsl.rule";
6363

64-
private static final Map<String, String> IMPLICIT_VARS = Map.of("command",
65-
ScriptJvmModelInferrer.VAR_RECEIVED_COMMAND, "state", ScriptJvmModelInferrer.VAR_NEW_STATE, "newState",
66-
ScriptJvmModelInferrer.VAR_NEW_STATE, "oldState", ScriptJvmModelInferrer.VAR_PREVIOUS_STATE,
67-
"triggeringItem", ScriptJvmModelInferrer.VAR_TRIGGERING_ITEM, "triggeringGroup",
68-
ScriptJvmModelInferrer.VAR_TRIGGERING_GROUP, "input", ScriptJvmModelInferrer.VAR_INPUT);
64+
private static final Map<String, String> IMPLICIT_VARS = Map.of( //
65+
"command", ScriptJvmModelInferrer.VAR_RECEIVED_COMMAND, //
66+
"state", ScriptJvmModelInferrer.VAR_NEW_STATE, //
67+
"newState", ScriptJvmModelInferrer.VAR_NEW_STATE, //
68+
"oldState", ScriptJvmModelInferrer.VAR_PREVIOUS_STATE, //
69+
"lastStateUpdate", ScriptJvmModelInferrer.VAR_LAST_STATE_UPDATE, //
70+
"lastStateChange", ScriptJvmModelInferrer.VAR_LAST_STATE_CHANGE, //
71+
"triggeringItem", ScriptJvmModelInferrer.VAR_TRIGGERING_ITEM, //
72+
"triggeringGroup", ScriptJvmModelInferrer.VAR_TRIGGERING_GROUP, //
73+
"input", ScriptJvmModelInferrer.VAR_INPUT);
6974

7075
private final Logger logger = LoggerFactory.getLogger(DSLScriptEngine.class);
7176

bundles/org.openhab.core.model.script/src/org/openhab/core/model/script/jvmmodel/ScriptJvmModelInferrer.xtend

+11
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
package org.openhab.core.model.script.jvmmodel
1414

1515
import com.google.inject.Inject
16+
import java.time.ZonedDateTime
1617
import java.util.Set
1718
import org.openhab.core.items.ItemRegistry
1819
import org.openhab.core.model.script.scoping.StateAndCommandProvider
@@ -61,6 +62,12 @@ class ScriptJvmModelInferrer extends AbstractModelInferrer {
6162
/** Variable name for the new state of an item in a "changed state triggered" or "updated state triggered" rule */
6263
public static final String VAR_NEW_STATE = "newState";
6364

65+
/** Variable name for the last update time of an item in a "changed state triggered" or "updated state triggered" rule */
66+
public static final String VAR_LAST_STATE_UPDATE = "lastStateUpdate";
67+
68+
/** Variable name for the last change time of an item in a "changed state triggered" rule */
69+
public static final String VAR_LAST_STATE_CHANGE = "lastStateChange";
70+
6471
/** Variable name for the received command in a "command triggered" rule */
6572
public static final String VAR_RECEIVED_COMMAND = "receivedCommand";
6673

@@ -160,6 +167,10 @@ class ScriptJvmModelInferrer extends AbstractModelInferrer {
160167
parameters += script.toParameter(VAR_NEW_STATUS, newThingStatusRef)
161168
val stateTypeRef2 = script.newTypeRef(State)
162169
parameters += script.toParameter(VAR_NEW_STATE, stateTypeRef2)
170+
val lastStateUpdateTypeRef = script.newTypeRef(ZonedDateTime)
171+
parameters += script.toParameter(VAR_LAST_STATE_UPDATE, lastStateUpdateTypeRef)
172+
val lastStateChangeTypeRef = script.newTypeRef(ZonedDateTime)
173+
parameters += script.toParameter(VAR_LAST_STATE_CHANGE, lastStateChangeTypeRef)
163174
val privateCacheTypeRef = script.newTypeRef(ValueCache)
164175
parameters += script.toParameter(VAR_PRIVATE_CACHE, privateCacheTypeRef)
165176
val sharedCacheTypeRef = script.newTypeRef(ValueCache)

bundles/org.openhab.core/src/main/java/org/openhab/core/events/AbstractEventFactory.java

+31-1
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,20 @@
1212
*/
1313
package org.openhab.core.events;
1414

15+
import java.io.IOException;
16+
import java.time.ZonedDateTime;
17+
import java.time.format.DateTimeFormatter;
1518
import java.util.Set;
1619

1720
import org.eclipse.jdt.annotation.NonNullByDefault;
1821
import org.eclipse.jdt.annotation.Nullable;
1922

2023
import com.google.gson.Gson;
24+
import com.google.gson.GsonBuilder;
25+
import com.google.gson.TypeAdapter;
26+
import com.google.gson.stream.JsonReader;
27+
import com.google.gson.stream.JsonToken;
28+
import com.google.gson.stream.JsonWriter;
2129

2230
/**
2331
* The {@link AbstractEventFactory} defines an abstract implementation of the {@link EventFactory} interface. Subclasses
@@ -31,7 +39,8 @@ public abstract class AbstractEventFactory implements EventFactory {
3139

3240
private final Set<String> supportedEventTypes;
3341

34-
private static final Gson JSONCONVERTER = new Gson();
42+
private static final Gson JSONCONVERTER = new GsonBuilder()
43+
.registerTypeAdapter(ZonedDateTime.class, new ZonedDateTimeAdapter()).create();
3544

3645
/**
3746
* Must be called in subclass constructor to define the supported event types.
@@ -120,4 +129,25 @@ protected static void checkNotNullOrEmpty(@Nullable String string, String argume
120129
throw new IllegalArgumentException("The argument '" + argumentName + "' must not be null or empty.");
121130
}
122131
}
132+
133+
public static class ZonedDateTimeAdapter extends TypeAdapter<ZonedDateTime> {
134+
135+
@Override
136+
public void write(JsonWriter out, @Nullable ZonedDateTime value) throws IOException {
137+
if (value == null) {
138+
out.nullValue();
139+
} else {
140+
out.value(value.format(DateTimeFormatter.ISO_ZONED_DATE_TIME));
141+
}
142+
}
143+
144+
@Override
145+
public @Nullable ZonedDateTime read(JsonReader in) throws IOException {
146+
if (in.peek() == JsonToken.NULL) {
147+
in.nextNull();
148+
return null;
149+
}
150+
return ZonedDateTime.parse(in.nextString(), DateTimeFormatter.ISO_ZONED_DATE_TIME);
151+
}
152+
}
123153
}

bundles/org.openhab.core/src/main/java/org/openhab/core/items/GenericItem.java

+10-8
Original file line numberDiff line numberDiff line change
@@ -250,9 +250,9 @@ public void setState(State state, @Nullable State lastState, @Nullable ZonedDate
250250
if (oldStateUpdate != null && lastStateUpdate != null && !oldStateUpdate.equals(lastStateUpdate)) {
251251
notifyListeners(oldState, state);
252252
}
253-
sendStateUpdatedEvent(state);
253+
sendStateUpdatedEvent(state, lastStateUpdate);
254254
if (!oldState.equals(state)) {
255-
sendStateChangedEvent(state, oldState);
255+
sendStateChangedEvent(state, oldState, lastStateUpdate, lastStateChange);
256256
}
257257
}
258258

@@ -273,9 +273,9 @@ protected final void applyState(State state) {
273273
lastState = oldState; // update before we notify listeners
274274
}
275275
notifyListeners(oldState, state);
276-
sendStateUpdatedEvent(state);
276+
sendStateUpdatedEvent(state, lastStateUpdate);
277277
if (stateChanged) {
278-
sendStateChangedEvent(state, oldState);
278+
sendStateChangedEvent(state, oldState, lastStateUpdate, lastStateChange);
279279
lastStateChange = now; // update after we've notified listeners
280280
}
281281
lastStateUpdate = now;
@@ -325,17 +325,19 @@ protected final void applyTimeSeries(TimeSeries timeSeries) {
325325
}
326326
}
327327

328-
private void sendStateUpdatedEvent(State newState) {
328+
private void sendStateUpdatedEvent(State newState, @Nullable ZonedDateTime lastStateUpdate) {
329329
EventPublisher eventPublisher1 = this.eventPublisher;
330330
if (eventPublisher1 != null) {
331-
eventPublisher1.post(ItemEventFactory.createStateUpdatedEvent(this.name, newState, null));
331+
eventPublisher1.post(ItemEventFactory.createStateUpdatedEvent(this.name, newState, lastStateUpdate, null));
332332
}
333333
}
334334

335-
private void sendStateChangedEvent(State newState, State oldState) {
335+
private void sendStateChangedEvent(State newState, State oldState, @Nullable ZonedDateTime lastStateUpdate,
336+
@Nullable ZonedDateTime lastStateChange) {
336337
EventPublisher eventPublisher1 = this.eventPublisher;
337338
if (eventPublisher1 != null) {
338-
eventPublisher1.post(ItemEventFactory.createStateChangedEvent(this.name, newState, oldState));
339+
eventPublisher1.post(ItemEventFactory.createStateChangedEvent(this.name, newState, oldState,
340+
lastStateUpdate, lastStateChange));
339341
}
340342
}
341343

0 commit comments

Comments
 (0)