2 * Copyright (c) 2010-2024 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.siemenshvac.internal.metadata;
16 import java.io.FileOutputStream;
17 import java.io.IOException;
18 import java.math.BigDecimal;
20 import java.nio.charset.StandardCharsets;
21 import java.nio.file.Files;
22 import java.time.Duration;
23 import java.time.Instant;
24 import java.util.ArrayList;
25 import java.util.HashMap;
26 import java.util.Hashtable;
27 import java.util.List;
28 import java.util.Locale;
30 import java.util.regex.Matcher;
31 import java.util.regex.Pattern;
33 import org.eclipse.jdt.annotation.NonNullByDefault;
34 import org.eclipse.jdt.annotation.Nullable;
35 import org.openhab.binding.siemenshvac.internal.constants.SiemensHvacBindingConstants;
36 import org.openhab.binding.siemenshvac.internal.converter.ConverterFactory;
37 import org.openhab.binding.siemenshvac.internal.converter.ConverterTypeException;
38 import org.openhab.binding.siemenshvac.internal.converter.TypeConverter;
39 import org.openhab.binding.siemenshvac.internal.handler.SiemensHvacBridgeConfig;
40 import org.openhab.binding.siemenshvac.internal.network.SiemensHvacCallback;
41 import org.openhab.binding.siemenshvac.internal.network.SiemensHvacConnector;
42 import org.openhab.binding.siemenshvac.internal.type.SiemensHvacChannelGroupTypeProvider;
43 import org.openhab.binding.siemenshvac.internal.type.SiemensHvacChannelTypeProvider;
44 import org.openhab.binding.siemenshvac.internal.type.SiemensHvacConfigDescriptionProvider;
45 import org.openhab.binding.siemenshvac.internal.type.SiemensHvacException;
46 import org.openhab.binding.siemenshvac.internal.type.SiemensHvacThingTypeProvider;
47 import org.openhab.binding.siemenshvac.internal.type.UidUtils;
48 import org.openhab.core.OpenHAB;
49 import org.openhab.core.config.core.ConfigDescriptionBuilder;
50 import org.openhab.core.config.core.ConfigDescriptionParameter;
51 import org.openhab.core.config.core.ConfigDescriptionParameterGroup;
52 import org.openhab.core.thing.Thing;
53 import org.openhab.core.thing.ThingTypeUID;
54 import org.openhab.core.thing.type.ChannelDefinition;
55 import org.openhab.core.thing.type.ChannelDefinitionBuilder;
56 import org.openhab.core.thing.type.ChannelGroupDefinition;
57 import org.openhab.core.thing.type.ChannelGroupType;
58 import org.openhab.core.thing.type.ChannelGroupTypeBuilder;
59 import org.openhab.core.thing.type.ChannelGroupTypeUID;
60 import org.openhab.core.thing.type.ChannelType;
61 import org.openhab.core.thing.type.ChannelTypeBuilder;
62 import org.openhab.core.thing.type.ChannelTypeUID;
63 import org.openhab.core.thing.type.StateChannelTypeBuilder;
64 import org.openhab.core.thing.type.ThingType;
65 import org.openhab.core.thing.type.ThingTypeBuilder;
66 import org.openhab.core.types.StateDescriptionFragmentBuilder;
67 import org.openhab.core.types.StateOption;
68 import org.osgi.service.component.annotations.Activate;
69 import org.osgi.service.component.annotations.Component;
70 import org.osgi.service.component.annotations.Reference;
71 import org.slf4j.Logger;
72 import org.slf4j.LoggerFactory;
74 import com.google.gson.JsonArray;
75 import com.google.gson.JsonElement;
76 import com.google.gson.JsonObject;
80 * @author Laurent Arnal - Initial contribution
83 @Component(immediate = true)
84 public class SiemensHvacMetadataRegistryImpl implements SiemensHvacMetadataRegistry {
86 private final Logger logger = LoggerFactory.getLogger(SiemensHvacMetadataRegistryImpl.class);
88 // A map contains data point config read from Api and/or WebPages
89 private Map<String, SiemensHvacMetadata> dptMap = new Hashtable<String, SiemensHvacMetadata>();
90 private @Nullable SiemensHvacMetadata root = null;
91 private @Nullable ArrayList<SiemensHvacMetadataDevice> devices = null;
93 private static final String JSON_DIR = OpenHAB.getUserDataFolder() + File.separatorChar + "siemenshvac";
95 private @Nullable SiemensHvacThingTypeProvider thingTypeProvider;
96 private @Nullable SiemensHvacChannelTypeProvider channelTypeProvider;
97 private @Nullable SiemensHvacChannelGroupTypeProvider channelGroupTypeProvider;
98 private @Nullable SiemensHvacConfigDescriptionProvider configDescriptionProvider;
99 private @Nullable SiemensHvacConnector hvacConnector;
100 private @Nullable SiemensHvacMetadataUser user;
101 private @Nullable Locale userLocale;
103 private final HashMap<String, SiemensHvacMetadataUser> userList;
105 public SiemensHvacMetadataRegistryImpl() {
106 userList = new HashMap<String, SiemensHvacMetadataUser>();
110 protected void setSiemensHvacConnector(SiemensHvacConnector hvacConnector) {
111 this.hvacConnector = hvacConnector;
114 protected void unsetSiemensHvacConnector(SiemensHvacConnector hvacConnector) {
115 this.hvacConnector = null;
119 protected void setThingTypeProvider(SiemensHvacThingTypeProvider thingTypeProvider) {
120 this.thingTypeProvider = thingTypeProvider;
123 protected void unsetThingTypeProvider(SiemensHvacThingTypeProvider thingTypeProvider) {
124 this.thingTypeProvider = null;
128 protected void setChannelTypeProvider(SiemensHvacChannelTypeProvider channelTypeProvider) {
129 this.channelTypeProvider = channelTypeProvider;
132 protected void unsetChannelTypeProvider(SiemensHvacChannelTypeProvider channelTypeProvider) {
133 this.channelTypeProvider = null;
138 protected void setChannelGroupTypeProvider(SiemensHvacChannelGroupTypeProvider channelGroupTypeProvider) {
139 this.channelGroupTypeProvider = channelGroupTypeProvider;
142 protected void unsetChannelGroupTypeProvider(SiemensHvacChannelGroupTypeProvider channelGroupTypeProvider) {
143 this.channelGroupTypeProvider = null;
147 protected void setConfigDescriptionProvider(SiemensHvacConfigDescriptionProvider configDescriptionProvider) {
148 this.configDescriptionProvider = configDescriptionProvider;
151 protected void unsetConfigDescriptionProvider(SiemensHvacConfigDescriptionProvider configDescriptionProvider) {
152 this.configDescriptionProvider = null;
156 public @Nullable SiemensHvacConnector getSiemensHvacConnector() {
157 return this.hvacConnector;
161 public @Nullable SiemensHvacChannelTypeProvider getChannelTypeProvider() {
162 return this.channelTypeProvider;
166 public @Nullable ArrayList<SiemensHvacMetadataDevice> getDevices() {
171 * Initializes the type generator.
175 public void initialize() {
178 public void initDptMap(@Nullable SiemensHvacMetadata node) {
183 if (node.getClass() == SiemensHvacMetadataMenu.class) {
184 SiemensHvacMetadataMenu mInformation = (SiemensHvacMetadataMenu) node;
186 for (SiemensHvacMetadata child : mInformation.getChilds().values()) {
191 if (!node.getLongDesc().isEmpty()) {
192 dptMap.put("byName" + node.getLongDesc(), node);
194 if (!node.getShortDesc().isEmpty()) {
195 dptMap.put("byName" + node.getShortDesc(), node);
198 dptMap.put("byId" + node.getId(), node);
199 dptMap.put("bySubId" + node.getSubId(), node);
201 if (node.getClass() == SiemensHvacMetadataDataPoint.class) {
202 SiemensHvacMetadataDataPoint dpi = (SiemensHvacMetadataDataPoint) node;
203 dptMap.put("byDptId" + dpi.getDptId(), node);
208 private int resolveCount = 0;
210 public ResolveCount(int count) {
211 resolveCount = count;
214 public void decreaseResolveCount() {
218 public int getResolveCount() {
223 public void resolveDetails(int unresolveCountP) {
224 ResolveCount rv = new ResolveCount(unresolveCountP);
226 for (String key : dptMap.keySet()) {
227 if (key.indexOf("byId") < 0) {
231 SiemensHvacMetadata node = dptMap.get(key);
233 if (node.getClass() == SiemensHvacMetadataDataPoint.class) {
234 SiemensHvacMetadataDataPoint dpi = (SiemensHvacMetadataDataPoint) node;
235 if (!dpi.getDetailsResolved()) {
236 resolveDptDetails(dpi, rv);
243 public int unresolveCount() {
245 for (String key : dptMap.keySet()) {
246 if (key.indexOf("byId") < 0) {
250 SiemensHvacMetadata node = dptMap.get(key);
252 if (node instanceof SiemensHvacMetadataDataPoint dpi) {
253 if (!dpi.getDetailsResolved()) {
265 public @Nullable SiemensHvacMetadataMenu getRoot() {
266 return (SiemensHvacMetadataMenu) root;
270 public void readMeta() throws SiemensHvacException {
271 ArrayList<SiemensHvacMetadataDevice> lcDevices = devices;
272 SiemensHvacConnector lcHvacConnector = hvacConnector;
278 if (lcHvacConnector == null) {
279 logger.debug("SiemensHvacMetadataRegistryImpl:ReadMeta(): lHvacConnector not initialize.");
285 SiemensHvacBridgeConfig config = lcHvacConnector.getBridgeConfiguration();
286 if (config == null) {
287 throw new SiemensHvacException("@offline.config-not-init");
290 SiemensHvacMetadataUser lcUser = null;
292 String userName = config.userName;
293 if (userList.containsKey(userName)) {
294 lcUser = userList.get(userName);
297 if (lcUser == null) {
298 throw new SiemensHvacException("@offline.user-not-find");
301 Map<String, Locale> map = new HashMap<String, Locale>();
302 map.put("English", Locale.forLanguageTag("en-UK"));
303 map.put("Deutsch", Locale.forLanguageTag("de-DE"));
304 map.put("Francais", Locale.forLanguageTag("fr-FR"));
305 map.put("Italiano", Locale.forLanguageTag("it-IT"));
306 map.put("Nederlands", Locale.forLanguageTag("nl-NL"));
307 map.put("Polski", Locale.forLanguageTag("pl-PL"));
308 map.put("Ceski", Locale.forLanguageTag("cs-CZ"));
309 map.put("Magyar", Locale.forLanguageTag("hu-HU"));
310 map.put("Espagnol", Locale.forLanguageTag("es-ES"));
311 map.put("Dansk", Locale.forLanguageTag("da-DK"));
312 map.put("Svenska", Locale.forLanguageTag("sv-SE"));
313 map.put("Suomi", Locale.forLanguageTag("fi-FI"));
314 map.put("Portugues", Locale.forLanguageTag("pt-PT"));
315 map.put("Russkij", Locale.forLanguageTag("ru-RU"));
316 map.put("Turkce", Locale.forLanguageTag("tr-TR"));
317 map.put("Slovensky", Locale.forLanguageTag("sl-SV"));
320 if (map.containsKey(lcUser.getLanguage())) {
321 this.userLocale = map.get(lcUser.getLanguage());
323 this.userLocale = Locale.getDefault();
326 logger.trace("siemensHvac:Initialization():Begin_0001");
328 File folder = new File(JSON_DIR);
330 if (!folder.exists()) {
331 logger.debug("Creating directory {}", folder);
335 logger.trace("siemensHvac:Initialization():ReadCache");
336 loadMetaDataFromCache();
338 // increase the timeout during this phase
339 // because we queued a lot of request
340 // and timeout start to run when request is queued (not executed)
342 Instant start = Instant.now();
343 lcHvacConnector.setTimeOut(600);
345 logger.trace("siemensHvac:Initialization():ReadDeviceList");
349 logger.trace("siemensHvac:Initialization():No cache information, root==null, reading metadata from device");
351 logger.trace("siemensHvac:Initialization():BeginReadMenu");
352 root = new SiemensHvacMetadataMenu();
354 changeLanguage(lcUser, 1);
355 readMetaData(root, -1, false);
356 lcHvacConnector.waitNoNewRequest();
357 lcHvacConnector.waitAllPendingRequest();
359 changeLanguage(lcUser, lcUser.getLanguageId());
360 readMetaData(root, -1, true);
361 lcHvacConnector.waitNoNewRequest();
362 lcHvacConnector.waitAllPendingRequest();
364 logger.trace("siemensHvac:Initialization():EndReadMenu");
368 logger.trace("siemensHvac:Initialization():BeginInitDptMap");
370 logger.trace("siemensHvac:Initialization():EndInitDptMap");
373 int unresolveCount = unresolveCount();
375 while (unresolveCount > 0) {
376 logger.trace("siemensHvac:Initialization():BeginResolveDtpMap {}", unresolveCount);
377 resolveDetails(unresolveCount);
378 lcHvacConnector.waitAllPendingRequest();
379 unresolveCount = unresolveCount();
382 Instant end = Instant.now();
383 lcHvacConnector.setTimeOut(30);
385 long elapseTime = Duration.between(start, end).toSeconds();
386 logger.trace("siemensHvac:Initialization():ReadMetadata in {} s", elapseTime);
388 logger.trace("siemensHvac:Initialization():SaveCache");
389 saveMetaDataToCache();
391 logger.trace("siemensHvac:Initialization():InitThing");
394 if (lcDevices != null) {
395 for (SiemensHvacMetadataDevice device : lcDevices) {
396 generateThingsType(device);
400 logger.trace("siemensHvac:InitDptMap():end");
404 public @Nullable SiemensHvacMetadataUser getUser() {
409 public @Nullable Locale getUserLocale() {
413 private void generateThingsType(SiemensHvacMetadataDevice device) {
414 SiemensHvacThingTypeProvider lcThingTypeProvider = thingTypeProvider;
415 logger.debug("Generate thing types for device: {} / {}", device.getName(), device.getSerialNr());
416 if (lcThingTypeProvider != null) {
417 ThingTypeUID thingTypeUID = UidUtils.generateThingTypeUID(device);
420 tt = lcThingTypeProvider.getInternalThingType(thingTypeUID);
423 List<ChannelGroupType> groupTypes = new ArrayList<>();
425 int treeId = device.getTreeId();
426 if (dptMap.containsKey("byId" + treeId)) {
427 SiemensHvacMetadataMenu menu = (SiemensHvacMetadataMenu) dptMap.get("byId" + treeId);
430 var childs = menu.getChilds().values();
431 for (SiemensHvacMetadata child : childs) {
432 generateThingsType(child, groupTypes, menu);
439 tt = createThingType(device, groupTypes);
440 lcThingTypeProvider.addThingType(tt);
445 private void generateThingsType(SiemensHvacMetadata child, List<ChannelGroupType> groupTypes,
446 SiemensHvacMetadataMenu menu) {
447 SiemensHvacChannelTypeProvider lcChannelTypeProvider = channelTypeProvider;
448 SiemensHvacChannelGroupTypeProvider lcChannelGroupTypeProvider = channelGroupTypeProvider;
450 if (child instanceof SiemensHvacMetadataMenu subMenu) {
451 List<ChannelDefinition> channelDefinitions = new ArrayList<>();
453 for (SiemensHvacMetadata childDt : subMenu.getChilds().values()) {
456 if (childDt instanceof SiemensHvacMetadataMenu) {
457 generateThingsType(childDt, groupTypes, menu);
459 if (childDt instanceof SiemensHvacMetadataDataPoint metadataDataPoint) {
460 if (metadataDataPoint.getDptType().isEmpty()) {
464 ChannelTypeUID channelTypeUID = UidUtils.generateChannelTypeUID(metadataDataPoint);
466 ChannelType channelType = null;
468 if (channelTypeProvider != null && lcChannelTypeProvider != null) {
469 channelType = lcChannelTypeProvider.getInternalChannelType(channelTypeUID);
470 if (channelType == null) {
471 channelType = createChannelType(metadataDataPoint, channelTypeUID);
472 lcChannelTypeProvider.addChannelType(channelType);
476 Map<String, String> props = new Hashtable<String, String>();
477 props.put("dptId", "" + metadataDataPoint.getDptId());
478 props.put("id", "" + metadataDataPoint.getId());
479 props.put("subId", "" + metadataDataPoint.getSubId());
480 props.put("groupdId", "" + metadataDataPoint.getGroupId());
482 String id = metadataDataPoint.getId() + "-"
483 + UidUtils.sanetizeId(metadataDataPoint.getShortDesc());
485 if (channelType != null) {
486 ChannelDefinition channelDef = new ChannelDefinitionBuilder(id, channelType.getUID())
487 .withLabel(metadataDataPoint.getShortDesc())
488 .withDescription(metadataDataPoint.getLongDesc()).withProperties(props).build();
490 channelDefinitions.add(channelDef);
493 } catch (SiemensHvacException ex) {
494 logger.warn("Unable to create channel for: {}", childDt);
499 ChannelGroupTypeUID groupTypeUID = UidUtils.generateChannelGroupTypeUID(subMenu);
500 ChannelGroupType groupType = null;
502 if (lcChannelGroupTypeProvider != null) {
503 groupType = lcChannelGroupTypeProvider.getInternalChannelGroupType(groupTypeUID);
505 if (groupType == null) {
506 String groupLabel = subMenu.getShortDesc();
507 groupType = ChannelGroupTypeBuilder.instance(groupTypeUID, groupLabel)
508 .withChannelDefinitions(channelDefinitions).withCategory("")
509 .withDescription(menu.getLongDesc()).build();
510 lcChannelGroupTypeProvider.addChannelGroupType(groupType);
511 groupTypes.add(groupType);
518 private ChannelType createChannelType(SiemensHvacMetadataDataPoint dpt, ChannelTypeUID channelTypeUID)
519 throws SiemensHvacException {
520 ChannelType channelType;
522 String itemType = getItemType(dpt);
523 String category = getCategory(dpt);
524 String label = itemType;
525 String description = "";
527 StateDescriptionFragmentBuilder stateFragment = StateDescriptionFragmentBuilder.create();
529 List<StateOption> options = new ArrayList<StateOption>();
530 if (dpt.getDptType().equals(SiemensHvacBindingConstants.DPT_TYPE_ENUM)) {
531 StringBuilder descBuilder = new StringBuilder();
532 descBuilder.append("Enum:");
533 List<SiemensHvacMetadataPointChild> childs = dpt.getChild();
536 for (SiemensHvacMetadataPointChild opt : childs) {
537 StateOption stOpt = new StateOption(opt.getValue(), opt.getText());
540 descBuilder.append("_");
543 descBuilder.append(String.format("(%s:%s)", opt.getValue(), opt.getText()));
546 description = descBuilder.toString();
547 label = channelTypeUID.getId();
548 } else if (dpt.getDptType().equals(SiemensHvacBindingConstants.DPT_TYPE_RADIO)) {
549 StringBuilder descBuilder = new StringBuilder();
550 descBuilder.append("Radio:");
551 SiemensHvacMetadataPointChild child = dpt.getChild().get(0);
553 if (dpt.getWriteAccess()) {
554 StateOption stOpt0 = new StateOption("OFF", child.getOpt0());
555 descBuilder.append(String.format("(%s:%s)", "OFF", child.getOpt0()));
558 StateOption stOpt1 = new StateOption("ON", child.getOpt1());
559 descBuilder.append(String.format("(%s:%s)", "ON", child.getOpt1()));
562 StateOption stOpt0 = new StateOption("CLOSED", child.getOpt0());
563 descBuilder.append(String.format("(%s:%s)", "OFF", child.getOpt0()));
566 StateOption stOpt1 = new StateOption("OPEN", child.getOpt1());
567 descBuilder.append(String.format("(%s:%s)", "ON", child.getOpt1()));
571 description = descBuilder.toString();
572 label = channelTypeUID.getId();
573 } else if (dpt.getDptType().equals(SiemensHvacBindingConstants.DPT_TYPE_NUMERIC)) {
574 BigDecimal min = new BigDecimal(dpt.getMin());
575 BigDecimal max = new BigDecimal(dpt.getMax());
576 BigDecimal step = new BigDecimal(dpt.getResolution());
578 stateFragment = stateFragment.withPattern(getStatePattern(dpt)).withMinimum(min).withMaximum(max)
579 .withStep(step).withReadOnly(false);
581 description = channelTypeUID.toString();
582 label = channelTypeUID.getId();
584 stateFragment = stateFragment.withPattern(getStatePattern(dpt)).withReadOnly(!dpt.getWriteAccess());
587 if (!options.isEmpty()) {
588 stateFragment = stateFragment.withOptions(options);
591 boolean isAdvanced = false;
592 if (channelTypeUID.getId().contains("-y")) {
595 if (channelTypeUID.getId().contains("-k")) {
598 if (channelTypeUID.getId().contains("histo")) {
601 if (channelTypeUID.getId().contains("-qx")) {
605 final StateChannelTypeBuilder channelTypeBuilder = ChannelTypeBuilder.state(channelTypeUID, label, itemType)
606 .withStateDescriptionFragment(stateFragment.build());
608 channelType = channelTypeBuilder.isAdvanced(isAdvanced).withDescription(description).withCategory(category)
615 * Creates the ThingType for the given device.
617 private ThingType createThingType(SiemensHvacMetadataDevice device, List<ChannelGroupType> groupTypes) {
618 SiemensHvacConfigDescriptionProvider lcConfigDescriptionProvider = configDescriptionProvider;
619 String name = device.getName();
620 String description = device.getName();
622 List<String> supportedBridgeTypeUids = new ArrayList<>();
623 supportedBridgeTypeUids.add(SiemensHvacBindingConstants.THING_TYPE_OZW.toString());
624 ThingTypeUID thingTypeUID = UidUtils.generateThingTypeUID(device);
626 Map<String, String> properties = new HashMap<>();
627 properties.put(Thing.PROPERTY_VENDOR, SiemensHvacBindingConstants.PROPERTY_VENDOR_NAME);
628 properties.put(Thing.PROPERTY_MODEL_ID, device.getType());
630 URI configDescriptionURI = getConfigDescriptionURI(device);
631 if (lcConfigDescriptionProvider != null
632 && lcConfigDescriptionProvider.getInternalConfigDescription(configDescriptionURI) == null) {
633 generateConfigDescription(device, groupTypes, configDescriptionURI);
636 List<ChannelGroupDefinition> groupDefinitions = new ArrayList<>();
637 for (ChannelGroupType groupType : groupTypes) {
638 String id = groupType.getUID().getId();
639 groupDefinitions.add(new ChannelGroupDefinition(id, groupType.getUID()));
642 return ThingTypeBuilder.instance(thingTypeUID, name).withSupportedBridgeTypeUIDs(supportedBridgeTypeUids)
643 .withDescription(description).withChannelGroupDefinitions(groupDefinitions).withProperties(properties)
644 .withRepresentationProperty(Thing.PROPERTY_MODEL_ID).withConfigDescriptionURI(configDescriptionURI)
645 .withCategory(SiemensHvacBindingConstants.CATEGORY_THING_HVAC).build();
648 private URI getConfigDescriptionURI(SiemensHvacMetadataDevice device) {
649 return URI.create((String.format("%s:%s", SiemensHvacBindingConstants.CONFIG_DESCRIPTION_URI_THING_PREFIX,
650 UidUtils.generateThingTypeUID(device))));
653 private void generateConfigDescription(SiemensHvacMetadataDevice device, List<ChannelGroupType> groupTypes,
654 URI configDescriptionURI) {
655 SiemensHvacConfigDescriptionProvider lcConfigDescriptionProvider = configDescriptionProvider;
656 List<ConfigDescriptionParameter> parms = new ArrayList<>();
657 List<ConfigDescriptionParameterGroup> groups = new ArrayList<>();
659 if (lcConfigDescriptionProvider != null) {
660 lcConfigDescriptionProvider.addConfigDescription(ConfigDescriptionBuilder.create(configDescriptionURI)
661 .withParameters(parms).withParameterGroups(groups).build());
665 public String getItemType(SiemensHvacMetadataDataPoint dpt) throws SiemensHvacException {
667 TypeConverter tp = ConverterFactory.getConverter(dpt.getDptType());
668 return tp.getItemType(dpt);
669 } catch (ConverterTypeException ex) {
670 throw new SiemensHvacException(String.format("Can't find convertor for type: %s", dpt.getDptType()), ex);
675 * Determines the category for the given Datapoint.
677 public static String getCategory(SiemensHvacMetadataDataPoint dp) {
678 String dpType = dp.getDptType();
679 String dptUnit = dp.getDptUnit();
681 if (dptUnit == null) {
683 } else if (dptUnit.contains("°C")) {
684 return SiemensHvacBindingConstants.CATEGORY_CHANNEL_TEMP;
685 } else if (dptUnit.contains("°F")) {
686 return SiemensHvacBindingConstants.CATEGORY_CHANNEL_TEMP;
687 } else if (dpType.contains(SiemensHvacBindingConstants.DPT_TYPE_DATE_TIME)) {
688 return SiemensHvacBindingConstants.CATEGORY_CHANNEL_TIME;
689 } else if (dpType.contains(SiemensHvacBindingConstants.DPT_TYPE_TIMEOFDAY)) {
690 return SiemensHvacBindingConstants.CATEGORY_CHANNEL_TIME;
691 } else if (dpType.contains(SiemensHvacBindingConstants.DPT_TYPE_ENUM)) {
692 return SiemensHvacBindingConstants.CATEGORY_CHANNEL_SWITCH;
693 } else if (dpType.contains(SiemensHvacBindingConstants.DPT_TYPE_RADIO)) {
694 return SiemensHvacBindingConstants.CATEGORY_CHANNEL_SWITCH;
695 } else if (dpType.contains(SiemensHvacBindingConstants.DPT_TYPE_NUMERIC)) {
696 return SiemensHvacBindingConstants.CATEGORY_CHANNEL_NUMBER;
698 return SiemensHvacBindingConstants.CATEGORY_CHANNEL_CONTROL_HEATING;
703 * Returns the state pattern metadata string with unit for the given Datapoint.
705 public static String getStatePattern(SiemensHvacMetadataDataPoint dpt) {
706 String unit = dpt.getDptUnit();
708 if ("%".equals(unit)) {
713 if (dpt.getDptType().equals(SiemensHvacBindingConstants.DPT_TYPE_NUMERIC)) {
714 String digitSt = dpt.getDecimalDigits();
715 if (digitSt != null && !"".equals(digitSt)) {
716 digits = Integer.parseInt(digitSt);
720 if (unit != null && !unit.isEmpty()) {
721 return String.format("%s %s", "%." + digits + "f", "%unit%");
723 return String.format("%s", "%." + digits + "f");
727 public void readUserInfo() throws SiemensHvacException {
728 SiemensHvacConnector lcHvacConnector = hvacConnector;
729 String request = "main.app?section=settings&subsection=user";
731 if (lcHvacConnector != null) {
732 String response = lcHvacConnector.doBasicRequest(request);
734 if (response != null) {
735 String st = response;
736 st = st.replace("\n", "");
738 Pattern pattern1 = Pattern.compile("table class=\\\"user_table\\\".*?>(.*?)<\\/table>");
739 Matcher matcher1 = pattern1.matcher(st);
741 if (matcher1.find()) {
742 String userTable = matcher1.group(1);
744 Pattern pattern2 = Pattern.compile("<tr.*?>(.*?)<\\/tr>");
745 Matcher matcher2 = pattern2.matcher(userTable);
748 while (matcher2.find()) {
749 String line = matcher2.group(1);
752 Pattern pattern3 = Pattern.compile("<td(.*?)>(.*?)<\\/td>");
753 Matcher matcher3 = pattern3.matcher(line);
756 String userName = "";
757 String userEdit = "";
759 while (matcher3.find()) {
760 String cell = matcher3.group(2);
761 String header = matcher3.group(1);
765 } else if (idxCell == 5) {
771 if ("".equals(userName)) {
775 Pattern pattern4 = Pattern.compile("userid=(.+?)");
776 Matcher matcher4 = pattern4.matcher(userEdit);
778 SiemensHvacMetadataUser user = new SiemensHvacMetadataUser();
779 user.setName(userName);
781 if (matcher4.find()) {
782 userId = matcher4.group(1);
783 user.setId(Integer.parseInt(userId));
789 request = "main.app?section=settings&subsection=user&action=modify";
790 if (userId != null) {
791 request = request + "&userid=" + userId;
793 response = lcHvacConnector.doBasicRequest(request);
795 Pattern pattern5 = Pattern.compile("<select name=\\\"language\\\".*>((.*|\\n)*?)</select>",
797 Matcher matcher5 = pattern5.matcher(response);
799 if (matcher5.find()) {
800 String optionsList = matcher5.group(1);
802 Pattern pattern6 = java.util.regex.Pattern
803 .compile("<option value=\\\"([^ ]*)\\\"(.*)>(.*)</option>", Pattern.MULTILINE);
804 Matcher matcher6 = pattern6.matcher(optionsList);
806 while (matcher6.find()) {
807 String id = matcher6.group(1);
808 String opt = matcher6.group(2);
809 String lang = matcher6.group(3);
811 if (opt.indexOf("selected") >= 0) {
812 user.setLanguage(lang);
813 user.setLanguageId(Integer.parseInt(id));
818 userList.put(userName, user);
829 public void changeLanguage(SiemensHvacMetadataUser user, int lang) {
831 SiemensHvacConnector lcHvacConnector = hvacConnector;
832 String request = "main.app?section=settings&subsection=user&action=modify";
833 if (user.getId() != -1) {
834 request = request + "&userid=" + user.getId();
836 request = request + "&language=" + lang + "&submit=OK";
837 if (lcHvacConnector != null) {
838 lcHvacConnector.doBasicRequest(request);
839 lcHvacConnector.resetSessionId(null, false);
840 lcHvacConnector.resetSessionId(null, true);
846 logger.error("siemensHvac:ResolveDpt:Error during dp reading: {}", e.getLocalizedMessage());
847 // Reset sessionId so we redone _auth on error
851 public void readDeviceList() {
853 SiemensHvacConnector lcHvacConnector = hvacConnector;
854 ArrayList<SiemensHvacMetadataDevice> lcDevices = devices;
856 lcDevices = new ArrayList<SiemensHvacMetadataDevice>();
858 String request = "api/devicelist/list.json?";
860 JsonObject response = null;
861 if (lcHvacConnector != null) {
862 response = lcHvacConnector.doRequest(request);
864 JsonArray devicesList = null;
865 if (response != null) {
866 devicesList = response.getAsJsonArray("Devices");
869 if (devicesList == null) {
873 for (JsonElement device : devicesList) {
875 JsonObject obj = (JsonObject) device;
879 String serialNr = "";
880 String treeDate = "";
881 String treeTime = "";
882 boolean treeGenerated = false;
884 if (obj.has("Name")) {
885 name = obj.get("Name").getAsString();
888 if (obj.has("Addr")) {
889 addr = obj.get("Addr").getAsString();
892 if (obj.has("Type")) {
893 type = obj.get("Type").getAsString();
896 if (obj.has("SerialNr")) {
897 serialNr = obj.get("SerialNr").getAsString();
900 if (obj.has("TreeDate")) {
901 treeDate = obj.get("TreeDate").getAsString();
904 if (obj.has("TreeTime")) {
905 treeTime = obj.get("TreeTime").getAsString();
908 if (obj.has("TreeGenerated")) {
909 treeGenerated = obj.get("TreeGenerated").getAsBoolean();
912 SiemensHvacMetadataDevice deviceObj = new SiemensHvacMetadataDevice();
913 deviceObj.setName(name);
914 deviceObj.setAddr(addr);
915 deviceObj.setSerialNr(serialNr);
916 deviceObj.setType(type);
917 deviceObj.setTreeDate(treeDate);
918 deviceObj.setTreeTime(treeTime);
919 deviceObj.setTreeGenerated(treeGenerated);
921 String request2 = "api/menutree/device_root.json?TreeName=Web&SerialNumber=" + serialNr;
922 if (lcHvacConnector != null) {
923 JsonObject response2 = lcHvacConnector.doRequest(request2);
925 if (response2 != null && response2.has("TreeItem")) {
926 JsonObject tree = response2.getAsJsonObject("TreeItem");
927 if (tree.has("Id")) {
928 int treeId = tree.get("Id").getAsInt();
929 deviceObj.setTreeId(treeId);
934 lcDevices.add(deviceObj);
937 } catch (Exception e) {
938 logger.error("siemensHvac:ResolveDpt:Error during dp reading: {}", e.getLocalizedMessage());
939 // Reset sessionId so we redone _auth on error
943 public void readMetaData(@Nullable SiemensHvacMetadata parent, int id, boolean localized) {
944 SiemensHvacConnector lcHvacConnector = hvacConnector;
945 String request = "api/menutree/list.json?";
947 request = request + "&Id=" + id;
950 if (lcHvacConnector != null) {
951 lcHvacConnector.doRequest(request, new SiemensHvacCallback() {
954 public void execute(URI uri, int status, @Nullable Object response) {
955 logger.debug("response for {}, status {}:", uri, status);
956 if (response instanceof JsonObject jsonResponse) {
957 decodeMetaDataResult(jsonResponse, parent, id, localized);
959 logger.debug("error status {}: {}", uri, status);
966 public void decodeMetaDataResult(JsonObject resultObj, @Nullable SiemensHvacMetadata parent, int id,
968 SiemensHvacConnector lcHvacConnector = hvacConnector;
969 if (resultObj.has("MenuItems")) {
970 if (parent != null) {
971 logger.debug("Decode menuItem for: {}", parent.getShortDesc());
973 SiemensHvacMetadata childNode;
974 JsonArray menuItems = resultObj.getAsJsonArray("MenuItems");
976 for (JsonElement child : menuItems) {
977 JsonObject menuItem = child.getAsJsonObject();
980 if (menuItem.has("Id")) {
981 itemId = menuItem.get("Id").getAsInt();
984 SiemensHvacMetadataMenu menu = (SiemensHvacMetadataMenu) parent;
986 if (menu.hasChild(itemId)) {
987 childNode = menu.getChild(itemId);
989 childNode = new SiemensHvacMetadataMenu();
990 childNode.setId(itemId);
991 childNode.setParent(parent);
993 if (parent != null) {
994 menu.addChild(childNode);
998 if (menuItem.has("Text")) {
999 JsonObject descObj = menuItem.getAsJsonObject("Text");
1004 String longDesc = "";
1005 String shortDesc = "";
1007 if (descObj.has("CatId")) {
1008 catId = descObj.get("CatId").getAsInt();
1010 if (descObj.has("GroupId")) {
1011 groupId = descObj.get("GroupId").getAsInt();
1013 if (descObj.has("Id")) {
1014 subItemId = descObj.get("Id").getAsInt();
1017 if (descObj.has("Long")) {
1018 longDesc = descObj.get("Long").getAsString();
1020 if (descObj.has("Short")) {
1021 shortDesc = descObj.get("Short").getAsString();
1024 childNode.setSubId(subItemId);
1025 childNode.setCatId(catId);
1026 childNode.setGroupId(groupId);
1028 childNode.setShortDescEn(shortDesc);
1029 childNode.setLongDescEn(longDesc);
1031 childNode.setShortDesc(shortDesc);
1032 childNode.setLongDesc(longDesc);
1035 readMetaData(childNode, itemId, localized);
1040 if (resultObj.has("DatapointItems"))
1043 if (parent != null) {
1044 logger.debug("Decode dp for: {}", parent.getShortDesc());
1047 SiemensHvacMetadata childNode;
1048 JsonArray dptItems = resultObj.getAsJsonArray("DatapointItems");
1050 Map<String, SiemensHvacMetadataDataPoint> idMap = new Hashtable<String, SiemensHvacMetadataDataPoint>();
1052 for (JsonElement child : dptItems) {
1053 JsonObject dptItem = child.getAsJsonObject();
1057 boolean hasWriteAccess = false;
1058 String address = "";
1060 if (dptItem.has("Id")) {
1061 nodeId = dptItem.get("Id").getAsInt();
1064 SiemensHvacMetadataMenu menu = (SiemensHvacMetadataMenu) parent;
1066 if (menu.hasChild(nodeId)) {
1067 childNode = menu.getChild(nodeId);
1069 childNode = new SiemensHvacMetadataDataPoint();
1070 childNode.setId(nodeId);
1071 childNode.setParent(parent);
1073 menu.addChild(childNode);
1076 if (dptItem.has("Address")) {
1077 address = dptItem.get("Address").getAsString();
1079 if (dptItem.has("DpSubKey")) {
1080 dpSubKey = dptItem.get("DpSubKey").getAsInt();
1082 if (dptItem.has("WriteAccess")) {
1083 hasWriteAccess = dptItem.get("WriteAccess").getAsBoolean();
1086 SiemensHvacMetadataDataPoint dptChild = (SiemensHvacMetadataDataPoint) childNode;
1088 dptChild.setId(nodeId);
1089 dptChild.setAddress(address);
1090 dptChild.setDptSubKey(dpSubKey);
1091 dptChild.setWriteAccess(hasWriteAccess);
1093 idMap.put("" + nodeId, dptChild);
1095 if (dptItem.has("Text")) {
1096 JsonObject descObj = dptItem.getAsJsonObject("Text");
1101 String longDesc = "";
1102 String shortDesc = "";
1104 if (descObj.has("CatId")) {
1105 catId = descObj.get("CatId").getAsInt();
1107 if (descObj.has("GroupId")) {
1108 groupId = descObj.get("GroupId").getAsInt();
1110 if (descObj.has("Id")) {
1111 subItemId = descObj.get("Id").getAsInt();
1113 if (descObj.has("Long")) {
1114 longDesc = descObj.get("Long").getAsString();
1116 if (descObj.has("Short")) {
1117 shortDesc = descObj.get("Short").getAsString();
1120 childNode.setSubId(subItemId);
1121 childNode.setCatId(catId);
1122 childNode.setGroupId(groupId);
1125 childNode.setShortDescEn(shortDesc);
1126 childNode.setLongDescEn(longDesc);
1128 childNode.setShortDesc(shortDesc);
1129 childNode.setLongDesc(longDesc);
1135 String request2 = "main.app?section=popcard&idtype=4";
1137 request2 = request2 + "&id=" + id;
1140 if (lcHvacConnector != null) {
1141 lcHvacConnector.doRequest(request2, new SiemensHvacCallback() {
1144 public void execute(URI uri, int status, @Nullable Object response) {
1145 if (response != null) {
1146 String st = (String) response;
1147 st = st.replace("\n", "");
1149 Pattern pattern = Pattern
1150 .compile("td class=\\\"dp_linenumber\\\".*?>(.*?)<\\/td>.+?(?=id)id=\"dp(.+?)\"");
1151 Matcher matcher = pattern.matcher(st);
1153 while (matcher.find()) {
1154 String id = matcher.group(2);
1155 String dptId = matcher.group(1);
1157 if (id != null && dptId != null && !id.isEmpty() && !dptId.isEmpty()) {
1158 if (idMap.containsKey(id)) {
1159 SiemensHvacMetadataDataPoint child = idMap.get(id);
1160 if (child != null) {
1161 child.setDptId(dptId);
1176 public @Nullable SiemensHvacMetadata getDptMap(@Nullable String key) {
1181 if (dptMap.containsKey("byMenu" + key)) {
1182 return dptMap.get("byMenu" + key);
1184 if (dptMap.containsKey("byName" + key)) {
1185 return dptMap.get("byName" + key);
1187 if (dptMap.containsKey("byDptId" + key)) {
1188 return dptMap.get("byDptId" + key);
1190 if (dptMap.containsKey("byId" + key)) {
1191 return dptMap.get("byId" + key);
1197 public void loadMetaDataFromCache() {
1198 SiemensHvacConnector lcHvacConnector = hvacConnector;
1202 file = new File(JSON_DIR + File.separator + "siemens.json");
1204 if (!file.exists()) {
1208 byte[] bytes = Files.readAllBytes(file.toPath());
1209 String js = new String(bytes, StandardCharsets.UTF_8);
1211 if (lcHvacConnector != null) {
1212 root = lcHvacConnector.getGsonWithAdapter().fromJson(js, SiemensHvacMetadataMenu.class);
1214 } catch (IOException ioe) {
1215 logger.warn("Couldn't read Siemens MetaData information from file '{}'.", file.getAbsolutePath());
1220 public void saveMetaDataToCache() {
1221 SiemensHvacConnector lcHvacConnector = hvacConnector;
1225 file = new File(JSON_DIR + File.separator + "siemens.json");
1227 if (!file.exists()) {
1228 file.getParentFile().mkdirs();
1229 file.createNewFile();
1232 try (FileOutputStream os = new FileOutputStream(file)) {
1233 if (lcHvacConnector != null) {
1234 String js = lcHvacConnector.getGsonWithAdapter().toJson(root);
1236 byte[] bt = js.getBytes();
1242 } catch (IOException ioe) {
1243 logger.warn("Couldn't write Siemens MetaData information to file '{}'.", file.getAbsolutePath());
1248 public void resolveDptDetails(SiemensHvacMetadataDataPoint dpt, ResolveCount rv) {
1249 SiemensHvacConnector lcHvacConnector = hvacConnector;
1250 if (dpt.getDetailsResolved()) {
1254 String request = "api/menutree/datapoint_desc.json?Id=" + dpt.getId();
1255 if (lcHvacConnector != null) {
1256 lcHvacConnector.doRequest(request, new SiemensHvacCallback() {
1259 public void execute(URI uri, int status, @Nullable Object response) {
1260 if (response instanceof JsonObject) {
1261 rv.decreaseResolveCount();
1262 logger.debug("siemensHvac:Initialization():ToResolve() {}", rv.getResolveCount());
1263 dpt.resolveDptDetails((JsonObject) response);
1265 logger.debug("Invalid response from Siemens gateway, result is not a JsonObject");
1273 public void invalidate() {
1275 SiemensHvacConnector lcHavConnector = hvacConnector;
1276 SiemensHvacChannelGroupTypeProvider lcChannelGroupTypeProvider = channelGroupTypeProvider;
1277 SiemensHvacThingTypeProvider lcThingTypeProvider = thingTypeProvider;
1278 SiemensHvacChannelTypeProvider lcChannelTypeProvider = channelTypeProvider;
1279 SiemensHvacConfigDescriptionProvider lcConfigDescriptionProvider = configDescriptionProvider;
1281 if (lcHavConnector != null) {
1282 lcHavConnector.invalidate();
1285 if (lcChannelGroupTypeProvider != null) {
1286 lcChannelGroupTypeProvider.invalidate();
1289 if (lcThingTypeProvider != null) {
1290 lcThingTypeProvider.invalidate();
1293 if (lcChannelTypeProvider != null) {
1294 lcChannelTypeProvider.invalidate();
1297 if (lcConfigDescriptionProvider != null) {
1298 lcConfigDescriptionProvider.invalidate();