001/* 002 * geordi 003 * 004 * Copyright (C) 2018 Richard "Shred" Körber 005 * https://github.com/shred/geordi 006 * 007 * This program is free software: you can redistribute it and/or modify 008 * it under the terms of the GNU General Public License as 009 * published by the Free Software Foundation, either version 3 of the 010 * License, or (at your option) any later version. 011 * 012 * This program is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 015 */ 016package org.shredzone.geordi.device; 017 018import java.io.IOException; 019import java.io.InputStreamReader; 020import java.io.Reader; 021import java.math.BigDecimal; 022import java.net.URL; 023import java.time.Instant; 024import java.util.List; 025import java.util.stream.Collectors; 026 027import javax.inject.Inject; 028 029import org.json.JSONObject; 030import org.shredzone.commons.xml.XQuery; 031import org.shredzone.geordi.GeordiException; 032import org.shredzone.geordi.data.Sample; 033import org.shredzone.geordi.sensor.Sensor; 034import org.shredzone.geordi.service.DatabaseService; 035import org.slf4j.Logger; 036import org.slf4j.LoggerFactory; 037 038/** 039 * A {@link Device} implementation that reads values from Homematic CCU2 home automation 040 * centrals. 041 * <p> 042 * Requires the <a href="https://github.com/hobbyquaker/XML-API">XML-API CCU addon</a> to 043 * be installed on the CCU2. 044 * 045 * @see <a href="https://www.eq-3.de/">eQ-3 AG</a> 046 */ 047public class Ccu2Device extends Device { 048 049 private final Logger log = LoggerFactory.getLogger(getClass()); 050 051 @Inject 052 private DatabaseService databaseService; 053 054 @Override 055 public List<Sample> readSensors() { 056 XQuery values = fetchFromServer(); 057 058 return databaseService.fetchSensors(this).stream() 059 .map(sensor -> getSensorValue(values, sensor)) 060 .filter(it -> it != null) 061 .collect(Collectors.toList()); 062 } 063 064 /** 065 * Reads the current sensor value from the given {@link Sensor}. 066 * 067 * @param values 068 * XML that was read from the CCU2 069 * @param sensor 070 * {@link Sensor} to be read 071 * @return {@link Sample} containing the sensor value 072 */ 073 private Sample getSensorValue(XQuery values, Sensor sensor) { 074 try { 075 JSONObject config = sensor.getConfig(); 076 077 XQuery sensorDevice; 078 079 if (config.has("datapointName")) { 080 sensorDevice = values.get(String.format( 081 "//datapoint[@name='%s']", 082 config.getString("datapointName"))); 083 } else if (config.has("datapointId")) { 084 sensorDevice = values.get(String.format( 085 "/stateList/device[@ise_id='%d']/channel[@ise_id='%d']/datapoint[@ise_id='%d']", 086 config.getInt("deviceId"), 087 config.getInt("channelId"), 088 config.getInt("datapointId"))); 089 } else { 090 sensorDevice = values.get(String.format( 091 "/stateList/device[@ise_id='%d']/channel[@ise_id='%d']/datapoint[@type='%s']", 092 config.getInt("deviceId"), 093 config.getInt("channelId"), 094 config.getString("type"))); 095 } 096 097 String timestampStr = sensorDevice.attr().get("timestamp"); 098 Instant timestamp = Instant.ofEpochMilli(Long.parseLong(timestampStr) * 1000L); 099 100 String valueStr = sensorDevice.attr().get("value"); 101 BigDecimal value; 102 if ("false".equals(valueStr)) { 103 value = BigDecimal.ZERO; 104 } else if ("true".equals(valueStr)) { 105 value = BigDecimal.ONE; 106 } else { 107 value = new BigDecimal(valueStr); 108 } 109 110 return new Sample(sensor, timestamp, value); 111 } catch (Exception ex) { 112 log.warn("Could not read sensor id {} ({})", sensor.getId(), sensor.getName(), ex); 113 return null; 114 } 115 } 116 117 /** 118 * Reads the current status from the CCU2. 119 * 120 * @return XML containing the sensor status 121 */ 122 private XQuery fetchFromServer() { 123 try { 124 URL url = new URL("http://" 125 + getConfig().getString("host") 126 + "/addons/xmlapi/statelist.cgi"); 127 128 try (Reader in = new InputStreamReader(url.openStream(), "iso-8859-1")) { 129 return XQuery.parse(in); 130 } 131 } catch (IOException ex) { 132 throw new GeordiException("Could not read CCU2", ex); 133 } 134 } 135 136}