use of org.openremote.model.value.Values in project openremote by openremote.
the class MacroProtocol method processLinkedAttributeWrite.
@Override
protected void processLinkedAttributeWrite(AttributeEvent event, AssetAttribute protocolConfiguration) {
AssetAttribute attribute = getLinkedAttribute(event.getAttributeRef());
if (attribute.isExecutable()) {
// This is a macro execution related write operation
AttributeExecuteStatus status = event.getValue().flatMap(Values::getString).flatMap(AttributeExecuteStatus::fromString).orElse(null);
AttributeRef attributeRef = event.getAttributeRef();
// Check if it's a cancellation request
if (status == AttributeExecuteStatus.REQUEST_CANCEL) {
LOG.fine("Request received to cancel macro execution: " + event);
executions.computeIfPresent(attributeRef, (attributeRef1, macroExecutionTask) -> {
macroExecutionTask.cancel();
return macroExecutionTask;
});
return;
}
// If protocol configuration is disabled then nothing to do here
if (!protocolConfiguration.isEnabled()) {
LOG.fine("Protocol configuration is disabled so cannot be executed: " + protocolConfiguration.getReferenceOrThrow());
return;
}
List<MacroAction> actions = getMacroActions(protocolConfiguration.getReferenceOrThrow());
if (actions.isEmpty()) {
LOG.fine("No actions to execute");
return;
}
executeMacro(attributeRef, actions, status == AttributeExecuteStatus.REQUEST_REPEATING);
return;
}
// Assume this is a write to a macro action value (default to index 0)
int actionIndex = getMacroActionIndex(attribute).orElse(0);
// Extract macro actions from protocol configuration rather than modify the in memory ones
List<MacroAction> actions = MacroConfiguration.getMacroActions(protocolConfiguration);
if (actions.isEmpty()) {
LOG.fine("No actions are available for the linked macro, maybe it is disabled?: " + protocolConfiguration.getReferenceOrThrow());
} else {
actionIndex = Math.min(actions.size(), Math.max(0, actionIndex));
LOG.fine("Updating macro action [" + actionIndex + "] value to: " + event.getValue().map(Value::toJson).orElse(""));
MacroAction action = actions.get(actionIndex);
action.setAttributeState(new AttributeState(action.getAttributeState().getAttributeRef(), event.getValue().orElse(null)));
MetaItem[] actionMeta = actions.stream().map(MacroAction::toMetaItem).toArray(MetaItem[]::new);
updateLinkedProtocolConfiguration(protocolConfiguration, protocolConfig -> MetaItem.replaceMetaByName(protocolConfig.getMeta(), META_MACRO_ACTION, Arrays.asList(actionMeta)));
}
}
use of org.openremote.model.value.Values in project openremote by openremote.
the class CalendarEvent method fromValue.
public static Optional<CalendarEvent> fromValue(Value value) {
if (value == null || value.getType() != ValueType.OBJECT) {
return Optional.empty();
}
ObjectValue objectValue = (ObjectValue) value;
Optional<Long> start = objectValue.get("start").flatMap(Values::getLongCoerced);
Optional<Long> end = objectValue.get("end").flatMap(Values::getLongCoerced);
Optional<RecurrenceRule> recurrence = RecurrenceRule.fromValue(objectValue.getObject("recurrence").orElse(null));
if (!start.isPresent() || !end.isPresent()) {
return Optional.empty();
}
return Optional.of(new CalendarEvent(new Date(1000L * start.get()), new Date(1000L * end.get()), recurrence.orElse(null)));
}
use of org.openremote.model.value.Values in project openremote by openremote.
the class AssetProcessingService method configure.
@Override
public void configure() throws Exception {
// A client wants to write attribute state through event bus
from(CLIENT_EVENT_TOPIC).routeId("FromClientUpdates").filter(body().isInstanceOf(AttributeEvent.class)).setHeader(HEADER_SOURCE, () -> CLIENT).to(ASSET_QUEUE);
// Process attribute events
/* TODO This message consumer should be transactionally consistent with the database, this is currently not the case
Our "if I have not processed this message before" duplicate detection:
- discard events with source time greater than server processing time (future events)
- discard events with source time less than last applied/stored event source time
- allow the rest (also events with same source time, order of application undefined)
Possible improvements moving towards at-least-once:
- Make AssetUpdateProcessor transactional with a two-phase commit API
- Replace at-most-once ClientEventService with at-least-once capable, embeddable message broker/protocol
- See pseudocode here: http://activemq.apache.org/should-i-use-xa.html
- Do we want JMS/AMQP/WSS or SOME_API/MQTT/WSS? ActiveMQ or Moquette?
*/
from(ASSET_QUEUE).routeId("AssetQueueProcessor").filter(body().isInstanceOf(AttributeEvent.class)).doTry().process(exchange -> withLock(getClass().getSimpleName() + "::processFromAssetQueue", () -> {
AttributeEvent event = exchange.getIn().getBody(AttributeEvent.class);
LOG.finest("Processing: " + event);
if (event.getEntityId() == null || event.getEntityId().isEmpty())
return;
if (event.getAttributeName() == null || event.getAttributeName().isEmpty())
return;
Source source = exchange.getIn().getHeader(HEADER_SOURCE, () -> null, Source.class);
if (source == null) {
throw new AssetProcessingException(MISSING_SOURCE);
}
// Process the asset update in a database transaction, this ensures that processors
// will see consistent database state and we only commit if no processor failed. This
// still won't make this procedure consistent with the message queue from which we consume!
persistenceService.doTransaction(em -> {
ServerAsset asset = assetStorageService.find(em, event.getEntityId(), true);
if (asset == null)
throw new AssetProcessingException(ASSET_NOT_FOUND);
AssetAttribute oldAttribute = asset.getAttribute(event.getAttributeName()).orElse(null);
if (oldAttribute == null)
throw new AssetProcessingException(ATTRIBUTE_NOT_FOUND);
// Agent attributes can't be updated with events
if (asset.getWellKnownType() == AssetType.AGENT) {
throw new AssetProcessingException(ILLEGAL_AGENT_UPDATE);
}
// For executable attributes, non-sensor sources can set a writable attribute execute status
if (oldAttribute.isExecutable() && source != SENSOR) {
Optional<AttributeExecuteStatus> status = event.getValue().flatMap(Values::getString).flatMap(AttributeExecuteStatus::fromString);
if (status.isPresent() && !status.get().isWrite()) {
throw new AssetProcessingException(INVALID_ATTRIBUTE_EXECUTE_STATUS);
}
}
switch(source) {
case CLIENT:
AuthContext authContext = exchange.getIn().getHeader(Constants.AUTH_CONTEXT, AuthContext.class);
if (authContext == null) {
throw new AssetProcessingException(NO_AUTH_CONTEXT);
}
// Check realm, must be accessible
if (!identityService.getIdentityProvider().isTenantActiveAndAccessible(authContext, asset)) {
throw new AssetProcessingException(INSUFFICIENT_ACCESS);
}
// Check read-only
if (oldAttribute.isReadOnly() && !authContext.isSuperUser()) {
throw new AssetProcessingException(INSUFFICIENT_ACCESS);
}
// Regular user must have write assets role
if (!authContext.hasResourceRoleOrIsSuperUser(ClientRole.WRITE_ASSETS.getValue(), Constants.KEYCLOAK_CLIENT_ID)) {
throw new AssetProcessingException(INSUFFICIENT_ACCESS);
}
// Check restricted user
if (identityService.getIdentityProvider().isRestrictedUser(authContext.getUserId())) {
// Must be asset linked to user
if (!assetStorageService.isUserAsset(authContext.getUserId(), event.getEntityId())) {
throw new AssetProcessingException(INSUFFICIENT_ACCESS);
}
// Must be writable by restricted client
if (!oldAttribute.isAccessRestrictedWrite()) {
throw new AssetProcessingException(INSUFFICIENT_ACCESS);
}
}
break;
case SENSOR:
Optional<AssetAttribute> protocolConfiguration = getAgentLink(oldAttribute).flatMap(agentService::getProtocolConfiguration);
// Sensor event must be for an attribute linked to a protocol configuration
if (!protocolConfiguration.isPresent()) {
throw new AssetProcessingException(INVALID_AGENT_LINK);
}
break;
}
// Either use the timestamp of the event or set event time to processing time
long processingTime = timerService.getCurrentTimeMillis();
long eventTime = event.getTimestamp() > 0 ? event.getTimestamp() : processingTime;
// the attribute until after that time (maybe that is desirable behaviour)
if (eventTime - processingTime > 0) {
// TODO: Decide how to handle update events in the future - ignore or change timestamp
throw new AssetProcessingException(EVENT_IN_FUTURE, "current time: " + new Date(processingTime) + "/" + processingTime + ", event time: " + new Date(eventTime) + "/" + eventTime);
}
// Check the last update timestamp of the attribute, ignoring any event that is older than last update
// TODO This means we drop out-of-sequence events but accept events with the same source timestamp
// TODO Several attribute events can occur in the same millisecond, then order of application is undefined
oldAttribute.getValueTimestamp().filter(t -> t >= 0 && eventTime < t).ifPresent(lastStateTime -> {
throw new AssetProcessingException(EVENT_OUTDATED, "last asset state time: " + new Date(lastStateTime) + "/" + lastStateTime + ", event time: " + new Date(eventTime) + "/" + eventTime);
});
// Create a copy of the attribute and set the new value and timestamp
AssetAttribute updatedAttribute = oldAttribute.deepCopy();
updatedAttribute.setValue(event.getValue().orElse(null), eventTime);
// Validate constraints of attribute
List<ValidationFailure> validationFailures = updatedAttribute.getValidationFailures();
if (!validationFailures.isEmpty()) {
throw new AssetProcessingException(ATTRIBUTE_VALIDATION_FAILURE, validationFailures.toString());
}
// Push through all processors
boolean consumedCompletely = processAssetUpdate(em, asset, updatedAttribute, source);
// Publish a new event for clients if no processor consumed the update completely
if (!consumedCompletely) {
publishClientEvent(asset, updatedAttribute);
}
});
})).endDoTry().doCatch(AssetProcessingException.class).process(handleAssetProcessingException(LOG));
}
use of org.openremote.model.value.Values in project openremote by openremote.
the class ProgramsProcessor method getPropertyWritePackets.
@Override
public List<VelbusPacket> getPropertyWritePackets(VelbusDevice device, String property, Value value) {
Optional<Pair<Integer, String>> channelNumberAndPropertySuffix = getChannelNumberAndPropertySuffix(device, CHANNEL_REGEX, property);
if (channelNumberAndPropertySuffix.isPresent()) {
switch(channelNumberAndPropertySuffix.get().value) {
case PROGRAM_STEPS_ENABLED_SUFFIX:
return Values.getBooleanCoerced(value).map(enabled -> getProgramStepsPackets(device, channelNumberAndPropertySuffix.get().key, enabled, 0xFFFFF)).orElse(null);
case PROGRAM_STEPS_DISABLED_SECONDS_SUFFIX:
return Values.getIntegerCoerced(value).map(durationSeconds -> getProgramStepsPackets(device, channelNumberAndPropertySuffix.get().key, false, durationSeconds)).orElse(null);
}
return null;
}
if (property.equals("ALL" + PROGRAM_STEPS_ENABLED_SUFFIX)) {
return Values.getBooleanCoerced(value).map(enabled -> getProgramStepsPackets(device, 0xFF, enabled, 0xFFFFF)).orElse(null);
}
if (property.equals("ALL" + PROGRAM_STEPS_DISABLED_SECONDS_SUFFIX)) {
return Values.getIntegerCoerced(value).map(durationSeconds -> getProgramStepsPackets(device, 0xFF, false, durationSeconds)).orElse(null);
}
if (property.equals("PROGRAM")) {
return EnumUtil.enumFromValue(Program.class, value).map(program -> Collections.singletonList(new VelbusPacket(device.getBaseAddress(), VelbusPacket.OutboundCommand.PROGRAM_SELECT.getCode(), VelbusPacket.PacketPriority.LOW, (byte) program.getCode()))).orElse(null);
}
if (property.equals("SUNRISE_ENABLED") || property.equals("SUNSET_ENABLED")) {
return Values.getBooleanCoerced(value).map(enabled -> {
boolean sunriseEnabled = device.getPropertyValue("SUNRISE_ENABLED") != null && ((BooleanDevicePropertyValue) device.getPropertyValue("SUNRISE_ENABLED")).getPropertyValue();
boolean sunsetEnabled = device.getPropertyValue("SUNSET_ENABLED") != null && ((BooleanDevicePropertyValue) device.getPropertyValue("SUNSET_ENABLED")).getPropertyValue();
if (property.equals("SUNRISE_ENABLED")) {
sunriseEnabled = enabled;
} else {
sunsetEnabled = enabled;
}
int sunriseSunsetByte = 0;
if (sunriseEnabled) {
sunriseSunsetByte += 1;
}
if (sunsetEnabled) {
sunriseSunsetByte += 2;
}
// Push new values into the device cache so they are instantly available
device.setProperty("SUNRISE_ENABLED", sunriseEnabled ? BooleanDevicePropertyValue.TRUE : BooleanDevicePropertyValue.FALSE);
device.setProperty("SUNSET_ENABLED", sunsetEnabled ? BooleanDevicePropertyValue.TRUE : BooleanDevicePropertyValue.FALSE);
return Collections.singletonList(new VelbusPacket(device.getBaseAddress(), VelbusPacket.OutboundCommand.SET_SUNRISE_SUNSET.getCode(), VelbusPacket.PacketPriority.LOW, (byte) 0xFF, (byte) sunriseSunsetByte));
}).orElse(null);
}
if (property.startsWith("ALARM")) {
int alarmNumber;
try {
alarmNumber = Integer.parseInt(property.substring(5, 6));
} catch (NumberFormatException e) {
LOG.log(Level.INFO, "Alarm number must be number 1 or 2");
return null;
}
String alarmProperty = property.substring(7);
switch(alarmProperty) {
case "ENABLED":
case "WAKE_TIME":
case "BED_TIME":
String alarmEnabledProperty = "ALARM" + alarmNumber + "_ENABLED";
String alarmWakeTimeProperty = "ALARM" + alarmNumber + "_WAKE_TIME";
String alarmBedTimeProperty = "ALARM" + alarmNumber + "_BED_TIME";
String alarmMasterProperty = "ALARM" + alarmNumber + "_MASTER";
if (!device.hasPropertyValue(alarmEnabledProperty) || !device.hasPropertyValue(alarmWakeTimeProperty) || !device.hasPropertyValue(alarmBedTimeProperty) || !device.hasPropertyValue(alarmMasterProperty)) {
LOG.info("Device cache doesn't contain alarm enabled, type and/or time properties");
return null;
}
boolean enabled = ((BooleanDevicePropertyValue) device.getPropertyValue(alarmEnabledProperty)).getPropertyValue();
String wakeTime = ((StringDevicePropertyValue) device.getPropertyValue(alarmWakeTimeProperty)).getPropertyValue();
String bedTime = ((StringDevicePropertyValue) device.getPropertyValue(alarmBedTimeProperty)).getPropertyValue();
boolean isGlobal = ((BooleanDevicePropertyValue) device.getPropertyValue(alarmMasterProperty)).getPropertyValue();
if ("ENABLED".equals(alarmProperty)) {
Optional<Boolean> setEnabled = Values.getBooleanCoerced(value);
if (!setEnabled.isPresent()) {
return null;
}
enabled = setEnabled.get();
} else if ("WAKE_TIME".equals(alarmProperty)) {
if (value == null) {
return null;
}
wakeTime = value.toString();
} else if ("BED_TIME".equals(alarmProperty)) {
if (value == null) {
return null;
}
bedTime = value.toString();
}
String[] wakeTimeValues = wakeTime.split(":");
String[] bedTimeValues = bedTime.split(":");
if (wakeTimeValues.length != 2 || bedTimeValues.length != 2) {
LOG.info("Time property values must be in the format HH:mm");
return null;
}
int wakeHours, wakeMins, bedHours, bedMins;
try {
wakeHours = Integer.parseInt(wakeTimeValues[0]);
wakeMins = Integer.parseInt(wakeTimeValues[1]);
bedHours = Integer.parseInt(bedTimeValues[0]);
bedMins = Integer.parseInt(bedTimeValues[1]);
if (wakeHours < 0 || wakeHours > 23 || wakeMins < 0 || wakeMins > 59 || bedHours < 0 || bedHours > 23 || bedMins < 0 || bedMins > 59) {
throw new NumberFormatException("Hours and/or minutes out of allowed range");
}
} catch (NumberFormatException e) {
LOG.log(Level.INFO, "Time property values must be in the format HH:mm", e);
return null;
}
// Update property values and also send memory read request to ensure update occurred
device.setProperty(alarmEnabledProperty, enabled ? BooleanDevicePropertyValue.TRUE : BooleanDevicePropertyValue.FALSE);
device.setProperty(alarmWakeTimeProperty, new StringDevicePropertyValue(wakeTime));
device.setProperty(alarmBedTimeProperty, new StringDevicePropertyValue(bedTime));
device.velbusNetwork.scheduleTask(() -> {
List<VelbusPacket> packets = getStatusRequestPackets(device);
device.velbusNetwork.sendPackets(packets.toArray(new VelbusPacket[packets.size()]));
}, 500);
return Collections.singletonList(new VelbusPacket(isGlobal ? 0x00 : device.getBaseAddress(), VelbusPacket.OutboundCommand.SET_ALARM.getCode(), VelbusPacket.PacketPriority.LOW, (byte) alarmNumber, (byte) wakeHours, (byte) wakeMins, (byte) bedHours, (byte) bedMins, (byte) (enabled ? 1 : 0)));
}
}
return null;
}
use of org.openremote.model.value.Values in project openremote by openremote.
the class RecurrenceRule method fromValue.
public static Optional<RecurrenceRule> fromValue(Value value) {
if (value == null || value.getType() != ValueType.OBJECT) {
return Optional.empty();
}
ObjectValue objectValue = (ObjectValue) value;
Optional<Frequency> frequency = objectValue.getString("frequency").flatMap(s -> EnumUtil.enumFromString(Frequency.class, s));
Integer interval = objectValue.get("interval").flatMap(Values::getIntegerCoerced).orElse(null);
Integer count = objectValue.get("count").flatMap(Values::getIntegerCoerced).orElse(null);
Optional<Long> until = objectValue.get("until").flatMap(Values::getLongCoerced);
if (!frequency.isPresent()) {
return Optional.empty();
}
RecurrenceRule rRule = new RecurrenceRule(frequency.get());
rRule.interval = interval;
rRule.count = count;
until.ifPresent(l -> rRule.until = new Date(1000L * l));
return Optional.of(rRule);
}
Aggregations