Skip to content
Snippets Groups Projects
Commit f510b1b3 authored by René Schöne's avatar René Schöne
Browse files

Merge branch 'dev' into 'master'

Updating master with current development state.

Closes #20, #14, #18, and #15

See merge request !4
parents a465e8c8 310ab641
No related branches found
No related tags found
No related merge requests found
Showing
with 1431 additions and 345 deletions
...@@ -8,9 +8,9 @@ import java.util.Objects; ...@@ -8,9 +8,9 @@ import java.util.Objects;
* @author rschoene - Initial contribution * @author rschoene - Initial contribution
*/ */
public class TupleHSB implements Cloneable { public class TupleHSB implements Cloneable {
public int hue; private int hue;
public int saturation; private int saturation;
public int brightness; private int brightness;
public static TupleHSB of(int hue, int saturation, int brightness) { public static TupleHSB of(int hue, int saturation, int brightness) {
TupleHSB result = new TupleHSB(); TupleHSB result = new TupleHSB();
result.hue = hue; result.hue = hue;
...@@ -19,6 +19,30 @@ public class TupleHSB implements Cloneable { ...@@ -19,6 +19,30 @@ public class TupleHSB implements Cloneable {
return result; return result;
} }
public int getHue() {
return hue;
}
public int getSaturation() {
return saturation;
}
public int getBrightness() {
return brightness;
}
public TupleHSB withDifferentHue(int hue) {
return TupleHSB.of(hue, this.saturation, this.brightness);
}
public TupleHSB withDifferentSaturation(int saturation) {
return TupleHSB.of(this.hue, saturation, this.brightness);
}
public TupleHSB withDifferentBrightness(int brightness) {
return TupleHSB.of(this.hue, this.saturation, brightness);
}
public String toString() { public String toString() {
return String.format("%s,%s,%s", hue, saturation, brightness); return String.format("%s,%s,%s", hue, saturation, brightness);
} }
......
...@@ -26,6 +26,7 @@ import java.util.stream.Collectors; ...@@ -26,6 +26,7 @@ import java.util.stream.Collectors;
public class OpenHab2Importer { public class OpenHab2Importer {
protected static final String thingTypesUrl = "http://%s/rest/thing-types"; protected static final String thingTypesUrl = "http://%s/rest/thing-types";
protected static final String thingTypeDetailUrl = "http://%s/rest/thing-types/%s";
protected static final String channelTypeUrl = "http://%s/rest/channel-types"; protected static final String channelTypeUrl = "http://%s/rest/channel-types";
protected static final String thingsUrl = "http://%s/rest/things"; protected static final String thingsUrl = "http://%s/rest/things";
protected static final String itemsUrl = "http://%s/rest/items"; protected static final String itemsUrl = "http://%s/rest/items";
...@@ -41,7 +42,7 @@ public class OpenHab2Importer { ...@@ -41,7 +42,7 @@ public class OpenHab2Importer {
nonDefaultChannelCategories = new HashSet<>(); nonDefaultChannelCategories = new HashSet<>();
} }
public Root importFrom(String host, int port) { public OpenHAB2Model importFrom(String host, int port) {
/* /*
Plan: Plan:
- requesting: thing-types, channel-types, things, items, links - requesting: thing-types, channel-types, things, items, links
...@@ -57,7 +58,8 @@ public class OpenHab2Importer { ...@@ -57,7 +58,8 @@ public class OpenHab2Importer {
mapper.registerModule(module); mapper.registerModule(module);
try { try {
Root model = Root.createEmptyRoot(); Root root = Root.createEmptyRoot();
OpenHAB2Model model = root.getOpenHAB2Model();
ThingTypeData[] thingTypeList = mapper.readValue(makeURL(thingTypesUrl, hostAndPort), ThingTypeData[].class); ThingTypeData[] thingTypeList = mapper.readValue(makeURL(thingTypesUrl, hostAndPort), ThingTypeData[].class);
logger.info("Read a total of {} thing type(s).", thingTypeList.length); logger.info("Read a total of {} thing type(s).", thingTypeList.length);
update(model, thingTypeList); update(model, thingTypeList);
...@@ -78,19 +80,14 @@ public class OpenHab2Importer { ...@@ -78,19 +80,14 @@ public class OpenHab2Importer {
logger.info("Read a total of {} link(s).", linkList.length); logger.info("Read a total of {} link(s).", linkList.length);
update(model, linkList); update(model, linkList);
// create empty MQTT root // get parameters of thing types
MqttRoot mqttRoot = new MqttRoot(); for (ThingType thingType : model.getThingTypeList()) {
mqttRoot.setHostByName(host); ThingTypeData data = mapper.readValue(makeURL(thingTypeDetailUrl, hostAndPort, thingType.getID()), ThingTypeData.class);
model.setMqttRoot(mqttRoot); update(thingType, data);
}
// create empty Influx root
InfluxRoot influxRoot = InfluxRoot.createDefault();
influxRoot.setHostByName(host);
model.setInfluxRoot(influxRoot);
return model; return model;
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); logger.catching(e);
return null; return null;
} }
} }
...@@ -99,7 +96,11 @@ public class OpenHab2Importer { ...@@ -99,7 +96,11 @@ public class OpenHab2Importer {
return URI.create(String.format(formatUrlString, hostAndPort)).toURL(); return URI.create(String.format(formatUrlString, hostAndPort)).toURL();
} }
private void update(Root model, ThingTypeData[] thingTypeList) { protected URL makeURL(String formatUrlString, String hostAndPort, String id) throws MalformedURLException {
return URI.create(String.format(formatUrlString, hostAndPort, id)).toURL();
}
private void update(OpenHAB2Model model, ThingTypeData[] thingTypeList) {
for (ThingTypeData thingTypeData : thingTypeList) { for (ThingTypeData thingTypeData : thingTypeList) {
ThingType thingType = new ThingType(); ThingType thingType = new ThingType();
thingType.setID(thingTypeData.UID); thingType.setID(thingTypeData.UID);
...@@ -109,7 +110,7 @@ public class OpenHab2Importer { ...@@ -109,7 +110,7 @@ public class OpenHab2Importer {
} }
} }
private void update(Root model, ChannelTypeData[] channelTypeList) { private void update(OpenHAB2Model model, ChannelTypeData[] channelTypeList) {
for (ChannelTypeData channelTypeData : channelTypeList) { for (ChannelTypeData channelTypeData : channelTypeList) {
ChannelType channelType = new ChannelType(); ChannelType channelType = new ChannelType();
channelType.setID(channelTypeData.UID); channelType.setID(channelTypeData.UID);
...@@ -162,7 +163,7 @@ public class OpenHab2Importer { ...@@ -162,7 +163,7 @@ public class OpenHab2Importer {
} }
} }
private void update(Root model, ThingData[] thingList) { private void update(OpenHAB2Model model, ThingData[] thingList) {
for (ThingData thingData : thingList) { for (ThingData thingData : thingList) {
Thing thing = new Thing(); Thing thing = new Thing();
thing.setID(thingData.UID); thing.setID(thingData.UID);
...@@ -181,7 +182,7 @@ public class OpenHab2Importer { ...@@ -181,7 +182,7 @@ public class OpenHab2Importer {
} }
} }
private void update(Root model, AbstractItemData[] itemList) { private void update(OpenHAB2Model model, AbstractItemData[] itemList) {
List<Tuple<Group, GroupItemData>> groupsWithMembers = new ArrayList<>(); List<Tuple<Group, GroupItemData>> groupsWithMembers = new ArrayList<>();
List<Tuple<Group, GroupItemData>> groupsInGroups = new ArrayList<>(); List<Tuple<Group, GroupItemData>> groupsInGroups = new ArrayList<>();
List<Tuple<Item, AbstractItemData>> itemsInGroups = new ArrayList<>(); List<Tuple<Item, AbstractItemData>> itemsInGroups = new ArrayList<>();
...@@ -234,11 +235,21 @@ public class OpenHab2Importer { ...@@ -234,11 +235,21 @@ public class OpenHab2Importer {
})); }));
} }
// if state is not set in openHAB, then use default state for the item // if state is not set in openHAB, then use default state for the item
item.disableSendState();
if (itemData.state.equals(NULL_STATE)) { if (itemData.state.equals(NULL_STATE)) {
item.setStateToDefault(); item.setStateToDefault();
} else { } else {
item.setStateFromString(itemData.state, false); item.setStateFromString(itemData.state, false);
} }
item.enableSendState();
if (itemData.metadata != null) {
for (Map.Entry<String, MetaDataData> entry : itemData.metadata.entrySet()) {
logger.debug("Add metadata for namespace {}", entry.getKey());
for (Map.Entry<String, String> metaDataEntry : entry.getValue().config.entrySet()) {
item.addMetaData(new ItemMetaData(metaDataEntry.getKey(), metaDataEntry.getValue()));
}
}
}
if (itemData.groupNames.size() > 0) { if (itemData.groupNames.size() > 0) {
itemsInGroups.add(Tuple.of(item, itemData)); itemsInGroups.add(Tuple.of(item, itemData));
} else { } else {
...@@ -288,15 +299,38 @@ public class OpenHab2Importer { ...@@ -288,15 +299,38 @@ public class OpenHab2Importer {
} }
} }
private void update(Root model, LinkData[] linkList) { private void update(OpenHAB2Model model, LinkData[] linkList) {
for (LinkData linkData : linkList) { for (LinkData linkData : linkList) {
Link link = new Link();
ifPresent(model.resolveChannel(linkData.channelUID), "Channel", linkData, ifPresent(model.resolveChannel(linkData.channelUID), "Channel", linkData,
channel -> channel.addLink(link)); channel -> ifPresent(model.resolveItem(linkData.itemName), "Item", linkData, channel::addLinkedItem));
ifPresent(model.resolveItem(linkData.itemName), "Item", linkData, link::setItem);
} }
} }
private void update(ThingType thingType, ThingTypeData data) {
// just set parameters
if (data == null || data.configParameters == null) {
logger.warn("No parameters given for thing type {}", thingType.getID());
return;
}
for (ParameterData parameterData : data.configParameters) {
Parameter parameter = new Parameter();
parameter.setID(parameterData.name);
parameter.setLabel(parameterData.label);
parameter.setDescription(parameterData.description);
parameter.setContext(parameterData.context);
if (parameterData.defaultValue != null) {
parameter.setDefaultValue(new ParameterDefaultValue(parameterData.defaultValue));
}
parameter.setRequired(parameterData.required);
parameter.setType(ParameterValueType.valueOf(titleCase(parameterData.type)));
thingType.addParameter(parameter);
}
}
private String titleCase(String type) {
return Character.toTitleCase(type.charAt(0)) + type.substring(1).toLowerCase();
}
@SuppressWarnings("OptionalUsedAsFieldOrParameterType") @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private <T> void ifPresent(Optional<T> opt, String type, Object data, private <T> void ifPresent(Optional<T> opt, String type, Object data,
Consumer<? super T> consumer) { Consumer<? super T> consumer) {
...@@ -307,7 +341,7 @@ public class OpenHab2Importer { ...@@ -307,7 +341,7 @@ public class OpenHab2Importer {
} }
} }
public Root importFrom(URL baseUrl) { public OpenHAB2Model importFrom(URL baseUrl) {
return importFrom(baseUrl.getHost(), return importFrom(baseUrl.getHost(),
baseUrl.getPort() == -1 ? baseUrl.getDefaultPort() : baseUrl.getPort()); baseUrl.getPort() == -1 ? baseUrl.getDefaultPort() : baseUrl.getPort());
} }
......
...@@ -3,6 +3,7 @@ package de.tudresden.inf.st.eraser.openhab2.data; ...@@ -3,6 +3,7 @@ package de.tudresden.inf.st.eraser.openhab2.data;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* Abstract JSON return type for either an item or a group in openHAB 2. * Abstract JSON return type for either an item or a group in openHAB 2.
...@@ -23,4 +24,5 @@ public class AbstractItemData { ...@@ -23,4 +24,5 @@ public class AbstractItemData {
public String transformedState; public String transformedState;
public StateDescriptionData stateDescription; public StateDescriptionData stateDescription;
public Boolean editable; public Boolean editable;
public Map<String, MetaDataData> metadata;
} }
package de.tudresden.inf.st.eraser.openhab2.data;
import com.fasterxml.jackson.annotation.JsonInclude;
import java.util.Map;
/**
* Item Meta data.
*
* @author rschoene - Initial contribution
*/
@SuppressWarnings("unused")
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
public class MetaDataData {
public String value;
public Map<String, String> config;
}
...@@ -9,7 +9,7 @@ import java.util.List; ...@@ -9,7 +9,7 @@ import java.util.List;
* *
* @author rschoene - Initial contribution * @author rschoene - Initial contribution
*/ */
@SuppressWarnings("unused") //@SuppressWarnings("unused")
@JsonInclude(JsonInclude.Include.NON_DEFAULT) @JsonInclude(JsonInclude.Include.NON_DEFAULT)
public class ParameterData { public class ParameterData {
public String context; public String context;
...@@ -19,24 +19,24 @@ public class ParameterData { ...@@ -19,24 +19,24 @@ public class ParameterData {
public String name; public String name;
public Boolean required; public Boolean required;
public String type; public String type;
public Integer min; // public Integer min;
public Integer max; public Integer max;
public Integer stepsize; // public Integer stepsize;
public String pattern; public String pattern;
public Boolean readOnly; public Boolean readOnly;
public Boolean multiple; // public Boolean multiple;
public Integer multipleLimit; // public Integer multipleLimit;
public String groupName; // public String groupName;
public Boolean advanced; // public Boolean advanced;
public Boolean verify; // public Boolean verify;
public Boolean limitToOptions; // public Boolean limitToOptions;
public String unit; public String unit;
public String unitLabel; // public String unitLabel;
public List<OptionData> options; // public List<OptionData> options;
public List<FilterCriteriaData> filterCriteria; // public List<FilterCriteriaData> filterCriteria;
public static class FilterCriteriaData { // public static class FilterCriteriaData {
public String name; // public String name;
public String value; // public String value;
} // }
} }
...@@ -19,4 +19,5 @@ public class ThingTypeData { ...@@ -19,4 +19,5 @@ public class ThingTypeData {
public boolean listed; public boolean listed;
public List<String> supportedBridgeTypeUIDs; public List<String> supportedBridgeTypeUIDs;
public boolean bridge; public boolean bridge;
public List<ParameterData> configParameters;
} }
package de.tudresden.inf.st.eraser.openhab2.mqtt; package de.tudresden.inf.st.eraser.openhab2.mqtt;
import de.tudresden.inf.st.eraser.jastadd.model.ExternalHost;
import de.tudresden.inf.st.eraser.jastadd.model.Item; import de.tudresden.inf.st.eraser.jastadd.model.Item;
import de.tudresden.inf.st.eraser.jastadd.model.Root; import de.tudresden.inf.st.eraser.jastadd.model.Root;
import de.tudresden.inf.st.eraser.util.MqttReceiver; import de.tudresden.inf.st.eraser.util.MqttReceiver;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.fusesource.hawtbuf.Buffer; import org.fusesource.mqtt.client.QoS;
import org.fusesource.hawtbuf.UTF8Buffer;
import org.fusesource.mqtt.client.*;
import java.io.IOException; import java.io.IOException;
import java.net.URI;
import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/** /**
* Update an imported model by subscribing to MQTT topics. * Update an imported model by subscribing to MQTT topics.
...@@ -36,29 +28,34 @@ public class MQTTUpdater implements AutoCloseable { ...@@ -36,29 +28,34 @@ public class MQTTUpdater implements AutoCloseable {
this.delegatee = new MqttReceiver(); this.delegatee = new MqttReceiver();
} }
public MQTTUpdater(Root model) throws IllegalArgumentException { public MQTTUpdater(Root root) throws IllegalArgumentException {
this(); this();
this.setModel(model); this.setRoot(root);
} }
/** /**
* Sets the model to update * Sets the model root to update
* @param model the model to update * @param root the model root to update
*/ */
public void setModel(Root model) { public void setRoot(Root root) {
delegatee.setHost(model.getMqttRoot().getHost().getHostName()); ExternalHost host = root.getMqttRoot().getHost();
delegatee.setHost(host.getHostName(), host.getPort());
delegatee.setOnMessage((topicString, message) -> delegatee.setOnMessage((topicString, message) ->
model.getMqttRoot().resolveTopic(topicString).ifPresent(topic -> root.getMqttRoot().resolveTopic(topicString).ifPresent(topic ->
itemUpdate(topic.getItem(), message))); topic.getItems().forEach(
delegatee.setTopicsForSubscription(model.getMqttRoot().getIncomingPrefix() + "#"); item -> itemUpdate(item, message))));
delegatee.setTopicsForSubscription(root.getMqttRoot().getIncomingPrefix() + "#");
delegatee.setQoSForSubscription(QoS.AT_LEAST_ONCE); delegatee.setQoSForSubscription(QoS.AT_LEAST_ONCE);
} }
private void itemUpdate(Item item, String state) { private void itemUpdate(Item item, String state) {
String oldState = item.getStateAsString();
if (oldState == null || !oldState.equals(state)) {
this.logger.debug("Update state of {} [{}] from '{}' to '{}'.", this.logger.debug("Update state of {} [{}] from '{}' to '{}'.",
item.getLabel(), item.getID(), item.getStateAsString(), state); item.getLabel(), item.getID(), oldState, state);
item.setStateFromString(state, false); item.setStateFromString(state, false);
} }
}
/** /**
* Waits until this updater is ready to receive MQTT messages. * Waits until this updater is ready to receive MQTT messages.
......
package de.tudresden.inf.st.eraser.parser; package de.tudresden.inf.st.eraser.parser;
import de.tudresden.inf.st.eraser.jastadd.model.*; import de.tudresden.inf.st.eraser.jastadd.model.*;
import de.tudresden.inf.st.eraser.util.JavaUtils;
import de.tudresden.inf.st.eraser.util.ParserUtils; import de.tudresden.inf.st.eraser.util.ParserUtils;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
...@@ -28,9 +29,11 @@ public class EraserParserHelper { ...@@ -28,9 +29,11 @@ public class EraserParserHelper {
private Map<Channel, String> missingChannelTypeMap = new HashMap<>(); private Map<Channel, String> missingChannelTypeMap = new HashMap<>();
private Map<Item, String> missingTopicMap = new HashMap<>(); private Map<Item, String> missingTopicMap = new HashMap<>();
private Map<Item, String> missingItemCategoryMap = new HashMap<>(); private Map<Item, String> missingItemCategoryMap = new HashMap<>();
private Map<Designator, String> missingItemForDesignator = new HashMap<>();
private Map<Thing, Iterable<String>> missingChannelListMap = new HashMap<>(); private Map<Thing, Iterable<String>> missingChannelListMap = new HashMap<>();
private Map<Channel, Iterable<String>> missingItemLinkListMap = new HashMap<>(); private Map<Channel, Iterable<String>> missingItemLinkListMap = new HashMap<>();
private Map<Item, Iterable<String>> missingControllingListMap = new HashMap<>();
private Map<Group, Iterable<String>> missingSubGroupListMap = new HashMap<>(); private Map<Group, Iterable<String>> missingSubGroupListMap = new HashMap<>();
private Map<Group, Iterable<String>> missingItemListMap = new HashMap<>(); private Map<Group, Iterable<String>> missingItemListMap = new HashMap<>();
private Map<ThingType, Iterable<String>> missingChannelTypeListMap = new HashMap<>(); private Map<ThingType, Iterable<String>> missingChannelTypeListMap = new HashMap<>();
...@@ -44,6 +47,7 @@ public class EraserParserHelper { ...@@ -44,6 +47,7 @@ public class EraserParserHelper {
private Root root; private Root root;
private static boolean checkUnusedElements = true; private static boolean checkUnusedElements = true;
private static Root initialRoot = null;
private class ItemPrototype extends DefaultItem { private class ItemPrototype extends DefaultItem {
...@@ -57,25 +61,41 @@ public class EraserParserHelper { ...@@ -57,25 +61,41 @@ public class EraserParserHelper {
EraserParserHelper.checkUnusedElements = checkUnusedElements; EraserParserHelper.checkUnusedElements = checkUnusedElements;
} }
public static void setInitialRoot(Root root) {
EraserParserHelper.initialRoot = root;
}
/** /**
* Post processing step after parsing a model, to resolve all references within the model. * Post processing step after parsing a model, to resolve all references within the model.
* @throws java.util.NoSuchElementException if a reference can not be resolved
*/ */
public void resolveReferences() { public void resolveReferences() {
if (this.root == null) {
// when parsing expressions
this.root = EraserParserHelper.initialRoot != null ? EraserParserHelper.initialRoot : createRoot();
}
if (checkUnusedElements) { if (checkUnusedElements) {
fillUnused(); fillUnused();
} }
resolve(thingTypeMap, missingThingTypeMap, Thing::setType); resolve(thingTypeMap, missingThingTypeMap, Thing::setType);
resolve(channelTypeMap, missingChannelTypeMap, Channel::setType); resolve(channelTypeMap, missingChannelTypeMap, Channel::setType);
if (itemMap == null || itemMap.isEmpty()) {
missingItemForDesignator.forEach((designator, itemName) ->
JavaUtils.ifPresentOrElse(root.getOpenHAB2Model().resolveItem(itemName),
designator::setItem,
() -> logger.warn("Could not resolve item {} for {}", itemName, designator)));
} else {
resolve(itemMap, missingItemForDesignator, Designator::setItem);
}
missingTopicMap.forEach((topic, parts) -> ParserUtils.createMqttTopic(topic, parts, this.root)); missingTopicMap.forEach((topic, parts) -> ParserUtils.createMqttTopic(topic, parts, this.root));
this.root.getMqttRoot().ensureCorrectPrefixes(); this.root.getMqttRoot().ensureCorrectPrefixes();
resolveList(channelMap, missingChannelListMap, Thing::addChannel); resolveList(channelMap, missingChannelListMap, Thing::addChannel);
resolveList(itemMap, missingItemLinkListMap, (channel, item) -> channel.addLink(new Link(item))); resolveList(itemMap, missingItemLinkListMap, Channel::addLinkedItem);
resolveList(groupMap, missingSubGroupListMap, Group::addGroup); resolveList(groupMap, missingSubGroupListMap, Group::addGroup);
resolveList(itemMap, missingItemListMap, this::addItemToGroup); resolveList(itemMap, missingItemListMap, this::addItemToGroup);
resolveList(channelTypeMap, missingChannelTypeListMap, ThingType::addChannelType); resolveList(channelTypeMap, missingChannelTypeListMap, ThingType::addChannelType);
resolveList(parameterMap, missingParameterListMap, ThingType::addParameter); resolveList(parameterMap, missingParameterListMap, ThingType::addParameter);
resolveList(itemMap, missingControllingListMap, Item::addControlling);
createUnknownGroupIfNecessary(); createUnknownGroupIfNecessary();
createChannelCategories(); createChannelCategories();
...@@ -87,7 +107,7 @@ public class EraserParserHelper { ...@@ -87,7 +107,7 @@ public class EraserParserHelper {
private void addItemToGroup(Group group, Item item) { private void addItemToGroup(Group group, Item item) {
groupedItems.add(item); groupedItems.add(item);
group.getItemList().insertChild(item, 0); group.addItem(item);
} }
private void fillUnused() { private void fillUnused() {
...@@ -112,13 +132,13 @@ public class EraserParserHelper { ...@@ -112,13 +132,13 @@ public class EraserParserHelper {
sortedDanglingItems.add(item); sortedDanglingItems.add(item);
} }
} }
ParserUtils.createUnknownGroup(this.root, sortedDanglingItems); ParserUtils.createUnknownGroup(this.root.getOpenHAB2Model(), sortedDanglingItems);
} }
} }
private void createChannelCategories() { private void createChannelCategories() {
channelCategoryMap.values().stream().sorted(Comparator.comparing(this::ident)).forEach( channelCategoryMap.values().stream().sorted(Comparator.comparing(this::ident)).forEach(
cc -> root.addChannelCategory(cc)); cc -> root.getOpenHAB2Model().addChannelCategory(cc));
channelCategoryMap.clear(); channelCategoryMap.clear();
} }
...@@ -126,6 +146,7 @@ public class EraserParserHelper { ...@@ -126,6 +146,7 @@ public class EraserParserHelper {
Map<String, ItemCategory> newCategories = new HashMap<>(); Map<String, ItemCategory> newCategories = new HashMap<>();
missingItemCategoryMap.forEach((item, category) -> missingItemCategoryMap.forEach((item, category) ->
item.setCategory(newCategories.computeIfAbsent(category, ItemCategory::new))); item.setCategory(newCategories.computeIfAbsent(category, ItemCategory::new)));
newCategories.values().forEach(node -> root.getOpenHAB2Model().addItemCategory(node));
} }
private void checkUnusedElements() { private void checkUnusedElements() {
...@@ -136,7 +157,7 @@ public class EraserParserHelper { ...@@ -136,7 +157,7 @@ public class EraserParserHelper {
if (elem instanceof ModelElement) { if (elem instanceof ModelElement) {
return ((ModelElement) elem).getID(); return ((ModelElement) elem).getID();
} else if (elem instanceof MqttTopic) { } else if (elem instanceof MqttTopic) {
return safeAllParts((MqttTopic) elem); return ((MqttTopic) elem).getTopicString();
} else if (elem instanceof DefaultChannelCategory) { } else if (elem instanceof DefaultChannelCategory) {
return ((DefaultChannelCategory) elem).getValue().name(); return ((DefaultChannelCategory) elem).getValue().name();
} else if (elem instanceof SimpleChannelCategory) { } else if (elem instanceof SimpleChannelCategory) {
...@@ -145,41 +166,23 @@ public class EraserParserHelper { ...@@ -145,41 +166,23 @@ public class EraserParserHelper {
return elem.toString(); return elem.toString();
} }
private String safeAllParts(MqttTopic elem) { private <Src extends ASTNode, Target extends ASTNode> void resolveList(
StringBuilder sb = new StringBuilder(elem.getPart()); Map<String, Target> resolved, Map<Src, Iterable<String>> missing, BiConsumer<Src, Target> adder) {
ASTNode parent; missing.forEach(
while (true) { (elem, keyList) -> keyList.forEach(
parent = elem.getParent(); key -> resolve0(resolved, key, elem, adder)));
if (parent == null) break; missing.clear();
assert parent instanceof List;
parent = parent.getParent();
if (parent == null || parent instanceof MqttRoot) {
break;
}
elem = (MqttTopic) parent;
sb.insert(0, elem.getPart() + "/");
}
return sb.toString();
} }
private <Src extends ASTNode, Target extends ASTNode> void resolveList(Map<String, Target> resolved, Map<Src, Iterable<String>> missing, BiConsumer<Src, Target> adder) { private <Src extends ASTNode, Target extends ASTNode> void resolve(
missing.forEach((elem, keyList) -> keyList.forEach(key -> { Map<String, Target> resolved, Map<Src, String> missing, BiConsumer<Src, Target> setter) {
Target value = resolved.get(key); missing.forEach(
if (value == null) { (elem, key) -> resolve0(resolved, key, elem, setter));
logger.warn("Reference in {} {} for '{}' cannot be resolved",
elem.getClass().getSimpleName(), ident(elem), key);
return;
}
if (checkUnusedElements) {
unusedElements.remove(value);
}
adder.accept(elem, value);
}));
missing.clear(); missing.clear();
} }
private <Src extends ASTNode, Target extends ASTNode> void resolve(Map<String, Target> resolved, Map<Src, String> missing, BiConsumer<Src, Target> setter) { private <Src extends ASTNode, Target extends ASTNode> void resolve0(
missing.forEach((elem, key) -> { Map<String, Target> resolved, String key, Src elem, BiConsumer<Src, Target> action) {
Target value = resolved.get(key); Target value = resolved.get(key);
if (value == null) { if (value == null) {
logger.warn("Reference in {} {} for '{}' cannot be resolved", logger.warn("Reference in {} {} for '{}' cannot be resolved",
...@@ -189,29 +192,47 @@ public class EraserParserHelper { ...@@ -189,29 +192,47 @@ public class EraserParserHelper {
if (checkUnusedElements) { if (checkUnusedElements) {
unusedElements.remove(value); unusedElements.remove(value);
} }
setter.accept(elem, value); action.accept(elem, value);
});
missing.clear();
} }
//--- Thing and ThingType ---
public Thing addThingType(Thing t, String typeName) { public Thing addThingType(Thing t, String typeName) {
missingThingTypeMap.put(t, typeName); missingThingTypeMap.put(t, typeName);
return t; return t;
} }
public ChannelType setItemType(ChannelType ct, String itemTypeName) { public ThingType setChannelTypes(ThingType tt, StringList channelTypeNames) {
ct.setItemType(ItemType.valueOf(itemTypeName)); missingChannelTypeListMap.put(tt, channelTypeNames);
return ct; return tt;
} }
public Item setCategory(Item item, String categoryName) { public ThingType setParameters(ThingType tt, StringList parameterNames) {
missingItemCategoryMap.put(item, categoryName); missingParameterListMap.put(tt, parameterNames);
return item; return tt;
} }
public Item setTopic(Item item, String mqttTopicName) { public Thing setChannels(Thing t, StringList channelNames) {
missingTopicMap.put(item, mqttTopicName); missingChannelListMap.put(t, channelNames);
return item; return t;
}
public Thing setID(Thing thing, String id) {
thing.setID(id);
return thing;
}
public ThingType setID(ThingType thingType, String id) {
thingType.setID(id);
thingTypeMap.put(id, thingType);
return thingType;
}
//--- Channel and ChannelType ---
public ChannelType setItemType(ChannelType ct, String itemTypeName) {
ct.setItemType(ItemType.valueOf(itemTypeName));
return ct;
} }
public ChannelType setChannelCategory(ChannelType ct, String name) { public ChannelType setChannelCategory(ChannelType ct, String name) {
...@@ -229,6 +250,95 @@ public class EraserParserHelper { ...@@ -229,6 +250,95 @@ public class EraserParserHelper {
return ct; return ct;
} }
public Channel setChannelType(Channel c, String channelTypeName) {
missingChannelTypeMap.put(c, channelTypeName);
return c;
}
public Channel setLinks(Channel c, StringList linkNames) {
missingItemLinkListMap.put(c, linkNames);
return c;
}
public ChannelType setID(ChannelType channelType, String id) {
channelType.setID(id);
channelTypeMap.put(id, channelType);
return channelType;
}
public Channel setID(Channel channel, String id) {
channel.setID(id);
channelMap.put(id, channel);
return channel;
}
//--- Item ---
public Item createItem() {
ItemPrototype result = new ItemPrototype();
result.disableSendState();
return result;
}
public Item setCategory(Item item, String categoryName) {
missingItemCategoryMap.put(item, categoryName);
return item;
}
public Item setMetaData(Item item, StringKeyMap metaData) {
for (AbstractMap.SimpleEntry<String, String> entry : metaData) {
item.addMetaData(new ItemMetaData(entry.getKey(), entry.getValue()));
}
return item;
}
public Item setControlling(Item item, StringList controlling) {
missingControllingListMap.put(item, controlling);
return item;
}
public Item retype(Item itemWithCorrectType, Item prototype) {
itemWithCorrectType.setID(prototype.getID());
itemWithCorrectType.setLabel(prototype.getLabel());
itemWithCorrectType.setMetaDataList(prototype.getMetaDataList());
if (!(itemWithCorrectType instanceof ActivityItem)) {
String state = prototype.getStateAsString();
itemWithCorrectType.disableSendState();
if (state.isEmpty()) {
itemWithCorrectType.setStateToDefault();
} else {
itemWithCorrectType.setStateFromString(state);
}
itemWithCorrectType.enableSendState();
}
moveMissingForRetype(itemWithCorrectType, prototype, missingTopicMap);
moveMissingForRetype(itemWithCorrectType, prototype, missingControllingListMap);
moveMissingForRetype(itemWithCorrectType, prototype, missingItemCategoryMap);
itemMap.put(prototype.getID(), itemWithCorrectType);
itemOrder.add(itemWithCorrectType);
return itemWithCorrectType;
}
private <T> void moveMissingForRetype(Item itemWithCorrectType, Item prototype, Map<Item, T> missingXMap) {
T value = missingXMap.get(prototype);
if (value != null) {
missingXMap.put(itemWithCorrectType, value);
}
missingXMap.remove(prototype);
}
public Item setID(Item item, String id) {
item.setID(id);
itemMap.put(id, item);
return item;
}
//--- Group ---
public Group setSubGroups(Group g, StringList subGroupNames) { public Group setSubGroups(Group g, StringList subGroupNames) {
missingSubGroupListMap.put(g, subGroupNames); missingSubGroupListMap.put(g, subGroupNames);
return g; return g;
...@@ -252,10 +362,8 @@ public class EraserParserHelper { ...@@ -252,10 +362,8 @@ public class EraserParserHelper {
params.iterator().forEachRemaining(paramList::add); params.iterator().forEachRemaining(paramList::add);
String param1, param2; String param1, param2;
if (paramList.size() == 2) { if (paramList.size() == 2) {
// ATTENTION: Here the order is reversed, as the parser reads from back to front param1 = paramList.get(0);
// So assign first parameter to second read parameter, and vice versa param2 = paramList.get(1);
param1 = paramList.get(1);
param2 = paramList.get(0);
} else { } else {
logger.error("Got {} instead of 2 parameters in group function {}!", paramList.size(), aggFunctionName); logger.error("Got {} instead of 2 parameters in group function {}!", paramList.size(), aggFunctionName);
param1 = "?"; param1 = "?";
...@@ -265,77 +373,77 @@ public class EraserParserHelper { ...@@ -265,77 +373,77 @@ public class EraserParserHelper {
return g; return g;
} }
public ThingType setChannelTypes(ThingType tt, StringList channelTypeNames) { public Group setID(Group group, String id) {
missingChannelTypeListMap.put(tt, channelTypeNames); group.setID(id);
return tt; groupMap.put(id, group);
return group;
} }
//--- Parameter ---
public Parameter setParameterValueType(Parameter p, String pvt) { public Parameter setParameterValueType(Parameter p, String pvt) {
p.setType(ParameterValueType.valueOf(toTitleCase(pvt))); p.setType(ParameterValueType.valueOf(JavaUtils.toTitleCase(pvt)));
return p; return p;
} }
private String toTitleCase(String s) {
if (s == null || s.isEmpty()) {
return s;
}
return s.substring(0, 1).toUpperCase() + s.substring(1);
}
public Parameter setDefault(Parameter p, String defaultValue) { public Parameter setDefault(Parameter p, String defaultValue) {
p.setDefaultValue(new ParameterDefaultValue(defaultValue)); p.setDefaultValue(new ParameterDefaultValue(defaultValue));
return p; return p;
} }
public ThingType setParameters(ThingType tt, StringList parameterNames) { public Parameter setID(Parameter parameter, String id) {
missingParameterListMap.put(tt, parameterNames); parameter.setID(id);
return tt; parameterMap.put(id, parameter);
return parameter;
} }
public Thing setChannels(Thing t, StringList channelNames) { //--- MQTT ---
missingChannelListMap.put(t, channelNames);
return t;
}
public Channel setChannelType(Channel c, String channelTypeName) { public Item setTopic(Item item, String mqttTopicName) {
missingChannelTypeMap.put(c, channelTypeName); missingTopicMap.put(item, mqttTopicName);
return c; return item;
} }
public Channel setLinks(Channel c, StringList linkNames) { //--- Activity ---
missingItemLinkListMap.put(c, linkNames);
return c; public MachineLearningRoot setActivities(MachineLearningRoot mlr, IntegerKeyMap map) {
for (AbstractMap.SimpleEntry<Integer, String> entry : map) {
Activity activity = new Activity();
activity.setIdentifier(entry.getKey());
activity.setLabel(entry.getValue());
mlr.addActivity(activity);
}
return mlr;
} }
//--- Root ---
public Root createRoot() { public Root createRoot() {
this.root = new Root(); this.root = Root.createEmptyRoot();
this.root.setMqttRoot(new MqttRoot());
this.root.setInfluxRoot(InfluxRoot.createDefault());
this.root.setMachineLearningRoot(new MachineLearningRoot());
return this.root; return this.root;
} }
public Root createRoot(Thing t) { public Root createRoot(Thing t) {
Root result = createRoot(); Root result = createRoot();
result.addThing(t); result.getOpenHAB2Model().addThing(t);
return result; return result;
} }
public Root createRoot(Group g) { public Root createRoot(Group g) {
Root result = createRoot(); Root result = createRoot();
result.addGroup(g); result.getOpenHAB2Model().addGroup(g);
return result; return result;
} }
public Root createRoot(ThingType tt) { public Root createRoot(ThingType tt) {
Root result = createRoot(); Root result = createRoot();
result.addThingType(tt); result.getOpenHAB2Model().addThingType(tt);
return result; return result;
} }
public Root createRoot(ChannelType ct) { public Root createRoot(ChannelType ct) {
Root result = createRoot(); Root result = createRoot();
result.addChannelType(ct); result.getOpenHAB2Model().addChannelType(ct);
return result; return result;
} }
...@@ -351,64 +459,30 @@ public class EraserParserHelper { ...@@ -351,64 +459,30 @@ public class EraserParserHelper {
return result; return result;
} }
public Item createItem() { public Root createRoot(MachineLearningRoot ml) {
return new ItemPrototype(); Root result = createRoot();
} result.setMachineLearningRoot(ml);
return result;
public Item retype(Item itemWithCorrectType, Item prototype) {
itemWithCorrectType.setID(prototype.getID());
itemWithCorrectType.setLabel(prototype.getLabel());
itemWithCorrectType.setStateFromString(prototype.getStateAsString());
missingTopicMap.put(itemWithCorrectType, missingTopicMap.get(prototype));
missingTopicMap.remove(prototype);
itemMap.put(prototype.getID(), itemWithCorrectType);
itemOrder.add(itemWithCorrectType);
return itemWithCorrectType;
}
public Thing setID(Thing thing, String id) {
thing.setID(id);
return thing;
}
public Item setID(Item item, String id) {
item.setID(id);
itemMap.put(id, item);
return item;
}
public Group setID(Group group, String id) {
group.setID(id);
groupMap.put(id, group);
return group;
}
public ThingType setID(ThingType thingType, String id) {
thingType.setID(id);
thingTypeMap.put(id, thingType);
return thingType;
} }
public Parameter setID(Parameter parameter, String id) { public Root createRoot(Rule rule) {
parameter.setID(id); Root result = createRoot();
parameterMap.put(id, parameter); result.addRule(rule);
return parameter; return result;
} }
public ChannelType setID(ChannelType channelType, String id) { //+++ newStuff (to be categorized) +++
channelType.setID(id); public Designator createDesignator(String itemName) {
channelTypeMap.put(id, channelType); Designator result = new Designator();
return channelType; missingItemForDesignator.put(result, itemName);
return result;
} }
public Channel setID(Channel channel, String id) { public Rule createRule(Condition c, Action a) {
channel.setID(id); Rule result = new Rule();
channelMap.put(id, channel); result.addCondition(c);
return channel; result.addAction(a);
return result;
} }
} }
...@@ -45,4 +45,11 @@ public class JavaUtils { ...@@ -45,4 +45,11 @@ public class JavaUtils {
return StreamSupport.stream(jastAddList.spliterator(), false); return StreamSupport.stream(jastAddList.spliterator(), false);
} }
public static String toTitleCase(String s) {
if (s == null || s.isEmpty()) {
return s;
}
return s.substring(0, 1).toUpperCase() + s.substring(1);
}
} }
package de.tudresden.inf.st.eraser.util;
import de.tudresden.inf.st.eraser.jastadd.model.ASTNode;
import de.tudresden.inf.st.eraser.jastadd.model.ModelElement;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* Fluent API to pretty-print ASTNodes in a line-based fashion.
*
* @author rschoene - Initial contribution
*/
public class MemberPrinter {
private static final Logger logger = LogManager.getLogger(MemberPrinter.class);
private static boolean addSemicolonNewlineWhenBuild = true;
private static boolean emitNothingOnEmptyBody = true;
private boolean empty = true;
private final StringBuilder sb;
public enum ListBracketType {
SQUARE("[", "]"),
ROUND("(", ")"),
CURLY("{", "}");
private final String begin;
private final String end;
ListBracketType(String begin, String end) {
this.begin = begin;
this.end = end;
}
}
public MemberPrinter(String elementName) {
this.sb = new StringBuilder();
if (elementName != null && !elementName.isEmpty()) {
sb.append(elementName).append(":");
}
}
public static void setAddSemicolonNewlineWhenBuild(boolean addSemicolonNewlineWhenBuild) {
MemberPrinter.addSemicolonNewlineWhenBuild = addSemicolonNewlineWhenBuild;
}
public static void setEmitNothingOnEmptyBody(boolean emitNothingOnEmptyBody) {
MemberPrinter.emitNothingOnEmptyBody = emitNothingOnEmptyBody;
}
public String build() {
if (empty) {
if (emitNothingOnEmptyBody) {
return "";
} else {
logger.debug("Emitting empty body {}", sb);
}
}
if (addSemicolonNewlineWhenBuild) {
sb.append(" ;\n");
}
return sb.toString();
}
/**
* Given a list of elements, concat the quoted id's of those elements, separated by commas,
* and appends it to the given StringBuilder.
* @param listOfElements the list of elements to concat
*/
private void concatIds(Iterable<? extends ModelElement> listOfElements) {
concatNodes(listOfElements, ModelElement::getID, true);
}
/**
* Given a list of elements, concat the quoted id's of those elements, separated by commas,
* and appends it to the given StringBuilder.
* @param listOfNodes the list of nodes
* @param mapping a function to map a node to a ModelElement
*/
private <T extends ASTNode> void concatIds(Iterable<T> listOfNodes,
Function<T, ? extends ModelElement> mapping) {
concatNodes(listOfNodes, t -> mapping.apply(t).getID(), true);
}
/**
* Given a list of nodes, apply a function on each of those, concat the output (separated by commas),
* and appends it to the given StringBuilder.
* @param listOfNodes the list of nodes
* @param mapping a function to map a node to a String
*/
private <T extends ASTNode> void concatNodes(Iterable<T> listOfNodes,
Function<T, String> mapping,
boolean quote) {
boolean first = true;
for (T t : listOfNodes) {
if (first) {
first = false;
} else {
sb.append(", ");
}
if (quote) {
sb.append("\"");
}
sb.append(mapping.apply(t));
if (quote) {
sb.append("\"");
}
}
}
/**
* Appends model elements to the builder, if their number is positive.
* @param name The name of the member
* @param listOfElements The list of model elements
* @return this
*/
public MemberPrinter addIds(String name, List<? extends ModelElement> listOfElements) {
return addIds(name, listOfElements.size(), listOfElements);
}
/**
* Appends model elements to the builder, if count is positive.
* @param name The name of the member
* @param count The number of items in the listOfElements
* @param listOfElements The list of model elements
* @return this
*/
public MemberPrinter addIds(String name, int count, Iterable<? extends ModelElement> listOfElements) {
if (count > 0) {
sb.append(' ').append(name).append("=[");
concatIds(listOfElements);
sb.append("]");
this.empty = false;
}
return this;
}
/**
* Appends nodes to the builder, if count is positive.
* @param name The name of the member
* @param count The number of items in the listOfNodes
* @param listOfNodes The list of nodes
* @param mapping A function to map a node to a ModelElement
* @return this
*/
public <T extends ASTNode> MemberPrinter addIds(
String name, int count, Iterable<T> listOfNodes,
Function<T, ? extends ModelElement> mapping) {
if (count > 0) {
sb.append(' ').append(name).append("=[");
concatIds(listOfNodes, mapping);
sb.append("]");
this.empty = false;
}
return this;
}
/**
* Applies the mapping to each node and appends the results to the builder, if count is positive.
* The list is enclosed with square brackets.
* @param name The name of the member
* @param count The number of items in the listOfNodes
* @param listOfNodes The list of nodes
* @param mapping A function to map a node to a String
* @param <T> The type of all nodes
* @return this
*/
public <T extends ASTNode> MemberPrinter addNodes(String name, int count, Iterable<T> listOfNodes,
Function<T, String> mapping) {
return addNodes(name, count, listOfNodes, mapping, ListBracketType.SQUARE);
}
/**
* Applies the mapping to each node and appends the results to the builder, if count is positive
* @param name The name of the member
* @param count The number of items in the listOfNodes
* @param listOfNodes The list of nodes
* @param mapping A function to map a node to a String
* @param <T> The type of all nodes
* @param bracketType The type of brackets to enclose the list with
* @return this
*/
public <T extends ASTNode> MemberPrinter addNodes(String name, int count, Iterable<T> listOfNodes,
Function<T, String> mapping, ListBracketType bracketType) {
if (count > 0) {
sb.append(' ').append(name).append("=").append(bracketType.begin);
concatNodes(listOfNodes, mapping, false);
sb.append(bracketType.end);
this.empty = false;
}
return this;
}
/**
* Appends a value to the builder, if the value is present.
* A function is used to get the value, such that it is only evaluated if the value is present
* @param name The name of the member
* @param isPresent Whether the optional value is present
* @param valueGetter A function to get the value
* @return this
*/
public MemberPrinter addOptional(String name, boolean isPresent, Supplier<String> valueGetter) {
if (isPresent) {
return add(name, valueGetter.get());
}
return this;
}
/**
* Appends the result of calling {@link ASTNode#prettyPrint()} for the child, if non-<code>null</code>.
* @param child The child to append
* @return this
*/
public MemberPrinter addOptionalPrettyPrint(ASTNode child) {
if (child != null) {
this.empty = false;
sb.append(child.prettyPrint());
}
return this;
}
/**
* Appends a the name of the flag, if it is set
* @param name The name of the member
* @param isSet Whether the flag is set
* @return this
*/
public MemberPrinter addFlag(String name, boolean isSet) {
if (isSet) {
this.empty = false;
sb.append(' ').append(name);
}
return this;
}
/**
* Appends a value to the builder, if the value is not <code>null</code> and not empty
* @param name The name of the member
* @param actualValue The value to append
* @return this
*/
public MemberPrinter addNonDefault(String name, String actualValue) {
return addNonDefault(name, actualValue, "");
}
/**
* Appends a value to the builder, if the value is not the given default
* @param name The name of the member
* @param actualValue The value to append
* @param defaultValue The default value
* @return this
*/
public MemberPrinter addNonDefault(String name, Object actualValue, Object defaultValue) {
if (!actualValue.equals(defaultValue)) {
this.empty = false;
return add(name, actualValue);
}
return this;
}
/**
* Appends a value to the builder, and issue a warning if it is either <code>null</code> or empty.
* @param name The name of the member
* @param actualValue The value to append
* @return this
*/
public MemberPrinter addRequired(String name, Object actualValue) {
if (actualValue == null || actualValue.toString().isEmpty()) {
logger.warn("Member \"{}\" not defined while printing", name);
actualValue = "";
}
return add(name, actualValue);
}
/**
* Appends a value to the builder, and issue a warning if it is either <code>null</code> or empty.
* @param name The name of the member
* @param objectValue An object to append
* @param mapping A function to get the real value from the object
* @return this
*/
public <T> MemberPrinter addRequired(String name, T objectValue, Function<T, String> mapping) {
String actualValue;
if (objectValue == null) {
logger.warn("Member \"{}\" not defined while printing", name);
actualValue = "";
} else {
actualValue = mapping.apply(objectValue);
if (actualValue.isEmpty()) {
logger.warn("Member \"{}\" empty while printing", name);
}
}
return add(name, actualValue);
}
private MemberPrinter add(String name, Object actualValue) {
this.empty = false;
sb.append(' ').append(name).append("=\"").append(actualValue).append("\"");
return this;
}
}
package de.tudresden.inf.st.eraser.util; package de.tudresden.inf.st.eraser.util;
import de.tudresden.inf.st.eraser.openhab2.mqtt.MQTTUpdater;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.fusesource.hawtbuf.Buffer; import org.fusesource.hawtbuf.Buffer;
...@@ -40,7 +39,7 @@ public class MqttReceiver implements AutoCloseable { ...@@ -40,7 +39,7 @@ public class MqttReceiver implements AutoCloseable {
private QoS qos; private QoS qos;
public MqttReceiver() { public MqttReceiver() {
this.logger = LogManager.getLogger(MQTTUpdater.class); this.logger = LogManager.getLogger(MqttReceiver.class);
this.readyLock = new ReentrantLock(); this.readyLock = new ReentrantLock();
this.readyCondition = readyLock.newCondition(); this.readyCondition = readyLock.newCondition();
this.ready = false; this.ready = false;
...@@ -50,8 +49,8 @@ public class MqttReceiver implements AutoCloseable { ...@@ -50,8 +49,8 @@ public class MqttReceiver implements AutoCloseable {
/** /**
* Sets the host to receive messages from * Sets the host to receive messages from
*/ */
public void setHost(String host) { public void setHost(String host, int port) {
this.host = URI.create("tcp://" + host + ":1883"); this.host = URI.create("tcp://" + host + ":" + port);
logger.debug("Host is {}", this.host); logger.debug("Host is {}", this.host);
} }
...@@ -134,6 +133,17 @@ public class MqttReceiver implements AutoCloseable { ...@@ -134,6 +133,17 @@ public class MqttReceiver implements AutoCloseable {
connection.connect(new Callback<Void>() { connection.connect(new Callback<Void>() {
@Override @Override
public void onSuccess(Void value) { public void onSuccess(Void value) {
connection.publish("components", "Eraser is listening".getBytes(), QoS.AT_LEAST_ONCE, false, new Callback<Void>() {
@Override
public void onSuccess(Void value) {
logger.debug("success sending welcome message");
}
@Override
public void onFailure(Throwable value) {
logger.debug("failure sending welcome message", value);
}
});
Topic[] topicArray = Arrays.stream(topics).map(topicName -> new Topic(topicName, qos)).toArray(Topic[]::new); Topic[] topicArray = Arrays.stream(topics).map(topicName -> new Topic(topicName, qos)).toArray(Topic[]::new);
logger.info("Connected, subscribing to {} topic(s) now.", topicArray.length); logger.info("Connected, subscribing to {} topic(s) now.", topicArray.length);
connection.subscribe(topicArray, new Callback<byte[]>() { connection.subscribe(topicArray, new Callback<byte[]>() {
...@@ -151,7 +161,7 @@ public class MqttReceiver implements AutoCloseable { ...@@ -151,7 +161,7 @@ public class MqttReceiver implements AutoCloseable {
@Override @Override
public void onFailure(Throwable cause) { public void onFailure(Throwable cause) {
logger.error("Could not subscribe, because {}", cause); logger.error("Could not subscribe", cause);
} }
}); });
} }
......
...@@ -3,12 +3,10 @@ package de.tudresden.inf.st.eraser.util; ...@@ -3,12 +3,10 @@ package de.tudresden.inf.st.eraser.util;
import beaver.Parser; import beaver.Parser;
import beaver.Scanner; import beaver.Scanner;
import beaver.Symbol; import beaver.Symbol;
import de.tudresden.inf.st.eraser.jastadd.model.Group; import de.tudresden.inf.st.eraser.jastadd.model.*;
import de.tudresden.inf.st.eraser.jastadd.model.Item;
import de.tudresden.inf.st.eraser.jastadd.model.MqttTopic;
import de.tudresden.inf.st.eraser.jastadd.model.Root;
import de.tudresden.inf.st.eraser.jastadd.parser.EraserParser; import de.tudresden.inf.st.eraser.jastadd.parser.EraserParser;
import de.tudresden.inf.st.eraser.jastadd.scanner.EraserScanner; import de.tudresden.inf.st.eraser.jastadd.scanner.EraserScanner;
import de.tudresden.inf.st.eraser.parser.EraserParserHelper;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
...@@ -16,8 +14,8 @@ import java.io.*; ...@@ -16,8 +14,8 @@ import java.io.*;
import java.net.URL; import java.net.URL;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.*; import java.util.Collection;
import java.util.stream.Collectors; import java.util.Objects;
/** /**
* Utility methods involving scanner and parser of the models. * Utility methods involving scanner and parser of the models.
...@@ -26,9 +24,42 @@ import java.util.stream.Collectors; ...@@ -26,9 +24,42 @@ import java.util.stream.Collectors;
*/ */
public class ParserUtils { public class ParserUtils {
public static final String UNKNOWN_GROUP_NAME = "Unknown";
private static boolean verboseLoading = false; private static boolean verboseLoading = false;
private static final Logger logger = LogManager.getLogger(ParserUtils.class); private static final Logger logger = LogManager.getLogger(ParserUtils.class);
private interface ReaderProvider {
Reader provide() throws IOException;
}
private static class ReaderProviderByName implements ReaderProvider {
private final String filename;
private final Class<?> clazz;
ReaderProviderByName(String filename, Class<?> clazz) {
this.filename = filename;
this.clazz = clazz;
}
@Override
public Reader provide() throws IOException {
return getReader(filename, clazz);
}
}
private static class ReaderProviderByURL implements ReaderProvider {
private final URL url;
ReaderProviderByURL(URL url) {
this.url = url;
}
@Override
public Reader provide() throws IOException {
return new InputStreamReader(url.openStream());
}
}
/** /**
* Print read tokens before loading the model. * Print read tokens before loading the model.
* This will effectively parse the input two times, thus slowing the operation. * This will effectively parse the input two times, thus slowing the operation.
...@@ -71,8 +102,23 @@ public class ParserUtils { ...@@ -71,8 +102,23 @@ public class ParserUtils {
*/ */
public static Root load(String fileName, Class<?> clazz) throws IOException, Parser.Exception { public static Root load(String fileName, Class<?> clazz) throws IOException, Parser.Exception {
logger.info("Loading model DSL file '{}'", fileName); logger.info("Loading model DSL file '{}'", fileName);
return load(new ReaderProviderByName(fileName, clazz));
}
/**
* Loads a model in a file from the given URL.
* @param url an URL pointing to a file
* @return the parsed model
* @throws IOException if the file could not be found, or opened
* @throws Parser.Exception if the file contains a malformed model
*/
public static Root load(URL url) throws IOException, Parser.Exception {
logger.info("Loading model DSL from '{}'", url);
return load(new ReaderProviderByURL(url));
}
Reader reader = getReader(fileName, clazz); private static Root load(ReaderProvider readerProvider) throws IOException, Parser.Exception {
Reader reader = readerProvider.provide();
if (verboseLoading) { if (verboseLoading) {
EraserScanner scanner = new EraserScanner(reader); EraserScanner scanner = new EraserScanner(reader);
try { try {
...@@ -87,7 +133,7 @@ public class ParserUtils { ...@@ -87,7 +133,7 @@ public class ParserUtils {
try { try {
reader.reset(); reader.reset();
} catch (IOException resetEx) { } catch (IOException resetEx) {
reader = getReader(fileName, clazz); reader = readerProvider.provide();
} }
} }
} }
...@@ -120,63 +166,91 @@ public class ParserUtils { ...@@ -120,63 +166,91 @@ public class ParserUtils {
} }
} }
public static void createUnknownGroup(Root model, Collection<Item> danglingItems) { /**
* Create well-known group call "Unknown" and add all dangling items to it.
* @param model The model to operate on
* @param danglingItems A list of items to add to the new group
*/
public static void createUnknownGroup(OpenHAB2Model model, Collection<Item> danglingItems) {
Group unknownGroup = new Group(); Group unknownGroup = new Group();
unknownGroup.setID("Unknown"); unknownGroup.setID(UNKNOWN_GROUP_NAME);
model.addGroup(unknownGroup); model.addGroup(unknownGroup);
danglingItems.forEach(unknownGroup::addItem); danglingItems.forEach(unknownGroup::addItem);
logger.warn("Creating group 'Unknown' for items: {}", PrinterUtils.concatIds(danglingItems)); logger.info("Created new {}", unknownGroup.prettyPrint().trim());
} }
public static void createMqttTopic(Item item, String topicName, Root root) { /**
String[] parts = topicName.split("/"); * Create a topic for the given topic name and assign it to the given item.
String firstPart = parts[0]; * @param item The item to which the topic will be assigned to
MqttTopic firstTopic = null; * @param topicSuffix The full topic name
for (MqttTopic topic : root.getMqttRoot().getTopicList()) { * @param root The model to operate on
if (topic.getPart().equals(firstPart)) { */
firstTopic = topic; public static void createMqttTopic(Item item, String topicSuffix, Root root) {
break; item.setTopic(root.getMqttRoot().resolveTopicSuffix(topicSuffix).orElseGet(() -> {
MqttTopic result = new MqttTopic();
result.setTopicString(topicSuffix);
root.getMqttRoot().addTopic(result);
return result;
}));
} }
public static NumberExpression parseNumberExpression(String expression_string) throws IOException, Parser.Exception {
return parseNumberExpression(expression_string, null);
} }
if (firstTopic == null) {
// no matching topic found for first part. create one. public static LogicalExpression parseLogicalExpression(String expression_string) throws IOException, Parser.Exception {
firstTopic = createTopic(firstPart, root); return parseLogicalExpression(expression_string, null);
} }
// MqttTopic firstTopic = firstPartTopicMap.computeIfAbsent(firstPart, part -> createTopic(part, root));
MqttTopic lastTopic = processRemainingTopicParts(firstTopic, parts, 1); public static NumberExpression parseNumberExpression(String expression_string, Root root) throws IOException, Parser.Exception {
item.setTopic(lastTopic); return (NumberExpression) parseExpression(expression_string, EraserParser.AltGoals.number_expression, root);
} }
private static MqttTopic processRemainingTopicParts(MqttTopic topic, String[] parts, int index) { public static LogicalExpression parseLogicalExpression(String expression_string, Root root) throws IOException, Parser.Exception {
if (index >= parts.length) { return (LogicalExpression) parseExpression(expression_string, EraserParser.AltGoals.logical_expression, root);
return topic;
} }
for (MqttTopic subTopic : topic.getSubTopicList()) {
if (subTopic.getPart().equals(parts[index])) { private static Expression parseExpression(String expression_string, short alt_goal, Root root) throws IOException, Parser.Exception {
// matching part found EraserParserHelper.setInitialRoot(root);
return processRemainingTopicParts(subTopic, parts, index + 1); StringReader reader = new StringReader(expression_string);
if (verboseLoading) {
EraserScanner scanner = new EraserScanner(reader);
try {
Symbol token;
while ((token = scanner.nextToken()).getId() != EraserParser.Terminals.EOF) {
logger.debug("start: {}, end: {}, id: {}, value: {}",
token.getStart(), token.getEnd(), EraserParser.Terminals.NAMES[token.getId()], token.value);
} }
} catch (Scanner.Exception e) {
e.printStackTrace();
} }
// no matching part was found. create remaining topics.
for (int currentIndex = index; currentIndex < parts.length; currentIndex++) {
MqttTopic newTopic = createSubTopic(parts[currentIndex]);
topic.addSubTopic(newTopic);
topic = newTopic;
} }
return topic; reader = new StringReader(expression_string);
EraserScanner scanner = new EraserScanner(reader);
EraserParser parser = new EraserParser();
Expression result = (Expression) parser.parse(scanner, alt_goal);
parser.resolveReferences();
reader.close();
EraserParserHelper.setInitialRoot(null);
return result;
} }
private static MqttTopic createSubTopic(String part) { public static Item parseItem(String definition)
return createTopic(part, null); throws IllegalArgumentException, IOException, Parser.Exception {
StringReader reader = new StringReader(definition);
EraserScanner scanner = new EraserScanner(reader);
EraserParser parser = new EraserParser();
Root root = (Root) parser.parse(scanner);
parser.resolveReferences();
reader.close();
int size = root.getOpenHAB2Model().items().size();
if (size == 0) {
throw new IllegalArgumentException("Model does not contain any items!");
} }
if (size > 1) {
private static MqttTopic createTopic(String part, Root root) { logger.warn("Model does contain {} items, ignoring all but the first.", size);
MqttTopic result = new MqttTopic();
result.setPart(part);
if (root != null) {
root.getMqttRoot().addTopic(result);
} }
return result; return root.getOpenHAB2Model().items().get(0);
} }
} }
package de.tudresden.inf.st.eraser.util;
import de.tudresden.inf.st.eraser.jastadd.model.ASTNode;
import de.tudresden.inf.st.eraser.jastadd.model.JastAddList;
import de.tudresden.inf.st.eraser.jastadd.model.ModelElement;
import java.util.function.Function;
/**
* Helper methods shared by multiple non-terminals.
*
* @author rschoene - Initial contribution
*/
public class PrinterUtils {
/**
* Given a list of elements, concat the quoted id's of those elements, separated by commas.
* @param listOfElements the list of elements to concat
* @return a comma-separated list of the ids of the given elements
*/
public static String concatIds(Iterable<? extends ModelElement> listOfElements) {
return concatIds(new StringBuilder(), listOfElements).toString();
}
/**
* Given a list of elements, concat the quoted id's of those elements, separated by commas,
* and appends it to the given StringBuilder.
* @param sb the StringBuilder to append to
* @param listOfElements the list of elements to concat
* @return the given StringBuilder
*/
public static StringBuilder concatIds(StringBuilder sb, Iterable<? extends ModelElement> listOfElements) {
boolean first = true;
for (ModelElement c : listOfElements) {
if (first) {
first = false;
} else {
sb.append(", ");
}
sb.append("\"").append(c.getID()).append("\"");
}
return sb;
}
/**
* Given a list of elements, concat the quoted id's of those elements, separated by commas,
* and appends it to the given StringBuilder.
* @param sb the StringBuilder to append to
* @param listOfNodes the list of nodes
* @param mapping a function to map a node to a ModelElement
* @return the given StringBuilder
*/
public static <T extends ASTNode> StringBuilder concatIds(StringBuilder sb, Iterable<T> listOfNodes,
Function<T, ? extends ModelElement> mapping) {
boolean first = true;
for (T t : listOfNodes) {
if (first) {
first = false;
} else {
sb.append(", ");
}
sb.append("\"").append(mapping.apply(t).getID()).append("\"");
}
return sb;
}
}
...@@ -4,8 +4,8 @@ ...@@ -4,8 +4,8 @@
<Console name="Console"> <Console name="Console">
<PatternLayout pattern="%highlight{%d{HH:mm:ss.SSS} %-5level} %c{1.} - %msg%n"/> <PatternLayout pattern="%highlight{%d{HH:mm:ss.SSS} %-5level} %c{1.} - %msg%n"/>
</Console> </Console>
<RollingFile name="RollingFile" fileName="logs/jastadd-mquat.log" <RollingFile name="RollingFile" fileName="logs/eraser.log"
filePattern="logs/jastadd-mquat-%i.log"> filePattern="logs/eraser-%i.log">
<PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n"/> <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n"/>
<Policies> <Policies>
<OnStartupTriggeringPolicy/> <OnStartupTriggeringPolicy/>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment