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