diff --git a/.gitignore b/.gitignore index a83dfdcf729..8ab072f83ff 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,8 @@ bin/ .metadata/ */plugin.xml_gen .DS_Store +*.iml +.idea/ distribution/smarthome/logs/*.log distribution/smarthome/*.zip diff --git a/bundles/automation/org.eclipse.smarthome.automation.module.core/ESH-INF/automation/moduletypes/EventTriggersTypeDefinition.json b/bundles/automation/org.eclipse.smarthome.automation.module.core/ESH-INF/automation/moduletypes/EventTriggersTypeDefinition.json index b44ad4b7531..c0b8c3530b3 100644 --- a/bundles/automation/org.eclipse.smarthome.automation.module.core/ESH-INF/automation/moduletypes/EventTriggersTypeDefinition.json +++ b/bundles/automation/org.eclipse.smarthome.automation.module.core/ESH-INF/automation/moduletypes/EventTriggersTypeDefinition.json @@ -110,6 +110,41 @@ "reference":"itemStateChangeTriggerID.event" } ] + }, + { + "uid":"ChannelEventTrigger", + "label":"Channel Trigger", + "description":"This triggers a rule if a channel emits a trigger", + "configDescriptions":[ + { + "name":"channelUID", + "type":"TEXT", + "context":"channel", + "label":"Channel", + "description":"the id of the channel which should be observed for triggers", + "required":true + } + ], + "children":[ + { + "id":"channelEventTriggerId", + "type":"GenericEventTrigger", + "configuration":{ + "eventSource":"$channelUID", + "eventTopic":"smarthome/channels/*/triggered", + "eventTypes":"ChannelTriggeredEvent" + } + } + ], + "outputs":[ + { + "name":"event", + "type":"org.eclipse.smarthome.core.events.Event", + "description":"the event of the channel trigger", + "label":"Event", + "reference":"channelEventTriggerId.event" + } + ] } ] } \ No newline at end of file diff --git a/bundles/automation/org.eclipse.smarthome.automation.module.core/META-INF/MANIFEST.MF b/bundles/automation/org.eclipse.smarthome.automation.module.core/META-INF/MANIFEST.MF index 4c7153e4052..a25916f55a0 100644 --- a/bundles/automation/org.eclipse.smarthome.automation.module.core/META-INF/MANIFEST.MF +++ b/bundles/automation/org.eclipse.smarthome.automation.module.core/META-INF/MANIFEST.MF @@ -14,6 +14,7 @@ Import-Package: com.google.common.collect, org.eclipse.smarthome.core.items, org.eclipse.smarthome.core.items.events, org.eclipse.smarthome.core.library.types, + org.eclipse.smarthome.core.thing.events, org.eclipse.smarthome.core.types, org.osgi.framework, org.osgi.service.component, diff --git a/bundles/automation/org.eclipse.smarthome.automation.module.core/src/main/java/org/eclipse/smarthome/automation/module/core/factory/BasicModuleHandlerFactory.java b/bundles/automation/org.eclipse.smarthome.automation.module.core/src/main/java/org/eclipse/smarthome/automation/module/core/factory/BasicModuleHandlerFactory.java index c87ccbfad6b..68afac1c4bd 100644 --- a/bundles/automation/org.eclipse.smarthome.automation.module.core/src/main/java/org/eclipse/smarthome/automation/module/core/factory/BasicModuleHandlerFactory.java +++ b/bundles/automation/org.eclipse.smarthome.automation.module.core/src/main/java/org/eclipse/smarthome/automation/module/core/factory/BasicModuleHandlerFactory.java @@ -23,6 +23,11 @@ import org.eclipse.smarthome.automation.module.core.handler.ItemStateConditionHandler; import org.eclipse.smarthome.core.events.EventPublisher; import org.eclipse.smarthome.core.items.ItemRegistry; +import org.eclipse.smarthome.core.thing.events.ThingEventFactory; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceReference; +import org.osgi.util.tracker.ServiceTracker; +import org.osgi.util.tracker.ServiceTrackerCustomizer; import org.osgi.service.component.ComponentContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -39,10 +44,10 @@ public class BasicModuleHandlerFactory extends BaseModuleHandlerFactory { private Logger logger = LoggerFactory.getLogger(BasicModuleHandlerFactory.class); - private static final Collection types = Arrays - .asList(new String[] { ItemStateConditionHandler.ITEM_STATE_CONDITION, - ItemPostCommandActionHandler.ITEM_POST_COMMAND_ACTION, GenericEventTriggerHandler.MODULE_TYPE_ID, - EventConditionHandler.MODULETYPE_ID, CompareConditionHandler.MODULE_TYPE }); + private static final Collection types = Arrays.asList(new String[] { + ItemStateConditionHandler.ITEM_STATE_CONDITION, ItemPostCommandActionHandler.ITEM_POST_COMMAND_ACTION, + GenericEventTriggerHandler.MODULE_TYPE_ID, EventConditionHandler.MODULETYPE_ID, + CompareConditionHandler.MODULE_TYPE }); private ItemRegistry itemRegistry; private EventPublisher eventPublisher; diff --git a/bundles/automation/org.eclipse.smarthome.automation.module.core/src/main/java/org/eclipse/smarthome/automation/module/core/handler/GenericEventTriggerHandler.java b/bundles/automation/org.eclipse.smarthome.automation.module.core/src/main/java/org/eclipse/smarthome/automation/module/core/handler/GenericEventTriggerHandler.java index 01c3956ed84..47848c74765 100644 --- a/bundles/automation/org.eclipse.smarthome.automation.module.core/src/main/java/org/eclipse/smarthome/automation/module/core/handler/GenericEventTriggerHandler.java +++ b/bundles/automation/org.eclipse.smarthome.automation.module.core/src/main/java/org/eclipse/smarthome/automation/module/core/handler/GenericEventTriggerHandler.java @@ -93,7 +93,6 @@ public void receive(Event event) { if (callback != null) { logger.trace("Received Event: Source: " + event.getSource() + " Topic: " + event.getTopic() + " Type: " + event.getType() + " Payload: " + event.getPayload()); - if (!event.getTopic().contains(source)) { return; } diff --git a/bundles/core/org.eclipse.smarthome.core.persistence/OSGI-INF/persistenceserviceregistry.xml b/bundles/core/org.eclipse.smarthome.core.persistence/OSGI-INF/persistenceserviceregistry.xml index 97ee56f6d8a..23aa9db2b4f 100644 --- a/bundles/core/org.eclipse.smarthome.core.persistence/OSGI-INF/persistenceserviceregistry.xml +++ b/bundles/core/org.eclipse.smarthome.core.persistence/OSGI-INF/persistenceserviceregistry.xml @@ -1,4 +1,13 @@ + diff --git a/bundles/core/org.eclipse.smarthome.core.thing.test/src/test/groovy/org/eclipse/smarthome/core/thing/events/ThingEventFactoryTest.groovy b/bundles/core/org.eclipse.smarthome.core.thing.test/src/test/groovy/org/eclipse/smarthome/core/thing/events/ThingEventFactoryTest.groovy index 325fafb56a7..f593853954f 100644 --- a/bundles/core/org.eclipse.smarthome.core.thing.test/src/test/groovy/org/eclipse/smarthome/core/thing/events/ThingEventFactoryTest.groovy +++ b/bundles/core/org.eclipse.smarthome.core.thing.test/src/test/groovy/org/eclipse/smarthome/core/thing/events/ThingEventFactoryTest.groovy @@ -10,17 +10,17 @@ package org.eclipse.smarthome.core.thing.events; import static org.hamcrest.CoreMatchers.* import static org.junit.Assert.* import static org.junit.matchers.JUnitMatchers.* - import org.eclipse.smarthome.core.events.Event +import org.eclipse.smarthome.core.thing.ChannelUID import org.eclipse.smarthome.core.thing.ThingStatus import org.eclipse.smarthome.core.thing.ThingStatusDetail import org.eclipse.smarthome.core.thing.ThingUID import org.eclipse.smarthome.core.thing.binding.builder.ThingStatusInfoBuilder import org.eclipse.smarthome.core.thing.dto.ThingDTOMapper +import org.eclipse.smarthome.core.thing.events.ThingEventFactory.TriggerEventPayloadBean import org.eclipse.smarthome.core.thing.internal.ThingImpl import org.eclipse.smarthome.test.OSGiTest import org.junit.Test - import com.google.gson.Gson /** @@ -40,6 +40,7 @@ class ThingEventFactoryTest extends OSGiTest { def THING_STATUS_EVENT_TYPE = ThingStatusInfoEvent.TYPE def THING_ADDED_EVENT_TYPE = ThingAddedEvent.TYPE + def CHANNEL_TRIGGERED_EVENT_TYPE = ChannelTriggeredEvent.TYPE def THING_STATUS_EVENT_TOPIC = ThingEventFactory.THING_STATUS_INFO_EVENT_TOPIC.replace("{thingUID}", THING_UID.getAsString()) def THING_ADDED_EVENT_TOPIC = ThingEventFactory.THING_ADDED_EVENT_TOPIC.replace("{thingUID}", THING_UID.getAsString()) @@ -47,6 +48,10 @@ class ThingEventFactoryTest extends OSGiTest { def THING_STATUS_EVENT_PAYLOAD = new Gson().toJson(THING_STATUS_INFO) def THING_ADDED_EVENT_PAYLOAD = new Gson().toJson(ThingDTOMapper.map(THING)) + def CHANNEL_UID = new ChannelUID(THING_UID, "channel") + def CHANNEL_TRIGGERED_EVENT_TOPIC = ThingEventFactory.CHANNEL_TRIGGERED_EVENT_TOPIC.replace("{channelUID}", CHANNEL_UID.getAsString()) + def TRIGGER_EVENT = "PRESSED" + def CHANNEL_TRIGGERED_EVENT_PAYLOAD = new Gson().toJson(new TriggerEventPayloadBean(TRIGGER_EVENT, CHANNEL_UID.getAsString())) @Test void 'ThingEventFactory creates Event as ThingStatusInfoEvent correctly'() { @@ -95,4 +100,28 @@ class ThingEventFactoryTest extends OSGiTest { assertThat event.getThing(), not(null) assertThat event.getThing().UID, is(THING_UID.getAsString()) } + + @Test + void 'ThingEventFactory creates ChannelTriggeredEvent correctly'() { + ChannelTriggeredEvent event = ThingEventFactory.createTriggerEvent(TRIGGER_EVENT, CHANNEL_UID) + + assertThat event.getType(), is(CHANNEL_TRIGGERED_EVENT_TYPE) + assertThat event.getTopic(), is(CHANNEL_TRIGGERED_EVENT_TOPIC) + assertThat event.getPayload(), is(CHANNEL_TRIGGERED_EVENT_PAYLOAD) + assertThat event.getEvent(), not(null) + assertThat event.getEvent(), is(TRIGGER_EVENT) + } + + @Test + void 'ThingEventFactory creates Event as ChannelTriggeredEvent correctly'() { + Event event = factory.createEvent(CHANNEL_TRIGGERED_EVENT_TYPE, CHANNEL_TRIGGERED_EVENT_TOPIC, CHANNEL_TRIGGERED_EVENT_PAYLOAD, null) + + assertThat event, is(instanceOf(ChannelTriggeredEvent)) + ChannelTriggeredEvent triggeredEvent = event as ChannelTriggeredEvent + assertThat triggeredEvent.getType(), is(CHANNEL_TRIGGERED_EVENT_TYPE) + assertThat triggeredEvent.getTopic(), is(CHANNEL_TRIGGERED_EVENT_TOPIC) + assertThat triggeredEvent.getPayload(), is(CHANNEL_TRIGGERED_EVENT_PAYLOAD) + assertThat triggeredEvent.getEvent(), not(null) + assertThat triggeredEvent.getEvent(), is(TRIGGER_EVENT) + } } diff --git a/bundles/core/org.eclipse.smarthome.core.thing.xml/src/main/java/org/eclipse/smarthome/core/thing/xml/internal/AbstractDescriptionTypeConverter.java b/bundles/core/org.eclipse.smarthome.core.thing.xml/src/main/java/org/eclipse/smarthome/core/thing/xml/internal/AbstractDescriptionTypeConverter.java index f52ba3a640b..6a6b894fb76 100644 --- a/bundles/core/org.eclipse.smarthome.core.thing.xml/src/main/java/org/eclipse/smarthome/core/thing/xml/internal/AbstractDescriptionTypeConverter.java +++ b/bundles/core/org.eclipse.smarthome.core.thing.xml/src/main/java/org/eclipse/smarthome/core/thing/xml/internal/AbstractDescriptionTypeConverter.java @@ -29,9 +29,9 @@ * definition information within an XML document into a concrete type object. *

