]> git.basschouten.com Git - openhab-addons.git/blob
522a24563c41b54e7b98b126e0b8941b1dc5945b
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.persistence.rrd4j.internal.charts;
14
15 import java.awt.Color;
16 import java.awt.Font;
17 import java.awt.image.BufferedImage;
18 import java.io.File;
19 import java.io.IOException;
20 import java.util.Date;
21 import java.util.HashMap;
22 import java.util.Hashtable;
23 import java.util.Map;
24 import java.util.Objects;
25
26 import javax.imageio.ImageIO;
27 import javax.servlet.Servlet;
28 import javax.servlet.ServletConfig;
29 import javax.servlet.ServletException;
30 import javax.servlet.ServletRequest;
31 import javax.servlet.ServletResponse;
32
33 import org.openhab.core.items.GroupItem;
34 import org.openhab.core.items.Item;
35 import org.openhab.core.items.ItemNotFoundException;
36 import org.openhab.core.library.items.NumberItem;
37 import org.openhab.core.ui.chart.ChartProvider;
38 import org.openhab.core.ui.items.ItemUIRegistry;
39 import org.openhab.persistence.rrd4j.internal.RRD4jPersistenceService;
40 import org.osgi.service.component.annotations.Activate;
41 import org.osgi.service.component.annotations.Component;
42 import org.osgi.service.component.annotations.Deactivate;
43 import org.osgi.service.component.annotations.Reference;
44 import org.osgi.service.http.HttpService;
45 import org.osgi.service.http.NamespaceException;
46 import org.rrd4j.ConsolFun;
47 import org.rrd4j.core.RrdDb;
48 import org.rrd4j.graph.RrdGraph;
49 import org.rrd4j.graph.RrdGraphDef;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
52
53 /**
54  * This servlet generates time-series charts for a given set of items.
55  * It accepts the following HTTP parameters:
56  * <ul>
57  * <li>w: width in pixels of image to generate</li>
58  * <li>h: height in pixels of image to generate</li>
59  * <li>period: the time span for the x-axis. Value can be h,4h,8h,12h,D,3D,W,2W,M,2M,4M,Y</li>
60  * <li>items: A comma separated list of item names to display
61  * <li>groups: A comma separated list of group names, whose members should be displayed
62  * </ul>
63  *
64  * @author Kai Kreuzer - Initial contribution
65  * @author Chris Jackson - a few improvements
66  * @author Jan N. Klug - a few improvements
67  *
68  */
69 @Component(service = ChartProvider.class)
70 public class RRD4jChartServlet implements Servlet, ChartProvider {
71
72     private final Logger logger = LoggerFactory.getLogger(RRD4jChartServlet.class);
73
74     /** the URI of this servlet */
75     public static final String SERVLET_NAME = "/rrdchart.png";
76
77     protected static final Color[] LINECOLORS = new Color[] { Color.RED, Color.GREEN, Color.BLUE, Color.MAGENTA,
78             Color.ORANGE, Color.CYAN, Color.PINK, Color.DARK_GRAY, Color.YELLOW };
79     protected static final Color[] AREACOLORS = new Color[] { new Color(255, 0, 0, 30), new Color(0, 255, 0, 30),
80             new Color(0, 0, 255, 30), new Color(255, 0, 255, 30), new Color(255, 128, 0, 30),
81             new Color(0, 255, 255, 30), new Color(255, 0, 128, 30), new Color(255, 128, 128, 30),
82             new Color(255, 255, 0, 30) };
83
84     protected static final Map<String, Long> PERIODS = new HashMap<>();
85
86     static {
87         PERIODS.put("h", -3600000L);
88         PERIODS.put("4h", -14400000L);
89         PERIODS.put("8h", -28800000L);
90         PERIODS.put("12h", -43200000L);
91         PERIODS.put("D", -86400000L);
92         PERIODS.put("3D", -259200000L);
93         PERIODS.put("W", -604800000L);
94         PERIODS.put("2W", -1209600000L);
95         PERIODS.put("M", -2592000000L);
96         PERIODS.put("2M", -5184000000L);
97         PERIODS.put("4M", -10368000000L);
98         PERIODS.put("Y", -31536000000L);
99     }
100
101     @Reference
102     protected HttpService httpService;
103
104     @Reference
105     protected ItemUIRegistry itemUIRegistry;
106
107     @Activate
108     protected void activate() {
109         try {
110             logger.debug("Starting up rrd chart servlet at {}", SERVLET_NAME);
111             httpService.registerServlet(SERVLET_NAME, this, new Hashtable<>(), httpService.createDefaultHttpContext());
112         } catch (NamespaceException e) {
113             logger.error("Error during servlet startup", e);
114         } catch (ServletException e) {
115             logger.error("Error during servlet startup", e);
116         }
117     }
118
119     @Deactivate
120     protected void deactivate() {
121         httpService.unregister(SERVLET_NAME);
122     }
123
124     @Override
125     public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
126         logger.debug("RRD4J received incoming chart request: {}", req);
127
128         int width = 480;
129         try {
130             width = Integer.parseInt(Objects.requireNonNull(req.getParameter("w")));
131         } catch (Exception e) {
132         }
133         int height = 240;
134         try {
135             height = Integer.parseInt(Objects.requireNonNull(req.getParameter("h")));
136         } catch (Exception e) {
137         }
138         Long period = PERIODS.get(req.getParameter("period"));
139         if (period == null) {
140             // use a day as the default period
141             period = PERIODS.get("D");
142         }
143         // Create the start and stop time
144         Date timeEnd = new Date();
145         Date timeBegin = new Date(timeEnd.getTime() + period);
146
147         // Set the content type to that provided by the chart provider
148         res.setContentType("image/" + getChartType());
149         try {
150             BufferedImage chart = createChart(null, null, timeBegin, timeEnd, height, width, req.getParameter("items"),
151                     req.getParameter("groups"), null, null);
152             ImageIO.write(chart, getChartType().toString(), res.getOutputStream());
153         } catch (ItemNotFoundException e) {
154             logger.debug("Item not found error while generating chart.");
155         } catch (IllegalArgumentException e) {
156             logger.debug("Illegal argument in chart", e);
157         }
158     }
159
160     /**
161      * Adds a line for the item to the graph definition.
162      * The color of the line is determined by the counter, it simply picks the according index from LINECOLORS (and
163      * rolls over if necessary).
164      *
165      * @param graphDef the graph definition to fill
166      * @param item the item to add a line for
167      * @param counter defines the number of the datasource and is used to determine the line color
168      */
169     protected void addLine(RrdGraphDef graphDef, Item item, int counter) {
170         Color color = LINECOLORS[counter % LINECOLORS.length];
171         String label = itemUIRegistry.getLabel(item.getName());
172         String rrdName = RRD4jPersistenceService.DB_FOLDER + File.separator + item.getName() + ".rrd";
173         ConsolFun consolFun;
174         if (label != null && label.contains("[") && label.contains("]")) {
175             label = label.substring(0, label.indexOf('['));
176         }
177         try {
178             RrdDb db = new RrdDb(rrdName);
179             consolFun = db.getRrdDef().getArcDefs()[0].getConsolFun();
180             db.close();
181         } catch (IOException e) {
182             consolFun = ConsolFun.MAX;
183         }
184         if (item instanceof NumberItem) {
185             // we only draw a line
186             graphDef.datasource(Integer.toString(counter), rrdName, "state", consolFun); // RRD4jService.getConsolidationFunction(item));
187             graphDef.line(Integer.toString(counter), color, label, 2);
188         } else {
189             // we draw a line and fill the area beneath it with a transparent color
190             graphDef.datasource(Integer.toString(counter), rrdName, "state", consolFun); // RRD4jService.getConsolidationFunction(item));
191             Color areaColor = AREACOLORS[counter % LINECOLORS.length];
192
193             graphDef.area(Integer.toString(counter), areaColor);
194             graphDef.line(Integer.toString(counter), color, label, 2);
195         }
196     }
197
198     @Override
199     public void init(ServletConfig config) throws ServletException {
200     }
201
202     @Override
203     public ServletConfig getServletConfig() {
204         return null;
205     }
206
207     @Override
208     public String getServletInfo() {
209         return null;
210     }
211
212     @Override
213     public void destroy() {
214     }
215
216     // ----------------------------------------------------------
217     // The following methods implement the ChartServlet interface
218
219     @Override
220     public String getName() {
221         return "rrd4j";
222     }
223
224     @Override
225     public BufferedImage createChart(String service, String theme, Date startTime, Date endTime, int height, int width,
226             String items, String groups, Integer dpi, Boolean legend) throws ItemNotFoundException {
227         RrdGraphDef graphDef = new RrdGraphDef();
228
229         long period = (startTime.getTime() - endTime.getTime()) / 1000;
230
231         graphDef.setWidth(width);
232         graphDef.setHeight(height);
233         graphDef.setAntiAliasing(true);
234         graphDef.setImageFormat("PNG");
235         graphDef.setStartTime(period);
236         graphDef.setTextAntiAliasing(true);
237         graphDef.setLargeFont(new Font("SansSerif", Font.PLAIN, 15));
238         graphDef.setSmallFont(new Font("SansSerif", Font.PLAIN, 11));
239
240         int seriesCounter = 0;
241
242         // Loop through all the items
243         if (items != null) {
244             String[] itemNames = items.split(",");
245             for (String itemName : itemNames) {
246                 Item item = itemUIRegistry.getItem(itemName);
247                 addLine(graphDef, item, seriesCounter++);
248             }
249         }
250
251         // Loop through all the groups and add each item from each group
252         if (groups != null) {
253             String[] groupNames = groups.split(",");
254             for (String groupName : groupNames) {
255                 Item item = itemUIRegistry.getItem(groupName);
256                 if (item instanceof GroupItem) {
257                     GroupItem groupItem = (GroupItem) item;
258                     for (Item member : groupItem.getMembers()) {
259                         addLine(graphDef, member, seriesCounter++);
260                     }
261                 } else {
262                     throw new ItemNotFoundException("Item '" + item.getName() + "' defined in groups is not a group.");
263                 }
264             }
265         }
266
267         // Write the chart as a PNG image
268         RrdGraph graph;
269         try {
270             graph = new RrdGraph(graphDef);
271             BufferedImage bi = new BufferedImage(graph.getRrdGraphInfo().getWidth(),
272                     graph.getRrdGraphInfo().getHeight(), BufferedImage.TYPE_INT_RGB);
273             graph.render(bi.getGraphics());
274
275             return bi;
276         } catch (IOException e) {
277             logger.error("Error generating graph.", e);
278         }
279
280         return null;
281     }
282
283     @Override
284     public ImageType getChartType() {
285         return ImageType.png;
286     }
287 }