]> git.basschouten.com Git - openhab-addons.git/blob
6c2a7e18370734424c594b3154364d750519cdd7
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.insteon.internal.device;
14
15 import java.util.HashMap;
16 import java.util.Map;
17
18 import org.eclipse.jdt.annotation.NonNullByDefault;
19 import org.eclipse.jdt.annotation.Nullable;
20 import org.openhab.binding.insteon.internal.InsteonResourceLoader;
21 import org.openhab.binding.insteon.internal.utils.HexUtils;
22 import org.w3c.dom.Element;
23 import org.w3c.dom.Node;
24 import org.w3c.dom.NodeList;
25 import org.xml.sax.SAXException;
26
27 /**
28  * The {@link ProductDataRegistry} represents the product data registry
29  *
30  * @author Jeremy Setton - Initial contribution
31  */
32 @NonNullByDefault
33 public class ProductDataRegistry extends InsteonResourceLoader {
34     private static final ProductDataRegistry PRODUCT_DATA_REGISTRY = new ProductDataRegistry();
35     private static final String RESOURCE_NAME = "/device-products.xml";
36
37     private Map<Integer, ProductData> products = new HashMap<>();
38
39     private ProductDataRegistry() {
40         super(RESOURCE_NAME);
41     }
42
43     /**
44      * Returns the product data for a given dev/sub category
45      *
46      * @param deviceCategory device category to match
47      * @param subCategory device subcategory to match
48      * @return product data matching provided parameters
49      */
50     public ProductData getProductData(int deviceCategory, int subCategory) {
51         int productId = getProductId(deviceCategory, subCategory);
52         if (!products.containsKey(productId)) {
53             logger.warn("unknown product for devCat:{} subCat:{} in device products xml file",
54                     HexUtils.getHexString(deviceCategory), HexUtils.getHexString(subCategory));
55             // fallback to matching product id using device category only
56             productId = getProductId(deviceCategory, ProductData.SUB_CATEGORY_UNKNOWN);
57         }
58
59         return ProductData.makeInsteonProduct(deviceCategory, subCategory, products.get(productId));
60     }
61
62     /**
63      * Returns the device type for a given dev/sub category
64      *
65      * @param deviceCategory device category to match
66      * @param subCategory device subcategory to match
67      * @return device type matching provided parameters
68      */
69     public @Nullable DeviceType getDeviceType(int deviceCategory, int subCategory) {
70         return getProductData(deviceCategory, subCategory).getDeviceType();
71     }
72
73     /**
74      * Returns product id based on dev/sub category
75      *
76      * @param deviceCategory device category to use
77      * @param subCategory device subcategory to use
78      * @return product key
79      */
80     private int getProductId(int deviceCategory, int subCategory) {
81         return deviceCategory << 8 | subCategory;
82     }
83
84     /**
85      * Returns known products
86      *
87      * @return currently known products
88      */
89     public Map<Integer, ProductData> getProducts() {
90         return products;
91     }
92
93     /**
94      * Initializes product data registry
95      */
96     @Override
97     protected void initialize() {
98         super.initialize();
99
100         logger.debug("loaded {} products", products.size());
101         if (logger.isTraceEnabled()) {
102             products.values().stream().map(String::valueOf).forEach(logger::trace);
103         }
104     }
105
106     /**
107      * Parses product data document
108      *
109      * @param element element to parse
110      * @throws SAXException
111      */
112     @Override
113     protected void parseDocument(Element element) throws SAXException {
114         NodeList nodes = element.getChildNodes();
115         for (int i = 0; i < nodes.getLength(); i++) {
116             Node node = nodes.item(i);
117             if (node.getNodeType() == Node.ELEMENT_NODE) {
118                 Element child = (Element) node;
119                 String nodeName = child.getNodeName();
120                 if ("product".equals(nodeName)) {
121                     parseProduct(child);
122                 }
123             }
124         }
125     }
126
127     /**
128      * Parses product node
129      *
130      * @param element element to parse
131      * @throws SAXException
132      */
133     private void parseProduct(Element element) throws SAXException {
134         int deviceCategory = getHexAttributeAsInteger(element, "devCat", ProductData.DEVICE_CATEGORY_UNKNOWN);
135         int subCategory = getHexAttributeAsInteger(element, "subCat", ProductData.SUB_CATEGORY_UNKNOWN);
136         int productKey = getHexAttributeAsInteger(element, "productKey", 0);
137         int firstRecord = getHexAttributeAsInteger(element, "firstRecord", 0);
138         if (deviceCategory == ProductData.DEVICE_CATEGORY_UNKNOWN) {
139             throw new SAXException("invalid product with no device category in device products xml file");
140         }
141         int productId = getProductId(deviceCategory, subCategory);
142         if (products.containsKey(productId)) {
143             logger.warn("overwriting previous definition of product {}", products.get(productId));
144         }
145
146         ProductData productData = ProductData.makeInsteonProduct(deviceCategory, subCategory);
147         productData.setProductKey(productKey);
148         productData.setFirstRecordLocation(firstRecord);
149
150         NodeList nodes = element.getChildNodes();
151         for (int i = 0; i < nodes.getLength(); i++) {
152             Node node = nodes.item(i);
153             if (node.getNodeType() == Node.ELEMENT_NODE) {
154                 Element child = (Element) node;
155                 String nodeName = child.getNodeName();
156                 String textContent = child.getTextContent();
157                 if ("description".equals(nodeName)) {
158                     productData.setDescription(textContent);
159                 } else if ("model".equals(nodeName)) {
160                     productData.setModel(textContent);
161                 } else if ("vendor".equals(nodeName)) {
162                     productData.setVendor(textContent);
163                 } else if ("device-type".equals(nodeName)) {
164                     parseDeviceType(child, productData);
165                 }
166             }
167         }
168         products.put(productId, productData);
169     }
170
171     /**
172      * Parses product device type element
173      *
174      * @param element element to parse
175      * @param productData product data to update
176      * @throws SAXException
177      */
178     private void parseDeviceType(Element element, ProductData productData) throws SAXException {
179         String deviceType = element.getTextContent();
180         if (deviceType == null) {
181             return; // undefined device type
182         }
183         if (DeviceTypeRegistry.getInstance().getDeviceType(deviceType) == null) {
184             throw new SAXException("invalid device type " + deviceType + " in device products xml file");
185         }
186         productData.setDeviceType(deviceType);
187     }
188
189     /**
190      * Singleton instance function
191      *
192      * @return ProductDataRegistry singleton reference
193      */
194     public static synchronized ProductDataRegistry getInstance() {
195         if (PRODUCT_DATA_REGISTRY.getProducts().isEmpty()) {
196             PRODUCT_DATA_REGISTRY.initialize();
197         }
198         return PRODUCT_DATA_REGISTRY;
199     }
200 }