* This class should be used for each type definition which inherits from the {@link AbstractDescriptionType} class. - * + * * @param the result type of the conversion - * + * * @author Michael Grammling - Initial Contribution */ public abstract class AbstractDescriptionTypeConverter extends GenericUnmarshaller { @@ -42,7 +42,7 @@ public abstract class AbstractDescriptionTypeConverter extends GenericUnmarsh /** * Creates a new instance of this class with the specified parameter. - * + * * @param clazz the class of the result type (must not be null) * @param type the name of the type (e.g. "thing-type", "channel-type") */ @@ -56,7 +56,7 @@ public AbstractDescriptionTypeConverter(Class clazz, String type) { /** * Returns the {@code id} attribute of the specific XML type definition. - * + * * @param attributes the attributes where to extract the ID information (must not be null) * @return the ID of the type definition (neither null, nor empty) */ @@ -66,10 +66,10 @@ protected String getID(Map attributes) { /** * Returns the full extracted UID of the specific XML type definition. - * + * * @param attributes the attributes where to extract the ID information (must not be null) * @param context the context where to extract the binding ID information (must not be null) - * + * * @return the UID of the type definition (neither null, nor empty) */ protected String getUID(Map attributes, UnmarshallingContext context) { @@ -83,7 +83,7 @@ protected String getUID(Map attributes, UnmarshallingContext con /** * Returns the value of the {@code label} tag from the specific XML type definition. - * + * * @param nodeIterator the iterator to be used to extract the information (must not be null) * @return the value of the label (neither null, nor empty) * @throws ConversionException if the label could not be read @@ -94,7 +94,7 @@ protected String readLabel(NodeIterator nodeIterator) throws ConversionException /** * Returns the value of the {@code description} tag from the specific XML type definition. - * + * * @param nodeIterator the iterator to be used to extract the information (must not be null) * @return the value of the description (could be null or empty) */ @@ -109,8 +109,8 @@ private URI readConfigDescriptionURI(NodeIterator nodeIterator) throws Conversio try { return new URI(uriText); } catch (NullPointerException | URISyntaxException ex) { - throw new ConversionException("The URI '" + uriText + "' in node " - + "'config-description-ref' is invalid!", ex); + throw new ConversionException( + "The URI '" + uriText + "' in node " + "'config-description-ref' is invalid!", ex); } } @@ -134,9 +134,9 @@ private ConfigDescription readConfigDescription(NodeIterator nodeIterator) { /** * Returns the value of the {@code config-description-ref} and the {@code config-description} tags from the specific * XML type definition. - * + * * @param nodeIterator the iterator to be used to extract the information (must not be null) - * + * * @return the URI and configuration object * (contains two elements: URI - could be null, ConfigDescription - could be null) */ @@ -155,19 +155,19 @@ protected Object[] getConfigDescriptionObjects(NodeIterator nodeIterator) { /** * The abstract unmarshal method which must be implemented by the according type converter. - * + * * @param reader the reader to be used to read XML information from a stream (not null) - * + * * @param context the context to be used for the XML document conversion (not null) - * + * * @param attributes the attributes map containing attributes of the type - only UID - * (not null, could be empty) - * + * * @param nodeIterator the iterator to be used to simply extract information in the right * order and appearance from the XML stream - * + * * @return the concrete type definition object (could be null) - * + * * @throws ConversionException if any conversion error occurs */ protected abstract T unmarshalType(HierarchicalStreamReader reader, UnmarshallingContext context, diff --git a/bundles/core/org.eclipse.smarthome.core.thing.xml/src/main/java/org/eclipse/smarthome/core/thing/xml/internal/ChannelTypeConverter.java b/bundles/core/org.eclipse.smarthome.core.thing.xml/src/main/java/org/eclipse/smarthome/core/thing/xml/internal/ChannelTypeConverter.java index cc4e8cbd0a9..6c8375ae5ab 100644 --- a/bundles/core/org.eclipse.smarthome.core.thing.xml/src/main/java/org/eclipse/smarthome/core/thing/xml/internal/ChannelTypeConverter.java +++ b/bundles/core/org.eclipse.smarthome.core.thing.xml/src/main/java/org/eclipse/smarthome/core/thing/xml/internal/ChannelTypeConverter.java @@ -12,15 +12,15 @@ import java.util.List; import java.util.Map; import java.util.Set; - import org.eclipse.smarthome.config.core.ConfigDescription; import org.eclipse.smarthome.config.xml.util.ConverterAttributeMapValidator; import org.eclipse.smarthome.config.xml.util.NodeIterator; import org.eclipse.smarthome.config.xml.util.NodeValue; +import org.eclipse.smarthome.core.thing.type.ChannelKind; import org.eclipse.smarthome.core.thing.type.ChannelType; import org.eclipse.smarthome.core.thing.type.ChannelTypeUID; +import org.eclipse.smarthome.core.types.EventDescription; import org.eclipse.smarthome.core.types.StateDescription; - import com.thoughtworks.xstream.converters.ConversionException; import com.thoughtworks.xstream.converters.Converter; import com.thoughtworks.xstream.converters.UnmarshallingContext; @@ -33,7 +33,7 @@ *

* This converter converts {@code channel-type} XML tags. It uses the {@link AbstractDescriptionTypeConverter} which * offers base functionality for each type definition. - * + * * @author Michael Grammling - Initial Contribution * @author Ivan Iliev - Added support for system wide channel types */ @@ -42,8 +42,8 @@ public class ChannelTypeConverter extends AbstractDescriptionTypeConverter attributes, String attributeName, boolean defaultValue) { @@ -57,7 +57,11 @@ private boolean readBoolean(Map attributes, String attributeName } private String readItemType(NodeIterator nodeIterator) throws ConversionException { - return (String) nodeIterator.nextValue("item-type", true); + return (String) nodeIterator.nextValue("item-type", false); + } + + private String readKind(NodeIterator nodeIterator) throws ConversionException { + return (String) nodeIterator.nextValue("kind", false); } private String readCategory(NodeIterator nodeIterator) throws ConversionException { @@ -104,6 +108,20 @@ private StateDescription readStateDescription(NodeIterator nodeIterator) { return null; } + private EventDescription readEventDescription(NodeIterator nodeIterator) { + Object nextNode = nodeIterator.next(); + + if (nextNode != null) { + if (nextNode instanceof EventDescription) { + return (EventDescription) nextNode; + } + + nodeIterator.revert(); + } + + return null; + } + @Override protected ChannelTypeXmlResult unmarshalType(HierarchicalStreamReader reader, UnmarshallingContext context, Map attributes, NodeIterator nodeIterator) throws ConversionException { @@ -115,17 +133,24 @@ protected ChannelTypeXmlResult unmarshalType(HierarchicalStreamReader reader, Un ChannelTypeUID channelTypeUID = new ChannelTypeUID(uid); String itemType = readItemType(nodeIterator); + String kind = readKind(nodeIterator); String label = super.readLabel(nodeIterator); String description = super.readDescription(nodeIterator); String category = readCategory(nodeIterator); Set tags = readTags(nodeIterator); StateDescription stateDescription = readStateDescription(nodeIterator); + EventDescription eventDescription = readEventDescription(nodeIterator); Object[] configDescriptionObjects = super.getConfigDescriptionObjects(nodeIterator); - ChannelType channelType = new ChannelType(channelTypeUID, advanced, itemType, label, description, category, - tags, stateDescription, (URI) configDescriptionObjects[0]); + if (kind == null) { + // Default for kind is 'state' + kind = "state"; + } + + ChannelType channelType = new ChannelType(channelTypeUID, advanced, itemType, ChannelKind.parse(kind), label, + description, category, tags, stateDescription, eventDescription, (URI) configDescriptionObjects[0]); ChannelTypeXmlResult channelTypeXmlResult = new ChannelTypeXmlResult(channelType, (ConfigDescription) configDescriptionObjects[1], system); diff --git a/bundles/core/org.eclipse.smarthome.core.thing.xml/src/main/java/org/eclipse/smarthome/core/thing/xml/internal/EventDescriptionConverter.java b/bundles/core/org.eclipse.smarthome.core.thing.xml/src/main/java/org/eclipse/smarthome/core/thing/xml/internal/EventDescriptionConverter.java new file mode 100644 index 00000000000..0eeafcc989f --- /dev/null +++ b/bundles/core/org.eclipse.smarthome.core.thing.xml/src/main/java/org/eclipse/smarthome/core/thing/xml/internal/EventDescriptionConverter.java @@ -0,0 +1,90 @@ +/** + * Copyright (c) 2014-2016 by the respective copyright holders. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.smarthome.core.thing.xml.internal; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import org.eclipse.smarthome.config.xml.util.GenericUnmarshaller; +import org.eclipse.smarthome.config.xml.util.NodeIterator; +import org.eclipse.smarthome.config.xml.util.NodeList; +import org.eclipse.smarthome.config.xml.util.NodeValue; +import org.eclipse.smarthome.core.types.EventDescription; +import org.eclipse.smarthome.core.types.EventOption; +import com.thoughtworks.xstream.converters.ConversionException; +import com.thoughtworks.xstream.converters.Converter; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; + +/** + * The {@link EventDescriptionConverter} is a concrete implementation of the {@code XStream} {@link Converter} interface + * used to convert a event description within an XML document into a {@link EventDescription} object. + *

+ * This converter converts {@code state} XML tags. + */ +public class EventDescriptionConverter extends GenericUnmarshaller { + + public EventDescriptionConverter() { + super(EventDescription.class); + } + + private List toListOfEventOptions(NodeList nodeList) throws ConversionException { + + if ("options".equals(nodeList.getNodeName())) { + List eventOptions = new ArrayList<>(); + + for (Object nodeObject : nodeList.getList()) { + eventOptions.add(toEventOption((NodeValue) nodeObject)); + } + + return eventOptions; + } + + throw new ConversionException("Unknown type '" + nodeList.getNodeName() + "'!"); + } + + private EventOption toEventOption(NodeValue nodeValue) throws ConversionException { + if ("option".equals(nodeValue.getNodeName())) { + String value; + String label; + + Map attributes = nodeValue.getAttributes(); + if ((attributes != null) && (attributes.containsKey("value"))) { + value = attributes.get("value"); + } else { + throw new ConversionException("The node 'option' requires the attribute 'value'!"); + } + + label = (String) nodeValue.getValue(); + + return new EventOption(value, label); + } + + throw new ConversionException("Unknown type in the list of 'options'!"); + } + + @Override + public final Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { + List eventOptions = null; + + NodeList nodes = (NodeList) context.convertAnother(context, NodeList.class); + NodeIterator nodeIterator = new NodeIterator(nodes.getList()); + + NodeList optionNodes = (NodeList) nodeIterator.next(); + if (optionNodes != null) { + eventOptions = toListOfEventOptions(optionNodes); + } + + nodeIterator.assertEndOfType(); + + EventDescription eventDescription = new EventDescription(eventOptions); + + return eventDescription; + } + +} diff --git a/bundles/core/org.eclipse.smarthome.core.thing.xml/src/main/java/org/eclipse/smarthome/core/thing/xml/internal/ThingDescriptionReader.java b/bundles/core/org.eclipse.smarthome.core.thing.xml/src/main/java/org/eclipse/smarthome/core/thing/xml/internal/ThingDescriptionReader.java index 47789514c06..41a941c1b08 100644 --- a/bundles/core/org.eclipse.smarthome.core.thing.xml/src/main/java/org/eclipse/smarthome/core/thing/xml/internal/ThingDescriptionReader.java +++ b/bundles/core/org.eclipse.smarthome.core.thing.xml/src/main/java/org/eclipse/smarthome/core/thing/xml/internal/ThingDescriptionReader.java @@ -8,7 +8,6 @@ package org.eclipse.smarthome.core.thing.xml.internal; import java.util.List; - import org.eclipse.smarthome.config.core.ConfigDescription; import org.eclipse.smarthome.config.core.ConfigDescriptionParameter; import org.eclipse.smarthome.config.core.ConfigDescriptionParameterGroup; @@ -24,8 +23,8 @@ import org.eclipse.smarthome.config.xml.util.NodeValue; import org.eclipse.smarthome.config.xml.util.NodeValueConverter; import org.eclipse.smarthome.config.xml.util.XmlDocumentReader; +import org.eclipse.smarthome.core.types.EventDescription; import org.eclipse.smarthome.core.types.StateDescription; - import com.thoughtworks.xstream.XStream; /** @@ -39,6 +38,7 @@ * @author Alex Tugarev - Extended by options and filter criteria * @author Thomas Höfer - Added thing and thing type properties * @author Chris Jackson - Added parameter groups and channel properties + * @author Moritz Kammerer - Added triggers */ public class ThingDescriptionReader extends XmlDocumentReader> { @@ -61,6 +61,7 @@ public void registerConverters(XStream xstream) { xstream.registerConverter(new ChannelTypeConverter()); xstream.registerConverter(new ChannelGroupTypeConverter()); xstream.registerConverter(new StateDescriptionConverter()); + xstream.registerConverter(new EventDescriptionConverter()); xstream.registerConverter(new ConfigDescriptionConverter()); xstream.registerConverter(new ConfigDescriptionParameterConverter()); xstream.registerConverter(new ConfigDescriptionParameterGroupConverter()); @@ -77,6 +78,7 @@ public void registerAliases(XStream xstream) { xstream.alias("supported-bridge-type-refs", NodeList.class); xstream.alias("bridge-type-ref", NodeAttributes.class); xstream.alias("item-type", NodeValue.class); + xstream.alias("kind", NodeValue.class); xstream.alias("label", NodeValue.class); xstream.alias("description", NodeValue.class); xstream.alias("channels", NodeList.class); @@ -87,6 +89,7 @@ public void registerAliases(XStream xstream) { xstream.alias("tags", NodeList.class); xstream.alias("tag", NodeValue.class); xstream.alias("state", StateDescription.class); + xstream.alias("event", EventDescription.class); xstream.alias("options", NodeList.class); xstream.alias("option", NodeValue.class); xstream.alias("config-descriptions", NodeList.class); diff --git a/bundles/core/org.eclipse.smarthome.core.thing.xml/src/main/java/org/eclipse/smarthome/core/thing/xml/internal/XmlChannelTypeProvider.java b/bundles/core/org.eclipse.smarthome.core.thing.xml/src/main/java/org/eclipse/smarthome/core/thing/xml/internal/XmlChannelTypeProvider.java index 852e3c40969..3ae86f7adcd 100644 --- a/bundles/core/org.eclipse.smarthome.core.thing.xml/src/main/java/org/eclipse/smarthome/core/thing/xml/internal/XmlChannelTypeProvider.java +++ b/bundles/core/org.eclipse.smarthome.core.thing.xml/src/main/java/org/eclipse/smarthome/core/thing/xml/internal/XmlChannelTypeProvider.java @@ -16,7 +16,6 @@ import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; - import org.eclipse.smarthome.core.common.osgi.ServiceBinder.Bind; import org.eclipse.smarthome.core.common.osgi.ServiceBinder.Unbind; import org.eclipse.smarthome.core.i18n.I18nProvider; @@ -344,8 +343,8 @@ private ChannelType createLocalizedChannelType(Bundle bundle, ChannelType channe StateDescription state = createLocalizedChannelState(bundle, channelType, channelTypeUID, locale); ChannelType localizedChannelType = new ChannelType(channelTypeUID, channelType.isAdvanced(), - channelType.getItemType(), label, description, channelType.getCategory(), channelType.getTags(), - state, channelType.getConfigDescriptionURI()); + channelType.getItemType(), channelType.getKind(), label, description, channelType.getCategory(), + channelType.getTags(), state, channelType.getEvent(), channelType.getConfigDescriptionURI()); localizedChannelTypeCache.put(localizedChannelTypeKey, localizedChannelType); diff --git a/bundles/core/org.eclipse.smarthome.core.thing.xml/thing-description-1.0.0.xsd b/bundles/core/org.eclipse.smarthome.core.thing.xml/thing-description-1.0.0.xsd index 2982f2536e4..c02d5186126 100644 --- a/bundles/core/org.eclipse.smarthome.core.thing.xml/thing-description-1.0.0.xsd +++ b/bundles/core/org.eclipse.smarthome.core.thing.xml/thing-description-1.0.0.xsd @@ -46,12 +46,14 @@ - + + + @@ -132,6 +134,12 @@ + + + + + + @@ -160,4 +168,4 @@ - + diff --git a/bundles/core/org.eclipse.smarthome.core.thing/META-INF/MANIFEST.MF b/bundles/core/org.eclipse.smarthome.core.thing/META-INF/MANIFEST.MF index 10909a4cc80..efea39b414a 100644 --- a/bundles/core/org.eclipse.smarthome.core.thing/META-INF/MANIFEST.MF +++ b/bundles/core/org.eclipse.smarthome.core.thing/META-INF/MANIFEST.MF @@ -21,6 +21,7 @@ Import-Package: com.google.common.base, org.eclipse.smarthome.core.i18n, org.eclipse.smarthome.core.items, org.eclipse.smarthome.core.items.events, + org.eclipse.smarthome.core.library.types, org.eclipse.smarthome.core.thing, org.eclipse.smarthome.core.thing.binding, org.eclipse.smarthome.core.thing.binding.builder, diff --git a/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/Channel.java b/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/Channel.java index 65cf84e197f..4c1ad3bcc09 100644 --- a/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/Channel.java +++ b/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/Channel.java @@ -13,9 +13,9 @@ import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; - import org.eclipse.smarthome.config.core.Configuration; import org.eclipse.smarthome.core.items.Item; +import org.eclipse.smarthome.core.thing.type.ChannelKind; import org.eclipse.smarthome.core.thing.type.ChannelTypeUID; /** @@ -34,6 +34,8 @@ public class Channel { private String acceptedItemType; + private ChannelKind kind; + private ChannelUID uid; private ChannelTypeUID channelTypeUID; @@ -57,31 +59,38 @@ public class Channel { public Channel(ChannelUID uid, String acceptedItemType) { this.uid = uid; this.acceptedItemType = acceptedItemType; + this.kind = ChannelKind.STATE; this.configuration = new Configuration(); this.properties = Collections.unmodifiableMap(new HashMap(0)); } public Channel(ChannelUID uid, String acceptedItemType, Configuration configuration) { - this(uid, null, acceptedItemType, configuration, new HashSet(0), null, null, null); + this(uid, null, acceptedItemType, ChannelKind.STATE, configuration, new HashSet(0), null, null, null); } public Channel(ChannelUID uid, String acceptedItemType, Set defaultTags) { - this(uid, null, acceptedItemType, null, defaultTags == null ? new HashSet(0) : defaultTags, null, null, - null); + this(uid, null, acceptedItemType, ChannelKind.STATE, null, + defaultTags == null ? new HashSet(0) : defaultTags, null, null, null); } public Channel(ChannelUID uid, String acceptedItemType, Configuration configuration, Set defaultTags, Map properties) { - this(uid, null, acceptedItemType, null, defaultTags == null ? new HashSet(0) : defaultTags, properties, - null, null); + this(uid, null, acceptedItemType, ChannelKind.STATE, null, + defaultTags == null ? new HashSet(0) : defaultTags, properties, null, null); } - public Channel(ChannelUID uid, ChannelTypeUID channelTypeUID, String acceptedItemType, Configuration configuration, - Set defaultTags, Map properties, String label, String description) { + public Channel(ChannelUID uid, ChannelTypeUID channelTypeUID, String acceptedItemType, ChannelKind kind, + Configuration configuration, Set defaultTags, Map properties, String label, + String description) { + if (kind == null) { + throw new IllegalArgumentException("kind must not be null"); + } + this.uid = uid; this.channelTypeUID = channelTypeUID; this.acceptedItemType = acceptedItemType; this.configuration = configuration; + this.kind = kind; this.label = label; this.description = description; this.properties = properties; @@ -103,6 +112,20 @@ public String getAcceptedItemType() { return this.acceptedItemType; } + /** + * Returns the channel kind. + * + * @return channel kind + */ + public ChannelKind getKind() { + if (kind == null) { + // STATE is the default. + return ChannelKind.STATE; + } + + return kind; + } + /** * Returns the unique id of the channel. * diff --git a/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/CommonTriggerEvents.java b/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/CommonTriggerEvents.java new file mode 100644 index 00000000000..4668f79ec07 --- /dev/null +++ b/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/CommonTriggerEvents.java @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2014-2016 by the respective copyright holders. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.smarthome.core.thing; + +/** + * Contains often used trigger events. + * + * @author Moritz Kammerer - Initial contribution and API. + */ +public final class CommonTriggerEvents { + /** + * Static class - no instances allowed. + */ + private CommonTriggerEvents() { + } + + public static final String PRESSED = "PRESSED"; + public static final String RELEASED = "RELEASED"; + public static final String SHORT_PRESSED = "SHORT_PRESSED"; + public static final String DOUBLE_PRESSED = "DOUBLE_PRESSED"; + public static final String LONG_PRESSED = "LONG_PRESSED"; + +} diff --git a/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/DefaultSystemChannelTypeProvider.java b/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/DefaultSystemChannelTypeProvider.java index a31e35cbb69..0fb6b7d5047 100644 --- a/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/DefaultSystemChannelTypeProvider.java +++ b/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/DefaultSystemChannelTypeProvider.java @@ -11,12 +11,14 @@ import java.util.Collection; import java.util.Collections; import java.util.Locale; - import org.eclipse.smarthome.core.thing.type.ChannelGroupType; import org.eclipse.smarthome.core.thing.type.ChannelGroupTypeUID; +import org.eclipse.smarthome.core.thing.type.ChannelKind; import org.eclipse.smarthome.core.thing.type.ChannelType; import org.eclipse.smarthome.core.thing.type.ChannelTypeProvider; import org.eclipse.smarthome.core.thing.type.ChannelTypeUID; +import org.eclipse.smarthome.core.types.EventDescription; +import org.eclipse.smarthome.core.types.EventOption; import org.eclipse.smarthome.core.types.StateDescription; /** @@ -26,6 +28,7 @@ * @author Chris Jackson - Added battery level * @author Dennis Nobel - Changed to {@link ChannelTypeProvider} * @author Markus Rathgeb - Make battery-low indication read-only + * @author Moritz Kammerer - Added system trigger types * */ public class DefaultSystemChannelTypeProvider implements ChannelTypeProvider { @@ -53,11 +56,38 @@ public class DefaultSystemChannelTypeProvider implements ChannelTypeProvider { new ChannelTypeUID("system:battery-level"), false, "Number", "Battery Level", null, "Battery", null, null, null); + /** + * System wide trigger {@link ChannelType} without event options. + */ + public static final ChannelType SYSTEM_TRIGGER = new ChannelType(new ChannelTypeUID("system:trigger"), false, null, + ChannelKind.TRIGGER, "Trigger", null, null, null, null, null, null); + + /** + * System wide trigger {@link ChannelType} which triggers "PRESSED" and "RELEASED" events. + */ + public static final ChannelType SYSTEM_RAWBUTTON = new ChannelType(new ChannelTypeUID("system:rawbutton"), false, + null, ChannelKind.TRIGGER, "Raw button", null, null, null, null, + new EventDescription(Arrays.asList(new EventOption(CommonTriggerEvents.PRESSED, null), + new EventOption(CommonTriggerEvents.RELEASED, null))), + null); + + /** + * System wide trigger {@link ChannelType} which triggers "SHORT_PRESSED", "DOUBLE_PRESSED" and "LONG_PRESSED" + * events. + */ + public static final ChannelType SYSTEM_BUTTON = new ChannelType(new ChannelTypeUID("system:button"), false, null, + ChannelKind.TRIGGER, "Button", null, null, null, null, + new EventDescription(Arrays.asList(new EventOption(CommonTriggerEvents.SHORT_PRESSED, null), + new EventOption(CommonTriggerEvents.DOUBLE_PRESSED, null), + new EventOption(CommonTriggerEvents.LONG_PRESSED, null))), + null); + private final Collection channelTypes; public DefaultSystemChannelTypeProvider() { - this.channelTypes = Collections.unmodifiableCollection(Arrays.asList(new ChannelType[] { - SYSTEM_CHANNEL_SIGNAL_STRENGTH, SYSTEM_CHANNEL_LOW_BATTERY, SYSTEM_CHANNEL_BATTERY_LEVEL })); + this.channelTypes = Collections.unmodifiableCollection( + Arrays.asList(new ChannelType[] { SYSTEM_CHANNEL_SIGNAL_STRENGTH, SYSTEM_CHANNEL_LOW_BATTERY, + SYSTEM_CHANNEL_BATTERY_LEVEL, SYSTEM_TRIGGER, SYSTEM_RAWBUTTON, SYSTEM_BUTTON })); } @@ -74,6 +104,12 @@ public ChannelType getChannelType(ChannelTypeUID channelTypeUID, Locale locale) return SYSTEM_CHANNEL_LOW_BATTERY; } else if (channelTypeUID.equals(SYSTEM_CHANNEL_BATTERY_LEVEL.getUID())) { return SYSTEM_CHANNEL_BATTERY_LEVEL; + } else if (channelTypeUID.equals(SYSTEM_TRIGGER.getUID())) { + return SYSTEM_TRIGGER; + } else if (channelTypeUID.equals(SYSTEM_RAWBUTTON.getUID())) { + return SYSTEM_RAWBUTTON; + } else if (channelTypeUID.equals(SYSTEM_BUTTON.getUID())) { + return SYSTEM_BUTTON; } return null; } diff --git a/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/binding/BaseThingHandler.java b/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/binding/BaseThingHandler.java index b0dc8d06da9..398134ddbe3 100644 --- a/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/binding/BaseThingHandler.java +++ b/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/binding/BaseThingHandler.java @@ -11,7 +11,6 @@ import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ScheduledExecutorService; - import org.eclipse.smarthome.config.core.Configuration; import org.eclipse.smarthome.config.core.validation.ConfigDescriptionValidator; import org.eclipse.smarthome.config.core.validation.ConfigValidationException; @@ -278,6 +277,53 @@ protected void updateState(String channelID, State state) { updateState(channelUID, state); } + /** + * Emits an event for the given channel. + * + * @param channelUID UID of the channel over which the event will be emitted + * @param event Event to emit + */ + protected void triggerChannel(ChannelUID channelUID, String event) { + synchronized (this) { + if (this.callback != null) { + this.callback.channelTriggered(this.getThing(), channelUID, event); + } else { + throw new IllegalStateException("Could not update state, because callback is missing"); + } + } + } + + /** + * Emits an event for the given channel. Will use the thing UID to infer the + * unique channel UID. + * + * @param channelUID UID of the channel over which the event will be emitted + * @param event Event to emit + */ + protected void triggerChannel(String channelUID, String event) { + triggerChannel(new ChannelUID(this.getThing().getUID(), channelUID), event); + } + + /** + * Emits an event for the given channel. Will use the thing UID to infer the + * unique channel UID. + * + * @param channelUID UID of the channel over which the event will be emitted + */ + protected void triggerChannel(String channelUID) { + triggerChannel(new ChannelUID(this.getThing().getUID(), channelUID), ""); + } + + /** + * Emits an event for the given channel. Will use the thing UID to infer the + * unique channel UID. + * + * @param channelUID UID of the channel over which the event will be emitted + */ + protected void triggerChannel(ChannelUID channelUID) { + triggerChannel(channelUID, ""); + } + /** * Sends a command for a channel of the thing. * diff --git a/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/binding/ThingHandlerCallback.java b/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/binding/ThingHandlerCallback.java index 87a2c239317..1b863d43f41 100644 --- a/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/binding/ThingHandlerCallback.java +++ b/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/binding/ThingHandlerCallback.java @@ -73,4 +73,12 @@ public interface ThingHandlerCallback { */ void migrateThingType(Thing thing, ThingTypeUID thingTypeUID, Configuration configuration); + /** + * Informs the framework that a channel has been triggered. + * + * @param channelUID UID of the channel over which has been triggered. + * @param event Event. + */ + void channelTriggered(Thing thing, ChannelUID channelUID, String event); + } diff --git a/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/binding/builder/ChannelBuilder.java b/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/binding/builder/ChannelBuilder.java index 3f2563edd68..83e4f286e08 100644 --- a/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/binding/builder/ChannelBuilder.java +++ b/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/binding/builder/ChannelBuilder.java @@ -10,10 +10,10 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; - import org.eclipse.smarthome.config.core.Configuration; import org.eclipse.smarthome.core.thing.Channel; import org.eclipse.smarthome.core.thing.ChannelUID; +import org.eclipse.smarthome.core.thing.type.ChannelKind; import org.eclipse.smarthome.core.thing.type.ChannelType; import org.eclipse.smarthome.core.thing.type.ChannelTypeUID; @@ -28,6 +28,7 @@ public class ChannelBuilder { private ChannelUID channelUID; private String acceptedItemType; + private ChannelKind kind; private Configuration configuration; private Set defaultTags; private Map properties; @@ -39,6 +40,7 @@ private ChannelBuilder(ChannelUID channelUID, String acceptedItemType, Set defaultTags) { return this; } + /** + * Sets the kind of the channel. + * + * @param kind kind. + * @return channel builder + */ + public ChannelBuilder withKind(ChannelKind kind) { + if (kind == null) { + throw new IllegalArgumentException("kind must not be null"); + } + + this.kind = kind; + return this; + } + /** * Builds and returns the channel. * * @return channel */ public Channel build() { - return new Channel(channelUID, channelTypeUID, acceptedItemType, configuration, defaultTags, properties, label, - description); + return new Channel(channelUID, channelTypeUID, acceptedItemType, kind, configuration, defaultTags, properties, + label, description); } } diff --git a/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/dto/ChannelDTO.java b/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/dto/ChannelDTO.java index d79fc98e2b9..e4846217a00 100644 --- a/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/dto/ChannelDTO.java +++ b/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/dto/ChannelDTO.java @@ -11,9 +11,9 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; - import org.eclipse.smarthome.config.core.Configuration; import org.eclipse.smarthome.core.thing.ChannelUID; +import org.eclipse.smarthome.core.thing.type.ChannelKind; /** * This is a data transfer object that is used to serialize channels. @@ -28,6 +28,7 @@ public class ChannelDTO { public String id; public String channelTypeUID; public String itemType; + public String kind; public String label; public String description; public Set defaultTags; @@ -37,8 +38,8 @@ public class ChannelDTO { public ChannelDTO() { } - public ChannelDTO(ChannelUID uid, String channelTypeUID, String itemType, String label, String description, - Map properties, Configuration configuration, Set defaultTags) { + public ChannelDTO(ChannelUID uid, String channelTypeUID, String itemType, ChannelKind kind, String label, + String description, Map properties, Configuration configuration, Set defaultTags) { this.uid = uid.toString(); this.id = uid.getId(); this.channelTypeUID = channelTypeUID; @@ -48,6 +49,7 @@ public ChannelDTO(ChannelUID uid, String channelTypeUID, String itemType, String this.properties = properties; this.configuration = toMap(configuration); this.defaultTags = new HashSet<>(defaultTags); + this.kind = kind.toString(); } private Map toMap(Configuration configuration) { diff --git a/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/dto/ChannelDTOMapper.java b/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/dto/ChannelDTOMapper.java index d3adae5c9f6..fa13b9a7f6b 100644 --- a/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/dto/ChannelDTOMapper.java +++ b/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/dto/ChannelDTOMapper.java @@ -11,6 +11,7 @@ import org.eclipse.smarthome.core.thing.Channel; import org.eclipse.smarthome.core.thing.ChannelUID; import org.eclipse.smarthome.core.thing.binding.builder.ChannelBuilder; +import org.eclipse.smarthome.core.thing.type.ChannelKind; import org.eclipse.smarthome.core.thing.type.ChannelTypeUID; /** @@ -30,7 +31,7 @@ public class ChannelDTOMapper { public static ChannelDTO map(Channel channel) { ChannelTypeUID channelTypeUID = channel.getChannelTypeUID(); String channelTypeUIDValue = channelTypeUID != null ? channelTypeUID.toString() : null; - return new ChannelDTO(channel.getUID(), channelTypeUIDValue, channel.getAcceptedItemType().toString(), + return new ChannelDTO(channel.getUID(), channelTypeUIDValue, channel.getAcceptedItemType(), channel.getKind(), channel.getLabel(), channel.getDescription(), channel.getProperties(), channel.getConfiguration(), channel.getDefaultTags()); } @@ -47,6 +48,6 @@ public static Channel map(ChannelDTO channelDTO) { return ChannelBuilder.create(channelUID, channelDTO.itemType) .withConfiguration(new Configuration(channelDTO.configuration)).withLabel(channelDTO.label) .withDescription(channelDTO.description).withProperties(channelDTO.properties).withType(channelTypeUID) - .withDefaultTags(channelDTO.defaultTags).build(); + .withDefaultTags(channelDTO.defaultTags).withKind(ChannelKind.parse(channelDTO.kind)).build(); } } diff --git a/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/dto/ChannelTypeDTO.java b/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/dto/ChannelTypeDTO.java index 4d4ceb8f368..86db7954304 100644 --- a/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/dto/ChannelTypeDTO.java +++ b/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/dto/ChannelTypeDTO.java @@ -9,9 +9,9 @@ import java.util.List; import java.util.Set; - import org.eclipse.smarthome.config.core.dto.ConfigDescriptionParameterDTO; import org.eclipse.smarthome.config.core.dto.ConfigDescriptionParameterGroupDTO; +import org.eclipse.smarthome.core.thing.type.ChannelKind; import org.eclipse.smarthome.core.types.StateDescription; /** @@ -28,6 +28,7 @@ public class ChannelTypeDTO { public String label; public String category; public String itemType; + public String kind; public StateDescription stateDescription; public Set tags; public String UID; @@ -36,8 +37,9 @@ public ChannelTypeDTO() { } public ChannelTypeDTO(String UID, String label, String description, String category, String itemType, - List parameters, List parameterGroups, - StateDescription stateDescription, Set tags) { + ChannelKind kind, List parameters, + List parameterGroups, StateDescription stateDescription, + Set tags) { this.UID = UID; this.label = label; this.description = description; @@ -46,5 +48,7 @@ public ChannelTypeDTO(String UID, String label, String description, String categ this.parameterGroups = parameterGroups; this.stateDescription = stateDescription; this.tags = tags; + this.kind = kind.toString(); + this.itemType = itemType; } } diff --git a/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/events/ChannelTriggeredEvent.java b/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/events/ChannelTriggeredEvent.java new file mode 100644 index 00000000000..9231a9e0c11 --- /dev/null +++ b/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/events/ChannelTriggeredEvent.java @@ -0,0 +1,76 @@ +/** + * Copyright (c) 2014-2016 by the respective copyright holders. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.smarthome.core.thing.events; + +import org.eclipse.smarthome.core.events.AbstractEvent; +import org.eclipse.smarthome.core.thing.ChannelUID; +import org.eclipse.smarthome.core.types.Type; + +/** + * {@link ChannelTriggeredEvent}s can be used to deliver triggers through the Eclipse SmartHome event bus. + * Trigger events must be created with the {@link ThingEventFactory}. + */ +public class ChannelTriggeredEvent extends AbstractEvent { + + /** + * The thing trigger event type. + */ + public final static String TYPE = ChannelTriggeredEvent.class.getSimpleName(); + + /** + * The channel which triggered the event. + */ + private final ChannelUID channel; + + /** + * The event. + */ + private final String event; + + /** + * Constructs a new thing trigger event. + * + * @param topic the topic. The topic includes the thing UID, see + * {@link ThingEventFactory#THING_TRIGGERED_EVENT_TOPIC} + * @param payload the payload. Contains a serialized {@link ThingEventFactory.TriggerEventPayloadBean}. + * @param source the source + * @param channel the channel which triggered the event + */ + protected ChannelTriggeredEvent(String topic, String payload, String source, String event, ChannelUID channel) { + super(topic, payload, source); + this.event = event; + this.channel = channel; + } + + /** + * Returns the event. + * + * @return the event + */ + public String getEvent() { + return event; + } + + /** + * @return the channel which triggered the event + */ + public ChannelUID getChannel() { + return channel; + } + + @Override + public String getType() { + return TYPE; + } + + @Override + public String toString() { + return channel + " triggered " + event; + } + +} diff --git a/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/events/ThingEventFactory.java b/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/events/ThingEventFactory.java index d18da32afa3..11c41b8d060 100644 --- a/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/events/ThingEventFactory.java +++ b/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/events/ThingEventFactory.java @@ -8,15 +8,15 @@ package org.eclipse.smarthome.core.thing.events; import java.util.List; - import org.eclipse.smarthome.core.events.AbstractEventFactory; import org.eclipse.smarthome.core.events.Event; +import org.eclipse.smarthome.core.thing.ChannelUID; import org.eclipse.smarthome.core.thing.Thing; import org.eclipse.smarthome.core.thing.ThingStatusInfo; import org.eclipse.smarthome.core.thing.ThingUID; import org.eclipse.smarthome.core.thing.dto.ThingDTO; import org.eclipse.smarthome.core.thing.dto.ThingDTOMapper; - +import org.eclipse.smarthome.core.types.Type; import com.google.common.base.Preconditions; import com.google.common.collect.Lists; import com.google.common.collect.Sets; @@ -28,7 +28,6 @@ * @author Dennis Nobel - Added status changed event */ public class ThingEventFactory extends AbstractEventFactory { - private static final String THING_STATUS_INFO_EVENT_TOPIC = "smarthome/things/{thingUID}/status"; private static final String THING_STATUS_INFO_CHANGED_EVENT_TOPIC = "smarthome/things/{thingUID}/statuschanged"; @@ -39,12 +38,14 @@ public class ThingEventFactory extends AbstractEventFactory { private static final String THING_UPDATED_EVENT_TOPIC = "smarthome/things/{thingUID}/updated"; + private static final String CHANNEL_TRIGGERED_EVENT_TOPIC = "smarthome/channels/{channelUID}/triggered"; + /** * Constructs a new ThingEventFactory. */ public ThingEventFactory() { super(Sets.newHashSet(ThingStatusInfoEvent.TYPE, ThingStatusInfoChangedEvent.TYPE, ThingAddedEvent.TYPE, - ThingRemovedEvent.TYPE, ThingUpdatedEvent.TYPE)); + ThingRemovedEvent.TYPE, ThingUpdatedEvent.TYPE, ChannelTriggeredEvent.TYPE)); } @Override @@ -60,14 +61,75 @@ protected Event createEventByType(String eventType, String topic, String payload event = createRemovedEvent(topic, payload); } else if (eventType.equals(ThingUpdatedEvent.TYPE)) { event = createUpdatedEvent(topic, payload); + } else if (eventType.equals(ChannelTriggeredEvent.TYPE)) { + event = createTriggerEvent(topic, payload, source); } + return event; } + /** + * This is a java bean that is used to serialize/deserialize trigger event payload. + */ + public static class TriggerEventPayloadBean { + private String event; + private String channel; + + /** + * Default constructor for deserialization e.g. by Gson. + */ + protected TriggerEventPayloadBean() { + } + + public TriggerEventPayloadBean(String event, String channel) { + this.event = event; + this.channel = channel; + } + + public String getEvent() { + return event; + } + + public String getChannel() { + return channel; + } + } + + /** + * Creates a trigger event from a {@link Type}. + * + * @param event Event. + * @param channel Channel which triggered the event. + * @return Created trigger event. + */ + public static ChannelTriggeredEvent createTriggerEvent(String event, ChannelUID channel) { + TriggerEventPayloadBean bean = new TriggerEventPayloadBean(event, channel.getAsString()); + String payload = serializePayload(bean); + String topic = buildTopic(CHANNEL_TRIGGERED_EVENT_TOPIC, channel); + return new ChannelTriggeredEvent(topic, payload, null, event, channel); + } + + /** + * Creates a trigger event from a payload. + * + * @param topic Event topic + * @param source Event source + * @param payload Payload + * @return created trigger event + */ + public ChannelTriggeredEvent createTriggerEvent(String topic, String payload, String source) { + TriggerEventPayloadBean bean = deserializePayload(payload, TriggerEventPayloadBean.class); + + ChannelUID channel = new ChannelUID(bean.getChannel()); + + return new ChannelTriggeredEvent(topic, payload, source, bean.getEvent(), channel); + } + private Event createStatusInfoEvent(String topic, String payload) throws Exception { String[] topicElements = getTopicElements(topic); - if (topicElements.length != 4) + if (topicElements.length != 4) { throw new IllegalArgumentException("ThingStatusInfoEvent creation failed, invalid topic: " + topic); + } ThingUID thingUID = new ThingUID(topicElements[2]); ThingStatusInfo thingStatusInfo = deserializePayload(payload, ThingStatusInfo.class); return new ThingStatusInfoEvent(topic, payload, thingUID, thingStatusInfo); @@ -75,8 +137,9 @@ private Event createStatusInfoEvent(String topic, String payload) throws Excepti private Event createStatusInfoChangedEvent(String topic, String payload) throws Exception { String[] topicElements = getTopicElements(topic); - if (topicElements.length != 4) + if (topicElements.length != 4) { throw new IllegalArgumentException("ThingStatusInfoChangedEvent creation failed, invalid topic: " + topic); + } ThingUID thingUID = new ThingUID(topicElements[2]); ThingStatusInfo[] thingStatusInfo = deserializePayload(payload, ThingStatusInfo[].class); return new ThingStatusInfoChangedEvent(topic, payload, thingUID, thingStatusInfo[0], thingStatusInfo[1]); @@ -94,8 +157,9 @@ private Event createRemovedEvent(String topic, String payload) throws Exception private Event createUpdatedEvent(String topic, String payload) throws Exception { ThingDTO[] thingDTO = deserializePayload(payload, ThingDTO[].class); - if (thingDTO.length != 2) + if (thingDTO.length != 2) { throw new IllegalArgumentException("ThingUpdateEvent creation failed, invalid payload: " + payload); + } return new ThingUpdatedEvent(topic, payload, thingDTO[0], thingDTO[1]); } @@ -113,7 +177,7 @@ public static ThingStatusInfoEvent createStatusInfoEvent(ThingUID thingUID, Thin Preconditions.checkArgument(thingUID != null, "The argument 'thingUID' must not be null."); Preconditions.checkArgument(thingStatusInfo != null, "The argument 'thingStatusInfo' must not be null."); - String topic = buildTopic(THING_STATUS_INFO_EVENT_TOPIC, thingUID.getAsString()); + String topic = buildTopic(THING_STATUS_INFO_EVENT_TOPIC, thingUID); String payload = serializePayload(thingStatusInfo); return new ThingStatusInfoEvent(topic, payload, thingUID, thingStatusInfo); } @@ -136,7 +200,7 @@ public static ThingStatusInfoChangedEvent createStatusInfoChangedEvent(ThingUID Preconditions.checkArgument(thingStatusInfo != null, "The argument 'thingStatusInfo' must not be null."); Preconditions.checkArgument(oldThingStatusInfo != null, "The argument 'oldThingStatusInfo' must not be null."); - String topic = buildTopic(THING_STATUS_INFO_CHANGED_EVENT_TOPIC, thingUID.getAsString()); + String topic = buildTopic(THING_STATUS_INFO_CHANGED_EVENT_TOPIC, thingUID); String payload = serializePayload(new ThingStatusInfo[] { thingStatusInfo, oldThingStatusInfo }); return new ThingStatusInfoChangedEvent(topic, payload, thingUID, thingStatusInfo, oldThingStatusInfo); } @@ -152,7 +216,7 @@ public static ThingStatusInfoChangedEvent createStatusInfoChangedEvent(ThingUID */ public static ThingAddedEvent createAddedEvent(Thing thing) { assertValidArgument(thing); - String topic = buildTopic(THING_ADDED_EVENT_TOPIC, thing.getUID().getAsString()); + String topic = buildTopic(THING_ADDED_EVENT_TOPIC, thing.getUID()); ThingDTO thingDTO = map(thing); String payload = serializePayload(thingDTO); return new ThingAddedEvent(topic, payload, thingDTO); @@ -169,7 +233,7 @@ public static ThingAddedEvent createAddedEvent(Thing thing) { */ public static ThingRemovedEvent createRemovedEvent(Thing thing) { assertValidArgument(thing); - String topic = buildTopic(THING_REMOVED_EVENT_TOPIC, thing.getUID().getAsString()); + String topic = buildTopic(THING_REMOVED_EVENT_TOPIC, thing.getUID()); ThingDTO thingDTO = map(thing); String payload = serializePayload(thingDTO); return new ThingRemovedEvent(topic, payload, thingDTO); @@ -188,7 +252,7 @@ public static ThingRemovedEvent createRemovedEvent(Thing thing) { public static ThingUpdatedEvent createUpdateEvent(Thing thing, Thing oldThing) { assertValidArgument(thing); assertValidArgument(oldThing); - String topic = buildTopic(THING_UPDATED_EVENT_TOPIC, thing.getUID().getAsString()); + String topic = buildTopic(THING_UPDATED_EVENT_TOPIC, thing.getUID()); ThingDTO thingDTO = map(thing); ThingDTO oldThingDTO = map(oldThing); List thingDTOs = Lists.newLinkedList(); @@ -203,8 +267,12 @@ private static void assertValidArgument(Thing thing) { Preconditions.checkArgument(thing.getUID() != null, "The thingUID of a thing must not be null."); } - private static String buildTopic(String topic, String thingUID) { - return topic.replace("{thingUID}", thingUID); + private static String buildTopic(String topic, ThingUID thingUID) { + return topic.replace("{thingUID}", thingUID.getAsString()); + } + + private static String buildTopic(String topic, ChannelUID channelUID) { + return topic.replace("{channelUID}", channelUID.getAsString()); } private static ThingDTO map(Thing thing) { diff --git a/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/internal/ChannelItemProvider.java b/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/internal/ChannelItemProvider.java index b3f7eb99435..16517080e8d 100644 --- a/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/internal/ChannelItemProvider.java +++ b/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/internal/ChannelItemProvider.java @@ -31,6 +31,7 @@ import org.eclipse.smarthome.core.thing.ThingRegistry; import org.eclipse.smarthome.core.thing.link.ItemChannelLink; import org.eclipse.smarthome.core.thing.link.ItemChannelLinkRegistry; +import org.eclipse.smarthome.core.thing.type.ChannelKind; import org.eclipse.smarthome.core.thing.type.ChannelType; import org.eclipse.smarthome.core.thing.type.TypeResolver; import org.slf4j.Logger; @@ -205,10 +206,13 @@ private void createItemForLink(ItemChannelLink link) { Channel channel = thingRegistry.getChannel(link.getUID()); if (channel != null) { Item item = null; - for (ItemFactory itemFactory : itemFactories) { - item = itemFactory.createItem(channel.getAcceptedItemType(), link.getItemName()); - if (item != null) { - break; + // Only create an item for state channels + if (channel.getKind() == ChannelKind.STATE) { + for (ItemFactory itemFactory : itemFactories) { + item = itemFactory.createItem(channel.getAcceptedItemType(), link.getItemName()); + if (item != null) { + break; + } } } if (item != null) { diff --git a/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/internal/ThingFactoryHelper.java b/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/internal/ThingFactoryHelper.java index dc5c894b0a3..a20c05c9f5a 100644 --- a/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/internal/ThingFactoryHelper.java +++ b/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/internal/ThingFactoryHelper.java @@ -10,7 +10,6 @@ import java.math.BigDecimal; import java.net.URI; import java.util.List; - import org.eclipse.smarthome.config.core.ConfigDescription; import org.eclipse.smarthome.config.core.ConfigDescriptionParameter; import org.eclipse.smarthome.config.core.ConfigDescriptionParameter.Type; @@ -29,7 +28,6 @@ import org.eclipse.smarthome.core.thing.type.TypeResolver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import com.google.common.collect.Lists; /** @@ -97,7 +95,7 @@ private static Channel createChannel(ChannelDefinition channelDefinition, ThingU ChannelBuilder channelBuilder = ChannelBuilder .create(new ChannelUID(thingUID, groupId, channelDefinition.getId()), type.getItemType()) - .withType(type.getUID()).withDefaultTags(type.getTags()); + .withType(type.getUID()).withDefaultTags(type.getTags()).withKind(type.getKind()); // If we want to override the label, add it... if (channelDefinition.getLabel() != null) { diff --git a/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/internal/ThingManager.java b/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/internal/ThingManager.java index 74fb8ec2609..84ba2560715 100644 --- a/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/internal/ThingManager.java +++ b/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/internal/ThingManager.java @@ -23,7 +23,6 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; - import org.eclipse.smarthome.config.core.BundleProcessor; import org.eclipse.smarthome.config.core.BundleProcessor.BundleProcessorListener; import org.eclipse.smarthome.config.core.ConfigDescription; @@ -68,7 +67,6 @@ import org.osgi.util.tracker.ServiceTracker; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import com.google.common.collect.LinkedListMultimap; import com.google.common.collect.Multimap; import com.google.common.collect.Multimaps; @@ -98,9 +96,9 @@ public class ThingManager extends AbstractItemEventSubscriber private static final String THING_MANAGER_THREADPOOL_NAME = "thingManager"; private final Multimap initializerVetoes = Multimaps - .synchronizedListMultimap(LinkedListMultimap.create()); + .synchronizedListMultimap(LinkedListMultimap. create()); private final Multimap initializerQueue = Multimaps - .synchronizedListMultimap(LinkedListMultimap.create()); + .synchronizedListMultimap(LinkedListMultimap. create()); private final class ThingHandlerTracker extends ServiceTracker { @@ -253,6 +251,11 @@ public void migrateThingType(final Thing thing, final ThingTypeUID thingTypeUID, ThingManager.this.migrateThingType(thing, thingTypeUID, configuration); } + @Override + public void channelTriggered(Thing thing, ChannelUID channelUID, String event) { + eventPublisher.post(ThingEventFactory.createTriggerEvent(event, channelUID)); + } + }; private ThingRegistryImpl thingRegistry; diff --git a/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/type/ChannelKind.java b/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/type/ChannelKind.java new file mode 100644 index 00000000000..b7f867b18c8 --- /dev/null +++ b/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/type/ChannelKind.java @@ -0,0 +1,38 @@ +package org.eclipse.smarthome.core.thing.type; + +/** + * Kind of the channel. + * + * @author Moritz Kammerer - Initial contribution and API. + */ +public enum ChannelKind { + /** + * Channels which have a stage. + */ + STATE, + /** + * Channels which can be triggered. + */ + TRIGGER; + + /** + * Parses the input string into a {@link ChannelKind}. + * + * @param input the input string + * @return the parsed ChannelKind. + * @throws IllegalArgumentException if the input couldn't be parsed. + */ + public static ChannelKind parse(String input) { + if (input == null) { + return STATE; + } + + for (ChannelKind value : values()) { + if (value.name().equalsIgnoreCase(input)) { + return value; + } + } + + throw new IllegalArgumentException("Unknown channel kind: '" + input + "'"); + } +} diff --git a/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/type/ChannelType.java b/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/type/ChannelType.java index d183a5b38c8..31855f44b9a 100644 --- a/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/type/ChannelType.java +++ b/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/type/ChannelType.java @@ -11,9 +11,9 @@ import java.util.Collections; import java.util.HashSet; import java.util.Set; - import org.eclipse.smarthome.config.core.ConfigDescription; import org.eclipse.smarthome.core.thing.Channel; +import org.eclipse.smarthome.core.types.EventDescription; import org.eclipse.smarthome.core.types.StateDescription; /** @@ -29,9 +29,11 @@ public class ChannelType extends AbstractDescriptionType { private final boolean advanced; private final String itemType; + private final ChannelKind kind; private final Set tags; private final String category; private final StateDescription state; + private final EventDescription event; private final URI configDescriptionURI; /** @@ -41,38 +43,68 @@ public class ChannelType extends AbstractDescriptionType { * the overall system (must neither be null, nor empty) * * @param advanced true if this channel type contains advanced features, otherwise false - * * @param itemType the item type of this Channel type, e.g. {@code ColorItem} (must neither be null nor empty) - * * @param label the human readable label for the according type * (must neither be null nor empty) - * * @param description the human readable description for the according type * (could be null or empty) - * * @param category the category of this Channel type, e.g. {@code TEMPERATURE} (could be null or empty) - * * @param tags all tags of this {@link ChannelType}, e.g. {@code Alarm} (could be null or empty) - * * @param state the restrictions of an item state which gives information how to interpret it * (could be null) - * * @param configDescriptionURI the link to the concrete ConfigDescription (could be null) - * * @throws IllegalArgumentException if the UID or the item type is null or empty, * or the the meta information is null */ public ChannelType(ChannelTypeUID uid, boolean advanced, String itemType, String label, String description, - String category, Set tags, StateDescription state, URI configDescriptionURI) - throws IllegalArgumentException { - - super(uid, label, description); + String category, Set tags, StateDescription state, URI configDescriptionURI) { + this(uid, advanced, itemType, ChannelKind.STATE, label, description, category, tags, state, null, + configDescriptionURI); if ((itemType == null) || (itemType.isEmpty())) { throw new IllegalArgumentException("The item type must neither be null nor empty!"); } + } + + /** + * Creates a new instance of this class with the specified parameters. + * + * @param uid the unique identifier which identifies this Channel type within + * the overall system (must neither be null, nor empty) + * @param advanced true if this channel type contains advanced features, otherwise false + * @param itemType the item type of this Channel type, e.g. {@code ColorItem} + * @param kind the channel kind. + * @param label the human readable label for the according type + * (must neither be null nor empty) + * @param description the human readable description for the according type + * (could be null or empty) + * @param category the category of this Channel type, e.g. {@code TEMPERATURE} (could be null or empty) + * @param tags all tags of this {@link ChannelType}, e.g. {@code Alarm} (could be null or empty) + * @param state the restrictions of an item state which gives information how to interpret it + * (could be null) + * @param configDescriptionURI the link to the concrete ConfigDescription (could be null) + * @throws IllegalArgumentException if the UID or the item type is null or empty, + * or the the meta information is null + */ + public ChannelType(ChannelTypeUID uid, boolean advanced, String itemType, ChannelKind kind, String label, + String description, String category, Set tags, StateDescription state, EventDescription event, + URI configDescriptionURI) throws IllegalArgumentException { + + super(uid, label, description); + + if (kind == null) { + throw new IllegalArgumentException("Kind must not be null!"); + } + + if (kind == ChannelKind.STATE && (itemType == null || itemType.isEmpty())) { + throw new IllegalArgumentException("If the kind is 'state', the item type must be set!"); + } + if (kind == ChannelKind.TRIGGER && itemType != null) { + throw new IllegalArgumentException("If the kind is 'trigger', the item type must not be set!"); + } this.itemType = itemType; + this.kind = kind; this.configDescriptionURI = configDescriptionURI; if (tags != null) { @@ -84,6 +116,7 @@ public ChannelType(ChannelTypeUID uid, boolean advanced, String itemType, String this.advanced = advanced; this.category = category; this.state = state; + this.event = event; } @Override @@ -94,12 +127,22 @@ public ChannelTypeUID getUID() { /** * Returns the item type of this {@link ChannelType}, e.g. {@code ColorItem}. * - * @return the item type of this Channel type, e.g. {@code ColorItem} (neither null nor empty) + * @return the item type of this Channel type, e.g. {@code ColorItem}. Can be null if the channel is a trigger + * channel. */ public String getItemType() { return this.itemType; } + /** + * Returns the kind of this {@link ChannelType}, e.g. {@code STATE}. + * + * @return the kind of this Channel type, e.g. {@code STATE}. + */ + public ChannelKind getKind() { + return kind; + } + /** * Returns all tags of this {@link ChannelType}, e.g. {@code Alarm}. * @@ -133,6 +176,16 @@ public StateDescription getState() { return state; } + /** + * Returns informations about the supported events. + * + * @return the event information + * (could be null) + */ + public EventDescription getEvent() { + return event; + } + /** * Returns {@code true} if this channel type contains advanced functionalities * which should be typically not shown in the basic view of user interfaces, diff --git a/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/util/ThingHelper.java b/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/util/ThingHelper.java index 33e84040711..dbef67df0c7 100644 --- a/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/util/ThingHelper.java +++ b/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/util/ThingHelper.java @@ -11,7 +11,6 @@ import java.util.Collection; import java.util.Collections; import java.util.List; - import org.eclipse.smarthome.config.core.Configuration; import org.eclipse.smarthome.core.thing.Bridge; import org.eclipse.smarthome.core.thing.Channel; @@ -24,7 +23,6 @@ import org.eclipse.smarthome.core.thing.dto.ThingDTO; import org.eclipse.smarthome.core.thing.internal.BridgeImpl; import org.eclipse.smarthome.core.thing.internal.ThingImpl; - import com.google.common.base.Joiner; import com.google.common.base.Objects; @@ -85,7 +83,7 @@ public static boolean equals(Thing a, Thing b) { private static String toString(List channels) { List strings = new ArrayList<>(channels.size()); for (Channel channel : channels) { - strings.add(channel.getUID().toString() + '#' + channel.getAcceptedItemType()); + strings.add(channel.getUID().toString() + '#' + channel.getAcceptedItemType() + '#' + channel.getKind()); } Collections.sort(strings); return Joiner.on(',').join(strings); diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/types/EventDescription.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/types/EventDescription.java new file mode 100644 index 00000000000..1e18d44cfa9 --- /dev/null +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/types/EventDescription.java @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2014-2016 by the respective copyright holders. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.smarthome.core.types; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Describes event options and gives information how to interpret it. + * + * @author Moritz Kammerer - Initial contribution and API + */ +public class EventDescription { + private final List options; + + /** + * Creates an event description object. + * + * @param options predefined list of options + */ + public EventDescription(List options) { + if (options != null) { + this.options = Collections.unmodifiableList(new ArrayList(options)); + } else { + this.options = Collections.unmodifiableList(new ArrayList(0)); + } + } + + /** + * Returns a list of predefined events with their label. + * + * @return list of predefined events with their label + */ + public List getOptions() { + return options; + } + + @Override + public String toString() { + return "EventDescription [options=" + options + "]"; + } + +} diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/types/EventOption.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/types/EventOption.java new file mode 100644 index 00000000000..11362415353 --- /dev/null +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/types/EventOption.java @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2014-2016 by the respective copyright holders. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.smarthome.core.types; + +/** + * Describes one possible value an event might have. + * + * @author Moritz Kammerer - Initial contribution and API + */ +public final class EventOption { + + private String value; + private String label; + + /** + * Creates a {@link EventOption} object. + * + * @param value + * value of the event + * @param label + * label + * @throws IllegalArgumentException + * if value is null + */ + public EventOption(String value, String label) { + if (value == null) { + throw new IllegalArgumentException("Value must not be null."); + } + this.value = value; + this.label = label; + } + + /** + * Returns the label (can be null). + * + * @return label (can be null) + */ + public String getLabel() { + return label; + } + + /** + * Returns the value (can not be null). + * + * @return value (can not be null) + */ + public String getValue() { + return value; + } + + @Override + public String toString() { + return "EventOption [value=" + value + ", label=" + label + "]"; + } +} \ No newline at end of file diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/types/TypeParser.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/types/TypeParser.java index 49ba78e0352..6432a6a0009 100644 --- a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/types/TypeParser.java +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/types/TypeParser.java @@ -17,7 +17,34 @@ * @author Kai Kreuzer - Initial contribution and API * */ -public class TypeParser { +public final class TypeParser { + + /** + * No instances allowed. + */ + private TypeParser() {} + + private static final String CORE_LIBRARY_PACKAGE = "org.eclipse.smarthome.core.library.types."; + + /** + * Parses a string into a type. + * + * @param typeName name of the type, for example StringType. + * @param input input string to parse. + * @return Parsed type or null, if the type couldn't be parsed. + */ + public static Type parseType(String typeName, String input) { + try { + Class stateClass = Class.forName(CORE_LIBRARY_PACKAGE + typeName); + Method valueOfMethod = stateClass.getMethod("valueOf", String.class); + return (Type) valueOfMethod.invoke(stateClass, input); + } catch (ClassNotFoundException e) { + } catch (NoSuchMethodException e) { + } catch (IllegalAccessException e) { + } catch (InvocationTargetException e) { + } + return null; + } /** *

diff --git a/bundles/io/org.eclipse.smarthome.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/channel/ChannelTypeResource.java b/bundles/io/org.eclipse.smarthome.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/channel/ChannelTypeResource.java index a9cefc2d4e1..c2a6ba1eb59 100644 --- a/bundles/io/org.eclipse.smarthome.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/channel/ChannelTypeResource.java +++ b/bundles/io/org.eclipse.smarthome.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/channel/ChannelTypeResource.java @@ -12,7 +12,6 @@ import java.util.List; import java.util.Locale; import java.util.Set; - import javax.ws.rs.GET; import javax.ws.rs.HeaderParam; import javax.ws.rs.Path; @@ -21,7 +20,6 @@ import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; - import org.eclipse.smarthome.config.core.ConfigDescription; import org.eclipse.smarthome.config.core.ConfigDescriptionRegistry; import org.eclipse.smarthome.config.core.dto.ConfigDescriptionDTO; @@ -34,7 +32,6 @@ import org.eclipse.smarthome.core.thing.type.ChannelTypeUID; import org.eclipse.smarthome.io.rest.LocaleUtil; import org.eclipse.smarthome.io.rest.RESTResource; - import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; @@ -75,7 +72,7 @@ protected void unsetConfigDescriptionRegistry(ConfigDescriptionRegistry configDe @GET @Produces(MediaType.APPLICATION_JSON) @ApiOperation(value = "Gets all available channel types.", response = ChannelTypeDTO.class, responseContainer = "Set") - @ApiResponses(value = @ApiResponse(code = 200, message = "OK") ) + @ApiResponses(value = @ApiResponse(code = 200, message = "OK")) public Response getAll( @HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @ApiParam(value = HttpHeaders.ACCEPT_LANGUAGE) String language) { Locale locale = LocaleUtil.getLocale(language); @@ -130,8 +127,8 @@ private ChannelTypeDTO convertToChannelTypeDTO(ChannelType channelType, Locale l } return new ChannelTypeDTO(channelType.getUID().toString(), channelType.getLabel(), channelType.getDescription(), - channelType.getCategory(), channelType.getItemType(), parameters, parameterGroups, - channelType.getState(), channelType.getTags()); + channelType.getCategory(), channelType.getItemType(), channelType.getKind(), parameters, + parameterGroups, channelType.getState(), channelType.getTags()); } private Set convertToChannelTypeDTOs(List channelTypes, Locale locale) { diff --git a/bundles/io/org.eclipse.smarthome.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/thing/EnrichedChannelDTO.java b/bundles/io/org.eclipse.smarthome.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/thing/EnrichedChannelDTO.java index c705cd7f50d..bbfce8decae 100644 --- a/bundles/io/org.eclipse.smarthome.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/thing/EnrichedChannelDTO.java +++ b/bundles/io/org.eclipse.smarthome.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/thing/EnrichedChannelDTO.java @@ -9,7 +9,6 @@ import java.util.HashSet; import java.util.Set; - import org.eclipse.smarthome.core.thing.dto.ChannelDTO; /** @@ -27,6 +26,7 @@ public EnrichedChannelDTO(ChannelDTO channelDTO, Set linkedItems) { this.id = channelDTO.id; this.channelTypeUID = channelDTO.channelTypeUID; this.itemType = channelDTO.itemType; + this.kind = channelDTO.kind; this.label = channelDTO.label; this.description = channelDTO.description; this.properties = channelDTO.properties; diff --git a/bundles/io/org.eclipse.smarthome.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/thing/ThingResource.java b/bundles/io/org.eclipse.smarthome.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/thing/ThingResource.java index 99ee283d626..b0eecdacd89 100644 --- a/bundles/io/org.eclipse.smarthome.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/thing/ThingResource.java +++ b/bundles/io/org.eclipse.smarthome.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/thing/ThingResource.java @@ -18,7 +18,6 @@ import java.util.Map.Entry; import java.util.Map; import java.util.Set; - import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.DefaultValue; @@ -36,7 +35,6 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.UriInfo; - import org.eclipse.smarthome.config.core.ConfigDescription; import org.eclipse.smarthome.config.core.ConfigDescriptionRegistry; import org.eclipse.smarthome.config.core.ConfigUtil; @@ -63,6 +61,7 @@ import org.eclipse.smarthome.core.thing.link.ItemChannelLink; import org.eclipse.smarthome.core.thing.link.ItemChannelLinkRegistry; import org.eclipse.smarthome.core.thing.link.ManagedItemChannelLinkProvider; +import org.eclipse.smarthome.core.thing.type.ChannelKind; import org.eclipse.smarthome.core.thing.type.ThingType; import org.eclipse.smarthome.core.thing.type.ThingTypeRegistry; import org.eclipse.smarthome.core.thing.util.ThingHelper; @@ -71,7 +70,6 @@ import org.eclipse.smarthome.io.rest.RESTResource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; @@ -250,6 +248,12 @@ public Response link(@PathParam("thingUID") @ApiParam(value = "thingUID") String String message = "Channel " + channelId + " for Thing " + thingUID + " does not exist!"; return JSONResponse.createResponse(Status.NOT_FOUND, null, message); } + if (channel.getKind() != ChannelKind.STATE) { + logger.info("Tried to link channel '{}' of thing '{}', which is not of kind 'state'", channel, thingUID); + String message = "Channel " + channelId + " for Thing " + thingUID + + " is not linkable, as it is not of kind 'state'!"; + return JSONResponse.createResponse(Status.FORBIDDEN, null, message); + } try { itemRegistry.getItem(itemName); diff --git a/bundles/model/org.eclipse.smarthome.model.rule.runtime/META-INF/MANIFEST.MF b/bundles/model/org.eclipse.smarthome.model.rule.runtime/META-INF/MANIFEST.MF index 249f5aca775..e349c500b4a 100644 --- a/bundles/model/org.eclipse.smarthome.model.rule.runtime/META-INF/MANIFEST.MF +++ b/bundles/model/org.eclipse.smarthome.model.rule.runtime/META-INF/MANIFEST.MF @@ -10,6 +10,8 @@ Import-Package: org.eclipse.smarthome.core.common.registry, org.eclipse.smarthome.core.events, org.eclipse.smarthome.core.items, org.eclipse.smarthome.core.items.events, + org.eclipse.smarthome.core.thing, + org.eclipse.smarthome.core.thing.events, org.eclipse.smarthome.core.types, org.eclipse.smarthome.model.core, org.eclipse.smarthome.model.script, diff --git a/bundles/model/org.eclipse.smarthome.model.rule.runtime/src/org/eclipse/smarthome/model/rule/runtime/internal/engine/RuleEngineImpl.java b/bundles/model/org.eclipse.smarthome.model.rule.runtime/src/org/eclipse/smarthome/model/rule/runtime/internal/engine/RuleEngineImpl.java index 523e8212634..25354961cfb 100644 --- a/bundles/model/org.eclipse.smarthome.model.rule.runtime/src/org/eclipse/smarthome/model/rule/runtime/internal/engine/RuleEngineImpl.java +++ b/bundles/model/org.eclipse.smarthome.model.rule.runtime/src/org/eclipse/smarthome/model/rule/runtime/internal/engine/RuleEngineImpl.java @@ -8,24 +8,27 @@ package org.eclipse.smarthome.model.rule.runtime.internal.engine; import static org.eclipse.smarthome.model.rule.runtime.internal.engine.RuleTriggerManager.TriggerTypes.*; - import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Set; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; - import org.eclipse.emf.ecore.EObject; +import org.eclipse.smarthome.core.events.Event; +import org.eclipse.smarthome.core.events.EventFilter; +import org.eclipse.smarthome.core.events.EventSubscriber; import org.eclipse.smarthome.core.items.GenericItem; import org.eclipse.smarthome.core.items.Item; import org.eclipse.smarthome.core.items.ItemNotFoundException; import org.eclipse.smarthome.core.items.ItemRegistry; import org.eclipse.smarthome.core.items.ItemRegistryChangeListener; import org.eclipse.smarthome.core.items.StateChangeListener; -import org.eclipse.smarthome.core.items.events.AbstractItemEventSubscriber; import org.eclipse.smarthome.core.items.events.ItemCommandEvent; +import org.eclipse.smarthome.core.items.events.ItemStateEvent; +import org.eclipse.smarthome.core.thing.events.ChannelTriggeredEvent; import org.eclipse.smarthome.core.types.Command; import org.eclipse.smarthome.core.types.State; import org.eclipse.smarthome.model.core.ModelRepository; @@ -42,7 +45,7 @@ import org.eclipse.xtext.naming.QualifiedName; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.inject.Injector; @@ -56,8 +59,8 @@ * */ @SuppressWarnings("restriction") -public class RuleEngineImpl extends AbstractItemEventSubscriber - implements ItemRegistryChangeListener, StateChangeListener, ModelRepositoryChangeListener, RuleEngine { +public class RuleEngineImpl implements ItemRegistryChangeListener, StateChangeListener, ModelRepositoryChangeListener, + RuleEngine, EventSubscriber { private final Logger logger = LoggerFactory.getLogger(RuleEngineImpl.class); @@ -204,8 +207,7 @@ public void stateUpdated(Item item, State state) { } } - @Override - protected void receiveCommand(ItemCommandEvent commandEvent) { + private void receiveCommand(ItemCommandEvent commandEvent) { if (!starting && triggerManager != null && itemRegistry != null) { String itemName = commandEvent.getItemName(); Command command = commandEvent.getItemCommand(); @@ -220,6 +222,14 @@ protected void receiveCommand(ItemCommandEvent commandEvent) { } } + private void receiveThingTrigger(ChannelTriggeredEvent event) { + String triggerEvent = event.getEvent(); + String channel = event.getChannel().getAsString(); + + Iterable rules = triggerManager.getRules(TRIGGER, channel, triggerEvent); + executeRules(rules, event); + } + private void internalItemAdded(Item item) { if (item instanceof GenericItem) { GenericItem genericItem = (GenericItem) item; @@ -307,6 +317,14 @@ protected synchronized void executeRules(Iterable rules) { } } + protected synchronized void executeRules(Iterable rules, ChannelTriggeredEvent event) { + for (Rule rule : rules) { + RuleEvaluationContext context = new RuleEvaluationContext(); + context.newValue(QualifiedName.create(RulesJvmModelInferrer.VAR_RECEIVED_EVENT), event); + executeRule(rule, context); + } + } + protected synchronized void executeRules(Iterable rules, Command command) { for (Rule rule : rules) { RuleEvaluationContext context = new RuleEvaluationContext(); @@ -338,4 +356,26 @@ public void updated(Item oldItem, Item item) { removed(oldItem); added(item); } + + private final Set subscribedEventTypes = ImmutableSet.of(ItemStateEvent.TYPE, ItemCommandEvent.TYPE, + ChannelTriggeredEvent.TYPE); + + @Override + public Set getSubscribedEventTypes() { + return subscribedEventTypes; + } + + @Override + public EventFilter getEventFilter() { + return null; + } + + @Override + public void receive(Event event) { + if (event instanceof ItemCommandEvent) { + receiveCommand((ItemCommandEvent) event); + } else if (event instanceof ChannelTriggeredEvent) { + receiveThingTrigger((ChannelTriggeredEvent) event); + } + } } diff --git a/bundles/model/org.eclipse.smarthome.model.rule.runtime/src/org/eclipse/smarthome/model/rule/runtime/internal/engine/RuleTriggerManager.java b/bundles/model/org.eclipse.smarthome.model.rule.runtime/src/org/eclipse/smarthome/model/rule/runtime/internal/engine/RuleTriggerManager.java index 7722dc4af20..03fcf31547f 100644 --- a/bundles/model/org.eclipse.smarthome.model.rule.runtime/src/org/eclipse/smarthome/model/rule/runtime/internal/engine/RuleTriggerManager.java +++ b/bundles/model/org.eclipse.smarthome.model.rule.runtime/src/org/eclipse/smarthome/model/rule/runtime/internal/engine/RuleTriggerManager.java @@ -19,7 +19,6 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; - import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.smarthome.core.items.Item; import org.eclipse.smarthome.core.types.Command; @@ -28,6 +27,7 @@ import org.eclipse.smarthome.core.types.TypeParser; import org.eclipse.smarthome.model.rule.rules.ChangedEventTrigger; import org.eclipse.smarthome.model.rule.rules.CommandEventTrigger; +import org.eclipse.smarthome.model.rule.rules.EventEmittedTrigger; import org.eclipse.smarthome.model.rule.rules.EventTrigger; import org.eclipse.smarthome.model.rule.rules.Rule; import org.eclipse.smarthome.model.rule.rules.RuleModel; @@ -46,7 +46,6 @@ import org.quartz.impl.matchers.GroupMatcher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; @@ -70,6 +69,7 @@ public enum TriggerTypes { UPDATE, // fires whenever a status update is received for an item CHANGE, // same as UPDATE, but only fires if the current item state is changed by the update COMMAND, // fires whenever a command is received for an item + TRIGGER, // fires whenever a trigger is emitted on a channel STARTUP, // fires when the rule engine bundle starts and once as soon as all required items are available SHUTDOWN, // fires when the rule engine bundle is stopped TIMER // fires at a given time @@ -79,6 +79,8 @@ public enum TriggerTypes { private Map> updateEventTriggeredRules = Maps.newHashMap(); private Map> changedEventTriggeredRules = Maps.newHashMap(); private Map> commandEventTriggeredRules = Maps.newHashMap(); + // Maps from channelName -> Rules + private Map> triggerEventTriggeredRules = Maps.newHashMap(); private Set systemStartupTriggeredRules = new CopyOnWriteArraySet<>(); private Set systemShutdownTriggeredRules = new CopyOnWriteArraySet<>(); private Set timerEventTriggeredRules = new CopyOnWriteArraySet<>(); @@ -123,6 +125,9 @@ public Iterable getRules(TriggerTypes type) { case COMMAND: result = Iterables.concat(commandEventTriggeredRules.values()); break; + case TRIGGER: + result = Iterables.concat(triggerEventTriggeredRules.values()); + break; default: result = Sets.newHashSet(); } @@ -174,6 +179,43 @@ public Iterable getRules(TriggerTypes triggerType, Item item, Command comm return internalGetRules(triggerType, item, null, command); } + /** + * Returns all rules for which the trigger condition is true for the given type and channel. + * + * @param triggerType + * @param channel + * @return all rules for which the trigger condition is true + */ + public Iterable getRules(TriggerTypes triggerType, String channel, String event) { + List result = Lists.newArrayList(); + + switch (triggerType) { + case TRIGGER: + Set rules = triggerEventTriggeredRules.get(channel); + if (rules == null) { + return Sets.newHashSet(); + } + for (Rule rule : rules) { + for (EventTrigger t : rule.getEventtrigger()) { + if (t instanceof EventEmittedTrigger) { + EventEmittedTrigger et = (EventEmittedTrigger) t; + if (et.getTrigger() != null) { + if (!et.getTrigger().equals(event)) { + continue; + } + } + result.add(rule); + } + } + } + break; + default: + return Sets.newHashSet(); + } + + return result; + } + private Iterable getAllRules(TriggerTypes type, String itemName) { switch (type) { case STARTUP: @@ -304,6 +346,9 @@ public void clear(TriggerTypes type) { case COMMAND: commandEventTriggeredRules.clear(); break; + case TRIGGER: + triggerEventTriggeredRules.clear(); + break; case TIMER: for (Rule rule : timerEventTriggeredRules) { removeTimerRule(rule); @@ -323,6 +368,7 @@ public void clearAll() { clear(CHANGE); clear(COMMAND); clear(TIMER); + clear(TRIGGER); } /** @@ -369,6 +415,14 @@ public synchronized void addRule(Rule rule) { logger.error("Cannot create timer for rule '{}': {}", rule.getName(), e.getMessage()); } } + } else if (t instanceof EventEmittedTrigger) { + EventEmittedTrigger eeTrigger = (EventEmittedTrigger) t; + Set rules = triggerEventTriggeredRules.get(eeTrigger.getChannel()); + if (rules == null) { + rules = new HashSet(); + triggerEventTriggeredRules.put(eeTrigger.getChannel(), rules); + } + rules.add(rule); } } } @@ -396,6 +450,9 @@ public void removeRule(TriggerTypes type, Rule rule) { case COMMAND: commandEventTriggeredRules.remove(rule); break; + case TRIGGER: + triggerEventTriggeredRules.remove(rule); + break; case TIMER: timerEventTriggeredRules.remove(rule); removeTimerRule(rule); @@ -423,6 +480,7 @@ public void removeRuleModel(RuleModel ruleModel) { removeRules(UPDATE, updateEventTriggeredRules.values(), ruleModel); removeRules(CHANGE, changedEventTriggeredRules.values(), ruleModel); removeRules(COMMAND, commandEventTriggeredRules.values(), ruleModel); + removeRules(TRIGGER, triggerEventTriggeredRules.values(), ruleModel); removeRules(STARTUP, Collections.singletonList(systemStartupTriggeredRules), ruleModel); removeRules(SHUTDOWN, Collections.singletonList(systemShutdownTriggeredRules), ruleModel); removeRules(TIMER, Collections.singletonList(timerEventTriggeredRules), ruleModel); diff --git a/bundles/model/org.eclipse.smarthome.model.rule/META-INF/MANIFEST.MF b/bundles/model/org.eclipse.smarthome.model.rule/META-INF/MANIFEST.MF index af2e18f1ba3..4c0764d381c 100644 --- a/bundles/model/org.eclipse.smarthome.model.rule/META-INF/MANIFEST.MF +++ b/bundles/model/org.eclipse.smarthome.model.rule/META-INF/MANIFEST.MF @@ -33,6 +33,7 @@ Import-Package: com.google.common.collect, org.eclipse.smarthome.model.core, org.eclipse.smarthome.model.items, org.eclipse.smarthome.model.persistence.extensions, + org.eclipse.smarthome.core.thing.events, org.eclipse.smarthome.model.script, org.eclipse.smarthome.model.script.engine.action, org.eclipse.xtext.xbase.lib, diff --git a/bundles/model/org.eclipse.smarthome.model.rule/src/org/eclipse/smarthome/model/rule/Rules.xtext b/bundles/model/org.eclipse.smarthome.model.rule/src/org/eclipse/smarthome/model/rule/Rules.xtext index 284fee08880..51542d55df8 100644 --- a/bundles/model/org.eclipse.smarthome.model.rule/src/org/eclipse/smarthome/model/rule/Rules.xtext +++ b/bundles/model/org.eclipse.smarthome.model.rule/src/org/eclipse/smarthome/model/rule/Rules.xtext @@ -30,6 +30,7 @@ EventTrigger: UpdateEventTrigger | CommandEventTrigger | ChangedEventTrigger | + EventEmittedTrigger | TimerTrigger | SystemTrigger ; @@ -46,6 +47,11 @@ ChangedEventTrigger: 'Item' item=ItemName 'changed' ('from' oldState=ValidState)? ('to' newState=ValidState)? ; +EventEmittedTrigger: + 'Channel' channel=(STRING|ID) 'triggered' (trigger=ValidTrigger)? +; + + TimerTrigger: 'Time' 'cron' cron=STRING | 'Time' 'is' time=('midnight' | 'noon') @@ -78,3 +84,7 @@ ValidState: ValidCommand: ID | Number | STRING ; + +ValidTrigger: + ID | Number | STRING +; \ No newline at end of file diff --git a/bundles/model/org.eclipse.smarthome.model.rule/src/org/eclipse/smarthome/model/rule/jvmmodel/RulesJvmModelInferrer.xtend b/bundles/model/org.eclipse.smarthome.model.rule/src/org/eclipse/smarthome/model/rule/jvmmodel/RulesJvmModelInferrer.xtend index bf7d4ad47ee..4e3e5a0f122 100644 --- a/bundles/model/org.eclipse.smarthome.model.rule/src/org/eclipse/smarthome/model/rule/jvmmodel/RulesJvmModelInferrer.xtend +++ b/bundles/model/org.eclipse.smarthome.model.rule/src/org/eclipse/smarthome/model/rule/jvmmodel/RulesJvmModelInferrer.xtend @@ -17,10 +17,12 @@ import org.eclipse.xtext.xbase.jvmmodel.IJvmDeclaredTypeAcceptor import org.eclipse.xtext.xbase.jvmmodel.JvmTypesBuilder import org.slf4j.Logger import org.slf4j.LoggerFactory +import org.eclipse.smarthome.model.rule.rules.EventEmittedTrigger +import org.eclipse.smarthome.core.thing.events.ChannelTriggeredEvent /** *

Infers a JVM model from the source model.

- * + * *

The JVM model should contain all elements that would appear in the Java code * which is generated from the source model. Other models link against the JVM model rather than the source model.

* @@ -29,112 +31,121 @@ import org.slf4j.LoggerFactory */ class RulesJvmModelInferrer extends ScriptJvmModelInferrer { + private final Logger logger = LoggerFactory.getLogger(RulesJvmModelInferrer) + + /** Variable name for the previous state of an item in a "changed state triggered" rule */ + public static final String VAR_PREVIOUS_STATE = "previousState"; + /** Variable name for the received command in a "command triggered" rule */ + public static final String VAR_RECEIVED_COMMAND = "receivedCommand"; - static private final Logger logger = LoggerFactory.getLogger(RulesJvmModelInferrer) - - /** Variable name for the previous state of an item in a "changed state triggered" rule */ - public static final String VAR_PREVIOUS_STATE = "previousState"; + /** Variable name for the received event in a "trigger event" rule */ + public static final String VAR_RECEIVED_EVENT = "receivedEvent"; - /** Variable name for the received command in a "command triggered" rule */ - public static final String VAR_RECEIVED_COMMAND = "receivedCommand"; - /** * conveninence API to build and initialize JvmTypes and their members. */ - @Inject extension JvmTypesBuilder - @Inject extension IQualifiedNameProvider - - @Inject - IItemRegistryProvider itemRegistryProvider - - @Inject - StateAndCommandProvider stateAndCommandProvider - - /** - * Is called for each instance of the first argument's type contained in a resource. - * - * @param element - the model to create one or more JvmDeclaredTypes from. - * @param acceptor - each created JvmDeclaredType without a container should be passed to the acceptor in order get attached to the - * current resource. - * @param isPreLinkingPhase - whether the method is called in a pre linking phase, i.e. when the global index isn't fully updated. You - * must not rely on linking using the index if iPrelinkingPhase is true - */ - - - def dispatch void infer(RuleModel ruleModel, IJvmDeclaredTypeAcceptor acceptor, boolean isPreIndexingPhase) { - val className = ruleModel.eResource.URI.lastSegment.split("\\.").head.toFirstUpper + "Rules" - acceptor.accept(ruleModel.toClass(className)).initializeLater [ - members += ruleModel.variables.map[ - toField(name, type?.cloneWithProxies) => [ field | - field.static = true - field.final = !writeable - field.initializer = right - ] - ] - - val Set fieldNames = newHashSet() - - val types = stateAndCommandProvider.allTypes - types.forEach [ type | - val name = type.toString - if (fieldNames.add(name)) { - members += ruleModel.toField(name, ruleModel.newTypeRef(type.class)) [ - static = true - ] - } else { - logger.warn("Duplicate field: '{}'. Ignoring '{}'.", name, type.class.name) - } - ] - - val itemRegistry = itemRegistryProvider.get - itemRegistry?.items?.forEach[ item | - val name = item.name - if (fieldNames.add(name)) { - members += ruleModel.toField(item.name, ruleModel.newTypeRef(item.class)) [ - static = true - ] - } else { - logger.warn("Duplicate field: '{}'. Ignoring '{}'.", item.name, item.class.name) - } - ] - - members += ruleModel.rules.map[ rule | - rule.toMethod("_" + rule.name, ruleModel.newTypeRef(Void.TYPE)) [ - static = true - if(containsCommandTrigger(rule)) { - val commandTypeRef = ruleModel.newTypeRef(Command) - parameters += rule.toParameter(VAR_RECEIVED_COMMAND, commandTypeRef) - } - if(containsStateChangeTrigger(rule)) { - val stateTypeRef = ruleModel.newTypeRef(State) - parameters += rule.toParameter(VAR_PREVIOUS_STATE, stateTypeRef) - } - - body = rule.script - ] - ] - ] - } - - - def private boolean containsCommandTrigger(Rule rule) { - for(EventTrigger trigger : rule.getEventtrigger()) { - if(trigger instanceof CommandEventTrigger) { - return true; - } - } - return false; - } - - def private boolean containsStateChangeTrigger(Rule rule) { - for(EventTrigger trigger : rule.getEventtrigger()) { - if(trigger instanceof ChangedEventTrigger) { - return true; - } - } - return false; - } + @Inject extension JvmTypesBuilder + @Inject extension IQualifiedNameProvider + + @Inject + IItemRegistryProvider itemRegistryProvider + + @Inject + StateAndCommandProvider stateAndCommandProvider + + /** + * Is called for each instance of the first argument's type contained in a resource. + * + * @param element - the model to create one or more JvmDeclaredTypes from. + * @param acceptor - each created JvmDeclaredType without a container should be passed to the acceptor in order get attached to the + * current resource. + * @param isPreLinkingPhase - whether the method is called in a pre linking phase, i.e. when the global index isn't fully updated. You + * must not rely on linking using the index if iPrelinkingPhase is true + */ + def dispatch void infer(RuleModel ruleModel, IJvmDeclaredTypeAcceptor acceptor, boolean isPreIndexingPhase) { + val className = ruleModel.eResource.URI.lastSegment.split("\\.").head.toFirstUpper + "Rules" + acceptor.accept(ruleModel.toClass(className)).initializeLater [ + members += ruleModel.variables.map [ + toField(name, type?.cloneWithProxies) => [ field | + field.static = true + field.final = !writeable + field.initializer = right + ] + ] + + val Set fieldNames = newHashSet() + + val types = stateAndCommandProvider.allTypes + types.forEach [ type | + val name = type.toString + if (fieldNames.add(name)) { + members += ruleModel.toField(name, ruleModel.newTypeRef(type.class)) [ + static = true + ] + } else { + logger.warn("Duplicate field: '{}'. Ignoring '{}'.", name, type.class.name) + } + ] + + val itemRegistry = itemRegistryProvider.get + itemRegistry?.items?.forEach [ item | + val name = item.name + if (fieldNames.add(name)) { + members += ruleModel.toField(item.name, ruleModel.newTypeRef(item.class)) [ + static = true + ] + } else { + logger.warn("Duplicate field: '{}'. Ignoring '{}'.", item.name, item.class.name) + } + ] + + members += ruleModel.rules.map [ rule | + rule.toMethod("_" + rule.name, ruleModel.newTypeRef(Void.TYPE)) [ + static = true + if (containsCommandTrigger(rule)) { + val commandTypeRef = ruleModel.newTypeRef(Command) + parameters += rule.toParameter(VAR_RECEIVED_COMMAND, commandTypeRef) + } + if (containsStateChangeTrigger(rule)) { + val stateTypeRef = ruleModel.newTypeRef(State) + parameters += rule.toParameter(VAR_PREVIOUS_STATE, stateTypeRef) + } + if (containsEventTrigger(rule)) { + val eventTypeRef = ruleModel.newTypeRef(ChannelTriggeredEvent) + parameters += rule.toParameter(VAR_RECEIVED_EVENT, eventTypeRef) + } + + body = rule.script + ] + ] + ] + } + + def private boolean containsCommandTrigger(Rule rule) { + for (EventTrigger trigger : rule.getEventtrigger()) { + if (trigger instanceof CommandEventTrigger) { + return true; + } + } + return false; + } + def private boolean containsStateChangeTrigger(Rule rule) { + for (EventTrigger trigger : rule.getEventtrigger()) { + if (trigger instanceof ChangedEventTrigger) { + return true; + } + } + return false; + } + def private boolean containsEventTrigger(Rule rule) { + for (EventTrigger trigger : rule.getEventtrigger()) { + if (trigger instanceof EventEmittedTrigger) { + return true; + } + } + return false; + } } diff --git a/bundles/model/org.eclipse.smarthome.model.thing/src/org/eclipse/smarthome/model/thing/Thing.xtext b/bundles/model/org.eclipse.smarthome.model.thing/src/org/eclipse/smarthome/model/thing/Thing.xtext index 1792de7619d..745f88d972f 100644 --- a/bundles/model/org.eclipse.smarthome.model.thing/src/org/eclipse/smarthome/model/thing/Thing.xtext +++ b/bundles/model/org.eclipse.smarthome.model.thing/src/org/eclipse/smarthome/model/thing/Thing.xtext @@ -49,7 +49,7 @@ ModelThing: ; ModelChannel: - type=ModelItemType ':' id=UID_SEGMENT + (channelType= ('State' | 'Trigger'))? type=ModelItemType ':' id=UID_SEGMENT ('[' properties+=ModelProperty (',' properties+=ModelProperty)* ']')? diff --git a/bundles/model/org.eclipse.smarthome.model.thing/src/org/eclipse/smarthome/model/thing/internal/GenericThingProvider.xtend b/bundles/model/org.eclipse.smarthome.model.thing/src/org/eclipse/smarthome/model/thing/internal/GenericThingProvider.xtend index 8605ef773e6..59f4e99ae1d 100644 --- a/bundles/model/org.eclipse.smarthome.model.thing/src/org/eclipse/smarthome/model/thing/internal/GenericThingProvider.xtend +++ b/bundles/model/org.eclipse.smarthome.model.thing/src/org/eclipse/smarthome/model/thing/internal/GenericThingProvider.xtend @@ -43,6 +43,7 @@ import java.util.ArrayList import org.eclipse.smarthome.core.thing.type.TypeResolver import java.util.Locale import org.eclipse.smarthome.core.i18n.LocaleProvider +import org.eclipse.smarthome.core.thing.type.ChannelKind /** * {@link ThingProvider} implementation which computes *.things files. @@ -274,9 +275,12 @@ class GenericThingProvider extends AbstractProvider implements ThingProvi val List channels = newArrayList modelChannels.forEach [ if (addedChannelIds.add(id)) { - channels += - ChannelBuilder.create(new ChannelUID(thingTypeUID, thingUID, id), type).withConfiguration( - createConfiguration).build + val kind = if (it.channelType == null) "State" else it.channelType + val parsedKind = ChannelKind.parse(kind) + val channel = ChannelBuilder.create(new ChannelUID(thingTypeUID, thingUID, id), type) + .withKind(parsedKind) + .withConfiguration(createConfiguration) + channels += channel.build() } ] channelDefinitions.forEach [ diff --git a/targetplatform/SmartHome Runtime.launch b/targetplatform/SmartHome Runtime.launch new file mode 100644 index 00000000000..e69de29bb2d