Search in sources :

Example 1 with Values

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)));
    }
}
Also used : AssetAttribute(org.openremote.model.asset.AssetAttribute) Values(org.openremote.model.value.Values)

Example 2 with Values

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)));
}
Also used : ObjectValue(org.openremote.model.value.ObjectValue) Values(org.openremote.model.value.Values) Date(java.util.Date)

Example 3 with Values

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));
}
Also used : ClientRole(org.openremote.model.security.ClientRole) AuthContext(org.openremote.container.security.AuthContext) AssetDatapointService(org.openremote.manager.datapoint.AssetDatapointService) Date(java.util.Date) CLIENT_EVENT_TOPIC(org.openremote.manager.event.ClientEventService.CLIENT_EVENT_TOPIC) ValidationFailure(org.openremote.model.ValidationFailure) Exchange(org.apache.camel.Exchange) ArrayList(java.util.ArrayList) Level(java.util.logging.Level) Processor(org.apache.camel.Processor) Container(org.openremote.container.Container) ContainerService(org.openremote.container.ContainerService) RulesService(org.openremote.manager.rules.RulesService) AttributeEvent(org.openremote.model.attribute.AttributeEvent) PersistenceService(org.openremote.container.persistence.PersistenceService) AgentService(org.openremote.manager.agent.AgentService) AgentLink.getAgentLink(org.openremote.model.asset.agent.AgentLink.getAgentLink) MessageBrokerService(org.openremote.container.message.MessageBrokerService) ManagerIdentityService(org.openremote.manager.security.ManagerIdentityService) Asset(org.openremote.model.asset.Asset) AssetType(org.openremote.model.asset.AssetType) EntityManager(javax.persistence.EntityManager) Constants(org.openremote.model.Constants) Logger(java.util.logging.Logger) MessageBrokerSetupService(org.openremote.container.message.MessageBrokerSetupService) Collectors(java.util.stream.Collectors) Reason(org.openremote.manager.asset.AssetProcessingException.Reason) AssetResource(org.openremote.model.asset.AssetResource) HEADER_SOURCE(org.openremote.model.attribute.AttributeEvent.HEADER_SOURCE) Value(org.openremote.model.value.Value) ClientEventService(org.openremote.manager.event.ClientEventService) List(java.util.List) RouteBuilder(org.apache.camel.builder.RouteBuilder) TimerService(org.openremote.container.timer.TimerService) Optional(java.util.Optional) Source(org.openremote.model.attribute.AttributeEvent.Source) Values(org.openremote.model.value.Values) AssetAttribute(org.openremote.model.asset.AssetAttribute) Protocol(org.openremote.agent.protocol.Protocol) AttributeExecuteStatus(org.openremote.model.attribute.AttributeExecuteStatus) GlobalLock.withLock(org.openremote.container.concurrent.GlobalLock.withLock) AuthContext(org.openremote.container.security.AuthContext) AttributeEvent(org.openremote.model.attribute.AttributeEvent) Source(org.openremote.model.attribute.AttributeEvent.Source) Date(java.util.Date) ValidationFailure(org.openremote.model.ValidationFailure) AttributeExecuteStatus(org.openremote.model.attribute.AttributeExecuteStatus) AssetAttribute(org.openremote.model.asset.AssetAttribute)

Example 4 with Values

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;
}
Also used : Value(org.openremote.model.value.Value) LOG(org.openremote.agent.protocol.velbus.AbstractVelbusProtocol.LOG) java.util(java.util) ValueType(org.openremote.model.value.ValueType) Pair(org.openremote.model.util.Pair) EnumUtil(org.openremote.model.util.EnumUtil) Values(org.openremote.model.value.Values) VelbusPacket(org.openremote.agent.protocol.velbus.VelbusPacket) AttributeType(org.openremote.model.attribute.AttributeType) Level(java.util.logging.Level) VelbusPacket(org.openremote.agent.protocol.velbus.VelbusPacket) Pair(org.openremote.model.util.Pair)

Example 5 with Values

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);
}
Also used : ObjectValue(org.openremote.model.value.ObjectValue) Values(org.openremote.model.value.Values) Date(java.util.Date)

Aggregations

Values (org.openremote.model.value.Values)5 Date (java.util.Date)3 Level (java.util.logging.Level)2 AssetAttribute (org.openremote.model.asset.AssetAttribute)2 Value (org.openremote.model.value.Value)2 java.util (java.util)1 ArrayList (java.util.ArrayList)1 List (java.util.List)1 Optional (java.util.Optional)1 Logger (java.util.logging.Logger)1 Collectors (java.util.stream.Collectors)1 EntityManager (javax.persistence.EntityManager)1 Exchange (org.apache.camel.Exchange)1 Processor (org.apache.camel.Processor)1 RouteBuilder (org.apache.camel.builder.RouteBuilder)1 Protocol (org.openremote.agent.protocol.Protocol)1 LOG (org.openremote.agent.protocol.velbus.AbstractVelbusProtocol.LOG)1 VelbusPacket (org.openremote.agent.protocol.velbus.VelbusPacket)1 Container (org.openremote.container.Container)1 ContainerService (org.openremote.container.ContainerService)1